5930 lines
249 KiB
JavaScript
5930 lines
249 KiB
JavaScript
// 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 });
|
||
};
|
||
});
|
||
})();
|