This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/tools/AnantaInjector/modules/bowling_agent.js
T

5930 lines
249 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// node_modules/frida-il2cpp-bridge/dist/index.js
var __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 Il2Cpp2;
(function(Il2Cpp3) {
Il2Cpp3.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(Il2Cpp3.module).toString(16);
}
};
getter(Il2Cpp3, "unityVersion", () => {
try {
const unityVersion = Il2Cpp3.$config.unityVersion ?? unityEngineCall("get_unityVersion");
if (unityVersion != null) {
return unityVersion;
}
} catch (_) {
}
const searchPattern = "69 6c 32 63 70 70";
for (const range of Il2Cpp3.module.enumerateRanges("r--").concat(Process.getRangeByAddress(Il2Cpp3.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 != void 0) {
return match;
}
}
}
raise("couldn't determine the Unity version, please specify it manually");
}, lazy);
getter(Il2Cpp3, "unityVersionIsBelow201830", () => {
return UnityVersion.lt(Il2Cpp3.unityVersion, "2018.3.0");
}, lazy);
getter(Il2Cpp3, "unityVersionIsBelow202120", () => {
return UnityVersion.lt(Il2Cpp3.unityVersion, "2021.2.0");
}, lazy);
function unityEngineCall(method) {
const handle = Il2Cpp3.exports.resolveInternalCall(Memory.allocUtf8String("UnityEngine.Application::" + method));
const nativeFunction = new NativeFunction(handle, "pointer", []);
return nativeFunction.isNull() ? null : new Il2Cpp3.String(nativeFunction()).asNullable()?.content ?? null;
}
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
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 = Il2Cpp3.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;
}
Il2Cpp3.boxed = boxed;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
Il2Cpp3.$config = {
moduleName: void 0,
unityVersion: void 0,
exports: void 0
};
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function dump(fileName, path) {
fileName = fileName ?? `${Il2Cpp3.application.identifier}_${Il2Cpp3.application.version}.cs`;
path = path ?? Il2Cpp3.application.dataPath ?? Process.getCurrentDir();
createDirectoryRecursively(path);
const destination = `${path}/${fileName}`;
const file = new File(destination, "w");
for (const assembly of Il2Cpp3.domain.assemblies) {
inform(`dumping ${assembly.name}...`);
for (const klass of assembly.image.classes) {
file.write(`${klass}
`);
}
}
file.flush();
file.close();
ok(`dump saved to ${destination}`);
showDeprecationNotice();
}
Il2Cpp3.dump = dump;
function dumpTree(path, ignoreAlreadyExistingDirectory = false) {
path = path ?? `${Il2Cpp3.application.dataPath ?? Process.getCurrentDir()}/${Il2Cpp3.application.identifier}_${Il2Cpp3.application.version}`;
if (!ignoreAlreadyExistingDirectory && directoryExists(path)) {
raise(`directory ${path} already exists - pass ignoreAlreadyExistingDirectory = true to skip this check`);
}
for (const assembly of Il2Cpp3.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}
`);
}
file.flush();
file.close();
}
ok(`dump saved to ${path}`);
showDeprecationNotice();
}
Il2Cpp3.dumpTree = dumpTree;
function directoryExists(path) {
return Il2Cpp3.corlib.class("System.IO.Directory").method("Exists").invoke(Il2Cpp3.string(path));
}
function createDirectoryRecursively(path) {
Il2Cpp3.corlib.class("System.IO.Directory").method("CreateDirectory").invoke(Il2Cpp3.string(path));
}
function showDeprecationNotice() {
warn("this api will be removed in a future release, please use `npx frida-il2cpp-bridge dump` instead");
}
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function installExceptionListener(targetThread = "current") {
const currentThread = Il2Cpp3.exports.threadGetCurrent();
return Interceptor.attach(Il2Cpp3.module.getExportByName("__cxa_throw"), function(args) {
if (targetThread == "current" && !Il2Cpp3.exports.threadGetCurrent().equals(currentThread)) {
return;
}
inform(new Il2Cpp3.Object(args[0].readPointer()));
});
}
Il2Cpp3.installExceptionListener = installExceptionListener;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
Il2Cpp3.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(Il2Cpp3.exports, lazy);
getter(Il2Cpp3, "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 = Il2Cpp3.$config.exports?.[exportName]?.() ?? Il2Cpp3.module.findExportByName(exportName) ?? Il2Cpp3.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;
}
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function is(klass) {
return (element) => {
if (element instanceof Il2Cpp3.Class) {
return klass.isAssignableFrom(element);
} else {
return klass.isAssignableFrom(element.class);
}
};
}
Il2Cpp3.is = is;
function isExactly(klass) {
return (element) => {
if (element instanceof Il2Cpp3.Class) {
return element.equals(klass);
} else {
return element.class.equals(klass);
}
};
}
Il2Cpp3.isExactly = isExactly;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
Il2Cpp3.gc = {
/**
* Gets the heap size in bytes.
*/
get heapSize() {
return Il2Cpp3.exports.gcGetHeapSize();
},
/**
* Determines whether the garbage collector is enabled.
*/
get isEnabled() {
return !Il2Cpp3.exports.gcIsDisabled();
},
/**
* Determines whether the garbage collector is incremental
* ([source](https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html)).
*/
get isIncremental() {
return !!Il2Cpp3.exports.gcIsIncremental();
},
/**
* Gets the number of nanoseconds the garbage collector can spend in a
* collection step.
*/
get maxTimeSlice() {
return Il2Cpp3.exports.gcGetMaxTimeSlice();
},
/**
* Gets the used heap size in bytes.
*/
get usedHeapSize() {
return Il2Cpp3.exports.gcGetUsedSize();
},
/**
* Enables or disables the garbage collector.
*/
set isEnabled(value) {
value ? Il2Cpp3.exports.gcEnable() : Il2Cpp3.exports.gcDisable();
},
/**
* Sets the number of nanoseconds the garbage collector can spend in
* a collection step.
*/
set maxTimeSlice(nanoseconds) {
Il2Cpp3.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 Il2Cpp3.Object(objects.add(i * Process.pointerSize).readPointer()));
}
};
const chooseCallback = new NativeCallback(callback, "void", ["pointer", "int", "pointer"]);
if (Il2Cpp3.unityVersionIsBelow202120) {
const onWorld = new NativeCallback(() => {
}, "void", []);
const state = Il2Cpp3.exports.livenessCalculationBegin(klass, 0, chooseCallback, NULL, onWorld, onWorld);
Il2Cpp3.exports.livenessCalculationFromStatics(state);
Il2Cpp3.exports.livenessCalculationEnd(state);
} else {
const realloc = (handle, size) => {
if (!handle.isNull() && size.compare(0) == 0) {
Il2Cpp3.free(handle);
return NULL;
} else {
return Il2Cpp3.alloc(size);
}
};
const reallocCallback = new NativeCallback(realloc, "pointer", ["pointer", "size_t", "pointer"]);
this.stopWorld();
const state = Il2Cpp3.exports.livenessAllocateStruct(klass, 0, chooseCallback, NULL, reallocCallback);
Il2Cpp3.exports.livenessCalculationFromStatics(state);
Il2Cpp3.exports.livenessFinalize(state);
this.startWorld();
Il2Cpp3.exports.livenessFreeStruct(state);
}
return matches;
},
/**
* Forces a garbage collection of the specified generation.
*/
collect(generation) {
Il2Cpp3.exports.gcCollect(generation < 0 ? 0 : generation > 2 ? 2 : generation);
},
/**
* Forces a garbage collection.
*/
collectALittle() {
Il2Cpp3.exports.gcCollectALittle();
},
/**
* Resumes all the previously stopped threads.
*/
startWorld() {
return Il2Cpp3.exports.gcStartWorld();
},
/**
* Performs an incremental garbage collection.
*/
startIncrementalCollection() {
return Il2Cpp3.exports.gcStartIncrementalCollection();
},
/**
* Stops all threads which may access the garbage collected heap, other
* than the caller.
*/
stopWorld() {
return Il2Cpp3.exports.gcStopWorld();
}
};
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Android;
(function(Android2) {
getter(Android2, "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() ?? void 0;
}
}
})(Android || (Android = {}));
function raise(message) {
const error = new Error(message);
error.name = "Il2CppError";
error.stack = error.stack?.replace(/^(Il2Cpp)?Error/, "\x1B[0m\x1B[38;5;9mil2cpp\x1B[0m")?.replace(/\n at (.+) \((.+):(.+)\)/, "\x1B[3m\x1B[2m")?.concat("\x1B[0m");
throw error;
}
function warn(message) {
globalThis.console.log(`\x1B[38;5;11mil2cpp\x1B[0m: ${message}`);
}
function ok(message) {
globalThis.console.log(`\x1B[38;5;10mil2cpp\x1B[0m: ${message}`);
}
function inform(message) {
globalThis.console.log(`\x1B[38;5;12mil2cpp\x1B[0m: ${message}`);
}
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;
}
function getter(target, key, get, decorator) {
globalThis.Object.defineProperty(target, key, decorator?.(target, key, { get, configurable: true }) ?? { get, configurable: true });
}
function cyrb53(str) {
let h1 = 3735928559;
let h2 = 1103547991;
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);
}
function exportsHash(module) {
return cyrb53(module.enumerateExports().sort((a, b) => a.name.localeCompare(b.name)).map((_) => _.name + _.address.sub(module.base)).join(""));
}
function lazy(_, propertyKey, descriptor) {
const getter2 = descriptor.get;
if (!getter2) {
throw new Error("@lazy can only be applied to getter accessors");
}
descriptor.get = function() {
const value = getter2.call(this);
Object.defineProperty(this, propertyKey, {
value,
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
writable: false
});
return value;
};
return descriptor;
}
var NativeStruct = class {
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;
}
};
function addFlippedEntries(obj) {
return Object.keys(obj).reduce((obj2, key) => (obj2[obj2[key]] = key, obj2), 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;
};
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;
}
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;
}
function recycle(Class) {
return new Proxy(Class, {
cache: /* @__PURE__ */ 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);
}
});
}
var UnityVersion;
(function(UnityVersion2) {
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];
}
UnityVersion2.find = find;
function gte(a, b) {
return compare(a, b) >= 0;
}
UnityVersion2.gte = gte;
function lt(a, b) {
return compare(a, b) < 0;
}
UnityVersion2.lt = lt;
function compare(a, b) {
const aMatches = a.match(pattern);
const bMatches = b.match(pattern);
for (let i = 1; i <= 3; i++) {
const a2 = Number(aMatches?.[i] ?? -1);
const b2 = Number(bMatches?.[i] ?? -1);
if (a2 > b2)
return 1;
else if (a2 < b2)
return -1;
}
return 0;
}
})(UnityVersion || (UnityVersion = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function alloc(size = Process.pointerSize) {
return Il2Cpp3.exports.alloc(size);
}
Il2Cpp3.alloc = alloc;
function free(pointer) {
return Il2Cpp3.exports.free(pointer);
}
Il2Cpp3.free = free;
function read(pointer, type) {
switch (type.enumValue) {
case Il2Cpp3.Type.Enum.BOOLEAN:
return !!pointer.readS8();
case Il2Cpp3.Type.Enum.BYTE:
return pointer.readS8();
case Il2Cpp3.Type.Enum.UBYTE:
return pointer.readU8();
case Il2Cpp3.Type.Enum.SHORT:
return pointer.readS16();
case Il2Cpp3.Type.Enum.USHORT:
return pointer.readU16();
case Il2Cpp3.Type.Enum.INT:
return pointer.readS32();
case Il2Cpp3.Type.Enum.UINT:
return pointer.readU32();
case Il2Cpp3.Type.Enum.CHAR:
return pointer.readU16();
case Il2Cpp3.Type.Enum.LONG:
return pointer.readS64();
case Il2Cpp3.Type.Enum.ULONG:
return pointer.readU64();
case Il2Cpp3.Type.Enum.FLOAT:
return pointer.readFloat();
case Il2Cpp3.Type.Enum.DOUBLE:
return pointer.readDouble();
case Il2Cpp3.Type.Enum.NINT:
case Il2Cpp3.Type.Enum.NUINT:
return pointer.readPointer();
case Il2Cpp3.Type.Enum.POINTER:
return new Il2Cpp3.Pointer(pointer.readPointer(), type.class.baseType);
case Il2Cpp3.Type.Enum.VALUE_TYPE:
return new Il2Cpp3.ValueType(pointer, type);
case Il2Cpp3.Type.Enum.OBJECT:
case Il2Cpp3.Type.Enum.CLASS:
return new Il2Cpp3.Object(pointer.readPointer());
case Il2Cpp3.Type.Enum.GENERIC_INSTANCE:
return type.class.isValueType ? new Il2Cpp3.ValueType(pointer, type) : new Il2Cpp3.Object(pointer.readPointer());
case Il2Cpp3.Type.Enum.STRING:
return new Il2Cpp3.String(pointer.readPointer());
case Il2Cpp3.Type.Enum.ARRAY:
case Il2Cpp3.Type.Enum.NARRAY:
return new Il2Cpp3.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`);
}
Il2Cpp3.read = read;
function write(pointer, value, type) {
switch (type.enumValue) {
case Il2Cpp3.Type.Enum.BOOLEAN:
return pointer.writeS8(+value);
case Il2Cpp3.Type.Enum.BYTE:
return pointer.writeS8(value);
case Il2Cpp3.Type.Enum.UBYTE:
return pointer.writeU8(value);
case Il2Cpp3.Type.Enum.SHORT:
return pointer.writeS16(value);
case Il2Cpp3.Type.Enum.USHORT:
return pointer.writeU16(value);
case Il2Cpp3.Type.Enum.INT:
return pointer.writeS32(value);
case Il2Cpp3.Type.Enum.UINT:
return pointer.writeU32(value);
case Il2Cpp3.Type.Enum.CHAR:
return pointer.writeU16(value);
case Il2Cpp3.Type.Enum.LONG:
return pointer.writeS64(value);
case Il2Cpp3.Type.Enum.ULONG:
return pointer.writeU64(value);
case Il2Cpp3.Type.Enum.FLOAT:
return pointer.writeFloat(value);
case Il2Cpp3.Type.Enum.DOUBLE:
return pointer.writeDouble(value);
case Il2Cpp3.Type.Enum.NINT:
case Il2Cpp3.Type.Enum.NUINT:
case Il2Cpp3.Type.Enum.POINTER:
case Il2Cpp3.Type.Enum.STRING:
case Il2Cpp3.Type.Enum.ARRAY:
case Il2Cpp3.Type.Enum.NARRAY:
return pointer.writePointer(value);
case Il2Cpp3.Type.Enum.VALUE_TYPE:
return Memory.copy(pointer, value, type.class.valueTypeSize), pointer;
case Il2Cpp3.Type.Enum.OBJECT:
case Il2Cpp3.Type.Enum.CLASS:
case Il2Cpp3.Type.Enum.GENERIC_INSTANCE:
return value instanceof Il2Cpp3.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`);
}
Il2Cpp3.write = write;
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(Il2Cpp3.Object.headerSize), convertedValue, fields[i].type);
}
return new Il2Cpp3.ValueType(handle, type);
} else if (value instanceof NativePointer) {
if (type.isByReference) {
return new Il2Cpp3.Reference(value, type);
}
switch (type.enumValue) {
case Il2Cpp3.Type.Enum.POINTER:
return new Il2Cpp3.Pointer(value, type.class.baseType);
case Il2Cpp3.Type.Enum.STRING:
return new Il2Cpp3.String(value);
case Il2Cpp3.Type.Enum.CLASS:
case Il2Cpp3.Type.Enum.GENERIC_INSTANCE:
case Il2Cpp3.Type.Enum.OBJECT:
return new Il2Cpp3.Object(value);
case Il2Cpp3.Type.Enum.ARRAY:
case Il2Cpp3.Type.Enum.NARRAY:
return new Il2Cpp3.Array(value);
default:
return value;
}
} else if (type.enumValue == Il2Cpp3.Type.Enum.BOOLEAN) {
return !!value;
} else if (type.enumValue == Il2Cpp3.Type.Enum.VALUE_TYPE && type.class.isEnum) {
return fromFridaValue([value], type);
} else {
return value;
}
}
Il2Cpp3.fromFridaValue = fromFridaValue;
function toFridaValue(value) {
if (typeof value == "boolean") {
return +value;
} else if (value instanceof Il2Cpp3.ValueType) {
if (value.type.class.isEnum) {
return value.field("value__").value;
} else {
const _ = value.type.class.fields.filter((_2) => !_2.isStatic).map((_2) => toFridaValue(_2.bind(value).value));
return _.length == 0 ? [0] : _;
}
} else {
return value;
}
}
Il2Cpp3.toFridaValue = toFridaValue;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
getter(Il2Cpp3, "module", () => {
return tryModule() ?? raise("Could not find IL2CPP module");
});
async function initialize(blocking = false) {
const module = tryModule() ?? await new Promise((resolve) => {
const [moduleName, fallbackModuleName] = getExpectedModuleNames();
const timeout = setTimeout(() => {
warn(`after 10 seconds, IL2CPP module '${moduleName}' has not been loaded yet, is the app running?`);
}, 1e4);
const moduleObserver = Process.attachModuleObserver({
onAdded(module2) {
if (module2.name == moduleName || fallbackModuleName && module2.name == fallbackModuleName) {
clearTimeout(timeout);
setImmediate(() => {
resolve(module2);
moduleObserver.detach();
});
}
}
});
});
Reflect.defineProperty(Il2Cpp3, "module", { value: module });
if (Il2Cpp3.exports.getCorlib().isNull()) {
return await new Promise((resolve) => {
const timeout = setTimeout(() => {
if (!Il2Cpp3.exports.getCorlib().isNull()) {
warn(`resuming execution despite IL2CPP initialization not being captured in time, please open an issue as this is suboptimal`);
interceptor.detach();
resolve(false);
}
}, 1e3);
const interceptor = Interceptor.attach(Il2Cpp3.exports.initialize, {
onEnter() {
clearTimeout(timeout);
},
onLeave() {
interceptor.detach();
blocking ? resolve(true) : setImmediate(() => resolve(false));
}
});
});
}
return false;
}
Il2Cpp3.initialize = initialize;
function tryModule() {
const [moduleName, fallback] = getExpectedModuleNames();
return Process.findModuleByName(moduleName) ?? Process.findModuleByName(fallback ?? moduleName) ?? (Process.platform == "darwin" ? Process.findModuleByAddress(DebugSymbol.fromName("il2cpp_init").address) : void 0) ?? void 0;
}
function getExpectedModuleNames() {
if (Il2Cpp3.$config.moduleName) {
return [Il2Cpp3.$config.moduleName];
}
switch (Process.platform) {
case "linux":
return [Android.apiLevel ? "libil2cpp.so" : "GameAssembly.so"];
case "windows":
return ["GameAssembly.dll"];
case "darwin":
return ["UnityFramework", "GameAssembly.dylib"];
}
raise(`${Process.platform} is not supported yet`);
}
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function nullable(valueOrNull, klass) {
const actualClass = typeof valueOrNull == "boolean" ? Il2Cpp3.corlib.class("System.Boolean") : typeof valueOrNull == "number" ? klass ?? Il2Cpp3.corlib.class("System.Int32") : valueOrNull instanceof Int64 ? Il2Cpp3.corlib.class("System.Int64") : valueOrNull instanceof UInt64 ? Il2Cpp3.corlib.class("System.UInt64") : valueOrNull instanceof NativePointer ? klass ?? Il2Cpp3.corlib.class("System.IntPtr") : valueOrNull instanceof Il2Cpp3.ValueType ? valueOrNull.type.class : klass ?? raise(`A class must be specified when constructing a nullable for value '${valueOrNull}'`);
if (actualClass.isValueType == false) {
raise(`Cannot create nullable value type out of a reference type '${actualClass.type.name}'`);
}
const inflatedClass = Il2Cpp3.corlib.class("System.Nullable`1").inflate(actualClass);
const struct = new Il2Cpp3.ValueType(Memory.alloc(inflatedClass.valueTypeSize), inflatedClass.type);
(struct.tryField("hasValue") ?? struct.field("has_value")).value = valueOrNull != null;
if (valueOrNull != null) {
struct.field("value").value = valueOrNull;
}
return struct;
}
Il2Cpp3.nullable = nullable;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
async function perform(block, flag = "bind") {
let attachedThread = null;
try {
const isInMainThread = await Il2Cpp3.initialize(flag == "main");
if (flag == "main" && !isInMainThread) {
return perform(() => Il2Cpp3.mainThread.schedule(block), "free");
}
if (Il2Cpp3.currentThread == null) {
attachedThread = Il2Cpp3.domain.attach();
}
if (flag == "bind" && attachedThread != null) {
Script.bindWeak(globalThis, () => attachedThread?.detach());
}
const result = block();
return result instanceof Promise ? await result : result;
} catch (error) {
Script.nextTick((_) => {
throw _;
}, error);
return Promise.reject(error);
} finally {
if (flag == "free" && attachedThread != null) {
attachedThread.detach();
}
}
}
Il2Cpp3.perform = perform;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Tracer {
/** @internal */
#state = {
depth: 0,
buffer: [],
history: /* @__PURE__ */ new Set(),
flush: () => {
if (this.#state.depth == 0) {
const message = `
${this.#state.buffer.join("\n")}
`;
if (this.#verbose) {
inform(message);
} else {
const hash = cyrb53(message);
if (!this.#state.history.has(hash)) {
this.#state.history.add(hash);
inform(message);
}
}
this.#state.buffer.length = 0;
}
}
};
/** @internal */
#threadId = Il2Cpp3.mainThread.id;
/** @internal */
#verbose = false;
/** @internal */
#applier;
/** @internal */
#targets = [];
/** @internal */
#domain;
/** @internal */
#assemblies;
/** @internal */
#classes;
/** @internal */
#methods;
/** @internal */
#assemblyFilter;
/** @internal */
#classFilter;
/** @internal */
#methodFilter;
/** @internal */
#parameterFilter;
constructor(applier) {
this.#applier = applier;
}
/** */
thread(thread) {
this.#threadId = thread.id;
return this;
}
/** Determines whether print duplicate logs. */
verbose(value) {
this.#verbose = value;
return this;
}
/** Sets the application domain as the place where to find the target methods. */
domain() {
this.#domain = Il2Cpp3.domain;
return this;
}
/** Sets the passed `assemblies` as the place where to find the target methods. */
assemblies(...assemblies) {
this.#assemblies = assemblies;
return this;
}
/** Sets the passed `classes` as the place where to find the target methods. */
classes(...classes) {
this.#classes = classes;
return this;
}
/** Sets the passed `methods` as the target methods. */
methods(...methods) {
this.#methods = methods;
return this;
}
/** Filters the assemblies where to find the target methods. */
filterAssemblies(filter) {
this.#assemblyFilter = filter;
return this;
}
/** Filters the classes where to find the target methods. */
filterClasses(filter) {
this.#classFilter = filter;
return this;
}
/** Filters the target methods. */
filterMethods(filter) {
this.#methodFilter = filter;
return this;
}
/** Filters the target methods. */
filterParameters(filter) {
this.#parameterFilter = filter;
return this;
}
/** Commits the current changes by finding the target methods. */
and() {
const filterMethod = (method) => {
if (this.#parameterFilter == void 0) {
this.#targets.push(method);
return;
}
for (const parameter of method.parameters) {
if (this.#parameterFilter(parameter)) {
this.#targets.push(method);
break;
}
}
};
const filterMethods = (values) => {
for (const method of values) {
filterMethod(method);
}
};
const filterClass = (klass) => {
if (this.#methodFilter == void 0) {
filterMethods(klass.methods);
return;
}
for (const method of klass.methods) {
if (this.#methodFilter(method)) {
filterMethod(method);
}
}
};
const filterClasses = (values) => {
for (const klass of values) {
filterClass(klass);
}
};
const filterAssembly = (assembly) => {
if (this.#classFilter == void 0) {
filterClasses(assembly.image.classes);
return;
}
for (const klass of assembly.image.classes) {
if (this.#classFilter(klass)) {
filterClass(klass);
}
}
};
const filterAssemblies = (assemblies) => {
for (const assembly of assemblies) {
filterAssembly(assembly);
}
};
const filterDomain = (domain) => {
if (this.#assemblyFilter == void 0) {
filterAssemblies(domain.assemblies);
return;
}
for (const assembly of domain.assemblies) {
if (this.#assemblyFilter(assembly)) {
filterAssembly(assembly);
}
}
};
this.#methods ? filterMethods(this.#methods) : this.#classes ? filterClasses(this.#classes) : this.#assemblies ? filterAssemblies(this.#assemblies) : this.#domain ? filterDomain(this.#domain) : void 0;
this.#assemblies = void 0;
this.#classes = void 0;
this.#methods = void 0;
this.#assemblyFilter = void 0;
this.#classFilter = void 0;
this.#methodFilter = void 0;
this.#parameterFilter = void 0;
return this;
}
/** Starts tracing. */
attach() {
for (const target of this.#targets) {
if (!target.virtualAddress.isNull()) {
try {
this.#applier(target, this.#state, this.#threadId);
} catch (e) {
switch (e.message) {
case /unable to intercept function at \w+; please file a bug/.exec(e.message)?.input:
case "already replaced this function":
break;
default:
throw e;
}
}
}
}
}
}
Il2Cpp3.Tracer = Tracer;
function trace(parameters = false) {
const applier = () => (method, state, threadId) => {
const paddedVirtualAddress = method.relativeVirtualAddress.toString(16).padStart(8, "0");
Interceptor.attach(method.virtualAddress, {
onEnter() {
if (this.threadId == threadId) {
state.buffer.push(`\x1B[2m0x${paddedVirtualAddress}\x1B[0m ${`\u2502 `.repeat(state.depth++)}\u250C\u2500\x1B[35m${method.class.type.name}::\x1B[1m${method.name}\x1B[0m\x1B[0m`);
}
},
onLeave() {
if (this.threadId == threadId) {
state.buffer.push(`\x1B[2m0x${paddedVirtualAddress}\x1B[0m ${`\u2502 `.repeat(--state.depth)}\u2514\u2500\x1B[33m${method.class.type.name}::\x1B[1m${method.name}\x1B[0m\x1B[0m`);
state.flush();
}
}
});
};
const applierWithParameters = () => (method, state, threadId) => {
const paddedVirtualAddress = method.relativeVirtualAddress.toString(16).padStart(8, "0");
const startIndex = +!method.isStatic | +Il2Cpp3.unityVersionIsBelow201830;
const callback = function(...args) {
if (this.threadId == threadId) {
const thisParameter = method.isStatic ? void 0 : new Il2Cpp3.Parameter("this", -1, method.class.type);
const parameters2 = thisParameter ? [thisParameter].concat(method.parameters) : method.parameters;
state.buffer.push(`\x1B[2m0x${paddedVirtualAddress}\x1B[0m ${`\u2502 `.repeat(state.depth++)}\u250C\u2500\x1B[35m${method.class.type.name}::\x1B[1m${method.name}\x1B[0m\x1B[0m(${parameters2.map((e) => `\x1B[32m${e.name}\x1B[0m = \x1B[31m${Il2Cpp3.fromFridaValue(args[e.position + startIndex], e.type)}\x1B[0m`).join(", ")})`);
}
const returnValue = method.nativeFunction(...args);
if (this.threadId == threadId) {
state.buffer.push(`\x1B[2m0x${paddedVirtualAddress}\x1B[0m ${`\u2502 `.repeat(--state.depth)}\u2514\u2500\x1B[33m${method.class.type.name}::\x1B[1m${method.name}\x1B[0m\x1B[0m${returnValue == void 0 ? "" : ` = \x1B[36m${Il2Cpp3.fromFridaValue(returnValue, method.returnType)}`}\x1B[0m`);
state.flush();
}
return returnValue;
};
method.revert();
const nativeCallback = new NativeCallback(callback, method.returnType.fridaAlias, method.fridaSignature);
Interceptor.replace(method.virtualAddress, nativeCallback);
};
return new Il2Cpp3.Tracer(parameters ? applierWithParameters() : applier());
}
Il2Cpp3.trace = trace;
function backtrace(mode) {
const methods = Il2Cpp3.domain.assemblies.flatMap((_) => _.image.classes.flatMap((_2) => _2.methods.filter((_3) => !_3.virtualAddress.isNull()))).sort((_, __) => _.virtualAddress.compare(__.virtualAddress));
const searchInsert = (target) => {
let left = 0;
let right = methods.length - 1;
while (left <= right) {
const pivot = Math.floor((left + right) / 2);
const comparison = methods[pivot].virtualAddress.compare(target);
if (comparison == 0) {
return methods[pivot];
} else if (comparison > 0) {
right = pivot - 1;
} else {
left = pivot + 1;
}
}
return methods[right];
};
const applier = () => (method, state, threadId) => {
Interceptor.attach(method.virtualAddress, function() {
if (this.threadId == threadId) {
const handles = globalThis.Thread.backtrace(this.context, mode);
handles.unshift(method.virtualAddress);
for (const handle of handles) {
if (handle.compare(Il2Cpp3.module.base) > 0 && handle.compare(Il2Cpp3.module.base.add(Il2Cpp3.module.size)) < 0) {
const method2 = searchInsert(handle);
if (method2) {
const offset = handle.sub(method2.virtualAddress);
if (offset.compare(4095) < 0) {
state.buffer.push(`\x1B[2m0x${method2.relativeVirtualAddress.toString(16).padStart(8, "0")}\x1B[0m\x1B[2m+0x${offset.toString(16).padStart(3, `0`)}\x1B[0m ${method2.class.type.name}::\x1B[1m${method2.name}\x1B[0m`);
}
}
}
}
state.flush();
}
});
};
return new Il2Cpp3.Tracer(applier());
}
Il2Cpp3.backtrace = backtrace;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Array2 extends NativeStruct {
/** Gets the Il2CppArray struct size, possibly equal to `Process.pointerSize * 4`. */
static get headerSize() {
return Il2Cpp3.corlib.class("System.Array").instanceSize;
}
/** @internal Gets a pointer to the first element of the current array. */
get elements() {
const array2 = Il2Cpp3.string("vfsfitvnm").object.method("ToCharArray", 0).invoke();
const offset = Memory.scanSync(array2.handle, 255, "76 00 66 00 73 00 66 00 69 00 74 00 76 00 6e 00 6d 00")[0]?.address?.sub(array2.handle) ?? raise("couldn't find the elements offset in the native array struct");
getter(Il2Cpp3.Array.prototype, "elements", function() {
return new Il2Cpp3.Pointer(this.handle.add(offset), this.elementType);
}, lazy);
return this.elements;
}
/** Gets the size of the object encompassed by the current array. */
get elementSize() {
return this.elementType.class.arrayElementSize;
}
/** Gets the type of the object encompassed by the current array. */
get elementType() {
return this.object.class.type.class.baseType;
}
/** Gets the total number of elements in all the dimensions of the current array. */
get length() {
return Il2Cpp3.exports.arrayGetLength(this);
}
/** Gets the encompassing object of the current array. */
get object() {
return new Il2Cpp3.Object(this);
}
/** Gets the element at the specified index of the current array. */
get(index) {
if (index < 0 || index >= this.length) {
raise(`cannot get element at index ${index} as the array length is ${this.length}`);
}
return this.elements.get(index);
}
/** Sets the element at the specified index of the current array. */
set(index, value) {
if (index < 0 || index >= this.length) {
raise(`cannot set element at index ${index} as the array length is ${this.length}`);
}
this.elements.set(index, value);
}
/** */
toString() {
return this.isNull() ? "null" : `[${this.elements.read(this.length, 0)}]`;
}
/** Iterable. */
*[Symbol.iterator]() {
for (let i = 0; i < this.length; i++) {
yield this.elements.get(i);
}
}
}
__decorate([
lazy
], Array2.prototype, "elementSize", null);
__decorate([
lazy
], Array2.prototype, "elementType", null);
__decorate([
lazy
], Array2.prototype, "length", null);
__decorate([
lazy
], Array2.prototype, "object", null);
__decorate([
lazy
], Array2, "headerSize", null);
Il2Cpp3.Array = Array2;
function array(klass, lengthOrElements) {
const length = typeof lengthOrElements == "number" ? lengthOrElements : lengthOrElements.length;
const array2 = new Il2Cpp3.Array(Il2Cpp3.exports.arrayNew(klass, length));
if (globalThis.Array.isArray(lengthOrElements)) {
array2.elements.write(lengthOrElements);
}
return array2;
}
Il2Cpp3.array = array;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
let Assembly = class Assembly extends NativeStruct {
/** Gets the image of this assembly. */
get image() {
if (Il2Cpp3.exports.assemblyGetImage.isNull()) {
const runtimeModule = this.object.tryMethod("GetType", 1)?.invoke(Il2Cpp3.string("<Module>"))?.asNullable()?.tryMethod("get_Module")?.invoke() ?? this.object.tryMethod("GetModules", 1)?.invoke(false)?.get(0) ?? raise(`couldn't find the runtime module object of assembly ${this.name}`);
return new Il2Cpp3.Image(runtimeModule.field("_impl").value);
}
return new Il2Cpp3.Image(Il2Cpp3.exports.assemblyGetImage(this));
}
/** Gets the name of this assembly. */
get name() {
return this.image.name.replace(".dll", "");
}
/** Gets the encompassing object of the current assembly. */
get object() {
for (const _ of Il2Cpp3.domain.object.method("GetAssemblies", 1).invoke(false)) {
if (_.field("_mono_assembly").value.equals(this)) {
return _;
}
}
raise("couldn't find the object of the native assembly struct");
}
};
__decorate([
lazy
], Assembly.prototype, "name", null);
__decorate([
lazy
], Assembly.prototype, "object", null);
Assembly = __decorate([
recycle
], Assembly);
Il2Cpp3.Assembly = Assembly;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
let Class = class Class extends NativeStruct {
/** Gets the actual size of the instance of the current class. */
get actualInstanceSize() {
const SystemString = Il2Cpp3.corlib.class("System.String");
const offset = SystemString.handle.offsetOf((_) => _.readInt() == SystemString.instanceSize - 2) ?? raise("couldn't find the actual instance size offset in the native class struct");
getter(Il2Cpp3.Class.prototype, "actualInstanceSize", function() {
return this.handle.add(offset).readS32();
}, lazy);
return this.actualInstanceSize;
}
/** Gets the array class which encompass the current class. */
get arrayClass() {
return new Il2Cpp3.Class(Il2Cpp3.exports.classGetArrayClass(this, 1));
}
/** Gets the size of the object encompassed by the current array class. */
get arrayElementSize() {
return Il2Cpp3.exports.classGetArrayElementSize(this);
}
/** Gets the name of the assembly in which the current class is defined. */
get assemblyName() {
return Il2Cpp3.exports.classGetAssemblyName(this).readUtf8String().replace(".dll", "");
}
/** Gets the class that declares the current nested class. */
get declaringClass() {
return new Il2Cpp3.Class(Il2Cpp3.exports.classGetDeclaringType(this)).asNullable();
}
/** Gets the encompassed type of this array, reference, pointer or enum type. */
get baseType() {
return new Il2Cpp3.Type(Il2Cpp3.exports.classGetBaseType(this)).asNullable();
}
/** Gets the class of the object encompassed or referred to by the current array, pointer or reference class. */
get elementClass() {
return new Il2Cpp3.Class(Il2Cpp3.exports.classGetElementClass(this)).asNullable();
}
/** Gets the fields of the current class. */
get fields() {
return readNativeIterator((_) => Il2Cpp3.exports.classGetFields(this, _)).map((_) => new Il2Cpp3.Field(_));
}
/** Gets the flags of the current class. */
get flags() {
return Il2Cpp3.exports.classGetFlags(this);
}
/** Gets the full name (namespace + name) of the current class. */
get fullName() {
return this.namespace ? `${this.namespace}.${this.name}` : this.name;
}
/** Gets the generic class of the current class if the current class is inflated. */
get genericClass() {
const klass = this.image.tryClass(this.fullName)?.asNullable();
return klass?.equals(this) ? null : klass ?? null;
}
/** Gets the generics parameters of this generic class. */
get generics() {
if (!this.isGeneric && !this.isInflated) {
return [];
}
const types = this.type.object.method("GetGenericArguments").invoke();
return globalThis.Array.from(types).map((_) => new Il2Cpp3.Class(Il2Cpp3.exports.classFromObject(_)));
}
/** Determines whether the GC has tracking references to the current class instances. */
get hasReferences() {
return !!Il2Cpp3.exports.classHasReferences(this);
}
/** Determines whether ther current class has a valid static constructor. */
get hasStaticConstructor() {
const staticConstructor = this.tryMethod(".cctor");
return staticConstructor != null && !staticConstructor.virtualAddress.isNull();
}
/** Gets the image in which the current class is defined. */
get image() {
return new Il2Cpp3.Image(Il2Cpp3.exports.classGetImage(this));
}
/** Gets the size of the instance of the current class. */
get instanceSize() {
return Il2Cpp3.exports.classGetInstanceSize(this);
}
/** Determines whether the current class is abstract. */
get isAbstract() {
return !!Il2Cpp3.exports.classIsAbstract(this);
}
/** Determines whether the current class is blittable. */
get isBlittable() {
return !!Il2Cpp3.exports.classIsBlittable(this);
}
/** Determines whether the current class is an enumeration. */
get isEnum() {
return !!Il2Cpp3.exports.classIsEnum(this);
}
/** Determines whether the current class is a generic one. */
get isGeneric() {
return !!Il2Cpp3.exports.classIsGeneric(this);
}
/** Determines whether the current class is inflated. */
get isInflated() {
return !!Il2Cpp3.exports.classIsInflated(this);
}
/** Determines whether the current class is an interface. */
get isInterface() {
return !!Il2Cpp3.exports.classIsInterface(this);
}
/** Determines whether the current class is a struct. */
get isStruct() {
return this.isValueType && !this.isEnum;
}
/** Determines whether the current class is a value type. */
get isValueType() {
return !!Il2Cpp3.exports.classIsValueType(this);
}
/** Gets the interfaces implemented or inherited by the current class. */
get interfaces() {
return readNativeIterator((_) => Il2Cpp3.exports.classGetInterfaces(this, _)).map((_) => new Il2Cpp3.Class(_));
}
/** Gets the methods implemented by the current class. */
get methods() {
return readNativeIterator((_) => Il2Cpp3.exports.classGetMethods(this, _)).map((_) => new Il2Cpp3.Method(_));
}
/** Gets the name of the current class. */
get name() {
return Il2Cpp3.exports.classGetName(this).readUtf8String();
}
/** Gets the namespace of the current class. */
get namespace() {
return Il2Cpp3.exports.classGetNamespace(this).readUtf8String() || void 0;
}
/** Gets the classes nested inside the current class. */
get nestedClasses() {
return readNativeIterator((_) => Il2Cpp3.exports.classGetNestedClasses(this, _)).map((_) => new Il2Cpp3.Class(_));
}
/** Gets the class from which the current class directly inherits. */
get parent() {
return new Il2Cpp3.Class(Il2Cpp3.exports.classGetParent(this)).asNullable();
}
/** Gets the pointer class of the current class. */
get pointerClass() {
return new Il2Cpp3.Class(Il2Cpp3.exports.classFromObject(this.type.object.method("MakePointerType").invoke()));
}
/** Gets the rank (number of dimensions) of the current array class. */
get rank() {
let rank = 0;
const name = this.name;
for (let i = this.name.length - 1; i > 0; i--) {
const c = name[i];
if (c == "]")
rank++;
else if (c == "[" || rank == 0)
break;
else if (c == ",")
rank++;
else
break;
}
return rank;
}
/** Gets a pointer to the static fields of the current class. */
get staticFieldsData() {
return Il2Cpp3.exports.classGetStaticFieldData(this);
}
/** Gets the size of the instance - as a value type - of the current class. */
get valueTypeSize() {
return Il2Cpp3.exports.classGetValueTypeSize(this, NULL);
}
/** Gets the type of the current class. */
get type() {
return new Il2Cpp3.Type(Il2Cpp3.exports.classGetType(this));
}
/** Allocates a new object of the current class. */
alloc() {
return new Il2Cpp3.Object(Il2Cpp3.exports.objectNew(this));
}
/** Gets the field identified by the given name. */
field(name) {
return this.tryField(name) ?? raise(`couldn't find field ${name} in class ${this.type.name}`);
}
/** Gets the hierarchy of the current class. */
*hierarchy(options) {
let klass = options?.includeCurrent ?? true ? this : this.parent;
while (klass) {
yield klass;
klass = klass.parent;
}
}
/** Builds a generic instance of the current generic class. */
inflate(...classes) {
if (!this.isGeneric) {
raise(`cannot inflate class ${this.type.name} as it has no generic parameters`);
}
if (this.generics.length != classes.length) {
raise(`cannot inflate class ${this.type.name} as it needs ${this.generics.length} generic parameter(s), not ${classes.length}`);
}
const types = classes.map((_) => _.type.object);
const typeArray = Il2Cpp3.array(Il2Cpp3.corlib.class("System.Type"), types);
const inflatedType = this.type.object.method("MakeGenericType", 1).invoke(typeArray);
return new Il2Cpp3.Class(Il2Cpp3.exports.classFromObject(inflatedType));
}
/** Calls the static constructor of the current class. */
initialize() {
Il2Cpp3.exports.classInitialize(this);
return this;
}
/** Determines whether an instance of `other` class can be assigned to a variable of the current type. */
isAssignableFrom(other) {
return !!Il2Cpp3.exports.classIsAssignableFrom(this, other);
}
/** Determines whether the current class derives from `other` class. */
isSubclassOf(other, checkInterfaces) {
return !!Il2Cpp3.exports.classIsSubclassOf(this, other, +checkInterfaces);
}
/** Gets the method identified by the given name and parameter count. */
method(name, parameterCount = -1) {
return this.tryMethod(name, parameterCount) ?? raise(`couldn't find method ${name} in class ${this.type.name}`);
}
/** Gets the nested class with the given name. */
nested(name) {
return this.tryNested(name) ?? raise(`couldn't find nested class ${name} in class ${this.type.name}`);
}
/** Allocates a new object of the current class and calls its default constructor. */
new() {
const object = this.alloc();
const exceptionArray = Memory.alloc(Process.pointerSize);
Il2Cpp3.exports.objectInitialize(object, exceptionArray);
const exception = exceptionArray.readPointer();
if (!exception.isNull()) {
raise(new Il2Cpp3.Object(exception).toString());
}
return object;
}
/** Gets the field with the given name. */
tryField(name) {
return new Il2Cpp3.Field(Il2Cpp3.exports.classGetFieldFromName(this, Memory.allocUtf8String(name))).asNullable();
}
/** Gets the method with the given name and parameter count. */
tryMethod(name, parameterCount = -1) {
return new Il2Cpp3.Method(Il2Cpp3.exports.classGetMethodFromName(this, Memory.allocUtf8String(name), parameterCount)).asNullable();
}
/** Gets the nested class with the given name. */
tryNested(name) {
return this.nestedClasses.find((_) => _.name == name);
}
/** */
toString() {
const inherited = [this.parent].concat(this.interfaces);
return `// ${this.assemblyName}
${this.isEnum ? `enum` : this.isStruct ? `struct` : this.isInterface ? `interface` : `class`} ${this.type.name}${inherited ? ` : ${inherited.map((_) => _?.type.name).join(`, `)}` : ``}
{
${this.fields.join(`
`)}
${this.methods.join(`
`)}
}`;
}
/** Executes a callback for every defined class. */
static enumerate(block) {
const callback = new NativeCallback((_) => block(new Il2Cpp3.Class(_)), "void", ["pointer", "pointer"]);
return Il2Cpp3.exports.classForEach(callback, NULL);
}
};
__decorate([
lazy
], Class.prototype, "arrayClass", null);
__decorate([
lazy
], Class.prototype, "arrayElementSize", null);
__decorate([
lazy
], Class.prototype, "assemblyName", null);
__decorate([
lazy
], Class.prototype, "declaringClass", null);
__decorate([
lazy
], Class.prototype, "baseType", null);
__decorate([
lazy
], Class.prototype, "elementClass", null);
__decorate([
lazy
], Class.prototype, "fields", null);
__decorate([
lazy
], Class.prototype, "flags", null);
__decorate([
lazy
], Class.prototype, "fullName", null);
__decorate([
lazy
], Class.prototype, "generics", null);
__decorate([
lazy
], Class.prototype, "hasReferences", null);
__decorate([
lazy
], Class.prototype, "hasStaticConstructor", null);
__decorate([
lazy
], Class.prototype, "image", null);
__decorate([
lazy
], Class.prototype, "instanceSize", null);
__decorate([
lazy
], Class.prototype, "isAbstract", null);
__decorate([
lazy
], Class.prototype, "isBlittable", null);
__decorate([
lazy
], Class.prototype, "isEnum", null);
__decorate([
lazy
], Class.prototype, "isGeneric", null);
__decorate([
lazy
], Class.prototype, "isInflated", null);
__decorate([
lazy
], Class.prototype, "isInterface", null);
__decorate([
lazy
], Class.prototype, "isValueType", null);
__decorate([
lazy
], Class.prototype, "interfaces", null);
__decorate([
lazy
], Class.prototype, "methods", null);
__decorate([
lazy
], Class.prototype, "name", null);
__decorate([
lazy
], Class.prototype, "namespace", null);
__decorate([
lazy
], Class.prototype, "nestedClasses", null);
__decorate([
lazy
], Class.prototype, "parent", null);
__decorate([
lazy
], Class.prototype, "pointerClass", null);
__decorate([
lazy
], Class.prototype, "rank", null);
__decorate([
lazy
], Class.prototype, "staticFieldsData", null);
__decorate([
lazy
], Class.prototype, "valueTypeSize", null);
__decorate([
lazy
], Class.prototype, "type", null);
Class = __decorate([
recycle
], Class);
Il2Cpp3.Class = Class;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
function delegate(klass, block) {
const SystemDelegate = Il2Cpp3.corlib.class("System.Delegate");
const SystemMulticastDelegate = Il2Cpp3.corlib.class("System.MulticastDelegate");
if (!SystemDelegate.isAssignableFrom(klass)) {
raise(`cannot create a delegate for ${klass.type.name} as it's a non-delegate class`);
}
if (klass.equals(SystemDelegate) || klass.equals(SystemMulticastDelegate)) {
raise(`cannot create a delegate for neither ${SystemDelegate.type.name} nor ${SystemMulticastDelegate.type.name}, use a subclass instead`);
}
const delegate2 = klass.alloc();
const key = delegate2.handle.toString();
const Invoke = delegate2.tryMethod("Invoke") ?? raise(`cannot create a delegate for ${klass.type.name}, there is no Invoke method`);
delegate2.method(".ctor").invoke(delegate2, Invoke.handle);
const callback = Invoke.wrap(block);
delegate2.field("method_ptr").value = callback;
delegate2.field("invoke_impl").value = callback;
Il2Cpp3._callbacksToKeepAlive[key] = callback;
return delegate2;
}
Il2Cpp3.delegate = delegate;
Il2Cpp3._callbacksToKeepAlive = {};
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
let Domain = class Domain extends NativeStruct {
/** Gets the assemblies that have been loaded into the execution context of the application domain. */
get assemblies() {
let handles = readNativeList((_) => Il2Cpp3.exports.domainGetAssemblies(this, _));
if (handles.length == 0) {
const assemblyObjects = this.object.method("GetAssemblies").overload().invoke();
handles = globalThis.Array.from(assemblyObjects).map((_) => _.field("_mono_assembly").value);
}
return handles.map((_) => new Il2Cpp3.Assembly(_));
}
/** Gets the encompassing object of the application domain. */
get object() {
return Il2Cpp3.corlib.class("System.AppDomain").method("get_CurrentDomain").invoke();
}
/** Opens and loads the assembly with the given name. */
assembly(name) {
return this.tryAssembly(name) ?? raise(`couldn't find assembly ${name}`);
}
/** Attached a new thread to the application domain. */
attach() {
return new Il2Cpp3.Thread(Il2Cpp3.exports.threadAttach(this));
}
/** Opens and loads the assembly with the given name. */
tryAssembly(name) {
return new Il2Cpp3.Assembly(Il2Cpp3.exports.domainGetAssemblyFromName(this, Memory.allocUtf8String(name))).asNullable();
}
};
__decorate([
lazy
], Domain.prototype, "assemblies", null);
__decorate([
lazy
], Domain.prototype, "object", null);
Domain = __decorate([
recycle
], Domain);
Il2Cpp3.Domain = Domain;
getter(Il2Cpp3, "domain", () => {
return new Il2Cpp3.Domain(Il2Cpp3.exports.domainGet());
}, lazy);
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Field extends NativeStruct {
/** Gets the class in which this field is defined. */
get class() {
return new Il2Cpp3.Class(Il2Cpp3.exports.fieldGetClass(this));
}
/** Gets the flags of the current field. */
get flags() {
return Il2Cpp3.exports.fieldGetFlags(this);
}
/** Determines whether this field value is known at compile time. */
get isLiteral() {
return (this.flags & 64) != 0;
}
/** Determines whether this field is static. */
get isStatic() {
return (this.flags & 16) != 0;
}
/** Determines whether this field is thread static. */
get isThreadStatic() {
const offset = Il2Cpp3.corlib.class("System.AppDomain").field("type_resolve_in_progress").offset;
getter(Il2Cpp3.Field.prototype, "isThreadStatic", function() {
return this.offset == offset;
}, lazy);
return this.isThreadStatic;
}
/** Gets the access modifier of this field. */
get modifier() {
switch (this.flags & 7) {
case 1:
return "private";
case 2:
return "private protected";
case 3:
return "internal";
case 4:
return "protected";
case 5:
return "protected internal";
case 6:
return "public";
}
}
/** Gets the name of this field. */
get name() {
return Il2Cpp3.exports.fieldGetName(this).readUtf8String();
}
/** Gets the offset of this field, calculated as the difference with its owner virtual address. */
get offset() {
return Il2Cpp3.exports.fieldGetOffset(this);
}
/** Gets the type of this field. */
get type() {
return new Il2Cpp3.Type(Il2Cpp3.exports.fieldGetType(this));
}
/** Gets the value of this field. */
get value() {
if (!this.isStatic) {
raise(`cannot access instance field ${this.class.type.name}::${this.name} from a class, use an object instead`);
}
const handle = Memory.alloc(Process.pointerSize);
Il2Cpp3.exports.fieldGetStaticValue(this.handle, handle);
return Il2Cpp3.read(handle, this.type);
}
/** Sets the value of this field. Thread static or literal values cannot be altered yet. */
set value(value) {
if (!this.isStatic) {
raise(`cannot access instance field ${this.class.type.name}::${this.name} from a class, use an object instead`);
}
if (this.isThreadStatic || this.isLiteral) {
raise(`cannot write the value of field ${this.name} as it's thread static or literal`);
}
const handle = (
// pointer-like values should be passed as-is, but boxed
// value types (primitives included) must be unboxed first
value instanceof Il2Cpp3.Object && this.type.class.isValueType ? value.unbox() : value instanceof NativeStruct ? value.handle : value instanceof NativePointer ? value : Il2Cpp3.write(Memory.alloc(this.type.class.valueTypeSize), value, this.type)
);
Il2Cpp3.exports.fieldSetStaticValue(this.handle, handle);
}
/** */
toString() {
return `${this.isThreadStatic ? `[ThreadStatic] ` : ``}${this.isStatic ? `static ` : ``}${this.type.name} ${this.name}${this.isLiteral ? ` = ${this.type.class.isEnum ? Il2Cpp3.read(this.value.handle, this.type.class.baseType) : this.value}` : ``};${this.isThreadStatic || this.isLiteral ? `` : ` // 0x${this.offset.toString(16)}`}`;
}
/**
* @internal
* Binds the current field to a {@link Il2Cpp.Object} or a
* {@link Il2Cpp.ValueType} (also known as *instances*), so that it is
* possible to retrieve its value - see {@link Il2Cpp.Field.value} for
* details. \
* Binding a static field is forbidden.
*/
bind(instance) {
if (this.isStatic) {
raise(`cannot bind static field ${this.class.type.name}::${this.name} to an instance`);
}
const offset = this.offset - (instance instanceof Il2Cpp3.ValueType ? Il2Cpp3.Object.headerSize : 0);
return new Proxy(this, {
get(target, property) {
if (property == "value") {
return Il2Cpp3.read(instance.handle.add(offset), target.type);
}
return Reflect.get(target, property);
},
set(target, property, value) {
if (property == "value") {
Il2Cpp3.write(instance.handle.add(offset), value, target.type);
return true;
}
return Reflect.set(target, property, value);
}
});
}
}
__decorate([
lazy
], Field.prototype, "class", null);
__decorate([
lazy
], Field.prototype, "flags", null);
__decorate([
lazy
], Field.prototype, "isLiteral", null);
__decorate([
lazy
], Field.prototype, "isStatic", null);
__decorate([
lazy
], Field.prototype, "isThreadStatic", null);
__decorate([
lazy
], Field.prototype, "modifier", null);
__decorate([
lazy
], Field.prototype, "name", null);
__decorate([
lazy
], Field.prototype, "offset", null);
__decorate([
lazy
], Field.prototype, "type", null);
Il2Cpp3.Field = Field;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class GCHandle {
handle;
/** @internal */
constructor(handle) {
this.handle = handle;
}
/** Gets the object associated to this handle. */
get target() {
return new Il2Cpp3.Object(Il2Cpp3.exports.gcHandleGetTarget(this.handle)).asNullable();
}
/** Frees this handle. */
free() {
return Il2Cpp3.exports.gcHandleFree(this.handle);
}
}
Il2Cpp3.GCHandle = GCHandle;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
let Image = class Image extends NativeStruct {
/** Gets the assembly in which the current image is defined. */
get assembly() {
return new Il2Cpp3.Assembly(Il2Cpp3.exports.imageGetAssembly(this));
}
/** Gets the amount of classes defined in this image. */
get classCount() {
if (Il2Cpp3.unityVersionIsBelow201830) {
return this.classes.length;
} else {
return Il2Cpp3.exports.imageGetClassCount(this);
}
}
/** Gets the classes defined in this image. */
get classes() {
if (Il2Cpp3.unityVersionIsBelow201830) {
const types = this.assembly.object.method("GetTypes").invoke(false);
const classes = globalThis.Array.from(types, (_) => new Il2Cpp3.Class(Il2Cpp3.exports.classFromObject(_)));
const Module = this.tryClass("<Module>");
if (Module) {
classes.unshift(Module);
}
return classes;
} else {
return globalThis.Array.from(globalThis.Array(this.classCount), (_, i) => new Il2Cpp3.Class(Il2Cpp3.exports.imageGetClass(this, i)));
}
}
/** Gets the name of this image. */
get name() {
return Il2Cpp3.exports.imageGetName(this).readUtf8String();
}
/** Gets the class with the specified name defined in this image. */
class(name) {
return this.tryClass(name) ?? raise(`couldn't find class ${name} in assembly ${this.name}`);
}
/** Gets the class with the specified name defined in this image. */
tryClass(name) {
const dotIndex = name.lastIndexOf(".");
const classNamespace = Memory.allocUtf8String(dotIndex == -1 ? "" : name.slice(0, dotIndex));
const className = Memory.allocUtf8String(name.slice(dotIndex + 1));
return new Il2Cpp3.Class(Il2Cpp3.exports.classFromName(this, classNamespace, className)).asNullable();
}
};
__decorate([
lazy
], Image.prototype, "assembly", null);
__decorate([
lazy
], Image.prototype, "classCount", null);
__decorate([
lazy
], Image.prototype, "classes", null);
__decorate([
lazy
], Image.prototype, "name", null);
Image = __decorate([
recycle
], Image);
Il2Cpp3.Image = Image;
getter(Il2Cpp3, "corlib", () => {
return new Il2Cpp3.Image(Il2Cpp3.exports.getCorlib());
}, lazy);
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class MemorySnapshot extends NativeStruct {
/** Captures a memory snapshot. */
static capture() {
return new Il2Cpp3.MemorySnapshot();
}
/** Creates a memory snapshot with the given handle. */
constructor(handle = Il2Cpp3.exports.memorySnapshotCapture()) {
super(handle);
}
/** Gets any initialized class. */
get classes() {
return readNativeIterator((_) => Il2Cpp3.exports.memorySnapshotGetClasses(this, _)).map((_) => new Il2Cpp3.Class(_));
}
/** Gets the objects tracked by this memory snapshot. */
get objects() {
return readNativeList((_) => Il2Cpp3.exports.memorySnapshotGetObjects(this, _)).filter((_) => !_.isNull()).map((_) => new Il2Cpp3.Object(_));
}
/** Frees this memory snapshot. */
free() {
Il2Cpp3.exports.memorySnapshotFree(this);
}
}
__decorate([
lazy
], MemorySnapshot.prototype, "classes", null);
__decorate([
lazy
], MemorySnapshot.prototype, "objects", null);
Il2Cpp3.MemorySnapshot = MemorySnapshot;
function memorySnapshot(block) {
const memorySnapshot2 = Il2Cpp3.MemorySnapshot.capture();
const result = block(memorySnapshot2);
memorySnapshot2.free();
return result;
}
Il2Cpp3.memorySnapshot = memorySnapshot;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Method extends NativeStruct {
/** Gets the class in which this method is defined. */
get class() {
return new Il2Cpp3.Class(Il2Cpp3.exports.methodGetClass(this));
}
/** Gets the flags of the current method. */
get flags() {
return Il2Cpp3.exports.methodGetFlags(this, NULL);
}
/** Gets the implementation flags of the current method. */
get implementationFlags() {
const implementationFlagsPointer = Memory.alloc(Process.pointerSize);
Il2Cpp3.exports.methodGetFlags(this, implementationFlagsPointer);
return implementationFlagsPointer.readU32();
}
/** */
get fridaSignature() {
const types = [];
for (const parameter of this.parameters) {
types.push(parameter.type.fridaAlias);
}
if (!this.isStatic || Il2Cpp3.unityVersionIsBelow201830) {
types.unshift("pointer");
}
if (this.isInflated) {
types.push("pointer");
}
return types;
}
/** Gets the generic parameters of this generic method. */
get generics() {
if (!this.isGeneric) {
return [];
}
const types = this.object.method("GetGenericArguments").invoke();
return globalThis.Array.from(types).map((_) => new Il2Cpp3.Class(Il2Cpp3.exports.classFromObject(_)));
}
/** Determines whether this method is external. */
get isExternal() {
return (this.implementationFlags & 4096) != 0;
}
/** Determines whether this method is generic. */
get isGeneric() {
return !!Il2Cpp3.exports.methodIsGeneric(this);
}
/** Determines whether this method is inflated (generic with a concrete type parameter). */
get isInflated() {
return !!Il2Cpp3.exports.methodIsInflated(this);
}
/** Determines whether this method is static. */
get isStatic() {
return !Il2Cpp3.exports.methodIsInstance(this);
}
/** Determines whether this method is synchronized. */
get isSynchronized() {
return (this.implementationFlags & 32) != 0;
}
/** Gets the access modifier of this method. */
get modifier() {
switch (this.flags & 7) {
case 1:
return "private";
case 2:
return "private protected";
case 3:
return "internal";
case 4:
return "protected";
case 5:
return "protected internal";
case 6:
return "public";
}
}
/** Gets the name of this method. */
get name() {
return Il2Cpp3.exports.methodGetName(this).readUtf8String();
}
/** @internal */
get nativeFunction() {
return new NativeFunction(this.virtualAddress, this.returnType.fridaAlias, this.fridaSignature);
}
/** Gets the encompassing object of the current method. */
get object() {
return new Il2Cpp3.Object(Il2Cpp3.exports.methodGetObject(this, NULL));
}
/** Gets the amount of parameters of this method. */
get parameterCount() {
return Il2Cpp3.exports.methodGetParameterCount(this);
}
/** Gets the parameters of this method. */
get parameters() {
return globalThis.Array.from(globalThis.Array(this.parameterCount), (_, i) => {
const parameterName = Il2Cpp3.exports.methodGetParameterName(this, i).readUtf8String();
const parameterType = Il2Cpp3.exports.methodGetParameterType(this, i);
return new Il2Cpp3.Parameter(parameterName, i, new Il2Cpp3.Type(parameterType));
});
}
/** Gets the relative virtual address (RVA) of this method. */
get relativeVirtualAddress() {
return this.virtualAddress.sub(Il2Cpp3.module.base);
}
/** Gets the return type of this method. */
get returnType() {
return new Il2Cpp3.Type(Il2Cpp3.exports.methodGetReturnType(this));
}
/** Gets the virtual address (VA) of this method. */
get virtualAddress() {
const FilterTypeName = Il2Cpp3.corlib.class("System.Reflection.Module").initialize().field("FilterTypeName").value;
const FilterTypeNameMethodPointer = FilterTypeName.field("method_ptr").value;
const FilterTypeNameMethod = FilterTypeName.field("method").value;
const offset = FilterTypeNameMethod.offsetOf((_) => _.readPointer().equals(FilterTypeNameMethodPointer)) ?? raise("couldn't find the virtual address offset in the native method struct");
getter(Il2Cpp3.Method.prototype, "virtualAddress", function() {
return this.handle.add(offset).readPointer();
}, lazy);
Il2Cpp3.corlib.class("System.Reflection.Module").method(".cctor").invoke();
return this.virtualAddress;
}
/** Replaces the body of this method. */
set implementation(block) {
try {
Interceptor.replace(this.virtualAddress, this.wrap(block));
} catch (e) {
switch (e.message) {
case "access violation accessing 0x0":
raise(`couldn't set implementation for method ${this.name} as it has a NULL virtual address`);
case /unable to intercept function at \w+; please file a bug/.exec(e.message)?.input:
warn(`couldn't set implementation for method ${this.name} as it may be a thunk`);
break;
case "already replaced this function":
warn(`couldn't set implementation for method ${this.name} as it has already been replaced by a thunk`);
break;
default:
throw e;
}
}
}
/** Creates a generic instance of the current generic method. */
inflate(...classes) {
if (!this.isGeneric || this.generics.length != classes.length) {
for (const method of this.overloads()) {
if (method.isGeneric && method.generics.length == classes.length) {
return method.inflate(...classes);
}
}
raise(`could not find inflatable signature of method ${this.name} with ${classes.length} generic parameter(s)`);
}
const types = classes.map((_) => _.type.object);
const typeArray = Il2Cpp3.array(Il2Cpp3.corlib.class("System.Type"), types);
const inflatedMethodObject = this.object.method("MakeGenericMethod", 1).invoke(typeArray);
return new Il2Cpp3.Method(inflatedMethodObject.field("mhandle").value);
}
/** Invokes this method. */
invoke(...parameters) {
if (!this.isStatic) {
raise(`cannot invoke non-static method ${this.name} as it must be invoked throught a Il2Cpp.Object, not a Il2Cpp.Class`);
}
return this.invokeRaw(NULL, ...parameters);
}
/** @internal */
invokeRaw(instance, ...parameters) {
const allocatedParameters = parameters.map(Il2Cpp3.toFridaValue);
if (!this.isStatic || Il2Cpp3.unityVersionIsBelow201830) {
allocatedParameters.unshift(instance);
}
if (this.isInflated) {
allocatedParameters.push(this.handle);
}
try {
const returnValue = this.nativeFunction(...allocatedParameters);
return Il2Cpp3.fromFridaValue(returnValue, this.returnType);
} catch (e) {
if (e == null) {
raise("an unexpected native invocation exception occurred, this is due to parameter types mismatch");
}
switch (e.message) {
case "bad argument count":
raise(`couldn't invoke method ${this.name} as it needs ${this.parameterCount} parameter(s), not ${parameters.length}`);
case "expected a pointer":
case "expected number":
case "expected array with fields":
raise(`couldn't invoke method ${this.name} using incorrect parameter types`);
}
throw e;
}
}
/** Gets the overloaded method with the given parameter types. */
overload(...typeNamesOrClasses) {
const method = this.tryOverload(...typeNamesOrClasses);
return method ?? raise(`couldn't find overloaded method ${this.name}(${typeNamesOrClasses.map((_) => _ instanceof Il2Cpp3.Class ? _.type.name : _)})`);
}
/** @internal */
*overloads() {
for (const klass of this.class.hierarchy()) {
for (const method of klass.methods) {
if (this.name == method.name) {
yield method;
}
}
}
}
/** Gets the parameter with the given name. */
parameter(name) {
return this.tryParameter(name) ?? raise(`couldn't find parameter ${name} in method ${this.name}`);
}
/** Restore the original method implementation. */
revert() {
Interceptor.revert(this.virtualAddress);
Interceptor.flush();
}
/** Gets the overloaded method with the given parameter types. */
tryOverload(...typeNamesOrClasses) {
const minScore = typeNamesOrClasses.length * 1;
const maxScore = typeNamesOrClasses.length * 2;
let candidate = void 0;
loop: for (const method of this.overloads()) {
if (method.parameterCount != typeNamesOrClasses.length)
continue;
let score = 0;
let i = 0;
for (const parameter of method.parameters) {
const desiredTypeNameOrClass = typeNamesOrClasses[i];
if (desiredTypeNameOrClass instanceof Il2Cpp3.Class) {
if (parameter.type.is(desiredTypeNameOrClass.type)) {
score += 2;
} else if (parameter.type.class.isAssignableFrom(desiredTypeNameOrClass)) {
score += 1;
} else {
continue loop;
}
} else if (parameter.type.name == desiredTypeNameOrClass) {
score += 2;
} else {
continue loop;
}
i++;
}
if (score < minScore) {
continue;
} else if (score == maxScore) {
return method;
} else if (candidate == void 0 || score > candidate[0]) {
candidate = [score, method];
} else if (score == candidate[0]) {
let i2 = 0;
for (const parameter of candidate[1].parameters) {
if (parameter.type.class.isAssignableFrom(method.parameters[i2].type.class)) {
candidate = [score, method];
continue loop;
}
i2++;
}
}
}
return candidate?.[1];
}
/** Gets the parameter with the given name. */
tryParameter(name) {
return this.parameters.find((_) => _.name == name);
}
/** */
toString() {
return `${this.isStatic ? `static ` : ``}${this.returnType.name} ${this.name}${this.generics.length > 0 ? `<${this.generics.map((_) => _.type.name).join(",")}>` : ""}(${this.parameters.join(`, `)});${this.virtualAddress.isNull() ? `` : ` // 0x${this.relativeVirtualAddress.toString(16).padStart(8, `0`)}`}`;
}
/**
* @internal
* Binds the current method to a {@link Il2Cpp.Object} or a
* {@link Il2Cpp.ValueType} (also known as *instances*), so that it is
* possible to invoke it - see {@link Il2Cpp.Method.invoke} for
* details. \
* Binding a static method is forbidden.
*/
bind(instance) {
if (this.isStatic) {
raise(`cannot bind static method ${this.class.type.name}::${this.name} to an instance`);
}
return new Proxy(this, {
get(target, property, receiver) {
switch (property) {
case "invoke":
const handle = instance instanceof Il2Cpp3.ValueType ? target.class.isValueType ? instance.handle.sub(structMethodsRequireObjectInstances() ? Il2Cpp3.Object.headerSize : 0) : raise(`cannot invoke method ${target.class.type.name}::${target.name} against a value type, you must box it first`) : target.class.isValueType ? instance.handle.add(structMethodsRequireObjectInstances() ? 0 : Il2Cpp3.Object.headerSize) : instance.handle;
return target.invokeRaw.bind(target, handle);
case "overloads":
return function* () {
for (const method of target[property]()) {
if (!method.isStatic) {
yield method;
}
}
};
case "inflate":
case "overload":
case "tryOverload":
const member = Reflect.get(target, property).bind(receiver);
return function(...args) {
return member(...args)?.bind(instance);
};
}
return Reflect.get(target, property);
}
});
}
/** @internal */
wrap(block) {
const startIndex = +!this.isStatic | +Il2Cpp3.unityVersionIsBelow201830;
return new NativeCallback((...args) => {
const thisObject = this.isStatic ? this.class : this.class.isValueType ? new Il2Cpp3.ValueType(args[0].add(structMethodsRequireObjectInstances() ? Il2Cpp3.Object.headerSize : 0), this.class.type) : new Il2Cpp3.Object(args[0]);
const parameters = this.parameters.map((_, i) => Il2Cpp3.fromFridaValue(args[i + startIndex], _.type));
const result = block.call(thisObject, ...parameters);
return Il2Cpp3.toFridaValue(result);
}, this.returnType.fridaAlias, this.fridaSignature);
}
}
__decorate([
lazy
], Method.prototype, "class", null);
__decorate([
lazy
], Method.prototype, "flags", null);
__decorate([
lazy
], Method.prototype, "implementationFlags", null);
__decorate([
lazy
], Method.prototype, "fridaSignature", null);
__decorate([
lazy
], Method.prototype, "generics", null);
__decorate([
lazy
], Method.prototype, "isExternal", null);
__decorate([
lazy
], Method.prototype, "isGeneric", null);
__decorate([
lazy
], Method.prototype, "isInflated", null);
__decorate([
lazy
], Method.prototype, "isStatic", null);
__decorate([
lazy
], Method.prototype, "isSynchronized", null);
__decorate([
lazy
], Method.prototype, "modifier", null);
__decorate([
lazy
], Method.prototype, "name", null);
__decorate([
lazy
], Method.prototype, "nativeFunction", null);
__decorate([
lazy
], Method.prototype, "object", null);
__decorate([
lazy
], Method.prototype, "parameterCount", null);
__decorate([
lazy
], Method.prototype, "parameters", null);
__decorate([
lazy
], Method.prototype, "relativeVirtualAddress", null);
__decorate([
lazy
], Method.prototype, "returnType", null);
Il2Cpp3.Method = Method;
let structMethodsRequireObjectInstances = () => {
const object = Il2Cpp3.corlib.class("System.Int64").alloc();
object.field("m_value").value = 3735928559;
const result = object.method("Equals", 1).overload(object.class).invokeRaw(object, 3735928559);
return (structMethodsRequireObjectInstances = () => result)();
};
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Object2 extends NativeStruct {
/** Gets the Il2CppObject struct size, possibly equal to `Process.pointerSize * 2`. */
static get headerSize() {
return Il2Cpp3.corlib.class("System.Object").instanceSize;
}
/**
* Returns the same object, but having its parent class as class.
* It basically is the C# `base` keyword, so that parent members can be
* accessed.
*
* **Example** \
* Consider the following classes:
* ```csharp
* class Foo
* {
* int foo()
* {
* return 1;
* }
* }
* class Bar : Foo
* {
* new int foo()
* {
* return 2;
* }
* }
* ```
* then:
* ```ts
* const Bar: Il2Cpp.Class = ...;
* const bar = Bar.new();
*
* console.log(bar.foo()); // 2
* console.log(bar.base.foo()); // 1
* ```
*/
get base() {
if (this.class.parent == null) {
raise(`class ${this.class.type.name} has no parent`);
}
return new Proxy(this, {
get(target, property, receiver) {
if (property == "class") {
return Reflect.get(target, property).parent;
} else if (property == "base") {
return Reflect.getOwnPropertyDescriptor(Il2Cpp3.Object.prototype, property).get.bind(receiver)();
}
return Reflect.get(target, property);
}
});
}
/** Gets the class of this object. */
get class() {
return new Il2Cpp3.Class(Il2Cpp3.exports.objectGetClass(this));
}
/** Returns a monitor for this object. */
get monitor() {
return new Il2Cpp3.Object.Monitor(this);
}
/** Gets the size of the current object. */
get size() {
return Il2Cpp3.exports.objectGetSize(this);
}
/** Gets the non-static field with the given name of the current class hierarchy. */
field(name) {
return this.tryField(name) ?? raise(`couldn't find non-static field ${name} in hierarchy of class ${this.class.type.name}`);
}
/** Gets the non-static method with the given name (and optionally parameter count) of the current class hierarchy. */
method(name, parameterCount = -1) {
return this.tryMethod(name, parameterCount) ?? raise(`couldn't find non-static method ${name} in hierarchy of class ${this.class.type.name}`);
}
/** Creates a reference to this object. */
ref(pin) {
return new Il2Cpp3.GCHandle(Il2Cpp3.exports.gcHandleNew(this, +pin));
}
/** Gets the correct virtual method from the given virtual method. */
virtualMethod(method) {
return new Il2Cpp3.Method(Il2Cpp3.exports.objectGetVirtualMethod(this, method)).bind(this);
}
/** Gets the non-static field with the given name of the current class hierarchy, if it exists. */
tryField(name) {
const field = this.class.tryField(name);
if (field?.isStatic) {
for (const klass of this.class.hierarchy({ includeCurrent: false })) {
for (const field2 of klass.fields) {
if (field2.name == name && !field2.isStatic) {
return field2.bind(this);
}
}
}
return void 0;
}
return field?.bind(this);
}
/** Gets the non-static method with the given name (and optionally parameter count) of the current class hierarchy, if it exists. */
tryMethod(name, parameterCount = -1) {
const method = this.class.tryMethod(name, parameterCount);
if (method?.isStatic) {
for (const klass of this.class.hierarchy()) {
for (const method2 of klass.methods) {
if (method2.name == name && !method2.isStatic && (parameterCount < 0 || method2.parameterCount == parameterCount)) {
return method2.bind(this);
}
}
}
return void 0;
}
return method?.bind(this);
}
/** */
toString() {
return this.isNull() ? "null" : this.method("ToString", 0).invoke().content ?? "null";
}
/** Unboxes the value type (either a primitive, a struct or an enum) out of this object. */
unbox() {
return this.class.isValueType ? new Il2Cpp3.ValueType(Il2Cpp3.exports.objectUnbox(this), this.class.type) : raise(`couldn't unbox instances of ${this.class.type.name} as they are not value types`);
}
/** Creates a weak reference to this object. */
weakRef(trackResurrection) {
return new Il2Cpp3.GCHandle(Il2Cpp3.exports.gcHandleNewWeakRef(this, +trackResurrection));
}
}
__decorate([
lazy
], Object2.prototype, "class", null);
__decorate([
lazy
], Object2.prototype, "size", null);
__decorate([
lazy
], Object2, "headerSize", null);
Il2Cpp3.Object = Object2;
(function(Object3) {
class Monitor {
handle;
/** @internal */
constructor(handle) {
this.handle = handle;
}
/** Acquires an exclusive lock on the current object. */
enter() {
return Il2Cpp3.exports.monitorEnter(this.handle);
}
/** Release an exclusive lock on the current object. */
exit() {
return Il2Cpp3.exports.monitorExit(this.handle);
}
/** Notifies a thread in the waiting queue of a change in the locked object's state. */
pulse() {
return Il2Cpp3.exports.monitorPulse(this.handle);
}
/** Notifies all waiting threads of a change in the object's state. */
pulseAll() {
return Il2Cpp3.exports.monitorPulseAll(this.handle);
}
/** Attempts to acquire an exclusive lock on the current object. */
tryEnter(timeout) {
return !!Il2Cpp3.exports.monitorTryEnter(this.handle, timeout);
}
/** Releases the lock on an object and attempts to block the current thread until it reacquires the lock. */
tryWait(timeout) {
return !!Il2Cpp3.exports.monitorTryWait(this.handle, timeout);
}
/** Releases the lock on an object and blocks the current thread until it reacquires the lock. */
wait() {
return Il2Cpp3.exports.monitorWait(this.handle);
}
}
Object3.Monitor = Monitor;
})(Object2 = Il2Cpp3.Object || (Il2Cpp3.Object = {}));
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Parameter {
/** Name of this parameter. */
name;
/** Position of this parameter. */
position;
/** Type of this parameter. */
type;
constructor(name, position, type) {
this.name = name;
this.position = position;
this.type = type;
}
/** */
toString() {
return `${this.type.name} ${this.name}`;
}
}
Il2Cpp3.Parameter = Parameter;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Pointer extends NativeStruct {
type;
constructor(handle, type) {
super(handle);
this.type = type;
}
/** Gets the element at the given index. */
get(index) {
return Il2Cpp3.read(this.handle.add(index * this.type.class.arrayElementSize), this.type);
}
/** Reads the given amount of elements starting at the given offset. */
read(length, offset = 0) {
const values = new globalThis.Array(length);
for (let i = 0; i < length; i++) {
values[i] = this.get(i + offset);
}
return values;
}
/** Sets the given element at the given index */
set(index, value) {
Il2Cpp3.write(this.handle.add(index * this.type.class.arrayElementSize), value, this.type);
}
/** */
toString() {
return this.handle.toString();
}
/** Writes the given elements starting at the given index. */
write(values, offset = 0) {
for (let i = 0; i < values.length; i++) {
this.set(i + offset, values[i]);
}
}
}
Il2Cpp3.Pointer = Pointer;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Reference extends NativeStruct {
type;
constructor(handle, type) {
super(handle);
this.type = type;
}
/** Gets the element referenced by the current reference. */
get value() {
return Il2Cpp3.read(this.handle, this.type);
}
/** Sets the element referenced by the current reference. */
set value(value) {
Il2Cpp3.write(this.handle, value, this.type);
}
/** */
toString() {
return this.isNull() ? "null" : `->${this.value}`;
}
}
Il2Cpp3.Reference = Reference;
function reference(value, type) {
const handle = Memory.alloc(Process.pointerSize);
switch (typeof value) {
case "boolean":
return new Il2Cpp3.Reference(handle.writeS8(+value), Il2Cpp3.corlib.class("System.Boolean").type);
case "number":
switch (type?.enumValue) {
case Il2Cpp3.Type.Enum.UBYTE:
return new Il2Cpp3.Reference(handle.writeU8(value), type);
case Il2Cpp3.Type.Enum.BYTE:
return new Il2Cpp3.Reference(handle.writeS8(value), type);
case Il2Cpp3.Type.Enum.CHAR:
case Il2Cpp3.Type.Enum.USHORT:
return new Il2Cpp3.Reference(handle.writeU16(value), type);
case Il2Cpp3.Type.Enum.SHORT:
return new Il2Cpp3.Reference(handle.writeS16(value), type);
case Il2Cpp3.Type.Enum.UINT:
return new Il2Cpp3.Reference(handle.writeU32(value), type);
case Il2Cpp3.Type.Enum.INT:
return new Il2Cpp3.Reference(handle.writeS32(value), type);
case Il2Cpp3.Type.Enum.ULONG:
return new Il2Cpp3.Reference(handle.writeU64(value), type);
case Il2Cpp3.Type.Enum.LONG:
return new Il2Cpp3.Reference(handle.writeS64(value), type);
case Il2Cpp3.Type.Enum.FLOAT:
return new Il2Cpp3.Reference(handle.writeFloat(value), type);
case Il2Cpp3.Type.Enum.DOUBLE:
return new Il2Cpp3.Reference(handle.writeDouble(value), type);
}
case "object":
if (value instanceof Il2Cpp3.ValueType || value instanceof Il2Cpp3.Pointer) {
return new Il2Cpp3.Reference(value.handle, value.type);
} else if (value instanceof Il2Cpp3.Object) {
return new Il2Cpp3.Reference(handle.writePointer(value), value.class.type);
} else if (value instanceof Il2Cpp3.String || value instanceof Il2Cpp3.Array) {
return new Il2Cpp3.Reference(handle.writePointer(value), value.object.class.type);
} else if (value instanceof NativePointer) {
switch (type?.enumValue) {
case Il2Cpp3.Type.Enum.NUINT:
case Il2Cpp3.Type.Enum.NINT:
return new Il2Cpp3.Reference(handle.writePointer(value), type);
}
} else if (value instanceof Int64) {
return new Il2Cpp3.Reference(handle.writeS64(value), Il2Cpp3.corlib.class("System.Int64").type);
} else if (value instanceof UInt64) {
return new Il2Cpp3.Reference(handle.writeU64(value), Il2Cpp3.corlib.class("System.UInt64").type);
}
default:
raise(`couldn't create a reference to ${value} using an unhandled type ${type?.name}`);
}
}
Il2Cpp3.reference = reference;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class String2 extends NativeStruct {
/** Gets the content of this string. */
get content() {
return Il2Cpp3.exports.stringGetChars(this).readUtf16String(this.length);
}
/** @unsafe Sets the content of this string - it may write out of bounds! */
set content(value) {
const offset = Il2Cpp3.string("vfsfitvnm").handle.offsetOf((_) => _.readInt() == 9) ?? raise("couldn't find the length offset in the native string struct");
globalThis.Object.defineProperty(Il2Cpp3.String.prototype, "content", {
set(value2) {
Il2Cpp3.exports.stringGetChars(this).writeUtf16String(value2 ?? "");
this.handle.add(offset).writeS32(value2?.length ?? 0);
}
});
this.content = value;
}
/** Gets the length of this string. */
get length() {
return Il2Cpp3.exports.stringGetLength(this);
}
/** Gets the encompassing object of the current string. */
get object() {
return new Il2Cpp3.Object(this);
}
/** */
toString() {
return this.isNull() ? "null" : `"${this.content}"`;
}
}
Il2Cpp3.String = String2;
function string(content) {
return new Il2Cpp3.String(Il2Cpp3.exports.stringNew(Memory.allocUtf8String(content ?? "")));
}
Il2Cpp3.string = string;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class Thread extends NativeStruct {
/** Gets the native id of the current thread. */
get id() {
let get = function() {
return this.internal.field("thread_id").value.toNumber();
};
if (Process.platform != "windows") {
const currentThreadId = Process.getCurrentThreadId();
const currentPosixThread = ptr(get.apply(Il2Cpp3.currentThread));
const offset = currentPosixThread.offsetOf((_) => _.readS32() == currentThreadId, 1024) ?? raise(`couldn't find the offset for determining the kernel id of a posix thread`);
const _get = get;
get = function() {
return ptr(_get.apply(this)).add(offset).readS32();
};
}
getter(Il2Cpp3.Thread.prototype, "id", get, lazy);
return this.id;
}
/** Gets the encompassing internal object (System.Threding.InternalThreead) of the current thread. */
get internal() {
return this.object.tryField("internal_thread")?.value ?? this.object;
}
/** Determines whether the current thread is the garbage collector finalizer one. */
get isFinalizer() {
return !Il2Cpp3.exports.threadIsVm(this);
}
/** Gets the managed id of the current thread. */
get managedId() {
return this.object.method("get_ManagedThreadId").invoke();
}
/** Gets the encompassing object of the current thread. */
get object() {
return new Il2Cpp3.Object(this);
}
/** @internal */
get staticData() {
return this.internal.field("static_data").value;
}
/** @internal */
get synchronizationContext() {
const get_ExecutionContext = this.object.tryMethod("GetMutableExecutionContext") ?? this.object.method("get_ExecutionContext");
const executionContext = get_ExecutionContext.invoke();
const synchronizationContext = executionContext.tryField("_syncContext")?.value ?? executionContext.tryMethod("get_SynchronizationContext")?.invoke() ?? this.tryLocalValue(Il2Cpp3.corlib.class("System.Threading.SynchronizationContext"));
return synchronizationContext?.asNullable() ?? null;
}
/** Detaches the thread from the application domain. */
detach() {
return Il2Cpp3.exports.threadDetach(this);
}
/** Schedules a callback on the current thread. */
schedule(block) {
const Post = this.synchronizationContext?.tryMethod("Post");
if (Post == null) {
return Process.runOnThread(this.id, block);
}
return new Promise((resolve) => {
const delegate = Il2Cpp3.delegate(Il2Cpp3.corlib.class("System.Threading.SendOrPostCallback"), () => {
const result = block();
setImmediate(() => resolve(result));
});
Script.bindWeak(globalThis, () => {
delegate.field("method_ptr").value = delegate.field("invoke_impl").value = Il2Cpp3.exports.domainGet;
});
Post.invoke(delegate, NULL);
});
}
/** @internal */
tryLocalValue(klass) {
for (let i = 0; i < 16; i++) {
const base = this.staticData.add(i * Process.pointerSize).readPointer();
if (!base.isNull()) {
const object = new Il2Cpp3.Object(base.readPointer()).asNullable();
if (object?.class?.isSubclassOf(klass, false)) {
return object;
}
}
}
}
}
__decorate([
lazy
], Thread.prototype, "internal", null);
__decorate([
lazy
], Thread.prototype, "isFinalizer", null);
__decorate([
lazy
], Thread.prototype, "managedId", null);
__decorate([
lazy
], Thread.prototype, "object", null);
__decorate([
lazy
], Thread.prototype, "staticData", null);
__decorate([
lazy
], Thread.prototype, "synchronizationContext", null);
Il2Cpp3.Thread = Thread;
getter(Il2Cpp3, "attachedThreads", () => {
if (Il2Cpp3.exports.threadGetAttachedThreads.isNull()) {
const currentThreadHandle = Il2Cpp3.currentThread?.handle ?? raise("Current thread is not attached to IL2CPP");
const pattern = currentThreadHandle.toMatchPattern();
const threads = [];
for (const range of Process.enumerateRanges("rw-")) {
if (range.file == void 0) {
const matches = Memory.scanSync(range.base, range.size, pattern);
if (matches.length == 1) {
while (true) {
const handle = matches[0].address.sub(matches[0].size * threads.length).readPointer();
if (handle.isNull() || !handle.readPointer().equals(currentThreadHandle.readPointer())) {
break;
}
threads.unshift(new Il2Cpp3.Thread(handle));
}
break;
}
}
}
return threads;
}
return readNativeList(Il2Cpp3.exports.threadGetAttachedThreads).map((_) => new Il2Cpp3.Thread(_));
});
getter(Il2Cpp3, "currentThread", () => {
return new Il2Cpp3.Thread(Il2Cpp3.exports.threadGetCurrent()).asNullable();
});
getter(Il2Cpp3, "mainThread", () => {
return Il2Cpp3.attachedThreads[0];
});
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
let Type = class Type extends NativeStruct {
/** */
static get Enum() {
const _ = (_2, block = (_3) => _3) => block(Il2Cpp3.corlib.class(_2)).type.enumValue;
const initial = {
VOID: _("System.Void"),
BOOLEAN: _("System.Boolean"),
CHAR: _("System.Char"),
BYTE: _("System.SByte"),
UBYTE: _("System.Byte"),
SHORT: _("System.Int16"),
USHORT: _("System.UInt16"),
INT: _("System.Int32"),
UINT: _("System.UInt32"),
LONG: _("System.Int64"),
ULONG: _("System.UInt64"),
NINT: _("System.IntPtr"),
NUINT: _("System.UIntPtr"),
FLOAT: _("System.Single"),
DOUBLE: _("System.Double"),
POINTER: _("System.IntPtr", (_2) => _2.field("m_value")),
VALUE_TYPE: _("System.Decimal"),
OBJECT: _("System.Object"),
STRING: _("System.String"),
CLASS: _("System.Array"),
ARRAY: _("System.Void", (_2) => _2.arrayClass),
NARRAY: _("System.Void", (_2) => new Il2Cpp3.Class(Il2Cpp3.exports.classGetArrayClass(_2, 2))),
GENERIC_INSTANCE: _("System.Int32", (_2) => _2.interfaces.find((_3) => _3.name.endsWith("`1")))
};
Reflect.defineProperty(this, "Enum", { value: initial });
return addFlippedEntries({
...initial,
VAR: _("System.Action`1", (_2) => _2.generics[0]),
MVAR: _("System.Array", (_2) => _2.method("AsReadOnly", 1).generics[0])
});
}
/** Gets the class of this type. */
get class() {
return new Il2Cpp3.Class(Il2Cpp3.exports.typeGetClass(this));
}
/** */
get fridaAlias() {
function getValueTypeFields(type) {
const instanceFields = type.class.fields.filter((_) => !_.isStatic);
return instanceFields.length == 0 ? ["char"] : instanceFields.map((_) => _.type.fridaAlias);
}
if (this.isByReference) {
return "pointer";
}
switch (this.enumValue) {
case Il2Cpp3.Type.Enum.VOID:
return "void";
case Il2Cpp3.Type.Enum.BOOLEAN:
return "bool";
case Il2Cpp3.Type.Enum.CHAR:
return "uchar";
case Il2Cpp3.Type.Enum.BYTE:
return "int8";
case Il2Cpp3.Type.Enum.UBYTE:
return "uint8";
case Il2Cpp3.Type.Enum.SHORT:
return "int16";
case Il2Cpp3.Type.Enum.USHORT:
return "uint16";
case Il2Cpp3.Type.Enum.INT:
return "int32";
case Il2Cpp3.Type.Enum.UINT:
return "uint32";
case Il2Cpp3.Type.Enum.LONG:
return "int64";
case Il2Cpp3.Type.Enum.ULONG:
return "uint64";
case Il2Cpp3.Type.Enum.FLOAT:
return "float";
case Il2Cpp3.Type.Enum.DOUBLE:
return "double";
case Il2Cpp3.Type.Enum.NINT:
case Il2Cpp3.Type.Enum.NUINT:
case Il2Cpp3.Type.Enum.POINTER:
case Il2Cpp3.Type.Enum.STRING:
case Il2Cpp3.Type.Enum.ARRAY:
case Il2Cpp3.Type.Enum.NARRAY:
return "pointer";
case Il2Cpp3.Type.Enum.VALUE_TYPE:
return this.class.isEnum ? this.class.baseType.fridaAlias : getValueTypeFields(this);
case Il2Cpp3.Type.Enum.CLASS:
case Il2Cpp3.Type.Enum.OBJECT:
case Il2Cpp3.Type.Enum.GENERIC_INSTANCE:
return this.class.isStruct ? getValueTypeFields(this) : this.class.isEnum ? this.class.baseType.fridaAlias : "pointer";
default:
return "pointer";
}
}
/** Determines whether this type is passed by reference. */
get isByReference() {
return this.name.endsWith("&");
}
/** Determines whether this type is primitive. */
get isPrimitive() {
switch (this.enumValue) {
case Il2Cpp3.Type.Enum.BOOLEAN:
case Il2Cpp3.Type.Enum.CHAR:
case Il2Cpp3.Type.Enum.BYTE:
case Il2Cpp3.Type.Enum.UBYTE:
case Il2Cpp3.Type.Enum.SHORT:
case Il2Cpp3.Type.Enum.USHORT:
case Il2Cpp3.Type.Enum.INT:
case Il2Cpp3.Type.Enum.UINT:
case Il2Cpp3.Type.Enum.LONG:
case Il2Cpp3.Type.Enum.ULONG:
case Il2Cpp3.Type.Enum.FLOAT:
case Il2Cpp3.Type.Enum.DOUBLE:
case Il2Cpp3.Type.Enum.NINT:
case Il2Cpp3.Type.Enum.NUINT:
return true;
default:
return false;
}
}
/** Gets the name of this type. */
get name() {
const handle = Il2Cpp3.exports.typeGetName(this);
try {
return handle.readUtf8String();
} finally {
Il2Cpp3.free(handle);
}
}
/** Gets the encompassing object of the current type. */
get object() {
return new Il2Cpp3.Object(Il2Cpp3.exports.typeGetObject(this));
}
/** Gets the {@link Il2Cpp.Type.Enum} value of the current type. */
get enumValue() {
return Il2Cpp3.exports.typeGetTypeEnum(this);
}
is(other) {
if (Il2Cpp3.exports.typeEquals.isNull()) {
return this.object.method("Equals").invoke(other.object);
}
return !!Il2Cpp3.exports.typeEquals(this, other);
}
/** */
toString() {
return this.name;
}
};
__decorate([
lazy
], Type.prototype, "class", null);
__decorate([
lazy
], Type.prototype, "fridaAlias", null);
__decorate([
lazy
], Type.prototype, "isByReference", null);
__decorate([
lazy
], Type.prototype, "isPrimitive", null);
__decorate([
lazy
], Type.prototype, "name", null);
__decorate([
lazy
], Type.prototype, "object", null);
__decorate([
lazy
], Type.prototype, "enumValue", null);
__decorate([
lazy
], Type, "Enum", null);
Type = __decorate([
recycle
], Type);
Il2Cpp3.Type = Type;
})(Il2Cpp2 || (Il2Cpp2 = {}));
var Il2Cpp2;
(function(Il2Cpp3) {
class ValueType extends NativeStruct {
type;
constructor(handle, type) {
super(handle);
this.type = type;
}
/** Boxes the current value type in a object. */
box() {
return new Il2Cpp3.Object(Il2Cpp3.exports.valueTypeBox(this.type.class, this));
}
/** Gets the non-static field with the given name of the current class hierarchy. */
field(name) {
return this.tryField(name) ?? raise(`couldn't find non-static field ${name} in hierarchy of class ${this.type.name}`);
}
/** Gets the non-static method with the given name (and optionally parameter count) of the current class hierarchy. */
method(name, parameterCount = -1) {
return this.tryMethod(name, parameterCount) ?? raise(`couldn't find non-static method ${name} in hierarchy of class ${this.type.name}`);
}
/** Gets the non-static field with the given name of the current class hierarchy, if it exists. */
tryField(name) {
const field = this.type.class.tryField(name);
if (field?.isStatic) {
for (const klass of this.type.class.hierarchy()) {
for (const field2 of klass.fields) {
if (field2.name == name && !field2.isStatic) {
return field2.bind(this);
}
}
}
return void 0;
}
return field?.bind(this);
}
/** Gets the non-static method with the given name (and optionally parameter count) of the current class hierarchy, if it exists. */
tryMethod(name, parameterCount = -1) {
const method = this.type.class.tryMethod(name, parameterCount);
if (method?.isStatic) {
for (const klass of this.type.class.hierarchy()) {
for (const method2 of klass.methods) {
if (method2.name == name && !method2.isStatic && (parameterCount < 0 || method2.parameterCount == parameterCount)) {
return method2.bind(this);
}
}
}
return void 0;
}
return method?.bind(this);
}
/** */
toString() {
const ToString = this.method("ToString", 0);
return this.isNull() ? "null" : (
// If ToString is defined within a value type class, we can
// avoid a boxing operation.
ToString.class.isValueType ? ToString.invoke().content ?? "null" : this.box().toString() ?? "null"
);
}
}
Il2Cpp3.ValueType = ValueType;
})(Il2Cpp2 || (Il2Cpp2 = {}));
globalThis.Il2Cpp = Il2Cpp2;
"use strict";
// ================= 保龄球场景实体探针(bowl_probe) =================
// 独立探针:前置是 frida-il2cpp-bridge bundle(由 _build_bowl_probe.py 拼接),
// 所以这里可以直接用 Il2Cpp / Il2Cpp.perform,不需要 gym_agent 的任何东西。
// 目的:回答 P0 —— 保龄球/球瓶的 sceneItem 是「场景里已存在」还是「需服务端生成」。
var BP_CACHE = {};
function bpClass(full) {
if (BP_CACHE[full] !== undefined) return BP_CACHE[full];
var res = null;
// 快路径:image.class(fullName) 直接查(gym_agent 同款)
try {
var asms = Il2Cpp.domain.assemblies;
for (var i = 0; i < asms.length; i++) {
try { var c = asms[i].image.class(full); if (c) { res = c; break; } } catch (e) { }
}
} catch (e) { }
BP_CACHE[full] = res;
return res;
}
function bpStr(v) {
if (v === null || v === undefined) return null;
try { if (v.isNull && v.isNull()) return null; } catch (e) { }
try {
var c = v.content;
if (typeof c === "string") return c;
} catch (e) { }
try { return String(v); } catch (e) { }
return null;
}
function bpNum(v) {
try {
if (v === null || v === undefined) return null;
if (typeof v === "number" || typeof v === "bigint") return Number(v);
if (typeof v === "boolean") return v;
var x = Number(v);
if (!isNaN(x)) return x;
} catch (e) { }
return null;
}
// ★ 2026-09-24 修复:IL2CPP 里 System.String 的类名是 "System.String"(带命名空间),
// 旧代码只判 === "String" ⇒ 判等恒假 ⇒ 所有字符串字段一律落到 "<obj>"。
// 后果:paths() 的「按资源名搜」从未生效(命中全靠 BP_BOWL_CFGIDS 白名单兜底),
// so 它永远搜不到「以另一个 cfg 存在的球瓶」。此函数统一收口。
// ★★ 2026-09-24 二次修复(致命):上一版写成了
// return bpIsStr(cn) || cn === "System.String" || ...
// 第一个操作数就是自己 ⇒ **无限递归** ⇒ RangeError: Maximum call stack size exceeded
// ⇒ bpField() 一调用就抛 ⇒ holds() / bowlst() / paths() / blobstat() 全部只返回
// out.err="Maximum call stack size exceeded",一条有效数据都拿不到。
// 正确写法就是下面这行纯字符串比对,**不许再引用自身**。
function bpIsStr(cn) {
return cn === "String" || cn === "System.String" || cn === "string";
}
// 读字段:字符串类字段自动取 content;枚举自动取数值
function bpField(o, name) {
var v;
try { v = o.field(name).value; } catch (e) { return "<err>"; }
if (v === null || v === undefined) return null;
try { if (v.isNull && v.isNull()) return null; } catch (e) { }
var cn = "";
try { cn = String(v.class.name); } catch (e) { }
if (bpIsStr(cn)) return bpStr(v);
if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return Number(v);
try { if (v.class.isEnum) return bpNum(bpEnumNum(v)); } catch (e) { }
return "<obj>";
}
// 枚举对象 → 数值
function bpEnumNum(v) {
// frida-il2cpp-bridge:enum 实例的 value 是底层整数
try { var u = v.value; if (typeof u === "number" || typeof u === "bigint") return u; } catch (e) { }
try { var f = v.field("value__").value; return f; } catch (e) { }
try { return v.method("get_value__").invoke(); } catch (e) { }
return null;
}
var BP_BOWL_MODELIDS = [52007069, 52007070, 52007071, 52007072, 52007073];
var BP_BOWL_CFGIDS = [80002967, 80002968, 80002969, 80002970, 80003145, 80004058];
function bpRpc(name, fn) { return { name: name, fn: fn }; }
var BOWL_RPCS = [];
// ---------- R1: sceneItem 注册表扫描(决定性) ----------
BOWL_RPCS.push(bpRpc("paths", function (kw, maxOut) {
var t0 = Date.now();
var out = { rpc: "paths", kw: String(kw || ""), total: 0, hits: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemPathInfoRuntime");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.total = objs ? objs.length : 0;
var kwl = out.kw.toLowerCase();
var limit = parseInt(maxOut || "400", 10);
var F = ["path", "pathId", "modelId", "sceneItemConfigId", "interactionType",
"sizeType", "destructionType", "isConfigCanMove", "isConfigCanMind"];
for (var i = 0; i < out.total; i++) {
var o = objs[i];
var rec = null;
try {
var p = bpField(o, "path");
var mid = bpField(o, "modelId");
var cid = bpField(o, "sceneItemConfigId");
var pid = bpField(o, "pathId");
var ps = (p === null ? "" : String(p));
var hit = false;
if (kwl) {
hit = ps.toLowerCase().indexOf(kwl) >= 0;
} else {
hit = true;
}
if (!hit && BP_BOWL_MODELIDS.indexOf(mid) >= 0) hit = true;
if (!hit && BP_BOWL_CFGIDS.indexOf(cid) >= 0) hit = true;
if (hit) {
rec = { path: ps, pathId: pid, modelId: mid, sceneItemConfigId: cid };
for (var k = 4; k < F.length; k++) {
try { rec[F[k]] = bpField(o, F[k]); } catch (e) { }
}
}
} catch (e) { }
if (rec) {
out.hits.push(rec);
if (out.hits.length >= limit) { out.capped = true; break; }
}
}
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) {
out.err = String(e && e.message ? e.message : e);
}
return JSON.stringify(out);
}));
// ---------- R2: 枚举真值(数值) ----------
BOWL_RPCS.push(bpRpc("enumv", function (cls) {
var out = { rpc: "enumv", cls: String(cls || ""), items: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var flds = K.fields;
for (var i = 0; i < flds.length; i++) {
var f = flds[i];
try {
if (!f.isStatic) continue;
if (String(f.name).indexOf("NativeFieldInfoPtr") === 0) continue;
if (String(f.name) === "value__") continue;
var v = f.value;
var n = bpNum(bpEnumNum(v));
if (n === null) n = bpNum(v);
out.items.push(String(f.name) + "=" + String(n));
} catch (e) { out.items.push(String(f.name) + "=ERR"); }
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R3: SceneItemMgr 集合/网格状态 ----------
BOWL_RPCS.push(bpRpc("simgrid", function () {
var out = { rpc: "simgrid", err: null };
try {
var K = bpClass("LX6.Item.SceneItemMgr");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var inst = null;
try { inst = K.method("get_Instance").invoke(); } catch (e) { }
if (!inst || (inst.isNull && inst.isNull())) {
var all = Il2Cpp.gc.choose(K);
inst = (all && all.length) ? all[0] : null;
}
if (!inst) { out.err = "no-instance"; return JSON.stringify(out); }
var colls = ["SceneItemInWorld", "SceneItemInAwake", "BallsInstanceIdSet",
"BasketballInstanceIdSet", "interactItemIds", "AllSceneItemGrid",
"GridIndexInfoDic", "IdToMetro", "waitToAddSceneItemId",
"sceneItemOccupyData", "playerOccupyDataMap", "noClearWhiteList"];
out.counts = {};
for (var i = 0; i < colls.length; i++) {
var n = colls[i], c = null, kind = "";
var v;
try { v = inst.field(n).value; } catch (e) { out.counts[n] = "nofield"; continue; }
if (v === null || v === undefined) { out.counts[n] = "null"; continue; }
try { if (v.isNull && v.isNull()) { out.counts[n] = "null"; continue; } } catch (e) { }
try { c = Number(v.method("get_Count").invoke()); kind = "count"; } catch (e) { }
if (c === null || isNaN(c)) {
try { c = Number(v.method("get_Length").invoke()); kind = "len"; } catch (e) { }
}
if (c === null || isNaN(c)) {
try { c = Number(v.method("get_Count").invoke()); kind = "count"; } catch (e) { }
}
out.counts[n] = (c === null || isNaN(c)) ? "<obj>" : (kind + ":" + c);
}
// BallsInstanceIdSet 内容(若有)
try {
var bs = inst.field("BallsInstanceIdSet").value;
if (bs && !(bs.isNull && bs.isNull())) {
var cnt = Number(bs.method("get_Count").invoke());
var en = bs.method("GetEnumerator").invoke();
var arr = [];
for (var q = 0; q < Math.min(cnt, 64); q++) {
if (!en.method("MoveNext").invoke()) break;
arr.push(String(en.method("get_Current").invoke()));
}
out.balls = { count: cnt, sample: arr };
}
} catch (e) { out.ballsErr = String(e && e.message ? e.message : e); }
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R4: 玩家坐标 ----------
BOWL_RPCS.push(bpRpc("pos", function () {
var out = { rpc: "pos", err: null };
try {
var KU = bpClass("LX6.Game.MyPlayerManager");
if (!KU) { out.err = "no-player-mgr"; return JSON.stringify(out); }
var u = KU.method("get_PlayerUnit").invoke();
if (!u || (u.isNull && u.isNull())) { out.err = "no-player-unit"; return JSON.stringify(out); }
var v = u.field("lastPosition").value;
out.pos = [v.handle.readFloat(), v.handle.add(4).readFloat(), v.handle.add(8).readFloat()];
out.unitId = String(u.field("instanceId").value);
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R6: 活着的 SceneItemHold 实例(决定性:球/瓶在不在世界里) ----------
BOWL_RPCS.push(bpRpc("holds", function (maxOut, kw) {
var t0 = Date.now();
var out = { rpc: "holds", count: 0, byCfg: {}, hits: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemHold");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.count = objs ? objs.length : 0;
var limit = parseInt(maxOut || "40", 10);
var kwl = String(kw || "").toLowerCase();
// 字段名只取一次
var names = [];
try {
var c = K, guard = 0;
while (c && guard++ < 8) {
for (var i = 0; i < c.fields.length; i++) {
var fn = String(c.fields[i].name);
if (fn.indexOf("Native") === 0) continue;
if (names.indexOf(fn) < 0) names.push(fn);
}
c = c.baseType;
}
} catch (e) { out.fieldErr = String(e && e.message ? e.message : e); }
out.fieldCount = names.length;
for (var q = 0; q < out.count; q++) {
var o = objs[q];
var rec = {};
var goName = null;
for (var k = 0; k < names.length; k++) {
var nm = names[k];
var val;
try { val = bpField(o, nm); } catch (e) { continue; }
if (val === "<obj>") {
// 尝试拿 GameObject 名字 / 配置名
try {
var ob = o.field(nm).value;
var cn = String(ob.class.name);
if (cn === "GameObject" || (ob.class.namespace === "UnityEngine" && cn === "GameObject")) {
var g = bpStr(ob.method("get_name").invoke());
rec[nm] = "<GO:" + g + ">";
goName = g;
} else {
var sf = null;
try { sf = bpStr(ob.field("name").value); } catch (e2) { }
if (sf === null) { try { sf = bpStr(ob.method("get_name").invoke()); } catch (e2) { } }
rec[nm] = "<" + cn + (sf ? ":" + sf : "") + ">";
}
} catch (e2) { rec[nm] = "<obj>"; }
} else {
rec[nm] = val;
}
}
var blob = JSON.stringify(rec).toLowerCase();
var keep = (kwl === "" ? true : blob.indexOf(kwl) >= 0);
// 也按保龄球 cfg id 抓
if (!keep) {
for (var z = 0; z < BP_BOWL_CFGIDS.length; z++) {
if (blob.indexOf('"' + BP_BOWL_CFGIDS[z] + '"') >= 0) { keep = true; break; }
}
}
if (keep && out.hits.length < limit) {
rec.__go = goName;
out.hits.push(rec);
}
// 统计 cfg id
try {
var cfg = rec["sceneItemConfigId"] !== undefined ? rec["sceneItemConfigId"]
: (rec["SceneItemConfigId"] !== undefined ? rec["SceneItemConfigId"] : rec["cfgId"]);
if (cfg !== undefined && cfg !== null) {
var kk = String(cfg);
out.byCfg[kk] = (out.byCfg[kk] || 0) + 1;
}
} catch (e3) { }
}
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R6b: 极轻量 hold 汇总(★ 自动监视专用) ----------
// 与 holds() 的区别:只读 1~2 个字段、不遍历类字段名、不 JSON.stringify 全字段
// ⇒ 可以每 3~5 秒调一次做趋势监视(holds() 全量太贵,只能手动按需调)。
//
// ★ 决胜读数:bowlCfg["80003145"].obj = 「场景里真实存在、且带 GameObject 的球瓶 hold 个数」。
// · 交互前 > 0 且交互后变 0 ⇒ 就是 hideSceneItem 干的(SetActive(false))
// · 交互后 obj 也 > 0 ⇒ 瓶还在,问题在渲染/位置而不是 hold
// · 全程都是 0/缺失 ⇒ 关卡里真没有球瓶场景物,只能靠 BF1 骗出来
BOWL_RPCS.push(bpRpc("holdsum", function () {
var t0 = Date.now();
var out = { rpc: "holdsum", count: 0, withObj: 0, cfgs: 0, bowlCfg: {}, top: [], ms: 0, err: null };
try {
var K = bpClass("LX6.Item.SceneItemHold");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.count = objs ? objs.length : 0;
var hist = {};
var want = {};
for (var w = 0; w < BP_BOWL_CFGIDS.length; w++) {
want[String(BP_BOWL_CFGIDS[w])] = { n: 0, obj: 0, xMin: null, xMax: null, y: null, s: null };
}
var CFG_FIELDS = ["sceneItemConfigId", "SceneItemConfigId", "cfgId", "CfgId", "configId", "ConfigId"];
for (var q = 0; q < out.count; q++) {
var h = objs[q];
// ---- ① SceneItemObj 是否非空(= hold 有没有真正的 GameObject)----
var hasObj = false;
try {
var ov = h.field("SceneItemObj").value;
hasObj = !(ov === null || ov === undefined);
if (hasObj) { try { if (ov.isNull && ov.isNull()) hasObj = false; } catch (e) { } }
} catch (e) {
try { hasObj = bpFieldTry(h, ["SceneItemObj", "sceneItemObj"]) !== null; } catch (e2) { }
}
if (hasObj) out.withObj++;
// ---- ①b 只对「球瓶/球的 cfg」读一次世界坐标 + 缩放(≤20 个 hold,开销可忽略)----
// 这是回答「瓶到底摆在哪 / 音效为什么偏晚」的核心读数。
var gx = null, gy = null, gs = null;
if (hasObj) {
var cfgWant = false;
for (var w3 = 0; w3 < BP_BOWL_CFGIDS.length; w3++) {
try {
var cv3 = bpField(h, "sceneItemConfigId");
if (typeof cv3 === "number" && Math.round(cv3) === BP_BOWL_CFGIDS[w3]) { cfgWant = true; }
} catch (e) { }
if (cfgWant) break;
}
if (cfgWant) {
try {
var ov2 = h.field("SceneItemObj").value;
if (ov2 && !(ov2.isNull && ov2.isNull())) {
var tt = ov2.method("get_transform").invoke();
var pp = tt.method("get_position").invoke();
gx = +Number(pp.field("x").value).toFixed(3);
gy = +Number(pp.field("y").value).toFixed(3);
var ss = tt.method("get_localScale").invoke();
gs = +Number(ss.field("x").value).toFixed(4);
}
} catch (e) { }
}
}
// ---- ② cfg id(走与 holds() 相同的 bpField 通道,保证读数一致)----
var cfg = null;
for (var ci = 0; ci < CFG_FIELDS.length; ci++) {
var cv;
try { cv = bpField(h, CFG_FIELDS[ci]); } catch (e) { continue; }
if (typeof cv === "number" && isFinite(cv) && cv > 1000) { cfg = Math.round(cv); break; }
}
var kk = (cfg === null ? "?" : String(cfg));
if (hist[kk] === undefined) hist[kk] = { n: 0, obj: 0 };
hist[kk].n++;
if (hasObj) hist[kk].obj++;
if (want[kk]) {
want[kk].n++;
if (hasObj) want[kk].obj++;
if (gx !== null) {
if (want[kk].xMin === null || gx < want[kk].xMin) want[kk].xMin = gx;
if (want[kk].xMax === null || gx > want[kk].xMax) want[kk].xMax = gx;
want[kk].y = gy;
want[kk].s = gs;
}
}
}
out.bowlCfg = want;
for (var key in hist) { out.top.push([key, hist[key].n, hist[key].obj]); }
out.cfgs = out.top.length;
out.top.sort(function (a, b) { return b[1] - a[1]; });
out.top = out.top.slice(0, 12);
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R6b2: ★★ scale 读数对照实验(判断读出来的 [0,0,1] 是真值还是读法有误)----------
// 动机:holdpos 读出「球瓶 SceneItemObj 的 localScale = (0,0,1)」—— x/y 为 0 会让模型
// 退化到画不出来,正好解释「有碰撞有音效但看不见」。但必须排除**读法**的问题。
// 做法:拿一批「确定带 MeshRenderer」的 GameObject(一定在画面上)做对照样本,
// 读 localScale + lossyScale。若它们也返回 (0,0,1),就是读法坏,不是瓶子坏。
BOWL_RPCS.push(bpRpc("ctrlscale", function (n) {
var t0 = Date.now();
var out = { rpc: "ctrlscale", samples: [], dist: {}, err: null };
try {
var lim = parseInt(n || "24", 10);
var R = bpClass("UnityEngine.MeshRenderer");
if (!R) { out.err = "no-meshrenderer"; return JSON.stringify(out); }
var rs = Il2Cpp.gc.choose(R);
out.renderers = rs ? rs.length : 0;
if (!rs || !rs.length) { out.err = "no-renderer-instances"; return JSON.stringify(out); }
var step = Math.max(1, Math.floor(rs.length / lim));
function v3(v) {
try {
return [+Number(v.field("x").value).toFixed(4),
+Number(v.field("y").value).toFixed(4),
+Number(v.field("z").value).toFixed(4)];
} catch (e) { return null; }
}
for (var i = 0; i < rs.length && out.samples.length < lim; i += step) {
var rec = {};
try {
var go = rs[i].method("get_gameObject").invoke();
try { rec.name = String(bpStr(go.method("get_name").invoke())); } catch (e) { }
var tt = go.method("get_transform").invoke();
rec.local = v3(tt.method("get_localScale").invoke());
rec.lossy = v3(tt.method("get_lossyScale").invoke());
rec.pos = v3(tt.method("get_position").invoke());
} catch (e) { rec.err = String(e && e.message ? e.message : e).slice(0, 80); }
out.samples.push(rec);
var k = JSON.stringify(rec.local);
out.dist[k] = (out.dist[k] || 0) + 1;
}
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R6c: ★★ 生成位置 + 可见性 实测(回答「交互后瓶生成在哪 / 为什么看不见」)----------
// 背景:用户观测到「听得到击瓶音效(有碰撞)但看不见瓶,且音效明显偏晚」。
// 本 RPC 一次给三组硬读数:
// ① objPos —— hold.SceneItemObj 的**世界坐标**(= 物理/碰撞实际生效的位置)
// ② subR/subN—— SceneItemObj 子树里 Renderer 的个数 / 节点总数
// subR = 0 ⇒ GameObject 是空壳 ⇒ 「有碰撞没画面」的直接原因
// ③ actSelf —— 是否被 SetActive(false)(hideSceneItem 干的)
// 再配合 bowlst("pin") 量「旁边道看得见的瓶」的世界坐标 ⇒ 两者 X 一减就是「音效偏晚」的米数。
BOWL_RPCS.push(bpRpc("holdpos", function (maxOut) {
var t0 = Date.now();
var out = { rpc: "holdpos", holdCount: 0, rows: [], agg: {}, err: null };
var limit = parseInt(maxOut || "60", 10);
try {
var K = bpClass("LX6.Item.SceneItemHold");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
// ---- 先建「哪些 GameObject 上有 Renderer」的指针集合(同 bowlst 的做法)----
var hasR = {};
var RSETS = ["UnityEngine.MeshRenderer", "UnityEngine.SkinnedMeshRenderer"];
for (var q = 0; q < RSETS.length; q++) {
var RC = null;
try { RC = bpClass(RSETS[q]); } catch (e) { }
if (!RC) continue;
var rs = null;
try { rs = Il2Cpp.gc.choose(RC); } catch (e) { continue; }
if (!rs) continue;
for (var z = 0; z < rs.length; z++) {
try {
var g = rs[z].method("get_gameObject").invoke();
if (g && g.handle && !g.handle.isNull()) hasR[g.handle.toString()] = 1;
} catch (e) { }
}
}
out.rendererGos = Object.keys(hasR).length;
function goName(g) { try { return String(bpStr(g.method("get_name").invoke())); } catch (e) { return "?"; } }
function actOf(g, m) { try { return String(g.method(m).invoke()); } catch (e) { return "?"; } }
var hs = Il2Cpp.gc.choose(K);
out.holdCount = hs ? hs.length : 0;
var CFG_FIELDS = ["sceneItemConfigId", "SceneItemConfigId", "cfgId", "CfgId", "configId", "ConfigId"];
var want = {};
for (var w = 0; w < BP_BOWL_CFGIDS.length; w++) want[BP_BOWL_CFGIDS[w]] = { n: 0, objOk: 0, subR: 0, active: 0, xMin: null, xMax: null };
for (var i = 0; i < out.holdCount && out.rows.length < limit; i++) {
var cfg = null;
for (var ci = 0; ci < CFG_FIELDS.length; ci++) {
try {
var cv = bpField(hs[i], CFG_FIELDS[ci]);
if (typeof cv === "number" && isFinite(cv) && cv > 1000) { cfg = Math.round(cv); break; }
} catch (e) { }
}
if (cfg === null || !want[cfg]) continue;
var r = { cfg: cfg };
try { r.id = String(bpField(hs[i], "instanceId")); } catch (e) { }
var ov = null;
try { ov = hs[i].field("SceneItemObj").value; } catch (e) { }
var objOk = !(ov === null || ov === undefined);
if (objOk) { try { if (ov.isNull && ov.isNull()) objOk = false; } catch (e) { } }
r.objOk = objOk;
want[cfg].n++;
if (objOk) {
want[cfg].objOk++;
r.name = goName(ov);
r.actSelf = actOf(ov, "get_activeSelf");
r.actHier = actOf(ov, "get_activeInHierarchy");
// ★ 2026-09-24:这里原来写 `r.actSelf === "True"`,而 actOf 返回的是**小写** "true"
// ⇒ active 恒为 0,和旁边 actSelf 分布打印出 {'true':10} 自相矛盾,误导判读。
if (String(r.actSelf).toLowerCase() === "true") want[cfg].active++;
try {
var t = ov.method("get_transform").invoke();
var p = t.method("get_position").invoke();
r.pos = [+Number(p.field("x").value).toFixed(3),
+Number(p.field("y").value).toFixed(3),
+Number(p.field("z").value).toFixed(3)];
var ls = t.method("get_localScale").invoke();
r.scale = [+Number(ls.field("x").value).toFixed(4),
+Number(ls.field("y").value).toFixed(4),
+Number(ls.field("z").value).toFixed(4)];
// ★ 加 lossyScale(不含父级缩放的对照)+ 父链,便于判断 scale=0 是真值还是读法问题
try {
var ly = t.method("get_lossyScale").invoke();
r.lossy = [+Number(ly.field("x").value).toFixed(4),
+Number(ly.field("y").value).toFixed(4),
+Number(ly.field("z").value).toFixed(4)];
} catch (e) { }
try {
var chain = [], cur = t, gch = 0;
while (cur && cur.handle && !cur.handle.isNull() && gch++ < 6) {
chain.push(String(bpStr(cur.method("get_gameObject").invoke()
.method("get_name").invoke())));
var pt = cur.method("get_parent").invoke();
if (!pt || !pt.handle || pt.handle.isNull()) break;
cur = pt;
}
r.path = chain.reverse().join("/");
} catch (e) { }
// ★★ 父级 lossy(最多上溯 3 层):用来区分两种可能 ——
// (a) 我们写进 placeInfo 的缩放是 0 ⇒ 本级 local 就是 0;
// (b) 容器(SceneItemInstanceRoot / DefaultSceneItem)被压成 0 ⇒ 本级 local=1 但 lossy=0。
try {
var pcur = t, pch2 = 0, lply = [];
while (pcur && pcur.handle && !pcur.handle.isNull() && pch2++ < 3) {
var pyt = pcur.method("get_parent").invoke();
if (!pyt || !pyt.handle || pyt.handle.isNull()) break;
var ly2 = pyt.method("get_lossyScale").invoke();
lply.push([+Number(ly2.field("x").value).toFixed(4),
+Number(ly2.field("y").value).toFixed(4),
+Number(ly2.field("z").value).toFixed(4)]);
pcur = pyt;
}
r.lossyP = lply;
} catch (e) { }
// 子树:数节点 + 数 Renderer
var nSub = 0, nR = 0, firstR = null;
var q2 = [{ t: t, d: 0 }];
while (q2.length && nSub < 300) {
var it2 = q2.shift();
nSub++;
try {
var gk = it2.t.method("get_gameObject").invoke();
if (gk && gk.handle && hasR[gk.handle.toString()]) {
nR++;
if (firstR === null) firstR = goName(gk);
}
} catch (e) { }
if (it2.d < 5) {
try {
var cc = Number(it2.t.method("get_childCount").invoke());
for (var k2 = 0; k2 < cc; k2++) {
try { q2.push({ t: it2.t.method("GetChild").invoke(k2), d: it2.d + 1 }); } catch (e) { }
}
} catch (e) { }
}
}
r.subN = nSub; r.subR = nR; r.subRName = firstR;
if (nR > 0) want[cfg].subR++;
if (r.pos) {
var A = want[cfg];
if (A.xMin === null || r.pos[0] < A.xMin) A.xMin = r.pos[0];
if (A.xMax === null || r.pos[0] > A.xMax) A.xMax = r.pos[0];
}
} catch (e) { r.tErr = String(e && e.message ? e.message : e); }
}
out.rows.push(r);
}
// 排序:按 X 升序(道头 → 瓶台)
out.rows.sort(function (a, b) {
var ax = (a.pos ? a.pos[0] : 1e9), bx = (b.pos ? b.pos[0] : 1e9);
return ax - bx;
});
out.agg = want;
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R6d: ★★ 锚点子树实测(回答「看得见的瓶到底在哪」)----------
// 背景(服务端 GameRouter.GameGround.cs 第148~163行的实测记录):
// · 官方场景里有锚点 GameObject,名字形如 BowlingPinPlacementL / R、BowlingGameNode、
// BowlingGameStart、MiniGameMachine_Bowling —— 共 4 台机器、每台 2 条道 = 8 条道。
// · 服务端 BowlingLanes[] 的 PinApexX 就是抄自 BowlingPinPlacement 的世界坐标。
// · **看得见的那些瓶网格 = BowlingPinPlacement 的子节点**(名字里未必有 "pin")。
// ⇒ 想回答「瓶该在哪 / 现在在哪 / 差多少米」,必须把锚点的**整棵子树连世界坐标**倒出来。
// 用法:kids("BowlingPinPlacement", 8, 4, 80)
BOWL_RPCS.push(bpRpc("kids", function (kw, maxRoots, maxDepth, maxKids) {
var t0 = Date.now();
var out = { rpc: "kids", kw: String(kw || ""), goTotal: 0, roots: [], err: null };
try {
var kwl = out.kw.toLowerCase();
if (!kwl) { out.err = "need-keyword"; return JSON.stringify(out); }
var G = bpClass("UnityEngine.GameObject");
if (!G) { out.err = "no-gameobject-class"; return JSON.stringify(out); }
// 先建「有 Renderer 的 GameObject」指针集合,用来判断哪些节点是看得见的
var hasR = {};
var RSETS = ["UnityEngine.MeshRenderer", "UnityEngine.SkinnedMeshRenderer"];
for (var q = 0; q < RSETS.length; q++) {
var RC = null;
try { RC = bpClass(RSETS[q]); } catch (e) { }
if (!RC) continue;
var rs = null;
try { rs = Il2Cpp.gc.choose(RC); } catch (e) { continue; }
if (!rs) continue;
for (var z = 0; z < rs.length; z++) {
try {
var g = rs[z].method("get_gameObject").invoke();
if (g && g.handle && !g.handle.isNull()) hasR[g.handle.toString()] = 1;
} catch (e) { }
}
}
out.rendererGos = Object.keys(hasR).length;
function nm(g) { try { return String(bpStr(g.method("get_name").invoke())); } catch (e) { return "?"; } }
function wpos(t) {
try {
var p = t.method("get_position").invoke();
return [+Number(p.field("x").value).toFixed(3),
+Number(p.field("y").value).toFixed(3),
+Number(p.field("z").value).toFixed(3)];
} catch (e) { return null; }
}
function hasRen(g) { try { return !!(g.handle && hasR[g.handle.toString()]); } catch (e) { return false; } }
function act(g, m) { try { return String(g.method(m).invoke()); } catch (e) { return "?"; } }
// ★★ 关键改动(2026-09-24 08:17 事故后):**不再用 Il2Cpp.gc.choose(GameObject)**。
// 那是"遍历整个 GC 堆",是全部探针里最贵的一类调用(现场就是这一族把游戏拖住 30 秒)。
// 改成从 SceneManager 的活动场景根节点做 BFS:节点数带硬上限、不碰 GC 堆、
// 而且场景里的对象本来就都在根节点的子树里 ⇒ 覆盖率等价。
var sm = bpClass("UnityEngine.SceneManagement.SceneManager");
if (!sm) { out.err = "no-scenemanager"; return JSON.stringify(out); }
var sc = sm.method("GetActiveScene").invoke();
try { out.scene = String(bpStr(sc.method("get_name").invoke())); } catch (e) { }
var rts = sc.method("GetRootGameObjects").invoke();
var rc = 0;
try { rc = Number(rts.method("get_Length").invoke()); } catch (e) { }
out.rootCount = rc;
var wantRoots = parseInt(maxRoots || "8", 10);
var wantDepth = parseInt(maxDepth || "4", 10);
var wantKids = parseInt(maxKids || "80", 10);
var maxNodes = 120000;
var bfs = [];
for (var i0 = 0; i0 < rc; i0++) {
try { bfs.push({ t: rts.method("get_Item").invoke(i0).method("get_transform").invoke(), d: 0 }); }
catch (e) { }
}
var visited = 0;
while (bfs.length && visited < maxNodes && out.roots.length < wantRoots) {
var node = bfs.shift();
visited++;
var g1 = null, name = "?";
try { g1 = node.t.method("get_gameObject").invoke(); name = nm(g1); } catch (e) { }
if (String(name).toLowerCase().indexOf(kwl) >= 0) {
var r = { name: name, actSelf: act(g1, "get_activeSelf"),
actHier: act(g1, "get_activeInHierarchy") };
try {
var rt = node.t;
r.pos = wpos(rt);
r.hasR = hasRen(g1);
var sc3 = rt.method("get_localScale").invoke();
r.scale = [+Number(sc3.field("x").value).toFixed(4),
+Number(sc3.field("y").value).toFixed(4),
+Number(sc3.field("z").value).toFixed(4)];
// 子树 BFS:连世界坐标一起给
var kids = [];
var q2 = [];
var cc0 = 0;
try { cc0 = Number(rt.method("get_childCount").invoke()); } catch (e) { }
for (var c0 = 0; c0 < cc0; c0++) {
try { q2.push({ t: rt.method("GetChild").invoke(c0), d: 1 }); } catch (e) { }
}
while (q2.length && kids.length < wantKids) {
var it2 = q2.shift();
var k = { d: it2.d };
try {
var g2 = it2.t.method("get_gameObject").invoke();
k.name = nm(g2);
k.hasR = hasRen(g2);
k.actSelf = act(g2, "get_activeSelf");
k.actHier = act(g2, "get_activeInHierarchy");
} catch (e) { }
k.pos = wpos(it2.t);
kids.push(k);
if (it2.d < wantDepth) {
var cn = 0;
try { cn = Number(it2.t.method("get_childCount").invoke()); } catch (e) { }
for (var k2 = 0; k2 < cn; k2++) {
try { q2.push({ t: it2.t.method("GetChild").invoke(k2), d: it2.d + 1 }); } catch (e) { }
}
}
}
r.kidN = kids.length;
r.kids = kids;
} catch (e) { r.tErr = String(e && e.message ? e.message : e); }
out.roots.push(r);
}
if (node.d < wantDepth) {
var cc1 = 0;
try { cc1 = Number(node.t.method("get_childCount").invoke()); } catch (e) { }
for (var c1 = 0; c1 < cc1; c1++) {
try { bfs.push({ t: node.t.method("GetChild").invoke(c1), d: node.d + 1 }); } catch (e) { }
}
}
}
out.visited = visited;
// 按 X 排序(道头 → 瓶台),便于和 PinApexX 直接比
out.roots.sort(function (a, b) {
var ax = (a.pos ? a.pos[0] : 1e9), bx = (b.pos ? b.pos[0] : 1e9);
return ax - bx;
});
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R7: 枚举所有活着的 GameObject(名字搜索,兜底用) ----------
BOWL_RPCS.push(bpRpc("gobjs", function (kw, maxOut) {
var t0 = Date.now();
var out = { rpc: "gobjs", kw: String(kw || ""), count: 0, hits: [], err: null };
try {
var K = bpClass("UnityEngine.GameObject");
if (!K) { out.err = "no-gameobject-class"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.count = objs ? objs.length : 0;
var kwl = out.kw.toLowerCase();
var limit = parseInt(maxOut || "100", 10);
for (var i = 0; i < out.count; i++) {
var go = objs[i];
var nm = null;
try { nm = bpStr(go.method("get_name").invoke()); } catch (e) { continue; }
if (nm === null) continue;
if (kwl && String(nm).toLowerCase().indexOf(kwl) < 0) continue;
var p3 = null;
try {
var t = go.method("get_transform").invoke();
if (t && !(t.isNull && t.isNull())) {
var wp = t.method("get_position").invoke();
p3 = [wp.field("x").value, wp.field("y").value, wp.field("z").value];
}
} catch (e) { }
out.hits.push({ name: nm, pos: p3 });
if (out.hits.length >= limit) { out.capped = true; break; }
}
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R5: 场景 GameObject 名称搜索 ----------
BOWL_RPCS.push(bpRpc("objs", function (kw, maxNodes, maxDepth) {
var out = { rpc: "objs", kw: String(kw || ""), visited: 0, hits: [], err: null };
try {
kw = out.kw.toLowerCase();
maxNodes = parseInt(maxNodes || "8000", 10);
maxDepth = parseInt(maxDepth || "6", 10);
var sm = bpClass("UnityEngine.SceneManagement.SceneManager");
if (!sm) { out.err = "no-scenemanager"; return JSON.stringify(out); }
var sc = sm.method("GetActiveScene").invoke();
out.scene = bpStr(sc.method("get_name").invoke());
var roots = sc.method("GetRootGameObjects").invoke();
var rc = 0;
try { rc = Number(roots.method("get_Length").invoke()); } catch (e) { }
out.rootCount = rc;
var queue = [];
for (var i = 0; i < rc; i++) {
try { queue.push({ go: roots.method("get_Item").invoke(i), depth: 0, path: "?" }); } catch (e) { }
}
var visited = 0;
while (queue.length && visited < maxNodes) {
var it = queue.shift();
visited++;
var nm = "?";
try { nm = bpStr(it.go.method("get_name").invoke()); } catch (e) { }
var path = it.path === "?" ? nm : (it.path + "/" + nm);
if (kw && String(nm).toLowerCase().indexOf(kw) >= 0) {
var w = null;
try {
var t = it.go.method("get_transform").invoke();
var wp = t.method("get_position").invoke();
w = [wp.field("x").value, wp.field("y").value, wp.field("z").value];
} catch (e) { }
out.hits.push({ path: path, pos: w, active: String(it.go.method("get_activeSelf").invoke()) });
if (out.hits.length >= 200) break;
}
if (it.depth < maxDepth) {
var ch = null;
try { ch = it.go.method("get_transform").invoke().method("get_childCount").invoke(); } catch (e) { }
if (ch !== null) {
for (var c = 0; c < Number(ch); c++) {
try {
queue.push({
go: it.go.method("get_transform").invoke().method("GetChild").invoke(c),
depth: it.depth + 1, path: path
});
} catch (e) { }
}
}
}
}
out.visited = visited;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R9: 保龄球专项状态(★ 只读):名字 + active + 父链 + 组件 + 缩放 ----------
// 用途:回答「交互前看得见的瓶,交互后去哪了」——
// · 若它们还在 gc 里、只是 actSelf/actHier = false ⇒ 被 SetActive(false) 藏了(可逆!)
// · 若 hasRenderer = false ⇒ 被换成了空壳
// · 若连对象都没了 ⇒ 被 Destroy 了
BOWL_RPCS.push(bpRpc("bowlst", function (kw, maxOut, wantComps) {
var t0 = Date.now();
var out = { rpc: "bowlst", kw: String(kw || ""), total: 0, hits: [], err: null };
try {
var K = bpClass("UnityEngine.GameObject");
if (!K) { out.err = "no-gameobject-class"; return JSON.stringify(out); }
// ★ wantComps:默认只建 Renderer 两个集合("r")。
// 以前无条件建 7 个集合(4 种 Collider + Rigidbody),在 2026-09-24 08:17 现场
// 正是它把游戏拖住 ~30 秒。要 collider/rigidbody 就显式传 "rc" 或 "all"。
var wc = String(wantComps || "r").toLowerCase();
out.want = wc;
var hasR = {}, hasC = {}, hasRb = {};
var sets = [["UnityEngine.MeshRenderer", hasR], ["UnityEngine.SkinnedMeshRenderer", hasR]];
if (wc === "rc" || wc === "all") {
sets.push(["UnityEngine.BoxCollider", hasC], ["UnityEngine.SphereCollider", hasC],
["UnityEngine.CapsuleCollider", hasC], ["UnityEngine.MeshCollider", hasC]);
}
if (wc === "all") sets.push(["UnityEngine.Rigidbody", hasRb]);
for (var q = 0; q < sets.length; q++) {
var CC = null;
try { CC = bpClass(sets[q][0]); } catch (e) { }
if (!CC) continue;
var cs = null;
try { cs = Il2Cpp.gc.choose(CC); } catch (e) { continue; }
if (!cs) continue;
for (var z = 0; z < cs.length; z++) {
try {
var g = cs[z].method("get_gameObject").invoke();
if (g && g.handle && !g.handle.isNull()) sets[q][1][g.handle.toString()] = 1;
} catch (e) { }
}
}
out.rC = Object.keys(hasR).length;
out.cC = Object.keys(hasC).length;
out.rbC = Object.keys(hasRb).length;
var objs = Il2Cpp.gc.choose(K);
out.total = objs ? objs.length : 0;
var kwl = out.kw.toLowerCase();
var limit = parseInt(maxOut || "300", 10);
for (var i = 0; i < out.total; i++) {
var go = objs[i];
var nm = null;
try { nm = bpStr(go.method("get_name").invoke()); } catch (e) { continue; }
if (nm === null || nm === undefined) continue;
nm = String(nm);
if (kwl && nm.toLowerCase().indexOf(kwl) < 0) continue;
var r = { name: nm };
var h = null;
try { h = go.handle.toString(); } catch (e) { }
r.hasRenderer = !!(h && hasR[h]);
r.hasCollider = !!(h && hasC[h]);
r.hasRigidbody = !!(h && hasRb[h]);
try { r.actSelf = String(go.method("get_activeSelf").invoke()); } catch (e) { }
try { r.actHier = String(go.method("get_activeInHierarchy").invoke()); } catch (e) { }
try { r.layer = Number(go.method("get_layer").invoke()); } catch (e) { }
try {
var t = go.method("get_transform").invoke();
if (t && t.handle && !t.handle.isNull()) {
try {
var wp = t.method("get_position").invoke();
r.pos = [+Number(wp.field("x").value).toFixed(3),
+Number(wp.field("y").value).toFixed(3),
+Number(wp.field("z").value).toFixed(3)];
} catch (e) { }
try {
var ls = t.method("get_localScale").invoke();
r.scale = [+Number(ls.field("x").value).toFixed(4),
+Number(ls.field("y").value).toFixed(4),
+Number(ls.field("z").value).toFixed(4)];
} catch (e) { }
try { r.child = Number(t.method("get_childCount").invoke()); } catch (e) { }
try {
var chain = [], cur = t, guard = 0;
while (cur && cur.handle && !cur.handle.isNull() && guard++ < 14) {
try {
chain.push(String(bpStr(cur.method("get_gameObject").invoke()
.method("get_name").invoke())));
} catch (e) { chain.push("?"); }
try {
var pt = cur.method("get_parent").invoke();
if (!pt || !pt.handle || pt.handle.isNull()) break;
cur = pt;
} catch (e) { break; }
}
r.path = chain.reverse().join("/");
} catch (e) { }
}
} catch (e) { r.tErr = String(e); }
out.hits.push(r);
if (out.hits.length >= limit) { out.capped = true; break; }
}
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R8: 类字段/静态值 ----------
BOWL_RPCS.push(bpRpc("clsfields", function (cn) {
var out = { rpc: "clsfields", cls: String(cn || ""), fields: [], methods: [], bases: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var c = K, guard = 0;
while (c && guard++ < 8) {
var own = [];
try {
for (var i = 0; i < c.fields.length; i++) {
var f = c.fields[i];
var tn = "?";
try { tn = String(f.type.name); } catch (e) { }
own.push(String(f.name) + ":" + tn + (f.isStatic ? " (static)" : ""));
}
} catch (e) { own.push("ERRFIELD"); }
out.fields.push({ cls: String(c.fullName), fields: own });
out.bases.push(String(c.fullName));
try {
if (guard === 1) {
for (var m = 0; m < c.methods.length; m++) {
var mm = c.methods[m];
var ps = [];
try {
for (var p = 0; p < mm.parameters.length; p++) ps.push(String(mm.parameters[p].type.name));
} catch (e) { }
out.methods.push(String(mm.name) + "(" + ps.join(",") + ")");
}
}
} catch (e) { }
try { c = c.baseType; } catch (e) { c = null; }
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R9: SceneItemBlobGrid 网格清单(决定性:网格坐标系 + 覆盖范围) ----------
BOWL_RPCS.push(bpRpc("grids", function (samples) {
var t0 = Date.now();
var out = { rpc: "grids", count: 0, xs: [], zs: [], pairs: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlobGrid");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.count = objs ? objs.length : 0;
var sx = {}, sz = {};
for (var i = 0; i < out.count; i++) {
try {
var x = Number(objs[i].field("indexX").value);
var z = Number(objs[i].field("indexZ").value);
if (!isNaN(x)) sx[x] = (sx[x] || 0) + 1;
if (!isNaN(z)) sz[z] = (sz[z] || 0) + 1;
} catch (e) { }
}
out.xs = Object.keys(sx).map(Number).sort(function (a, b) { return a - b; });
out.zs = Object.keys(sz).map(Number).sort(function (a, b) { return a - b; });
out.xRange = out.xs.length ? [out.xs[0], out.xs[out.xs.length - 1]] : null;
out.zRange = out.zs.length ? [out.zs[0], out.zs[out.zs.length - 1]] : null;
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R10: 指定网格的完整字段(找出里面装了什么) ----------
BOWL_RPCS.push(bpRpc("griddump", function (ix, iz) {
var out = { rpc: "griddump", want: [Number(ix), Number(iz)], found: 0, rows: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlobGrid");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
var names = [];
var c = K, guard = 0;
while (c && guard++ < 8) {
for (var i = 0; i < c.fields.length; i++) {
var fn = String(c.fields[i].name);
if (fn.indexOf("Native") === 0) continue;
if (names.indexOf(fn) < 0) names.push(fn);
}
c = c.baseType;
}
out.fieldNames = names;
for (var q = 0; q < (objs ? objs.length : 0); q++) {
var o = objs[q];
var x, z;
try { x = Number(o.field("indexX").value); z = Number(o.field("indexZ").value); } catch (e) { continue; }
if (x !== out.want[0] || z !== out.want[1]) continue;
out.found++;
if (out.rows.length >= 3) continue;
var rec = {};
for (var k = 0; k < names.length; k++) {
var nm = names[k];
var v;
try { v = o.field(nm).value; } catch (e) { rec[nm] = "<err>"; continue; }
if (v === null || v === undefined) { rec[nm] = null; continue; }
try { if (v.isNull && v.isNull()) { rec[nm] = null; continue; } } catch (e) { }
var cn = "";
try { cn = String(v.class.name); } catch (e) { }
if (bpIsStr(cn)) { rec[nm] = bpStr(v); continue; }
if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") { rec[nm] = Number(v); continue; }
if (cn === "Int32" || cn === "UInt32" || cn === "Single") {
try { rec[nm] = Number(v.method("ToString").invoke().content); } catch (e) { rec[nm] = "<num>"; }
continue;
}
var cnt = null;
try { cnt = Number(v.method("get_Count").invoke()); } catch (e) { }
if (cnt === null) { try { cnt = Number(v.method("get_Length").invoke()); } catch (e) { } }
rec[nm] = cnt === null ? ("<" + cn + ">") : ("<" + cn + ":" + cnt + ">");
}
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R11: SceneItemBlob 静态状态 ----------
BOWL_RPCS.push(bpRpc("blobstat", function () {
var out = { rpc: "blobstat", cls: {}, err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlob");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
out.name = String(K.fullName);
out.fields = [];
var c = K, guard = 0;
while (c && guard++ < 6) {
for (var i = 0; i < c.fields.length; i++) {
var f = c.fields[i];
var nm = String(f.name);
if (nm.indexOf("Native") === 0) continue;
var tn = "?";
try { tn = String(f.type.name); } catch (e) { }
var val = "";
try {
var v = f.value;
if (v === null || v === undefined) val = "null";
else if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") val = String(v);
else {
var cn = ""; try { cn = String(v.class.name); } catch (e) { }
if (bpIsStr(cn)) val = String(bpStr(v));
else {
var cnt = null;
try { cnt = Number(v.method("get_Count").invoke()); } catch (e) { }
if (cnt === null) { try { cnt = Number(v.method("get_Length").invoke()); } catch (e) { } }
val = cnt === null ? ("<" + cn + ">") : ("<" + cn + ":" + cnt + ">");
}
}
} catch (e) { val = "<err>"; }
out.fields.push(nm + ":" + tn + (f.isStatic ? " static" : "") + " = " + val);
}
try { c = c.baseType; } catch (e) { c = null; }
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R12: 注册表 path 字符串(找出真实 prefab 名,用来搜 GameObject) ----------
BOWL_RPCS.push(bpRpc("pathstr", function (kw, maxOut) {
var t0 = Date.now();
var out = { rpc: "pathstr", kw: String(kw || ""), total: 0, rows: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemPathInfoRuntime");
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var objs = Il2Cpp.gc.choose(K);
out.total = objs ? objs.length : 0;
var kwl = out.kw.toLowerCase();
var limit = parseInt(maxOut || "40", 10);
var strategies = [];
for (var i = 0; i < out.total && out.rows.length < limit; i++) {
var o = objs[i];
var cfg, mid, raw = null, how = "";
try { cfg = Number(o.field("sceneItemConfigId").value); } catch (e) { continue; }
try { mid = Number(o.field("modelId").value); } catch (e) { }
// 多策略取 path 字符串
try {
var v = o.field("path").value;
var cn = ""; try { cn = String(v.class.name); } catch (e) { }
var cand = [
["content", function () { return v.content; }],
["ToString", function () { return v.method("ToString").invoke().content; }],
["str", function () { return String(v); }],
["field_value", function () { return bpStr(v.field("value").value); }],
["f_name", function () { return bpStr(v.field("name").value); }],
["f_path", function () { return bpStr(v.field("path").value); }],
["f_str", function () { return bpStr(v.field("str").value); }]
];
for (var s = 0; s < cand.length; s++) {
try {
var r = cand[s][1]();
if (typeof r === "string" && r.length && r !== "<obj>") { raw = r; how = cand[s][0]; break; }
} catch (e) { }
}
if (raw === null) {
if (strategies.indexOf(cn) < 0) strategies.push(cn);
raw = "<objclass:" + cn + ">";
how = "none";
}
} catch (e) { raw = "<err>"; how = "err"; }
var blob = String(raw).toLowerCase();
var keep = !kwl || blob.indexOf(kwl) >= 0
|| BP_BOWL_CFGIDS.indexOf(cfg) >= 0;
if (keep) out.rows.push({ cfg: cfg, modelId: mid, path: raw, how: how });
}
out.objClasses = strategies;
out.ms = Date.now() - t0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R13: 读类静态字段的「值」(找 blob 文件路径 / 注册表大小) ----------
BOWL_RPCS.push(bpRpc("statv", function (cn, nameFilter) {
var out = { rpc: "statv", cls: String(cn || ""), rows: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var nf = String(nameFilter || "").toLowerCase();
var c = K, guard = 0;
while (c && guard++ < 8) {
var flds = [];
try { flds = c.fields; } catch (e) { }
for (var i = 0; i < flds.length; i++) {
var f = flds[i];
var nm = String(f.name);
if (!f.isStatic) continue;
if (nm.indexOf("NativeFieldInfoPtr") === 0) continue;
if (nf && nm.toLowerCase().indexOf(nf) < 0) continue;
var tn = "?";
try { tn = String(f.type.name); } catch (e) { }
var val = "";
try {
var v = f.value;
if (v === null || v === undefined) val = "null";
else if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") val = String(v);
else {
var cn2 = ""; try { cn2 = String(v.class.name); } catch (e) { }
if (bpIsStr(cn2)) val = "STR:" + String(bpStr(v));
else {
var cnt = null;
try { cnt = Number(v.method("get_Count").invoke()); } catch (e) { }
if (cnt === null || isNaN(cnt)) { try { cnt = Number(v.method("get_Length").invoke()); } catch (e) { } }
val = (cnt === null || isNaN(cnt)) ? ("<" + cn2 + ">") : ("<" + cn2 + ":" + cnt + ">");
}
}
} catch (e) { val = "<err:" + String(e && e.message ? e.message : e) + ">"; }
out.rows.push({ cls: String(c.fullName), field: nm, type: tn, value: val });
}
try { c = c.baseType; } catch (e) { c = null; }
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R14: 泛型/嵌套类型的字段清单(SceneItemPlaceInfoData 等) ----------
BOWL_RPCS.push(bpRpc("placefields", function () {
var out = { rpc: "placefields", classes: [], err: null };
var names = ["LX6.Item.SceneItemPlaceInfoData", "LX6.Item.ChunkBlobData`1",
"LX6.Item.SceneItemBlobHeadInfo", "LX6.Item.DestructionType",
"LX6.Item.SceneItemSizeType", "LX6.Item.GridItemType"];
for (var n = 0; n < names.length; n++) {
var rec = { cls: names[n], found: false, fields: [], methods: [] };
try {
var K = bpClass(names[n]);
if (K) {
rec.found = true;
rec.real = String(K.fullName);
var c = K, guard = 0;
while (c && guard++ < 4) {
try {
for (var i = 0; i < c.fields.length; i++) {
var f = c.fields[i];
var tn = "?";
try { tn = String(f.type.name); } catch (e) { }
rec.fields.push(String(c.fullName) + "." + String(f.name) + ":" + tn);
}
} catch (e) { }
try { c = c.baseType; } catch (e) { c = null; }
}
}
} catch (e) { rec.err = String(e && e.message ? e.message : e); }
out.classes.push(rec);
}
out.ok = true;
return JSON.stringify(out);
}));
// ---------- R15: 枚举 NativeHashMap / Dictionary 的键值(SceneItemChunkMap / placeIdToGridSeekId) ----------
BOWL_RPCS.push(bpRpc("mapdump", function (cn, fieldName, maxOut) {
var out = { rpc: "mapdump", cls: String(cn || ""), field: String(fieldName || ""), count: null, rows: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "class-not-found"; return JSON.stringify(out); }
var v = null;
try { v = K.field(out.field).value; } catch (e) { out.err = "no-field"; return JSON.stringify(out); }
if (v === null || v === undefined) { out.err = "null"; return JSON.stringify(out); }
try { if (v.isNull && v.isNull()) { out.err = "null-inst"; return JSON.stringify(out); } } catch (e) { }
try { out.count = Number(v.method("get_Count").invoke()); } catch (e) {
try { out.count = Number(v.method("get_Length").invoke()); } catch (e2) { }
}
var lim = parseInt(maxOut || "60", 10);
// 尝试多种枚举姿势
var tried = [];
function tryIt(tag, fn) {
try { var r = fn(); tried.push(tag + "=OK(" + r + ")"); return true; }
catch (e) { tried.push(tag + "=" + String(e && e.message ? e.message : e).slice(0, 60)); return false; }
}
// 姿势1: GetEnumerator
tryIt("GetEnumerator", function () {
var en = v.method("GetEnumerator").invoke();
var n = 0;
while (n < lim && en.method("MoveNext").invoke()) {
var kv = en.method("get_Current").invoke();
var ks = "", vs = "";
try { ks = String(bpField(kv, "Key")); } catch (e) { try { ks = String(bpField(kv, "key")); } catch (e2) { } }
try { vs = String(bpField(kv, "Value")); } catch (e) { try { vs = String(bpField(kv, "value")); } catch (e2) { } }
out.rows.push({ k: ks, v: vs });
n++;
}
return n;
});
out.tried = tried;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R16: Dictionary 内省 + 尝试枚举 placeIdToGridSeekId ----------
BOWL_RPCS.push(bpRpc("placekeys", function (maxOut, fieldName) {
var out = { rpc: "placekeys", dictCls: null, dictFields: [], count: null,
entriesField: null, arrCls: null, arrLen: null,
entryFields: [], rows: [], tried: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlob");
if (!K) { out.err = "no-blob"; return JSON.stringify(out); }
var d;
try { d = K.field(fieldName || "placeIdToGridSeekId").value; } catch (e) { out.err = "no-field"; return JSON.stringify(out); }
if (!d) { out.err = "null-dict"; return JSON.stringify(out); }
try { out.dictCls = String(d.class.fullName); } catch (e) { }
// 1) dict 自身字段名
try {
for (var i = 0; i < d.class.fields.length; i++) {
var fn = String(d.class.fields[i].name);
if (fn.indexOf("Native") === 0) continue;
out.dictFields.push(fn);
}
} catch (e) { }
// 2) count
try { out.count = Number(d.field("_count").value); } catch (e) { out.tried.push("_count:" + String(e.message).slice(0, 50)); }
if (out.count === null) { try { out.count = Number(d.method("get_Count").invoke()); } catch (e) { } }
// 3) entries 数组
var arr = null;
var cands = ["_entries", "entries"];
for (var c = 0; c < cands.length; c++) {
try { arr = d.field(cands[c]).value; if (arr) { out.entriesField = cands[c]; break; } } catch (e) { }
}
if (!arr) { out.err = "no-entries-array"; return JSON.stringify(out); }
try { out.arrCls = String(arr.class.fullName); } catch (e) { }
try { out.arrLen = Number(arr.method("get_Length").invoke()); } catch (e) { out.tried.push("arrLen:" + String(e.message).slice(0, 40)); }
// 4) 取一个 Entry 看字段
var e0 = null;
var getters = ["GetValue", "Get"];
for (var g = 0; g < getters.length && !e0; g++) {
try { e0 = arr.method(getters[g]).invoke(0); out.tried.push(getters[g] + "=OK"); }
catch (e) { out.tried.push(getters[g] + "=" + String(e.message).slice(0, 50)); }
}
if (!e0) { out.err = "no-entry-getter"; return JSON.stringify(out); }
try {
out.entryCls = String(e0.class.fullName);
for (var q = 0; q < e0.class.fields.length; q++) {
var ef = String(e0.class.fields[q].name);
if (ef.indexOf("Native") === 0) continue;
out.entryFields.push(ef);
}
} catch (e) { out.entryCls_err = String(e.message); }
// 5) 正式枚举
var lim = parseInt(maxOut || "40", 10);
var n = out.count !== null ? out.count : out.arrLen;
for (var i = 0; i < n && out.rows.length < lim; i++) {
var e = null;
for (var g2 = 0; g2 < getters.length && !e; g2++) {
try { e = arr.method(getters[g2]).invoke(i); } catch (err) { }
}
if (!e) continue;
var rec = {};
for (var w = 0; w < out.entryFields.length; w++) {
var nm = out.entryFields[w];
try {
var val = e.field(nm).value;
if (val === null || val === undefined) { rec[nm] = null; continue; }
var cn = ""; try { cn = String(val.class.name); } catch (err) { }
if (typeof val === "number" || typeof val === "bigint" || typeof val === "boolean") { rec[nm] = Number(val); continue; }
if (bpIsStr(cn)) { rec[nm] = String(bpStr(val)); continue; }
// 值类型/结构:展开一层
var sub = {};
try {
for (var z = 0; z < val.class.fields.length && z < 16; z++) {
var zf = String(val.class.fields[z].name);
if (zf.indexOf("Native") === 0) continue;
try { sub[zf] = String(bpField(val, zf)); } catch (err) { }
}
} catch (err) { }
rec[nm] = sub;
} catch (err) { rec[nm] = "<err>"; }
}
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// 内存 hex dump 小工具
function hexOf(ptr, len) {
try {
var bytes = new Uint8Array(ptr.readByteArray(len));
var s = "";
for (var i = 0; i < bytes.length; i++) {
var h = bytes[i].toString(16);
if (h.length < 2) h = "0" + h;
s += h;
}
return s;
} catch (e) { return "hexx:" + String(e.message || e); }
}
// 多名字段取读:全部失败返回 null(区别于 bpField 的 "<err>")
function bpFieldTry(o, names) { for (var i = 0; i < names.length; i++) {
try {
var v = o.field(names[i]).value;
if (v === null || v === undefined) continue;
try { if (v.isNull && v.isNull()) continue; } catch (e) { }
return v;
} catch (e) { }
}
return null;
}
// 把「可能装箱的值/结构」拍平成一个对象
function bpFlatten(v, maxFields) {
if (v === null || v === undefined) return null;
if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return Number(v);
var cn = "";
try { cn = String(v.class.name); } catch (e) { }
if (bpIsStr(cn)) return String(bpStr(v));
try { if (v.class.isEnum) return bpNum(bpEnumNum(v)); } catch (e) { }
var out = {};
try {
var lim = maxFields || 20;
for (var z = 0; z < v.class.fields.length && z < lim; z++) {
var zf = String(v.class.fields[z].name);
if (zf.indexOf("Native") === 0) continue;
try {
var zv = v.field(zf).value;
if (zv === null || zv === undefined) { out[zf] = null; continue; }
var zcn = ""; try { zcn = String(zv.class.name); } catch (e) { }
if (bpIsStr(zcn)) out[zf] = String(bpStr(zv));
else if (typeof zv === "number" || typeof zv === "bigint" || typeof zv === "boolean") out[zf] = Number(zv);
else { try { if (zv.class.isEnum) out[zf] = bpNum(bpEnumNum(zv)); else out[zf] = "<" + zcn + ">"; } catch (e) { out[zf] = "<obj>"; } }
} catch (e) { out[zf] = "<err>"; }
}
} catch (e) { }
return out;
}
// ---------- R16: 用 GetEnumerator 枚举 Dictionary(key/value 多名字兜底) ----------
BOWL_RPCS.push(bpRpc("dictdump", function (fieldName, maxOut, clsName) {
var out = { rpc: "dictdump", cls: String(clsName || "LX6.Item.SceneItemBlob"), field: String(fieldName || ""),
count: null, kvFields: [], valCls: null, valFields: [], rows: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "no-class"; return JSON.stringify(out); }
var d;
try { d = K.field(out.field).value; } catch (e) { out.err = "no-field:" + String(e.message).slice(0, 80); return JSON.stringify(out); }
if (!d) { out.err = "null"; return JSON.stringify(out); }
try { out.count = Number(d.field("_count").value); } catch (e) { }
if (out.count === null) { try { out.count = Number(d.method("get_Count").invoke()); } catch (e) { } }
var en = d.method("GetEnumerator").invoke(); // Enumerator 结构
var lim = parseInt(maxOut || "40", 10);
var n = (out.count === null ? lim : out.count);
for (var i = 0; i < n && out.rows.length < lim; i++) {
var more = false;
try { more = !!en.method("MoveNext").invoke(); } catch (e) { out.err = "MoveNext:" + String(e.message).slice(0, 60); break; }
if (!more) break;
var kv = null;
try { kv = en.method("get_Current").invoke(); } catch (e) { out.err = "get_Current:" + String(e.message).slice(0, 60); break; }
if (!kv) continue;
if (out.kvFields.length === 0) {
try {
for (var q = 0; q < kv.class.fields.length; q++) {
var qn = String(kv.class.fields[q].name);
if (qn.indexOf("Native") === 0) continue;
out.kvFields.push(qn);
}
} catch (e) { }
}
var kk = bpFieldTry(kv, ["key", "Key", "Item1"]);
var vv = bpFieldTry(kv, ["value", "Value", "Item2"]);
if (vv && out.valCls === null) {
try {
out.valCls = String(vv.class.fullName);
for (var w = 0; w < vv.class.fields.length; w++) {
var wn = String(vv.class.fields[w].name);
if (wn.indexOf("Native") === 0) continue;
out.valFields.push(wn);
}
} catch (e) { }
}
var row = { k: bpFlatten(kk, 4), v: bpFlatten(vv, 20) };
// 泛型字段枚举失败时:直接读装箱结构内存(header 0x10 之后是内联字段)
if (out.kvFields.length === 0) {
row.raw = {};
try {
var hv = kv.handle;
row.raw.at0 = hexOf(hv, 32);
var b = hv.add(0x10);
row.raw.u64b0 = b.readU64().toString();
row.raw.u32a = b.readU32();
row.raw.u32b = b.add(4).readU32();
row.raw.u32c = b.add(8).readU32();
row.raw.u32d = b.add(12).readU32();
row.raw.u32e = b.add(16).readU32();
row.raw.u32f = b.add(20).readU32();
row.raw.u32g = b.add(24).readU32();
row.raw.u32h = b.add(28).readU32();
} catch (e) { row.raw.err = String(e.message || e); }
}
out.rows.push(row);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R17: seekIndex 是否有数据(= 该网格有没有授权摆位) ----------
// 约定(实测):seekIndex = (indexX + 34) * 10000 + (indexZ + 75)
BOWL_RPCS.push(bpRpc("seekat", function (listStr) {
var out = { rpc: "seekat", rows: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlob");
if (!K) { out.err = "no-blob"; return JSON.stringify(out); }
var d = K.field("seekIndexToDetailHeadListIndex").value;
var ks = String(listStr || "").split(",");
for (var i = 0; i < ks.length; i++) {
var k = parseInt(ks[i], 10);
if (isNaN(k)) continue;
var rec = { seek: k, ix: Math.floor(k / 10000) - 34, iz: (k % 10000) - 75 };
try {
rec.has = !!d.method("ContainsKey").invoke(k);
if (rec.has) rec.head = Number(d.method("get_Item").invoke(k));
} catch (e) { rec.err = String(e.message || e).slice(0, 80); }
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R18: 手动激活网格 AOI(让客户端把授权摆位实例化出来) ----------
BOWL_RPCS.push(bpRpc("aoiadd", function (ixList, izList, mode) {
var out = { rpc: "aoiadd", grids: [], holdsBefore: 0, holdsAfter: 0, hits: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemBlobGrid");
var HK = bpClass("LX6.Item.SceneItemHold");
if (!K) { out.err = "no-grid-class"; return JSON.stringify(out); }
var before = HK ? Il2Cpp.gc.choose(HK) : null;
out.holdsBefore = before ? before.length : 0;
var objs = Il2Cpp.gc.choose(K);
var gmap = {};
for (var i = 0; i < (objs ? objs.length : 0); i++) {
try {
var x = Number(objs[i].field("indexX").value);
var z = Number(objs[i].field("indexZ").value);
gmap[x + "," + z] = objs[i];
} catch (e) { }
}
out.gridsTotal = objs ? objs.length : 0;
var ixs = String(ixList || "").split(","), izs = String(izList || "").split(",");
var useStatic = String(mode || "") === "static";
for (var a = 0; a < ixs.length; a++) {
for (var b = 0; b < izs.length; b++) {
var ix = parseInt(ixs[a], 10), iz = parseInt(izs[b], 10);
if (isNaN(ix) || isNaN(iz)) continue;
var g = gmap[ix + "," + iz];
if (!g) { out.grids.push({ ix: ix, iz: iz, r: "no-grid" }); continue; }
var rr = [];
for (var v = 0; v < 2; v++) {
try { g.method("OnAoiAdd").invoke(v); rr.push("OnAoiAdd(" + v + ")=ok"); }
catch (e) { rr.push("OnAoiAdd(" + v + ")=" + String(e.message || e).slice(0, 70)); }
}
if (useStatic) {
for (var v2 = 0; v2 < 2; v2++) {
try { K.method("LocalOnAOIAdd").invoke(g, v2); rr.push("LocalOnAOIAdd(" + v2 + ")=ok"); }
catch (e) { rr.push("LocalOnAOIAdd(" + v2 + ")=" + String(e.message || e).slice(0, 70)); }
}
}
out.grids.push({ ix: ix, iz: iz, r: rr.join(" | ") });
}
}
var after = HK ? Il2Cpp.gc.choose(HK) : null;
out.holdsAfter = after ? after.length : 0;
// 采样 hold 的 id + GameObject 名
for (var q = 0; q < (after ? after.length : 0) && out.hits.length < 40; q++) {
var rec = {};
try {
var iid = bpFieldTry(after[q], ["InstanceId", "instanceId", "SceneItemId", "sceneItemId"]);
rec.id = String(bpFlatten(iid, 2));
} catch (e) { }
try {
var o = bpFieldTry(after[q], ["SceneItemObj", "sceneItemObj", "gameObject", "GameObject"]);
if (o) { try { rec.go = String(bpStr(o.method("get_name").invoke())); } catch (e) { rec.go = "<n>"; } }
} catch (e) { }
out.hits.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R19: 造 Vector3 / Quaternion 值类型 ----------
// 值类型参数:bundle 的 fromFridaValue 对「数组」按字段声明顺序写入
// Vector3 = {x,y,z}、Quaternion = {x,y,z,w} → 直接传 JS 数组
function bpVec3Arr(x, y, z) { return [Number(x), Number(y), Number(z)]; }
function bpQuatArr(x, y, z, w) { return [Number(x), Number(y), Number(z), Number(w)]; }
function bpVec3Obj(x, y, z) {
var K = bpClass("UnityEngine.Vector3");
if (!K) return null;
var o = null;
try { o = K.alloc(); } catch (e) { try { o = K.new(); } catch (e2) { return null; } }
try { o.field("x").value = x; o.field("y").value = y; o.field("z").value = z; } catch (e) { }
return o;
}
function bpQuat(x, y, z, w) {
var K = bpClass("UnityEngine.Quaternion");
if (!K) return null;
var o = null;
try { o = K.alloc(); } catch (e) { try { o = K.new(); } catch (e2) { return null; } }
try { o.field("x").value = x; o.field("y").value = y; o.field("z").value = z; o.field("w").value = w; } catch (e) { }
return o;
}
function bpHoldName(id) {
try {
var M = bpClass("LX6.Item.SceneItemMgr");
var inst = M.method("get_Instance").invoke();
var h = inst.method("GetSceneItemHold").invoke(Number(id));
if (!h) return null;
try { if (h.isNull && h.isNull()) return null; } catch (e) { }
var o = bpFieldTry(h, ["SceneItemObj", "sceneItemObj", "Obj", "obj", "Go", "go", "m_Obj"]);
if (!o) {
var names = ["get_SceneItemObj", "get_Obj", "get_Go", "get_GameObject", "get_SceneItemGo"];
for (var i = 0; i < names.length; i++) {
try { var t = h.method(names[i]).invoke(); if (t) { o = t; break; } } catch (e) { }
}
}
if (!o) return "<hold-no-obj>";
try { if (o.isNull && o.isNull()) return "<hold-obj-null>"; } catch (e) { }
try { return String(bpStr(o.method("get_name").invoke())); } catch (e) { return "<no-name>"; }
} catch (e) { return "<err:" + String(e.message || e).slice(0, 60) + ">"; }
}
// ---------- R20: 直接生成保龄球(试探哪条 API 可用) ----------
BOWL_RPCS.push(bpRpc("spawnball", function (instId, idA, idB, x, y, z, yaw) {
var out = { rpc: "spawnball", tries: [], err: null };
var M = bpClass("LX6.Item.SceneItemMgr");
if (!M) { out.err = "no-mgr"; return JSON.stringify(out); }
var inst = M.method("get_Instance").invoke();
if (!inst) { out.err = "no-inst"; return JSON.stringify(out); }
// 先确认 Vector3/Quaternion 能造
var v = bpVec3Arr(x, y, z);
var q = bpQuatArr(0, 0, 0, 1);
var sv = bpVec3Arr(1, 1, 1);
out.vec = v; out.quat = q;
// 试读回一个分量验证
var vobj = bpVec3Obj(x, y, z); try { out.vecRead = [Number(vobj.field("x").value), Number(vobj.field("y").value), Number(vobj.field("z").value)]; } catch (e) { out.vecReadErr = String(e.message); }
var ii = Number(instId);
var ids = [["pathIdA", Number(idA)], ["pathIdB", Number(idB)]];
// 姿势1: AddSceneItemBySceneItemIdSync
for (var i = 0; i < ids.length; i++) {
var rec = { api: "AddSceneItemBySceneItemIdSync", which: ids[i][0], id: ids[i][1] };
try {
r = inst.method("AddSceneItemBySceneItemIdSync").invoke(
Number(ii), Number(ids[i][1]), v, q, sv,
Number(0), 0, 0, null, null, true, false);
rec.r = "ok";
} catch (e) { rec.r = String(e.message || e).slice(0, 160); }
rec.hold = bpHoldName(ii);
out.tries.push(rec);
}
// 姿势2: AddSceneItemByPrefabNameSync
for (var j = 0; j < ids.length; j++) {
var rec2 = { api: "AddSceneItemByPrefabNameSync", which: ids[j][0], id: ids[j][1] };
try {
inst.method("AddSceneItemByPrefabNameSync").invoke(
Number(ii + 1), Number(ids[j][1]), v, q, sv,
Number(0), 0, 0, null, null, true, false);
rec2.r = "ok";
} catch (e) { rec2.r = String(e.message || e).slice(0, 160); }
rec2.hold = bpHoldName(ii + 1);
out.tries.push(rec2);
}
// 姿势3: AddSceneItemBySceneItemIdWithGameObject 变体
var rec3 = { api: "SyncAddSceneItemBySceneItemIdWithGameObject", id: Number(idA) };
try {
inst.method("SyncAddSceneItemBySceneItemIdWithGameObject").invoke(
Number(ii + 2), Number(Number(idA)), v, q, sv, 0, 0,
Number(0), null, Number(0), true, 0.0, false, null, 0, Number(0), false);
rec3.r = "ok";
} catch (e) { rec3.r = String(e.message || e).slice(0, 200); }
rec3.hold = bpHoldName(ii + 2);
out.tries.push(rec3);
out.ok = true;
return JSON.stringify(out);
}));
// ---------- R21: 保龄球模板 id 一览(从注册表按 cfgId 取) ----------
BOWL_RPCS.push(bpRpc("tpl", function () {
var out = { rpc: "tpl", rows: [], err: null };
try {
var K = bpClass("LX6.Item.SceneItemPathInfoRuntime");
var objs = Il2Cpp.gc.choose(K);
for (var i = 0; i < (objs ? objs.length : 0); i++) {
var cfg = null;
try { cfg = Number(objs[i].field("sceneItemConfigId").value); } catch (e) { continue; }
if (BP_BOWL_CFGIDS.indexOf(cfg) < 0) continue;
var rec = { cfg: cfg };
try { rec.pathId = Number(objs[i].field("pathId").value); } catch (e) { }
try { rec.modelId = Number(objs[i].field("modelId").value); } catch (e) { }
try { rec.path = String(bpStr(objs[i].field("path").value)); } catch (e) { }
try { rec.sizeType = bpNum(bpEnumNum(objs[i].field("sizeType").value)); } catch (e) { }
try { rec.destruction = bpNum(bpEnumNum(objs[i].field("destructionType").value)); } catch (e) { }
try { rec.canMove = objs[i].field("isConfigCanMove").value; } catch (e) { }
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R22: 查单个 hold + Hold 总数 ----------
BOWL_RPCS.push(bpRpc("holdat", function (idsCsv) {
var out = { rpc: "holdat", rows: [], err: null };
try {
var list = String(idsCsv || "").split(",");
for (var i = 0; i < list.length; i++) {
var id = parseInt(list[i], 10);
if (isNaN(id)) continue;
out.rows.push({ id: id, go: bpHoldName(id) });
}
var HK = bpClass("LX6.Item.SceneItemHold");
var objs = HK ? Il2Cpp.gc.choose(HK) : null;
out.holdCount = objs ? objs.length : 0;
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R23: 方法签名内省(frida 签名 + 参数类型) ----------
BOWL_RPCS.push(bpRpc("msig", function (cls, name) {
var out = { rpc: "msig", cls: String(cls || ""), name: String(name || ""), rows: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "no-class"; return JSON.stringify(out); }
var ms = null;
try { ms = K.methods; } catch (e) { out.err = "no-methods"; return JSON.stringify(out); }
for (var i = 0; i < ms.length; i++) {
var m = ms[i];
if (String(m.name) !== out.name) continue;
var rec = { paramCount: null, ret: null, sig: null, params: [] };
try { rec.paramCount = Number(m.parameterCount); } catch (e) { }
try { rec.ret = String(m.returnType.name); } catch (e) { }
try { rec.sig = String(m.fridaSignature); } catch (e) { rec.sig = "<" + String(e.message).slice(0, 60) + ">"; }
try {
for (var p = 0; p < m.parameters.length; p++) {
var pm = m.parameters[p];
var pt = pm.type;
var one = String(pm.name) + " : " + String(pt.name);
try { one += " | ev=" + pt.enumValue; } catch (e) { }
try { one += " | byref=" + (!!pt.isByReference); } catch (e) { }
try { one += " | vt=" + (!!(pt.class && pt.class.isValueType)); } catch (e) { }
try { one += " | vsz=" + (pt.class ? pt.class.valueTypeSize : "?"); } catch (e) { }
rec.params.push(one);
}
} catch (e) { rec.params.push("<perr>"); }
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R24: 值类型参数封送方式正交试验(用 Vector3.Distance 静态方法,无害可观测) ----------
BOWL_RPCS.push(bpRpc("vectest", function () {
var out = { rpc: "vectest", modes: [], err: null };
try {
out.hasValueType = (typeof Il2Cpp.ValueType === "function");
out.hasArray = (typeof Il2Cpp.array === "function");
out.hasString = (typeof Il2Cpp.string === "function");
var V = bpClass("UnityEngine.Vector3");
if (!V) { out.err = "no-vector3"; return JSON.stringify(out); }
// 静态方法
var M = null;
try { M = V.method("Distance"); } catch (e) { out.err = "no-Distance:" + String(e.message).slice(0, 80); return JSON.stringify(out); }
try { out.distSig = String(M.fridaSignature); } catch (e) { }
try { out.distParamCount = Number(M.parameterCount); } catch (e) { }
var A = [1, 0, 0], B = [4, 0, 0]; // 期望距离 3
// mode 0: 原生 JS 数组
(function () {
var r = { mode: "js-array" };
try { r.res = String(M.invoke(A, B)); } catch (e) { r.err = String(e.message || e).slice(0, 140); }
out.modes.push(r);
})();
// mode 1: Il2Cpp.ValueType
(function () {
var r = { mode: "ValueType" };
if (typeof Il2Cpp.ValueType !== "function") { r.err = "no-Il2Cpp.ValueType"; out.modes.push(r); return; }
function mk(a, b, c) {
var vt = new Il2Cpp.ValueType(Memory.alloc(V.class.valueTypeSize), V.type);
vt.field("x").value = a; vt.field("y").value = b; vt.field("z").value = c;
return vt;
}
try {
var va = mk(1, 0, 0), vb = mk(4, 0, 0);
try { r.readback = [Number(va.field("x").value), Number(va.field("y").value), Number(va.field("z").value)]; } catch (e) { }
r.res = String(M.invoke(va, vb));
} catch (e) { r.err = String(e.message || e).slice(0, 140); }
out.modes.push(r);
})();
// mode 2: 3 个扁平标量(若签名为扁平)
(function () {
var r = { mode: "flat-scalars" };
try { r.res = String(M.invoke(1, 0, 0, 4, 0, 0)); } catch (e) { r.err = String(e.message || e).slice(0, 140); }
out.modes.push(r);
})();
// mode 3: 指针(12 字节缓冲)
(function () {
var r = { mode: "pointer12" };
try {
var pa = Memory.alloc(12); pa.writeFloat(1); pa.add(4).writeFloat(0); pa.add(8).writeFloat(0);
var pb = Memory.alloc(12); pb.writeFloat(4); pb.add(4).writeFloat(0); pb.add(8).writeFloat(0);
r.res = String(M.invoke(pa, pb));
} catch (e) { r.err = String(e.message || e).slice(0, 140); }
out.modes.push(r);
})();
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R25: 列出一个类的全部方法(名字/参数数/返回/是否静态/frida 签名) ----------
BOWL_RPCS.push(bpRpc("mlist", function (cls, reStr, cap) {
var out = { rpc: "mlist", cls: String(cls || ""), rows: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "no-class"; return JSON.stringify(out); }
var rx = reStr ? new RegExp(String(reStr), "i") : null;
var lim = cap ? Number(cap) : 400;
var ms = K.methods;
out.total = ms.length;
for (var i = 0; i < ms.length && out.rows.length < lim; i++) {
var m = ms[i], nm = "";
try { nm = String(m.name); } catch (e) { continue; }
if (rx && !rx.test(nm)) continue;
var rec = { n: nm };
try { rec.pc = Number(m.parameterCount); } catch (e) { }
try { rec.ret = String(m.returnType.name); } catch (e) { }
try { rec.st = !!m.isStatic; } catch (e) { }
try { rec.sig = String(m.fridaSignature); } catch (e) { }
out.rows.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R26: 通用强类型调用器 ----------
// spec = JSON 数组,元素 {"t":类型,"v":值}
// t: u64|u32|i32|f|bool|null|ptr|str|v3|q|vt(按形参类型构造值类型)
// instMode: "static" 静态 | "instance" 走 get_Instance | 其它 = 静态字段名(取其对象后实例调用)
BOWL_RPCS.push(bpRpc("invk", function (cls, mname, pcStr, instMode, specStr) {
var out = { rpc: "invk", cls: String(cls || ""), m: String(mname || ""), ts: [], err: null };
try {
var K = bpClass(out.cls);
if (!K) { out.err = "no-class"; return JSON.stringify(out); }
var pc = (pcStr === "" || pcStr === null || pcStr === undefined) ? -1 : Number(pcStr);
var spec = JSON.parse(String(specStr || "[]"));
var target = null;
if (String(instMode) === "static") {
target = K.method(out.m, pc);
} else {
var holder = null;
if (String(instMode) === "instance") {
try { holder = K.method("get_Instance").invoke(); } catch (e) { }
} else {
try { holder = K.field(String(instMode)).value; } catch (e) { }
}
if (!holder) { out.err = "no-holder(" + instMode + ")"; return JSON.stringify(out); }
try { if (holder.isNull && holder.isNull()) { out.err = "null-holder"; return JSON.stringify(out); } } catch (e) { }
target = holder.method(out.m, pc);
}
try { out.pc = Number(target.parameterCount); } catch (e) { }
try { out.ret = String(target.returnType.name); } catch (e) { }
var args = [];
for (var i = 0; i < spec.length; i++) {
var a = spec[i], t = String(a.t);
out.ts.push(t);
if (t === "null") args.push(null);
else if (t === "ptr") args.push(ptr(Number(a.v || 0)));
else if (t === "bool") args.push(!!a.v);
else if (t === "str") args.push(Il2Cpp.string(String(a.v)));
else if (t === "v3") args.push([Number(a.v[0]), Number(a.v[1]), Number(a.v[2])]);
else if (t === "q") args.push([Number(a.v[0]), Number(a.v[1]), Number(a.v[2]), Number(a.v[3])]);
else if (t === "vt") {
var pt = target.parameters[i].type;
args.push(Il2Cpp.fromFridaValue(a.v, pt));
}
// vtref: 按形参类型构造值类型,但传它的 handle(用于 ref/out 形参)
else if (t === "vtref") {
var pt2 = target.parameters[i].type;
var vt2 = Il2Cpp.fromFridaValue(a.v, pt2);
args.push(vt2.handle);
}
// new: 新建托管对象。v = [类名, 泛型参数类名...];泛型用反引号,如 HashSet`1
else if (t === "new") {
var names = a.v;
var K2 = null, nm0 = String(names[0]);
K2 = bpClass(nm0);
if (!K2) K2 = bpClass(nm0.replace("`1", ""));
if (!K2) throw new Error("no-cls:" + nm0);
if (names.length > 1) {
var tas = [];
for (var q = 1; q < names.length; q++) {
var tc = bpClass(String(names[q]));
if (!tc) throw new Error("no-arg-cls:" + names[q]);
tas.push(tc);
}
K2 = K2.inflate.apply(K2, tas);
}
args.push(K2.new());
} else args.push(Number(a.v));
}
var rr = target.invoke.apply(target, args);
try { out.res = String(rr); } catch (e) { out.res = "<unprintable>"; }
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R27: 封送正交试验 2(无害 API:null 指针 / 值类型+标量混合) ----------
BOWL_RPCS.push(bpRpc("ptest", function () {
var out = { rpc: "ptest", rows: [], err: null };
function T(name, fn) {
var r = { n: name };
try { r.res = String(fn()); } catch (e) { r.err = String(e && e.message ? e.message : e).slice(0, 190); }
out.rows.push(r);
}
var O = bpClass("UnityEngine.Object");
out.hasObjectCls = !!O;
if (O) {
T("op_Equality(null,null)", function () {
return O.method("op_Equality").invoke(null, null);
});
T("op_Equality(inst,inst)", function () {
var M = bpClass("LX6.Item.SceneItemMgr");
var inst = M.method("get_Instance").invoke();
return O.method("op_Equality").invoke(inst, inst);
});
}
var C = bpClass("UnityEngine.Color");
out.hasColorCls = !!C;
if (C) {
T("Color.Lerp([4],[4],f)", function () {
return String(C.method("Lerp").invoke([1, 1, 1, 1], [0, 0, 0, 0], 0.5));
});
}
var Q = bpClass("UnityEngine.Quaternion");
out.hasQuatCls = !!Q;
if (Q) {
T("Quaternion.Angle([q],[q])", function () {
return String(Q.method("Angle").invoke([0, 0, 0, 1], [0, 0, 0.7071068, 0.7071068]));
});
}
return JSON.stringify(out);
}));
// ---------- R28: 打印一个 hold 的全部字段 + 关联 GameObject ----------
BOWL_RPCS.push(bpRpc("holdf", function (id) {
var out = { rpc: "holdf", id: Number(id), fields: [], err: null };
try {
var M = bpClass("LX6.Item.SceneItemMgr");
var inst = M.method("get_Instance").invoke();
var h = inst.method("GetSceneItemHold").invoke(Number(id));
if (!h) { out.err = "no-hold"; return JSON.stringify(out); }
try { out.cls = String(h.class.name); } catch (e) { }
try {
var fs = h.class.fields;
for (var i = 0; i < fs.length; i++) {
var f = fs[i], nm = "";
try { nm = String(f.name); } catch (e) { continue; }
var rec = { n: nm };
try { rec.st = !!f.isStatic; } catch (e) { }
try { rec.t = String(f.type.name); } catch (e) { }
if (!rec.st) {
try { rec.v = bpFlatten(f.bind(h).value, 8); } catch (e) { rec.v = "<err>"; }
}
out.fields.push(rec);
}
} catch (e) { out.fErr = String(e.message); }
try { out.go = bpHoldName(Number(id)); } catch (e) { }
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- R29: 按 instanceId 设置 hold 的字段(用于关掉 ECS 渲染等) ----------
BOWL_RPCS.push(bpRpc("holdset", function (id, specStr) {
var out = { rpc: "holdset", id: Number(id), done: [], err: null };
try {
var M = bpClass("LX6.Item.SceneItemMgr");
var inst = M.method("get_Instance").invoke();
var h = inst.method("GetSceneItemHold").invoke(Number(id));
if (!h) { out.err = "no-hold"; return JSON.stringify(out); }
var spec = JSON.parse(String(specStr || "{}"));
for (var k in spec) {
var rec = { n: k };
try {
var f = h.field(k);
f.value = spec[k];
try { rec.back = String(f.value); } catch (e) { }
rec.ok = true;
} catch (e) { rec.err = String(e && e.message ? e.message : e).slice(0, 130); }
out.done.push(rec);
}
out.ok = true;
} catch (e) { out.err = String(e && e.message ? e.message : e); }
return JSON.stringify(out);
}));
// ---------- 入口 ----------
// ★★ 重调用互斥闸(2026-09-24 08:17 现场事故之后加的,**这是防卡死的核心**):
// 事故:自动快照里连着调 bowlst(内部要跑 7 次 Il2Cpp.gc.choose)+ 监视线程每 4 秒
// 调一次 holdsum(也要 gc.choose),把 GC 堆全量遍历叠在一起,
// 游戏主线程被拖住 ~30 秒不动(Player.log 08:17:38 停写,08:18:09 才断开)。
// 现在三条规则:
// ① 任何"重"RPC 之间**互斥**:重入直接返回 busy,不会叠成风暴。
// ② 每次耗时记进 BP_COST,`bpsCost()` 可查;>5s 计入 BP_SLOW,>8s 直接 send 一条告警。
// ③ 轻 RPC(只读 JS 变量 / 类元数据,不碰 gc)不受限,可以高频轮询 ——
// `bfStatus` 就属于这一类,它**一个 IL2CPP 调用都不做**,所以心跳用它。
var BP_HEAVY_BUSY = false, BP_HEAVY_OWNER = null, BP_COST = {}, BP_SLOW = 0;
var BP_LIGHT_RPCS = { pos: 1, clsfields: 1, enumv: 1 };
var BOWL_EXPORTS = {};
BOWL_RPCS.forEach(function (r) {
var isLight = !!BP_LIGHT_RPCS[r.name];
BOWL_EXPORTS[r.name] = function () {
var args = Array.prototype.slice.call(arguments);
var res = null;
if (!isLight && BP_HEAVY_BUSY) {
return JSON.stringify({
rpc: r.name, err: "busy: heavy probe already running (" + BP_HEAVY_OWNER + ")"
});
}
if (!isLight) { BP_HEAVY_BUSY = true; BP_HEAVY_OWNER = r.name; }
var t = Date.now();
try {
res = r.fn.apply(null, args);
} catch (e) {
res = JSON.stringify({ rpc: r.name, err: String(e && e.message ? e.message : e) });
} finally {
if (!isLight) {
var ms = Date.now() - t;
BP_COST[r.name] = ms;
if (ms > 5000) BP_SLOW++;
if (ms > 8000) {
try { send(JSON.stringify({ bf: "[SLOW] " + r.name + " took " + ms + "ms" })); } catch (e) { }
}
BP_HEAVY_BUSY = false; BP_HEAVY_OWNER = null;
}
}
// 只把小结果回送(避免几百 KB 的 dump 反复走通道 / 刷屏)
if ("window" in globalThis && typeof globalThis.send === "function") {
try { if (String(res).length <= 1200) send(String(res)); } catch (e) { }
}
return res;
};
});
// ★ 代价查询(轻):看哪些 RPC 慢、有没有卡在 busy
BOWL_EXPORTS.bpsCost = function () {
return JSON.stringify({
rpc: "bpsCost", cost: BP_COST, slow: BP_SLOW,
busy: BP_HEAVY_BUSY, owner: BP_HEAVY_OWNER
});
};
Il2Cpp.perform(function () {
rpc.exports = Object.assign(rpc.exports || {}, BOWL_EXPORTS);
try { send(JSON.stringify({ ready: true, rpcs: BOWL_RPCS.length })); } catch (e) { }
});
// ================== 保龄球客户端补丁 BF1(常驻版)==================
//
// 原理(v7..v12 实测闭环):
// 客户端造 SceneItemHold 的**唯一**入口是
// LX6.Item.SceneItemBlob.TryGetSceneItemPlaceInfoDataByPlaceId(
// UInt64 uniqueId, out MA_Ref<SceneItemPlaceInfoData> data, out SceneItemBlobType& blobType)
// 它是对「关卡烘焙摆位表」的一次查表。官方关卡里**没有烘焙任何球瓶/球**
// (离线证据:StreamingAssets/Blocks/vfc_1286 里 cell(67,39) 的 blob 只有 19 个
// 椅子/长椅/垃圾桶/广告牌;BowlingConfig.json 记录数为 0),
// 所以服务端推来的球瓶 id 查不到 → 返回 false → 不调 SyncAddSceneItemByLocalData
// → 不建 hold → 「球瓶按下按钮后消失」、「投球时手上没球」、「pool[101] is nil」。
//
// 本补丁让这个函数对「服务端的球瓶/球 id」返回 true,并把一个我们构造好的
// SceneItemPlaceInfoData 交出去 ⇒ 客户端原生流程全通:
// SyncAddSceneItemByLocalData → SyncAddSceneItemByIdData → DoSceneItemHolderEnterWorld
//
// SceneItemPlaceInfoData 实测内存布局(instanceSize=112;frida 报的 field.offset 比实际大 16):
// +0x00 BlobArray<Int32> linkPath {ptr,len} +0x10 UInt64 uniqueId
// +0x18 UInt32 sceneItemCfgId +0x1C Int32 pathId
// +0x20 Int32 groupId +0x24 f32 posX
// +0x28 f32 posY +0x2C f32 posZ
// +0x30 f32 eulerX +0x34 f32 eulerY
// +0x38 f32 eulerZ +0x3C Int32 iScale
// +0x40 Int32 navmeshId +0x44 BlobArray<SpecialParamData>
// +0x54 BlobArray<UInt64> linkSceneItemId
//
// 做法:先主动查一个真 placeId 拿「合法模板」,再克隆成我们的 15 个条目。
(function () {
'use strict';
function sendlog(s) { try { send(JSON.stringify({ bf: String(s) })); } catch (e) { } }
// ★★ 2026-09-30 卡死修复 ①:热路径(hook 回调)**绝不能**直接 send()。
// send() 是给 Python 宿主的 IPC:宿主正忙 / 出站队列有背压时会**阻塞调用它的线程**,
// 而 hook 回调跑在**客户端物件加载线程**上 ⇒ 整条加载流水线停摆
// (症状正是实测到的:CPU≈0.05 核、日志 0 增长的静默死锁)。
// ⇒ 热路径只往缓冲区塞一行,由 setInterval 在 frida 自己的 JS 线程上批量发出。
var LOGBUF = [];
var LOGBUF_MAX = 200; // 满了就丢,绝不扩张、绝不阻塞
function sendlogLater(s) {
try { if (LOGBUF.length < LOGBUF_MAX) LOGBUF.push(String(s)); } catch (e) { }
}
function flushLog() {
if (!LOGBUF.length) return;
try {
var n = Math.min(LOGBUF.length, 30); // 每次最多 30 行,避免刷屏
for (var i = 0; i < n; i++) send(JSON.stringify({ bf: LOGBUF[i] }));
LOGBUF.splice(0, n);
} catch (e) { }
}
var LOGIV = setInterval(flushLog, 700);
// ---- 数据:与 GameRouter.GameGround.cs 的 BowlingLanes / 常量 完全一致 ----
// (laneZ, PinApexX, GameNodeX) 顺序 = C# BowlingLanes[](按 z 降序)
var LANES = [
[1566.7732, 2710.2510, 2692.2559],
[1564.7737, 2710.2056, 2692.2559],
[1572.1193, 2710.3689, 2692.3738],
[1570.1198, 2710.3235, 2692.3738],
[1576.1143, 2710.4600, 2692.4648],
[1574.1147, 2710.4146, 2692.4648],
[1581.4462, 2710.5840, 2692.5889],
[1579.4467, 2710.5386, 2692.5889]
];
var PIN_CFG = 80003145, PIN_PATH = 1959229003;
var BALL_CFG = 80002967, BALL_PATH = 1838106492;
var ID_BASE = 0xd3e03a0c01; // _bowlingSceneItemSeq 起点 910000000000 + 1
var BATCH = 15; // 10 瓶 + 5 球
// 模板候选(球馆格 (67,39) 的 5 个真实烘焙 placeId)
var TPL_CANDIDATES = [
'0x482716fd0e9d2f30', '0x92204198eaef66de', '0x9bc102684b95d776',
'0xce21103f6e9c1809', '0xebf189795fb8d298'
];
var TPL = null, TPLPTR = null, SLOTS = null;
var ARMED = false, FAKED = 0, HITS = 0;
var LANEIDX = -1, ITEMS = null;
var PMGR = null, PCACHE = { t: 0, z: null };
var QUERY_VA = null;
function bfClass(full) {
var asms;
try { asms = Il2Cpp.domain.assemblies; } catch (e) { return null; }
for (var j = 0; j < asms.length; j++) {
try { var cc = asms[j].image.class(full); if (cc) return cc; } catch (e) { }
}
for (var i = 0; i < asms.length; i++) {
var classes = null;
try { classes = asms[i].image.classes; } catch (e) { continue; }
if (!classes) continue;
for (var c = 0; c < classes.length; c++) {
var nm = null;
try { nm = String(classes[c].name); } catch (e) { continue; }
if (nm === full) return classes[c];
}
}
return null;
}
function cp(dst, src, len) {
try { dst.writeByteArray(src.readByteArray(len)); return true; } catch (e) { return false; }
}
// ★ 热路径优化(2026-09-24):isOurs 被 GetSceneItemHold / 查表 hook 的**每一次**调用触发,
// 原来每次都 toString(16) + indexOf,字符串分配很贵。改成指针区间比较。
// 我们关心的 id 全在 0xD3E03A0000..0xD3E0400000(= 十进制 910000000000 起)这一段。
var OURS_LO = null, OURS_HI = null;
function isOurs(p) {
try {
if (OURS_LO === null) { OURS_LO = ptr('0xd3e03a0000'); OURS_HI = ptr('0xd3e0400000'); }
if (p && typeof p.compare === 'function') {
if (p.compare(OURS_LO) < 0) return false;
if (p.compare(OURS_HI) >= 0) return false;
return true;
}
if (typeof p === 'number') return p >= 0xd3e03a0000 && p < 0xd3e0400000;
return p.toString(16).indexOf('d3e03a') === 0;
} catch (e) { return false; }
}
// ★ 紧急刹车:把三个 Interceptor 全部摘掉(游戏一卡就能立刻停手,不需要关客户端)
var LISTENERS = [];
// ★★ 停机标志:置上之后所有 hook 回调立刻短路,配合 detach 保证「卸载时没有任何
// JS 正在跑」—— 这是「关掉加载器 → 游戏卡死」那种事故的对症解法。
var DISARMED = false;
// ---- 由玩家 z 选最近的道(与 C# BowlingPickLane 同规则)----
function pickLaneIdx(z) {
if (typeof z !== 'number' || !isFinite(z)) return -1;
var best = -1, bd = 1e9;
for (var i = 0; i < LANES.length; i++) {
var d = Math.abs(LANES[i][0] - z);
if (d < bd) { bd = d; best = i; }
}
return best;
}
// ---- 15 个条目(10 瓶 1-2-3-4 三角 + 5 球一排),与 C# PushBowlingSceneItemsAsync 同公式 ----
function buildItems(laneIdx) {
var L = LANES[laneIdx];
var laneZ = L[0], pinApexX = L[1], gameNodeX = L[2];
var laneHeadX = gameNodeX + 0.24;
var out = [];
for (var r = 0; r < 4; r++) {
var n = r + 1;
for (var c = 0; c < n; c++) {
out.push({
cfg: PIN_CFG, path: PIN_PATH, x: pinApexX + r * 0.26, y: 0.02,
z: laneZ + (c - (n - 1) * 0.5) * 0.30, s: 100
});
}
}
for (var b = 0; b < 5; b++) {
out.push({
cfg: BALL_CFG, path: BALL_PATH, x: laneHeadX - 1.0, y: 0.12,
z: laneZ + (b - 2) * 0.35, s: 100
});
}
return out;
}
Il2Cpp.perform(function () {
var blob = bfClass('LX6.Item.SceneItemBlob');
if (!blob) { sendlog('ERR: SceneItemBlob not found'); return; }
try { QUERY_VA = blob.method('TryGetSceneItemPlaceInfoDataByPlaceId').virtualAddress; }
catch (e) { sendlog('ERR: query method ' + e); return; }
SLOTS = Memory.alloc(112 * BATCH);
function writeSlot(slot, idPtr, it) {
if (TPL) cp(slot, TPL, 112);
slot.add(0x10).writePointer(idPtr);
slot.add(0x18).writeU32(it.cfg);
slot.add(0x1C).writeS32(it.path);
slot.add(0x24).writeFloat(it.x);
slot.add(0x28).writeFloat(it.y);
slot.add(0x2C).writeFloat(it.z);
// ★★ 「最小覆写」原则 —— 2026-09-24 08:32 实测定案(bfTplDump 逐字段对比得出):
//
// 旧实现(v12)在拷完模板后还额外做了:
// +0x20 groupId 写 0 → 模板本来就是 0,无影响
// +0x30 eulerX 写 0 → 模板本来就是 0,无影响
// +0x34 eulerY 写 0 → 模板是 359.963(≈整圈),与缩放无关
// +0x38 eulerZ 写 0 → 模板本来就是 0,无影响
// +0x3C iScale 写 100 → 模板是 **0** ← ★★★ 就是它
//
// 症状:生成对象的 localScale 退化成 **[0, 0, 1]** ⇒ 尺寸压塌、画不出来
// (有碰撞、有音效,就是没画面)。改回沿用模板后 localScale = [1, 1, 1],
// 用户实测「可以看到球了,也有击中的物理效果」。
//
// `iScale` 很可能是**尺寸类型枚举/索引**(资源表里球瓶 sizeType=0、球 sizeType=1),
// 0 = 默认尺寸;写 100 是非法值 ⇒ 退化。
//
// ⇒ 教训:**含义没吃透的字段,"保持模板原值"永远比"写个我猜的值"安全。**
// 想改什么就改什么,别顺手清零。诊断用 bfTplDump / bfSetField。
}
function fetchTpl() {
for (var i = 0; i < TPL_CANDIDATES.length; i++) {
try {
var fn = new NativeFunction(ptr(QUERY_VA), 'int',
['uint64', 'pointer', 'pointer']);
var bA = Memory.alloc(64), bB = Memory.alloc(64);
var zero = new ArrayBuffer(64);
bA.writeByteArray(zero); bB.writeByteArray(zero);
var ok = fn(uint64(TPL_CANDIDATES[i]), bA, bB);
if (!ok) continue;
var p = bA.readPointer();
if (!p || p.toString() === '0x0') continue;
var t = Memory.alloc(112);
if (!cp(t, p, 112)) continue;
TPL = t; TPLPTR = p;
sendlog('template captured from ' + TPL_CANDIDATES[i] + ' -> ' + p.toString(16) +
' cfg=' + TPL.add(0x18).readU32() + ' z=' + TPL.add(0x2C).readFloat().toFixed(2));
return true;
} catch (e) { sendlog('tpl try err ' + e); }
}
return false;
}
// ★★ 2026-09-30 卡死修复 ②(**主犯**):热路径里绝不调用任何 IL2CPP API。
//
// 原实现:onLeave → playerZ() → PMGR.method('get_PlayerUnit',0).invoke()
// invoke() = il2cpp_runtime_invoke:要进 VM、可能触发**类加载**与 **GC**,
// 还要拿 VM / 类加载器相关的锁。而我们的回调就跑在**客户端物件加载线程**上,
// 那个线程此刻正在做类加载 + 反序列化 —— 两边互抢同一把锁 ⇒ **死锁**。
//
// 修法:
// · 解析动作搬到 Il2Cpp.perform 的安全上下文里做(启动时一次,且不在任何
// hook 回调内)⇒ 只拿到 PlayerUnit 句柄 + lastPosition 字段偏移;
// · 热路径退化成 **纯原始内存读**(零 IL2CPP 调用、零锁、零 GC);
// · 需要刷新时由 Il2Cpp.perform 在 frida 自己的线程上做,不碰游戏线程。
var PUNIT = null, POFF = -1, PKLASS = null;
function refreshPlayer() {
try {
if (!PMGR) PMGR = bfClass('LX6.Game.MyPlayerManager');
if (!PMGR) return;
var u = PMGR.method('get_PlayerUnit', 0).invoke();
if (!u || u.handle.isNull()) return;
var v = u.field('lastPosition').value;
if (!v || !v.handle) return;
var off = v.handle.sub(u.handle).toInt32(); // 字段相对对象头的偏移
if (off < 0 || off > 0x4000) return;
PUNIT = u.handle; POFF = off;
try { PKLASS = u.handle.readPointer(); } catch (e) { PKLASS = null; }
var zz = u.handle.add(off + 8).readFloat(); // Vector3: x=+0 y=+4 z=+8
if (isFinite(zz)) PCACHE = { t: Date.now(), z: zz };
} catch (e) { }
}
function playerZ() {
if (PUNIT && POFF >= 0) {
try {
// 类指针不变 ⇒ 对象没被回收(IL2CPP 的 GC 是非移动式)
if (PKLASS && !PUNIT.readPointer().equals(PKLASS)) return PCACHE.z;
var zz = PUNIT.add(POFF + 8).readFloat();
if (isFinite(zz) && zz !== 0) { PCACHE = { t: Date.now(), z: zz }; return zz; }
} catch (e) { }
}
return PCACHE.z; // 拿不到就用上一次的值 —— 永远不阻塞、永远不进 IL2CPP
}
// ★ 卡死修复 ③(配套):模板探测必须**在挂 hook 之前**做。
// 挂上之后再调 fetchTpl() 就是通过被 hook 的地址重入同一个函数(见 onLeave 里的说明)。
try { fetchTpl(); } catch (e) { }
if (TPL) sendlog('template captured (pre-hook probe) -> ' + TPLPTR.toString(16));
// 启动时解析一次玩家句柄(此刻不在任何 hook 回调里 ⇒ 可以安全调用 IL2CPP API)
try { refreshPlayer(); } catch (e) { }
sendlog('player resolved: unit=' + (PUNIT ? PUNIT.toString(16) : 'null') +
' off=' + POFF + ' z=' + (PCACHE.z === null ? '?' : PCACHE.z.toFixed(2)));
// ---------------- 核心 hook ----------------
LISTENERS.push(Interceptor.attach(ptr(QUERY_VA), {
onEnter: function (args) {
this.id = args[0];
this.outA = args[1];
this.outB = args[2];
},
onLeave: function (retval) {
if (DISARMED) return; // ★ 已停机:一个字节都不再碰
var ok = 0;
try { ok = retval.toInt32(); } catch (e) { }
if (ok !== 0) {
// 真实 placeId 成功 —— 若还没模板,立刻收一份
if (!TPL) {
try {
var p = this.outA.readPointer();
if (p && p.toString() !== '0x0') {
var t = Memory.alloc(112);
if (cp(t, p, 112)) {
TPL = t; TPLPTR = p;
sendlogLater('template captured (hook) -> ' + p.toString(16));
}
}
} catch (e) { }
}
return;
}
if (!isOurs(this.id)) return;
HITS++;
if (!ARMED) return;
// ★ 卡死修复 ③:这里原本是 `if (!TPL) { if (!fetchTpl()) return; }`。
// fetchTpl() 会通过 NativeFunction **直接调用 QUERY_VA**,而 QUERY_VA 此刻
// 已经被我们 hook 了 ⇒ 等于「在自己的 onLeave 里重入同一个被 hook 的函数」。
// 一旦该函数内部有非重入锁(或它自己也要进 VM)就会自锁 ⇒ 死锁。
// ⇒ 模板只留两个来源:① 挂 hook **之前**的探测;② 真实查表成功时的**被动**
// 捕获(纯内存读,安全)。拿不到模板就这一次不伪造,等下一次。
if (!TPL) return;
// 选道:按玩家 z;拿不到就退回上一次/第 0 条
var z = playerZ();
var li = pickLaneIdx(z);
if (li >= 0 && li !== LANEIDX) {
LANEIDX = li;
ITEMS = buildItems(LANEIDX);
sendlogLater('lane -> #' + LANEIDX + ' (laneZ=' + LANES[LANEIDX][0] +
', playerZ=' + (z === null ? '?' : z.toFixed(2)) + ')');
}
if (LANEIDX < 0) { LANEIDX = 0; ITEMS = buildItems(0); }
var n = 0;
try { n = parseInt(this.id.toString(16), 16); } catch (e) { }
var idx = (n - ID_BASE) % BATCH;
if (idx < 0) idx += BATCH;
var it = ITEMS[idx];
try {
writeSlot(SLOTS.add(idx * 112), this.id, it);
this.outA.writePointer(SLOTS.add(idx * 112));
retval.replace(ptr(1));
FAKED++;
if (FAKED <= 20) {
sendlogLater('FAKED id=' + this.id.toString(16) + ' idx=' + idx +
' cfg=' + it.cfg + ' pos=(' + it.x.toFixed(2) + ',' +
it.y.toFixed(2) + ',' + it.z.toFixed(2) + ')');
}
} catch (e) { sendlogLater('fake err ' + e); }
}
}));
ARMED = true;
sendlog('armed. waiting for template... (hint: stand inside the bowling alley so the AOI table loads)');
// ---- 观测 hook(★ 卡死修复 ④:默认**不挂**)----
// 这两个 hook 只统计/打日志,对伪造功能零贡献,却挂在 GetSceneItemHold /
// DoSceneItemHolderEnterWorld 上 —— 与核心 hook 是同一条物件加载热路径。
// 每多一个 Interceptor,每次调用就多一次 trampoline 跳转 + 一次 JS 回调
// (而且另一个模块 balloon_fix.js 也 hook 了 QUERY_VA,热路径上本来就有两份)。
// ⇒ 默认关;要诊断时用 rpc `bfDiag(true)` 现挂,`bfDiag(false)` 摘掉。
var DIAG = false;
var DIAG_LS = [];
var HOLDS_OK = 0, HOLDS_NIL = 0;
function attachDiag() {
if (DIAG_LS.length) return;
try {
var mgrCls = bfClass('LX6.Item.SceneItemMgr');
var GET_VA = mgrCls.method('GetSceneItemHold').virtualAddress;
DIAG_LS.push(Interceptor.attach(ptr(GET_VA), {
onEnter: function (args) {
if (DISARMED) return;
// ★ 顺序反了会多跑一次 isOurs:这里先试更可能是 id 的那个实参
var a1 = isOurs(args[1]) ? args[1] : null;
this.idp = a1 !== null ? a1 : (isOurs(args[0]) ? args[0] : null);
},
onLeave: function (rv) {
if (DISARMED || !this.idp) return; // ★ 停机后一个字节都不再碰
var s = '0x' + rv.toString(16);
if (s === '0x0') {
HOLDS_NIL++;
if (HOLDS_NIL <= 20) sendlogLater('hold NIL for ' + this.idp.toString(16));
} else {
HOLDS_OK++;
if (HOLDS_OK <= 20) sendlogLater('hold OK for ' + this.idp.toString(16));
}
}
}));
// DoSceneItemHolderEnterWorld:hold 真正进世界的时刻
try {
var EW_VA = mgrCls.method('DoSceneItemHolderEnterWorld').virtualAddress;
DIAG_LS.push(Interceptor.attach(ptr(EW_VA), {
onEnter: function (args) {
if (DISARMED) return;
if (isOurs(args[1])) sendlogLater('holder entered world: ' + args[1].toString(16));
}
}));
} catch (e) { }
} catch (e) { sendlog('watch hook err ' + e); }
}
if (DIAG) attachDiag();
// ★ 卡死修复 ③(配套):这个 tick 不再调 fetchTpl() —— 挂上 hook 之后再调它就是
// 重入被 hook 的函数。模板只靠「挂 hook 前的探测」+「真实查表成功时的被动捕获」。
//
// ⛑⛑ 2026-09-30 11:0x 二次修正:这里**曾经**是 `Il2Cpp.perform(refreshPlayer)`,
// 那是个错误 —— `Il2Cpp.perform` 要把任务投递到 IL2CPP 线程执行并等待结果,
// 而与此同时「物件加载线程」可能正堵在这个 script 的运行时锁上等 hook 回调 ⇒
// 两边互等(定时器等游戏线程 / 游戏线程等锁)。这是**我自己引入的**新死锁面。
// ⇒ 干脆不再周期性刷新:启动时解析一次就够了(IL2CPP 用非移动 GC,句柄长期有效),
// 解析失败时 playerZ() 会安全返回上一次的值,不会阻塞、不会进 IL2CPP。
// (真的要重新解析,用 rpc `bfRefetch` 或重启注入器。)
var tick = setInterval(function () {
if (DISARMED) return;
}, 3000);
// 供外部查询状态
rpc.exports = rpc.exports || {};
rpc.exports.bfStatus = function () {
return JSON.stringify({
armed: ARMED, tpl: !!TPL,
tplPtr: TPLPTR ? TPLPTR.toString(16) : null,
tplCfg: TPL ? TPL.add(0x18).readU32() : null,
lane: LANEIDX, hits: HITS, faked: FAKED,
holdsOk: HOLDS_OK, holdsNil: HOLDS_NIL,
playerZ: PCACHE.z, hooks: LISTENERS.length,
punit: PUNIT ? PUNIT.toString(16) : null, poff: POFF, diag: DIAG,
disarmed: DISARMED
});
};
// ★ 卡死修复 ③:挂上 hook 之后**不能**再用 fetchTpl() 主动探测
// (= 通过被 hook 的地址重入同一个函数)⇒ 只清模板,交给被动捕获。
rpc.exports.bfRefetch = function () {
TPL = null;
if (LISTENERS.length > 0) return 'cleared — will re-capture passively on next real lookup';
return fetchTpl() ? 'ok' : 'fail';
};
// ★ 卡死修复 ④:观测 hook 的开关(默认关,见 attachDiag 的说明)
rpc.exports.bfDiag = function (on) {
DIAG = !!on;
if (DIAG) { attachDiag(); return 'diag on (' + DIAG_LS.length + ' listener(s))'; }
var n = 0;
for (var i = 0; i < DIAG_LS.length; i++) { try { DIAG_LS[i].detach(); n++; } catch (e) { } }
DIAG_LS = [];
return 'diag off (detached ' + n + ')';
};
// ★★ 诊断 RPC:把 112 字节的 placeInfo 逐字段摊开。
// 目的:回答「+0x30/+0x34/+0x38 到底是 euler 角还是缩放分量」
// —— 拿**真实模板**(合法 scene item)的值做基准,一看便知。
function dump112(p, tag) {
var o = { tag: tag, hex: null, dec: [], err: null };
try {
var b = new Uint8Array(p.readByteArray(112));
var h = [];
for (var i = 0; i < 112; i++) h.push(('0' + b[i].toString(16)).slice(-2));
o.hex = h.join('');
for (var off = 0; off < 112; off += 4) {
var s = null, f = null;
try { s = p.add(off).readS32(); } catch (e) { }
try { f = p.add(off).readFloat(); } catch (e) { }
o.dec.push('+0x' + ('0' + off.toString(16)).slice(-2) +
' i32=' + s + ' f32=' + (f === null ? '?' : (Math.abs(f) < 1e-7 ? '0' : f.toFixed(5))));
}
} catch (e) { o.err = String(e); }
return o;
}
rpc.exports.bfTplDump = function () {
var out = { rpc: 'bfTplDump', tplCfg: null, template: null, slot0: null, err: null };
if (!TPL) { out.err = 'no-template-yet'; return JSON.stringify(out); }
try { out.tplCfg = TPL.add(0x18).readU32(); } catch (e) { }
out.template = dump112(TPL, 'template(real)');
if (SLOTS) { try { out.slot0 = dump112(SLOTS, 'slot0(what-we-write)'); } catch (e) { } }
return JSON.stringify(out);
};
// ★ 实验用:改「我们写入的 slot」里任意 4 字节字段。
// 例:bfSetField(0, 0x3c, 1) 把条 0 的 +0x3C 改成 1
// bfSetField(0, 0x3c, 0) 改回模板原值
// kind:'i'(默认,Int32)或 'f'(float)。
// ⚠ 只影响**之后**新建的 hold;已经进世界的对象不会变(要重开一局才生效)。
rpc.exports.bfSetField = function (idx, off, val, kind) {
if (!SLOTS) return 'no-slots';
idx = parseInt(idx, 10); off = parseInt(off, 10);
if (!(idx >= 0 && idx < BATCH) || off < 0 || off > 108) return 'bad-args';
var p = SLOTS.add(idx * 112 + off);
if (kind === 'f') p.writeFloat(Number(val)); else p.writeS32(parseInt(val, 10));
return 'slot[' + idx + ']+0x' + off.toString(16) + ' = ' + val + ' (' + (kind || 'i32') + ')';
};
// ★ 紧急刹车:游戏一卡就调它,三个 Interceptor 全摘掉,立刻停止一切干扰。
// (不需要关客户端;Python 侧 Ctrl+C 也行,但钩子会留在进程里直到游戏退出。)
rpc.exports.bfDisarm = function () {
DISARMED = true;
var n = 0;
for (var i = 0; i < LISTENERS.length; i++) {
try { LISTENERS[i].detach(); n++; } catch (e) { }
}
LISTENERS = [];
for (var j = 0; j < DIAG_LS.length; j++) {
try { DIAG_LS[j].detach(); n++; } catch (e) { }
}
DIAG_LS = [];
ARMED = false;
try { clearInterval(tick); } catch (e) { }
try { clearInterval(LOGIV); } catch (e) { }
try { flushLog(); } catch (e) { } // 停机前把缓冲区里剩的打出来
sendlog('DISARMED: detached ' + n + ' listener(s)');
return 'detached ' + n;
};
// ★★ 停机协议(加载器关闭前**必调**,也是唯一该在 unload 脚本之前调的接口)
// ① 置 DISARMED ⇒ 所有 hook 回调立刻短路(在途的那次也安全)
// ② detach 全部 Interceptor
// ③ 清掉 setInterval(补模板用的那个)
// 返回后 agent 已彻底停手 —— 这时才允许 Python 侧 unload 脚本。
rpc.exports.bowlShutdown = function () {
DISARMED = true;
try { clearInterval(tick); } catch (e) { }
try { clearInterval(LOGIV); } catch (e) { }
var n = 0;
for (var i = 0; i < LISTENERS.length; i++) {
try { LISTENERS[i].detach(); n++; } catch (e) { }
}
LISTENERS = [];
for (var j = 0; j < DIAG_LS.length; j++) {
try { DIAG_LS[j].detach(); n++; } catch (e) { }
}
DIAG_LS = [];
ARMED = false;
try { flushLog(); } catch (e) { } // 停机前把缓冲区里剩的打出来
try { sendlog('SHUTDOWN: disabled + detached ' + n + ' listener(s)'); } catch (e) { }
return JSON.stringify({ ok: true, detached: n, faked: FAKED, holdsOk: HOLDS_OK });
};
});
})();