#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; using UnityExplorer.Core.Config; using UnhollowerBaseLib.Attributes; namespace UnityExplorer { public class Il2CppReflection : ReflectionUtility { protected override void Initialize() { base.Initialize(); TryLoadGameModules(); BuildDeobfuscationCache(); OnTypeLoaded += TryCacheDeobfuscatedType; } #region IL2CPP Extern and pointers // 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); public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _); public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr) { if (!cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr)) { il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>) .MakeGenericType(new Type[] { type }) .GetField("NativeClassPtr", BF.Public | BF.Static) .GetValue(null); cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr); } return il2cppPtr != IntPtr.Zero; } #endregion #region Deobfuscation cache private static readonly Dictionary DeobfuscatedTypes = new Dictionary(); private static readonly Dictionary reverseDeobCache = new Dictionary(); private static void BuildDeobfuscationCache() { float start = UnityEngine.Time.realtimeSinceStartup; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.TryGetTypes()) TryCacheDeobfuscatedType(type); } if (DeobfuscatedTypes.Count > 0) { ExplorerCore.Log($"Built deobfuscation cache in {UnityEngine.Time.realtimeSinceStartup - start} seconds, " + $"initial count: {DeobfuscatedTypes.Count} "); } } private static void TryCacheDeobfuscatedType(Type type) { try { if (!type.CustomAttributes.Any()) return; foreach (var att in type.CustomAttributes) { // Thanks to Slaynash for this if (att.AttributeType == typeof(ObfuscatedNameAttribute)) { string obfuscatedName = att.ConstructorArguments[0].Value.ToString(); DeobfuscatedTypes.Add(obfuscatedName, type); reverseDeobCache.Add(type.FullName, obfuscatedName); } } } catch { } } internal override string Internal_ProcessTypeInString(string theString, Type type) { if (reverseDeobCache.TryGetValue(type.FullName, out string obName)) return theString.Replace(obName, type.FullName); return theString; } #endregion // Get type by name internal override Type Internal_GetTypeByName(string fullName) { if (DeobfuscatedTypes.TryGetValue(fullName, out Type deob)) return deob; return base.Internal_GetTypeByName(fullName); } #region Get actual type internal override Type Internal_GetActualType(object obj) { if (obj == null) return null; var type = obj.GetType(); try { if (IsString(obj)) return typeof(string); if (IsIl2CppPrimitive(type)) return il2cppPrimitivesToMono[type.FullName]; if (obj is Il2CppSystem.Object cppObject) { var cppType = cppObject.GetIl2CppType(); // check if type is injected IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer); if (RuntimeSpecificsStore.IsInjected(classPtr)) { // Note: This will fail on injected subclasses. // - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected. // Not sure on solution yet. return GetTypeByName(cppType.FullName) ?? type; } if (AllTypes.TryGetValue(cppType.FullName, out Type primitive) && primitive.IsPrimitive) return primitive; return GetUnhollowedType(cppType) ?? type; } } catch (Exception ex) { ExplorerCore.LogWarning("Exception in IL2CPP GetActualType: " + ex); } return type; } public static Type GetUnhollowedType(CppType cppType) { var fullname = cppType.FullName; if (DeobfuscatedTypes.TryGetValue(fullname, out Type deob)) return deob; if (fullname.StartsWith("System.")) fullname = $"Il2Cpp{fullname}"; AllTypes.TryGetValue(fullname, out Type monoType); return monoType; } #endregion #region Casting private static readonly Dictionary cppClassPointers = new Dictionary(); internal override object Internal_TryCast(object obj, Type castTo) { if (obj == null) return null; var type = obj.GetType(); if (type == castTo) return obj; // from structs if (type.IsValueType) { // from il2cpp primitive to system primitive if (IsIl2CppPrimitive(type) && castTo.IsPrimitive) { return MakeMonoPrimitive(obj); } // from system primitive to il2cpp primitive else if (IsIl2CppPrimitive(castTo)) { return MakeIl2CppPrimitive(castTo, obj); } // from other structs to il2cpp object else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) { return BoxIl2CppObject(obj); } else return obj; } // from string to il2cpp.Object / il2cpp.String if (obj is string && typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) { return BoxStringToType(obj, castTo); } // from il2cpp objects... if (!(obj is Il2CppObjectBase cppObj)) return obj; // from Il2CppSystem.Object to a struct if (castTo.IsValueType) return UnboxCppObject(cppObj, castTo); // or to system string else if (castTo == typeof(string)) return UnboxString(obj); if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr)) return obj; // Casting from il2cpp object to il2cpp object... IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer); if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr)) return null; if (RuntimeSpecificsStore.IsInjected(castToPtr)) { var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer); return injectedObj ?? obj; } try { return Activator.CreateInstance(castTo, cppObj.Pointer); } catch { return obj; } } //private static bool IsAssignableFrom(Type thisType, Type fromType) //{ // if (!Il2CppTypeNotNull(fromType, out IntPtr fromTypePtr) // || !Il2CppTypeNotNull(thisType, out IntPtr thisTypePtr)) // { // // one or both of the types are not Il2Cpp types, use normal check // return thisType.IsAssignableFrom(fromType); // } // // return il2cpp_class_is_assignable_from(thisTypePtr, fromTypePtr); //} #endregion #region Boxing and unboxing ValueTypes // cached il2cpp unbox methods internal static readonly Dictionary unboxMethods = new Dictionary(); // Unbox an il2cpp object to a struct or System primitive. public object UnboxCppObject(Il2CppObjectBase cppObj, Type toType) { if (!toType.IsValueType) return null; try { if (toType.IsEnum) return Enum.Parse(toType, cppObj.ToString()); var name = toType.AssemblyQualifiedName; if (!unboxMethods.ContainsKey(name)) { unboxMethods.Add(name, typeof(Il2CppObjectBase) .GetMethod("Unbox") .MakeGenericMethod(toType)); } return unboxMethods[name].Invoke(cppObj, ArgumentUtility.EmptyArgs); } catch (Exception ex) { ExplorerCore.LogWarning("Exception Unboxing Il2Cpp object to struct: " + ex); return null; } } private static Il2CppSystem.Object BoxIl2CppObject(object cppStruct, Type structType) { return GetMethodInfo(structType, "BoxIl2CppObject", ArgumentUtility.EmptyTypes) .Invoke(cppStruct, ArgumentUtility.EmptyArgs) as Il2CppSystem.Object; } public Il2CppSystem.Object BoxIl2CppObject(object value) { if (value == null) return null; try { var type = value.GetType(); if (!type.IsValueType) return null; if (type.IsEnum) return Il2CppSystem.Enum.Parse(Il2CppType.From(type), value.ToString()); if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType)) return BoxIl2CppObject(MakeIl2CppPrimitive(cppType, value), cppType); return BoxIl2CppObject(value, type); } catch (Exception ex) { ExplorerCore.LogWarning("Exception in BoxIl2CppObject: " + ex); return null; } } // Helpers for Il2Cpp primitive <-> Mono internal static readonly Dictionary il2cppPrimitivesToMono = new Dictionary { { "Il2CppSystem.Boolean", typeof(bool) }, { "Il2CppSystem.Byte", typeof(byte) }, { "Il2CppSystem.SByte", typeof(sbyte) }, { "Il2CppSystem.Char", typeof(char) }, { "Il2CppSystem.Double", typeof(double) }, { "Il2CppSystem.Single", typeof(float) }, { "Il2CppSystem.Int32", typeof(int) }, { "Il2CppSystem.UInt32", typeof(uint) }, { "Il2CppSystem.Int64", typeof(long) }, { "Il2CppSystem.UInt64", typeof(ulong) }, { "Il2CppSystem.Int16", typeof(short) }, { "Il2CppSystem.UInt16", typeof(ushort) }, { "Il2CppSystem.IntPtr", typeof(IntPtr) }, { "Il2CppSystem.UIntPtr", typeof(UIntPtr) } }; public static bool IsIl2CppPrimitive(object obj) => IsIl2CppPrimitive(obj.GetType()); public static bool IsIl2CppPrimitive(Type type) => il2cppPrimitivesToMono.ContainsKey(type.FullName); public object MakeMonoPrimitive(object cppPrimitive) { return GetFieldInfo(cppPrimitive.GetType(), "m_value").GetValue(cppPrimitive); } public object MakeIl2CppPrimitive(Type cppType, object monoValue) { var cppStruct = Activator.CreateInstance(cppType); GetFieldInfo(cppType, "m_value").SetValue(cppStruct, monoValue); return cppStruct; } #endregion #region String boxing/unboxing private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String"; private const string STRING_FULLNAME = "System.String"; public bool IsString(object obj) { if (obj is string || obj is Il2CppSystem.String) return true; if (obj is Il2CppSystem.Object cppObj) { var type = cppObj.GetIl2CppType(); return type.FullName == IL2CPP_STRING_FULLNAME || type.FullName == STRING_FULLNAME; } return false; } public object BoxStringToType(object value, Type castTo) { if (castTo == typeof(Il2CppSystem.String)) return (Il2CppSystem.String)(value as string); else return (Il2CppSystem.Object)(value as string); } public string UnboxString(object value) { if (value is string s) return s; s = null; if (value is Il2CppSystem.Object cppObject) s = cppObject.ToString(); else if (value is Il2CppSystem.String cppString) s = cppString; return s; } #endregion #region Singleton finder internal override void Internal_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.Internal_FindSingleton(possibleNames, type, flags, instances); } #endregion #region Force-loading game modules internal static string UnhollowedFolderPath => Path.GetFullPath( #if ML Path.Combine("MelonLoader", "Managed") #elif BIE Path.Combine("BepInEx", "unhollowed") #else Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules") #endif ); // Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded. // Force loading all il2cpp modules internal void TryLoadGameModules() { if (Directory.Exists(UnhollowedFolderPath)) { var files = Directory.GetFiles(UnhollowedFolderPath); foreach (var filePath in files) { try { DoLoadModule(filePath, true); } catch //(Exception ex) { //ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}"); } } } else ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'"); } internal bool DoLoadModule(string fullPath, bool suppressWarning = false) { if (!File.Exists(fullPath)) return false; try { Assembly.LoadFile(fullPath); //Assembly.Load(File.ReadAllBytes(fullPath)); return true; } catch (Exception e) { if (!suppressWarning) Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}"); } return false; } #endregion #region Il2cpp reflection blacklist public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist); // These methods currently cause a crash in most il2cpp games, // even from doing "GetParameters()" on the MemberInfo. // Blacklisting until the issue is fixed in Unhollower. public static HashSet defaultIl2CppBlacklist = new HashSet { // These were deprecated a long time ago, still show up in some IL2CPP games for some reason "UnityEngine.MonoBehaviour.allowPrefabModeInPlayMode", "UnityEngine.MonoBehaviour.runInEditMode", "UnityEngine.Component.animation", "UnityEngine.Component.audio", "UnityEngine.Component.camera", "UnityEngine.Component.collider", "UnityEngine.Component.collider2D", "UnityEngine.Component.constantForce", "UnityEngine.Component.hingeJoint", "UnityEngine.Component.light", "UnityEngine.Component.networkView", "UnityEngine.Component.particleSystem", "UnityEngine.Component.renderer", "UnityEngine.Component.rigidbody", "UnityEngine.Component.rigidbody2D", "UnityEngine.Light.flare", // These can cause a crash in IL2CPP "Il2CppSystem.Type.DeclaringMethod", "Il2CppSystem.RuntimeType.DeclaringMethod", "Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData", "Unity.Profiling.ProfilerRecorder.CopyTo", "Unity.Profiling.ProfilerRecorder.StartNew", "UnityEngine.Analytics.Analytics.RegisterEvent", "UnityEngine.Analytics.Analytics.SendEvent", "UnityEngine.Analytics.ContinuousEvent+ConfigureEventDelegate.Invoke", "UnityEngine.Analytics.ContinuousEvent.ConfigureEvent", "UnityEngine.Animations.AnimationLayerMixerPlayable.Create", "UnityEngine.Animations.AnimationLayerMixerPlayable.CreateHandle", "UnityEngine.Animations.AnimationMixerPlayable.Create", "UnityEngine.Animations.AnimationMixerPlayable.CreateHandle", "UnityEngine.AssetBundle.RecompressAssetBundleAsync", "UnityEngine.Audio.AudioMixerPlayable.Create", "UnityEngine.BoxcastCommand.ScheduleBatch", "UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties", "UnityEngine.CapsulecastCommand.ScheduleBatch", "UnityEngine.Collider2D.Cast", "UnityEngine.Collider2D.Raycast", "UnityEngine.ComputeBuffer+BeginBufferWriteDelegate.Invoke", "UnityEngine.ComputeBuffer+EndBufferWriteDelegate.Invoke", "UnityEngine.ComputeBuffer.BeginBufferWrite", "UnityEngine.ComputeBuffer.EndBufferWrite", "UnityEngine.Cubemap+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.Cubemap+SetPixelDataImplDelegate.Invoke", "UnityEngine.Cubemap.SetPixelDataImpl", "UnityEngine.Cubemap.SetPixelDataImplArray", "UnityEngine.CubemapArray+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.CubemapArray+SetPixelDataImplDelegate.Invoke", "UnityEngine.CubemapArray.SetPixelDataImpl", "UnityEngine.CubemapArray.SetPixelDataImplArray", "UnityEngine.Experimental.Playables.MaterialEffectPlayable.Create", "UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstanceDelegate.Invoke", "UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstance_Procedural_InjectedDelegate.Invoke", "UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance", "UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural", "UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural_Injected", "UnityEngine.Experimental.Rendering.RayTracingShader+DispatchDelegate.Invoke", "UnityEngine.Experimental.Rendering.RayTracingShader.Dispatch", "UnityEngine.Experimental.Rendering.RenderPassAttachment.Clear", "UnityEngine.GUI.DoButtonGrid", "UnityEngine.GUI.Slider", "UnityEngine.GUI.Toolbar", "UnityEngine.Graphics.DrawMeshInstancedIndirect", "UnityEngine.Graphics.DrawMeshInstancedProcedural", "UnityEngine.Graphics.DrawProcedural", "UnityEngine.Graphics.DrawProceduralIndirect", "UnityEngine.Graphics.DrawProceduralIndirectNow", "UnityEngine.Graphics.DrawProceduralNow", "UnityEngine.LineRenderer+BakeMeshDelegate.Invoke", "UnityEngine.LineRenderer.BakeMesh", "UnityEngine.Mesh.GetIndices", "UnityEngine.Mesh.GetTriangles", "UnityEngine.Mesh.SetIndices", "UnityEngine.Mesh.SetTriangles", "UnityEngine.Physics2D.BoxCast", "UnityEngine.Physics2D.CapsuleCast", "UnityEngine.Physics2D.CircleCast", "UnityEngine.PhysicsScene.BoxCast", "UnityEngine.PhysicsScene.CapsuleCast", "UnityEngine.PhysicsScene.OverlapBox", "UnityEngine.PhysicsScene.OverlapCapsule", "UnityEngine.PhysicsScene.SphereCast", "UnityEngine.PhysicsScene2D.BoxCast", "UnityEngine.PhysicsScene2D.CapsuleCast", "UnityEngine.PhysicsScene2D.CircleCast", "UnityEngine.PhysicsScene2D.GetRayIntersection", "UnityEngine.PhysicsScene2D.Linecast", "UnityEngine.PhysicsScene2D.OverlapArea", "UnityEngine.PhysicsScene2D.OverlapBox", "UnityEngine.PhysicsScene2D.OverlapCapsule", "UnityEngine.PhysicsScene2D.OverlapCircle", "UnityEngine.PhysicsScene2D.OverlapCollider", "UnityEngine.PhysicsScene2D.OverlapPoint", "UnityEngine.PhysicsScene2D.Raycast", "UnityEngine.Playables.Playable.Create", "UnityEngine.Profiling.CustomSampler.Create", "UnityEngine.RaycastCommand.ScheduleBatch", "UnityEngine.RemoteConfigSettings+QueueConfigDelegate.Invoke", "UnityEngine.RemoteConfigSettings.QueueConfig", "UnityEngine.RenderTexture.GetTemporaryImpl", "UnityEngine.Rendering.AsyncGPUReadback.Request", "UnityEngine.Rendering.AttachmentDescriptor.ConfigureClear", "UnityEngine.Rendering.BatchRendererGroup+AddBatch_InjectedDelegate.Invoke", "UnityEngine.Rendering.BatchRendererGroup.AddBatch", "UnityEngine.Rendering.BatchRendererGroup.AddBatch_Injected", "UnityEngine.Rendering.CommandBuffer+Internal_DispatchRaysDelegate.Invoke", "UnityEngine.Rendering.CommandBuffer.DispatchRays", "UnityEngine.Rendering.CommandBuffer.DrawMeshInstancedProcedural", "UnityEngine.Rendering.CommandBuffer.Internal_DispatchRays", "UnityEngine.Rendering.CommandBuffer.ResolveAntiAliasedSurface", "UnityEngine.Rendering.ScriptableRenderContext.BeginRenderPass", "UnityEngine.Rendering.ScriptableRenderContext.BeginScopedRenderPass", "UnityEngine.Rendering.ScriptableRenderContext.BeginScopedSubPass", "UnityEngine.Rendering.ScriptableRenderContext.BeginSubPass", "UnityEngine.Rendering.ScriptableRenderContext.SetupCameraProperties", "UnityEngine.Rigidbody2D.Cast", "UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke", "UnityEngine.Scripting.GarbageCollector.CollectIncremental", "UnityEngine.SpherecastCommand.ScheduleBatch", "UnityEngine.Texture.GetPixelDataSize", "UnityEngine.Texture.GetPixelDataOffset", "UnityEngine.Texture.GetPixelDataOffset", "UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke", "UnityEngine.Texture2D.SetPixelDataImpl", "UnityEngine.Texture2D.SetPixelDataImplArray", "UnityEngine.Texture2DArray+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.Texture2DArray+SetPixelDataImplDelegate.Invoke", "UnityEngine.Texture2DArray.SetPixelDataImpl", "UnityEngine.Texture2DArray.SetPixelDataImplArray", "UnityEngine.Texture3D+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.Texture3D+SetPixelDataImplDelegate.Invoke", "UnityEngine.Texture3D.SetPixelDataImpl", "UnityEngine.Texture3D.SetPixelDataImplArray", "UnityEngine.TrailRenderer+BakeMeshDelegate.Invoke", "UnityEngine.TrailRenderer.BakeMesh", "UnityEngine.WWW.LoadFromCacheOrDownload", "UnityEngine.XR.InputDevice.SendHapticImpulse", }; #endregion #region IL2CPP IEnumerable and IDictionary protected override bool Internal_TryGetEntryType(Type enumerableType, out Type type) { // Check for system types (not unhollowed) if (base.Internal_TryGetEntryType(enumerableType, out type)) return true; // Type is either an IL2CPP enumerable, or its not generic. if (type.IsGenericType) { // Temporary naive solution until IL2CPP interface support improves. // This will work fine for most cases, but there are edge cases which would not work. type = type.GetGenericArguments()[0]; return true; } // Unable to determine entry type type = typeof(object); return false; } protected override bool Internal_TryGetEntryTypes(Type type, out Type keys, out Type values) { if (base.Internal_TryGetEntryTypes(type, out keys, out values)) return true; // Type is either an IL2CPP dictionary, or its not generic. if (type.IsGenericType) { // Naive solution until IL2CPP interfaces improve. var args = type.GetGenericArguments(); if (args.Length == 2) { keys = args[0]; values = args[1]; return true; } } keys = typeof(object); values = typeof(object); return false; } // Temp fix until Unhollower interface support improves internal static readonly Dictionary getEnumeratorMethods = new Dictionary(); internal static readonly Dictionary enumeratorInfos = new Dictionary(); internal static readonly HashSet notSupportedTypes = new HashSet(); // IEnumerables internal static IntPtr cppIEnumerablePointer; protected override bool Internal_IsEnumerable(Type type) { if (base.Internal_IsEnumerable(type)) return true; try { if (cppIEnumerablePointer == IntPtr.Zero) Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out cppIEnumerablePointer); if (cppIEnumerablePointer != IntPtr.Zero && Il2CppTypeNotNull(type, out IntPtr assignFromPtr) && il2cpp_class_is_assignable_from(cppIEnumerablePointer, assignFromPtr)) { return true; } } catch { } return false; } internal class EnumeratorInfo { internal MethodInfo moveNext; internal PropertyInfo current; } protected override bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator) { if (list is IEnumerable) return base.Internal_TryGetEnumerator(list, out enumerator); try { PrepareCppEnumerator(list, out object cppEnumerator, out EnumeratorInfo info); enumerator = EnumerateCppList(info, cppEnumerator); return true; } catch //(Exception ex) { //ExplorerCore.LogWarning($"Exception enumerating IEnumerable: {ex.ReflectionExToString()}"); enumerator = null; return false; } } private static void PrepareCppEnumerator(object list, out object cppEnumerator, out EnumeratorInfo info) { info = null; cppEnumerator = null; if (list == null) throw new ArgumentNullException("list"); // Some ugly reflection to use the il2cpp interface for the instance type var type = list.GetType(); var key = type.AssemblyQualifiedName; if (!getEnumeratorMethods.ContainsKey(key)) { getEnumeratorMethods.Add(key, type.GetMethod("GetEnumerator")); // ensure the enumerator type is supported try { var test = getEnumeratorMethods[key].Invoke(list, null); test.GetType().GetMethod("MoveNext").Invoke(test, null); } catch { notSupportedTypes.Add(key); } } if (notSupportedTypes.Contains(key)) throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext."); cppEnumerator = getEnumeratorMethods[key].Invoke(list, null); var enumeratorType = cppEnumerator.GetType(); var enumInfoKey = enumeratorType.AssemblyQualifiedName; if (!enumeratorInfos.ContainsKey(enumInfoKey)) { enumeratorInfos.Add(enumInfoKey, new EnumeratorInfo { current = enumeratorType.GetProperty("Current"), moveNext = enumeratorType.GetMethod("MoveNext"), }); } info = enumeratorInfos[enumInfoKey]; } internal static IEnumerator EnumerateCppList(EnumeratorInfo info, object enumerator) { // Yield and return the actual entries while ((bool)info.moveNext.Invoke(enumerator, null)) yield return info.current.GetValue(enumerator); } // IDictionary internal static IntPtr cppIDictionaryPointer; protected override bool Internal_IsDictionary(Type type) { if (base.Internal_IsDictionary(type)) return true; try { if (cppIDictionaryPointer == IntPtr.Zero) if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out cppIDictionaryPointer)) return false; if (Il2CppTypeNotNull(type, out IntPtr classPtr) && il2cpp_class_is_assignable_from(cppIDictionaryPointer, classPtr)) return true; } catch { } return false; } protected override bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator dictEnumerator) { if (dictionary is IDictionary) return base.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator); try { var type = dictionary.GetType(); if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type)) { dictEnumerator = EnumerateCppHashTable(dictionary.TryCast()); return true; } var keys = type.GetProperty("Keys").GetValue(dictionary, null); var keyCollType = keys.GetType(); var cacheKey = keys.GetType().AssemblyQualifiedName; if (!getEnumeratorMethods.ContainsKey(cacheKey)) { getEnumeratorMethods.Add(cacheKey, keyCollType.GetMethod("GetEnumerator")); // test support try { var test = getEnumeratorMethods[cacheKey].Invoke(keys, null); test.GetType().GetMethod("MoveNext").Invoke(test, null); } catch { notSupportedTypes.Add(cacheKey); } } if (notSupportedTypes.Contains(cacheKey)) throw new Exception($"The IDictionary type '{type.FullName}' does not support MoveNext."); var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null); var keyInfo = new EnumeratorInfo { current = keyEnumerator.GetType().GetProperty("Current"), moveNext = keyEnumerator.GetType().GetMethod("MoveNext"), }; var values = type.GetProperty("Values").GetValue(dictionary, null); var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null); var valueInfo = new EnumeratorInfo { current = valueEnumerator.GetType().GetProperty("Current"), moveNext = valueEnumerator.GetType().GetMethod("MoveNext"), }; dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator); return true; } catch //(Exception ex) { //ExplorerCore.LogWarning($"Exception enumerating IDictionary: {ex.ReflectionExToString()}"); dictEnumerator = null; return false; } } internal static IEnumerator EnumerateCppDict(EnumeratorInfo keyInfo, object keyEnumerator, EnumeratorInfo valueInfo, object valueEnumerator) { while ((bool)keyInfo.moveNext.Invoke(keyEnumerator, null)) { valueInfo.moveNext.Invoke(valueEnumerator, null); var key = keyInfo.current.GetValue(keyEnumerator, null); var value = valueInfo.current.GetValue(valueEnumerator, null); yield return new DictionaryEntry(key, value); } } internal static IEnumerator EnumerateCppHashTable(Il2CppSystem.Collections.Hashtable hashtable) { for (int i = 0; i < hashtable.buckets.Count; i++) { var bucket = hashtable.buckets[i]; if (bucket == null || bucket.key == null) continue; yield return new DictionaryEntry(bucket.key, bucket.val); } } #endregion } } #endif