diff --git a/.gitignore b/.gitignore index 749f2ca07..aaaff9533 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,10 @@ global-metadata.dat libil2cpp.so codegen/ -codegen.zip \ No newline at end of file +codegen.zip +0.3.0/ +extern/ +codegen-rs/ + +stuff/ +json_cordl/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 902f920fc..da0b8f63a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,11 +15,53 @@ "kind": "bin" } }, + "args": [ + "--metadata", + "./stuff/unity2022.3/global-metadata.dat", + "--libil2cpp", + "./stuff/unity2022.3/libil2cpp.so", + "cpp" + ], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'cordl' json", + "cargo": { + "args": ["build", "--bin=cordl", "--package=cordl"], + "filter": { + "name": "cordl", + "kind": "bin" + } + }, "args": [ "--metadata", "./stuff/bs1.34.2/global-metadata.dat", "--libil2cpp", - "./stuff/bs1.34.2/libil2cpp.so" + "./stuff/bs1.34.2/libil2cpp.so", + "--multi-json", + "./json_cordl" + ], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'cordl' rust", + "cargo": { + "args": ["build", "--bin=cordl", "--package=cordl"], + "filter": { + "name": "cordl", + "kind": "bin" + } + }, + "args": [ + "--metadata", + "./stuff/unity2022.3/global-metadata.dat", + "--libil2cpp", + "./stuff/unity2022.3/libil2cpp.so", + "rust" ], "cwd": "${workspaceFolder}" }, diff --git a/Cargo.lock b/Cargo.lock index 38ae9dab1..77d94277b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,7 +204,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -347,7 +347,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -408,9 +408,13 @@ dependencies = [ "log", "pathdiff", "pretty_env_logger", + "prettyplease", + "proc-macro2", + "quote", "rayon", "serde", "serde_json", + "syn 2.0.90", "topological-sort", "walkdir", ] @@ -723,7 +727,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -805,6 +809,16 @@ dependencies = [ "log", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.90", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -931,7 +945,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -986,9 +1000,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1021,7 +1035,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8371dbfe5..e6f488723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,13 @@ edition = "2021" [features] -default = ["il2cpp_v31"] +default = ["il2cpp_v31", "json", "rust", "cpp"] il2cpp_v31 = ["brocolib_il2cpp_v31"] il2cpp_v29 = ["brocolib_il2cpp_v29"] +json = ["dep:serde_json", "dep:serde"] +rust = ["dep:quote", "dep:prettyplease", "dep:syn", "dep:proc-macro2"] +cpp = [] + # Alias a second version of the dependency with a different package name [dependencies.brocolib_il2cpp_v31] @@ -41,10 +45,16 @@ log = "0.4.20" pretty_env_logger = "0.5.0" rayon = "1.8" filesize = "0.2.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { version = "1.0", features = ["derive"], optional = true } +serde_json = {version = "1.0", optional = true } bitflags = "2.6.0" +# Rust syntax generation +quote = {version = "1", optional = true} +prettyplease = {version = "0.2", optional = true} +syn = { version = "2", optional = true } +proc-macro2 = { version = "1", optional = true } + [profiles.release] opt-level = 3 lto = true diff --git a/cordl_internals_rs/Cargo_template.toml b/cordl_internals_rs/Cargo_template.toml new file mode 100644 index 000000000..cc7dbd123 --- /dev/null +++ b/cordl_internals_rs/Cargo_template.toml @@ -0,0 +1,15 @@ +[package] +name = "bs_cordl" +version = "0.1.0" +edition = "2021" + +[dependencies] +quest_hook = { git = "https://github.com/Fernthedev/quest-hook-rs.git", features = ["il2cpp_v31"], branch = "cordl-fixes"} +# quest_hook = { path = "../../quest-hook-rs", features = ["il2cpp_v31"]} + +[lib] + +[features] +default = [] + +#cordl_features \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b5f35b324..a8a64c9da 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2023-12-28" \ No newline at end of file +channel = "nightly-2024-12-18" \ No newline at end of file diff --git a/src/data/name_components.rs b/src/data/name_components.rs index 56e804308..39407313b 100644 --- a/src/data/name_components.rs +++ b/src/data/name_components.rs @@ -4,38 +4,29 @@ pub struct NameComponents { pub declaring_types: Option>, pub name: String, pub generics: Option>, - pub is_pointer: bool, } impl NameComponents { // TODO: Add setting for adding :: prefix // however, this cannot be allowed in all cases pub fn combine_all(&self) -> String { - let combined_declaring_types = self.declaring_types.as_ref().map(|d| d.join("::")); + let mut completed = self.name.clone(); - // will be empty if no namespace or declaring types - let prefix = combined_declaring_types - .as_ref() - .or(self.namespace.as_ref()) - .map(|s| { - if s.is_empty() { - "::".to_string() - } else { - format!("::{s}::") - } - }) - .unwrap_or_default(); + // add declaring types + if let Some(declaring_types) = self.declaring_types.as_ref() { + completed = format!("{}/{completed}", declaring_types.join("/")); + } - let mut completed = format!("{prefix}{}", self.name); + // add namespace + if let Some(namespace) = self.namespace.as_ref() { + completed = format!("{namespace}.{completed}"); + } + // add generics if let Some(generics) = &self.generics { completed = format!("{completed}<{}>", generics.join(",")); } - if self.is_pointer { - completed = format!("{completed}*") - } - completed } @@ -55,16 +46,10 @@ impl NameComponents { } } - pub fn as_pointer(&self) -> Self { + pub fn remove_namespace(self) -> Self { Self { - is_pointer: true, - ..self.clone() - } - } - pub fn remove_pointer(&self) -> Self { - Self { - is_pointer: false, - ..self.clone() + namespace: None, + ..self } } diff --git a/src/data/type_resolver.rs b/src/data/type_resolver.rs index 1d568da5c..a771d7d19 100644 --- a/src/data/type_resolver.rs +++ b/src/data/type_resolver.rs @@ -52,7 +52,7 @@ pub struct TypeResolver<'a, 'b> { pub collection: &'a TypeContextCollection, } -impl<'a, 'b> TypeResolver<'a, 'b> { +impl TypeResolver<'_, '_> { pub fn resolve_type( &self, declaring_cs_type: &mut CsType, @@ -103,6 +103,7 @@ impl<'a, 'b> TypeResolver<'a, 'b> { | Il2CppTypeEnum::Void | Il2CppTypeEnum::Boolean | Il2CppTypeEnum::Char + | Il2CppTypeEnum::Object | Il2CppTypeEnum::String => { declaring_cs_type .requirements @@ -112,10 +113,8 @@ impl<'a, 'b> TypeResolver<'a, 'b> { )); ResolvedTypeData::Primitive(to_resolve.ty) } - - Il2CppTypeEnum::Object + Il2CppTypeEnum::Class | Il2CppTypeEnum::Valuetype - | Il2CppTypeEnum::Class | Il2CppTypeEnum::Typedbyref // ptr types | Il2CppTypeEnum::I diff --git a/src/generate/cpp/cpp_context.rs b/src/generate/cpp/cpp_context.rs index 184481bf3..5d045eb87 100644 --- a/src/generate/cpp/cpp_context.rs +++ b/src/generate/cpp/cpp_context.rs @@ -1,5 +1,5 @@ use std::cmp::Ordering; -use std::io::Write; +use std::io::{BufWriter, Write}; use std::{ collections::{HashMap, HashSet}, fs::{create_dir_all, remove_file, File}, @@ -74,20 +74,6 @@ pub struct CppContext { /// /// - `write_il2cpp_arg_macros`: Writes IL2CPP argument macros for the given C++ type. impl CppContext { - /// Retrieves a mutable reference to a C++ type based on the given root tag. - pub fn get_cpp_type_recursive_mut(&mut self, root_tag: CsTypeTag) -> Option<&mut CppType> { - let ty = self.typedef_types.get_mut(&root_tag); - - ty - } - - /// Retrieves an immutable reference to a C++ type based on the given root tag. - pub fn get_cpp_type_recursive(&self, root_tag: CsTypeTag) -> Option<&CppType> { - let ty = self.typedef_types.get(&root_tag); - - ty - } - /// Returns the include path for the C++ type definitions. pub fn get_include_path(&self) -> &PathBuf { &self.typedef_path @@ -150,7 +136,7 @@ impl CppContext { let tdi = tag.get_tdi(); let mut cpp_ty = CppType::make_cpp_type(*tag, ty, config); - cpp_ty.nested_fixup(ty, metadata, config); + cpp_ty.nested_fixup(context_tag, ty, metadata, config); if metadata.blacklisted_types.contains(&tdi) { let result = match t.is_value_type() { @@ -209,17 +195,17 @@ impl CppContext { trace!("Writing {:?}", self.typedef_path.as_path()); let mut typedef_writer = Writer { - stream: File::create(self.typedef_path.as_path())?, + stream: BufWriter::new(File::create(self.typedef_path.as_path())?), indent: 0, newline: true, }; let mut typeimpl_writer = Writer { - stream: File::create(self.type_impl_path.as_path())?, + stream: BufWriter::new(File::create(self.type_impl_path.as_path())?), indent: 0, newline: true, }; let mut fundamental_writer = Writer { - stream: File::create(self.fundamental_path.as_path())?, + stream: BufWriter::new(File::create(self.fundamental_path.as_path())?), indent: 0, newline: true, }; diff --git a/src/generate/cpp/cpp_fields.rs b/src/generate/cpp/cpp_fields.rs index 7451c89d1..938e8d441 100644 --- a/src/generate/cpp/cpp_fields.rs +++ b/src/generate/cpp/cpp_fields.rs @@ -7,7 +7,6 @@ use crate::generate::metadata::CordlMetadata; use crate::generate::type_extensions::{ TypeDefinitionExtensions, TypeDefinitionIndexExtensions, TypeExtentions, }; -use crate::generate::writer::Writable; use itertools::Itertools; use log::warn; @@ -21,7 +20,7 @@ use brocolib::global_metadata::TypeDefinitionIndex; use super::config::CppGenerationConfig; use super::cpp_members::{ CppFieldDecl, CppFieldImpl, CppInclude, CppNestedStruct, CppNestedUnion, CppNonMember, - CppStaticAssert, CppTemplate, + CppStaticAssert, CppTemplate, WritableDebug, }; use super::cpp_members::{ CppLine, CppMember, CppMethodDecl, CppMethodImpl, CppParam, CppPropertyDecl, @@ -211,7 +210,8 @@ pub(crate) fn handle_const_fields( }; for field_info in fields.iter().filter(|f| f.is_const) { - let cpp_field_template = make_cpp_field_decl(cpp_type, field_info, name_resolver, config); + let mut cpp_field_template = + make_cpp_field_decl(cpp_type, field_info, name_resolver, config); let f_resolved_type = &field_info.field_ty; let f_type = field_info.field_ty.get_type(metadata); let f_name = &field_info.name; @@ -222,6 +222,13 @@ pub(crate) fn handle_const_fields( let def_value = def_value.expect("Constant with no default value?"); + if matches!( + f_resolved_type.data, + ResolvedTypeData::Primitive(Il2CppTypeEnum::String) + ) { + cpp_field_template.field_ty = "::ConstString".to_owned(); + } + match f_resolved_type.data { ResolvedTypeData::Primitive(_) => { // primitive type @@ -345,7 +352,7 @@ pub(crate) fn handle_instance_fields( resulting_fields .into_iter() - .map(|member| CppMember::FieldDecl(member)) + .map(CppMember::FieldDecl) .for_each(|member| cpp_type.declarations.push(member.into())); }; } @@ -448,7 +455,7 @@ pub(crate) fn handle_valuetype_fields( let backing_fields = fields .iter() - .map(|f| make_cpp_field_decl(cpp_type, &f, name_resolver, config)) + .map(|f| make_cpp_field_decl(cpp_type, f, name_resolver, config)) .map(|mut f| { f.cpp_name = fixup_backing_field(&f.cpp_name); f @@ -459,7 +466,7 @@ pub(crate) fn handle_valuetype_fields( } else { let backing_fields = fields .iter() - .map(|f| make_cpp_field_decl(cpp_type, &f, name_resolver, config)) + .map(|f| make_cpp_field_decl(cpp_type, f, name_resolver, config)) .collect_vec(); handle_instance_fields(cpp_type, &backing_fields, metadata, tdi); @@ -480,7 +487,6 @@ pub(crate) fn prop_decl_from_fieldinfo( let f_cpp_name = &cpp_field.cpp_name; let f_offset = cs_field.offset.unwrap_or(u32::MAX); let f_size = cs_field.size; - let _field_ty_cpp_name = &cs_field.field_ty; let (getter_name, setter_name) = method_names_from_fieldinfo(f_cpp_name); @@ -640,7 +646,7 @@ pub(crate) fn prop_methods_from_fieldinfo( }; // construct getter and setter bodies - let getter_body: Vec> = + let getter_body: Vec> = if let Some(instance_null_check) = instance_null_check { vec![ Arc::new(CppLine::make(instance_null_check.into())), @@ -650,7 +656,7 @@ pub(crate) fn prop_methods_from_fieldinfo( vec![Arc::new(CppLine::make(getter_call))] }; - let setter_body: Vec> = + let setter_body: Vec> = if let Some(instance_null_check) = instance_null_check { vec![ Arc::new(CppLine::make(instance_null_check.into())), @@ -725,8 +731,6 @@ pub(crate) fn handle_referencetype_fields( false => Some(t), }); - let _declaring_cpp_full_name = cpp_type.cpp_name_components.remove_pointer().combine_all(); - let cpp_field_decl = make_cpp_field_decl(cpp_type, field_info, name_resolver, config); let prop = prop_decl_from_fieldinfo(metadata, field_info, &cpp_field_decl); @@ -750,7 +754,7 @@ pub(crate) fn handle_referencetype_fields( let backing_fields = fields .iter() - .map(|f| make_cpp_field_decl(cpp_type, &f, name_resolver, config)) + .map(|f| make_cpp_field_decl(cpp_type, f, name_resolver, config)) .map(|mut f| { f.cpp_name = fixup_backing_field(&f.cpp_name); f @@ -760,29 +764,13 @@ pub(crate) fn handle_referencetype_fields( handle_instance_fields(cpp_type, &backing_fields, metadata, tdi); } -pub(crate) fn field_collision_check(instance_fields: &[CsField]) -> bool { - let mut next_offset = 0; - return instance_fields - .iter() - .sorted_by(|a, b| a.offset.cmp(&b.offset)) - .any(|field| { - let offset = field.offset.unwrap_or(u32::MAX); - if offset < next_offset { - true - } else { - next_offset = offset + field.size as u32; - false - } - }); -} - // inspired by what il2cpp does for explicitly laid out types pub(crate) fn pack_fields_into_single_union(fields: &[CppFieldDecl]) -> CppNestedUnion { // get the min offset to use as a base for the packed structs let min_offset = fields.iter().map(|f| f.offset.unwrap()).min().unwrap_or(0); let packed_structs = fields - .into_iter() + .iter() .cloned() .map(|field| { let structs = field_into_offset_structs(min_offset, field); diff --git a/src/generate/cpp/cpp_main.rs b/src/generate/cpp/cpp_main.rs index 61f80f673..36f98f963 100644 --- a/src/generate/cpp/cpp_main.rs +++ b/src/generate/cpp/cpp_main.rs @@ -25,6 +25,7 @@ use crate::{ pub fn run_cpp( cs_collection: TypeContextCollection, metadata: &CordlMetadata, + format: bool, ) -> color_eyre::Result<()> { let mut cpp_context_collection = CppContextCollection::from_cs_collection(cs_collection, metadata, &STATIC_CONFIG); @@ -364,6 +365,10 @@ pub fn run_cpp( // } } + if format { + format_files()?; + } + Ok(()) } diff --git a/src/generate/cpp/cpp_members.rs b/src/generate/cpp/cpp_members.rs index 9665c8b34..a865aa609 100644 --- a/src/generate/cpp/cpp_members.rs +++ b/src/generate/cpp/cpp_members.rs @@ -8,6 +8,7 @@ use crate::generate::{ use std::{ collections::HashMap, + fmt::Debug, hash::Hash, path::{Path, PathBuf}, rc::Rc, @@ -56,8 +57,8 @@ impl From for CppTemplate { .into_iter() .map(|(constraint, name)| { let cpp_ty = match constraint { - CsGenericTemplateType::Any => "typename".to_string(), - CsGenericTemplateType::Reference => { + CsGenericTemplateType::AnyType => "typename".to_string(), + CsGenericTemplateType::ReferenceType => { CORDL_REFERENCE_TYPE_CONSTRAINT.to_string() } }; @@ -99,6 +100,9 @@ impl CppLine { } } +pub trait WritableDebug: Writable + Debug {} +impl WritableDebug for T {} + #[derive(Debug, Eq, Hash, PartialEq, Clone)] pub struct CppForwardDeclareGroup { // TODO: Make this group lots into a single namespace @@ -285,7 +289,7 @@ pub struct CppMethodDecl { pub is_inline: bool, pub brief: Option, - pub body: Option>>, + pub body: Option>>, } impl PartialEq for CppMethodDecl { @@ -389,7 +393,7 @@ pub struct CppMethodImpl { pub prefix_modifiers: Vec, pub brief: Option, - pub body: Vec>, + pub body: Vec>, } impl PartialEq for CppMethodImpl { @@ -470,7 +474,7 @@ pub struct CppConstructorDecl { pub initialized_values: HashMap, pub brief: Option, - pub body: Option>>, + pub body: Option>>, } impl PartialEq for CppConstructorDecl { @@ -521,7 +525,7 @@ pub struct CppConstructorImpl { pub template: Option, - pub body: Vec>, + pub body: Vec>, } impl PartialEq for CppConstructorImpl { diff --git a/src/generate/cpp/cpp_members_serialize.rs b/src/generate/cpp/cpp_members_serialize.rs index c41267774..f79aa0685 100644 --- a/src/generate/cpp/cpp_members_serialize.rs +++ b/src/generate/cpp/cpp_members_serialize.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use std::io::Write; -use crate::generate::writer::{Writable, Writer, SortLevel, Sortable}; +use crate::generate::writer::{SortLevel, Sortable, Writable, Writer}; use super::cpp_members::{ CppCommentedString, CppConstructorDecl, CppConstructorImpl, CppFieldDecl, CppFieldImpl, diff --git a/src/generate/cpp/cpp_name_components.rs b/src/generate/cpp/cpp_name_components.rs new file mode 100644 index 000000000..7c9bba8d2 --- /dev/null +++ b/src/generate/cpp/cpp_name_components.rs @@ -0,0 +1,103 @@ +use crate::data::name_components::NameComponents; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash, Clone)] +pub struct CppNameComponents { + pub namespace: Option, + pub declaring_types: Option>, + pub name: String, + pub generics: Option>, + pub is_pointer: bool, +} + +impl CppNameComponents { + // TODO: Add setting for adding :: prefix + // however, this cannot be allowed in all cases + pub fn combine_all(&self) -> String { + let combined_declaring_types = self.declaring_types.as_ref().map(|d| d.join("::")); + + // will be empty if no namespace or declaring types + let prefix = combined_declaring_types + .as_ref() + .or(self.namespace.as_ref()) + .map(|s| { + if s.is_empty() { + "::".to_string() + } else { + format!("::{s}::") + } + }) + .unwrap_or_default(); + + let mut completed = format!("{prefix}{}", self.name); + + if let Some(generics) = &self.generics { + completed = format!("{completed}<{}>", generics.join(",")); + } + + if self.is_pointer { + completed = format!("{completed}*") + } + + completed + } + + pub fn into_ref_generics(self) -> Self { + Self { + generics: self + .generics + .map(|opt| opt.into_iter().map(|_| "void*".to_string()).collect()), + ..self + } + } + + pub fn remove_generics(self) -> Self { + Self { + generics: None, + ..self + } + } + + pub fn as_pointer(&self) -> Self { + Self { + is_pointer: true, + ..self.clone() + } + } + pub fn remove_pointer(&self) -> Self { + Self { + is_pointer: false, + ..self.clone() + } + } + + /// just cpp name with generics + pub fn formatted_name(&self, include_generics: bool) -> String { + if let Some(generics) = &self.generics + && include_generics + { + format!("{}<{}>", self.name, generics.join(",")) + } else { + self.name.to_string() + } + } +} + +impl From for CppNameComponents { + fn from(value: String) -> Self { + Self { + name: value, + ..Default::default() + } + } +} +impl From for CppNameComponents { + fn from(value: NameComponents) -> Self { + Self { + declaring_types: value.declaring_types, + generics: value.generics, + name: value.name, + namespace: value.namespace, + ..Default::default() + } + } +} diff --git a/src/generate/cpp/cpp_name_resolver.rs b/src/generate/cpp/cpp_name_resolver.rs index f720e6686..48e0c8a5a 100644 --- a/src/generate/cpp/cpp_name_resolver.rs +++ b/src/generate/cpp/cpp_name_resolver.rs @@ -2,16 +2,14 @@ use brocolib::{global_metadata::Il2CppTypeDefinition, runtime_metadata::Il2CppTy use itertools::Itertools; use crate::{ - data::{ - name_components::NameComponents, - type_resolver::{ResolvedType, ResolvedTypeData, TypeUsage}, - }, + data::type_resolver::{ResolvedType, ResolvedTypeData, TypeUsage}, generate::{metadata::CordlMetadata, type_extensions::TypeDefinitionExtensions}, }; use super::{ cpp_context_collection::CppContextCollection, cpp_members::{CppForwardDeclare, CppInclude}, + cpp_name_components::CppNameComponents, cpp_type::CppType, handlers::unity, }; @@ -26,14 +24,14 @@ pub struct CppNameResolver<'a, 'b> { pub collection: &'a CppContextCollection, } -impl<'a, 'b> CppNameResolver<'a, 'b> { +impl<'b> CppNameResolver<'_, 'b> { pub fn resolve_name( &self, declaring_cpp_type: &mut CppType, ty: &ResolvedType, type_usage: TypeUsage, hard_include: bool, - ) -> NameComponents { + ) -> CppNameComponents { let metadata = self.cordl_metadata; match &ty.data { ResolvedTypeData::Array(array_type) => { @@ -41,7 +39,7 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { self.resolve_name(declaring_cpp_type, array_type, type_usage, hard_include); let generic_formatted = generic.combine_all(); - NameComponents { + CppNameComponents { name: "ArrayW".into(), namespace: Some("".into()), generics: Some(vec![ @@ -58,13 +56,18 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { let generic_types_formatted = vec .iter() .map(|(r, inc)| { - self.resolve_name(declaring_cpp_type, r, type_usage, *inc && hard_include) + self.resolve_name( + declaring_cpp_type, + r, + TypeUsage::GenericArg, + *inc && hard_include, + ) }) .map(|n| n.combine_all()) .collect_vec(); // add generics to type def - NameComponents { + CppNameComponents { generics: Some(generic_types_formatted), ..type_def_name_components } @@ -89,75 +92,30 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { ResolvedTypeData::Ptr(resolved_type) => { let generic_formatted = self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); - NameComponents { + CppNameComponents { namespace: Some("cordl_internals".into()), generics: Some(vec![generic_formatted.combine_all()]), name: "Ptr".into(), ..Default::default() } } - ResolvedTypeData::Type(resolved_tag) => { - if *resolved_tag == declaring_cpp_type.self_tag { - return declaring_cpp_type.cpp_name_components.clone(); - } - - let resolved_context_root_tag = self.collection.get_context_root_tag(*resolved_tag); - let self_context_root_tag = self - .collection - .get_context_root_tag(declaring_cpp_type.self_tag); - - let incl_context = self - .collection - .get_context(*resolved_tag) - .unwrap_or_else(|| panic!("Unable to find type {ty:#?}")); - let incl_ty = self - .collection - .get_cpp_type(*resolved_tag) - .unwrap_or_else(|| { - let td = &metadata.metadata.global_metadata.type_definitions - [resolved_tag.get_tdi()]; - - println!( - "ty {resolved_tag:#?} vs aliased {:#?}", - self.collection.alias_context.get(resolved_tag) - ); - println!("{}", incl_context.fundamental_path.display()); - panic!( - "Unable to find type {ty:#?} {}", - td.full_name(metadata.metadata, true) - ); - }); - - if hard_include { - declaring_cpp_type.requirements.add_dependency(incl_ty); - } - - let is_own_context = resolved_context_root_tag == self_context_root_tag; - - if !is_own_context { - match hard_include { - // can add include - true => { - declaring_cpp_type.requirements.add_def_include( - Some(incl_ty), - CppInclude::new_context_typedef(incl_context), - ); - declaring_cpp_type.requirements.add_impl_include( - Some(incl_ty), - CppInclude::new_context_typeimpl(incl_context), - ); - } - // add forward declare - false => { - declaring_cpp_type.requirements.add_forward_declare(( - CppForwardDeclare::from_cpp_type(incl_ty), - CppInclude::new_context_typedef(incl_context), - )); - } - } - } - - self.resolve_redirect(incl_ty, type_usage) + ResolvedTypeData::Type(resolved_tag) => self.resolve_type( + resolved_tag, + declaring_cpp_type, + metadata, + hard_include, + type_usage, + ), + ResolvedTypeData::Primitive(il2_cpp_type_enum) + if *il2_cpp_type_enum == Il2CppTypeEnum::Object => + { + self.resolve_type( + &metadata.object_tdi.into(), + declaring_cpp_type, + metadata, + hard_include, + type_usage, + ) } ResolvedTypeData::Primitive(il2_cpp_type_enum) => { let requirements = &mut declaring_cpp_type.requirements; @@ -197,7 +155,6 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { Il2CppTypeEnum::Void => "void".to_string(), Il2CppTypeEnum::Boolean => "bool".to_string(), Il2CppTypeEnum::Char => "char16_t".to_string(), - Il2CppTypeEnum::Object => "void*".to_string(), Il2CppTypeEnum::String => { requirements.needs_stringw_include(); @@ -206,7 +163,7 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { _ => panic!("Unsupported type {il2_cpp_type_enum:#?}"), }; - NameComponents::from(s) + CppNameComponents::from(s) } ResolvedTypeData::Blacklisted(cs_type_tag) => { let td = &metadata.metadata.global_metadata.type_definitions[cs_type_tag.get_tdi()]; @@ -218,7 +175,7 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); let generic_formatted = generic.combine_all(); - NameComponents { + CppNameComponents { name: "ByRef".into(), namespace: Some("".into()), generics: Some(vec![generic_formatted.clone()]), @@ -231,7 +188,7 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); let generic_formatted = generic.combine_all(); - NameComponents { + CppNameComponents { name: "ByRefConst".into(), namespace: Some("".into()), generics: Some(vec![generic_formatted.clone()]), @@ -242,7 +199,72 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { } } - fn resolve_redirect(&self, incl_ty: &CppType, type_usage: TypeUsage) -> NameComponents { + fn resolve_type( + &self, + resolved_tag: &crate::generate::cs_type_tag::CsTypeTag, + declaring_cpp_type: &mut CppType, + metadata: &CordlMetadata<'b>, + hard_include: bool, + type_usage: TypeUsage, + ) -> CppNameComponents { + if *resolved_tag == declaring_cpp_type.self_tag { + return self.resolve_redirect(declaring_cpp_type, type_usage); + } + let resolved_context_root_tag = self.collection.get_context_root_tag(*resolved_tag); + let self_context_root_tag = self + .collection + .get_context_root_tag(declaring_cpp_type.self_tag); + let incl_context = self + .collection + .get_context(*resolved_tag) + .unwrap_or_else(|| panic!("Unable to find type {resolved_tag:#?}")); + let incl_ty = self + .collection + .get_cpp_type(*resolved_tag) + .unwrap_or_else(|| { + let td = + &metadata.metadata.global_metadata.type_definitions[resolved_tag.get_tdi()]; + + println!( + "ty {resolved_tag:#?} vs aliased {:#?}", + self.collection.alias_context.get(resolved_tag) + ); + println!("{}", incl_context.fundamental_path.display()); + panic!( + "Unable to find type {resolved_tag:#?} {}", + td.full_name(metadata.metadata, true) + ); + }); + if hard_include { + declaring_cpp_type.requirements.add_dependency(incl_ty); + } + let is_own_context = resolved_context_root_tag == self_context_root_tag; + if !is_own_context { + match hard_include { + // can add include + true => { + declaring_cpp_type.requirements.add_def_include( + Some(incl_ty), + CppInclude::new_context_typedef(incl_context), + ); + declaring_cpp_type.requirements.add_impl_include( + Some(incl_ty), + CppInclude::new_context_typeimpl(incl_context), + ); + } + // add forward declare + false => { + declaring_cpp_type.requirements.add_forward_declare(( + CppForwardDeclare::from_cpp_type(incl_ty), + CppInclude::new_context_typedef(incl_context), + )); + } + } + } + self.resolve_redirect(incl_ty, type_usage) + } + + fn resolve_redirect(&self, incl_ty: &CppType, type_usage: TypeUsage) -> CppNameComponents { let mut name_components = incl_ty.cpp_name_components.clone(); name_components = unity::unity_object_resolve_handler( name_components, @@ -253,7 +275,7 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { name_components } - fn wrapper_type_for_tdi(td: &Il2CppTypeDefinition) -> NameComponents { + fn wrapper_type_for_tdi(td: &Il2CppTypeDefinition) -> CppNameComponents { if td.is_enum_type() { return ENUM_WRAPPER_TYPE.to_string().into(); } @@ -270,8 +292,8 @@ impl<'a, 'b> CppNameResolver<'a, 'b> { } } -fn il2cpp_object_name_component() -> NameComponents { - NameComponents { +fn il2cpp_object_name_component() -> CppNameComponents { + CppNameComponents { name: IL2CPP_OBJECT_TYPE.to_string(), is_pointer: true, generics: None, diff --git a/src/generate/cpp/cpp_type.rs b/src/generate/cpp/cpp_type.rs index 84aa7d32d..616857c4e 100644 --- a/src/generate/cpp/cpp_type.rs +++ b/src/generate/cpp/cpp_type.rs @@ -2,6 +2,7 @@ use std::{ collections::{HashMap, HashSet}, rc::Rc, sync::Arc, + usize, }; use brocolib::global_metadata::{FieldIndex, MethodIndex, TypeDefinitionIndex}; @@ -37,8 +38,9 @@ use super::{ cpp_members::{ CppConstructorDecl, CppConstructorImpl, CppFieldDecl, CppForwardDeclare, CppInclude, CppLine, CppMember, CppMethodData, CppMethodDecl, CppMethodImpl, CppNestedStruct, - CppNonMember, CppParam, CppPropertyDecl, CppTemplate, CppUsingAlias, + CppNonMember, CppParam, CppPropertyDecl, CppTemplate, CppUsingAlias, WritableDebug, }, + cpp_name_components::CppNameComponents, cpp_name_resolver::{CppNameResolver, VALUE_WRAPPER_TYPE}, }; @@ -183,7 +185,7 @@ pub struct CppType { pub cpp_template: Option, pub cs_name_components: NameComponents, - pub cpp_name_components: NameComponents, + pub cpp_name_components: CppNameComponents, pub(crate) prefix_comments: Vec, pub packing: Option, pub size_info: Option, @@ -377,7 +379,7 @@ impl CppType { ) -> CppType { let cs_name_components = &cs_type.cs_name_components; - let cpp_name_components = NameComponents { + let cpp_name_components = CppNameComponents { declaring_types: cs_name_components .declaring_types .as_ref() @@ -389,11 +391,10 @@ impl CppType { }), generics: cs_name_components.generics.clone(), name: config.name_cpp(&cs_name_components.name), - namespace: cs_name_components - .namespace - .as_ref() - .map(|s| config.namespace_cpp(s)), - is_pointer: cs_name_components.is_pointer, + namespace: Some( + config.namespace_cpp(&cs_name_components.namespace.clone().unwrap_or_default()), + ), + is_pointer: cs_type.is_reference_type, }; let generic_instantiations_args_types = cs_type.generic_instantiations_args_types.clone(); @@ -435,6 +436,7 @@ impl CppType { pub fn nested_fixup( &mut self, + context_tag: CsTypeTag, cs_type: &CsType, metadata: &CordlMetadata, config: &CppGenerationConfig, @@ -444,22 +446,33 @@ impl CppType { return; }; - let declaring_td = declaring_tag + let mut declaring_td = declaring_tag .get_tdi() .get_type_definition(metadata.metadata); + let mut declaring_name = declaring_td.get_name_components(metadata.metadata).name; - let combined_name = self - .cpp_name_components - .clone() - .remove_generics() - .remove_pointer() - .combine_all(); + while declaring_td.declaring_type_index != u32::MAX { + let declaring_ty = + &metadata.metadata_registration.types[declaring_td.declaring_type_index as usize]; - self.cpp_name_components.namespace = - Some(config.namespace_cpp(declaring_td.namespace(metadata.metadata))); - self.cpp_name_components.declaring_types = None; // remove declaring types + let declaring_tag = CsTypeTag::from_type_data(declaring_ty.data, metadata.metadata); + + declaring_td = declaring_tag + .get_tdi() + .get_type_definition(metadata.metadata); + + let name = declaring_td.get_name_components(metadata.metadata).name; + declaring_name = format!("{declaring_name}_{name}",); + } + let context_td = context_tag.get_tdi().get_type_definition(metadata.metadata); + let declaring_namespace = context_td.namespace(metadata.metadata); + + let combined_name = format!("{}_{}", declaring_name, self.name()); + + self.cpp_name_components.namespace = Some(config.namespace_cpp(declaring_namespace)); self.cpp_name_components.name = config.sanitize_to_cpp_name(&combined_name); + self.cpp_name_components.declaring_types = None; // remove declaring types } pub fn fill( @@ -646,76 +659,14 @@ impl CppType { self.declarations.reserve(constructors.len()); for ctor in constructors { - let m_params = ctor + let m_params_with_def = ctor .parameters - .into_iter() - .map(|p| self.make_param(p, name_resolver, config)) - .collect_vec(); - - let params_no_default = m_params .iter() - .cloned() - .map(|mut c| { - c.def_value = None; - c - }) + .map(|p| self.make_param(p.clone(), name_resolver, config)) .collect_vec(); - let ty_full_cpp_name = self.cpp_name_components.combine_all(); - - let decl: CppMethodDecl = CppMethodDecl { - cpp_name: "New_ctor".into(), - return_type: ty_full_cpp_name.clone(), - parameters: params_no_default, - template: ctor.template.clone().map(|t| t.into()), - body: None, // TODO: - brief: None, - is_no_except: false, - is_constexpr: false, - instance: false, - is_const: false, - is_implicit_operator: false, - is_explicit_operator: false, - - is_virtual: false, - is_inline: true, - prefix_modifiers: vec![], - suffix_modifiers: vec![], - }; - - // To avoid trailing ({},) - let base_ctor_params = CppParam::params_names(&decl.parameters).join(", "); - - let allocate_call = format!( - "THROW_UNLESS(::il2cpp_utils::NewSpecific<{ty_full_cpp_name}>({base_ctor_params}))" - ); - - let declaring_template = if self - .cpp_template - .as_ref() - .is_some_and(|t| !t.names.is_empty()) - { - self.cpp_template.clone() - } else { - None - }; - - let cpp_constructor_impl = CppMethodImpl { - body: vec![Arc::new(CppLine::make(format!("return {allocate_call};")))], - - declaring_cpp_full_name: self - .cpp_name_components - .remove_pointer() - .combine_all(), - parameters: m_params.to_vec(), - template: declaring_template, - ..decl.clone().into() - }; - - self.implementations - .push(CppMember::MethodImpl(cpp_constructor_impl).into()); - - self.declarations.push(CppMember::MethodDecl(decl).into()); + let template: Option = ctor.template.clone().map(|t| t.into()); + self.create_ref_constructor(&m_params_with_def, template.as_ref()); } } @@ -816,7 +767,7 @@ impl CppType { false => "static_cast(this)".to_string(), }; - let body: Vec> = vec![Arc::new(CppLine::make(format!( + let body: Vec> = vec![Arc::new(CppLine::make(format!( "return static_cast<{interface_cpp_pointer}>({convert_line});" )))]; let declaring_cpp_full_name = self.cpp_name_components.remove_pointer().combine_all(); @@ -929,10 +880,6 @@ impl CppType { // T UnityEngine.Component::GetComponent() -> bs_hook::Il2CppWrapperType UnityEngine.Component::GetComponent() let template = method.template.clone().map(|t| t.into()); - if method.name == ".ctor" { - Self::create_ref_constructor(self, method, &m_params_with_def, &template); - } - let mut cpp_ret_type = name_resolver.resolve_name(self, &method.return_type, TypeUsage::ReturnType, false); @@ -952,7 +899,8 @@ impl CppType { // static functions with same name and params but // different ret types can exist // so we add their ret types - let fixup_name = match cpp_m_name == "op_Implicit" || cpp_m_name == "op_Explicit" { + + match cpp_m_name == "op_Implicit" || cpp_m_name == "op_Explicit" { true => { cpp_m_name + "_" @@ -961,9 +909,7 @@ impl CppType { .replace('*', "_") } false => cpp_m_name, - }; - - fixup_name + } }; let metadata = name_resolver.cordl_metadata; @@ -1115,13 +1061,13 @@ impl CppType { .iter() .chain(method_body_lines.iter()) .cloned() - .map(|l| -> Arc { Arc::new(CppLine::make(l)) }) + .map(|l| -> Arc { Arc::new(CppLine::make(l)) }) .collect_vec(), false => method_info_lines .iter() .chain(method_body_lines.iter()) .cloned() - .map(|l| -> Arc { Arc::new(CppLine::make(l)) }) + .map(|l| -> Arc { Arc::new(CppLine::make(l)) }) .collect_vec(), }; @@ -1151,67 +1097,66 @@ impl CppType { .is_some_and(|t| !t.names.is_empty()); // don't emit method size structs for generic methods - if let Some(addr) = &method.method_data.addrs - && let Some(size) = method.method_data.estimated_size - { - let il2cpp_method = &metadata.metadata.global_metadata.methods[method.method_index]; - let declaring_tdi = &il2cpp_method.declaring_type; - let declaring_td = declaring_tdi.get_type_definition(metadata.metadata); - let declaring_tag: CsTypeTag = CsTypeTag::TypeDefinitionIndex(*declaring_tdi); - - let resolved_generic_types = self - .method_generic_instantiation_map - .get(&method.method_index) - .cloned() - .map(|g| { - g.iter() - .map(|t| name_resolver.resolve_name(self, t, TypeUsage::TypeName, false)) - .map(|n| n.combine_all()) - .collect_vec() - }); + let addr = method.method_data.addrs.unwrap_or(u64::MAX); + let size = method.method_data.estimated_size.unwrap_or(usize::MAX); - let interface_declaring_cpp_type: Option<&CppType> = - if *declaring_tdi == self.self_tag.get_tdi() { - Some(self) - } else { - name_resolver.collection.get_cpp_type(declaring_tag) - }; + let il2cpp_method = &metadata.metadata.global_metadata.methods[method.method_index]; + let declaring_tdi = &il2cpp_method.declaring_type; + let declaring_td = declaring_tdi.get_type_definition(metadata.metadata); + let declaring_tag: CsTypeTag = CsTypeTag::TypeDefinitionIndex(*declaring_tdi); - let has_template_args = self - .cpp_template - .as_ref() - .is_some_and(|t| !t.names.is_empty()); - - // don't emit method size structs for generic methods - if template.is_none() && !has_template_args && !is_generic_method_inst { - self.nonmember_implementations - .push(Arc::new(CppNonMember::SizeStruct( - CppMethodSizeStruct { - ret_ty: method_decl.return_type.clone(), - cpp_method_name: method_decl.cpp_name.clone(), - method_name: m_name.to_string(), - declaring_type_name: method_impl.declaring_cpp_full_name.clone(), - declaring_classof_call, - method_info_lines, - method_info_var: METHOD_INFO_VAR_NAME.to_string(), - instance: method_decl.instance, - params: method_decl.parameters.clone(), - declaring_template: self.cpp_template.clone(), - template: template.clone(), - generic_literals: resolved_generic_types, - method_data: CppMethodData { - addrs: *addr, - estimated_size: size, - }, - interface_clazz_of: interface_declaring_cpp_type - .map(|d| d.classof_cpp_name()) - .unwrap_or_else(|| format!("Bad stuff happened {declaring_td:?}")), - is_final, - slot: method.method_data.slot, - } - .into(), - ))); - } + let resolved_generic_types = self + .method_generic_instantiation_map + .get(&method.method_index) + .cloned() + .map(|g| { + g.iter() + .map(|t| name_resolver.resolve_name(self, t, TypeUsage::TypeName, false)) + .map(|n| n.combine_all()) + .collect_vec() + }); + + let interface_declaring_cpp_type: Option<&CppType> = + if *declaring_tdi == self.self_tag.get_tdi() { + Some(self) + } else { + name_resolver.collection.get_cpp_type(declaring_tag) + }; + + let has_template_args = self + .cpp_template + .as_ref() + .is_some_and(|t| !t.names.is_empty()); + + // don't emit method size structs for generic methods + if template.is_none() && !has_template_args && !is_generic_method_inst { + self.nonmember_implementations + .push(Arc::new(CppNonMember::SizeStruct( + CppMethodSizeStruct { + ret_ty: method_decl.return_type.clone(), + cpp_method_name: method_decl.cpp_name.clone(), + method_name: m_name.to_string(), + declaring_type_name: method_impl.declaring_cpp_full_name.clone(), + declaring_classof_call, + method_info_lines, + method_info_var: METHOD_INFO_VAR_NAME.to_string(), + instance: method_decl.instance, + params: method_decl.parameters.clone(), + declaring_template: self.cpp_template.clone(), + template: template.clone(), + generic_literals: resolved_generic_types, + method_data: CppMethodData { + addrs: addr, + estimated_size: size, + }, + interface_clazz_of: interface_declaring_cpp_type + .map(|d| d.classof_cpp_name()) + .unwrap_or_else(|| format!("Bad stuff happened {declaring_td:?}")), + is_final, + slot: method.method_data.slot, + } + .into(), + ))); } // TODO: Revise this @@ -1575,7 +1520,7 @@ impl CppType { // so then Parent() let base_ctor = self.parent.as_ref().map(|s| (s.clone(), "".to_string())); - let body: Vec> = instance_fields + let body: Vec> = instance_fields .iter() .map(|p| { let name = &p.name; @@ -1583,7 +1528,7 @@ impl CppType { }) .map(Arc::new) // Why is this needed? _sigh_ - .map(|arc| -> Arc { arc }) + .map(|arc| -> Arc { arc }) .collect_vec(); let params_no_def = instance_fields @@ -2069,12 +2014,7 @@ impl CppType { .push(CppMember::ConstructorDecl(default_ctor).into()); } - fn create_ref_constructor( - &mut self, - _method: &CsMethod, - m_params: &[CppParam], - template: &Option, - ) { + fn create_ref_constructor(&mut self, m_params: &[CppParam], template: Option<&CppTemplate>) { if self.is_value_type || self.is_enum_type { return; } @@ -2094,7 +2034,7 @@ impl CppType { cpp_name: "New_ctor".into(), return_type: ty_full_cpp_name.clone(), parameters: params_no_default, - template: template.clone(), + template: template.cloned(), body: None, // TODO: brief: None, is_no_except: false, @@ -2171,7 +2111,8 @@ impl CppType { impl ToString for CsValue { fn to_string(&self) -> String { match self { - CsValue::String(s) => format!("\"{s}\""), + CsValue::String(s) => format!("u\"{s}\""), + CsValue::Char(s) => format!("u'{s}'"), CsValue::Bool(v) => match v { true => "true", false => "false", @@ -2197,7 +2138,7 @@ impl ToString for CsValue { } // make it include at least one decimal place - format!("static_cast({f:1}f)") + format!("static_cast({f:.1}f)") } CsValue::F64(f) => { if *f == f64::INFINITY { @@ -2210,7 +2151,7 @@ impl ToString for CsValue { return "NAN".to_owned(); } - format!("static_cast({f:1})") + format!("static_cast({f:.1})") } CsValue::Object(_bytes) => todo!(), CsValue::ValueType(_bytes) => todo!(), diff --git a/src/generate/cpp/handlers/unity.rs b/src/generate/cpp/handlers/unity.rs index ad340965f..4f2cccd23 100644 --- a/src/generate/cpp/handlers/unity.rs +++ b/src/generate/cpp/handlers/unity.rs @@ -3,11 +3,12 @@ use log::info; use std::{path::PathBuf, sync::Arc}; use crate::{ - data::{name_components::NameComponents, type_resolver::TypeUsage}, + data::type_resolver::TypeUsage, generate::{ cpp::{ cpp_context_collection::CppContextCollection, cpp_members::{CppInclude, CppMember}, + cpp_name_components::CppNameComponents, cpp_type::CppType, }, cs_type_tag::CsTypeTag, @@ -38,11 +39,11 @@ pub fn register_unity( } pub fn unity_object_resolve_handler( - original: NameComponents, + original: CppNameComponents, cpp_type: &CppType, metadata: &CordlMetadata, typ_usage: TypeUsage, -) -> NameComponents { +) -> CppNameComponents { if !matches!( typ_usage, TypeUsage::Field | TypeUsage::Property | TypeUsage::GenericArg | TypeUsage::ReturnType @@ -59,7 +60,7 @@ pub fn unity_object_resolve_handler( return original; } - NameComponents { + CppNameComponents { namespace: Some("".to_string()), declaring_types: None, name: "UnityW".to_string(), diff --git a/src/generate/cpp/mod.rs b/src/generate/cpp/mod.rs index 812018ca4..ec6e6aa1d 100644 --- a/src/generate/cpp/mod.rs +++ b/src/generate/cpp/mod.rs @@ -6,6 +6,7 @@ mod cpp_context_collection; mod cpp_fields; mod cpp_members; mod cpp_members_serialize; +mod cpp_name_components; mod cpp_name_resolver; mod cpp_type; mod handlers; diff --git a/src/generate/cs_members.rs b/src/generate/cs_members.rs index 09f4f9a5b..1fef235f0 100644 --- a/src/generate/cs_members.rs +++ b/src/generate/cs_members.rs @@ -5,9 +5,9 @@ use itertools::Itertools; use crate::data::type_resolver::ResolvedType; +use std::hash::Hash; - -use std::{hash::Hash}; +use super::cs_type_tag::CsTypeTag; #[derive(Debug, Eq, Hash, PartialEq, Clone, Default, PartialOrd, Ord)] pub struct CsGenericTemplate { @@ -17,8 +17,8 @@ pub struct CsGenericTemplate { #[derive(Debug, Eq, Hash, PartialEq, Clone, Default, PartialOrd, Ord)] pub enum CsGenericTemplateType { #[default] - Any, - Reference, + AnyType, + ReferenceType, } impl CsGenericTemplate { @@ -26,7 +26,7 @@ impl CsGenericTemplate { CsGenericTemplate { names: names .into_iter() - .map(|s| (CsGenericTemplateType::Any, s)) + .map(|s| (CsGenericTemplateType::AnyType, s)) .collect(), } } @@ -34,7 +34,7 @@ impl CsGenericTemplate { CsGenericTemplate { names: names .into_iter() - .map(|s| (CsGenericTemplateType::Reference, s)) + .map(|s| (CsGenericTemplateType::ReferenceType, s)) .collect(), } } @@ -93,6 +93,7 @@ pub struct CsMethodSizeData { #[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum CsValue { String(String), + Char(String), Bool(bool), U8(u8), @@ -206,26 +207,26 @@ pub struct CsMethod { pub parameters: Vec, pub instance: bool, pub template: Option, - pub method_data: CsMethodData, pub brief: Option, + + pub declaring_type: CsTypeTag, + + pub method_data: CsMethodData, pub method_flags: CSMethodFlags, } // TODO: Generics #[derive(Clone, Debug)] pub struct CsConstructor { - pub cpp_name: String, + pub name: String, pub parameters: Vec, pub template: Option, - - pub brief: Option, } impl PartialEq for CsConstructor { fn eq(&self, other: &Self) -> bool { - self.cpp_name == other.cpp_name + self.name == other.name && self.parameters == other.parameters && self.template == other.template - && self.brief == other.brief } } diff --git a/src/generate/cs_type.rs b/src/generate/cs_type.rs index c359b7237..918f430d9 100644 --- a/src/generate/cs_type.rs +++ b/src/generate/cs_type.rs @@ -79,8 +79,11 @@ pub struct CsType { pub is_value_type: bool, pub is_enum_type: bool, pub is_reference_type: bool, + pub is_compiler_generated: bool, + pub requirements: CsTypeRequirements, + pub enum_backing_type: Option, pub parent: Option, pub interfaces: Vec, pub generic_template: Option, // Names of templates e.g T, TKey etc. @@ -223,7 +226,7 @@ impl CsType { declaring_ty.map(|t| CsTypeTag::from_type_data(t.data, metadata.metadata)); let cs_name_components = t.get_name_components(metadata.metadata); - let is_pointer = cs_name_components.is_pointer; + let is_pointer = t.is_reference_type(metadata.metadata); // TODO: Come up with a way to avoid this extra call to layout the entire type // We really just want to call it once for a given size and then move on @@ -250,6 +253,7 @@ impl CsType { is_value_type: t.is_value_type(), is_enum_type: t.is_enum_type(), + is_compiler_generated: t.is_compiler_generated(metadata.metadata), is_reference_type: is_pointer, requirements: Default::default(), @@ -263,6 +267,7 @@ impl CsType { method_generic_instantiation_map: Default::default(), nested_types: Default::default(), + enum_backing_type: None, }; if t.parent_index == u32::MAX { @@ -290,6 +295,20 @@ impl CsType { self.make_fields(type_resolver); self.make_properties(type_resolver); self.make_methods(type_resolver); + + let metadata = type_resolver.cordl_metadata; + let tdi = self.self_tag.get_tdi(); + let t = Self::get_type_definition(metadata, tdi); + + if t.element_type_index != u32::MAX && t.is_enum_type() { + let element_type = metadata + .metadata_registration + .types + .get(t.element_type_index as usize) + .unwrap(); + + self.enum_backing_type = Some(element_type.ty); + } } fn make_parameters( @@ -484,7 +503,7 @@ impl CsType { let f_offset = get_offset(field, i, &mut offset_iter, field_offsets, metadata, t); // calculate / fetch the field size - let f_size = get_size(field, self.generic_instantiations_args_types.as_ref(), &metadata); + let f_size = get_size(field, self.generic_instantiations_args_types.as_ref(), metadata); // TODO: Check a flag to look for default values to speed this up @@ -501,8 +520,8 @@ impl CsType { instance: !f_type.is_static() && !f_type.is_constant(), readonly: f_type.is_constant(), brief_comment: Some(format!("Field {f_name}, offset: 0x{:x}, size: 0x{f_size:x}, def value: {def_value:?}", f_offset.unwrap_or(u32::MAX))), + is_const: f_type.is_constant() || def_value.is_some(), value: def_value, - is_const: false, } }) .collect_vec(); @@ -747,7 +766,17 @@ impl CsType { flag = flag.union(CSMethodFlags::SPECIAL_NAME); } - let mut method_decl = CsMethod { + // don't emit method size structs for generic methods + let is_concrete = !method.is_abstract_method(); + let method_data = CsMethodData { + addrs: is_concrete.then(|| method_calc.map(|c| c.addrs)).flatten(), + estimated_size: is_concrete + .then(|| method_calc.map(|c| c.estimated_size)) + .flatten(), + slot: (method.slot != u16::MAX).then_some(method.slot), + }; + + let method_decl = CsMethod { brief: format!( "Method {m_name}, addr 0x{:x}, size 0x{:x}, virtual {}, abstract: {}, final {}", method_calc.map(|m| m.addrs).unwrap_or(u64::MAX), @@ -766,10 +795,11 @@ impl CsType { TypeUsage::ReturnType, true, ), + declaring_type: method.declaring_type.into(), parameters: m_params_no_def.clone(), instance: !method.is_static_method(), template: template.clone(), - method_data: Default::default(), + method_data, }; // if type is a generic @@ -778,14 +808,14 @@ impl CsType { .as_ref() .is_some_and(|t| !t.names.is_empty()); - // don't emit method size structs for generic methods - if let Some(method_calc) = method_calc { - let is_concrete = !method.is_abstract_method(); - method_decl.method_data = CsMethodData { - addrs: is_concrete.then_some(method_calc.addrs), - estimated_size: is_concrete.then_some(method_calc.estimated_size), - slot: (method.slot != u16::MAX).then_some(method.slot), - } + if method.name(metadata.metadata) == ".ctor" { + let constructor = CsConstructor { + name: m_name.to_string(), + parameters: method_decl.parameters.clone(), + template: method_decl.template.clone(), + }; + + self.constructors.push(constructor); } if !is_generic_method_inst { @@ -833,7 +863,7 @@ impl CsType { .escape_default() .to_string(); - CsValue::String(res) + CsValue::Char(res) } Il2CppTypeEnum::String => { let stru16_len = cursor.read_compressed_i32::().unwrap(); diff --git a/src/generate/json/json_gen.rs b/src/generate/json/json_gen.rs index c0f5c2528..12ec78cdd 100644 --- a/src/generate/json/json_gen.rs +++ b/src/generate/json/json_gen.rs @@ -63,6 +63,14 @@ pub struct JsonMethod { pub ret: String, pub ret_ty_tag: JsonResolvedTypeData, pub parameters: Vec, + pub method_info: JsonMethodInfo, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JsonMethodInfo { + pub estimated_size: Option, + pub addrs: Option, + pub slot: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -85,7 +93,7 @@ fn make_field(field: &CsField, name_resolver: &JsonNameResolver) -> JsonField { ty_name, offset, - ty_tag: ty.into(), + ty_tag: ty, } } fn make_property(property: &CsProperty, name_resolver: &JsonNameResolver) -> JsonProperty { @@ -142,11 +150,18 @@ fn make_method(method: &CsMethod, name_resolver: &JsonNameResolver) -> JsonMetho .map(|p| make_param(p, name_resolver)) .collect_vec(); + let json_method_info = JsonMethodInfo { + addrs: method.method_data.addrs, + estimated_size: method.method_data.estimated_size, + slot: method.method_data.slot, + }; + JsonMethod { name: method.name.to_string(), parameters: params, ret: ret_ty_name, ret_ty_tag: ret_ty, + method_info: json_method_info, } } @@ -165,8 +180,7 @@ pub fn make_type( let fields = td .fields .iter() - .enumerate() - .map(|(_i, f)| make_field(f, &name_resolver)) + .map(|f| make_field(f, &name_resolver)) .collect_vec(); let properties = td .properties diff --git a/src/generate/json/json_name_resolver.rs b/src/generate/json/json_name_resolver.rs index 2bc4837dc..3f7ac5657 100644 --- a/src/generate/json/json_name_resolver.rs +++ b/src/generate/json/json_name_resolver.rs @@ -15,7 +15,7 @@ pub struct JsonNameResolver<'a, 'b> { pub collection: &'a TypeContextCollection, } -impl<'a, 'b> JsonNameResolver<'a, 'b> { +impl JsonNameResolver<'_, '_> { pub fn resolve_name(&self, ty: &ResolvedType) -> NameComponents { let metadata = self.cordl_metadata; match &ty.data { @@ -27,7 +27,6 @@ impl<'a, 'b> JsonNameResolver<'a, 'b> { name: "Array".into(), namespace: Some("".into()), generics: Some(vec![generic_formatted.clone()]), - is_pointer: false, ..Default::default() } } @@ -103,7 +102,6 @@ impl<'a, 'b> JsonNameResolver<'a, 'b> { name: "Blacklisted".into(), namespace: None, generics: Some(vec![td.full_name(metadata.metadata, true)]), - is_pointer: false, ..Default::default() } } @@ -115,7 +113,6 @@ impl<'a, 'b> JsonNameResolver<'a, 'b> { name: "ByRef".into(), namespace: Some("".into()), generics: Some(vec![generic_formatted.clone()]), - is_pointer: false, ..Default::default() } } @@ -127,7 +124,6 @@ impl<'a, 'b> JsonNameResolver<'a, 'b> { name: "ByRefConst".into(), namespace: Some("".into()), generics: Some(vec![generic_formatted.clone()]), - is_pointer: false, ..Default::default() } } diff --git a/src/generate/json/mod.rs b/src/generate/json/mod.rs index 7ea26cef0..c6d570fe7 100644 --- a/src/generate/json/mod.rs +++ b/src/generate/json/mod.rs @@ -108,7 +108,7 @@ pub fn is_real_declaring_type(ty: &CsType, metadata: &CordlMetadata) -> bool { let tdi = ty.self_tag.get_tdi(); let td = tdi.get_type_definition(metadata.metadata); - let is_compiler_generated = td.is_compiler_generated(); + let is_compiler_generated = td.is_compiler_generated(metadata.metadata); !is_compiler_generated && is_declaring_type diff --git a/src/generate/metadata.rs b/src/generate/metadata.rs index 7d3b90ad6..78a96c194 100644 --- a/src/generate/metadata.rs +++ b/src/generate/metadata.rs @@ -48,6 +48,8 @@ pub struct CordlMetadata<'a> { pub child_to_parent_map: HashMap>, pub unity_object_tdi: TypeDefinitionIndex, + pub string_tdi: TypeDefinitionIndex, + pub object_tdi: TypeDefinitionIndex, pub name_to_tdi: HashMap, TypeDefinitionIndex>, pub blacklisted_types: HashSet, diff --git a/src/generate/mod.rs b/src/generate/mod.rs index 72b644277..227f01f58 100644 --- a/src/generate/mod.rs +++ b/src/generate/mod.rs @@ -8,5 +8,9 @@ pub mod offsets; pub mod type_extensions; pub mod writer; +#[cfg(feature = "cpp")] pub mod cpp; +#[cfg(feature = "json")] pub mod json; +#[cfg(feature = "rust")] +pub mod rust; diff --git a/src/generate/offsets/mod.rs b/src/generate/offsets/mod.rs index 28d2b09de..b23fd4518 100644 --- a/src/generate/offsets/mod.rs +++ b/src/generate/offsets/mod.rs @@ -4,7 +4,6 @@ mod offsets_29; #[cfg(feature = "il2cpp_v29")] pub use offsets_29::*; - #[cfg(feature = "il2cpp_v31")] mod offsets_31; diff --git a/src/generate/rust/config.rs b/src/generate/rust/config.rs new file mode 100644 index 000000000..d0b42d3d2 --- /dev/null +++ b/src/generate/rust/config.rs @@ -0,0 +1,122 @@ +use std::{path::PathBuf, sync::LazyLock}; + +pub static STATIC_CONFIG: LazyLock = LazyLock::new(|| RustGenerationConfig { + source_path: PathBuf::from("./codegen-rs/src"), + cargo_config: PathBuf::from("./codegen-rs/Cargo.toml"), +}); + +pub struct RustGenerationConfig { + pub source_path: PathBuf, + pub cargo_config: PathBuf, +} + +impl RustGenerationConfig { + pub fn namespace_rs(&self, string: &str) -> String { + let final_ns = if string.is_empty() { + "GlobalNamespace".to_owned() + } else { + string.replace(['<', '>', '`', '/'], "_").replace('.', "::") + }; + + format!("crate::{final_ns}") + } + + #[inline] + pub fn name_rs(&self, string: &str) -> String { + self.name_rs_plus(string, &[]) + } + + pub fn name_rs_plus(&self, string: &str, additional_exclude: &[&str]) -> String { + if string.trim().is_empty() { + // TODO: handle when multiple params are empty whitespace + return "_cordl_fixed_empty_name_whitespace".to_string(); + } + + if additional_exclude.contains(&string) { + return format!("_cordl_{string}"); + } + if string.to_lowercase() == "mod" { + return format!("_cordl_{string}"); + } + + match string { + // https://github.com/sc2ad/Il2Cpp-Modding-Codegen/blob/b3267c7099f0cc1853e57a1118d1bba3884b5f03/Codegen-CLI/Program.cs#L77-L87 + "alignas" | "alignof" | "and" | "and_eq" | "asm" | "atomic_cancel" + | "atomic_commit" | "atomic_noexcept" | "auto" | "bitand" | "bitor" | "bool" + | "break" | "case" | "catch" | "char" | "char8_t" | "char16_t" | "char32_t" + | "class" | "compl" | "concept" | "const" | "consteval" | "constexpr" | "constinit" + | "const_cast" | "continue" | "co_await" | "co_return" | "co_yield" | "decltype" + | "default" | "delete" | "do" | "double" | "dynamic_cast" | "else" | "enum" + | "explicit" | "export" | "extern" | "false" | "float" | "for" | "friend" | "goto" + | "if" | "inline" | "int" | "long" | "mutable" | "namespace" | "new" | "noexcept" + | "not" | "not_eq" | "nullptr" | "operator" | "or" | "or_eq" | "private" + | "protected" | "public" | "reflexpr" | "register" | "reinterpret_cast" + | "requires" | "return" | "short" | "signed" | "sizeof" | "static" + | "static_assert" | "static_cast" | "struct" | "switch" | "synchronized" + | "template" | "this" | "thread_local" | "throw" | "true" | "try" | "typedef" + | "typeid" | "typename" | "union" | "unsigned" | "using" | "virtual" | "void" + | "volatile" | "wchar_t" | "while" | "xor" | "xor_eq" | "INT_MAX" | "INT_MIN" + | "Assert" | "bzero" | "ID" | "VERSION" | "NULL" | "EOF" | "MOD_ID" | "errno" | "linux" | "module" + | "INFINITY" | "NAN" | "type" | "size" | "time" | "clock" | "rand" | "srand" | "exit" | "match" | + "panic" | "assert" | "debug_assert" | "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" + | "unreachable" | "unimplemented" | "todo" | "trait" | "impl" | "ref" | "mut" | "as" | "use" | "pub" + | "Ok" | "Err" | "ffi" | "c_void" | "c_char" | "c_uchar" | "c_schar" | "c_short" | "c_ushort" + | "c_int" | "c_uint" | "c_long" | "c_ulong" | "c_longlong" | "c_ulonglong" | "c_float" | "c_double" + | "where" | "Self" | "async" | "await" | "move" | "dyn" | "super" | "crate" | "mod" | "let" | "fn" | "in" + | "priv" | "box" | "loop" | "final" | "macro" | "override" | "self" | "gen" | "_" | + // networking headers + "EPERM" + | "ENOENT" | "ESRCH" | "EINTR" | "EIO" | "ENXIO" | "E2BIG" | "ENOEXEC" | "EBADF" + | "ECHILD" | "EAGAIN" | "ENOMEM" | "EACCES" | "EFAULT" | "ENOTBLK" | "EBUSY" + | "EEXIST" | "EXDEV" | "ENODEV" | "ENOTDIR" | "EISDIR" | "EINVAL" | "ENFILE" + | "EMFILE" | "ENOTTY" | "ETXTBSY" | "EFBIG" | "ENOSPC" | "ESPIPE" | "EROFS" + | "EMLINK" | "EPIPE" | "EDOM" | "ERANGE" | "EDEADLK" | "ENAMETOOLONG" | "ENOLCK" + | "ENOSYS" | "ENOTEMPTY" | "ELOOP" | "EWOULDBLOCK" | "ENOMSG" | "EIDRM" | "ECHRNG" + | "EL2NSYNC" | "EL3HLT" | "EL3RST" | "ELNRNG" | "EUNATCH" | "ENOCSI" | "EL2HLT" + | "EBADE" | "EBADR" | "EXFULL" | "ENOANO" | "EBADRQC" | "EBADSLT" | "EDEADLOCK" + | "EBFONT" | "ENOSTR" | "ENODATA" | "ETIME" | "ENOSR" | "ENONET" | "ENOPKG" + | "EREMOTE" | "ENOLINK" | "EADV" | "ESRMNT" | "ECOMM" | "EPROTO" | "EMULTIHOP" + | "EDOTDOT" | "EBADMSG" | "EOVERFLOW" | "ENOTUNIQ" | "EBADFD" | "EREMCHG" + | "ELIBACC" | "ELIBBAD" | "ELIBSCN" | "ELIBMAX" | "ELIBEXEC" | "EILSEQ" + | "ERESTART" | "ESTRPIPE" | "EUSERS" | "ENOTSOCK" | "EDESTADDRREQ" | "EMSGSIZE" + | "EPROTOTYPE" | "ENOPROTOOPT" | "EPROTONOSUPPORT" | "ESOCKTNOSUPPORT" + | "EOPNOTSUPP" | "EPFNOSUPPORT" | "EAFNOSUPPORT" | "EADDRINUSE" | "EADDRNOTAVAIL" + | "ENETDOWN" | "ENETUNREACH" | "ENETRESET" | "ECONNABORTED" | "ECONNRESET" + | "ENOBUFS" | "EISCONN" | "ENOTCONN" | "ESHUTDOWN" | "ETOOMANYREFS" | "ETIMEDOUT" + | "ECONNREFUSED" | "EHOSTDOWN" | "EHOSTUNREACH" | "EALREADY" | "EINPROGRESS" + | "ESTALE" | "EUCLEAN" | "ENOTNAM" | "ENAVAIL" | "EISNAM" | "EREMOTEIO" | "EDQUOT" + | "ENOMEDIUM" | "EMEDIUMTYPE" | "ECANCELED" | "ENOKEY" | "EKEYEXPIRED" + | "EKEYREVOKED" | "EKEYREJECTED" | "EOWNERDEAD" | "ENOTRECOVERABLE" | "ERFKILL" + | "EHWPOISON" | "ENOTSUP" => { + format!("_cordl_{string}") + } + + + _ => self.sanitize_to_rs_name(string), + } + } + /// for converting C++ names into just a single C++ word + pub fn sanitize_to_rs_name(&self, string: &str) -> String { + // Coincidentally the same as path_name + let mut s = string.replace( + [ + '<', '`', '>', '/', '.', ':', '|', ',', '(', ')', '*', '=', '$', '[', ']', '-', + ' ', '=', '<', '`', '>', '/', '.', '|', ',', '(', ')', '[', ']', '-', '&', + ], + "_", + ); + + if s.chars().next().is_some_and(|c| c.is_numeric()) { + s = format!("_cordl_{s}"); + } + s + } + pub fn namespace_path(&self, string: &str) -> String { + string.replace(['<', '>', '`', '/'], "_").replace('.', "/") + } + + pub(crate) fn feature_name(&self, s: &str) -> String { + s.replace([':', '`', '<', '>', '$', '=', ',', '|'], "_") + .replace(['.', '/'], "+") + } +} diff --git a/src/generate/rust/mod.rs b/src/generate/rust/mod.rs new file mode 100644 index 000000000..efa5737d3 --- /dev/null +++ b/src/generate/rust/mod.rs @@ -0,0 +1,12 @@ +mod rust_members; +mod rust_type; + +mod config; +mod rust_context; +mod rust_context_collection; +mod rust_name_components; +mod rust_name_resolver; + +mod rust_fields; + +pub mod rust_main; diff --git a/src/generate/rust/rust_context.rs b/src/generate/rust/rust_context.rs new file mode 100644 index 000000000..c7c3bc772 --- /dev/null +++ b/src/generate/rust/rust_context.rs @@ -0,0 +1,184 @@ +use std::{ + collections::{HashMap, HashSet}, + fs::{create_dir_all, File}, + io::BufWriter, + path::{self, PathBuf}, +}; + +use color_eyre::eyre::ContextCompat; +use itertools::Itertools; +use log::{trace, warn}; +use std::io::Write; + +use crate::generate::{ + cs_type_tag::CsTypeTag, type_extensions::TypeDefinitionExtensions, writer::Writer, +}; + +use super::rust_type::RustType; + +pub struct RustContext { + // combined header + pub fundamental_path: PathBuf, + + // Types to write, typedef + pub typedef_types: HashMap, + + // Name -> alias + pub typealias_types: HashSet<(String, String)>, +} + +impl RustContext { + pub(crate) fn make( + context_tag: crate::generate::cs_type_tag::CsTypeTag, + context: &crate::generate::context::TypeContext, + metadata: &crate::generate::metadata::CordlMetadata<'_>, + config: &super::config::RustGenerationConfig, + ) -> RustContext { + let tdi = context_tag.get_tdi(); + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + let components = t.get_name_components(metadata.metadata); + + let ns = &components.namespace.as_deref().unwrap_or("GlobalNamespace"); + let name = &components.name; + + let path = PathBuf::from(config.namespace_path(ns)); + + let path_name = match t.declaring_type_index != u32::MAX { + true => { + let name = config.name_rs(name); + let base_name = components.declaring_types.unwrap_or_default().join("_"); + + format!("{base_name}_{name}") + } + false => config.name_rs(name), + }; + + let fundamental_path = config + .source_path + .join(path.join(format!("{path_name}_mod")).with_extension("rs")); + + let mut x: RustContext = RustContext { + fundamental_path, + typedef_types: Default::default(), + typealias_types: Default::default(), + }; + + for (tag, ty) in &context.typedef_types { + let mut rs_ty = RustType::make_rust_type(*tag, ty, config); + rs_ty.nested_fixup(&context_tag, ty, metadata, config); + rs_ty.enum_fixup(ty); + + // TODO: Implement blacklist + // let tdi = tag.get_tdi(); + // if metadata.blacklisted_types.contains(&tdi) { + // let result = match t.is_value_type() { + // true => format!( + // "{VALUE_WRAPPER_TYPE}<{:x}>", + // ty.size_info.as_ref().unwrap().instance_size + // ), + // false => IL2CPP_OBJECT_TYPE.to_string(), + // }; + + // if !t.is_value_type() { + // x.typealias_types.insert(( + // rs_ty.cpp_namespace(), + // CppUsingAlias { + // alias: rs_ty.name().to_string(), + // result, + // template: Default::default(), + // }, + // )); + // continue; + // } + // } + + x.typedef_types.insert(*tag, rs_ty); + } + + x + } + + /// Returns an immutable reference to the map of C++ types. + pub fn get_types(&self) -> &HashMap { + &self.typedef_types + } + + /// Returns a mutable reference to the map of C++ types. + pub fn get_types_mut(&mut self) -> &mut HashMap { + &mut self.typedef_types + } + + pub(crate) fn write( + &self, + config: &super::config::RustGenerationConfig, + ) -> Result<(), color_eyre::eyre::Error> { + let _base_path = &config.source_path; + + if !self + .fundamental_path + .parent() + .context("parent is not a directory!")? + .is_dir() + { + // Assume it's never a file + create_dir_all( + self.fundamental_path + .parent() + .context("Failed to create all directories!")?, + )?; + } + + trace!("Writing {:?}", self.fundamental_path.as_path()); + let mut typedef_writer = Writer { + stream: BufWriter::new(File::create(self.fundamental_path.as_path())?), + indent: 0, + newline: true, + }; + + let modules: HashSet<&String> = self + .typedef_types + .values() + .flat_map(|t| t.requirements.get_modules().iter()) + .sorted() + .collect(); + + for m in modules { + writeln!(typedef_writer, "use {m};")?; + } + + for t in self + .typedef_types + .values() + .sorted_by(|a, b| a.rs_name().cmp(b.rs_name())) + { + if t.is_compiler_generated { + warn!("Skipping compiler generated type: {}", t.name()); + continue; + } + + t.write(&mut typedef_writer, config)?; + } + + Ok(()) + } + + pub(crate) fn insert_rust_type(&mut self, new_rs_ty: RustType) { + self.typedef_types.insert(new_rs_ty.self_tag, new_rs_ty); + } + + pub fn get_module_path(&self, config: &super::config::RustGenerationConfig) -> String { + let relative_path = + pathdiff::diff_paths(&self.fundamental_path, &config.source_path).unwrap(); + + let module_name = relative_path.file_stem().unwrap().to_string_lossy(); + + let module_path = relative_path + .parent() + .unwrap() + .to_string_lossy() + .replace(path::MAIN_SEPARATOR, "::"); + + format!("crate::{module_path}::{module_name}") + } +} diff --git a/src/generate/rust/rust_context_collection.rs b/src/generate/rust/rust_context_collection.rs new file mode 100644 index 000000000..84cbaec57 --- /dev/null +++ b/src/generate/rust/rust_context_collection.rs @@ -0,0 +1,381 @@ +use std::{ + collections::{HashMap, HashSet}, + ffi::OsStr, + fs::File, + io::{BufWriter, Write}, +}; + +use rayon::prelude::*; + +use itertools::Itertools; +use log::{info, trace}; +use rayon::iter::ParallelIterator; +use walkdir::WalkDir; + +use crate::generate::{ + cs_context_collection::TypeContextCollection, cs_type::CsType, cs_type_tag::CsTypeTag, + metadata::CordlMetadata, +}; + +use super::{ + config::RustGenerationConfig, rust_context::RustContext, rust_members::RustFeature, + rust_name_resolver::RustNameResolver, rust_type::RustType, +}; + +#[derive(Default)] +pub struct RustContextCollection { + // Should always be a TypeDefinitionIndex + all_contexts: HashMap, + pub alias_context: HashMap, + filled_types: HashSet, + filling_types: HashSet, + borrowing_types: HashSet, +} + +impl RustContextCollection { + pub fn from_cs_collection( + collection: TypeContextCollection, + metadata: &CordlMetadata, + config: &RustGenerationConfig, + ) -> RustContextCollection { + let mut cpp_collection = RustContextCollection::default(); + + info!("Making CppContextCollection from TypeContextCollection"); + for (tag, context) in collection.get() { + cpp_collection + .all_contexts + .insert(*tag, RustContext::make(*tag, context, metadata, config)); + } + cpp_collection.alias_context = collection.alias_context; + + info!("Filling typedefs in CppContextCollection"); + for (_, context) in collection.all_contexts { + for (tag, cs_type) in context.typedef_types { + cpp_collection.fill(tag, cs_type, metadata, config); + } + } + + cpp_collection + } + + fn do_fill_rust_type( + &mut self, + rs_type: &mut RustType, + cs_type: CsType, + metadata: &CordlMetadata, + config: &RustGenerationConfig, + ) { + let tag = rs_type.self_tag; + + if self.filled_types.contains(&tag) { + return; + } + if self.filling_types.contains(&tag) { + panic!("Currently filling type {tag:?}, cannot fill") + } + + // Move ownership to local + self.filling_types.insert(tag); + + let name_resolver = RustNameResolver { + cordl_metadata: metadata, + collection: self, + config, + }; + + rs_type.fill(cs_type, &name_resolver, config); + + self.filled_types.insert(tag); + self.filling_types.remove(&tag.clone()); + } + + pub fn fill( + &mut self, + type_tag: CsTypeTag, + cs_type: CsType, + metadata: &CordlMetadata, + config: &RustGenerationConfig, + ) { + let context_tag = self.get_context_root_tag(type_tag); + + if self.filled_types.contains(&type_tag) { + return; + } + + if self.borrowing_types.contains(&context_tag) { + panic!("Borrowing context {context_tag:?}"); + } + + // Move ownership to local + let cpp_type_entry = self + .all_contexts + .get_mut(&context_tag) + .expect("No cpp context") + .typedef_types + .remove_entry(&type_tag); + + // In some occasions, the CppContext can be empty + if let Some((_t, mut cpp_type)) = cpp_type_entry { + self.do_fill_rust_type(&mut cpp_type, cs_type, metadata, config); + + // Move ownership back up + self.all_contexts + .get_mut(&context_tag) + .expect("No cpp context") + .insert_rust_type(cpp_type); + } + } + + /// + /// By default will only look for nested types of the context, ignoring other CppTypes + /// + pub fn get_cpp_type(&self, ty: CsTypeTag) -> Option<&RustType> { + let context_root_tag = self.get_context_root_tag(ty); + + self.get_context(context_root_tag) + .and_then(|c| c.get_types().get(&ty)) + } + + /// + /// By default will only look for nested types of the context, ignoring other CppTypes + /// + pub fn get_cpp_type_mut(&mut self, ty: CsTypeTag) -> Option<&mut RustType> { + let context_root_tag = self.get_context_root_tag(ty); + + self.get_context_mut(context_root_tag) + .and_then(|c| c.get_types_mut().get_mut(&ty)) + } + + pub fn borrow_cpp_type(&mut self, ty: CsTypeTag, func: F) + where + F: Fn(&mut Self, RustType) -> RustType, + { + let context_ty = self.get_context_root_tag(ty); + if self.borrowing_types.contains(&context_ty) { + panic!("Already borrowing this context!"); + } + + let context = self.all_contexts.get_mut(&context_ty).unwrap(); + + // TODO: Needed? + // self.borrowing_types.insert(context_ty); + + // search in root + // clone to avoid failing il2cpp_name + let Some(declaring_cpp_type) = context.typedef_types.get(&ty).cloned() else { + panic!("No type {context_ty:#?} found!") + }; + let _old_tag = declaring_cpp_type.self_tag; + let new_cpp_ty = func(self, declaring_cpp_type); + + let context = self.all_contexts.get_mut(&context_ty).unwrap(); + + context.insert_rust_type(new_cpp_ty); + + self.borrowing_types.remove(&context_ty); + } + + pub fn get_context(&self, type_tag: CsTypeTag) -> Option<&RustContext> { + let context_tag = self.get_context_root_tag(type_tag); + if self.borrowing_types.contains(&context_tag) { + panic!("Borrowing this context! {context_tag:?}"); + } + self.all_contexts.get(&context_tag) + } + pub fn get_context_mut(&mut self, type_tag: CsTypeTag) -> Option<&mut RustContext> { + let context_tag = self.get_context_root_tag(type_tag); + if self.borrowing_types.contains(&context_tag) { + panic!("Borrowing this context! {context_tag:?}"); + } + self.all_contexts + .get_mut(&self.get_context_root_tag(context_tag)) + } + + pub fn get_context_root_tag(&self, ty: CsTypeTag) -> CsTypeTag { + self.alias_context + .get(&ty) + .cloned() + // .map(|t| self.get_context_root_tag(*t)) + .unwrap_or(ty) + } + + pub(crate) fn get(&self) -> &HashMap { + &self.all_contexts + } + pub(crate) fn get_mut(&mut self) -> &mut HashMap { + &mut self.all_contexts + } + + pub fn write_all(&self, config: &RustGenerationConfig) -> color_eyre::Result<()> { + let amount = self.all_contexts.len() as f64; + self.all_contexts + .iter() + .enumerate() + .try_for_each(|(i, (_, c))| { + trace!( + "Writing {:.4}% ({}/{}) {}", + (i as f64 / amount * 100.0), + i, + amount, + c.fundamental_path.display(), + ); + c.write(config) + }) + } + + pub fn write_feature_block(&self, config: &RustGenerationConfig) -> color_eyre::Result<()> { + let dependency_graph: Vec<(&RustType, Vec<&RustFeature>)> = self + .all_contexts + .values() + .flat_map(|c| c.typedef_types.values()) + .filter(|t| t.self_feature.is_some()) + .map(|t| { + let dependencies = t + .requirements + .get_dependencies() + .iter() + .filter(|o| **o != t.self_tag) + .filter_map(|o| self.get_cpp_type(*o)) + .filter_map(|o| o.self_feature.as_ref()) + .collect_vec(); + (t, dependencies) + }) + .collect(); + + let feature_block = dependency_graph + // combine all features with same name that somehow exist + .into_iter() + .into_group_map_by(|(t, _)| t.self_feature.as_ref().unwrap().name.clone()) + .into_iter() + .map(|(feature_name, features)| { + ( + feature_name, + features.into_iter().fold(Vec::new(), |mut a, b| { + a.extend(b.1); + a + }), + ) + }) + // make feature block + .map(|(feature_name, features)| { + let dependencies = features + .iter() + .map(|s| format!("\"{}\"", s.name)) + // Sort so things don't break git diffs + .sorted() + .join(", "); + + let feature = format!("\"{feature_name}\" = [{dependencies}]",); + + feature + }) + .unique() + // Sort so things don't break git diffs + .sorted() + .join("\n"); + + let mut cargo_config = + match std::fs::read_to_string("./cordl_internals_rs/Cargo_template.toml") { + Ok(content) => content, + Err(_) => { + eprintln!("Failed to load file `./cordl_internals_rs/Cargo_template.toml`"); + return Err(color_eyre::eyre::eyre!("Failed to load Cargo template")); + } + }; + + cargo_config = cargo_config.replace("#cordl_features", &feature_block); + + let mut file = File::create(&config.cargo_config)?; + file.write_all(cargo_config.as_bytes())?; + + Ok(()) + } + + pub fn write_namespace_modules(&self, config: &RustGenerationConfig) -> color_eyre::Result<()> { + info!("Writing namespace modules!"); + fn make_mod_dir(dir: &std::path::Path, name: &str) -> Result<(), color_eyre::eyre::Error> { + if !dir.exists() { + return Ok(()); + } + + let mut modules_paths = WalkDir::new(dir) + .max_depth(1) + .min_depth(1) + .into_iter() + .map(|c| c.map(|entry| entry.into_path())) + .collect::>>()?; + + // Sort so things don't break git diffs + modules_paths.sort(); + + if modules_paths.is_empty() { + return Ok(()); + } + + let mod_path = dir.join(name).with_extension("rs"); + let mod_file = File::options() + .truncate(false) + .append(true) + .create(true) + .open(&mod_path)?; + let mut buf_writer = BufWriter::new(mod_file); + + for module in &modules_paths { + if module == dir || *module == mod_path { + continue; + } + + if !module.exists() { + continue; + } + + let file_stem = module.file_stem().unwrap().to_string_lossy(); + + if module.is_dir() { + make_mod_dir(module, "mod.rs")?; + writeln!(buf_writer, "// namespace {};", file_stem)?; + writeln!(buf_writer, "pub mod {};", file_stem)?; + } else if module.extension() == Some(OsStr::new("rs")) { + writeln!(buf_writer, "// class {}; export all", file_stem)?; + writeln!(buf_writer, "mod {};", file_stem)?; + writeln!(buf_writer, "pub use {}::*;", file_stem)?; + } + } + + buf_writer.flush()?; + + Ok(()) + } + + let mod_file = File::options() + .create(true) + .append(true) + .truncate(false) + .open(config.source_path.join("lib.rs"))?; + let mut buf_writer = BufWriter::new(mod_file); + writeln!( + buf_writer, + " + #![feature(inherent_associated_types)] + + #![allow(clippy::all)] + #![allow(unused)] + #![allow(non_snake_case)] + #![allow(non_camel_case_types)] + #![allow(non_upper_case_globals)] + #![allow(non_ascii_idents)] + #![allow(bad_style)] + #![allow(clippy::module_name_repetitions)] + #![allow(clippy::similar_names)] + #![allow(clippy::case_sensitive_file_name)] + #![allow(clippy::enum_variant_names)] + #![allow(clippy::large_enum_variant)] + " + )?; + buf_writer.flush()?; + + make_mod_dir(&config.source_path, "lib.rs")?; + + Ok(()) + } +} diff --git a/src/generate/rust/rust_fields.rs b/src/generate/rust/rust_fields.rs new file mode 100644 index 000000000..17a76ddef --- /dev/null +++ b/src/generate/rust/rust_fields.rs @@ -0,0 +1,554 @@ +use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::Il2CppTypeEnum}; +use itertools::Itertools; +use log::warn; +use quote::{format_ident, quote}; +use syn::parse_quote; + +use crate::{ + data::type_resolver::{ResolvedTypeData, TypeUsage}, + generate::{ + cs_members::{CsField, CsValue}, + metadata::CordlMetadata, + type_extensions::{TypeDefinitionExtensions, TypeDefinitionIndexExtensions}, + }, +}; + +/* +/// @brief Explicitly laid out type with union based offsets +union { + + +#pragma pack(push, tp, 1) +struct { +/// @brief Padding field 0x0 + uint8_t ___U0_padding[0x0]; +/// @brief Field U0, offset: 0x0, size: 0x4, def value: None + uint32_t ___U0; +}; +#pragma pack(pop, tp) +struct { +/// @brief Padding field 0x0 for alignment + uint8_t ___U0_padding_forAlignment[0x0]; +/// @brief Field U0, offset: 0x0, size: 0x4, def value: None + uint32_t ___U0_forAlignment; +}; + +#pragma pack(push, tp, 1) +struct { +/// @brief Padding field 0x4 + uint8_t ___U1_padding[0x4]; +/// @brief Field U1, offset: 0x4, size: 0x4, def value: None + uint32_t ___U1; +}; +#pragma pack(pop, tp) +struct { +/// @brief Padding field 0x4 for alignment + uint8_t ___U1_padding_forAlignment[0x4]; +/// @brief Field U1, offset: 0x4, size: 0x4, def value: None + uint32_t ___U1_forAlignment; +}; +#pragma pack(push, tp, 1) +struct { +/// @brief Padding field 0x8 + uint8_t ___U2_padding[0x8]; +/// @brief Field U2, offset: 0x8, size: 0x4, def value: None + uint32_t ___U2; +}; +#pragma pack(pop, tp) +struct { +/// @brief Padding field 0x8 for alignment + uint8_t ___U2_padding_forAlignment[0x8]; +/// @brief Field U2, offset: 0x8, size: 0x4, def value: None + uint32_t ___U2_forAlignment; +}; +#pragma pack(push, tp, 1) +struct { +/// @brief Padding field 0x0 + uint8_t ___ulo64LE_padding[0x0]; +/// @brief Field ulo64LE, offset: 0x0, size: 0x8, def value: None + uint64_t ___ulo64LE; +}; +#pragma pack(pop, tp) +struct { +/// @brief Padding field 0x0 for alignment + uint8_t ___ulo64LE_padding_forAlignment[0x0]; +/// @brief Field ulo64LE, offset: 0x0, size: 0x8, def value: None + uint64_t ___ulo64LE_forAlignment; +}; +#pragma pack(push, tp, 1) +struct { +/// @brief Padding field 0x8 + uint8_t ___uhigh64LE_padding[0x8]; +/// @brief Field uhigh64LE, offset: 0x8, size: 0x8, def value: None + uint64_t ___uhigh64LE; +}; +#pragma pack(pop, tp) +struct { +/// @brief Padding field 0x8 for alignment + uint8_t ___uhigh64LE_padding_forAlignment[0x8]; +/// @brief Field uhigh64LE, offset: 0x8, size: 0x8, def value: None + uint64_t ___uhigh64LE_forAlignment; +}; +}; +*/ + +use super::{ + config::RustGenerationConfig, + rust_members::{ + ConstRustField, RustField, RustFunction, RustParam, RustStruct, RustUnion, Visibility, + }, + rust_name_resolver::RustNameResolver, + rust_type::RustType, +}; + +pub(crate) fn handle_valuetype_fields( + cpp_type: &mut RustType, + fields: &[CsField], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, +) { + let metadata = name_resolver.cordl_metadata; + let tdi = cpp_type.self_tag.get_tdi(); + // Value types only need getter fixes for explicit layout types + let t = tdi.get_type_definition(metadata.metadata); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + // instance fields for explicit layout value types are special + if t.is_explicit_layout() { + // TODO: Figure out layouts for explicit layout types + // let backing_fields = fields + // .iter() + // .map(|f| make_rust_field(cpp_type, &f, name_resolver, config)) + // // .map(|mut f| { + // // f.name = fixup_backing_field(&f.cpp_name); + // // f + // // }) + // .collect_vec(); + + // handle_instance_fields(cpp_type, &backing_fields, metadata, tdi); + } + let backing_fields = fields + .iter() + .map(|f| make_rust_field(cpp_type, f, name_resolver, config)) + .collect_vec(); + + handle_instance_fields(cpp_type, &backing_fields, fields, metadata, tdi); +} + +pub(crate) fn handle_referencetype_fields( + cpp_type: &mut RustType, + fields: &[CsField], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, +) { + let metadata = name_resolver.cordl_metadata; + let tdi = cpp_type.self_tag.get_tdi(); + let t = tdi.get_type_definition(metadata.metadata); + + if t.is_explicit_layout() { + warn!( + "Reference type with explicit layout: {}", + cpp_type.rs_name_components.combine_all() + ); + } + + // if no fields, skip + if t.field_count == 0 { + return; + } + + let backing_fields = fields + .iter() + .filter(|f| f.instance && !f.is_const) + .map(|f| make_rust_field(cpp_type, f, name_resolver, config)) + // .map(|mut f| { + // f.cpp_name = fixup_backing_field(&f.cpp_name); + // f + // }) + .collect_vec(); + + handle_instance_fields(cpp_type, &backing_fields, fields, metadata, tdi); +} + +pub fn handle_static_fields( + cpp_type: &mut RustType, + fields: &[CsField], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, +) { + let metadata = name_resolver.cordl_metadata; + + let tdi = cpp_type.self_tag.get_tdi(); + let t = tdi.get_type_definition(metadata.metadata); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + return; + todo!(); + + // we want only static fields + // we ignore constants + for field_info in fields.iter().filter(|f| !f.instance && !f.is_const) { + let f_name = &field_info.name; + + let field_ty_cpp_name = + name_resolver.resolve_name(cpp_type, &field_info.field_ty, TypeUsage::Field, true); + let field_ty_ast = field_ty_cpp_name.to_type_token(); + + // non const field + // instance field access on ref types is special + // ref type instance fields are specially named because the field getters are supposed to be used + let f_cpp_name = config.name_rs(f_name); + + let klass_resolver = cpp_type.classof_name(); + + let getter_call = + quote!("return getStaticField<{field_ty_ast}, \"{f_name}\", {klass_resolver}>();"); + + let setter_var_name = format_ident!("value"); + let setter_call = + quote!("setStaticField<{field_ty_ast}, \"{f_name}\", {klass_resolver}>(std::forward<{field_ty_ast}>({setter_var_name}));"); + + let getter_name = format_ident!("getStaticF_{}", f_cpp_name); + let setter_name = format_ident!("setStaticF_{}", f_cpp_name); + + let get_return_type = field_ty_cpp_name; + + let getter_decl = RustFunction { + name: getter_name.clone(), + is_ref: false, + is_mut: false, + is_self: false, + generics: Default::default(), + + return_type: Some(get_return_type.to_type_token()), + params: vec![], + visibility: (Visibility::Public), + body: Some(parse_quote! { + #getter_call + , + }), + where_clause: None, + }; + + let setter_decl = RustFunction { + name: setter_name, + generics: Default::default(), + + is_ref: false, + is_mut: false, + is_self: false, + + return_type: None, + params: vec![RustParam { + name: setter_var_name, + param_type: field_ty_cpp_name.to_type_token(), + }], + visibility: (Visibility::Public), + body: Some(parse_quote!( + #setter_call + )), + where_clause: None, + }; + + // only push accessors if declaring ref type, or if static field + cpp_type.methods.push(getter_decl); + cpp_type.methods.push(setter_decl); + } +} + +pub(crate) fn handle_const_fields( + cpp_type: &mut RustType, + fields: &[CsField], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, +) { + let metadata = name_resolver.cordl_metadata; + + // if no fields, skip + if fields.is_empty() { + return; + } + + let fields = fields + .iter() + .filter(|f| f.is_const) + .filter_map(|field_info| { + let f_resolved_type = &field_info.field_ty; + let mut f_type = name_resolver + .resolve_name(cpp_type, f_resolved_type, TypeUsage::Field, true) + .to_type_token(); + let f_name = format_ident!("{}", config.name_rs(&field_info.name)); + + if f_resolved_type.data == ResolvedTypeData::Primitive(Il2CppTypeEnum::String) { + f_type = parse_quote!(&'static str); + } + + // const fields with enum types not supported right now + // TODO: + if !cpp_type.is_enum_type && matches!(f_resolved_type.data, ResolvedTypeData::Type(_)) { + return None; + } + let def_value = field_info.value.as_ref(); + + let def_value = def_value.expect("Constant with no default value?"); + + let rs_def_value: syn::Expr = match def_value { + CsValue::String(s) => { + let new_s = s.replace("\\\\", "\\"); + + parse_quote! { #new_s } + } + CsValue::Char(c) => syn::parse_str(format!("'{}'", c).as_str()).unwrap(), + CsValue::Bool(b) => parse_quote! { #b }, + CsValue::U8(u) => parse_quote! { #u }, + CsValue::U16(u) => parse_quote! { #u }, + CsValue::U32(u) => parse_quote! { #u }, + CsValue::U64(u) => parse_quote! { #u }, + CsValue::I8(i) => parse_quote! { #i }, + CsValue::I16(i) => parse_quote! { #i }, + CsValue::I32(i) => parse_quote! { #i }, + CsValue::I64(i) => parse_quote! { #i }, + CsValue::F32(f) => match f { + f if f.is_finite() => parse_quote! { #f }, + f if f.is_infinite() => { + if f.is_sign_positive() { + parse_quote! { std::f32::INFINITY } + } else { + parse_quote! { std::f32::NEG_INFINITY } + } + } + f if f.is_nan() => parse_quote! { std::f64::NAN }, + _ => panic!("Unexpected f32 value: {}", f), + }, + CsValue::F64(f) => match f { + f if f.is_finite() => parse_quote! { #f }, + f if f.is_infinite() => { + if f.is_sign_positive() { + parse_quote! { std::f64::INFINITY } + } else { + parse_quote! { std::f64::NEG_INFINITY } + } + } + f if f.is_nan() => parse_quote! { std::f64::NAN }, + _ => panic!("Unexpected f64 value: {}", f), + }, + CsValue::Null => parse_quote! { Default::default() }, + CsValue::Object(_) => todo!(), + CsValue::ValueType(_) => todo!(), + }; + + let cpp_field_template = ConstRustField { + name: f_name, + field_type: f_type, + visibility: Visibility::Public, + value: rs_def_value, + }; + + Some((cpp_field_template, field_info)) + }) + .sorted_by(|a, b| a.1.name.cmp(&b.1.name)) + .collect_vec(); + + if cpp_type.is_enum_type { + // enums cannot have multiple entries with the same value + for f in fields + .into_iter() + .unique_by(|f| f.1.value.as_ref().unwrap().to_string()) + { + cpp_type.constants.push(f.0); + } + } else { + for f in fields { + cpp_type.constants.push(f.0); + } + } +} + +fn handle_instance_fields( + cpp_type: &mut RustType, + fields: &[RustField], + cs_fields: &[CsField], + metadata: &CordlMetadata, + tdi: TypeDefinitionIndex, +) { + let t = tdi.get_type_definition(metadata.metadata); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + // let property_exists = |to_find: &str| cpp_type.fields.iter().any(|d| d.name == to_find); + + // explicit layout types are packed into single unions + if t.is_explicit_layout() { + // oh no! the fields are unionizing! don't tell elon musk! + let last_field = cs_fields + .iter() + .filter(|t| t.offset.is_some()) + .max_by(|a, b| { + let offset = a.offset.cmp(&b.offset); + let size = a.size.cmp(&b.size); + + offset.then(size) + }); + + if let Some(last_field) = last_field { + // make the type as big as it needs to be to match ABI + let size = last_field.offset.unwrap() as usize + last_field.size; + + let size_field = RustField { + name: format_ident!("padding"), + field_type: parse_quote!([u8; #size]), + visibility: Visibility::Private, + offset: 0, + }; + + cpp_type.fields.push(size_field); + } + + // let u = pack_fields_into_single_union(fields); + // cpp_type.fields.push(RustField { + // name: "explicit_layout".to_string(), + // field_type: RustItem::Union(u), + // visibility: Visibility::Private, + // offset: 0, + // }); + } else { + fields + .iter() + .cloned() + .for_each(|member| cpp_type.fields.push(member)); + }; +} + +// inspired by what il2cpp does for explicitly laid out types +pub(crate) fn pack_fields_into_single_union(fields: &[RustField]) -> RustUnion { + // get the min offset to use as a base for the packed structs + let min_offset = fields.iter().map(|f| f.offset).min().unwrap_or(0); + + let packed_structs = fields + .iter() + .cloned() + .flat_map(|field| { + let structs = field_into_offset_structs(min_offset, field); + + vec![structs.0, structs.1] + }) + .collect_vec(); + + todo!() + // let fields = packed_structs + // .into_iter() + // .enumerate() + // .map(|(i, struc)| RustField { + // name: format!("struct{}", i), + // field_type: RustItem::Struct(struc), + // visibility: Visibility::Private, + // offset: 0, + // }) + // .collect_vec(); + + // RustUnion { fields } +} + +pub(crate) fn field_into_offset_structs( + _min_offset: u32, + field: RustField, +) -> (RustStruct, RustStruct) { + // il2cpp basically turns each field into 2 structs within a union: + // 1 which is packed with size 1, and padded with offset to fit to the end + // the other which has the same padding and layout, except this one is for alignment so it's just packed as the parent struct demands + + let actual_offset = field.offset; + let padding = field.offset; + + let f_name = &field.name; + + let packed_padding_cpp_name = format!("{f_name}_padding"); + let alignment_padding_cpp_name = format!("{f_name}_padding_forAlignment"); + let alignment_cpp_name = format!("{f_name}_forAlignment"); + + let packed_padding_field = RustField { + name: format_ident!("{}", packed_padding_cpp_name), + field_type: parse_quote!([u8; {padding:x}]), + visibility: Visibility::Private, + offset: actual_offset, + // brief_comment: Some(format!("Padding field 0x{padding:x}")), + // const_expr: false, + // cpp_name: packed_padding_cpp_name, + // field_ty: "uint8_t".into(), + // offset: Some(*actual_offset), + // instance: true, + // is_private: false, + // readonly: false, + // value: None, + }; + + let alignment_padding_field = RustField { + name: format_ident!("{}", alignment_padding_cpp_name), + field_type: parse_quote!([u8; #padding]), + visibility: Visibility::Private, + offset: actual_offset, + // brief_comment: Some(format!("Padding field 0x{padding:x} for alignment")), + // const_expr: false, + // cpp_name: alignment_padding_cpp_name, + // field_ty: "uint8_t".into(), + // offset: Some(*actual_offset), + // instance: true, + // is_private: false, + // readonly: false, + // value: None, + }; + + let alignment_field = RustField { + name: format_ident!("{}", alignment_cpp_name), + visibility: Visibility::Private, + ..field.clone() + }; + + let packed_field = RustField { + visibility: Visibility::Public, + ..field + }; + + let packed_struct = RustStruct { + fields: vec![packed_padding_field.clone(), packed_field.clone()], + + packing: Some(1), + }; + + let alignment_struct = RustStruct { + fields: vec![(alignment_padding_field), (alignment_field)], + + packing: None, + }; + + (packed_struct, alignment_struct) +} + +fn make_rust_field( + cpp_type: &mut RustType, + f: &CsField, + name_resolver: &RustNameResolver<'_, '_>, + config: &RustGenerationConfig, +) -> RustField { + let field_type = name_resolver.resolve_name(cpp_type, &f.field_ty, TypeUsage::Field, true); + + assert!(f.instance && !f.is_const, "Static field not allowed!"); + + RustField { + name: format_ident!("{}", config.name_rs(&f.name)), + field_type: field_type.to_type_token(), + visibility: Visibility::Public, + offset: f.offset.unwrap_or_default(), + } +} diff --git a/src/generate/rust/rust_main.rs b/src/generate/rust/rust_main.rs new file mode 100644 index 000000000..08728818b --- /dev/null +++ b/src/generate/rust/rust_main.rs @@ -0,0 +1,314 @@ +use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; + +use itertools::Itertools; +use log::info; +use rayon::iter::ParallelIterator; + +use crate::generate::{ + cs_context_collection::TypeContextCollection, + metadata::CordlMetadata, + rust::{config::STATIC_CONFIG, rust_context_collection::RustContextCollection}, + type_extensions::{TypeDefinitionExtensions, TypeDefinitionIndexExtensions}, +}; + +pub fn run_rust( + cs_collection: TypeContextCollection, + metadata: &CordlMetadata, +) -> color_eyre::Result<()> { + let rs_context_collection = + RustContextCollection::from_cs_collection(cs_collection, metadata, &STATIC_CONFIG); + + info!("Registering handlers!"); + + // let e = cpp_context_collection.cyclic_include_check()?; + + if STATIC_CONFIG.source_path.exists() { + std::fs::remove_dir_all(&STATIC_CONFIG.source_path)?; + } + std::fs::create_dir_all(&STATIC_CONFIG.source_path)?; + + const write_all: bool = true; + if write_all { + info!("Writing all"); + rs_context_collection.write_all(&STATIC_CONFIG)?; + } else { + // for t in &metadata.type_definitions { + // // Handle the generation for a single type + // let dest = open_writer(&metadata, &config, &t); + // write_type(&metadata, &config, &t, &dest); + // } + fn make_td_tdi(idx: u32) -> TypeData { + TypeData::TypeDefinitionIndex(TypeDefinitionIndex::new(idx)) + } + // All indices require updating + // cpp_context_collection.get()[&make_td_tdi(123)].write()?; + // cpp_context_collection.get()[&make_td_tdi(342)].write()?; + // cpp_context_collection.get()[&make_td_tdi(512)].write()?; + // cpp_context_collection.get()[&make_td_tdi(1024)].write()?; + // cpp_context_collection.get()[&make_td_tdi(600)].write()?; + // cpp_context_collection.get()[&make_td_tdi(1000)].write()?; + // cpp_context_collection.get()[&make_td_tdi(420)].write()?; + // cpp_context_collection.get()[&make_td_tdi(69)].write()?; + // cpp_context_collection.get()[&make_td_tdi(531)].write()?; + // cpp_context_collection.get()[&make_td_tdi(532)].write()?; + // cpp_context_collection.get()[&make_td_tdi(533)].write()?; + // cpp_context_collection.get()[&make_td_tdi(534)].write()?; + // cpp_context_collection.get()[&make_td_tdi(535)].write()?; + // cpp_context_collection.get()[&make_td_tdi(1455)].write()?; + + info!("Generic type"); + let types = || { + rs_context_collection + .get() + .iter() + .filter(|(tag, _)| !metadata.blacklisted_types.contains(&tag.get_tdi())) + }; + + types() + .find(|(_, c)| c.get_types().iter().any(|(_, t)| t.generics.is_some())) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("List Generic type"); + types() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.rs_name_components.generics.is_some() && t.rs_name() == "List_1" + }) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("Value type"); + types() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.is_value_type && t.name() == "Color" && t.namespace() == Some("UnityEngine") + }) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + // info!("Nested type"); + // cpp_context_collection + // .get() + // .iter() + // .find(|(_, c)| { + // c.get_types().iter().any(|(_, t)| { + // t.nested_types + // .iter() + // .any(|(_, n)| !n.declarations.is_empty()) + // }) + // }) + // .unwrap() + // .1 + // .write()?; + // Doesn't exist anymore? + // info!("AlignmentUnion type"); + // cpp_context_collection + // .get() + // .iter() + // .find(|(_, c)| { + // c.get_types() + // .iter() + // .any(|(_, t)| t.is_value_type && &t.name()== "AlignmentUnion") + // }) + // .unwrap() + // .1 + // .write()?; + info!("Array type"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "Array" && t.namespace() == Some("System")) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + + info!("Enum type"); + types() + .find(|(_, c)| c.get_types().iter().any(|(_, t)| t.is_enum_type)) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.Object"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "Object" && t.namespace() == Some("UnityEngine")) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("BeatmapSaveDataHelpers"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "BeatmapSaveDataHelpers") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("HMUI.ViewController"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("HMUI") && t.name() == "ViewController") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.Component"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("UnityEngine") && t.name() == "Component") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.GameObject"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("UnityEngine") && t.name() == "GameObject") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("MainFlowCoordinator"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace().is_none() && t.name() == "MainFlowCoordinator") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("OVRPlugin"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace().is_none() && t.name() == "OVRPlugin") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("HMUI.IValueChanger"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("HMUI") && t.name() == "IValueChanger`1") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.ValueType"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("System") && t.name() == "ValueType") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.ValueTuple_2"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("System") && t.name() == "ValueTuple`2") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Decimal"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("System") && t.name() == "Decimal") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Enum"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("System") && t.name() == "Enum") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Multicast"); + types() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.namespace() == Some("System") && t.name() == "MulticastDelegate" + }) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Delegate"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == Some("System") && t.name() == "Delegate") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("BeatmapSaveDataVersion3.BeatmapSaveData.EventBoxGroup`1"); + types() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name().contains("EventBoxGroup`1")) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("Explicitly laid out type"); + types() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + !t.is_compiler_generated + && t.self_tag + .get_tdi() + .get_type_definition(metadata.metadata) + .is_explicit_layout() + }) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + + // for (_, context) in cpp_context_collection.get() { + // context.write().unwrap(); + // } + } + + rs_context_collection.write_namespace_modules(&STATIC_CONFIG)?; + rs_context_collection.write_feature_block(&STATIC_CONFIG)?; + + Ok(()) +} + +fn format_files() -> color_eyre::Result<()> { + todo!(); +} diff --git a/src/generate/rust/rust_members.rs b/src/generate/rust/rust_members.rs new file mode 100644 index 000000000..921646196 --- /dev/null +++ b/src/generate/rust/rust_members.rs @@ -0,0 +1,223 @@ +use std::str::FromStr; + +use itertools::Itertools; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::parse_quote; + +#[derive(Clone, Debug, Default)] +pub enum Visibility { + Public, + PublicCrate, + #[default] + Private, +} + +#[derive(Clone)] +pub struct RustStruct { + pub fields: Vec, + pub packing: Option, +} +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash, Clone)] +pub struct RustGeneric { + pub name: String, + pub bounds: Vec, +} + +impl RustGeneric { + pub fn to_token_stream(&self) -> syn::GenericParam { + let name = format_ident!("{}", self.name); + match self.bounds.is_empty() { + true => parse_quote!(#name), + false => { + let bounds = self + .bounds + .iter() + .map(|b| -> syn::Type { syn::parse_str(b.as_str()).unwrap() }); + parse_quote!(#name: #(#bounds)+*) + } + } + } +} + +impl FromStr for RustGeneric { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(RustGeneric { + name: s.to_string(), + bounds: Default::default(), + }) + } +} + +impl From for RustGeneric { + fn from(value: String) -> Self { + RustGeneric { + name: value, + bounds: Default::default(), + } + } +} + +impl ToString for RustGeneric { + fn to_string(&self) -> String { + match self.bounds.is_empty() { + true => self.name.clone(), + false => format!("{}: {}", self.name, self.bounds.join("+")), + } + } +} + +#[derive(Clone)] +pub struct RustUnion { + pub fields: Vec, +} + +#[derive(Clone)] + +pub struct ConstRustField { + pub name: syn::Ident, + pub field_type: syn::Type, + pub value: syn::Expr, + pub visibility: Visibility, +} + +#[derive(Clone)] +pub struct RustField { + pub name: syn::Ident, + pub field_type: syn::Type, + pub visibility: Visibility, + pub offset: u32, +} + +#[derive(Clone, Debug)] + +pub struct RustFeature { + pub name: String, +} + +#[derive(Clone)] +pub struct RustFunction { + pub name: syn::Ident, + pub params: Vec, + pub return_type: Option, + pub body: Option>, + pub generics: Vec, + pub where_clause: Option, + + pub is_self: bool, + pub is_ref: bool, + pub is_mut: bool, + pub visibility: Visibility, +} + +#[derive(Clone)] +pub struct RustParam { + pub name: syn::Ident, + pub param_type: syn::Type, +} + +#[derive(Clone)] +pub struct RustTraitImpl { + pub name: String, + pub impl_data: syn::ItemImpl, +} + +#[derive(Clone)] +pub struct RustImpl { + pub trait_name: Option, + pub type_name: String, + + pub generics: Vec, + pub lifetimes: Vec, + + pub methods: Vec, +} + +type Generic = String; +type Lifetime = String; + +impl RustFunction { + pub fn to_token_stream(&self) -> TokenStream { + let name: syn::Ident = format_ident!("{}", self.name); + let generics: Option = match self.generics.is_empty() { + true => None, + false => { + let generics = self.generics.iter().map(|g| -> syn::GenericParam { + syn::parse_str(g.to_string().as_str()).unwrap() + }); + Some(parse_quote!(<#(#generics),*>)) + } + }; + + let self_param: Option = match self.is_self { + true if self.is_mut && self.is_ref => Some(parse_quote! { &mut self }), + true if self.is_ref => Some(parse_quote! { &self }), + true if self.is_mut => Some(parse_quote! { mut self }), + true => Some(parse_quote! { self }), + false => None, + }; + + let params = self.params.iter().map(|p| -> syn::FnArg { + let name = format_ident!("{}", p.name); + let param_type = &p.param_type; + parse_quote! { #name: #param_type } + }); + let return_type: syn::ReturnType = match &self.return_type { + Some(t_ty) => { + parse_quote! { -> #t_ty } + } + None => parse_quote! {}, + }; + let where_clause = &self.where_clause; + + let visibility = self.visibility.to_token_stream(); + let mut tokens = match self_param { + Some(self_param) => { + quote! { + #visibility fn #name #generics (#self_param, #(#params),*) #return_type #where_clause + } + } + None => { + quote! { + #visibility fn #name #generics (#(#params),*) #return_type #where_clause + } + } + }; + + if let Some(body) = &self.body { + tokens = quote! { + #tokens { + #(#body)* + } + }; + } else { + tokens = quote! { + #tokens; + }; + } + + tokens + } +} + +impl Visibility { + pub fn to_token_stream(&self) -> syn::Visibility { + match self { + Visibility::Public => parse_quote! { pub }, + Visibility::PublicCrate => parse_quote! { pub(crate) }, + Visibility::Private => parse_quote! {}, + } + } +} + +impl ToString for Visibility { + fn to_string(&self) -> String { + match self { + Visibility::Public => "pub".to_string(), + Visibility::PublicCrate => "pub(crate)".to_string(), + Visibility::Private => "".to_string(), + } + } +} diff --git a/src/generate/rust/rust_name_components.rs b/src/generate/rust/rust_name_components.rs new file mode 100644 index 000000000..a8734db5e --- /dev/null +++ b/src/generate/rust/rust_name_components.rs @@ -0,0 +1,284 @@ +use itertools::Itertools; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::parse_quote; + +use crate::data::name_components::NameComponents; + +use super::rust_members::RustGeneric; + +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash, Clone)] +pub struct RustNameComponents { + pub name: String, + pub namespace: Option, + pub generics: Option>, + + pub is_ref: bool, + pub is_dyn: bool, + pub is_static_ref: bool, + pub is_ptr: bool, + pub is_mut: bool, +} + +impl RustNameComponents { + // TODO: Add setting for adding :: prefix + // however, this cannot be allowed in all cases + pub fn combine_all(&self) -> String { + // will be empty if no namespace or declaring types + let prefix = self + .namespace + .as_ref() + .map(|s| format!("{s}::")) + .unwrap_or_default(); + + let mut completed = format!("{prefix}{}", self.name); + + if let Some(generics) = &self.generics { + completed = format!( + "{completed}<{}>", + generics.iter().map(|s| &s.name).join(",") + ); + } + + let mut prefix: String = String::new(); + // & + if self.is_static_ref { + prefix = "&'static ".to_string(); + } else if self.is_ref { + prefix = "&".to_string(); + } else if self.is_ptr { + prefix = "*".to_string(); + } + // mut + if self.is_mut { + prefix += "mut "; + } + if self.is_dyn { + prefix += "dyn "; + } + + // add & or * or mut + completed = prefix + &completed; + + completed + } + + pub fn wrap_by_gc(self) -> RustNameComponents { + if !self.is_ptr { + return self; + } + + RustNameComponents { + namespace: Some("quest_hook::libil2cpp".to_owned()), + name: "Gc".to_string(), + generics: Some(vec![RustGeneric { + name: self.with_no_prefix().combine_all(), + ..Default::default() + }]), + is_ref: false, + is_ptr: false, + is_mut: false, + is_static_ref: false, + is_dyn: false, + } + } + + pub fn with_no_prefix(mut self) -> RustNameComponents { + self.is_ref = false; + self.is_ptr = false; + self.is_mut = false; + self.is_static_ref = false; + self + } + + pub fn with_ref(mut self) -> RustNameComponents { + self.is_ref = true; + + self.is_static_ref = false; + self.is_ptr = false; + self + } + pub fn with_static_ref(mut self) -> RustNameComponents { + self.is_static_ref = true; + + self.is_ref = false; + self.is_ptr = false; + self + } + + pub fn with_ptr(mut self) -> RustNameComponents { + self.is_ptr = true; + + self.is_ref = false; + self.is_static_ref = false; + self + } + pub fn with_mut(mut self) -> RustNameComponents { + self.is_mut = true; + self + } + pub fn without_mut(mut self) -> RustNameComponents { + self.is_mut = false; + self + } + + pub fn remove_generics(mut self) -> RustNameComponents { + self.generics = None; + self + } + pub fn remove_generics_bounds(mut self) -> RustNameComponents { + self.generics = self.generics.map(|g| { + g.into_iter() + .map(|mut g| { + g.bounds = Default::default(); + g + }) + .collect() + }); + self + } + pub fn remove_namespace(mut self) -> RustNameComponents { + self.namespace = None; + self + } + + pub fn to_name_ident(&self) -> TokenStream { + match self.generics { + Some(ref generics) => { + let generics = generics.iter().map(|g| -> syn::GenericArgument { + let s = g.to_string(); + syn::parse_str(&s).unwrap() + }); + + let name = format_ident!(r#"{}"#, self.name); + + quote! { + #name <#(#generics),*> + } + } + None => format_ident!(r#"{}"#, self.name).to_token_stream(), + } + } + + pub fn to_type_path_token(&self) -> syn::TypePath { + let mut completed = self + .clone() + .remove_generics_bounds() + .to_name_ident() + .to_token_stream(); + + // add namespace + if let Some(namespace) = self.namespace.as_ref() { + let namespace_ident: syn::Path = + syn::parse_str(namespace).expect("Failed to parse namespace"); + completed = quote! { + #namespace_ident::#completed + } + } + + parse_quote! { + #completed + } + } + + pub fn to_type_token(&self) -> syn::Type { + let completed = self.to_type_path_token(); + + // add & or * or mut + let mut prefix = if self.is_ref { + quote! { & } + } else if self.is_ptr { + quote! { * } + } else { + quote! {} + }; + + if self.is_mut { + prefix = parse_quote! { #prefix mut }; + } + if self.is_dyn { + prefix = parse_quote! { #prefix dyn }; + } + + parse_quote! { + #prefix #completed + } + } +} + +impl NameComponents { + pub fn to_combined_ident(&self) -> TokenStream { + todo!(); + let mut completed = match self.name.split_once('`') { + Some((a, b)) => { + let ident_a = format_ident!(r#"{}"#, a); + let ident_b: syn::Lit = syn::parse_str(b).expect("Failed to parse number"); + + quote! { + #ident_a ^ #ident_b + } + } + None => format_ident!(r#"{}"#, self.name).to_token_stream(), + }; + + // add declaring types + if let Some(declaring_types) = self.declaring_types.as_ref() { + let declaring_types = declaring_types.iter().map(|g| format_ident!(r#"{}"#, g)); + + completed = quote! { + #(#declaring_types)/ * / #completed + } + } + + // add namespace + // if let Some(namespace_str) = self.namespace.as_ref() { + // let namespace: syn::punctuated::Punctuated = + // syn::parse_str(&namespace_str).expect("Failed to parse namespace"); + + // completed = quote! { + // #namespace.#completed + // } + // } + + // add generics + if let Some(generics_strings) = &self.generics { + let generics: Vec = generics_strings + .iter() + .map(|g| syn::parse_str(g).expect("Failed to parse generic")) + .collect(); + + completed = quote! { + #completed <#(#generics),*> + } + } + + completed + } +} + +impl From for RustNameComponents { + fn from(value: NameComponents) -> Self { + Self { + name: value.name, + namespace: value.namespace, + generics: value.generics.map(|g| { + g.into_iter() + .map(|g| RustGeneric { + name: g, + ..Default::default() + }) + .collect_vec() + }), + ..Default::default() + } + } +} + +impl From for RustNameComponents { + fn from(value: String) -> Self { + Self { + name: value, + ..Default::default() + } + } +} diff --git a/src/generate/rust/rust_name_resolver.rs b/src/generate/rust/rust_name_resolver.rs new file mode 100644 index 000000000..36a5215fc --- /dev/null +++ b/src/generate/rust/rust_name_resolver.rs @@ -0,0 +1,257 @@ +use brocolib::{global_metadata::Il2CppTypeDefinition, runtime_metadata::Il2CppTypeEnum}; +use itertools::Itertools; + +use crate::{ + data::type_resolver::{ResolvedType, ResolvedTypeData, TypeUsage}, + generate::{cs_type_tag::CsTypeTag, metadata::CordlMetadata}, +}; + +use super::{ + config::RustGenerationConfig, rust_context_collection::RustContextCollection, + rust_members::RustGeneric, rust_name_components::RustNameComponents, rust_type::RustType, +}; + +pub struct RustNameResolver<'a, 'b> { + pub cordl_metadata: &'a CordlMetadata<'b>, + pub collection: &'a RustContextCollection, + pub config: &'a RustGenerationConfig, +} + +impl<'b> RustNameResolver<'_, 'b> { + pub fn resolve_name( + &self, + declaring_cpp_type: &mut RustType, + ty: &ResolvedType, + type_usage: TypeUsage, + hard_include: bool, + ) -> RustNameComponents { + let metadata = self.cordl_metadata; + match &ty.data { + ResolvedTypeData::Array(array_type) => { + let generic = + self.resolve_name(declaring_cpp_type, array_type, type_usage, hard_include); + let generic_formatted = generic.combine_all(); + + // declaring_cpp_type.requirements.needs_array_include(); + + RustNameComponents { + name: "Il2CppArray".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + generics: Some(vec![generic_formatted.clone().into()]), + is_ptr: true, + is_mut: true, + + ..Default::default() + } + } + ResolvedTypeData::GenericInst(resolved_type, vec) => { + let type_def_name_components = + self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); + let generic_types_formatted = vec + .iter() + .map(|(r, inc)| { + self.resolve_name(declaring_cpp_type, r, type_usage, *inc && hard_include) + }) + .map(|n| n.combine_all()) + .map(RustGeneric::from) + .collect_vec(); + + // add generics to type def + RustNameComponents { + generics: Some(generic_types_formatted), + ..type_def_name_components + } + } + ResolvedTypeData::GenericArg(gen_param_idx, _arg_idx) => { + let generic_param = + &metadata.metadata.global_metadata.generic_parameters[*gen_param_idx]; + + generic_param.name(metadata.metadata).to_string().into() + } + ResolvedTypeData::GenericMethodArg(_method_index, gen_param_idx, _method_arg) => { + let generic_param = + &metadata.metadata.global_metadata.generic_parameters[*gen_param_idx]; + + // let arg = declaring_cpp_type + // .method_generic_instantiation_map + // .get(&method_index) + // .and_then(|v| v.get(method_arg as usize)); + + generic_param.name(metadata.metadata).to_string().into() + } + ResolvedTypeData::Ptr(resolved_type) => { + let generic_formatted = + self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); + // RustNameComponents { + // namespace: Some("cordl_internals".into()), + // generics: Some(vec![generic_formatted.combine_all().into()]), + // name: "Ptr".into(), + // ..Default::default() + // } + + // TODO: Ptr type + RustNameComponents { + name: "Il2CppObject".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + is_ptr: true, + is_mut: true, + + ..Default::default() + } + } + ResolvedTypeData::Type(resolved_tag) => { + self.get_type_from_tag(*resolved_tag, declaring_cpp_type, metadata) + } + ResolvedTypeData::Primitive(s) if *s == Il2CppTypeEnum::String => { + RustNameComponents { + name: "Il2CppString".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + is_mut: true, + is_ptr: true, + + ..Default::default() + } + // let tag = CsTypeTag::TypeDefinitionIndex(self.cordl_metadata.string_tdi); + // self.get_type_from_tag(tag, declaring_cpp_type, metadata) + } + ResolvedTypeData::Primitive(s) if *s == Il2CppTypeEnum::Object => { + // let tag = CsTypeTag::TypeDefinitionIndex(self.cordl_metadata.object_tdi); + // self.get_type_from_tag(tag, declaring_cpp_type, metadata) + RustNameComponents { + name: "Il2CppObject".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + is_mut: true, + is_ptr: true, + + ..Default::default() + } + } + ResolvedTypeData::Primitive(s) if *s == Il2CppTypeEnum::Void => RustNameComponents { + name: "Void".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + + ..Default::default() + }, + + ResolvedTypeData::Primitive(il2_cpp_type_enum) => { + let s = Self::primitive_to_rust_ty(il2_cpp_type_enum).to_string(); + RustNameComponents::from(s) + } + ResolvedTypeData::Blacklisted(cs_type_tag) => { + let td = &metadata.metadata.global_metadata.type_definitions[cs_type_tag.get_tdi()]; + + Self::wrapper_type_for_tdi(td) + } + ResolvedTypeData::ByRef(resolved_type) => { + let generic = + self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); + let generic_formatted = generic.combine_all(); + + // declaring_cpp_type.requirements.needs_byref_include(); + + RustNameComponents { + name: "ByRefMut".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + + generics: Some(vec![generic_formatted.clone().into()]), + ..Default::default() + } + } + ResolvedTypeData::ByRefConst(resolved_type) => { + let generic = + self.resolve_name(declaring_cpp_type, resolved_type, type_usage, hard_include); + let generic_formatted = generic.combine_all(); + + // declaring_cpp_type.requirements.needs_byref_const_include(); + + RustNameComponents { + name: "ByRef".into(), + namespace: Some("quest_hook::libil2cpp".to_string()), + + generics: Some(vec![generic_formatted.clone().into()]), + ..Default::default() + } + } + } + } + + fn get_type_from_tag( + &self, + resolved_tag: CsTypeTag, + declaring_cpp_type: &mut RustType, + metadata: &CordlMetadata<'b>, + ) -> RustNameComponents { + if resolved_tag == declaring_cpp_type.self_tag { + return declaring_cpp_type.rs_name_components.clone(); + } + + let resolved_context_root_tag = self.collection.get_context_root_tag(resolved_tag); + let self_context_root_tag = self + .collection + .get_context_root_tag(declaring_cpp_type.self_tag); + + let incl_context = self + .collection + .get_context(resolved_tag) + .unwrap_or_else(|| panic!("Unable to find type {resolved_tag:#?}")); + let incl_ty = self + .collection + .get_cpp_type(resolved_tag) + .unwrap_or_else(|| { + let td = + &metadata.metadata.global_metadata.type_definitions[resolved_tag.get_tdi()]; + + println!( + "ty {resolved_tag:#?} vs aliased {:#?}", + self.collection.alias_context.get(&resolved_tag) + ); + println!("{}", incl_context.fundamental_path.display()); + panic!( + "Unable to find type {resolved_tag:#?} {}", + td.full_name(metadata.metadata, true) + ); + }); + + let is_own_context = resolved_context_root_tag == self_context_root_tag; + if !is_own_context { + // declaring_cpp_type + // .requirements + // .add_module(&incl_context.get_module_path(self.config)); + } + + // add dependency + if incl_ty.self_tag != declaring_cpp_type.self_tag { + declaring_cpp_type + .requirements + .add_dependency(incl_ty.self_tag); + } + + incl_ty.rs_name_components.clone() + } + + fn wrapper_type_for_tdi(_td: &Il2CppTypeDefinition) -> RustNameComponents { + "Blacklisted".to_string().into() + } + + pub fn primitive_to_rust_ty(il2_cpp_type_enum: &Il2CppTypeEnum) -> &str { + match il2_cpp_type_enum { + Il2CppTypeEnum::I1 => "i8", + Il2CppTypeEnum::I2 => "i16", + Il2CppTypeEnum::I4 => "i32", + Il2CppTypeEnum::I8 => "i64", + Il2CppTypeEnum::U1 => "u8", + Il2CppTypeEnum::U2 => "u16", + Il2CppTypeEnum::U4 => "u32", + Il2CppTypeEnum::U8 => "u64", + + Il2CppTypeEnum::R4 => "f32", + Il2CppTypeEnum::R8 => "f64", + + Il2CppTypeEnum::Void => "()", + Il2CppTypeEnum::Boolean => "bool", + Il2CppTypeEnum::Char => "char", + + _ => panic!("Unsupported type {il2_cpp_type_enum:#?}"), + } + } +} diff --git a/src/generate/rust/rust_type.rs b/src/generate/rust/rust_type.rs new file mode 100644 index 000000000..a1136f253 --- /dev/null +++ b/src/generate/rust/rust_type.rs @@ -0,0 +1,1313 @@ +use std::collections::HashSet; + +use color_eyre::eyre::{Context, ContextCompat, Result}; +use itertools::Itertools; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::parse_quote; + +use crate::{ + data::{ + name_components::NameComponents, + type_resolver::{ResolvedType, TypeUsage}, + }, + generate::{ + cs_members::{CsConstructor, CsField, CsMethod, CsParam}, + cs_type::CsType, + cs_type_tag::{self, CsTypeTag}, + metadata::CordlMetadata, + offsets::SizeInfo, + type_extensions::{TypeDefinitionExtensions, TypeDefinitionIndexExtensions}, + writer::Writer, + }, +}; + +use super::{ + config::RustGenerationConfig, + rust_fields, + rust_members::{ + ConstRustField, RustFeature, RustField, RustFunction, RustGeneric, RustParam, + RustTraitImpl, Visibility, + }, + rust_name_components::RustNameComponents, + rust_name_resolver::RustNameResolver, +}; + +use std::io::Write; + +const PARENT_FIELD: &str = "__cordl_parent"; + +#[derive(Clone, Debug, Default)] +pub struct RustTypeRequirements { + required_modules: HashSet, + required_types: HashSet, +} + +impl RustTypeRequirements { + pub fn add_module(&mut self, module: &str) { + self.required_modules.insert(module.to_string()); + } + pub fn add_dependency(&mut self, cs_type_tag: CsTypeTag) { + self.required_types.insert(cs_type_tag); + } + + pub(crate) fn needs_object_include(&mut self) { + self.add_module("quest_hook::libil2cpp::Il2CppObject"); + } + + pub(crate) fn needs_array_include(&mut self) { + self.add_module("quest_hook::libil2cpp::Il2CppArray"); + } + + pub(crate) fn needs_string_include(&mut self) { + self.add_module("quest_hook::libil2cpp::Il2CppString"); + } + + pub(crate) fn needs_byref_include(&mut self) { + // todo!() + } + + pub(crate) fn needs_byref_const_include(&mut self) { + // todo!() + } + + pub(crate) fn get_modules(&self) -> &HashSet { + &self.required_modules + } + pub fn get_dependencies(&self) -> &HashSet { + &self.required_types + } +} + +#[derive(Clone)] +pub struct RustType { + // TODO: union + pub fields: Vec, + pub constants: Vec, + pub methods: Vec, + pub traits: Vec, + pub nested_types: Vec, + + pub is_value_type: bool, + pub is_enum_type: bool, + pub is_reference_type: bool, + pub is_interface: bool, + + pub self_tag: CsTypeTag, + pub self_feature: Option, + + pub parent: Option, + pub backing_type_enum: Option, + + pub generics: Option>, + pub cs_name_components: NameComponents, + pub rs_name_components: RustNameComponents, + pub(crate) prefix_comments: Vec, + + pub requirements: RustTypeRequirements, + pub packing: Option, + pub size_info: Option, + pub is_compiler_generated: bool, +} +impl RustType { + pub(crate) fn make_rust_type( + tag: CsTypeTag, + cs_type: &CsType, + config: &RustGenerationConfig, + ) -> Self { + let cs_name_components = &cs_type.cs_name_components; + + let generics = cs_type.generic_template.as_ref().map(|g| { + g.names + .iter() + .map(|(ty, s)| RustGeneric { + name: s.to_string(), + bounds: vec!["quest_hook::libil2cpp::Type".to_string()], + }) + .collect_vec() + }); + + let rs_name_components = RustNameComponents { + generics: generics.clone(), + name: config.name_rs(&cs_name_components.name), + namespace: Some( + config.namespace_rs(&cs_name_components.namespace.clone().unwrap_or_default()), + ), + is_ref: false, + is_dyn: false, + is_ptr: cs_type.is_reference_type, + is_mut: cs_type.is_reference_type, // value types don't need to be mutable + ..Default::default() + }; + + let feature_name = + config.feature_name(&cs_name_components.clone().remove_generics().combine_all()); + + RustType { + fields: Default::default(), + methods: Default::default(), + traits: Default::default(), + constants: Default::default(), + nested_types: Default::default(), + + is_value_type: cs_type.is_value_type, + is_enum_type: cs_type.is_enum_type, + is_reference_type: cs_type.is_reference_type, + is_interface: cs_type.is_interface, + parent: Default::default(), + backing_type_enum: Default::default(), + + requirements: RustTypeRequirements::default(), + self_feature: Some(RustFeature { name: feature_name }), + self_tag: tag, + generics, + + rs_name_components, + cs_name_components: cs_type.cs_name_components.clone(), + prefix_comments: vec![], + packing: cs_type.packing.map(|p| p as u32), + size_info: cs_type.size_info.clone(), + is_compiler_generated: cs_type.is_compiler_generated, + } + } + + pub fn fill( + &mut self, + cs_type: CsType, + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, + ) { + if cs_type.is_interface || cs_type.namespace() == "System" && cs_type.name() == "Object" { + self.make_object_parent(); + } else { + self.make_parent(cs_type.parent.as_ref(), name_resolver); + } + + self.make_nested_types(&cs_type.nested_types, name_resolver); + self.make_interfaces(&cs_type.interfaces, name_resolver, config); + + self.make_fields(&cs_type.fields, name_resolver, config); + + self.make_methods(&cs_type.methods, name_resolver, config); + + // add phantom markers + self.make_generics(); + + if self.is_reference_type { + self.make_ref_constructors(&cs_type.constructors, name_resolver, config); + } + + if self.is_interface { + self.methods.push(RustFunction { + name: format_ident!("from_object_mut"), + body: Some(parse_quote! { + unsafe{ (object_param as *mut Self) } + }), + generics: Default::default(), + is_mut: false, + is_ref: false, + is_self: false, + where_clause: None, + params: vec![RustParam { + name: format_ident!("object_param"), + param_type: parse_quote!(*mut quest_hook::libil2cpp::Il2CppObject), + }], + return_type: Some(parse_quote!(*mut Self)), + visibility: Visibility::Public, + }); + } + + // check if any method name matches more than once + let duplicated_methods = self + .methods + .iter() + .filter(|m| { + self.methods + .iter() + .filter(|m2| m2.is_self == m.is_self && m2.name == m.name) + .count() + > 1 + }) + .collect_vec(); + if !duplicated_methods.is_empty() { + panic!( + "Duplicate method names found! {} {}", + self.rs_name_components.combine_all(), + duplicated_methods.iter().map(|m| &m.name).join(", ") + ); + } + + if let Some(backing_type) = cs_type.enum_backing_type { + let backing_ty = RustNameResolver::primitive_to_rust_ty(&backing_type); + let resolved_ty = RustNameComponents { + name: backing_ty.to_owned(), + namespace: None, + generics: None, + is_ref: false, + is_ptr: false, + is_mut: false, + ..Default::default() + }; + + self.backing_type_enum = Some(resolved_ty); + } + } + + fn make_parent( + &mut self, + parent: Option<&ResolvedType>, + name_resolver: &RustNameResolver<'_, '_>, + ) { + if self.is_value_type || self.is_enum_type { + return; + } + + let Some(parent) = parent else { return }; + let parent = name_resolver + .resolve_name(self, parent, TypeUsage::TypeName, true) + .with_no_prefix(); + let parent_field = RustField { + name: format_ident!("{}", PARENT_FIELD), + field_type: parent.to_type_token(), + visibility: Visibility::Private, + offset: 0, + }; + + self.fields.insert(0, parent_field); + self.parent = Some(parent); + } + fn make_object_parent(&mut self) { + if self.is_value_type || self.is_enum_type { + return; + } + + let parent = RustNameComponents { + name: "Il2CppObject".to_string(), + namespace: Some("quest_hook::libil2cpp".to_string()), + generics: None, + is_mut: false, + is_ptr: false, + + is_ref: false, + is_dyn: false, + is_static_ref: false, + }; + let parent_field = RustField { + name: format_ident!("{}", PARENT_FIELD), + field_type: parent.to_type_token(), + visibility: Visibility::Private, + offset: 0, + }; + + self.fields.insert(0, parent_field); + self.parent = Some(parent); + } + + fn make_nested_types( + &mut self, + nested_types: &HashSet, + name_resolver: &RustNameResolver<'_, '_>, + ) { + let nested_types = nested_types + .iter() + .filter_map(|tag| name_resolver.collection.get_cpp_type(*tag)) + .sorted_by(|a, b| a.rs_name_components.name.cmp(&b.rs_name_components.name)) + .map(|rust_type| -> syn::ItemType { + let mut name = name_resolver + .config + .name_rs(&rust_type.cs_name_components.name); + + if name == "Target" { + // avoid conflict with Deref + name = "TargetType".to_string(); + } + + let name_ident = format_ident!("{name}",); + + let target = rust_type.rs_name_components.to_type_path_token(); + + let declaring_generic_count = + self.generics.as_ref().map(|g| g.len()).unwrap_or_default(); + let target_generics = rust_type.get_generics(declaring_generic_count); + + let visibility = match rust_type.is_interface { + false => Visibility::Public, + true => Visibility::Private, + } + .to_token_stream(); + + let feature = rust_type.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + parse_quote! { + #feature + #visibility type #name_ident #target_generics = #target; + } + }); + + self.nested_types = nested_types.collect(); + } + + fn make_fields( + &mut self, + fields: &[CsField], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, + ) { + let instance_fields = fields + .iter() + .filter(|f| f.instance && !f.is_const) + .cloned() + .collect_vec(); + + if self.is_value_type && !self.is_enum_type { + rust_fields::handle_valuetype_fields(self, &instance_fields, name_resolver, config); + } else { + rust_fields::handle_referencetype_fields(self, &instance_fields, name_resolver, config); + } + + rust_fields::handle_static_fields(self, fields, name_resolver, config); + rust_fields::handle_const_fields(self, fields, name_resolver, config); + + // for f in fields { + // if !f.instance || f.is_const { + // continue; + // } + // let field_type = name_resolver.resolve_name(self, &f.field_ty, TypeUsage::Field, true); + + // let rust_field = RustField { + // name: config.name_rs(&f.name), + // field_type: RustItem::NamedType(field_type.combine_all()), + // visibility: Visibility::Public, + // offset: f.offset.unwrap_or_default(), + // }; + // self.fields.push(rust_field); + // } + } + + fn make_generics(&mut self) { + let Some(generic) = &self.generics else { + return; + }; + + for g in generic.iter() { + let name = format_ident!("{}", g.name); + + self.fields.push(RustField { + name: format_ident!("__cordl_phantom_{name}"), + field_type: parse_quote!(std::marker::PhantomData<#name>), + visibility: Visibility::Private, + offset: 0, + }); + } + } + + fn make_interfaces( + &mut self, + interfaces: &[ResolvedType], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, + ) { + // TODO: Implement AsMut + for i in interfaces { + let self_ident = self.rs_name_components.to_type_path_token(); + + let generics = self.get_generics(0); + + let interface = name_resolver.resolve_name(self, i, TypeUsage::TypeName, true); + let interface_ident = interface.to_type_path_token(); + + let impl_data: Vec = match self.is_reference_type { + true => parse_quote! { + unsafe { std::mem::transmute(self) } + }, + false => parse_quote! { + // TODO: implement for value types + todo!() + }, + }; + let as_ref = RustTraitImpl { + name: interface.combine_all(), + impl_data: parse_quote! { + impl #generics AsRef<#interface_ident> for #self_ident { + fn as_ref(&self) -> & #interface_ident { + #(#impl_data)* + } + } + }, + }; + let as_mut = RustTraitImpl { + name: interface.combine_all(), + impl_data: parse_quote! { + impl #generics AsMut<#interface_ident> for #self_ident { + fn as_mut(&mut self) -> &mut #interface_ident { + #(#impl_data)* + } + } + }, + }; + self.traits.push(as_ref); + self.traits.push(as_mut); + } + } + + fn make_ref_constructors( + &mut self, + constructors: &[CsConstructor], + name_resolver: &RustNameResolver<'_, '_>, + config: &RustGenerationConfig, + ) { + let overloaded_method_data = constructors + .iter() + .map(|m| (m.name.clone(), m.parameters.as_slice())) + .collect_vec(); + + for (i, c) in constructors.iter().enumerate() { + let m_name_rs = self.make_overloaded_name( + &overloaded_method_data, + name_resolver, + ("New".to_string(), c.parameters.as_slice()), + i, + ); + + let params = c + .parameters + .iter() + .map(|p| self.make_parameter(p, name_resolver, config)) + .collect_vec(); + + let param_names = params.iter().map(|p| &p.name); + + let body: Vec = parse_quote! { + let __cordl_object: &mut Self = ::class().instantiate(); + + quest_hook::libil2cpp::ObjectType::as_object_mut(__cordl_object).invoke_void(".ctor", (#(#param_names),*))?; + + Ok(__cordl_object.into()) + }; + let generics = c + .template + .as_ref() + .map(|t| { + t.just_names() + .map(|g| RustGeneric { + name: g.clone(), + bounds: vec!["quest_hook::libil2cpp::Type".to_string()], + }) + .collect_vec() + }) + .unwrap_or_default(); + + let combined_generics = self + .generics + .clone() + .unwrap_or_default() + .into_iter() + .chain(generics.clone().into_iter()) + .map(|mut g| { + // TODO: Add these bounds on demand + let bounds = vec![ + "quest_hook::libil2cpp::Type".to_string(), + "quest_hook::libil2cpp::Argument".to_owned(), + "quest_hook::libil2cpp::Returned".to_owned(), + ]; + + g.bounds.extend(bounds); + g + }) + .map(|g| -> syn::GenericParam { g.to_token_stream() }) + .collect_vec(); + + let where_clause: syn::WhereClause = parse_quote! { + where #(#combined_generics),* + }; + + let rust_func = RustFunction { + name: format_ident!("{}", m_name_rs), + body: Some(body), + generics, + + is_mut: true, + is_ref: true, + is_self: false, + params, + where_clause: Some(where_clause), + + return_type: Some(parse_quote!( + quest_hook::libil2cpp::Result> + )), + visibility: (Visibility::Public), + }; + self.methods.push(rust_func); + } + } + + fn make_overloaded_name<'a>( + &mut self, + overload_methods: &Vec<(String, &'a [CsParam])>, + name_resolver: &RustNameResolver<'_, '_>, + (m_name, m_params): (String, &'a [CsParam]), + index: usize, + ) -> String { + let config = name_resolver.config; + + let mut m_name_rs = config.name_rs(&m_name); + if overload_methods.len() == 1 { + return m_name_rs; + } + + let param_types: Vec<_> = overload_methods + .iter() + .map(|(m_name, m_params)| { + m_params + .iter() + .map(|p| { + name_resolver + .resolve_name(self, &p.il2cpp_ty, TypeUsage::Parameter, true) + .name + }) + .map(|s| config.name_rs(&s)) + .collect::>() + }) + .collect(); + + let current_param_types = m_params + .iter() + .map(|p| { + name_resolver + .resolve_name(self, &p.il2cpp_ty, TypeUsage::Parameter, true) + .name + }) + .map(|s| config.name_rs(&s)) + .collect::>(); + + let differing_params: Vec<_> = current_param_types + .iter() + .enumerate() + .filter(|(i, ty)| param_types.iter().any(|types| types.get(*i) != Some(ty))) + .map(|(_, ty)| ty.clone()) + .collect(); + + if !differing_params.is_empty() { + m_name_rs = format!("{m_name_rs}_{}", differing_params.join("_")); + } else { + // fallback + m_name_rs = format!("{m_name_rs}_{}", current_param_types.join("_")); + } + + if m_name_rs.chars().last().is_some_and(|s| s.is_numeric()) { + m_name_rs = format!("{m_name_rs}_{index}"); + } else { + m_name_rs = format!("{m_name_rs}{index}"); + } + m_name_rs + } + + fn make_methods( + &mut self, + methods: &[CsMethod], + name_resolver: &RustNameResolver, + config: &RustGenerationConfig, + ) { + for (_, overload_methods) in methods + .iter() + .filter(|m| m.instance) + .into_group_map_by(|m| &m.name) + { + let overloaded_method_data = overload_methods + .iter() + .map(|m| (m.name.clone(), m.parameters.as_slice())) + .collect_vec(); + + for (i, m) in overload_methods.iter().enumerate() { + let m_name = &m.name; + + let m_name_rs = self.make_overloaded_name( + &overloaded_method_data, + name_resolver, + (m_name.clone(), m.parameters.as_slice()), + i, + ); + + let m_ret_ty = name_resolver + .resolve_name(self, &m.return_type, TypeUsage::ReturnType, true) + .wrap_by_gc(); + let m_ret_ty_ident = m_ret_ty.to_type_token(); + let m_result_ty: syn::Type = + parse_quote!(quest_hook::libil2cpp::Result<#m_ret_ty_ident>); + + let params = m + .parameters + .iter() + .map(|p| self.make_parameter(p, name_resolver, config)) + .collect_vec(); + + let param_names = params.iter().map(|p| &p.name); + + let body = self.make_method_body(m, m_name, param_names, m_ret_ty_ident); + + let generics = m + .template + .as_ref() + .map(|t| { + t.just_names() + .map(|g| -> RustGeneric { + RustGeneric { + name: g.clone(), + bounds: vec![], + } + }) + .collect_vec() + }) + .unwrap_or_default(); + + let combined_generics = self + .generics + .clone() + .unwrap_or_default() + .into_iter() + .chain(generics.clone().into_iter()) + .map(|mut g| { + // TODO: Add these bounds on demand + let bounds = vec![ + "quest_hook::libil2cpp::Type".to_string(), + "quest_hook::libil2cpp::Argument".to_owned(), + "quest_hook::libil2cpp::Returned".to_owned(), + ]; + + g.bounds.extend(bounds); + g + }) + .map(|g| -> syn::GenericParam { g.to_token_stream() }) + .collect_vec(); + + let where_clause: syn::WhereClause = parse_quote! { + where #(#combined_generics),* + }; + + let rust_func = RustFunction { + name: format_ident!("{m_name_rs}"), + body: Some(body), + generics, + is_mut: m.instance, + is_ref: m.instance, + is_self: m.instance, + params, + where_clause: Some(where_clause), + + return_type: Some(m_result_ty), + visibility: (Visibility::Public), + }; + self.methods.push(rust_func); + } + } + } + + fn make_method_body<'a>( + &self, + m: &CsMethod, + m_name: &String, + param_names: impl Iterator, + m_ret_ty: syn::Type, + ) -> Vec { + let is_value_type = self.is_value_type || self.is_enum_type; + + let invoke_call: Vec = match (m.instance, is_value_type) { + // instance, value type + (true, true) => parse_quote! { + + let __cordl_ret: #m_ret_ty = quest_hook::libil2cpp::ValueTypeExt::invoke(self, #m_name, ( #(#param_names),* ))?; + + Ok(__cordl_ret.into()) + }, + // instance, ref type + (true, false) => parse_quote! { + let __cordl_object: &mut quest_hook::libil2cpp::Il2CppObject = quest_hook::libil2cpp::ObjectType::as_object_mut(self); + + let __cordl_ret: #m_ret_ty = __cordl_object.invoke(#m_name, ( #(#param_names),* ))?; + + Ok(__cordl_ret.into()) + }, + // static + (false, _) => parse_quote! { + let __cordl_ret: #m_ret_ty = ::class().invoke(#m_name, ( #(#param_names),* ) )?; + + Ok(__cordl_ret.into()) + }, + }; + + parse_quote! { + #(#invoke_call)* + } + } + + fn make_parameter( + &mut self, + p: &CsParam, + name_resolver: &RustNameResolver<'_, '_>, + config: &RustGenerationConfig, + ) -> RustParam { + let p_ty = name_resolver + .resolve_name(self, &p.il2cpp_ty, TypeUsage::Field, true) + .wrap_by_gc(); + // let p_il2cpp_ty = p.il2cpp_ty.get_type(name_resolver.cordl_metadata); + + let name_rs = config.name_rs(&p.name); + RustParam { + name: format_ident!("{name_rs}"), + param_type: p_ty.to_type_token(), + // is_ref: p_il2cpp_ty.is_byref(), + // is_ptr: !p_il2cpp_ty.valuetype, + // is_mut: true, + } + } + + pub fn name(&self) -> &String { + &self.cs_name_components.name + } + + pub fn namespace(&self) -> Option<&str> { + self.cs_name_components.namespace.as_deref() + } + + pub fn rs_name(&self) -> &String { + &self.rs_name_components.name + } + pub fn rs_namespace(&self) -> &Option { + &self.rs_name_components.namespace + } + + pub(crate) fn write(&self, writer: &mut Writer, config: &RustGenerationConfig) -> Result<()> { + if self.is_value_type { + if self.is_enum_type { + self.write_enum_type(writer, config)?; + } else { + self.write_value_type(writer, config)?; + } + } + + if self.is_interface { + self.write_interface(writer, config)?; + } else if self.is_reference_type { + self.write_reference_type(writer, config)?; + } + + Ok(()) + } + + pub fn nested_fixup( + &mut self, + context_tag: &CsTypeTag, + cs_type: &CsType, + metadata: &CordlMetadata, + config: &RustGenerationConfig, + ) { + // Nested type unnesting fix + let Some(declaring_tag) = cs_type.declaring_ty.as_ref() else { + return; + }; + + let mut declaring_td = declaring_tag + .get_tdi() + .get_type_definition(metadata.metadata); + let mut declaring_name = declaring_td.get_name_components(metadata.metadata).name; + + while declaring_td.declaring_type_index != u32::MAX { + let declaring_ty = + &metadata.metadata_registration.types[declaring_td.declaring_type_index as usize]; + + let declaring_tag = + cs_type_tag::CsTypeTag::from_type_data(declaring_ty.data, metadata.metadata); + + declaring_td = declaring_tag + .get_tdi() + .get_type_definition(metadata.metadata); + + let name = declaring_td.get_name_components(metadata.metadata).name; + declaring_name = format!("{declaring_name}_{name}",); + } + + let context_td = context_tag.get_tdi().get_type_definition(metadata.metadata); + let declaring_namespace = context_td.namespace(metadata.metadata); + + let combined_name = format!("{}_{}", declaring_name, self.name()); + + self.rs_name_components.namespace = Some(config.namespace_rs(declaring_namespace)); + self.rs_name_components.name = config.name_rs(&combined_name); + } + pub fn enum_fixup(&mut self, cs_type: &CsType) { + if !cs_type.is_enum_type { + return; + } + self.rs_name_components.generics = None; + self.generics = None; + } + + fn write_reference_type( + &self, + writer: &mut Writer, + config: &RustGenerationConfig, + ) -> Result<()> { + let name_ident = self.rs_name_components.clone().to_name_ident(); + let path_ident = self.rs_name_components.to_type_path_token(); + + let generics = self.get_generics(0); + let generics_names = self.get_generics_names(0); + + let fields = self.fields.iter().map(|f| { + let f_name = format_ident!(r#"{}"#, f.name); + let f_ty = &f.field_type; + let f_visibility = match f.visibility { + Visibility::Public => quote! { pub }, + Visibility::PublicCrate => quote! { pub(crate) }, + Visibility::Private => quote! {}, + }; + + quote! { + #f_visibility #f_name: #f_ty + } + }); + + let cs_namespace = self + .cs_name_components + .namespace + .clone() + .unwrap_or_default(); + let cs_name_str = self + .cs_name_components + .clone() + .remove_namespace() + .remove_generics() + .combine_all(); + + let quest_hook_path: syn::Path = parse_quote!(quest_hook::libil2cpp); + let macro_invoke: syn::ItemMacro = parse_quote! { + #quest_hook_path::unsafe_impl_reference_type!(in #quest_hook_path for #path_ident => #cs_namespace.#cs_name_str #generics_names); + }; + + let feature = self.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + let mut tokens = quote! { + #feature + #[repr(C)] + #[derive(Debug)] + pub struct #name_ident { + #(#fields),* + } + + #feature + #macro_invoke + + }; + + if let Some(parent) = &self.parent { + let parent_name = parent.clone().to_type_path_token(); + let parent_field_ident = format_ident!(r#"{}"#, PARENT_FIELD); + + tokens.extend(quote! { + #feature + impl #generics std::ops::Deref for #path_ident { + type Target = #parent_name; + + fn deref(&self) -> &Self::Target { + unsafe {&self.#parent_field_ident} + } + } + + #feature + impl #generics std::ops::DerefMut for #path_ident { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe{ &mut self.#parent_field_ident } + } + } + + }); + } + + writer.write_pretty_tokens(tokens)?; + + self.write_impl(writer, config)?; + Ok(()) + } + + fn write_enum_type(&self, writer: &mut Writer, config: &RustGenerationConfig) -> Result<()> { + let fields = self.constants.iter().map(|f| -> syn::Variant { + let name = &f.name; + let val = &f.value; + + parse_quote! { + #name = #val + } + }); + let backing_type = self + .backing_type_enum + .as_ref() + .wrap_err("No enum backing type found!")? + .to_type_token(); + + let name_ident = self.rs_name_components.to_name_ident(); + let path_ident = self.rs_name_components.to_type_path_token(); + + let cs_namespace = self + .cs_name_components + .namespace + .clone() + .unwrap_or_default(); + let cs_name_str = self + .cs_name_components + .clone() + .remove_namespace() + .combine_all(); + + let quest_hook_path: syn::Path = parse_quote!(quest_hook::libil2cpp); + let macro_invoke: syn::ItemMacro = parse_quote! { + #quest_hook_path::unsafe_impl_value_type!(in #quest_hook_path for #path_ident => #cs_namespace.#cs_name_str); + }; + + let feature = self.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + let tokens = quote! { + #feature + #[repr(#backing_type)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum #name_ident { + #(#fields),* + } + + #feature + #macro_invoke + }; + + writer.write_pretty_tokens(tokens)?; + + // self.write_impl(writer, config)?; + + Ok(()) + } + + fn write_value_type(&self, writer: &mut Writer, config: &RustGenerationConfig) -> Result<()> { + let generics = self.get_generics(0); + let generic_names = self.get_generics_names(0); + + let name_ident = self.rs_name_components.clone().to_name_ident(); + let path_ident = self.rs_name_components.to_type_path_token(); + + let fields = self.fields.iter().map(|f| { + let f_name = format_ident!(r#"{}"#, f.name); + let f_ty = &f.field_type; + let f_visibility = match f.visibility { + Visibility::Public => quote! { pub }, + Visibility::PublicCrate => quote! { pub(crate) }, + Visibility::Private => quote! {}, + }; + + quote! { + #f_visibility #f_name: #f_ty + } + }); + + let cs_namespace = self + .cs_name_components + .namespace + .clone() + .unwrap_or_default(); + let cs_name_str = self + .cs_name_components + .clone() + .remove_namespace() + .combine_all(); + + let quest_hook_path: syn::Path = parse_quote!(quest_hook::libil2cpp); + let macro_invoke: syn::ItemMacro = parse_quote! { + #quest_hook_path::unsafe_impl_value_type!(in #quest_hook_path for #path_ident => #cs_namespace.#cs_name_str #generic_names); + }; + + let feature = self.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + let tokens = quote! { + #feature + #[repr(C)] + #[derive(Debug, Clone)] + pub struct #name_ident { + #(#fields),* + } + + #feature + #macro_invoke + + #feature + unsafe impl #generics #quest_hook_path::ThisArgument for #path_ident { + type Type = Self; + + fn matches(method: &#quest_hook_path::MethodInfo) -> bool { + ::matches_this_argument(method) + } + + fn invokable(&mut self) -> *mut std::ffi::c_void { + unsafe { #quest_hook_path::value_box(self) as *mut std::ffi::c_void } + } + } + }; + + writer.write_pretty_tokens(tokens)?; + + self.write_impl(writer, config)?; + + Ok(()) + } + + fn get_generics(&self, skip_amount: usize) -> Option { + self.generics + .as_ref() + .map(|g| { + g.iter() + .skip(skip_amount) + .map(|g| -> syn::GenericArgument { + let s = g.to_string(); + syn::parse_str(&s).unwrap() + }) + .collect_vec() + }) + .map(|g| -> syn::Generics { + parse_quote! { <#(#g),*> } + }) + } + fn get_generics_names(&self, skip_amount: usize) -> Option { + self.generics + .as_ref() + .map(|g| { + g.iter() + .skip(skip_amount) + .map(|g| -> syn::GenericArgument { syn::parse_str(&g.name).unwrap() }) + .collect_vec() + }) + .map(|g| -> syn::Generics { + parse_quote! { <#(#g),*> } + }) + } + + fn write_impl(&self, writer: &mut Writer, _config: &RustGenerationConfig) -> Result<()> { + let name_ident = self.rs_name_components.clone().to_name_ident(); + let path_ident = self.rs_name_components.clone().to_type_path_token(); + + let generics = self.get_generics(0); + + let const_fields = self + .constants + .iter() + .sorted_by(|a, b| a.name.cmp(&b.name)) + .map(|f| -> syn::ImplItemConst { + let name = &f.name; + let val = &f.value; + let f_ty = &f.field_type; + + parse_quote! { + pub const #name: #f_ty = #val; + } + }); + + let methods = self + .methods + .iter() + .sorted_by(|a, b| a.name.cmp(&b.name)) + .cloned() + .map(|mut f| { + f.body = f.body.or(Some(parse_quote! { + todo!() + })); + f + }) + .map(|f| f.to_token_stream()) + .map(|f| -> syn::ImplItemFn { parse_quote!(#f) }); + + let feature = self.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + let nested_types = &self + .nested_types + .iter() + .sorted_by(|a, b| a.ident.cmp(&b.ident)) + .collect_vec(); + + let other_impls = self + .traits + .iter() + .sorted_by(|a, b| a.name.cmp(&b.name)) + .map(|t| -> syn::ItemImpl { + let impl_data = &t.impl_data; + + parse_quote! { + #feature + #impl_data + } + }) + .collect_vec(); + + let impl_tokens: syn::ItemImpl = parse_quote! { + impl #generics #path_ident { + #(#const_fields)* + #(#nested_types)* + #(#methods)* + } + }; + + let impl_object_tokens: Option = self.parent.as_ref().map(|_| -> syn::ItemImpl { + let parent_field_ident = format_ident!(r#"{}"#, PARENT_FIELD); + + parse_quote! { + #feature + impl #generics quest_hook::libil2cpp::ObjectType for #path_ident { + fn as_object(&self) -> &quest_hook::libil2cpp::Il2CppObject { + quest_hook::libil2cpp::ObjectType::as_object(&self.#parent_field_ident) + } + + fn as_object_mut(&mut self) -> &mut quest_hook::libil2cpp::Il2CppObject { + quest_hook::libil2cpp::ObjectType::as_object_mut(&mut self.#parent_field_ident) + } + } + } + }); + + let tokens = quote! { + #feature + #impl_tokens + + + #impl_object_tokens + + #(#other_impls)* + }; + + writer.write_pretty_tokens(tokens.to_token_stream())?; + Ok(()) + } + + fn write_interface(&self, writer: &mut Writer, config: &RustGenerationConfig) -> Result<()> { + let name_ident = self.rs_name_components.clone().to_name_ident(); + let path_ident = self.rs_name_components.to_type_path_token(); + + let generics = self.get_generics(0); + let generics_names = self.get_generics_names(0); + + let fields = self.fields.iter().map(|f| { + let f_name = format_ident!(r#"{}"#, f.name); + let f_ty = &f.field_type; + let f_visibility = match f.visibility { + Visibility::Public => quote! { pub }, + Visibility::PublicCrate => quote! { pub(crate) }, + Visibility::Private => quote! {}, + }; + + quote! { + #f_visibility #f_name: #f_ty + } + }); + + let cs_namespace = self + .cs_name_components + .namespace + .clone() + .unwrap_or_default(); + let cs_name_str = self + .cs_name_components + .clone() + .remove_namespace() + .remove_generics() + .combine_all(); + + let quest_hook_path: syn::Path = parse_quote!(quest_hook::libil2cpp); + let macro_invoke: syn::ItemMacro = parse_quote! { + #quest_hook_path::unsafe_impl_reference_type!(in #quest_hook_path for #path_ident => #cs_namespace.#cs_name_str #generics_names); + }; + + let feature = self.self_feature.as_ref().map(|f| { + let name = &f.name; + quote! { + #[cfg(feature = #name)] + } + }); + + let mut tokens = quote! { + #feature + #[repr(C)] + #[derive(Debug)] + pub struct #name_ident { + #(#fields),* + } + + #feature + #macro_invoke + + }; + + if let Some(parent) = &self.parent { + let parent_name = parent.clone().to_type_path_token(); + let parent_field_ident = format_ident!(r#"{}"#, PARENT_FIELD); + + tokens.extend(quote! { + #feature + impl #generics std::ops::Deref for #path_ident { + type Target = #parent_name; + + fn deref(&self) -> &Self::Target { + unsafe {&self.#parent_field_ident} + } + } + + #feature + impl #generics std::ops::DerefMut for #path_ident { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe{ &mut self.#parent_field_ident } + } + } + + }); + } + + writer.write_pretty_tokens(tokens)?; + + self.write_impl(writer, config)?; + + Ok(()) + } + + pub(crate) fn classof_name(&self) -> String { + format!( + "<{} as quest_hook::libil2cpp::Type>::class()", + self.rs_name() + ) + } +} + +impl Writer { + pub(crate) fn write_pretty_tokens(&mut self, tokens: TokenStream) -> Result<()> { + let syntax_tree = syn::parse2(tokens.clone()).with_context(|| format!("{tokens}"))?; + let formatted = prettyplease::unparse(&syntax_tree); + + self.stream.write_all(formatted.as_bytes())?; + Ok(()) + } +} diff --git a/src/generate/type_extensions.rs b/src/generate/type_extensions.rs index 7f9c8e32e..bd5bff9b0 100644 --- a/src/generate/type_extensions.rs +++ b/src/generate/type_extensions.rs @@ -169,7 +169,8 @@ impl TypeExtentions for Il2CppType { pub trait TypeDefinitionExtensions { fn is_value_type(&self) -> bool; fn is_enum_type(&self) -> bool; - fn is_compiler_generated(&self) -> bool; + fn is_special_name(&self) -> bool; + fn is_compiler_generated(&self, metadata: &Metadata) -> bool; fn is_interface(&self) -> bool; fn is_explicit_layout(&self) -> bool; fn is_assignable_to(&self, other_td: &Il2CppTypeDefinition, metadata: &Metadata) -> bool; @@ -191,7 +192,15 @@ impl TypeDefinitionExtensions for Il2CppTypeDefinition { self.bitfield & 2 != 0 } - fn is_compiler_generated(&self) -> bool { + fn is_compiler_generated(&self, metadata: &Metadata) -> bool { + self.is_special_name() + || (self.name(metadata).starts_with('<') && self.name(metadata).contains(">d__")) + || self.name(metadata).contains("<>c") + || self + .name(metadata) + .starts_with("") + } + fn is_special_name(&self) -> bool { self.flags & TYPE_ATTRIBUTE_SPECIAL_NAME != 0 } @@ -228,14 +237,6 @@ impl TypeDefinitionExtensions for Il2CppTypeDefinition { return true; } - if self.get_name_components(metadata).name == "Object" { - println!( - "{:?} {:?}", - self.get_name_components(metadata), - parent_ty.ty - ); - } - // if object, clearly this does not inherit `other_td` if !matches!( parent_ty.ty, @@ -278,7 +279,13 @@ impl TypeDefinitionExtensions for Il2CppTypeDefinition { } fn get_name_components(&self, metadata: &Metadata) -> NameComponents { - let namespace = self.namespace(metadata); + let namespace_str = self.namespace(metadata); + let namespace = if namespace_str.is_empty() { + None + } else { + Some(namespace_str.to_string()) + }; + let name = self.name(metadata); let generics = match self.generic_container_index.is_valid() { @@ -296,7 +303,6 @@ impl TypeDefinitionExtensions for Il2CppTypeDefinition { let _ty = &metadata.runtime_metadata.metadata_registration.types[self.byval_type_index as usize]; - let is_pointer = self.is_reference_type(metadata); match self.declaring_type_index != u32::MAX { true => { @@ -319,15 +325,13 @@ impl TypeDefinitionExtensions for Il2CppTypeDefinition { name: name.to_string(), declaring_types: Some(declaring_types), generics, - is_pointer, } } false => NameComponents { - namespace: Some(namespace.to_string()), + namespace, name: name.to_string(), declaring_types: None, generics, - is_pointer, }, } } diff --git a/src/generate/writer.rs b/src/generate/writer.rs index 0ee5a3ff3..0599debbe 100644 --- a/src/generate/writer.rs +++ b/src/generate/writer.rs @@ -1,7 +1,10 @@ -use std::{fs::File, io::Write}; +use std::{ + fs::File, + io::{BufWriter, Write}, +}; pub struct Writer { - pub stream: File, + pub stream: BufWriter, pub indent: u16, pub newline: bool, } @@ -37,7 +40,7 @@ impl Write for Writer { } } -pub trait Writable: std::fmt::Debug { +pub trait Writable { fn write(&self, writer: &mut Writer) -> color_eyre::Result<()>; } diff --git a/src/main.rs b/src/main.rs index 49fc225db..944cbf43d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,16 +7,16 @@ #![feature(exit_status_error)] #![feature(iterator_try_collect)] -#[cfg(feature="il2cpp_v31")] +#[cfg(feature = "il2cpp_v31")] extern crate brocolib_il2cpp_v31 as brocolib; -#[cfg(feature="il2cpp_v29")] +#[cfg(feature = "il2cpp_v29")] extern crate brocolib_il2cpp_v29 as brocolib; use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; use byteorder::LittleEndian; use color_eyre::eyre::Context; -use generate::{cpp, json, metadata::CordlMetadata}; +use generate::metadata::CordlMetadata; use itertools::Itertools; extern crate pretty_env_logger; @@ -40,9 +40,14 @@ mod helpers; #[derive(Clone, Copy, Debug, clap::ValueEnum)] enum TargetLang { + #[cfg(feature = "cpp")] Cpp, + #[cfg(feature = "json")] SingleJSON, + #[cfg(feature = "json")] MultiJSON, + #[cfg(feature = "rust")] + Rust, } #[derive(Parser)] @@ -106,13 +111,21 @@ fn main() -> color_eyre::Result<()> { })?; let il2cpp_metadata = brocolib::Metadata::parse(&global_metadata_data, &elf_data)?; - let unity_object_tdi_idx = il2cpp_metadata - .global_metadata - .type_definitions - .as_vec() - .iter() - .position(|v| v.full_name(&il2cpp_metadata, false) == "UnityEngine.Object") - .unwrap(); + let get_tdi = |full_name: &str| { + let tdi = il2cpp_metadata + .global_metadata + .type_definitions + .as_vec() + .iter() + .position(|t| t.full_name(&il2cpp_metadata, false) == full_name) + .unwrap_or_else(|| panic!("Unable to find TDI for {full_name}")); + + TypeDefinitionIndex::new(tdi as u32) + }; + + let unity_object_tdi_idx = get_tdi("UnityEngine.Object"); + let object_tdi_idx = get_tdi("System.Object"); + let str_tdi_idx = get_tdi("System.String"); let mut metadata = CordlMetadata { metadata: &il2cpp_metadata, @@ -121,7 +134,11 @@ fn main() -> color_eyre::Result<()> { method_calculations: Default::default(), parent_to_child_map: Default::default(), child_to_parent_map: Default::default(), - unity_object_tdi: TypeDefinitionIndex::new(unity_object_tdi_idx as u32), + + unity_object_tdi: unity_object_tdi_idx, + object_tdi: object_tdi_idx, + string_tdi: str_tdi_idx, + name_to_tdi: Default::default(), blacklisted_types: Default::default(), pointer_size: generate::metadata::PointerSize::Bytes8, @@ -383,18 +400,41 @@ fn main() -> color_eyre::Result<()> { } match cli.target { - TargetLang::Cpp => cpp::cpp_main::run_cpp(cs_context_collection, &metadata), + #[cfg(feature = "cpp")] + TargetLang::Cpp => { + use generate::cpp; + + cpp::cpp_main::run_cpp(cs_context_collection, &metadata, cli.format)?; + Ok(()) + } + #[cfg(feature = "json")] TargetLang::SingleJSON => { + use generate::json; + let json = Path::new("./json"); println!("Writing json file {json:?}"); - json::make_json(&metadata, &cs_context_collection, json) + json::make_json(&metadata, &cs_context_collection, json)?; + Ok(()) } + #[cfg(feature = "json")] TargetLang::MultiJSON => { + use generate::json; + let json_folder = Path::new("./multi_json"); println!("Writing json file {json_folder:?}"); - json::make_json_folder(&metadata, &cs_context_collection, json_folder) + json::make_json_folder(&metadata, &cs_context_collection, json_folder)?; + Ok(()) + } + + #[cfg(feature = "rust")] + TargetLang::Rust => { + use generate::rust; + rust::rust_main::run_rust(cs_context_collection, &metadata)?; + + Ok(()) } + _ => color_eyre::Result::<()>::Ok(()), }?; Ok(())