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; using System.Text; namespace UnityExplorer { public static class ReflectionUtility { static ReflectionUtility() { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) CacheTypes(asm); AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded; } private static readonly Dictionary allCachedTypes = new Dictionary(); private static void CacheTypes(Assembly asm) { foreach (var type in asm.TryGetTypes()) { if (allCachedTypes.ContainsKey(type.FullName)) continue; if (type.FullName.ContainsIgnoreCase("PrivateImplementationDetails")) continue; allCachedTypes.Add(type.FullName, type); } } private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args) { if (args.LoadedAssembly == null) return; s_cachedTypeInheritance.Clear(); s_cachedGenericParameterInheritance.Clear(); CacheTypes(args.LoadedAssembly); } 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 (object.ReferenceEquals(objA, objB)) return true; if (objA is UnityEngine.Object unityA && objB is UnityEngine.Object unityB) { if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr) return true; } #if CPP if (objA is Il2CppSystem.Object cppA && objB is Il2CppSystem.Object cppB && cppA.Pointer == cppB.Pointer) return true; #endif return false; } /// /// 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) { var type = GetActualType(obj); if (type.IsValueType) return obj; return ReflectionProvider.Instance.Cast(obj, type); } /// /// 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) { if (castTo.IsValueType) return obj; return ReflectionProvider.Instance.Cast(obj, castTo); } public static T TryCast(this object obj) { var type = typeof(T); if (type.IsValueType) return (T)obj; return 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) => !typeof(UnityEngine.Transform).IsAssignableFrom(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) { allCachedTypes.TryGetValue(fullName, out Type type); return type; } // 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 readonly Dictionary> s_cachedGenericParameterInheritance = new Dictionary>(); /// /// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain. /// Also works for generic parameters by analyzing the constraints. /// /// 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 key = baseType.AssemblyQualifiedName; if (!s_cachedTypeInheritance.ContainsKey(key)) { var set = new HashSet(); if (!baseType.IsAbstract && !baseType.IsInterface) set.Add(baseType); var keys = allCachedTypes.Keys.ToArray(); for (int i = 0; i < keys.Length; i++) { var type = allCachedTypes[keys[i]]; try { if ((type.IsAbstract && type.IsSealed) // ignore static classes || (!allowAbstract && type.IsAbstract) || (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))) continue; if (baseType.IsAssignableFrom(type) && !set.Contains(type)) set.Add(type); } catch { } } s_cachedTypeInheritance.Add(key, set); } return s_cachedTypeInheritance[key]; } /// /// 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 = true) { if (innerMost) { while (e.InnerException != null) { if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException) break; e = e.InnerException; } } return $"{e.GetType()}: {e.Message}"; } } }