Reflection cleanup, fix il2cpp struct and enum boxing

And temp removing il2cpp IDictionary / IEnumerable helpers, will see what is necessary after knah's rewrite.
This commit is contained in:
Sinai 2021-05-07 01:22:55 +10:00
parent 1ee10c2507
commit 8534c08f49
18 changed files with 716 additions and 960 deletions

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UnityExplorer
{
public static class ReflectionExtensions
{
// ReflectionUtility extensions
public static Type GetActualType(this object obj)
=> ReflectionUtility.Instance.Internal_GetActualType(obj);
public static object TryCast(this object obj)
=> ReflectionUtility.Instance.Internal_TryCast(obj, ReflectionUtility.Instance.Internal_GetActualType(obj));
public static object TryCast(this object obj, Type castTo)
=> ReflectionUtility.Instance.Internal_TryCast(obj, castTo);
public static T TryCast<T>(this object obj)
{
try
{
return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T));
}
catch
{
return default;
}
}
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
=> ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
// ------- Misc extensions --------
/// <summary>
/// Safely try to get all Types inside an Assembly.
/// </summary>
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>();
}
}
/// <summary>
/// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality.
/// </summary>
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;
}
/// <summary>
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
/// </summary>
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}";
}
}
}

View File

@ -0,0 +1,456 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
namespace UnityExplorer
{
public class Il2CppReflection : ReflectionUtility
{
public Il2CppReflection()
{
TryLoadGameModules();
BuildDeobfuscationCache();
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
}
private void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
foreach (var type in args.LoadedAssembly.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
#region IL2CPP Extern and pointers
// Extern C++ methods
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
return il2cppPtr != IntPtr.Zero;
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
return il2cppPtr != IntPtr.Zero;
}
#endregion
#region Deobfuscation cache
private static readonly Dictionary<string, Type> DeobfuscatedTypes = new Dictionary<string, Type>();
//internal static Dictionary<string, string> s_deobfuscatedTypeNames = new Dictionary<string, string>();
private static void BuildDeobfuscationCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
if (DeobfuscatedTypes.Count > 0)
ExplorerCore.Log($"Built deobfuscation cache, count: {DeobfuscatedTypes.Count}");
}
private static void TryCacheDeobfuscatedType(Type type)
{
try
{
// Thanks to Slaynash for this
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
{
var cppType = Il2CppType.From(type);
if (!DeobfuscatedTypes.ContainsKey(cppType.FullName))
{
DeobfuscatedTypes.Add(cppType.FullName, type);
//s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName);
}
}
}
catch { }
}
#endregion
// Get type by name
internal override Type Internal_GetTypeByName(string fullName)
{
if (DeobfuscatedTypes.TryGetValue(fullName, out Type deob))
return deob;
return base.Internal_GetTypeByName(fullName);
}
#region Get actual type
internal override Type Internal_GetActualType(object obj)
{
if (obj == null)
return null;
return DoGetActualType(obj);
}
internal Type DoGetActualType(object obj)
{
var type = obj.GetType();
try
{
if (IsString(obj))
return typeof(string);
if (obj is Il2CppSystem.Object cppObject)
{
var cppType = cppObject.GetIl2CppType();
// check if type is injected
IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
if (RuntimeSpecificsStore.IsInjected(classPtr))
{
// Note: This will fail on injected subclasses.
// - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected.
// Not sure on solution yet.
return GetTypeByName(cppType.FullName) ?? type;
}
return GetUnhollowedType(cppType) ?? type;
}
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning("Exception in GetActualType: " + ex);
}
return type;
}
public static Type GetUnhollowedType(CppType cppType)
{
var fullname = cppType.FullName;
if (DeobfuscatedTypes.TryGetValue(fullname, out Type deob))
return deob;
if (fullname.StartsWith("System."))
fullname = $"Il2Cpp{fullname}";
AllTypes.TryGetValue(fullname, out Type monoType);
return monoType;
}
#endregion
#region Casting
private static readonly Dictionary<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
internal override object Internal_TryCast(object obj, Type castTo)
{
if (obj == null)
return null;
var type = obj.GetType();
// If casting from ValueType or string to Il2CppSystem.Object...
if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{
if (type.IsValueType)
return BoxIl2CppObject(obj);
if (obj is string)
{
BoxStringToType(ref obj, castTo);
return obj;
}
}
if (!(obj is Il2CppSystem.Object cppObj))
return obj;
// If going from Il2CppSystem.Object to a ValueType or string...
if (castTo.IsValueType)
{
if (castTo.FullName.StartsWith("Il2CppSystem."))
AllTypes.TryGetValue(cppObj.GetIl2CppType().FullName, out castTo);
return Unbox(cppObj, castTo);
}
else if (castTo == typeof(string))
return UnboxString(obj);
// Casting from il2cpp object to il2cpp object...
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
return obj;
IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
return null;
if (RuntimeSpecificsStore.IsInjected(castToPtr))
{
var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
return injectedObj ?? obj;
}
try
{
return Activator.CreateInstance(castTo, cppObj.Pointer);
}
catch
{
return obj;
}
}
#endregion
#region Boxing and unboxing ValueTypes
// cached il2cpp unbox methods
internal static readonly Dictionary<string, MethodInfo> unboxMethods = new Dictionary<string, MethodInfo>();
public object Unbox(object obj, Type type)
{
if (!type.IsValueType)
return null;
if (!(obj is Il2CppSystem.Object cppObj))
return obj;
try
{
if (type.IsEnum)
return Enum.ToObject(type, Il2CppSystem.Enum.ToUInt64(cppObj));
var name = type.AssemblyQualifiedName;
if (!unboxMethods.ContainsKey(type.AssemblyQualifiedName))
{
unboxMethods.Add(name, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(type));
}
return unboxMethods[name].Invoke(obj, new object[0]);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception Unboxing Il2Cpp object to struct: " + ex);
return null;
}
}
private static readonly Type[] emptyTypes = new Type[0];
private static readonly object[] emptyArgs = new object[0];
public Il2CppSystem.Object BoxIl2CppObject(object value)
{
if (value == null)
return null;
try
{
var type = value.GetType();
if (!type.IsValueType)
return null;
if (type.IsEnum)
{
// TODO not tested
return Il2CppSystem.Enum.ToObject(Il2CppType.From(type), (ulong)value);
}
if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType))
{
// Create an Il2CppSystem representation of the value
var cppStruct = Activator.CreateInstance(cppType);
// set the 'm_value' field of the il2cpp struct to the system value
GetFieldInfo(cppType, "m_value").SetValue(cppStruct, value);
// set the cpp representations as our references
value = cppStruct;
type = cppType;
}
return (Il2CppSystem.Object)GetMethodInfo(type, "BoxIl2CppObject", emptyTypes)
.Invoke(value, emptyArgs);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception in BoxIl2CppObject: " + ex);
return null;
}
}
#endregion
#region Strings
private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
private const string STRING_FULLNAME = "System.String";
public bool IsString(object obj)
{
if (obj is string || obj is Il2CppSystem.String)
return true;
if (obj is Il2CppSystem.Object cppObj)
{
var type = cppObj.GetIl2CppType();
return type.FullName == IL2CPP_STRING_FULLNAME || type.FullName == STRING_FULLNAME;
}
return false;
}
public void BoxStringToType(ref object value, Type castTo)
{
if (castTo == typeof(Il2CppSystem.String))
value = (Il2CppSystem.String)(value as string);
else
value = (Il2CppSystem.Object)(value as string);
}
public string UnboxString(object value)
{
if (value is string s)
return s;
s = null;
if (value is Il2CppSystem.Object cppObject)
s = cppObject.ToString();
else if (value is Il2CppSystem.String cppString)
s = cppString;
return s;
}
internal override string Internal_ProcessTypeInString(string theString, Type type, ref string typeName)
{
if (!Il2CppTypeNotNull(type))
return theString;
var cppType = Il2CppType.From(type);
if (cppType != null && DeobfuscatedTypes.ContainsKey(cppType.FullName))
{
typeName = DeobfuscatedTypes[cppType.FullName].FullName;
theString = theString.Replace(cppType.FullName, typeName);
}
return theString;
}
#endregion
#region Singleton finder
internal override void Internal_FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
{
PropertyInfo pi;
foreach (var name in possibleNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
base.Internal_FindSingleton(possibleNames, type, flags, instances);
}
#endregion
#region FORCE LOADING GAME MODULES
// 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))
moduleName += ".dll";
#if ML
var path = Path.Combine("MelonLoader", "Managed", $"{moduleName}");
#else
var path = Path.Combine("BepInEx", "unhollowed", $"{moduleName}");
#endif
return DoLoadModule(path);
}
internal bool DoLoadModule(string fullPath)
{
if (!File.Exists(fullPath))
return false;
try
{
Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
{
Console.WriteLine(e.GetType() + ", " + e.Message);
}
return false;
}
#endregion
}
}
#endif

