From 6e9944aa9bd1dc5fd52b3f12dabb9676f9050e21 Mon Sep 17 00:00:00 2001 From: ste Date: Wed, 25 Aug 2021 20:38:31 +0200 Subject: [PATCH] v0.5.1: #1 fixed, #26 fixed, `Il2Cpp.Thread` added, `Il2Cpp.Domain` changed to static, and few more --- README.md | 84 +++- package.json | 32 +- src/il2cpp/api.ts | 532 ++++++++++++++---------- src/il2cpp/base.ts | 46 +- src/il2cpp/dumper.ts | 6 +- src/il2cpp/index.ts | 1 + src/il2cpp/structs/class.ts | 150 ++++--- src/il2cpp/structs/domain.ts | 40 +- src/il2cpp/structs/field.ts | 18 + src/il2cpp/structs/gc.ts | 35 ++ src/il2cpp/structs/generic-instance.ts | 6 +- src/il2cpp/structs/image.ts | 17 +- src/il2cpp/structs/metadata-snapshot.ts | 5 +- src/il2cpp/structs/method.ts | 49 ++- src/il2cpp/structs/object.ts | 51 ++- src/il2cpp/structs/parameter.ts | 4 + src/il2cpp/structs/thread.ts | 43 ++ src/il2cpp/structs/type.ts | 6 - src/il2cpp/structs/value-type.ts | 16 +- src/il2cpp/tracer.ts | 3 +- src/il2cpp/utils.ts | 3 +- src/index.ts | 3 + src/utils/native-struct.ts | 4 + src/utils/utils.ts | 13 +- tsconfig.json | 14 +- 25 files changed, 795 insertions(+), 386 deletions(-) create mode 100644 src/il2cpp/structs/thread.ts diff --git a/README.md b/README.md index dc607f8..6c2d3f5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Frida](https://img.shields.io/badge/-frida-ef6456?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyAgIHZlcnNpb249IjEuMSIgICBpZD0iTGF5ZXJfMSIgICB4PSIwcHgiICAgeT0iMHB4IiAgIHZpZXdCb3g9IjAgMCA5LjcyOTk3OTkgMTAuOTM1NzEyIiAgIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwNC40IDM5IiAgIHhtbDpzcGFjZT0icHJlc2VydmUiICAgc29kaXBvZGk6ZG9jbmFtZT0ibG9nby5zdmciICAgd2lkdGg9IjkuNzI5OTc5NSIgICBoZWlnaHQ9IjEwLjkzNTcxMiIgICBpbmtzY2FwZTp2ZXJzaW9uPSIxLjEgKGNlNjY2M2IzYjcsIDIwMjEtMDUtMjUpIiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIgICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnMgICBpZD0iZGVmczkiIC8+PHNvZGlwb2RpOm5hbWVkdmlldyAgIGlkPSJuYW1lZHZpZXc3IiAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIgICBib3JkZXJvcGFjaXR5PSIxLjAiICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIiAgIGlua3NjYXBlOnBhZ2VjaGVja2VyYm9hcmQ9IjAiICAgc2hvd2dyaWQ9ImZhbHNlIiAgIGZpdC1tYXJnaW4tdG9wPSIwIiAgIGZpdC1tYXJnaW4tbGVmdD0iMCIgICBmaXQtbWFyZ2luLXJpZ2h0PSIwIiAgIGZpdC1tYXJnaW4tYm90dG9tPSIwIiAgIGlua3NjYXBlOnpvb209IjYuOTE3ODA4NCIgICBpbmtzY2FwZTpjeD0iLTAuMTQ0NTU0NDUiICAgaW5rc2NhcGU6Y3k9Ii04LjYwMDk4OTkiICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIiAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEwMDgiICAgaW5rc2NhcGU6d2luZG93LXg9IjAiICAgaW5rc2NhcGU6d2luZG93LXk9IjAiICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJMYXllcl8xIiAvPjxnICAgaWQ9Imc0IiAgIHN0eWxlPSJkaXNwbGF5OmlubGluZTtmaWxsOiNmZmZmZmYiICAgdHJhbnNmb3JtPSJtYXRyaXgoMC4yODA0MDI4NiwwLDAsMC4yODA0MDI4NiwtMTEuNTgwNjM4LDApIj48cGF0aCAgIGZpbGw9IiNmZmZmZmYiICAgZD0iTSA1MS40LDM5IEggNDEuMyBMIDQ5LjcsMjYuMSBDIDQ0LjksMjMuOCA0Mi4zLDE5LjYgNDIuMywxMy41IDQyLjMsNC44IDQ4LjIsMCA1OC41LDAgSCA3NiBWIDM5IEggNjcgViAyOCBIIDU4LjUgNTcuNyBaIE0gNjcsMjAgViA3IGggLTguNSBjIC00LjksMCAtNy43LDIgLTcuNyw2LjQgMCw0LjUgMi44LDYuNiA3LjcsNi42IHoiICAgaWQ9InBhdGgyIiAgIHN0eWxlPSJmaWxsOiNmZmZmZmYiIC8+PC9nPjwvc3ZnPg==)](https://frida.re) [![NPM](https://img.shields.io/npm/v/frida-il2cpp-bridge?label=&logo=npm&style=for-the-badge)](https://npmjs.org/package/frida-il2cpp-bridge) -A Frida module to dump, trace or hijack any Il2Cpp application at runtime with a high level of abstraction, without needing the `global-metadata.dat` file. +A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the `global-metadata.dat` file. ![Screenshot_20210724_195807](https://user-images.githubusercontent.com/46219656/126877297-97529b9b-e74b-4130-9b6e-061b938a5737.png) ## Compatibility @@ -15,13 +15,6 @@ It should work for any Unity version in the inclusive range **5.3.0** - **2021.1 **Android** is supported; **Linux** and **Windows** are not tested; **iOS** is not supported yet ([#15](https://github.com/vfsfitvnm/frida-il2cpp-bridge/issues/15)). - -## Known limitations -A lot of aspects are still unknown to me. -- Absent generic classes or methods utilities -- Missing traceback system - - ## Acknowledgements Thanks to [meme](https://github.com/meme) and [tryso](https://github.com/tryso) for helping and getting me into this, and to [djkaty](https://github.com/djkaty) and [nneonneo](https://github.com/nneonneo) for providing the IL2CPP C @@ -41,9 +34,11 @@ headers. * [Heap scan](#heap-scan) * [Methods](#methods) * [Invocation](#invocation) - * [Replacement & Interception](#replacement-&-interception) + * [Replacement & Interception](#replacement--interception) + * [Generics handling](#generics-handling) * [Miscellaneous](#miscellaneous) * [How to handle overloading](#how-to-handle-overloading) + * [Ghidra script](#ghidra-script) --- @@ -88,15 +83,15 @@ Learn more about `packages.json` [here](https://docs.npmjs.com/cli/v7/configurin "scripts": { "build": "frida-compile -o _.js -w index.ts", "attach": "run() { frida -U \"$1\" -l _.js --runtime=v8; }; run", - "attach-with-spawn": "run() { frida -U -f \"$1\" -l _.js --no-pause --runtime=v8; }; run", - "app0-with-spawn": "npm run attach-with-spawn com.example.application0", + "spawn": "run() { frida -U -f \"$1\" -l _.js --no-pause --runtime=v8; }; run", + "app0-spawn": "npm run spawn com.example.application0", "app1": "npm run \"Application1 Name\"", - "app1-with-spawn": "npm run attach-with-spawn com.example.application1" + "app1-spawn": "npm run spawn com.example.application1" }, "devDependencies": { "@types/frida-gum": "^17.1.0", "frida-compile": "^10.2.4", - "frida-il2cpp-bridge": "^0.5.0" + "frida-il2cpp-bridge": "^0.5.1" } } ``` @@ -218,7 +213,7 @@ struct System.Int32 : System.ValueType, System.IFormattable, System.IConvertible import "frida-il2cpp-bridge"; Il2Cpp.perform(() => { - const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image; + const mscorlib = Il2Cpp.Domain.assemblies.mscorlib.image; const SystemString = mscorlib.classes["System.String"]; // simple trace, it only traces method calls @@ -327,7 +322,7 @@ words, however `Il2Cpp.Tracer` does not use `Interceptor.attach`, but a combinat import "frida-il2cpp-bridge"; Il2Cpp.perform(() => { - const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image; + const mscorlib = Il2Cpp.Domain.assemblies.mscorlib.image; const SystemType = mscorlib.classes["System.Type"]; // it relies on classes gc descriptors @@ -356,7 +351,7 @@ know how they internally work, I read enough uncommented C++ source code for my import "frida-il2cpp-bridge"; Il2Cpp.perform(() => { - const mscorlib = Il2Cpp.Domain.reference.assemblies.mscorlib.image; + const mscorlib = Il2Cpp.Domain.assemblies.mscorlib.image; const SystemString = mscorlib.classes["System.String"]; const IsNullOrEmpty = mscorlib.classes["System.String"].methods.IsNullOrEmpty; @@ -410,6 +405,42 @@ Il2Cpp.perform(() => { function (ch: number, chars: Il2Cpp.Reference>): boolean {} ``` +### Generics handling + +Dealing with generics is problematic when the `global-metadata.dat` file is ignored. You can +gather the inflated version (if any) via `Il2Cpp.Class.inflate` and `Il2Cpp.method.inflate`. +Reference types (aka objects) all shares the same code: it is easy to retrieve virtual address in this case. Value types (aka primitives and structs) does not share any code. +`inflate` will always return an inflated class or method (you must match the number of type arguments with the number of types you pass to `inflate`), but the returned value it's not +necessarely a class or method that has been implemented. +```ts +Il2Cpp.perform(() => { + const classes = Il2Cpp.Image.corlib.classes; + + const SystemObject = classes["System.Object"]; + const SystemInt32 = classes["System.Object"]; + + + const GenericList = classes["System.Collections.Generic.List"]; + + // This class is shared among all reference types + const SystemObjectList = GenericList.inflate(SystemObject); + + // This class is specific to System.Int32, because it's a value type + const SystemInt32List = GenericList.inflate(SystemInt32); + + + // static T UnsafeCast(System.Object o); + const UnsafeCast = classes["System.Runtime.CompilerServices.JitHelpers"].methods.UnsafeCast; + // UnsafeCast is a generic method, its virtual address is null + + // This is the UnsafeCast for every reference type + const SystemObjectUnsafeCast = UnsafeCast.inflate(SystemObject); + + // This doesn't make sense, but this is the UnsafeCast specific to System.Int32, because it's a value type + const SystemInt32UnsafeCast = UnsafeCast.inflate(SystemInt32); +}); +``` + --- ## Miscellaneous @@ -427,3 +458,24 @@ System.Boolean Equals(System.String value, System.StringComparison comparisonTyp Basically, an underscore is appended to the method name (key) until the key can be used. +### Ghidra script + +The following script parses the file outputted by `Il2Cpp.Dumper` and looks for methods using regular expression. + +```py +import re +from ghidra.program.model.address import AddressFactory +from ghidra.program.model.symbol.SourceType import USER_DEFINED + +address_factory = getAddressFactory() + +with open("/path/to/dump.cs", "r") as file: + content = file.read() + +matches = re.findall("([^\s]+)(?:\(.+)(0x[0123456789abcdef]{8})", content) + +for match in matches: + function = getFunctionAt(address_factory.getAddress(match[1])) + if function: + function.setName(match[0], USER_DEFINED) +``` \ No newline at end of file diff --git a/package.json b/package.json index 66dcc2d..96ef9d8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frida-il2cpp-bridge", - "version": "0.5.0", - "description": "A Frida module to dump, trace or hijack any Il2Cpp application at runtime with a high level of abstraction, without needing the global-metadata.dat file.", + "version": "0.5.1", + "description": "A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.", "keywords": [ "frida", "il2cpp", @@ -9,8 +9,14 @@ "trace", "global-metadata" ], - "module": "./dist/index.js", + "repository": { + "type": "git", + "url": "https://github.com/vfsfitvnm/frida-il2cpp-bridge.git" + }, + "license": "MIT", + "author": "vfsfitvnm", "main": "./dist/index.js", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ "/dist/*" @@ -18,26 +24,20 @@ "scripts": { "build": "tsc" }, - "author": "vfsfitvnm", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/vfsfitvnm/frida-il2cpp-bridge.git" - }, "prettier": { - "tabWidth": 4, "arrowParens": "avoid", - "trailingComma": "none", + "bracketSpacing": true, "printWidth": 140, - "bracketSpacing": true - }, - "devDependencies": { - "@types/frida-gum": "^17.1.0", - "prettier": "^2.3.2" + "tabWidth": 4, + "trailingComma": "none" }, "dependencies": { "decorator-cache-getter": "^1.0.0", "fastest-levenshtein": "^1.0.12", "kleur": "^4.1.4" + }, + "devDependencies": { + "@types/frida-gum": "^17.1.0", + "prettier": "^2.3.2" } } diff --git a/src/il2cpp/api.ts b/src/il2cpp/api.ts index 4012e52..40be196 100644 --- a/src/il2cpp/api.ts +++ b/src/il2cpp/api.ts @@ -7,698 +7,790 @@ class Il2CppApi { @cache static get _alloc() { - return new NativeFunction(this.r`alloc`, "pointer", ["size_t"]); + return this.r("alloc", "pointer", ["size_t"]); + } + + @cache + static get _allocationGranularity() { + return this.r("allocation_granularity", "uint32", []); } @cache static get _arrayGetElements() { - return new NativeFunction(this.r`array_elements`, "pointer", ["pointer"]); + return this.r("array_elements", "pointer", ["pointer"]); } @cache static get _arrayGetLength() { - return new NativeFunction(this.r`array_length`, "uint32", ["pointer"]); + return this.r("array_length", "uint32", ["pointer"]); } @cache static get _arrayNew() { - return new NativeFunction(this.r`array_new`, "pointer", ["pointer", "uint32"]); + return this.r("array_new", "pointer", ["pointer", "uint32"]); } @cache static get _assemblyGetImage() { - return new NativeFunction(this.r`assembly_get_image`, "pointer", ["pointer"]); + return this.r("assembly_get_image", "pointer", ["pointer"]); } @cache static get _assemblyGetName() { - return new NativeFunction(this.r`assembly_get_name`, "pointer", ["pointer"]); + return this.r("assembly_get_name", "pointer", ["pointer"]); + } + + @cache + static get _classForEach() { + return this.r("class_for_each", "void", ["pointer", "pointer"]); } @cache static get _classFromName() { - return new NativeFunction(this.r`class_from_name`, "pointer", ["pointer", "pointer", "pointer"]); + return this.r("class_from_name", "pointer", ["pointer", "pointer", "pointer"]); } + @cache + static get _classFromSystemType() { + return this.r("class_from_system_type", "pointer", ["pointer"]); + } + @cache static get _classFromType() { - return new NativeFunction(this.r`class_from_type`, "pointer", ["pointer"]); + return this.r("class_from_type", "pointer", ["pointer"]); } @cache static get _classGetArrayClass() { - return new NativeFunction(this.r`array_class_get`, "pointer", ["pointer", "uint32"]); + return this.r("array_class_get", "pointer", ["pointer", "uint32"]); } @cache static get _classGetArrayElementSize() { - return new NativeFunction(this.r`class_array_element_size`, "int", ["pointer"]); + return this.r("class_array_element_size", "int", ["pointer"]); } @cache static get _classGetAssemblyName() { - return new NativeFunction(this.r`class_get_assemblyname`, "pointer", ["pointer"]); + return this.r("class_get_assemblyname", "pointer", ["pointer"]); } @cache static get _classGetDeclaringType() { - return new NativeFunction(this.r`class_get_declaring_type`, "pointer", ["pointer"]); + return this.r("class_get_declaring_type", "pointer", ["pointer"]); } @cache static get _classGetElementClass() { - return new NativeFunction(this.r`class_get_element_class`, "pointer", ["pointer"]); + return this.r("class_get_element_class", "pointer", ["pointer"]); } @cache static get _classGetFieldCount() { - return new NativeFunction(this.r`class_get_field_count`, "uint16", ["pointer"]); + return this.r("class_num_fields", "size_t", ["pointer"]); } @cache static get _classGetFields() { - return new NativeFunction(this.r`class_get_fields`, "pointer", ["pointer", "pointer"]); + return this.r("class_get_fields", "pointer", ["pointer", "pointer"]); + } + + @cache + static get _classGetFlags() { + return this.r("class_get_fields", "int", ["pointer"]); } @cache static get _classGetImage() { - return new NativeFunction(this.r`class_get_image`, "pointer", ["pointer"]); + return this.r("class_get_image", "pointer", ["pointer"]); } @cache static get _classGetInstanceSize() { - return new NativeFunction(this.r`class_instance_size`, "uint32", ["pointer"]); + return this.r("class_instance_size", "int32", ["pointer"]); } @cache static get _classGetInterfaceCount() { - return new NativeFunction(this.r`class_get_interface_count`, "uint16", ["pointer"]); + return this.r("class_get_interface_count", "uint16", ["pointer"]); } @cache static get _classGetInterfaces() { - return new NativeFunction(this.r`class_get_interfaces`, "pointer", ["pointer", "pointer"]); + return this.r("class_get_interfaces", "pointer", ["pointer", "pointer"]); } @cache static get _classGetMethodCount() { - return new NativeFunction(this.r`class_get_method_count`, "uint16", ["pointer"]); + return this.r("class_get_method_count", "uint16", ["pointer"]); } @cache static get _classGetMethods() { - return new NativeFunction(this.r`class_get_methods`, "pointer", ["pointer", "pointer"]); + return this.r("class_get_methods", "pointer", ["pointer", "pointer"]); } @cache static get _classGetName() { - return new NativeFunction(this.r`class_get_name`, "pointer", ["pointer"]); + return this.r("class_get_name", "pointer", ["pointer"]); } @cache static get _classGetNamespace() { - return new NativeFunction(this.r`class_get_namespace`, "pointer", ["pointer"]); + return this.r("class_get_namespace", "pointer", ["pointer"]); } @cache static get _classGetParent() { - return new NativeFunction(this.r`class_get_parent`, "pointer", ["pointer"]); + return this.r("class_get_parent", "pointer", ["pointer"]); + } + + @cache + static get _classGetRank() { + return this.r("class_get_rank", "int", ["pointer"]); } @cache static get _classGetStaticFieldData() { - return new NativeFunction(this.r`class_get_static_field_data`, "pointer", ["pointer"]); + return this.r("class_get_static_field_data", "pointer", ["pointer"]); + } + + @cache + static get _classGetValueSize() { + return this.r("class_value_size", "int32", ["pointer", "pointer"]); } @cache static get _classGetType() { - return new NativeFunction(this.r`class_get_type`, "pointer", ["pointer"]); + return this.r("class_get_type", "pointer", ["pointer"]); } @cache static get _classHasClassConstructor() { - return new NativeFunction(this.r`class_has_class_constructor`, "bool", ["pointer"]); + return this.r("class_has_class_constructor", "bool", ["pointer"]); + } + + @cache + static get _classHasReferences() { + return this.r("class_has_references", "bool", ["pointer"]); } @cache static get _classInit() { - return new NativeFunction(this.r`runtime_class_init`, "void", ["pointer"]); + return this.r("runtime_class_init", "void", ["pointer"]); + } + + @cache + static get _classIsAbstract() { + return this.r("class_is_abstract", "bool", ["pointer"]); } @cache static get _classIsAssignableFrom() { - return new NativeFunction(this.r`class_is_assignable_from`, "bool", ["pointer", "pointer"]); + return this.r("class_is_assignable_from", "bool", ["pointer", "pointer"]); + } + + @cache + static get _classIsBlittable() { + return this.r("class_is_blittable", "bool", ["pointer"]); } @cache static get _classIsEnum() { - return new NativeFunction(this.r`class_is_enum`, "bool", ["pointer"]); + return this.r("class_is_enum", "bool", ["pointer"]); } @cache static get _classIsGeneric() { - return new NativeFunction(this.r`class_is_generic`, "bool", ["pointer"]); + return this.r("class_is_generic", "bool", ["pointer"]); } @cache static get _classIsInflated() { - return new NativeFunction(this.r`class_is_inflated`, "bool", ["pointer"]); + return this.r("class_is_inflated", "bool", ["pointer"]); } @cache static get _classIsInterface() { - return new NativeFunction(this.r`class_is_interface`, "bool", ["pointer"]); + return this.r("class_is_interface", "bool", ["pointer"]); } @cache static get _classIsStaticConstructorFinished() { - return new NativeFunction(this.r`class_is_class_constructor_finished`, "bool", ["pointer"]); + return this.r("class_is_class_constructor_finished", "bool", ["pointer"]); } @cache static get _classIsSubclassOf() { - return new NativeFunction(this.r`class_is_subclass_of`, "bool", ["pointer", "pointer", "bool"]); + return this.r("class_is_subclass_of", "bool", ["pointer", "pointer", "bool"]); } @cache static get _classIsValueType() { - return new NativeFunction(this.r`class_is_valuetype`, "bool", ["pointer"]); + return this.r("class_is_valuetype", "bool", ["pointer"]); } @cache static get _domainAssemblyOpen() { - return new NativeFunction(this.r`domain_assembly_open`, "pointer", ["pointer", "pointer"]); + return this.r("domain_assembly_open", "pointer", ["pointer", "pointer"]); } @cache static get _domainGet() { - return new NativeFunction(this.r`domain_get`, "pointer", []); + return this.r("domain_get", "pointer", []); } @cache static get _domainGetAssemblies() { - return new NativeFunction(this.r`domain_get_assemblies`, "pointer", ["pointer", "pointer"]); + return this.r("domain_get_assemblies", "pointer", ["pointer", "pointer"]); } @cache static get _domainGetName() { - return new NativeFunction(this.r`domain_get_name`, "pointer", ["pointer"]); + return this.r("domain_get_name", "pointer", ["pointer"]); } @cache static get _fieldGetClass() { - return new NativeFunction(this.r`field_get_parent`, "pointer", ["pointer"]); + return this.r("field_get_parent", "pointer", ["pointer"]); + } + + @cache + static get _fieldGetFlags() { + return this.r("field_get_flags", "int", ["pointer"]); } @cache static get _fieldGetName() { - return new NativeFunction(this.r`field_get_name`, "pointer", ["pointer"]); + return this.r("field_get_name", "pointer", ["pointer"]); } @cache static get _fieldGetOffset() { - return new NativeFunction(this.r`field_get_offset`, "int32", ["pointer"]); + return this.r("field_get_offset", "int32", ["pointer"]); } @cache static get _fieldGetStaticValue() { - return new NativeFunction(this.r`field_static_get_value`, "void", ["pointer", "pointer"]); + return this.r("field_static_get_value", "void", ["pointer", "pointer"]); } @cache static get _fieldGetType() { - return new NativeFunction(this.r`field_get_type`, "pointer", ["pointer"]); + return this.r("field_get_type", "pointer", ["pointer"]); } @cache static get _fieldGetValue() { - return new NativeFunction(this.r`field_get_value`, "void", ["pointer", "pointer", "pointer"]); + return this.r("field_get_value", "void", ["pointer", "pointer", "pointer"]); } @cache static get _fieldIsInstance() { - return new NativeFunction(this.r`field_is_instance`, "bool", ["pointer"]); + return this.r("field_is_instance", "bool", ["pointer"]); } @cache static get _fieldIsLiteral() { - return new NativeFunction(this.r`field_is_literal`, "bool", ["pointer"]); + return this.r("field_is_literal", "bool", ["pointer"]); } @cache static get _fieldSetStaticValue() { - return new NativeFunction(this.r`field_static_set_value`, "void", ["pointer", "pointer"]); + return this.r("field_static_set_value", "void", ["pointer", "pointer"]); } @cache static get _free() { - return new NativeFunction(this.r`free`, "void", ["pointer"]); + return this.r("free", "void", ["pointer"]); } @cache static get _gcCollect() { - return new NativeFunction(this.r`gc_collect`, "void", ["int"]); + return this.r("gc_collect", "void", ["int"]); } @cache static get _gcCollectALittle() { - return new NativeFunction(this.r`gc_collect_a_little`, "void", []); + return this.r("gc_collect_a_little", "void", []); } @cache static get _gcDisable() { - return new NativeFunction(this.r`gc_disable`, "void", []); + return this.r("gc_disable", "void", []); } @cache static get _gcEnable() { - return new NativeFunction(this.r`gc_enable`, "void", []); + return this.r("gc_enable", "void", []); } @cache static get _gcGetHeapSize() { - return new NativeFunction(this.r`gc_get_heap_size`, "int64", []); + return this.r("gc_get_heap_size", "int64", []); + } + + @cache + static get _gcGetMaxTimeSlice() { + return this.r("gc_get_max_time_slice_ns", "int64", []); } @cache static get _gcGetUsedSize() { - return new NativeFunction(this.r`gc_get_used_size`, "int64", []); + return this.r("gc_get_used_size", "int64", []); } @cache static get _gcHandleGetTarget() { - return new NativeFunction(this.r`gchandle_get_target`, "pointer", ["uint32"]); + return this.r("gchandle_get_target", "pointer", ["uint32"]); } @cache static get _gcHandleFree() { - return new NativeFunction(this.r`gchandle_free`, "void", ["uint32"]); + return this.r("gchandle_free", "void", ["uint32"]); } @cache static get _gcHandleNew() { - return new NativeFunction(this.r`gchandle_new`, "uint32", ["pointer", "bool"]); + return this.r("gchandle_new", "uint32", ["pointer", "bool"]); } @cache static get _gcHandleNewWeakRef() { - return new NativeFunction(this.r`gchandle_new_weakref`, "uint32", ["pointer", "bool"]); + return this.r("gchandle_new_weakref", "uint32", ["pointer", "bool"]); } @cache static get _gcIsDisabled() { - return new NativeFunction(this.r`gc_is_disabled`, "bool", []); + return this.r("gc_is_disabled", "bool", []); + } + + @cache + static get _gcIsIncremental() { + return this.r("gc_is_incremental", "bool", []); + } + + @cache + static get _gcSetMaxTimeSlice() { + return this.r("gc_set_max_time_slice_ns", "void", ["int64"]); + } + + @cache + static get _gcStartIncrementalCollection() { + return this.r("gc_start_incremental_collection", "void", []); + } + + @cache + static get _gcStartWorld() { + return this.r("start_gc_world", "void", []); + } + + @cache + static get _gcStopWorld() { + return this.r("stop_gc_world", "void", []); } @cache static get _genericClassGetCachedClass() { - return new NativeFunction(this.r`generic_class_get_cached_class`, "pointer", ["pointer"]); + return this.r("generic_class_get_cached_class", "pointer", ["pointer"]); } @cache static get _genericClassGetClassGenericInstance() { - return new NativeFunction(this.r`generic_class_get_class_generic_instance`, "pointer", ["pointer"]); + return this.r("generic_class_get_class_generic_instance", "pointer", ["pointer"]); } @cache static get _genericClassGetMethodGenericInstance() { - return new NativeFunction(this.r`generic_class_get_method_generic_instance`, "pointer", ["pointer"]); + return this.r("generic_class_get_method_generic_instance", "pointer", ["pointer"]); } @cache static get _genericInstanceGetTypeCount() { - return new NativeFunction(this.r`generic_instance_get_type_count`, "uint32", ["pointer"]); + return this.r("generic_instance_get_type_count", "uint32", ["pointer"]); } @cache static get _genericInstanceGetTypes() { - return new NativeFunction(this.r`generic_instance_get_types`, "pointer", ["pointer"]); + return this.r("generic_instance_get_types", "pointer", ["pointer"]); } @cache static get _getCorlib() { - return new NativeFunction(this.r`get_corlib`, "pointer", []); + return this.r("get_corlib", "pointer", []); } @cache static get _imageGetAssembly() { - return new NativeFunction(this.r`image_get_assembly`, "pointer", ["pointer"]); + return this.r("image_get_assembly", "pointer", ["pointer"]); } @cache static get _imageGetClass() { - return new NativeFunction(this.r`image_get_class`, "pointer", ["pointer", "uint"]); + return this.r("image_get_class", "pointer", ["pointer", "uint"]); } @cache static get _imageGetClassCount() { - return new NativeFunction(this.r`image_get_class_count`, "uint32", ["pointer"]); + return this.r("image_get_class_count", "uint32", ["pointer"]); } @cache static get _imageGetClassStart() { - return new NativeFunction(this.r`image_get_class_start`, "uint32", ["pointer"]); + return this.r("image_get_class_start", "uint32", ["pointer"]); + } + + @cache + static get _imageGetEntryPoint() { + return this.r("image_get_entry_point", "pointer", ["pointer"]); } @cache static get _imageGetName() { - return new NativeFunction(this.r`image_get_name`, "pointer", ["pointer"]); + return this.r("image_get_name", "pointer", ["pointer"]); + } + + @cache + static get _imageGetHashTable() { + return this.r("image_get_hash_table", "pointer", ["pointer"]); } @cache static get _init() { - return new NativeFunction(this.r`init`, "void", []); + return this.r("init", "void", []); } @cache static get _livenessCalculationBegin() { - return new NativeFunction(this.r`unity_liveness_calculation_begin`, "pointer", [ - "pointer", - "int", - "pointer", - "pointer", - "pointer", - "pointer" - ]); + return this.r("unity_liveness_calculation_begin", "pointer", ["pointer", "int", "pointer", "pointer", "pointer", "pointer"]); } @cache static get _livenessCalculationEnd() { - return new NativeFunction(this.r`unity_liveness_calculation_end`, "void", ["pointer"]); + return this.r("unity_liveness_calculation_end", "void", ["pointer"]); } @cache static get _livenessCalculationFromStatics() { - return new NativeFunction(this.r`unity_liveness_calculation_from_statics`, "void", ["pointer"]); + return this.r("unity_liveness_calculation_from_statics", "void", ["pointer"]); } @cache static get _memorySnapshotCapture() { - return new NativeFunction(this.r`capture_memory_snapshot`, "pointer", []); + return this.r("capture_memory_snapshot", "pointer", []); } @cache static get _memorySnapshotFree() { - return new NativeFunction(this.r`free_captured_memory_snapshot`, "void", ["pointer"]); + return this.r("free_captured_memory_snapshot", "void", ["pointer"]); } @cache static get _memorySnapshotGetMetadataSnapshot() { - return new NativeFunction(this.r`memory_snapshot_get_metadata_snapshot`, "pointer", ["pointer"]); + return this.r("memory_snapshot_get_metadata_snapshot", "pointer", ["pointer"]); } @cache static get _memorySnapshotGetObjects() { - return new NativeFunction(this.r`memory_snapshot_get_objects`, "pointer", ["pointer"]); + return this.r("memory_snapshot_get_objects", "pointer", ["pointer"]); } @cache static get _memorySnapshotGetTrackedObjectCount() { - return new NativeFunction(this.r`memory_snapshot_get_tracked_object_count`, "uint64", ["pointer"]); + return this.r("memory_snapshot_get_tracked_object_count", "uint64", ["pointer"]); } @cache static get _metadataSnapshotGetMetadataTypeCount() { - return new NativeFunction(this.r`metadata_snapshot_get_metadata_type_count`, "uint32", ["pointer"]); + return this.r("metadata_snapshot_get_metadata_type_count", "uint32", ["pointer"]); } @cache static get _metadataSnapshotGetMetadataTypes() { - return new NativeFunction(this.r`metadata_snapshot_get_metadata_types`, "pointer", ["pointer", "pointer"]); + return this.r("metadata_snapshot_get_metadata_types", "pointer", ["pointer", "pointer"]); } @cache static get _metadataTypeGetAssemblyName() { - return new NativeFunction(this.r`metadata_type_get_assembly_name`, "pointer", ["pointer"]); + return this.r("metadata_type_get_assembly_name", "pointer", ["pointer"]); } @cache static get _metadataTypeGetBaseOrElementTypeIndex() { - return new NativeFunction(this.r`metadata_type_get_base_or_element_type_index`, "uint32", ["pointer"]); + return this.r("metadata_type_get_base_or_element_type_index", "uint32", ["pointer"]); } @cache static get _metadataTypeGetName() { - return new NativeFunction(this.r`metadata_type_get_name`, "pointer", ["pointer"]); + return this.r("metadata_type_get_name", "pointer", ["pointer"]); } @cache static get _metadataTypeGetClass() { - return new NativeFunction(this.r`metadata_type_get_class`, "pointer", ["pointer"]); + return this.r("metadata_type_get_class", "pointer", ["pointer"]); } @cache static get _methodGetClass() { - return new NativeFunction(this.r`method_get_class`, "pointer", ["pointer"]); + return this.r("method_get_class", "pointer", ["pointer"]); + } + + @cache + static get _methodGetFlags() { + return this.r("method_get_flags", "uint32", ["pointer", "pointer"]); + } + + @cache + static get _methodGetFromReflection() { + return this.r("method_get_from_reflection", "pointer", ["pointer"]); } @cache static get _methodGetName() { - return new NativeFunction(this.r`method_get_name`, "pointer", ["pointer"]); + return this.r("method_get_name", "pointer", ["pointer"]); } @cache static get _methodGetObject() { - return new NativeFunction(this.r`method_get_object`, "pointer", ["pointer", "pointer"]); + return this.r("method_get_object", "pointer", ["pointer", "pointer"]); } @cache static get _methodGetParamCount() { - return new NativeFunction(this.r`method_get_param_count`, "uint8", ["pointer"]); + return this.r("method_get_param_count", "uint8", ["pointer"]); } @cache static get _methodGetParameters() { - return new NativeFunction(this.r`method_get_parameters`, "pointer", ["pointer", "pointer"]); + return this.r("method_get_parameters", "pointer", ["pointer", "pointer"]); } @cache static get _methodGetPointer() { - return new NativeFunction(this.r`method_get_pointer`, "pointer", ["pointer"]); + return this.r("method_get_pointer", "pointer", ["pointer"]); } @cache static get _methodGetReturnType() { - return new NativeFunction(this.r`method_get_return_type`, "pointer", ["pointer"]); + return this.r("method_get_return_type", "pointer", ["pointer"]); } @cache static get _methodIsGeneric() { - return new NativeFunction(this.r`method_is_generic`, "bool", ["pointer"]); + return this.r("method_is_generic", "bool", ["pointer"]); } @cache static get _methodIsInflated() { - return new NativeFunction(this.r`method_is_inflated`, "bool", ["pointer"]); + return this.r("method_is_inflated", "bool", ["pointer"]); } @cache static get _methodIsInstance() { - return new NativeFunction(this.r`method_is_instance`, "bool", ["pointer"]); + return this.r("method_is_instance", "bool", ["pointer"]); } @cache static get _monitorEnter() { - return new NativeFunction(this.r`monitor_enter`, "void", ["pointer"]); + return this.r("monitor_enter", "void", ["pointer"]); } @cache static get _monitorExit() { - return new NativeFunction(this.r`monitor_exit`, "void", ["pointer"]); + return this.r("monitor_exit", "void", ["pointer"]); } @cache static get _monitorPulse() { - return new NativeFunction(this.r`monitor_pulse`, "void", ["pointer"]); + return this.r("monitor_pulse", "void", ["pointer"]); } @cache static get _monitorPulseAll() { - return new NativeFunction(this.r`monitor_pulse_all`, "void", ["pointer"]); + return this.r("monitor_pulse_all", "void", ["pointer"]); } @cache static get _monitorTryEnter() { - return new NativeFunction(this.r`monitor_try_enter`, "bool", ["pointer", "uint32"]); + return this.r("monitor_try_enter", "bool", ["pointer", "uint32"]); } @cache static get _monitorTryWait() { - return new NativeFunction(this.r`monitor_try_wait`, "bool", ["pointer", "uint32"]); + return this.r("monitor_try_wait", "bool", ["pointer", "uint32"]); } @cache static get _monitorWait() { - return new NativeFunction(this.r`monitor_wait`, "void", ["pointer"]); + return this.r("monitor_wait", "void", ["pointer"]); } @cache static get _objectGetClass() { - return new NativeFunction(this.r`object_get_class`, "pointer", ["pointer"]); + return this.r("object_get_class", "pointer", ["pointer"]); } @cache static get _objectGetHeaderSize() { - return new NativeFunction(this.r`object_header_size`, "uint", []); + return this.r("object_header_size", "uint", []); + } + + @cache + static get _objectGetVirtualMethod() { + return this.r("object_get_virtual_method", "pointer", ["pointer", "pointer"]); } @cache static get _objectNew() { - return new NativeFunction(this.r`object_new`, "pointer", ["pointer"]); + return this.r("object_new", "pointer", ["pointer"]); + } + + @cache + static get _objectGetSize() { + return this.r("object_get_size", "uint32", ["pointer"]); } @cache static get _objectUnbox() { - return new NativeFunction(this.r`object_unbox`, "pointer", ["pointer"]); + return this.r("object_unbox", "pointer", ["pointer"]); } @cache static get _parameterGetName() { - return new NativeFunction(this.r`parameter_get_name`, "pointer", ["pointer"]); + return this.r("parameter_get_name", "pointer", ["pointer"]); } @cache static get _parameterGetPosition() { - return new NativeFunction(this.r`parameter_get_position`, "int32", ["pointer"]); + return this.r("parameter_get_position", "int32", ["pointer"]); } @cache static get _parameterGetType() { - return new NativeFunction(this.r`parameter_get_type`, "pointer", ["pointer"]); + return this.r("parameter_get_type", "pointer", ["pointer"]); } @cache static get _shutdown() { - return new NativeFunction(this.r`shutdown`, "void", []); + return this.r("shutdown", "void", []); } @cache static get _stringChars() { - return new NativeFunction(this.r`string_chars`, "pointer", ["pointer"]); + return this.r("string_chars", "pointer", ["pointer"]); } @cache static get _stringLength() { - return new NativeFunction(this.r`string_length`, "int32", ["pointer"]); + return this.r("string_length", "int32", ["pointer"]); } @cache static get _stringNew() { - return new NativeFunction(this.r`string_new`, "pointer", ["pointer"]); + return this.r("string_new", "pointer", ["pointer"]); } @cache static get _stringSetLength() { - return new NativeFunction(this.r`string_set_length`, "void", ["pointer", "int32"]); + return this.r("string_set_length", "void", ["pointer", "int32"]); } @cache static get _valueBox() { - return new NativeFunction(this.r`value_box`, "pointer", ["pointer", "pointer"]); + return this.r("value_box", "pointer", ["pointer", "pointer"]); } @cache static get _threadAttach() { - return new NativeFunction(this.r`thread_attach`, "pointer", ["pointer"]); + return this.r("thread_attach", "pointer", ["pointer"]); } @cache - static get _threadDetach() { - return new NativeFunction(this.r`thread_detach`, "void", ["pointer"]); + static get _threadCurrent() { + return this.r("thread_current", "pointer", []); } @cache - static get _threadCurrent() { - return new NativeFunction(this.r`thread_current`, "pointer", []); + static get _threadGetAllAttachedThreads() { + return this.r("thread_get_all_attached_threads", "pointer", ["pointer"]); + } + + @cache + static get _threadIsVm() { + return this.r("is_vm_thread", "bool", ["pointer"]); + } + + @cache + static get _threadDetach() { + return this.r("thread_detach", "void", ["pointer"]); } @cache static get _typeGetClassOrElementClass() { - return new NativeFunction(this.r`type_get_class_or_element_class`, "pointer", ["pointer"]); + return this.r("type_get_class_or_element_class", "pointer", ["pointer"]); } @cache static get _typeGetDataType() { - return new NativeFunction(this.r`type_get_data_type`, "pointer", ["pointer"]); + return this.r("type_get_data_type", "pointer", ["pointer"]); } @cache static get _typeGetGenericClass() { - return new NativeFunction(this.r`type_get_generic_class`, "pointer", ["pointer"]); + return this.r("type_get_generic_class", "pointer", ["pointer"]); } @cache static get _typeGetName() { - return new NativeFunction(this.r`type_get_name`, "pointer", ["pointer"]); + return this.r("type_get_name", "pointer", ["pointer"]); } @cache static get _typeGetObject() { - return new NativeFunction(this.r`type_get_object`, "pointer", ["pointer"]); + return this.r("type_get_object", "pointer", ["pointer"]); } @cache static get _typeGetTypeEnum() { - return new NativeFunction(this.r`type_get_type`, "int", ["pointer"]); + return this.r("type_get_type", "int", ["pointer"]); } @cache static get _typeIsByReference() { - return new NativeFunction(this.r`type_is_byref`, "bool", ["pointer"]); - } - - @cache - static get _typeOffsetOfTypeEnum() { - return new NativeFunction(this.r`type_offset_of_type`, "uint16", []); + return this.r("type_is_byref", "bool", ["pointer"]); } /** @internal */ @cache - static get sources(): (Module | CModule)[] { - return [Il2Cpp.module, createMissingApi()]; - } - - /** @internal */ - private static r(exportName: readonly string[]): NativePointer { - const name = "il2cpp_" + exportName; - for (const source of this.sources) { - const result = source instanceof Module ? source.findExportByName(name) : source[name]; - if (result) { - return result as NativePointer; - } - } - raise(`Couldn't resolve export "${name}".`); - } -} - -function createMissingApi(): CModule { - const isEqualOrAbove_5_3_2 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.2"); - const isEqualOrAbove_5_3_3 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.3"); - const isEqualOrAbove_5_3_6 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.6"); - const isEqualOrAbove_5_4_4 = +Il2Cpp.unityVersion.isEqualOrAbove("5.4.4"); - const isEqualOrAbove_5_5_0 = +Il2Cpp.unityVersion.isEqualOrAbove("5.5.0"); - const isEqualOrAbove_5_6_0 = +Il2Cpp.unityVersion.isEqualOrAbove("5.6.0"); - const isEqualOrAbove_2017_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2017.1.0"); - const isEqualOrAbove_2017_1_3 = +Il2Cpp.unityVersion.isEqualOrAbove("2017.1.3"); - const isEqualOrAbove_2018_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.1.0"); - const isEqualOrAbove_2018_2_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.2.0"); - const isEqualOrAbove_2018_3_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.3.0"); - const isEqualOrAbove_2018_3_8 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.3.8"); - const isEqualOrAbove_2019_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2019.1.0"); - const isEqualOrAbove_2020_2_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2020.2.0"); - - const isBelow_5_3_3 = +!isEqualOrAbove_5_3_3; - const isBelow_5_3_6 = +!isEqualOrAbove_5_3_6; - const isBelow_5_5_0 = +!isEqualOrAbove_5_5_0; - const isBelow_2018_1_0 = +!isEqualOrAbove_2018_1_0; - const isBelow_2018_3_0 = +!isEqualOrAbove_2018_3_0; - const isBelow_2019_3_0 = +Il2Cpp.unityVersion.isBelow("2019.3.0"); - const isBelow_2020_2_0 = +!isEqualOrAbove_2020_2_0; - - const isNotEqual_2017_2_0 = +!Il2Cpp.unityVersion.isEqual("2017.2.0"); - const isNotEqual_5_5_0 = +!Il2Cpp.unityVersion.isEqual("5.5.0"); - - return new CModule(` + static get cModule(): Record { + const isEqualOrAbove_5_3_2 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.2"); + const isEqualOrAbove_5_3_3 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.3"); + const isEqualOrAbove_5_3_6 = +Il2Cpp.unityVersion.isEqualOrAbove("5.3.6"); + const isEqualOrAbove_5_4_4 = +Il2Cpp.unityVersion.isEqualOrAbove("5.4.4"); + const isEqualOrAbove_5_5_0 = +Il2Cpp.unityVersion.isEqualOrAbove("5.5.0"); + const isEqualOrAbove_5_6_0 = +Il2Cpp.unityVersion.isEqualOrAbove("5.6.0"); + const isEqualOrAbove_2017_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2017.1.0"); + const isEqualOrAbove_2017_1_3 = +Il2Cpp.unityVersion.isEqualOrAbove("2017.1.3"); + const isEqualOrAbove_2018_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.1.0"); + const isEqualOrAbove_2018_2_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.2.0"); + const isEqualOrAbove_2018_3_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.3.0"); + const isEqualOrAbove_2018_3_8 = +Il2Cpp.unityVersion.isEqualOrAbove("2018.3.8"); + const isEqualOrAbove_2019_1_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2019.1.0"); + const isEqualOrAbove_2020_2_0 = +Il2Cpp.unityVersion.isEqualOrAbove("2020.2.0"); + + const isBelow_5_3_3 = +!isEqualOrAbove_5_3_3; + const isBelow_5_3_6 = +!isEqualOrAbove_5_3_6; + const isBelow_5_5_0 = +!isEqualOrAbove_5_5_0; + const isBelow_2017_1_0 = +!isEqualOrAbove_2017_1_0; + const isBelow_2018_1_0 = +!isEqualOrAbove_2018_1_0; + const isBelow_2018_3_0 = +!isEqualOrAbove_2018_3_0; + const isBelow_2019_3_0 = +Il2Cpp.unityVersion.isBelow("2019.3.0"); + const isBelow_2020_2_0 = +!isEqualOrAbove_2020_2_0; + + const isNotEqual_2017_2_0 = +!Il2Cpp.unityVersion.isEqual("2017.2.0"); + const isNotEqual_5_5_0 = +!Il2Cpp.unityVersion.isEqual("5.5.0"); + + return new CModule(` #include #define FIELD_ATTRIBUTE_STATIC 0x0010 @@ -968,12 +1060,6 @@ struct _Il2CppType unsigned int pinned: 1; }; -uint16_t -il2cpp_type_offset_of_type (void) -{ - return (uint16_t) offsetof (Il2CppType, type); -} - const Il2CppType * il2cpp_type_get_data_type (const Il2CppType * type) { @@ -1010,8 +1096,8 @@ struct _Il2CppClass Il2CppType byval_arg; Il2CppType this_arg; #else - const Il2CppType* byval_arg; - const Il2CppType* this_arg; + const Il2CppType * byval_arg; + const Il2CppType * this_arg; #endif Il2CppClass * element_class; Il2CppClass * castClass; @@ -1030,7 +1116,7 @@ struct _Il2CppClass Il2CppClass * klass; #endif FieldInfo * fields; - const struct EventInfo* events; + const struct EventInfo * events; const struct PropertyInfo * properties; const MethodInfo ** methods; Il2CppClass ** nestedTypes; @@ -1134,11 +1220,13 @@ il2cpp_class_get_method_count (const Il2CppClass * klass) return klass->method_count; } -uint16_t -il2cpp_class_get_field_count (const Il2CppClass * klass) +#if ${isBelow_2018_1_0} +uint8_t +il2cpp_class_get_rank (const Il2CppClass * klass) { - return klass->field_count; + return klass->rank; } +#endif uint8_t il2cpp_class_has_class_constructor (const Il2CppClass * klass) @@ -1152,6 +1240,14 @@ il2cpp_class_is_class_constructor_finished (const Il2CppClass * klass) return klass->cctor_finished; } +#if ${isBelow_2017_1_0} +uint8_t +il2cpp_class_is_blittable (const Il2CppClass * klass) +{ + return klass->is_blittable; +} +#endif + #if ${isBelow_2019_3_0} void * il2cpp_class_get_static_field_data (const Il2CppClass * klass) @@ -1320,24 +1416,24 @@ il2cpp_method_get_pointer(const MethodInfo * method) const ParameterInfo * il2cpp_method_get_parameters (const MethodInfo * method, - void ** iter) + void ** iter) { uint16_t parameters_count = method->parameters_count; if (iter != 0 && parameters_count > 0) { - void* temp = *iter; + void * temp = *iter; if (temp == 0) { - *iter = (void**) method->parameters; + *iter = (void **) method->parameters; return method->parameters; } else { - const ParameterInfo * parameterInfo = (ParameterInfo*) *iter + 1; + const ParameterInfo * parameterInfo = (ParameterInfo *) *iter + 1; if (parameterInfo < method->parameters + parameters_count) { - *iter = (void*) parameterInfo; + *iter = (void *) parameterInfo; return parameterInfo; } } @@ -1355,7 +1451,7 @@ struct _Il2CppString void il2cpp_string_set_length (Il2CppString * string, - int32_t length) + int32_t length) { string->length = length; } @@ -1398,14 +1494,13 @@ il2cpp_array_elements (Il2CppArraySize * array) { void * il2cpp_array_elements (Il2CppArray * array) { - return (void*) array->vector; + return (void *) array->vector; } #endif struct _Il2CppMetadataType { uint32_t flags; - // Il2CppMetadataTypeFlags flags; struct Il2CppMetadataField * fields; uint32_t fieldCount; uint32_t staticsSize; @@ -1455,7 +1550,7 @@ il2cpp_metadata_snapshot_get_metadata_type_count (Il2CppMetadataSnapshot * metad Il2CppMetadataType * il2cpp_metadata_snapshot_get_metadata_types (Il2CppMetadataSnapshot * metadata_snapshot, - void ** iter) + void ** iter) { uint32_t metadata_type_count = metadata_snapshot->typeCount; @@ -1464,7 +1559,7 @@ il2cpp_metadata_snapshot_get_metadata_types (Il2CppMetadataSnapshot * metadata_s void * temp = *iter; if (temp == 0) { - *iter = (void**) metadata_snapshot->types; + *iter = (void **) metadata_snapshot->types; return metadata_snapshot->types; } else @@ -1542,7 +1637,26 @@ il2cpp_memory_snapshot_get_tracked_object_count (Il2CppManagedMemorySnapshot * s { return snapshot->gcHandles.trackedObjectCount; } -`); + `); + } + + /** @internal */ + private static r( + exportName: string, + retType: RetType, + argTypes: ArgTypes, + options?: NativeFunctionOptions + // options: NativeFunctionOptions = { exceptions: "propagate" } + ) { + exportName = "il2cpp_" + exportName; + const exportPointer = Il2Cpp.module.findExportByName(exportName) || this.cModule[exportName]; + + if (exportPointer == null) { + raise(`Couldn't resolve export "${exportName}".`); + } + + return new NativeFunction(exportPointer, retType, argTypes, options); + } } Il2Cpp.Api = Il2CppApi; diff --git a/src/il2cpp/base.ts b/src/il2cpp/base.ts index e5bec67..7424fb0 100644 --- a/src/il2cpp/base.ts +++ b/src/il2cpp/base.ts @@ -4,11 +4,18 @@ import { UnityVersion } from "./version"; import { platformNotSupported, raise, warn } from "../utils/console"; import { forModule } from "../utils/native-wait"; +import { since } from "./decorators"; /** */ class Il2CppBase { protected constructor() {} + /** */ + @since("2019.3.0") + static get allocationGranularity(): number { + return Il2Cpp.Api._allocationGranularity(); + } + /** @internal */ static get il2CppModuleName(): string { return Process.platform == "linux" ? "libil2cpp.so" : Process.platform == "windows" ? "GameAssembly.dll" : platformNotSupported(); @@ -28,18 +35,32 @@ class Il2CppBase { /** The Unity version of the current application. */ @cache static get unityVersion(): UnityVersion { - const range = Process.getRangeByAddress(Process.getModuleByName(this.unityModuleName).base); + const unityModule = Process.getModuleByName(this.unityModuleName); + const ranges = [...unityModule.enumerateRanges("r--"), Process.getRangeByAddress(unityModule.base)]; - const address = Memory.scanSync(range.base, range.size, "45787065637465642076657273696f6e3a")[0].address; - const unityVersion = new UnityVersion(address.readUtf8String()!); + for (const range of ranges) { + const scan = Memory.scanSync(range.base, range.size, "45787065637465642076657273696f6e3a")[0]; + + if (scan != undefined) { + const unityVersion = new UnityVersion(scan.address.readUtf8String()!); - if (unityVersion == undefined) { - raise("Couldn't obtain the Unity version."); - } else if (unityVersion.isBelow("5.3.0") || unityVersion.isEqualOrAbove("2021.1.0")) { - raise(`Unity version "${unityVersion}" is not valid or supported.`); + if (unityVersion.isBelow("5.3.0") || unityVersion.isEqualOrAbove("2021.2.0")) { + raise(`Unity version "${unityVersion}" is not valid or supported.`); + } + + return unityVersion; + } } - return unityVersion; + raise("Couldn't obtain the Unity version."); + } + + static alloc(size: number | UInt64 = Process.pointerSize): NativePointer { + return Il2Cpp.Api._alloc(size); + } + + static free(pointer: NativePointerValue): void { + return Il2Cpp.Api._free(pointer); } /** @internal Waits for Il2Cpp native libraries to be loaded and initialized. */ @@ -55,7 +76,7 @@ class Il2CppBase { await new Promise(resolve => { const interceptor = Interceptor.attach(Il2Cpp.Api._init, { onLeave() { - setTimeout(() => { + setImmediate(() => { interceptor.detach(); resolve(); }); @@ -68,16 +89,17 @@ class Il2CppBase { /** Attaches the caller thread to Il2Cpp domain and executes the given block. */ static perform(block: () => void): void { function executor() { - const isForeignThread = Il2Cpp.Api._threadCurrent().isNull(); + let thread = Il2Cpp.Thread.current; + const isForeignThread = thread == null; if (isForeignThread) { - Il2Cpp.Api._threadAttach(Il2Cpp.Domain.reference); + thread = Il2Cpp.Domain.attach(); } block(); if (isForeignThread) { - Il2Cpp.Api._threadDetach(Il2Cpp.Api._threadCurrent()); + thread?.detach(); } } diff --git a/src/il2cpp/dumper.ts b/src/il2cpp/dumper.ts index 8f15793..f82332a 100644 --- a/src/il2cpp/dumper.ts +++ b/src/il2cpp/dumper.ts @@ -10,14 +10,14 @@ class Il2CppDumper { /** */ @cache static get directoryPath(): string { - const UnityEngine = getUntilFound(Il2Cpp.Domain.reference.assemblies, "UnityEngine.CoreModule", "UnityEngine")!.image; + const UnityEngine = getUntilFound(Il2Cpp.Domain.assemblies, "UnityEngine.CoreModule", "UnityEngine")!.image; const Application = UnityEngine.classes["UnityEngine.Application"]; return Application.methods.get_persistentDataPath.invoke().content!; } /** */ static get fileName(): string { - const UnityEngine = getUntilFound(Il2Cpp.Domain.reference.assemblies, "UnityEngine.CoreModule", "UnityEngine")!.image; + const UnityEngine = getUntilFound(Il2Cpp.Domain.assemblies, "UnityEngine.CoreModule", "UnityEngine")!.image; const Application = UnityEngine.classes["UnityEngine.Application"]; try { @@ -33,7 +33,7 @@ class Il2CppDumper { static classicDump(fileName?: string, destinationDirectoryPath?: string): void { this.dump( function* (): Generator { - for (const assembly of Object.values(Il2Cpp.Domain.reference.assemblies)) { + for (const assembly of Il2Cpp.Domain.assemblies) { inform(`Dumping \x1b[1m${assembly.name}\x1b[0m...`); for (const klass of Object.values(assembly.image.classes)) { yield klass.toString(); diff --git a/src/il2cpp/index.ts b/src/il2cpp/index.ts index abff789..09d1591 100644 --- a/src/il2cpp/index.ts +++ b/src/il2cpp/index.ts @@ -23,6 +23,7 @@ import "./structs/parameter"; import "./structs/pointer"; import "./structs/reference"; import "./structs/string"; +import "./structs/thread"; import "./structs/type"; import "./structs/type-enum"; import "./structs/value-type"; diff --git a/src/il2cpp/structs/class.ts b/src/il2cpp/structs/class.ts index f8a8f81..15f562d 100644 --- a/src/il2cpp/structs/class.ts +++ b/src/il2cpp/structs/class.ts @@ -1,7 +1,9 @@ import { cache } from "decorator-cache-getter"; +import { since } from "../decorators"; import { NonNullNativeStruct } from "../../utils/native-struct"; -import { addLevenshtein, formatNativePointer, getOrNull, preventKeyClash } from "../../utils/utils"; +import { addLevenshtein, getOrNull, makeIterable, preventKeyClash } from "../../utils/utils"; +import { raise } from "../../utils/console"; /** Represents a `Il2CppClass`. */ class Il2CppClass extends NonNullNativeStruct { @@ -37,13 +39,13 @@ class Il2CppClass extends NonNullNativeStruct { /** Gets the amount of the fields of the current class. */ @cache - get fieldCount(): number { + get fieldCount(): UInt64 { return Il2Cpp.Api._classGetFieldCount(this); } /** Gets the fields of the current class. */ @cache - get fields(): Readonly> { + get fields(): IterableRecord { const iterator = Memory.alloc(Process.pointerSize); const record: Record = {}; @@ -55,7 +57,13 @@ class Il2CppClass extends NonNullNativeStruct { record[field.name!] = field; } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); + } + + /** Gets the flags of the current class. */ + @cache + get flags(): number { + return Il2Cpp.Api._classGetFlags(this); } /** Determines whether the current class has a class constructor. */ @@ -64,6 +72,12 @@ class Il2CppClass extends NonNullNativeStruct { return !!Il2Cpp.Api._classHasClassConstructor(this); } + /** Determines whether the GC has tracking references to the current class instances. */ + @cache + get hasReferences(): boolean { + return !!Il2Cpp.Api._classHasReferences(this); + } + /** Gets the image in which the current class is defined. */ @cache get image(): Il2Cpp.Image { @@ -76,6 +90,18 @@ class Il2CppClass extends NonNullNativeStruct { return Il2Cpp.Api._classGetInstanceSize(this); } + /** Determines whether the current class is abstract. */ + @cache + get isAbstract(): boolean { + return !!Il2Cpp.Api._classIsAbstract(this); + } + + /** Determines whether the current class is blittable. */ + @cache + get isBlittable(): boolean { + return !!Il2Cpp.Api._classIsBlittable(this); + } + /** Determines whether the current class is an enumeration. */ @cache get isEnum(): boolean { @@ -88,7 +114,7 @@ class Il2CppClass extends NonNullNativeStruct { return !!Il2Cpp.Api._classIsGeneric(this); } - /** */ + /** Determines whether the current class is inflated. */ @cache get isInflated(): boolean { return !!Il2Cpp.Api._classIsInflated(this); @@ -119,7 +145,7 @@ class Il2CppClass extends NonNullNativeStruct { /** Gets the interfaces implemented or inherited by the current class. */ @cache - get interfaces(): Readonly> { + get interfaces(): IterableRecord { const iterator = Memory.alloc(Process.pointerSize); const record: Record = {}; @@ -131,7 +157,7 @@ class Il2CppClass extends NonNullNativeStruct { record[klass.type.name] = klass; } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); } /** Gets the amount of the implemented methods by the current class. */ @@ -142,7 +168,7 @@ class Il2CppClass extends NonNullNativeStruct { /** Gets the methods implemented by the current class. */ @cache - get methods(): Readonly> { + get methods(): IterableRecord { const iterator = Memory.alloc(Process.pointerSize); const record: Record = preventKeyClash({}); @@ -154,7 +180,7 @@ class Il2CppClass extends NonNullNativeStruct { record[method.name] = method; } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); } /** Gets the name of the current class. */ @@ -175,18 +201,46 @@ class Il2CppClass extends NonNullNativeStruct { return getOrNull(Il2Cpp.Api._classGetParent(this), Il2Cpp.Class); } + /** Gets the rank (number of dimensions) of the current array class. */ + @cache + get rank(): number { + return Il2Cpp.Api._classGetRank(this); + } + /** Gets a pointer to the static fields of the current class. */ @cache get staticFieldsData(): NativePointer { return Il2Cpp.Api._classGetStaticFieldData(this); } + /** */ + @cache + get valueSize(): number { + return Il2Cpp.Api._classGetValueSize(this, NULL); + } + /** Gets the type of the current class. */ @cache get type(): Il2Cpp.Type { return new Il2Cpp.Type(Il2Cpp.Api._classGetType(this)); } + /** */ + inflate(...classes: Il2Cpp.Class[]): Il2Cpp.Class { + if (!this.isGeneric) { + raise(`Cannot inflate ${this.type.name} because it's not generic.`); + } + + const typeArray = Il2Cpp.Array.from( + Il2Cpp.Image.corlib.classes["System.RuntimeType"], + classes.map(klass => klass.type.object) + ); + const inflatedType = this.type.object.methods.MakeGenericType.invoke(typeArray); + + // TODO: typeArray leaks + return new Il2Cpp.Class(Il2Cpp.Api._classFromSystemType(inflatedType)); + } + /** Calls the static constructor of the current class. */ initialize(): void { Il2Cpp.Api._classInit(this); @@ -203,48 +257,42 @@ class Il2CppClass extends NonNullNativeStruct { } override toString(): string { - const spacer = "\n "; - let text = "// " + this.image.name + "\n"; - text += this.isEnum ? "enum" : this.isValueType ? "struct" : this.isInterface ? "interface" : "class"; - text += " " + this.type.name; - if (this.parent != null || this.interfaceCount > 0) text += " : "; - if (this.parent != null) { - text += this.parent.type.name; - if (this.interfaceCount > 0) text += ", "; - } - if (this.interfaceCount > 0) text += Object.keys(this.interfaces).join(", "); - text += "\n{"; - for (const field of Object.values(this.fields)) { - text += spacer; - if (field.isStatic) text += "static "; - text += field.type.name + " " + field.name; - if (field.isLiteral) { - if (field.type.class.isEnum) { - text += " = " + field.valueHandle.readS32(); - } else { - text += " = " + field.value; - } - } - text += ";"; - if (!field.isLiteral) { - text += " // 0x" + field.offset.toString(16); - } - } - if (this.fieldCount && this.methodCount > 0) text += "\n"; - - for (const method of Object.values(this.methods)) { - text += spacer; - if (method.isStatic) text += "static "; - text += method.returnType.name + " " + method.name + "("; - for (const parameter of Object.values(method.parameters)) { - if (parameter.position > 0) text += ", "; - text += parameter.type.name + " " + parameter.name; - } - text += ");"; - if (!method.virtualAddress.isNull()) text += " // " + formatNativePointer(method.relativeVirtualAddress); - } - text += "\n}\n\n"; - return text; + let fieldsString = ""; + let methodsString = ""; + + for (const field of this.fields) fieldsString += "\n " + field; + for (const method of this.methods) methodsString += "\n " + method; + + return ( + "// " + + this.image.name + + "\n" + + (this.isEnum ? "enum" : this.isValueType ? "struct" : this.isInterface ? "interface" : "class") + + " " + + this.type.name + + (this.parent != null || this.interfaceCount > 0 ? " : " : "") + + (this.parent != null ? this.parent.type.name + (this.interfaceCount > 0 ? ", " : "") : "") + + (this.interfaceCount > 0 ? Object.keys(this.interfaces).join(", ") : "") + + "\n{" + + fieldsString + + (this.fieldCount.toNumber() > 0 && this.methodCount > 0 ? "\n" : "") + + methodsString + + "\n}\n\n" + ); + } + + /** */ + @since("2019.3.0") + static enumerate(block: (klass: Il2Cpp.Class) => void): void { + const callback = new NativeCallback( + function (klass: NativePointer, _: NativePointer): void { + block(new Il2Cpp.Class(klass)); + }, + "void", + ["pointer", "pointer"] + ); + + return Il2Cpp.Api._classForEach(callback, NULL); } } diff --git a/src/il2cpp/structs/domain.ts b/src/il2cpp/structs/domain.ts index b278e4a..2cbb7fb 100644 --- a/src/il2cpp/structs/domain.ts +++ b/src/il2cpp/structs/domain.ts @@ -1,20 +1,14 @@ import { cache } from "decorator-cache-getter"; -import { NativeStruct } from "../../utils/native-struct"; -import { addLevenshtein, getOrNull } from "../../utils/utils"; -import { warn } from "../../utils/console"; +import { addLevenshtein, getOrNull, makeIterable } from "../../utils/utils"; /** Represents a `Il2CppDomain`. */ -class Il2CppDomain extends NativeStruct { - /** Gets the current application domain. */ - @cache - static get reference(): Il2Cpp.Domain { - return new Il2Cpp.Domain(Il2Cpp.Api._domainGet()); - } +class Il2CppDomain { + protected constructor() {} - /** Gets the assemblies that have been loaded into the execution context of this domain. */ + /** Gets the assemblies that have been loaded into the execution context of the application domain. */ @cache - get assemblies(): Readonly> { + static get assemblies(): IterableRecord { const record: Record = {}; const sizePointer = Memory.alloc(Process.pointerSize); @@ -28,14 +22,14 @@ class Il2CppDomain extends NativeStruct { } if (count == 0) { - warn("The domain contains 0 assemblies, let's follow plan B."); const AppDomain = Il2Cpp.Image.corlib.classes["System.AppDomain"].methods.get_CurrentDomain.invoke(); for (const assemblyObject of AppDomain.methods.GetAssemblies_.invoke>()) { const assemblyName = assemblyObject.base.base.methods.GetSimpleName.invoke().content; if (assemblyName != null) { - const assembly = Il2Cpp.Domain.reference.open(assemblyName); + const assembly = Il2Cpp.Domain.open(assemblyName); + if (assembly != null) { record[assembly.name] = assembly; } @@ -43,17 +37,29 @@ class Il2CppDomain extends NativeStruct { } } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); + } + + /** Gets the application domain handle. */ + @cache + static get handle(): NativePointer { + return Il2Cpp.Api._domainGet(); } - /** Gets the name of the current application domain. */ + /** Gets the name of the application domain. */ @cache - get name(): string { + // @ts-ignore + static get name(): string { return Il2Cpp.Api._domainGetName(this).readUtf8String()!; } + /** Attached a new thread to the application domain. */ + static attach(): Il2Cpp.Thread { + return new Il2Cpp.Thread(Il2Cpp.Api._threadAttach(this)); + } + /** Opens and loads the assembly with the given name. */ - open(assemblyName: string): Il2Cpp.Assembly | null { + static open(assemblyName: string): Il2Cpp.Assembly | null { return getOrNull(Il2Cpp.Api._domainAssemblyOpen(this, Memory.allocUtf8String(assemblyName)), Il2Cpp.Assembly); } } diff --git a/src/il2cpp/structs/field.ts b/src/il2cpp/structs/field.ts index 5811e89..15ce335 100644 --- a/src/il2cpp/structs/field.ts +++ b/src/il2cpp/structs/field.ts @@ -15,6 +15,12 @@ class Il2CppField extends NonNullNativeStruct { return new Il2Cpp.Class(Il2Cpp.Api._fieldGetClass(this)); } + /** Gets the flags of the current field. */ + @cache + get flags(): number { + return Il2Cpp.Api._fieldGetFlags(this); + } + /** Determines whether this field value is written at compile time. */ @cache get isLiteral(): boolean { @@ -87,6 +93,18 @@ class Il2CppField extends NonNullNativeStruct { return overridePropertyValue(new Il2Cpp.Field(this.handle), "valueHandle", valueHandle); } + + override toString(): string { + return ( + (this.isStatic ? "static " : "") + + this.type.name + + " " + + this.name + + (this.isLiteral + ? " = " + (this.type.class.isEnum ? this.valueHandle.readS32() : this.value) + ";" + : "; // 0x" + this.offset.toString(16)) + ); + } } Il2Cpp.Field = Il2CppField; diff --git a/src/il2cpp/structs/gc.ts b/src/il2cpp/structs/gc.ts index b535f8f..9badebf 100644 --- a/src/il2cpp/structs/gc.ts +++ b/src/il2cpp/structs/gc.ts @@ -15,6 +15,18 @@ class Il2CppGC { return !Il2Cpp.Api._gcIsDisabled(); } + /** Determines whether the garbage collector is incremental. */ + @since("2019.1.0") + static get isIncremental(): boolean { + return !!Il2Cpp.Api._gcIsIncremental(); + } + + /** */ + @since("2019.1.0", "2019.1.0") + static get maxTimeSlice(): Int64 { + return Il2Cpp.Api._gcGetMaxTimeSlice(); + } + /** Gets the used heap size in bytes. */ static get usedHeapSize(): Int64 { return Il2Cpp.Api._gcGetUsedSize(); @@ -25,6 +37,11 @@ class Il2CppGC { value ? Il2Cpp.Api._gcEnable() : Il2Cpp.Api._gcDisable(); } + /** */ + static set maxTimeSlice(nanoseconds: number | Int64) { + Il2Cpp.Api._gcSetMaxTimeSlice(nanoseconds); + } + /** Returns the heap allocated objects of the specified class. This variant reads GC descriptors. */ static choose(klass: Il2Cpp.Class): Il2Cpp.Object[] { const matches: Il2Cpp.Object[] = []; @@ -55,6 +72,24 @@ class Il2CppGC { static collectALittle(): void { Il2Cpp.Api._gcCollectALittle(); } + + /** */ + @since("2019.3.0") + static startWorld(): void { + return Il2Cpp.Api._gcStartWorld(); + } + + /** */ + @since("2020.2.0") + static startIncrementalCollection(): void { + return Il2Cpp.Api._gcStartIncrementalCollection(); + } + + /** */ + @since("2019.3.0") + static stopWorld(): void { + return Il2Cpp.Api._gcStopWorld(); + } } Reflect.set(Il2Cpp, "GC", Il2CppGC); diff --git a/src/il2cpp/structs/generic-instance.ts b/src/il2cpp/structs/generic-instance.ts index 372f47e..48b7ac7 100644 --- a/src/il2cpp/structs/generic-instance.ts +++ b/src/il2cpp/structs/generic-instance.ts @@ -1,7 +1,7 @@ import { cache } from "decorator-cache-getter"; import { NonNullNativeStruct } from "../../utils/native-struct"; -import { addLevenshtein } from "../../utils/utils"; +import { addLevenshtein, makeIterable } from "../../utils/utils"; /** Represents a `Il2CppGenericInst`. */ class Il2CppGenericInstance extends NonNullNativeStruct { @@ -13,7 +13,7 @@ class Il2CppGenericInstance extends NonNullNativeStruct { /** */ @cache - get types(): Readonly> { + get types(): IterableRecord { const record: Record = {}; const startPointer = Il2Cpp.Api._genericInstanceGetTypes(this); @@ -23,7 +23,7 @@ class Il2CppGenericInstance extends NonNullNativeStruct { record[type.name] = type; } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); } } diff --git a/src/il2cpp/structs/image.ts b/src/il2cpp/structs/image.ts index c19bcbe..bbbc485 100644 --- a/src/il2cpp/structs/image.ts +++ b/src/il2cpp/structs/image.ts @@ -3,7 +3,7 @@ import { cache } from "decorator-cache-getter"; import { since } from "../decorators"; import { NonNullNativeStruct } from "../../utils/native-struct"; -import { addLevenshtein, getOrNull } from "../../utils/utils"; +import { addLevenshtein, getOrNull, makeIterable } from "../../utils/utils"; /** Represents a `Il2CppImage`. */ class Il2CppImage extends NonNullNativeStruct { @@ -27,7 +27,7 @@ class Il2CppImage extends NonNullNativeStruct { /** Gets the classes defined in this image. */ @cache - get classes(): Readonly> { + get classes(): IterableRecord { const record: Record = {}; if (Il2Cpp.unityVersion.isBefore2018_3_0) { @@ -35,11 +35,10 @@ class Il2CppImage extends NonNullNativeStruct { const end = start + this.classCount; const globalIndex = Memory.alloc(Process.pointerSize); - globalIndex.add(Il2Cpp.Type.offsetOfTypeEnum).writeInt(0x20); for (let i = start; i < end; i++) { - const klass = new Il2Cpp.Class(Il2Cpp.Api._typeGetClassOrElementClass(globalIndex.writeInt(i))); - record[klass.type!.name!] = klass; + const klass = new Il2Cpp.Class(Il2Cpp.Api._typeGetClassOrElementClass(globalIndex.writeS32(i))); + record[klass.type.name] = klass; } } else { const end = this.classCount; @@ -50,7 +49,7 @@ class Il2CppImage extends NonNullNativeStruct { } } - return addLevenshtein(record); + return makeIterable(addLevenshtein(record)); } /** Gets the index of the first class defined in this image. */ @@ -59,6 +58,12 @@ class Il2CppImage extends NonNullNativeStruct { return Il2Cpp.Api._imageGetClassStart(this); } + /** */ + @cache + get entryPoint(): Il2Cpp.Method | null { + return getOrNull(Il2Cpp.Api._imageGetEntryPoint(this), Il2Cpp.Method); + } + /** Gets the name of this image. */ @cache get name(): string { diff --git a/src/il2cpp/structs/metadata-snapshot.ts b/src/il2cpp/structs/metadata-snapshot.ts index e6ea5b5..f1a6eca 100644 --- a/src/il2cpp/structs/metadata-snapshot.ts +++ b/src/il2cpp/structs/metadata-snapshot.ts @@ -1,6 +1,7 @@ import { cache } from "decorator-cache-getter"; import { NonNullNativeStruct } from "../../utils/native-struct"; +import { addLevenshtein, makeIterable } from "../../utils/utils"; /** Represents a `Il2CppMetadataSnapshot`. */ class Il2CppMetadataSnapshot extends NonNullNativeStruct { @@ -12,7 +13,7 @@ class Il2CppMetadataSnapshot extends NonNullNativeStruct { /** */ @cache - get metadataTypes(): Readonly> { + get metadataTypes(): IterableRecord { const iterator = Memory.alloc(Process.pointerSize); const record: Record = {}; @@ -23,7 +24,7 @@ class Il2CppMetadataSnapshot extends NonNullNativeStruct { record[metadataType.name] = metadataType; } - return record; + return makeIterable(addLevenshtein(record)); } } diff --git a/src/il2cpp/structs/method.ts b/src/il2cpp/structs/method.ts index 23d4cf4..aab5232 100644 --- a/src/il2cpp/structs/method.ts +++ b/src/il2cpp/structs/method.ts @@ -3,18 +3,27 @@ import { cache } from "decorator-cache-getter"; import { shouldBeInstance } from "../decorators"; import { fromFridaValue, toFridaValue } from "../utils"; -import { addLevenshtein, overridePropertyValue } from "../../utils/utils"; +import { addLevenshtein, formatNativePointer, makeIterable, overridePropertyValue } from "../../utils/utils"; import { raise, warn } from "../../utils/console"; import { NonNullNativeStruct } from "../../utils/native-struct"; /** Represents a `MethodInfo`. */ class Il2CppMethod extends NonNullNativeStruct { - /** Gets the class in which this field is defined. */ + /** Gets the class in which this method is defined. */ @cache get class(): Il2Cpp.Class { return new Il2Cpp.Class(Il2Cpp.Api._methodGetClass(this)); } + /** Gets the flags (and implementation flags) of the current method. */ + @cache + get flags(): [number, number] { + const implementationFlagsPointer = Memory.alloc(Process.pointerSize); + const flags = Il2Cpp.Api._methodGetFlags(this, implementationFlagsPointer); + + return [flags, implementationFlagsPointer.readU32()]; + } + /** */ @cache get fridaSignature(): NativeCallbackArgumentType[] { @@ -72,7 +81,7 @@ class Il2CppMethod extends NonNullNativeStruct { /** Gets the parameters of this method. */ @cache - get parameters(): Readonly> { + get parameters(): IterableRecord { const iterator = Memory.alloc(Process.pointerSize); const accessor: Record = {}; @@ -84,7 +93,7 @@ class Il2CppMethod extends NonNullNativeStruct { accessor[parameter.name!] = parameter; } - return addLevenshtein(accessor); + return makeIterable(addLevenshtein(accessor)); } /** Gets the relative virtual address (RVA) of this method. */ @@ -138,6 +147,23 @@ class Il2CppMethod extends NonNullNativeStruct { } } + /** */ + inflate(...classes: Il2Cpp.Class[]): Il2Cpp.Method { + if (!this.isGeneric) { + raise(`Cannot inflate ${this.name} because it's not generic.`); + } + + const typeArray = Il2Cpp.Array.from( + Il2Cpp.Image.corlib.classes["System.RuntimeType"], + classes.map(klass => klass.type.object) + ); + + const inflatedMethodObject = this.object.methods.MakeGenericMethod.invoke(typeArray); + + // TODO: will typeArray leak? + return new Il2Cpp.Method(Il2Cpp.Api._methodGetFromReflection(inflatedMethodObject)); + } + /** Invokes this method. */ @shouldBeInstance(false) invoke(...parameters: Il2Cpp.Parameter.Type[]): T { @@ -147,7 +173,7 @@ class Il2CppMethod extends NonNullNativeStruct { /** @internal */ invokeRaw(instance: NativePointer, ...parameters: Il2Cpp.Parameter.Type[]): Il2Cpp.Method.ReturnType { if (this.parameterCount != parameters.length) { - raise(`This method takes ${this.parameterCount} parameters, but ${parameters.length} were supplied.`); + raise(`${this.name} requires ${this.parameterCount} parameters, but ${parameters.length} were supplied.`); } const allocatedParameters = Object.values(this.parameters).map((parameter: Il2Cpp.Parameter, index: number) => @@ -180,6 +206,19 @@ class Il2CppMethod extends NonNullNativeStruct { } ); } + + override toString(): string { + return ( + (this.isStatic ? "static " : "") + + this.returnType.name + + " " + + this.name + + "(" + + Object.values(this.parameters).join(", ") + + ");" + + (this.virtualAddress.isNull() ? "" : " // " + formatNativePointer(this.relativeVirtualAddress)) + ); + } } Il2Cpp.Method = Il2CppMethod; diff --git a/src/il2cpp/structs/object.ts b/src/il2cpp/structs/object.ts index 893272f..ebdb6bc 100644 --- a/src/il2cpp/structs/object.ts +++ b/src/il2cpp/structs/object.ts @@ -3,7 +3,7 @@ import { cache } from "decorator-cache-getter"; import { checkNull } from "../decorators"; import { NativeStruct } from "../../utils/native-struct"; -import { addLevenshtein, filterMap, overridePropertyValue } from "../../utils/utils"; +import { addLevenshtein, filterMap, getOrNull, makeIterable, overridePropertyValue } from "../../utils/utils"; /** Represents a `Il2CppObject`. */ class Il2CppObject extends NativeStruct { @@ -32,28 +32,38 @@ class Il2CppObject extends NativeStruct { /** Gets the fields of this object. */ @cache - get fields(): Readonly> { - return addLevenshtein( - filterMap( - this.class.fields, - (field: Il2Cpp.Field) => !field.isStatic, - (field: Il2Cpp.Field) => field.withHolder(this) + get fields(): IterableRecord { + return makeIterable( + addLevenshtein( + filterMap( + this.class.fields, + (field: Il2Cpp.Field) => !field.isStatic, + (field: Il2Cpp.Field) => field.withHolder(this) + ) ) ); } /** Gets the methods of this object. */ @cache - get methods(): Readonly> { - return addLevenshtein( - filterMap( - this.class.methods, - (method: Il2Cpp.Method) => !method.isStatic, - (method: Il2Cpp.Method) => method.withHolder(this) + get methods(): IterableRecord { + return makeIterable( + addLevenshtein( + filterMap( + this.class.methods, + (method: Il2Cpp.Method) => !method.isStatic, + (method: Il2Cpp.Method) => method.withHolder(this) + ) ) ); } + /** */ + @cache + get size(): number { + return Il2Cpp.Api._objectGetSize(this); + } + /** Acquires an exclusive lock on the current object. */ enter(): void { return Il2Cpp.Api._monitorEnter(this); @@ -64,6 +74,11 @@ class Il2CppObject extends NativeStruct { return Il2Cpp.Api._monitorExit(this); } + /** */ + getVirtualMethod(method: Il2Cpp.Method): Il2Cpp.Method | null { + return getOrNull(Il2Cpp.Api._objectGetVirtualMethod(this, method), Il2Cpp.Method); + } + /** Notifies a thread in the waiting queue of a change in the locked object's state. */ pulse(): void { return Il2Cpp.Api._monitorPulse(this); @@ -110,16 +125,8 @@ class Il2CppObject extends NativeStruct { while (!("ToString" in object.methods)) { object = object.base; } - return object.methods.ToString.invoke().content; - // if ("ToString" in this.methods) { - // return this.methods.ToString.invoke().content; - // } else { - // const UnityEngineJSONSerializeModule = Il2Cpp.Domain.reference.assemblies["UnityEngine.JSONSerializeModule"]; - // return UnityEngineJSONSerializeModule.image.classes["UnityEngine.JsonUtility"].methods.ToJson_.invoke(this, true) - // .content; - // return `{ ${mapToArray(this.fields, (field: Il2Cpp.Field) => `${field.name} = ${field.value}`).join(", ")} }`; - // } + return object.methods.ToString.invoke().content; } } diff --git a/src/il2cpp/structs/parameter.ts b/src/il2cpp/structs/parameter.ts index efbb1e1..4a3ee04 100644 --- a/src/il2cpp/structs/parameter.ts +++ b/src/il2cpp/structs/parameter.ts @@ -21,6 +21,10 @@ class Il2CppParameter extends NonNullNativeStruct { get type(): Il2Cpp.Type { return new Il2Cpp.Type(Il2Cpp.Api._parameterGetType(this)); } + + override toString(): string { + return this.type.name + " " + this.name; + } } Il2Cpp.Parameter = Il2CppParameter; diff --git a/src/il2cpp/structs/thread.ts b/src/il2cpp/structs/thread.ts new file mode 100644 index 0000000..447a261 --- /dev/null +++ b/src/il2cpp/structs/thread.ts @@ -0,0 +1,43 @@ +import { NativeStruct } from "../../utils/native-struct"; +import { getOrNull } from "../../utils/utils"; + +class Il2CppThread extends NativeStruct { + /** Gets the attached threads. */ + static get all(): Il2CppThread[] { + const array: Il2Cpp.Thread[] = []; + + const sizePointer = Memory.alloc(Process.pointerSize); + const startPointer = Il2Cpp.Api._threadGetAllAttachedThreads(sizePointer); + + const size = sizePointer.readInt(); + + for (let i = 0; i < size; i++) { + array.push(new Il2Cpp.Thread(startPointer.add(i * Process.pointerSize).readPointer())); + } + + return array; + } + + /** Gets the current attached thread, if any. */ + static get current(): Il2CppThread | null { + return getOrNull(Il2Cpp.Api._threadCurrent(), Il2CppThread); + } + + /** */ + get isVmThread(): boolean { + return !!Il2Cpp.Api._threadIsVm(this); + } + + /** Detaches the thread from the application domain. */ + detach(): void { + return Il2Cpp.Api._threadDetach(this); + } +} + +Il2Cpp.Thread = Il2CppThread; + +declare global { + namespace Il2Cpp { + class Thread extends Il2CppThread {} + } +} \ No newline at end of file diff --git a/src/il2cpp/structs/type.ts b/src/il2cpp/structs/type.ts index b8fa2d9..07107b3 100644 --- a/src/il2cpp/structs/type.ts +++ b/src/il2cpp/structs/type.ts @@ -6,12 +6,6 @@ import { warn } from "../../utils/console"; /** Represents a `Il2CppType`. */ class Il2CppType extends NonNullNativeStruct { - /** @internal */ - @cache - static get offsetOfTypeEnum() { - return Il2Cpp.Api._typeOffsetOfTypeEnum(); - } - /** Gets the class of this type. */ @cache get class(): Il2Cpp.Class { diff --git a/src/il2cpp/structs/value-type.ts b/src/il2cpp/structs/value-type.ts index 805afa1..1bc1f3e 100644 --- a/src/il2cpp/structs/value-type.ts +++ b/src/il2cpp/structs/value-type.ts @@ -3,7 +3,7 @@ import { cache } from "decorator-cache-getter"; import { checkNull } from "../decorators"; import { NativeStruct } from "../../utils/native-struct"; -import { addLevenshtein, filterMap } from "../../utils/utils"; +import { addLevenshtein, filterMap, makeIterable } from "../../utils/utils"; /** */ class Il2CppValueType extends NativeStruct { @@ -14,12 +14,14 @@ class Il2CppValueType extends NativeStruct { /** */ @cache - get fields(): Readonly> { - return addLevenshtein( - filterMap( - this.type.class.fields, - (field: Il2Cpp.Field) => !field.isStatic, - (field: Il2Cpp.Field) => field.withHolder(this) + get fields(): IterableRecord { + return makeIterable( + addLevenshtein( + filterMap( + this.type.class.fields, + (field: Il2Cpp.Field) => !field.isStatic, + (field: Il2Cpp.Field) => field.withHolder(this) + ) ) ); } diff --git a/src/il2cpp/tracer.ts b/src/il2cpp/tracer.ts index 62a7c35..e6fb05c 100644 --- a/src/il2cpp/tracer.ts +++ b/src/il2cpp/tracer.ts @@ -1,6 +1,7 @@ +import kleur from "kleur"; + import { inform } from "../utils/console"; import { formatNativePointer } from "../utils/utils"; -import kleur from "kleur"; /** Tracing utilities. */ class Il2CppTracer { diff --git a/src/il2cpp/utils.ts b/src/il2cpp/utils.ts index 02b6b43..428e4a4 100644 --- a/src/il2cpp/utils.ts +++ b/src/il2cpp/utils.ts @@ -171,7 +171,8 @@ function arrayToValueType(type: Il2Cpp.Type, nativeValues: any[]): Il2Cpp.ValueT return arr; } - const valueType = Memory.alloc(type.class.instanceSize - Il2Cpp.Object.headerSize); + // TODO: check if it's equal to (type.class.instanceSize - Il2Cpp.Object.headerSize) + const valueType = Memory.alloc(type.class.valueSize); nativeValues = nativeValues.flat(Infinity); const typesAndOffsets = iter(type); diff --git a/src/index.ts b/src/index.ts index dec9c36..217f704 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,9 @@ declare global { /** https://docs.microsoft.com/en-us/javascript/api/@azure/keyvault-certificates/requireatleastone */ type RequireAtLeastOne = { [K in keyof T]-?: Required> & Partial>> }[keyof T]; + /** */ + type IterableRecord = Readonly> & Iterable; + /** @internal */ namespace console { function log(message?: any, ...optionalParams: any[]): void; diff --git a/src/utils/native-struct.ts b/src/utils/native-struct.ts index 00c105d..a760654 100644 --- a/src/utils/native-struct.ts +++ b/src/utils/native-struct.ts @@ -21,6 +21,10 @@ export class NativeStruct implements ObjectWrapper { isNull(): boolean { return this.handle.isNull(); } + + toJSON(): string | null { + return this.toString(); + } } /** Scaffold class whom pointer cannot be null. */ diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 7c2ea84..b832dca 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -37,7 +37,7 @@ export function addLevenshtein(object: Record): } /** @internal */ -export function getUntilFound(record: Readonly>, ...keys: T[]): V | undefined { +export function getUntilFound(record: Record, ...keys: string[]): V | undefined { for (const key of keys) { if (key in record) { return record[key]; @@ -45,6 +45,17 @@ export function getUntilFound(record: Readonly(source: Record): Record & Iterable { + Reflect.set(source, Symbol.iterator, function* () { + for (const value of Object.values(source)) { + yield value; + } + }); + + return source as any; +} + /** @internal */ export function map(source: Record, map: (value: V) => U): Record { const record: Record = {}; diff --git a/tsconfig.json b/tsconfig.json index 26d823f..db368b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,18 @@ { "compilerOptions": { - "target": "esnext", + "declaration": true, + "esModuleInterop": true, + "experimentalDecorators": true, "lib": [ "es2020" ], "module": "commonjs", "moduleResolution": "node", - "experimentalDecorators": true, - "esModuleInterop": true, + "outDir": "dist", + "removeComments": true, "strict": true, - "alwaysStrict": true, - "declaration": true, - "declarationMap": false, "stripInternal": true, - "removeComments": true, - "outDir": "dist" + "target": "esnext", }, "files": [ "src/index.ts"