Add hook source editor for custom dynamic hooks

This commit is contained in:
Sinai 2021-09-07 17:23:20 +10:00
parent 427f23b80a
commit 371054d6df
4 changed files with 315 additions and 166 deletions

View File

@ -59,7 +59,7 @@ namespace UnityExplorer.Hooks
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100); UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
DeleteButton.OnClick += OnDeleteClicked; DeleteButton.OnClick += OnDeleteClicked;
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Log Hook Source", new Color(0.15f, 0.15f, 0.15f)); EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150); UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
EditPatchButton.OnClick += OnEditPatchClicked; EditPatchButton.OnClick += OnEditPatchClicked;

View File

@ -21,18 +21,24 @@ namespace UnityExplorer.Hooks
public bool Enabled; public bool Enabled;
public MethodInfo TargetMethod; public MethodInfo TargetMethod;
public string GeneratedSource; public string PatchSourceCode;
private string shortSignature; private readonly string shortSignature;
private PatchProcessor patchProcessor; private PatchProcessor patchProcessor;
private HarmonyMethod patchDelegate;
private MethodInfo patchDelegateMethodInfo; private MethodInfo postfix;
private MethodInfo prefix;
private MethodInfo finalizer;
private MethodInfo transpiler;
public HookInstance(MethodInfo targetMethod) public HookInstance(MethodInfo targetMethod)
{ {
this.TargetMethod = targetMethod; this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}"; this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
if (GenerateProcessorAndDelegate())
GenerateDefaultPatchSourceCode(targetMethod);
if (CompileAndGenerateProcessor(PatchSourceCode))
Patch(); Patch();
} }
@ -41,30 +47,53 @@ namespace UnityExplorer.Hooks
// TypeDefinition.Definition // TypeDefinition.Definition
private static readonly PropertyInfo pi_Definition = ReflectionUtility.GetPropertyInfo(typeof(TypeDefinition), "Definition"); private static readonly PropertyInfo pi_Definition = ReflectionUtility.GetPropertyInfo(typeof(TypeDefinition), "Definition");
private bool GenerateProcessorAndDelegate() public bool CompileAndGenerateProcessor(string patchSource)
{ {
Unpatch();
try try
{ {
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod); patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method // Dynamically compile the patch method
scriptEvaluator.Run(GeneratePatchSourceCode(TargetMethod)); var codeBuilder = new StringBuilder();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(patchSource);
codeBuilder.AppendLine("}");
scriptEvaluator.Run(codeBuilder.ToString());
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0) if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
throw new FormatException($"Unable to compile the generated patch!"); throw new FormatException($"Unable to compile the generated patch!");
// TODO: Publicize MCS to avoid this reflection // TODO: Publicize MCS to avoid this reflection
// Get the last defined type in the source file // Get the most recent Patch type in the source file
var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator)).Containers.Last(); var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator))
// Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type), then get the method called Patch. .Containers
this.patchDelegateMethodInfo = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)) .Last(it => it.MemberName.Name.StartsWith("DynamicPatch_"));
.GetMetaInfo() // Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type)
.GetMethod("Patch", ReflectionUtility.FLAGS); var patchClass = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)).GetMetaInfo();
// Actually create the harmony patch // Create the harmony patches as defined
this.patchDelegate = new HarmonyMethod(patchDelegateMethodInfo);
patchProcessor.AddPostfix(patchDelegate); postfix = patchClass.GetMethod("Postfix", ReflectionUtility.FLAGS);
if (postfix != null)
patchProcessor.AddPostfix(new HarmonyMethod(postfix));
prefix = patchClass.GetMethod("Prefix", ReflectionUtility.FLAGS);
if (prefix != null)
patchProcessor.AddPrefix(new HarmonyMethod(prefix));
finalizer = patchClass.GetMethod("Finalizer", ReflectionUtility.FLAGS);
if (finalizer != null)
patchProcessor.AddFinalizer(new HarmonyMethod(finalizer));
transpiler = patchClass.GetMethod("Transpiler", ReflectionUtility.FLAGS);
if (transpiler != null)
patchProcessor.AddTranspiler(new HarmonyMethod(transpiler));
return true; return true;
} }
@ -75,16 +104,12 @@ namespace UnityExplorer.Hooks
} }
} }
private string GeneratePatchSourceCode(MethodInfo targetMethod) private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{ {
var codeBuilder = new StringBuilder(); var codeBuilder = new StringBuilder();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
// Arguments // Arguments
codeBuilder.Append(" public static void Patch(System.Reflection.MethodBase __originalMethod"); codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
if (!targetMethod.IsStatic) if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance"); codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
@ -113,34 +138,36 @@ namespace UnityExplorer.Hooks
// Log message // Log message
var logMessage = new StringBuilder(); var logMessage = new StringBuilder();
logMessage.AppendLine($"$@\"Patch called: {shortSignature}"); logMessage.Append($"Patch called: {shortSignature}\\n");
if (!targetMethod.IsStatic) if (!targetMethod.IsStatic)
logMessage.AppendLine("__instance: {__instance.ToString()}"); logMessage.Append("__instance: {__instance.ToString()}\\n");
paramIdx = 0; paramIdx = 0;
foreach (var param in parameters) foreach (var param in parameters)
{ {
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
Type pType = param.ParameterType; Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType(); if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType) if (pType.IsValueType)
logMessage.AppendLine($"Parameter {paramIdx} {param.Name}: {{__{paramIdx}.ToString()}}"); logMessage.Append($"{{__{paramIdx}.ToString()}}");
else else
logMessage.AppendLine($"Parameter {paramIdx} {param.Name}: {{__{paramIdx}?.ToString() ?? \"null\"}}"); logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
logMessage.Append("\\n");
paramIdx++; paramIdx++;
} }
if (targetMethod.ReturnType != typeof(void)) if (targetMethod.ReturnType != typeof(void))
{ {
logMessage.Append("Return value: ");
if (targetMethod.ReturnType.IsValueType) if (targetMethod.ReturnType.IsValueType)
logMessage.AppendLine("Return value: {__result.ToString()}"); logMessage.Append("{__result.ToString()}");
else else
logMessage.AppendLine("Return value: {__result?.ToString() ?? \"null\"}"); logMessage.Append("{__result?.ToString() ?? \"null\"}");
logMessage.Append("\\n");
} }
logMessage.Append('"'); codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log({logMessage});");
codeBuilder.AppendLine(" }"); codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {"); codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");"); codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
@ -150,11 +177,7 @@ namespace UnityExplorer.Hooks
codeBuilder.AppendLine("}"); codeBuilder.AppendLine("}");
// End class return PatchSourceCode = codeBuilder.ToString();
codeBuilder.AppendLine("}");
return GeneratedSource = codeBuilder.ToString();
} }
public void TogglePatch() public void TogglePatch()
@ -181,12 +204,16 @@ namespace UnityExplorer.Hooks
public void Unpatch() public void Unpatch()
{ {
if (!Enabled)
return;
try try
{ {
this.patchProcessor.Unpatch(patchDelegateMethodInfo); if (prefix != null)
patchProcessor.Unpatch(prefix);
if (postfix != null)
patchProcessor.Unpatch(postfix);
if (finalizer != null)
patchProcessor.Unpatch(finalizer);
if (transpiler != null)
patchProcessor.Unpatch(transpiler);
Enabled = false; Enabled = false;
} }
catch (Exception ex) catch (Exception ex)

View File

@ -6,6 +6,7 @@ using System.Reflection;
using System.Text; using System.Text;
using HarmonyLib; using HarmonyLib;
using UnityEngine; using UnityEngine;
using UnityExplorer.CSConsole;
using UnityExplorer.UI; using UnityExplorer.UI;
using UnityExplorer.UI.Panels; using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets; using UnityExplorer.UI.Widgets;
@ -19,77 +20,25 @@ namespace UnityExplorer.Hooks
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager); public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
public int ItemCount => addingMethods ? currentFilteredMethods.Count : currentHooks.Count; // This class acts as the data source for both current hooks and eligable methods when adding hooks.
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
// correct pool cells.
private bool isAddingMethods;
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
private bool addingMethods; // current hooks
private HashSet<string> hookedSignatures = new HashSet<string>(); private readonly HashSet<string> hookedSignatures = new HashSet<string>();
private readonly OrderedDictionary currentHooks = new OrderedDictionary(); private readonly OrderedDictionary currentHooks = new OrderedDictionary();
// adding hooks
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>(); private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
private readonly List<MethodInfo> currentFilteredMethods = new List<MethodInfo>(); private readonly List<MethodInfo> filteredEligableMethods = new List<MethodInfo>();
public void OnClassSelectedForHooks(string typeFullName) // hook editor
{ private readonly LexerBuilder Lexer = new LexerBuilder();
var type = ReflectionUtility.GetTypeByName(typeFullName); private HookInstance currentEditedHook;
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true)); // ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
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) public void EnableOrDisableHookClicked(int index)
{ {
@ -111,32 +60,12 @@ namespace UnityExplorer.Hooks
public void EditPatchClicked(int index) public void EditPatchClicked(int index)
{ {
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
var hook = (HookInstance)currentHooks[index]; var hook = (HookInstance)currentHooks[index];
ExplorerCore.Log(hook.GeneratedSource); currentEditedHook = hook;
Panel.EditorInput.Text = hook.PatchSourceCode;
} }
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 // Set current hook cell
public void SetCell(HookCell cell, int index) public void SetCell(HookCell cell, int index)
@ -157,18 +86,128 @@ namespace UnityExplorer.Hooks
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f)); hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
} }
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
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();
filteredEligableMethods.Clear();
currentAddEligableMethods.Clear();
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod /* || method.IsAbstract */ || ReflectionUtility.IsBlacklisted(method))
continue;
currentAddEligableMethods.Add(method);
filteredEligableMethods.Add(method);
}
isAddingMethods = true;
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
Panel.AddHooksScrollPool.Refresh(true, true);
}
public void DoneAddingHooks()
{
isAddingMethods = false;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
Panel.HooksScrollPool.Refresh(true, false);
}
public void AddHookClicked(int index)
{
if (index >= this.filteredEligableMethods.Count)
return;
AddHook(filteredEligableMethods[index]);
Panel.AddHooksScrollPool.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 OnAddHookFilterInputChanged(string input)
{
filteredEligableMethods.Clear();
if (string.IsNullOrEmpty(input))
filteredEligableMethods.AddRange(currentAddEligableMethods);
else
{
foreach (var method in currentAddEligableMethods)
{
if (method.Name.ContainsIgnoreCase(input))
filteredEligableMethods.Add(method);
}
}
Panel.AddHooksScrollPool.Refresh(true, true);
}
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
public void OnEditorInputChanged(string value)
{
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
Panel.EditorInput.Component.caretPosition, out _);
}
public void EditorInputCancel()
{
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
public void EditorInputSave()
{
var input = Panel.EditorInput.Text;
bool wasEnabled = currentEditedHook.Enabled;
if (currentEditedHook.CompileAndGenerateProcessor(input))
{
if (wasEnabled)
currentEditedHook.Patch();
currentEditedHook.PatchSourceCode = input;
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
}
// OnBorrow methods not needed
public void OnCellBorrowed(HookCell cell) { }
public void OnCellBorrowed(AddHookCell cell) { }
// Set eligable method cell // Set eligable method cell
public void SetCell(AddHookCell cell, int index) public void SetCell(AddHookCell cell, int index)
{ {
if (index >= this.currentFilteredMethods.Count) if (index >= this.filteredEligableMethods.Count)
{ {
cell.Disable(); cell.Disable();
return; return;
} }
cell.CurrentDisplayedIndex = index; cell.CurrentDisplayedIndex = index;
var method = this.currentFilteredMethods[index]; var method = this.filteredEligableMethods[index];
cell.MethodNameLabel.text = HighlightMethod(method); cell.MethodNameLabel.text = HighlightMethod(method);

View File

@ -13,6 +13,13 @@ namespace UnityExplorer.UI.Panels
{ {
public class HookManagerPanel : UIPanel public class HookManagerPanel : UIPanel
{ {
public enum Pages
{
CurrentHooks,
ClassMethodSelector,
HookSourceEditor
}
public override UIManager.Panels PanelType => UIManager.Panels.HookManager; public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks"; public override string Name => "Hooks";
@ -20,14 +27,22 @@ namespace UnityExplorer.UI.Panels
public override int MinHeight => 600; public override int MinHeight => 600;
public override bool ShowByDefault => false; public override bool ShowByDefault => false;
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
private GameObject currentHooksPanel;
public ScrollPool<HookCell> HooksScrollPool; public ScrollPool<HookCell> HooksScrollPool;
public ScrollPool<AddHookCell> MethodResultsScrollPool; private InputFieldRef classSelectorInputField;
private GameObject addHooksPanel; private GameObject addHooksPanel;
private GameObject currentHooksPanel; public ScrollPool<AddHookCell> AddHooksScrollPool;
private InputFieldRef classInputField;
private Text addHooksLabel; private Text addHooksLabel;
private InputFieldRef methodFilterInput; private InputFieldRef AddHooksMethodFilterInput;
private GameObject editorPanel;
public InputFieldScroller EditorInputScroller { get; private set; }
public InputFieldRef EditorInput => EditorInputScroller.InputField;
public Text EditorInputText { get; private set; }
public Text EditorHighlightText { get; private set; }
public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value; public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value;
@ -35,22 +50,38 @@ namespace UnityExplorer.UI.Panels
private void OnClassInputAddClicked() private void OnClassInputAddClicked()
{ {
HookManager.Instance.OnClassSelectedForHooks(this.classInputField.Text); HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
} }
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}"; public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
public void SetAddPanelActive(bool show) public void SetPage(Pages page)
{ {
addHooksPanel.SetActive(show); switch (page)
currentHooksPanel.SetActive(!show); {
case Pages.CurrentHooks:
currentHooksPanel.SetActive(true);
addHooksPanel.SetActive(false);
editorPanel.SetActive(false);
break;
case Pages.ClassMethodSelector:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(true);
editorPanel.SetActive(false);
break;
case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(false);
editorPanel.SetActive(true);
break;
}
} }
public void ResetMethodFilter() => methodFilterInput.Text = string.Empty; public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void ConstructPanelContent() public override void ConstructPanelContent()
{ {
// Active hooks scroll pool // ~~~~~~~~~ Active hooks scroll pool
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content); currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999); UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
@ -60,9 +91,9 @@ namespace UnityExplorer.UI.Panels
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f)); new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999); UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
classInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to..."); classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(classInputField.Component.gameObject, flexibleWidth: 9999); UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classInputField, false, false); new TypeCompleter(typeof(object), classSelectorInputField, true, false);
var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks"); var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25); UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
@ -76,7 +107,7 @@ namespace UnityExplorer.UI.Panels
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999); UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance); HooksScrollPool.Initialize(HookManager.Instance);
// Add hooks panel // ~~~~~~~~~ Add hooks panel
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content); addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999); UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
@ -90,22 +121,75 @@ namespace UnityExplorer.UI.Panels
var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f)); var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999); UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
doneButton.OnClick += HookManager.Instance.CloseAddHooks; doneButton.OnClick += HookManager.Instance.DoneAddingHooks;
var patchAllButton = UIFactory.CreateButton(buttonRow, "PatchAllButton", "Hook ALL methods in class", new Color(0.3f, 0.3f, 0.2f)); AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(patchAllButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999); UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
patchAllButton.OnClick += HookManager.Instance.OnHookAllClicked; AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
methodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names..."); AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
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); out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999); UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
MethodResultsScrollPool.Initialize(HookManager.Instance); AddHooksScrollPool.Initialize(HookManager.Instance);
addHooksPanel.gameObject.SetActive(false); addHooksPanel.gameObject.SetActive(false);
// ~~~~~~~~~ Hook source editor panel
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.content);
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
var editorLabel = UIFactory.CreateLabel(editorPanel,
"EditorLabel",
"Edit Harmony patch source as desired. Accepted method names are Prefix, Postfix, Finalizer and Transpiler (can define multiple).\n\n" +
"Hooks are temporary! Please copy the source into your IDE to avoid losing work if you wish to keep it!",
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
var editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
var editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorSaveButton.OnClick += HookManager.Instance.EditorInputSave;
var editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorDoneButton.OnClick += HookManager.Instance.EditorInputCancel;
int fontSize = 16;
var inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out var inputScroller, fontSize);
EditorInputScroller = inputScroller;
EditorInput.OnValueChanged += HookManager.Instance.OnEditorInputChanged;
EditorInputText = EditorInput.Component.textComponent;
EditorInputText.supportRichText = false;
EditorInputText.color = Color.clear;
EditorInput.Component.customCaretColor = true;
EditorInput.Component.caretColor = Color.white;
EditorInput.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
highlightTextRect.pivot = new Vector2(0, 1);
highlightTextRect.anchorMin = Vector2.zero;
highlightTextRect.anchorMax = Vector2.one;
highlightTextRect.offsetMin = Vector2.zero;
highlightTextRect.offsetMax = Vector2.zero;
EditorHighlightText = highlightTextObj.AddComponent<Text>();
EditorHighlightText.color = Color.white;
EditorHighlightText.supportRichText = true;
EditorHighlightText.fontSize = fontSize;
// Set fonts
EditorInputText.font = UIManager.ConsoleFont;
EditorInput.PlaceholderText.font = UIManager.ConsoleFont;
EditorHighlightText.font = UIManager.ConsoleFont;
editorPanel.SetActive(false);
} }
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
@ -115,6 +199,5 @@ namespace UnityExplorer.UI.Panels
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth); this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight); this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
} }
} }
} }