View File

@ -10,9 +10,33 @@ using System.Text;
namespace UnityExplorer
{
public static class ReflectionUtility
public class ReflectionUtility
{
// The Instance and instance methods are not for public use, they're only so IL2CPP can override.
// This class and the Extensions class expose static methods to use instead.
public const BF FLAGS = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
internal static readonly ReflectionUtility Instance =
#if CPP
new Il2CppReflection();
#else
new ReflectionUtility();
#endif
static ReflectionUtility()
{
SetupTypeCache();
}
#region Type cache
/// <summary>Key: Type.FullName</summary>
public static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
private static readonly List<string> allTypeNames = new List<string>();
private static void SetupTypeCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
CacheTypes(asm);
@ -22,10 +46,6 @@ namespace UnityExplorer
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
}
/// <summary>Key: Type.FullName</summary>
public static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
private static readonly List<string> allTypeNames = new List<string>();
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
if (args.LoadedAssembly == null)
@ -42,7 +62,7 @@ namespace UnityExplorer
if (AllTypes.ContainsKey(type.FullName))
AllTypes[type.FullName] = type;
else
{
{
AllTypes.Add(type.FullName, type);
allTypeNames.Add(type.FullName);
}
@ -60,89 +80,7 @@ namespace UnityExplorer
}
}
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)
{
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;
}
/// <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) => ReflectionProvider.Instance.Cast(obj, GetActualType(obj));
/// <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) => ReflectionProvider.Instance.Cast(obj, castTo);
/// <summary>Try to cast the object to the type.</summary>
public static T TryCast<T>(this object obj) => ReflectionProvider.Instance.TryCast<T>(obj);
/// <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>();
#endregion
/// <summary>
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
@ -150,25 +88,74 @@ namespace UnityExplorer
/// <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)
{
=> Instance.Internal_GetTypeByName(fullName);
internal virtual Type Internal_GetTypeByName(string fullName)
{
AllTypes.TryGetValue(fullName, out Type type);
return type;
}
// Getting the actual type of an object
internal virtual Type Internal_GetActualType(object obj)
=> obj.GetType();
// Force-casting an object to a type
internal virtual object Internal_TryCast(object obj, Type castTo)
=> obj;
// Processing deobfuscated type names in strings
public static string ProcessTypeInString(Type type, string theString, ref string typeName)
=> Instance.Internal_ProcessTypeInString(theString, type, ref typeName);
internal virtual string Internal_ProcessTypeInString(string theString, Type type, ref string typeName)
=> theString;
// Force loading modules
public static bool LoadModule(string moduleName)
=> Instance.Internal_LoadModule(moduleName);
internal virtual bool Internal_LoadModule(string moduleName)
=> false;
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
internal virtual void Internal_FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
{
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in possibleNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
}
// Universal helpers
#region Type inheritance cache
// cache for GetBaseTypes
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));
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType());
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(this Type type)
public static Type[] GetAllBaseTypes(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
@ -193,6 +180,11 @@ namespace UnityExplorer
return ret;
}
#endregion
#region Type and Generic Parameter implementation cache
// cache for GetImplementationsOf
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>>();
@ -218,7 +210,7 @@ namespace UnityExplorer
/// </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>
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric)
{
var key = GetImplementationKey(baseType); //baseType.FullName;
@ -308,33 +300,10 @@ namespace UnityExplorer
return s_cachedGenericParameterInheritance[key];
}
/// <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>();
}
}
#endregion
#region Internal MemberInfo Cache
internal static Dictionary<Type, Dictionary<string, FieldInfo>> s_cachedFieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
@ -344,7 +313,7 @@ namespace UnityExplorer
s_cachedFieldInfos.Add(type, new Dictionary<string, FieldInfo>());
if (!s_cachedFieldInfos[type].ContainsKey(fieldName))
s_cachedFieldInfos[type].Add(fieldName, type.GetField(fieldName, AllFlags));
s_cachedFieldInfos[type].Add(fieldName, type.GetField(fieldName, FLAGS));
return s_cachedFieldInfos[type][fieldName];
}
@ -357,7 +326,7 @@ namespace UnityExplorer
s_cachedPropInfos.Add(type, new Dictionary<string, PropertyInfo>());
if (!s_cachedPropInfos[type].ContainsKey(propertyName))
s_cachedPropInfos[type].Add(propertyName, type.GetProperty(propertyName, AllFlags));
s_cachedPropInfos[type].Add(propertyName, type.GetProperty(propertyName, FLAGS));
return s_cachedPropInfos[type][propertyName];
}
@ -388,9 +357,9 @@ namespace UnityExplorer
if (!s_cachedMethodInfos[type].ContainsKey(sig))
{
if (argumentTypes != null)
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags, null, argumentTypes, null));
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, FLAGS, null, argumentTypes, null));
else
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags));
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, FLAGS));
}
return s_cachedMethodInfos[type][sig];
@ -407,26 +376,7 @@ namespace UnityExplorer
}
}
/// <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>
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;
#endregion
e = e.InnerException;
}
}
return $"{e.GetType()}: {e.Message}";
}
}
}

