diff --git a/src/CachedObjects/CacheMethod.cs b/src/CachedObjects/CacheMethod.cs index 14659e6..6cb9b6a 100644 --- a/src/CachedObjects/CacheMethod.cs +++ b/src/CachedObjects/CacheMethod.cs @@ -16,6 +16,10 @@ namespace Explorer private bool m_evaluated = false; private CacheObjectBase m_cachedReturnValue; + private bool m_isEvaluating; + private ParameterInfo[] m_arguments; + private string[] m_argumentInput; + public bool HasParameters { get @@ -29,13 +33,6 @@ namespace Explorer } private bool? m_hasParams; - // ======= TODO ======= - private bool m_isEvaluating; - private string[] m_argumentNames; - private Type[] m_argumentTypes; - private string[] m_argumentInput; - // ===================== - public static bool CanEvaluate(MethodInfo mi) { // generic type args not supported yet @@ -44,21 +41,15 @@ namespace Explorer return false; } - // TODO primitive params (commented out impl below) - if (mi.GetParameters().Length > 0) + // only primitive and string args supported + foreach (var param in mi.GetParameters()) { - return false; + if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string)) + { + return false; + } } - //// only primitive and string args supported - //foreach (var param in mi.GetParameters()) - //{ - // if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string)) - // { - // return false; - // } - //} - return true; } @@ -66,69 +57,68 @@ namespace Explorer { base.Init(); - // TODO cache params + var mi = MemberInfo as MethodInfo; + + m_arguments = mi.GetParameters(); + m_argumentInput = new string[m_arguments.Length]; } public override void UpdateValue() { base.UpdateValue(); - - // TODO update params (?) - } - - private void Evaluate() - { - m_evaluated = true; - - var mi = MemberInfo as MethodInfo; - - object ret; - - if (!HasParameters) - { - ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]); - } - else - { - // TODO parse params, invoke if valid - throw new NotImplementedException("TODO"); - } - - if (ret != null) - { - m_cachedReturnValue = GetCacheObject(ret); - if (m_cachedReturnValue is CacheList cacheList) - { - cacheList.WhiteSpace = 0f; - cacheList.ButtonWidthOffset += 70f; - } - m_cachedReturnValue.UpdateValue(); - } - else - { - m_cachedReturnValue = null; - } - } + } public override void DrawValue(Rect window, float width) { GUILayout.BeginVertical(null); - GUILayout.BeginHorizontal(null); - if (GUILayout.Button("Evaluate", new GUILayoutOption[] { GUILayout.Width(70) })) + string evaluateLabel = "Evaluate"; + if (HasParameters) { - if (HasParameters) + if (m_isEvaluating) { - throw new NotImplementedException("TODO"); + for (int i = 0; i < m_arguments.Length; i++) + { + var name = m_arguments[i].Name; + var input = m_argumentInput[i]; + var type = m_arguments[i].ParameterType.Name; + + GUILayout.BeginHorizontal(null); + m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) }); + GUILayout.Label(i + ": " + name + " (" + type + ")", null); + + GUILayout.EndHorizontal(); + } + + GUILayout.BeginHorizontal(null); + if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) })) + { + Evaluate(); + m_isEvaluating = false; + } + if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) })) + { + m_isEvaluating = false; + } } else + { + GUILayout.BeginHorizontal(null); + if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) })) + { + m_isEvaluating = true; + } + } + } + else + { + GUILayout.BeginHorizontal(null); + if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) })) { Evaluate(); } } - GUI.skin.label.wordWrap = false; - GUILayout.Label($"{ValueType}", null); - GUI.skin.label.wordWrap = true; + GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(null); @@ -148,16 +138,88 @@ namespace Explorer } else { - GUILayout.Label($"null", null); + GUILayout.Label($"null ({ValueType})", null); } } else { - GUILayout.Label($"Not yet evaluated", null); + GUILayout.Label($"Not yet evaluated ({ValueType})", null); } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } + + private void Evaluate() + { + m_evaluated = true; + + var mi = MemberInfo as MethodInfo; + + object ret = null; + + if (!HasParameters) + { + ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]); + } + else + { + var arguments = new List(); + for (int i = 0; i < m_arguments.Length; i++) + { + var input = m_argumentInput[i]; + var type = m_arguments[i].ParameterType; + + if (type == typeof(string)) + { + arguments.Add(input); + } + else + { + try + { + if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed) + { + arguments.Add(parsed); + } + else + { + throw new Exception(); + } + + } + catch + { + MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'"); + break; + } + } + } + + if (arguments.Count == m_arguments.Length) + { + ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, arguments.ToArray()); + } + else + { + MelonLogger.Log($"Did not invoke because {m_arguments.Length - arguments.Count} arguments could not be parsed!"); + } + } + + if (ret != null) + { + m_cachedReturnValue = GetCacheObject(ret); + if (m_cachedReturnValue is CacheList cacheList) + { + cacheList.WhiteSpace = 0f; + cacheList.ButtonWidthOffset += 70f; + } + m_cachedReturnValue.UpdateValue(); + } + else + { + m_cachedReturnValue = null; + } + } } } diff --git a/src/Helpers/UIStyles.cs b/src/Helpers/UIStyles.cs index a0286de..c02c91d 100644 --- a/src/Helpers/UIStyles.cs +++ b/src/Helpers/UIStyles.cs @@ -69,8 +69,8 @@ namespace Explorer 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)); + m_nofocusTex = MakeTex(1, 1, new Color(0.1f, 0.1f, 0.1f, 0.7f)); + m_focusTex = MakeTex(1, 1, new Color(0.3f, 0.3f, 0.3f, 1f)); newSkin.window.normal.background = m_nofocusTex; newSkin.window.onNormal.background = m_focusTex; diff --git a/src/Windows/GameObjectWindow.cs b/src/Windows/GameObjectWindow.cs index 40043bd..702bfd0 100644 --- a/src/Windows/GameObjectWindow.cs +++ b/src/Windows/GameObjectWindow.cs @@ -12,8 +12,8 @@ namespace Explorer public class GameObjectWindow : UIWindow { public override string Title => WindowManager.TabView - ? m_object.name - : $"GameObject Inspector ({m_object.name})"; + ? $"[G] {m_object.name}" + : $"GameObject Inspector ({m_object.name})"; public GameObject m_object; diff --git a/src/Windows/ReflectionWindow.cs b/src/Windows/ReflectionWindow.cs index 30bef67..5cca87c 100644 --- a/src/Windows/ReflectionWindow.cs +++ b/src/Windows/ReflectionWindow.cs @@ -13,13 +13,13 @@ namespace Explorer public class ReflectionWindow : UIWindow { public override string Title => WindowManager.TabView - ? ObjectType.Name - : $"Reflection Inspector ({ObjectType.Name})"; + ? $"[R] {ObjectType.Name}" + : $"Reflection Inspector ({ObjectType.Name})"; public Type ObjectType; - private CacheObjectBase[] m_cachedMembers; - private CacheObjectBase[] m_cachedMemberFiltered; + private CacheObjectBase[] m_allCachedMembers; + private CacheObjectBase[] m_cachedMembersFiltered; private int m_pageOffset; private int m_limitPerPage = 20; @@ -72,7 +72,7 @@ namespace Explorer public override void Update() { - m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray(); + m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray(); if (m_autoUpdate) { @@ -82,7 +82,7 @@ namespace Explorer private void UpdateValues() { - foreach (var member in m_cachedMemberFiltered) + foreach (var member in m_cachedMembersFiltered) { member.UpdateValue(); } @@ -140,7 +140,7 @@ namespace Explorer { if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method) { - if (member.Name.Contains("Il2CppType") || member.Name.StartsWith("get_")) + if (member.Name.Contains("Il2CppType") || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) continue; try @@ -165,7 +165,7 @@ namespace Explorer } } - m_cachedMembers = list.ToArray(); + m_allCachedMembers = list.ToArray(); } // =========== GUI DRAW =========== // @@ -174,6 +174,8 @@ namespace Explorer { try { + // ====== HEADER ====== + var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect; if (!WindowManager.TabView) @@ -241,18 +243,17 @@ namespace Explorer GUILayout.Space(10); - // prev/next page buttons GUILayout.BeginHorizontal(null); GUILayout.Label("Limit per page:", new GUILayoutOption[] { GUILayout.Width(125) }); var limitString = m_limitPerPage.ToString(); limitString = GUILayout.TextField(limitString, new GUILayoutOption[] { GUILayout.Width(60) }); - if (int.TryParse(limitString, out int i)) + if (int.TryParse(limitString, out int lim)) { - m_limitPerPage = i; + m_limitPerPage = lim; } - int count = m_cachedMemberFiltered.Length; + int count = m_cachedMembersFiltered.Length; if (count > m_limitPerPage) { int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limitPerPage)) - 1; @@ -274,12 +275,47 @@ namespace Explorer } GUILayout.EndHorizontal(); + // ====== BODY ====== + scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); GUILayout.Space(10); - DrawMembers(this.m_cachedMemberFiltered); + UIStyles.HorizontalLine(Color.grey); + GUILayout.BeginVertical(GUI.skin.box, null); + + int index = 0; + var members = this.m_cachedMembersFiltered; + int offsetIndex = (m_pageOffset * m_limitPerPage) + index; + + if (offsetIndex >= count) + { + int maxOffset = (int)Mathf.Ceil((float)(m_cachedMembersFiltered.Length / (decimal)m_limitPerPage)) - 1; + if (m_pageOffset > maxOffset) + { + m_pageOffset = 0; + } + } + + for (int j = offsetIndex; (j < offsetIndex + m_limitPerPage && j < members.Length); j++) + { + var holder = members[j]; + + GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); + + try + { + holder.Draw(rect, 180f); + } + catch { } + + GUILayout.EndHorizontal(); + + index++; + } + + GUILayout.EndVertical(); GUILayout.EndScrollView(); if (!WindowManager.TabView) @@ -289,69 +325,21 @@ namespace Explorer GUILayout.EndArea(); } } + catch (Il2CppException e) + { + if (!e.Message.Contains("in a group with only")) + { + throw; + } + } catch (Exception e) { - MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message); + MelonLogger.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message); DestroyWindow(); return; } } - private void DrawMembers(CacheObjectBase[] members) - { - // todo pre-cache list based on current search, otherwise this doesnt work. - - int i = 0; - DrawMembersInternal("Properties", MemberTypes.Property, members, ref i); - DrawMembersInternal("Fields", MemberTypes.Field, members, ref i); - DrawMembersInternal("Methods", MemberTypes.Method, members, ref i); - } - - private void DrawMembersInternal(string title, MemberTypes filter, CacheObjectBase[] members, ref int index) - { - if (m_filter != filter && m_filter != MemberTypes.All) - { - return; - } - - var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect; - - UIStyles.HorizontalLine(Color.grey); - - GUILayout.Label($"{title}", null); - - int offset = (m_pageOffset * m_limitPerPage) + index; - - if (offset >= m_cachedMemberFiltered.Length) - { - m_pageOffset = 0; - offset = 0; - } - - for (int j = offset; j < offset + m_limitPerPage && j < members.Length; j++) - { - var holder = members[j]; - - if (holder.MemberInfoType != filter || !ShouldProcessMember(holder)) continue; - - GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); - try - { - holder.Draw(rect, 180f); - } - catch // (Exception e) - { - //MelonLogger.Log("Exception drawing member " + holder.MemberInfo.Name); - //MelonLogger.Log(e.GetType() + ", " + e.Message); - //MelonLogger.Log(e.StackTrace); - } - GUILayout.EndHorizontal(); - - index++; - if (index >= m_limitPerPage) break; - } - } - private void FilterToggle(MemberTypes mode, string label) { if (m_filter == mode) @@ -365,6 +353,7 @@ namespace Explorer if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) { m_filter = mode; + m_pageOffset = 0; } GUI.color = Color.white; } diff --git a/src/Windows/ResizeDrag.cs b/src/Windows/ResizeDrag.cs index e1d0f7d..ae67ab3 100644 --- a/src/Windows/ResizeDrag.cs +++ b/src/Windows/ResizeDrag.cs @@ -20,55 +20,85 @@ namespace Explorer public static Rect ResizeWindow(Rect _rect, int ID) { - if (RESIZE_FAILED) return _rect; + if (!RESIZE_FAILED) + { + var origRect = _rect; - var origRect = _rect; + try + { + GUILayout.BeginHorizontal(GUI.skin.box, null); - try + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) }); + + 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(); + } + catch (Il2CppException e) when (e.Message.StartsWith("System.ArgumentException")) + { + // suppress + return origRect; + } + catch (Exception e) + { + RESIZE_FAILED = true; + MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message); + return origRect; + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } + else { GUILayout.BeginHorizontal(GUI.skin.box, null); - GUI.skin.label.alignment = TextAnchor.MiddleCenter; - GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) }); + GUILayout.Label("Resize window:", new GUILayoutOption[] { GUILayout.Width(100) }); - 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)) + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("Width:", new GUILayoutOption[] { GUILayout.Width(60) }); + if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) })) { - isResizing = true; - m_currentWindow = ID; - m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height); + _rect.width -= 5f; } - else if (!Input.GetMouseButton(0)) + if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) })) { - isResizing = false; + _rect.width += 5f; } - - if (isResizing && ID == m_currentWindow) + GUILayout.Label("Height:", new GUILayoutOption[] { GUILayout.Width(60) }); + if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) })) { - _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 + _rect.height -= 5f; + } + if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) })) + { + _rect.height += 5f; } GUILayout.EndHorizontal(); + GUI.skin.label.alignment = TextAnchor.UpperLeft; } - catch (Il2CppException e) when (e.Message.StartsWith("System.ArgumentException")) - { - // suppress - return origRect; - } - catch (Exception e) - { - RESIZE_FAILED = true; - MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message); - return origRect; - } - - GUI.skin.label.alignment = TextAnchor.UpperLeft; return _rect; } diff --git a/src/Windows/TabViewWindow.cs b/src/Windows/TabViewWindow.cs index 7008f6d..29ca491 100644 --- a/src/Windows/TabViewWindow.cs +++ b/src/Windows/TabViewWindow.cs @@ -3,17 +3,19 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using MelonLoader; using UnityEngine; namespace Explorer { public class TabViewWindow : UIWindow { - public override string Title => "Tab View"; + public override string Title => $"Tabs ({WindowManager.Windows.Count})"; public static TabViewWindow Instance => m_instance ?? (m_instance = new TabViewWindow()); private static TabViewWindow m_instance; + private UIWindow m_targetWindow; public int TargetTabID = 0; public override bool IsTabViewWindow => true; @@ -24,13 +26,43 @@ namespace Explorer } public override void Init() { } - public override void Update() { } + public override void Update() + { + while (TargetTabID >= WindowManager.Windows.Count) + { + TargetTabID--; + } + + if (TargetTabID == -1 && WindowManager.Windows.Count > 0) + { + TargetTabID = 0; + } + + if (TargetTabID >= 0) + { + m_targetWindow = WindowManager.Windows[TargetTabID]; + } + else + { + m_targetWindow = null; + } + + m_targetWindow?.Update(); + } public override void WindowFunction(int windowID) { try { - Header(); + GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20)); + if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "Close All")) + { + foreach (var window in WindowManager.Windows) + { + window.DestroyWindow(); + } + return; + } GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box); @@ -51,6 +83,7 @@ namespace Explorer bool focused = i == TargetTabID; string color = focused ? "" : ""; + GUI.color = focused ? Color.green : Color.white; var window = WindowManager.Windows[i]; if (GUILayout.Button(color + window.Title + "", new GUILayoutOption[] { GUILayout.Width(200) })) @@ -62,20 +95,12 @@ namespace Explorer window.DestroyWindow(); } } + GUI.color = Color.white; GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUI.skin.button.alignment = TextAnchor.MiddleCenter; - while (TargetTabID >= WindowManager.Windows.Count) - { - TargetTabID--; - } - - if (TargetTabID >= 0) - { - var window = WindowManager.Windows[TargetTabID]; - window.WindowFunction(window.windowID); - } + m_targetWindow.WindowFunction(m_targetWindow.windowID); try { diff --git a/src/Windows/UIWindow.cs b/src/Windows/UIWindow.cs index 9bc6cc0..a6c837a 100644 --- a/src/Windows/UIWindow.cs +++ b/src/Windows/UIWindow.cs @@ -7,8 +7,6 @@ using MelonLoader; using UnhollowerBaseLib; using UnhollowerRuntimeLib; using UnityEngine; -using UnityEngine.Events; -using UnityEngine.EventSystems; namespace Explorer { @@ -46,15 +44,7 @@ namespace Explorer public void DestroyWindow() { - try - { - WindowManager.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}"); - } + WindowManager.DestroyWindow(this); } public void OnGUI() @@ -72,12 +62,10 @@ namespace Explorer public void Header() { - if (!WindowManager.TabView || IsTabViewWindow) - { - GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20)); - } if (!WindowManager.TabView) { + 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(); diff --git a/src/Windows/WindowManager.cs b/src/Windows/WindowManager.cs index 0afc67c..3ec7a57 100644 --- a/src/Windows/WindowManager.cs +++ b/src/Windows/WindowManager.cs @@ -22,19 +22,46 @@ namespace Explorer public static int CurrentWindowID { get; set; } = 500000; private static Rect m_lastWindowRect; + private static readonly List m_windowsToDestroy = new List(); + public WindowManager() { Instance = this; } + public static void DestroyWindow(UIWindow window) + { + m_windowsToDestroy.Add(window); + } + public void Update() { - for (int i = 0; i < Windows.Count; i++) + if (m_windowsToDestroy.Count > 0) { - var window = Windows[i]; - if (window != null) + foreach (var window in m_windowsToDestroy) { - window.Update(); + if (Windows.Contains(window)) + { + Windows.Remove(window); + } + } + + m_windowsToDestroy.Clear(); + } + + if (TabView) + { + TabViewWindow.Instance.Update(); + } + else + { + for (int i = 0; i < Windows.Count; i++) + { + var window = Windows[i]; + if (window != null) + { + window.Update(); + } } } }