From 7dbf694642b8c7d0b011137148d5164e4d11fbab Mon Sep 17 00:00:00 2001 From: Sinai Date: Mon, 17 May 2021 21:48:10 +1000 Subject: [PATCH] IL2CPP reflection fixes and improvements --- src/Core/Reflection/Il2CppReflection.cs | 458 +++++++++++++---------- src/Core/Reflection/ReflectionUtility.cs | 71 ++-- 2 files changed, 316 insertions(+), 213 deletions(-) diff --git a/src/Core/Reflection/Il2CppReflection.cs b/src/Core/Reflection/Il2CppReflection.cs index 2289e24..d372361 100644 --- a/src/Core/Reflection/Il2CppReflection.cs +++ b/src/Core/Reflection/Il2CppReflection.cs @@ -440,12 +440,6 @@ namespace UnityExplorer // Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded. - internal void TryLoadGameModules() - { - Internal_LoadModule("Assembly-CSharp"); - Internal_LoadModule("Assembly-CSharp-firstpass"); - } - internal override bool Internal_LoadModule(string moduleName) { if (!moduleName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase)) @@ -458,7 +452,41 @@ namespace UnityExplorer return DoLoadModule(path); } - internal bool DoLoadModule(string fullPath) + // Force loading all il2cpp modules + + internal void TryLoadGameModules() + { + string dirpath = + #if ML + Path.Combine("MelonLoader", "Managed"); + #elif BIE + Path.Combine("BepInEx", "unhollowed"); + #else + Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules"); + #endif + ; + + if (Directory.Exists(dirpath)) + { + var files = Directory.GetFiles(dirpath); + foreach (var filePath in files) + { + var name = Path.GetFileName(filePath); + if (!name.StartsWith("Unity") && !name.StartsWith("Assembly-CSharp")) + continue; + try + { + DoLoadModule(filePath, true); + } + catch //(Exception ex) + { + //ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}"); + } + } + } + } + + internal bool DoLoadModule(string fullPath, bool suppressWarning = false) { if (!File.Exists(fullPath)) return false; @@ -470,16 +498,17 @@ namespace UnityExplorer } catch (Exception e) { - Console.WriteLine(e.GetType() + ", " + e.Message); + if (!suppressWarning) + Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}"); } return false; } - #endregion +#endregion - #region Il2cpp reflection blacklist +#region Il2cpp reflection blacklist public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist); @@ -625,191 +654,238 @@ namespace UnityExplorer "UnityEngine.XR.InputDevice.SendHapticImpulse", }; - #endregion +#endregion - // (Disabled) - #region Temp il2cpp list/dictionary fixes +#region Temp il2cpp list/dictionary fixes - //// Temp fix until Unhollower interface support improves - // - //internal static IntPtr s_cppEnumerableClassPtr; - //internal static IntPtr s_cppDictionaryClassPtr; - // - //public override bool Internal_IsEnumerable(Type type) - //{ - // if (base.Internal_IsEnumerable(type)) - // return true; - // - // try - // { - // if (s_cppEnumerableClassPtr == IntPtr.Zero) - // Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr); - // - // if (s_cppEnumerableClassPtr != IntPtr.Zero - // && Il2CppTypeNotNull(type, out IntPtr assignFromPtr) - // && il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, assignFromPtr)) - // { - // return true; - // } - // } - // catch { } - // - // return false; - //} - // - //// Lists - // - //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; - //} - // - //internal static IEnumerator EnumerateCppList(object list) - //{ - // if (list == null) - // yield break; - // - // var cppEnumerable = list.TryCast(); - // if (cppEnumerable == null) - // { - // ExplorerCore.LogWarning("Failed to cast an IEnumerable to the interface!"); - // yield break; - // } - // - // // Some ugly reflection to use the il2cpp interface for the instance type - // - // var type = cppEnumerable.GetActualType(); - // if (!s_getEnumeratorMethods.ContainsKey(type)) - // s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator")); - // - // var enumerator = s_getEnumeratorMethods[type].Invoke(cppEnumerable.TryCast(type), 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]; - // - // // Yield and return the actual entries - // while ((bool)info.moveNext.Invoke(enumerator, null)) - // yield return info.current.GetValue(enumerator); - //} - // - //// Dicts todo - // - //public override bool Internal_IsDictionary(Type type) - //{ - // if (base.Internal_IsDictionary(type)) - // return true; - // - // try - // { - // if (s_cppDictionaryClassPtr == IntPtr.Zero) - // if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr)) - // return false; - // - // if (Il2CppTypeNotNull(type, out IntPtr classPtr)) - // { - // if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr)) - // return true; - // } - // } - // catch { } - // - // return false; - //} - // - //internal static IEnumerator> EnumerateCppDictionary(object dictionary) - //{ - // var cppDict = dictionary?.TryCast(); - // if (cppDict == null) - // yield break; - // - //} - // - // - // - ////public IDictionary EnumerateDictionary(object value) - ////{ - //// var valueType = value.GetActualType(); - //// - //// Type typeOfKeys, typeOfValues; - //// if (valueType.IsGenericType && valueType.GetGenericArguments() is ParameterInfo[] args && args.Length == 2) - //// { - //// typeOfKeys = args[0]; - //// typeOfValues = args[1]; - //// } - //// else - //// typeOfKeys = typeOfValues = typeof(object); - //// - //// var keyList = new List(); - //// var valueList = new List(); - //// - //// var hashtable = value.TryCast(); - //// - //// if (hashtable != null) - //// { - //// EnumerateCppHashtable(hashtable, keyList, valueList); - //// } - //// else - //// { - //// var keys = valueType.GetProperty("Keys").GetValue(value, null); - //// EnumerateCppCollection(keys, keyList); - //// - //// var values = valueType.GetProperty("Values").GetValue(value, null); - //// EnumerateCppCollection(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 EnumerateCppCollection(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); - //// } - ////} + // Temp fix until Unhollower interface support improves - #endregion + 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 } } diff --git a/src/Core/Reflection/ReflectionUtility.cs b/src/Core/Reflection/ReflectionUtility.cs index f60811b..d5dc8a5 100644 --- a/src/Core/Reflection/ReflectionUtility.cs +++ b/src/Core/Reflection/ReflectionUtility.cs @@ -458,28 +458,55 @@ namespace UnityExplorer #endregion - // This is NOT working properly. Fails on IL2CPP IList, probably more stuff too. + // Temp fix for IL2CPP until interface support improves + + // IsEnumerable - //// Temp fix for IL2CPP until interface support improves - // - //public static bool IsEnumerable(Type type) - //{ - // return Instance.Internal_IsEnumerable(type); - //} - // - //public virtual bool Internal_IsEnumerable(Type type) - //{ - // return typeof(IEnumerable).IsAssignableFrom(type); - //} - // - //public static bool IsDictionary(Type type) - //{ - // return Instance.Internal_IsDictionary(type); - //} - // - //public virtual bool Internal_IsDictionary(Type type) - //{ - // return typeof(IDictionary).IsAssignableFrom(type); - //} + public static bool IsEnumerable(Type type) => Instance.Internal_IsEnumerable(type); + + protected virtual bool Internal_IsEnumerable(Type type) + { + return typeof(IEnumerable).IsAssignableFrom(type); + } + + // TryGetEnumerator (list) + + public static bool TryGetEnumerator(object list, out IEnumerator enumerator) + => Instance.Internal_TryGetEnumerator(list, out enumerator); + + protected virtual bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator) + { + enumerator = (list as IEnumerable).GetEnumerator(); + return true; + } + + // IsDictionary + + public static bool IsDictionary(Type type) => Instance.Internal_IsDictionary(type); + + protected virtual bool Internal_IsDictionary(Type type) + { + return typeof(IDictionary).IsAssignableFrom(type); + } + + // TryGetEnumerator (dictionary) + + public static bool TryGetDictEnumerator(object dictionary, out IEnumerator dictEnumerator) + => Instance.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator); + + protected virtual bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator dictEnumerator) + { + dictEnumerator = EnumerateDictionary((IDictionary)dictionary); + return true; + } + + private IEnumerator EnumerateDictionary(IDictionary dict) + { + var enumerator = dict.GetEnumerator(); + while (enumerator.MoveNext()) + { + yield return new DictionaryEntry(enumerator.Key, enumerator.Value); + } + } } }