View File

@ -22,7 +22,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.IL2CPP;
Reflection = new Il2CppReflection();
//Reflection = new Il2CppReflection();
TextureUtil = new Il2CppTextureUtil();
}

View File

@ -1,625 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
public class Il2CppReflection : ReflectionProvider
{
public Il2CppReflection() : base()
{
Instance = this;
TryLoadGameModules();
BuildDeobfuscationCache();
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
}
private void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
foreach (var type in args.LoadedAssembly.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
public override object Cast(object obj, Type castTo)
{
return Il2CppCast(obj, castTo);
}
public override T TryCast<T>(object obj)
{
try
{
return (T)Il2CppCast(obj, typeof(T));
}
catch
{
return default;
}
}
public override Type GetActualType(object obj)
{
if (obj == null)
return null;
var type = obj.GetType();
try
{
if (obj is Il2CppSystem.Object cppObject)
{
var cppType = cppObject.GetIl2CppType();
// check if type is injected
IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
if (RuntimeSpecificsStore.IsInjected(classPtr))
{
// Note: This will fail on injected subclasses.
// - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected.
// Not sure on solution yet.
return ReflectionUtility.GetTypeByName(cppType.FullName) ?? type;
}
return GetMonoType(cppType) ?? type;
}
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning("Exception in GetActualType: " + ex);
}
return type;
}
public static Type GetMonoType(CppType cppType)
{
if (DeobfuscatedTypes.TryGetValue(cppType.AssemblyQualifiedName, out Type deob))
return deob;
var fullname = cppType.FullName;
if (fullname.StartsWith("System."))
fullname = $"Il2Cpp{fullname}";
ReflectionUtility.AllTypes.TryGetValue(fullname, out Type monoType);
return monoType;
}
// cached class pointers for Il2CppCast
private static readonly Dictionary<string, IntPtr> s_cppClassPointers = new Dictionary<string, IntPtr>();
/// <summary>
/// Attempt to cast the object to its underlying type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <returns>The object, as the underlying type if successful or the input value if not.</returns>
public static object Il2CppCast(object obj) => Il2CppCast(obj, Instance.GetActualType(obj));
/// <summary>
/// Attempt to cast the object to the provided type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <param name="castTo">The Type you want to cast to.</param>
/// <returns>The object, as the type (or a normal C# object) if successful or the input value if not.</returns>
public static object Il2CppCast(object obj, Type castTo)
{
if (obj == null)
return null;
var type = obj.GetType();
if (type.IsValueType && typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
return BoxIl2CppObject(obj);
if (!(obj is Il2CppSystem.Object cppObj))
return obj;
if (castTo.IsValueType)
{
if (castTo.FullName.StartsWith("Il2CppSystem."))
ReflectionUtility.AllTypes.TryGetValue(cppObj.GetIl2CppType().FullName, out castTo);
return Unbox(cppObj, castTo);
}
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
return obj;
IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
return null;
if (RuntimeSpecificsStore.IsInjected(castToPtr))
{
var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
return injectedObj ?? obj;
}
try
{
return Activator.CreateInstance(castTo, cppObj.Pointer);
}
catch
{
return obj;
}
}
// struct boxing
// cached il2cpp unbox methods
internal static readonly Dictionary<string, MethodInfo> s_unboxMethods = new Dictionary<string, MethodInfo>();
/// <summary>
/// Attempt to unbox the object to the underlying struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(object obj) => Unbox(obj, Instance.GetActualType(obj));
/// <summary>
/// Attempt to unbox the object to the struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <param name="type">The type of the struct you want to unbox to.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(object obj, Type type)
{
if (!type.IsValueType)
return null;
if (!(obj is Il2CppSystem.Object))
return obj;
var name = type.AssemblyQualifiedName;
if (!s_unboxMethods.ContainsKey(name))
{
s_unboxMethods.Add(name, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(type));
}
return s_unboxMethods[name].Invoke(obj, new object[0]);
}
//internal static Dictionary<string, Type> monoToIl2CppType = new Dictionary<string, Type>();
internal static Dictionary<Type, MethodInfo> structBoxMethods = new Dictionary<Type, MethodInfo>();
internal static Dictionary<Type, FieldInfo> structValueFields = new Dictionary<Type, FieldInfo>();
/// <summary>
/// Try to box a value to Il2CppSystem.Object using the Il2CppSystem representation of the value type.
/// </summary>
/// <param name="value">The value, eg 5 (System.Int32)</param>
/// <returns>The boxed Il2CppSystem.Object for the value, eg for '5' it would be a boxed Il2CppSytem.Int32.</returns>
public static Il2CppSystem.Object BoxIl2CppObject(object value)
{
string key = $"Il2Cpp{value.GetType().FullName}";
if (ReflectionUtility.AllTypes.TryGetValue(key, out Type type) && type.IsValueType)
{
var cppStruct = Activator.CreateInstance(type);
if (!structValueFields.ContainsKey(type))
structValueFields.Add(type, type.GetField("m_value"));
structValueFields[type].SetValue(cppStruct, value);
if (!structBoxMethods.ContainsKey(type))
structBoxMethods.Add(type, type.GetMethod("BoxIl2CppObject"));
return structBoxMethods[type].Invoke(cppStruct, null) as Il2CppSystem.Object;
}
else
{
ExplorerCore.LogWarning("Couldn't get type to box to");
return null;
}
}
// deobfuscation
private static readonly Dictionary<string, Type> DeobfuscatedTypes = new Dictionary<string, Type>();
internal static Dictionary<string, string> s_deobfuscatedTypeNames = new Dictionary<string, string>();
private static void BuildDeobfuscationCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
if (s_deobfuscatedTypeNames.Count > 0)
ExplorerCore.Log($"Built deobfuscation cache, count: {s_deobfuscatedTypeNames.Count}");
}
private static void TryCacheDeobfuscatedType(Type type)
{
try
{
// Thanks to Slaynash for this
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
{
var cppType = Il2CppType.From(type);
if (!DeobfuscatedTypes.ContainsKey(cppType.AssemblyQualifiedName))
{
DeobfuscatedTypes.Add(cppType.AssemblyQualifiedName, type);
s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName);
}
}
}
catch { }
}
public override Type GetDeobfuscatedType(Type type)
{
try
{
if (DeobfuscatedTypes.ContainsKey(type.AssemblyQualifiedName))
return DeobfuscatedTypes[type.AssemblyQualifiedName];
}
catch { }
return type;
}
// misc helpers
/// <summary>
/// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type.
/// </summary>
/// <param name="type">The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for.</param>
/// <returns>True if successful, false if not.</returns>
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
/// <summary>
/// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type.
/// </summary>
/// <param name="type">The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for.</param>
/// <param name="il2cppPtr">The IntPtr for the Il2Cpp class, or IntPtr.Zero if not found.</param>
/// <returns>True if successful, false if not.</returns>
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (s_cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
return il2cppPtr != IntPtr.Zero;
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
s_cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
return il2cppPtr != IntPtr.Zero;
}
#region EXTERN CAST METHODS
// Extern C++ methods
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
#endregion
// Strings
private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
public override bool IsString(object obj) => obj is string || obj.GetActualType().FullName == IL2CPP_STRING_FULLNAME;
public override void BoxStringToType(ref object value, Type castTo)
{
if (castTo == typeof(Il2CppSystem.String))
value = (Il2CppSystem.String)(value as string);
else
value = (Il2CppSystem.Object)(value as string);
}
public override string UnboxString(object value)
{
if (value is string s)
return s;
s = null;
// strings boxed as Il2CppSystem.Objects can behave weirdly.
// GetActualType will find they are a string, but if its boxed
// then we need to unbox it like this...
if (value is Il2CppSystem.Object cppObject)
s = cppObject.ToString();
else if (value is Il2CppSystem.String cppString)
s = cppString;
return s;
}
public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName)
{
if (!Il2CppTypeNotNull(type))
return theString;
var cppType = Il2CppType.From(type);
if (cppType != null && s_deobfuscatedTypeNames.ContainsKey(cppType.FullName))
{
typeName = s_deobfuscatedTypeNames[cppType.FullName];
theString = theString.Replace(cppType.FullName, typeName);
}
return theString;
}
//// Not currently using, not sure if its necessary anymore, was necessary to prevent crashes at one point.
//public override bool IsReflectionSupported(Type type)
//{
// try
// {
// var gArgs = type.GetGenericArguments();
// if (!gArgs.Any())
// return true;
//
// foreach (var gType in gArgs)
// {
// if (!Supported(gType))
// return false;
// }
//
// return true;
//
// bool Supported(Type t)
// {
// if (!typeof(Il2CppSystem.Object).IsAssignableFrom(t))
// return true;
//
// if (!Il2CppTypeNotNull(t, out IntPtr ptr))
// return false;
//
// return CppType.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is CppType;
// }
// }
// catch
// {
// return false;
// }
//}
#region FORCE LOADING GAME MODULES
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
internal static void TryLoadGameModules()
{
Instance.LoadModule("Assembly-CSharp");
Instance.LoadModule("Assembly-CSharp-firstpass");
}
public override bool LoadModule(string module)
{
#if ML
var path = Path.Combine("MelonLoader", "Managed", $"{module}.dll");
#else
var path = Path.Combine("BepInEx", "unhollowed", $"{module}.dll");
#endif
return LoadModuleInternal(path);
}
internal static bool LoadModuleInternal(string fullPath)
{
if (!File.Exists(fullPath))
return false;
try
{
Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
{
Console.WriteLine(e.GetType() + ", " + e.Message);
}
return false;
}
#endregion
#region IL2CPP IENUMERABLE / IDICTIONARY
// dictionary and enumerable cast helpers (until unhollower rewrite)
internal static IntPtr s_cppEnumerableClassPtr;
internal static IntPtr s_cppDictionaryClassPtr;
public override bool IsAssignableFrom(Type toAssignTo, Type toAssignFrom)
{
if (toAssignTo.IsAssignableFrom(toAssignFrom))
return true;
if (toAssignTo == typeof(IEnumerable))
{
try
{
if (s_cppEnumerableClassPtr == IntPtr.Zero)
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr);
if (s_cppEnumerableClassPtr != IntPtr.Zero
&& Il2CppTypeNotNull(toAssignFrom, out IntPtr assignFromPtr)
&& il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, assignFromPtr))
{
return true;
}
}
catch { }
}
else if (toAssignTo == typeof(IDictionary))
{
try
{
if (s_cppDictionaryClassPtr == IntPtr.Zero)
if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr))
return false;
if (Il2CppTypeNotNull(toAssignFrom, out IntPtr classPtr))
{
if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr))
return true;
}
}
catch { }
}
return false;
}
internal static readonly Dictionary<Type, MethodInfo> s_getEnumeratorMethods = new Dictionary<Type, MethodInfo>();
internal static readonly Dictionary<Type, EnumeratorInfo> s_enumeratorInfos = new Dictionary<Type, EnumeratorInfo>();
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
public override IEnumerable EnumerateEnumerable(object value)
{
if (value == null)
return null;
var cppEnumerable = (value as Il2CppSystem.Object)?.TryCast<Il2CppSystem.Collections.IEnumerable>();
if (cppEnumerable != null)
{
var type = value.GetType();
if (!s_getEnumeratorMethods.ContainsKey(type))
s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator"));
var enumerator = s_getEnumeratorMethods[type].Invoke(value, 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];
// iterate
var list = new List<object>();
while ((bool)info.moveNext.Invoke(enumerator, null))
list.Add(info.current.GetValue(enumerator));
return list;
}
return null;
}
public override IDictionary EnumerateDictionary(object value, Type typeOfKeys, Type typeOfValues)
{
var valueType = ReflectionUtility.GetActualType(value);
var keyList = new List<object>();
var valueList = new List<object>();
var hashtable = value.TryCast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable;
if (hashtable != null)
{
EnumerateCppHashtable(hashtable, keyList, valueList);
}
else
{
var keys = valueType.GetProperty("Keys").GetValue(value, null);
var values = valueType.GetProperty("Values").GetValue(value, null);
EnumerateCppIDictionary(keys, keyList);
EnumerateCppIDictionary(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 EnumerateCppIDictionary(object collection, List<object> 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<object> keys, List<object> 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);
}
}
public override void FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
{
PropertyInfo pi;
foreach (var name in possibleNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
base.FindSingleton(possibleNames, type, flags, instances);
}
#endregion
}
}
#endif

