diff --git a/src/CachedObjects/CacheObjectBase.cs b/src/CachedObjects/CacheObjectBase.cs index e1b673c..3f6d199 100644 --- a/src/CachedObjects/CacheObjectBase.cs +++ b/src/CachedObjects/CacheObjectBase.cs @@ -172,7 +172,7 @@ namespace Explorer { holder = new CacheDictionary(); } - else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType)) + else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppEnumerable(valueType)) { holder = new CacheList(); } diff --git a/src/CachedObjects/Object/CacheDictionary.cs b/src/CachedObjects/Object/CacheDictionary.cs index af7530b..056eff1 100644 --- a/src/CachedObjects/Object/CacheDictionary.cs +++ b/src/CachedObjects/Object/CacheDictionary.cs @@ -228,7 +228,7 @@ namespace Explorer var negativeWhitespace = window.width - (whitespace + 100f); GUI.skin.button.alignment = TextAnchor.MiddleLeft; - string btnLabel = $"[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}>"; + string btnLabel = $"[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}>"; if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) })) { WindowManager.InspectObject(Value, out bool _); diff --git a/src/CachedObjects/Object/CacheList.cs b/src/CachedObjects/Object/CacheList.cs index 6713d45..28c2971 100644 --- a/src/CachedObjects/Object/CacheList.cs +++ b/src/CachedObjects/Object/CacheList.cs @@ -40,7 +40,7 @@ namespace Explorer private Type m_genericTypeDef; // Cached ToArray method for Lists - public MethodInfo GenericToArrayMethod + public MethodInfo CppListToArrayMethod { get => GetGenericToArrayMethod(); } @@ -60,7 +60,7 @@ namespace Explorer { if (m_enumerable == null && Value != null) { - m_enumerable = Value as IEnumerable ?? GetEnumerableFromIl2CppList(); + m_enumerable = Value as IEnumerable ?? EnumerateWithReflection(); } return m_enumerable; } @@ -100,21 +100,45 @@ namespace Explorer return m_itemProperty; } - private IEnumerable GetEnumerableFromIl2CppList() + private IEnumerable EnumerateWithReflection() { if (Value == null) return null; if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>)) { - return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]); + return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]); + } + else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>)) + { + return CppHashSetToMono(); } else { - return ConvertIListToMono(); + return CppIListToMono(); } } - private IList ConvertIListToMono() + 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; + } + + private IList CppIListToMono() { try { diff --git a/src/CachedObjects/Struct/CacheColor.cs b/src/CachedObjects/Struct/CacheColor.cs index c05bc59..b9e4217 100644 --- a/src/CachedObjects/Struct/CacheColor.cs +++ b/src/CachedObjects/Struct/CacheColor.cs @@ -49,10 +49,10 @@ namespace Explorer } } - var c = (Color)Value; - GUI.color = c; - GUILayout.Label($"Color: {c.ToString()}", null); - GUI.color = Color.white; + //var c = (Color)Value; + //GUI.color = c; + GUILayout.Label($"Color: {((Color)Value).ToString()}", null); + //GUI.color = Color.white; if (CanWrite && IsExpanded) { diff --git a/src/Config/ModConfig.cs b/src/Config/ModConfig.cs index 19b6a98..c28e58a 100644 --- a/src/Config/ModConfig.cs +++ b/src/Config/ModConfig.cs @@ -28,25 +28,31 @@ namespace Explorer Directory.CreateDirectory(EXPLORER_FOLDER); } - if (File.Exists(SETTINGS_PATH)) - { - LoadSettings(false); - } - else - { - Instance = new ModConfig(); - SaveSettings(false); - } + if (LoadSettings()) return; + + Instance = new ModConfig(); + SaveSettings(); } - public static void LoadSettings(bool checkExist = true) + // returns true if settings successfully loaded + public static bool LoadSettings(bool checkExist = true) { if (checkExist && !File.Exists(SETTINGS_PATH)) - return; + return false; - var file = File.OpenRead(SETTINGS_PATH); - Instance = (ModConfig)Serializer.Deserialize(file); - file.Close(); + try + { + using (var file = File.OpenRead(SETTINGS_PATH)) + { + Instance = (ModConfig)Serializer.Deserialize(file); + } + } + catch + { + return false; + } + + return Instance != null; } public static void SaveSettings(bool checkExist = true) @@ -54,9 +60,10 @@ namespace Explorer if (checkExist && File.Exists(SETTINGS_PATH)) File.Delete(SETTINGS_PATH); - FileStream file = File.Create(SETTINGS_PATH); - Serializer.Serialize(file, Instance); - file.Close(); + using (var file = File.Create(SETTINGS_PATH)) + { + Serializer.Serialize(file, Instance); + } } } } diff --git a/src/CppExplorer.csproj b/src/CppExplorer.csproj index f1c14a1..6a568db 100644 --- a/src/CppExplorer.csproj +++ b/src/CppExplorer.csproj @@ -1,4 +1,4 @@ - + @@ -29,6 +29,10 @@ ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll False + + ..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll + False + ..\lib\mcs.dll True @@ -118,4 +122,4 @@ - + \ No newline at end of file diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index 8c59525..afc24c5 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -37,13 +37,14 @@ namespace Explorer return typeof(IEnumerable).IsAssignableFrom(t); } - // Only Il2Cpp List needs this check. C# List is IEnumerable. - public static bool IsCppList(Type t) + // Checks for Il2Cpp List or HashSet. + public static bool IsCppEnumerable(Type t) { 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.IList<>).IsAssignableFrom(g) + || typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g); } else { @@ -89,18 +90,20 @@ namespace Explorer { if (obj == null) return null; + // Need to use GetIl2CppType for Il2CppSystem Objects if (obj is Il2CppSystem.Object ilObject) { - var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName; - - if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType")) + // Prevent weird behaviour when inspecting an Il2CppSystem.Type object. + if (ilObject is ILType) { - return t; + return typeof(ILType); } - return ilObject.GetType(); + // Get the System.Type using the qualified name, or fallback to GetType. + return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType(); } + // It's a normal object, this is fine return obj.GetType(); } @@ -109,12 +112,11 @@ namespace Explorer var list = new List(); var type = GetActualType(obj); - list.Add(type); - while (type.BaseType != null) + while (type != null) { - type = type.BaseType; list.Add(type); + type = type.BaseType; } return list.ToArray(); diff --git a/src/MainMenu/Pages/SearchPage.cs b/src/MainMenu/Pages/SearchPage.cs index 78067be..e982dce 100644 --- a/src/MainMenu/Pages/SearchPage.cs +++ b/src/MainMenu/Pages/SearchPage.cs @@ -428,7 +428,7 @@ namespace Explorer { var t = ReflectionHelpers.GetActualType(obj); - if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t)) + if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppEnumerable(t)) { continue; } diff --git a/src/Tests/TestClass.cs b/src/Tests/TestClass.cs index d763d55..91a2221 100644 --- a/src/Tests/TestClass.cs +++ b/src/Tests/TestClass.cs @@ -14,6 +14,25 @@ namespace Explorer.Tests public static TestClass Instance => m_instance ?? (m_instance = new TestClass()); private static TestClass m_instance; + public TestClass() + { + ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet(); + ILHashSetTest.Add("1"); + ILHashSetTest.Add("2"); + ILHashSetTest.Add("3"); + } + + // test HashSets + + public static HashSet HashSetTest = new HashSet + { + "One", + "Two", + "Three" + }; + + public static Il2CppSystem.Collections.Generic.HashSet ILHashSetTest; + // Test indexed parameter public string this[int arg0, string arg1] diff --git a/src/Windows/ReflectionWindow.cs b/src/Windows/ReflectionWindow.cs index da5d2e7..1db6ed8 100644 --- a/src/Windows/ReflectionWindow.cs +++ b/src/Windows/ReflectionWindow.cs @@ -32,10 +32,14 @@ namespace Explorer private UnityEngine.Object m_uObj; private Component m_component; - private static readonly HashSet _memberBlacklist = new HashSet + private static readonly HashSet _typeAndMemberBlacklist = new HashSet { // Causes a crash "Type.DeclaringMethod", + }; + + private static readonly HashSet _methodStartsWithBlacklist = new HashSet + { // Pointless (handled by Properties) "get_", "set_" @@ -157,7 +161,11 @@ namespace Explorer continue; // check blacklisted members - if (_memberBlacklist.Any(it => member.Name.StartsWith(it))) + var name = member.DeclaringType.Name + "." + member.Name; + if (_typeAndMemberBlacklist.Any(it => it == name)) + continue; + + if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it))) continue; // compare signature to already cached members