diff --git a/src/CSConsole/ConsoleController.cs b/src/CSConsole/ConsoleController.cs index 5bf8f87..11ba457 100644 --- a/src/CSConsole/ConsoleController.cs +++ b/src/CSConsole/ConsoleController.cs @@ -202,7 +202,7 @@ namespace UnityExplorer.CSConsole { // The compiled code was not REPL, so it was a using directive or it defined classes. - string output = ScriptEvaluator._textWriter.ToString(); + string output = Evaluator._textWriter.ToString(); var outputSplit = output.Split('\n'); if (outputSplit.Length >= 2) output = outputSplit[outputSplit.Length - 2]; diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs index 26e05c1..aaa2e7b 100644 --- a/src/Core/Config/ConfigManager.cs +++ b/src/Core/Config/ConfigManager.cs @@ -37,6 +37,7 @@ namespace UnityExplorer.Core.Config public static ConfigElement CSConsoleData; public static ConfigElement OptionsPanelData; public static ConfigElement ConsoleLogData; + public static ConfigElement HookManagerData; internal static readonly Dictionary ConfigElements = new Dictionary(); internal static readonly Dictionary InternalConfigs = new Dictionary(); @@ -126,6 +127,7 @@ namespace UnityExplorer.Core.Config CSConsoleData = new ConfigElement("CSConsole", "", "", true); OptionsPanelData = new ConfigElement("OptionsPanel", "", "", true); ConsoleLogData = new ConfigElement("ConsoleLog", "", "", true); + HookManagerData = new ConfigElement("HookManager", "", "", true); } } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 0b76e7b..c086d69 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -20,7 +20,7 @@ namespace UnityExplorer public static class ExplorerCore { public const string NAME = "UnityExplorer"; - public const string VERSION = "4.2.1"; + public const string VERSION = "4.3.0"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.unityexplorer"; diff --git a/src/Hooks/AddHookCell.cs b/src/Hooks/AddHookCell.cs new file mode 100644 index 0000000..09e3325 --- /dev/null +++ b/src/Hooks/AddHookCell.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.Hooks +{ + public class AddHookCell : ICell + { + public bool Enabled => UIRoot.activeSelf; + + public RectTransform Rect { get; set; } + public GameObject UIRoot { get; set; } + + public float DefaultHeight => 30; + + public Text MethodNameLabel; + public Text HookedLabel; + public ButtonRef HookButton; + + public int CurrentDisplayedIndex; + + private void OnHookClicked() + { + HookManager.Instance.AddHookClicked(CurrentDisplayedIndex); + } + + public void Enable() + { + this.UIRoot.SetActive(true); + } + + public void Disable() + { + this.UIRoot.SetActive(false); + } + + public GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); + Rect = UIRoot.GetComponent(); + UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); + UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green); + UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100); + + HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f)); + UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100); + HookButton.OnClick += OnHookClicked; + + MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999); + + return UIRoot; + } + } +} diff --git a/src/Hooks/HookCell.cs b/src/Hooks/HookCell.cs new file mode 100644 index 0000000..d7133bd --- /dev/null +++ b/src/Hooks/HookCell.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.Hooks +{ + public class HookCell : ICell + { + public bool Enabled => UIRoot.activeSelf; + + public RectTransform Rect { get; set; } + public GameObject UIRoot { get; set; } + + public float DefaultHeight => 30; + + public Text MethodNameLabel; + public ButtonRef EditPatchButton; + public ButtonRef ToggleActiveButton; + public ButtonRef DeleteButton; + + public int CurrentDisplayedIndex; + + private void OnToggleActiveClicked() + { + HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex); + } + + private void OnDeleteClicked() + { + HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex); + } + + private void OnEditPatchClicked() + { + HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex); + } + + public GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); + Rect = UIRoot.GetComponent(); + UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); + UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999); + + ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f)); + UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100); + ToggleActiveButton.OnClick += OnToggleActiveClicked; + + DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100); + DeleteButton.OnClick += OnDeleteClicked; + + EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Log Patch Source", new Color(0.15f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150); + EditPatchButton.OnClick += OnEditPatchClicked; + + return UIRoot; + } + + public void Disable() + { + UIRoot.SetActive(false); + } + + public void Enable() + { + UIRoot.SetActive(true); + } + } +} diff --git a/src/Hooks/HookInstance.cs b/src/Hooks/HookInstance.cs new file mode 100644 index 0000000..105bed9 --- /dev/null +++ b/src/Hooks/HookInstance.cs @@ -0,0 +1,193 @@ +using System; +using System.CodeDom.Compiler; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using HarmonyLib; +using Microsoft.CSharp; +using UnityExplorer.CSConsole; + +namespace UnityExplorer.Hooks +{ + public class HookInstance + { + private static readonly StringBuilder evalOutput = new StringBuilder(); + private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput)); + + // Instance + + public bool Enabled; + public MethodInfo TargetMethod; + public string GeneratedSource; + + private string shortSignature; + private PatchProcessor patchProcessor; + private HarmonyMethod patchDelegate; + private MethodInfo patchDelegateMethodInfo; + + public HookInstance(MethodInfo targetMethod) + { + this.TargetMethod = targetMethod; + this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}"; + GenerateProcessorAndDelegate(); + Patch(); + } + + private void GenerateProcessorAndDelegate() + { + try + { + patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod); + + // Dynamically compile the patch method + + scriptEvaluator.Run(GeneratePatchSourceCode(TargetMethod)); + + // Get the compiled method and check for errors + + string output = scriptEvaluator._textWriter.ToString(); + var outputSplit = output.Split('\n'); + if (outputSplit.Length >= 2) + output = outputSplit[outputSplit.Length - 2]; + evalOutput.Clear(); + if (ScriptEvaluator._reportPrinter.ErrorsCount > 0) + throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}"); + + // Could publicize MCS to avoid this reflection, but not bothering for now + var source = (Mono.CSharp.CompilationSourceFile)ReflectionUtility.GetFieldInfo(typeof(Mono.CSharp.Evaluator), "source_file") + .GetValue(scriptEvaluator); + var type = (Mono.CSharp.Class)source.Containers.Last(); + var systemType = ((Mono.CSharp.TypeSpec)ReflectionUtility.GetPropertyInfo(typeof(Mono.CSharp.TypeDefinition), "Definition") + .GetValue(type, null)) + .GetMetaInfo(); + + this.patchDelegateMethodInfo = systemType.GetMethod("Patch", ReflectionUtility.FLAGS); + + // Actually create the harmony patch + this.patchDelegate = new HarmonyMethod(patchDelegateMethodInfo); + patchProcessor.AddPostfix(patchDelegate); + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}"); + } + } + + private string GeneratePatchSourceCode(MethodInfo targetMethod) + { + var codeBuilder = new StringBuilder(); + + codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}"); + codeBuilder.AppendLine("{"); + + // Arguments + + codeBuilder.Append(" public static void Patch(System.Reflection.MethodBase __originalMethod"); + + if (!targetMethod.IsStatic) + codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance"); + + if (targetMethod.ReturnType != typeof(void)) + codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result"); + + int paramIdx = 0; + var parameters = targetMethod.GetParameters(); + foreach (var param in parameters) + { + codeBuilder.Append($", {param.ParameterType.FullName} __{paramIdx}"); + paramIdx++; + } + + codeBuilder.Append(")\n"); + + // Patch body + + codeBuilder.AppendLine(" {"); + + codeBuilder.AppendLine(" try {"); + + // Log message + + var logMessage = new StringBuilder(); + logMessage.AppendLine($"$@\"Patch called: {shortSignature}"); + + if (!targetMethod.IsStatic) + logMessage.AppendLine("__instance: {__instance.ToString()}"); + + paramIdx = 0; + foreach (var param in parameters) + { + if (param.ParameterType.IsValueType) + logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}.ToString()}}"); + else + logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}?.ToString() ?? \"null\"}}"); + paramIdx++; + } + + if (targetMethod.ReturnType != typeof(void)) + { + if (targetMethod.ReturnType.IsValueType) + logMessage.AppendLine("Return value: {__result.ToString()}"); + else + logMessage.AppendLine("Return value: {__result?.ToString() ?? \"null\"}"); + } + + logMessage.Append('"'); + + codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log({logMessage});"); + codeBuilder.AppendLine(" }"); + codeBuilder.AppendLine(" catch (System.Exception ex) {"); + codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");"); + codeBuilder.AppendLine(" }"); + + // End patch body + + codeBuilder.AppendLine(" }"); + + // End class + + codeBuilder.AppendLine("}"); + + return GeneratedSource = codeBuilder.ToString(); + } + + public void TogglePatch() + { + Enabled = !Enabled; + if (Enabled) + Patch(); + else + Unpatch(); + } + + public void Patch() + { + try + { + patchProcessor.Patch(); + Enabled = true; + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}"); + } + } + + public void Unpatch() + { + if (!Enabled) + return; + + try + { + this.patchProcessor.Unpatch(patchDelegateMethodInfo); + Enabled = false; + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception unpatching method: {ex}"); + } + } + } +} diff --git a/src/Hooks/HookManager.cs b/src/Hooks/HookManager.cs new file mode 100644 index 0000000..25a80ad --- /dev/null +++ b/src/Hooks/HookManager.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; +using System.Text; +using HarmonyLib; +using UnityEngine; +using UnityExplorer.UI; +using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.Hooks +{ + public class HookManager : ICellPoolDataSource, ICellPoolDataSource + { + private static HookManager s_instance; + public static HookManager Instance => s_instance ?? (s_instance = new HookManager()); + + public HookManagerPanel Panel => UIManager.GetPanel(UIManager.Panels.HookManager); + + public int ItemCount => addingMethods ? currentFilteredMethods.Count : currentHooks.Count; + + private bool addingMethods; + private HashSet hookedSignatures = new HashSet(); + private readonly OrderedDictionary currentHooks = new OrderedDictionary(); + private readonly List currentAddEligableMethods = new List(); + private readonly List currentFilteredMethods = new List(); + + public void OnClassSelectedForHooks(string typeFullName) + { + var type = ReflectionUtility.GetTypeByName(typeFullName); + if (type == null) + { + ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!"); + return; + } + + Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true)); + + Panel.ResetMethodFilter(); + currentFilteredMethods.Clear(); + currentAddEligableMethods.Clear(); + foreach (var method in type.GetMethods(ReflectionUtility.FLAGS)) + { + if (method.IsGenericMethod || method.IsAbstract || ReflectionUtility.IsBlacklisted(method)) + continue; + currentAddEligableMethods.Add(method); + currentFilteredMethods.Add(method); + } + + addingMethods = true; + Panel.SetAddPanelActive(true); + Panel.MethodResultsScrollPool.Refresh(true, true); + } + + public void CloseAddHooks() + { + addingMethods = false; + Panel.SetAddPanelActive(false); + Panel.HooksScrollPool.Refresh(true, false); + } + + public void OnHookAllClicked() + { + foreach (var method in currentAddEligableMethods) + AddHook(method); + Panel.MethodResultsScrollPool.Refresh(true, false); + } + + public void AddHookClicked(int index) + { + if (index >= this.currentFilteredMethods.Count) + return; + + AddHook(currentFilteredMethods[index]); + Panel.MethodResultsScrollPool.Refresh(true, false); + } + + public void AddHook(MethodInfo method) + { + var sig = method.FullDescription(); + if (hookedSignatures.Contains(sig)) + return; + + var hook = new HookInstance(method); + if (hook.Enabled) + { + hookedSignatures.Add(sig); + currentHooks.Add(sig, hook); + } + } + + public void EnableOrDisableHookClicked(int index) + { + var hook = (HookInstance)currentHooks[index]; + hook.TogglePatch(); + + Panel.HooksScrollPool.Refresh(true, false); + } + + public void DeleteHookClicked(int index) + { + var hook = (HookInstance)currentHooks[index]; + hook.Unpatch(); + currentHooks.RemoveAt(index); + hookedSignatures.Remove(hook.TargetMethod.FullDescription()); + + Panel.HooksScrollPool.Refresh(true, false); + } + + public void EditPatchClicked(int index) + { + var hook = (HookInstance)currentHooks[index]; + ExplorerCore.Log(hook.GeneratedSource); + } + + public void OnAddHookFilterInputChanged(string input) + { + currentFilteredMethods.Clear(); + + if (string.IsNullOrEmpty(input)) + currentFilteredMethods.AddRange(currentAddEligableMethods); + else + { + foreach (var method in currentAddEligableMethods) + { + if (method.Name.ContainsIgnoreCase(input)) + currentFilteredMethods.Add(method); + } + } + + Panel.MethodResultsScrollPool.Refresh(true, true); + } + + // OnBorrow methods not needed + public void OnCellBorrowed(HookCell cell) { } + public void OnCellBorrowed(AddHookCell cell) { } + + // Set current hook cell + + public void SetCell(HookCell cell, int index) + { + if (index >= this.currentHooks.Count) + { + cell.Disable(); + return; + } + + cell.CurrentDisplayedIndex = index; + var hook = (HookInstance)this.currentHooks[index]; + + cell.MethodNameLabel.text = HighlightMethod(hook.TargetMethod); + + cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled"; + RuntimeProvider.Instance.SetColorBlockAuto(cell.ToggleActiveButton.Component, + hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f)); + } + + // Set eligable method cell + + public void SetCell(AddHookCell cell, int index) + { + if (index >= this.currentFilteredMethods.Count) + { + cell.Disable(); + return; + } + + cell.CurrentDisplayedIndex = index; + var method = this.currentFilteredMethods[index]; + + cell.MethodNameLabel.text = HighlightMethod(method); + + var sig = method.FullDescription(); + if (hookedSignatures.Contains(sig)) + { + cell.HookButton.Component.gameObject.SetActive(false); + cell.HookedLabel.gameObject.SetActive(true); + } + else + { + cell.HookButton.Component.gameObject.SetActive(true); + cell.HookedLabel.gameObject.SetActive(false); + } + } + + // private static readonly string VOID_HIGHLIGHT = $"void "; + + private static readonly Dictionary highlightedMethods = new Dictionary(); + + private string HighlightMethod(MethodInfo method) + { + var sig = method.FullDescription(); + if (highlightedMethods.ContainsKey(sig)) + return highlightedMethods[sig]; + + var sb = new StringBuilder(); + + // declaring type + sb.Append(SignatureHighlighter.Parse(method.DeclaringType, false)); + sb.Append('.'); + + // method name + var color = !method.IsStatic + ? SignatureHighlighter.METHOD_INSTANCE + : SignatureHighlighter.METHOD_STATIC; + sb.Append($"{method.Name}"); + + // arguments + sb.Append('('); + var args = method.GetParameters(); + if (args != null && args.Any()) + { + int i = 0; + foreach (var param in args) + { + sb.Append(SignatureHighlighter.Parse(param.ParameterType, false)); + sb.Append(' '); + sb.Append($"{param.Name}"); + i++; + if (i < args.Length) + sb.Append(", "); + } + } + sb.Append(')'); + + var ret = sb.ToString(); + highlightedMethods.Add(sig, ret); + return ret; + } + } +} diff --git a/src/UI/Panels/HookManagerPanel.cs b/src/UI/Panels/HookManagerPanel.cs new file mode 100644 index 0000000..d6fd5db --- /dev/null +++ b/src/UI/Panels/HookManagerPanel.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.Hooks; +using UnityExplorer.UI.Widgets; +using UnityExplorer.UI.Widgets.AutoComplete; + +namespace UnityExplorer.UI.Panels +{ + public class HookManagerPanel : UIPanel + { + public override UIManager.Panels PanelType => UIManager.Panels.HookManager; + + public override string Name => "Hooks"; + public override int MinWidth => 500; + public override int MinHeight => 600; + public override bool ShowByDefault => false; + + public ScrollPool HooksScrollPool; + public ScrollPool MethodResultsScrollPool; + + private GameObject addHooksPanel; + private GameObject currentHooksPanel; + private InputFieldRef classInputField; + private Text addHooksLabel; + private InputFieldRef methodFilterInput; + + public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value; + + public override void DoSaveToConfigElement() => ConfigManager.HookManagerData.Value = this.ToSaveData(); + + private void OnClassInputAddClicked() + { + HookManager.Instance.OnClassSelectedForHooks(this.classInputField.Text); + } + + public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}"; + + public void SetAddPanelActive(bool show) + { + addHooksPanel.SetActive(show); + currentHooksPanel.SetActive(!show); + } + + public void ResetMethodFilter() => methodFilterInput.Text = string.Empty; + + public override void ConstructPanelContent() + { + // Active hooks scroll pool + + currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content); + UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(currentHooksPanel, true, true, true, true); + + var addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4, + new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999); + + classInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to..."); + UIFactory.SetLayoutElement(classInputField.Component.gameObject, flexibleWidth: 9999); + new TypeCompleter(typeof(object), classInputField, false, false); + + var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks"); + UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25); + addButton.OnClick += OnClassInputAddClicked; + + var hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter); + UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999); + + HooksScrollPool = UIFactory.CreateScrollPool(currentHooksPanel, "HooksScrollPool", + out GameObject hooksScroll, out GameObject hooksContent); + UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999); + HooksScrollPool.Initialize(HookManager.Instance); + + // Add hooks panel + + addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content); + UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(addHooksPanel, true, true, true, true); + + addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter); + UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999); + + var buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5); + UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999); + + var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f)); + UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999); + doneButton.OnClick += HookManager.Instance.CloseAddHooks; + + var patchAllButton = UIFactory.CreateButton(buttonRow, "PatchAllButton", "Hook ALL methods in class", new Color(0.3f, 0.3f, 0.2f)); + UIFactory.SetLayoutElement(patchAllButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999); + patchAllButton.OnClick += HookManager.Instance.OnHookAllClicked; + + methodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names..."); + UIFactory.SetLayoutElement(methodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999); + methodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged; + + MethodResultsScrollPool = UIFactory.CreateScrollPool(addHooksPanel, "MethodAddScrollPool", + out GameObject addScrollRoot, out GameObject addContent); + UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999); + MethodResultsScrollPool.Initialize(HookManager.Instance); + + addHooksPanel.gameObject.SetActive(false); + } + + protected internal override void DoSetDefaultPosAndAnchors() + { + this.Rect.anchorMin = new Vector2(0.5f, 0.5f); + this.Rect.anchorMax = new Vector2(0.5f, 0.5f); + this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth); + this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight); + } + + } +} diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index 6cd9a86..9f0c374 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -29,6 +29,7 @@ namespace UnityExplorer.UI AutoCompleter, MouseInspector, UIInspectorResults, + HookManager, } public enum VerticalAnchor @@ -107,6 +108,7 @@ namespace UnityExplorer.UI UIPanels.Add(Panels.ObjectExplorer, new ObjectExplorerPanel()); UIPanels.Add(Panels.Inspector, new InspectorPanel()); UIPanels.Add(Panels.CSConsole, new CSConsolePanel()); + UIPanels.Add(Panels.HookManager, new HookManagerPanel()); UIPanels.Add(Panels.ConsoleLog, new LogPanel()); UIPanels.Add(Panels.Options, new OptionsPanel()); UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel()); @@ -242,14 +244,14 @@ namespace UnityExplorer.UI NavBarRect.anchorMin = new Vector2(0.5f, 1f); NavBarRect.anchorMax = new Vector2(0.5f, 1f); NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 0); - NavBarRect.sizeDelta = new Vector2(1000f, 35f); + NavBarRect.sizeDelta = new Vector2(1080f, 35f); break; case VerticalAnchor.Bottom: NavBarRect.anchorMin = new Vector2(0.5f, 0f); NavBarRect.anchorMax = new Vector2(0.5f, 0f); NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 35); - NavBarRect.sizeDelta = new Vector2(1000f, 35f); + NavBarRect.sizeDelta = new Vector2(1080f, 35f); break; } } diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 3731919..2017d41 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -215,6 +215,9 @@ + + + @@ -235,6 +238,7 @@ + @@ -269,6 +273,7 @@ +