View File

@ -21,7 +21,7 @@ namespace UnityExplorer.Core.Runtime.Mono
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.Mono;
Reflection = new MonoReflection();
//Reflection = new MonoReflection();
TextureUtil = new MonoTextureUtil();
}

View File

@ -1,47 +0,0 @@
#if MONO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Runtime.Mono
{
public class MonoReflection : ReflectionProvider
{
// Mono doesn't need to explicitly cast things.
public override object Cast(object obj, Type castTo)
=> obj;
public override T TryCast<T>(object obj)
{
try
{
return (T)obj;
}
catch
{
return default;
}
}
// Vanilla GetType is fine for mono
public override Type GetActualType(object obj)
=> obj.GetType();
public override bool IsAssignableFrom(Type toAssignTo, Type toAssignFrom)
=> toAssignTo.IsAssignableFrom(toAssignFrom);
public override bool LoadModule(string module)
=> true;
public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName)
=> theString;
public override Type GetDeobfuscatedType(Type type) => type;
// not necessary
public override void BoxStringToType(ref object _string, Type castTo) { }
}
}
#endif

View File

@ -52,9 +52,9 @@ namespace UnityExplorer.Core.Runtime.Mono
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.AllFlags);
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.AllFlags);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
if (method != null)
return m_encodeToPNGMethod = method;

