frida-il2cpp-bridge
Version:
A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.
1,244 lines • 158 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var Il2Cpp;
(function (Il2Cpp) {
/** */
Il2Cpp.application = {
/**
* Gets the data path name of the current application, e.g.
* `/data/emulated/0/Android/data/com.example.application/files`
* on Android.
*
* **This information is not guaranteed to exist.**
*
* ```ts
* Il2Cpp.perform(() => {
* // prints /data/emulated/0/Android/data/com.example.application/files
* console.log(Il2Cpp.application.dataPath);
* });
* ```
*/
get dataPath() {
return unityEngineCall("get_persistentDataPath");
},
/**
* Gets the identifier name of the current application, e.g.
* `com.example.application` on Android.
*
* In case the identifier cannot be retrieved, the main module name is
* returned instead, which typically is the process name.
*
* ```ts
* Il2Cpp.perform(() => {
* // prints com.example.application
* console.log(Il2Cpp.application.identifier);
* });
* ```
*/
get identifier() {
return unityEngineCall("get_identifier") ?? unityEngineCall("get_bundleIdentifier") ?? Process.mainModule.name;
},
/**
* Gets the version name of the current application, e.g. `4.12.8`.
*
* In case the version cannot be retrieved, an hash of the IL2CPP
* module is returned instead.
*
* ```ts
* Il2Cpp.perform(() => {
* // prints 4.12.8
* console.log(Il2Cpp.application.version);
* });
* ```
*/
get version() {
return unityEngineCall("get_version") ?? exportsHash(Il2Cpp.module).toString(16);
}
};
// prettier-ignore
getter(Il2Cpp, "unityVersion", () => {
try {
const unityVersion = Il2Cpp.$config.unityVersion ?? unityEngineCall("get_unityVersion");
if (unityVersion != null) {
return unityVersion;
}
}
catch (_) {
}
const searchPattern = "69 6c 32 63 70 70";
for (const range of Il2Cpp.module.enumerateRanges("r--").concat(Process.getRangeByAddress(Il2Cpp.module.base))) {
for (let { address } of Memory.scanSync(range.base, range.size, searchPattern)) {
while (address.readU8() != 0) {
address = address.sub(1);
}
const match = UnityVersion.find(address.add(1).readCString());
if (match != undefined) {
return match;
}
}
}
raise("couldn't determine the Unity version, please specify it manually");
}, lazy);
// prettier-ignore
getter(Il2Cpp, "unityVersionIsBelow201830", () => {
return UnityVersion.lt(Il2Cpp.unityVersion, "2018.3.0");
}, lazy);
// prettier-ignore
getter(Il2Cpp, "unityVersionIsBelow202120", () => {
return UnityVersion.lt(Il2Cpp.unityVersion, "2021.2.0");
}, lazy);
function unityEngineCall(method) {
const handle = Il2Cpp.exports.resolveInternalCall(Memory.allocUtf8String("UnityEngine.Application::" + method));
const nativeFunction = new NativeFunction(handle, "pointer", []);
return nativeFunction.isNull() ? null : new Il2Cpp.String(nativeFunction()).asNullable()?.content ?? null;
}
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/** Create a boxed primitive */
function boxed(value, type) {
const mapping = {
int8: "System.SByte",
uint8: "System.Byte",
int16: "System.Int16",
uint16: "System.UInt16",
int32: "System.Int32",
uint32: "System.UInt32",
int64: "System.Int64",
uint64: "System.UInt64",
char: "System.Char",
intptr: "System.IntPtr",
uintptr: "System.UIntPtr"
};
const className = typeof value == "boolean"
? "System.Boolean"
: typeof value == "number"
? mapping[type ?? "int32"]
: value instanceof Int64
? "System.Int64"
: value instanceof UInt64
? "System.UInt64"
: value instanceof NativePointer
? mapping[type ?? "intptr"]
: raise(`Cannot create boxed primitive using value of type '${typeof value}'`);
const object = Il2Cpp.corlib.class(className ?? raise(`Unknown primitive type name '${type}'`)).alloc();
(object.tryField("m_value") ?? object.tryField("_pointer") ?? raise(`Could not find primitive field in class '${className}'`)).value = value;
return object;
}
Il2Cpp.boxed = boxed;
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* Set of configurations users can override. It is for advanced use cases,
* when certain values cannot be detected automatically. \
* For reference, see:
* - {@link Il2Cpp.module};
* - {@link Il2Cpp.unityVersion};
* - {@link Il2Cpp.exports};
*/
Il2Cpp.$config = {
moduleName: undefined,
unityVersion: undefined,
exports: undefined
};
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* @deprecated
* Dumps the application, i.e. it creates a dummy `.cs` file that contains
* all the class, field and method declarations.
*
* The dump is very useful when it comes to inspecting the application as
* you can easily search for succulent members using a simple text search,
* hence this is typically the very first thing it should be done when
* working with a new application. \
* Keep in mind the dump is version, platform and arch dependentend, so
* it has to be re-genereated if any of these changes.
*
* The file is generated in the **target** device, so you might need to
* pull it to the host device afterwards.
*
* Dumping *may* require a file name and a directory path (a place where the
* application can write to). If not provided, the target path is generated
* automatically using the information from {@link Il2Cpp.application}.
*
* ```ts
* Il2Cpp.perform(() => {
* Il2Cpp.dump();
* });
* ```
*
* For instance, the dump resembles the following:
* ```
* class Mono.DataConverter.PackContext : System.Object
* {
* System.Byte[] buffer; // 0x10
* System.Int32 next; // 0x18
* System.String description; // 0x20
* System.Int32 i; // 0x28
* Mono.DataConverter conv; // 0x30
* System.Int32 repeat; // 0x38
* System.Int32 align; // 0x3c
*
* System.Void Add(System.Byte[] group); // 0x012ef4f0
* System.Byte[] Get(); // 0x012ef6ec
* System.Void .ctor(); // 0x012ef78c
* }
* ```
*/
function dump(fileName, path) {
fileName = fileName ?? `${Il2Cpp.application.identifier}_${Il2Cpp.application.version}.cs`;
path = path ?? Il2Cpp.application.dataPath ?? Process.getCurrentDir();
createDirectoryRecursively(path);
const destination = `${path}/${fileName}`;
const file = new File(destination, "w");
for (const assembly of Il2Cpp.domain.assemblies) {
inform(`dumping ${assembly.name}...`);
for (const klass of assembly.image.classes) {
file.write(`${klass}\n\n`);
}
}
file.flush();
file.close();
ok(`dump saved to ${destination}`);
showDeprecationNotice();
}
Il2Cpp.dump = dump;
/**
* @deprecated
* Just like {@link Il2Cpp.dump}, but a `.cs` file per assembly is
* generated instead of having a single big `.cs` file. For instance, all
* classes within `System.Core` and `System.Runtime.CompilerServices.Unsafe`
* are dumped into `System/Core.cs` and
* `System/Runtime/CompilerServices/Unsafe.cs`, respectively.
*
* ```ts
* Il2Cpp.perform(() => {
* Il2Cpp.dumpTree();
* });
* ```
*/
function dumpTree(path, ignoreAlreadyExistingDirectory = false) {
path = path ?? `${Il2Cpp.application.dataPath ?? Process.getCurrentDir()}/${Il2Cpp.application.identifier}_${Il2Cpp.application.version}`;
if (!ignoreAlreadyExistingDirectory && directoryExists(path)) {
raise(`directory ${path} already exists - pass ignoreAlreadyExistingDirectory = true to skip this check`);
}
for (const assembly of Il2Cpp.domain.assemblies) {
inform(`dumping ${assembly.name}...`);
const destination = `${path}/${assembly.name.replaceAll(".", "/")}.cs`;
createDirectoryRecursively(destination.substring(0, destination.lastIndexOf("/")));
const file = new File(destination, "w");
for (const klass of assembly.image.classes) {
file.write(`${klass}\n\n`);
}
file.flush();
file.close();
}
ok(`dump saved to ${path}`);
showDeprecationNotice();
}
Il2Cpp.dumpTree = dumpTree;
function directoryExists(path) {
return Il2Cpp.corlib.class("System.IO.Directory").method("Exists").invoke(Il2Cpp.string(path));
}
function createDirectoryRecursively(path) {
Il2Cpp.corlib.class("System.IO.Directory").method("CreateDirectory").invoke(Il2Cpp.string(path));
}
function showDeprecationNotice() {
warn("this api will be removed in a future release, please use `npx frida-il2cpp-bridge dump` instead");
}
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* Installs a listener to track any thrown (unrecoverable) C# exception. \
* This may be useful when incurring in `abort was called` errors.
*
* By default, it only tracks exceptions that were thrown by the *caller*
* thread.
*
* **It may not work for every platform.**
*
* ```ts
* Il2Cpp.perform(() => {
* Il2Cpp.installExceptionListener("all");
*
* // rest of the code
* });
* ```
*
* For instance, it may print something along:
* ```
* System.NullReferenceException: Object reference not set to an instance of an object.
* at AddressableLoadWrapper+<LoadGameObject>d__3[T].MoveNext () [0x00000] in <00000000000000000000000000000000>:0
* at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0
* ```
*/
function installExceptionListener(targetThread = "current") {
const currentThread = Il2Cpp.exports.threadGetCurrent();
return Interceptor.attach(Il2Cpp.module.getExportByName("__cxa_throw"), function (args) {
if (targetThread == "current" && !Il2Cpp.exports.threadGetCurrent().equals(currentThread)) {
return;
}
inform(new Il2Cpp.Object(args[0].readPointer()));
});
}
Il2Cpp.installExceptionListener = installExceptionListener;
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* The **core** object where all the necessary IL2CPP native functions are
* held. \
* `frida-il2cpp-bridge` is built around this object by providing an
* easy-to-use abstraction layer: the user isn't expected to use it directly,
* but it can in case of advanced use cases.
*
* The exports depends on the Unity version, hence some of them may be
* unavailable; moreover, they are searched by **name** (e.g.
* `il2cpp_class_from_name`) hence they might get stripped, hidden or
* renamed by a nasty obfuscator.
*
* However, it is possible to override or set the handle of any of the
* exports using {@link Il2Cpp.$config.exports}:
* ```ts
* Il2Cpp.$config.exports = {
* il2cpp_image_get_class: () => Il2Cpp.module.base.add(0x1204c),
* il2cpp_class_get_parent: () => {
* return Memory.scanSync(Il2Cpp.module.base, Il2Cpp.module.size, "2f 10 ee 10 34 a8")[0].address;
* },
* };
*
* Il2Cpp.perform(() => {
* // ...
* });
* ```
*/
Il2Cpp.exports = {
get alloc() {
return r("il2cpp_alloc", "pointer", ["size_t"]);
},
get arrayGetLength() {
return r("il2cpp_array_length", "uint32", ["pointer"]);
},
get arrayNew() {
return r("il2cpp_array_new", "pointer", ["pointer", "uint32"]);
},
get assemblyGetImage() {
return r("il2cpp_assembly_get_image", "pointer", ["pointer"]);
},
get classForEach() {
return r("il2cpp_class_for_each", "void", ["pointer", "pointer"]);
},
get classFromName() {
return r("il2cpp_class_from_name", "pointer", ["pointer", "pointer", "pointer"]);
},
get classFromObject() {
return r("il2cpp_class_from_system_type", "pointer", ["pointer"]);
},
get classGetArrayClass() {
return r("il2cpp_array_class_get", "pointer", ["pointer", "uint32"]);
},
get classGetArrayElementSize() {
return r("il2cpp_class_array_element_size", "int", ["pointer"]);
},
get classGetAssemblyName() {
return r("il2cpp_class_get_assemblyname", "pointer", ["pointer"]);
},
get classGetBaseType() {
return r("il2cpp_class_enum_basetype", "pointer", ["pointer"]);
},
get classGetDeclaringType() {
return r("il2cpp_class_get_declaring_type", "pointer", ["pointer"]);
},
get classGetElementClass() {
return r("il2cpp_class_get_element_class", "pointer", ["pointer"]);
},
get classGetFieldFromName() {
return r("il2cpp_class_get_field_from_name", "pointer", ["pointer", "pointer"]);
},
get classGetFields() {
return r("il2cpp_class_get_fields", "pointer", ["pointer", "pointer"]);
},
get classGetFlags() {
return r("il2cpp_class_get_flags", "int", ["pointer"]);
},
get classGetImage() {
return r("il2cpp_class_get_image", "pointer", ["pointer"]);
},
get classGetInstanceSize() {
return r("il2cpp_class_instance_size", "int32", ["pointer"]);
},
get classGetInterfaces() {
return r("il2cpp_class_get_interfaces", "pointer", ["pointer", "pointer"]);
},
get classGetMethodFromName() {
return r("il2cpp_class_get_method_from_name", "pointer", ["pointer", "pointer", "int"]);
},
get classGetMethods() {
return r("il2cpp_class_get_methods", "pointer", ["pointer", "pointer"]);
},
get classGetName() {
return r("il2cpp_class_get_name", "pointer", ["pointer"]);
},
get classGetNamespace() {
return r("il2cpp_class_get_namespace", "pointer", ["pointer"]);
},
get classGetNestedClasses() {
return r("il2cpp_class_get_nested_types", "pointer", ["pointer", "pointer"]);
},
get classGetParent() {
return r("il2cpp_class_get_parent", "pointer", ["pointer"]);
},
get classGetStaticFieldData() {
return r("il2cpp_class_get_static_field_data", "pointer", ["pointer"]);
},
get classGetValueTypeSize() {
return r("il2cpp_class_value_size", "int32", ["pointer", "pointer"]);
},
get classGetType() {
return r("il2cpp_class_get_type", "pointer", ["pointer"]);
},
get classHasReferences() {
return r("il2cpp_class_has_references", "bool", ["pointer"]);
},
get classInitialize() {
return r("il2cpp_runtime_class_init", "void", ["pointer"]);
},
get classIsAbstract() {
return r("il2cpp_class_is_abstract", "bool", ["pointer"]);
},
get classIsAssignableFrom() {
return r("il2cpp_class_is_assignable_from", "bool", ["pointer", "pointer"]);
},
get classIsBlittable() {
return r("il2cpp_class_is_blittable", "bool", ["pointer"]);
},
get classIsEnum() {
return r("il2cpp_class_is_enum", "bool", ["pointer"]);
},
get classIsGeneric() {
return r("il2cpp_class_is_generic", "bool", ["pointer"]);
},
get classIsInflated() {
return r("il2cpp_class_is_inflated", "bool", ["pointer"]);
},
get classIsInterface() {
return r("il2cpp_class_is_interface", "bool", ["pointer"]);
},
get classIsSubclassOf() {
return r("il2cpp_class_is_subclass_of", "bool", ["pointer", "pointer", "bool"]);
},
get classIsValueType() {
return r("il2cpp_class_is_valuetype", "bool", ["pointer"]);
},
get domainGetAssemblyFromName() {
return r("il2cpp_domain_assembly_open", "pointer", ["pointer", "pointer"]);
},
get domainGet() {
return r("il2cpp_domain_get", "pointer", []);
},
get domainGetAssemblies() {
return r("il2cpp_domain_get_assemblies", "pointer", ["pointer", "pointer"]);
},
get fieldGetClass() {
return r("il2cpp_field_get_parent", "pointer", ["pointer"]);
},
get fieldGetFlags() {
return r("il2cpp_field_get_flags", "int", ["pointer"]);
},
get fieldGetName() {
return r("il2cpp_field_get_name", "pointer", ["pointer"]);
},
get fieldGetOffset() {
return r("il2cpp_field_get_offset", "int32", ["pointer"]);
},
get fieldGetStaticValue() {
return r("il2cpp_field_static_get_value", "void", ["pointer", "pointer"]);
},
get fieldGetType() {
return r("il2cpp_field_get_type", "pointer", ["pointer"]);
},
get fieldSetStaticValue() {
return r("il2cpp_field_static_set_value", "void", ["pointer", "pointer"]);
},
get free() {
return r("il2cpp_free", "void", ["pointer"]);
},
get gcCollect() {
return r("il2cpp_gc_collect", "void", ["int"]);
},
get gcCollectALittle() {
return r("il2cpp_gc_collect_a_little", "void", []);
},
get gcDisable() {
return r("il2cpp_gc_disable", "void", []);
},
get gcEnable() {
return r("il2cpp_gc_enable", "void", []);
},
get gcGetHeapSize() {
return r("il2cpp_gc_get_heap_size", "int64", []);
},
get gcGetMaxTimeSlice() {
return r("il2cpp_gc_get_max_time_slice_ns", "int64", []);
},
get gcGetUsedSize() {
return r("il2cpp_gc_get_used_size", "int64", []);
},
get gcHandleGetTarget() {
return r("il2cpp_gchandle_get_target", "pointer", ["uint32"]);
},
get gcHandleFree() {
return r("il2cpp_gchandle_free", "void", ["uint32"]);
},
get gcHandleNew() {
return r("il2cpp_gchandle_new", "uint32", ["pointer", "bool"]);
},
get gcHandleNewWeakRef() {
return r("il2cpp_gchandle_new_weakref", "uint32", ["pointer", "bool"]);
},
get gcIsDisabled() {
return r("il2cpp_gc_is_disabled", "bool", []);
},
get gcIsIncremental() {
return r("il2cpp_gc_is_incremental", "bool", []);
},
get gcSetMaxTimeSlice() {
return r("il2cpp_gc_set_max_time_slice_ns", "void", ["int64"]);
},
get gcStartIncrementalCollection() {
return r("il2cpp_gc_start_incremental_collection", "void", []);
},
get gcStartWorld() {
return r("il2cpp_start_gc_world", "void", []);
},
get gcStopWorld() {
return r("il2cpp_stop_gc_world", "void", []);
},
get getCorlib() {
return r("il2cpp_get_corlib", "pointer", []);
},
get imageGetAssembly() {
return r("il2cpp_image_get_assembly", "pointer", ["pointer"]);
},
get imageGetClass() {
return r("il2cpp_image_get_class", "pointer", ["pointer", "uint"]);
},
get imageGetClassCount() {
return r("il2cpp_image_get_class_count", "uint32", ["pointer"]);
},
get imageGetName() {
return r("il2cpp_image_get_name", "pointer", ["pointer"]);
},
get initialize() {
return r("il2cpp_init", "void", ["pointer"]);
},
get livenessAllocateStruct() {
return r("il2cpp_unity_liveness_allocate_struct", "pointer", ["pointer", "int", "pointer", "pointer", "pointer"]);
},
get livenessCalculationBegin() {
return r("il2cpp_unity_liveness_calculation_begin", "pointer", ["pointer", "int", "pointer", "pointer", "pointer", "pointer"]);
},
get livenessCalculationEnd() {
return r("il2cpp_unity_liveness_calculation_end", "void", ["pointer"]);
},
get livenessCalculationFromStatics() {
return r("il2cpp_unity_liveness_calculation_from_statics", "void", ["pointer"]);
},
get livenessFinalize() {
return r("il2cpp_unity_liveness_finalize", "void", ["pointer"]);
},
get livenessFreeStruct() {
return r("il2cpp_unity_liveness_free_struct", "void", ["pointer"]);
},
get memorySnapshotCapture() {
return r("il2cpp_capture_memory_snapshot", "pointer", []);
},
get memorySnapshotFree() {
return r("il2cpp_free_captured_memory_snapshot", "void", ["pointer"]);
},
get memorySnapshotGetClasses() {
return r("il2cpp_memory_snapshot_get_classes", "pointer", ["pointer", "pointer"]);
},
get memorySnapshotGetObjects() {
return r("il2cpp_memory_snapshot_get_objects", "pointer", ["pointer", "pointer"]);
},
get methodGetClass() {
return r("il2cpp_method_get_class", "pointer", ["pointer"]);
},
get methodGetFlags() {
return r("il2cpp_method_get_flags", "uint32", ["pointer", "pointer"]);
},
get methodGetName() {
return r("il2cpp_method_get_name", "pointer", ["pointer"]);
},
get methodGetObject() {
return r("il2cpp_method_get_object", "pointer", ["pointer", "pointer"]);
},
get methodGetParameterCount() {
return r("il2cpp_method_get_param_count", "uint8", ["pointer"]);
},
get methodGetParameterName() {
return r("il2cpp_method_get_param_name", "pointer", ["pointer", "uint32"]);
},
get methodGetParameters() {
return r("il2cpp_method_get_parameters", "pointer", ["pointer", "pointer"]);
},
get methodGetParameterType() {
return r("il2cpp_method_get_param", "pointer", ["pointer", "uint32"]);
},
get methodGetReturnType() {
return r("il2cpp_method_get_return_type", "pointer", ["pointer"]);
},
get methodIsGeneric() {
return r("il2cpp_method_is_generic", "bool", ["pointer"]);
},
get methodIsInflated() {
return r("il2cpp_method_is_inflated", "bool", ["pointer"]);
},
get methodIsInstance() {
return r("il2cpp_method_is_instance", "bool", ["pointer"]);
},
get monitorEnter() {
return r("il2cpp_monitor_enter", "void", ["pointer"]);
},
get monitorExit() {
return r("il2cpp_monitor_exit", "void", ["pointer"]);
},
get monitorPulse() {
return r("il2cpp_monitor_pulse", "void", ["pointer"]);
},
get monitorPulseAll() {
return r("il2cpp_monitor_pulse_all", "void", ["pointer"]);
},
get monitorTryEnter() {
return r("il2cpp_monitor_try_enter", "bool", ["pointer", "uint32"]);
},
get monitorTryWait() {
return r("il2cpp_monitor_try_wait", "bool", ["pointer", "uint32"]);
},
get monitorWait() {
return r("il2cpp_monitor_wait", "void", ["pointer"]);
},
get objectGetClass() {
return r("il2cpp_object_get_class", "pointer", ["pointer"]);
},
get objectGetVirtualMethod() {
return r("il2cpp_object_get_virtual_method", "pointer", ["pointer", "pointer"]);
},
get objectInitialize() {
return r("il2cpp_runtime_object_init_exception", "void", ["pointer", "pointer"]);
},
get objectNew() {
return r("il2cpp_object_new", "pointer", ["pointer"]);
},
get objectGetSize() {
return r("il2cpp_object_get_size", "uint32", ["pointer"]);
},
get objectUnbox() {
return r("il2cpp_object_unbox", "pointer", ["pointer"]);
},
get resolveInternalCall() {
return r("il2cpp_resolve_icall", "pointer", ["pointer"]);
},
get stringGetChars() {
return r("il2cpp_string_chars", "pointer", ["pointer"]);
},
get stringGetLength() {
return r("il2cpp_string_length", "int32", ["pointer"]);
},
get stringNew() {
return r("il2cpp_string_new", "pointer", ["pointer"]);
},
get valueTypeBox() {
return r("il2cpp_value_box", "pointer", ["pointer", "pointer"]);
},
get threadAttach() {
return r("il2cpp_thread_attach", "pointer", ["pointer"]);
},
get threadDetach() {
return r("il2cpp_thread_detach", "void", ["pointer"]);
},
get threadGetAttachedThreads() {
return r("il2cpp_thread_get_all_attached_threads", "pointer", ["pointer"]);
},
get threadGetCurrent() {
return r("il2cpp_thread_current", "pointer", []);
},
get threadIsVm() {
return r("il2cpp_is_vm_thread", "bool", ["pointer"]);
},
get typeEquals() {
return r("il2cpp_type_equals", "bool", ["pointer", "pointer"]);
},
get typeGetClass() {
return r("il2cpp_class_from_type", "pointer", ["pointer"]);
},
get typeGetName() {
return r("il2cpp_type_get_name", "pointer", ["pointer"]);
},
get typeGetObject() {
return r("il2cpp_type_get_object", "pointer", ["pointer"]);
},
get typeGetTypeEnum() {
return r("il2cpp_type_get_type", "int", ["pointer"]);
}
};
decorate(Il2Cpp.exports, lazy);
getter(Il2Cpp, "memorySnapshotExports", () => new CModule("#include <stdint.h>\n#include <string.h>\n\ntypedef struct Il2CppManagedMemorySnapshot Il2CppManagedMemorySnapshot;\ntypedef struct Il2CppMetadataType Il2CppMetadataType;\n\nstruct Il2CppManagedMemorySnapshot\n{\n struct Il2CppManagedHeap\n {\n uint32_t section_count;\n void * sections;\n } heap;\n struct Il2CppStacks\n {\n uint32_t stack_count;\n void * stacks;\n } stacks;\n struct Il2CppMetadataSnapshot\n {\n uint32_t type_count;\n Il2CppMetadataType * types;\n } metadata_snapshot;\n struct Il2CppGCHandles\n {\n uint32_t tracked_object_count;\n void ** pointers_to_objects;\n } gc_handles;\n struct Il2CppRuntimeInformation\n {\n uint32_t pointer_size;\n uint32_t object_header_size;\n uint32_t array_header_size;\n uint32_t array_bounds_offset_in_header;\n uint32_t array_size_offset_in_header;\n uint32_t allocation_granularity;\n } runtime_information;\n void * additional_user_information;\n};\n\nstruct Il2CppMetadataType\n{\n uint32_t flags;\n void * fields;\n uint32_t field_count;\n uint32_t statics_size;\n uint8_t * statics;\n uint32_t base_or_element_type_index;\n char * name;\n const char * assembly_name;\n uint64_t type_info_address;\n uint32_t size;\n};\n\nuintptr_t\nil2cpp_memory_snapshot_get_classes (\n const Il2CppManagedMemorySnapshot * snapshot, Il2CppMetadataType ** iter)\n{\n const int zero = 0;\n const void * null = 0;\n\n if (iter != NULL && snapshot->metadata_snapshot.type_count > zero)\n {\n if (*iter == null)\n {\n *iter = snapshot->metadata_snapshot.types;\n return (uintptr_t) (*iter)->type_info_address;\n }\n else\n {\n Il2CppMetadataType * metadata_type = *iter + 1;\n\n if (metadata_type < snapshot->metadata_snapshot.types +\n snapshot->metadata_snapshot.type_count)\n {\n *iter = metadata_type;\n return (uintptr_t) (*iter)->type_info_address;\n }\n }\n }\n return 0;\n}\n\nvoid **\nil2cpp_memory_snapshot_get_objects (\n const Il2CppManagedMemorySnapshot * snapshot, uint32_t * size)\n{\n *size = snapshot->gc_handles.tracked_object_count;\n return snapshot->gc_handles.pointers_to_objects;\n}\n"), lazy);
function r(exportName, retType, argTypes) {
const handle = Il2Cpp.$config.exports?.[exportName]?.() ?? Il2Cpp.module.findExportByName(exportName) ?? Il2Cpp.memorySnapshotExports[exportName];
const target = new NativeFunction(handle ?? NULL, retType, argTypes);
return target.isNull()
? new Proxy(target, {
get(value, name) {
const property = value[name];
return typeof property === "function" ? property.bind(value) : property;
},
apply() {
if (handle == null) {
raise(`couldn't resolve export ${exportName}`);
}
else if (handle.isNull()) {
raise(`export ${exportName} points to NULL IL2CPP library has likely been stripped, obfuscated, or customized`);
}
}
})
: target;
}
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* Creates a filter to include elements whose type can be assigned to a
* variable of the given class. \
* It relies on {@link Il2Cpp.Class.isAssignableFrom}.
*
* ```ts
* const IComparable = Il2Cpp.corlib.class("System.IComparable");
*
* const objects = [
* Il2Cpp.corlib.class("System.Object").new(),
* Il2Cpp.corlib.class("System.String").new()
* ];
*
* const comparables = objects.filter(Il2Cpp.is(IComparable));
* ```
*/
function is(klass) {
return (element) => {
if (element instanceof Il2Cpp.Class) {
return klass.isAssignableFrom(element);
}
else {
return klass.isAssignableFrom(element.class);
}
};
}
Il2Cpp.is = is;
/**
* Creates a filter to include elements whose type can be corresponds to
* the given class. \
* It compares the native handle of the element classes.
*
* ```ts
* const String = Il2Cpp.corlib.class("System.String");
*
* const objects = [
* Il2Cpp.corlib.class("System.Object").new(),
* Il2Cpp.corlib.class("System.String").new()
* ];
*
* const strings = objects.filter(Il2Cpp.isExactly(String));
* ```
*/
function isExactly(klass) {
return (element) => {
if (element instanceof Il2Cpp.Class) {
return element.equals(klass);
}
else {
return element.class.equals(klass);
}
};
}
Il2Cpp.isExactly = isExactly;
})(Il2Cpp || (Il2Cpp = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* The object literal to interacts with the garbage collector.
*/
Il2Cpp.gc = {
/**
* Gets the heap size in bytes.
*/
get heapSize() {
return Il2Cpp.exports.gcGetHeapSize();
},
/**
* Determines whether the garbage collector is enabled.
*/
get isEnabled() {
return !Il2Cpp.exports.gcIsDisabled();
},
/**
* Determines whether the garbage collector is incremental
* ([source](https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html)).
*/
get isIncremental() {
return !!Il2Cpp.exports.gcIsIncremental();
},
/**
* Gets the number of nanoseconds the garbage collector can spend in a
* collection step.
*/
get maxTimeSlice() {
return Il2Cpp.exports.gcGetMaxTimeSlice();
},
/**
* Gets the used heap size in bytes.
*/
get usedHeapSize() {
return Il2Cpp.exports.gcGetUsedSize();
},
/**
* Enables or disables the garbage collector.
*/
set isEnabled(value) {
value ? Il2Cpp.exports.gcEnable() : Il2Cpp.exports.gcDisable();
},
/**
* Sets the number of nanoseconds the garbage collector can spend in
* a collection step.
*/
set maxTimeSlice(nanoseconds) {
Il2Cpp.exports.gcSetMaxTimeSlice(nanoseconds);
},
/**
* Returns the heap allocated objects of the specified class. \
* This variant reads GC descriptors.
*/
choose(klass) {
const matches = [];
const callback = (objects, size) => {
for (let i = 0; i < size; i++) {
matches.push(new Il2Cpp.Object(objects.add(i * Process.pointerSize).readPointer()));
}
};
const chooseCallback = new NativeCallback(callback, "void", ["pointer", "int", "pointer"]);
if (Il2Cpp.unityVersionIsBelow202120) {
const onWorld = new NativeCallback(() => { }, "void", []);
const state = Il2Cpp.exports.livenessCalculationBegin(klass, 0, chooseCallback, NULL, onWorld, onWorld);
Il2Cpp.exports.livenessCalculationFromStatics(state);
Il2Cpp.exports.livenessCalculationEnd(state);
}
else {
const realloc = (handle, size) => {
if (!handle.isNull() && size.compare(0) == 0) {
Il2Cpp.free(handle);
return NULL;
}
else {
return Il2Cpp.alloc(size);
}
};
const reallocCallback = new NativeCallback(realloc, "pointer", ["pointer", "size_t", "pointer"]);
this.stopWorld();
const state = Il2Cpp.exports.livenessAllocateStruct(klass, 0, chooseCallback, NULL, reallocCallback);
Il2Cpp.exports.livenessCalculationFromStatics(state);
Il2Cpp.exports.livenessFinalize(state);
this.startWorld();
Il2Cpp.exports.livenessFreeStruct(state);
}
return matches;
},
/**
* Forces a garbage collection of the specified generation.
*/
collect(generation) {
Il2Cpp.exports.gcCollect(generation < 0 ? 0 : generation > 2 ? 2 : generation);
},
/**
* Forces a garbage collection.
*/
collectALittle() {
Il2Cpp.exports.gcCollectALittle();
},
/**
* Resumes all the previously stopped threads.
*/
startWorld() {
return Il2Cpp.exports.gcStartWorld();
},
/**
* Performs an incremental garbage collection.
*/
startIncrementalCollection() {
return Il2Cpp.exports.gcStartIncrementalCollection();
},
/**
* Stops all threads which may access the garbage collected heap, other
* than the caller.
*/
stopWorld() {
return Il2Cpp.exports.gcStopWorld();
}
};
})(Il2Cpp || (Il2Cpp = {}));
/** @internal */
var Android;
(function (Android) {
// prettier-ignore
getter(Android, "apiLevel", () => {
const value = getProperty("ro.build.version.sdk");
return value ? parseInt(value) : null;
}, lazy);
function getProperty(name) {
const handle = Process.findModuleByName("libc.so")?.findExportByName("__system_property_get");
if (handle) {
const __system_property_get = new NativeFunction(handle, "void", ["pointer", "pointer"]);
const value = Memory.alloc(92).writePointer(NULL);
__system_property_get(Memory.allocUtf8String(name), value);
return value.readCString() ?? undefined;
}
}
})(Android || (Android = {}));
/** @internal */
function raise(message) {
const error = new Error(message);
// in the stack message, it is only used by V8 - qjs ignores it
error.name = "Il2CppError";
error.stack = error.stack
// reset style and replace "(Il2Cpp)?Error" with custom tag
?.replace(/^(Il2Cpp)?Error/, "\x1b[0m\x1b[38;5;9mil2cpp\x1b[0m")
// replace the (unhelpful) first line of the stack ("at raise ...") and
// add style to the stack lines
?.replace(/\n at (.+) \((.+):(.+)\)/, "\x1b[3m\x1b[2m")
// reset style
?.concat("\x1B[0m");
throw error;
}
/** @internal */
function warn(message) {
globalThis.console.log(`\x1b[38;5;11mil2cpp\x1b[0m: ${message}`);
}
/** @internal */
function ok(message) {
globalThis.console.log(`\x1b[38;5;10mil2cpp\x1b[0m: ${message}`);
}
/** @internal */
function inform(message) {
globalThis.console.log(`\x1b[38;5;12mil2cpp\x1b[0m: ${message}`);
}
/** @internal */
function decorate(target, decorator, descriptors = Object.getOwnPropertyDescriptors(target)) {
for (const key in descriptors) {
descriptors[key] = decorator(target, key, descriptors[key]);
}
Object.defineProperties(target, descriptors);
return target;
}
/** @internal */
function getter(target, key, get, decorator) {
globalThis.Object.defineProperty(target, key, decorator?.(target, key, { get, configurable: true }) ?? { get, configurable: true });
}
/** @internal https://stackoverflow.com/a/52171480/16885569 */
function cyrb53(str) {
let h1 = 0xdeadbeef;
let h2 = 0x41c6ce57;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
/** @internal */
function exportsHash(module) {
return cyrb53(module
.enumerateExports()
.sort((a, b) => a.name.localeCompare(b.name))
.map(_ => _.name + _.address.sub(module.base))
.join(""));
}
/** @internal */
function lazy(_, propertyKey, descriptor) {
const getter = descriptor.get;
if (!getter) {
throw new Error("@lazy can only be applied to getter accessors");
}
descriptor.get = function () {
const value = getter.call(this);
Object.defineProperty(this, propertyKey, {
value,
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
writable: false
});
return value;
};
return descriptor;
}
/** Scaffold class. */
class NativeStruct {
handle;
constructor(handleOrWrapper) {
if (handleOrWrapper instanceof NativePointer) {
this.handle = handleOrWrapper;
}
else {
this.handle = handleOrWrapper.handle;
}
}
equals(other) {
return this.handle.equals(other.handle);
}
isNull() {
return this.handle.isNull();
}
asNullable() {
return this.isNull() ? null : this;
}
}
/** @internal */
function addFlippedEntries(obj) {
return Object.keys(obj).reduce((obj, key) => ((obj[obj[key]] = key), obj), obj);
}
NativePointer.prototype.offsetOf = function (condition, depth) {
depth ??= 512;
for (let i = 0; depth > 0 ? i < depth : i < -depth; i++) {
if (condition(depth > 0 ? this.add(i) : this.sub(i))) {
return i;
}
}
return null;
};
/** @internal */
function readNativeIterator(block) {
const array = [];
const iterator = Memory.alloc(Process.pointerSize);
let handle = block(iterator);
while (!handle.isNull()) {
array.push(handle);
handle = block(iterator);
}
return array;
}
/** @internal */
function readNativeList(block) {
const lengthPointer = Memory.alloc(Process.pointerSize);
const startPointer = block(lengthPointer);
if (startPointer.isNull()) {
return [];
}
const array = new Array(lengthPointer.readInt());
for (let i = 0; i < array.length; i++) {
array[i] = startPointer.add(i * Process.pointerSize).readPointer();
}
return array;
}
/** @internal */
function recycle(Class) {
return new Proxy(Class, {
cache: new Map(),
construct(Target, argArray) {
const handle = argArray[0].toUInt32();
if (!this.cache.has(handle)) {
this.cache.set(handle, new Target(argArray[0]));
}
return this.cache.get(handle);
}
});
}
/** @internal */
var UnityVersion;
(function (UnityVersion) {
const pattern = /(6\d{3}|20\d{2}|\d)\.(\d)\.(\d{1,2})(?:[abcfp]|rc){0,2}\d?/;
function find(string) {
return string?.match(pattern)?.[0];
}
UnityVersion.find = find;
function gte(a, b) {
return compare(a, b) >= 0;
}
UnityVersion.gte = gte;
function lt(a, b) {
return compare(a, b) < 0;
}
UnityVersion.lt = lt;
function compare(a, b) {
const aMatches = a.match(pattern);
const bMatches = b.match(pattern);
for (let i = 1; i <= 3; i++) {
const a = Number(aMatches?.[i] ?? -1);
const b = Number(bMatches?.[i] ?? -1);
if (a > b)
return 1;
else if (a < b)
return -1;
}
return 0;
}
})(UnityVersion || (UnityVersion = {}));
var Il2Cpp;
(function (Il2Cpp) {
/**
* Allocates the given amount of bytes - it's equivalent to C's `malloc`. \
* The allocated memory should be freed manually.
*/
function alloc(size = Process.pointerSize) {
return Il2Cpp.exports.alloc(size);
}
Il2Cpp.alloc = alloc;
/**
* Frees a previously allocated memory using {@link Il2Cpp.alloc} - it's
* equivalent to C's `free`..
*
* ```ts
* const handle = Il2Cpp.alloc(64);
*
* // ...
*
* Il2Cpp.free(handle);
* ```
*/
function free(pointer) {
return Il2Cpp.exports.free(pointer);
}
Il2Cpp.free = free;
/** @internal */
function read(pointer, type) {
switch (type.enumValue) {
case Il2Cpp.Type.Enum.BOOLEAN:
return !!pointer.readS8();
case Il2Cpp.Type.Enum.BYTE:
return pointer.readS8();
case Il2Cpp.Type.Enum.UBYTE:
return pointer.readU8();
case Il2Cpp.Type.Enum.SHORT:
return pointer.readS16();
case Il2Cpp.Type.Enum.USHORT:
return pointer.readU16();
case Il2Cpp.Type.Enum.INT:
return pointer.readS32();
case Il2Cpp.Type.Enum.UINT:
return pointer.readU32();
case Il2Cpp.Type.Enum.CHAR:
return pointer.readU16();
case Il2Cpp.Type.Enum.LONG:
return pointer.readS64();
case Il2Cpp.Type.Enum.ULONG:
return pointer.readU64();
case Il2Cpp.Type.Enum.FLOAT:
return pointer.readFloat();
case Il2Cpp.Type.Enum.DOUBLE:
return pointer.readDouble();
case Il2Cpp.Type.Enum.NINT:
case Il2Cpp.Type.Enum.NUINT:
return pointer.readPointer();
case Il2Cpp.Type.Enum.POINTER:
return new Il2Cpp.Pointer(pointer.readPointer(), type.class.baseType);
case Il2Cpp.Type.Enum.VALUE_TYPE:
return new Il2Cpp.ValueType(pointer, type);
case Il2Cpp.Type.Enum.OBJECT:
case Il2Cpp.Type.Enum.CLASS:
return new Il2Cpp.Object(pointer.readPointer());
case Il2Cpp.Type.Enum.GENERIC_INSTANCE:
return type.class.isValueType ? new Il2Cpp.ValueType(pointer, type) : new Il2Cpp.Object(pointer.readPointer());
case Il2Cpp.Type.Enum.STRING:
return new Il2Cpp.String(pointer.readPointer());
case Il2Cpp.Type.Enum.ARRAY:
case Il2Cpp.Type.Enum.NARRAY:
return new Il2Cpp.Array(pointer.readPointer());
}
raise(`couldn't read the value from ${pointer} using an unhandled or unknown type ${type.name} (${type.enumValue}), please file an issue`);
}
Il2Cpp.read = read;
/** @internal */
function write(pointer, value, type) {
switch (type.enumValue) {
case Il2Cpp.Type.Enum.BOOLEAN:
return pointer.writeS8(+value);
case Il2Cpp.Type.Enum.BYTE:
return pointer.writeS8(value);
case Il2Cpp.Type.Enum.UBYTE:
return pointer.writeU8(value);
case Il2Cpp.Type.Enum.SHORT:
return pointer.writeS16(value);
case Il2Cpp.Type.Enum.USHORT:
return pointer.writeU16(value);
case Il2Cpp.Type.Enum.INT:
return pointer.writeS32(value);
case Il2Cpp.Type.Enum.UINT:
return pointer.writeU32(value);
case Il2Cpp.Type.Enum.CHAR:
return pointer.writeU16(value);
case Il2Cpp.Type.Enum.LONG:
return pointer.writeS64(value);
case Il2Cpp.Type.Enum.ULONG:
return pointer.writeU64(value);
case Il2Cpp.Type.Enum.FLOAT:
return pointer.writeFloat(value);
case Il2Cpp.Type.Enum.DOUBLE:
return pointer.writeDouble(value);
case Il2Cpp.Type.Enum.NINT:
case Il2Cpp.Type.Enum.NUINT:
case Il2Cpp.Type.Enum.POINTER:
case Il2Cpp.Type.Enum.STRING:
case Il2Cpp.Type.Enum.ARRAY:
case Il2Cpp.Type.Enum.NARRAY:
return pointer.writePointer(value);
case Il2Cpp.Type.Enum.VALUE_TYPE:
return Memory.copy(pointer, value, type.class.valueTypeSize), pointer;
case Il2Cpp.Type.Enum.OBJECT:
case Il2Cpp.Type.Enum.CLASS:
case Il2Cpp.Type.Enum.GENERIC_INSTANCE:
return value instanceof Il2Cpp.ValueType ? (Memory.copy(pointer, value, type.class.valueTypeSize), pointer) : pointer.writePointer(value);
}
raise(`couldn't write value ${value} to ${pointer} using an unhandled or unknown type ${type.name} (${type.enumValue}), please file an issue`);
}
Il2Cpp.write = write;
/** @internal */
function fromFridaValue(value, type) {
if (globalThis.Array.isArray(value)) {
const handle = Memory.alloc(type.class.valueTypeSize);
const fields = type.class.fields.filter(_ => !_.isStatic);
for (let i = 0; i < fields.length; i++) {
const convertedValue = fromFridaValue(value[i], fields[i].type);
write(handle.add(fields[i].offset).sub(Il2Cpp.Object.headerSize), convertedValue, fields[i].type);
}
return new Il2Cpp.ValueType(handle, type);
}
else if (value instanceof NativePointer) {
if (type.isByReference) {
return new Il2Cpp.Reference(value, type);
}
switch (type.enumValue) {
case Il2Cpp.Type.Enum.POINTER:
return new Il2Cpp.Pointer(value, type.class.baseType);
case Il2Cpp.Type.Enum.STRING:
return new Il2Cpp.String(value);
case Il2Cpp.Type.Enum.CLASS:
case Il2Cpp.Type.Enum.GENERIC_INSTANCE:
case Il2Cpp.Type.Enum.OBJECT:
return new Il2Cpp.Object(value);
case Il2Cpp.Type.Enum.ARRAY:
case Il2Cpp.Type.Enum.NARRAY:
return new Il2Cpp.Array(value);
default:
return value;
}
}