// Read-only runtime metadata inspection; no account values or method calls. #include #include #include #include #include #include #include #include #define LOG(...) __android_log_print(ANDROID_LOG_INFO, "RadicadePlatform", __VA_ARGS__) #define API(name, ret, args) ret (*name) args = (ret (*) args)dlsym(lib, #name); if (!name) { LOG("Missing API: %s", #name); return 0; } static void *inspect(void *unused) { (void)unused; // Let Unity finish its single-threaded runtime initialization first. sleep(30); void *lib = 0; for (int i = 0; i < 120 && !lib; ++i) { lib = dlopen("libil2cpp.so", RTLD_NOW | RTLD_NOLOAD); if (!lib) sleep(1); } if (!lib) { LOG("IL2CPP not loaded within 120 seconds"); return 0; } API(il2cpp_domain_get, void *, (void)); API(il2cpp_domain_get_assemblies, const void **, (void *, size_t *)); API(il2cpp_thread_attach, void *, (void *)); API(il2cpp_thread_detach, void, (void *)); API(il2cpp_assembly_get_image, void *, (const void *)); API(il2cpp_image_get_name, const char *, (void *)); API(il2cpp_image_get_class_count, size_t, (void *)); API(il2cpp_image_get_class, void *, (void *, size_t)); API(il2cpp_class_get_name, const char *, (void *)); API(il2cpp_class_get_namespace, const char *, (void *)); API(il2cpp_class_from_name, void *, (void *, const char *, const char *)); API(il2cpp_class_get_methods, const void *, (void *, void **)); API(il2cpp_method_get_name, const char *, (const void *)); API(il2cpp_method_get_param_count, uint32_t, (const void *)); API(il2cpp_class_get_fields, void *, (void *, void **)); API(il2cpp_field_get_name, const char *, (void *)); API(il2cpp_field_get_offset, size_t, (void *)); API(il2cpp_field_get_type, void *, (void *)); API(il2cpp_type_get_name, char *, (void *)); API(il2cpp_free, void, (void *)); void *domain = 0; size_t count = 0; const void **assemblies = 0; for (int i = 0; i < 120; ++i) { domain = il2cpp_domain_get(); if (domain) assemblies = il2cpp_domain_get_assemblies(domain, &count); if (count) break; sleep(1); } if (!domain || !count) { LOG("IL2CPP domain unavailable"); return 0; } void *thread = il2cpp_thread_attach(domain); if (!thread) { LOG("Thread attach failed"); return 0; } char fields_path[192], map_path[192]; unsigned user = (unsigned)getuid() / 100000; snprintf(fields_path, sizeof(fields_path), "/data/user/%u/com.radicade.questtls/files/quest-recnet-fields.tsv", user); snprintf(map_path, sizeof(map_path), "/data/user/%u/com.radicade.questtls/files/quest-method-map.tsv", user); FILE *fields = fopen(fields_path, "w"); FILE *map = fopen(map_path, "w"); if (map) { for (size_t a = 0; a < count; ++a) { void *img = il2cpp_assembly_get_image(assemblies[a]); size_t classes = il2cpp_image_get_class_count(img); for (size_t c = 0; c < classes; ++c) { void *klass = il2cpp_image_get_class(img, c); if (!klass) continue; if (fields && !strncmp(il2cpp_image_get_name(img), "RecNet.", 7)) { void *fi = 0, *f; while ((f = il2cpp_class_get_fields(klass, &fi))) { char *tn = il2cpp_type_get_name(il2cpp_field_get_type(f)); fprintf(fields, "%s\t%s\t%zx\t%s\n", il2cpp_class_get_name(klass), il2cpp_field_get_name(f), il2cpp_field_get_offset(f), tn ? tn : "?"); if (tn) il2cpp_free(tn); } } void *iter = 0; const void *method; while ((method = il2cpp_class_get_methods(klass, &iter))) { void *code = *(void *const *)method; Dl_info info = {0}; if (code && dladdr(code, &info)) fprintf(map, "%lx\t%s\t%s\t%s\t%s\t%u\n", (unsigned long)((uintptr_t)code - (uintptr_t)info.dli_fbase), il2cpp_image_get_name(img), il2cpp_class_get_namespace(klass), il2cpp_class_get_name(klass), il2cpp_method_get_name(method), il2cpp_method_get_param_count(method)); } } } fclose(map); LOG("Full method map saved (metadata only)"); } else LOG("Cannot open method map output"); const char *names[] = {"PlatformManager", "PlatformManagerBootstrapper", "OculusPlatformManager", "StandalonePlatformManager", "GooglePlayPlatformManager", "BootSequence"}; for (size_t a = 0; a < count; ++a) { void *img = il2cpp_assembly_get_image(assemblies[a]); if (strcmp(il2cpp_image_get_name(img), "Assembly-CSharp.dll")) continue; for (size_t n = 0; n < sizeof(names)/sizeof(names[0]); ++n) { void *klass = il2cpp_class_from_name(img, "", names[n]); LOG("Class %s found=%d", names[n], klass != 0); if (!klass) continue; void *iter = 0; const void *method; while ((method = il2cpp_class_get_methods(klass, &iter))) { void *code = *(void *const *)method; Dl_info info = {0}; uintptr_t rva = dladdr(code, &info) ? (uintptr_t)code - (uintptr_t)info.dli_fbase : 0; LOG("Method %s.%s argc=%u rva=0x%lx", names[n], il2cpp_method_get_name(method), il2cpp_method_get_param_count(method), (unsigned long)rva); } iter = 0; void *field; while ((field = il2cpp_class_get_fields(klass, &iter))) LOG("Field %s.%s offset=0x%lx", names[n], il2cpp_field_get_name(field), (unsigned long)il2cpp_field_get_offset(field)); } } if (fields) fclose(fields); il2cpp_thread_detach(thread); LOG("Inspection complete"); return 0; } JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { (void)vm; (void)reserved; pthread_t thread; if (!pthread_create(&thread, 0, inspect, 0)) pthread_detach(thread); return JNI_VERSION_1_6; }