View File

@ -1,66 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UnityExplorer.Core.Runtime
{
public abstract class ReflectionProvider
{
public static ReflectionProvider Instance;
public ReflectionProvider()
{
Instance = this;
}
public abstract Type GetActualType(object obj);
public abstract Type GetDeobfuscatedType(Type type);
public abstract object Cast(object obj, Type castTo);
public abstract T TryCast<T>(object obj);
public abstract bool IsAssignableFrom(Type toAssignTo, Type toAssignFrom);
//public abstract bool IsReflectionSupported(Type type);
public abstract string ProcessTypeFullNameInString(Type type, string theString, ref string typeName);
public abstract bool LoadModule(string module);
public virtual bool IsString(object obj) => obj is string;
public abstract void BoxStringToType(ref object _string, Type castTo);
public virtual string UnboxString(object value) => (string)value;
public virtual IDictionary EnumerateDictionary(object value, Type typeOfKeys, Type typeOfValues)
=> null;
public virtual IEnumerable EnumerateEnumerable(object value)
=> null;
public virtual void FindSingleton(string[] s_instanceNames, Type type, BindingFlags flags, List<object> instances)
{
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in s_instanceNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
}
}
}

View File

@ -16,7 +16,6 @@ namespace UnityExplorer
{
public static RuntimeProvider Instance;
public ReflectionProvider Reflection;
public TextureUtilProvider TextureUtil;
public RuntimeProvider()

View File

@ -111,7 +111,7 @@ namespace UnityExplorer.Core
if (sceneUtil == null)
throw new Exception("This version of Unity does not ship with the 'SceneUtility' class, or it was not unstripped.");
var method = sceneUtil.GetMethod("GetScenePathByBuildIndex", ReflectionUtility.AllFlags);
var method = sceneUtil.GetMethod("GetScenePathByBuildIndex", ReflectionUtility.FLAGS);
int sceneCount = SceneManager.sceneCountInBuildSettings;
for (int i = 0; i < sceneCount; i++)
{

View File

@ -141,7 +141,7 @@ namespace UnityExplorer.Core.Search
return list;
}
internal static string[] s_instanceNames = new string[]
internal static string[] instanceNames = new string[]
{
"m_instance",
"m_Instance",
@ -175,7 +175,7 @@ namespace UnityExplorer.Core.Search
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
continue;
ReflectionProvider.Instance.FindSingleton(s_instanceNames, type, flags, instances);
ReflectionUtility.FindSingleton(instanceNames, type, flags, instances);
}
catch { }
}

