Files
Radicade/Tools/quest-platform-inspect.c
2026-10-16 23:33:13 -07:00

125 lines
6.1 KiB
C

// Read-only runtime metadata inspection; no account values or method calls.
#include <android/log.h>
#include <dlfcn.h>
#include <jni.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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;
}