mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-16 14:17:51 +08:00
Implement HookManager UI and logic
This commit is contained in:
parent
6989ea1b19
commit
fbdb84eefa
@ -202,7 +202,7 @@ namespace UnityExplorer.CSConsole
|
|||||||
{
|
{
|
||||||
// The compiled code was not REPL, so it was a using directive or it defined classes.
|
// 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');
|
var outputSplit = output.Split('\n');
|
||||||
if (outputSplit.Length >= 2)
|
if (outputSplit.Length >= 2)
|
||||||
output = outputSplit[outputSplit.Length - 2];
|
output = outputSplit[outputSplit.Length - 2];
|
||||||
|
@ -37,6 +37,7 @@ namespace UnityExplorer.Core.Config
|
|||||||
public static ConfigElement<string> CSConsoleData;
|
public static ConfigElement<string> CSConsoleData;
|
||||||
public static ConfigElement<string> OptionsPanelData;
|
public static ConfigElement<string> OptionsPanelData;
|
||||||
public static ConfigElement<string> ConsoleLogData;
|
public static ConfigElement<string> ConsoleLogData;
|
||||||
|
public static ConfigElement<string> HookManagerData;
|
||||||
|
|
||||||
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
|
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
|
||||||
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
|
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
|
||||||
@ -126,6 +127,7 @@ namespace UnityExplorer.Core.Config
|
|||||||
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
|
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
|
||||||
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
|
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
|
||||||
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
|
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
|
||||||
|
HookManagerData = new ConfigElement<string>("HookManager", "", "", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ namespace UnityExplorer
|
|||||||
public static class ExplorerCore
|
public static class ExplorerCore
|
||||||
{
|
{
|
||||||
public const string NAME = "UnityExplorer";
|
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 AUTHOR = "Sinai";
|
||||||
public const string GUID = "com.sinai.unityexplorer";
|
public const string GUID = "com.sinai.unityexplorer";
|
||||||
|
|
||||||
|
63
src/Hooks/AddHookCell.cs
Normal file
63
src/Hooks/AddHookCell.cs
Normal file
@ -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<RectTransform>();
|
||||||
|
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft);
|
||||||
|
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
|
||||||
|
UIRoot.AddComponent<ContentSizeFitter>().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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
src/Hooks/HookCell.cs
Normal file
79
src/Hooks/HookCell.cs
Normal file
@ -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<RectTransform>();
|
||||||
|
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
|
||||||
|
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
|
||||||
|
UIRoot.AddComponent<ContentSizeFitter>().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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
193
src/Hooks/HookInstance.cs
Normal file
193
src/Hooks/HookInstance.cs
Normal file
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
src/Hooks/HookManager.cs
Normal file
233
src/Hooks/HookManager.cs
Normal file
@ -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<HookCell>, ICellPoolDataSource<AddHookCell>
|
||||||
|
{
|
||||||
|
private static HookManager s_instance;
|
||||||
|
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
|
||||||
|
|
||||||
|
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
|
||||||
|
|
||||||
|
public int ItemCount => addingMethods ? currentFilteredMethods.Count : currentHooks.Count;
|
||||||
|
|
||||||
|
private bool addingMethods;
|
||||||
|
private HashSet<string> hookedSignatures = new HashSet<string>();
|
||||||
|
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
|
||||||
|
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
|
||||||
|
private readonly List<MethodInfo> currentFilteredMethods = new List<MethodInfo>();
|
||||||
|
|
||||||
|
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 = $"<color=#{SignatureHighlighter.keywordBlueHex}>void</color> ";
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> highlightedMethods = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
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($"<color={color}>{method.Name}</color>");
|
||||||
|
|
||||||
|
// 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($"<color={SignatureHighlighter.LOCAL_ARG}>{param.Name}</color>");
|
||||||
|
i++;
|
||||||
|
if (i < args.Length)
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.Append(')');
|
||||||
|
|
||||||
|
var ret = sb.ToString();
|
||||||
|
highlightedMethods.Add(sig, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
src/UI/Panels/HookManagerPanel.cs
Normal file
120
src/UI/Panels/HookManagerPanel.cs
Normal file
@ -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<HookCell> HooksScrollPool;
|
||||||
|
public ScrollPool<AddHookCell> 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<VerticalLayoutGroup>(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<HookCell>(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<VerticalLayoutGroup>(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<AddHookCell>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,7 @@ namespace UnityExplorer.UI
|
|||||||
AutoCompleter,
|
AutoCompleter,
|
||||||
MouseInspector,
|
MouseInspector,
|
||||||
UIInspectorResults,
|
UIInspectorResults,
|
||||||
|
HookManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VerticalAnchor
|
public enum VerticalAnchor
|
||||||
@ -107,6 +108,7 @@ namespace UnityExplorer.UI
|
|||||||
UIPanels.Add(Panels.ObjectExplorer, new ObjectExplorerPanel());
|
UIPanels.Add(Panels.ObjectExplorer, new ObjectExplorerPanel());
|
||||||
UIPanels.Add(Panels.Inspector, new InspectorPanel());
|
UIPanels.Add(Panels.Inspector, new InspectorPanel());
|
||||||
UIPanels.Add(Panels.CSConsole, new CSConsolePanel());
|
UIPanels.Add(Panels.CSConsole, new CSConsolePanel());
|
||||||
|
UIPanels.Add(Panels.HookManager, new HookManagerPanel());
|
||||||
UIPanels.Add(Panels.ConsoleLog, new LogPanel());
|
UIPanels.Add(Panels.ConsoleLog, new LogPanel());
|
||||||
UIPanels.Add(Panels.Options, new OptionsPanel());
|
UIPanels.Add(Panels.Options, new OptionsPanel());
|
||||||
UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel());
|
UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel());
|
||||||
@ -242,14 +244,14 @@ namespace UnityExplorer.UI
|
|||||||
NavBarRect.anchorMin = new Vector2(0.5f, 1f);
|
NavBarRect.anchorMin = new Vector2(0.5f, 1f);
|
||||||
NavBarRect.anchorMax = new Vector2(0.5f, 1f);
|
NavBarRect.anchorMax = new Vector2(0.5f, 1f);
|
||||||
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 0);
|
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 0);
|
||||||
NavBarRect.sizeDelta = new Vector2(1000f, 35f);
|
NavBarRect.sizeDelta = new Vector2(1080f, 35f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VerticalAnchor.Bottom:
|
case VerticalAnchor.Bottom:
|
||||||
NavBarRect.anchorMin = new Vector2(0.5f, 0f);
|
NavBarRect.anchorMin = new Vector2(0.5f, 0f);
|
||||||
NavBarRect.anchorMax = new Vector2(0.5f, 0f);
|
NavBarRect.anchorMax = new Vector2(0.5f, 0f);
|
||||||
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 35);
|
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 35);
|
||||||
NavBarRect.sizeDelta = new Vector2(1000f, 35f);
|
NavBarRect.sizeDelta = new Vector2(1080f, 35f);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,6 +215,9 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Hooks\HookCell.cs" />
|
||||||
|
<Compile Include="Hooks\HookInstance.cs" />
|
||||||
|
<Compile Include="Hooks\HookManager.cs" />
|
||||||
<Compile Include="Core\Config\InternalConfigHandler.cs" />
|
<Compile Include="Core\Config\InternalConfigHandler.cs" />
|
||||||
<Compile Include="CacheObject\CacheConfigEntry.cs" />
|
<Compile Include="CacheObject\CacheConfigEntry.cs" />
|
||||||
<Compile Include="CacheObject\Views\CacheConfigCell.cs" />
|
<Compile Include="CacheObject\Views\CacheConfigCell.cs" />
|
||||||
@ -235,6 +238,7 @@
|
|||||||
<Compile Include="Core\Utility\ArgumentUtility.cs" />
|
<Compile Include="Core\Utility\ArgumentUtility.cs" />
|
||||||
<Compile Include="Core\Utility\MiscUtility.cs" />
|
<Compile Include="Core\Utility\MiscUtility.cs" />
|
||||||
<Compile Include="Core\Utility\ParseUtility.cs" />
|
<Compile Include="Core\Utility\ParseUtility.cs" />
|
||||||
|
<Compile Include="Hooks\MethodAddCell.cs" />
|
||||||
<Compile Include="Inspectors\GameObjectWidgets\ComponentCell.cs" />
|
<Compile Include="Inspectors\GameObjectWidgets\ComponentCell.cs" />
|
||||||
<Compile Include="Inspectors\GameObjectWidgets\ComponentList.cs" />
|
<Compile Include="Inspectors\GameObjectWidgets\ComponentList.cs" />
|
||||||
<Compile Include="Inspectors\GameObjectWidgets\GameObjectControls.cs" />
|
<Compile Include="Inspectors\GameObjectWidgets\GameObjectControls.cs" />
|
||||||
@ -269,6 +273,7 @@
|
|||||||
<Compile Include="Inspectors\ReflectionInspector.cs" />
|
<Compile Include="Inspectors\ReflectionInspector.cs" />
|
||||||
<Compile Include="CacheObject\IValues\InteractiveValueStruct.cs" />
|
<Compile Include="CacheObject\IValues\InteractiveValueStruct.cs" />
|
||||||
<Compile Include="UI\Models\InputFieldRef.cs" />
|
<Compile Include="UI\Models\InputFieldRef.cs" />
|
||||||
|
<Compile Include="UI\Panels\HookManagerPanel.cs" />
|
||||||
<Compile Include="UI\Panels\UiInspectorResultsPanel.cs" />
|
<Compile Include="UI\Panels\UiInspectorResultsPanel.cs" />
|
||||||
<Compile Include="UI\Pool.cs" />
|
<Compile Include="UI\Pool.cs" />
|
||||||
<Compile Include="UI\Panels\LogPanel.cs" />
|
<Compile Include="UI\Panels\LogPanel.cs" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user