View File

@ -4,6 +4,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
#if CPP
using UnhollowerRuntimeLib;
using UnhollowerBaseLib;
#endif
namespace UnityExplorer.Tests
{
@ -126,10 +130,10 @@ namespace UnityExplorer.Tests
public static Il2CppSystem.String testStringThree = "string boxed as cpp string";
public static string nullString = null;
public static List<Il2CppSystem.Object> cppBoxedList;
public static UnhollowerBaseLib.Il2CppStructArray<int> CppIntStructArray;
public static UnhollowerBaseLib.Il2CppStringArray CppStringArray;
public static UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object> CppReferenceArray;
public static List<Il2CppSystem.Object> CppBoxedList;
public static Il2CppStructArray<int> CppIntStructArray;
public static Il2CppStringArray CppStringArray;
public static Il2CppReferenceArray<Il2CppSystem.Object> CppReferenceArray;
public static Il2CppSystem.Object cppBoxedInt;
public static Il2CppSystem.Int32 cppInt;
@ -144,6 +148,29 @@ namespace UnityExplorer.Tests
BigList.Add(i.ToString());
#if CPP
CppBoxedList = new List<Il2CppSystem.Object>();
CppBoxedList.Add((Il2CppSystem.String)"boxedString");
CppBoxedList.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject());
try
{
var cppType = Il2CppType.Of<CameraClearFlags>();
if (cppType != null)
{
var boxedEnum = Il2CppSystem.Enum.Parse(cppType, "Color");
CppBoxedList.Add(boxedEnum);
}
var structBox = Vector3.one.BoxIl2CppObject();
CppBoxedList.Add(structBox);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Test fail: {ex}");
}
CppIntStructArray = new UnhollowerBaseLib.Il2CppStructArray<int>(5);
CppIntStructArray[0] = 0;
CppIntStructArray[1] = 1;
@ -161,9 +188,6 @@ namespace UnityExplorer.Tests
CppReferenceArray[2] = (Il2CppSystem.String)"whats up";
cppBoxedInt = new Il2CppSystem.Int32() { m_value = 5 }.BoxIl2CppObject();
cppBoxedList = new List<Il2CppSystem.Object>();
cppBoxedList.Add((Il2CppSystem.String)"boxedString");
cppBoxedList.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject());
cppInt = new Il2CppSystem.Int32 { m_value = 420 };
TestWritableBoxedList = new List<Il2CppSystem.Object>();

