diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..b7768ef2f --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: LLVM + +#Stolen from scad +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IncludeBlocks: Preserve +PointerAlignment: Left +UseTab: Never +Cpp11BracedListStyle: false +QualifierAlignment: Right +SortIncludes: Never +ColumnLimit: 200 diff --git a/Cargo.lock b/Cargo.lock index cd4c29d0a..8a004c905 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -18,22 +18,68 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "anyhow" -version = "1.0.71" +name = "aho-corasick" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] [[package]] -name = "atty" -version = "0.2.14" +name = "anstream" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", ] +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "autocfg" version = "1.1.0" @@ -42,16 +88,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.6.2", - "object", + "miniz_oxide", + "object 0.32.1", "rustc-demangle", ] @@ -103,7 +149,7 @@ version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", @@ -144,38 +190,45 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "brocolib" version = "0.1.0" -source = "git+https://github.com/StackDoubleFlow/brocolib.git?branch=il2cpp_v29#58e1448cf99173d6d4710988972e8909609c3716" +source = "git+https://github.com/Fernthedev/brocolib.git?branch=fix/type-full-name-generics#d643d9d20722dcd57e4d90ced831909bc147375f" dependencies = [ "bad64", "binde", "binread", "byteorder", - "object", + "object 0.30.4", "thiserror", ] [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -206,42 +259,43 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ - "atty", - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "indexmap", - "once_cell", "strsim", - "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.25" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.41", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "color-eyre" @@ -260,9 +314,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -270,6 +324,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "cordl" version = "0.1.0" @@ -280,8 +340,17 @@ dependencies = [ "bytes", "clap", "color-eyre", + "filesize", + "fs_extra", + "include_dir", "indent_write", "itertools", + "log", + "pathdiff", + "pretty_env_logger", + "rayon", + "topological-sort", + "walkdir", ] [[package]] @@ -293,6 +362,38 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if", +] + [[package]] name = "cstr_core" version = "0.2.6" @@ -311,35 +412,73 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "env_logger" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ "indenter", "once_cell", ] +[[package]] +name = "filesize" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d741e2415d4e2e5bd1c1d00409d1a8865a57892c2d689b504365655d237d43" +dependencies = [ + "winapi", +] + [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", - "miniz_oxide 0.7.1", + "miniz_oxide", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "gimli" -version = "0.27.2" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -347,12 +486,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.1" @@ -361,11 +494,33 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" dependencies = [ - "libc", + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", ] [[package]] @@ -381,29 +536,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] -name = "indexmap" -version = "1.9.3" +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "autocfg", - "hashbrown", + "hermit-abi", + "rustix", + "windows-sys 0.48.0", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -422,9 +578,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" @@ -437,26 +593,38 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.5.0" +name = "linux-raw-sys" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "miniz_oxide" -version = "0.6.2" +name = "memchr" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ - "adler", + "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -489,34 +657,37 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "flate2", "memchr", ] [[package]] -name = "once_cell" -version = "1.17.2" +name = "object" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] [[package]] -name = "os_str_bytes" -version = "6.5.0" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "owo-colors" @@ -524,6 +695,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -532,66 +709,86 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "pretty_env_logger" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "env_logger", + "log", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro2" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", - "quote", - "version_check", ] [[package]] -name = "proc-macro2" -version = "1.0.59" +name = "rayon" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ - "unicode-ident", + "either", + "rayon-core", ] [[package]] -name = "quote" -version = "1.0.28" +name = "rayon-core" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "proc-macro2", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "regex" -version = "1.8.3" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -605,26 +802,48 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "static_assertions" @@ -651,9 +870,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -662,37 +881,31 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.41", ] [[package]] @@ -705,22 +918,27 @@ dependencies = [ "once_cell", ] +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -738,9 +956,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "sharded-slab", "thread_local", @@ -749,9 +967,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "valuable" @@ -760,10 +984,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] -name = "version_check" -version = "0.9.4" +name = "walkdir" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "winapi" @@ -783,9 +1011,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -795,3 +1023,135 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 54c012c57..9bdad35cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,30 @@ -[package] -name = "cordl" -author = "Sc2ad" -version = "0.1.0" -edition = "2021" - -[dependencies] -brocolib = { git = "https://github.com/StackDoubleFlow/brocolib.git", branch = "il2cpp_v29" } -clap = { version = "3.1.8", features = ["derive"] } -anyhow = { version = "*" } -indent_write = { version = "*" } -color-eyre = "0.6" -itertools = "0.10" -bytes = "*" -byteorder = "1" \ No newline at end of file +[package] +name = "cordl" +author = "Sc2ad" +version = "0.1.0" +edition = "2021" + +[dependencies] +brocolib = { git = "https://github.com/Fernthedev/brocolib.git", branch = "fix/type-full-name-generics" } +clap = { version = "4", features = ["derive"] } +anyhow = { version = "*" } +indent_write = { version = "*" } +color-eyre = "0.6" +itertools = "0.11" +bytes = "*" +byteorder = "1" +topological-sort = "0.2" +fs_extra = "*" +include_dir= "*" + +# utils +pathdiff = "0.2" +walkdir = "2" +log = "0.4.20" +pretty_env_logger = "0.5.0" +rayon = "1.8.0" +filesize = "0.2.0" + +[profiles.release] +opt-level = 3 +lto = true diff --git a/README.md b/README.md index 9f8dfe7b8..7c49082aa 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # cordl + +Requires Ubuntu 22.04 (lib6 requirement) + +While this may generate headers for Android IL2CPP (or any future supported platforms), you are highly suggested to run this under WSL for convenience. + +`libclang` is required for building the binary. \ No newline at end of file diff --git a/cordl_internals/box-utils.hpp b/cordl_internals/box-utils.hpp new file mode 100644 index 000000000..084d54ac1 --- /dev/null +++ b/cordl_internals/box-utils.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "config.hpp" +#include "concepts.hpp" +#include "internal.hpp" +#include "beatsaber-hook/shared/utils/base-wrapper-type.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-type-check.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-functions.hpp" + +namespace { +namespace cordl_internals { +#pragma region boxing + template + CORDL_HIDDEN Il2CppObject* Box(T); + + template + CORDL_HIDDEN Il2CppObject* Box(T*); + + template<> + CORDL_HIDDEN constexpr Il2CppObject* Box(Il2CppObject* t) { return t; } + + template + requires(!std::is_pointer_v && !std::is_base_of_v) + CORDL_HIDDEN Il2CppObject* Box(T* t) { return il2cpp_functions::value_box(classof(T), t); } + + template + requires(!std::is_base_of_v) + CORDL_HIDDEN Il2CppObject* Box(T t) { + return il2cpp_functions::value_box(classof(T), t.convert()); + } + + template + requires(!std::is_base_of_v) + CORDL_HIDDEN Il2CppObject* Box(T* t) { + return il2cpp_functions::value_box(classof(T), t->convert()); + } +#pragma endregion // boxing + +#pragma region unboxing + template + CORDL_HIDDEN T Unbox(Il2CppObject* t) { + return *static_cast(il2cpp_functions::object_unbox(t)); + } + + template<::il2cpp_utils::il2cpp_reference_type_wrapper T> + CORDL_HIDDEN T Unbox(Il2CppObject* t) { return T(t); } + + template<::il2cpp_utils::il2cpp_reference_type_pointer T> + CORDL_HIDDEN T Unbox(Il2CppObject* t) { return reinterpret_cast(t); } +#pragma endregion // unboxing + +} +} // end anonymous namespace diff --git a/cordl_internals/concepts.hpp b/cordl_internals/concepts.hpp new file mode 100644 index 000000000..de922634f --- /dev/null +++ b/cordl_internals/concepts.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include "beatsaber-hook/shared/utils/type-concepts.hpp" +#include "beatsaber-hook/shared/utils/size-concepts.hpp" + +namespace { +namespace cordl_internals { + template + concept convertible_to = std::is_convertible_v; + + template + concept is_or_is_backed_by = + std::is_same_v || (requires { + typename T::__CORDL_BACKING_ENUM_TYPE; + } && std::is_same_v); + + template + concept il2cpp_convertible = requires(T const& t) { + {t.convert()} -> convertible_to; + }; + +#pragma region offset check + /// @brief struct to check validity of an offset, since the requires clause makes it so only valid structs for this exist, we get nice errors + /// @tparam instance_sz the size of the instance + /// @tparam offset the offset of the field + /// @tparam value_sz the size of the field + template + requires(offset <= instance_sz && (offset + value_sz) <= instance_sz) + struct offset_check { + static constexpr bool value = true; + }; + + /// @brief shorthand to offset_check<...>::value + /// @tparam instance_sz the size of the instance + /// @tparam offset the offset of the field + /// @tparam value_sz the size of the field + template + constexpr bool offset_check_v = offset_check::value; + + // if you compile with the define COMPILE_TIME_OFFSET_CHECKS cordl will evaluate each field access to check whether you are going out of bounds for the field access, and if so it will not compile + // this shouldn't happen ever, but it helps as a sanity check. can be disabled to save on compile time + #ifdef COMPILE_TIME_OFFSET_CHECKS + #define OFFSET_CHECK(instance_size, offset, value_size, message) static_assert(::cordl_internals::offset_check_v, message) + #else + #define OFFSET_CHECK(instance_size, offset, value_size, message) + #endif + + // if you compile with the define COMPILE_TIME_SIZE_CHECKS cordl will evaluate all sizes of objects to see whether they match what il2cpp says they should be + // this should always be fine, but it helps as a sanity check. can be disabled to save on compile time + #ifdef COMPILE_TIME_SIZE_CHECKS + #define SIZE_CHECK(t, message) static_assert(il2cpp_safe(t), message) + #else + #define SIZE_CHECK(t, message) + #endif +#pragma endregion // offset check +} +} // end anonymous namespace diff --git a/cordl_internals/config.hpp b/cordl_internals/config.hpp new file mode 100644 index 000000000..4ad1aaeed --- /dev/null +++ b/cordl_internals/config.hpp @@ -0,0 +1,44 @@ +#pragma once + +#ifndef CORDL_ALWAYS_INLINE +// always inline attribute +#define CORDL_ALWAYS_INLINE __attribute__((alwaysinline)) +#endif +#ifndef CORDL_HIDDEN +// hidden attribute +#define CORDL_HIDDEN __attribute__((visibility("hidden"))) +#endif + +#if defined(__cpp_modules) && CORDL_COMPILE_MODULES +#define CORDL_MODULE_EXPORT_STRUCT export +#define CORDL_MODULE_EXPORT(m) export module m; +#define CORDL_MODULE_INIT module; + +#else + +#define CORDL_MODULE_EXPORT_STRUCT +#define CORDL_MODULE_EXPORT(m) +#define CORDL_MODULE_INIT + +#endif + + +#ifndef CORDL_METHOD +// attributes for methods +#define CORDL_METHOD CORDL_HIDDEN CORDL_ALWAYS_INLINE CORDL_MODULE_EXPORT_STRUCT +#endif + +#ifndef CORDL_TYPE +// attributes for types +#define CORDL_TYPE CORDL_HIDDEN +#endif + +#ifndef CORDL_FIELD +// attributes for fields +#define CORDL_FIELD CORDL_HIDDEN +#endif +#ifndef CORDL_PROP +// attributes for properties +#define CORDL_PROP CORDL_HIDDEN +#endif + diff --git a/cordl_internals/cordl_internals.hpp b/cordl_internals/cordl_internals.hpp new file mode 100644 index 000000000..449ab51e9 --- /dev/null +++ b/cordl_internals/cordl_internals.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "config.hpp" +#include "size-utils.hpp" +#include "ptr-utils.hpp" +#include "method-utils.hpp" +#include "field-utils.hpp" + +#include "beatsaber-hook/shared/utils/byref.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils-methods.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils-properties.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils-fields.hpp" +#include "beatsaber-hook/shared/utils/utils.h" +#include "beatsaber-hook/shared/utils/typedefs.h" + +#include "concepts.hpp" +#include "box-utils.hpp" + +// TODO: Implement +template +using ByRefConst = ::ByRef; + +template struct ArrayW; +struct Il2CppObject; + +namespace { +namespace cordl_internals { + // Base type for interfaces, as interfaces will wrap instances too (autoboxed VTs as well) + struct InterfaceW : public ::bs_hook::Il2CppWrapperType { + explicit constexpr InterfaceW(void* o) noexcept : ::bs_hook::Il2CppWrapperType(o) {} + + constexpr static bool __IL2CPP_VALUE_TYPE = false; + + // TODO: operator to safely typecast to types it may be implemented on? maybe better as an operator on whatever inherits this... + // something that has a requires(std::is_convertible_v)... conversion can always be forced by using .convert() anyway + }; +} +} diff --git a/cordl_internals/exceptions.hpp b/cordl_internals/exceptions.hpp new file mode 100644 index 000000000..900c2c2ff --- /dev/null +++ b/cordl_internals/exceptions.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "beatsaber-hook/shared/utils/il2cpp-utils-exceptions.hpp" + +namespace { +namespace cordl_internals { + struct FieldException : public ::il2cpp_utils::exceptions::StackTraceException { + using StackTraceException::StackTraceException; + }; + + struct NullException : public ::il2cpp_utils::exceptions::StackTraceException { + using StackTraceException::StackTraceException; + }; +} +} // end anonymous namespace \ No newline at end of file diff --git a/cordl_internals/field-utils.hpp b/cordl_internals/field-utils.hpp new file mode 100644 index 000000000..f37d0b02c --- /dev/null +++ b/cordl_internals/field-utils.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "config.hpp" +#include "concepts.hpp" +#include "internal.hpp" +#include "exceptions.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-utils-fields.hpp" + +#include +#include + +namespace UnityEngine { + class Object; +} + +namespace { +namespace cordl_internals { + + /// @brief method to find a field info in a klass + /// @tparam name field name + /// @tparam klass_resolver method to get the Il2CppClass* on which to get the klass + template + CORDL_HIDDEN FieldInfo* FindField() { + static auto* klass = klass_resolver(); + if (!klass) + throw NullException(std::string("Class for static field with name: ") + + name.data.data() + " is null!"); + static auto* field = ::il2cpp_utils::FindField(klass, name); + if (!field) + throw FieldException(std::string("Could not set static field with name: ") + + name.data.data()); + return field; + } + +#pragma region static field setters + + /// @brief template for setting a static field on a class + /// @tparam T field type + /// @tparam name field name + /// @tparam klass_resolver method to get the Il2CppClass* on which the field resides + template + CORDL_HIDDEN void setStaticField(T&& v); + + /// @brief method to set a field that's a reference type + template<::il2cpp_utils::il2cpp_reference_type T, internal::NTTPString name, auto klass_resolver> + CORDL_HIDDEN void setStaticField(T&& v) { + static auto* field = FindField(); + auto value = il2cpp_utils::il2cpp_reference_type_value(std::forward(v)); + ::il2cpp_functions::field_static_set_value(field, value); + } + + /// @brief method to set a field that's a value type + template<::il2cpp_utils::il2cpp_value_type T, internal::NTTPString name, auto klass_resolver> + CORDL_HIDDEN void setStaticField(T&& v) { + static auto* field = FindField(); + ::il2cpp_functions::field_static_set_value(field, static_cast(&v)); + } + + /// @brief method to set a field that's a trivial type + template + CORDL_HIDDEN void setStaticField(T&& v) { + static auto* field = FindField(); + ::il2cpp_functions::field_static_set_value( + field, const_cast(static_cast(&v))); + } + +#pragma endregion // static field setters + +#pragma region static field getters + + /// @brief template for getting a static field on a class + /// @tparam T field type + /// @tparam name field name + /// @tparam klass_resolver method to get the Il2CppClass* on which the field resides + template + [[nodiscard]] CORDL_HIDDEN T getStaticField(); + + /// @brief method to set a field that's a reference type + template <::il2cpp_utils::il2cpp_reference_type T, internal::NTTPString name, auto klass_resolver> + [[nodiscard]] CORDL_HIDDEN T getStaticField() { + static auto* field = FindField(); + void* val{}; + ::il2cpp_functions::field_static_get_value(field, &val); + + if constexpr (il2cpp_utils::il2cpp_reference_type_pointer) { + return static_cast(val); + } else if constexpr (il2cpp_utils::il2cpp_reference_type_wrapper) { + return T(val); + } else { + return {}; + } + } + + /// @brief method to set a field that's a trivial type + template + [[nodiscard]] CORDL_HIDDEN T getStaticField() { + static auto* field = FindField(); + T val{}; + ::il2cpp_functions::field_static_get_value(field, static_cast(&val)); + return val; + } +#pragma endregion // static field getters +} +} // end anonymous namespace diff --git a/cordl_internals/internal.hpp b/cordl_internals/internal.hpp new file mode 100644 index 000000000..9ed809f9c --- /dev/null +++ b/cordl_internals/internal.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "config.hpp" +#include +#include +#include +#include + +namespace UnityEngine { + class Object; +} + +namespace { +namespace cordl_internals { + namespace internal { + template struct NTTPString { + constexpr NTTPString(char const (&n)[sz]) : data{} { + std::copy_n(n, sz, data.begin()); + } + std::array data; + constexpr operator std::string_view() const { + return {data.data(), sz}; + } + }; + } + + /// @brief gets an offset from a given pointer + template + CORDL_HIDDEN constexpr inline void** getAtOffset(void* instance) { + return static_cast(static_cast(static_cast(instance) + offset)); + } + + /// @brief gets an offset from a given pointer + template + CORDL_HIDDEN constexpr inline const void* const* getAtOffset(const void* instance) { + return static_cast(static_cast(static_cast(instance) + offset)); + } + + /// @brief reads the cachedptr on the given unity object instance + template + requires(std::is_convertible_v) + CORDL_HIDDEN inline constexpr void* read_cachedptr(T instance) { + return *static_cast(getAtOffset<0x10>(static_cast(instance))); + } + + // if you compile with the define RUNTIME_FIELD_NULL_CHECKS at runtime every field access will be null checked for you, and a c++ exception will be thrown if the instance is null. + // in case of a unity object, the m_CachedPtr is also checked. Since this can incur some overhead you can also just not define RUNTIME_FIELD_NULL_CHECKS to save performance + #ifdef CORDL_RUNTIME_FIELD_NULL_CHECKS + #define CORDL_FIELD_NULL_CHECK(inst) if (!inst) throw ::cordl_internals::NullException(std::string("Field access on nullptr instance, please make sure your instance is not null")) + #else + #define CORDL_FIELD_NULL_CHECK(instance) + #endif + + template + requires(std::is_pointer_v) + constexpr inline void* convert(T&& inst) { return static_cast(const_cast(static_cast(inst))); } + + template + constexpr inline void* convert(T&& inst) { return inst.convert(); } +} +} diff --git a/cordl_internals/method-utils.hpp b/cordl_internals/method-utils.hpp new file mode 100644 index 000000000..f2776e62c --- /dev/null +++ b/cordl_internals/method-utils.hpp @@ -0,0 +1,268 @@ +#pragma once + +#include "config.hpp" +#include "concepts.hpp" +#include "exceptions.hpp" +#include "box-utils.hpp" +#include +#include +#include "il2cpp-tabledefs.h" + +namespace { +namespace cordl_internals { +#pragma region extract values + template + CORDL_HIDDEN void* ExtractValue(T& arg) noexcept; + + template + requires(il2cpp_convertible) + CORDL_HIDDEN void* ExtractValue(T& arg) noexcept { return arg.convert(); } + + template + requires(std::is_pointer_v) + CORDL_HIDDEN void* ExtractValue(T& arg) noexcept { + return const_cast(static_cast(arg)); + } + + template<> + CORDL_HIDDEN void* ExtractValue(nullptr_t&) noexcept { return nullptr; } + template<> + CORDL_HIDDEN void* ExtractValue(void*& arg) noexcept { return arg; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppType*&) noexcept { return nullptr; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppClass*&) noexcept { return nullptr; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppObject*& arg) noexcept { + if (arg) { + il2cpp_functions::Init(); + auto k = il2cpp_functions::object_get_class(arg); + if (k && il2cpp_functions::class_is_valuetype(k)) { + // boxed value type, unbox it + return il2cpp_functions::object_unbox(static_cast(arg)); + } + } + + return arg; + } + + template + CORDL_HIDDEN void* ExtractValue(T&& arg) noexcept; + + template + requires(il2cpp_convertible) + CORDL_HIDDEN void* ExtractValue(T&& arg) noexcept { return arg.convert(); } + + template + requires(std::is_pointer_v) + CORDL_HIDDEN void* ExtractValue(T&& arg) noexcept { + return const_cast(static_cast(arg)); + } + + template<> + CORDL_HIDDEN void* ExtractValue(nullptr_t&&) noexcept { return nullptr; } + template<> + CORDL_HIDDEN void* ExtractValue(void*&& arg) noexcept { return arg; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppType*&&) noexcept { return nullptr; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppClass*&&) noexcept { return nullptr; } + + template<> + CORDL_HIDDEN constexpr void* ExtractValue(Il2CppObject*&& arg) noexcept { + if (arg) { + il2cpp_functions::Init(); + auto k = il2cpp_functions::object_get_class(arg); + if (k && il2cpp_functions::class_is_valuetype(k)) { + // boxed value type, unbox it + return il2cpp_functions::object_unbox(static_cast(arg)); + } + } + + return arg; + } + + CORDL_HIDDEN inline auto ExtractValues() { + return ::std::vector(); + } + + template + CORDL_HIDDEN std::vector ExtractValues(T&& arg, TArgs&& ...args) { + auto firstVal = ExtractValue(arg); + auto otherVals = ExtractValues(args...); + otherVals.insert(otherVals.begin(), firstVal); + return otherVals; + } + +#pragma endregion // extract values + +#pragma region extract type values + template + CORDL_HIDDEN void* ExtractTypeValue(T& arg) { + return const_cast(static_cast(&arg)); + } + + template + CORDL_HIDDEN void* ExtractTypeValue(T&& arg) { + return const_cast(static_cast(&arg)); + } + + template<> + CORDL_HIDDEN constexpr void* ExtractTypeValue(std::nullptr_t&) { return nullptr; } + + template<> + CORDL_HIDDEN constexpr void* ExtractTypeValue(std::nullptr_t&&) { return nullptr; } + + template<> + CORDL_HIDDEN void* ExtractTypeValue<::bs_hook::Il2CppWrapperType>(::bs_hook::Il2CppWrapperType& arg) { + if (arg) { // is it even a set value + il2cpp_functions::Init(); + auto k = il2cpp_functions::object_get_class(static_cast(arg)); + if (k && il2cpp_functions::class_is_valuetype(k)) { + // boxed value type, unbox it + return il2cpp_functions::object_unbox(static_cast(arg)); + } + return arg.convert(); + } else { + return nullptr; + } + } + + template<> + CORDL_HIDDEN void* ExtractTypeValue<::bs_hook::Il2CppWrapperType>(::bs_hook::Il2CppWrapperType&& arg) { + if (arg) { // is it even a set value + il2cpp_functions::Init(); + auto k = il2cpp_functions::object_get_class(static_cast(arg)); + if (k && il2cpp_functions::class_is_valuetype(k)) { + // boxed value type, unbox it + return il2cpp_functions::object_unbox(static_cast(arg)); + } + return arg.convert(); + } else { + return nullptr; + } + } + + template + requires(!std::is_same_v) + CORDL_HIDDEN constexpr void* ExtractTypeValue(T& arg) { return arg.convert(); } + + template + requires(!std::is_same_v) + CORDL_HIDDEN constexpr void* ExtractTypeValue(T&& arg) { return arg.convert(); } + + template + requires(std::is_pointer_v) + CORDL_HIDDEN constexpr void* ExtractTypeValue(T& arg) { return arg; } + + template + requires(std::is_pointer_v) + CORDL_HIDDEN constexpr void* ExtractTypeValue(T&& arg) { return arg; } + +#pragma endregion // extract type values + +#pragma region extract type + template CORDL_HIDDEN Il2CppType const* ExtractType() { + return ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_type::get() + .value_or(nullptr); + } + + template CORDL_HIDDEN Il2CppType const* ExtractType(T&& arg) { + return ::il2cpp_utils::il2cpp_type_check::il2cpp_arg_type::get(arg) + .value_or(nullptr); + } + + CORDL_HIDDEN auto ExtractTypes() { + return std::vector(); + } + + template + CORDL_HIDDEN std::vector ExtractTypes(T&& arg, + TArgs&&... args) { + auto tFirst = ExtractType(arg); + auto tOthers = ExtractTypes(args...); + if (tFirst) tOthers.insert(tOthers.begin(), tFirst); + return tOthers; + } + +#pragma endregion // extract type + template + CORDL_HIDDEN TOut RunMethodRethrow(T&& instance, MethodInfo const* method, + TArgs&&... params) { + CRASH_UNLESS(method); + + // get the instance value, regardless of if it is boxed or anything + auto inst = ExtractValue(instance); + + // do a null check for reference instance method calls +#ifndef NO_RUNTIME_INSTANCE_METHOD_NULL_CHECKS + if constexpr (::il2cpp_utils::il2cpp_reference_type) { + if ((method->flags & METHOD_ATTRIBUTE_STATIC) == 0) { // method is instance method + if (!inst) { + // if inst evaluates false, we are dealing with a nullptr instance, and the instance method call is a bad idea + std::stringstream str; + // FIXME: should we use this string, or something else? log a stacktrace? + str << "Instance was null for method call of "; + str << method->klass->name; str << "::"; str << method->name; + throw NullException(str.str()); + } + + #ifndef ALLOW_INVALID_UNITY_METHOD_CALLS + if constexpr (std::is_convertible_v) { + if (!read_cachedptr(static_cast(inst))) { + // if cached ptr evaluates as false, we are dealing with an invalid unity instance, and the instance method call is a bad idea + std::stringstream str; + // FIXME: should we use this string, or something else? log a stacktrace? + str << "Instance was null for method call of "; + str << method->klass->name; str << "::"; str << method->name; + throw NullException(str.str()); + } + } + #endif + } + } +#endif + + if constexpr (checkTypes && sizeof...(TArgs) > 0) { // param type check + std::array types{ ExtractType( + params)... }; + // TODO: check types array against types in methodinfo + + auto outType = ExtractType(); + if (outType) { + // TODO: check return type against methodinfo return type + } + } + + Il2CppException* exp = nullptr; + std::array invokeParams{ExtractTypeValue(params)...}; + il2cpp_functions::Init(); + auto* ret = il2cpp_functions::runtime_invoke(method, inst, + invokeParams.data(), &exp); + + // an exception was thrown, rethrow it! + if (exp) throw il2cpp_utils::RunMethodException(exp, method); + + if constexpr (!std::is_same_v) { // return type is not void, we should return something! + // FIXME: what if the return type is a ByRef ? + if constexpr (::il2cpp_utils::il2cpp_type_check::need_box::value) { // value type returns from runtime invoke are boxed + // FIXME: somehow allow the gc free as an out of scope instead of having to temporarily save the retval? + auto retval = Unbox(ret); + il2cpp_functions::il2cpp_GC_free(ret); + return retval; + } else if constexpr (il2cpp_utils::il2cpp_reference_type_wrapper) { // ref type returns are just that, ref type returns + return TOut(ret); + } else { // probably ref type pointer + return static_cast(static_cast(ret)); + } + + } + } +} +} // end anonymous namespace diff --git a/cordl_internals/ptr-utils.hpp b/cordl_internals/ptr-utils.hpp new file mode 100644 index 000000000..be2cdd0c0 --- /dev/null +++ b/cordl_internals/ptr-utils.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#include "config.hpp" +#include "concepts.hpp" +#include "beatsaber-hook/shared/utils/il2cpp-type-check.hpp" + +namespace { +namespace cordl_internals { + template + requires(std::is_pointer_v) + using to_const_pointer = std::remove_pointer_t const*; + + + + /// @brief type to wrap a pointer to a T, not recommended to be used with anything that's not il2cpp compatible + /// @tparam T type that instance points to + template + requires(!::il2cpp_utils::il2cpp_reference_type_wrapper) + struct Ptr { + constexpr Ptr() : instance(nullptr) {} + constexpr explicit Ptr(void* i) : instance(i) {} + constexpr void* convert() const { return const_cast(instance); } + + constexpr Ptr(T* i) : instance(i) {} + constexpr Ptr(T& i) : instance(&i) {} + + constexpr operator T&() const { return *static_cast(const_cast(instance)); } + constexpr operator T*() const { return static_cast(const_cast(instance)); } + T* operator ->() const { return static_cast(const_cast(instance)); } + + protected: + void* instance; + }; + + // specific instantiation for void pointers + template<> + struct Ptr { + constexpr Ptr() : instance(nullptr) {} + constexpr Ptr(void* i) : instance(i) {} + constexpr void* convert() const { return const_cast(instance); } + constexpr operator void*() const { return const_cast(instance); } + + protected: + void* instance; + }; + + static_assert(sizeof(Ptr) == sizeof(void*)); +} +} // end anonymous namespace +// Ptr is neither Ref nor Val type +template<> struct CORDL_HIDDEN ::il2cpp_utils::GenRefTypeTrait<::cordl_internals::Ptr> { constexpr static bool value = false; }; +template<> struct CORDL_HIDDEN ::il2cpp_utils::GenValueTypeTrait<::cordl_internals::Ptr> { constexpr static bool value = false; }; + +template +struct CORDL_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_type<::cordl_internals::Ptr> { + static inline const Il2CppType* get() { + static auto* typ = &::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class::get()->this_arg; + return typ; + } +}; + +template +struct CORDL_HIDDEN ::il2cpp_utils::il2cpp_type_check::il2cpp_arg_type<::cordl_internals::Ptr> { + static inline const Il2CppType* get([[maybe_unused]] ::cordl_internals::Ptr arg) { + return ::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_type<::cordl_internals::Ptr>::get(); + } +}; diff --git a/cordl_internals/size-utils.hpp b/cordl_internals/size-utils.hpp new file mode 100644 index 000000000..e2f194ba4 --- /dev/null +++ b/cordl_internals/size-utils.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace { + +namespace cordl_internals { + template + requires(sizeof(T) == sz) + struct size_check { + static constexpr bool value = true; + }; + + template + requires(sizeof(T) == sz) + static constexpr bool size_check_v = true; +} // namespace cordl_internals +} // end anonymous namespace diff --git a/ps4/Il2CppUserAssemblies.elf b/ps4/Il2CppUserAssemblies.elf new file mode 100644 index 000000000..cf63ce69e Binary files /dev/null and b/ps4/Il2CppUserAssemblies.elf differ diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7d9dba4ba..5a1c3228e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2023-05-29" \ No newline at end of file +channel = "nightly-2023-12-17" \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 000000000..80afc099a --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod name_components; diff --git a/src/data/name_components.rs b/src/data/name_components.rs new file mode 100644 index 000000000..a9546fb76 --- /dev/null +++ b/src/data/name_components.rs @@ -0,0 +1,90 @@ +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Default, Hash, Clone)] +pub struct NameComponents { + pub namespace: Option, + 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("::")); + + // 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 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 NameComponents { + fn from(value: String) -> Self { + Self { + name: value, + ..Default::default() + } + } +} diff --git a/src/generate/config.rs b/src/generate/config.rs index 2ec1645f2..156cdb651 100644 --- a/src/generate/config.rs +++ b/src/generate/config.rs @@ -3,24 +3,97 @@ use std::path::PathBuf; pub struct GenerationConfig { pub source_path: PathBuf, pub header_path: PathBuf, + pub dst_internals_path: PathBuf, + pub dst_header_internals_file: PathBuf, + pub use_anonymous_namespace: bool, } impl GenerationConfig { pub fn namespace_cpp(&self, string: &str) -> String { - if string.is_empty() { + let final_ns = if string.is_empty() { "GlobalNamespace".to_owned() } else { string.replace(['<', '>', '`', '/'], "_").replace('.', "::") + }; + + match self.use_anonymous_namespace { + true => format!("::{final_ns}"), + false => final_ns, } } + + #[inline] pub fn name_cpp(&self, string: &str) -> String { + self.name_cpp_plus(string, &[]) + } + + pub fn name_cpp_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}"); + } + + 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" | + // 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}") + } + + // Coincidentally the same as path_name + _ => string.replace(['<', '`', '>', '/', '.', '|', ',', '(', ')', '[', ']'], "_"), + } + } + pub fn generic_nested_name(&self, string: &str) -> String { // Coincidentally the same as path_name - string.replace(['<', '`', '>', '/', '.'], "_") + string.replace(['<', '`', '>', '/', '.', ':', '|', ',', '(', ')'], "_") } pub fn namespace_path(&self, string: &str) -> String { string.replace(['<', '>', '`', '/'], "_").replace('.', "/") } pub fn path_name(&self, string: &str) -> String { - string.replace(['<', '>', '`', '.', '/'], "_") + string.replace(['<', '>', '`', '.', '/', ',', '(', ')'], "_") } } diff --git a/src/generate/constants.rs b/src/generate/constants.rs deleted file mode 100644 index 27aae448d..000000000 --- a/src/generate/constants.rs +++ /dev/null @@ -1,100 +0,0 @@ -use brocolib::{ - global_metadata::{Il2CppMethodDefinition, Il2CppTypeDefinition}, - runtime_metadata::Il2CppType, -}; - -pub const TYPE_ATTRIBUTE_INTERFACE: u32 = 0x00000020; -pub const TYPE_ATTRIBUTE_NESTED_PUBLIC: u32 = 0x00000002; - -pub const FIELD_ATTRIBUTE_PUBLIC: u16 = 0x0006; -pub const FIELD_ATTRIBUTE_PRIVATE: u16 = 0x0001; -pub const FIELD_ATTRIBUTE_STATIC: u16 = 0x0010; -pub const FIELD_ATTRIBUTE_LITERAL: u16 = 0x0040; - -pub const METHOD_ATTRIBUTE_PUBLIC: u16 = 0x0006; -pub const METHOD_ATTRIBUTE_STATIC: u16 = 0x0010; -pub const METHOD_ATTRIBUTE_FINAL: u16 = 0x0020; -pub const METHOD_ATTRIBUTE_VIRTUAL: u16 = 0x0040; -pub const METHOD_ATTRIBUTE_HIDE_BY_SIG: u16 = 0x0080; -pub const METHOD_ATTRIBUTE_ABSTRACT: u16 = 0x0400; -pub const METHOD_ATTRIBUTE_SPECIAL_NAME: u16 = 0x0800; - -pub trait MethodDefintionExtensions { - fn is_public_method(&self) -> bool; - fn is_abstract_method(&self) -> bool; - fn is_static_method(&self) -> bool; - fn is_virtual_method(&self) -> bool; - fn is_hidden_sig(&self) -> bool; - fn is_special_name(&self) -> bool; - fn is_final_method(&self) -> bool; -} - -impl MethodDefintionExtensions for Il2CppMethodDefinition { - fn is_public_method(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_PUBLIC) != 0 - } - - fn is_virtual_method(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_VIRTUAL) != 0 - } - - fn is_static_method(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_STATIC) != 0 - } - - fn is_abstract_method(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_ABSTRACT) != 0 - } - - fn is_hidden_sig(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_HIDE_BY_SIG) != 0 - } - - fn is_special_name(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_SPECIAL_NAME) != 0 - } - - fn is_final_method(&self) -> bool { - (self.flags & METHOD_ATTRIBUTE_FINAL) != 0 - } -} - -pub trait ParameterDefinitionExtensions { - fn is_param_optional(&self) -> bool; -} - -pub trait TypeExtentions { - fn is_static(&self) -> bool; - fn is_const(&self) -> bool; - fn is_byref(&self) -> bool; -} - -impl TypeExtentions for Il2CppType { - fn is_static(&self) -> bool { - (self.attrs & FIELD_ATTRIBUTE_STATIC) != 0 - } - - // FIELD_ATTRIBUTE_LITERAL - fn is_const(&self) -> bool { - (self.attrs & FIELD_ATTRIBUTE_LITERAL) != 0 - } - - fn is_byref(&self) -> bool { - self.byref - } -} - -pub trait TypeDefinitionExtensions { - fn is_value_type(&self) -> bool; - fn is_enum_type(&self) -> bool; -} - -impl TypeDefinitionExtensions for Il2CppTypeDefinition { - fn is_value_type(&self) -> bool { - self.bitfield & 1 != 0 - } - - fn is_enum_type(&self) -> bool { - self.bitfield & 2 != 0 - } -} diff --git a/src/generate/context.rs b/src/generate/context.rs index 18a61f0bd..ee82f8222 100644 --- a/src/generate/context.rs +++ b/src/generate/context.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; +use std::io::Write; use std::{ collections::{HashMap, HashSet}, fs::{create_dir_all, remove_file, File}, @@ -5,24 +7,33 @@ use std::{ }; use brocolib::global_metadata::TypeDefinitionIndex; + use color_eyre::eyre::ContextCompat; -use brocolib::runtime_metadata::TypeData; use itertools::Itertools; +use log::{info, trace}; +use pathdiff::diff_paths; -use crate::generate::members::CppInclude; +use crate::generate::cs_type::CORDL_NO_INCLUDE_IMPL_DEFINE; +use crate::generate::members::CppForwardDeclare; +use crate::generate::{members::CppInclude, type_extensions::TypeDefinitionExtensions}; +use crate::helpers::sorting::DependencyGraph; +use crate::STATIC_CONFIG; +use super::cpp_type_tag::CppTypeTag; +use super::cs_type::IL2CPP_OBJECT_TYPE; use super::{ config::GenerationConfig, cpp_type::CppType, cs_type::CSType, + members::CppUsingAlias, metadata::Metadata, writer::{CppWriter, Writable}, }; // Holds the contextual information for creating a C++ file // Will hold various metadata, such as includes, type definitions, and extraneous writes -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CppContext { pub typedef_path: PathBuf, pub type_impl_path: PathBuf, @@ -31,14 +42,17 @@ pub struct CppContext { pub fundamental_path: PathBuf, // Types to write, typedef - typedef_types: HashMap, + pub typedef_types: HashMap, + + // Namespace -> alias + pub typealias_types: HashSet<(String, CppUsingAlias)>, } impl CppContext { pub fn get_cpp_type_recursive_mut( &mut self, - root_tag: TypeData, - child_tag: TypeData, + root_tag: CppTypeTag, + child_tag: CppTypeTag, ) -> Option<&mut CppType> { let ty = self.typedef_types.get_mut(&root_tag); if root_tag == child_tag { @@ -49,38 +63,43 @@ impl CppContext { } pub fn get_cpp_type_recursive( &self, - root_tag: TypeData, - child_tag: TypeData, + root_tag: CppTypeTag, + child_tag: CppTypeTag, ) -> Option<&CppType> { let ty = self.typedef_types.get(&root_tag); + // if a root type if root_tag == child_tag { return ty; } ty.and_then(|ty| ty.get_nested_type(child_tag)) } - // pub fn get_cpp_type(&mut self, t: TypeData) -> Option<&CppType> { - // self.typedef_types.get(&t) - // } pub fn get_include_path(&self) -> &PathBuf { &self.typedef_path } - pub fn get_types(&self) -> &HashMap { + pub fn get_types(&self) -> &HashMap { &self.typedef_types } // TODO: Move out, this is CSContext - fn make( + pub fn make( metadata: &Metadata, config: &GenerationConfig, tdi: TypeDefinitionIndex, - tag: TypeData, + tag: CppTypeTag, + generic_inst: Option<&Vec>, ) -> CppContext { let t = &metadata.metadata.global_metadata.type_definitions[tdi]; - let ns = t.namespace(metadata.metadata); - let name = t.name(metadata.metadata); + + let components = t.get_name_components(metadata.metadata); + + let ns = &components.namespace.unwrap_or_default(); + let name = &components.name; + + let cpp_namespace = config.namespace_cpp(ns); + let cpp_name = config.namespace_cpp(name); let ns_path = config.namespace_path(ns); let path = if ns_path.is_empty() { @@ -88,38 +107,68 @@ impl CppContext { } else { ns_path + "/" }; + let path_name = match t.declaring_type_index != u32::MAX { + true => { + let name = config.path_name(name); + let base_name = components.declaring_types.unwrap_or_default().join("_"); + + format!("{base_name}_{name}") + } + false => config.path_name(name), + }; + let mut x = CppContext { - typedef_path: config.header_path.join(format!( - "{}__{}_def.hpp", - path, - &config.path_name(name) - )), - type_impl_path: config.header_path.join(format!( - "{}__{}_impl.hpp", - path, - &config.path_name(name) - )), - fundamental_path: config.header_path.join(format!( - "{}{}.hpp", - path, - &config.path_name(name) - )), + typedef_path: config + .header_path + .join(format!("{path}zzzz__{path_name}_def.hpp")), + type_impl_path: config + .header_path + .join(format!("{path}zzzz__{path_name}_impl.hpp")), + fundamental_path: config.header_path.join(format!("{path}{path_name}.hpp")), typedef_types: Default::default(), + typealias_types: Default::default(), }; - match CppType::make_cpp_type(metadata, config, tag) { + + if metadata.blacklisted_types.contains(&tdi) { + if !t.is_value_type() { + x.typealias_types.insert(( + cpp_namespace, + CppUsingAlias { + alias: cpp_name.to_string(), + result: IL2CPP_OBJECT_TYPE.to_string(), + template: Default::default(), + }, + )); + } + return x; + } + + match CppType::make_cpp_type(metadata, config, tdi, tag, generic_inst) { Some(cpptype) => { - x.typedef_types - .insert(TypeData::TypeDefinitionIndex(tdi), cpptype); + x.insert_cpp_type(cpptype); } None => { - println!("Unable to create valid CppContext for type: {ns}::{name}!"); + info!( + "Unable to create valid CppContext for type: {}!", + t.full_name(metadata.metadata, true) + ); } } x } - pub fn write(&self) -> color_eyre::Result<()> { + pub fn insert_cpp_type(&mut self, cpp_type: CppType) { + if cpp_type.nested { + panic!( + "Cannot have a root type as a nested type! {}", + &cpp_type.cpp_name_components.combine_all() + ); + } + self.typedef_types.insert(cpp_type.self_tag, cpp_type); + } + + pub fn write(&self, config: &GenerationConfig) -> color_eyre::Result<()> { // Write typedef file first if Path::exists(self.typedef_path.as_path()) { remove_file(self.typedef_path.as_path())?; @@ -137,7 +186,9 @@ impl CppContext { )?; } - println!("Writing {:?}", self.typedef_path.as_path()); + let base_path = &config.header_path; + + trace!("Writing {:?}", self.typedef_path.as_path()); let mut typedef_writer = CppWriter { stream: File::create(self.typedef_path.as_path())?, indent: 0, @@ -154,24 +205,208 @@ impl CppContext { newline: true, }; - // Write includes for typedef - self.typedef_types + writeln!(typedef_writer, "#pragma once")?; + writeln!(typeimpl_writer, "#pragma once")?; + writeln!(fundamental_writer, "#pragma once")?; + + // Include cordl config + // this is so confusing but basically gets the relative folder + // navigation for `_config.hpp` + let dest_path = diff_paths( + &STATIC_CONFIG.dst_header_internals_file, + self.typedef_path.parent().unwrap(), + ) + .unwrap(); + + // write typedefs.h include first - this makes include order mostly happy (probably System.Object would still be weird!) + CppInclude::new_exact("beatsaber-hook/shared/utils/typedefs.h") + .write(&mut typedef_writer)?; + CppInclude::new_exact(dest_path).write(&mut typedef_writer)?; + + // after including cordl internals + // macro module init + writeln!(typedef_writer, "CORDL_MODULE_INIT")?; + + // alphabetical sorted + let typedef_types = self + .typedef_types .values() - .flat_map(|t| &t.requirements.required_includes) + .flat_map(|t: &CppType| -> Vec<&CppType> { + t.nested_types_flattened().values().copied().collect_vec() + }) + .chain(self.typedef_types.values()) + .sorted_by(|a, b| a.cpp_name_components.cmp(&b.cpp_name_components)) + // Enums go after stubs + .sorted_by(|a, b| { + if a.is_enum_type == b.is_enum_type { + return Ordering::Equal; + } + + if a.is_enum_type { + Ordering::Less + } else if b.is_enum_type { + Ordering::Greater + } else { + Ordering::Equal + } + }) + // Stubs are first + .sorted_by(|a, b| { + if a.is_stub == b.is_stub { + return Ordering::Equal; + } + + if a.is_stub { + Ordering::Less + } else if b.is_stub { + Ordering::Greater + } else { + Ordering::Equal + } + }) + // Value types are last + .sorted_by(|a, b| { + let a_strictly_vt = a.is_value_type && !a.is_enum_type; + let b_strictly_vt = b.is_value_type && !b.is_enum_type; + + if a_strictly_vt == b_strictly_vt { + return Ordering::Equal; + } + + if a_strictly_vt { + Ordering::Greater + } else if b_strictly_vt { + Ordering::Less + } else { + Ordering::Equal + } + }) + .collect_vec(); + + let typedef_root_types = typedef_types + .iter() + .cloned() + .filter(|t: &&CppType| self.typedef_types.contains_key(&t.self_tag)) + .collect_vec(); + + let mut ts = DependencyGraph::::new(|a, b| a.cmp(b)); + for cpp_type in &typedef_root_types { + ts.add_root_dependency(&cpp_type.self_tag); + + for dep in cpp_type.requirements.depending_types.iter().sorted() { + ts.add_dependency(&cpp_type.self_tag, dep); + + // add dependency for generic instantiations + // for all types with the same TDI + if let CppTypeTag::TypeDefinitionIndex(tdi) = dep { + // find all generic tags that have the same TDI + let generic_tags_in_context = + typedef_root_types.iter().filter(|t| match t.self_tag { + CppTypeTag::TypeDefinitionIndex(_) => false, + CppTypeTag::GenericInstantiation(gen_inst) => gen_inst.tdi == *tdi, + }); + + generic_tags_in_context.for_each(|generic_dep| { + ts.add_dependency(&cpp_type.self_tag, &generic_dep.self_tag); + }) + } + } + } + + // types that don't depend on anyone + // we take these because they get undeterministically sorted + // and can be first anyways + let mut undepended_cpp_types = vec![]; + + // currently sorted from root to dependencies + // aka least depended to most depended + let mut typedef_root_types_sorted = ts + .topological_sort() + .into_iter() + .filter_map(|t| self.typedef_types.get(t)) + .collect_vec(); + + // add the items with no dependencies at the tail + // when reversed these will be first and can be allowed to be first + typedef_root_types_sorted.append(&mut undepended_cpp_types); + // typedef_root_types_sorted.reverse(); + + // Write includes for typedef + typedef_types + .iter() + .flat_map(|t| &t.requirements.required_def_includes) .unique() + .sorted() .try_for_each(|i| i.write(&mut typedef_writer))?; + // Write includes for typeimpl + typedef_types + .iter() + .flat_map(|t| &t.requirements.required_impl_includes) + .unique() + .sorted() + .try_for_each(|i| i.write(&mut typeimpl_writer))?; + + // add module declarations + writeln!( + typedef_writer, + "CORDL_MODULE_EXPORT({})", + self.fundamental_path.file_stem().unwrap().to_string_lossy() + )?; + + // anonymous namespace + if STATIC_CONFIG.use_anonymous_namespace { + writeln!(typedef_writer, "CORDL_MODULE_EXPORT_STRUCT namespace {{")?; + writeln!(typeimpl_writer, "CORDL_MODULE_EXPORT_STRUCT namespace {{")?; + } + // write forward declares + // and includes for impl { - self.typedef_types - .values() - .flat_map(|t| &t.requirements.forward_declares) - .map(|(d, _)| d) + CppInclude::new_exact(diff_paths(&self.typedef_path, base_path).unwrap()) + .write(&mut typeimpl_writer)?; + + let forward_declare_and_includes = || { + typedef_types + .iter() + .flat_map(|t| &t.requirements.forward_declares) + }; + + forward_declare_and_includes() + .map(|(_fd, inc)| inc) .unique() // TODO: Check forward declare is not of own type - .try_for_each(|i| i.write(&mut typedef_writer))?; + .try_for_each(|i| -> color_eyre::Result<()> { + i.write(&mut typeimpl_writer)?; + Ok(()) + })?; + + forward_declare_and_includes() + .map(|(fd, _inc)| fd) + .unique() + .try_for_each(|fd| fd.write(&mut typedef_writer))?; + + writeln!(typedef_writer, "// Forward declare root types")?; + //Forward declare all types + typedef_root_types + .iter() + .map(|t| CppForwardDeclare::from_cpp_type(t)) + // TODO: Check forward declare is not of own type + .try_for_each(|fd| { + // Forward declare and include + fd.write(&mut typedef_writer) + })?; + + writeln!(typedef_writer, "// Write type traits")?; + typedef_root_types + .iter() + .try_for_each(|cpp_type| -> color_eyre::Result<()> { + if cpp_type.generic_instantiations_args_types.is_none() { + cpp_type.write_type_trait(&mut typedef_writer)?; + } + Ok(()) + })?; - CppInclude::new(self.type_impl_path.to_path_buf()).write(&mut typeimpl_writer)?; // This is likely not necessary // self.typedef_types // .values() @@ -182,214 +417,132 @@ impl CppContext { // .try_for_each(|i| i.write(&mut typeimpl_writer))?; } - for t in self.typedef_types.values() { + for t in &typedef_root_types_sorted { if t.nested { - continue; + panic!( + "Cannot have a root type as a nested type! {}", + &t.cpp_name_components.combine_all() + ); } + // if t.generic_instantiation_args.is_none() || true { + // t.write_def(&mut typedef_writer)?; + // t.write_impl(&mut typeimpl_writer)?; + // } else { + // t.write_def(&mut typeimpl_writer)?; + // t.write_impl(&mut typeimpl_writer)?; + // } + t.write_def(&mut typedef_writer)?; t.write_impl(&mut typeimpl_writer)?; } - CppInclude::new(self.typedef_path.to_path_buf()).write(&mut fundamental_writer)?; - CppInclude::new(self.type_impl_path.to_path_buf()).write(&mut fundamental_writer)?; - - // TODO: Write type impl and fundamental files here - Ok(()) - } -} - -pub struct CppContextCollection { - all_contexts: HashMap, - alias_context: HashMap, - filled_types: HashSet, - filling_types: HashSet, -} - -impl CppContextCollection { - pub fn fill(&mut self, metadata: &Metadata, config: &GenerationConfig, ty: TypeData) { - let type_tag: TypeData = ty; - let tdi = CppType::get_tag_tdi(type_tag); - - assert!( - !metadata.child_to_parent_map.contains_key(&tdi), - "Do not fill a child" - ); - - let context_tag = self.get_context_root_tag(type_tag); - - if self.filled_types.contains(&type_tag) { - return; + // end anonymous namespace + if STATIC_CONFIG.use_anonymous_namespace { + writeln!(typedef_writer, "}} // end anonymous namespace")?; + writeln!(typeimpl_writer, "}} // end anonymous namespace")?; } - // 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); - - self.filling_types.insert(type_tag); - - // In some occasions, the CppContext can be empty - if let Some((t, mut cpp_type)) = cpp_type_entry { - assert!(!cpp_type.nested, "Cannot fill a nested type!"); + // write macros + typedef_types + .iter() + .try_for_each(|t| Self::write_il2cpp_arg_macros(t, &mut typedef_writer))?; - cpp_type.fill_from_il2cpp(metadata, config, self, tdi); - - // Move ownership back up - self.all_contexts - .get_mut(&context_tag) - .expect("No cpp context") - .typedef_types - .insert(t, cpp_type); + // Fundamental + { + CppInclude::new_exact(diff_paths(&self.typedef_path, base_path).unwrap()) + .write(&mut fundamental_writer)?; + + // if guard for intellisense + writeln!(typeimpl_writer, "#ifndef {CORDL_NO_INCLUDE_IMPL_DEFINE}")?; + CppInclude::new_exact(diff_paths(&self.type_impl_path, base_path).unwrap()) + .write(&mut fundamental_writer)?; + writeln!(typeimpl_writer, "#endif")?; } - self.filled_types.insert(type_tag); - self.filling_types.remove(&type_tag); + // TODO: Write type impl and fundamental files here + Ok(()) } - fn alias_nested_types(&mut self, owner: &CppType, root_tag: TypeData) { - for nested_type in &owner.nested_types { - // println!( - // "Aliasing {:?} to {:?}", - // nested_type.self_tag, owner.self_tag - // ); - self.alias_context.insert(nested_type.self_tag, root_tag); - self.alias_nested_types(nested_type, root_tag); + fn write_il2cpp_arg_macros( + ty: &CppType, + writer: &mut super::writer::CppWriter, + ) -> color_eyre::Result<()> { + let is_generic_instantiation = ty.generic_instantiations_args_types.is_some(); + if is_generic_instantiation { + return Ok(()); } - } - - pub fn fill_nested_types( - &mut self, - metadata: &Metadata, - config: &GenerationConfig, - owner_ty: TypeData, - ) { - let owner_type_tag = owner_ty; - let owner = self - .get_cpp_type_mut(owner_type_tag) - .unwrap_or_else(|| panic!("Owner does not exist {owner_type_tag:?}")); - - // we clone, then write later - // since we're modifying only 1 type exclusively - // and we don't rely on any other type at this time - // we can clone - // sad inefficient memory usage but oh well - let mut nested_types = owner.nested_types.clone(); - nested_types.iter_mut().for_each(|nested_type| { - let nested_tag = nested_type.self_tag; - self.filling_types.insert(nested_tag); - let tdi = CppType::get_tag_tdi(nested_tag); - - nested_type.fill_from_il2cpp(metadata, config, self, tdi); - - self.filled_types.insert(nested_tag); - self.filling_types.remove(&nested_tag); - }); - // nested_tags.into_iter().for_each(|nested_tag| { - // self.filling_types.insert(nested_tag); - - // let nested_type = nested_types - // .iter_mut() - // .find(|n| n.self_tag == nested_tag) - // .unwrap(); - // let tdi = CppType::get_tag_tdi(nested_tag); - - // nested_type.fill_from_il2cpp(metadata, config, self, tdi); - - // self.filled_types.insert(nested_tag); - // self.filling_types.remove(&nested_tag); - // }); - - self.get_cpp_type_mut(owner_type_tag).unwrap().nested_types = nested_types; - } - pub fn get_context_root_tag(&self, ty: TypeData) -> TypeData { - let tag = ty; - self.alias_context - .get(&tag) - .cloned() - // .map(|t| self.get_context_root_tag(*t)) - .unwrap_or(tag) - } + let template_container_type = ty.is_stub + || ty + .cpp_template + .as_ref() + .is_some_and(|t| !t.names.is_empty()); - pub fn make_from( - &mut self, - metadata: &Metadata, - config: &GenerationConfig, - ty: TypeData, - ) -> &mut CppContext { - let type_tag = ty; - assert!( - !metadata - .child_to_parent_map - .contains_key(&CppType::get_tag_tdi(type_tag)), - "Cannot create context for nested type", - ); - let context_root_tag = self.get_context_root_tag(type_tag); - - if self.filling_types.contains(&context_root_tag) { - panic!("Currently filling type {context_root_tag:?}, cannot fill") + if !ty.is_value_type && !ty.is_stub && !template_container_type && !is_generic_instantiation + { + // reference types need no boxing + writeln!( + writer, + "NEED_NO_BOX({});", + ty.cpp_name_components + .clone() + .remove_generics() + .remove_pointer() + .combine_all() + )?; } - // Why is the borrow checker so dumb? - // Using entries causes borrow checker to die :( - if self.all_contexts.contains_key(&context_root_tag) { - return self.all_contexts.get_mut(&context_root_tag).unwrap(); + if ty.nested { + writeln!( + writer, + "// TODO: Nested type, check correct definition print!" + )?; } - let tdi = CppType::get_tag_tdi(context_root_tag); - let context = CppContext::make(metadata, config, tdi, context_root_tag); - // Now do children - for cpp_type in context.typedef_types.values() { - self.alias_nested_types(cpp_type, cpp_type.self_tag); - } - self.all_contexts.insert(context_root_tag, context); - self.all_contexts.get_mut(&context_root_tag).unwrap() - // self.all_contexts - // .entry(context_root_tag) - // .or_insert_with(|| { - // let tdi = CppType::get_tag_tdi(context_root_tag); - // let context = CppContext::make(metadata, config, tdi, context_root_tag); - // // Now do children - // for (_tag, cpp_type) in &context.typedef_types { - // self.alias_nested_types(&cpp_type, cpp_type.self_tag); - // } - // context - // }) - } + let macro_arg_define = { + match //ty.generic_instantiation_args.is_some() || + template_container_type { + true => match ty.is_value_type { + true => "DEFINE_IL2CPP_ARG_TYPE_GENERIC_STRUCT", + false => "DEFINE_IL2CPP_ARG_TYPE_GENERIC_CLASS", + }, + false => "DEFINE_IL2CPP_ARG_TYPE", + } + }; - pub fn get_cpp_type(&self, ty: TypeData) -> Option<&CppType> { - let tag = ty; - let context_root_tag = self.get_context_root_tag(tag); - self.get_context(context_root_tag) - .and_then(|c| c.get_cpp_type_recursive(context_root_tag, tag)) - } - pub fn get_cpp_type_mut(&mut self, ty: TypeData) -> Option<&mut CppType> { - let tag = ty; - let context_root_tag = self.get_context_root_tag(tag); - self.get_context_mut(context_root_tag) - .and_then(|c| c.get_cpp_type_recursive_mut(context_root_tag, tag)) - } + // Essentially splits namespace.foo/nested_foo into (namespace, foo/nested_foo) - pub fn get_context(&self, type_tag: TypeData) -> Option<&CppContext> { - self.all_contexts.get(&self.get_context_root_tag(type_tag)) - } - pub fn get_context_mut(&mut self, type_tag: TypeData) -> Option<&mut CppContext> { - self.all_contexts - .get_mut(&self.get_context_root_tag(type_tag)) - } + let namespace = ty.cs_name_components.namespace.clone().unwrap_or_default(); + let combined_name = match &ty.cs_name_components.declaring_types { + None => ty.cs_name_components.name.clone(), + Some(declaring_types) => format!( + "{}/{}", + declaring_types.join("/"), + ty.cs_name_components.name.clone() + ), + }; - pub fn new() -> CppContextCollection { - CppContextCollection { - all_contexts: Default::default(), - filled_types: Default::default(), - filling_types: Default::default(), - alias_context: Default::default(), - } - } - pub fn get(&self) -> &HashMap { - &self.all_contexts + // generics shouldn't emit with a pointer, while regular types should honor the pointer + let cpp_name = match template_container_type { + true => ty + .cpp_name_components + .clone() + .remove_generics() + .remove_pointer() + .combine_all(), + + false => ty + .cpp_name_components + .clone() + .remove_generics() + .combine_all(), + }; + + writeln!( + writer, + "{macro_arg_define}({cpp_name}, \"{namespace}\", \"{combined_name}\");", + )?; + + Ok(()) } } diff --git a/src/generate/context_collection.rs b/src/generate/context_collection.rs new file mode 100644 index 000000000..aaed59b94 --- /dev/null +++ b/src/generate/context_collection.rs @@ -0,0 +1,777 @@ +use core::panic; +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::Write, +}; + +use brocolib::{ + global_metadata::TypeDefinitionIndex, + runtime_metadata::{Il2CppMethodSpec, TypeData}, +}; +use itertools::Itertools; +use log::{info, trace, warn}; +use pathdiff::diff_paths; + +use crate::{ + generate::{cpp_type::CppType, cs_type::CSType}, + STATIC_CONFIG, +}; + +use super::{ + config::GenerationConfig, + context::CppContext, + cpp_type_tag::{CppTypeTag, GenericInstantiation}, + metadata::Metadata, + type_extensions::TypeDefinitionExtensions, +}; + +pub struct CppContextCollection { + // Should always be a TypeDefinitionIndex + all_contexts: HashMap, + pub alias_context: HashMap, + pub alias_nested_type_to_parent: HashMap, + filled_types: HashSet, + filling_types: HashSet, + borrowing_types: HashSet, +} + +impl CppContextCollection { + pub fn fill_cpp_type( + &mut self, + cpp_type: &mut CppType, + metadata: &Metadata, + config: &GenerationConfig, + ) { + let tag = cpp_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); + + cpp_type.fill_from_il2cpp(metadata, config, self); + + self.filled_types.insert(tag); + self.filling_types.remove(&tag.clone()); + } + + pub fn fill(&mut self, metadata: &Metadata, config: &GenerationConfig, type_tag: CppTypeTag) { + let _tdi = CppType::get_cpp_tag_tdi(type_tag); + + 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 { + assert!(!cpp_type.nested, "Cannot fill a nested type!"); + + self.fill_cpp_type(&mut cpp_type, metadata, config); + + // Move ownership back up + self.all_contexts + .get_mut(&context_tag) + .expect("No cpp context") + .insert_cpp_type(cpp_type); + } + } + + fn alias_nested_types(&mut self, owner: &CppType, root_tag: CppTypeTag, context_check: bool) { + for (tag, nested_type) in &owner.nested_types { + // info!( + // "Aliasing {:?} to {:?}", + // nested_type.self_tag, owner.self_tag + // ); + self.alias_type_to_context(*tag, root_tag, context_check, false); + self.alias_type_to_parent(*tag, owner.self_tag, context_check); + + self.alias_nested_types(nested_type, root_tag, context_check); + } + } + + pub fn alias_type_to_context( + &mut self, + src: CppTypeTag, + dest: CppTypeTag, + context_check: bool, + overrid: bool, + ) { + if self.alias_context.contains_key(&dest) && !overrid { + panic!("Aliasing an aliased type! {src:?} to {dest:?}"); + } + if context_check && !self.all_contexts.contains_key(&dest) { + panic!("Aliased context {src:?} to {dest:?} doesn't have a context"); + } + if self.alias_context.contains_key(&src) && context_check { + panic!("Already aliased this key!"); + } + self.alias_context.insert(src, dest); + } + + pub fn alias_type_to_parent( + &mut self, + src: CppTypeTag, + dest: CppTypeTag, + _context_check: bool, + ) { + // if context_check && !self.all_contexts.contains_key(&dest) { + // panic!("Aliased nested type {src:?} to {dest:?} doesn't have a parent"); + // } + if src == dest { + panic!("Self {src:?} can't point to dest!") + } + if self.alias_nested_type_to_parent.get(&dest) == Some(&src) { + panic!("Parent {dest:?} can't be assigned to src {src:?}!") + } + self.alias_nested_type_to_parent.insert(src, dest); + } + + pub fn fill_nested_types( + &mut self, + metadata: &Metadata, + config: &GenerationConfig, + owner_ty: CppTypeTag, + ) { + let owner_type_tag = owner_ty; + let owner = self + .get_cpp_type_mut(owner_type_tag) + .unwrap_or_else(|| panic!("Owner does not exist {owner_type_tag:?}")); + + // we clone, then write later + // since we're modifying only 1 type exclusively + // and we don't rely on any other type at this time + // we can clone + + // sad inefficient memory usage but oh well + let nested_types: HashMap = owner + .nested_types + .clone() + .into_iter() + .map(|(nested_tag, mut nested_type)| { + self.fill_cpp_type(&mut nested_type, metadata, config); + + (nested_tag, nested_type) + }) + .collect(); + + self.get_cpp_type_mut(owner_type_tag).unwrap().nested_types = nested_types; + } + + pub fn get_context_root_tag(&self, ty: CppTypeTag) -> CppTypeTag { + self.alias_context + .get(&ty) + .cloned() + // .map(|t| self.get_context_root_tag(*t)) + .unwrap_or(ty) + } + pub fn get_parent_or_self_tag(&self, ty: CppTypeTag) -> CppTypeTag { + self.alias_nested_type_to_parent + .get(&ty) + .cloned() + .map(|t| self.get_parent_or_self_tag(t)) + .unwrap_or(ty) + } + + pub fn make_nested_from( + &mut self, + metadata: &Metadata<'_>, + config: &GenerationConfig, + tdi: TypeDefinitionIndex, + generic_inst: Option<&Vec>, + ) -> Option<&mut CppContext> { + let ty_data = CppTypeTag::TypeDefinitionIndex(tdi); + let ty_def = &metadata.metadata.global_metadata.type_definitions[tdi]; + let context_root_tag = self.get_context_root_tag(ty_data); + + if self.filling_types.contains(&context_root_tag) { + panic!("Currently filling type {context_root_tag:?}, cannot fill") + } + + // Why is the borrow checker so dumb? + // Using entries causes borrow checker to die :( + if self.filled_types.contains(&ty_data) { + return Some(self.all_contexts.get_mut(&context_root_tag).unwrap()); + } + + if self.get_cpp_type(ty_data).is_some() { + return self.get_context_mut(ty_data); + } + + let context_tag = self.get_context_root_tag(ty_data); + let context_type_data: TypeDefinitionIndex = context_tag.into(); + let context_td = &metadata.metadata.global_metadata.type_definitions[context_type_data]; + + if metadata.blacklisted_types.contains(&tdi) { + warn!( + "Skipping nested type because it's blacklisted! {context_tag:?} {}", + context_td.full_name(metadata.metadata, true) + ); + return None; + } + + let nested_inherits_declaring = ty_def.is_assignable_to(context_td, metadata.metadata); + if nested_inherits_declaring { + warn!( + "Nested type \"{}\" inherits declaring type \"{}\"", + ty_def.full_name(metadata.metadata, true), + context_td.full_name(metadata.metadata, true) + ); + } + + match nested_inherits_declaring { + true => { + // If a nested type inherits its declaring type, move it to its own CppContext + + let context = CppContext::make(metadata, config, tdi, ty_data, generic_inst); + + // Unnest type does not alias to another context or type + self.alias_context.remove(&ty_data); + self.alias_nested_type_to_parent.remove(&ty_data); + + self.all_contexts.insert(ty_data, context); + self.all_contexts.get_mut(&ty_data) + } + false => { + let new_cpp_type = + CppType::make_cpp_type(metadata, config, tdi, ty_data, generic_inst) + .expect("Failed to make nested type"); + + let context = self.get_context_mut(ty_data).unwrap(); + // self.alias_type_to_context(new_cpp_type.self_tag, context_root_tag, true); + + // context.insert_cpp_type(stub); + context.insert_cpp_type(new_cpp_type); + + Some(context) + } + } + } + + /// Make a generic type + /// based of an existing type definition + /// and give it the generic args + pub fn make_generic_from( + &mut self, + method_spec: &Il2CppMethodSpec, + metadata: &mut Metadata, + config: &GenerationConfig, + ) -> Option<&mut CppContext> { + // Not a generic class, no type needed + if method_spec.class_inst_index == u32::MAX { + return None; + } + // Skip generic methods? + if method_spec.method_inst_index != u32::MAX { + return None; + } + + let method = + &metadata.metadata.global_metadata.methods[method_spec.method_definition_index]; + let ty_def = &metadata.metadata.global_metadata.type_definitions[method.declaring_type]; + + if ty_def.is_interface() { + // Skip interface + info!( + "Skipping make interface for generic instantiation {}", + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + let type_data = CppTypeTag::TypeDefinitionIndex(method.declaring_type); + let tdi = method.declaring_type; + let context_root_tag = self.get_context_root_tag(type_data); + + if metadata.blacklisted_types.contains(&tdi) { + warn!( + "Skipping generic instantiation {tdi:?} {} {}", + method_spec.class_inst_index, + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + if self.filling_types.contains(&context_root_tag) { + panic!("Currently filling type {context_root_tag:?}, cannot fill") + } + + let generic_class_ty_data = CppTypeTag::GenericInstantiation(GenericInstantiation { + tdi, + inst: method_spec.class_inst_index as usize, + }); + + let generic_inst = + &metadata.metadata_registration.generic_insts[method_spec.class_inst_index as usize]; + + // Why is the borrow checker so dumb? + // Using entries causes borrow checker to die :( + if self.filled_types.contains(&generic_class_ty_data) { + return Some(self.all_contexts.get_mut(&context_root_tag).unwrap()); + } + + if self.get_cpp_type(generic_class_ty_data).is_some() { + return self.get_context_mut(generic_class_ty_data); + } + + // make original type a stub + self.borrow_cpp_type(type_data, |_, mut cpptype| { + cpptype.is_stub = true; + + cpptype + }); + + let mut new_cpp_type = CppType::make_cpp_type( + metadata, + config, + tdi, + generic_class_ty_data, + Some(&generic_inst.types), + ) + .expect("Failed to make generic type"); + new_cpp_type.self_tag = generic_class_ty_data; + self.alias_type_to_context(new_cpp_type.self_tag, context_root_tag, true, false); + + // TODO: Not needed since making a cpp type will already be a stub in other passes? + // this is the generic stub + // this might cause problems, hopefully not + // since two types can coexist with the TDI though only one is nested + // let mut stub = new_cpp_type.clone(); + // stub.self_tag = type_data; + + new_cpp_type.requirements.add_dependency_tag(type_data); + + // if generic type is a nested type + // put it under the parent's `nested_types` field + // otherwise put it in the typedef's hashmap + + let context = self.get_context_mut(generic_class_ty_data).unwrap(); + + // context.insert_cpp_type(stub); + context.insert_cpp_type(new_cpp_type); + + Some(context) + } + + /// + /// It's important this gets called AFTER the type is filled + /// + pub fn fill_generic_method_inst( + &mut self, + method_spec: &Il2CppMethodSpec, + metadata: &mut Metadata, + config: &GenerationConfig, + ) -> Option<&mut CppContext> { + if method_spec.method_inst_index == u32::MAX { + return None; + } + + let method = + &metadata.metadata.global_metadata.methods[method_spec.method_definition_index]; + + // is reference type + // only make generic spatialization + let type_data = CppTypeTag::TypeDefinitionIndex(method.declaring_type); + let tdi = method.declaring_type; + + let ty_def = &metadata.metadata.global_metadata.type_definitions[method.declaring_type]; + + if metadata.blacklisted_types.contains(&tdi) { + info!( + "Skipping {tdi:?} {} since it is blacklisted", + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + if ty_def.is_interface() { + // Skip interface + info!( + "Skipping fill generic method interface for generic instantiation {}", + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + let context_root_tag = self.get_context_root_tag(type_data); + + let generic_class_ty_data = if method_spec.class_inst_index != u32::MAX { + CppTypeTag::GenericInstantiation(GenericInstantiation { + tdi, + inst: method_spec.class_inst_index as usize, + }) + } else { + type_data + }; + + self.borrow_cpp_type(generic_class_ty_data, |collection, mut cpp_type| { + let method_index = method_spec.method_definition_index; + cpp_type.add_method_generic_inst(method_spec, metadata); + cpp_type.create_method(ty_def, method_index, metadata, collection, config, true); + + cpp_type + }); + + self.all_contexts.get_mut(&context_root_tag) + } + + pub fn fill_generic_class_inst( + &mut self, + method_spec: &Il2CppMethodSpec, + metadata: &mut Metadata, + config: &GenerationConfig, + ) -> Option<&mut CppContext> { + if method_spec.class_inst_index == u32::MAX { + return None; + } + // Skip generic methods? + if method_spec.method_inst_index != u32::MAX { + return None; + } + + let method = + &metadata.metadata.global_metadata.methods[method_spec.method_definition_index]; + + let ty_def = &metadata.metadata.global_metadata.type_definitions[method.declaring_type]; + + // only make generic spatialization + let type_data = CppTypeTag::TypeDefinitionIndex(method.declaring_type); + let tdi = method.declaring_type; + + if metadata.blacklisted_types.contains(&tdi) { + info!( + "Skipping {tdi:?} {} since it is blacklisted", + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + if ty_def.is_interface() { + // Skip interface + info!( + "Skipping fill class interface for generic instantiation {}", + ty_def.full_name(metadata.metadata, true) + ); + return None; + } + + let context_root_tag = self.get_context_root_tag(type_data); + + let generic_class_ty_data = if method_spec.class_inst_index != u32::MAX { + CppTypeTag::GenericInstantiation(GenericInstantiation { + tdi, + inst: method_spec.class_inst_index as usize, + }) + } else { + type_data + }; + + self.borrow_cpp_type(generic_class_ty_data, |collection, mut cpp_type| { + // cpp_type.make_generics_args(metadata, collection); + collection.fill_cpp_type(&mut cpp_type, metadata, config); + + cpp_type + }); + + self.all_contexts.get_mut(&context_root_tag) + } + + pub fn make_from( + &mut self, + metadata: &Metadata, + config: &GenerationConfig, + type_tag: TypeData, + generic_inst: Option<&Vec>, + ) -> &mut CppContext { + assert!( + !metadata + .child_to_parent_map + .contains_key(&CppType::get_tag_tdi(type_tag)), + "Cannot create context for nested type", + ); + let context_root_tag = self.get_context_root_tag(type_tag.into()); + + if self.filling_types.contains(&context_root_tag) { + panic!("Currently filling type {context_root_tag:?}, cannot fill") + } + + if self.borrowing_types.contains(&context_root_tag) { + panic!("Currently borrowing context {context_root_tag:?}, cannot fill") + } + + // Why is the borrow checker so dumb? + // Using entries causes borrow checker to die :( + if self.all_contexts.contains_key(&context_root_tag) { + return self.all_contexts.get_mut(&context_root_tag).unwrap(); + } + + let tdi = CppType::get_cpp_tag_tdi(context_root_tag); + let context = CppContext::make(metadata, config, tdi, context_root_tag, generic_inst); + // Now do children + for cpp_type in context.typedef_types.values() { + self.alias_nested_types(cpp_type, cpp_type.self_tag, false); + } + self.all_contexts.insert(context_root_tag, context); + self.all_contexts.get_mut(&context_root_tag).unwrap() + } + + /// + /// By default will only look for nested types of the context, ignoring other CppTypes + /// + pub fn get_cpp_type(&self, ty: CppTypeTag) -> Option<&CppType> { + let tag = ty; + let context_root_tag = self.get_context_root_tag(tag); + let parent_root_tag = self.get_parent_or_self_tag(tag); + + self.get_context(context_root_tag) + .and_then(|c| c.get_cpp_type_recursive(parent_root_tag, tag)) + } + + /// + /// By default will only look for nested types of the context, ignoring other CppTypes + /// + pub fn get_cpp_type_mut(&mut self, ty: CppTypeTag) -> Option<&mut CppType> { + let tag = ty; + let context_root_tag = self.get_context_root_tag(tag); + let parent_root_tag = self.get_parent_or_self_tag(tag); + self.get_context_mut(context_root_tag) + .and_then(|c| c.get_cpp_type_recursive_mut(parent_root_tag, tag)) + } + + pub fn borrow_cpp_type(&mut self, ty: CppTypeTag, func: F) + where + F: Fn(&mut Self, CppType) -> CppType, + { + let context_ty = self.get_context_root_tag(ty); + if self.borrowing_types.contains(&context_ty) { + panic!("Already borrowing this context!"); + } + + let declaring_ty = self.get_parent_or_self_tag(ty); + + let (result_cpp_type, old_tag); + + { + 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 declaring_cpp_type = context.typedef_types.get(&declaring_ty).cloned(); + (result_cpp_type, old_tag) = match declaring_cpp_type { + Some(old_cpp_type) => { + let old_tag = old_cpp_type.self_tag; + let new_cpp_ty = func(self, old_cpp_type); + + (new_cpp_ty, Some(old_tag)) + } + None => { + let mut declaring_ty = context + .typedef_types + .get(&declaring_ty) + .expect("Parent ty not found in context") + .clone(); + + let found = declaring_ty.borrow_nested_type_mut(ty, self, &func); + + if !found { + panic!("No nested or parent type found for type {ty:?}!"); + } + + (declaring_ty, None) + } + }; + } + + // avoid the borrow checker's wrath + let context = self.all_contexts.get_mut(&context_ty).unwrap(); + if let Some(old_tag) = old_tag { + context.typedef_types.remove(&old_tag); + } + context.insert_cpp_type(result_cpp_type); + self.borrowing_types.remove(&context_ty); + } + + pub fn get_context(&self, type_tag: CppTypeTag) -> Option<&CppContext> { + 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: CppTypeTag) -> Option<&mut CppContext> { + 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 new() -> CppContextCollection { + CppContextCollection { + all_contexts: Default::default(), + filled_types: Default::default(), + filling_types: Default::default(), + alias_nested_type_to_parent: Default::default(), + alias_context: Default::default(), + borrowing_types: Default::default(), + } + } + pub fn get(&self) -> &HashMap { + &self.all_contexts + } + + pub fn write_all(&self, config: &GenerationConfig) -> 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_namespace_headers(&self) -> color_eyre::Result<()> { + self.all_contexts + .iter() + .into_group_map_by(|(_, c)| c.fundamental_path.parent()) + .into_iter() + .try_for_each(|(dir, contexts)| -> color_eyre::Result<()> { + let namespace = if dir.unwrap() == STATIC_CONFIG.header_path { + "GlobalNamespace" + } else { + dir.unwrap().file_name().unwrap().to_str().unwrap() + }; + + let str = contexts + .iter() + // ignore empty contexts + .filter(|(_, c)| !c.typedef_types.is_empty()) + // ignore weird named types + .filter(|(_, c)| { + !c.fundamental_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .starts_with('_') + }) + // add includes + .map(|(_, c)| { + let stripped_path = + diff_paths(&c.fundamental_path, &STATIC_CONFIG.header_path).unwrap(); + format!("#include \"{}\"", stripped_path.display()) + }) + .sorted() + .unique() + .join("\n"); + + let path = dir.unwrap().join(namespace).with_extension("hpp"); + + info!( + "Creating namespace glob include {path:?} for {} files", + contexts.len() + ); + + let mut file = File::create(path)?; + + writeln!( + file, + "#ifdef __cpp_modules + module; + #endif + " + )?; + writeln!(file, "#pragma once")?; + file.write_all(str.as_bytes())?; + + writeln!(file)?; + writeln!( + file, + "#ifdef __cpp_modules + export module {namespace}; + #endif + " + )?; + + Ok(()) + })?; + Ok(()) + } +} + +// Get root parent for a reference type, which is System.Object +// for generic sharing +fn get_root_parent<'a>( + metadata: &mut Metadata<'a>, + ty_def: &'a brocolib::global_metadata::Il2CppTypeDefinition, +) -> Option<&'a brocolib::global_metadata::Il2CppTypeDefinition> { + // is reference type + // only make generic spatialization + if ty_def.is_value_type() || ty_def.is_enum_type() { + return Some(ty_def); + } + + let mut parent_index = ty_def.parent_index; + loop { + if parent_index == u32::MAX { + break; + } + + let parent_ty = metadata + .metadata_registration + .types + .get(parent_index as usize) + .unwrap(); + if let TypeData::TypeDefinitionIndex(parent_tdi) = parent_ty.data { + let parent_ty_def = &metadata.metadata.global_metadata.type_definitions[parent_tdi]; + + parent_index = parent_ty_def.parent_index; + } else { + break; + } + } + if parent_index == u32::MAX { + return Some(ty_def); + } + + let parent_ty = metadata + .metadata_registration + .types + .get(parent_index as usize) + .unwrap(); + if let TypeData::TypeDefinitionIndex(parent_tdi) = parent_ty.data { + Some(&metadata.metadata.global_metadata.type_definitions[parent_tdi]) + } else { + Some(ty_def) + } +} diff --git a/src/generate/cpp_type.rs b/src/generate/cpp_type.rs index 895cecfb3..bdbdb08f4 100644 --- a/src/generate/cpp_type.rs +++ b/src/generate/cpp_type.rs @@ -6,169 +6,269 @@ use std::{ use color_eyre::eyre::Context; -use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; +use brocolib::global_metadata::{MethodIndex, TypeIndex}; use itertools::Itertools; +use crate::data::name_components::NameComponents; + use super::{ - members::{CppForwardDeclare, CppInclude, CppMember, CppTemplate}, - writer::Writable, + context_collection::CppContextCollection, + cpp_type_tag::CppTypeTag, + members::{CppForwardDeclare, CppInclude, CppMember, CppNonMember, CppTemplate}, + writer::{CppWriter, Sortable, Writable}, offsets::SizeInfo, }; +pub const CORDL_TYPE_MACRO: &str = "CORDL_TYPE"; +pub const __CORDL_IS_VALUE_TYPE: &str = "__IL2CPP_IS_VALUE_TYPE"; +pub const __CORDL_BACKING_ENUM_TYPE: &str = "__CORDL_BACKING_ENUM_TYPE"; + +pub const CORDL_REFERENCE_TYPE_CONSTRAINT: &str = "::il2cpp_utils::il2cpp_reference_type"; +pub const CORDL_NUM_ENUM_TYPE_CONSTRAINT: &str = "::cordl_internals::is_or_is_backed_by"; +pub const CORDL_METHOD_HELPER_NAMESPACE: &str = "::cordl_internals"; + #[derive(Debug, Clone, Default)] pub struct CppTypeRequirements { pub forward_declares: HashSet<(CppForwardDeclare, CppInclude)>, // Only value types or classes - pub required_includes: HashSet, + pub required_def_includes: HashSet, + pub required_impl_includes: HashSet, + + // Lists both types we forward declare or include + pub depending_types: HashSet, +} + +impl CppTypeRequirements { + pub fn add_forward_declare(&mut self, cpp_data: (CppForwardDeclare, CppInclude)) { + // self.depending_types.insert(cpp_type.self_tag); + self.forward_declares.insert(cpp_data); + } + + pub fn add_def_include(&mut self, cpp_type: Option<&CppType>, cpp_include: CppInclude) { + if let Some(cpp_type) = cpp_type { + self.depending_types.insert(cpp_type.self_tag); + } + self.required_def_includes.insert(cpp_include); + } + pub fn add_impl_include(&mut self, cpp_type: Option<&CppType>, cpp_include: CppInclude) { + if let Some(cpp_type) = cpp_type { + self.depending_types.insert(cpp_type.self_tag); + } + self.required_impl_includes.insert(cpp_include); + } + pub fn add_dependency(&mut self, cpp_type: &CppType) { + self.depending_types.insert(cpp_type.self_tag); + } + pub fn add_dependency_tag(&mut self, tag: CppTypeTag) { + self.depending_types.insert(tag); + } } // Represents all of the information necessary for a C++ TYPE! // A C# type will be TURNED INTO this #[derive(Debug, Clone)] pub struct CppType { - pub self_tag: TypeData, + pub self_tag: CppTypeTag, pub nested: bool, pub(crate) prefix_comments: Vec, - pub(crate) namespace: String, - pub(crate) cpp_namespace: String, - pub(crate) name: String, - pub(crate) cpp_name: String, - pub(crate) parent_ty_tdi: Option, - pub(crate) parent_ty_cpp_name: Option, + pub size_info: Option, + pub packing: Option, - pub cpp_full_name: String, + // Computed by TypeDefinition.full_name() + // Then fixed for generic types in CppContextCollection::make_generic_from/fill_generic_inst + pub cpp_name_components: NameComponents, + pub cs_name_components: NameComponents, - pub declarations: Vec, - pub implementations: Vec, + pub declarations: Vec>, + pub implementations: Vec>, /// Outside of the class declaration /// Move to CsType/CppType? - pub nonmember_implementations: Vec>, - pub nonmember_declarations: Vec>, + pub nonmember_implementations: Vec>, + pub nonmember_declarations: Vec>, pub is_value_type: bool, + pub is_enum_type: bool, + pub is_reference_type: bool, pub requirements: CppTypeRequirements, pub inherit: Vec, - pub generic_args: CppTemplate, // Names of templates e.g T, TKey etc. + pub cpp_template: Option, // Names of templates e.g T, TKey etc. - pub nested_types: Vec, + /// contains the array of generic Il2CppType indexes + pub generic_instantiations_args_types: Option>, // GenericArg -> Instantiation Arg + pub method_generic_instantiation_map: HashMap>, // MethodIndex -> Generic Args + pub is_stub: bool, + pub is_interface: bool, + pub is_hidden: bool, + + pub nested_types: HashMap, } impl CppTypeRequirements { pub fn need_wrapper(&mut self) { - self.required_includes.insert(CppInclude::new( - "beatsaber-hook/shared/utils/base-wrapper-type.hpp".into(), - )); + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/base-wrapper-type.hpp"), + ); } pub fn needs_int_include(&mut self) { - self.required_includes - .insert(CppInclude::new_system("cstdint".into())); + self.add_def_include(None, CppInclude::new_system("cstdint")); + } + pub fn needs_byte_include(&mut self) { + self.add_def_include(None, CppInclude::new_system("cstddef")); + } + pub fn needs_math_include(&mut self) { + self.add_def_include(None, CppInclude::new_system("cmath")); } pub fn needs_stringw_include(&mut self) { - self.required_includes.insert(CppInclude::new( - "beatsaber-hook/shared/utils/typedefs-string.hpp".into(), - )); + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/typedefs-string.hpp"), + ); } pub fn needs_arrayw_include(&mut self) { - self.required_includes.insert(CppInclude::new( - "beatsaber-hook/shared/utils/typedefs-array".into(), - )); + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/typedefs-array.hpp"), + ); + } + + pub fn needs_byref_include(&mut self) { + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/byref.hpp"), + ); + } + + pub fn needs_enum_include(&mut self) { + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/enum-type.hpp"), + ); + } + + pub fn needs_value_include(&mut self) { + self.add_def_include( + None, + CppInclude::new_exact("beatsaber-hook/shared/utils/value-type.hpp"), + ); } } impl CppType { - pub fn namespace(&self) -> &String { - &self.namespace + pub fn namespace(&self) -> String { + self.cs_name_components + .namespace + .clone() + .unwrap_or_default() } - pub fn cpp_namespace(&self) -> &str { - &self.cpp_namespace + pub fn cpp_namespace(&self) -> String { + self.cpp_name_components + .namespace + .clone() + .unwrap_or_default() } pub fn name(&self) -> &String { - &self.name + &self.cs_name_components.name } pub fn cpp_name(&self) -> &String { - &self.cpp_name + &self.cpp_name_components.name } - pub fn nested_types_flattened(&self) -> HashMap { + pub fn nested_types_flattened(&self) -> HashMap { self.nested_types .iter() - .flat_map(|n| n.nested_types_flattened()) - .chain(self.nested_types.iter().map(|n| (n.self_tag, n))) + .flat_map(|(_, n)| n.nested_types_flattened()) + .chain(self.nested_types.iter().map(|(tag, n)| (*tag, n))) .collect() } - pub fn get_nested_type_mut(&mut self, tag: TypeData) -> Option<&mut CppType> { - self.nested_types.iter_mut().find_map(|n| { - if n.self_tag == tag { - return Some(n); - } + pub fn get_nested_type_mut(&mut self, tag: CppTypeTag) -> Option<&mut CppType> { + // sadly + if self.nested_types.get_mut(&tag).is_some() { + return self.nested_types.get_mut(&tag); + } + self.nested_types.values_mut().find_map(|n| { // Recurse n.get_nested_type_mut(tag) }) } - pub fn get_nested_type(&self, tag: TypeData) -> Option<&CppType> { - self.nested_types.iter().find_map(|n| { - if n.self_tag == tag { - return Some(n); - } - - // Recurse - n.get_nested_type(tag) + pub fn get_nested_type(&self, tag: CppTypeTag) -> Option<&CppType> { + self.nested_types.get(&tag).or_else(|| { + self.nested_types.iter().find_map(|(_, n)| { + // Recurse + n.get_nested_type(tag) + }) }) } - pub fn formatted_complete_cpp_name(&self) -> &String { - return &self.cpp_full_name; - // We found a valid type that we have defined for this idx! - // TODO: We should convert it here. - // Ex, if it is a generic, convert it to a template specialization - // If it is a normal type, handle it accordingly, etc. - // match &self.parent_ty_cpp_name { - // Some(parent_ty) => { - // format!("{}::{parent_ty}::{}", self.cpp_namespace(), self.cpp_name()) - // } - // None => format!("{}::{}", self.cpp_namespace(), self.cpp_name()), - // } + pub fn borrow_nested_type_mut( + &mut self, + ty: CppTypeTag, + context: &mut CppContextCollection, + func: &F, + ) -> bool + where + F: Fn(&mut CppContextCollection, CppType) -> CppType, + { + let nested_index = self.nested_types.get(&ty); + + match nested_index { + None => { + for nested_ty in self.nested_types.values_mut() { + if nested_ty.borrow_nested_type_mut(ty, context, func) { + return true; + } + } + + false + } + Some(old_nested_cpp_type) => { + // clone to avoid breaking il2cpp + let old_nested_cpp_type_tag = old_nested_cpp_type.self_tag; + let new_cpp_type = func(context, old_nested_cpp_type.clone()); + + // Remove old type, which may have a new type tag + self.nested_types.remove(&old_nested_cpp_type_tag); + self.nested_types + .insert(new_cpp_type.self_tag, new_cpp_type); + + true + } + } } pub fn write_impl(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - self.write_impl_internal(writer, Some(self.cpp_namespace())) + self.write_impl_internal(writer) } pub fn write_def(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - self.write_def_internal(writer, Some(self.cpp_namespace()), true) + self.write_def_internal(writer, Some(&self.cpp_namespace())) } pub fn write_impl_internal( &self, writer: &mut super::writer::CppWriter, - namespace: Option<&str>, ) -> color_eyre::Result<()> { - if let Some(namespace) = namespace { - writeln!(writer, "namespace {namespace} {{")?; - } - // Write all declarations within the type here - self.implementations + self.nonmember_implementations .iter() .try_for_each(|d| d.write(writer))?; - self.nonmember_implementations + + // Write all declarations within the type here + self.implementations .iter() + .sorted_by(|a, b| a.sort_level().cmp(&b.sort_level())) .try_for_each(|d| d.write(writer))?; // TODO: Figure out self.nested_types .iter() - .try_for_each(|n| n.write_impl_internal(writer, None))?; - - if let Some(namespace) = namespace { - writeln!(writer, "}} // end namespace {namespace}")?; - } + .try_for_each(|(_tag, n)| n.write_impl_internal(writer))?; Ok(()) } @@ -177,92 +277,209 @@ impl CppType { &self, writer: &mut super::writer::CppWriter, namespace: Option<&str>, - fd: bool, ) -> color_eyre::Result<()> { - self.prefix_comments.iter().for_each(|pc| { - writeln!(writer, "// {pc}") - .context("Prefix comment") - .unwrap(); - }); - - // forward declare self - { - if fd { - writeln!( - writer, - "// Forward declaring type: {}::{}", - namespace.unwrap_or(""), - self.name() - )?; - } + self.prefix_comments + .iter() + .try_for_each(|pc| writeln!(writer, "// {pc}").context("Prefix comment"))?; + let type_kind = match self.is_value_type { + true => "struct", + false => "class", + }; + + // Just forward declare + if !self.is_stub { if let Some(n) = &namespace { - writeln!(writer, "namespace {n} {{",)?; + writeln!(writer, "namespace {n} {{")?; writer.indent(); } - if fd { - // template<...> - self.generic_args.write(writer)?; - writeln!(writer, "struct {};", self.name())?; + // Write type definition + if let Some(generic_args) = &self.cpp_template { + writeln!(writer, "// cpp template")?; + generic_args.write(writer)?; } - } - - // Write type definition - self.generic_args.write(writer)?; - writeln!(writer, "// Is value type: {}", self.is_value_type)?; - // Type definition plus inherit lines - match self.inherit.is_empty() { - true => writeln!(writer, "struct {} {{", self.cpp_name())?, - false => writeln!( + writeln!(writer, "// Is value type: {}", self.is_value_type)?; + writeln!( writer, - "struct {} : {} {{", - self.cpp_name(), - self.inherit - .iter() - .map(|s| format!("public {s}")) - .join(", ") - )?, - } + "// Dependencies: {:?}", + self.requirements.depending_types + )?; + writeln!(writer, "// Self: {:?}", self.self_tag)?; - writer.indent(); + let clazz_name = self + .cpp_name_components + .formatted_name(self.generic_instantiations_args_types.is_some()); - self.nested_types.iter().try_for_each(|n| { writeln!( writer, - "// Forward declare nested type\nstruct {};", - n.cpp_name - ) - })?; + "// CS Name: {}", + self.cs_name_components.combine_all() + )?; + + // Type definition plus inherit lines + + // add_generic_inst sets template to [] + // if self.generic_instantiation_args.is_some() { + // writeln!(writer, "template<>")?; + // } + // start writing class + let cordl_hide = match self.is_hidden { + true => CORDL_TYPE_MACRO, + false => "", + }; + + if let Some(packing) = &self.packing { + writeln!(writer, "#pragma pack(push, {packing})")?; + } - self.nested_types - .iter() - .try_for_each(|n| n.write_def_internal(writer, None, false))?; - // Write all declarations within the type here - self.declarations.iter().for_each(|d| { - d.write(writer).unwrap(); - }); - - writeln!( - writer, - "static constexpr bool __CORDL_IS_VALUE_TYPE = {};", - self.is_value_type - )?; - // Type complete - writer.dedent(); - writeln!(writer, "}};")?; - - // NON MEMBER DECLARATIONS - self.nonmember_declarations - .iter() - .try_for_each(|d| d.write(writer))?; + match self.inherit.is_empty() { + true => writeln!(writer, "{type_kind} {cordl_hide} {clazz_name} {{")?, + false => writeln!( + writer, + "{type_kind} {cordl_hide} {clazz_name} : {} {{", + self.inherit + .iter() + .map(|s| format!("public {s}")) + .join(", ") + )?, + } + + writer.indent(); + + // add public access + writeln!(writer, "public:")?; + + self.nested_types + .values() + .map(|t| (t, CppForwardDeclare::from_cpp_type(t))) + .unique_by(|(_, n)| n.clone()) + .try_for_each(|(t, nested_forward_declare)| { + writeln!( + writer, + "// nested type forward declare {} is stub {} {:?} {:?}\n//{:?}", + t.cs_name_components.combine_all(), + t.is_stub, + t.cs_name_components.generics, + t.generic_instantiations_args_types, + t.self_tag + )?; + nested_forward_declare.write(writer) + })?; + + self.nested_types + .iter() + .try_for_each(|(_, n)| -> color_eyre::Result<()> { + writer.indent(); + writeln!( + writer, + "// nested type {} is stub {}", + n.cs_name_components.combine_all(), + n.is_stub + )?; + n.write_def_internal(writer, None)?; + writer.dedent(); + Ok(()) + })?; + writeln!(writer, "// Declarations")?; + // Write all declarations within the type here + self.declarations + .iter() + .sorted_by(|a, b| { + // fields and unions need to be sorted by offset to work correctly + + let a_offset = match a.as_ref() { + CppMember::FieldDecl(f) => f.offset, + CppMember::NestedUnion(u) => u.offset, + _ => u32::MAX, + }; + + let b_offset = match b.as_ref() { + CppMember::FieldDecl(f) => f.offset, + CppMember::NestedUnion(u) => u.offset, + _ => u32::MAX, + }; + + a_offset.cmp(&b_offset) + }) + // sort by sort level after fields have been ordered correctly + .sorted_by(|a, b| a.sort_level().cmp(&b.sort_level())) + .try_for_each(|d| -> color_eyre::Result<()> { + d.write(writer)?; + writeln!(writer)?; + Ok(()) + })?; - // Namespace complete - if let Some(n) = namespace { + writeln!( + writer, + "static constexpr bool {__CORDL_IS_VALUE_TYPE} = {};", + self.is_value_type + )?; + // Type complete writer.dedent(); - writeln!(writer, "}} // namespace {n}")?; + writeln!(writer, "}};")?; + + if self.packing.is_some() { + writeln!(writer, "#pragma pack(pop)")?; + } + + // NON MEMBER DECLARATIONS + writeln!(writer, "// Non member Declarations")?; + + self.nonmember_declarations + .iter() + .try_for_each(|d| -> color_eyre::Result<()> { + d.write(writer)?; + writeln!(writer)?; + Ok(()) + })?; + + // Namespace complete + if let Some(n) = namespace { + writer.dedent(); + writeln!(writer, "}} // namespace end def {n}")?; + } } + // TODO: Write additional meta-info here, perhaps to ensure correct conversions? Ok(()) } + + pub fn write_type_trait(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + if self.cpp_template.is_some() { + // generic + // macros from bs hook + let type_trait_macro = if self.is_enum_type || self.is_value_type { + "MARK_GEN_VAL_T" + } else { + "MARK_GEN_REF_PTR_T" + }; + + writeln!( + writer, + "{type_trait_macro}({});", + self.cpp_name_components + .clone() + .remove_generics() + .remove_pointer() + .combine_all() + )?; + } else { + // non-generic + // macros from bs hook + let type_trait_macro = if self.is_enum_type || self.is_value_type { + "MARK_VAL_T" + } else { + "MARK_REF_PTR_T" + }; + + writeln!( + writer, + "{type_trait_macro}({});", + self.cpp_name_components.remove_pointer().combine_all() + )?; + } + + Ok(()) + } } diff --git a/src/generate/cpp_type_tag.rs b/src/generate/cpp_type_tag.rs new file mode 100644 index 000000000..8f1502816 --- /dev/null +++ b/src/generate/cpp_type_tag.rs @@ -0,0 +1,102 @@ +use brocolib; + +use brocolib::runtime_metadata::TypeData; + +use brocolib::global_metadata::TypeDefinitionIndex; + +// TODO: +/// Indices into the [`Il2CppMetadataRegistration::generic_insts`] field +pub type GenericInstIndex = usize; + +// TDI -> Generic inst +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GenericInstantiation { + pub tdi: TypeDefinitionIndex, + /// Indices into the [`Il2CppMetadataRegistration::generic_insts`] field + pub inst: GenericInstIndex, +} + +// Unique identifier for a CppType +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum CppTypeTag { + TypeDefinitionIndex(TypeDefinitionIndex), + GenericInstantiation(GenericInstantiation), +} + +impl From for CppTypeTag { + fn from(value: TypeDefinitionIndex) -> Self { + CppTypeTag::TypeDefinitionIndex(value) + } +} + +impl From for CppTypeTag { + fn from(value: TypeData) -> Self { + match value { + TypeData::TypeDefinitionIndex(i) => i.into(), + _ => panic!("Can't use {value:?} for CppTypeTag"), + } + } +} + +impl From for TypeData { + fn from(value: CppTypeTag) -> Self { + match value { + CppTypeTag::TypeDefinitionIndex(i) => TypeData::TypeDefinitionIndex(i), + CppTypeTag::GenericInstantiation(gen) => TypeData::GenericClassIndex(gen.inst), // TODO:? + _ => panic!("Can't go from {value:?} to TypeData"), + } + } +} + +impl From for TypeDefinitionIndex { + fn from(value: CppTypeTag) -> Self { + match value { + CppTypeTag::TypeDefinitionIndex(i) => i, + CppTypeTag::GenericInstantiation(generic_inst) => generic_inst.tdi, + _ => panic!("Type is not a TDI! {value:?}"), + } + } +} + +impl CppTypeTag { + pub fn from_generic_class_index( + generic_class_idx: usize, + metadata: &brocolib::Metadata, + ) -> Self { + let generic_class = &metadata + .runtime_metadata + .metadata_registration + .generic_classes[generic_class_idx]; + + let ty: brocolib::runtime_metadata::Il2CppType = + metadata.runtime_metadata.metadata_registration.types[generic_class.type_index]; + // Unwrap + let TypeData::TypeDefinitionIndex(tdi) = ty.data else { + panic!("No TDI for generic inst!") + }; + + Self::GenericInstantiation(GenericInstantiation { + tdi, + inst: generic_class + .context + .class_inst_idx + .expect("Not a generic class inst idx"), + }) + } + pub fn from_type_data(type_data: TypeData, metadata: &brocolib::Metadata) -> Self { + match type_data { + TypeData::TypeDefinitionIndex(tdi) => tdi.into(), + TypeData::GenericClassIndex(generic_class_idx) => { + Self::from_generic_class_index(generic_class_idx, metadata) + } + _ => todo!(), + } + } + + pub fn get_tdi(&self) -> TypeDefinitionIndex { + match self { + CppTypeTag::TypeDefinitionIndex(tdi) => *tdi, + CppTypeTag::GenericInstantiation(gen_inst) => gen_inst.tdi, + } + } +} diff --git a/src/generate/cs_context_collection.rs b/src/generate/cs_context_collection.rs new file mode 100644 index 000000000..63dec2977 --- /dev/null +++ b/src/generate/cs_context_collection.rs @@ -0,0 +1,51 @@ +use brocolib::global_metadata::TypeDefinitionIndex; + +use super::{ + context_collection::CppContextCollection, cpp_type_tag::CppTypeTag, metadata::Metadata, +}; + +pub trait CsContextCollection { + fn get_cpp_context_collection(&self) -> &CppContextCollection; + fn get_mut_cpp_context_collection(&mut self) -> &mut CppContextCollection; + + fn alias_nested_types_il2cpp( + &mut self, + owner_ty: TypeDefinitionIndex, + root_tag: CppTypeTag, + metadata: &Metadata, + nested: bool, + ); +} + +impl CsContextCollection for CppContextCollection { + fn get_cpp_context_collection(&self) -> &CppContextCollection { + self + } + + fn get_mut_cpp_context_collection(&mut self) -> &mut CppContextCollection { + self + } + + fn alias_nested_types_il2cpp( + &mut self, + owner_tdi: TypeDefinitionIndex, + root_tag: CppTypeTag, + metadata: &Metadata, + nested: bool, + ) { + let owner_tag = CppTypeTag::TypeDefinitionIndex(owner_tdi); + let owner_ty = &metadata.metadata.global_metadata.type_definitions[owner_tdi]; + + for nested_type_tdi in owner_ty.nested_types(metadata.metadata) { + // let nested_type = &metadata.metadata.global_metadata.type_definitions[*nested_type_tdi]; + + let nested_tag = CppTypeTag::TypeDefinitionIndex(*nested_type_tdi); + + self.alias_type_to_context(nested_tag, root_tag, true, false); + if nested { + self.alias_type_to_parent(nested_tag, owner_tag, true); + } + self.alias_nested_types_il2cpp(*nested_type_tdi, root_tag, metadata, nested); + } + } +} diff --git a/src/generate/cs_type.rs b/src/generate/cs_type.rs index 146b55795..0584ce0c0 100644 --- a/src/generate/cs_type.rs +++ b/src/generate/cs_type.rs @@ -1,36 +1,102 @@ +use core::panic; +use log::{debug, info, warn}; use std::{ collections::HashMap, io::{Cursor, Read}, rc::Rc, + slice::Iter, + sync::Arc, }; use brocolib::{ global_metadata::{ - FieldIndex, Il2CppTypeDefinition, MethodIndex, ParameterIndex, TypeDefinitionIndex, + FieldIndex, Il2CppFieldDefinition, Il2CppTypeDefinition, MethodIndex, ParameterIndex, + TypeDefinitionIndex, TypeIndex, }, - runtime_metadata::{Il2CppType, Il2CppTypeEnum, TypeData}, + runtime_metadata::{Il2CppMethodSpec, Il2CppType, Il2CppTypeEnum, TypeData}, }; use byteorder::{LittleEndian, ReadBytesExt}; + use itertools::Itertools; +use crate::{ + data::name_components::NameComponents, + generate::{ + members::{CppNestedUnion, CppUsingAlias}, + offsets, + }, + helpers::cursor::ReadBytesExtensions, +}; + use super::{ config::GenerationConfig, - constants::{ - MethodDefintionExtensions, TypeDefinitionExtensions, TypeExtentions, - TYPE_ATTRIBUTE_INTERFACE, + context_collection::CppContextCollection, + cpp_type::{ + CppType, CppTypeRequirements, CORDL_METHOD_HELPER_NAMESPACE, + CORDL_NUM_ENUM_TYPE_CONSTRAINT, CORDL_REFERENCE_TYPE_CONSTRAINT, __CORDL_BACKING_ENUM_TYPE, }, - context::CppContextCollection, - cpp_type::CppType, + cpp_type_tag::CppTypeTag, members::{ - CppCommentedString, CppConstructorDecl, CppConstructorImpl, CppField, CppForwardDeclare, - CppInclude, CppMember, CppMethodData, CppMethodDecl, CppMethodImpl, CppMethodSizeStruct, - CppParam, CppProperty, CppTemplate, + CppConstructorDecl, CppConstructorImpl, CppFieldDecl, CppFieldImpl, + CppForwardDeclare, CppInclude, CppLine, CppMember, CppMethodData, CppMethodDecl, + CppMethodImpl, CppMethodSizeStruct, CppNestedStruct, CppNonMember, CppParam, + CppPropertyDecl, CppStaticAssert, CppTemplate, }, metadata::Metadata, + type_extensions::{ + Il2CppTypeEnumExtensions, MethodDefintionExtensions, ParameterDefinitionExtensions, + TypeDefinitionExtensions, TypeExtentions, + }, + writer::Writable, }; type Endian = LittleEndian; +// negative +pub const VALUE_TYPE_SIZE_OFFSET: u32 = 0x10; + +pub const VALUE_TYPE_WRAPPER_SIZE: &str = "__IL2CPP_VALUE_TYPE_SIZE"; +pub const REFERENCE_TYPE_WRAPPER_SIZE: &str = "__IL2CPP_REFERENCE_TYPE_SIZE"; +pub const REFERENCE_TYPE_FIELD_SIZE: &str = "__fields"; +pub const REFERENCE_WRAPPER_INSTANCE_NAME: &str = "::bs_hook::Il2CppWrapperType::instance"; + +pub const VALUE_WRAPPER_TYPE: &str = "::bs_hook::ValueType"; +pub const ENUM_WRAPPER_TYPE: &str = "::bs_hook::EnumType"; +pub const INTERFACE_WRAPPER_TYPE: &str = "::cordl_internals::InterfaceW"; +pub const IL2CPP_OBJECT_TYPE: &str = "Il2CppObject"; +pub const CORDL_NO_INCLUDE_IMPL_DEFINE: &str = "CORDL_NO_IMPL_INCLUDE"; +pub const CORDL_ACCESSOR_FIELD_PREFIX: &str = "___"; + +pub const ENUM_PTR_TYPE: &str = "::bs_hook::EnumPtr"; +pub const VT_PTR_TYPE: &str = "::bs_hook::VTPtr"; + +const SIZEOF_IL2CPP_OBJECT: u32 = 0x10; + +#[derive(Clone, Debug)] +pub struct FieldInfo<'a> { + cpp_field: CppFieldDecl, + field: &'a Il2CppFieldDefinition, + field_type: &'a Il2CppType, + is_constant: bool, + is_static: bool, + is_pointer: bool, + + offset: Option, + size: usize, +} + +struct FieldInfoSet<'a> { + fields: Vec>>, + size: u32, + offset: u32, +} + +impl<'a> FieldInfoSet<'a> { + fn max(&self) -> u32 { + self.size + self.offset + } +} + pub trait CSType: Sized { fn get_mut_cpp_type(&mut self) -> &mut CppType; // idk how else to do this fn get_cpp_type(&self) -> &CppType; // idk how else to do this @@ -41,32 +107,72 @@ pub trait CSType: Sized { _ => panic!("Unsupported type: {tag:?}"), } } + fn get_cpp_tag_tdi(tag: CppTypeTag) -> TypeDefinitionIndex { + tag.into() + } - fn parent_joined_cpp_name( - metadata: &Metadata, - config: &GenerationConfig, - tdi: TypeDefinitionIndex, - ) -> String { - let parent = metadata.child_to_parent_map.get(&tdi); - let ty = &metadata.metadata.global_metadata.type_definitions[tdi]; + fn parent_joined_cpp_name(metadata: &Metadata, tdi: TypeDefinitionIndex) -> String { + let ty_def = &metadata.metadata.global_metadata.type_definitions[tdi]; - let self_name = ty.name(metadata.metadata); + let name = ty_def.name(metadata.metadata); - match parent { - Some(parent_ty_cpp_name) => { - let parent_name = - Self::parent_joined_cpp_name(metadata, config, parent_ty_cpp_name.tdi); + if ty_def.declaring_type_index != u32::MAX { + let declaring_ty = + metadata.metadata_registration.types[ty_def.declaring_type_index as usize]; - format!("{parent_name}::{self_name}") + if let TypeData::TypeDefinitionIndex(declaring_tdi) = declaring_ty.data { + return Self::parent_joined_cpp_name(metadata, declaring_tdi) + "/" + name; + } else { + return declaring_ty.full_name(metadata.metadata) + "/" + name; } - None => config.name_cpp(self_name), } + + ty_def.full_name(metadata.metadata, true) + } + + fn fixup_into_generic_instantiation(&mut self) -> &mut CppType { + let cpp_type = self.get_mut_cpp_type(); + assert!( + cpp_type.generic_instantiations_args_types.is_some(), + "No generic instantiation args!" + ); + + cpp_type.cpp_template = Some(CppTemplate { names: vec![] }); + cpp_type.is_stub = false; + cpp_type.cpp_name_components.generics = None; + + cpp_type + } + + fn add_method_generic_inst( + &mut self, + method_spec: &Il2CppMethodSpec, + metadata: &Metadata, + ) -> &mut CppType { + assert!(method_spec.method_inst_index != u32::MAX); + + let cpp_type = self.get_mut_cpp_type(); + + let inst = metadata + .metadata_registration + .generic_insts + .get(method_spec.method_inst_index as usize) + .unwrap(); + + cpp_type.method_generic_instantiation_map.insert( + method_spec.method_definition_index, + inst.types.iter().map(|t| *t as TypeIndex).collect(), + ); + + cpp_type } fn make_cpp_type( metadata: &Metadata, config: &GenerationConfig, - tag: TypeData, + tdi: TypeDefinitionIndex, + tag: CppTypeTag, + generic_inst_types: Option<&Vec>, ) -> Option { // let iface = metadata.interfaces.get(t.interfaces_start); // Then, handle interfaces @@ -75,71 +181,132 @@ pub trait CSType: Sized { // - This includes constructors // inherited methods will be inherited - let tdi = Self::get_tag_tdi(tag); - let parent_pair = metadata.child_to_parent_map.get(&tdi); - let t = &metadata.metadata.global_metadata.type_definitions[tdi]; // Generics + // This is a generic type def + // TODO: Constraints! let generics = t.generic_container_index.is_valid().then(|| { - let generic_tdi = t.generic_container(metadata.metadata).owner_index; - let generic_t = &metadata.metadata.global_metadata.type_definitions[tdi]; - - println!("Generic TDI {generic_tdi} vs TDI {tdi:?}"); - println!("{generic_t:?}"); - t.generic_container(metadata.metadata) .generic_parameters(metadata.metadata) .iter() - .map(|param| (param, param.constraints(metadata.metadata))) .collect_vec() }); - let cpp_template = CppTemplate { - names: generics - .unwrap_or_default() - .iter() - .map(|(g, _)| g.name(metadata.metadata).to_string()) - .collect(), - }; + let cpp_template = generics.as_ref().map(|g| { + CppTemplate::make_typenames(g.iter().map(|g| g.name(metadata.metadata).to_string())) + }); let ns = t.namespace(metadata.metadata); let name = t.name(metadata.metadata); let full_name = t.full_name(metadata.metadata, false); - let cpp_full_name = if ns.is_empty() { - format!("GlobalNamespace::{}", config.namespace_cpp(&full_name)) - } else { - config.namespace_cpp(&full_name) + + if metadata.blacklisted_types.contains(&tdi) { + info!("Skipping {full_name} ({tdi:?}) because it's blacklisted"); + + return None; + } + + // all nested types are unnested + let nested = false; // t.declaring_type_index != u32::MAX; + let cs_name_components = t.get_name_components(metadata.metadata); + + let is_pointer = cs_name_components.is_pointer; + + let cpp_name_components = NameComponents { + declaring_types: cs_name_components + .declaring_types + .as_ref() + .map(|declaring_types| { + declaring_types + .iter() + .map(|s| config.name_cpp(s)) + .collect_vec() + }), + 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, }; + // 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 + // Every type should have a valid metadata size, even if it is 0 + let size_info: offsets::SizeInfo = + offsets::get_size_info(t, tdi, generic_inst_types, metadata); + + // best results of cordl are when specified packing is strictly what is used, but experimentation may be required + let packing = size_info.specified_packing; + + // Modified later for nested types let mut cpptype = CppType { self_tag: tag, - nested: parent_pair.is_some(), - prefix_comments: vec![format!("Type: {ns}::{name}")], - namespace: config.namespace_cpp(ns), - cpp_namespace: config.namespace_cpp(ns), - name: config.name_cpp(name), - cpp_name: config.name_cpp(name), - cpp_full_name, - - parent_ty_tdi: parent_pair.map(|p| p.tdi), - parent_ty_cpp_name: parent_pair - .map(|p| Self::parent_joined_cpp_name(metadata, config, p.tdi)), + nested, + prefix_comments: vec![format!("Type: {ns}::{name}"), format!("{size_info:?}")], + + size_info: Some(size_info), + packing, + + cpp_name_components, + cs_name_components, declarations: Default::default(), implementations: Default::default(), nonmember_implementations: Default::default(), nonmember_declarations: Default::default(), + is_value_type: t.is_value_type(), + is_enum_type: t.is_enum_type(), + is_reference_type: is_pointer, requirements: Default::default(), + inherit: Default::default(), - generic_args: cpp_template, + is_interface: t.is_interface(), + cpp_template, + + generic_instantiations_args_types: generic_inst_types.cloned(), + method_generic_instantiation_map: Default::default(), + + is_stub: false, + is_hidden: true, nested_types: Default::default(), }; + if cpptype.generic_instantiations_args_types.is_some() { + cpptype.fixup_into_generic_instantiation(); + } + + // Nested type unnesting fix + if t.declaring_type_index != u32::MAX { + let declaring_ty = &metadata + .metadata + .runtime_metadata + .metadata_registration + .types[t.declaring_type_index as usize]; + + let declaring_tag = CppTypeTag::from_type_data(declaring_ty.data, metadata.metadata); + let declaring_tdi: TypeDefinitionIndex = declaring_tag.into(); + let declaring_td = &metadata.metadata.global_metadata.type_definitions[declaring_tdi]; + let combined_name = cpptype + .cpp_name_components + .clone() + .remove_generics() + .remove_pointer() + .combine_all(); + + cpptype.cpp_name_components.namespace = + Some(config.namespace_cpp(declaring_td.namespace(metadata.metadata))); + cpptype.cpp_name_components.declaring_types = None; // remove declaring types + + cpptype.cpp_name_components.name = config.generic_nested_name(&combined_name); + } + if t.parent_index == u32::MAX { - if t.flags & TYPE_ATTRIBUTE_INTERFACE == 0 { - println!("Skipping type: {ns}::{name} because it has parent index: {} and is not an interface!", t.parent_index); + if !t.is_interface() && t.full_name(metadata.metadata, true) != "System.Object" { + info!("Skipping type: {ns}::{name} because it has parent index: {} and is not an interface!", t.parent_index); return None; } } else if metadata @@ -151,8 +318,6 @@ pub trait CSType: Sized { panic!("NO PARENT! But valid index found: {}", t.parent_index); } - cpptype.make_nested_types(metadata, config, tdi); - Some(cpptype) } @@ -161,222 +326,197 @@ pub trait CSType: Sized { metadata: &Metadata, config: &GenerationConfig, ctx_collection: &CppContextCollection, - tdi: TypeDefinitionIndex, ) { + if self.get_cpp_type().is_stub { + // Do not fill stubs + return; + } + + let tdi: TypeDefinitionIndex = self.get_cpp_type().self_tag.into(); + + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + self.make_generics_args(metadata, ctx_collection, tdi); self.make_parents(metadata, ctx_collection, tdi); - self.make_fields(metadata, ctx_collection, tdi); - self.make_properties(metadata, ctx_collection, tdi); + self.make_interfaces(metadata, ctx_collection, tdi); + + // we depend on parents and generic args here + // default ctor + if t.is_value_type() || t.is_enum_type() { + self.create_valuetype_constructor(metadata, ctx_collection, config, tdi); + self.create_valuetype_field_wrapper(); + if t.is_enum_type() { + self.create_enum_wrapper(metadata, ctx_collection, tdi); + self.create_enum_backing_type_constant(metadata, ctx_collection, tdi); + } + self.add_default_ctor(false); + } else if t.is_interface() { + // self.make_interface_constructors(); + self.delete_move_ctor(); + self.delete_copy_ctor(); + // self.delete_default_ctor(); + } else { + // ref type + self.delete_move_ctor(); + self.delete_copy_ctor(); + self.add_default_ctor(true); + // self.delete_default_ctor(); + } + + if !t.is_interface() { + self.create_size_assert(); + } + + self.make_nested_types(metadata, ctx_collection, config, tdi); + self.make_fields(metadata, ctx_collection, config, tdi); + self.make_properties(metadata, ctx_collection, config, tdi); self.make_methods(metadata, config, ctx_collection, tdi); + if !t.is_interface() { + self.create_size_padding(metadata, tdi); + } + if let Some(func) = metadata.custom_type_handler.get(&tdi) { func(self.get_mut_cpp_type()) } } - fn make_methods( + // fn make_generic_constraints( + // &mut self, + // metadata: &Metadata, + // config: &GenerationConfig, + // ctx_collection: &CppContextCollection, + // tdi: TypeDefinitionIndex, + // ) { + // let t = Self::get_type_definition(metadata, tdi); + + // if !t.generic_container_index.is_valid() { + // return; + // } + + // let generic_class = metadata.metadata_registration.generic_classes.iter().find(|t| t.); + // metadata.metadata_registration.generic_insts.get(generic_class.unwrap().context.class_inst_idx.unwrap()) + + // let generics = t.generic_container(metadata.metadata); + + // let generic_constraints: Vec> = generics + // .generic_parameters(metadata.metadata) + // .iter() + // .map(|p| p.constraints(metadata.metadata)) + // .map(|c| { + // c.iter() + // .map(|ti| { + // self.cppify_name_il2cpp( + // ctx_collection, + // metadata, + // metadata + // .metadata_registration + // .types + // .get(*ti as usize) + // .unwrap(), + // true, + // ) + // }) + // .filter(|l| !l.is_empty()) + // .collect() + // }) + // .filter(|l: &Vec| !l.is_empty()) + // .collect(); + // let cpp_type = self.get_mut_cpp_type(); + // } + + fn make_generics_args( &mut self, metadata: &Metadata, - config: &GenerationConfig, ctx_collection: &CppContextCollection, tdi: TypeDefinitionIndex, ) { let cpp_type = self.get_mut_cpp_type(); - let t = Self::get_type_definition(metadata, tdi); - - // default ctor - if t.is_value_type() { - let fields = t - .fields(metadata.metadata) - .iter() - .map(|field| { - let f_type = metadata - .metadata_registration - .types - .get(field.type_index as usize) - .unwrap(); - - let cpp_name = - cpp_type.cppify_name_il2cpp(ctx_collection, metadata, f_type, false); - CppParam { - name: field.name(metadata.metadata).to_string(), - ty: cpp_name, - modifiers: "".to_string(), - def_value: Some("{}".to_string()), - } - }) - .collect_vec(); - cpp_type - .declarations - .push(CppMember::ConstructorImpl(CppConstructorImpl { - holder_cpp_ty_name: cpp_type.cpp_name().clone(), - parameters: fields, - is_constexpr: true, - template: CppTemplate::default(), - })); + if cpp_type.generic_instantiations_args_types.is_none() { + return; } - // Then, handle methods - if t.method_count > 0 { - // Write comment for methods - cpp_type - .declarations - .push(CppMember::Comment(CppCommentedString { - data: "".to_string(), - comment: Some("Methods".to_string()), - })); - - cpp_type.declarations.reserve(5 * t.method_count as usize); - - // Then, for each method, write it out - for (i, method) in t.methods(metadata.metadata).iter().enumerate() { - let method_index = MethodIndex::new(t.method_start.index() + i as u32); - let m_name = method.name(metadata.metadata); - - // Skip class/static constructor - // if method.is_special_name() - // && !(m_name.starts_with("get") || m_name.starts_with("set") || m_name == (".ctor")) - if m_name == ".cctor" { - // println!("Skipping {}", m_name); - continue; - } + let generic_instantiations_args_types = + cpp_type.generic_instantiations_args_types.clone().unwrap(); - let m_ret_type = metadata - .metadata_registration - .types - .get(method.return_type as usize) - .unwrap(); - let mut m_params: Vec = - Vec::with_capacity(method.parameter_count as usize); + let td = &metadata.metadata.global_metadata.type_definitions[tdi]; + let _ty = &metadata.metadata_registration.types[td.byval_type_index as usize]; + let generic_container = td.generic_container(metadata.metadata); - for (pi, param) in method.parameters(metadata.metadata).iter().enumerate() { - let param_index = - ParameterIndex::new(method.parameter_start.index() + pi as u32); - let param_type = metadata - .metadata_registration - .types - .get(param.type_index as usize) - .unwrap(); + let mut template_args: Vec<(String, String)> = vec![]; - let param_cpp_name = - cpp_type.cppify_name_il2cpp(ctx_collection, metadata, param_type, false); + let generic_instantiation_args: Vec = generic_instantiations_args_types + .iter() + .enumerate() + .map(|(gen_param_idx, u)| { + let t = metadata.metadata_registration.types.get(*u).unwrap(); - let def_value = Self::param_default_value(metadata, param_index); + let gen_param = generic_container + .generic_parameters(metadata.metadata) + .iter() + .find(|p| p.num as usize == gen_param_idx) + .expect("No generic parameter found for this index!"); - m_params.push(CppParam { - name: param.name(metadata.metadata).to_string(), - def_value, - ty: param_cpp_name, - modifiers: if param_type.is_byref() { - String::from("byref") - } else { - String::from("") - }, - }); - } + (t, gen_param) + }) + .map(|(t, gen_param)| { + let gen_name = gen_param.name(metadata.metadata).to_string(); - let generics = if method.generic_container_index.is_valid() { - method - .generic_container(metadata.metadata) - .unwrap() - .generic_parameters(metadata.metadata) - .iter() - .map(|param| param.name(metadata.metadata).to_string()) - .collect_vec() - } else { - vec![] - }; + parse_generic_arg( + t, + gen_name, + cpp_type, + ctx_collection, + metadata, + &mut template_args, + ) + }) + .map(|n| n.combine_all()) + .collect(); - let template = CppTemplate { names: generics }; + // Handle nested types + // Assumes these nested types exist, + // which are created in the make_generic type func + // TODO: Base off a CppType the alias path - // Need to include this type - let m_ret_cpp_type_name = - cpp_type.cppify_name_il2cpp(ctx_collection, metadata, m_ret_type, false); + // Add template constraint + // get all args with constraints + if !template_args.is_empty() { + cpp_type.cpp_template = Some(CppTemplate { + names: template_args, + }); + } - let method_calc = &metadata.method_calculations[&method_index]; + cpp_type.cpp_name_components.generics = + Some(generic_instantiation_args.into_iter().collect_vec()); + } - if m_name == ".ctor" && !t.is_value_type() { - cpp_type - .implementations - .push(CppMember::ConstructorImpl(CppConstructorImpl { - holder_cpp_ty_name: cpp_type.cpp_name().clone(), - parameters: m_params.clone(), - is_constexpr: false, - template: template.clone(), - })); - cpp_type - .declarations - .push(CppMember::ConstructorDecl(CppConstructorDecl { - ty: cpp_type.formatted_complete_cpp_name().clone(), - parameters: m_params.clone(), - template: template.clone(), - })); - } + fn make_methods( + &mut self, + metadata: &Metadata, + config: &GenerationConfig, + ctx_collection: &CppContextCollection, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); - let declaring_type = method.declaring_type(metadata.metadata); - let tag = TypeData::TypeDefinitionIndex(method.declaring_type); - let declaring_cpp_type: Option<&CppType> = if tag == cpp_type.self_tag { - Some(cpp_type) - } else { - ctx_collection.get_cpp_type(tag) - }; + // Then, handle methods + if t.method_count > 0 { + // 2 because each method gets a method struct and method decl + // a constructor will add an additional one for each + cpp_type + .declarations + .reserve(2 * (t.method_count as usize + 1)); + cpp_type + .implementations + .reserve(t.method_count as usize + 1); - cpp_type - .nonmember_implementations - .push(Rc::new(CppMethodSizeStruct { - ret_ty: m_ret_cpp_type_name.clone(), - cpp_method_name: config.name_cpp(m_name), - complete_type_name: cpp_type.formatted_complete_cpp_name().clone(), - instance: !method.is_static_method(), - params: m_params.clone(), - template: template.clone(), - method_data: CppMethodData { - addrs: method_calc.addrs, - estimated_size: method_calc.estimated_size, - }, - interface_clazz_of: declaring_cpp_type - .map(|d| d.classof_cpp_name()) - .unwrap_or_else(|| format!("Bad stuff happened {declaring_type:?}")), - is_final: method.is_final_method(), - slot: if method.slot != u16::MAX { - Some(method.slot) - } else { - None - }, - })); - cpp_type - .implementations - .push(CppMember::MethodImpl(CppMethodImpl { - cpp_method_name: config.name_cpp(m_name), - cs_method_name: m_name.to_string(), - holder_cpp_namespaze: cpp_type.cpp_namespace().to_string(), - holder_cpp_name: match &cpp_type.parent_ty_cpp_name { - Some(p) => format!("{p}::{}", cpp_type.cpp_name().clone()), - None => cpp_type.cpp_name().clone(), - }, - return_type: m_ret_cpp_type_name.clone(), - parameters: m_params.clone(), - instance: !method.is_static_method(), - suffix_modifiers: Default::default(), - prefix_modifiers: Default::default(), - template: template.clone(), - })); - cpp_type - .declarations - .push(CppMember::MethodDecl(CppMethodDecl { - cpp_name: config.name_cpp(m_name), - return_type: m_ret_cpp_type_name, - parameters: m_params, - instance: !method.is_static_method(), - prefix_modifiers: Default::default(), - suffix_modifiers: Default::default(), - method_data: CppMethodData { - addrs: method_calc.addrs, - estimated_size: method_calc.estimated_size, - }, - is_virtual: method.is_virtual_method() && !method.is_final_method(), - template, - })); + // Then, for each method, write it out + for (i, _method) in t.methods(metadata.metadata).iter().enumerate() { + let method_index = MethodIndex::new(t.method_start.index() + i as u32); + self.create_method(t, method_index, metadata, ctx_collection, config, false); } } } @@ -385,194 +525,2934 @@ pub trait CSType: Sized { &mut self, metadata: &Metadata, ctx_collection: &CppContextCollection, + config: &GenerationConfig, tdi: TypeDefinitionIndex, ) { let cpp_type = self.get_mut_cpp_type(); let t = Self::get_type_definition(metadata, tdi); - // Then, handle fields + // if no fields, skip if t.field_count == 0 { return; } - // Write comment for fields - cpp_type - .declarations - .push(CppMember::Comment(CppCommentedString { - data: "".to_string(), - comment: Some("Fields".to_string()), - })); - // Then, for each field, write it out - cpp_type.declarations.reserve(t.field_count as usize); - for (i, field) in t.fields(metadata.metadata).iter().enumerate() { - let field_index = FieldIndex::new(t.field_start.index() + i as u32); - let f_name = field.name(metadata.metadata); - let f_offset = metadata - .metadata_registration - .field_offsets - .as_ref() - .unwrap()[tdi.index() as usize][i]; + + let field_offsets = &metadata + .metadata_registration + .field_offsets + .as_ref() + .unwrap()[tdi.index() as usize]; + + let mut offsets = Vec::::new(); + if let Some(sz) = offsets::get_size_of_type_table(metadata, tdi) { + if sz.instance_size == 0 { + // At this point we need to compute the offsets + debug!( + "Computing offsets for TDI: {:?}, as it has a size of 0", + tdi + ); + let _resulting_size = offsets::layout_fields( + metadata, + t, + tdi, + cpp_type.generic_instantiations_args_types.as_ref(), + Some(&mut offsets), + false, + ); + } + } + let mut offset_iter = offsets.iter(); + + let get_offset = |field: &Il2CppFieldDefinition, i: usize, iter: &mut Iter| { let f_type = metadata .metadata_registration .types .get(field.type_index as usize) .unwrap(); + let f_name = field.name(metadata.metadata); - let _f_type_data = f_type.data; - - let cpp_name = cpp_type.cppify_name_il2cpp(ctx_collection, metadata, f_type, false); + match f_type.is_static() || f_type.is_constant() { + // return u32::MAX for static fields as an "invalid offset" value + true => None, + false => Some({ + // If we have a hotfix offset, use that instead + // We can safely assume this always returns None even if we "next" past the end + let offset = if let Some(computed_offset) = iter.next() { + *computed_offset + } else { + field_offsets[i] + }; - let def_value = Self::field_default_value(metadata, field_index); + if offset < metadata.object_size() as u32 { + warn!("Field {f_name} ({offset:x}) of {} is smaller than object size {:x} is value type {}", + t.full_name(metadata.metadata, true), + metadata.object_size(), + t.is_value_type() || t.is_enum_type() + ); + } - // Need to include this type - cpp_type.declarations.push(CppMember::Field(CppField { - name: f_name.to_owned(), - ty: cpp_name, - offset: f_offset, - instance: !f_type.is_static() && !f_type.is_const(), - readonly: f_type.is_const(), - classof_call: cpp_type.classof_cpp_name(), - literal_value: def_value, - use_wrapper: !t.is_value_type(), - })); - } - } + // TODO: Is the offset supposed to be smaller than object size for fixups? + match t.is_value_type() && offset >= metadata.object_size() as u32 { + true => { + // value type fixup + offset - metadata.object_size() as u32 + } + false => offset, + } + }), + } + }; - fn make_parents( - &mut self, - metadata: &Metadata, - ctx_collection: &CppContextCollection, - tdi: TypeDefinitionIndex, - ) { - let cpp_type = self.get_mut_cpp_type(); - let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + let get_size = |field: &Il2CppFieldDefinition, gen_args: Option<&Vec>| { + let f_type = metadata + .metadata_registration + .types + .get(field.type_index as usize) + .unwrap(); - let ns = t.namespace(metadata.metadata); - let name = t.name(metadata.metadata); + let sa = offsets::get_il2cpptype_sa(metadata, f_type, gen_args); - if t.parent_index == u32::MAX { - // TYPE_ATTRIBUTE_INTERFACE = 0x00000020 - if t.flags & TYPE_ATTRIBUTE_INTERFACE == 0 { - println!("Skipping type: {ns}::{name} because it has parent index: {} and is not an interface!", t.parent_index); - } - } else if let Some(parent_type) = metadata - .metadata_registration - .types - .get(t.parent_index as usize) - { - // We have a parent, lets do something with it - let inherit_type = - cpp_type.cppify_name_il2cpp(ctx_collection, metadata, parent_type, true); - cpp_type.inherit.push(inherit_type); - } else { - panic!("NO PARENT! But valid index found: {}", t.parent_index); - } + sa.size - for &interface_index in t.interfaces(metadata.metadata) { - let int_ty = &metadata.metadata_registration.types[interface_index as usize]; + // // TODO: is valuetype even a good way of checking whether we should be size of pointer? + // match f_type.valuetype { + // false => metadata.pointer_size as u32, + // true => { + // match f_type.data { + // TypeData::TypeDefinitionIndex(f_tdi) => { + // let f_td = Self::get_type_definition(metadata, f_tdi); - // We have a parent, lets do something with it - let inherit_type = cpp_type.cppify_name_il2cpp(ctx_collection, metadata, int_ty, true); - cpp_type.inherit.push(inherit_type); - } - } + // }, + // TypeData::GenericClassIndex(class_index) => { - fn make_nested_types( - &mut self, - metadata: &Metadata, - config: &GenerationConfig, - tdi: TypeDefinitionIndex, - ) { - let cpp_type = self.get_mut_cpp_type(); - let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + // } + // } + // let f_tag = CppTypeTag::from_type_data(f_type.data, metadata.metadata); + // let f_tdi = f_tag.get_tdi(); + // let f_td = Self::get_type_definition(metadata, f_tdi); - if t.nested_type_count == 0 { - return; - } + // if let Some(sz) = offsets::get_size_of_type_table(metadata, f_tdi) { + // if sz.instance_size == 0 { + // // At this point we need to compute the offsets + // debug!( + // "Computing offsets for TDI: {:?}, as it has a size of 0", + // tdi + // ); + // let _resulting_size = offsets::layout_fields( + // metadata, + // f_td, + // f_tdi, + // gen_args.as_ref(), + // None, + // ); - let mut nested_types: Vec = Vec::with_capacity(t.nested_type_count as usize); + // // TODO: check for VT fixup? + // if f_td.is_value_type() { + // _resulting_size.size as u32 - metadata.object_size() as u32 + // } else { + // _resulting_size.size as u32 + // } + // } else { + // sz.instance_size - metadata.object_size() as u32 + // } + // } else { + // 0 + // } + // } + // } + }; - for &nested_type_index in t.nested_types(metadata.metadata) { - let nt_ty = &metadata.metadata.global_metadata.type_definitions[nested_type_index]; + let fields = t + .fields(metadata.metadata) + .iter() + .enumerate() + .filter_map(|(i, field)| { + let f_type = metadata + .metadata_registration + .types + .get(field.type_index as usize) + .unwrap(); + + let field_index = FieldIndex::new(t.field_start.index() + i as u32); + let f_name = field.name(metadata.metadata); + + let f_cpp_name = config.name_cpp_plus(f_name, &[cpp_type.cpp_name().as_str()]); + + let f_offset = get_offset(field, i, &mut offset_iter); + + // calculate / fetch the field size + let f_size = if let Some(generics) = &cpp_type.generic_instantiations_args_types { + get_size(field, Some(generics)) + } else { + get_size(field, None) + }; + + if let TypeData::TypeDefinitionIndex(field_tdi) = f_type.data + && metadata.blacklisted_types.contains(&field_tdi) + { + if !cpp_type.is_value_type && !cpp_type.is_enum_type { + return None; + } + warn!("Value type uses {tdi:?} which is blacklisted! TODO"); + } + + // Var types are default pointers so we need to get the name component's pointer bool + let (field_ty_cpp_name, field_is_pointer) = + if f_type.is_constant() && f_type.ty == Il2CppTypeEnum::String { + ("::ConstString".to_string(), false) + } else { + let include_depth = match f_type.valuetype { + true => usize::MAX, + false => 0, + }; + + let field_name_components = cpp_type.cppify_name_il2cpp( + ctx_collection, + metadata, + f_type, + include_depth, + ); + + ( + field_name_components.combine_all(), + field_name_components.is_pointer, + ) + }; + + // TODO: Check a flag to look for default values to speed this up + let def_value = Self::field_default_value(metadata, field_index); + + assert!(def_value.is_none() || (def_value.is_some() && f_type.is_param_optional())); + + let cpp_field_decl = CppFieldDecl { + cpp_name: f_cpp_name, + field_ty: field_ty_cpp_name, + offset: f_offset.unwrap_or(u32::MAX), + 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))), + value: def_value, + const_expr: false, + is_private: false, + }; + + Some(FieldInfo { + cpp_field: cpp_field_decl, + field, + field_type: f_type, + is_constant: f_type.is_constant(), + is_static: f_type.is_static(), + is_pointer: field_is_pointer, + offset: f_offset, + size: f_size, + }) + }) + .collect_vec(); + + for field_info in fields.iter() { + let f_type = field_info.field_type; + + // only push def dependency if valuetype field & not a primitive builtin + if f_type.valuetype && !f_type.ty.is_primitive_builtin() { + let field_cpp_tag: CppTypeTag = + CppTypeTag::from_type_data(f_type.data, metadata.metadata); + let field_cpp_td_tag: CppTypeTag = field_cpp_tag.get_tdi().into(); + let field_cpp_type = ctx_collection.get_cpp_type(field_cpp_td_tag); + + if field_cpp_type.is_some() { + let field_cpp_context = ctx_collection + .get_context(field_cpp_td_tag) + .expect("No context for cpp value type"); + + cpp_type.requirements.add_def_include( + field_cpp_type, + CppInclude::new_context_typedef(field_cpp_context), + ); + + cpp_type.requirements.add_impl_include( + field_cpp_type, + CppInclude::new_context_typeimpl(field_cpp_context), + ); + } + } + } + + if t.is_value_type() || t.is_enum_type() { + cpp_type.handle_valuetype_fields(&fields, ctx_collection, metadata, tdi); + } else { + cpp_type.handle_referencetype_fields(&fields, ctx_collection, metadata, tdi); + } + + cpp_type.handle_static_fields(&fields, metadata, tdi); + cpp_type.handle_const_fields(&fields, ctx_collection, metadata, tdi); + } + + fn handle_static_fields( + &mut self, + fields: &[FieldInfo], + metadata: &Metadata, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + // we want only static fields + // we ignore constants + for field_info in fields.iter().filter(|f| f.is_static && !f.is_constant) { + let f_type = field_info.field_type; + let f_name = field_info.field.name(metadata.metadata); + let f_offset = field_info.offset.unwrap_or(u32::MAX); + let f_size = field_info.size; + let field_ty_cpp_name = &field_info.cpp_field.field_ty; + + // 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 = field_info.cpp_field.cpp_name.clone(); + + let klass_resolver = cpp_type.classof_cpp_name(); + + let getter_call = + format!("return {CORDL_METHOD_HELPER_NAMESPACE}::getStaticField<{field_ty_cpp_name}, \"{f_name}\", {klass_resolver}>();"); + + let setter_var_name = "value"; + let setter_call = + format!("{CORDL_METHOD_HELPER_NAMESPACE}::setStaticField<{field_ty_cpp_name}, \"{f_name}\", {klass_resolver}>(std::forward<{field_ty_cpp_name}>({setter_var_name}));"); + + // don't get a template that has no names + let useful_template = + cpp_type + .cpp_template + .clone() + .and_then(|t| match t.names.is_empty() { + true => None, + false => Some(t), + }); + + let getter_name = format!("getStaticF_{}", f_cpp_name); + let setter_name = format!("setStaticF_{}", f_cpp_name); + + let get_return_type = field_ty_cpp_name.clone(); + + let getter_decl = CppMethodDecl { + cpp_name: getter_name.clone(), + instance: false, + return_type: get_return_type, + + brief: None, + body: None, // TODO: + // Const if instance for now + is_const: false, + is_constexpr: !f_type.is_static() || f_type.is_constant(), + is_inline: true, + is_virtual: false, + is_operator: false, + is_no_except: false, // TODO: + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let setter_decl = CppMethodDecl { + cpp_name: setter_name, + instance: false, + return_type: "void".to_string(), + + brief: None, + body: None, //TODO: + is_const: false, // TODO: readonly fields? + is_constexpr: !f_type.is_static() || f_type.is_constant(), + is_inline: true, + is_virtual: false, + is_operator: false, + is_no_except: false, // TODO: + parameters: vec![CppParam { + def_value: None, + modifiers: "".to_string(), + name: setter_var_name.to_string(), + ty: field_ty_cpp_name.clone(), + }], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let getter_impl = CppMethodImpl { + body: vec![Arc::new(CppLine::make(getter_call.clone()))], + declaring_cpp_full_name: cpp_type + .cpp_name_components + .remove_pointer() + .combine_all(), + template: useful_template.clone(), + + ..getter_decl.clone().into() + }; + + let setter_impl = CppMethodImpl { + body: vec![Arc::new(CppLine::make(setter_call))], + declaring_cpp_full_name: cpp_type + .cpp_name_components + .remove_pointer() + .combine_all(), + template: useful_template.clone(), + + ..setter_decl.clone().into() + }; + + // instance fields on a ref type should declare a cpp property + + let prop_decl = CppPropertyDecl { + cpp_name: f_cpp_name, + prop_ty: field_ty_cpp_name.clone(), + instance: !f_type.is_static() && !f_type.is_constant(), + getter: getter_decl.cpp_name.clone().into(), + setter: setter_decl.cpp_name.clone().into(), + indexable: false, + brief_comment: Some(format!( + "Field {f_name}, offset 0x{f_offset:x}, size 0x{f_size:x} " + )), + }; + + // only push accessors if declaring ref type, or if static field + cpp_type + .declarations + .push(CppMember::Property(prop_decl).into()); + + // decl + cpp_type + .declarations + .push(CppMember::MethodDecl(setter_decl).into()); + + cpp_type + .declarations + .push(CppMember::MethodDecl(getter_decl).into()); + + // impl + cpp_type + .implementations + .push(CppMember::MethodImpl(setter_impl).into()); + + cpp_type + .implementations + .push(CppMember::MethodImpl(getter_impl).into()); + } + } + fn handle_const_fields( + &mut self, + fields: &[FieldInfo], + ctx_collection: &CppContextCollection, + metadata: &Metadata, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + let declaring_cpp_template = if cpp_type + .cpp_template + .as_ref() + .is_some_and(|t| !t.names.is_empty()) + { + cpp_type.cpp_template.clone() + } else { + None + }; + + for field_info in fields.iter().filter(|f| f.is_constant) { + let f_type = field_info.field_type; + let f_name = field_info.field.name(metadata.metadata); + let f_offset = field_info.offset.unwrap_or(u32::MAX); + let f_size = field_info.size; + + let def_value = field_info.cpp_field.value.as_ref(); + + let def_value = def_value.expect("Constant with no default value?"); + + match f_type.ty.is_primitive_builtin() { + false => { + // other type + let field_decl = CppFieldDecl { + instance: false, + readonly: f_type.is_constant(), + value: None, + const_expr: false, + brief_comment: Some(format!("Field {f_name} value: {def_value}")), + ..field_info.cpp_field.clone() + }; + let field_impl = CppFieldImpl { + value: def_value.clone(), + const_expr: true, + declaring_type: cpp_type.cpp_name_components.remove_pointer().combine_all(), + declaring_type_template: declaring_cpp_template.clone(), + ..field_decl.clone().into() + }; + + // get enum type to include impl + // this is needed since the enum constructor is not defined + // in the declaration + // TODO: Make enum ctors inline defined + if f_type.valuetype && f_type.ty == Il2CppTypeEnum::Valuetype { + let field_cpp_tag: CppTypeTag = + CppTypeTag::from_type_data(f_type.data, metadata.metadata); + let field_cpp_td_tag: CppTypeTag = field_cpp_tag.get_tdi().into(); + let field_cpp_type = ctx_collection.get_cpp_type(field_cpp_td_tag); + + if field_cpp_type.is_some_and(|f| f.is_enum_type) { + let field_cpp_context = ctx_collection + .get_context(field_cpp_td_tag) + .expect("No context for cpp enum type"); + + cpp_type.requirements.add_impl_include( + field_cpp_type, + CppInclude::new_context_typeimpl(field_cpp_context), + ); + } + } + + cpp_type + .declarations + .push(CppMember::FieldDecl(field_decl).into()); + cpp_type + .implementations + .push(CppMember::FieldImpl(field_impl).into()); + } + true => { + // primitive type + let field_decl = CppFieldDecl { + instance: false, + const_expr: true, + readonly: f_type.is_constant(), + + brief_comment: Some(format!( + "Field {f_name} offset 0x{f_offset:x} size 0x{f_size:x}" + )), + value: Some(def_value.clone()), + ..field_info.cpp_field.clone() + }; + + cpp_type + .declarations + .push(CppMember::FieldDecl(field_decl).into()); + } + } + } + } + + fn handle_instance_fields( + &mut self, + fields: &[FieldInfo], + ctx_collection: &CppContextCollection, + metadata: &Metadata, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + let instance_field_decls = fields + .iter() + .filter(|f| f.offset.is_some() && !f.is_static && !f.is_constant) + .cloned() + .collect_vec(); + + let property_exists = |to_find: &str| { + cpp_type.declarations.iter().any(|d| match d.as_ref() { + CppMember::Property(p) => p.cpp_name == to_find, + _ => false, + }) + }; + + let resulting_fields = instance_field_decls + .into_iter() + .map(|d| { + let mut f = d.cpp_field; + if property_exists(&f.cpp_name) { + f.cpp_name = format!("_cordl_{}", &f.cpp_name); + + // make private if a property with this name exists + f.is_private = true; + } + + FieldInfo { cpp_field: f, ..d } + }) + .collect_vec(); + + // 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 u = Self::pack_fields_into_single_union(resulting_fields); + cpp_type.declarations.push(CppMember::NestedUnion(u).into()); + } else { + resulting_fields + .into_iter() + .map(|member| CppMember::FieldDecl(member.cpp_field)) + .for_each(|member| cpp_type.declarations.push(member.into())); + }; + } + + fn fixup_backing_field( + fieldname: &str + ) -> String { + format!("{CORDL_ACCESSOR_FIELD_PREFIX}{fieldname}") + } + + fn handle_valuetype_fields( + &mut self, + fields: &[FieldInfo], + ctx_collection: &CppContextCollection, + metadata: &Metadata, + tdi: TypeDefinitionIndex, + ) { + // Value types only need getter fixes for explicit layout types + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + // if no fields, skip + if t.field_count == 0 { + return; + } + + // instance fields for explicit layout value types are special + if t.is_explicit_layout() { + for field_info in fields.iter().filter(|f| !f.is_constant && !f.is_static) { + // don't get a template that has no names + let template = + cpp_type + .cpp_template + .clone() + .and_then(|t| match t.names.is_empty() { + true => None, + false => Some(t), + }); + + + let declaring_cpp_full_name = cpp_type + .cpp_name_components + .remove_pointer() + .combine_all(); + + let prop = Self::prop_decl_from_fieldinfo(metadata, field_info); + let (accessor_decls, accessor_impls) = Self::prop_methods_from_fieldinfo(field_info, template, declaring_cpp_full_name, false); + + cpp_type.declarations.push(CppMember::Property(prop).into()); + + accessor_decls + .into_iter() + .for_each(|method| { + cpp_type.declarations.push(CppMember::MethodDecl(method).into()); + }); + + accessor_impls + .into_iter() + .for_each(|method| { + cpp_type.implementations.push(CppMember::MethodImpl(method).into()); + }); + } + + let backing_fields = fields + .iter() + .cloned() + .map(|mut f| { + f.cpp_field.cpp_name = Self::fixup_backing_field(&f.cpp_field.cpp_name); + f + }) + .collect_vec(); + + cpp_type.handle_instance_fields(&backing_fields, ctx_collection, metadata, tdi); + } else { + cpp_type.handle_instance_fields(fields, ctx_collection, metadata, tdi); + } + } + + // create prop and field declaration from passed field info + fn prop_decl_from_fieldinfo ( + metadata: &Metadata, + field_info: &FieldInfo, + ) -> CppPropertyDecl { + if field_info.is_static { + panic!("Can't turn static fields into declspec properties!"); + } + + let f_name = field_info.field.name(metadata.metadata); + let f_offset = field_info.offset.unwrap_or(u32::MAX); + let f_size = field_info.size; + let field_ty_cpp_name = &field_info.cpp_field.field_ty; + + let f_cpp_name = &field_info.cpp_field.cpp_name; + + let getter_name = format!("__get_{}", f_cpp_name); + let setter_name = format!("__set_{}", f_cpp_name); + + CppPropertyDecl { + cpp_name: f_cpp_name.clone(), + prop_ty: field_ty_cpp_name.clone(), + instance: !field_info.is_static, + getter: Some(getter_name), + setter: Some(setter_name), + indexable: false, + brief_comment: Some(format!( + "Field {f_name}, offset 0x{f_offset:x}, size 0x{f_size:x} " + )), + } + } + + fn prop_methods_from_fieldinfo( + field_info: &FieldInfo, + template: Option, + declaring_cpp_name: String, + declaring_is_ref: bool + ) -> (Vec, Vec) { + + let f_type = field_info.field_type; + let field_ty_cpp_name = &field_info.cpp_field.field_ty; + + let f_cpp_name = &field_info.cpp_field.cpp_name; + let cordl_field_name = Self::fixup_backing_field(f_cpp_name); + let field_access = format!("this->{cordl_field_name}"); + + let getter_name = format!("__get_{}", f_cpp_name); + let setter_name = format!("__set_{}", f_cpp_name); + + let (get_return_type, const_get_return_type) = match field_info.is_pointer { + // Var types are default pointers + true => ( + field_ty_cpp_name.clone(), + format!("::cordl_internals::to_const_pointer<{field_ty_cpp_name}> const",), + ), + false => ( + field_ty_cpp_name.clone(), + format!("{field_ty_cpp_name} const"), + ), + }; + + // field accessors emit as ref because they are fields, you should be able to access them the same + let (get_return_type, const_get_return_type) = ( + format!("{get_return_type}&"), + format!("{const_get_return_type}&"), + ); + + // for ref types we emit an instance null check that is dependent on a compile time define, + // that way we can prevent nullptr access and instead throw, if the user wants this + // technically "this" should never ever be null, but in native modding this can happen + let instance_null_check = match declaring_is_ref { + true => Some("CORDL_FIELD_NULL_CHECK(static_cast(this));"), + false => None + }; + + let getter_call = format!("return {field_access};"); + let setter_var_name = "value"; + // if the declaring type is a value type, we should not use wbarrier + let setter_call = match !f_type.valuetype && declaring_is_ref { + // ref type field write on a ref type + true => { + format!("il2cpp_functions::gc_wbarrier_set_field(this, static_cast(static_cast(&{field_access})), cordl_internals::convert(std::forward({setter_var_name})));") + } + false => { + format!("{field_access} = {setter_var_name};") + } + }; + + let getter_decl = CppMethodDecl { + cpp_name: getter_name.clone(), + instance: true, + return_type: get_return_type, + + brief: None, + body: None, // TODO: + // Const if instance for now + is_const: false, + is_constexpr: !f_type.is_static() || f_type.is_constant(), + is_inline: true, + is_virtual: false, + is_operator: false, + is_no_except: false, // TODO: + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let const_getter_decl = CppMethodDecl { + cpp_name: getter_name, + instance: true, + return_type: const_get_return_type, + + brief: None, + body: None, // TODO: + // Const if instance for now + is_const: true, + is_constexpr: !f_type.is_static() || f_type.is_constant(), + is_inline: true, + is_virtual: false, + is_operator: false, + is_no_except: false, // TODO: + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let setter_decl = CppMethodDecl { + cpp_name: setter_name, + instance: true, + return_type: "void".to_string(), + + brief: None, + body: None, //TODO: + is_const: false, // TODO: readonly fields? + is_constexpr: !f_type.is_static() || f_type.is_constant(), + is_inline: true, + is_virtual: false, + is_operator: false, + is_no_except: false, // TODO: + parameters: vec![CppParam { + def_value: None, + modifiers: "".to_string(), + name: setter_var_name.to_string(), + ty: field_ty_cpp_name.clone(), + }], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + // construct getter and setter bodies + let getter_body: Vec> = if let Some(instance_null_check) = instance_null_check { + vec![ + Arc::new(CppLine::make(instance_null_check.into())), + Arc::new(CppLine::make(getter_call)), + ] + } else { + vec![Arc::new(CppLine::make(getter_call))] + }; + + let setter_body: Vec> = if let Some(instance_null_check) = instance_null_check { + vec![ + Arc::new(CppLine::make(instance_null_check.into())), + Arc::new(CppLine::make(setter_call)), + ] + } else { + vec![Arc::new(CppLine::make(setter_call))] + }; + + let getter_impl = CppMethodImpl { + body: getter_body.clone(), + declaring_cpp_full_name: declaring_cpp_name.clone(), + template: template.clone(), + + ..getter_decl.clone().into() + }; + + let const_getter_impl = CppMethodImpl { + body: getter_body, + declaring_cpp_full_name: declaring_cpp_name.clone(), + template: template.clone(), + + ..const_getter_decl.clone().into() + }; + + let setter_impl = CppMethodImpl { + body: setter_body, + declaring_cpp_full_name: declaring_cpp_name.clone(), + template: template.clone(), + + ..setter_decl.clone().into() + }; + + (vec![ + getter_decl, + const_getter_decl, + setter_decl, + ], vec![ + getter_impl, + const_getter_impl, + setter_impl, + ]) + } + + fn handle_referencetype_fields( + &mut self, + fields: &[FieldInfo], + ctx_collection: &CppContextCollection, + metadata: &Metadata, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + if t.is_explicit_layout() { + warn!("Reference type with explicit layout: {}", cpp_type.cpp_name_components.combine_all()); + } + + // if no fields, skip + if t.field_count == 0 { + return; + } + + for field_info in fields.iter().filter(|f| !f.is_constant && !f.is_static) { + // don't get a template that has no names + let template = + cpp_type + .cpp_template + .clone() + .and_then(|t| match t.names.is_empty() { + true => None, + false => Some(t), + }); + + + let declaring_cpp_full_name = cpp_type + .cpp_name_components + .remove_pointer() + .combine_all(); + + let prop = Self::prop_decl_from_fieldinfo(metadata, field_info); + let (accessor_decls, accessor_impls) = Self::prop_methods_from_fieldinfo(field_info, template, declaring_cpp_full_name, true); + + cpp_type.declarations.push(CppMember::Property(prop).into()); + + accessor_decls + .into_iter() + .for_each(|method| { + cpp_type.declarations.push(CppMember::MethodDecl(method).into()); + }); + + accessor_impls + .into_iter() + .for_each(|method| { + cpp_type.implementations.push(CppMember::MethodImpl(method).into()); + }); + } + + let backing_fields = fields + .iter() + .cloned() + .map(|mut f| { + f.cpp_field.cpp_name = Self::fixup_backing_field(&f.cpp_field.cpp_name); + f + }) + .collect_vec(); + + cpp_type.handle_instance_fields(&backing_fields, ctx_collection, metadata, tdi); + } + + fn field_collision_check(instance_fields: &[FieldInfo]) -> 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 + fn pack_fields_into_single_union(fields: Vec) -> 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() + .map(|field| { + let structs = Self::field_into_offset_structs(min_offset, field); + + vec![structs.0, structs.1] + }) + .flat_map(|v| v.into_iter()) + .collect_vec(); + + let declarations = packed_structs + .into_iter() + .map(|s| CppMember::NestedStruct(s).into()) + .collect_vec(); + + CppNestedUnion { + brief_comment: Some("Explicitly laid out type with union based offsets".into()), + declarations, + offset: min_offset, + is_private: true, + } + } + + fn field_into_offset_structs( + min_offset: u32, + field: FieldInfo, + ) -> (CppNestedStruct, CppNestedStruct) { + // 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 Some(actual_offset) = &field.offset else { + panic!("don't call field_into_offset_structs with non instance fields!") + }; + + let padding = actual_offset; + + let packed_padding_cpp_name = + format!("{}_padding[0x{padding:x}]", field.cpp_field.cpp_name); + let alignment_padding_cpp_name = format!( + "{}_padding_forAlignment[0x{padding:x}]", + field.cpp_field.cpp_name + ); + let alignment_cpp_name = format!("{}_forAlignment", field.cpp_field.cpp_name); + + let packed_padding_field = CppFieldDecl { + brief_comment: Some(format!("Padding field 0x{padding:x}")), + const_expr: false, + cpp_name: packed_padding_cpp_name, + field_ty: "uint8_t".into(), + offset: *actual_offset, + instance: true, + is_private: false, + readonly: false, + value: None, + }; + + let alignment_padding_field = CppFieldDecl { + 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: *actual_offset, + instance: true, + is_private: false, + readonly: false, + value: None, + }; + + let alignment_field = CppFieldDecl { + cpp_name: alignment_cpp_name, + is_private: false, + ..field.cpp_field.clone() + }; + + let packed_field = CppFieldDecl { + is_private: false, + ..field.cpp_field + }; + + let packed_struct = CppNestedStruct { + declaring_name: "".into(), + base_type: None, + declarations: vec![ + CppMember::FieldDecl(packed_padding_field).into(), + CppMember::FieldDecl(packed_field).into(), + ], + brief_comment: None, + is_class: false, + is_enum: false, + is_private: false, + packing: Some(1), + }; + + let alignment_struct = CppNestedStruct { + declaring_name: "".into(), + base_type: None, + declarations: vec![ + CppMember::FieldDecl(alignment_padding_field).into(), + CppMember::FieldDecl(alignment_field).into(), + ], + brief_comment: None, + is_class: false, + is_enum: false, + is_private: false, + packing: None, + }; + + (packed_struct, alignment_struct) + } + + /// generates the fields for the value type or reference type\ + /// handles unions + fn make_or_unionize_fields(instance_fields: &[FieldInfo]) -> Vec { + // make all fields like usual + if !Self::field_collision_check(instance_fields) { + return instance_fields + .iter() + .map(|d| CppMember::FieldDecl(d.cpp_field.clone())) + .collect_vec(); + } + // we have a collision, investigate and handle + + let mut offset_map = HashMap::new(); + + fn accumulated_size(fields: &[FieldInfo]) -> u32 { + fields.iter().map(|f| f.size as u32).sum() + } + + let mut current_max: u32 = 0; + let mut current_offset: u32 = 0; + + // TODO: Field padding for exact offsets (explicit layouts?) + + // you can't sort instance fields on offset/size because it will throw off the unionization process + instance_fields + .iter() + .sorted_by(|a, b| a.size.cmp(&b.size)) + .rev() + .sorted_by(|a, b| a.offset.cmp(&b.offset)) + .for_each(|field| { + let offset = field.offset.unwrap_or(u32::MAX); + let size = field.size as u32; + let max = offset + size; + + if max > current_max { + current_offset = offset; + current_max = max; + } + + let current_set = + offset_map + .entry(current_offset) + .or_insert_with(|| FieldInfoSet { + fields: vec![], + offset: current_offset, + size, + }); + + if current_max > current_set.max() { + current_set.size = size + } + + // if we have a last vector & the size of its fields + current_offset is smaller than current max add to that list + if let Some(last) = current_set.fields.last_mut() + && current_offset + accumulated_size(last) == offset + { + last.push(field.clone()); + } else { + current_set.fields.push(vec![field.clone()]); + } + }); + + offset_map + .into_values() + .map(|field_set| { + // if we only have one list, just emit it as a set of fields + if field_set.fields.len() == 1 { + return field_set + .fields + .into_iter() + .flat_map(|v| v.into_iter()) + .map(|d| CppMember::FieldDecl(d.cpp_field)) + .collect_vec(); + } + // we had more than 1 list, so we have unions to emit + let declarations = field_set + .fields + .into_iter() + .map(|struct_contents| { + if struct_contents.len() == 1 { + // emit a struct with only 1 field as just a field + return struct_contents + .into_iter() + .map(|d| CppMember::FieldDecl(d.cpp_field)) + .collect_vec(); + } + vec![ + // if we have more than 1 field, emit a nested struct + CppMember::NestedStruct(CppNestedStruct { + base_type: None, + declaring_name: "".to_string(), + is_enum: false, + is_class: false, + is_private: false, + declarations: struct_contents + .into_iter() + .map(|d| CppMember::FieldDecl(d.cpp_field).into()) + .collect_vec(), + brief_comment: Some(format!( + "Anonymous struct offset 0x{:x}, size 0x{:x}", + field_set.offset, field_set.size + )), + packing: None, + }), + ] + }) + .flat_map(|v| v.into_iter()) + .collect_vec(); + + // wrap our set into a union + vec![CppMember::NestedUnion(CppNestedUnion { + brief_comment: Some(format!( + "Anonymous union offset 0x{:x}, size 0x{:x}", + field_set.offset, field_set.size + )), + declarations: declarations.into_iter().map(|d| d.into()).collect_vec(), + offset: field_set.offset, + is_private: false, + })] + }) + .flat_map(|v| v.into_iter()) + .collect_vec() + } + + fn make_parents( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + let ns = t.namespace(metadata.metadata); + let name = t.name(metadata.metadata); + + if t.parent_index == u32::MAX { + // TYPE_ATTRIBUTE_INTERFACE = 0x00000020 + match t.is_interface() { + true => { + // FIXME: should interfaces have a base type? I don't think they need to + // cpp_type.inherit.push(INTERFACE_WRAPPER_TYPE.to_string()); + } + false => { + info!("Skipping type: {ns}::{name} because it has parent index: {} and is not an interface!", t.parent_index); + } + } + return; + } + + let parent_type = metadata + .metadata_registration + .types + .get(t.parent_index as usize) + .unwrap_or_else(|| panic!("NO PARENT! But valid index found: {}", t.parent_index)); + + let parent_ty: CppTypeTag = CppTypeTag::from_type_data(parent_type.data, metadata.metadata); + + // handle value types and enum types specially + match t.is_value_type() || t.is_enum_type() { + // parent will be a value wrapper type + // which will have the size + // OF THE TYPE ITSELF, NOT PARENT + true => { + // let Some(size_info) = &cpp_type.size_info else { + // panic!("No size for value/enum type!") + // }; + + // if t.is_enum_type() { + // cpp_type.requirements.needs_enum_include(); + // } else if t.is_value_type() { + // cpp_type.requirements.needs_value_include(); + // } + + // let wrapper = wrapper_type_for_tdi(t); + + // cpp_type.inherit.push(wrapper.to_string()); + } + // handle as reference type + false => { + // make sure our parent is intended\ + let is_ref_type = matches!( + parent_type.ty, + Il2CppTypeEnum::Class | Il2CppTypeEnum::Genericinst | Il2CppTypeEnum::Object + ); + assert!(is_ref_type, "Not a class, object or generic inst!"); + + // We have a parent, lets do something with it + let inherit_type = + cpp_type.cppify_name_il2cpp(ctx_collection, metadata, parent_type, usize::MAX); + + if is_ref_type { + // TODO: Figure out why some generic insts don't work here + let parent_tdi: TypeDefinitionIndex = parent_ty.into(); + + let base_type_context = ctx_collection + .get_context(parent_ty) + .or_else(|| ctx_collection.get_context(parent_tdi.into())) + .unwrap_or_else(|| { + panic!( + "No CppContext for base type {inherit_type:?}. Using tag {parent_ty:?}" + ) + }); + + let base_type_cpp_type = ctx_collection + .get_cpp_type(parent_ty) + .or_else(|| ctx_collection.get_cpp_type(parent_tdi.into())) + .unwrap_or_else(|| { + panic!( + "No CppType for base type {inherit_type:?}. Using tag {parent_ty:?}" + ) + }); + + cpp_type.requirements.add_impl_include( + Some(base_type_cpp_type), + CppInclude::new_context_typeimpl(base_type_context), + ) + } + + cpp_type + .inherit + .push(inherit_type.remove_pointer().combine_all()); + } + } + } + + fn make_interfaces( + &mut self, + metadata: &Metadata<'_>, + ctx_collection: &CppContextCollection, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + for &interface_index in t.interfaces(metadata.metadata) { + let int_ty = &metadata.metadata_registration.types[interface_index as usize]; + + // We have an interface, lets do something with it + let interface_cpp_name = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, int_ty, 0) + .remove_pointer() + .combine_all(); + let interface_cpp_pointer = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, int_ty, 0) + .as_pointer() + .combine_all(); + + let method_decl = CppMethodDecl { + body: Default::default(), + brief: Some(format!("Convert operator to {interface_cpp_name:?}")), + cpp_name: interface_cpp_pointer.clone(), + return_type: "".to_string(), + instance: true, + is_const: false, + is_constexpr: true, + is_no_except: !t.is_value_type() && !t.is_enum_type(), + is_operator: true, + is_virtual: false, + is_inline: true, + parameters: vec![], + template: None, + prefix_modifiers: vec![], + suffix_modifiers: vec![], + }; + + let method_impl_template = if cpp_type + .cpp_template + .as_ref() + .is_some_and(|c| !c.names.is_empty()) + { + cpp_type.cpp_template.clone() + } else { + None + }; + + let convert_line = match t.is_value_type() || t.is_enum_type() { + true => { + // box + "static_cast(::cordl_internals::Box(this))".to_string() + } + false => "static_cast(this)".to_string(), + }; + + let method_impl = CppMethodImpl { + body: vec![Arc::new(CppLine::make(format!( + "return static_cast<{interface_cpp_pointer}>({convert_line});" + )))], + declaring_cpp_full_name: cpp_type + .cpp_name_components + .remove_pointer() + .combine_all(), + template: method_impl_template, + ..method_decl.clone().into() + }; + cpp_type + .declarations + .push(CppMember::MethodDecl(method_decl).into()); + + cpp_type + .implementations + .push(CppMember::MethodImpl(method_impl).into()); + } + } + + fn make_nested_types( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + config: &GenerationConfig, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + if t.nested_type_count == 0 { + return; + } + + let generic_instantiation_args = cpp_type.cpp_name_components.generics.clone(); + + let aliases = t + .nested_types(metadata.metadata) + .iter() + .filter(|t| !metadata.blacklisted_types.contains(t)) + .map(|nested_tdi| { + let nested_td = &metadata.metadata.global_metadata.type_definitions[*nested_tdi]; + let nested_tag = CppTypeTag::TypeDefinitionIndex(*nested_tdi); + + let nested_context = ctx_collection + .get_context(nested_tag) + .expect("Unable to find CppContext"); + let nested = ctx_collection + .get_cpp_type(nested_tag) + .expect("Unable to find nested CppType"); + + let alias = CppUsingAlias::from_cpp_type( + config.name_cpp(nested_td.name(metadata.metadata)), + nested, + generic_instantiation_args.clone(), + // if no generic args are made, we can do the generic fixup + // ORDER OF PASSES MATTERS + nested.generic_instantiations_args_types.is_none(), + ); + let fd = CppForwardDeclare::from_cpp_type(nested); + let inc = CppInclude::new_context_typedef(nested_context); + + (alias, fd, inc) + }) + .collect_vec(); + + for (alias, fd, inc) in aliases { + cpp_type + .declarations + .insert(0, CppMember::CppUsingAlias(alias).into()); + cpp_type.requirements.add_forward_declare((fd, inc)); + } + + // forward + + // old way of making nested types + + // let mut nested_types: Vec = Vec::with_capacity(t.nested_type_count as usize); + + // for &nested_type_index in t.nested_types(metadata.metadata) { + // let nt_ty = &metadata.metadata.global_metadata.type_definitions[nested_type_index]; + + // // We have a parent, lets do something with it + // let nested_type = CppType::make_cpp_type( + // metadata, + // config, + // CppTypeTag::TypeDefinitionIndex(nested_type_index), + // nested_type_index, + // ); + + // match nested_type { + // Some(unwrapped) => nested_types.push(unwrapped), + // None => info!("Failed to make nested CppType {nt_ty:?}"), + // }; + // } + + // cpp_type.nested_types = nested_types.into_iter().map(|t| (t.self_tag, t)).collect() + } + + fn make_properties( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + config: &GenerationConfig, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + + // Then, handle properties + if t.property_count == 0 { + return; + } + + cpp_type.declarations.reserve(t.property_count as usize); + // Then, for each field, write it out + for prop in t.properties(metadata.metadata) { + let p_name = prop.name(metadata.metadata); + let p_setter = (prop.set != u32::MAX).then(|| prop.set_method(t, metadata.metadata)); + let p_getter = (prop.get != u32::MAX).then(|| prop.get_method(t, metadata.metadata)); + + // if this is a static property, skip emitting a cpp property since those can't be static + if p_getter.or(p_setter).unwrap().is_static_method() { + continue; + } + + let p_type_index = match p_getter { + Some(g) => g.return_type as usize, + None => p_setter.unwrap().parameters(metadata.metadata)[0].type_index as usize, + }; + + let p_type = metadata + .metadata_registration + .types + .get(p_type_index) + .unwrap(); + + let p_ty_cpp_name = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, p_type, 0) + .combine_all(); + + let _method_map = |p: MethodIndex| { + let method_calc = metadata.method_calculations.get(&p).unwrap(); + CppMethodData { + estimated_size: method_calc.estimated_size, + addrs: method_calc.addrs, + } + }; + + let _abstr = p_getter.is_some_and(|p| p.is_abstract_method()) + || p_setter.is_some_and(|p| p.is_abstract_method()); + + let index = p_getter.is_some_and(|p| p.parameter_count > 0); + + // Need to include this type + cpp_type.declarations.push( + CppMember::Property(CppPropertyDecl { + cpp_name: config.name_cpp(p_name), + prop_ty: p_ty_cpp_name.clone(), + // methods generated in make_methods + setter: p_setter.map(|m| config.name_cpp(m.name(metadata.metadata))), + getter: p_getter.map(|m| config.name_cpp(m.name(metadata.metadata))), + indexable: index, + brief_comment: None, + instance: true, + }) + .into(), + ); + } + } + + fn create_size_assert(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + + // FIXME: make this work with templated types that either: have a full template (complete instantiation), or only require a pointer (size should be stable) + // for now, skip templated types + if cpp_type.cpp_template.is_some() { + return; + } + + if let Some(size) = cpp_type.size_info.as_ref().map(|s| s.instance_size) { + let cpp_name = cpp_type.cpp_name_components.remove_pointer().combine_all(); + + let assert = CppStaticAssert { + condition: format!("::cordl_internals::size_check_v<{cpp_name}, 0x{size:x}>"), + message: Some("Size mismatch!".to_string()), + }; + + cpp_type + .nonmember_declarations + .push(Rc::new(CppNonMember::CppStaticAssert(assert))); + } else { + todo!("Why does this type not have a valid size??? {cpp_type:?}"); + } + } + /// + /// add missing size for type + /// + fn create_size_padding(&mut self, metadata: &Metadata, tdi: TypeDefinitionIndex) { + let cpp_type = self.get_mut_cpp_type(); + + // // get type metadata size + let Some(type_definition_sizes) = &metadata.metadata_registration.type_definition_sizes + else { + return; + }; + + let metadata_size = &type_definition_sizes.get(tdi.index() as usize); + + let Some(metadata_size) = metadata_size else { + return; + }; + + // // ignore types that aren't sized + if metadata_size.instance_size == 0 || metadata_size.instance_size == u32::MAX { + return; + } + + // // if the size matches what we calculated, we're fine + // if metadata_size.instance_size == calculated_size { + // return; + // } + // let remaining_size = metadata_size.instance_size.abs_diff(calculated_size); + + let Some(size_info) = cpp_type.size_info.as_ref() else { + return; + }; + + // for all types, the size il2cpp metadata says the type should be, for generics this is calculated though + let metadata_size_instance = size_info.instance_size; + + // align the calculated size to the next multiple of natural_alignment, similiar to what happens when clang compiles our generated code + // this comes down to adding our size, and removing any bits that make it more than the next multiple of alignment + let aligned_calculated_size = match size_info.natural_alignment as u32 { + 0 => size_info.calculated_instance_size, + alignment => (size_info.calculated_instance_size + alignment) & !(alignment - 1), + }; + + // return if calculated layout size == metadata size + if aligned_calculated_size == metadata_size_instance { + return; + } + + let remaining_size = metadata_size_instance.abs_diff(size_info.calculated_instance_size); + + // pack the remaining size to fit the packing of the type + let closest_packing = |size: u32| { + match size { + 0 => 0, + 1 => 1, + 2 => 2, + 3 => 4, + 4 => 4, + _ => 8 + } + }; + + let packing = cpp_type.packing.unwrap_or_else(|| closest_packing(size_info.calculated_instance_size)); + let packed_remaining_size = match packing == 0 { + true => remaining_size, + false => remaining_size & !(packing as u32 - 1), + }; + + // if the packed remaining size ends up being 0, don't emit padding + if packed_remaining_size == 0 { + return; + } + + cpp_type.declarations.push( + CppMember::FieldDecl(CppFieldDecl { + cpp_name: format!("_cordl_size_padding[0x{packed_remaining_size:x}]").to_string(), + field_ty: "uint8_t".into(), + offset: size_info.instance_size, + instance: true, + readonly: false, + const_expr: false, + value: None, + brief_comment: Some(format!( + "Size padding 0x{:x} - 0x{:x} = 0x{remaining_size:x}, packed as 0x{packed_remaining_size:x}", + metadata_size_instance, size_info.calculated_instance_size + )), + is_private: false, + }) + .into(), + ); + } + + fn create_ref_size(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + if let Some(size) = cpp_type.size_info.as_ref().map(|s| s.instance_size) { + cpp_type.declarations.push( + CppMember::FieldDecl(CppFieldDecl { + cpp_name: REFERENCE_TYPE_WRAPPER_SIZE.to_string(), + field_ty: "auto".to_string(), + offset: u32::MAX, + instance: false, + readonly: false, + const_expr: true, + value: Some(format!("0x{size:x}")), + brief_comment: Some("The size of the true reference type".to_string()), + is_private: false, + }) + .into(), + ); + + // here we push an instance field like uint8_t __fields[total_size - base_size] to make sure ref types are the exact size they should be + let fixup_size = match cpp_type.inherit.first() { + Some(base_type) => format!("0x{size:x} - sizeof({base_type})"), + None => format!("0x{size:x}"), + }; + + cpp_type.declarations.push( + CppMember::FieldDecl(CppFieldDecl { + cpp_name: format!("{REFERENCE_TYPE_FIELD_SIZE}[{fixup_size}]"), + field_ty: "uint8_t".to_string(), + offset: u32::MAX, + instance: true, + readonly: false, + const_expr: false, + value: Some("".into()), + brief_comment: Some( + "The size this ref type adds onto its base type, may evaluate to 0" + .to_string(), + ), + is_private: false, + }) + .into(), + ); + } else { + todo!("Why does this type not have a valid size??? {:?}", cpp_type); + } + } + fn create_enum_backing_type_constant( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + + let t = Self::get_type_definition(metadata, tdi); + + let backing_field_idx = t.element_type_index as usize; + let backing_field_ty = &metadata.metadata_registration.types[backing_field_idx]; + + let enum_base = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, backing_field_ty, 0) + .remove_pointer() + .combine_all(); + + cpp_type.declarations.push( + CppMember::CppUsingAlias(CppUsingAlias { + alias: __CORDL_BACKING_ENUM_TYPE.to_string(), + result: enum_base, + template: None, + }) + .into(), + ); + } + + fn create_enum_wrapper( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + let t = Self::get_type_definition(metadata, tdi); + let unwrapped_name = format!("__{}_Unwrapped", cpp_type.cpp_name()); + let backing_field = metadata + .metadata_registration + .types + .get(t.element_type_index as usize) + .unwrap(); + + let enum_base = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, backing_field, 0) + .remove_pointer() + .combine_all(); + + let enum_entries = t + .fields(metadata.metadata) + .iter() + .enumerate() + .map(|(i, field)| { + let field_index = FieldIndex::new(t.field_start.index() + i as u32); + + (field_index, field) + }) + .filter_map(|(field_index, field)| { + let f_type = metadata + .metadata_registration + .types + .get(field.type_index as usize) + .unwrap(); + + f_type.is_static().then(|| { + // enums static fields are always the enum values + let f_name = field.name(metadata.metadata); + let value = Self::field_default_value(metadata, field_index) + .expect("Enum without value!"); + + // prepend enum name with __E_ to prevent accidentally creating enum values that are reserved for builtin macros + format!("__E_{f_name} = {value},") + }) + }) + .map(|s| -> CppMember { CppMember::CppLine(s.into()) }); + + let nested_struct = CppNestedStruct { + base_type: Some(enum_base), + declaring_name: unwrapped_name.clone(), + is_class: false, + is_enum: true, + is_private: false, + declarations: enum_entries.map(Rc::new).collect(), + brief_comment: Some(format!("Nested struct {unwrapped_name}")), + packing: None, + }; + cpp_type + .declarations + .push(CppMember::NestedStruct(nested_struct).into()); + + let operator_body = format!("return static_cast<{unwrapped_name}>(this->value__);"); + let operator_decl = CppMethodDecl { + cpp_name: Default::default(), + instance: true, + return_type: unwrapped_name, + + brief: Some("Conversion into unwrapped enum value".to_string()), + body: Some(vec![Arc::new(CppLine::make(operator_body))]), // TODO: + is_const: true, + is_constexpr: true, + is_virtual: false, + is_operator: true, + is_no_except: true, // TODO: + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + is_inline: true, + }; + + cpp_type + .declarations + .push(CppMember::MethodDecl(operator_decl).into()); + } + + fn create_valuetype_field_wrapper(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + if cpp_type.size_info.is_none() { + todo!("Why does this type not have a valid size??? {:?}", cpp_type); + } + + let size = cpp_type + .size_info + .as_ref() + .map(|s| s.instance_size) + .unwrap(); + + cpp_type.requirements.needs_byte_include(); + cpp_type.declarations.push( + CppMember::FieldDecl(CppFieldDecl { + cpp_name: VALUE_TYPE_WRAPPER_SIZE.to_string(), + field_ty: "auto".to_string(), + offset: u32::MAX, + instance: false, + readonly: false, + const_expr: true, + value: Some(format!("0x{size:x}")), + brief_comment: Some("The size of the true value type".to_string()), + is_private: false, + }) + .into(), + ); + + // cpp_type.declarations.push( + // CppMember::ConstructorDecl(CppConstructorDecl { + // cpp_name: cpp_type.cpp_name().clone(), + // parameters: vec![CppParam { + // name: "instance".to_string(), + // ty: format!("std::array"), + // modifiers: Default::default(), + // def_value: None, + // }], + // template: None, + // is_constexpr: true, + // is_explicit: true, + // is_default: false, + // is_no_except: true, + // is_delete: false, + // is_protected: false, + // base_ctor: Some(( + // cpp_type.inherit.first().unwrap().to_string(), + // "instance".to_string(), + // )), + // initialized_values: Default::default(), + // brief: Some( + // "Constructor that lets you initialize the internal array explicitly".into(), + // ), + // body: Some(vec![]), + // }) + // .into(), + // ); + } + + fn create_valuetype_constructor( + &mut self, + metadata: &Metadata, + ctx_collection: &CppContextCollection, + config: &GenerationConfig, + tdi: TypeDefinitionIndex, + ) { + let cpp_type = self.get_mut_cpp_type(); + + let t = &metadata.metadata.global_metadata.type_definitions[tdi]; + + let instance_fields = t + .fields(metadata.metadata) + .iter() + .filter_map(|field| { + let f_type = metadata + .metadata_registration + .types + .get(field.type_index as usize) + .unwrap(); + + // ignore statics or constants + if f_type.is_static() || f_type.is_constant() { + return None; + } + + let f_type_cpp_name = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, f_type, 0) + .combine_all(); + + // Get the inner type of a Generic Inst + // e.g ReadOnlySpan -> ReadOnlySpan + let def_value = Self::type_default_value(metadata, Some(cpp_type), f_type); + + let f_cpp_name = config.name_cpp_plus( + field.name(metadata.metadata), + &[cpp_type.cpp_name().as_str()], + ); + + Some(CppParam { + name: f_cpp_name, + ty: f_type_cpp_name, + modifiers: "".to_string(), + // no default value for first param + def_value: Some(def_value), + }) + }) + .collect_vec(); + + + if instance_fields.is_empty() { + return; + } + // Maps into the first parent -> "" + // so then Parent() + let base_ctor = cpp_type + .inherit + .first() + .map(|s| (s.clone(), "".to_string())); + + let body: Vec> = instance_fields + .iter() + .map(|p| { + let name = &p.name; + CppLine::make(format!("this->{name} = {name};")) + }) + .map(Arc::new) + // Why is this needed? _sigh_ + .map(|arc| -> Arc { arc }) + .collect_vec(); + + let params_no_def = instance_fields + .iter() + .cloned() + .map(|mut c| { + c.def_value = None; + c + }) + .collect_vec(); + + let constructor_decl = CppConstructorDecl { + cpp_name: cpp_type.cpp_name().clone(), + template: None, + is_constexpr: true, + is_explicit: false, + is_default: false, + is_no_except: true, + is_delete: false, + is_protected: false, - // We have a parent, lets do something with it - let nested_type = CppType::make_cpp_type( - metadata, - config, - TypeData::TypeDefinitionIndex(nested_type_index), - ); + base_ctor, + initialized_values: HashMap::new(), + // initialize values with params + // initialized_values: instance_fields + // .iter() + // .map(|p| (p.name.to_string(), p.name.to_string())) + // .collect(), + parameters: params_no_def, + brief: None, + body: None, + }; - match nested_type { - Some(unwrapped) => nested_types.push(unwrapped), - None => println!("Failed to make nested CppType {nt_ty:?}"), - }; + let method_impl_template = if cpp_type + .cpp_template + .as_ref() + .is_some_and(|c| !c.names.is_empty()) + { + cpp_type.cpp_template.clone() + } else { + None + }; + + let constructor_impl = CppConstructorImpl { + body, + template: method_impl_template, + parameters: instance_fields, + declaring_full_name: cpp_type.cpp_name_components.remove_pointer().combine_all(), + ..constructor_decl.clone().into() + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(constructor_decl).into()); + cpp_type + .implementations + .push(CppMember::ConstructorImpl(constructor_impl).into()); + } + + fn create_valuetype_default_constructors(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + // create the various copy and move ctors and operators + let cpp_name = cpp_type.cpp_name(); + let wrapper = format!("{VALUE_WRAPPER_TYPE}<{VALUE_TYPE_WRAPPER_SIZE}>::instance"); + + let move_ctor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + ty: cpp_name.clone(), + name: "".to_string(), + modifiers: "&&".to_string(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: true, + is_no_except: false, + is_delete: false, + is_protected: false, + base_ctor: None, + initialized_values: Default::default(), + brief: None, + body: None, + }; + + let copy_ctor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + ty: cpp_name.clone(), + name: "".to_string(), + modifiers: "const &".to_string(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: true, + is_no_except: false, + is_delete: false, + is_protected: false, + base_ctor: None, + initialized_values: Default::default(), + brief: None, + body: None, + }; + + let move_operator_eq = CppMethodDecl { + cpp_name: "operator=".to_string(), + return_type: format!("{cpp_name}&"), + parameters: vec![CppParam { + ty: cpp_name.clone(), + name: "o".to_string(), + modifiers: "&&".to_string(), + def_value: None, + }], + instance: true, + template: None, + suffix_modifiers: vec![], + prefix_modifiers: vec![], + is_virtual: false, + is_constexpr: true, + is_const: false, + is_no_except: true, + is_operator: false, + is_inline: false, + brief: None, + body: Some(vec![ + Arc::new(CppLine::make(format!( + "this->{wrapper} = std::move(o.{wrapper});" + ))), + Arc::new(CppLine::make("return *this;".to_string())), + ]), + }; + + let copy_operator_eq = CppMethodDecl { + cpp_name: "operator=".to_string(), + return_type: format!("{cpp_name}&"), + parameters: vec![CppParam { + ty: cpp_name.clone(), + name: "o".to_string(), + modifiers: "const &".to_string(), + def_value: None, + }], + instance: true, + template: None, + suffix_modifiers: vec![], + prefix_modifiers: vec![], + is_virtual: false, + is_constexpr: true, + is_const: false, + is_no_except: true, + is_operator: false, + is_inline: false, + brief: None, + body: Some(vec![ + Arc::new(CppLine::make(format!("this->{wrapper} = o.{wrapper};"))), + Arc::new(CppLine::make("return *this;".to_string())), + ]), + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(move_ctor).into()); + cpp_type + .declarations + .push(CppMember::ConstructorDecl(copy_ctor).into()); + cpp_type + .declarations + .push(CppMember::MethodDecl(move_operator_eq).into()); + cpp_type + .declarations + .push(CppMember::MethodDecl(copy_operator_eq).into()); + } + + fn create_ref_default_constructor(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let cpp_name = cpp_type.cpp_name().clone(); + + let cs_name = cpp_type.name().clone(); + + // Skip if System.ValueType or System.Enum + if cpp_type.namespace() == "System" && (cs_name == "ValueType" || cs_name == "Enum") { + return; } - cpp_type.nested_types = nested_types + let default_ctor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: true, + is_no_except: true, + is_delete: false, + is_protected: true, + + base_ctor: None, + initialized_values: HashMap::new(), + brief: Some("Default ctor for custom type constructor invoke".to_string()), + body: None, + }; + let copy_ctor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + name: "".to_string(), + modifiers: " const&".to_string(), + ty: cpp_name.clone(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: true, + is_no_except: true, + is_delete: false, + is_protected: false, + + base_ctor: None, + initialized_values: HashMap::new(), + brief: None, + body: None, + }; + let move_ctor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + name: "".to_string(), + modifiers: "&&".to_string(), + ty: cpp_name.clone(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: true, + is_no_except: true, + is_delete: false, + is_protected: false, + + base_ctor: None, + initialized_values: HashMap::new(), + brief: None, + body: None, + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(default_ctor).into()); + cpp_type + .declarations + .push(CppMember::ConstructorDecl(copy_ctor).into()); + cpp_type + .declarations + .push(CppMember::ConstructorDecl(move_ctor).into()); + + // // Delegates and such are reference types with no inheritance + // if cpp_type.inherit.is_empty() { + // return; + // } + + // let base_type = cpp_type + // .inherit + // .get(0) + // .expect("No parent for reference type?"); + + // cpp_type.declarations.push( + // CppMember::ConstructorDecl(CppConstructorDecl { + // cpp_name: cpp_name.clone(), + // parameters: vec![CppParam { + // name: "ptr".to_string(), + // modifiers: "".to_string(), + // ty: "void*".to_string(), + // def_value: None, + // }], + // template: None, + // is_constexpr: true, + // is_explicit: true, + // is_default: false, + // is_no_except: true, + // is_delete: false, + // is_protected: false, + + // base_ctor: Some((base_type.clone(), "ptr".to_string())), + // initialized_values: HashMap::new(), + // brief: None, + // body: Some(vec![]), + // }) + // .into(), + // ); + } + fn make_interface_constructors(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let cpp_name = cpp_type.cpp_name().clone(); + + let base_type = cpp_type + .inherit + .first() + .expect("No parent for interface type?"); + + cpp_type.declarations.push( + CppMember::ConstructorDecl(CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + name: "ptr".to_string(), + modifiers: "".to_string(), + ty: "void*".to_string(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: true, + is_default: false, + is_no_except: true, + is_delete: false, + is_protected: false, + + base_ctor: Some((base_type.clone(), "ptr".to_string())), + initialized_values: HashMap::new(), + brief: None, + body: Some(vec![]), + }) + .into(), + ); } + fn create_ref_default_operators(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let cpp_name = cpp_type.cpp_name(); - fn make_properties( + // Skip if System.ValueType or System.Enum + if cpp_type.namespace() == "System" + && (cpp_type.cpp_name() == "ValueType" || cpp_type.cpp_name() == "Enum") + { + return; + } + + // Delegates and such are reference types with no inheritance + if cpp_type.inherit.is_empty() { + return; + } + + cpp_type.declarations.push( + CppMember::CppLine(CppLine { + line: format!( + " + constexpr {cpp_name}& operator=(std::nullptr_t) noexcept {{ + this->{REFERENCE_WRAPPER_INSTANCE_NAME} = nullptr; + return *this; + }}; + + constexpr {cpp_name}& operator=(void* o) noexcept {{ + this->{REFERENCE_WRAPPER_INSTANCE_NAME} = o; + return *this; + }}; + + constexpr {cpp_name}& operator=({cpp_name}&& o) noexcept = default; + constexpr {cpp_name}& operator=({cpp_name} const& o) noexcept = default; + " + ), + }) + .into(), + ); + } + + fn delete_move_ctor(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let t = &cpp_type.cpp_name_components.name; + + let move_ctor = CppConstructorDecl { + cpp_name: t.clone(), + parameters: vec![CppParam { + def_value: None, + modifiers: "&&".to_string(), + name: "".to_string(), + ty: t.clone(), + }], + template: None, + is_constexpr: false, + is_explicit: false, + is_default: false, + is_no_except: false, + is_protected: false, + is_delete: true, + base_ctor: None, + initialized_values: Default::default(), + brief: Some("delete move ctor to prevent accidental deref moves".to_string()), + body: None, + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(move_ctor).into()); + } + + fn delete_copy_ctor(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let t = &cpp_type.cpp_name_components.name; + + let move_ctor = CppConstructorDecl { + cpp_name: t.clone(), + parameters: vec![CppParam { + def_value: None, + modifiers: "const&".to_string(), + name: "".to_string(), + ty: t.clone(), + }], + template: None, + is_constexpr: false, + is_explicit: false, + is_default: false, + is_no_except: false, + is_delete: true, + is_protected: false, + base_ctor: None, + initialized_values: Default::default(), + brief: Some("delete copy ctor to prevent accidental deref copies".to_string()), + body: None, + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(move_ctor).into()); + } + + fn add_default_ctor(&mut self, protected: bool) { + let cpp_type = self.get_mut_cpp_type(); + let t = &cpp_type.cpp_name_components.name; + + let default_ctor_decl = CppConstructorDecl { + cpp_name: t.clone(), + parameters: vec![], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: false, + is_no_except: false, + is_delete: false, + is_protected: protected, + base_ctor: None, + initialized_values: Default::default(), + brief: Some("default ctor".to_string()), + body: None, + }; + + let default_ctor_impl = CppConstructorImpl { + body: vec![], + declaring_full_name: cpp_type.cpp_name_components.remove_pointer().combine_all(), + template: cpp_type.cpp_template.clone(), + ..default_ctor_decl.clone().into() + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(default_ctor_decl).into()); + + cpp_type + .implementations + .push(CppMember::ConstructorImpl(default_ctor_impl).into()); + } + + fn delete_default_ctor(&mut self) { + let cpp_type = self.get_mut_cpp_type(); + let t = &cpp_type.cpp_name_components.name; + + let default_ctor = CppConstructorDecl { + cpp_name: t.clone(), + parameters: vec![], + template: None, + is_constexpr: false, + is_explicit: false, + is_default: false, + is_no_except: false, + is_delete: true, + is_protected: false, + base_ctor: None, + initialized_values: Default::default(), + brief: Some( + "delete default ctor to prevent accidental value type instantiations of ref types" + .to_string(), + ), + body: None, + }; + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(default_ctor).into()); + } + + fn create_ref_constructor( + cpp_type: &mut CppType, + declaring_type: &Il2CppTypeDefinition, + m_params: &[CppParam], + template: &Option, + ) { + if declaring_type.is_value_type() || declaring_type.is_enum_type() { + return; + } + + let params_no_default = m_params + .iter() + .cloned() + .map(|mut c| { + c.def_value = None; + c + }) + .collect_vec(); + + let ty_full_cpp_name = cpp_type.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: template.clone(), + body: None, // TODO: + brief: None, + is_no_except: false, + is_constexpr: false, + instance: false, + is_const: false, + is_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::New<{ty_full_cpp_name}>({base_ctor_params}))"); + + let declaring_template = if cpp_type + .cpp_template + .as_ref() + .is_some_and(|t| !t.names.is_empty()) + { + cpp_type.cpp_template.clone() + } else { + None + }; + + let cpp_constructor_impl = CppMethodImpl { + body: vec![Arc::new(CppLine::make(format!("return {allocate_call};")))], + + declaring_cpp_full_name: cpp_type.cpp_name_components.remove_pointer().combine_all(), + parameters: m_params.to_vec(), + template: declaring_template, + ..decl.clone().into() + }; + + cpp_type + .implementations + .push(CppMember::MethodImpl(cpp_constructor_impl).into()); + + cpp_type + .declarations + .push(CppMember::MethodDecl(decl).into()); + } + + fn create_method( &mut self, + declaring_type: &Il2CppTypeDefinition, + method_index: MethodIndex, + metadata: &Metadata, ctx_collection: &CppContextCollection, - tdi: TypeDefinitionIndex, + config: &GenerationConfig, + is_generic_method_inst: bool, ) { + let method = &metadata.metadata.global_metadata.methods[method_index]; let cpp_type = self.get_mut_cpp_type(); - let t = Self::get_type_definition(metadata, tdi); - // Then, handle properties - if t.property_count == 0 { + // TODO: sanitize method name for c++ + let m_name = method.name(metadata.metadata); + if m_name == ".cctor" { + // info!("Skipping {}", m_name); return; } - // Write comment for properties - cpp_type - .declarations - .push(CppMember::Comment(CppCommentedString { - data: "".to_string(), - comment: Some("Properties".to_string()), - })); - cpp_type.declarations.reserve(t.property_count as usize); - // Then, for each field, write it out - for prop in t.properties(metadata.metadata) { - let p_name = prop.name(metadata.metadata); - let p_setter = (prop.set != u32::MAX).then(|| prop.set_method(t, metadata.metadata)); - let p_getter = (prop.get != u32::MAX).then(|| prop.get_method(t, metadata.metadata)); - let p_type_index = match p_getter { - Some(g) => g.return_type as usize, - None => p_setter.unwrap().parameters(metadata.metadata)[0].type_index as usize, - }; + let m_ret_type = metadata + .metadata_registration + .types + .get(method.return_type as usize) + .unwrap(); - let p_type = metadata + let mut m_params_with_def: Vec = + Vec::with_capacity(method.parameter_count as usize); + + for (pi, param) in method.parameters(metadata.metadata).iter().enumerate() { + let param_index = ParameterIndex::new(method.parameter_start.index() + pi as u32); + let param_type = metadata .metadata_registration .types - .get(p_type_index) + .get(param.type_index as usize) .unwrap(); - let p_cpp_name = cpp_type.cppify_name_il2cpp(ctx_collection, metadata, p_type, false); + let def_value = Self::param_default_value(metadata, param_index); + + let make_param_cpp_type_name = |cpp_type: &mut CppType| -> String { + let full_name = param_type.full_name(metadata.metadata); + if full_name == "System.Enum" { + cpp_type.requirements.needs_enum_include(); + ENUM_PTR_TYPE.into() + } else if full_name == "System.ValueType" { + cpp_type.requirements.needs_value_include(); + VT_PTR_TYPE.into() + } else { + cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, param_type, 0) + .combine_all() + } + }; + + let param_cpp_name = { + let fixup_name = match is_generic_method_inst { + false => cpp_type.il2cpp_mvar_use_param_name( + metadata, + method_index, + make_param_cpp_type_name, + param_type, + ), + true => make_param_cpp_type_name(cpp_type), + }; + + cpp_type.il2cpp_byref(fixup_name, param_type) + }; + + m_params_with_def.push(CppParam { + name: config.name_cpp(param.name(metadata.metadata)), + def_value, + ty: param_cpp_name, + modifiers: "".to_string(), + }); + } + + let m_params_no_def: Vec = m_params_with_def + .iter() + .cloned() + .map(|mut p| { + p.def_value = None; + p + }) + .collect_vec(); + + // TODO: Add template if a generic inst e.g + // T UnityEngine.Component::GetComponent() -> bs_hook::Il2CppWrapperType UnityEngine.Component::GetComponent() + let template = if method.generic_container_index.is_valid() { + match is_generic_method_inst { + true => Some(CppTemplate { names: vec![] }), + false => { + let generics = method + .generic_container(metadata.metadata) + .unwrap() + .generic_parameters(metadata.metadata) + .iter() + .map(|param| param.name(metadata.metadata).to_string()); + + Some(CppTemplate::make_typenames(generics)) + } + } + } else { + None + }; + + let declaring_type_template = if cpp_type + .cpp_template + .as_ref() + .is_some_and(|t| !t.names.is_empty()) + { + cpp_type.cpp_template.clone() + } else { + None + }; + + let literal_types = if is_generic_method_inst { + cpp_type + .method_generic_instantiation_map + .get(&method_index) + .cloned() + } else { + None + }; + + let resolved_generic_types = literal_types.map(|literal_types| { + literal_types + .iter() + .map(|t| &metadata.metadata_registration.types[*t as usize]) + .map(|t| { + cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, t, 0) + .combine_all() + }) + .collect_vec() + }); + + // Lazy cppify + let make_ret_cpp_type_name = |cpp_type: &mut CppType| -> String { + let full_name = m_ret_type.full_name(metadata.metadata); + if full_name == "System.Enum" { + cpp_type.requirements.needs_enum_include(); + ENUM_PTR_TYPE.into() + } else if full_name == "System.ValueType" { + cpp_type.requirements.needs_value_include(); + VT_PTR_TYPE.into() + } else { + cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, m_ret_type, 0) + .combine_all() + } + }; + + let m_ret_cpp_type_name = { + let fixup_name = match is_generic_method_inst { + false => cpp_type.il2cpp_mvar_use_param_name( + metadata, + method_index, + make_ret_cpp_type_name, + m_ret_type, + ), + true => make_ret_cpp_type_name(cpp_type), + }; + + cpp_type.il2cpp_byref(fixup_name, m_ret_type) + }; + + // Reference type constructor + if m_name == ".ctor" { + Self::create_ref_constructor(cpp_type, declaring_type, &m_params_with_def, &template); + } + let cpp_m_name = { + let cpp_m_name = config.name_cpp(m_name); + + // 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" { + true => { + cpp_m_name + + "_" + + &config + .generic_nested_name(&m_ret_cpp_type_name) + .replace('*', "_") + } + false => cpp_m_name, + }; + + match &resolved_generic_types { + Some(resolved_generic_types) => { + format!("{fixup_name}<{}>", resolved_generic_types.join(", ")) + } + None => fixup_name, + } + }; + + let declaring_type = method.declaring_type(metadata.metadata); + let tag = CppTypeTag::TypeDefinitionIndex(method.declaring_type); + + let method_calc = metadata.method_calculations.get(&method_index); + + // generic methods don't have definitions if not an instantiation + let method_stub = !is_generic_method_inst && template.is_some(); + + let method_decl = CppMethodDecl { + body: None, + brief: format!( + "Method {m_name} addr 0x{:x}, size 0x{:x}, virtual {}, abstract {}, final {}", + method_calc.map(|m| m.addrs).unwrap_or(u64::MAX), + method_calc.map(|m| m.estimated_size).unwrap_or(usize::MAX), + method.is_virtual_method(), + method.is_abstract_method(), + method.is_final_method() + ) + .into(), + is_const: false, + is_constexpr: false, + is_no_except: false, + cpp_name: cpp_m_name.clone(), + return_type: m_ret_cpp_type_name.clone(), + parameters: m_params_with_def.clone(), + instance: !method.is_static_method(), + template: template.clone(), + suffix_modifiers: Default::default(), + prefix_modifiers: Default::default(), + is_virtual: false, + is_operator: false, + is_inline: true, + }; + + let instance_ptr: String = if method.is_static_method() { + "nullptr".into() + } else { + "this".into() + }; + + const METHOD_INFO_VAR_NAME: &str = "___internal_method"; + + let method_invoke_params = vec![instance_ptr.as_str(), METHOD_INFO_VAR_NAME]; + let param_names = CppParam::params_names(&method_decl.parameters).map(|s| s.as_str()); + let declaring_type_cpp_full_name = + cpp_type.cpp_name_components.remove_pointer().combine_all(); + + let declaring_classof_call = format!( + "::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<{}>::get()", + cpp_type.cpp_name_components.combine_all() + ); + + let extract_self_class = + "il2cpp_functions::object_get_class(reinterpret_cast(this))"; + + let params_types_format: String = CppParam::params_types(&method_decl.parameters) + .map(|t| format!("::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_type<{t}>::get()")) + .join(", "); + + let resolve_instance_slot_lines = if method.slot != u16::MAX { + let slot = &method.slot; + vec![format!( + "auto* {METHOD_INFO_VAR_NAME} = THROW_UNLESS((::il2cpp_utils::ResolveVtableSlot( + {extract_self_class}, + {declaring_classof_call}, + {slot} + )));" + )] + } else { + vec![] + }; + + // TODO: link the method to the interface that originally declared it + // then the resolve should look something like: + // resolve(classof(GlobalNamespace::BeatmapLevelPack*), classof(GlobalNamespace::IBeatmapLevelPack*), 0); + // that way the resolve should work correctly, but it should only happen like that for non-interfaces + + let resolve_metadata_slot_lines = if method.slot != u16::MAX { + let self_classof_call = ""; + let declaring_classof_call = ""; + let slot = &method.slot; + + vec![format!( + "auto* {METHOD_INFO_VAR_NAME} = THROW_UNLESS((::il2cpp_utils::ResolveVtableSlot( + {self_classof_call}, + {declaring_classof_call}, + {slot} + )));" + )] + } else { + vec![] + }; + + let method_info_lines = match &template { + Some(template) => { + // generic + let template_names = template + .just_names() + .map(|t| { + format!( + "::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<{t}>::get()" + ) + }) + .join(", "); + + vec![ + format!("static auto* ___internal_method_base = THROW_UNLESS((::il2cpp_utils::FindMethod( + {declaring_classof_call}, + \"{m_name}\", + std::vector{{{template_names}}}, + ::std::vector{{{params_types_format}}} + )));"), + format!("static auto* {METHOD_INFO_VAR_NAME} = THROW_UNLESS(::il2cpp_utils::MakeGenericMethod( + ___internal_method_base, + std::vector{{{template_names}}} + ));"), + ] + } + None => { + vec![ + format!("static auto* {METHOD_INFO_VAR_NAME} = THROW_UNLESS((::il2cpp_utils::FindMethod( + {declaring_classof_call}, + \"{m_name}\", + std::vector{{}}, + ::std::vector{{{params_types_format}}} + )));"), + ] + } + }; + + let method_body_lines = [format!( + "return ::cordl_internals::RunMethodRethrow<{m_ret_cpp_type_name}, false>({});", + method_invoke_params + .into_iter() + .chain(param_names) + .join(", ") + )]; + + // static auto ___internal__logger = ::Logger::get().WithContext("::Org::BouncyCastle::Crypto::Parameters::DHPrivateKeyParameters::Equals"); + // auto* ___internal__method = THROW_UNLESS((::il2cpp_utils::FindMethod(this, "Equals", std::vector{}, ::std::vector{::il2cpp_utils::ExtractType(obj)}))); + // return ::il2cpp_utils::RunMethodRethrow(this, ___internal__method, obj); + + // instance methods should resolve slots if this is an interface, or if this is a virtual/abstract method, and not a final method + // static methods can't be virtual or interface anyway so checking for that here is irrelevant + let should_resolve_slot = cpp_type.is_interface || ((method.is_virtual_method() || method.is_abstract_method()) && !method.is_final_method()); + + let method_body = match should_resolve_slot { + true => resolve_instance_slot_lines + .iter() + .chain(method_body_lines.iter()) + .cloned() + .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)) }) + .collect_vec(), + }; + + let method_impl = CppMethodImpl { + body: method_body, + parameters: m_params_with_def.clone(), + brief: None, + declaring_cpp_full_name: declaring_type_cpp_full_name, + instance: !method.is_static_method(), + suffix_modifiers: Default::default(), + prefix_modifiers: Default::default(), + template: template.clone(), + declaring_type_template: declaring_type_template.clone(), + + // defaults + ..method_decl.clone().into() + }; + + // check if declaring type is the current type or the interface + // we check TDI because if we are a generic instantiation + // we just use ourselves if the declaring type is also the same TDI + let interface_declaring_cpp_type: Option<&CppType> = + if tag.get_tdi() == cpp_type.self_tag.get_tdi() { + Some(cpp_type) + } else { + ctx_collection.get_cpp_type(tag) + }; + + // don't emit method size structs for generic methods + + // don't emit method size structs for generic methods + + // if type is a generic + let has_template_args = cpp_type + .cpp_template + .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 + && template.is_none() + && !has_template_args + && !is_generic_method_inst + { + cpp_type + .nonmember_implementations + .push(Rc::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(), + template: template.clone(), + generic_literals: resolved_generic_types, + method_data: CppMethodData { + addrs: method_calc.addrs, + estimated_size: method_calc.estimated_size, + }, + interface_clazz_of: interface_declaring_cpp_type + .map(|d| d.classof_cpp_name()) + .unwrap_or_else(|| format!("Bad stuff happened {declaring_type:?}")), + is_final: method.is_final_method(), + slot: if method.slot != u16::MAX { + Some(method.slot) + } else { + None + }, + } + .into(), + ))); + } - let method_map = |p: MethodIndex| { - let method_calc = metadata.method_calculations.get(&p).unwrap(); - CppMethodData { - estimated_size: method_calc.estimated_size, - addrs: method_calc.addrs, - } - }; + // TODO: Revise this + const ALLOW_GENERIC_METHOD_STUBS_IMPL: bool = true; + // If a generic instantiation or not a template + if !method_stub || ALLOW_GENERIC_METHOD_STUBS_IMPL { + cpp_type + .implementations + .push(CppMember::MethodImpl(method_impl).into()); + } - // Need to include this type - cpp_type.declarations.push(CppMember::Property(CppProperty { - name: p_name.to_owned(), - ty: p_cpp_name.clone(), - classof_call: cpp_type.classof_cpp_name(), - setter: p_setter.map(|_| method_map(prop.set_method_index(t))), - getter: p_getter.map(|_| method_map(prop.get_method_index(t))), - abstr: p_getter.is_some_and(|p| p.is_abstract_method()) - || p_setter.is_some_and(|p| p.is_abstract_method()), - instance: !p_getter.or(p_setter).unwrap().is_static_method(), - })); + if !is_generic_method_inst { + cpp_type + .declarations + .push(CppMember::MethodDecl(method_decl).into()); } } - fn default_value_blob(metadata: &Metadata, ty: Il2CppTypeEnum, data_index: usize) -> String { + fn default_value_blob( + metadata: &Metadata, + ty: &Il2CppType, + data_index: usize, + string_quotes: bool, + string_as_u16: bool, + ) -> String { let data = &metadata .metadata .global_metadata @@ -581,49 +3461,216 @@ pub trait CSType: Sized { let mut cursor = Cursor::new(data); - match ty { + const UNSIGNED_SUFFIX: &str = "u"; + match ty.ty { Il2CppTypeEnum::Boolean => (if data[0] == 0 { "false" } else { "true" }).to_string(), - Il2CppTypeEnum::I1 => cursor.read_i8().unwrap().to_string(), - Il2CppTypeEnum::I2 => cursor.read_i16::().unwrap().to_string(), - Il2CppTypeEnum::Valuetype | Il2CppTypeEnum::I4 => { - cursor.read_i32::().unwrap().to_string() + Il2CppTypeEnum::I1 => { + format!("static_cast(0x{:x})", cursor.read_i8().unwrap()) + } + Il2CppTypeEnum::I2 => { + format!( + "static_cast(0x{:x})", + cursor.read_i16::().unwrap() + ) + } + Il2CppTypeEnum::I4 => { + format!( + "static_cast(0x{:x})", + cursor.read_compressed_i32::().unwrap() + ) } // TODO: We assume 64 bit Il2CppTypeEnum::I | Il2CppTypeEnum::I8 => { - cursor.read_i64::().unwrap().to_string() + format!( + "static_cast(0x{:x})", + cursor.read_i64::().unwrap() + ) + } + Il2CppTypeEnum::U1 => { + format!( + "static_cast(0x{:x}{UNSIGNED_SUFFIX})", + cursor.read_u8().unwrap() + ) + } + Il2CppTypeEnum::U2 => { + format!( + "static_cast(0x{:x}{UNSIGNED_SUFFIX})", + cursor.read_u16::().unwrap() + ) + } + Il2CppTypeEnum::U4 => { + format!( + "static_cast(0x{:x}{UNSIGNED_SUFFIX})", + cursor.read_u32::().unwrap() + ) } - Il2CppTypeEnum::U1 => cursor.read_u8().unwrap().to_string(), - Il2CppTypeEnum::U2 => cursor.read_u16::().unwrap().to_string(), - Il2CppTypeEnum::U4 => cursor.read_u32::().unwrap().to_string(), // TODO: We assume 64 bit Il2CppTypeEnum::U | Il2CppTypeEnum::U8 => { - cursor.read_u64::().unwrap().to_string() + format!( + "static_cast(0x{:x}{UNSIGNED_SUFFIX})", + cursor.read_u64::().unwrap() + ) } - // https://learn.microsoft.com/en-us/nimbusml/concepts/types // https://en.cppreference.com/w/cpp/types/floating-point - Il2CppTypeEnum::R4 => cursor.read_f32::().unwrap().to_string(), - Il2CppTypeEnum::R8 => cursor.read_f64::().unwrap().to_string(), + Il2CppTypeEnum::R4 => { + let val = format!("{}", cursor.read_f32::().unwrap()); + if !val.contains('.') + && val + .find(|c: char| !c.is_ascii_digit() && c != '-') + .is_none() + { + val + ".0" + } else { + val.replace("inf", "INFINITY").replace("NaN", "NAN") + } + } + Il2CppTypeEnum::R8 => { + let val = format!("{}", cursor.read_f64::().unwrap()); + if !val.contains('.') + && val + .find(|c: char| !c.is_ascii_digit() && c != '-') + .is_none() + { + val + ".0" + } else { + val.replace("inf", "INFINITY").replace("NaN", "NAN") + } + } Il2CppTypeEnum::Char => { - String::from_utf16_lossy(&[cursor.read_u16::().unwrap()]) + let res = String::from_utf16_lossy(&[cursor.read_u16::().unwrap()]) + .escape_default() + .to_string(); + + if string_quotes { + let literal_prefix = if string_as_u16 { "u" } else { "" }; + return format!("{literal_prefix}'{res}'"); + } + + res } Il2CppTypeEnum::String => { - let size = cursor.read_u32::().unwrap(); - let mut str = String::with_capacity(size as usize); - unsafe { - cursor.read_exact(str.as_bytes_mut()).unwrap(); + // UTF-16 byte array len + // which means the len is 2x the size of the string's len + let stru16_len = cursor.read_compressed_i32::().unwrap(); + if stru16_len == -1 { + return "".to_string(); + } + + let mut buf = vec![0u8; stru16_len as usize]; + + cursor.read_exact(buf.as_mut_slice()).unwrap(); + + let res = String::from_utf8(buf).unwrap().escape_default().to_string(); + + if string_quotes { + let literal_prefix = if string_as_u16 { "u" } else { "" }; + return format!("{literal_prefix}\"{res}\""); } - str + + res } + // Il2CppTypeEnum::Genericinst => match ty.data { + // TypeData::GenericClassIndex(inst_idx) => { + // let gen_class = &metadata + // .metadata + // .runtime_metadata + // .metadata_registration + // .generic_classes[inst_idx]; + + // let inner_ty = &metadata.metadata_registration.types[gen_class.type_index]; + + // Self::default_value_blob( + // metadata, + // inner_ty, + // data_index, + // string_quotes, + // string_as_u16, + // ) + // } + // _ => todo!(), + // }, Il2CppTypeEnum::Genericinst + | Il2CppTypeEnum::Byref + | Il2CppTypeEnum::Ptr + | Il2CppTypeEnum::Array | Il2CppTypeEnum::Object | Il2CppTypeEnum::Class - | Il2CppTypeEnum::Szarray => "nullptr".to_string(), + | Il2CppTypeEnum::Szarray => { + let def = Self::type_default_value(metadata, None, ty); + format!("/* TODO: Fix these default values */ {ty:?} */ {def}") + } _ => "unknown".to_string(), } } + fn unbox_nullable_valuetype<'a>(metadata: &'a Metadata, ty: &'a Il2CppType) -> &'a Il2CppType { + if let Il2CppTypeEnum::Valuetype = ty.ty { + match ty.data { + TypeData::TypeDefinitionIndex(tdi) => { + let type_def = &metadata.metadata.global_metadata.type_definitions[tdi]; + + // System.Nullable`1 + if type_def.name(metadata.metadata) == "Nullable`1" + && type_def.namespace(metadata.metadata) == "System" + { + return metadata + .metadata_registration + .types + .get(type_def.byval_type_index as usize) + .unwrap(); + } + } + _ => todo!(), + } + } + + ty + } + + fn type_default_value( + metadata: &Metadata, + cpp_type: Option<&CppType>, + ty: &Il2CppType, + ) -> String { + let matched_ty: &Il2CppType = match ty.data { + // get the generic inst + TypeData::GenericClassIndex(inst_idx) => { + let gen_class = &metadata + .metadata + .runtime_metadata + .metadata_registration + .generic_classes[inst_idx]; + + &metadata.metadata_registration.types[gen_class.type_index] + } + // get the underlying type of the generic param + TypeData::GenericParameterIndex(param) => match param.is_valid() { + true => { + let gen_param = &metadata.metadata.global_metadata.generic_parameters[param]; + + cpp_type + .and_then(|cpp_type| { + cpp_type + .generic_instantiations_args_types + .as_ref() + .and_then(|gen_args| gen_args.get(gen_param.num as usize)) + .map(|t| &metadata.metadata_registration.types[*t]) + }) + .unwrap_or(ty) + } + false => ty, + }, + _ => ty, + }; + + match matched_ty.valuetype { + true => "{}".to_string(), + false => "nullptr".to_string(), + } + } + fn field_default_value(metadata: &Metadata, field_index: FieldIndex) -> Option { metadata .metadata @@ -633,13 +3680,18 @@ pub trait CSType: Sized { .iter() .find(|f| f.field_index == field_index) .map(|def| { - let ty = metadata + let ty: &Il2CppType = metadata .metadata_registration .types .get(def.type_index as usize) .unwrap(); - Self::default_value_blob(metadata, ty.ty, def.data_index.index() as usize) + // get default value for given type + if !def.data_index.is_valid() { + return Self::type_default_value(metadata, None, ty); + } + + Self::default_value_blob(metadata, ty, def.data_index.index() as usize, true, true) }) } fn param_default_value(metadata: &Metadata, parameter_index: ParameterIndex) -> Option { @@ -657,8 +3709,11 @@ pub trait CSType: Sized { .get(def.type_index as usize) .unwrap(); + ty = Self::unbox_nullable_valuetype(metadata, ty); + + // This occurs when the type is `null` or `default(T)` for value types if !def.data_index.is_valid() { - return "nullptr".to_string(); + return Self::type_default_value(metadata, None, ty); } if let Il2CppTypeEnum::Valuetype = ty.ty { @@ -681,28 +3736,116 @@ pub trait CSType: Sized { } } - Self::default_value_blob(metadata, ty.ty, def.data_index.index() as usize) + Self::default_value_blob(metadata, ty, def.data_index.index() as usize, true, true) }) } + fn il2cpp_byref(&mut self, cpp_name: String, typ: &Il2CppType) -> String { + let requirements = &mut self.get_mut_cpp_type().requirements; + // handle out T or + // ref T when T is a value type + + // typ.valuetype -> false when T& + // apparently even if `T` is a valuetype + if typ.is_param_out() || (typ.byref && !typ.valuetype) { + requirements.needs_byref_include(); + return format!("ByRef<{cpp_name}>"); + } + + if typ.is_param_in() { + requirements.needs_byref_include(); + + return format!("ByRefConst<{cpp_name}>"); + } + + cpp_name + } + + // Basically decides to use the template param name (if applicable) + // instead of the generic instantiation of the type + // TODO: Make this less confusing + fn il2cpp_mvar_use_param_name<'a>( + &mut self, + metadata: &'a Metadata, + method_index: MethodIndex, + // use a lambda to do this lazily + cpp_name: impl FnOnce(&mut CppType) -> String, + typ: &'a Il2CppType, + ) -> String { + let tys = self + .get_mut_cpp_type() + .method_generic_instantiation_map + .remove(&method_index); + + // fast path for generic param name + // otherwise cpp_name() will default to generic param anyways + let ret = match typ.ty { + Il2CppTypeEnum::Mvar => match typ.data { + TypeData::GenericParameterIndex(index) => { + let generic_param = + &metadata.metadata.global_metadata.generic_parameters[index]; + + let owner = generic_param.owner(metadata.metadata); + assert!(owner.is_method != u32::MAX); + + generic_param.name(metadata.metadata).to_string() + } + _ => todo!(), + }, + _ => cpp_name(self.get_mut_cpp_type()), + }; + + if let Some(tys) = tys { + self.get_mut_cpp_type() + .method_generic_instantiation_map + .insert(method_index, tys); + } + + ret + } + fn cppify_name_il2cpp( &mut self, ctx_collection: &CppContextCollection, metadata: &Metadata, typ: &Il2CppType, - add_include: bool, - ) -> String { - let tag = typ.data; - - let _context_tag = ctx_collection.get_context_root_tag(tag); + include_depth: usize, + ) -> NameComponents { let cpp_type = self.get_mut_cpp_type(); - let mut nested_types: HashMap = cpp_type - .nested_types_flattened() - .into_iter() - .map(|(t, c)| (t, c.formatted_complete_cpp_name().clone())) - .collect(); - let requirements = &mut cpp_type.requirements; + let mut requirements = cpp_type.requirements.clone(); + + let res = cpp_type.cppify_name_il2cpp_recurse( + &mut requirements, + ctx_collection, + metadata, + typ, + include_depth, + cpp_type.generic_instantiations_args_types.as_ref(), + ); + + cpp_type.requirements = requirements; + + res + } + + /// [declaring_generic_inst_types] the generic instantiation of the declaring type + fn cppify_name_il2cpp_recurse( + &self, + requirements: &mut CppTypeRequirements, + ctx_collection: &CppContextCollection, + metadata: &Metadata, + typ: &Il2CppType, + include_depth: usize, + declaring_generic_inst_types: Option<&Vec>, + ) -> NameComponents { + let add_include = include_depth > 0; + let next_include_depth = if add_include { include_depth - 1 } else { 0 }; + + let typ_tag = typ.data; + + let cpp_type = self.get_cpp_type(); + match typ.ty { Il2CppTypeEnum::I1 | Il2CppTypeEnum::U1 @@ -713,82 +3856,282 @@ pub trait CSType: Sized { | Il2CppTypeEnum::I8 | Il2CppTypeEnum::U8 | Il2CppTypeEnum::I - | Il2CppTypeEnum::U - | Il2CppTypeEnum::R4 - | Il2CppTypeEnum::R8 => { + | Il2CppTypeEnum::U => { requirements.needs_int_include(); } + Il2CppTypeEnum::R4 | Il2CppTypeEnum::R8 => { + requirements.needs_math_include(); + } _ => (), }; - match typ.ty { - Il2CppTypeEnum::Object => { - requirements.need_wrapper(); - "::bs_hook::Il2CppWrapperType".to_string() - } - Il2CppTypeEnum::Valuetype | Il2CppTypeEnum::Class => { + let ret = match typ.ty { + // Commented so types use System.Object + // might revert + + // Il2CppTypeEnum::Object => { + // requirements.need_wrapper(); + // OBJECT_WRAPPER_TYPE.to_string() + // } + Il2CppTypeEnum::Object + | Il2CppTypeEnum::Valuetype + | Il2CppTypeEnum::Class + | Il2CppTypeEnum::Typedbyref => { + let typ_cpp_tag: CppTypeTag = typ_tag.into(); // Self - if tag == cpp_type.self_tag { - // TODO: println!("Warning! This is self referencing, handle this better in the future"); - return cpp_type.formatted_complete_cpp_name().clone(); + + // we add :: here since we can't add it to method ddefinitions + // e.g void ::Foo::method() <- not allowed + if typ_cpp_tag == cpp_type.self_tag { + return cpp_type.cpp_name_components.clone(); } - // Skip nested classes - if let Some(nested) = nested_types.remove(&tag) { - return nested; + if let TypeData::TypeDefinitionIndex(tdi) = typ.data { + let td = &metadata.metadata.global_metadata.type_definitions[tdi]; + + // TODO: Do we need generic inst types here? Hopefully not! + let _size = offsets::get_sizeof_type(td, tdi, None, metadata); + + if metadata.blacklisted_types.contains(&tdi) { + // classes should return Il2CppObject* + if typ.ty == Il2CppTypeEnum::Class { + return NameComponents { + name: IL2CPP_OBJECT_TYPE.to_string(), + is_pointer: true, + generics: None, + namespace: None, + declaring_types: None, + }; + } + return wrapper_type_for_tdi(td).to_string().into(); + } + } + + if add_include { + requirements.add_dependency_tag(typ_cpp_tag); } // In this case, just inherit the type // But we have to: // - Determine where to include it from - let to_incl = ctx_collection - .get_context(typ.data) - .unwrap_or_else(|| panic!("no context for type {typ:?}")); + let to_incl = ctx_collection.get_context(typ_cpp_tag).unwrap_or_else(|| { + let t = &metadata.metadata.global_metadata.type_definitions + [Self::get_tag_tdi(typ.data)]; - // - Include it - if add_include { - requirements - .required_includes - .insert(CppInclude::new_context(to_incl)); - } - let inc = CppInclude::new_context(to_incl); - let to_incl_ty = ctx_collection - .get_cpp_type(typ.data) + panic!( + "no context for type {typ:?} {}", + t.full_name(metadata.metadata, true) + ) + }); + + let other_context_ty = ctx_collection.get_context_root_tag(typ_cpp_tag); + let own_context_ty = ctx_collection.get_context_root_tag(cpp_type.self_tag); + + let typedef_incl = CppInclude::new_context_typedef(to_incl); + let typeimpl_incl = CppInclude::new_context_typeimpl(to_incl); + let to_incl_cpp_ty = ctx_collection + .get_cpp_type(typ.data.into()) .unwrap_or_else(|| panic!("Unable to get type to include {:?}", typ.data)); - // Forward declare it - if !add_include { - requirements - .forward_declares - .insert((CppForwardDeclare::from_cpp_type(to_incl_ty), inc)); + let own_context = other_context_ty == own_context_ty; + + // - Include it + // Skip including the context if we're already in it + if !own_context { + match add_include { + // add def include + true => { + requirements.add_def_include(Some(to_incl_cpp_ty), typedef_incl.clone()); + requirements.add_impl_include(Some(to_incl_cpp_ty), typeimpl_incl.clone()); + } + // TODO: Remove? + // ignore nested types + // false if to_incl_cpp_ty.nested => { + // TODO: What should we do here? + // error!("Can't forward declare nested type! Including!"); + // requirements.add_include(Some(to_incl_cpp_ty), inc); + // } + // forward declare + false => { + requirements.add_forward_declare(( + CppForwardDeclare::from_cpp_type(to_incl_cpp_ty), + typedef_incl, + )); + } + } } - to_incl_ty.formatted_complete_cpp_name().clone() + to_incl_cpp_ty.cpp_name_components.clone() + + // match to_incl_cpp_ty.is_enum_type || to_incl_cpp_ty.is_value_type { + // true => ret, + // false => format!("{ret}*"), + // } } - // TODO: MVAR and VAR + // Single dimension array Il2CppTypeEnum::Szarray => { requirements.needs_arrayw_include(); - let generic: String = match typ.data { + let generic = match typ.data { TypeData::TypeIndex(e) => { let ty = &metadata.metadata_registration.types[e]; - self.cppify_name_il2cpp(ctx_collection, metadata, ty, false) + + cpp_type.cppify_name_il2cpp_recurse( + requirements, + ctx_collection, + metadata, + ty, + include_depth, + declaring_generic_inst_types, + ) } _ => panic!("Unknown type data for array {typ:?}!"), }; - format!("::ArrayW<{generic}>") + let generic_formatted = generic.combine_all(); + + NameComponents { + name: "ArrayW".into(), + namespace: Some("".into()), + generics: Some(vec![ + generic_formatted.clone(), + format!("::Array<{generic_formatted}>*"), + ]), + is_pointer: false, + ..Default::default() + } + } + // multi dimensional array + Il2CppTypeEnum::Array => { + // FIXME: when stack further implements the TypeData::ArrayType we can actually implement this fully to be a multidimensional array, whatever that might mean + warn!("Multidimensional array was requested but this is not implemented, typ: {typ:?}, instead returning Il2CppObject!"); + NameComponents { + name: IL2CPP_OBJECT_TYPE.to_string(), + is_pointer: true, + generics: None, + namespace: None, + declaring_types: None, + } } - Il2CppTypeEnum::Mvar | Il2CppTypeEnum::Var => match typ.data { - // TODO: Alias to actual generic + Il2CppTypeEnum::Mvar => match typ.data { TypeData::GenericParameterIndex(index) => { - let generic_param = + let generic_param: &brocolib::global_metadata::Il2CppGenericParameter = + &metadata.metadata.global_metadata.generic_parameters[index]; + + let owner = generic_param.owner(metadata.metadata); + assert!(owner.is_method != u32::MAX); + + let (_gen_param_idx, gen_param) = owner + .generic_parameters(metadata.metadata) + .iter() + .find_position(|&p| p.name_index == generic_param.name_index) + .unwrap(); + + let method_index = MethodIndex::new(owner.owner_index); + let _method = &metadata.metadata.global_metadata.methods[method_index]; + + let method_args_opt = + cpp_type.method_generic_instantiation_map.get(&method_index); + + if method_args_opt.is_none() { + return gen_param.name(metadata.metadata).to_string().into(); + } + + let method_args = method_args_opt.unwrap(); + + let ty_idx = method_args[gen_param.num as usize]; + let ty = metadata + .metadata_registration + .types + .get(ty_idx as usize) + .unwrap(); + + cpp_type.cppify_name_il2cpp_recurse( + requirements, + ctx_collection, + metadata, + ty, + include_depth, + declaring_generic_inst_types, + ) + } + _ => todo!(), + }, + Il2CppTypeEnum::Var => match typ.data { + // Il2CppMetadataGenericParameterHandle + TypeData::GenericParameterIndex(index) => { + let generic_param: &brocolib::global_metadata::Il2CppGenericParameter = &metadata.metadata.global_metadata.generic_parameters[index]; - let name = generic_param.name(metadata.metadata); + let owner = generic_param.owner(metadata.metadata); + let (_gen_param_idx, _gen_param) = owner + .generic_parameters(metadata.metadata) + .iter() + .find_position(|&p| p.name_index == generic_param.name_index) + .unwrap(); + + let ty_idx_opt = cpp_type + .generic_instantiations_args_types + .as_ref() + .and_then(|args| args.get(generic_param.num as usize)) + .cloned(); - name.to_string() + // if template arg is not found + if ty_idx_opt.is_none() { + let gen_name = generic_param.name(metadata.metadata); + + // true if the type is intentionally a generic template type and not a specialization + let has_generic_template = + cpp_type.cpp_template.as_ref().is_some_and(|template| { + template.just_names().any(|name| name == gen_name) + }); + + return match has_generic_template { + true => gen_name.to_string().into(), + false => panic!("/* TODO: FIX THIS, THIS SHOULDN'T HAPPEN! NO GENERIC INST ARGS FOUND HERE */ {gen_name}"), + }; + } + + let ty_var = &metadata.metadata_registration.types[ty_idx_opt.unwrap()]; + + let generics = &cpp_type + .cpp_name_components + .generics + .as_ref() + .expect("Generic instantiation args not made yet!"); + + let resolved_var = generics + .get(generic_param.num as usize) + .expect("No generic parameter at index found!") + .clone(); + + let is_pointer = !ty_var.valuetype + // if resolved_var exists in generic template, it can't be a pointer! + && (cpp_type.cpp_template.is_none() + || !cpp_type + .cpp_template + .as_ref() + .is_some_and(|t| t.just_names().any(|s| s == &resolved_var))); + + NameComponents { + is_pointer, + name: resolved_var, + ..Default::default() + } + + // This is for calculating on the fly + // which is slower and won't work for the reference type lookup fix + // we do in make_generic_args + + // let ty_idx = ty_idx_opt.unwrap(); + + // let ty = metadata + // .metadata_registration + // .types + // .get(ty_idx as usize) + // .unwrap(); + // self.cppify_name_il2cpp(ctx_collection, metadata, ty, add_include) } _ => todo!(), }, @@ -801,59 +4144,155 @@ pub trait CSType: Sized { .get(generic_class.context.class_inst_idx.unwrap()) .unwrap(); - let types = generic_inst - .types + let new_generic_inst_types = &generic_inst.types; + + let generic_type_def = &mr.types[generic_class.type_index]; + let TypeData::TypeDefinitionIndex(tdi) = generic_type_def.data else { + panic!() + }; + + if add_include { + let generic_tag = CppTypeTag::from_type_data(typ.data, metadata.metadata); + + // depend on both tdi and generic instantiation + requirements.add_dependency_tag(tdi.into()); + requirements.add_dependency_tag(generic_tag); + } + + let generic_types_formatted = new_generic_inst_types + // let generic_types_formatted = new_generic_inst_types .iter() .map(|t| mr.types.get(*t).unwrap()) - .map(|t| self.cppify_name_il2cpp(ctx_collection, metadata, t, false)); + // if t is a Var, we use the generic inst provided by the caller + // TODO: This commented code breaks generic params where we intentionally use the template name + // .map(|inst_t| match inst_t.data { + // TypeData::GenericParameterIndex(gen_param_idx) => { + // let gen_param = + // &metadata.metadata.global_metadata.generic_parameters + // [gen_param_idx]; + // declaring_generic_inst_types + // .and_then(|declaring_generic_inst_types| { + // // TODO: Figure out why we this goes out of bounds + // declaring_generic_inst_types.get(gen_param.num as usize) + // }) + // .map(|t| &mr.types[*t]) + // // fallback to T since generic typedefs can be called + // .unwrap_or(inst_t) + // } + // _ => inst_t, + // }) + .map(|t| { + cpp_type.cppify_name_il2cpp_recurse( + requirements, + ctx_collection, + metadata, + t, + next_include_depth, + // use declaring generic inst since we're cppifying generic args + declaring_generic_inst_types, + ) + }) + .map(|n| n.combine_all()) + .collect_vec(); - let generic_types = types.collect_vec(); + let generic_type_def = &mr.types[generic_class.type_index]; + let type_def_name_components = cpp_type.cppify_name_il2cpp_recurse( + requirements, + ctx_collection, + metadata, + generic_type_def, + include_depth, + Some(new_generic_inst_types), + ); - let generic_type = &mr.types[generic_class.type_index]; - let owner_name = - self.cppify_name_il2cpp(ctx_collection, metadata, generic_type, false); - - format!("{owner_name}<{}>", generic_types.join(",")) + // add generics to type def + NameComponents { + generics: Some(generic_types_formatted), + ..type_def_name_components + } } _ => panic!("Unknown type data for generic inst {typ:?}!"), }, - Il2CppTypeEnum::I1 => "int8_t".to_string(), - Il2CppTypeEnum::I2 => "int16_t".to_string(), - Il2CppTypeEnum::I4 => "int32_t".to_string(), - // TODO: We assume 64 bit - Il2CppTypeEnum::I | Il2CppTypeEnum::I8 => "int64_t".to_string(), - Il2CppTypeEnum::U1 => "uint8_t".to_string(), - Il2CppTypeEnum::U2 => "uint16_t".to_string(), - Il2CppTypeEnum::U4 => "uint32_t".to_string(), - // TODO: We assume 64 bit - Il2CppTypeEnum::U | Il2CppTypeEnum::U8 => "uint64_t".to_string(), + Il2CppTypeEnum::I1 => "int8_t".to_string().into(), + Il2CppTypeEnum::I2 => "int16_t".to_string().into(), + Il2CppTypeEnum::I4 => "int32_t".to_string().into(), + Il2CppTypeEnum::I8 => "int64_t".to_string().into(), + Il2CppTypeEnum::I => "void*".to_string().into(), + Il2CppTypeEnum::U1 => "uint8_t".to_string().into(), + Il2CppTypeEnum::U2 => "uint16_t".to_string().into(), + Il2CppTypeEnum::U4 => "uint32_t".to_string().into(), + Il2CppTypeEnum::U8 => "uint64_t".to_string().into(), + Il2CppTypeEnum::U => "void*".to_string().into(), // https://learn.microsoft.com/en-us/nimbusml/concepts/types // https://en.cppreference.com/w/cpp/types/floating-point - Il2CppTypeEnum::R4 => "float32_t".to_string(), - Il2CppTypeEnum::R8 => "float64_t".to_string(), + Il2CppTypeEnum::R4 => "float_t".to_string().into(), + Il2CppTypeEnum::R8 => "double_t".to_string().into(), - Il2CppTypeEnum::Void => "void".to_string(), - Il2CppTypeEnum::Boolean => "bool".to_string(), - Il2CppTypeEnum::Char => "char16_t".to_string(), + Il2CppTypeEnum::Void => "void".to_string().into(), + Il2CppTypeEnum::Boolean => "bool".to_string().into(), + Il2CppTypeEnum::Char => "char16_t".to_string().into(), Il2CppTypeEnum::String => { requirements.needs_stringw_include(); - "::StringW".to_string() + "::StringW".to_string().into() + } + Il2CppTypeEnum::Ptr => { + let generic = match typ.data { + TypeData::TypeIndex(e) => { + let ty = &metadata.metadata_registration.types[e]; + cpp_type.cppify_name_il2cpp_recurse( + requirements, + ctx_collection, + metadata, + ty, + include_depth, + declaring_generic_inst_types, + ) + } + + _ => panic!("Unknown type data for array {typ:?}!"), + }; + + let generic_formatted = generic.combine_all(); + + NameComponents { + namespace: Some("cordl_internals".into()), + generics: Some(vec![generic_formatted]), + name: "Ptr".into(), + ..Default::default() + } } - Il2CppTypeEnum::Ptr => "void*".to_owned(), + // Il2CppTypeEnum::Typedbyref => { + // // TODO: test this + // if add_include && let TypeData::TypeDefinitionIndex(tdi) = typ.data { + // cpp_type.requirements.add_dependency_tag(tdi.into()); + // } + + // "::System::TypedReference".to_string() + // // "::cordl_internals::TypedByref".to_string() + // }, // TODO: Void and the other primitives - _ => format!("/* UNKNOWN TYPE! {typ:?} */"), - } + _ => format!("/* UNKNOWN TYPE! {typ:?} */").into(), + }; + + ret } fn classof_cpp_name(&self) -> String { format!( "::il2cpp_utils::il2cpp_type_check::il2cpp_no_arg_class<{}>::get", - self.get_cpp_type().formatted_complete_cpp_name() + self.get_cpp_type().cpp_name_components.combine_all() ) } + fn type_name_byref_fixup(ty: &Il2CppType, name: &str) -> String { + match ty.valuetype { + true => name.to_string(), + false => format!("{name}*"), + } + } + fn get_type_definition<'a>( metadata: &'a Metadata, tdi: TypeDefinitionIndex, @@ -862,11 +4301,181 @@ pub trait CSType: Sized { } } +fn wrapper_type_for_tdi(td: &Il2CppTypeDefinition) -> &str { + if td.is_enum_type() { + return ENUM_WRAPPER_TYPE; + } + + if td.is_value_type() { + return VALUE_WRAPPER_TYPE; + } + + if td.is_interface() { + return INTERFACE_WRAPPER_TYPE; + } + + IL2CPP_OBJECT_TYPE +} + +/// +/// This makes generic args for types such as ValueTask> work +/// by recursively checking if any generic arg is a reference or numeric type (for enums) +/// +fn parse_generic_arg( + t: &Il2CppType, + gen_name: String, + cpp_type: &mut CppType, + ctx_collection: &CppContextCollection, + metadata: &Metadata<'_>, + template_args: &mut Vec<(String, String)>, +) -> NameComponents { + // If reference type, we use a template and add a requirement + if !t.valuetype { + template_args.push(( + CORDL_REFERENCE_TYPE_CONSTRAINT.to_string(), + gen_name.clone(), + )); + return gen_name.into(); + } + + /* + mscorelib.xml + + + + + + + + + + */ + let enum_system_type_discriminator = match t.data { + TypeData::TypeDefinitionIndex(tdi) => { + let td = &metadata.metadata.global_metadata.type_definitions[tdi]; + let namespace = td.namespace(metadata.metadata); + let name = td.name(metadata.metadata); + + if namespace == "System" { + match name { + "SByteEnum" => Some(Il2CppTypeEnum::I1), + "Int16Enum" => Some(Il2CppTypeEnum::I2), + "Int32Enum" => Some(Il2CppTypeEnum::I4), + "Int64Enum" => Some(Il2CppTypeEnum::I8), + "ByteEnum" => Some(Il2CppTypeEnum::U1), + "UInt16Enum" => Some(Il2CppTypeEnum::U2), + "UInt32Enum" => Some(Il2CppTypeEnum::U4), + "UInt64Enum" => Some(Il2CppTypeEnum::U8), + _ => None, + } + } else { + None + } + } + _ => None, + }; + + let inner_enum_type = enum_system_type_discriminator.map(|e| Il2CppType { + attrs: u16::MAX, + byref: false, + data: TypeData::TypeIndex(usize::MAX), + pinned: false, + ty: e, + valuetype: true, + }); + + // if int, int64 etc. + // this allows for enums to be supported + // if matches!( + // t.ty, + // Il2CppTypeEnum::I1 + // | Il2CppTypeEnum::I2 + // | Il2CppTypeEnum::I4 + // | Il2CppTypeEnum::I8 + // | Il2CppTypeEnum::U1 + // | Il2CppTypeEnum::U2 + // | Il2CppTypeEnum::U4 + // | Il2CppTypeEnum::U8 + // ) || + if let Some(inner_enum_type) = inner_enum_type { + let inner_enum_type_cpp = cpp_type + .cppify_name_il2cpp(ctx_collection, metadata, &inner_enum_type, 0) + .combine_all(); + + template_args.push(( + format!("{CORDL_NUM_ENUM_TYPE_CONSTRAINT}<{inner_enum_type_cpp}>",), + gen_name.clone(), + )); + + return gen_name.into(); + } + + let inner_type = cpp_type.cppify_name_il2cpp(ctx_collection, metadata, t, 0); + + match t.data { + TypeData::GenericClassIndex(gen_class_idx) => { + let gen_class = &metadata.metadata_registration.generic_classes[gen_class_idx]; + let gen_class_ty = &metadata.metadata_registration.types[gen_class.type_index]; + let TypeData::TypeDefinitionIndex(gen_class_tdi) = gen_class_ty.data else { + todo!() + }; + let gen_class_td = &metadata.metadata.global_metadata.type_definitions[gen_class_tdi]; + + let gen_container = gen_class_td.generic_container(metadata.metadata); + + let gen_class_inst = &metadata.metadata_registration.generic_insts + [gen_class.context.class_inst_idx.unwrap()]; + + // this relies on the fact TDIs do not include their generic params + let non_generic_inner_type = + cpp_type.cppify_name_il2cpp(ctx_collection, metadata, gen_class_ty, 0); + + let inner_generic_params = gen_class_inst + .types + .iter() + .enumerate() + .map(|(param_idx, u)| { + let t = metadata.metadata_registration.types.get(*u).unwrap(); + let gen_param = gen_container + .generic_parameters(metadata.metadata) + .iter() + .find(|p| p.num as usize == param_idx) + .expect("No generic param at this num"); + + (t, gen_param) + }) + .map(|(t, gen_param)| { + let inner_gen_name = gen_param.name(metadata.metadata).to_owned(); + let mangled_gen_name = + format!("{inner_gen_name}_cordlgen_{}", template_args.len()); + parse_generic_arg( + t, + mangled_gen_name, + cpp_type, + ctx_collection, + metadata, + template_args, + ) + }) + .map(|n| n.combine_all()) + .collect_vec(); + + NameComponents { + generics: Some(inner_generic_params), + ..non_generic_inner_type + } + } + _ => inner_type, + } +} + impl CSType for CppType { + #[inline(always)] fn get_mut_cpp_type(&mut self) -> &mut CppType { self } + #[inline(always)] fn get_cpp_type(&self) -> &CppType { self } diff --git a/src/generate/members.rs b/src/generate/members.rs index ec5d87662..097436e19 100644 --- a/src/generate/members.rs +++ b/src/generate/members.rs @@ -1,11 +1,78 @@ use itertools::Itertools; +use pathdiff::diff_paths; -use super::{context::CppContext, cpp_type::CppType}; -use std::path::PathBuf; +use crate::STATIC_CONFIG; + +use super::{ + context::CppContext, + cpp_type::{CppType, CORDL_REFERENCE_TYPE_CONSTRAINT}, + writer::Writable, +}; + +use std::{ + collections::HashMap, + hash::Hash, + path::{Path, PathBuf}, + rc::Rc, + sync::Arc, +}; #[derive(Debug, Eq, Hash, PartialEq, Clone, Default, PartialOrd, Ord)] pub struct CppTemplate { - pub names: Vec, + pub names: Vec<(String, String)>, +} + +impl CppTemplate { + pub fn make_typenames(names: impl Iterator) -> Self { + CppTemplate { + names: names + .into_iter() + .map(|s| ("typename".to_string(), s)) + .collect(), + } + } + pub fn make_ref_types(names: impl Iterator) -> Self { + CppTemplate { + names: names + .into_iter() + .map(|s| (CORDL_REFERENCE_TYPE_CONSTRAINT.to_string(), s)) + .collect(), + } + } + + pub fn just_names(&self) -> impl Iterator { + self.names.iter().map(|(_constraint, t)| t) + } +} + +#[derive(Debug, Eq, Hash, PartialEq, Clone, Default, PartialOrd, Ord)] +pub struct CppStaticAssert { + pub condition: String, + pub message: Option, +} + +#[derive(Debug, Eq, Hash, PartialEq, Clone, Default, PartialOrd, Ord)] +pub struct CppLine { + pub line: String, +} + +impl From for CppLine { + fn from(value: String) -> Self { + CppLine { line: value } + } +} +impl From<&str> for CppLine { + fn from(value: &str) -> Self { + CppLine { + line: value.to_string(), + } + } +} + +impl CppLine { + pub fn make(v: String) -> Self { + CppLine { line: v } + } } #[derive(Debug, Eq, Hash, PartialEq, Clone)] @@ -20,9 +87,10 @@ pub struct CppForwardDeclareGroup { pub struct CppForwardDeclare { // TODO: Make this group lots into a single namespace pub is_struct: bool, - pub namespace: Option, - pub name: String, - pub templates: CppTemplate, // names of template arguments, T, TArgs etc. + pub cpp_namespace: Option, + pub cpp_name: String, + pub templates: Option, // names of template arguments, T, TArgs etc. + pub literals: Option>, } #[derive(Debug, Eq, Hash, PartialEq, Clone)] @@ -37,15 +105,37 @@ pub struct CppInclude { pub system: bool, } -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct CppUsingAlias { + pub result: String, + pub alias: String, + pub template: Option, +} + +#[derive(Clone, Debug)] pub enum CppMember { - Field(CppField), + FieldDecl(CppFieldDecl), + FieldImpl(CppFieldImpl), MethodDecl(CppMethodDecl), MethodImpl(CppMethodImpl), - Property(CppProperty), - Comment(CppCommentedString), + Property(CppPropertyDecl), ConstructorDecl(CppConstructorDecl), ConstructorImpl(CppConstructorImpl), + NestedStruct(CppNestedStruct), + NestedUnion(CppNestedUnion), + CppUsingAlias(CppUsingAlias), + Comment(CppCommentedString), + CppStaticAssert(CppStaticAssert), + CppLine(CppLine), +} + +#[derive(Clone, Debug)] +pub enum CppNonMember { + SizeStruct(Box), + CppUsingAlias(CppUsingAlias), + Comment(CppCommentedString), + CppStaticAssert(CppStaticAssert), + CppLine(CppLine), } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -54,31 +144,75 @@ pub struct CppMethodData { pub addrs: u64, } -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct CppMethodSizeStruct { pub cpp_method_name: String, - pub complete_type_name: String, + pub method_name: String, + pub declaring_type_name: String, + pub declaring_classof_call: String, pub ret_ty: String, pub instance: bool, pub params: Vec, pub method_data: CppMethodData, - pub template: CppTemplate, + // this is so bad + pub method_info_lines: Vec, + pub method_info_var: String, + + pub template: Option, + pub generic_literals: Option>, pub interface_clazz_of: String, pub is_final: bool, pub slot: Option, } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct CppField { - pub name: String, - pub ty: String, +pub struct CppFieldDecl { + pub cpp_name: String, + pub field_ty: String, pub offset: u32, pub instance: bool, pub readonly: bool, - pub classof_call: String, - pub literal_value: Option, - pub use_wrapper: bool, + pub const_expr: bool, + pub value: Option, + pub brief_comment: Option, + pub is_private: bool, +} +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct CppFieldImpl { + pub declaring_type: String, + pub declaring_type_template: Option, + pub cpp_name: String, + pub field_ty: String, + pub readonly: bool, + pub const_expr: bool, + pub value: String, +} + +impl From for CppFieldImpl { + fn from(value: CppFieldDecl) -> Self { + Self { + const_expr: value.const_expr, + readonly: value.readonly, + cpp_name: value.cpp_name, + field_ty: value.field_ty, + declaring_type: "".to_string(), + declaring_type_template: Default::default(), + value: value.value.unwrap_or_default(), + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct CppPropertyDecl { + pub cpp_name: String, + pub prop_ty: String, + pub instance: bool, + pub getter: Option, + pub setter: Option, + /// Whether this property is one that's indexable (accessor methods take an index argument) + pub indexable: bool, + pub brief_comment: Option, } #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -97,149 +231,332 @@ pub struct CppParam { } // TODO: Generics -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct CppMethodDecl { pub cpp_name: String, pub return_type: String, pub parameters: Vec, pub instance: bool, - pub template: CppTemplate, + pub template: Option, // TODO: Use bitflags to indicate these attributes // Holds unique of: // const // override // noexcept - pub suffix_modifiers: String, + pub suffix_modifiers: Vec, // Holds unique of: // constexpr // static // inline // explicit(...) // virtual - pub prefix_modifiers: String, - // TODO: Add all descriptions missing for the method - pub method_data: CppMethodData, + pub prefix_modifiers: Vec, pub is_virtual: bool, + pub is_constexpr: bool, + pub is_const: bool, + pub is_no_except: bool, + pub is_operator: bool, + pub is_inline: bool, + + pub brief: Option, + pub body: Option>>, +} + +impl From for CppMethodImpl { + fn from(value: CppMethodDecl) -> Self { + Self { + body: value.body.unwrap_or_default(), + brief: value.brief, + cpp_method_name: value.cpp_name, + declaring_cpp_full_name: "".into(), + instance: value.instance, + is_const: value.is_const, + is_no_except: value.is_no_except, + is_operator: value.is_operator, + is_virtual: value.is_virtual, + is_constexpr: value.is_constexpr, + is_inline: value.is_inline, + parameters: value.parameters, + prefix_modifiers: value.prefix_modifiers, + suffix_modifiers: value.suffix_modifiers, + return_type: value.return_type, + template: value.template, + declaring_type_template: Default::default(), + } + } } // TODO: Generic -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct CppMethodImpl { pub cpp_method_name: String, - pub cs_method_name: String, - - pub holder_cpp_namespaze: String, - pub holder_cpp_name: String, + pub declaring_cpp_full_name: String, pub return_type: String, pub parameters: Vec, pub instance: bool, - pub template: CppTemplate, + pub declaring_type_template: Option, + pub template: Option, + pub is_const: bool, + pub is_virtual: bool, + pub is_constexpr: bool, + pub is_no_except: bool, + pub is_operator: bool, + pub is_inline: bool, + // TODO: Use bitflags to indicate these attributes // Holds unique of: // const // override // noexcept - pub suffix_modifiers: String, + pub suffix_modifiers: Vec, // Holds unique of: // constexpr // static // inline // explicit(...) // virtual - pub prefix_modifiers: String, + pub prefix_modifiers: Vec, + + pub brief: Option, + pub body: Vec>, } // TODO: Generics -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct CppConstructorDecl { - pub ty: String, + pub cpp_name: String, pub parameters: Vec, - pub template: CppTemplate, + pub template: Option, + + pub is_constexpr: bool, + pub is_explicit: bool, + pub is_default: bool, + pub is_no_except: bool, + pub is_delete: bool, + pub is_protected: bool, + + // call base ctor + pub base_ctor: Option<(String, String)>, + pub initialized_values: HashMap, + + pub brief: Option, + pub body: Option>>, } -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug)] pub struct CppConstructorImpl { - pub holder_cpp_ty_name: String, + pub declaring_full_name: String, + pub declaring_name: String, pub parameters: Vec, + pub base_ctor: Option<(String, String)>, + pub initialized_values: HashMap, + pub is_constexpr: bool, - pub template: CppTemplate, + pub is_no_except: bool, + pub is_default: bool, + + pub template: Option, + + pub body: Vec>, } -#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct CppProperty { - pub name: String, - pub ty: String, - pub setter: Option, - pub getter: Option, - pub abstr: bool, - pub instance: bool, - pub classof_call: String, +#[derive(Clone, Debug)] +pub struct CppNestedStruct { + pub declaring_name: String, + pub base_type: Option, + pub declarations: Vec>, + pub is_enum: bool, + pub is_class: bool, + pub is_private: bool, + pub brief_comment: Option, + pub packing: Option, +} + +#[derive(Clone, Debug)] +pub struct CppNestedUnion { + pub declarations: Vec>, + pub brief_comment: Option, + pub offset: u32, + pub is_private: bool, +} + +impl From for CppConstructorImpl { + fn from(value: CppConstructorDecl) -> Self { + Self { + body: value.body.unwrap_or_default(), + declaring_full_name: value.cpp_name.clone(), + declaring_name: value.cpp_name, + is_constexpr: value.is_constexpr, + is_default: value.is_default, + base_ctor: value.base_ctor, + initialized_values: value.initialized_values, + is_no_except: value.is_no_except, + parameters: value.parameters, + template: value.template, + } + } } -// Writing impl CppForwardDeclare { pub fn from_cpp_type(cpp_type: &CppType) -> Self { + Self::from_cpp_type_long(cpp_type, false) + } + pub fn from_cpp_type_long(cpp_type: &CppType, force_generics: bool) -> Self { + let ns = if !cpp_type.nested { + Some(cpp_type.cpp_namespace().to_string()) + } else { + None + }; + + assert!( + cpp_type.cpp_name_components.declaring_types.is_none(), + "Can't forward declare nested types!" + ); + + // literals should only be added for generic specializations + let literals = if cpp_type.generic_instantiations_args_types.is_some() || force_generics { + cpp_type.cpp_name_components.generics.clone() + } else { + None + }; + Self { is_struct: cpp_type.is_value_type, - namespace: Some(cpp_type.cpp_namespace().to_string()), - name: cpp_type.name().clone(), - templates: cpp_type.generic_args.clone(), + cpp_namespace: ns, + cpp_name: cpp_type.cpp_name().clone(), + templates: cpp_type.cpp_template.clone(), + literals, } } } impl CppParam { - pub fn params_as_args(params: &[CppParam]) -> String { - params - .iter() - .map(|p| match &p.def_value { - Some(val) => format!("{}{} {} = {val}", p.ty, p.modifiers, p.name), - None => format!("{}{} {}", p.ty, p.modifiers, p.name), - }) - .join(", ") + pub fn params_as_args(params: &[CppParam]) -> impl Iterator + '_ { + params.iter().map(|p| match &p.def_value { + Some(val) => format!("{}{} {} = {val}", p.ty, p.modifiers, p.name), + None => format!("{} {} {}", p.ty, p.modifiers, p.name), + }) } - pub fn params_as_args_no_default(params: &[CppParam]) -> String { + pub fn params_as_args_no_default(params: &[CppParam]) -> impl Iterator + '_ { params .iter() - .map(|p| format!("{}{} {}", p.ty, p.modifiers, p.name)) - .join(", ") + .map(|p| format!("{} {} {}", p.ty, p.modifiers, p.name)) } - pub fn params_names(params: &[CppParam]) -> String { - params.iter().map(|p| &p.name).join(", ") + pub fn params_names(params: &[CppParam]) -> impl Iterator { + params.iter().map(|p| &p.name) } - pub fn params_types(params: &[CppParam]) -> String { - params.iter().map(|p| &p.ty).join(", ") + pub fn params_types(params: &[CppParam]) -> impl Iterator { + params.iter().map(|p| &p.ty) } - pub fn params_il2cpp_types(params: &[CppParam]) -> String { + pub fn params_il2cpp_types(params: &[CppParam]) -> impl Iterator + '_ { params .iter() .map(|p| format!("::il2cpp_utils::ExtractType({})", p.name)) - .join(", ") } } impl CppInclude { - pub fn new_context(context: &CppContext) -> Self { + // smelly use of config but whatever + pub fn new_context_typedef(context: &CppContext) -> Self { + Self { + include: diff_paths(&context.typedef_path, &STATIC_CONFIG.header_path).unwrap(), + system: false, + } + } + pub fn new_context_typeimpl(context: &CppContext) -> Self { + Self { + include: diff_paths(&context.type_impl_path, &STATIC_CONFIG.header_path).unwrap(), + system: false, + } + } + pub fn new_context_fundamental(context: &CppContext) -> Self { Self { - include: context.fundamental_path.clone(), + include: diff_paths(&context.fundamental_path, &STATIC_CONFIG.header_path).unwrap(), system: false, } } - pub fn new_system(str: PathBuf) -> Self { + pub fn new_system>(str: P) -> Self { Self { - include: str, + include: str.as_ref().to_path_buf(), system: true, } } - pub fn new(str: PathBuf) -> Self { + pub fn new_exact>(str: P) -> Self { Self { - include: str, + include: str.as_ref().to_path_buf(), system: false, } } } + +impl CppUsingAlias { + // TODO: Rewrite + pub fn from_cpp_type( + alias: String, + cpp_type: &CppType, + forwarded_generic_args_opt: Option>, + fixup_generic_args: bool, + ) -> Self { + let forwarded_generic_args = forwarded_generic_args_opt.unwrap_or_default(); + + // splits literals and template + let (literal_args, template) = match &cpp_type.cpp_template { + Some(other_template) => { + // Skip the first args as those aren't necessary + let extra_template_args = other_template + .names + .iter() + .skip(forwarded_generic_args.len()) + .cloned() + .collect_vec(); + + let remaining_cpp_template = match !extra_template_args.is_empty() { + true => Some(CppTemplate { + names: extra_template_args, + }), + false => None, + }; + + // Essentially, all nested types inherit their declaring type's generic params. + // Append the rest of the template params as generic parameters + match remaining_cpp_template { + Some(remaining_cpp_template) => ( + forwarded_generic_args + .iter() + .chain(remaining_cpp_template.just_names()) + .cloned() + .collect_vec(), + Some(remaining_cpp_template), + ), + None => (forwarded_generic_args, None), + } + } + None => (forwarded_generic_args, None), + }; + + let do_fixup = fixup_generic_args && !literal_args.is_empty(); + + let mut name_components = cpp_type.cpp_name_components.clone(); + if do_fixup { + name_components = name_components.remove_generics(); + } + + let mut result = name_components.remove_pointer().combine_all(); + + // easy way to tell it's a generic instantiation + if do_fixup { + result = format!("{result}<{}>", literal_args.join(", ")) + } + + Self { + alias, + result, + template, + } + } +} diff --git a/src/generate/members_serialize.rs b/src/generate/members_serialize.rs index ddd805413..5613eb540 100644 --- a/src/generate/members_serialize.rs +++ b/src/generate/members_serialize.rs @@ -1,23 +1,22 @@ use super::{ members::*, - writer::{CppWriter, Writable}, + writer::{CppWriter, SortLevel, Sortable, Writable}, }; + use itertools::Itertools; use std::io::Write; impl Writable for CppTemplate { fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { - if !self.names.is_empty() { - writeln!( - writer, - "template<{}>", - self.names - .iter() - .map(|s| format!("typename {s}")) - .collect::>() - .join(",") - )?; - } + writeln!( + writer, + "template<{}>", + self.names + .iter() + .map(|(constraint, t)| format!("{constraint} {t}")) + .collect_vec() + .join(",") + )?; Ok(()) } @@ -25,23 +24,37 @@ impl Writable for CppTemplate { impl Writable for CppForwardDeclare { fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { - if let Some(namespace) = &self.namespace { + if let Some(namespace) = &self.cpp_namespace { writeln!(writer, "namespace {namespace} {{")?; } - self.templates.write(writer)?; + if let Some(templates) = &self.templates { + templates.write(writer)?; + } + + // don't write template twice + if self.literals.is_some() && self.templates.is_none() { + // forward declare for instantiation + writeln!(writer, "template<>")?; + } + + let name = match &self.literals { + Some(literals) => { + format!("{}<{}>", self.cpp_name, literals.join(",")) + } + None => self.cpp_name.clone(), + }; writeln!( writer, - "{} {};", + "{} {name};", match self.is_struct { true => "struct", false => "class", - }, - self.name + } )?; - if self.namespace.is_some() { + if self.cpp_namespace.is_some() { writeln!(writer, "}}")?; } @@ -51,325 +64,769 @@ impl Writable for CppForwardDeclare { impl Writable for CppCommentedString { fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + writeln!(writer, "{}", self.data)?; if let Some(val) = &self.comment { writeln!(writer, "// {val}")?; } - writeln!(writer, "{}", self.data)?; Ok(()) } } impl Writable for CppInclude { fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + // this is so bad + let path = if cfg!(windows) { + self.include.to_string_lossy().replace('\\', "/") + } else { + self.include.to_string_lossy().to_string() + }; + if self.system { - writeln!(writer, "#include <{}>", self.include.to_str().unwrap())?; + writeln!(writer, "#include <{path}>")?; } else { - writeln!(writer, "#include \"{}\"", self.include.to_str().unwrap())?; + writeln!(writer, "#include \"{path}\"")?; } Ok(()) } } +impl Writable for CppUsingAlias { + fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + if let Some(template) = &self.template { + template.write(writer)?; + } -impl Writable for CppField { + // TODO: Figure out how to forward template + if let Some(_template) = &self.template { + writeln!(writer, "using {} = {};", self.alias, self.result)?; + } else { + writeln!(writer, "using {} = {};", self.alias, self.result)?; + } + + Ok(()) + } +} +impl Sortable for CppUsingAlias { + fn sort_level(&self) -> SortLevel { + SortLevel::UsingAlias + } +} + +impl Writable for CppFieldDecl { fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - writeln!( - writer, - "// Field: name: {}, Type Name: {}, Offset: 0x{:x}", - self.name, self.ty, self.offset - )?; + if let Some(comment) = &self.brief_comment { + writeln!(writer, "/// @brief {comment}")?; + } + + if self.is_private { + writeln!(writer, "private:")?; + } + + let ty = &self.field_ty; + let name = &self.cpp_name; + let mut prefix_mods: Vec<&str> = vec![]; + let mut suffix_mods: Vec<&str> = vec![]; + + if !self.instance { + prefix_mods.push("static"); + } + + if self.const_expr { + prefix_mods.push("constexpr"); + } else if self.readonly { + suffix_mods.push("const"); + } - let cpp_name = if self.literal_value.is_some() { - format!("_{}", &self.name) + let prefixes = prefix_mods.join(" "); + let suffixes = suffix_mods.join(" "); + + if let Some(value) = &self.value { + writeln!(writer, "{prefixes} {ty} {suffixes} {name}{{{value}}};")?; } else { - self.name.to_string() - }; + writeln!(writer, "{prefixes} {ty} {suffixes} {name};")?; + } - match self.use_wrapper { - // no wrapper/is C++ literal - false => writeln!( - writer, - "{}{} {} = {}", - if self.instance { "" } else { "inline static " }, - self.ty, - self.name, - self.literal_value.as_ref().unwrap_or(&"{}".to_string()) - )?, - // wrapper - true => { - // literal il2cpp value - if let Some(literal) = &self.literal_value { - writeln!(writer, "constexpr {} {} = {literal};", self.ty, self.name)?; - } - if self.instance { - writeln!( - writer, - "::bs_hook::InstanceField<{}, 0x{:x},{}> {cpp_name};", - self.ty, self.offset, self.readonly - )?; - } else { - writeln!( - writer, - "static inline ::bs_hook::StaticField<{},\"{}\",&{},{}> {cpp_name};", - self.ty, self.name, self.classof_call, self.readonly - )?; - } - } + if self.is_private { + writeln!(writer, "public:")?; } Ok(()) } } -impl Writable for CppMethodDecl { - // declaration - fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - writeln!( - writer, - "// Method: name: {}, Return Type Name: {} Parameters: {:?} Addr {:x} Size {:x}", - self.cpp_name, - self.return_type, - self.parameters, - self.method_data.addrs, - self.method_data.estimated_size - )?; +impl Sortable for CppFieldDecl { + fn sort_level(&self) -> SortLevel { + SortLevel::Fields + } +} - self.template.write(writer)?; +impl Writable for CppFieldImpl { + fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + if let Some(template) = &self.declaring_type_template { + template.write(writer)?; + } - if !self.instance { - write!(writer, "static ")?; - } else if self.is_virtual { - write!(writer, "virtual ")?; + let ty = &self.field_ty; + let name = &self.cpp_name; + let declaring_ty = self + .declaring_type + .strip_prefix("::") + .unwrap_or(&self.declaring_type); + let mut prefix_mods: Vec<&str> = vec![]; + let mut suffix_mods: Vec<&str> = vec![]; + + if self.const_expr { + prefix_mods.push("constexpr"); + } else if self.readonly { + suffix_mods.push("const"); } + + let prefixes = prefix_mods.join(" "); + let suffixes = suffix_mods.join(" "); + + let value = &self.value; writeln!( writer, - "{} {}({});", - self.return_type, - self.cpp_name, - CppParam::params_as_args(&self.parameters) + "{prefixes} {ty} {suffixes} {declaring_ty}::{name}{{{value}}};" )?; Ok(()) } } +impl Sortable for CppFieldImpl { + fn sort_level(&self) -> SortLevel { + SortLevel::FieldsImpl + } +} -impl Writable for CppMethodImpl { +impl Writable for CppMethodDecl { // declaration fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - self.template.write(writer)?; - - if !self.instance { - write!(writer, "static ")?; + if let Some(brief) = &self.brief { + writeln!(writer, "/// @brief {brief}")?; } - // Start - writeln!( - writer, - "{} {}::{}({}){{", - self.return_type, - self.holder_cpp_name, - self.cpp_method_name, - CppParam::params_as_args_no_default(&self.parameters) - )?; + // Param default comments + self.parameters + .iter() + .filter(|t| t.def_value.is_some()) + .try_for_each(|param| { + writeln!( + writer, + "/// @param {}: {} (default: {})", + param.name, + param.ty, + param.def_value.as_ref().unwrap() + ) + })?; + + if let Some(template) = &self.template { + template.write(writer)?; + } - // static auto ___internal__logger = ::Logger::get().WithContext("::Org::BouncyCastle::Crypto::Parameters::DHPrivateKeyParameters::Equals"); - // auto* ___internal__method = THROW_UNLESS((::il2cpp_utils::FindMethod(this, "Equals", std::vector{}, ::std::vector{::il2cpp_utils::ExtractType(obj)}))); - // return ::il2cpp_utils::RunMethodRethrow(this, ___internal__method, obj); + let body = &self.body; - // Body + let mut prefix_modifiers = self + .prefix_modifiers + .iter() + .map(|s| s.as_str()) + .collect_vec(); - let complete_type_name = format!("{}::{}", self.holder_cpp_namespaze, self.holder_cpp_name); - let params_format = CppParam::params_types(&self.parameters); + let mut suffix_modifiers = self + .suffix_modifiers + .iter() + .map(|s| s.as_str()) + .collect_vec(); - writeln!(writer, "static auto ___internal_method = ::il2cpp_utils::il2cpp_type_check::MetadataGetter(&{complete_type_name}::{})>::methodInfo();", - self.return_type, - self.cpp_method_name)?; + if !self.instance { + prefix_modifiers.push("static"); + } - write!( - writer, - "return ::il2cpp_utils::RunMethodRethrow<{}, false>(this, ___internal__method,", - self.return_type - )?; + if self.is_constexpr { + prefix_modifiers.push("constexpr") + } else if self.is_inline { + //implicitly inline + prefix_modifiers.push("inline") + } - let param_names = CppParam::params_names(&self.parameters); + if self.is_virtual { + prefix_modifiers.push("virtual"); + } + if self.is_operator { + prefix_modifiers.push("operator") + } - if !param_names.is_empty() { - write!(writer, ", {param_names}")?; + if self.is_const && self.instance { + suffix_modifiers.push("const"); + } + if self.is_no_except { + suffix_modifiers.push("noexcept"); } - writeln!(writer, ");")?; + let suffixes = suffix_modifiers.join(" "); + let prefixes = prefix_modifiers.join(" "); + + let ret = &self.return_type; + let name = &self.cpp_name; + let params = CppParam::params_as_args(&self.parameters).join(", "); + + match body { + Some(body) => { + writeln!(writer, "{prefixes} {ret} {name}({params}) {suffixes} {{")?; + // Body + body.iter().try_for_each(|w| w.write(writer))?; + + writeln!(writer, "}}")?; + } + None => { + writeln!(writer, "{prefixes} {ret} {name}({params}) {suffixes};",)?; + } + } - // End - writeln!(writer, "}}")?; Ok(()) } } +impl Sortable for CppMethodDecl { + fn sort_level(&self) -> SortLevel { + SortLevel::Methods + } +} -impl Writable for CppConstructorDecl { +impl Writable for CppMethodImpl { // declaration fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - writeln!(writer, "// Ctor Parameters {:?}", self.parameters)?; + if let Some(brief) = &self.brief { + writeln!(writer, "/// @brief {brief}")?; + } + + // Param default comments + self.parameters + .iter() + .filter(|t| t.def_value.is_some()) + .try_for_each(|param| { + writeln!( + writer, + "/// @param {}: {} (default: {})", + param.name, + param.ty, + param.def_value.as_ref().unwrap() + ) + })?; + + if let Some(declaring_type_template) = &self.declaring_type_template { + declaring_type_template.write(writer)?; + } + if let Some(template) = &self.template { + template.write(writer)?; + } + + let mut prefix_modifiers = self + .prefix_modifiers + .iter() + .map(|s| s.as_str()) + .collect_vec(); + + let mut suffix_modifiers = self + .suffix_modifiers + .iter() + .map(|s| s.as_str()) + .collect_vec(); + + if self.is_constexpr { + prefix_modifiers.push("constexpr"); + } else if self.is_inline { + prefix_modifiers.push("inline"); + } + + if self.is_virtual { + prefix_modifiers.push("virtual"); + } + + if self.is_const && self.instance { + suffix_modifiers.push("const"); + } + if self.is_no_except { + suffix_modifiers.push("noexcept"); + } + + let suffixes = suffix_modifiers.join(" "); + let prefixes = prefix_modifiers.join(" "); + let ret = &self.return_type; + let declaring_type = self + .declaring_cpp_full_name + .strip_prefix("::") + .unwrap_or(&self.declaring_cpp_full_name); + let name = &self.cpp_method_name; + let params = CppParam::params_as_args_no_default(&self.parameters).join(", "); - self.template.write(writer)?; writeln!( writer, - "{}({});", - self.ty, - CppParam::params_as_args(&self.parameters) + "{prefixes} {ret} {declaring_type}::{}{name}({params}) {suffixes} {{", + if self.is_operator { "operator " } else { "" } )?; + + // Body + self.body.iter().try_for_each(|w| w.write(writer))?; + + // End + writeln!(writer, "}}")?; Ok(()) } } -impl Writable for CppConstructorImpl { +impl Sortable for CppMethodImpl { + fn sort_level(&self) -> SortLevel { + SortLevel::Methods + } +} + +impl Writable for CppConstructorDecl { // declaration fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + // I'm lazy + if self.is_protected { + writeln!(writer, "protected:")?; + } + writeln!(writer, "// Ctor Parameters {:?}", self.parameters)?; + if let Some(brief) = &self.brief { + writeln!(writer, "// @brief {brief}")?; + } - // Constructor - self.template.write(writer)?; + if let Some(template) = &self.template { + template.write(writer)?; + } - if self.is_constexpr { - // TODO: - write!( - writer, - "inline {}({})", - self.holder_cpp_ty_name, - CppParam::params_as_args(&self.parameters) - )?; - } else { - write!( - writer, - "{}({})", - self.holder_cpp_ty_name, - CppParam::params_as_args_no_default(&self.parameters) - )?; + // Add empty body if initialize values or base ctor are defined + let body = &self.body; + + let name = &self.cpp_name; + let params = CppParam::params_as_args(&self.parameters).join(", "); + + // if the ctor is deleted, we don't need to + if self.is_delete { + writeln!(writer, "{name}({params}) = delete;")?; + + return Ok(()); } + let mut prefix_modifiers = vec![]; + let mut suffix_modifiers = vec![]; + if self.is_constexpr { - // Constexpr constructor + prefix_modifiers.push("constexpr") + } else if self.body.is_some() { + //implicitly inline + prefix_modifiers.push("inline") + } + + if self.is_explicit { + prefix_modifiers.push("explicit"); + } + if self.is_no_except { + suffix_modifiers.push("noexcept"); + } + + let prefixes = prefix_modifiers.join(" "); + let suffixes = suffix_modifiers.join(" "); + + if let Some(body) = &body + && !self.is_default + { + let initializers = match self.initialized_values.is_empty() && self.base_ctor.is_none() + { + true => "".to_string(), + false => { + let mut initializers_list = self + .initialized_values + .iter() + .map(|(name, value)| format!("{}({})", name, value)) + .collect_vec(); + + if let Some((base_ctor, args)) = &self.base_ctor { + initializers_list.insert(0, format!("{base_ctor}({args})")) + } + + format!(": {}", initializers_list.join(",")) + } + }; + writeln!( writer, - " : {} {{", - self.parameters - .iter() - .map(|p| format!("{}({})", &p.name, &p.name)) - .collect_vec() - .join(",") + "{prefixes} {name}({params}) {suffixes} {initializers} {{", )?; + + body.iter().try_for_each(|w| w.write(writer))?; + writeln!(writer, "}}")?; } else { - // Call base constructor - writeln!( - writer, - " : ::bs_hook::Il2CppWrapperType(::il2cpp_utils::New(classof({}), {})) {{", - self.holder_cpp_ty_name, - CppParam::params_names(&self.parameters) - )?; + match self.is_default { + true => writeln!(writer, "{prefixes} {name}({params}) {suffixes} = default;")?, + false => writeln!(writer, "{prefixes} {name}({params}) {suffixes};")?, + }; } - // End - writeln!(writer, "}}")?; + // I'm lazy + if self.is_protected { + writeln!(writer, "public:")?; + } Ok(()) } } +impl Sortable for CppConstructorDecl { + fn sort_level(&self) -> SortLevel { + SortLevel::Constructors + } +} -impl Writable for CppProperty { +impl Writable for CppConstructorImpl { + // declaration fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { - writeln!( - writer, - "// Property: name: {}, Type Name: {}, setter {} getter {} abstract {}", - self.name, - self.ty, - self.setter.is_some(), - self.getter.is_some(), - self.abstr - )?; + writeln!(writer, "// Ctor Parameters {:?}", self.parameters)?; - // TODO: - if self.abstr { - return Ok(()); + // Constructor + if let Some(template) = &self.template { + template.write(writer)?; + } + + let initializers = match self.initialized_values.is_empty() && self.base_ctor.is_none() { + true => "".to_string(), + false => { + let mut initializers_list = self + .initialized_values + .iter() + .map(|(name, value)| format!("{}({})", name, value)) + .collect_vec(); + + if let Some((base_ctor, args)) = &self.base_ctor { + initializers_list.insert(0, format!("{base_ctor}({args})")) + } + + format!(": {}", initializers_list.join(",")) + } + }; + + let mut suffix_modifiers: Vec<&str> = vec![]; + if self.is_no_except { + suffix_modifiers.push("noexcept") + } + + let mut prefix_modifiers: Vec<&str> = vec![]; + if self.is_constexpr { + prefix_modifiers.push("constexpr") } - if self.instance { + let full_name = &self.declaring_full_name; + let declaring_name = &self.declaring_name; + let params = CppParam::params_as_args_no_default(&self.parameters).join(", "); + + let prefixes = prefix_modifiers.join(" "); + let suffixes = suffix_modifiers.join(" "); + + if self.is_default { writeln!( writer, - "::bs_hook::InstanceProperty<\"{}\",{},{},{}> {};", - self.name, - self.ty, - self.getter.is_some(), - self.setter.is_some(), - self.name + "{prefixes} {full_name}::{declaring_name}({params}) {suffixes} {initializers} = default;", )?; } else { writeln!( writer, - "static inline ::bs_hook::StaticProperty<{},\"{}\",{},{}, &{}> {};", - self.ty, - self.name, - self.getter.is_some(), - self.setter.is_some(), - self.classof_call, - self.name + "{prefixes} {full_name}::{declaring_name}({params}) {suffixes} {initializers} {{", )?; + + self.body.iter().try_for_each(|w| w.write(writer))?; + // End + writeln!(writer, "}}")?; } Ok(()) } } +impl Sortable for CppConstructorImpl { + fn sort_level(&self) -> SortLevel { + SortLevel::Constructors + } +} + +impl Writable for CppPropertyDecl { + fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + let mut prefix_modifiers: Vec<&str> = vec![]; + let suffix_modifiers: Vec<&str> = vec![]; + + let mut property_vec: Vec = vec![]; + + if let Some(getter) = &self.getter { + property_vec.push(format!("get={getter}")); + } + if let Some(setter) = &self.setter { + property_vec.push(format!("put={setter}")); + } + + if !self.instance { + prefix_modifiers.push("static"); + } + + let property = property_vec.join(", "); + let ty = &self.prop_ty; + let identifier = &self.cpp_name; + + let prefixes = prefix_modifiers.join(" "); + let suffixes = suffix_modifiers.join(" "); + + // i.e. list->get_Item(int32_t index) takes an index argument, this way you can go list->Item[t] + let brackets = match self.indexable { + true => "[]", + false => "", + }; + + if let Some(comment) = &self.brief_comment { + writeln!(writer, "/// @brief {comment}")?; + } + + writeln!( + writer, + "{prefixes} __declspec(property({property})) {ty} {suffixes} {identifier}{brackets};" + )?; + + Ok(()) + } +} +impl Sortable for CppPropertyDecl { + fn sort_level(&self) -> SortLevel { + SortLevel::Properties + } +} impl Writable for CppMethodSizeStruct { fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { writeln!( writer, "// Writing Method size for method: {}.{}", - self.complete_type_name, self.cpp_method_name + self.declaring_type_name, self.cpp_method_name )?; - let params_format = CppParam::params_types(&self.params); + let template = self.template.clone().unwrap_or_default(); + + let complete_type_name = &self.declaring_type_name; + let classof_call = &self.declaring_classof_call; + let cpp_method_name = &self.cpp_method_name; + let ret_type = &self.ret_ty; + let size = &self.method_data.estimated_size; + let addr = &self.method_data.addrs; + + let interface_klass_of = &self.interface_clazz_of; + + let params_format = CppParam::params_types(&self.params).join(", "); + + let method_info_var = &self.method_info_var; + + // if we have a slot, this isn't final and we aren't an interface, do a slot resolve + // interface classes don't actually have vtables to perform a slot resolve on (count == 0) + let method_info_lines = if let Some(slot) = self.slot && !self.is_final { + vec![ + format!(" + static auto* {method_info_var} = THROW_UNLESS(::il2cpp_utils::ResolveVtableSlot( + {classof_call}, + {interface_klass_of}(), + {slot} + ));") + ] + } else { + self.method_info_lines.clone() + }.join("\n"); - let method_info_rhs = if let Some(slot) = self.slot && !self.is_final { - format!("THROW_UNLESS(::il2cpp_utils::ResolveVtableSlot((*reinterpret_cast(this))->klass, {}(), {slot}))", - self.interface_clazz_of - ) + let f_ptr_prefix = if self.instance { + format!("{}::", self.declaring_type_name) } else { - format!("THROW_UNLESS(::il2cpp_utils::FindMethod(this, \"{}\", std::vector{{}}, ::std::vector{{{params_format}}}))", - self.cpp_method_name - ) + "".to_string() }; + template.write(writer)?; + writeln!( writer, - "template<> -struct ::il2cpp_utils::il2cpp_type_check::MetadataGetter(&{}::{})> {{ - constexpr static const usize size() {{ - return 0x{:x}; - }} - constexpr static const usize addrs() {{ - return 0x{:x}; - }} + " +struct CORDL_HIDDEN ::il2cpp_utils::il2cpp_type_check::MetadataGetter(&{complete_type_name}::{cpp_method_name})> {{ + constexpr static std::size_t size = 0x{size:x}; + constexpr static std::size_t addrs = 0x{addr:x}; inline static const ::MethodInfo* methodInfo() {{ - return {method_info_rhs}; + {method_info_lines} + return {method_info_var}; }} -}};", - self.ret_ty, - self.complete_type_name, - self.complete_type_name, - self.cpp_method_name, - self.method_data.estimated_size, - self.method_data.addrs +}};" )?; Ok(()) } } +impl Sortable for CppMethodSizeStruct { + fn sort_level(&self) -> SortLevel { + SortLevel::SizeStruct + } +} + +impl Writable for CppStaticAssert { + fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + let condition = &self.condition; + match &self.message { + None => writeln!(writer, "static_assert({condition})"), + Some(message) => writeln!(writer, "static_assert({condition}, \"{message}\");"), + }?; + Ok(()) + } +} +impl Writable for CppLine { + fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()> { + writer.write_all(self.line.as_bytes())?; + writeln!(writer)?; // add line ending + Ok(()) + } +} + +impl Writable for CppNestedStruct { + fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + if self.is_private { + writeln!(writer, "private:")?; + } + + if let Some(brief) = &self.brief_comment { + writeln!(writer, "/// @brief {brief}")?; + } + + if let Some(packing) = self.packing { + writeln!(writer, "#pragma pack(push, tp, {packing})")?; + } + + let mut struct_declaration = match self.is_class { + true => "class", + false => "struct", + } + .to_string(); + + let mut base_type_fixed = self.base_type.clone().map(|s| format!("public {s}")); + if self.is_enum { + base_type_fixed = self.base_type.clone(); + struct_declaration = format!("enum {struct_declaration}"); + } + + match &base_type_fixed { + Some(base_type) => writeln!( + writer, + "{struct_declaration} {} : {base_type} {{", + self.declaring_name + )?, + None => writeln!(writer, "{struct_declaration} {} {{", self.declaring_name)?, + } + + self.declarations.iter().try_for_each(|d| d.write(writer))?; + + writeln!(writer, "}};")?; + if self.packing.is_some() { + writeln!(writer, "#pragma pack(pop, tp)")?; + } + if self.is_private { + writeln!(writer, "public:")?; + } + + Ok(()) + } +} + +impl Sortable for CppNestedStruct { + fn sort_level(&self) -> SortLevel { + SortLevel::NestedStruct + } +} + +impl Writable for CppNestedUnion { + fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + if self.is_private { + writeln!(writer, "private:")?; + } + if let Some(brief) = &self.brief_comment { + writeln!(writer, "/// @brief {brief}")?; + } + + writeln!(writer, "union {{")?; + self.declarations + .iter() + .try_for_each(|member| -> color_eyre::Result<()> { + member.write(writer)?; + Ok(()) + })?; + + writeln!(writer, "}};")?; + + if self.is_private { + writeln!(writer, "public:")?; + } + Ok(()) + } +} + +impl Sortable for CppNestedUnion { + fn sort_level(&self) -> SortLevel { + SortLevel::NestedUnion + } +} + impl Writable for CppMember { fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { match self { - CppMember::Field(f) => f.write(writer), + CppMember::FieldDecl(f) => f.write(writer), + CppMember::FieldImpl(f) => f.write(writer), CppMember::MethodDecl(m) => m.write(writer), CppMember::Property(p) => p.write(writer), CppMember::Comment(c) => c.write(writer), CppMember::MethodImpl(i) => i.write(writer), CppMember::ConstructorDecl(c) => c.write(writer), CppMember::ConstructorImpl(ci) => ci.write(writer), + CppMember::NestedStruct(e) => e.write(writer), + CppMember::NestedUnion(u) => u.write(writer), + CppMember::CppUsingAlias(alias) => alias.write(writer), + CppMember::CppLine(line) => line.write(writer), + CppMember::CppStaticAssert(sa) => sa.write(writer), + } + } +} + +impl Writable for CppNonMember { + fn write(&self, writer: &mut super::writer::CppWriter) -> color_eyre::Result<()> { + match self { + CppNonMember::SizeStruct(ss) => ss.write(writer), + CppNonMember::Comment(c) => c.write(writer), + CppNonMember::CppUsingAlias(alias) => alias.write(writer), + CppNonMember::CppLine(line) => line.write(writer), + CppNonMember::CppStaticAssert(sa) => sa.write(writer), + } + } +} + +impl Sortable for CppMember { + fn sort_level(&self) -> SortLevel { + match self { + CppMember::FieldDecl(t) => t.sort_level(), + CppMember::FieldImpl(t) => t.sort_level(), + CppMember::MethodDecl(t) => t.sort_level(), + CppMember::MethodImpl(t) => t.sort_level(), + CppMember::Property(t) => t.sort_level(), + CppMember::ConstructorDecl(t) => t.sort_level(), + CppMember::ConstructorImpl(t) => t.sort_level(), + CppMember::NestedStruct(t) => t.sort_level(), + CppMember::NestedUnion(t) => t.sort_level(), + CppMember::CppUsingAlias(t) => t.sort_level(), + CppMember::CppStaticAssert(_) => SortLevel::Unknown, + CppMember::Comment(_) => SortLevel::Unknown, + CppMember::CppLine(_) => SortLevel::Unknown, + } + } +} + +impl Sortable for CppNonMember { + fn sort_level(&self) -> SortLevel { + match self { + CppNonMember::SizeStruct(ss) => ss.sort_level(), + CppNonMember::CppUsingAlias(t) => t.sort_level(), + CppNonMember::CppStaticAssert(_) => SortLevel::Unknown, + CppNonMember::Comment(_) => SortLevel::Unknown, + CppNonMember::CppLine(_) => SortLevel::Unknown, } } } diff --git a/src/generate/metadata.rs b/src/generate/metadata.rs index cf534a5df..581265af8 100644 --- a/src/generate/metadata.rs +++ b/src/generate/metadata.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use brocolib::global_metadata::{Il2CppTypeDefinition, MethodIndex, TypeDefinitionIndex}; use itertools::Itertools; @@ -10,6 +10,13 @@ pub struct MethodCalculations { pub addrs: u64, } +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum PointerSize { + Bytes4 = 4, + Bytes8 = 8, +} + #[derive(Clone)] pub struct TypeDefinitionPair<'a> { pub ty: &'a Il2CppTypeDefinition, @@ -22,7 +29,12 @@ impl<'a> TypeDefinitionPair<'a> { } } -type TypeHandlerFn = Box; +pub type TypeHandlerFn = Box; +pub type Il2cppNamespace<'a> = &'a str; +pub type Il2cppName<'a> = &'a str; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Il2cppFullName<'a>(pub Il2cppNamespace<'a>, pub Il2cppName<'a>); pub struct Metadata<'a> { pub metadata: &'a brocolib::Metadata<'a, 'a>, @@ -35,12 +47,37 @@ pub struct Metadata<'a> { pub child_to_parent_map: HashMap>, // - pub custom_type_handler: HashMap + pub custom_type_handler: HashMap, + pub name_to_tdi: HashMap, TypeDefinitionIndex>, + pub blacklisted_types: HashSet, + + pub pointer_size: PointerSize, + pub packing_field_offset: u8, + pub size_is_default_offset: u8, + pub specified_packing_field_offset: u8, + pub packing_is_default_offset: u8, } impl<'a> Metadata<'a> { + /// Returns the size of the base object. + /// To be used for boxing/unboxing and various offset computations. + pub fn object_size(&self) -> u8 { + (self.pointer_size as u8) * 2 + } + pub fn parse(&mut self) { let gm = &self.metadata.global_metadata; + self.parse_name_tdi(gm); + self.parse_type_hierarchy(gm); + self.parse_method_size(gm); + } + + fn parse_type_hierarchy(&mut self, gm: &'a brocolib::global_metadata::GlobalMetadata) { + // self.parentToChildMap = childToParent + // .into_iter() + // .map(|(p, p_tdi, c)| (p_tdi, c)) + // .collect(); + // child -> parent let parent_to_child_map: Vec<(TypeDefinitionPair<'a>, Vec>)> = gm .type_definitions @@ -90,22 +127,19 @@ impl<'a> Metadata<'a> { .into_iter() .map(|(p, c)| (p.tdi, c.into_iter().collect_vec())) .collect(); + } - // self.parentToChildMap = childToParent - // .into_iter() - // .map(|(p, p_tdi, c)| (p_tdi, c)) - // .collect(); - - // method index -> address + fn parse_method_size(&mut self, gm: &brocolib::global_metadata::GlobalMetadata) { // sorted by address - let mut method_addresses_sorted: Vec = self + // method index -> address + let method_addresses_sorted: Vec = self .code_registration .code_gen_modules .iter() .flat_map(|m| &m.method_pointers) .copied() + .sorted() .collect(); - method_addresses_sorted.sort(); // address -> method index in sorted list let method_addresses_sorted_map: HashMap = method_addresses_sorted .iter() @@ -126,34 +160,63 @@ impl<'a> Metadata<'a> { .iter() .find(|i| cgm.name == i.name(self.metadata)) .unwrap(); - let mut method_calculations: HashMap = - HashMap::new(); - for ty in img.types(self.metadata) { - for (i, method) in ty.methods(self.metadata).iter().enumerate() { - let method_index = MethodIndex::new(ty.method_start.index() + i as u32); - let method_pointer_index = method.token.rid() as usize - 1; - let method_pointer = - *cgm.method_pointers.get(method_pointer_index).unwrap(); - - let sorted_address_index = - *method_addresses_sorted_map.get(&method_pointer).unwrap(); - let next_method_pointer = method_addresses_sorted - .get(sorted_address_index + 1) - .cloned() - .unwrap_or(0); - - method_calculations.insert( - method_index, - MethodCalculations { - estimated_size: method_pointer.abs_diff(next_method_pointer) - as usize, - addrs: method_pointer, - }, - ); - } - } + + let method_calculations: HashMap = + img.types(self.metadata) + .iter() + // get all methods + .flat_map(|ty| { + ty.methods(self.metadata).iter().enumerate().map(|(i, m)| { + (MethodIndex::new(ty.method_start.index() + i as u32), m) + }) + }) + // get method calculations + .map(|(method_index, method)| { + let method_pointer_index = method.token.rid() as usize - 1; + let method_pointer = + *cgm.method_pointers.get(method_pointer_index).unwrap(); + + let sorted_address_index = + *method_addresses_sorted_map.get(&method_pointer).unwrap(); + let next_method_pointer = method_addresses_sorted + .get(sorted_address_index + 1) + .cloned() + .unwrap_or(0); + + let estimated_size = + if method_pointer == 0x0 || next_method_pointer == 0x0 { + usize::MAX + } else { + method_pointer.abs_diff(next_method_pointer) as usize + }; + + ( + method_index, + MethodCalculations { + estimated_size, + addrs: method_pointer, + }, + ) + }) + .collect(); + method_calculations }) .collect(); } + + fn parse_name_tdi(&mut self, gm: &brocolib::global_metadata::GlobalMetadata) { + self.name_to_tdi = gm + .type_definitions + .as_vec() + .iter() + .enumerate() + .map(|(tdi, td)| { + ( + Il2cppFullName(td.namespace(self.metadata), td.name(self.metadata)), + TypeDefinitionIndex::new(tdi as u32), + ) + }) + .collect(); + } } diff --git a/src/generate/mod.rs b/src/generate/mod.rs index 7277bdb9a..65f50b87a 100644 --- a/src/generate/mod.rs +++ b/src/generate/mod.rs @@ -1,9 +1,13 @@ pub mod config; -pub mod constants; pub mod context; +pub mod context_collection; pub mod cpp_type; +pub mod cpp_type_tag; +pub mod cs_context_collection; pub mod cs_type; pub mod members; pub mod members_serialize; pub mod metadata; +pub mod offsets; +pub mod type_extensions; pub mod writer; diff --git a/src/generate/offsets.rs b/src/generate/offsets.rs new file mode 100644 index 000000000..0834804d7 --- /dev/null +++ b/src/generate/offsets.rs @@ -0,0 +1,795 @@ +use crate::TypeDefinitionIndex; + +use brocolib::global_metadata::Il2CppTypeDefinition; +use brocolib::runtime_metadata::Il2CppTypeDefinitionSizes; +use brocolib::runtime_metadata::TypeData; +use brocolib::runtime_metadata::{Il2CppType, Il2CppTypeEnum}; +use itertools::Itertools; +use log::debug; + +use log::warn; + +use crate::generate::type_extensions::TypeExtentions; +use core::mem; + +use super::metadata::PointerSize; + +use super::metadata::Metadata; +use super::type_extensions::TypeDefinitionExtensions; + +const IL2CPP_SIZEOF_STRUCT_WITH_NO_INSTANCE_FIELDS: u32 = 1; + +#[derive(Debug, Clone)] +pub struct SizeInfo { + pub instance_size: u32, + pub native_size: i32, + pub calculated_instance_size: u32, + pub calculated_native_size: i32, + pub minimum_alignment: u8, + pub natural_alignment: u8, + pub packing: Option, + pub specified_packing: Option, +} + +pub fn get_size_info<'a>( + t: &'a Il2CppTypeDefinition, + tdi: TypeDefinitionIndex, + generic_inst_types: Option<&Vec>, + metadata: &'a Metadata, +) -> SizeInfo { + let size_metadata = get_size_of_type_table(metadata, tdi).unwrap(); + let mut instance_size = size_metadata.instance_size; + let mut native_size = size_metadata.native_size; + + let sa = layout_fields(metadata, t, tdi, generic_inst_types, None, true); + let mut calculated_instance_size = sa.size; + + let minimum_alignment = sa.alignment; + let natural_alignment = sa.natural_alignment; + + if instance_size == 0 && !t.is_interface() { + instance_size = sa.size.try_into().unwrap(); + native_size = sa.actual_size.try_into().unwrap(); + } + + if t.is_value_type() || t.is_enum_type() { + instance_size = instance_size + .checked_sub(metadata.object_size() as u32) + .unwrap(); + calculated_instance_size = calculated_instance_size + .checked_sub(metadata.object_size() as usize) + .unwrap(); + } + + let packing = get_type_def_packing(metadata, t); + let specified_packing = get_packing(metadata, t); + + SizeInfo { + instance_size, + calculated_instance_size: calculated_instance_size as u32, + + native_size, + minimum_alignment, + natural_alignment, + calculated_native_size: sa.actual_size as i32, + packing, + specified_packing, + } +} + +pub fn get_size_and_packing<'a>( + t: &'a Il2CppTypeDefinition, + tdi: TypeDefinitionIndex, + generic_inst_types: Option<&Vec>, + metadata: &'a Metadata, +) -> (u32, Option) { + let size_metadata = get_size_of_type_table(metadata, tdi).unwrap(); + let mut metadata_size = size_metadata.instance_size; + + if metadata_size == 0 && !t.is_interface() { + let sa = layout_fields(metadata, t, tdi, generic_inst_types, None, true); + metadata_size = sa.size.try_into().unwrap(); + } + + if t.is_value_type() || t.is_enum_type() { + metadata_size = metadata_size + .checked_sub(metadata.object_size() as u32) + .unwrap() + } + + let packing = get_packing(metadata, t); + + (metadata_size, packing) +} + +pub fn get_il2cpptype_sa( + metadata: &Metadata<'_>, + ty: &Il2CppType, + generic_inst_types: Option<&Vec>, +) -> SizeAndAlignment { + get_type_size_and_alignment(ty, generic_inst_types, metadata) +} + +pub fn get_sizeof_type<'a>( + t: &'a Il2CppTypeDefinition, + tdi: TypeDefinitionIndex, + generic_inst_types: Option<&Vec>, + metadata: &'a Metadata, +) -> u32 { + let size_metadata = get_size_of_type_table(metadata, tdi).unwrap(); + let mut metadata_size = size_metadata.instance_size; + + if metadata_size == 0 && !t.is_interface() { + debug!( + "Computing instance size by laying out type for tdi: {tdi:?} {}", + t.full_name(metadata.metadata, true) + ); + metadata_size = layout_fields(metadata, t, tdi, generic_inst_types, None, true) + .size + .try_into() + .unwrap(); + // Remove implicit size of object from total size of instance + } + + if t.is_value_type() || t.is_enum_type() { + // For value types we need to ALWAYS subtract our object size + metadata_size = metadata_size + .checked_sub(metadata.object_size() as u32) + .unwrap(); + debug!( + "Resulting computed instance size (post subtracting) for type {:?} is: {}", + t.full_name(metadata.metadata, true), + metadata_size + ); + + // If we are still 0, todo! + if metadata_size == 0 { + todo!("We do not yet support cases where the instance type would be a 0 AFTER we have done computation! type: {}", t.full_name(metadata.metadata, true)); + } + } + + metadata_size +} + +const PACKING_SIZE_ZERO: u32 = 0; +const PACKING_SIZE_ONE: u32 = 1; +const PACKING_SIZE_TWO: u32 = 2; +const PACKING_SIZE_FOUR: u32 = 3; +const PACKING_SIZE_EIGHT: u32 = 4; +const PACKING_SIZE_SIXTEEN: u32 = 5; +const PACKING_SIZE_THIRTYTWO: u32 = 6; +const PACKING_SIZE_SIXTYFOUR: u32 = 7; +const PACKING_SIZE_ONEHUNDREDTWENTYEIGHT: u32 = 8; + +// GlobalMetadata::StructLayoutPack +fn packing_value(bitfield: u32, packing_field_offset: u8) -> u8 { + match (bitfield >> (packing_field_offset - 1)) & 0xF { + PACKING_SIZE_ZERO => 0, + PACKING_SIZE_ONE => 1, + PACKING_SIZE_TWO => 2, + PACKING_SIZE_FOUR => 4, + PACKING_SIZE_EIGHT => 8, + PACKING_SIZE_SIXTEEN => 16, + PACKING_SIZE_THIRTYTWO => 32, + PACKING_SIZE_SIXTYFOUR => 64, + PACKING_SIZE_ONEHUNDREDTWENTYEIGHT => 128, + _ => { + warn!("Invalid packing value read"); + 0 + } + } +} + +// MetadataCache::StructLayoutPackIsDefault +fn packing_is_default(bitfield: u32, packing_is_default_offset: u8) -> bool { + ((bitfield >> (packing_is_default_offset - 1)) & 0x1) != 0 +} + +/// RuntimeType::GetPacking +fn get_packing(metadata: &Metadata<'_>, ty_def: &Il2CppTypeDefinition) -> Option { + // according to this, packing is by default n = 8 + // https://learn.microsoft.com/en-us/cpp/preprocessor/pack?view=msvc-170 + if packing_is_default(ty_def.bitfield, metadata.packing_is_default_offset) { + return None; + } + let packing = packing_value(ty_def.bitfield, metadata.specified_packing_field_offset); + + Some(packing) +} + +/// GlobalMetadata::FromTypeDefinition +fn get_type_def_packing(metadata: &Metadata, ty_def: &Il2CppTypeDefinition) -> Option { + let packing = packing_value(ty_def.bitfield, metadata.packing_field_offset); + + // packing 8 is default + // 0 is likely None + if packing == 0 { + return None; + } + + Some(packing) +} + +// MetadataCache::StructLayoutPackIsDefault +fn size_is_default(bitfield: u32, size_is_default_offset: u8) -> bool { + ((bitfield >> (size_is_default_offset - 1)) & 0x1) != 0 +} + +fn get_size(metadata: &Metadata<'_>, tdi: TypeDefinitionIndex, ty_def: &Il2CppTypeDefinition) -> Option { + if size_is_default(ty_def.bitfield, metadata.size_is_default_offset) { + return None; + } + + get_size_of_type_table(metadata, tdi).map(|sz| sz.native_size as u32) +} + +/// Inspired by libil2cpp Class::LayoutFieldsLocked +pub fn layout_fields( + metadata: &Metadata<'_>, + declaring_ty_def: &Il2CppTypeDefinition, + declaring_tdi: TypeDefinitionIndex, + generic_inst_types: Option<&Vec>, + offsets: Option<&mut Vec>, + strictly_calculated: bool, +) -> SizeAndAlignment { + let mut instance_size: usize; + let mut actual_size: usize; + + let mut minimum_alignment: u8; + let mut natural_alignment: u8 = 0; + + // packing calculation based on RuntimeType::GetPacking + let packing = get_type_def_packing(metadata, declaring_ty_def); + + assert!( + packing.unwrap_or_default() <= 128, + "Packing must be valid! Actual: {packing:?}", + ); + + // assign base size values based on parent type (or no parent type) + if declaring_ty_def.parent_index == u32::MAX { + instance_size = metadata.object_size() as usize; + actual_size = metadata.object_size() as usize; + minimum_alignment = metadata.pointer_size as u8; + } else { + let parent_sa = get_parent_sa(metadata, declaring_ty_def.parent_index, generic_inst_types); + + instance_size = parent_sa.size; + actual_size = parent_sa.actual_size; + + if declaring_ty_def.is_value_type() { + minimum_alignment = 1; + } else { + minimum_alignment = parent_sa.alignment; + } + } + + // if we have fields, do something with their values + if declaring_ty_def.field_count > 0 { + let mut local_offsets: Vec = vec![]; + let sa = layout_instance_fields( + metadata, + declaring_ty_def, + declaring_tdi, + generic_inst_types, + Some(&mut local_offsets), + SizeAndAlignment { + size: instance_size, + actual_size, + alignment: minimum_alignment, + natural_alignment, + packing, + }, + ); + + let mut offsets_opt = offsets; + if let Some(offsets) = offsets_opt.as_mut() { + offsets.append(&mut local_offsets); + } + + if declaring_ty_def.is_value_type() && local_offsets.is_empty() { + instance_size = (IL2CPP_SIZEOF_STRUCT_WITH_NO_INSTANCE_FIELDS + + metadata.object_size() as u32) as usize; + actual_size = (IL2CPP_SIZEOF_STRUCT_WITH_NO_INSTANCE_FIELDS + + metadata.object_size() as u32) as usize; + } + + instance_size = update_instance_size_for_generic_class( + declaring_ty_def, + declaring_tdi, + instance_size, + metadata, + ); + + instance_size = sa.size; + actual_size = sa.actual_size; + minimum_alignment = sa.alignment; + natural_alignment = sa.natural_alignment; + } else { + instance_size = update_instance_size_for_generic_class( + declaring_ty_def, + declaring_tdi, + instance_size, + metadata, + ); + } + + // if we have an explicit size, use that + if !strictly_calculated && (declaring_ty_def.is_explicit_layout() || !size_is_default(declaring_ty_def.bitfield, metadata.size_is_default_offset)) + && let Some(sz) = get_size_of_type_table(metadata, declaring_tdi) + { + instance_size = sz.instance_size as usize; + if sz.native_size >= 0 { + actual_size = sz.native_size as usize; + } + } + + SizeAndAlignment { + size: instance_size, + actual_size, + alignment: minimum_alignment, + natural_alignment, + packing, + } +} + +/// equivalent to libil2cpp FieldLayout::LayoutFields with the instance field filter +fn layout_instance_fields( + metadata: &Metadata<'_>, + declaring_ty_def: &Il2CppTypeDefinition, + declaring_tdi: TypeDefinitionIndex, + generic_inst_types: Option<&Vec>, + offsets: Option<&mut Vec>, + parent_sa: SizeAndAlignment, +) -> SizeAndAlignment { + let parent_size = parent_sa.size; + let actual_parent_size = parent_sa.actual_size; + let parent_alignment = parent_sa.alignment; + let packing = parent_sa.packing; + + let mut instance_size = parent_size; + let mut actual_size = actual_parent_size; + let mut minimum_alignment = parent_alignment; + let mut natural_alignment: u8 = 0; + + let mut offsets_opt = offsets; + for (i, f) in declaring_ty_def + .fields(metadata.metadata) + .iter() + .enumerate() + { + let field_ty = &metadata + .metadata + .runtime_metadata + .metadata_registration + .types[f.type_index as usize]; + + if field_ty.is_static() || field_ty.is_constant() { + // filter for instance fields + continue; + } + + let sa = get_type_size_and_alignment(field_ty, generic_inst_types, metadata); + let mut alignment = sa.alignment; + if alignment < 4 && sa.natural_alignment != 0 { + alignment = sa.natural_alignment; + } + + if let Some(packing) = packing + && packing != 0 + { + alignment = std::cmp::min(sa.alignment, packing); + } + + let mut offset = actual_size; + + offset += (alignment - 1) as usize; + offset &= !(alignment as usize - 1); + + // explicit layout & we have a value in the offset table + if declaring_ty_def.is_explicit_layout() + && let Some(special_offset) = get_offset_of_type_table(metadata, declaring_tdi, i) + { + offset = special_offset; + } + + if let Some(offsets) = offsets_opt.as_mut() { + offsets.push(offset as u32); + } + + actual_size = usize::max(actual_size, offset + std::cmp::max(sa.size, 1)); + minimum_alignment = std::cmp::max(minimum_alignment, alignment); + natural_alignment = std::cmp::max( + natural_alignment, + std::cmp::max(sa.alignment, sa.natural_alignment), + ); + } + + instance_size = align_to(actual_size, minimum_alignment as usize); + + SizeAndAlignment { + size: instance_size, + actual_size, + alignment: minimum_alignment, + natural_alignment, + packing, + } +} + +fn get_offset_of_type_table( + metadata: &Metadata<'_>, + tdi: TypeDefinitionIndex, + field: usize, +) -> Option { + let field_offsets = metadata + .metadata_registration + .field_offsets + .as_ref() + .unwrap(); + + if let Some(offsets) = field_offsets.get(tdi.index() as usize) { + offsets.get(field).cloned().map(|o| o as usize) + } else { + None + } +} + +fn get_parent_sa( + metadata: &Metadata<'_>, + parent_index: u32, + generic_inst_types: Option<&Vec>, +) -> SizeAndAlignment { + let parent_ty = &metadata.metadata_registration.types[parent_index as usize]; + let (parent_tdi, parent_generics) = match parent_ty.data { + TypeData::TypeDefinitionIndex(parent_tdi) => (parent_tdi, None), + TypeData::GenericClassIndex(generic_index) => { + let generic_class = &metadata + .metadata + .runtime_metadata + .metadata_registration + .generic_classes[generic_index]; + + let generic_inst = &metadata.metadata_registration.generic_insts + [generic_class.context.class_inst_idx.unwrap()]; + + let generic_ty = &metadata.metadata_registration.types[generic_class.type_index]; + let TypeData::TypeDefinitionIndex(parent_tdi) = generic_ty.data else { + panic!( + "Failed to find TypeDefinitionIndex for generic class: {:?}", + generic_ty.data + ); + }; + + // replace all Var with the parent generic args + + let true_generics = generic_inst + .types + .iter() + .map(|t_index| { + let ty = &metadata + .metadata + .runtime_metadata + .metadata_registration + .types[*t_index]; + if let TypeData::GenericParameterIndex(generic_param_index) = ty.data + && let Some(generic_args) = &generic_inst_types + { + let generic_param = &metadata.metadata.global_metadata.generic_parameters + [generic_param_index]; + + generic_args[generic_param.num as usize] + } else { + // we don't know what it is, so just default later + *t_index + } + }) + .collect_vec(); + + (parent_tdi, Some(true_generics)) + } + _ => todo!("Not yet implemented: {:?}", parent_ty.data), + }; + + layout_fields( + metadata, + &metadata.metadata.global_metadata.type_definitions[parent_tdi], + parent_tdi, + parent_generics.as_ref(), + None, + false, + ) +} + +fn update_instance_size_for_generic_class( + ty_def: &Il2CppTypeDefinition, + tdi: TypeDefinitionIndex, + instance_size: usize, + metadata: &Metadata<'_>, +) -> usize { + // need to set this in case there are no fields in a generic instance type + if !ty_def.generic_container_index.is_valid() { + return instance_size; + } + let generic_type_size = get_size_of_type_table(metadata, tdi) + .map(|s| s.instance_size) + .unwrap_or(0) as usize; + + // If the generic class has an instance size, it was explictly set + if generic_type_size > 0 && generic_type_size > instance_size { + debug!( + "Generic instance size overwrite! Old: {}, New: {}, for tdi: {:?}", + instance_size, generic_type_size, tdi + ); + return generic_type_size; + } + + instance_size +} + +pub fn get_size_of_type_table<'a>( + metadata: &'a Metadata<'a>, + tdi: TypeDefinitionIndex, +) -> Option<&'a Il2CppTypeDefinitionSizes> { + if let Some(size_table) = &metadata + .metadata + .runtime_metadata + .metadata_registration + .type_definition_sizes + { + size_table.get(tdi.index() as usize) + } else { + None + } +} + +enum OffsetType { + Pointer, + Int8, + Int16, + Int32, + Int64, + IntPtr, + Float, + Double, +} + +/// Returns the alignment of a specified type, as expected in il2cpp. +/// This is done through inspecting alignments through il2cpp directly in clang. +/// Done via: offsetof({uint8_t pad, T t}, t); +fn get_alignment_of_type(ty: OffsetType, pointer_size: PointerSize) -> u8 { + match ty { + OffsetType::Pointer => pointer_size as u8, + OffsetType::Int8 => 1, + OffsetType::Int16 => 2, + OffsetType::Int32 => 4, + OffsetType::Int64 => 8, + OffsetType::IntPtr => pointer_size as u8, + OffsetType::Float => 4, + OffsetType::Double => 8, + } +} + +fn get_type_size_and_alignment( + ty: &Il2CppType, + generic_inst_types: Option<&Vec>, + metadata: &Metadata, +) -> SizeAndAlignment { + let mut sa = SizeAndAlignment { + alignment: 0, + natural_alignment: 0, + size: 0, + actual_size: 0, + packing: None, + }; + + if ty.byref && !ty.valuetype { + sa.size = metadata.pointer_size as usize; + sa.alignment = get_alignment_of_type(OffsetType::Pointer, metadata.pointer_size); + return sa; + } + + // only handle if generic inst, otherwise let the rest handle it as before + // aka a pointer size + if ty.ty == Il2CppTypeEnum::Var + && let TypeData::GenericParameterIndex(generic_param_index) = ty.data + && let Some(generic_args) = &generic_inst_types + { + let generic_param = + &metadata.metadata.global_metadata.generic_parameters[generic_param_index]; + + let resulting_ty_idx = generic_args[generic_param.num as usize]; + let resulting_ty = &metadata.metadata_registration.types[resulting_ty_idx]; + + if resulting_ty == ty { + warn!( + "Var points to itself! Type: {resulting_ty:?} generic args: {generic_args:?} {}", + ty.full_name(metadata.metadata) + ); + } + // If Var, this is partial instantiation + // we just treat it as Ptr below + if resulting_ty.ty != Il2CppTypeEnum::Var { + return get_type_size_and_alignment(resulting_ty, None, metadata); + } + } + + match ty.ty { + Il2CppTypeEnum::I1 | Il2CppTypeEnum::U1 | Il2CppTypeEnum::Boolean => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Int8, metadata.pointer_size); + } + Il2CppTypeEnum::I2 | Il2CppTypeEnum::U2 | Il2CppTypeEnum::Char => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Int16, metadata.pointer_size); + } + Il2CppTypeEnum::I4 | Il2CppTypeEnum::U4 => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Int32, metadata.pointer_size); + } + Il2CppTypeEnum::I8 | Il2CppTypeEnum::U8 => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Int64, metadata.pointer_size); + } + Il2CppTypeEnum::R4 => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Float, metadata.pointer_size); + } + Il2CppTypeEnum::R8 => { + sa.size = mem::size_of::(); + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Double, metadata.pointer_size); + } + + Il2CppTypeEnum::Ptr + | Il2CppTypeEnum::Fnptr + | Il2CppTypeEnum::String + | Il2CppTypeEnum::Szarray + | Il2CppTypeEnum::Array + | Il2CppTypeEnum::Class + | Il2CppTypeEnum::Object + | Il2CppTypeEnum::Mvar + | Il2CppTypeEnum::Var + | Il2CppTypeEnum::I + | Il2CppTypeEnum::U => { + // voidptr_t + sa.size = metadata.pointer_size as usize; + sa.actual_size = sa.size; + sa.alignment = get_alignment_of_type(OffsetType::Pointer, metadata.pointer_size); + } + Il2CppTypeEnum::Valuetype => { + let TypeData::TypeDefinitionIndex(value_tdi) = ty.data else { + panic!( + "Failed to find a valid TypeDefinitionIndex from type's data: {:?}", + ty.data + ) + }; + let value_td = &metadata.metadata.global_metadata.type_definitions[value_tdi]; + + if value_td.is_enum_type() { + let enum_base_type = + metadata.metadata_registration.types[value_td.element_type_index as usize]; + return get_type_size_and_alignment(&enum_base_type, None, metadata); + } + + // Size of the value type comes from the instance size - size of the wrapper object + // The way we compute the instance size is by grabbing the TD and performing a full field walk over that type + // Specifically, we call: layout_fields_for_type + // TODO: We should cache this call + let res = layout_fields(metadata, value_td, value_tdi, None, None, false); + sa.size = res.size - metadata.object_size() as usize; + sa.actual_size = res.actual_size; + sa.alignment = res.alignment; + sa.natural_alignment = res.natural_alignment; + sa.packing = res.packing; + } + Il2CppTypeEnum::Genericinst => { + let TypeData::GenericClassIndex(gtype) = ty.data else { + panic!( + "Failed to find a valid GenericClassIndex from type's data: {:?}", + ty.data + ) + }; + let mr = &metadata.metadata_registration; + let generic_class = mr.generic_classes.get(gtype).unwrap(); + + let new_generic_inst = &mr.generic_insts[generic_class.context.class_inst_idx.unwrap()]; + + let generic_type_def = &mr.types[generic_class.type_index]; + + let TypeData::TypeDefinitionIndex(tdi) = generic_type_def.data else { + panic!( + "Failed to find a valid TypeDefinitionIndex from type's data: {:?}", + generic_type_def.data + ) + }; + let td = &metadata.metadata.global_metadata.type_definitions[tdi]; + + // reference type + if !td.is_value_type() && !td.is_enum_type() { + sa.size = metadata.pointer_size as usize; + sa.alignment = get_alignment_of_type(OffsetType::Pointer, metadata.pointer_size); + return sa; + } + + // enum type + if td.is_enum_type() { + let enum_base_type = + metadata.metadata_registration.types[td.element_type_index as usize]; + return get_type_size_and_alignment( + &enum_base_type, + Some(&new_generic_inst.types), + metadata, + ); + } + + // GenericInst fields can use generic args of their declaring type + // so we redirect Var to the declaring type args + let new_generic_inst_types = new_generic_inst + .types + .iter() + .map(|t_idx| { + let t = &metadata.metadata_registration.types[*t_idx]; + + match t.data { + TypeData::GenericParameterIndex(generic_param_idx) => { + let generic_param = + &metadata.metadata.global_metadata.generic_parameters + [generic_param_idx]; + + generic_inst_types + .map(|generic_inst_types| { + generic_inst_types[generic_param.num as usize] + }) + // fallback to Var because we may not pass generic types + // when sizing a type def + .unwrap_or(*t_idx) + } + _ => *t_idx, + } + }) + .collect_vec(); + + // Size of the value type comes from the instance size + // We compute the instance size by grabbing the TD and performing a full field walk over that type + // by calling layout_fields_for_type + // TODO: We should cache this call + let res = layout_fields(metadata, td, tdi, Some(&new_generic_inst_types), None, false); + sa.size = res.size - metadata.object_size() as usize; + sa.actual_size = res.actual_size; + sa.alignment = res.alignment; + sa.natural_alignment = res.natural_alignment; + sa.packing = res.packing; + // sa.natural_alignment = res.natural_alignment; + } + _ => { + panic!( + "Failed to compute type size and alignment of type: {:?}", + ty + ); + } + } + + sa +} + +fn align_to(size: usize, alignment: usize) -> usize { + if size & (alignment - 1) != 0 { + (size + alignment - 1) & !(alignment - 1) + } else { + size + } +} + +#[derive(Debug)] +pub struct SizeAndAlignment { + pub size: usize, + actual_size: usize, + alignment: u8, + natural_alignment: u8, + packing: Option, +} diff --git a/src/generate/type_extensions.rs b/src/generate/type_extensions.rs new file mode 100644 index 000000000..7c50b9a8d --- /dev/null +++ b/src/generate/type_extensions.rs @@ -0,0 +1,429 @@ +use core::panic; + +use brocolib::{ + global_metadata::{Il2CppMethodDefinition, Il2CppTypeDefinition}, + runtime_metadata::{Il2CppType, Il2CppTypeEnum, TypeData}, + Metadata, +}; +use itertools::Itertools; + +use crate::data::name_components::NameComponents; + +use super::config::GenerationConfig; + +pub const PARAM_ATTRIBUTE_IN: u16 = 0x0001; +pub const PARAM_ATTRIBUTE_OUT: u16 = 0x0002; +pub const PARAM_ATTRIBUTE_OPTIONAL: u16 = 0x0010; + +pub const TYPE_ATTRIBUTE_INTERFACE: u32 = 0x00000020; +pub const TYPE_ATTRIBUTE_NESTED_PUBLIC: u32 = 0x00000002; +pub const TYPE_ATTRIBUTE_EXPLICIT_LAYOUT: u32 = 0x00000010; + +pub const FIELD_ATTRIBUTE_PUBLIC: u16 = 0x0006; +pub const FIELD_ATTRIBUTE_PRIVATE: u16 = 0x0001; +pub const FIELD_ATTRIBUTE_STATIC: u16 = 0x0010; +pub const FIELD_ATTRIBUTE_LITERAL: u16 = 0x0040; + +pub const METHOD_ATTRIBUTE_PUBLIC: u16 = 0x0006; +pub const METHOD_ATTRIBUTE_STATIC: u16 = 0x0010; +pub const METHOD_ATTRIBUTE_FINAL: u16 = 0x0020; +pub const METHOD_ATTRIBUTE_VIRTUAL: u16 = 0x0040; +pub const METHOD_ATTRIBUTE_HIDE_BY_SIG: u16 = 0x0080; +pub const METHOD_ATTRIBUTE_ABSTRACT: u16 = 0x0400; +pub const METHOD_ATTRIBUTE_SPECIAL_NAME: u16 = 0x0800; + +pub trait MethodDefintionExtensions { + fn is_public_method(&self) -> bool; + fn is_abstract_method(&self) -> bool; + fn is_static_method(&self) -> bool; + fn is_virtual_method(&self) -> bool; + fn is_hidden_sig(&self) -> bool; + fn is_special_name(&self) -> bool; + fn is_final_method(&self) -> bool; +} + +impl MethodDefintionExtensions for Il2CppMethodDefinition { + fn is_public_method(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_PUBLIC) != 0 + } + + fn is_virtual_method(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_VIRTUAL) != 0 + } + + fn is_static_method(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_STATIC) != 0 + } + + fn is_abstract_method(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_ABSTRACT) != 0 + } + + fn is_hidden_sig(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_HIDE_BY_SIG) != 0 + } + + fn is_special_name(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_SPECIAL_NAME) != 0 + } + + fn is_final_method(&self) -> bool { + (self.flags & METHOD_ATTRIBUTE_FINAL) != 0 + } +} + +pub trait ParameterDefinitionExtensions { + fn is_param_optional(&self) -> bool; + fn is_param_in(&self) -> bool; + fn is_param_out(&self) -> bool; +} + +impl ParameterDefinitionExtensions for Il2CppType { + fn is_param_optional(&self) -> bool { + (self.attrs & PARAM_ATTRIBUTE_OPTIONAL) != 0 + } + + fn is_param_in(&self) -> bool { + (self.attrs & PARAM_ATTRIBUTE_IN) != 0 + } + + fn is_param_out(&self) -> bool { + (self.attrs & PARAM_ATTRIBUTE_OUT) != 0 + } +} + +pub trait TypeExtentions { + fn is_static(&self) -> bool; + fn is_constant(&self) -> bool; + fn is_byref(&self) -> bool; + + fn fill_generic_inst<'a>( + &'a self, + generic_types: &[&'a Il2CppType], + metadata: &'a Metadata, + ) -> (&'a Il2CppType, Option>); +} + +impl TypeExtentions for Il2CppType { + fn is_static(&self) -> bool { + (self.attrs & FIELD_ATTRIBUTE_STATIC) != 0 + } + + // FIELD_ATTRIBUTE_LITERAL + fn is_constant(&self) -> bool { + (self.attrs & FIELD_ATTRIBUTE_LITERAL) != 0 + } + + fn is_byref(&self) -> bool { + self.byref + } + + /// Returns the actual type for the given generic inst + /// or drills down and fixes it in generic instantiations + fn fill_generic_inst<'a>( + &'a self, + generic_types: &[&'a Il2CppType], + metadata: &'a Metadata, + ) -> (&'a Il2CppType, Option>) { + match &self.data { + TypeData::GenericParameterIndex(gen_param_idx) => { + let gen_param = &metadata.global_metadata.generic_parameters[*gen_param_idx]; + + (generic_types[gen_param.num as usize], None) + } + TypeData::GenericClassIndex(generic_class_idx) => { + let generic_class = &metadata + .runtime_metadata + .metadata_registration + .generic_classes[*generic_class_idx]; + + let class_inst_idx = generic_class.context.class_inst_idx.unwrap(); + let class_inst = &metadata + .runtime_metadata + .metadata_registration + .generic_insts[class_inst_idx]; + let instantiated_generic_types = class_inst + .types + .iter() + .map(|t| { + let ty = &metadata.runtime_metadata.metadata_registration.types[*t]; + + ty.fill_generic_inst(generic_types, metadata) + }) + .map(|(t, _ts)| t) + .collect_vec(); + + let td_type_idx = &generic_class.type_index; + let td_type = &metadata.runtime_metadata.metadata_registration.types[*td_type_idx]; + let TypeData::TypeDefinitionIndex(_tdi) = td_type.data else { + panic!() + }; + // let td = &metadata.global_metadata.type_definitions[tdi]; + + (td_type, Some(instantiated_generic_types)) + } + _ => (self, None), + } + } +} + +pub trait TypeDefinitionExtensions { + fn is_value_type(&self) -> bool; + fn is_enum_type(&self) -> bool; + fn is_interface(&self) -> bool; + fn is_explicit_layout(&self) -> bool; + fn is_assignable_to(&self, other_td: &Il2CppTypeDefinition, metadata: &Metadata) -> bool; + + fn get_name_components(&self, metadata: &Metadata) -> NameComponents; + + fn full_name_cpp( + &self, + metadata: &Metadata, + config: &GenerationConfig, + with_generics: bool, + ) -> String; + fn full_name_nested( + &self, + metadata: &Metadata, + config: &GenerationConfig, + with_generics: bool, + ) -> String; +} + +impl TypeDefinitionExtensions for Il2CppTypeDefinition { + fn is_value_type(&self) -> bool { + self.bitfield & 1 != 0 + } + + fn is_enum_type(&self) -> bool { + self.bitfield & 2 != 0 + } + + fn is_interface(&self) -> bool { + self.flags & TYPE_ATTRIBUTE_INTERFACE != 0 + } + fn is_explicit_layout(&self) -> bool { + self.flags & TYPE_ATTRIBUTE_EXPLICIT_LAYOUT != 0 + } + + fn is_assignable_to(&self, other_td: &Il2CppTypeDefinition, metadata: &Metadata) -> bool { + // same type + if self.byval_type_index == other_td.byval_type_index { + return true; + } + + // does not inherit anything + if self.parent_index == u32::MAX { + return false; + } + + let parent_ty = + &metadata.runtime_metadata.metadata_registration.types[self.parent_index as usize]; + + // direct inheritance + if other_td.byval_type_index == self.parent_index { + return true; + } + + // if object, clearly this does not inherit `other_td` + if parent_ty.ty == Il2CppTypeEnum::Object { + return false; + } + + let parent_tdi = match parent_ty.data { + TypeData::TypeDefinitionIndex(tdi) => tdi, + TypeData::GenericClassIndex(gen_idx) => { + let gen_inst = &metadata + .runtime_metadata + .metadata_registration + .generic_classes[gen_idx]; + + let gen_ty = + &metadata.runtime_metadata.metadata_registration.types[gen_inst.type_index]; + + let TypeData::TypeDefinitionIndex(gen_tdi) = gen_ty.data else { + todo!() + }; + + gen_tdi + } + _ => panic!( + "Unsupported type: {:?} {}", + parent_ty, + parent_ty.full_name(metadata) + ), + }; + + // check if parent is descendant of `other_td` + let parent_td = &metadata.global_metadata.type_definitions[parent_tdi]; + parent_td.is_assignable_to(other_td, metadata) + } + + fn get_name_components(&self, metadata: &Metadata) -> NameComponents { + let namespace = self.namespace(metadata); + let name = self.name(metadata); + + let generics = match self.generic_container_index.is_valid() { + true => { + let gc = self.generic_container(metadata); + Some( + gc.generic_parameters(metadata) + .iter() + .map(|p| p.name(metadata).to_string()) + .collect_vec(), + ) + } + false => None, + }; + + let ty = + &metadata.runtime_metadata.metadata_registration.types[self.byval_type_index as usize]; + let is_pointer = + (!self.is_value_type() && !self.is_enum_type()) || ty.ty == Il2CppTypeEnum::Class; + + match self.declaring_type_index != u32::MAX { + true => { + let declaring_ty = metadata.runtime_metadata.metadata_registration.types + [self.declaring_type_index as usize]; + + let declaring_ty_names = match declaring_ty.data { + brocolib::runtime_metadata::TypeData::TypeDefinitionIndex(tdi) => { + let declaring_td = &metadata.global_metadata.type_definitions[tdi]; + declaring_td.get_name_components(metadata) + } + _ => todo!(), + }; + + let mut declaring_types = declaring_ty_names.declaring_types.unwrap_or_default(); + declaring_types.push(declaring_ty_names.name); + + NameComponents { + namespace: declaring_ty_names.namespace, + name: name.to_string(), + declaring_types: Some(declaring_types), + generics, + is_pointer, + } + } + false => NameComponents { + namespace: Some(namespace.to_string()), + name: name.to_string(), + declaring_types: None, + generics, + is_pointer, + }, + } + } + + // TODO: Use name components + fn full_name_cpp( + &self, + metadata: &Metadata, + config: &GenerationConfig, + with_generics: bool, + ) -> String { + let namespace = config.namespace_cpp(self.namespace(metadata)); + let name = config.name_cpp(self.name(metadata)); + + let mut full_name = String::new(); + + if self.declaring_type_index != u32::MAX { + let declaring_ty = metadata.runtime_metadata.metadata_registration.types + [self.declaring_type_index as usize]; + + let s = match declaring_ty.data { + brocolib::runtime_metadata::TypeData::TypeDefinitionIndex(tdi) => { + let declaring_td = &metadata.global_metadata.type_definitions[tdi]; + declaring_td.full_name_cpp(metadata, config, with_generics) + } + _ => declaring_ty.full_name(metadata), + }; + + full_name.push_str(&s); + full_name.push_str("::"); + } else { + // only write namespace if no declaring type + full_name.push_str(&namespace); + full_name.push_str("::"); + } + + full_name.push_str(&name); + if self.generic_container_index.is_valid() && with_generics { + let gc = self.generic_container(metadata); + full_name.push_str(&gc.to_string(metadata)); + } + full_name + } + + // separates nested types with / + // TODO: Use name components + fn full_name_nested( + &self, + metadata: &Metadata, + config: &GenerationConfig, + with_generics: bool, + ) -> String { + let namespace = config.namespace_cpp(self.namespace(metadata)); + let name = config.name_cpp(self.name(metadata)); + + let mut full_name = String::new(); + + if self.declaring_type_index != u32::MAX { + let declaring_ty = metadata.runtime_metadata.metadata_registration.types + [self.declaring_type_index as usize]; + + let declaring_ty = match declaring_ty.data { + brocolib::runtime_metadata::TypeData::TypeDefinitionIndex(tdi) => { + let declaring_td = &metadata.global_metadata.type_definitions[tdi]; + declaring_td.full_name_nested(metadata, config, with_generics) + } + _ => declaring_ty.full_name(metadata), + }; + + full_name.push_str(&declaring_ty); + full_name.push('/'); + } else { + // only write namespace if no declaring type + full_name.push_str(&namespace); + full_name.push('.'); + } + + full_name.push_str(&name); + if self.generic_container_index.is_valid() && with_generics { + let gc = self.generic_container(metadata); + full_name.push_str(&gc.to_string(metadata)); + } + full_name + } +} + +pub trait Il2CppTypeEnumExtensions { + fn is_primitive_builtin(&self) -> bool; +} + +impl Il2CppTypeEnumExtensions for Il2CppTypeEnum { + fn is_primitive_builtin(&self) -> bool { + // check if not a ref type + !matches!( + self, + Il2CppTypeEnum::Byref + | Il2CppTypeEnum::Valuetype // value type class + | Il2CppTypeEnum::Class + | Il2CppTypeEnum::Var + | Il2CppTypeEnum::Array + | Il2CppTypeEnum::Genericinst + | Il2CppTypeEnum::Typedbyref + | Il2CppTypeEnum::I + | Il2CppTypeEnum::U + | Il2CppTypeEnum::Fnptr + | Il2CppTypeEnum::Object + | Il2CppTypeEnum::Szarray + | Il2CppTypeEnum::Mvar + | Il2CppTypeEnum::Internal + | Il2CppTypeEnum::Modifier + | Il2CppTypeEnum::Sentinel + | Il2CppTypeEnum::Pinned + | Il2CppTypeEnum::Enum + ) + } +} diff --git a/src/generate/writer.rs b/src/generate/writer.rs index 258aa2b8f..aa1c2f77b 100644 --- a/src/generate/writer.rs +++ b/src/generate/writer.rs @@ -40,3 +40,32 @@ impl Write for CppWriter { pub trait Writable: std::fmt::Debug { fn write(&self, writer: &mut CppWriter) -> color_eyre::Result<()>; } + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub enum SortLevel { + SizeStruct, + UsingAlias, + NestedStruct, + Properties, + Methods, + Constructors, + FieldsImpl, + Unknown, + NestedUnion, + Fields, +} + +pub trait Sortable { + fn sort_level(&self) -> SortLevel; +} + +// impl PartialEq for dyn Sortable { +// fn eq(&self, other: &Self) -> bool { +// todo!() +// } +// } +// impl PartialOrd for dyn Sortable { +// fn partial_cmp(&self, other: &Self) -> Option { +// self.sort_level().partial_cmp(&other.sort_level()) +// } +// } diff --git a/src/handlers/il2cpp_internals.rs b/src/handlers/il2cpp_internals.rs new file mode 100644 index 000000000..0e247b9d2 --- /dev/null +++ b/src/handlers/il2cpp_internals.rs @@ -0,0 +1,296 @@ +use color_eyre::Result; +use log::{info, trace, warn}; +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; + +use crate::generate::{ + cpp_type::CppType, + cs_type::VALUE_TYPE_WRAPPER_SIZE, + members::{CppConstructorDecl, CppLine, CppMember, CppMethodDecl, CppParam}, + metadata::{Il2cppFullName, Metadata}, +}; + +static EQUIVALENTS: LazyLock> = LazyLock::new(|| { + HashMap::from([ + ("System.RuntimeType", "Il2CppReflectionRuntimeType"), + ("System.MonoType", "Il2CppReflectionMonoType"), + ("System.Reflection.EventInfo", "Il2CppReflectionEvent"), + ("System.Reflection.MonoEvent", "Il2CppReflectionMonoEvent"), + ( + "System.Reflection.MonoEventInfo", + "Il2CppReflectionMonoEventInfo", + ), + ("System.Reflection.MonoField", "Il2CppReflectionField"), + ("System.Reflection.MonoProperty", "Il2CppReflectionProperty"), + ("System.Reflection.MonoMethod", "Il2CppReflectionMethod"), + ( + "System.Reflection.MonoGenericMethod", + "Il2CppReflectionGenericMethod", + ), + ("System.Reflection.MonoMethodInfo", "Il2CppMethodInfo"), + ("System.Reflection.MonoPropertyInfo", "Il2CppPropertyInfo"), + ( + "System.Reflection.ParameterInfo", + "Il2CppReflectionParameter", + ), + ("System.Reflection.Module", "Il2CppReflectionModule"), + ( + "System.Reflection.AssemblyName", + "Il2CppReflectionAssemblyName", + ), + ("System.Reflection.Assembly", "Il2CppReflectionAssembly"), + ( + "System.Reflection.Emit.UnmanagedMarshal", + "Il2CppReflectionMarshal", + ), + ("System.Reflection.Pointer", "Il2CppReflectionPointer"), + ("System.Threading.InternalThread", "Il2CppInternalThread"), + ("System.Threading.Thread", "Il2CppThread"), + ("System.Exception", "Il2CppException"), + ("System.SystemException", "Il2CppSystemException"), + ("System.ArgumentException", "Il2CppArgumentException"), + ("System.TypedReference", "Il2CppTypedRef"), + ("System.Delegate", "Il2CppDelegate"), + ("System.MarshalByRefObject", "Il2CppMarshalByRefObject"), + ("System.__Il2CppComObject", "Il2CppComObject"), + ("System.AppDomain", "Il2CppAppDomain"), + ("System.Diagnostics.StackFrame", "Il2CppStackFrame"), + ( + "System.Globalization.DateTimeFormatInfo", + "Il2CppDateTimeFormatInfo", + ), + ( + "System.Globalization.NumberFormatInfo", + "Il2CppNumberFormatInfo", + ), + ("System.Globalization.CultureInfo", "Il2CppCultureInfo"), + ("System.Globalization.RegionInfo", "Il2CppRegionInfo"), + ( + "System.Runtime.InteropServices.SafeHandle", + "Il2CppSafeHandle", + ), + ("System.Text.StringBuilder", "Il2CppStringBuilder"), + ("System.Net.SocketAddress", "Il2CppSocketAddress"), + ("System.Globalization.SortKey", "Il2CppSortKey"), + ( + "System.Runtime.InteropServices.ErrorWrapper", + "Il2CppErrorWrapper", + ), + ( + "System.Runtime.Remoting.Messaging.AsyncResult", + "Il2CppAsyncResult", + ), + ("System.MonoAsyncCall", "Il2CppAsyncCall"), + ( + "System.Reflection.ManifestResourceInfo", + "Il2CppManifestResourceInfo", + ), + ( + "System.Runtime.Remoting.Contexts.Context", + "Il2CppAppContext", + ), + ]) +}); + +pub fn register_il2cpp_types(metadata: &mut Metadata) -> Result<()> { + info!("Registering il2cpp type handler!"); + + for (cordl_t, il2cpp_t) in EQUIVALENTS.iter() { + info!("Registering il2cpp type handler {cordl_t} to {il2cpp_t}"); + + let (cordl_t_ns, cordl_t_name) = cordl_t.rsplit_once('.').expect("No namespace?"); + let il2cpp_name = Il2cppFullName(cordl_t_ns, cordl_t_name); + + let cordl_tdi = metadata.name_to_tdi.get(&il2cpp_name); + + match cordl_tdi { + Some(cordl_tdi) => { + metadata.custom_type_handler.insert( + *cordl_tdi, + Box::new(|cpp_type| il2cpp_alias_handler(cpp_type, cordl_t, il2cpp_t)), + ); + } + None => { + warn!("Could not find TDI for {cordl_t}"); + } + } + } + + Ok(()) +} + +fn il2cpp_alias_handler(cpp_type: &mut CppType, cordl_t: &str, il2cpp_t: &str) { + trace!("Replacing {cordl_t} for il2cpp il2cpp_t for type {il2cpp_t}"); + + // there is an il2cpp api il2cpp_t configured for this type, + // we should emit some conversion operators for that + + if cpp_type.is_value_type { + value_type_convert(cpp_type, il2cpp_t); + } else { + reference_type_convert(cpp_type, il2cpp_t); + } +} + +fn reference_type_convert(cpp_type: &mut CppType, il2cpp_t: &str) { + let cpp_name = cpp_type.cpp_name(); + + let operator_body = format!("return static_cast<{il2cpp_t}*>(this->convert());"); + let conversion_operator = CppMethodDecl { + cpp_name: Default::default(), + instance: true, + return_type: format!("{il2cpp_t}*"), + + brief: Some(format!("Conversion into il2cpp il2cpp_t {il2cpp_t}")), + body: Some(vec![Arc::new(CppLine::make(operator_body))]), // TODO: + is_const: false, + is_constexpr: true, + is_virtual: false, + is_operator: true, + is_inline: true, + is_no_except: true, + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let const_operator_body = format!("return static_cast<{il2cpp_t} const*>(this->convert());"); + let const_conversion_operator = CppMethodDecl { + cpp_name: Default::default(), + instance: true, + return_type: format!("{il2cpp_t} const*"), + + brief: Some(format!("Conversion into il2cpp il2cpp_t {il2cpp_t}")), + body: Some(vec![Arc::new(CppLine::make(const_operator_body))]), // TODO: + is_const: true, + is_constexpr: true, + is_virtual: false, + is_operator: true, + is_inline: true, + is_no_except: true, + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let il2cpp_t_constructor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + name: "il2cpp_ptr".to_string(), + modifiers: "".to_string(), + ty: format!("{il2cpp_t}*").to_string(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_explicit: false, + is_default: false, + is_no_except: true, + is_delete: false, + is_protected: false, + + // use the void* ctor overload + base_ctor: Some(( + cpp_name.clone(), + "static_cast(il2cpp_ptr)".to_string(), + )), + initialized_values: HashMap::new(), + brief: None, + body: Some(vec![]), + }; + + cpp_type + .declarations + .push(CppMember::MethodDecl(conversion_operator).into()); + cpp_type + .declarations + .push(CppMember::MethodDecl(const_conversion_operator).into()); + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(il2cpp_t_constructor).into()); +} + +fn value_type_convert(cpp_type: &mut CppType, il2cpp_t: &str) { + let cpp_name = cpp_type.cpp_name(); + + let operator_body = format!("return *static_cast<{il2cpp_t}*>(this->convert());"); + let conversion_operator = CppMethodDecl { + cpp_name: Default::default(), + instance: true, + return_type: il2cpp_t.to_string(), + + brief: Some(format!("Conversion into il2cpp il2cpp_t {il2cpp_t}")), + body: Some(vec![Arc::new(CppLine::make(operator_body))]), // TODO: + is_const: false, + is_constexpr: true, + is_virtual: false, + is_operator: true, + is_inline: true, + is_no_except: true, + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let const_operator_body = format!("return *static_cast<{il2cpp_t} const*>(this->convert());"); + let const_conversion_operator = CppMethodDecl { + cpp_name: Default::default(), + instance: true, + return_type: il2cpp_t.to_string(), + + brief: Some(format!("Conversion into il2cpp il2cpp_t {il2cpp_t}")), + body: Some(vec![Arc::new(CppLine::make(const_operator_body))]), // TODO: + is_const: true, + is_constexpr: true, + is_virtual: false, + is_operator: true, + is_inline: true, + is_no_except: true, + parameters: vec![], + prefix_modifiers: vec![], + suffix_modifiers: vec![], + template: None, + }; + + let il2cpp_t_constructor = CppConstructorDecl { + cpp_name: cpp_name.clone(), + parameters: vec![CppParam { + name: "il2cpp_eq".to_string(), + modifiers: "".to_string(), + ty: format!("{il2cpp_t} const&").to_string(), + def_value: None, + }], + template: None, + is_constexpr: true, + is_protected: false, + is_explicit: false, + is_default: false, + is_no_except: true, + is_delete: false, + // use the array ctor overload + base_ctor: Some(( + cpp_name.clone(), + format!("std::bit_cast>(il2cpp_eq)"), + )), + initialized_values: HashMap::new(), + brief: None, + body: Some(vec![]), + }; + + cpp_type + .declarations + .push(CppMember::MethodDecl(conversion_operator).into()); + cpp_type + .declarations + .push(CppMember::MethodDecl(const_conversion_operator).into()); + + cpp_type + .declarations + .push(CppMember::ConstructorDecl(il2cpp_t_constructor).into()); +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index e103ea6bd..f351e7f7b 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1 +1,4 @@ -pub mod unity; \ No newline at end of file +pub mod il2cpp_internals; +pub mod object; +pub mod unity; +pub mod value_type; diff --git a/src/handlers/object.rs b/src/handlers/object.rs new file mode 100644 index 000000000..96a7b193d --- /dev/null +++ b/src/handlers/object.rs @@ -0,0 +1,48 @@ +use color_eyre::Result; +use log::info; + +use crate::generate::{ + cpp_type::CppType, + cs_type::IL2CPP_OBJECT_TYPE, + members::CppMember, + metadata::{Il2cppFullName, Metadata}, +}; + +pub fn register_system(metadata: &mut Metadata) -> Result<()> { + info!("Registering system handler!"); + register_system_object_type_handler(metadata)?; + + Ok(()) +} + +fn register_system_object_type_handler(metadata: &mut Metadata) -> Result<()> { + info!("Registering System.Object handler!"); + + let system_object_tdi = metadata + .name_to_tdi + .get(&Il2cppFullName("System", "Object")) + .expect("No System.Object TDI found"); + + metadata + .custom_type_handler + .insert(*system_object_tdi, Box::new(system_object_handler)); + + Ok(()) +} + +fn system_object_handler(cpp_type: &mut CppType) { + info!("Found System.Object type, adding systemW!"); + // clear inherit so that bs hook can dof include order shenanigans + cpp_type.requirements.need_wrapper(); + cpp_type.inherit = vec![IL2CPP_OBJECT_TYPE.to_string()]; + + // Remove field because it does not size properly and is not necessary + cpp_type + .declarations + .retain(|t| !matches!(t.as_ref(), CppMember::FieldDecl(_))); + + // remove size assert too because System::Object will be wrong due to include ordering + // cpp_type + // .nonmember_declarations + // .retain(|t| !matches!(t.as_ref(), CppNonMember::CppStaticAssert(_))); +} diff --git a/src/handlers/unity.rs b/src/handlers/unity.rs index 33b79bc07..37b612127 100644 --- a/src/handlers/unity.rs +++ b/src/handlers/unity.rs @@ -1,56 +1,71 @@ -use std::path::PathBuf; - -use anyhow::anyhow; -use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; use color_eyre::Result; +use log::info; +use std::{path::PathBuf, rc::Rc}; use crate::generate::{ - context::CppContextCollection, cpp_type::CppType, members::CppInclude, metadata::Metadata, + cpp_type::CppType, + members::{CppInclude, CppMember}, + metadata::{Il2cppFullName, Metadata}, }; -pub fn register_unity( - cpp_context_collection: &CppContextCollection, - metadata: &mut Metadata, -) -> Result<()> { - println!("Registering unity handler!"); - register_unity_object_type_handler(cpp_context_collection, metadata)?; +pub fn register_unity(metadata: &mut Metadata) -> Result<()> { + info!("Registering unity handler!"); + register_unity_object_type_handler(metadata)?; Ok(()) } -fn register_unity_object_type_handler( - cpp_context_collection: &CppContextCollection, - metadata: &mut Metadata, -) -> Result<()> { - println!("Registering UnityEngine.Object handler!"); - - let (tag, _unity_cpp_context) = cpp_context_collection - .get() - .iter() - .find(|(_, c)| { - c.get_types() - .iter() - .any(|(_, t)| t.name == "Object" && t.namespace == "UnityEngine") - }) - .unwrap_or_else(|| panic!("No UnityEngine.Object type found!")); - - if let TypeData::TypeDefinitionIndex(tdi) = tag { - metadata - .custom_type_handler - .insert(*tdi, Box::new(unity_object_handler)); - } +fn register_unity_object_type_handler(metadata: &mut Metadata) -> Result<()> { + info!("Registering UnityEngine.Object handler!"); + + let unity_object_tdi = metadata + .name_to_tdi + .get(&Il2cppFullName("UnityEngine", "Object")) + .expect("No UnityEngine.Object TDI found"); + + metadata + .custom_type_handler + .insert(*unity_object_tdi, Box::new(unity_object_handler)); Ok(()) } fn unity_object_handler(cpp_type: &mut CppType) { - println!("Found UnityEngine.Object type, adding UnityW!"); - cpp_type.inherit.push("::UnityW".to_owned()); + info!("Found UnityEngine.Object type, adding UnityW!"); + cpp_type.inherit = vec!["bs_hook::UnityW".to_owned()]; let path = PathBuf::from(r"beatsaber-hook/shared/utils/unityw.hpp"); cpp_type .requirements - .required_includes - .insert(CppInclude::new(path)); + .add_def_include(None, CppInclude::new_exact(path)); + + // Fixup ctor call declarations + cpp_type + .declarations + .iter_mut() + .filter(|t| matches!(t.as_ref(), CppMember::ConstructorDecl(_))) + .for_each(|d| { + let CppMember::ConstructorDecl(constructor) = Rc::get_mut(d).unwrap() else { + panic!() + }; + + if let Some(base_ctor) = &mut constructor.base_ctor { + base_ctor.0 = "UnityW".to_string(); + } + }); + // Fixup ctor call implementations + cpp_type + .implementations + .iter_mut() + .filter(|t| matches!(t.as_ref(), CppMember::ConstructorImpl(_))) + .for_each(|d| { + let CppMember::ConstructorImpl(constructor) = Rc::get_mut(d).unwrap() else { + panic!() + }; + + if let Some(base_ctor) = &mut constructor.base_ctor { + base_ctor.0 = "UnityW".to_string(); + } + }); } diff --git a/src/handlers/value_type.rs b/src/handlers/value_type.rs new file mode 100644 index 000000000..76042097d --- /dev/null +++ b/src/handlers/value_type.rs @@ -0,0 +1,100 @@ +use std::rc::Rc; + +use color_eyre::Result; + +use crate::generate::{ + cpp_type::CppType, + cs_type::{ENUM_WRAPPER_TYPE, VALUE_WRAPPER_TYPE}, + members::CppMember, + metadata::{Il2cppFullName, Metadata}, +}; + +use log::info; + +pub fn register_value_type(metadata: &mut Metadata) -> Result<()> { + info!("Registering value type handler!"); + register_value_type_object_handler(metadata)?; + + Ok(()) +} + +fn register_value_type_object_handler(metadata: &mut Metadata) -> Result<()> { + info!("Registering System.ValueType handler!"); + info!("Registering System.Enum handler!"); + + let value_type_tdi = metadata + .name_to_tdi + .get(&Il2cppFullName("System", "ValueType")) + .expect("No System.ValueType TDI found"); + let enum_type_tdi = metadata + .name_to_tdi + .get(&Il2cppFullName("System", "Enum")) + .expect("No System.ValueType TDI found"); + + metadata + .custom_type_handler + .insert(*value_type_tdi, Box::new(value_type_handler)); + metadata + .custom_type_handler + .insert(*enum_type_tdi, Box::new(enum_type_handler)); + + Ok(()) +} + +fn unified_type_handler(cpp_type: &mut CppType, _base_ctor: &str) { + // We don't replace parent anymore + // cpp_type.inherit = vec![base_ctor.to_string()]; + + // Fixup ctor call + cpp_type + .implementations + .retain_mut(|d| !matches!(d.as_ref(), CppMember::ConstructorImpl(_))); + cpp_type + .declarations + .iter_mut() + .filter(|t| matches!(t.as_ref(), CppMember::ConstructorDecl(_))) + .for_each(|d| { + let CppMember::ConstructorDecl(constructor) = Rc::get_mut(d).unwrap() else { + panic!() + }; + + // We don't replace base ctor anymore + // constructor.base_ctor = Some((base_ctor.to_string(), "".to_string())); + constructor.body = Some(vec![]); + constructor.is_constexpr = true; + }); + + // remove all method decl/impl + cpp_type + .declarations + .retain(|t| !matches!(t.as_ref(), CppMember::MethodDecl(_))); + // remove all method decl/impl + cpp_type + .implementations + .retain(|t| !matches!(t.as_ref(), CppMember::MethodImpl(_))); + + // Remove method size structs + cpp_type.nonmember_implementations.clear(); +} +fn value_type_handler(cpp_type: &mut CppType) { + info!("Found System.ValueType, removing inheritance!"); + unified_type_handler( + cpp_type, + format!( + "{VALUE_WRAPPER_TYPE}<0x{:x}>", + cpp_type.size_info.as_ref().map(|s| s.calculated_instance_size).unwrap() + ) + .as_str(), + ); +} +fn enum_type_handler(cpp_type: &mut CppType) { + info!("Found System.Enum type, removing inheritance!"); + unified_type_handler( + cpp_type, + format!( + "{ENUM_WRAPPER_TYPE}<0x{:x}>", + cpp_type.size_info.as_ref().map(|s| s.calculated_instance_size).unwrap() + ) + .as_str(), + ); +} diff --git a/src/helpers/cursor.rs b/src/helpers/cursor.rs new file mode 100644 index 000000000..e138e1ff0 --- /dev/null +++ b/src/helpers/cursor.rs @@ -0,0 +1,62 @@ +use byteorder::{ByteOrder, ReadBytesExt}; + +pub trait ReadBytesExtensions: ReadBytesExt { + fn read_compressed_u32(&mut self) -> Result; + fn read_compressed_i32(&mut self) -> Result; +} + +impl ReadBytesExtensions for R { + // stolen from libil2cpp/utils/MemoryRead.cpp + // thanks Stack + fn read_compressed_u32(&mut self) -> Result { + let mut val: u32; + let read = self.read_u8()?; + + if (read & 0x80) == 0 { + // 1 byte written + val = read as u32; + } else if (read & 0xC0) == 0x80 { + // 2 bytes written + val = (read as u32 & !0x80) << 8; + val |= self.read_u8()? as u32; + } else if (read & 0xE0) == 0xC0 { + // 4 bytes written + val = (read as u32 & !0xC0) << 24; + val |= (self.read_u8()? as u32) << 16; + val |= (self.read_u8()? as u32) << 8; + val |= self.read_u8()? as u32; + } else if read == 0xF0 { + // 5 bytes written, we had a really large int32! + val = self.read_u32::()?; + } else if read == 0xFE { + // Special encoding for Int32.MaxValue + val = u32::MAX - 1; + } else if read == 0xFF { + // Yes we treat UInt32.MaxValue (and Int32.MinValue, see ReadCompressedInt32) specially + val = u32::MAX; + } else { + panic!("Invalid compressed integer format"); + } + + Ok(val) + } + + fn read_compressed_i32(&mut self) -> Result { + let mut encoded = self.read_compressed_u32::()?; + + // -UINT32_MAX can't be represted safely in an int32_t, so we treat it specially + if encoded == u32::MAX { + return Ok(i32::MIN); + } + + let is_negative: bool = (encoded & 1) != 0; + encoded >>= 1; + let result = if is_negative { + -((encoded + 1) as i32) + } else { + encoded as i32 + }; + + Ok(result) + } +} diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs new file mode 100644 index 000000000..71d67d667 --- /dev/null +++ b/src/helpers/mod.rs @@ -0,0 +1,2 @@ +pub mod cursor; +pub mod sorting; diff --git a/src/helpers/sorting.rs b/src/helpers/sorting.rs new file mode 100644 index 000000000..bd3cd930f --- /dev/null +++ b/src/helpers/sorting.rs @@ -0,0 +1,86 @@ +use std::{ + cmp::Ordering, + collections::{HashMap, HashSet, VecDeque}, + fmt::Debug, + hash::Hash, +}; + +use itertools::Itertools; + +// Define a dependency graph structure to store dependency relationships +pub struct DependencyGraph<'a, A, F> { + dependencies: HashMap<&'a A, HashSet<&'a A>>, + sorting: F, +} + +impl<'a, A, F> DependencyGraph<'a, A, F> +where + A: Eq + Hash + Debug, + F: FnMut(&&'a A, &&'a A) -> Ordering + Copy, +{ + // Initialize a new empty dependency graph + pub fn new(sorting: F) -> Self { + Self { + dependencies: HashMap::new(), + sorting, + } + } + + // Add a dependency relationship to the graph + pub fn add_root_dependency(&mut self, dependent: &'a A) -> bool { + self.dependencies + .try_insert(dependent, HashSet::new()) + .is_ok() + } + + // Add a dependency relationship to the graph + pub fn add_dependency(&mut self, dependent: &'a A, dependency: &'a A) { + self.dependencies + .entry(dependent) + .or_default() + .insert(dependency); + } + + // Topologically sorts the dependency graph, handling cyclic dependencies + pub fn topological_sort(&self) -> Vec<&'a A> { + let mut visited = HashSet::new(); + let mut stack = VecDeque::new(); + + let mut sort_fn = self.sorting; + + for dependent in self.dependencies.keys().sorted_by(|a, b| sort_fn(*a, *b)) { + if !visited.contains(dependent) { + self.topological_sort_recurse(dependent, &mut visited, &mut stack); + } + } + + stack.into_iter().collect_vec() + } + + // Perform a recursive topological sort for the given dependency, stack, and visited collection + fn topological_sort_recurse( + &self, + main: &'a A, + visited: &mut HashSet<&'a A>, + stack: &mut VecDeque<&'a A>, + ) { + if visited.contains(main) { + return; + } + + visited.insert(main); + + let mut sort_fn = self.sorting; + + if let Some(dependencies) = self.dependencies.get(main) { + let mut sorted_dependencies: Vec<_> = dependencies.iter().collect(); + sorted_dependencies.sort_by(|a, b| (sort_fn)(*a, *b)); + + for dependency in sorted_dependencies { + self.topological_sort_recurse(dependency, visited, stack); + } + } + + stack.push_back(main); + } +} diff --git a/src/main.rs b/src/main.rs index 221649423..155d9b6c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,244 +1,732 @@ -#![feature(entry_insert)] -#![feature(let_chains)] -#![feature(core_intrinsics)] -#![feature(slice_as_chunks)] - -use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; -use generate::{config::GenerationConfig, context::CppContextCollection, metadata::Metadata}; - -use std::{fs, path::PathBuf, time}; - -use clap::{Parser, Subcommand}; - -use crate::{generate::members::CppMember, handlers::unity}; -mod generate; -mod handlers; - -#[derive(Parser)] -#[clap(author, version, about, long_about = None)] -struct Cli { - /// The global-metadata.dat file to use - #[clap(short, long, value_parser, value_name = "FILE")] - metadata: PathBuf, - - /// The libil2cpp.so file to use - #[clap(short, long, value_parser, value_name = "FILE")] - libil2cpp: PathBuf, - - #[clap(subcommand)] - command: Option, -} - -#[derive(Subcommand)] -enum Commands {} - -fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - let cli = Cli::parse(); - // let cli = Cli { - // metadata: PathBuf::from("global-metadata.dat"), - // libil2cpp: PathBuf::from("libil2cpp.so"), - // command: None, - // }; - - let global_metadata_data = fs::read(cli.metadata)?; - let elf_data = fs::read(cli.libil2cpp)?; - let il2cpp_metadata = brocolib::Metadata::parse(&global_metadata_data, &elf_data)?; - - let config = GenerationConfig { - header_path: PathBuf::from("./codegen/include"), - source_path: PathBuf::from("./codegen/src"), - }; - - let mut metadata = Metadata { - metadata: &il2cpp_metadata, - code_registration: &il2cpp_metadata.runtime_metadata.code_registration, - metadata_registration: &il2cpp_metadata.runtime_metadata.metadata_registration, - method_calculations: Default::default(), - parent_to_child_map: Default::default(), - child_to_parent_map: Default::default(), - custom_type_handler: Default::default(), - }; - let t = time::Instant::now(); - println!("Parsing metadata methods"); - metadata.parse(); - println!("Finished in {}ms", t.elapsed().as_millis()); - let mut cpp_context_collection = CppContextCollection::new(); - - // First, make all the contexts - println!("Making types"); - for tdi_u64 in 0..metadata - .metadata - .global_metadata - .type_definitions - .as_vec() - .len() - { - let tdi = TypeDefinitionIndex::new(tdi_u64 as u32); - // Skip children, creating the parents creates them too - if metadata.child_to_parent_map.contains_key(&tdi) { - continue; - } - cpp_context_collection.make_from(&metadata, &config, TypeData::TypeDefinitionIndex(tdi)); - } - - println!("Registering handlers!"); - unity::register_unity(&cpp_context_collection, &mut metadata)?; - println!("Handlers registered!"); - - // Fill them now - println!("Filling root types"); - for tdi_u64 in 0..metadata - .metadata - .global_metadata - .type_definitions - .as_vec() - .len() - { - let tdi = TypeDefinitionIndex::new(tdi_u64 as u32); - - if metadata.child_to_parent_map.contains_key(&tdi) { - continue; - } - cpp_context_collection.fill(&metadata, &config, TypeData::TypeDefinitionIndex(tdi)); - } - // Fill children - println!("Nested types pass"); - for parent in metadata.parent_to_child_map.keys() { - let owner = cpp_context_collection - .get_cpp_type(TypeData::TypeDefinitionIndex(*parent)) - .unwrap(); - - // **Ignore this, we no longer recurse:** - // skip children of children - // only fill first grade children of types - // if owner.nested { - // continue; - // } - - let owner_ty = owner.self_tag; - - cpp_context_collection.fill_nested_types(&metadata, &config, owner_ty); - } - - // 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()?; - println!("Generic type"); - cpp_context_collection - .get() - .iter() - .find(|(_, c)| { - c.get_types() - .iter() - .any(|(_, t)| !t.generic_args.names.is_empty()) - }) - .unwrap() - .1 - .write()?; - println!("Value type"); - cpp_context_collection - .get() - .iter() - .find(|(_, c)| { - c.get_types() - .iter() - .any(|(_, t)| t.is_value_type && t.name == "Color" && t.namespace == "UnityEngine") - }) - .unwrap() - .1 - .write()?; - println!("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? - // println!("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()?; - println!("Array type"); - cpp_context_collection - .get() - .iter() - .find(|(_, c)| { - c.get_types() - .iter() - .any(|(_, t)| t.name == "Array" && t.namespace == "System") - }) - .unwrap() - .1 - .write()?; - println!("Default param"); - cpp_context_collection - .get() - .iter() - .filter(|(_, c)| { - c.get_types().iter().any(|(_, t)| { - t.declarations.iter().any(|d| { - if let CppMember::MethodDecl(m) = d { - m.parameters.iter().any(|p| p.def_value.is_some()) - } else { - false - } - }) - }) - }) - .nth(2) - .unwrap() - .1 - .write()?; - println!("UnityEngine.Object"); - cpp_context_collection - .get() - .iter() - .find(|(_, c)| { - c.get_types() - .iter() - .any(|(_, t)| t.name == "Object" && t.namespace == "UnityEngine") - }) - .unwrap() - .1 - .write()?; - // for (_, context) in cpp_context_collection.get() { - // context.write().unwrap(); - // } - - Ok(()) -} +#![feature(entry_insert)] +#![feature(let_chains)] +#![feature(slice_as_chunks)] +#![feature(read_buf)] +#![feature(map_try_insert)] +#![feature(lazy_cell)] +#![feature(exit_status_error)] + +use brocolib::{global_metadata::TypeDefinitionIndex, runtime_metadata::TypeData}; +use color_eyre::Result; +use generate::{config::GenerationConfig, metadata::Metadata}; +use itertools::Itertools; +extern crate pretty_env_logger; +use filesize::PathExt; +use include_dir::{include_dir, Dir}; +use log::{info, trace, warn}; +use rayon::prelude::*; +use walkdir::DirEntry; + +use std::{fs, path::PathBuf, process::Command, sync::LazyLock, time}; + +use clap::{Parser, Subcommand}; + +use crate::{ + generate::{ + context_collection::CppContextCollection, cpp_type_tag::CppTypeTag, + cs_context_collection::CsContextCollection, members::CppMember, + }, + handlers::{object, value_type}, +}; +mod data; +mod generate; +mod handlers; +mod helpers; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + /// The global-metadata.dat file to use + #[clap(short, long, value_parser, value_name = "FILE")] + metadata: PathBuf, + + /// The libil2cpp.so file to use + #[clap(short, long, value_parser, value_name = "FILE")] + libil2cpp: PathBuf, + /// Whether to format with clang-format + #[clap(short, long)] + format: bool, + + /// Whether to generate generic method specializations + #[clap(short, long)] + gen_generic_methods_specializations: bool, + + #[clap(subcommand)] + command: Option, +} + +#[derive(Subcommand)] +enum Commands {} + +pub static STATIC_CONFIG: LazyLock = LazyLock::new(|| GenerationConfig { + header_path: PathBuf::from("./codegen/include"), + source_path: PathBuf::from("./codegen/src"), + dst_internals_path: PathBuf::from("./codegen/include/cordl_internals"), + dst_header_internals_file: PathBuf::from( + "./codegen/include/cordl_internals/cordl_internals.hpp", + ), + use_anonymous_namespace: false, +}); + +static INTERNALS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/cordl_internals"); + +fn main() -> color_eyre::Result<()> { + color_eyre::install()?; + let cli: Cli = Cli::parse(); + pretty_env_logger::formatted_builder() + .filter_level(log::LevelFilter::Trace) + .parse_default_env() + .init(); + if !cli.format { + info!("Add --format/-f to format with clang-format at end") + } + // let cli = Cli { + // metadata: PathBuf::from("global-metadata.dat"), + // libil2cpp: PathBuf::from("libil2cpp.so"), + // command: None, + // }; + + if STATIC_CONFIG.header_path.exists() { + std::fs::remove_dir_all(&STATIC_CONFIG.header_path)?; + } + std::fs::create_dir_all(&STATIC_CONFIG.header_path)?; + + info!( + "Copying config to codegen folder {:?}", + STATIC_CONFIG.dst_internals_path + ); + + std::fs::create_dir_all(&STATIC_CONFIG.dst_internals_path)?; + + // extract contents of the cordl internals folder into destination + INTERNALS_DIR.extract(&STATIC_CONFIG.dst_internals_path)?; + + let global_metadata_data = fs::read(cli.metadata)?; + let elf_data = fs::read(cli.libil2cpp)?; + let il2cpp_metadata = brocolib::Metadata::parse(&global_metadata_data, &elf_data)?; + + let mut metadata = Metadata { + metadata: &il2cpp_metadata, + code_registration: &il2cpp_metadata.runtime_metadata.code_registration, + metadata_registration: &il2cpp_metadata.runtime_metadata.metadata_registration, + method_calculations: Default::default(), + parent_to_child_map: Default::default(), + child_to_parent_map: Default::default(), + // TODO: These should come from args to the program? + custom_type_handler: Default::default(), + name_to_tdi: Default::default(), + blacklisted_types: Default::default(), + pointer_size: generate::metadata::PointerSize::Bytes8, + // For most il2cpp versions + packing_field_offset: 7, + size_is_default_offset: 12, + specified_packing_field_offset: 13, + packing_is_default_offset: 11, + }; + let t = time::Instant::now(); + info!("Parsing metadata methods"); + metadata.parse(); + info!("Finished in {}ms", t.elapsed().as_millis()); + let mut cpp_context_collection = CppContextCollection::new(); + + // blacklist types + { + let mut blacklist_type = |full_name: &str| { + let tdi = metadata + .metadata + .global_metadata + .type_definitions + .as_vec() + .iter() + .enumerate() + .find(|(_, t)| t.full_name(metadata.metadata, false) == full_name); + + if let Some((tdi, _td)) = tdi { + info!("Blacklisted {full_name}"); + + metadata + .blacklisted_types + .insert(TypeDefinitionIndex::new(tdi as u32)); + } else { + warn!("Unable to blacklist {full_name}") + } + }; + + blacklist_type("UnityEngine.XR.XRInputSubsystemDescriptor"); + blacklist_type("UnityEngine.XR.XRMeshSubsystemDescriptor"); + blacklist_type("UnityEngine.XR.XRDisplaySubsystem"); + blacklist_type("UIToolkitUtilities.Controls.Table"); // TODO: Make System.Enum work properly + // blacklist_type("NetworkPacketSerializer`2::<>c__DisplayClass4_0`1"); + // blacklist_type("NetworkPacketSerializer`2::<>c__DisplayClass8_0`1"); + // blacklist_type("NetworkPacketSerializer`2::<>c__DisplayClass7_0`1"); + // blacklist_type("NetworkPacketSerializer`2::<>c__DisplayClass5_0`1"); + // blacklist_type("NetworkPacketSerializer`2::<>c__DisplayClass10_0"); + // blacklist_type("NetworkPacketSerializer`2::<>c__6`1"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass14_0`5"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass10_0`1"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass11_0`2"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass12_0`3"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass13_0`4"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass14_0`5"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass15_0`1"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass16_0`2"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass17_0`3"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass18_0`4"); + // blacklist_type("RpcHandler`1::<>c__DisplayClass19_0`5"); + } + { + let _blacklist_types = |full_name: &str| { + let tdis = metadata + .metadata + .global_metadata + .type_definitions + .as_vec() + .iter() + .enumerate() + .filter(|(_, t)| t.full_name(metadata.metadata, false).contains(full_name)) + .collect_vec(); + + match tdis.is_empty() { + true => warn!("Unable to blacklist {full_name}"), + false => { + for (tdi, td) in tdis { + info!("Blacklisted {}", td.full_name(metadata.metadata, true)); + + metadata + .blacklisted_types + .insert(TypeDefinitionIndex::new(tdi as u32)); + } + } + } + }; + // blacklist_types("<>c__DisplayClass"); + } + { + // First, make all the contexts + info!("Making types"); + let type_defs = metadata.metadata.global_metadata.type_definitions.as_vec(); + let total = type_defs.len(); + for tdi_u64 in 0..total { + let tdi = TypeDefinitionIndex::new(tdi_u64 as u32); + + let ty_def = &metadata.metadata.global_metadata.type_definitions[tdi]; + let _ty = &metadata.metadata_registration.types[ty_def.byval_type_index as usize]; + + if ty_def.declaring_type_index != u32::MAX { + continue; + } + + trace!( + "Making types {:.4}% ({tdi_u64}/{total})", + (tdi_u64 as f64 / total as f64 * 100.0) + ); + cpp_context_collection.make_from( + &metadata, + &STATIC_CONFIG, + TypeData::TypeDefinitionIndex(tdi), + None, + ); + cpp_context_collection.alias_nested_types_il2cpp( + tdi, + CppTypeTag::TypeDefinitionIndex(tdi), + &metadata, + false, + ); + } + } + { + // First, make all the contexts + info!("Making nested types"); + let type_defs = metadata.metadata.global_metadata.type_definitions.as_vec(); + let total = type_defs.len(); + for tdi_u64 in 0..total { + let tdi = TypeDefinitionIndex::new(tdi_u64 as u32); + + let ty_def = &metadata.metadata.global_metadata.type_definitions[tdi]; + + if ty_def.declaring_type_index == u32::MAX { + continue; + } + + trace!( + "Making nested types {:.4}% ({tdi_u64}/{total})", + (tdi_u64 as f64 / total as f64 * 100.0) + ); + cpp_context_collection.make_nested_from(&metadata, &STATIC_CONFIG, tdi, None); + } + } + + // { + // let total = metadata.metadata_registration.generic_method_table.len() as f64; + // info!("Making generic type instantiations"); + // for (i, generic_class) in metadata + // .metadata_registration + // .generic_method_table + // .iter() + // .enumerate() + // { + // trace!( + // "Making generic type instantiations {:.4}% ({i}/{total})", + // (i as f64 / total * 100.0) + // ); + // let method_spec = metadata + // .metadata_registration + // .method_specs + // .get(generic_class.generic_method_index as usize) + // .unwrap(); + + // cpp_context_collection.make_generic_from(method_spec, &mut metadata, &STATIC_CONFIG); + // } + // } + // { + // let total = metadata.metadata_registration.generic_method_table.len() as f64; + // info!("Filling generic types!"); + // for (i, generic_class) in metadata + // .metadata_registration + // .generic_method_table + // .iter() + // .enumerate() + // { + // trace!( + // "Filling generic type instantiations {:.4}% ({i}/{total})", + // (i as f64 / total * 100.0) + // ); + // let method_spec = metadata + // .metadata_registration + // .method_specs + // .get(generic_class.generic_method_index as usize) + // .unwrap(); + + // cpp_context_collection.fill_generic_class_inst( + // method_spec, + // &mut metadata, + // &STATIC_CONFIG, + // ); + // } + // } + + if cli.gen_generic_methods_specializations { + let total = metadata.metadata_registration.generic_method_table.len() as f64; + info!("Filling generic methods!"); + for (i, generic_class) in metadata + .metadata_registration + .generic_method_table + .iter() + .enumerate() + { + trace!( + "Filling generic method instantiations {:.4}% ({i}/{total})", + (i as f64 / total * 100.0) + ); + let method_spec = metadata + .metadata_registration + .method_specs + .get(generic_class.generic_method_index as usize) + .unwrap(); + + cpp_context_collection.fill_generic_method_inst( + method_spec, + &mut metadata, + &STATIC_CONFIG, + ); + } + } + + info!("Registering handlers!"); + // il2cpp_internals::register_il2cpp_types(&mut metadata)?; + // unity::register_unity(&mut metadata)?; + object::register_system(&mut metadata)?; + value_type::register_value_type(&mut metadata)?; + info!("Handlers registered!"); + + { + // Fill them now + info!("Filling types"); + let type_defs = metadata.metadata.global_metadata.type_definitions.as_vec(); + let total = type_defs.len(); + for tdi_u64 in 0..total { + let tdi = TypeDefinitionIndex::new(tdi_u64 as u32); + + trace!( + "Filling type {:.4} ({tdi_u64}/{total})", + (tdi_u64 as f64 / total as f64 * 100.0) + ); + + cpp_context_collection.fill( + &metadata, + &STATIC_CONFIG, + CppTypeTag::TypeDefinitionIndex(tdi), + ); + } + } + + const write_all: bool = true; + if write_all { + cpp_context_collection.write_all(&STATIC_CONFIG)?; + cpp_context_collection.write_namespace_headers()?; + } 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"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| c.get_types().iter().any(|(_, t)| t.cpp_template.is_some())) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("List Generic type"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.cpp_name_components.generics.is_some() && t.cpp_name() == "List_1" + }) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("Value type"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.is_value_type && t.name() == "Color" && t.namespace() == "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"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "Array" && t.namespace() == "System") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("Default param"); + cpp_context_collection + .get() + .iter() + .filter(|(_, c)| { + c.get_types().iter().any(|(_, t)| { + t.implementations.iter().any(|d| { + if let CppMember::MethodImpl(m) = d.as_ref() { + m.parameters.iter().any(|p| p.def_value.is_some()) + } else { + false + } + }) + }) + }) + .nth(2) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("Enum type"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| c.get_types().iter().any(|(_, t)| t.is_enum_type)) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.Object"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "Object" && t.namespace() == "UnityEngine") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("BeatmapSaveDataHelpers"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name() == "BeatmapSaveDataHelpers") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("HMUI.ViewController"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "HMUI" && t.name() == "ViewController") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.Component"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "UnityEngine" && t.name() == "Component") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("UnityEngine.GameObject"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "UnityEngine" && t.name() == "GameObject") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("MainFlowCoordinator"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace().is_empty() && t.name() == "MainFlowCoordinator") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("OVRPlugin"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace().is_empty() && t.name() == "OVRPlugin") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("HMUI.IValueChanger"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "HMUI" && t.name() == "IValueChanger`1") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.ValueType"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "ValueType") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.ValueTuple_2"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "ValueTuple`2") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Decimal"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "Decimal") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Enum"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "Enum") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Multicast"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "MulticastDelegate") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("System.Delegate"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.namespace() == "System" && t.name() == "Delegate") + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + info!("BeatmapSaveDataVersion3.BeatmapSaveData.EventBoxGroup`1"); + cpp_context_collection + .get() + .iter() + .find(|(_, c)| { + c.get_types() + .iter() + .any(|(_, t)| t.name().contains("EventBoxGroup`1")) + }) + .unwrap() + .1 + .write(&STATIC_CONFIG)?; + // for (_, context) in cpp_context_collection.get() { + // context.write().unwrap(); + // } + } + + if cli.format { + format_files()?; + } + + Ok(()) +} + +fn format_files() -> Result<()> { + info!("Formatting!"); + + use walkdir::WalkDir; + + let files: Vec = WalkDir::new(&STATIC_CONFIG.header_path) + .into_iter() + .filter(|f| f.as_ref().is_ok_and(|f| f.path().is_file())) + .try_collect()?; + + let file_count = files.len(); + + info!( + "{file_count} files across {} threads", + rayon::current_num_threads() + ); + // easily get file size for a given file + fn file_size(file: &DirEntry) -> usize { + match std::fs::metadata(file.path()) { + Ok(data) => file.path().size_on_disk_fast(&data).unwrap() as usize, + Err(_) => 0, + } + } + + files + .iter() + // sort on file size + .sorted_by(|a, b| file_size(a).cmp(&file_size(b))) + // reverse to go big -> small, so we can work on other files while big files are happening + .rev() + // parallelism + .enumerate() + .par_bridge() + .into_par_iter() + .try_for_each(|(file_num, file)| -> Result<()> { + let path = file.path(); + info!( + "Formatting [{}/{file_count}] {}", + file_num + 1, + path.display() + ); + let mut command = Command::new("clang-format"); + command.arg("-i").arg(path); + + command.spawn()?.wait()?.exit_ok()?; + + Ok(()) + })?; + + info!("Done formatting!"); + Ok(()) +}