mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-15 22:07:48 +08:00
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:
parent
1ee10c2507
commit
8534c08f49
108
src/Core/Reflection/Extensions.cs
Normal file
108
src/Core/Reflection/Extensions.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
456
src/Core/Reflection/Il2CppReflection.cs
Normal file
456
src/Core/Reflection/Il2CppReflection.cs
Normal 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
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ namespace UnityExplorer
|
||||
{
|
||||
public static RuntimeProvider Instance;
|
||||
|
||||
public ReflectionProvider Reflection;
|
||||
public TextureUtilProvider TextureUtil;
|
||||
|
||||
public RuntimeProvider()
|
||||
|
@ -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++)
|
||||
{
|
||||
|
@ -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 { }
|
||||
}
|
||||
|
@ -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>();
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
@ -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;
|
||||
|
@ -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]));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user