using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using BF = System.Reflection.BindingFlags; using UnityExplorer.Core.Runtime; namespace UnityExplorer { public static class ReflectionUtility { public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; public static bool ValueEqual(this T objA, T objB) { return (objA == null && objB == null) || (objA != null && objA.Equals(objB)); } public static bool ReferenceEqual(this object objA, object objB) { if (objA.TryCast() is UnityEngine.Object unityA) { var unityB = objB.TryCast(); if (unityB && unityA.m_CachedPtr == unityB.m_CachedPtr) return true; } return object.ReferenceEquals(objA, objB); } /// /// Helper for IL2CPP to get the underlying true Type (Unhollowed) of the object. /// /// The object to get the true Type for. /// The most accurate Type of the object which could be identified. public static Type GetActualType(this object obj) { if (obj == null) return null; return ReflectionProvider.Instance.GetActualType(obj); } /// /// Cast an object to its underlying Type. /// /// The object to cast /// The object, cast to the underlying Type if possible, otherwise the original object. public static object TryCast(this object obj) => ReflectionProvider.Instance.Cast(obj, GetActualType(obj)); /// /// Cast an object to a Type, if possible. /// /// The object to cast /// The Type to cast to /// The object, cast to the Type provided if possible, otherwise the original object. public static object TryCast(this object obj, Type castTo) => ReflectionProvider.Instance.Cast(obj, castTo); public static T TryCast(this object obj) => ReflectionProvider.Instance.TryCast(obj); /// /// Check if the provided Type is assignable to IEnumerable. /// /// The Type to check /// True if the Type is assignable to IEnumerable, otherwise false. public static bool IsEnumerable(this Type t) => ReflectionProvider.Instance.IsAssignableFrom(typeof(IEnumerable), t); /// /// Check if the provided Type is assignable to IDictionary. /// /// The Type to check /// True if the Type is assignable to IDictionary, otherwise false. public static bool IsDictionary(this Type t) => ReflectionProvider.Instance.IsAssignableFrom(typeof(IDictionary), t); /// /// [INTERNAL] Used to load Unhollowed DLLs in IL2CPP. /// internal static bool LoadModule(string module) => ReflectionProvider.Instance.LoadModule(module); // cache for GetTypeByName internal static readonly Dictionary s_typesByName = new Dictionary(); /// /// Find a in the current AppDomain whose matches the provided . /// /// The you want to search for - case sensitive and full matches only. /// The Type if found, otherwise null. public static Type GetTypeByName(string fullName) { s_typesByName.TryGetValue(fullName, out Type ret); if (ret != null) return ret; foreach (var type in from asm in AppDomain.CurrentDomain.GetAssemblies() from type in asm.TryGetTypes() select type) { if (type.FullName == fullName) { ret = type; break; } } if (s_typesByName.ContainsKey(fullName)) s_typesByName[fullName] = ret; else s_typesByName.Add(fullName, ret); return ret; } // cache for GetBaseTypes internal static readonly Dictionary s_cachedBaseTypes = new Dictionary(); /// /// Get all base types of the provided Type, including itself. /// public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj)); /// /// Get all base types of the provided Type, including itself. /// public static Type[] GetAllBaseTypes(this Type type) { if (type == null) throw new ArgumentNullException("type"); var name = type.AssemblyQualifiedName; if (s_cachedBaseTypes.TryGetValue(name, out Type[] ret)) return ret; List list = new List(); while (type != null) { list.Add(type); type = type.BaseType; } ret = list.ToArray(); s_cachedBaseTypes.Add(name, ret); return ret; } // cache for GetImplementationsOf internal static readonly Dictionary> s_cachedTypeInheritance = new Dictionary>(); internal static int s_lastAssemblyCount; /// /// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain. /// /// The base type, which can optionally be abstract / interface. /// All implementations of the type in the current AppDomain. public static HashSet GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric) { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); if (!s_cachedTypeInheritance.ContainsKey(baseType) || assemblies.Length != s_lastAssemblyCount) { if (assemblies.Length != s_lastAssemblyCount) { s_cachedTypeInheritance.Clear(); s_lastAssemblyCount = assemblies.Length; } var set = new HashSet(); if (!baseType.IsAbstract && !baseType.IsInterface) set.Add(baseType); foreach (var asm in assemblies) { foreach (var t in asm.TryGetTypes().Where(t => (allowAbstract || (!t.IsAbstract && !t.IsInterface)) && (allowGeneric || !t.IsGenericType))) { try { if (baseType.IsAssignableFrom(t) && !set.Contains(t)) set.Add(t); } catch { } } } s_cachedTypeInheritance.Add(baseType, set); } return s_cachedTypeInheritance[baseType]; } /// /// Safely get all valid Types inside an Assembly. /// /// The Assembly to find Types in. /// All possible Types which could be retrieved from the Assembly, or an empty array. public static IEnumerable TryGetTypes(this Assembly asm) { try { return asm.GetTypes(); } catch (ReflectionTypeLoadException e) { try { return asm.GetExportedTypes(); } catch { return e.Types.Where(t => t != null); } } catch { return Enumerable.Empty(); } } internal static Dictionary> s_cachedFieldInfos = new Dictionary>(); public static FieldInfo GetFieldInfo(Type type, string fieldName) { if (!s_cachedFieldInfos.ContainsKey(type)) s_cachedFieldInfos.Add(type, new Dictionary()); if (!s_cachedFieldInfos[type].ContainsKey(fieldName)) s_cachedFieldInfos[type].Add(fieldName, type.GetField(fieldName, AllFlags)); return s_cachedFieldInfos[type][fieldName]; } internal static Dictionary> s_cachedPropInfos = new Dictionary>(); public static PropertyInfo GetPropertyInfo(Type type, string propertyName) { if (!s_cachedPropInfos.ContainsKey(type)) s_cachedPropInfos.Add(type, new Dictionary()); if (!s_cachedPropInfos[type].ContainsKey(propertyName)) s_cachedPropInfos[type].Add(propertyName, type.GetProperty(propertyName, AllFlags)); return s_cachedPropInfos[type][propertyName]; } internal static Dictionary> s_cachedMethodInfos = new Dictionary>(); public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes) { if (!s_cachedMethodInfos.ContainsKey(type)) s_cachedMethodInfos.Add(type, new Dictionary()); var sig = methodName; if (argumentTypes != null) { sig += "("; for (int i = 0; i < argumentTypes.Length; i++) { if (i > 0) sig += ","; sig += argumentTypes[i].FullName; } sig += ")"; } try { if (!s_cachedMethodInfos[type].ContainsKey(sig)) { if (argumentTypes != null) s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags, null, argumentTypes, null)); else s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags)); } return s_cachedMethodInfos[type][sig]; } catch (AmbiguousMatchException) { ExplorerCore.LogWarning($"AmbiguousMatchException trying to get method '{sig}'"); return null; } catch (Exception e) { ExplorerCore.LogWarning($"{e.GetType()} trying to get method '{sig}': {e.Message}\r\n{e.StackTrace}"); return null; } } /// /// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception. /// /// The Exception to convert to string. /// Should the inner-most Exception of the stack be used? If false, the Exception you provided will be used directly. /// The exception to string. public static string ReflectionExToString(this Exception e, bool innerMost = false) { if (innerMost) { while (e.InnerException != null) { if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException) break; e = e.InnerException; } } return $"{e.GetType()}: {e.Message}"; } } }