diff --git a/src/CachedObjects/CacheObjectBase.cs b/src/CachedObjects/CacheObjectBase.cs index 3e63c57..f990963 100644 --- a/src/CachedObjects/CacheObjectBase.cs +++ b/src/CachedObjects/CacheObjectBase.cs @@ -115,6 +115,11 @@ namespace Explorer { CacheObjectBase holder; + // This is pretty ugly, could probably make a cleaner implementation. + // However, the only cleaner ways I can think of are slower and probably not worth it. + + // Note: the order is somewhat important. + if (memberInfo is MethodInfo mi) { if (CacheMethod.CanEvaluate(mi)) @@ -154,14 +159,15 @@ namespace Explorer { holder = new CacheRect(); } - else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType)) - { - holder = new CacheList(); - } + // must check this before IsEnumerable else if (ReflectionHelpers.IsDictionary(valueType)) { holder = new CacheDictionary(); } + else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType)) + { + holder = new CacheList(); + } else { holder = new CacheOther(); diff --git a/src/CachedObjects/Object/CacheDictionary.cs b/src/CachedObjects/Object/CacheDictionary.cs index 6454bcc..64baddc 100644 --- a/src/CachedObjects/Object/CacheDictionary.cs +++ b/src/CachedObjects/Object/CacheDictionary.cs @@ -1,34 +1,278 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using MelonLoader; using UnityEngine; +using System.Reflection; namespace Explorer { public class CacheDictionary : CacheObjectBase { + public bool IsExpanded { get; set; } + public PageHelper Pages = new PageHelper(); + public float WhiteSpace = 215f; + public float ButtonWidthOffset = 290f; - public override void Init() + private CacheObjectBase[] m_cachedKeys; + private CacheObjectBase[] m_cachedValues; + + public Type TypeOfKeys { - //base.Init(); + get + { + if (m_keysType == null) GetGenericArguments(); + return m_keysType; + } + } + private Type m_keysType; - Value = "Unsupported"; + public Type TypeOfValues + { + get + { + if (m_valuesType == null) GetGenericArguments(); + return m_valuesType; + } + } + private Type m_valuesType; + + public IDictionary IDict + { + get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono(); + set => m_iDictionary = value; + } + private IDictionary m_iDictionary; + + // This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary. + private IDictionary Il2CppDictionaryToMono() + { + // note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type. + + // make generic dictionary from key and value type + var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>) + .MakeGenericType(TypeOfKeys, TypeOfValues)); + + // get keys and values + var keys = ValueType.GetProperty("Keys") .GetValue(Value); + var values = ValueType.GetProperty("Values").GetValue(Value); + + // create a list to hold them + var keyList = new List(); + var valueList = new List(); + + // get keys enumerator and store keys + var keyEnumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null); + var keyCollectionType = keyEnumerator.GetType(); + var keyMoveNext = keyCollectionType.GetMethod("MoveNext"); + var keyCurrent = keyCollectionType.GetProperty("Current"); + while ((bool)keyMoveNext.Invoke(keyEnumerator, null)) + { + keyList.Add(keyCurrent.GetValue(keyEnumerator)); + } + + // get values enumerator and store values + var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null); + var valueCollectionType = valueEnumerator.GetType(); + var valueMoveNext = valueCollectionType.GetMethod("MoveNext"); + var valueCurrent = valueCollectionType.GetProperty("Current"); + while ((bool)valueMoveNext.Invoke(valueEnumerator, null)) + { + valueList.Add(valueCurrent.GetValue(valueEnumerator)); + } + + // finally iterate into actual dictionary + for (int i = 0; i < keyList.Count; i++) + { + dict.Add(keyList[i], valueList[i]); + } + + return dict; + } + + // ========== Methods ========== + + private void GetGenericArguments() + { + if (m_keysType == null || m_valuesType == null) + { + if (this.MemInfo != null) + { + Type memberType = null; + switch (this.MemInfo.MemberType) + { + case MemberTypes.Field: + memberType = (MemInfo as FieldInfo).FieldType; + break; + case MemberTypes.Property: + memberType = (MemInfo as PropertyInfo).PropertyType; + break; + } + + if (memberType != null && memberType.IsGenericType) + { + m_keysType = memberType.GetGenericArguments()[0]; + m_valuesType = memberType.GetGenericArguments()[1]; + } + } + else if (Value != null) + { + var type = Value.GetType(); + if (type.IsGenericType) + { + m_keysType = type.GetGenericArguments()[0]; + m_valuesType = type.GetGenericArguments()[1]; + } + } + } + + return; } public override void UpdateValue() { - //base.UpdateValue(); + base.UpdateValue(); + // reset + IDict = null; + if (Value == null || IDict == null) + { + return; + } + + var keys = new List(); + foreach (var key in IDict.Keys) + { + var cache = GetCacheObject(key, TypeOfKeys); + cache.UpdateValue(); + keys.Add(cache); + } + + var values = new List(); + foreach (var val in IDict.Values) + { + var cache = GetCacheObject(val, TypeOfValues); + cache.UpdateValue(); + values.Add(cache); + } + + m_cachedKeys = keys.ToArray(); + m_cachedValues = values.ToArray(); } + // ============= GUI Draw ============= + public override void DrawValue(Rect window, float width) { - GUILayout.Label("Dictionary (unsupported)", null); + if (m_cachedKeys == null || m_cachedValues == null) + { + GUILayout.Label("Cached keys or values is null!", null); + return; + } + + int count = m_cachedKeys.Length; + + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + string btnLabel = $"[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}>"; + if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) })) + { + WindowManager.InspectObject(Value, out bool _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + GUILayout.Space(5); + + if (IsExpanded) + { + float whitespace = WhiteSpace; + if (whitespace > 0) + { + ClampLabelWidth(window, ref whitespace); + } + + Pages.ItemCount = count; + + if (count > Pages.ItemsPerPage) + { + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(null); + + GUILayout.Space(whitespace); + + Pages.CurrentPageLabel(); + + // prev/next page buttons + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Left); + } + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Right); + } + + Pages.DrawLimitInputArea(); + + GUILayout.Space(5); + } + + int offset = Pages.CalculateOffsetIndex(); + + for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) + { + var key = m_cachedKeys[i]; + var val = m_cachedValues[i]; + + //collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(null); + + //GUILayout.Space(whitespace); + + if (key == null || val == null) + { + GUILayout.Label($"[{i}] (null)", null); + } + else + { + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) }); + + GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.MinWidth((window.width / 3) - 60f) }); + GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) }); + key.DrawValue(window, (window.width / 2) - 30f); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) }); + val.DrawValue(window, (window.width / 2) - 30f); + GUILayout.EndHorizontal(); + } + + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } } } } diff --git a/src/CachedObjects/Object/CacheList.cs b/src/CachedObjects/Object/CacheList.cs index db12f3d..1036516 100644 --- a/src/CachedObjects/Object/CacheList.cs +++ b/src/CachedObjects/Object/CacheList.cs @@ -218,6 +218,7 @@ namespace Explorer if (GetCacheObject(obj, t) is CacheObjectBase cached) { + cached.UpdateValue(); list.Add(cached); } else @@ -262,7 +263,7 @@ namespace Explorer } GUI.skin.button.alignment = TextAnchor.MiddleLeft; - string btnLabel = "[" + count + "] " + EntryType + ""; + string btnLabel = "[" + count + "] " + EntryType.FullName + ""; if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) })) { WindowManager.InspectObject(Value, out bool _); @@ -305,12 +306,6 @@ namespace Explorer GUILayout.Space(5); } - //int offset = ArrayOffset * ArrayLimit; - //if (offset >= count) - //{ - // offset = 0; - // ArrayOffset = 0; - //} int offset = Pages.CalculateOffsetIndex(); for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) diff --git a/src/CppExplorer.cs b/src/CppExplorer.cs index 27d6fa9..90b0a4f 100644 --- a/src/CppExplorer.cs +++ b/src/CppExplorer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -12,7 +13,7 @@ namespace Explorer public class CppExplorer : MelonMod { public const string GUID = "com.sinai.cppexplorer"; - public const string VERSION = "1.5.8"; + public const string VERSION = "1.5.9"; public const string AUTHOR = "Sinai"; public const string NAME = "CppExplorer" diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index b61bb24..b0b8277 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -74,12 +74,13 @@ namespace Explorer return false; } - public static bool IsArray(Type t) + public static bool IsEnumerable(Type t) { return typeof(System.Collections.IEnumerable).IsAssignableFrom(t); } - public static bool IsList(Type t) + // Only Il2Cpp List needs this check. C# List is IEnumerable. + public static bool IsCppList(Type t) { if (t.IsGenericType) { @@ -98,21 +99,21 @@ namespace Explorer { return t.IsGenericType && t.GetGenericTypeDefinition() is Type typeDef - && typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>)); + && (typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>)) + || typeDef.IsAssignableFrom(typeof(Dictionary<,>))); } public static Type GetTypeByName(string typeName) { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { - try + foreach (var type in GetTypesSafe(asm)) { - if (asm.GetType(typeName) is Type type) + if (type.FullName == typeName) { return type; } } - catch { } } return null; @@ -137,6 +138,13 @@ namespace Explorer return obj.GetType(); } + public static IEnumerable GetTypesSafe(Assembly asm) + { + try { return asm.GetTypes(); } + catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); } + catch { return Enumerable.Empty(); } + } + public static Type[] GetAllBaseTypes(object obj) { var list = new List(); diff --git a/src/MainMenu/Pages/SearchPage.cs b/src/MainMenu/Pages/SearchPage.cs index d27655e..4c4059d 100644 --- a/src/MainMenu/Pages/SearchPage.cs +++ b/src/MainMenu/Pages/SearchPage.cs @@ -172,16 +172,6 @@ namespace Explorer GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) }); m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) }); - //GUI.skin.label.alignment = TextAnchor.MiddleRight; - //GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) }); - //var resultinput = m_limit.ToString(); - //resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) }); - //if (int.TryParse(resultinput, out int _i) && _i > 0) - //{ - // m_limit = _i; - //} - //GUI.skin.label.alignment = TextAnchor.UpperLeft; - GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(null); @@ -263,7 +253,7 @@ namespace Explorer CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput)); } - private List FindAllObjectsOfType(string _search, string _type) + private List FindAllObjectsOfType(string searchQuery, string typeName) { Il2CppSystem.Type searchType = null; @@ -271,13 +261,18 @@ namespace Explorer { try { - var findType = ReflectionHelpers.GetTypeByName(_type); - searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName); - //MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName); + if (ReflectionHelpers.GetTypeByName(typeName) is Type t) + { + searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName); + } + else + { + throw new Exception($"Could not find a Type by the name of '{typeName}'!"); + } } catch (Exception e) { - MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace); + MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message); } } else if (TypeMode == TypeFilter.Object) @@ -295,7 +290,10 @@ namespace Explorer if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType)) { - MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!"); + if (searchType != null) + { + MelonLogger.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!"); + } return new List(); } @@ -310,7 +308,7 @@ namespace Explorer { if (i >= 2000) break; - if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower())) + if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower())) { continue; } @@ -387,7 +385,7 @@ namespace Explorer public static IEnumerable GetInstanceClassScanner() { var query = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(GetTypesSafe) + .SelectMany(ReflectionHelpers.GetTypesSafe) .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); var flags = BindingFlags.Public | BindingFlags.Static; @@ -430,7 +428,7 @@ namespace Explorer { var t = ReflectionHelpers.GetActualType(obj); - if (!FilterName(t.FullName) || ReflectionHelpers.IsArray(t) || ReflectionHelpers.IsList(t)) + if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t)) { continue; } @@ -439,12 +437,5 @@ namespace Explorer } } } - - public static IEnumerable GetTypesSafe(Assembly asm) - { - try { return asm.GetTypes(); } - catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); } - catch { return Enumerable.Empty(); } - } } }