View File

@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEngine;

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -96,10 +97,7 @@ namespace UnityExplorer.UI.CacheObject
public void SetUserValue(object value)
{
if (State == ValueState.String)
ReflectionProvider.Instance.BoxStringToType(ref value, FallbackType);
else
value = value.TryCast(FallbackType);
value = value.TryCast(FallbackType);
TrySetUserValue(value);
}
@ -147,16 +145,16 @@ namespace UnityExplorer.UI.CacheObject
State = ValueState.Boolean;
else if (type.IsPrimitive || type == typeof(decimal))
State = ValueState.Number;
else if (ReflectionProvider.Instance.IsString(Value))
else if (type == typeof(string))
State = ValueState.String;
else if (type.IsEnum)
State = ValueState.Enum;
// todo Color and ValueStruct
else if (type.IsDictionary())
else if (typeof(IDictionary).IsAssignableFrom(type))
State = ValueState.Dictionary;
else if (type.IsEnumerable())
else if (typeof(IEnumerable).IsAssignableFrom(type))
State = ValueState.Collection;
else
State = ValueState.Unsupported;
@ -174,12 +172,12 @@ namespace UnityExplorer.UI.CacheObject
case ValueState.NotEvaluated:
label = $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>"; break;
case ValueState.Exception:
label = $"<i><color=red>{ReflectionUtility.ReflectionExToString(LastException)}</color></i>"; break;
label = $"<i><color=red>{LastException.ReflectionExToString()}</color></i>"; break;
case ValueState.Boolean:
case ValueState.Number:
label = null; break;
case ValueState.String:
string s = ReflectionProvider.Instance.UnboxString(Value);
string s = Value as string;
if (s.Length > 200)
s = $"{s.Substring(0, 200)}...";
label = $"\"{s}\""; break;

View File

