UnityExplorer/src/Core/ReflectionUtility.cs

365 lines
13 KiB
C#
Raw Normal View History

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;
namespace UnityExplorer
{
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);
}
public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
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-28 23:58:13 +10:00
if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr)
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;
}
/// <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>
public static Type GetActualType(this object obj)
{
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>
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);
}
/// <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>
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-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
/// <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)
=> !typeof(UnityEngine.Transform).IsAssignableFrom(t)
&& ReflectionProvider.Instance.IsAssignableFrom(typeof(IEnumerable), t);
/// <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);
/// <summary>
/// [INTERNAL] Used to load Unhollowed DLLs in IL2CPP.
/// </summary>
internal static bool LoadModule(string module)
=> 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;
}
// cache for GetBaseTypes
2021-04-23 21:50:58 +10:00
internal static readonly Dictionary<string, Type[]> s_cachedBaseTypes = new Dictionary<string, Type[]>();
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj));
/// <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))
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);
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
}
/// <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>();
}
}
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;
}
}
/// <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)
{
if (innerMost)
{
while (e.InnerException != null)
{
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break;
2021-04-07 17:20:09 +10:00
e = e.InnerException;
}
}
return $"{e.GetType()}: {e.Message}";
}
}
}