diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index dff0850..4938056 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -90,6 +90,7 @@ namespace UnityExplorer { UIManager.Init(); Log("Initialized UnityExplorer UI."); + // InspectorManager.Instance.Inspect(Tests.TestClass.Instance); } catch (Exception e) { diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index 810a873..5400c5e 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -52,7 +52,7 @@ namespace UnityExplorer.Helpers return list.ToArray(); } - public static Type GetActualType(object obj) + public static Type GetActualType(this object obj) { if (obj == null) return null; @@ -107,6 +107,19 @@ namespace UnityExplorer.Helpers private static readonly Dictionary CppClassPointers = new Dictionary(); + /// + /// Attempt to cast the object to its underlying type. + /// + /// The object you want to cast. + /// The object, as the underlying type if successful or the input value if not. + public static object Il2CppCast(this object obj) => Il2CppCast(obj, GetActualType(obj)); + + /// + /// Attempt to cast the object to the provided type. + /// + /// The object you want to cast. + /// The Type you want to cast to. + /// The object, as the type (or a normal C# object) if successful or the input value if not. public static object Il2CppCast(this object obj, Type castTo) { if (!(obj is Il2CppSystem.Object ilObj)) @@ -126,6 +139,39 @@ namespace UnityExplorer.Helpers return Activator.CreateInstance(castTo, ilObj.Pointer); } + internal static readonly Dictionary s_unboxMethods = new Dictionary(); + + /// + /// Attempt to unbox the object to the underlying struct type. + /// + /// The object which is a struct underneath. + /// The struct if successful, otherwise null. + public static object Unbox(this object obj) => Unbox(obj, GetActualType(obj)); + + /// + /// Attempt to unbox the object to the struct type. + /// + /// The object which is a struct underneath. + /// The type of the struct you want to unbox to. + /// The struct if successful, otherwise null. + public static object Unbox(this object obj, Type type) + { + if (!type.IsValueType) + return null; + + if (!(obj is Il2CppSystem.Object)) + return obj; + + if (!s_unboxMethods.ContainsKey(type)) + { + s_unboxMethods.Add(type, typeof(Il2CppObjectBase) + .GetMethod("Unbox") + .MakeGenericMethod(type)); + } + + return s_unboxMethods[type].Invoke(obj, new object[0]); + } + public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _); public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr) @@ -215,50 +261,56 @@ namespace UnityExplorer.Helpers public static bool LoadModule(string module) => true; #endif +#if CPP + internal static IntPtr s_cppEnumerableClassPtr; +#endif + public static bool IsEnumerable(Type t) { if (typeof(IEnumerable).IsAssignableFrom(t)) - { return true; +#if CPP + try + { + if (s_cppEnumerableClassPtr == IntPtr.Zero) + Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr); + + if (s_cppEnumerableClassPtr != IntPtr.Zero + && Il2CppTypeNotNull(t, out IntPtr classPtr) + && il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, classPtr)) + { + return true; + } } + catch { } +#endif + return false; + } #if CPP - if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g) - { - return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g) - || typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g) - || typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g); - } - else - { - return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t); - } -#else - return false; + internal static IntPtr s_cppDictionaryClassPtr; #endif - } public static bool IsDictionary(Type t) { if (typeof(IDictionary).IsAssignableFrom(t)) - { return true; - } - #if CPP - if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g) + try { - return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g) - || typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g); + if (s_cppDictionaryClassPtr == IntPtr.Zero) + if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr)) + return false; + + if (Il2CppTypeNotNull(t, out IntPtr classPtr)) + { + if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr)) + return true; + } } - else - { - return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t) - || typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t); - } -#else - return false; + catch { } #endif + return false; } public static string ExceptionToString(Exception e, bool innerMost = false) diff --git a/src/Inspectors/Reflection/InteractiveValue/InteractiveDictionary.cs b/src/Inspectors/Reflection/InteractiveValue/InteractiveDictionary.cs index 6e6aa93..c49e820 100644 --- a/src/Inspectors/Reflection/InteractiveValue/InteractiveDictionary.cs +++ b/src/Inspectors/Reflection/InteractiveValue/InteractiveDictionary.cs @@ -10,6 +10,9 @@ using UnityExplorer.Helpers; using UnityExplorer.UI; using UnityExplorer.UI.Shared; using System.Reflection; +#if CPP +using CppDictionary = Il2CppSystem.Collections.IDictionary; +#endif namespace UnityExplorer.Inspectors.Reflection { @@ -44,7 +47,11 @@ namespace UnityExplorer.Inspectors.Reflection } internal IDictionary RefIDictionary; - +#if CPP + internal CppDictionary RefCppDictionary; +#else + internal IDictionary RefCppDictionary = null; +#endif internal Type m_typeOfKeys; internal Type m_typeofValues; @@ -65,6 +72,11 @@ namespace UnityExplorer.Inspectors.Reflection { RefIDictionary = Value as IDictionary; +#if CPP + try { RefCppDictionary = (Value as Il2CppSystem.Object).TryCast(); } + catch { } +#endif + if (m_subContentParent.activeSelf) { GetCacheEntries(); @@ -129,13 +141,6 @@ namespace UnityExplorer.Inspectors.Reflection { var value = RefIDictionary[key]; - //if (index >= m_rowHolders.Count) - //{ - // AddRowHolder(); - //} - - //var holder = m_rowHolders[index]; - var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent); cacheKey.CreateIValue(key, this.m_typeOfKeys); cacheKey.Disable(); @@ -206,9 +211,10 @@ namespace UnityExplorer.Inspectors.Reflection RefreshDisplay(); } - #region CPP fixes +#region CPP fixes #if CPP // temp fix for Il2Cpp IDictionary until interfaces are fixed + private IDictionary EnumerateWithReflection() { var valueType = Value?.GetType() ?? FallbackType; @@ -222,8 +228,8 @@ namespace UnityExplorer.Inspectors.Reflection var valueList = new List(); // store entries with reflection - EnumerateWithReflection(keys, keyList); - EnumerateWithReflection(values, valueList); + EnumerateCollection(keys, keyList); + EnumerateCollection(values, valueList); // make actual mono dictionary var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>) @@ -236,7 +242,7 @@ namespace UnityExplorer.Inspectors.Reflection return dict; } - private void EnumerateWithReflection(object collection, List list) + private void EnumerateCollection(object collection, List list) { // invoke GetEnumerator var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null); @@ -253,9 +259,9 @@ namespace UnityExplorer.Inspectors.Reflection } #endif - #endregion +#endregion - #region UI CONSTRUCTION +#region UI CONSTRUCTION internal GameObject m_listContent; internal LayoutElement m_listLayout; @@ -309,6 +315,6 @@ namespace UnityExplorer.Inspectors.Reflection // m_rowHolders.Add(obj); //} - #endregion +#endregion } } diff --git a/src/Inspectors/Reflection/InteractiveValue/InteractiveEnumerable.cs b/src/Inspectors/Reflection/InteractiveValue/InteractiveEnumerable.cs index 83af2c4..441dd30 100644 --- a/src/Inspectors/Reflection/InteractiveValue/InteractiveEnumerable.cs +++ b/src/Inspectors/Reflection/InteractiveValue/InteractiveEnumerable.cs @@ -37,6 +37,11 @@ namespace UnityExplorer.Inspectors.Reflection internal IEnumerable RefIEnumerable; internal IList RefIList; +#if CPP + internal Il2CppSystem.Collections.ICollection CppICollection; +#else + internal ICollection CppICollection = null; +#endif internal readonly Type m_baseEntryType; @@ -49,6 +54,14 @@ namespace UnityExplorer.Inspectors.Reflection RefIEnumerable = Value as IEnumerable; RefIList = Value as IList; +#if CPP + if (Value != null && RefIList == null) + { + try { CppICollection = (Value as Il2CppSystem.Object).TryCast(); } + catch { } + } +#endif + if (m_subContentParent.activeSelf) { GetCacheEntries(); @@ -77,8 +90,8 @@ namespace UnityExplorer.Inspectors.Reflection if (Value != null) { string count = "?"; - if (m_recacheWanted && RefIList != null) - count = RefIList.Count.ToString(); + if (m_recacheWanted && (RefIList != null || CppICollection != null)) + count = RefIList?.Count.ToString() ?? CppICollection.Count.ToString(); else if (!m_recacheWanted) count = m_entries.Count.ToString(); @@ -169,89 +182,62 @@ namespace UnityExplorer.Inspectors.Reflection RefreshDisplay(); } - #region CPP Helpers +#region CPP Helpers #if CPP // some temp fixes for Il2Cpp IEnumerables until interfaces are fixed + internal static readonly Dictionary s_getEnumeratorMethods = new Dictionary(); + + internal static readonly Dictionary s_enumeratorInfos = new Dictionary(); + + internal class EnumeratorInfo + { + internal MethodInfo moveNext; + internal PropertyInfo current; + } + private IEnumerable EnumerateWithReflection() { - if (Value.IsNullOrDestroyed()) + if (Value == null) return null; - var genericDef = Value.GetType().GetGenericTypeDefinition(); - - if (genericDef == typeof(Il2CppSystem.Collections.Generic.List<>)) - return CppListToMono(genericDef); - else if (genericDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>)) - return CppHashSetToMono(); - else - return CppIListToMono(); - } - - // List.ToArray() - private IEnumerable CppListToMono(Type genericTypeDef) - { - if (genericTypeDef == null) return null; - - return genericTypeDef - .MakeGenericType(new Type[] { this.m_baseEntryType }) - .GetMethod("ToArray") - .Invoke(Value, new object[0]) as IEnumerable; - } - - // HashSet.GetEnumerator - private IEnumerable CppHashSetToMono() - { - var set = new HashSet(); - - // invoke GetEnumerator - var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, 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)) - set.Add(current.GetValue(enumerator)); - - return set; - } - - // IList.Item - private IList CppIListToMono() - { - try + // new test + var CppEnumerable = (Value as Il2CppSystem.Object)?.TryCast(); + if (CppEnumerable != null) { - var genericType = typeof(List<>).MakeGenericType(new Type[] { this.m_baseEntryType }); - var list = (IList)Activator.CreateInstance(genericType); + 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(); - for (int i = 0; ; i++) + if (!s_enumeratorInfos.ContainsKey(enumeratorType)) { - try + s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo { - var itm = Value?.GetType() - .GetProperty("Item") - .GetValue(Value, new object[] { i }); - list.Add(itm); - } - catch { break; } + current = enumeratorType.GetProperty("Current"), + moveNext = enumeratorType.GetMethod("MoveNext"), + }); } + var info = s_enumeratorInfos[enumeratorType]; + + // iterate + var list = new List(); + while ((bool)info.moveNext.Invoke(enumerator, null)) + list.Add(info.current.GetValue(enumerator)); return list; } - catch (Exception e) - { - ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message); - return null; - } + + return null; } #endif - #endregion +#endregion - #region UI CONSTRUCTION +#region UI CONSTRUCTION internal GameObject m_listContent; internal LayoutElement m_listLayout; @@ -296,6 +282,6 @@ namespace UnityExplorer.Inspectors.Reflection contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; } - #endregion +#endregion } } diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index d1d38e1..3055617 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -128,6 +128,8 @@ namespace UnityExplorer.Tests public static Il2CppSystem.Collections.Generic.HashSet CppHashSetTest; public static Il2CppSystem.Collections.Generic.List CppStringTest; public static Il2CppSystem.Collections.IList CppIList; + public static Il2CppSystem.Collections.Generic.Dictionary CppDictTest; + public static Il2CppSystem.Collections.Generic.Dictionary CppDictTest2; #endif public TestClass() @@ -143,21 +145,7 @@ namespace UnityExplorer.Tests } #if CPP - //TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600); - TestTexture = new Texture(); - TestTexture.name = "TestTexture"; - - var r = new Rect(0, 0, TestTexture.width, TestTexture.height); - var v2 = Vector2.zero; - var v4 = Vector4.zero; - TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false); - - GameObject.DontDestroyOnLoad(TestTexture); - GameObject.DontDestroyOnLoad(TestSprite); - - //// test loading a tex from file - //var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png"); - //ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}"); + TextureSpriteTest(); CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet(); CppHashSetTest.Add("1"); @@ -167,9 +155,38 @@ namespace UnityExplorer.Tests CppStringTest = new Il2CppSystem.Collections.Generic.List(); CppStringTest.Add("1"); CppStringTest.Add("2"); + + CppDictTest = new Il2CppSystem.Collections.Generic.Dictionary(); + CppDictTest.Add("key1", "value1"); + CppDictTest.Add("key2", "value2"); + CppDictTest.Add("key3", "value3"); + + CppDictTest2 = new Il2CppSystem.Collections.Generic.Dictionary(); + CppDictTest2.Add(0, 0.5f); + CppDictTest2.Add(1, 0.5f); + CppDictTest2.Add(2, 0.5f); #endif } + private void TextureSpriteTest() + { + //TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600); + //TestTexture = new Texture(); + //TestTexture.name = "TestTexture"; + + //var r = new Rect(0, 0, TestTexture.width, TestTexture.height); + //var v2 = Vector2.zero; + //var v4 = Vector4.zero; + //TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false); + + //GameObject.DontDestroyOnLoad(TestTexture); + //GameObject.DontDestroyOnLoad(TestSprite); + + //// test loading a tex from file + //var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png"); + //ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}"); + } + public static string TestRefInOutGeneric(ref string arg0, in int arg1, out string arg2) where T : Component { arg2 = "this is arg2";