@ -60,7 +60,7 @@ namespace UnityExplorer.UI.Utility
return CLASS_INSTANCE;
}
private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
//private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
private static bool GetNamespace(Type type, out string ns)
{
@ -73,7 +73,7 @@ namespace UnityExplorer.UI.Utility
if (type == null)
throw new ArgumentNullException("type");
syntaxBuilder.Clear();
var syntaxBuilder = new StringBuilder();
// Namespace
@ -105,12 +105,6 @@ namespace UnityExplorer.UI.Utility
{
syntaxBuilder.Append('.');
//string memColor = GetMemberInfoColor(memberInfo, out bool isStatic);
//if (isStatic)
// syntaxBuilder.Append(OPEN_ITALIC);
//syntaxBuilder.Append($"<color={memColor}>{memberInfo.Name}{CLOSE_COLOR}");
int start = syntaxBuilder.Length - 1;
syntaxBuilder.Append(OPEN_COLOR)
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
@ -128,7 +122,6 @@ namespace UnityExplorer.UI.Utility
{
var args = method.GetGenericArguments();
if (args.Length > 0)
//syntaxBuilder.Append($"<{ParseGenericArgs(args, true)}>");
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
}
}
@ -136,31 +129,6 @@ namespace UnityExplorer.UI.Utility
return syntaxBuilder.ToString();
}
//public static string ParseType(Type type, bool includeNamespace = false, bool includeDllName = false)
//{
// var sb = new StringBuilder();
//
// bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter);
//
// if (!isGeneric && includeNamespace && GetNamespace(type, out string ns))
// //sb.Append($"<color={NAMESPACE}>{ns}{CLOSE_COLOR}.");
// sb.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.');
//
// sb.Append(HighlightType(type));
//
// if (includeDllName)
// {
// if (!string.IsNullOrEmpty(type.Assembly.Location))
// //sb.Append($" ({Path.GetFileName(type.Assembly.Location)})");
// sb.Append(' ').Append('(').Append(Path.GetFileName(type.Assembly.Location)).Append(')');
// else
// //sb.Append($" ({type.Assembly.GetName().Name})");
// sb.Append(' ').Append('(').Append(type.Assembly.GetName().Name).Append(')');
// }
//
// return sb.ToString();
//}
private static readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
private static bool EndsWith(this StringBuilder sb, string _string)
@ -198,7 +166,6 @@ namespace UnityExplorer.UI.Utility
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
{
//typeName = $"<color={CONST}>{typeName}</color>";
sb.Insert(0, $"<color={CONST}>");
sb.Append(CLOSE_COLOR);
}
@ -216,26 +183,22 @@ namespace UnityExplorer.UI.Utility
// make sure the typename actually has expected "`N" format.
if (sb[sb.Length - suffixLen] == '`')
//typeName = typeName.Substring(0, typeName.Length - suffixLen);
sb.Remove(sb.Length - suffixLen, suffixLen);
}
// highlight the base name itself
// do this after removing the `N suffix, so only the name itself is in the color tags.
//typeName = $"<color={GetClassColor(type)}>{typeName}</color>";
sb.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
sb.Append(CLOSE_COLOR);
// parse the generic args, if any
if (args.Length > 0)
{
//typeName += $"<{ParseGenericArgs(args)}>";
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
}
}
if (isArray)
//typeName += "[]";
sb.Append('[').Append(']');
var ret = sb.ToString();
@ -248,24 +211,20 @@ namespace UnityExplorer.UI.Utility
{
if (args.Length < 1)
return string.Empty;
//string ret = "";
var sb = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
if (i > 0)
//ret += ", ";
sb.Append(',').Append(' ');
if (isGenericParams)
{
//ret += $"<color={CONST}>{args[i].Name}</color>";
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
continue;
}
//ret += ParseType(args[i]);
sb.Append(HighlightType(args[i]));
}

View File

@ -70,7 +70,7 @@ namespace UnityExplorer.UI.Utility
{
var toString = ToString(value);
if (type.IsEnumerable())
if (typeof(IEnumerable).IsAssignableFrom(type))
{
if (value is IList iList)
_stringBuilder.Append($"[{iList.Count}] ");
@ -80,7 +80,7 @@ namespace UnityExplorer.UI.Utility
else
_stringBuilder.Append("[?] ");
}
else if (type.IsDictionary())
else if (typeof(IDictionary).IsAssignableFrom(type))
{
if (value is IDictionary iDict)
_stringBuilder.Append($"[{iDict.Count}] ");
@ -167,14 +167,14 @@ namespace UnityExplorer.UI.Utility
}
string _ = null;
toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref _);
toString = ReflectionUtility.ProcessTypeInString(type, toString, ref _);
#if CPP
if (value is Il2CppSystem.Type cppType)
{
var monoType = Core.Runtime.Il2Cpp.Il2CppReflection.GetMonoType(cppType);
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
if (monoType != null)
toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(monoType, toString, ref _);
toString = ReflectionUtility.ProcessTypeInString(monoType, toString, ref _);
}
#endif

View File

@ -222,6 +222,8 @@
<Compile Include="Core\CSharp\ScriptEvaluator.cs" />
<Compile Include="Core\CSharp\ScriptInteraction.cs" />
<Compile Include="Core\ExplorerBehaviour.cs" />
<Compile Include="Core\Reflection\Extensions.cs" />
<Compile Include="Core\Reflection\Il2CppReflection.cs" />
<Compile Include="Core\Utility\MiscUtility.cs" />
<Compile Include="Inspectors_OLD\GameObjects\ChildList.cs" />
<Compile Include="Inspectors_OLD\GameObjects\ComponentList.cs" />
@ -271,17 +273,14 @@
<Compile Include="Core\Input\InputSystem.cs" />
<Compile Include="Core\Input\LegacyInput.cs" />
<Compile Include="Core\Input\NoInput.cs" />
<Compile Include="Core\ReflectionUtility.cs" />
<Compile Include="Core\Reflection\ReflectionUtility.cs" />
<Compile Include="Core\Runtime\Il2Cpp\AssetBundle.cs" />
<Compile Include="Core\Runtime\Il2Cpp\ICallManager.cs" />
<Compile Include="Core\Runtime\Il2Cpp\Il2CppCoroutine.cs" />
<Compile Include="Core\Runtime\Il2Cpp\Il2CppProvider.cs" />
<Compile Include="Core\Runtime\Il2Cpp\Il2CppReflection.cs" />
<Compile Include="Core\Runtime\Il2Cpp\Il2CppTextureUtil.cs" />
<Compile Include="Core\Runtime\Mono\MonoProvider.cs" />
<Compile Include="Core\Runtime\Mono\MonoReflection.cs" />
<Compile Include="Core\Runtime\Mono\MonoTextureUtil.cs" />
<Compile Include="Core\Runtime\ReflectionProvider.cs" />
<Compile Include="Core\Runtime\RuntimeContext.cs" />
<Compile Include="Core\Runtime\RuntimeProvider.cs" />
<Compile Include="Core\Runtime\TextureUtilProvider.cs" />