#if CPP using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnhollowerBaseLib; using UnhollowerRuntimeLib; using System.Runtime.InteropServices; using System.Reflection; using System.Collections; using System.IO; using System.Diagnostics.CodeAnalysis; using UnityExplorer.Core; using CppType = Il2CppSystem.Type; using BF = System.Reflection.BindingFlags; namespace UnityExplorer.Core.Runtime.Il2Cpp { [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")] public class Il2CppReflection : ReflectionProvider { public Il2CppReflection() : base() { Instance = this; TryLoadGameModules(); } public override object Cast(object obj, Type castTo) { return Il2CppCast(obj, castTo); } public override T TryCast(object obj) { try { return (T)Il2CppCast(obj, typeof(T)); } catch { return default; } } public override void BoxStringToType(ref object value, Type castTo) { if (castTo == typeof(Il2CppSystem.String)) value = (Il2CppSystem.String)(value as string); else value = (Il2CppSystem.Object)(value as string); } public override string UnboxString(object value) { if (value is string s) return s; s = null; // strings boxed as Il2CppSystem.Objects can behave weirdly. // GetActualType will find they are a string, but if its boxed // then we need to unbox it like this... if (value is Il2CppSystem.Object cppObject) s = cppObject.ToString(); else if (value is Il2CppSystem.String cppString) s = cppString; return s; } public override string ProcessTypeNameInString(Type type, string theString, ref string typeName) { if (!Il2CppTypeNotNull(type)) return theString; var cppType = Il2CppType.From(type); if (cppType != null && s_deobfuscatedTypeNames.ContainsKey(cppType.FullName)) { typeName = s_deobfuscatedTypeNames[cppType.FullName]; theString = theString.Replace(cppType.FullName, typeName); } return theString; } public override Type GetActualType(object obj) { if (obj == null) return null; var type = obj.GetType(); if (obj is Il2CppSystem.Object cppObject) { // weird specific case - if the object is an Il2CppSystem.Type, then return so manually. if (cppObject is CppType) return typeof(CppType); if (!string.IsNullOrEmpty(type.Namespace)) { // Il2CppSystem-namespace objects should just return GetType, // because using GetIl2CppType returns the System namespace type instead. if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem.")) return cppObject.GetType(); } var cppType = cppObject.GetIl2CppType(); // check if type is injected IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer); if (RuntimeSpecificsStore.IsInjected(classPtr)) { var typeByName = ReflectionUtility.GetTypeByName(cppType.FullName); if (typeByName != null) return typeByName; } // this should be fine for all other il2cpp objects var getType = GetMonoType(cppType); if (getType != null) return getType; } return type; } // caching for GetMonoType private static readonly Dictionary Il2CppToMonoType = new Dictionary(); // keep deobfuscated type name cache, used to display proper name. internal static Dictionary s_deobfuscatedTypeNames = new Dictionary(); /// /// Try to get the Mono (Unhollowed) Type representation of the provided . /// /// The Cpp Type you want to convert to Mono. /// The Mono Type if found, otherwise null. public static Type GetMonoType(CppType cppType) { string name = cppType.AssemblyQualifiedName; if (Il2CppToMonoType.ContainsKey(name)) return Il2CppToMonoType[name]; Type ret = Type.GetType(name); // Thanks to Slaynash for this deobfuscation snippet! if (ret == null) { string baseName = cppType.FullName; string baseAssembly = cppType.Assembly.GetName().name; ret = AppDomain.CurrentDomain .GetAssemblies() .FirstOrDefault(a => a.GetName().Name == baseAssembly)? .TryGetTypes() .FirstOrDefault(t => t.CustomAttributes.Any(ca => ca.AttributeType.Name == "ObfuscatedNameAttribute" && (string)ca.ConstructorArguments[0].Value == baseName)); if (ret != null) { // deobfuscated type was found, add to cache. s_deobfuscatedTypeNames.Add(cppType.FullName, ret.FullName); } } Il2CppToMonoType.Add(name, ret); return ret; } // cached class pointers for Il2CppCast private static readonly Dictionary s_cppClassPointers = new Dictionary(); /// /// Attempt to cast the object to its underlying type. /// /// The object you want to cast. /// The object, as the underlying type if successful or the input value if not. public static object Il2CppCast(object obj) => Il2CppCast(obj, Instance.GetActualType(obj)); /// /// Attempt to cast the object to the provided type. /// /// The object you want to cast. /// The Type you want to cast to. /// The object, as the type (or a normal C# object) if successful or the input value if not. public static object Il2CppCast(object obj, Type castTo) { if (!(obj is Il2CppSystem.Object cppObj)) return obj; if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr)) return obj; IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer); if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr)) return null; if (RuntimeSpecificsStore.IsInjected(castToPtr)) return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer); if (castTo == typeof(string)) return cppObj.ToString(); return Activator.CreateInstance(castTo, cppObj.Pointer); } /// /// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type. /// /// The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for. /// True if successful, false if not. public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _); /// /// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type. /// /// The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for. /// The IntPtr for the Il2Cpp class, or IntPtr.Zero if not found. /// True if successful, false if not. public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr) { if (s_cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr)) return il2cppPtr != IntPtr.Zero; il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>) .MakeGenericType(new Type[] { type }) .GetField("NativeClassPtr", BF.Public | BF.Static) .GetValue(null); s_cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr); return il2cppPtr != IntPtr.Zero; } // Extern C++ methods [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass); [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern IntPtr il2cpp_object_get_class(IntPtr obj); internal static IntPtr s_cppEnumerableClassPtr; internal static IntPtr s_cppDictionaryClassPtr; public override bool IsAssignableFrom(Type toAssignTo, Type toAssignFrom) { if (toAssignTo.IsAssignableFrom(toAssignFrom)) return true; if (toAssignTo == typeof(IEnumerable)) { try { if (s_cppEnumerableClassPtr == IntPtr.Zero) Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr); if (s_cppEnumerableClassPtr != IntPtr.Zero && Il2CppTypeNotNull(toAssignFrom, out IntPtr assignFromPtr) && il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, assignFromPtr)) { return true; } } catch { } } else if (toAssignTo == typeof(IDictionary)) { try { if (s_cppDictionaryClassPtr == IntPtr.Zero) if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr)) return false; if (Il2CppTypeNotNull(toAssignFrom, out IntPtr classPtr)) { if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr)) return true; } } catch { } } return false; } public override bool IsReflectionSupported(Type type) { try { var gArgs = type.GetGenericArguments(); if (!gArgs.Any()) return true; foreach (var gType in gArgs) { if (!Supported(gType)) return false; } return true; bool Supported(Type t) { if (!typeof(Il2CppSystem.Object).IsAssignableFrom(t)) return true; if (!Il2CppTypeNotNull(t, out IntPtr ptr)) return false; return CppType.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is CppType; } } catch { return false; } } // Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded. internal static void TryLoadGameModules() { Instance.LoadModule("Assembly-CSharp"); Instance.LoadModule("Assembly-CSharp-firstpass"); } public override bool LoadModule(string module) { #if ML var path = Path.Combine("MelonLoader", "Managed", $"{module}.dll"); #else var path = Path.Combine("BepInEx", "unhollowed", $"{module}.dll"); #endif return LoadModuleInternal(path); } internal static bool LoadModuleInternal(string fullPath) { if (!File.Exists(fullPath)) return false; try { Assembly.Load(File.ReadAllBytes(fullPath)); return true; } catch (Exception e) { Console.WriteLine(e.GetType() + ", " + e.Message); } return false; } internal static readonly Dictionary s_getEnumeratorMethods = new Dictionary(); internal static readonly Dictionary s_enumeratorInfos = new Dictionary(); internal class EnumeratorInfo { internal MethodInfo moveNext; internal PropertyInfo current; } public override IEnumerable EnumerateEnumerable(object value) { if (value == null) return null; var cppEnumerable = (value as Il2CppSystem.Object)?.TryCast(); if (cppEnumerable != null) { var type = value.GetType(); if (!s_getEnumeratorMethods.ContainsKey(type)) s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator")); var enumerator = s_getEnumeratorMethods[type].Invoke(value, null); var enumeratorType = enumerator.GetType(); if (!s_enumeratorInfos.ContainsKey(enumeratorType)) { s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo { current = enumeratorType.GetProperty("Current"), moveNext = enumeratorType.GetMethod("MoveNext"), }); } var info = s_enumeratorInfos[enumeratorType]; // iterate var list = new List(); while ((bool)info.moveNext.Invoke(enumerator, null)) list.Add(info.current.GetValue(enumerator)); return list; } return null; } public override IDictionary EnumerateDictionary(object value, Type typeOfKeys, Type typeOfValues) { var valueType = ReflectionUtility.GetActualType(value); var keyList = new List(); var valueList = new List(); var hashtable = value.TryCast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable; if (hashtable != null) { EnumerateCppHashtable(hashtable, keyList, valueList); } else { var keys = valueType.GetProperty("Keys").GetValue(value, null); var values = valueType.GetProperty("Values").GetValue(value, null); EnumerateCppIDictionary(keys, keyList); EnumerateCppIDictionary(values, valueList); } var dict = Activator.CreateInstance(typeof(Dictionary<,>) .MakeGenericType(typeOfKeys, typeOfValues)) as IDictionary; for (int i = 0; i < keyList.Count; i++) dict.Add(keyList[i], valueList[i]); return dict; } private void EnumerateCppIDictionary(object collection, List list) { // invoke GetEnumerator var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null); // get the type of it var enumeratorType = enumerator.GetType(); // reflect MoveNext and Current var moveNext = enumeratorType.GetMethod("MoveNext"); var current = enumeratorType.GetProperty("Current"); // iterate while ((bool)moveNext.Invoke(enumerator, null)) { list.Add(current.GetValue(enumerator, null)); } } private void EnumerateCppHashtable(Il2CppSystem.Collections.Hashtable hashtable, List keys, List values) { for (int i = 0; i < hashtable.buckets.Count; i++) { var bucket = hashtable.buckets[i]; if (bucket == null || bucket.key == null) continue; keys.Add(bucket.key); values.Add(bucket.val); } } public override void FindSingleton(string[] possibleNames, Type type, BF flags, List instances) { PropertyInfo pi; foreach (var name in possibleNames) { pi = type.GetProperty(name, flags); if (pi != null) { var instance = pi.GetValue(null, null); if (instance != null) { instances.Add(instance); return; } } } base.FindSingleton(possibleNames, type, flags, instances); } // ~~~~~~~~~~ not used ~~~~~~~~~~~~ // cached il2cpp unbox methods internal static readonly Dictionary s_unboxMethods = new Dictionary(); /// /// Attempt to unbox the object to the underlying struct type. /// /// The object which is a struct underneath. /// The struct if successful, otherwise null. public static object Unbox(object obj) => Unbox(obj, Instance.GetActualType(obj)); /// /// Attempt to unbox the object to the struct type. /// /// The object which is a struct underneath. /// The type of the struct you want to unbox to. /// The struct if successful, otherwise null. public static object Unbox(object obj, Type type) { if (!type.IsValueType) return null; if (!(obj is Il2CppSystem.Object)) return obj; var name = type.AssemblyQualifiedName; if (!s_unboxMethods.ContainsKey(name)) { s_unboxMethods.Add(name, typeof(Il2CppObjectBase) .GetMethod("Unbox") .MakeGenericMethod(type)); } return s_unboxMethods[name].Invoke(obj, new object[0]); } } } #endif