From 8534c08f49e1effd8d8308f25a5f2d3ca0e93e28 Mon Sep 17 00:00:00 2001 From: Sinai Date: Fri, 7 May 2021 01:22:55 +1000 Subject: [PATCH] Reflection cleanup, fix il2cpp struct and enum boxing And temp removing il2cpp IDictionary / IEnumerable helpers, will see what is necessary after knah's rewrite. --- src/Core/Reflection/Extensions.cs | 108 +++ src/Core/Reflection/Il2CppReflection.cs | 456 +++++++++++++ .../{ => Reflection}/ReflectionUtility.cs | 240 +++---- src/Core/Runtime/Il2Cpp/Il2CppProvider.cs | 2 +- src/Core/Runtime/Il2Cpp/Il2CppReflection.cs | 625 ------------------ src/Core/Runtime/Mono/MonoProvider.cs | 2 +- src/Core/Runtime/Mono/MonoReflection.cs | 47 -- src/Core/Runtime/Mono/MonoTextureUtil.cs | 4 +- src/Core/Runtime/ReflectionProvider.cs | 66 -- src/Core/Runtime/RuntimeProvider.cs | 1 - src/Core/SceneHandler.cs | 2 +- src/Core/Search/SearchProvider.cs | 4 +- src/Core/Tests/TestClass.cs | 38 +- src/ExplorerCore.cs | 1 + src/UI/CacheObject/CacheObjectBase.cs | 16 +- src/UI/Utility/SignatureHighlighter.cs | 47 +- src/UI/Utility/ToStringUtility.cs | 10 +- src/UnityExplorer.csproj | 7 +- 18 files changed, 716 insertions(+), 960 deletions(-) create mode 100644 src/Core/Reflection/Extensions.cs create mode 100644 src/Core/Reflection/Il2CppReflection.cs rename src/Core/{ => Reflection}/ReflectionUtility.cs (66%) delete mode 100644 src/Core/Runtime/Il2Cpp/Il2CppReflection.cs delete mode 100644 src/Core/Runtime/Mono/MonoReflection.cs delete mode 100644 src/Core/Runtime/ReflectionProvider.cs diff --git a/src/Core/Reflection/Extensions.cs b/src/Core/Reflection/Extensions.cs new file mode 100644 index 0000000..b451318 --- /dev/null +++ b/src/Core/Reflection/Extensions.cs @@ -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(this object obj) + { + try + { + return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T)); + } + catch + { + return default; + } + } + + public static HashSet GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric) + => ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric); + + // ------- Misc extensions -------- + + /// + /// Safely try to get all Types inside an Assembly. + /// + 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(); + } + } + + + /// + /// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality. + /// + 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 to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception. + /// + 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}"; + } + } +} diff --git a/src/Core/Reflection/Il2CppReflection.cs b/src/Core/Reflection/Il2CppReflection.cs new file mode 100644 index 0000000..9e83455 --- /dev/null +++ b/src/Core/Reflection/Il2CppReflection.cs @@ -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 DeobfuscatedTypes = new Dictionary(); + //internal static Dictionary s_deobfuscatedTypeNames = new Dictionary(); + + 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 cppClassPointers = new Dictionary(); + + 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 unboxMethods = new Dictionary(); + + 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 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 \ No newline at end of file diff --git a/src/Core/ReflectionUtility.cs b/src/Core/Reflection/ReflectionUtility.cs similarity index 66% rename from src/Core/ReflectionUtility.cs rename to src/Core/Reflection/ReflectionUtility.cs index 03af37e..393c18f 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/Reflection/ReflectionUtility.cs @@ -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 + + /// Key: Type.FullName + public static readonly SortedDictionary AllTypes = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly List allTypeNames = new List(); + + private static void SetupTypeCache() { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) CacheTypes(asm); @@ -22,10 +46,6 @@ namespace UnityExplorer AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded; } - /// Key: Type.FullName - public static readonly SortedDictionary AllTypes = new SortedDictionary(StringComparer.OrdinalIgnoreCase); - private static readonly List allTypeNames = new List(); - 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(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) => 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); - - /// Try to cast the object to the type. - 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) - => !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(); + #endregion /// /// Find a in the current AppDomain whose matches the provided . @@ -150,25 +88,74 @@ namespace UnityExplorer /// The you want to search for - case sensitive and full matches only. /// The Type if found, otherwise null. 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 instances) + => Instance.Internal_FindSingleton(possibleNames, type, flags, instances); + + internal virtual void Internal_FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List 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 s_cachedBaseTypes = new Dictionary(); /// /// Get all base types of the provided Type, including itself. /// - public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj)); + public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType()); /// /// Get all base types of the provided Type, including itself. /// - 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> s_cachedTypeInheritance = new Dictionary>(); internal static readonly Dictionary> s_cachedGenericParameterInheritance = new Dictionary>(); @@ -218,7 +210,7 @@ namespace UnityExplorer /// /// 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) + public static HashSet GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric) { var key = GetImplementationKey(baseType); //baseType.FullName; @@ -308,33 +300,10 @@ namespace UnityExplorer return s_cachedGenericParameterInheritance[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(); - } - } +#endregion + + + #region Internal MemberInfo Cache internal static Dictionary> s_cachedFieldInfos = new Dictionary>(); @@ -344,7 +313,7 @@ namespace UnityExplorer s_cachedFieldInfos.Add(type, new Dictionary()); 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()); 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 } } - /// - /// 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; +#endregion - e = e.InnerException; - } - } - - return $"{e.GetType()}: {e.Message}"; - } } } diff --git a/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs b/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs index 06e9bea..e2d6df3 100644 --- a/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs +++ b/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs @@ -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(); } diff --git a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs deleted file mode 100644 index caf7342..0000000 --- a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs +++ /dev/null @@ -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(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 s_cppClassPointers = new Dictionary(); - - /// - /// Attempt to cast the object to its underlying type. - /// - /// The object you want to cast. - /// The object, as the underlying type if successful or the input value if not. - public static object Il2CppCast(object obj) => Il2CppCast(obj, Instance.GetActualType(obj)); - - /// - /// Attempt to cast the object to the provided type. - /// - /// The object you want to cast. - /// The Type you want to cast to. - /// The object, as the type (or a normal C# object) if successful or the input value if not. - 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 s_unboxMethods = new Dictionary(); - - /// - /// Attempt to unbox the object to the underlying struct type. - /// - /// The object which is a struct underneath. - /// The struct if successful, otherwise null. - public static object Unbox(object obj) => Unbox(obj, Instance.GetActualType(obj)); - - /// - /// Attempt to unbox the object to the struct type. - /// - /// The object which is a struct underneath. - /// The type of the struct you want to unbox to. - /// The struct if successful, otherwise null. - 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 monoToIl2CppType = new Dictionary(); - internal static Dictionary structBoxMethods = new Dictionary(); - internal static Dictionary structValueFields = new Dictionary(); - - /// - /// Try to box a value to Il2CppSystem.Object using the Il2CppSystem representation of the value type. - /// - /// The value, eg 5 (System.Int32) - /// The boxed Il2CppSystem.Object for the value, eg for '5' it would be a boxed Il2CppSytem.Int32. - 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 DeobfuscatedTypes = new Dictionary(); - internal static Dictionary s_deobfuscatedTypeNames = new Dictionary(); - - 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 - - /// - /// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type. - /// - /// The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for. - /// True if successful, false if not. - public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _); - - /// - /// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type. - /// - /// The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for. - /// The IntPtr for the Il2Cpp class, or IntPtr.Zero if not found. - /// True if successful, false if not. - 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 s_getEnumeratorMethods = new Dictionary(); - - internal static readonly Dictionary s_enumeratorInfos = new Dictionary(); - - 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(); - 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(); - 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(); - var valueList = new List(); - - 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 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 keys, List 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 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 \ No newline at end of file diff --git a/src/Core/Runtime/Mono/MonoProvider.cs b/src/Core/Runtime/Mono/MonoProvider.cs index a861dd0..9c415a0 100644 --- a/src/Core/Runtime/Mono/MonoProvider.cs +++ b/src/Core/Runtime/Mono/MonoProvider.cs @@ -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(); } diff --git a/src/Core/Runtime/Mono/MonoReflection.cs b/src/Core/Runtime/Mono/MonoReflection.cs deleted file mode 100644 index 5ac3db3..0000000 --- a/src/Core/Runtime/Mono/MonoReflection.cs +++ /dev/null @@ -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(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 \ No newline at end of file diff --git a/src/Core/Runtime/Mono/MonoTextureUtil.cs b/src/Core/Runtime/Mono/MonoTextureUtil.cs index 0ae2b83..3a0a5a7 100644 --- a/src/Core/Runtime/Mono/MonoTextureUtil.cs +++ b/src/Core/Runtime/Mono/MonoTextureUtil.cs @@ -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; diff --git a/src/Core/Runtime/ReflectionProvider.cs b/src/Core/Runtime/ReflectionProvider.cs deleted file mode 100644 index 2d63b5c..0000000 --- a/src/Core/Runtime/ReflectionProvider.cs +++ /dev/null @@ -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(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 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; - } - } - } - } - } -} diff --git a/src/Core/Runtime/RuntimeProvider.cs b/src/Core/Runtime/RuntimeProvider.cs index 655dd14..615e97b 100644 --- a/src/Core/Runtime/RuntimeProvider.cs +++ b/src/Core/Runtime/RuntimeProvider.cs @@ -16,7 +16,6 @@ namespace UnityExplorer { public static RuntimeProvider Instance; - public ReflectionProvider Reflection; public TextureUtilProvider TextureUtil; public RuntimeProvider() diff --git a/src/Core/SceneHandler.cs b/src/Core/SceneHandler.cs index 7cbd0cc..b0d633e 100644 --- a/src/Core/SceneHandler.cs +++ b/src/Core/SceneHandler.cs @@ -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++) { diff --git a/src/Core/Search/SearchProvider.cs b/src/Core/Search/SearchProvider.cs index a60f393..9c40d83 100644 --- a/src/Core/Search/SearchProvider.cs +++ b/src/Core/Search/SearchProvider.cs @@ -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 { } } diff --git a/src/Core/Tests/TestClass.cs b/src/Core/Tests/TestClass.cs index c27c206..816a840 100644 --- a/src/Core/Tests/TestClass.cs +++ b/src/Core/Tests/TestClass.cs @@ -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 cppBoxedList; - public static UnhollowerBaseLib.Il2CppStructArray CppIntStructArray; - public static UnhollowerBaseLib.Il2CppStringArray CppStringArray; - public static UnhollowerBaseLib.Il2CppReferenceArray CppReferenceArray; + public static List CppBoxedList; + public static Il2CppStructArray CppIntStructArray; + public static Il2CppStringArray CppStringArray; + public static Il2CppReferenceArray 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(); + CppBoxedList.Add((Il2CppSystem.String)"boxedString"); + CppBoxedList.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject()); + + try + { + var cppType = Il2CppType.Of(); + 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(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(); - cppBoxedList.Add((Il2CppSystem.String)"boxedString"); - cppBoxedList.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject()); cppInt = new Il2CppSystem.Int32 { m_value = 420 }; TestWritableBoxedList = new List(); diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 064b6eb..1943a21 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using UnityEngine; diff --git a/src/UI/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs index 2383ad8..76010f9 100644 --- a/src/UI/CacheObject/CacheObjectBase.cs +++ b/src/UI/CacheObject/CacheObjectBase.cs @@ -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 = $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})"; break; case ValueState.Exception: - label = $"{ReflectionUtility.ReflectionExToString(LastException)}"; break; + label = $"{LastException.ReflectionExToString()}"; 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; diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index f4e8c9f..497663f 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -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($"{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($"{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 typeToRichType = new Dictionary(); 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 = $"{typeName}"; sb.Insert(0, $""); 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 = $"{typeName}"; 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 += $"{args[i].Name}"; sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR); continue; } - //ret += ParseType(args[i]); sb.Append(HighlightType(args[i])); } diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 0df196a..495469a 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -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 diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 3898073..caca36f 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -222,6 +222,8 @@ + + @@ -271,17 +273,14 @@ - + - - -