diff --git a/README.md b/README.md index 37d04cf..ae4b9e1 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,15 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins * Press F7 to show or hide the menu. * Simply browse through the scene, search for objects, etc, most of it is pretty self-explanatory. +[![](img.png)](img.png) + +* An overview of the different CppExplorer menus. + ### Scene Explorer * A simple menu which allows you to traverse the Transform heirarchy of the scene. * Click on a GameObject to set it as the current path, or Inspect it to send it to an Inspector Window. -[![](https://i.imgur.com/BzTOCvp.png)](https://i.imgur.com/BzTOCvp.png) - ### Inspectors CppExplorer has two main inspector modes: GameObject Inspector, and Reflection Inspector. @@ -59,30 +61,22 @@ CppExplorer has two main inspector modes: GameObject Inspector, and Re * Allows you to see the children and components on a GameObject. * Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc. -[![](https://i.imgur.com/DiDvl0Q.png)](https://i.imgur.com/DiDvl0Q.png) - ### Reflection Inspector * The Reflection Inspector is used for all other supported objects. * Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods. * Can search and filter members for the ones you are interested in. -[![](https://i.imgur.com/Pq127XD.png)](https://i.imgur.com/Pq127XD.png) - ### Object Search * You can search for an `UnityEngine.Object` with the Object Search feature. * Filter by name, type, etc. * For GameObjects and Transforms you can filter which scene they are found in too. -[![](https://i.imgur.com/lK2RthM.png)](https://i.imgur.com/lK2RthM.png) - ### C# REPL console * A simple C# REPL console, allows you to execute a method body on the fly. -[![](https://i.imgur.com/5U4D1a8.png)](https://i.imgur.com/5U4D1a8.png) - ### Inspect-under-mouse * Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse. diff --git a/img.png b/img.png new file mode 100644 index 0000000..1164d20 Binary files /dev/null and b/img.png differ diff --git a/src/CppExplorer.cs b/src/CppExplorer.cs index 761b5cf..25714f4 100644 --- a/src/CppExplorer.cs +++ b/src/CppExplorer.cs @@ -13,7 +13,7 @@ namespace Explorer public class CppExplorer : MelonMod { public const string NAME = "CppExplorer"; - public const string VERSION = "1.6.3"; + public const string VERSION = "1.6.4"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.cppexplorer"; @@ -50,6 +50,8 @@ namespace Explorer { Instance = this; + InputHelper.CheckInput(); + new MainMenu(); new WindowManager(); diff --git a/src/CppExplorer.csproj b/src/CppExplorer.csproj index 5ae78f1..6866327 100644 --- a/src/CppExplorer.csproj +++ b/src/CppExplorer.csproj @@ -11,7 +11,7 @@ v4.7.2 512 true - + CppExplorer false none @@ -22,7 +22,7 @@ 4 x64 false - + ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll diff --git a/src/Extensions/ReflectionExtensions.cs b/src/Extensions/ReflectionExtensions.cs index 66d159e..a45f724 100644 --- a/src/Extensions/ReflectionExtensions.cs +++ b/src/Extensions/ReflectionExtensions.cs @@ -3,14 +3,38 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Reflection; namespace Explorer { public static class ReflectionExtensions { + /// + /// Extension to allow for easy, non-generic Il2Cpp casting. + /// The extension is on System.Object, but only Il2Cpp objects would be a valid target. + /// public static object Il2CppCast(this object obj, Type castTo) { return ReflectionHelpers.Il2CppCast(obj, castTo); } + + /// + /// Extension to safely try to get all Types from an Assembly, with a fallback for ReflectionTypeLoadException. + /// + public static IEnumerable TryGetTypes(this Assembly asm) + { + try + { + return asm.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t != null); + } + catch + { + return Enumerable.Empty(); + } + } } } diff --git a/src/Helpers/InputHelper.cs b/src/Helpers/InputHelper.cs index e477a82..e38a83b 100644 --- a/src/Helpers/InputHelper.cs +++ b/src/Helpers/InputHelper.cs @@ -1,51 +1,107 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; using UnityEngine; +using MelonLoader; namespace Explorer { /// - /// Version-agnostic UnityEngine.Input module using Reflection + /// Version-agnostic UnityEngine Input module using Reflection. /// - public class InputHelper + public static class InputHelper { - private static readonly Type input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"); + public static void CheckInput() + { + if (Input == null) + { + MelonLogger.Log("UnityEngine.Input is null, trying to load manually...."); - private static readonly PropertyInfo mousePositionInfo = input.GetProperty("mousePosition"); + if ((TryLoad("UnityEngine.InputLegacyModule.dll") || TryLoad("UnityEngine.CoreModule.dll")) && Input != null) + { + MelonLogger.Log("Ok!"); + } + else + { + MelonLogger.Log("Could not load Input module!"); + } - private static readonly MethodInfo getKey = input.GetMethod("GetKey", new Type[] { typeof(KeyCode) }); - private static readonly MethodInfo getKeyDown = input.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) }); - private static readonly MethodInfo getMouseButtonDown = input.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) }); - private static readonly MethodInfo getMouseButton = input.GetMethod("GetMouseButton", new Type[] { typeof(int) }); + bool TryLoad(string module) + { + var path = $@"MelonLoader\Managed\{module}"; + if (!File.Exists(path)) return false; + + try + { + Assembly.Load(File.ReadAllBytes(path)); + return true; + } + catch (Exception e) + { + MelonLogger.Log(e.GetType() + ", " + e.Message); + return false; + } + } + } + } + + public static Type Input => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input")); + private static Type _input; + + private static PropertyInfo MousePosInfo => _mousePosition ?? (_mousePosition = Input?.GetProperty("mousePosition")); + private static PropertyInfo _mousePosition; + + private static MethodInfo GetKeyInfo => _getKey ?? (_getKey = Input?.GetMethod("GetKey", new Type[] { typeof(KeyCode) })); + private static MethodInfo _getKey; + + private static MethodInfo GetKeyDownInfo => _getKeyDown ?? (_getKeyDown = Input?.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) })); + private static MethodInfo _getKeyDown; + + private static MethodInfo GetMouseButtonInfo => _getMouseButton ?? (_getMouseButton = Input?.GetMethod("GetMouseButton", new Type[] { typeof(int) })); + private static MethodInfo _getMouseButton; + + private static MethodInfo GetMouseButtonDownInfo => _getMouseButtonDown ?? (_getMouseButtonDown = Input?.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) })); + private static MethodInfo _getMouseButtonDown; #pragma warning disable IDE1006 // Camel-case property (Unity style) - public static Vector3 mousePosition => (Vector3)mousePositionInfo.GetValue(null); + public static Vector3 mousePosition + { + get + { + if (Input == null) return Vector3.zero; + return (Vector3)MousePosInfo.GetValue(null); + } + } #pragma warning restore IDE1006 public static bool GetKeyDown(KeyCode key) { - return (bool)getKeyDown.Invoke(null, new object[] { key }); + if (Input == null) return false; + return (bool)GetKeyDownInfo.Invoke(null, new object[] { key }); } public static bool GetKey(KeyCode key) { - return (bool)getKey.Invoke(null, new object[] { key }); + if (Input == null) return false; + return (bool)GetKeyInfo.Invoke(null, new object[] { key }); } /// 1 = left, 2 = middle, 3 = right, etc public static bool GetMouseButtonDown(int btn) { - return (bool)getMouseButtonDown.Invoke(null, new object[] { btn }); + if (Input == null) return false; + return (bool)GetMouseButtonDownInfo.Invoke(null, new object[] { btn }); } /// 1 = left, 2 = middle, 3 = right, etc public static bool GetMouseButton(int btn) { - return (bool)getMouseButton.Invoke(null, new object[] { btn }); + if (Input == null) return false; + return (bool)GetMouseButtonInfo.Invoke(null, new object[] { btn }); } } } diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index 47c30bf..8c59525 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -1,16 +1,13 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Reflection; using UnhollowerBaseLib; using UnhollowerRuntimeLib; using UnityEngine; using BF = System.Reflection.BindingFlags; -using MelonLoader; -using System.Collections; -using Mono.CSharp; +using ILType = Il2CppSystem.Type; namespace Explorer { @@ -18,11 +15,11 @@ namespace Explorer { public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; - public static Il2CppSystem.Type GameObjectType => Il2CppType.Of(); - public static Il2CppSystem.Type TransformType => Il2CppType.Of(); - public static Il2CppSystem.Type ObjectType => Il2CppType.Of(); - public static Il2CppSystem.Type ComponentType => Il2CppType.Of(); - public static Il2CppSystem.Type BehaviourType => Il2CppType.Of(); + public static ILType GameObjectType => Il2CppType.Of(); + public static ILType TransformType => Il2CppType.Of(); + public static ILType ObjectType => Il2CppType.Of(); + public static ILType ComponentType => Il2CppType.Of(); + public static ILType BehaviourType => Il2CppType.Of(); private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast"); @@ -35,47 +32,6 @@ namespace Explorer .Invoke(obj, null); } - public static string ExceptionToString(Exception e) - { - if (IsFailedGeneric(e)) - { - return "Unable to initialize this type."; - } - else if (IsObjectCollected(e)) - { - return "Garbage collected in Il2Cpp."; - } - - return e.GetType() + ", " + e.Message; - } - - public static bool IsFailedGeneric(Exception e) - { - return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException)); - } - - public static bool IsObjectCollected(Exception e) - { - return IsExceptionOfType(e, typeof(ObjectCollectedException)); - } - - public static bool IsExceptionOfType(Exception e, Type t, bool strict = true, bool checkInner = true) - { - bool isType; - - if (strict) - isType = e.GetType() == t; - else - isType = t.IsAssignableFrom(e.GetType()); - - if (isType) return true; - - if (e.InnerException != null && checkInner) - return IsExceptionOfType(e.InnerException, t, strict); - else - return false; - } - public static bool IsEnumerable(Type t) { return typeof(IEnumerable).IsAssignableFrom(t); @@ -117,7 +73,7 @@ namespace Explorer { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { - foreach (var type in GetTypesSafe(asm)) + foreach (var type in asm.TryGetTypes()) { if (type.FullName == fullName) { @@ -148,13 +104,6 @@ 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(); @@ -170,5 +119,46 @@ namespace Explorer return list.ToArray(); } + + public static string ExceptionToString(Exception e) + { + if (IsFailedGeneric(e)) + { + return "Unable to initialize this type."; + } + else if (IsObjectCollected(e)) + { + return "Garbage collected in Il2Cpp."; + } + + return e.GetType() + ", " + e.Message; + } + + public static bool IsFailedGeneric(Exception e) + { + return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException)); + } + + public static bool IsObjectCollected(Exception e) + { + return IsExceptionOfType(e, typeof(ObjectCollectedException)); + } + + public static bool IsExceptionOfType(Exception e, Type t, bool strict = true, bool checkInner = true) + { + bool isType; + + if (strict) + isType = e.GetType() == t; + else + isType = t.IsAssignableFrom(e.GetType()); + + if (isType) return true; + + if (e.InnerException != null && checkInner) + return IsExceptionOfType(e.InnerException, t, strict); + else + return false; + } } } diff --git a/src/MainMenu/Pages/ScenePage.cs b/src/MainMenu/Pages/ScenePage.cs index 26d173e..3472789 100644 --- a/src/MainMenu/Pages/ScenePage.cs +++ b/src/MainMenu/Pages/ScenePage.cs @@ -192,7 +192,7 @@ namespace Explorer } } - // --------- GUI Draw Function --------- // + // --------- GUI Draw Function --------- // public override void DrawWindow() { @@ -215,11 +215,9 @@ namespace Explorer GUILayout.EndVertical(); } - catch (Exception e) + catch { - MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message); - MelonLogger.Log(e.StackTrace); - m_currentTransform = null; + // supress } } @@ -229,39 +227,7 @@ namespace Explorer // Current Scene label GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) }); - try - { - // Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC. - var scenes = SceneManager.GetAllScenes().ToList(); - - if (scenes.Count > 1) - { - int changeWanted = 0; - if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) })) - { - changeWanted = -1; - } - if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) })) - { - changeWanted = 1; - } - if (changeWanted != 0) - { - int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene)); - index += changeWanted; - if (index > scenes.Count - 1) - { - index = 0; - } - else if (index < 0) - { - index = scenes.Count - 1; - } - m_currentScene = scenes[index].name; - } - } - } - catch { } + SceneChangeButtons(); GUILayout.Label("" + m_currentScene + "", null); //new GUILayoutOption[] { GUILayout.Width(250) }); GUILayout.EndHorizontal(); @@ -269,7 +235,9 @@ namespace Explorer // ----- GameObject Search ----- GUILayout.BeginHorizontal(GUI.skin.box, null); GUILayout.Label("Search Scene:", new GUILayoutOption[] { GUILayout.Width(100) }); + m_searchInput = GUILayout.TextField(m_searchInput, null); + if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) })) { Search(); @@ -279,6 +247,39 @@ namespace Explorer GUILayout.Space(5); } + private void SceneChangeButtons() + { + // Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC. + var scenes = SceneManager.GetAllScenes().ToList(); + + if (scenes.Count > 1) + { + int changeWanted = 0; + if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) })) + { + changeWanted = -1; + } + if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) })) + { + changeWanted = 1; + } + if (changeWanted != 0) + { + int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene)); + index += changeWanted; + if (index > scenes.Count - 1) + { + index = 0; + } + else if (index < 0) + { + index = scenes.Count - 1; + } + m_currentScene = scenes[index].name; + } + } + } + private void DrawPageButtons() { GUILayout.BeginHorizontal(null); diff --git a/src/MainMenu/Pages/SearchPage.cs b/src/MainMenu/Pages/SearchPage.cs index 4c4059d..78067be 100644 --- a/src/MainMenu/Pages/SearchPage.cs +++ b/src/MainMenu/Pages/SearchPage.cs @@ -385,8 +385,8 @@ namespace Explorer public static IEnumerable GetInstanceClassScanner() { var query = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(ReflectionHelpers.GetTypesSafe) - .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); + .SelectMany(t => t.TryGetTypes()) + .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); var flags = BindingFlags.Public | BindingFlags.Static; var flatFlags = flags | BindingFlags.FlattenHierarchy;