mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-16 22:27:45 +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
|
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()
|
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())
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
CacheTypes(asm);
|
CacheTypes(asm);
|
||||||
@ -22,10 +46,6 @@ namespace UnityExplorer
|
|||||||
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
|
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)
|
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.LoadedAssembly == null)
|
if (args.LoadedAssembly == null)
|
||||||
@ -60,89 +80,7 @@ namespace UnityExplorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
#endregion
|
||||||
|
|
||||||
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>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
|
/// 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>
|
/// <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>
|
/// <returns>The Type if found, otherwise null.</returns>
|
||||||
public static Type GetTypeByName(string fullName)
|
public static Type GetTypeByName(string fullName)
|
||||||
|
=> Instance.Internal_GetTypeByName(fullName);
|
||||||
|
|
||||||
|
internal virtual Type Internal_GetTypeByName(string fullName)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
AllTypes.TryGetValue(fullName, out Type type);
|
AllTypes.TryGetValue(fullName, out Type type);
|
||||||
return 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
|
// cache for GetBaseTypes
|
||||||
internal static readonly Dictionary<string, Type[]> s_cachedBaseTypes = new Dictionary<string, Type[]>();
|
internal static readonly Dictionary<string, Type[]> s_cachedBaseTypes = new Dictionary<string, Type[]>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all base types of the provided Type, including itself.
|
/// Get all base types of the provided Type, including itself.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Type[] GetAllBaseTypes(this object obj) => GetAllBaseTypes(GetActualType(obj));
|
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all base types of the provided Type, including itself.
|
/// Get all base types of the provided Type, including itself.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Type[] GetAllBaseTypes(this Type type)
|
public static Type[] GetAllBaseTypes(Type type)
|
||||||
{
|
{
|
||||||
if (type == null)
|
if (type == null)
|
||||||
throw new ArgumentNullException("type");
|
throw new ArgumentNullException("type");
|
||||||
@ -193,6 +180,11 @@ namespace UnityExplorer
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Type and Generic Parameter implementation cache
|
||||||
|
|
||||||
// cache for GetImplementationsOf
|
// 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_cachedTypeInheritance = new Dictionary<string, HashSet<Type>>();
|
||||||
internal static readonly Dictionary<string, HashSet<Type>> s_cachedGenericParameterInheritance = 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>
|
/// </summary>
|
||||||
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
|
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
|
||||||
/// <returns>All implementations of the type in the current AppDomain.</returns>
|
/// <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;
|
var key = GetImplementationKey(baseType); //baseType.FullName;
|
||||||
|
|
||||||
@ -308,33 +300,10 @@ namespace UnityExplorer
|
|||||||
return s_cachedGenericParameterInheritance[key];
|
return s_cachedGenericParameterInheritance[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// Safely get all valid Types inside an Assembly.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="asm">The Assembly to find Types in.</param>
|
#region Internal MemberInfo Cache
|
||||||
/// <returns>All possible Types which could be retrieved from the Assembly, or an empty array.</returns>
|
|
||||||
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return asm.GetTypes();
|
|
||||||
}
|
|
||||||
catch (ReflectionTypeLoadException e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return asm.GetExportedTypes();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return e.Types.Where(t => t != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return Enumerable.Empty<Type>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static Dictionary<Type, Dictionary<string, FieldInfo>> s_cachedFieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
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>());
|
s_cachedFieldInfos.Add(type, new Dictionary<string, FieldInfo>());
|
||||||
|
|
||||||
if (!s_cachedFieldInfos[type].ContainsKey(fieldName))
|
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];
|
return s_cachedFieldInfos[type][fieldName];
|
||||||
}
|
}
|
||||||
@ -357,7 +326,7 @@ namespace UnityExplorer
|
|||||||
s_cachedPropInfos.Add(type, new Dictionary<string, PropertyInfo>());
|
s_cachedPropInfos.Add(type, new Dictionary<string, PropertyInfo>());
|
||||||
|
|
||||||
if (!s_cachedPropInfos[type].ContainsKey(propertyName))
|
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];
|
return s_cachedPropInfos[type][propertyName];
|
||||||
}
|
}
|
||||||
@ -388,9 +357,9 @@ namespace UnityExplorer
|
|||||||
if (!s_cachedMethodInfos[type].ContainsKey(sig))
|
if (!s_cachedMethodInfos[type].ContainsKey(sig))
|
||||||
{
|
{
|
||||||
if (argumentTypes != null)
|
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
|
else
|
||||||
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, AllFlags));
|
s_cachedMethodInfos[type].Add(sig, type.GetMethod(methodName, FLAGS));
|
||||||
}
|
}
|
||||||
|
|
||||||
return s_cachedMethodInfos[type][sig];
|
return s_cachedMethodInfos[type][sig];
|
||||||
@ -407,26 +376,7 @@ namespace UnityExplorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// 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;
|
|
||||||
|
|
||||||
e = e.InnerException;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"{e.GetType()}: {e.Message}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
ExplorerCore.Context = RuntimeContext.IL2CPP;
|
ExplorerCore.Context = RuntimeContext.IL2CPP;
|
||||||
Reflection = new Il2CppReflection();
|
//Reflection = new Il2CppReflection();
|
||||||
TextureUtil = new Il2CppTextureUtil();
|
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()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
ExplorerCore.Context = RuntimeContext.Mono;
|
ExplorerCore.Context = RuntimeContext.Mono;
|
||||||
Reflection = new MonoReflection();
|
//Reflection = new MonoReflection();
|
||||||
TextureUtil = new MonoTextureUtil();
|
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()
|
private static MethodInfo GetEncodeToPNGMethod()
|
||||||
{
|
{
|
||||||
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
|
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)
|
if (method != null)
|
||||||
return m_encodeToPNGMethod = method;
|
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 static RuntimeProvider Instance;
|
||||||
|
|
||||||
public ReflectionProvider Reflection;
|
|
||||||
public TextureUtilProvider TextureUtil;
|
public TextureUtilProvider TextureUtil;
|
||||||
|
|
||||||
public RuntimeProvider()
|
public RuntimeProvider()
|
||||||
|
@ -111,7 +111,7 @@ namespace UnityExplorer.Core
|
|||||||
if (sceneUtil == null)
|
if (sceneUtil == null)
|
||||||
throw new Exception("This version of Unity does not ship with the 'SceneUtility' class, or it was not unstripped.");
|
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;
|
int sceneCount = SceneManager.sceneCountInBuildSettings;
|
||||||
for (int i = 0; i < sceneCount; i++)
|
for (int i = 0; i < sceneCount; i++)
|
||||||
{
|
{
|
||||||
|
@ -141,7 +141,7 @@ namespace UnityExplorer.Core.Search
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string[] s_instanceNames = new string[]
|
internal static string[] instanceNames = new string[]
|
||||||
{
|
{
|
||||||
"m_instance",
|
"m_instance",
|
||||||
"m_Instance",
|
"m_Instance",
|
||||||
@ -175,7 +175,7 @@ namespace UnityExplorer.Core.Search
|
|||||||
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
|
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ReflectionProvider.Instance.FindSingleton(s_instanceNames, type, flags, instances);
|
ReflectionUtility.FindSingleton(instanceNames, type, flags, instances);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
#if CPP
|
||||||
|
using UnhollowerRuntimeLib;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace UnityExplorer.Tests
|
namespace UnityExplorer.Tests
|
||||||
{
|
{
|
||||||
@ -126,10 +130,10 @@ namespace UnityExplorer.Tests
|
|||||||
public static Il2CppSystem.String testStringThree = "string boxed as cpp string";
|
public static Il2CppSystem.String testStringThree = "string boxed as cpp string";
|
||||||
public static string nullString = null;
|
public static string nullString = null;
|
||||||
|
|
||||||
public static List<Il2CppSystem.Object> cppBoxedList;
|
public static List<Il2CppSystem.Object> CppBoxedList;
|
||||||
public static UnhollowerBaseLib.Il2CppStructArray<int> CppIntStructArray;
|
public static Il2CppStructArray<int> CppIntStructArray;
|
||||||
public static UnhollowerBaseLib.Il2CppStringArray CppStringArray;
|
public static Il2CppStringArray CppStringArray;
|
||||||
public static UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object> CppReferenceArray;
|
public static Il2CppReferenceArray<Il2CppSystem.Object> CppReferenceArray;
|
||||||
|
|
||||||
public static Il2CppSystem.Object cppBoxedInt;
|
public static Il2CppSystem.Object cppBoxedInt;
|
||||||
public static Il2CppSystem.Int32 cppInt;
|
public static Il2CppSystem.Int32 cppInt;
|
||||||
@ -144,6 +148,29 @@ namespace UnityExplorer.Tests
|
|||||||
BigList.Add(i.ToString());
|
BigList.Add(i.ToString());
|
||||||
|
|
||||||
#if CPP
|
#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 = new UnhollowerBaseLib.Il2CppStructArray<int>(5);
|
||||||
CppIntStructArray[0] = 0;
|
CppIntStructArray[0] = 0;
|
||||||
CppIntStructArray[1] = 1;
|
CppIntStructArray[1] = 1;
|
||||||
@ -161,9 +188,6 @@ namespace UnityExplorer.Tests
|
|||||||
CppReferenceArray[2] = (Il2CppSystem.String)"whats up";
|
CppReferenceArray[2] = (Il2CppSystem.String)"whats up";
|
||||||
|
|
||||||
cppBoxedInt = new Il2CppSystem.Int32() { m_value = 5 }.BoxIl2CppObject();
|
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 };
|
cppInt = new Il2CppSystem.Int32 { m_value = 420 };
|
||||||
|
|
||||||
TestWritableBoxedList = new List<Il2CppSystem.Object>();
|
TestWritableBoxedList = new List<Il2CppSystem.Object>();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -96,10 +97,7 @@ namespace UnityExplorer.UI.CacheObject
|
|||||||
|
|
||||||
public void SetUserValue(object value)
|
public void SetUserValue(object value)
|
||||||
{
|
{
|
||||||
if (State == ValueState.String)
|
value = value.TryCast(FallbackType);
|
||||||
ReflectionProvider.Instance.BoxStringToType(ref value, FallbackType);
|
|
||||||
else
|
|
||||||
value = value.TryCast(FallbackType);
|
|
||||||
|
|
||||||
TrySetUserValue(value);
|
TrySetUserValue(value);
|
||||||
}
|
}
|
||||||
@ -147,16 +145,16 @@ namespace UnityExplorer.UI.CacheObject
|
|||||||
State = ValueState.Boolean;
|
State = ValueState.Boolean;
|
||||||
else if (type.IsPrimitive || type == typeof(decimal))
|
else if (type.IsPrimitive || type == typeof(decimal))
|
||||||
State = ValueState.Number;
|
State = ValueState.Number;
|
||||||
else if (ReflectionProvider.Instance.IsString(Value))
|
else if (type == typeof(string))
|
||||||
State = ValueState.String;
|
State = ValueState.String;
|
||||||
else if (type.IsEnum)
|
else if (type.IsEnum)
|
||||||
State = ValueState.Enum;
|
State = ValueState.Enum;
|
||||||
|
|
||||||
// todo Color and ValueStruct
|
// todo Color and ValueStruct
|
||||||
|
|
||||||
else if (type.IsDictionary())
|
else if (typeof(IDictionary).IsAssignableFrom(type))
|
||||||
State = ValueState.Dictionary;
|
State = ValueState.Dictionary;
|
||||||
else if (type.IsEnumerable())
|
else if (typeof(IEnumerable).IsAssignableFrom(type))
|
||||||
State = ValueState.Collection;
|
State = ValueState.Collection;
|
||||||
else
|
else
|
||||||
State = ValueState.Unsupported;
|
State = ValueState.Unsupported;
|
||||||
@ -174,12 +172,12 @@ namespace UnityExplorer.UI.CacheObject
|
|||||||
case ValueState.NotEvaluated:
|
case ValueState.NotEvaluated:
|
||||||
label = $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>"; break;
|
label = $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>"; break;
|
||||||
case ValueState.Exception:
|
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.Boolean:
|
||||||
case ValueState.Number:
|
case ValueState.Number:
|
||||||
label = null; break;
|
label = null; break;
|
||||||
case ValueState.String:
|
case ValueState.String:
|
||||||
string s = ReflectionProvider.Instance.UnboxString(Value);
|
string s = Value as string;
|
||||||
if (s.Length > 200)
|
if (s.Length > 200)
|
||||||
s = $"{s.Substring(0, 200)}...";
|
s = $"{s.Substring(0, 200)}...";
|
||||||
label = $"\"{s}\""; break;
|
label = $"\"{s}\""; break;
|
||||||
|
@ -60,7 +60,7 @@ namespace UnityExplorer.UI.Utility
|
|||||||
return CLASS_INSTANCE;
|
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)
|
private static bool GetNamespace(Type type, out string ns)
|
||||||
{
|
{
|
||||||
@ -73,7 +73,7 @@ namespace UnityExplorer.UI.Utility
|
|||||||
if (type == null)
|
if (type == null)
|
||||||
throw new ArgumentNullException("type");
|
throw new ArgumentNullException("type");
|
||||||
|
|
||||||
syntaxBuilder.Clear();
|
var syntaxBuilder = new StringBuilder();
|
||||||
|
|
||||||
// Namespace
|
// Namespace
|
||||||
|
|
||||||
@ -105,12 +105,6 @@ namespace UnityExplorer.UI.Utility
|
|||||||
{
|
{
|
||||||
syntaxBuilder.Append('.');
|
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;
|
int start = syntaxBuilder.Length - 1;
|
||||||
syntaxBuilder.Append(OPEN_COLOR)
|
syntaxBuilder.Append(OPEN_COLOR)
|
||||||
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
|
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
|
||||||
@ -128,7 +122,6 @@ namespace UnityExplorer.UI.Utility
|
|||||||
{
|
{
|
||||||
var args = method.GetGenericArguments();
|
var args = method.GetGenericArguments();
|
||||||
if (args.Length > 0)
|
if (args.Length > 0)
|
||||||
//syntaxBuilder.Append($"<{ParseGenericArgs(args, true)}>");
|
|
||||||
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
|
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,31 +129,6 @@ namespace UnityExplorer.UI.Utility
|
|||||||
return syntaxBuilder.ToString();
|
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 readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
|
||||||
|
|
||||||
private static bool EndsWith(this StringBuilder sb, 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))
|
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
|
||||||
{
|
{
|
||||||
//typeName = $"<color={CONST}>{typeName}</color>";
|
|
||||||
sb.Insert(0, $"<color={CONST}>");
|
sb.Insert(0, $"<color={CONST}>");
|
||||||
sb.Append(CLOSE_COLOR);
|
sb.Append(CLOSE_COLOR);
|
||||||
}
|
}
|
||||||
@ -216,26 +183,22 @@ namespace UnityExplorer.UI.Utility
|
|||||||
|
|
||||||
// make sure the typename actually has expected "`N" format.
|
// make sure the typename actually has expected "`N" format.
|
||||||
if (sb[sb.Length - suffixLen] == '`')
|
if (sb[sb.Length - suffixLen] == '`')
|
||||||
//typeName = typeName.Substring(0, typeName.Length - suffixLen);
|
|
||||||
sb.Remove(sb.Length - suffixLen, suffixLen);
|
sb.Remove(sb.Length - suffixLen, suffixLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// highlight the base name itself
|
// highlight the base name itself
|
||||||
// do this after removing the `N suffix, so only the name itself is in the color tags.
|
// 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.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
|
||||||
sb.Append(CLOSE_COLOR);
|
sb.Append(CLOSE_COLOR);
|
||||||
|
|
||||||
// parse the generic args, if any
|
// parse the generic args, if any
|
||||||
if (args.Length > 0)
|
if (args.Length > 0)
|
||||||
{
|
{
|
||||||
//typeName += $"<{ParseGenericArgs(args)}>";
|
|
||||||
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
|
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray)
|
if (isArray)
|
||||||
//typeName += "[]";
|
|
||||||
sb.Append('[').Append(']');
|
sb.Append('[').Append(']');
|
||||||
|
|
||||||
var ret = sb.ToString();
|
var ret = sb.ToString();
|
||||||
@ -249,23 +212,19 @@ namespace UnityExplorer.UI.Utility
|
|||||||
if (args.Length < 1)
|
if (args.Length < 1)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
//string ret = "";
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
for (int i = 0; i < args.Length; i++)
|
for (int i = 0; i < args.Length; i++)
|
||||||
{
|
{
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
//ret += ", ";
|
|
||||||
sb.Append(',').Append(' ');
|
sb.Append(',').Append(' ');
|
||||||
|
|
||||||
if (isGenericParams)
|
if (isGenericParams)
|
||||||
{
|
{
|
||||||
//ret += $"<color={CONST}>{args[i].Name}</color>";
|
|
||||||
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
|
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//ret += ParseType(args[i]);
|
|
||||||
sb.Append(HighlightType(args[i]));
|
sb.Append(HighlightType(args[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ namespace UnityExplorer.UI.Utility
|
|||||||
{
|
{
|
||||||
var toString = ToString(value);
|
var toString = ToString(value);
|
||||||
|
|
||||||
if (type.IsEnumerable())
|
if (typeof(IEnumerable).IsAssignableFrom(type))
|
||||||
{
|
{
|
||||||
if (value is IList iList)
|
if (value is IList iList)
|
||||||
_stringBuilder.Append($"[{iList.Count}] ");
|
_stringBuilder.Append($"[{iList.Count}] ");
|
||||||
@ -80,7 +80,7 @@ namespace UnityExplorer.UI.Utility
|
|||||||
else
|
else
|
||||||
_stringBuilder.Append("[?] ");
|
_stringBuilder.Append("[?] ");
|
||||||
}
|
}
|
||||||
else if (type.IsDictionary())
|
else if (typeof(IDictionary).IsAssignableFrom(type))
|
||||||
{
|
{
|
||||||
if (value is IDictionary iDict)
|
if (value is IDictionary iDict)
|
||||||
_stringBuilder.Append($"[{iDict.Count}] ");
|
_stringBuilder.Append($"[{iDict.Count}] ");
|
||||||
@ -167,14 +167,14 @@ namespace UnityExplorer.UI.Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
string _ = null;
|
string _ = null;
|
||||||
toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref _);
|
toString = ReflectionUtility.ProcessTypeInString(type, toString, ref _);
|
||||||
|
|
||||||
#if CPP
|
#if CPP
|
||||||
if (value is Il2CppSystem.Type cppType)
|
if (value is Il2CppSystem.Type cppType)
|
||||||
{
|
{
|
||||||
var monoType = Core.Runtime.Il2Cpp.Il2CppReflection.GetMonoType(cppType);
|
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
|
||||||
if (monoType != null)
|
if (monoType != null)
|
||||||
toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(monoType, toString, ref _);
|
toString = ReflectionUtility.ProcessTypeInString(monoType, toString, ref _);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -222,6 +222,8 @@
|
|||||||
<Compile Include="Core\CSharp\ScriptEvaluator.cs" />
|
<Compile Include="Core\CSharp\ScriptEvaluator.cs" />
|
||||||
<Compile Include="Core\CSharp\ScriptInteraction.cs" />
|
<Compile Include="Core\CSharp\ScriptInteraction.cs" />
|
||||||
<Compile Include="Core\ExplorerBehaviour.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="Core\Utility\MiscUtility.cs" />
|
||||||
<Compile Include="Inspectors_OLD\GameObjects\ChildList.cs" />
|
<Compile Include="Inspectors_OLD\GameObjects\ChildList.cs" />
|
||||||
<Compile Include="Inspectors_OLD\GameObjects\ComponentList.cs" />
|
<Compile Include="Inspectors_OLD\GameObjects\ComponentList.cs" />
|
||||||
@ -271,17 +273,14 @@
|
|||||||
<Compile Include="Core\Input\InputSystem.cs" />
|
<Compile Include="Core\Input\InputSystem.cs" />
|
||||||
<Compile Include="Core\Input\LegacyInput.cs" />
|
<Compile Include="Core\Input\LegacyInput.cs" />
|
||||||
<Compile Include="Core\Input\NoInput.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\AssetBundle.cs" />
|
||||||
<Compile Include="Core\Runtime\Il2Cpp\ICallManager.cs" />
|
<Compile Include="Core\Runtime\Il2Cpp\ICallManager.cs" />
|
||||||
<Compile Include="Core\Runtime\Il2Cpp\Il2CppCoroutine.cs" />
|
<Compile Include="Core\Runtime\Il2Cpp\Il2CppCoroutine.cs" />
|
||||||
<Compile Include="Core\Runtime\Il2Cpp\Il2CppProvider.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\Il2Cpp\Il2CppTextureUtil.cs" />
|
||||||
<Compile Include="Core\Runtime\Mono\MonoProvider.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\Mono\MonoTextureUtil.cs" />
|
||||||
<Compile Include="Core\Runtime\ReflectionProvider.cs" />
|
|
||||||
<Compile Include="Core\Runtime\RuntimeContext.cs" />
|
<Compile Include="Core\Runtime\RuntimeContext.cs" />
|
||||||
<Compile Include="Core\Runtime\RuntimeProvider.cs" />
|
<Compile Include="Core\Runtime\RuntimeProvider.cs" />
|
||||||
<Compile Include="Core\Runtime\TextureUtilProvider.cs" />
|
<Compile Include="Core\Runtime\TextureUtilProvider.cs" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user