diff --git a/src/CppExplorer.cs b/src/CppExplorer.cs index 7df55f4..5881dc7 100644 --- a/src/CppExplorer.cs +++ b/src/CppExplorer.cs @@ -6,6 +6,7 @@ using System.Text; using UnityEngine; using MelonLoader; using UnhollowerBaseLib; +using Harmony; namespace Explorer { @@ -61,13 +62,11 @@ namespace Explorer new MainMenu(); new WindowManager(); - //var harmony = HarmonyInstance.Create(ID); - //harmony.PatchAll(); - // done init ShowMenu = true; } + // On scene change public override void OnLevelWasLoaded(int level) { if (ScenePage.Instance != null) @@ -77,6 +76,7 @@ namespace Explorer } } + // Update public override void OnUpdate() { if (Input.GetKeyDown(KeyCode.F7)) @@ -86,6 +86,12 @@ namespace Explorer if (ShowMenu) { + if (!Cursor.visible) + { + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + } + MainMenu.Instance.Update(); WindowManager.Instance.Update(); diff --git a/src/CppExplorer.csproj b/src/CppExplorer.csproj index 7506e56..a2274dd 100644 --- a/src/CppExplorer.csproj +++ b/src/CppExplorer.csproj @@ -46,7 +46,7 @@ ..\lib\mcs.dll - True + False ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll @@ -90,12 +90,8 @@ ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.ParticleSystemModule.dll False - - ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.Physics2DModule.dll - False - - ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll + ..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll False diff --git a/src_2018/CppExplorer.cs b/src_2018/CppExplorer.cs new file mode 100644 index 0000000..5881dc7 --- /dev/null +++ b/src_2018/CppExplorer.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using MelonLoader; +using UnhollowerBaseLib; +using Harmony; + +namespace Explorer +{ + public class CppExplorer : MelonMod + { + // consts + + public const string ID = "com.sinai.cppexplorer"; + public const string NAME = "IL2CPP Runtime Explorer"; + public const string VERSION = "1.1.0"; + public const string AUTHOR = "Sinai"; + + // fields + + public static CppExplorer Instance; + private string m_objUnderMouseName = ""; + private Camera m_main; + + // props + + public static bool ShowMenu { get; set; } = false; + public static int ArrayLimit { get; set; } = 100; + public bool MouseInspect { get; set; } = false; + + public static string ActiveSceneName + { + get + { + return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; + } + } + + public Camera MainCamera + { + get + { + if (m_main == null) + { + m_main = Camera.main; + } + return m_main; + } + } + + // methods + + public override void OnApplicationStart() + { + base.OnApplicationStart(); + + Instance = this; + + new MainMenu(); + new WindowManager(); + + // done init + ShowMenu = true; + } + + // On scene change + public override void OnLevelWasLoaded(int level) + { + if (ScenePage.Instance != null) + { + ScenePage.Instance.OnSceneChange(); + SearchPage.Instance.OnSceneChange(); + } + } + + // Update + public override void OnUpdate() + { + if (Input.GetKeyDown(KeyCode.F7)) + { + ShowMenu = !ShowMenu; + } + + if (ShowMenu) + { + if (!Cursor.visible) + { + Cursor.visible = true; + Cursor.lockState = CursorLockMode.None; + } + + MainMenu.Instance.Update(); + WindowManager.Instance.Update(); + + if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1)) + { + MouseInspect = !MouseInspect; + } + + if (MouseInspect) + { + InspectUnderMouse(); + } + } + else if (MouseInspect) + { + MouseInspect = false; + } + } + + private void InspectUnderMouse() + { + Ray ray = MainCamera.ScreenPointToRay(Input.mousePosition); + + if (Physics.Raycast(ray, out RaycastHit hit, 1000f)) + { + var obj = hit.transform.gameObject; + + m_objUnderMouseName = GetGameObjectPath(obj.transform); + + if (Input.GetMouseButtonDown(0)) + { + MouseInspect = false; + m_objUnderMouseName = ""; + + WindowManager.InspectObject(obj, out _); + } + } + else + { + m_objUnderMouseName = ""; + } + } + + public override void OnGUI() + { + base.OnGUI(); + + MainMenu.Instance.OnGUI(); + WindowManager.Instance.OnGUI(); + + if (MouseInspect) + { + if (m_objUnderMouseName != "") + { + var pos = Input.mousePosition; + var rect = new Rect( + pos.x - (Screen.width / 2), // x + Screen.height - pos.y - 50, // y + Screen.width, // w + 50 // h + ); + + var origAlign = GUI.skin.label.alignment; + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + + //shadow text + GUI.Label(rect, $"{m_objUnderMouseName}"); + //white text + GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName); + + GUI.skin.label.alignment = origAlign; + } + } + } + + // ************** public helpers ************** + + public static object Il2CppCast(object obj, Type castTo) + { + var method = typeof(Il2CppObjectBase).GetMethod("TryCast"); + var generic = method.MakeGenericMethod(castTo); + return generic.Invoke(obj, null); + } + + public static string GetGameObjectPath(Transform _transform) + { + return GetGameObjectPath(_transform, true); + } + + public static string GetGameObjectPath(Transform _transform, bool _includeItemName) + { + string text = _includeItemName ? ("/" + _transform.name) : ""; + GameObject gameObject = _transform.gameObject; + while (gameObject.transform.parent != null) + { + gameObject = gameObject.transform.parent.gameObject; + text = "/" + gameObject.name + text; + } + return text; + } + + public static Type GetType(string _type) + { + try + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + if (asm.GetType(_type) is Type type) + { + return type; + } + } + catch { } + } + + return null; + } + catch + { + return null; + } + } + } +} diff --git a/src_2018/CppExplorer.csproj b/src_2018/CppExplorer.csproj new file mode 100644 index 0000000..b195b77 --- /dev/null +++ b/src_2018/CppExplorer.csproj @@ -0,0 +1,123 @@ + + + + + Debug + AnyCPU + {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D} + Library + Properties + CppExplorer + CppExplorer_2018 + v4.7.2 + 512 + true + + + + false + none + false + ..\Release\ + + + prompt + 4 + x64 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2Cppmscorlib.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2CppSystem.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2CppSystem.Core.dll + False + + + ..\lib\mcs.dll + False + + + ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll + False + + + + + + + + + ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll + False + + + ..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll + False + + + ..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll + False + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src_2018/CppExplorer.sln b/src_2018/CppExplorer.sln new file mode 100644 index 0000000..5845c60 --- /dev/null +++ b/src_2018/CppExplorer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30128.74 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A} + EndGlobalSection +EndGlobal diff --git a/src_2018/ILBehaviour.cs b/src_2018/ILBehaviour.cs new file mode 100644 index 0000000..a00d88d --- /dev/null +++ b/src_2018/ILBehaviour.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using MelonLoader; +using UnhollowerRuntimeLib; + +namespace Explorer +{ + //public class ILBehaviour : MonoBehaviour + //{ + // public ILBehaviour(IntPtr intPtr) : base(intPtr) { } + + // public static T AddToGameObject(GameObject _go) where T : ILBehaviour + // { + // Il2CppSystem.Type ilType = UnhollowerRuntimeLib.Il2CppType.Of(); + + // if (ilType == null) + // { + // MelonLogger.Log("Error - could not get MB as ilType"); + // return null; + // } + + // var obj = typeof(T) + // .GetConstructor(new Type[] { typeof(IntPtr) }) + // .Invoke(new object[] { _go.AddComponent(UnhollowerRuntimeLib.Il2CppType.Of()).Pointer }); + + // return (T)obj; + // } + //} +} diff --git a/src_2018/Inspectors/GameObjectWindow.cs b/src_2018/Inspectors/GameObjectWindow.cs new file mode 100644 index 0000000..16d265e --- /dev/null +++ b/src_2018/Inspectors/GameObjectWindow.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MelonLoader; +using UnhollowerRuntimeLib; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Explorer +{ + public class GameObjectWindow : WindowManager.UIWindow + { + public override Il2CppSystem.String Name { get => "GameObject Inspector"; set => Name = value; } + + public GameObject m_object; + + // gui element holders + private string m_name; + private string m_scene; + + private Vector2 m_transformScroll = Vector2.zero; + private Transform[] m_children; + + private Vector2 m_compScroll = Vector2.zero; + //private Component[] m_components; + + private float m_translateAmount = 0.3f; + private float m_rotateAmount = 50f; + private float m_scaleAmount = 0.1f; + + List m_cachedDestroyList = new List(); + //private string m_addComponentInput = ""; + + private string m_setParentInput = ""; + + public bool GetObjectAsGameObject() + { + if (Target == null) + { + MelonLogger.Log("Target is null!"); + return false; + } + + var targetType = Target.GetType(); + + if (targetType == typeof(GameObject)) + { + m_object = Target as GameObject; + return true; + } + else if (targetType == typeof(Transform)) + { + m_object = (Target as Transform).gameObject; + return true; + } + + MelonLogger.Log("Error: Target is null or not a GameObject/Transform!"); + DestroyWindow(); + return false; + } + + public override void Init() + { + if (!GetObjectAsGameObject()) + { + return; + } + + m_name = m_object.name; + m_scene = m_object.scene == null ? "null" : m_object.scene.name; + + //var listComps = new Il2CppSystem.Collections.Generic.List(); + //m_object.GetComponents(listComps); + //m_components = listComps.ToArray(); + + var list = new List(); + for (int i = 0; i < m_object.transform.childCount; i++) + { + list.Add(m_object.transform.GetChild(i)); + } + m_children = list.ToArray(); + } + + public override void Update() + { + if (!m_object && !GetObjectAsGameObject()) + { + MelonLogger.Log("Object is null! Destroying window..."); + DestroyWindow(); + } + } + + private void InspectGameObject(GameObject obj) + { + var window = WindowManager.InspectObject(obj, out bool created); + + if (created) + { + window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height); + DestroyWindow(); + } + } + + private void ReflectObject(Il2CppSystem.Object obj) + { + var window = WindowManager.InspectObject(obj, out bool created); + + if (created) + { + if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100)) + { + window.m_rect = new Rect( + this.m_rect.x + this.m_rect.width + 20, + this.m_rect.y, + 550, + 700); + } + else + { + window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700); + } + } + } + + public override void WindowFunction(int windowID) + { + Header(); + + GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box); + + scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Scene: " + (m_scene == "" ? "n/a" : m_scene) + "", null); + if (m_scene == CppExplorer.ActiveSceneName) + { + if (GUILayout.Button("< View in Scene Explorer", new GUILayoutOption[] { GUILayout.Width(230) })) + { + ScenePage.Instance.SetTransformTarget(m_object); + MainMenu.SetCurrentPage(0); + } + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) }); + string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform); + if (m_object.transform.parent != null) + { + if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) })) + { + InspectGameObject(m_object.transform.parent.gameObject); + } + } + GUILayout.TextArea(pathlabel, null); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) }); + GUILayout.TextArea(m_name, null); + GUILayout.EndHorizontal(); + + // --- Horizontal Columns section --- + GUILayout.BeginHorizontal(null); + + GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) }); + TransformList(); + GUILayout.EndVertical(); + + GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) }); + ComponentList(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); // end horiz columns + + GameObjectControls(); + + GUILayout.EndScrollView(); + + m_rect = WindowManager.ResizeWindow(m_rect, windowID); + + GUILayout.EndArea(); + } + + private void TransformList() + { + GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) }); + m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView); + + GUILayout.Label("Children:", null); + if (m_children != null && m_children.Length > 0) + { + foreach (var obj in m_children.Where(x => x.childCount > 0)) + { + if (!obj) + { + GUILayout.Label("null", null); + continue; + } + UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60); + } + foreach (var obj in m_children.Where(x => x.childCount == 0)) + { + if (!obj) + { + GUILayout.Label("null", null); + continue; + } + UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60); + } + } + else + { + GUILayout.Label("None", null); + } + + GUILayout.EndScrollView(); + GUILayout.EndVertical(); + } + + + private void ComponentList() + { + GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) }); + m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView); + GUILayout.Label("Components", null); + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + if (m_cachedDestroyList.Count > 0) + { + m_cachedDestroyList.Clear(); + } + + var m_components = new Il2CppSystem.Collections.Generic.List(); + m_object.GetComponentsInternal(Il2CppType.Of(), false, false, true, false, m_components); + + var ilTypeOfTransform = Il2CppType.Of(); + var ilTypeOfBehaviour = Il2CppType.Of(); + foreach (var component in m_components) + { + var ilType = component.GetIl2CppType(); + if (ilType == ilTypeOfTransform) + { + continue; + } + + GUILayout.BeginHorizontal(null); + if (ilTypeOfBehaviour.IsAssignableFrom(ilType)) + { + BehaviourEnabledBtn(component.TryCast()); + } + if (GUILayout.Button("" + ilType.Name + "", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) })) + { + ReflectObject(component); + } + if (GUILayout.Button("-", new GUILayoutOption[] { GUILayout.Width(20) })) + { + m_cachedDestroyList.Add(component); + } + GUILayout.EndHorizontal(); + } + + + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + if (m_cachedDestroyList.Count > 0) + { + for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--) + { + var comp = m_cachedDestroyList[i]; + GameObject.Destroy(comp); + } + } + + GUILayout.EndScrollView(); + + //GUILayout.BeginHorizontal(null); + //m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 150) }); + //if (GUILayout.Button("Add Component", new GUILayoutOption[] { GUILayout.Width(120) })) + //{ + // if (HPExplorer.GetType(m_addComponentInput) is Type type && typeof(Component).IsAssignableFrom(type)) + // { + // var comp = m_object.AddComponent(type); + // var list = m_components.ToList(); + // list.Add(comp); + // m_components = list.ToArray(); + // } + // else + // { + // MelonLogger.LogWarning($"Could not get type '{m_addComponentInput}'. If it's not a typo, try the fully qualified name."); + // } + //} + //GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + } + + private void BehaviourEnabledBtn(Behaviour obj) + { + var _col = GUI.color; + bool _enabled = obj.enabled; + if (_enabled) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.red; + } + + // ------ toggle active button ------ + + _enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) }); + if (obj.enabled != _enabled) + { + obj.enabled = _enabled; + } + GUI.color = _col; + } + + private void GameObjectControls() + { + GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) }); + GUILayout.Label("GameObject Controls", null); + + GUILayout.BeginHorizontal(null); + bool m_active = m_object.activeSelf; + m_active = GUILayout.Toggle(m_active, (m_active ? "Enabled " : "Disabled") + "", + new GUILayoutOption[] { GUILayout.Width(80) }); + if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); } + + UIStyles.InstantiateButton(m_object, 100); + + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + + if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) })) + { + m_object.transform.parent = null; + } + m_setParentInput = GUILayout.TextField(m_setParentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width - 280) }); + if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) })) + { + if (GameObject.Find(m_setParentInput) is GameObject newparent) + { + m_object.transform.parent = newparent.transform; + } + else + { + MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'"); + } + } + GUILayout.EndHorizontal(); + + GUILayout.BeginVertical(GUI.skin.box, null); + + var t = m_object.transform; + TranslateControl(t, TranslateType.Position, ref m_translateAmount, false); + TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true); + TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false); + + GUILayout.EndVertical(); + + if (GUILayout.Button("Destroy", null)) + { + GameObject.Destroy(m_object); + DestroyWindow(); + return; + } + + GUILayout.EndVertical(); + } + + public enum TranslateType + { + Position, + Rotation, + Scale + } + + private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime) + { + GUILayout.BeginHorizontal(null); + GUILayout.Label("" + mode + ":", new GUILayoutOption[] { GUILayout.Width(65) }); + + Vector3 vector = Vector3.zero; + switch (mode) + { + case TranslateType.Position: vector = transform.localPosition; break; + case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break; + case TranslateType.Scale: vector = transform.localScale; break; + } + GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) }); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUI.skin.label.alignment = TextAnchor.MiddleRight; + + GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref vector.x, amount, multByTime); + + GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref vector.y, amount, multByTime); + + GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref vector.z, amount, multByTime); + + switch (mode) + { + case TranslateType.Position: transform.localPosition = vector; break; + case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break; + case TranslateType.Scale: transform.localScale = vector; break; + } + + GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) }); + var input = amount.ToString("F3"); + input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) }); + if (float.TryParse(input, out float f)) + { + amount = f; + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + GUILayout.EndHorizontal(); + } + + private void PlusMinusFloat(ref float f, float amount, bool multByTime) + { + string s = f.ToString("F3"); + s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) }); + if (float.TryParse(s, out float f2)) + { + f = f2; + } + if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) })) + { + f -= multByTime ? amount * Time.deltaTime : amount; + } + if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) })) + { + f += multByTime ? amount * Time.deltaTime : amount; + } + } + + + } +} diff --git a/src_2018/Inspectors/ReflectionWindow.cs b/src_2018/Inspectors/ReflectionWindow.cs new file mode 100644 index 0000000..0d1ba66 --- /dev/null +++ b/src_2018/Inspectors/ReflectionWindow.cs @@ -0,0 +1,552 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MelonLoader; +using Mono.CSharp; +using UnhollowerBaseLib; +using UnityEngine; + +namespace Explorer +{ + public class ReflectionWindow : WindowManager.UIWindow + { + public override Il2CppSystem.String Name { get => "Object Reflection"; set => Name = value; } + + public Type m_objectType; + public object m_object; + + private List m_FieldInfos; + private List m_PropertyInfos; + + private bool m_autoUpdate = false; + private string m_search = ""; + public MemberFilter m_filter = MemberFilter.Property; + + public enum MemberFilter + { + Both, + Property, + Field + } + + public Type GetActualType(object m_object) + { + if (m_object is Il2CppSystem.Object ilObject) + { + var iltype = ilObject.GetIl2CppType(); + return Type.GetType(iltype.AssemblyQualifiedName); + } + else + { + return m_object.GetType(); + } + } + + public Type[] GetAllBaseTypes(object m_object) + { + var list = new List(); + + if (m_object is Il2CppSystem.Object ilObject) + { + var ilType = ilObject.GetIl2CppType(); + if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged) + { + list.Add(ilTypeToManaged); + + while (ilType.BaseType != null) + { + ilType = ilType.BaseType; + if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged) + { + list.Add(ilBaseTypeToManaged); + } + } + } + } + else + { + var type = m_object.GetType(); + list.Add(type); + while (type.BaseType != null) + { + type = type.BaseType; + list.Add(type); + } + } + + return list.ToArray(); + } + + public override void Init() + { + m_object = Target; + + m_FieldInfos = new List(); + m_PropertyInfos = new List(); + + var type = GetActualType(m_object); + if (type == null) + { + MelonLogger.Log("could not get underlying type for object. ToString(): " + m_object.ToString()); + return; + } + + m_objectType = type; + GetFields(m_object); + GetProperties(m_object); + + UpdateValues(); + } + + public override void Update() + { + if (m_autoUpdate) + { + UpdateValues(); + } + } + + private void UpdateValues() + { + if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field) + { + foreach (var holder in this.m_FieldInfos) + { + if (m_search == "" || holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower())) + { + holder.UpdateValue(m_object); + } + } + } + + if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property) + { + foreach (var holder in this.m_PropertyInfos) + { + if (m_search == "" || holder.propInfo.Name.ToLower().Contains(m_search.ToLower())) + { + holder.UpdateValue(m_object); + } + } + } + } + + public override void WindowFunction(int windowID) + { + try + { + Header(); + + GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Type: " + m_objectType.Name + "", null); + + bool unityObj = m_object is UnityEngine.Object; + + if (unityObj) + { + GUILayout.Label("Name: " + (m_object as UnityEngine.Object).name, null); + } + GUILayout.EndHorizontal(); + + if (unityObj) + { + GUILayout.BeginHorizontal(null); + + GUILayout.Label("Tools:", new GUILayoutOption[] { GUILayout.Width(80) }); + + UIStyles.InstantiateButton((UnityEngine.Object)m_object); + + if (m_object is Component comp && comp.gameObject is GameObject obj) + { + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("GameObject:", null); + if (GUILayout.Button("" + obj.name + "", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) })) + { + WindowManager.InspectObject(obj, out bool _); + } + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } + + GUILayout.EndHorizontal(); + } + + UIStyles.HorizontalLine(Color.grey); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Search:", new GUILayoutOption[] { GUILayout.Width(75) }); + m_search = GUILayout.TextField(m_search, null); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Filter:", new GUILayoutOption[] { GUILayout.Width(75) }); + FilterToggle(MemberFilter.Both, "Both"); + FilterToggle(MemberFilter.Property, "Properties"); + FilterToggle(MemberFilter.Field, "Fields"); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Values:", new GUILayoutOption[] { GUILayout.Width(75) }); + if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) })) + { + UpdateValues(); + } + GUI.color = m_autoUpdate ? Color.green : Color.red; + m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) }); + GUI.color = Color.white; + GUILayout.EndHorizontal(); + + GUILayout.Space(10); + + scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); + + GUILayout.Space(10); + + if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field) + { + UIStyles.HorizontalLine(Color.grey); + + GUILayout.Label("Fields", null); + + foreach (var holder in this.m_FieldInfos) + { + if (m_search != "" && !holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower())) + { + continue; + } + + GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); + holder.Draw(this); + GUILayout.EndHorizontal(); + } + } + + if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property) + { + UIStyles.HorizontalLine(Color.grey); + + GUILayout.Label("Properties", null); + + foreach (var holder in this.m_PropertyInfos) + { + if (m_search != "" && !holder.propInfo.Name.ToLower().Contains(m_search.ToLower())) + { + continue; + } + + GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); + holder.Draw(this); + GUILayout.EndHorizontal(); + } + } + + GUILayout.EndScrollView(); + + m_rect = WindowManager.ResizeWindow(m_rect, windowID); + + GUILayout.EndArea(); + } + catch (Exception e) + { + MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message); + DestroyWindow(); + return; + } + } + + private void FilterToggle(MemberFilter mode, string label) + { + if (m_filter == mode) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.white; + } + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) + { + m_filter = mode; + } + GUI.color = Color.white; + } + + public static bool IsList(Type t) + { + return t.IsGenericType + && t.GetGenericTypeDefinition() is Type typeDef + && (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))); + } + + private void GetProperties(object m_object, List names = null) + { + if (names == null) + { + names = new List(); + } + + var types = GetAllBaseTypes(m_object); + + foreach (var type in types) + { + foreach (var pi in type.GetProperties(At.flags)) + { + if (pi.Name == "Il2CppType") + { + continue; + } + + if (names.Contains(pi.Name)) + { + continue; + } + names.Add(pi.Name); + + var piHolder = new PropertyInfoHolder(type, pi); + m_PropertyInfos.Add(piHolder); + } + } + } + + private void GetFields(object m_object, List names = null) + { + if (names == null) + { + names = new List(); + } + + var types = GetAllBaseTypes(m_object); + + foreach (var type in types) + { + foreach (var fi in type.GetFields(At.flags)) + { + if (names.Contains(fi.Name)) + { + continue; + } + names.Add(fi.Name); + + var fiHolder = new FieldInfoHolder(type, fi); + m_FieldInfos.Add(fiHolder); + } + } + } + + + /* ********************* + * PROPERTYINFO HOLDER + */ + + public class PropertyInfoHolder + { + public Type classType; + public PropertyInfo propInfo; + public object m_value; + + public PropertyInfoHolder(Type _type, PropertyInfo _propInfo) + { + classType = _type; + propInfo = _propInfo; + } + + public void Draw(ReflectionWindow window) + { + if (propInfo.CanWrite) + { + UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object, SetValue); + } + else + { + UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object); + } + } + + public void UpdateValue(object obj) + { + try + { + if (obj is Il2CppSystem.Object ilObject) + { + var declaringType = this.propInfo.DeclaringType; + if (declaringType == typeof(Il2CppObjectBase)) + { + m_value = ilObject.Pointer; + } + else + { + var cast = CppExplorer.Il2CppCast(obj, declaringType); + m_value = this.propInfo.GetValue(cast, null); + } + } + else + { + m_value = this.propInfo.GetValue(obj, null); + } + } + catch (Exception e) + { + //MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name); + //MelonLogger.Log(e.GetType() + ", " + e.Message); + + //var inner = e.InnerException; + //while (inner != null) + //{ + // MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message); + // inner = inner.InnerException; + //} + + m_value = null; + } + } + + public void SetValue(object obj) + { + try + { + if (propInfo.PropertyType.IsEnum) + { + if (System.Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null) + { + m_value = enumValue; + } + } + else if (propInfo.PropertyType.IsPrimitive) + { + if (propInfo.PropertyType == typeof(float)) + { + if (float.TryParse(m_value.ToString(), out float f)) + { + m_value = f; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!"); + } + } + else if (propInfo.PropertyType == typeof(double)) + { + if (double.TryParse(m_value.ToString(), out double d)) + { + m_value = d; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!"); + } + } + else if (propInfo.PropertyType != typeof(bool)) + { + if (int.TryParse(m_value.ToString(), out int i)) + { + m_value = i; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + propInfo.PropertyType); + } + } + } + + var declaring = propInfo.DeclaringType; + var cast = CppExplorer.Il2CppCast(obj, declaring); + + propInfo.SetValue(propInfo.GetAccessors()[0].IsStatic ? null : cast, m_value, null); + } + catch + { + //MelonLogger.Log("Exception trying to set property " + this.propInfo.Name); + } + } + } + + + /* ********************* + * FIELDINFO HOLDER + */ + + public class FieldInfoHolder + { + public Type classType; + public FieldInfo fieldInfo; + public object m_value; + + public FieldInfoHolder(Type _type, FieldInfo _fieldInfo) + { + classType = _type; + fieldInfo = _fieldInfo; + } + + public void UpdateValue(object obj) + { + m_value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj); + } + + public void Draw(ReflectionWindow window) + { + bool canSet = !(fieldInfo.IsLiteral && !fieldInfo.IsInitOnly); + + if (canSet) + { + UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object, SetValue); + } + else + { + UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object); + } + } + + public void SetValue(object obj) + { + if (fieldInfo.FieldType.IsEnum) + { + if (System.Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null) + { + m_value = enumValue; + } + } + else if (fieldInfo.FieldType.IsPrimitive) + { + if (fieldInfo.FieldType == typeof(float)) + { + if (float.TryParse(m_value.ToString(), out float f)) + { + m_value = f; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!"); + } + } + else if (fieldInfo.FieldType == typeof(double)) + { + if (double.TryParse(m_value.ToString(), out double d)) + { + m_value = d; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!"); + } + } + else if (fieldInfo.FieldType != typeof(bool)) + { + if (int.TryParse(m_value.ToString(), out int i)) + { + m_value = i; + } + else + { + MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + fieldInfo.FieldType); + } + } + } + + fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value); + } + } + } +} diff --git a/src_2018/MainMenu/MainMenu.cs b/src_2018/MainMenu/MainMenu.cs new file mode 100644 index 0000000..7488cef --- /dev/null +++ b/src_2018/MainMenu/MainMenu.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.EventSystems; +using MelonLoader; + +namespace Explorer +{ + public class MainMenu + { + public static MainMenu Instance; + + public MainMenu() + { + Instance = this; + + Pages.Add(new ScenePage()); + Pages.Add(new SearchPage()); + Pages.Add(new ConsolePage()); + + foreach (var page in Pages) + { + page.Init(); + } + } + + public const int MainWindowID = 10; + public static Rect MainRect = new Rect(5, 5, 550, 700); + private static readonly List Pages = new List(); + private static int m_currentPage = 0; + + public static void SetCurrentPage(int index) + { + if (index < 0 || Pages.Count <= index) + { + MelonLogger.Log("cannot set page " + index); + return; + } + m_currentPage = index; + GUI.BringWindowToFront(MainWindowID); + GUI.FocusWindow(MainWindowID); + } + + public void Update() + { + Pages[m_currentPage].Update(); + } + + public void OnGUI() + { + if (CppExplorer.ShowMenu) + { + var origSkin = GUI.skin; + GUI.skin = UIStyles.WindowSkin; + + MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, "IL2CPP Runtime Explorer"); + + GUI.skin = origSkin; + } + } + + private void MainWindow(int id) + { + GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20)); + + if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), "Hide (F7)")) + { + CppExplorer.ShowMenu = false; + return; + } + + GUILayout.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box); + + MainHeader(); + + var page = Pages[m_currentPage]; + page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView); + page.DrawWindow(); + GUILayout.EndScrollView(); + + MainRect = WindowManager.ResizeWindow(MainRect, MainWindowID); + + GUILayout.EndArea(); + } + + private void MainHeader() + { + GUILayout.BeginHorizontal(null); + GUILayout.Label("Options:", new GUILayoutOption[] { GUILayout.Width(70) }); + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("Array Limit:", new GUILayoutOption[] { GUILayout.Width(70) }); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + var _input = GUILayout.TextField(CppExplorer.ArrayLimit.ToString(), new GUILayoutOption[] { GUILayout.Width(60) }); + if (int.TryParse(_input, out int _lim)) + { + CppExplorer.ArrayLimit = _lim; + } + CppExplorer.Instance.MouseInspect = GUILayout.Toggle(CppExplorer.Instance.MouseInspect, "Inspect Under Mouse (Shift + RMB)", null); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + for (int i = 0; i < Pages.Count; i++) + { + if (m_currentPage == i) + GUI.color = Color.green; + else + GUI.color = Color.white; + + if (GUILayout.Button(Pages[i].Name, null)) + { + m_currentPage = i; + } + } + GUILayout.EndHorizontal(); + + GUILayout.Space(10); + GUI.color = Color.white; + } + + public abstract class WindowPage + { + public virtual string Name { get; set; } + + public Vector2 scroll = Vector2.zero; + + public abstract void Init(); + + public abstract void DrawWindow(); + + public abstract void Update(); + } + } +} diff --git a/src_2018/MainMenu/Pages/Console/REPL.cs b/src_2018/MainMenu/Pages/Console/REPL.cs new file mode 100644 index 0000000..97ab678 --- /dev/null +++ b/src_2018/MainMenu/Pages/Console/REPL.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Mono.CSharp; +using UnityEngine; +using Attribute = System.Attribute; +using Object = UnityEngine.Object; + +namespace Explorer +{ + public class REPL : InteractiveBase + { + static REPL() + { + var go = new GameObject("UnityREPL"); + GameObject.DontDestroyOnLoad(go); + //go.transform.parent = HPExplorer.Instance.transform; + MB = go.AddComponent(); + } + + [Documentation("MB - A dummy MonoBehaviour for accessing Unity.")] + public static ReplHelper MB { get; } + + [Documentation("find() - find a UnityEngine.Object of type T.")] + public static T find() where T : Object + { + return MB.Find(); + } + + [Documentation("findAll() - find all UnityEngine.Object of type T.")] + public static T[] findAll() where T : Object + { + return MB.FindAll(); + } + + [Documentation("runCoroutine(enumerator) - runs an IEnumerator as a Unity coroutine.")] + public static object runCoroutine(IEnumerator i) + { + return MB.RunCoroutine(i); + } + + [Documentation("endCoroutine(co) - ends a Unity coroutine.")] + public static void endCoroutine(Coroutine c) + { + MB.EndCoroutine(c); + } + + ////[Documentation("type() - obtain type info about a type T. Provides some Reflection helpers.")] + ////public static TypeHelper type() + ////{ + //// return new TypeHelper(typeof(T)); + ////} + + ////[Documentation("type(obj) - obtain type info about object obj. Provides some Reflection helpers.")] + ////public static TypeHelper type(object instance) + ////{ + //// return new TypeHelper(instance); + ////} + + //[Documentation("dir(obj) - lists all available methods and fiels of a given obj.")] + //public static string dir(object instance) + //{ + // return type(instance).info(); + //} + + //[Documentation("dir() - lists all available methods and fields of type T.")] + //public static string dir() + //{ + // return type().info(); + //} + + //[Documentation("findrefs(obj) - find references to the object in currently loaded components.")] + //public static Component[] findrefs(object obj) + //{ + // if (obj == null) throw new ArgumentNullException(nameof(obj)); + + // var results = new List(); + // foreach (var component in Object.FindObjectsOfType()) + // { + // var type = component.GetType(); + + // var nameBlacklist = new[] { "parent", "parentInternal", "root", "transform", "gameObject" }; + // var typeBlacklist = new[] { typeof(bool) }; + + // foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + // .Where(x => x.CanRead && !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.PropertyType))) + // { + // try + // { + // if (Equals(prop.GetValue(component, null), obj)) + // { + // results.Add(component); + // goto finish; + // } + // } + // catch { } + // } + // foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + // .Where(x => !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.FieldType))) + // { + // try + // { + // if (Equals(field.GetValue(component), obj)) + // { + // results.Add(component); + // goto finish; + // } + // } + // catch { } + // } + // finish:; + // } + + // return results.ToArray(); + //} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + private class DocumentationAttribute : Attribute + { + public DocumentationAttribute(string doc) + { + Docs = doc; + } + + public string Docs { get; } + } + } +} \ No newline at end of file diff --git a/src_2018/MainMenu/Pages/Console/REPLHelper.cs b/src_2018/MainMenu/Pages/Console/REPLHelper.cs new file mode 100644 index 0000000..d228761 --- /dev/null +++ b/src_2018/MainMenu/Pages/Console/REPLHelper.cs @@ -0,0 +1,34 @@ +using System.Collections; +//using Il2CppSystem; +using MelonLoader; +using UnityEngine; +using System; +using Object = UnityEngine.Object; + +namespace Explorer +{ + public class ReplHelper : MonoBehaviour + { + public ReplHelper(IntPtr intPtr) : base(intPtr) { } + + public T Find() where T : Object + { + return FindObjectOfType(); + } + + public T[] FindAll() where T : Object + { + return FindObjectsOfType(); + } + + public object RunCoroutine(IEnumerator enumerator) + { + return MelonCoroutines.Start(enumerator); + } + + public void EndCoroutine(Coroutine c) + { + StopCoroutine(c); + } + } +} \ No newline at end of file diff --git a/src_2018/MainMenu/Pages/ConsolePage.cs b/src_2018/MainMenu/Pages/ConsolePage.cs new file mode 100644 index 0000000..ac583ed --- /dev/null +++ b/src_2018/MainMenu/Pages/ConsolePage.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using System.Reflection; +using Mono.CSharp; +using System.IO; +using MelonLoader; + +namespace Explorer +{ + public class ConsolePage : MainMenu.WindowPage + { + public override string Name { get => "Console"; set => base.Name = value; } + + private ScriptEvaluator _evaluator; + private readonly StringBuilder _sb = new StringBuilder(); + + private string MethodInput = ""; + private string UsingInput = ""; + + public static List UsingDirectives; + + private static readonly string[] m_defaultUsing = new string[] + { + "System", + "UnityEngine", + "System.Linq", + "System.Collections", + "System.Collections.Generic", + "System.Reflection", + "MelonLoader" + }; + + public override void Init() + { + UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp(); + + try + { + MethodInput = @"// This is a basic REPL console used to execute a method. +// Some common directives are added by default, you can add more below. +// If you want to return some output, MelonLogger.Log() it. + +MelonLogger.Log(""hello world"");"; + + ResetConsole(); + } + catch (Exception e) + { + MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}\r\nStack: {e.StackTrace}"); + } + } + + public void ResetConsole() + { + if (_evaluator != null) + { + _evaluator.Dispose(); + } + + _evaluator = new ScriptEvaluator(new StringWriter(_sb)) { InteractiveBaseClass = typeof(REPL) }; + + UsingDirectives = new List(); + UsingDirectives.AddRange(m_defaultUsing); + foreach (string asm in UsingDirectives) + { + Evaluate(AsmToUsing(asm)); + } + } + + public string AsmToUsing(string asm, bool richtext = false) + { + if (richtext) + { + return $"using {asm};"; + } + return $"using {asm};"; + } + + public void AddUsing(string asm) + { + if (!UsingDirectives.Contains(asm)) + { + UsingDirectives.Add(asm); + Evaluate(AsmToUsing(asm)); + } + } + + public object Evaluate(string str) + { + object ret = VoidType.Value; + + _evaluator.Compile(str, out var compiled); + + try + { + compiled?.Invoke(ref ret); + } + catch (Exception e) + { + MelonLogger.LogWarning(e.ToString()); + } + + return ret; + } + + + public override void DrawWindow() + { + GUILayout.Label("REPL Console", null); + + GUILayout.Label("Method:", null); + MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(300) }); + + if (GUILayout.Button("Execute", null)) + { + try + { + MethodInput = MethodInput.Trim(); + + if (!string.IsNullOrEmpty(MethodInput)) + { + var result = Evaluate(MethodInput); + + if (result != null && !Equals(result, VoidType.Value)) + { + MelonLogger.Log("[Console Output]\r\n" + result.ToString()); + } + } + } + catch (Exception e) + { + MelonLogger.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace); + } + } + + GUILayout.Label("Using directives:", null); + foreach (var asm in UsingDirectives) + { + GUILayout.Label(AsmToUsing(asm, true), null); + } + GUILayout.BeginHorizontal(null); + GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) }); + UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) }); + if (GUILayout.Button("Add", new GUILayoutOption[] { GUILayout.Width(50) })) + { + AddUsing(UsingInput); + } + if (GUILayout.Button("Reset", null)) + { + ResetConsole(); + } + GUILayout.EndHorizontal(); + } + + public override void Update() { } + + private class VoidType + { + public static readonly VoidType Value = new VoidType(); + private VoidType() { } + } + + } + + internal class ScriptEvaluator : Evaluator, IDisposable + { + private static readonly HashSet StdLib = + new HashSet(StringComparer.InvariantCultureIgnoreCase) { "mscorlib", "System.Core", "System", "System.Xml" }; + + private readonly TextWriter _logger; + + public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger)) + { + _logger = logger; + + ImportAppdomainAssemblies(ReferenceAssembly); + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; + } + + public void Dispose() + { + AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; + _logger.Dispose(); + } + + private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) + { + string name = args.LoadedAssembly.GetName().Name; + if (StdLib.Contains(name)) + return; + ReferenceAssembly(args.LoadedAssembly); + } + + private static CompilerContext BuildContext(TextWriter tw) + { + var reporter = new StreamReportPrinter(tw); + + var settings = new CompilerSettings + { + Version = LanguageVersion.Experimental, + GenerateDebugInfo = false, + StdLib = true, + Target = Target.Library, + WarningLevel = 0, + EnhancedWarnings = false + }; + + return new CompilerContext(settings, reporter); + } + + private static void ImportAppdomainAssemblies(Action import) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + string name = assembly.GetName().Name; + if (StdLib.Contains(name)) + continue; + import(assembly); + } + } + } +} diff --git a/src_2018/MainMenu/Pages/ScenePage.cs b/src_2018/MainMenu/Pages/ScenePage.cs new file mode 100644 index 0000000..59fc7c7 --- /dev/null +++ b/src_2018/MainMenu/Pages/ScenePage.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MelonLoader; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Explorer +{ + public class ScenePage : MainMenu.WindowPage + { + public static ScenePage Instance; + + public override string Name { get => "Scene Explorer"; set => base.Name = value; } + + // ----- Holders for GUI elements ----- // + + private string m_currentScene = ""; + + // gameobject list + private Transform m_currentTransform; + private List m_objectList = new List(); + + // search bar + private bool m_searching = false; + private string m_searchInput = ""; + private List m_searchResults = new List(); + + // ------------ Init and Update ------------ // + + public override void Init() + { + Instance = this; + } + + public void OnSceneChange() + { + m_currentScene = CppExplorer.ActiveSceneName; + + m_currentTransform = null; + CancelSearch(); + + } + + public override void Update() + { + if (!m_searching) + { + m_objectList = new List(); + if (m_currentTransform) + { + var noChildren = new List(); + for (int i = 0; i < m_currentTransform.childCount; i++) + { + var child = m_currentTransform.GetChild(i); + + if (child) + { + if (child.childCount > 0) + m_objectList.Add(child.gameObject); + else + noChildren.Add(child.gameObject); + } + } + m_objectList.AddRange(noChildren); + noChildren = null; + } + else + { + var scene = SceneManager.GetActiveScene(); + var rootObjects = scene.GetRootGameObjects(); + + // add objects with children first + foreach (var obj in rootObjects.Where(x => x.transform.childCount > 0)) + { + m_objectList.Add(obj); + } + foreach (var obj in rootObjects.Where(x => x.transform.childCount == 0)) + { + m_objectList.Add(obj); + } + } + } + } + + // --------- GUI Draw Functions --------- // + + public override void DrawWindow() + { + try + { + // Current Scene label + GUILayout.Label("Current Scene: " + m_currentScene + "", null); + + // ----- 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(); + } + GUILayout.EndHorizontal(); + + GUILayout.Space(15); + + // ************** GameObject list *************** + + // ----- main explorer ------ + if (!m_searching) + { + if (m_currentTransform != null) + { + GUILayout.BeginHorizontal(null); + if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) })) + { + TraverseUp(); + } + else + { + GUILayout.Label(CppExplorer.GetGameObjectPath(m_currentTransform), null); + } + GUILayout.EndHorizontal(); + } + else + { + GUILayout.Label("Scene Root GameObjects:", null); + } + + if (m_objectList.Count > 0) + { + foreach (var obj in m_objectList) + { + UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170); + } + } + else + { + // if m_currentTransform != null ... + } + } + else // ------ Scene Search results ------ + { + if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) })) + { + CancelSearch(); + } + + GUILayout.Label("Search Results:", null); + + if (m_searchResults.Count > 0) + { + foreach (var obj in m_searchResults) + { + UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170); + } + } + else + { + GUILayout.Label("No results found!", null); + } + } + } + catch + { + m_currentTransform = null; + } + } + + + + // -------- Actual Methods (not drawing GUI) ---------- // + + public void SetTransformTarget(GameObject obj) + { + m_currentTransform = obj.transform; + CancelSearch(); + } + + public void TraverseUp() + { + if (m_currentTransform.parent != null) + { + m_currentTransform = m_currentTransform.parent; + } + else + { + m_currentTransform = null; + } + } + + public void Search() + { + m_searchResults = SearchSceneObjects(m_searchInput); + m_searching = true; + } + + public void CancelSearch() + { + m_searching = false; + } + + public List SearchSceneObjects(string _search) + { + var matches = new List(); + + foreach (var obj in Resources.FindObjectsOfTypeAll()) + { + if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == CppExplorer.ActiveSceneName) + { + matches.Add(obj); + } + } + + return matches; + } + } +} diff --git a/src_2018/MainMenu/Pages/SearchPage.cs b/src_2018/MainMenu/Pages/SearchPage.cs new file mode 100644 index 0000000..603de38 --- /dev/null +++ b/src_2018/MainMenu/Pages/SearchPage.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using System.Reflection; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; +using UnhollowerRuntimeLib; +using MelonLoader; +using UnhollowerBaseLib; + +namespace Explorer +{ + public class SearchPage : MainMenu.WindowPage + { + public static SearchPage Instance; + + public override string Name { get => "Advanced Search"; set => base.Name = value; } + + private string m_searchInput = ""; + private string m_typeInput = ""; + private int m_limit = 100; + + public SceneFilter SceneMode = SceneFilter.Any; + public TypeFilter TypeMode = TypeFilter.Object; + + public enum SceneFilter + { + Any, + This, + DontDestroy, + None + } + + public enum TypeFilter + { + Object, + GameObject, + Component, + Custom + } + + private List m_searchResults = new List(); + private Vector2 resultsScroll = Vector2.zero; + + public override void Init() + { + Instance = this; + } + + public void OnSceneChange() + { + m_searchResults.Clear(); + } + + public override void Update() + { + } + + public override void DrawWindow() + { + try + { + // helpers + GUILayout.BeginHorizontal(GUI.skin.box, null); + GUILayout.Label("Helpers", new GUILayoutOption[] { GUILayout.Width(70) }); + if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) })) + { + m_searchResults = GetInstanceClassScanner().ToList(); + } + GUILayout.EndHorizontal(); + + // search box + SearchBox(); + + // results + GUILayout.BeginVertical(GUI.skin.box, null); + + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label("Results", null); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + + resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView); + + var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height); + + if (m_searchResults.Count > 0) + { + for (int i = 0; i < m_searchResults.Count; i++) + { + var obj = m_searchResults[i]; + + UIStyles.DrawValue(ref obj, _temprect); + } + } + else + { + GUILayout.Label("No results found!", null); + } + + GUILayout.EndScrollView(); + + GUILayout.EndVertical(); + } + catch + { + m_searchResults.Clear(); + } + } + + private void SearchBox() + { + GUILayout.BeginVertical(GUI.skin.box, null); + + // ----- GameObject Search ----- + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label("Search", null); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + + GUILayout.BeginHorizontal(null); + + 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("Result limit:", new GUILayoutOption[] { GUILayout.Width(100) }); + 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); + + GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) }); + ClassFilterToggle(TypeFilter.Object, "Object"); + ClassFilterToggle(TypeFilter.GameObject, "GameObject"); + ClassFilterToggle(TypeFilter.Component, "Component"); + ClassFilterToggle(TypeFilter.Custom, "Custom"); + GUILayout.EndHorizontal(); + if (TypeMode == TypeFilter.Custom) + { + GUILayout.BeginHorizontal(null); + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) }); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + m_typeInput = GUILayout.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) }); + GUILayout.EndHorizontal(); + } + + GUILayout.BeginHorizontal(null); + GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) }); + SceneFilterToggle(SceneFilter.Any, "Any", 60); + SceneFilterToggle(SceneFilter.This, "This Scene", 100); + SceneFilterToggle(SceneFilter.DontDestroy, "DontDestroyOnLoad", 140); + SceneFilterToggle(SceneFilter.None, "No Scene", 80); + GUILayout.EndHorizontal(); + + if (GUILayout.Button("Search", null)) + { + Search(); + } + + GUILayout.EndVertical(); + } + + private void ClassFilterToggle(TypeFilter mode, string label) + { + if (TypeMode == mode) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.white; + } + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) + { + TypeMode = mode; + } + GUI.color = Color.white; + } + + private void SceneFilterToggle(SceneFilter mode, string label, float width) + { + if (SceneMode == mode) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.white; + } + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width) })) + { + SceneMode = mode; + } + GUI.color = Color.white; + } + + + // -------------- ACTUAL METHODS (not Gui draw) ----------------- // + + // credit: ManlyMarco (RuntimeUnityEditor) + public static IEnumerable GetInstanceClassScanner() + { + var query = AppDomain.CurrentDomain.GetAssemblies() + .Where(x => !x.FullName.StartsWith("Mono")) + .SelectMany(GetTypesSafe) + .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); + + foreach (var type in query) + { + object obj = null; + try + { + obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null); + } + catch + { + try + { + obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null); + } + catch + { + } + } + if (obj != null && !obj.ToString().StartsWith("Mono")) + { + yield return obj; + } + } + } + + public static IEnumerable GetTypesSafe(Assembly asm) + { + try { return asm.GetTypes(); } + catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); } + catch { return Enumerable.Empty(); } + } + + // ======= search functions ======= + + private void Search() + { + m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput); + } + + private List FindAllObjectsOfType(string _search, string _type) + { + Il2CppSystem.Type type = null; + + if (TypeMode == TypeFilter.Custom) + { + try + { + var findType = CppExplorer.GetType(_type); + type = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName); + MelonLogger.Log("Got type: " + type.AssemblyQualifiedName); + } + catch (Exception e) + { + MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace); + } + } + else if (TypeMode == TypeFilter.Object) + { + type = Il2CppType.Of(); + } + else if (TypeMode == TypeFilter.GameObject) + { + type = Il2CppType.Of(); + } + else if (TypeMode == TypeFilter.Component) + { + type = Il2CppType.Of(); + } + + if (!Il2CppType.Of().IsAssignableFrom(type)) + { + MelonLogger.LogError("Your Class Type must inherit from UnityEngine.Object! Leave blank to default to UnityEngine.Object"); + return new List(); + } + + var matches = new List(); + int added = 0; + + //MelonLogger.Log("Trying to get IL Type. ASM name: " + type.Assembly.GetName().Name + ", Namespace: " + type.Namespace + ", name: " + type.Name); + + //var asmName = type.Assembly.GetName().Name; + //if (asmName.Contains("UnityEngine")) + //{ + // asmName = "UnityEngine"; + //} + + //var intPtr = IL2CPP.GetIl2CppClass(asmName, type.Namespace, type.Name); + //var ilType = Il2CppType.TypeFromPointer(intPtr); + + foreach (var obj in Resources.FindObjectsOfTypeAll(type)) + { + if (added == m_limit) + { + break; + } + + if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower())) + { + continue; + } + + if (SceneMode != SceneFilter.Any) + { + if (SceneMode == SceneFilter.None) + { + if (!NoSceneFilter(obj, obj.GetType())) + { + continue; + } + } + else + { + GameObject go; + + var objtype = obj.GetType(); + if (objtype == typeof(GameObject)) + { + go = obj as GameObject; + } + else if (typeof(Component).IsAssignableFrom(objtype)) + { + go = (obj as Component).gameObject; + } + else { continue; } + + if (!go) { continue; } + + if (SceneMode == SceneFilter.This) + { + if (go.scene.name != CppExplorer.ActiveSceneName || go.scene.name == "DontDestroyOnLoad") + { + continue; + } + } + else if (SceneMode == SceneFilter.DontDestroy) + { + if (go.scene.name != "DontDestroyOnLoad") + { + continue; + } + } + } + } + + if (!matches.Contains(obj)) + { + matches.Add(obj); + added++; + } + } + + return matches; + } + + public static bool ThisSceneFilter(object obj, Type type) + { + if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type)) + { + var go = obj as GameObject ?? (obj as Component).gameObject; + + if (go != null && go.scene.name == CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad") + { + return true; + } + } + + return false; + } + + public static bool DontDestroyFilter(object obj, Type type) + { + if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type)) + { + var go = obj as GameObject ?? (obj as Component).gameObject; + + if (go != null && go.scene.name == "DontDestroyOnLoad") + { + return true; + } + } + + return false; + } + + public static bool NoSceneFilter(object obj, Type type) + { + if (type == typeof(GameObject)) + { + var go = obj as GameObject; + + if (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad") + { + return true; + } + else + { + return false; + } + } + else if (typeof(Component).IsAssignableFrom(type)) + { + var go = (obj as Component).gameObject; + + if (go == null || (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")) + { + return true; + } + else + { + return false; + } + } + else + { + return true; + } + } + } +} diff --git a/src_2018/Properties/AssemblyInfo.cs b/src_2018/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..38d40b3 --- /dev/null +++ b/src_2018/Properties/AssemblyInfo.cs @@ -0,0 +1,41 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Explorer; +using MelonLoader; + +[assembly: MelonInfo(typeof(CppExplorer), CppExplorer.NAME, CppExplorer.VERSION, CppExplorer.AUTHOR)] +[assembly: MelonGame(null, null)] + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle(CppExplorer.NAME)] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany(CppExplorer.AUTHOR)] +[assembly: AssemblyProduct(CppExplorer.NAME)] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b21dbde3-5d6f-4726-93ab-cc3cc68bae7d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src_2018/UIStyles.cs b/src_2018/UIStyles.cs new file mode 100644 index 0000000..a605092 --- /dev/null +++ b/src_2018/UIStyles.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Il2CppSystem.Collections; +using Il2CppSystem.Reflection; +using MelonLoader; +using UnhollowerBaseLib; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Explorer +{ + public class UIStyles + { + public static GUISkin WindowSkin + { + get + { + if (_customSkin == null) + { + try + { + _customSkin = CreateWindowSkin(); + } + catch + { + _customSkin = GUI.skin; + } + } + + return _customSkin; + } + } + + public static void HorizontalLine(Color color) + { + var c = GUI.color; + GUI.color = color; + GUILayout.Box(GUIContent.none, HorizontalBar, null); + GUI.color = c; + } + + private static GUISkin _customSkin; + + public static Texture2D m_nofocusTex; + public static Texture2D m_focusTex; + + private static GUIStyle _horizBarStyle; + + private static GUIStyle HorizontalBar + { + get + { + if (_horizBarStyle == null) + { + _horizBarStyle = new GUIStyle(); + _horizBarStyle.normal.background = Texture2D.whiteTexture; + _horizBarStyle.margin = new RectOffset(0, 0, 4, 4); + _horizBarStyle.fixedHeight = 2; + } + + return _horizBarStyle; + } + } + + private static GUISkin CreateWindowSkin() + { + var newSkin = Object.Instantiate(GUI.skin); + Object.DontDestroyOnLoad(newSkin); + + m_nofocusTex = MakeTex(550, 700, new Color(0.1f, 0.1f, 0.1f, 0.7f)); + m_focusTex = MakeTex(550, 700, new Color(0.3f, 0.3f, 0.3f, 1f)); + + newSkin.window.normal.background = m_nofocusTex; + newSkin.window.onNormal.background = m_focusTex; + + newSkin.box.normal.textColor = Color.white; + newSkin.window.normal.textColor = Color.white; + newSkin.button.normal.textColor = Color.white; + newSkin.textField.normal.textColor = Color.white; + newSkin.label.normal.textColor = Color.white; + + return newSkin; + } + + public static Texture2D MakeTex(int width, int height, Color col) + { + Color[] pix = new Color[width * height]; + for (int i = 0; i < pix.Length; ++i) + { + pix[i] = col; + } + Texture2D result = new Texture2D(width, height); + result.SetPixels(pix); + result.Apply(); + return result; + } + + // *********************************** METHODS FOR DRAWING VALUES IN GUI ************************************ + + // helper for "Instantiate" button on UnityEngine.Objects + public static void InstantiateButton(Object obj, float width = 100) + { + if (GUILayout.Button("Instantiate", new GUILayoutOption[] { GUILayout.Width(width) })) + { + var newobj = Object.Instantiate(obj); + + WindowManager.InspectObject(newobj, out _); + } + } + + // helper for drawing a styled button for a GameObject or Transform + public static void GameobjButton(GameObject obj, Action specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380) + { + if (obj == null) + { + GUILayout.Label("null", null); + return; + } + + bool enabled = obj.activeSelf; + bool children = obj.transform.childCount > 0; + + GUILayout.BeginHorizontal(null); + GUI.skin.button.alignment = TextAnchor.UpperLeft; + + // ------ build name ------ + + string label = children ? "[" + obj.transform.childCount + " children] " : ""; + label += obj.name; + + // ------ Color ------- + + if (enabled) + { + if (children) + { + GUI.color = Color.green; + } + else + { + GUI.color = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f); + } + } + else + { + GUI.color = Color.red; + } + + // ------ toggle active button ------ + + enabled = GUILayout.Toggle(enabled, "", new GUILayoutOption[] { GUILayout.Width(18) }); + if (obj.activeSelf != enabled) + { + obj.SetActive(enabled); + } + + // ------- actual button --------- + + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Height(22), GUILayout.Width(width) })) + { + if (specialInspectMethod != null) + { + specialInspectMethod(obj); + } + else + { + WindowManager.InspectObject(obj, out bool _); + } + } + + // ------ small "Inspect" button on the right ------ + + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + GUI.color = Color.white; + + if (showSmallInspectBtn) + { + if (GUILayout.Button("Inspect", null)) + { + WindowManager.InspectObject(obj, out bool _); + } + } + + GUILayout.EndHorizontal(); + } + + public static void DrawMember(ref object value, string valueType, string memberName, Rect rect, object setTarget = null, Action setAction = null, float labelWidth = 180, bool autoSet = false) + { + GUILayout.Label("" + memberName + ":", new GUILayoutOption[] { GUILayout.Width(labelWidth) }); + + DrawValue(ref value, rect, valueType, memberName, setTarget, setAction, autoSet); + } + + public static void DrawValue(ref object value, Rect rect, string nullValueType = null, string memberName = null, object setTarget = null, Action setAction = null, bool autoSet = false) + { + if (value == null) + { + GUILayout.Label("null (" + nullValueType + ")", null); + } + else + { + var valueType = value.GetType(); + if (valueType.IsPrimitive || value.GetType() == typeof(string)) + { + DrawPrimitive(ref value, rect, setTarget, setAction); + } + else if (valueType == typeof(GameObject) || valueType == typeof(Transform)) + { + GameObject go; + if (value.GetType() == typeof(Transform)) + { + go = (value as Transform).gameObject; + } + else + { + go = (value as GameObject); + } + + UIStyles.GameobjButton(go, null, false, rect.width - 250); + } + else if (valueType.IsEnum) + { + if (setAction != null) + { + if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) })) + { + SetEnum(ref value, -1); + setAction.Invoke(setTarget); + } + if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) })) + { + SetEnum(ref value, 1); + setAction.Invoke(setTarget); + } + } + + GUILayout.Label(value.ToString(), null); + } + else if (value is System.Collections.IEnumerable || ReflectionWindow.IsList(valueType)) + { + System.Collections.IEnumerable enumerable; + + if (value is System.Collections.IEnumerable isEnumerable) + { + enumerable = isEnumerable; + } + else + { + var listValueType = value.GetType().GetGenericArguments()[0]; + var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType }); + var method = listType.GetMethod("ToArray"); + enumerable = (System.Collections.IEnumerable)method.Invoke(value, new object[0]); + } + + int count = enumerable.Cast().Count(); + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + string btnLabel = "[" + count + "] " + valueType + ""; + if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) })) + { + WindowManager.InspectObject(value, out bool _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + var enumerator = enumerable.GetEnumerator(); + if (enumerator != null) + { + int i = 0; + while (enumerator.MoveNext()) + { + var obj = enumerator.Current; + + //collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(null); + GUILayout.Space(190); + + if (i > CppExplorer.ArrayLimit) + { + GUILayout.Label($"{count - CppExplorer.ArrayLimit} results omitted, array is too long!", null); + break; + } + + if (obj == null) + { + GUILayout.Label("null", null); + } + else + { + var type = obj.GetType(); + var lbl = i + ": " + obj.ToString() + ""; + + if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) + { + GUILayout.Label(lbl, null); + } + else + { + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + if (GUILayout.Button(lbl, null)) + { + WindowManager.InspectObject(obj, out _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + } + //var type = obj.GetType(); + //DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true); + } + + i++; + } + } + } + else + { + var label = value.ToString(); + + if (valueType == typeof(Object)) + { + label = (value as Object).name; + } + else if (value is Vector4 vec4) + { + label = vec4.ToString(); + } + else if (value is Vector3 vec3) + { + label = vec3.ToString(); + } + else if (value is Vector2 vec2) + { + label = vec2.ToString(); + } + else if (value is Rect rec) + { + label = rec.ToString(); + } + else if (value is Matrix4x4 matrix) + { + label = matrix.ToString(); + } + else if (value is Color col) + { + label = col.ToString(); + } + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + if (GUILayout.Button("" + label + "", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) })) + { + WindowManager.InspectObject(value, out bool _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + } + } + } + + // Helper for drawing primitive values (with Apply button) + + public static void DrawPrimitive(ref object value, Rect m_rect, object setTarget = null, Action setAction = null, bool autoSet = false) + { + bool allowSet = setAction != null; + + if (value.GetType() == typeof(bool)) + { + bool b = (bool)value; + var color = "" : "red>"); + + if (allowSet) + { + value = GUILayout.Toggle((bool)value, color + value.ToString() + "", null); + + if (b != (bool)value) + { + setAction.Invoke(setTarget); + } + } + } + else + { + if (value.ToString().Length > 37) + { + value = GUILayout.TextArea(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) }); + } + else + { + value = GUILayout.TextField(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) }); + } + + if (autoSet || (allowSet && GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(60) }))) + { + setAction.Invoke(setTarget); + } + } + } + + // Helper for setting an enum + + public static void SetEnum(ref object value, int change) + { + var type = value.GetType(); + var names = Enum.GetNames(type).ToList(); + + int newindex = names.IndexOf(value.ToString()) + change; + + if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count)) + { + value = Enum.Parse(type, names[newindex]); + } + } + } +} diff --git a/src_2018/WindowManager.cs b/src_2018/WindowManager.cs new file mode 100644 index 0000000..5878065 --- /dev/null +++ b/src_2018/WindowManager.cs @@ -0,0 +1,258 @@ +using System; +using System.CodeDom; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Harmony; +using MelonLoader; +using UnhollowerBaseLib; +using UnhollowerRuntimeLib; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.EventSystems; + +namespace Explorer +{ + public class WindowManager + { + public static WindowManager Instance; + + public static List Windows = new List(); + public static int CurrentWindowID { get; set; } = 500000; + private static Rect m_lastWindowRect; + + public WindowManager() + { + Instance = this; + } + + public void Update() + { + foreach (var window in Windows) + { + window.Update(); + } + } + + public void OnGUI() + { + foreach (var window in Windows) + { + window.OnGUI(); + } + } + + // ========= Public Helpers ========= + + public static bool IsMouseInWindow + { + get + { + if (!CppExplorer.ShowMenu) + { + return false; + } + + foreach (var window in Windows) + { + if (RectContainsMouse(window.m_rect)) + { + return true; + } + } + return RectContainsMouse(MainMenu.MainRect); + } + } + + private static bool RectContainsMouse(Rect rect) + { + return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)); + } + + public static int NextWindowID() + { + return CurrentWindowID++; + } + + public static Rect GetNewWindowRect() + { + return GetNewWindowRect(ref m_lastWindowRect); + } + + public static Rect GetNewWindowRect(ref Rect lastRect) + { + Rect rect = new Rect(0, 0, 550, 700); + + var mainrect = MainMenu.MainRect; + if (mainrect.x <= (Screen.width - mainrect.width - 100)) + { + rect = new Rect(mainrect.x + mainrect.width + 20, mainrect.y, rect.width, rect.height); + } + + if (lastRect.x == rect.x) + { + rect = new Rect(rect.x + 25, rect.y + 25, rect.width, rect.height); + } + + lastRect = rect; + + return rect; + } + + public static UIWindow InspectObject(object obj, out bool createdNew) + { + createdNew = false; + + foreach (var window in Windows) + { + if (obj == window.Target) + { + GUI.BringWindowToFront(window.windowID); + GUI.FocusWindow(window.windowID); + return window; + } + } + + createdNew = true; + if (obj is GameObject || obj is Transform) + { + return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject); + } + else + { + return InspectReflection(obj); + } + } + + private static UIWindow InspectGameObject(GameObject obj) + { + var new_window = UIWindow.CreateWindow(obj); + GUI.FocusWindow(new_window.windowID); + + return new_window; + } + + public static UIWindow InspectReflection(object obj) + { + var new_window = UIWindow.CreateWindow(obj); + GUI.FocusWindow(new_window.windowID); + + return new_window; + } + + // ============= Resize Window Helper ============ + + static readonly GUIContent gcDrag = new GUIContent("<->"); + + private static bool isResizing = false; + private static Rect m_currentResize; + private static int m_currentWindow; + + public static Rect ResizeWindow(Rect _rect, int ID) + { + //GUILayout.BeginHorizontal(null); + //GUILayout.Space(_rect.width - 35); + + //GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Width(25), GUILayout.Height(25) }); + + //var r = GUILayoutUtility.GetLastRect(); + + //Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)); + + //if (r.Contains(mouse) && Input.GetMouseButtonDown(0)) + //{ + // isResizing = true; + // m_currentWindow = ID; + // m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height); + //} + //else if (!Input.GetMouseButton(0)) + //{ + // isResizing = false; + //} + + //if (isResizing && ID == m_currentWindow) + //{ + // _rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x)); + // _rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y)); + // _rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x + // _rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y + //} + + //GUILayout.EndHorizontal(); + + return _rect; + } + + // ============ GENERATED WINDOW HOLDER ============ + + public abstract class UIWindow + { + public abstract Il2CppSystem.String Name { get; set; } + + public object Target; + + public int windowID; + public Rect m_rect = new Rect(0, 0, 550, 700); + + public Vector2 scroll = Vector2.zero; + + public static UIWindow CreateWindow(object target) where T: UIWindow + { + //var component = (UIWindow)AddToGameObject(Instance.gameObject); + var component = Activator.CreateInstance(); + + component.Target = target; + component.windowID = NextWindowID(); + component.m_rect = GetNewWindowRect(); + + Windows.Add(component); + + component.Init(); + + return component; + } + + public void DestroyWindow() + { + try + { + Windows.Remove(this); + } + catch (Exception e) + { + MelonLogger.Log("Exception removing Window from WindowManager.Windows list!"); + MelonLogger.Log($"{e.GetType()} : {e.Message}\r\n{e.StackTrace}"); + } + //Destroy(this); + } + + public abstract void Init(); + public abstract void WindowFunction(int windowID); + public abstract void Update(); + + public void OnGUI() + { + if (CppExplorer.ShowMenu) + { + var origSkin = GUI.skin; + + GUI.skin = UIStyles.WindowSkin; + m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Name); + + GUI.skin = origSkin; + } + } + + public void Header() + { + GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20)); + + if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "X")) + { + DestroyWindow(); + return; + } + } + } + } +} diff --git a/src_2018/utils/AccessTools.cs b/src_2018/utils/AccessTools.cs new file mode 100644 index 0000000..3d6ef49 --- /dev/null +++ b/src_2018/utils/AccessTools.cs @@ -0,0 +1,65 @@ +using System; +using System.Reflection; + +namespace Explorer +{ + /// + /// AccessTools + /// Some helpers for Reflection (GetValue, SetValue, Call, InheritBaseValues) + /// + public static class At + { + public static Il2CppSystem.Reflection.BindingFlags ilFlags = Il2CppSystem.Reflection.BindingFlags.Public + | Il2CppSystem.Reflection.BindingFlags.NonPublic + | Il2CppSystem.Reflection.BindingFlags.Instance + | Il2CppSystem.Reflection.BindingFlags.Static; + + public static BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static; + + //reflection call + public static object Call(object obj, string method, params object[] args) + { + var methodInfo = obj.GetType().GetMethod(method, flags); + if (methodInfo != null) + { + return methodInfo.Invoke(obj, args); + } + return null; + } + + // set value + public static void SetValue(T value, Type type, object obj, string field) + { + FieldInfo fieldInfo = type.GetField(field, flags); + if (fieldInfo != null) + { + fieldInfo.SetValue(obj, value); + } + } + + // get value + public static object GetValue(Type type, object obj, string value) + { + FieldInfo fieldInfo = type.GetField(value, flags); + if (fieldInfo != null) + { + return fieldInfo.GetValue(obj); + } + else + { + return null; + } + } + + // inherit base values + public static void InheritBaseValues(object _derived, object _base) + { + foreach (FieldInfo fi in _base.GetType().GetFields(flags)) + { + try { _derived.GetType().GetField(fi.Name).SetValue(_derived, fi.GetValue(_base)); } catch { } + } + + return; + } + } +}