2021-03-18 17:17:29 +11:00
|
|
|
|
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;
|
2021-05-05 21:27:09 +10:00
|
|
|
|
using System.Text;
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
2021-04-04 03:41:36 +10:00
|
|
|
|
namespace UnityExplorer
|
2021-03-18 17:17:29 +11:00
|
|
|
|
{
|
|
|
|
|
public static class ReflectionUtility
|
|
|
|
|
{
|
2021-05-05 21:27:09 +10:00
|
|
|
|
static ReflectionUtility()
|
|
|
|
|
{
|
|
|
|
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
|
|
|
CacheTypes(asm);
|
|
|
|
|
|
|
|
|
|
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<string, Type> allCachedTypes = new Dictionary<string, Type>();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-03-31 22:58:17 +11:00
|
|
|
|
public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
2021-04-15 20:18:03 +10:00
|
|
|
|
public static bool ValueEqual<T>(this T objA, T objB)
|
|
|
|
|
{
|
|
|
|
|
return (objA == null && objB == null) || (objA != null && objA.Equals(objB));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool ReferenceEqual(this object objA, object objB)
|
|
|
|
|
{
|
2021-04-28 23:58:13 +10:00
|
|
|
|
if (object.ReferenceEquals(objA, objB))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (objA is UnityEngine.Object unityA && objB is UnityEngine.Object unityB)
|
2021-04-15 20:18:03 +10:00
|
|
|
|
{
|
2021-04-28 23:58:13 +10:00
|
|
|
|
if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr)
|
2021-04-15 20:18:03 +10:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-28 23:58:13 +10:00
|
|
|
|
#if CPP
|
|
|
|
|
if (objA is Il2CppSystem.Object cppA && objB is Il2CppSystem.Object cppB
|
|
|
|
|
&& cppA.Pointer == cppB.Pointer)
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return false;
|
2021-04-15 20:18:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 17:17:29 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper for IL2CPP to get the underlying true Type (Unhollowed) of the object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="obj">The object to get the true Type for.</param>
|
|
|
|
|
/// <returns>The most accurate Type of the object which could be identified.</returns>
|
2021-04-04 03:41:36 +10:00
|
|
|
|
public static Type GetActualType(this object obj)
|
2021-03-18 17:17:29 +11:00
|
|
|
|
{
|
|
|
|
|
if (obj == null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
return ReflectionProvider.Instance.GetActualType(obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cast an object to its underlying Type.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="obj">The object to cast</param>
|
|
|
|
|
/// <returns>The object, cast to the underlying Type if possible, otherwise the original object.</returns>
|
2021-04-15 20:18:03 +10:00
|
|
|
|
public static object TryCast(this object obj)
|
2021-05-04 20:10:46 +10:00
|
|
|
|
{
|
|
|
|
|
var type = GetActualType(obj);
|
|
|
|
|
|
|
|
|
|
if (type.IsValueType)
|
|
|
|
|
return obj;
|
|
|
|
|
return ReflectionProvider.Instance.Cast(obj, type);
|
|
|
|
|
}
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Cast an object to a Type, if possible.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="obj">The object to cast</param>
|
|
|
|
|
/// <param name="castTo">The Type to cast to </param>
|
|
|
|
|
/// <returns>The object, cast to the Type provided if possible, otherwise the original object.</returns>
|
2021-04-15 20:18:03 +10:00
|
|
|
|
public static object TryCast(this object obj, Type castTo)
|
2021-05-04 20:10:46 +10:00
|
|
|
|
{
|
|
|
|
|
if (castTo.IsValueType)
|
|
|
|
|
return obj;
|
|
|
|
|
return ReflectionProvider.Instance.Cast(obj, castTo);
|
|
|
|
|
}
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
2021-04-07 17:20:09 +10:00
|
|
|
|
public static T TryCast<T>(this object obj)
|
2021-05-04 20:10:46 +10:00
|
|
|
|
{
|
|
|
|
|
var type = typeof(T);
|
|
|
|
|
if (type.IsValueType)
|
|
|
|
|
return (T)obj;
|
|
|
|
|
return ReflectionProvider.Instance.TryCast<T>(obj);
|
|
|
|
|
}
|
2021-04-07 17:20:09 +10:00
|
|
|
|
|
2021-03-18 17:17:29 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check if the provided Type is assignable to IEnumerable.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="t">The Type to check</param>
|
|
|
|
|
/// <returns>True if the Type is assignable to IEnumerable, otherwise false.</returns>
|
|
|
|
|
public static bool IsEnumerable(this Type t)
|
2021-04-28 20:47:48 +10:00
|
|
|
|
=> !typeof(UnityEngine.Transform).IsAssignableFrom(t)
|
|
|
|
|
&& ReflectionProvider.Instance.IsAssignableFrom(typeof(IEnumerable), t);
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check if the provided Type is assignable to IDictionary.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="t">The Type to check</param>
|
|
|
|
|
/// <returns>True if the Type is assignable to IDictionary, otherwise false.</returns>
|
|
|
|
|
public static bool IsDictionary(this Type t)
|
|
|
|
|
=> ReflectionProvider.Instance.IsAssignableFrom(typeof(IDictionary), t);
|
|
|
|
|
|
2021-03-30 19:50:04 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// [INTERNAL] Used to load Unhollowed DLLs in IL2CPP.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static bool LoadModule(string module)
|
2021-03-18 17:17:29 +11:00
|
|
|
|
=> ReflectionProvider.Instance.LoadModule(module);
|
|
|
|
|
|
|
|
|
|
// cache for GetTypeByName
|
|
|
|
|
internal static readonly Dictionary<string, Type> s_typesByName = new Dictionary<string, Type>();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="fullName">The <see cref="Type.FullName"/> you want to search for - case sensitive and full matches only.</param>
|
|
|
|
|
/// <returns>The Type if found, otherwise null.</returns>
|
|
|
|
|
public static Type GetTypeByName(string fullName)
|
|
|
|
|
{
|
2021-05-05 21:27:09 +10:00
|
|
|
|
allCachedTypes.TryGetValue(fullName, out Type type);
|
|
|
|
|
return type;
|
2021-03-18 17:17:29 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cache for GetBaseTypes
|
2021-04-23 21:50:58 +10:00
|
|
|
|
internal static readonly Dictionary<string, Type[]> s_cachedBaseTypes = new Dictionary<string, Type[]>();
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get all base types of the provided Type, including itself.
|
|
|
|
|
/// </summary>
|
2021-04-04 03:41:36 +10:00
|
|
|
|
public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj));
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get all base types of the provided Type, including itself.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static Type[] GetAllBaseTypes(this Type type)
|
|
|
|
|
{
|
|
|
|
|
if (type == null)
|
|
|
|
|
throw new ArgumentNullException("type");
|
|
|
|
|
|
|
|
|
|
var name = type.AssemblyQualifiedName;
|
|
|
|
|
|
2021-04-23 21:50:58 +10:00
|
|
|
|
if (s_cachedBaseTypes.TryGetValue(name, out Type[] ret))
|
2021-03-18 17:17:29 +11:00
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
List<Type> list = new List<Type>();
|
|
|
|
|
|
|
|
|
|
while (type != null)
|
|
|
|
|
{
|
|
|
|
|
list.Add(type);
|
|
|
|
|
type = type.BaseType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = list.ToArray();
|
|
|
|
|
|
2021-04-23 21:50:58 +10:00
|
|
|
|
s_cachedBaseTypes.Add(name, ret);
|
2021-03-18 17:17:29 +11:00
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-23 21:50:58 +10:00
|
|
|
|
// cache for GetImplementationsOf
|
2021-05-05 21:27:09 +10:00
|
|
|
|
internal static readonly Dictionary<string, HashSet<Type>> s_cachedTypeInheritance = new Dictionary<string, HashSet<Type>>();
|
|
|
|
|
internal static readonly Dictionary<string, HashSet<Type>> s_cachedGenericParameterInheritance = new Dictionary<string, HashSet<Type>>();
|
2021-04-23 21:50:58 +10:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain.
|
2021-05-05 21:27:09 +10:00
|
|
|
|
/// Also works for generic parameters by analyzing the constraints.
|
2021-04-23 21:50:58 +10:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
|
|
|
|
|
/// <returns>All implementations of the type in the current AppDomain.</returns>
|
2021-04-24 04:00:15 +10:00
|
|
|
|
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
|
2021-04-23 21:50:58 +10:00
|
|
|
|
{
|
2021-05-05 21:27:09 +10:00
|
|
|
|
var key = baseType.AssemblyQualifiedName;
|
2021-04-23 21:50:58 +10:00
|
|
|
|
|
2021-05-05 21:27:09 +10:00
|
|
|
|
if (!s_cachedTypeInheritance.ContainsKey(key))
|
2021-04-23 21:50:58 +10:00
|
|
|
|
{
|
|
|
|
|
var set = new HashSet<Type>();
|
|
|
|
|
|
|
|
|
|
if (!baseType.IsAbstract && !baseType.IsInterface)
|
|
|
|
|
set.Add(baseType);
|
|
|
|
|
|
2021-05-05 21:27:09 +10:00
|
|
|
|
var keys = allCachedTypes.Keys.ToArray();
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
2021-04-23 21:50:58 +10:00
|
|
|
|
{
|
2021-05-05 21:27:09 +10:00
|
|
|
|
var type = allCachedTypes[keys[i]];
|
|
|
|
|
try
|
2021-04-23 21:50:58 +10:00
|
|
|
|
{
|
2021-05-05 21:27:09 +10:00
|
|
|
|
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);
|
2021-04-23 21:50:58 +10:00
|
|
|
|
}
|
2021-05-05 21:27:09 +10:00
|
|
|
|
catch { }
|
2021-04-23 21:50:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 21:27:09 +10:00
|
|
|
|
s_cachedTypeInheritance.Add(key, set);
|
2021-04-23 21:50:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 21:27:09 +10:00
|
|
|
|
return s_cachedTypeInheritance[key];
|
2021-04-23 21:50:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 17:17:29 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Safely get all valid Types inside an Assembly.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="asm">The Assembly to find Types in.</param>
|
|
|
|
|
/// <returns>All possible Types which could be retrieved from the Assembly, or an empty array.</returns>
|
|
|
|
|
public static IEnumerable<Type> 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<Type>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-31 22:58:17 +11:00
|
|
|
|
internal static Dictionary<Type, Dictionary<string, FieldInfo>> s_cachedFieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
|
|
|
|
|
|
|
|
|
public static FieldInfo GetFieldInfo(Type type, string fieldName)
|
|
|
|
|
{
|
|
|
|
|
if (!s_cachedFieldInfos.ContainsKey(type))
|
|
|
|
|
s_cachedFieldInfos.Add(type, new Dictionary<string, FieldInfo>());
|
|
|
|
|
|
|
|
|
|
if (!s_cachedFieldInfos[type].ContainsKey(fieldName))
|
|
|
|
|
s_cachedFieldInfos[type].Add(fieldName, type.GetField(fieldName, AllFlags));
|
|
|
|
|
|
|
|
|
|
return s_cachedFieldInfos[type][fieldName];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal static Dictionary<Type, Dictionary<string, PropertyInfo>> s_cachedPropInfos = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
|
|
|
|
|
|
|
|
|
public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
|
|
|
|
|
{
|
|
|
|
|
if (!s_cachedPropInfos.ContainsKey(type))
|
|
|
|
|
s_cachedPropInfos.Add(type, new Dictionary<string, PropertyInfo>());
|
|
|
|
|
|
|
|
|
|
if (!s_cachedPropInfos[type].ContainsKey(propertyName))
|
|
|
|
|
s_cachedPropInfos[type].Add(propertyName, type.GetProperty(propertyName, AllFlags));
|
|
|
|
|
|
|
|
|
|
return s_cachedPropInfos[type][propertyName];
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-10 18:24:16 +10:00
|
|
|
|
internal static Dictionary<Type, Dictionary<string, MethodInfo>> s_cachedMethodInfos = new Dictionary<Type, Dictionary<string, MethodInfo>>();
|
|
|
|
|
|
|
|
|
|
public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes)
|
|
|
|
|
{
|
|
|
|
|
if (!s_cachedMethodInfos.ContainsKey(type))
|
|
|
|
|
s_cachedMethodInfos.Add(type, new Dictionary<string, MethodInfo>());
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-18 17:17:29 +11:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="e">The Exception to convert to string.</param>
|
|
|
|
|
/// <param name="innerMost">Should the inner-most Exception of the stack be used? If false, the Exception you provided will be used directly.</param>
|
|
|
|
|
/// <returns>The exception to string.</returns>
|
2021-04-27 21:22:21 +10:00
|
|
|
|
public static string ReflectionExToString(this Exception e, bool innerMost = true)
|
2021-03-18 17:17:29 +11:00
|
|
|
|
{
|
|
|
|
|
if (innerMost)
|
|
|
|
|
{
|
|
|
|
|
while (e.InnerException != null)
|
|
|
|
|
{
|
|
|
|
|
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
|
|
|
|
|
break;
|
2021-04-07 17:20:09 +10:00
|
|
|
|
|
2021-03-18 17:17:29 +11:00
|
|
|
|
e = e.InnerException;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $"{e.GetType()}: {e.Message}";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|