mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-16 22:27:45 +08:00
Add hook source editor for custom dynamic hooks
This commit is contained in:
parent
427f23b80a
commit
371054d6df
@ -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;
|
||||||
|
|
||||||
|
@ -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");
|
||||||
@ -106,55 +131,53 @@ namespace UnityExplorer.Hooks
|
|||||||
|
|
||||||
// Patch body
|
// Patch body
|
||||||
|
|
||||||
codeBuilder.AppendLine(" {");
|
codeBuilder.AppendLine("{");
|
||||||
|
|
||||||
codeBuilder.AppendLine(" try {");
|
codeBuilder.AppendLine(" try {");
|
||||||
|
|
||||||
// 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(" }");
|
||||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log({logMessage});");
|
codeBuilder.AppendLine(" catch (System.Exception ex) {");
|
||||||
codeBuilder.AppendLine(" }");
|
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
|
||||||
codeBuilder.AppendLine(" catch (System.Exception ex) {");
|
codeBuilder.AppendLine(" }");
|
||||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
|
|
||||||
codeBuilder.AppendLine(" }");
|
|
||||||
|
|
||||||
// End patch body
|
// End patch body
|
||||||
|
|
||||||
codeBuilder.AppendLine(" }");
|
|
||||||
|
|
||||||
// End class
|
|
||||||
|
|
||||||
codeBuilder.AppendLine("}");
|
codeBuilder.AppendLine("}");
|
||||||
|
|
||||||
return GeneratedSource = codeBuilder.ToString();
|
return PatchSourceCode = 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)
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user