Compare commits

...

30 Commits
4.7.4 ... 4.7.6

Author SHA1 Message Date
e92556805b Update BepInExConfigHandler.cs 2022-04-23 01:05:51 +10:00
8662742461 Bump version 2022-04-23 00:19:31 +10:00
63393a9d66 Update README.md 2022-04-22 22:31:36 +10:00
13986f95c1 Update UnityExplorer.STANDALONE.Mono.dll 2022-04-22 22:27:48 +10:00
b47bfa1e83 Remove Mono restriction on generic type eligibility 2022-04-22 22:27:43 +10:00
12c51248fe Update Editor package 2022-04-22 22:20:01 +10:00
9b9cb54a79 Fix a typo 2022-04-22 22:19:43 +10:00
6a28a93e3a Update for obsolete method 2022-04-22 22:11:24 +10:00
32e718faeb Bump UniverseLib 2022-04-22 21:31:30 +10:00
4d46b74d54 Fix references for rename 2022-04-22 21:04:50 +10:00
7e5246cead Recache types when borrowing 2022-04-22 21:04:23 +10:00
abf5267364 Fix HookCreator method filtering 2022-04-22 21:03:51 +10:00
3afee7254c Fix results TypeCompleter issues 2022-04-22 21:03:33 +10:00
1643d4b7dd Allow generic class construction for unbound types 2022-04-22 21:03:11 +10:00
cef8c12d20 Fix TryFocusActiveInspector for classes 2022-04-22 21:02:45 +10:00
5e07847356 Make current hooks view smaller in height 2022-04-22 21:02:08 +10:00
2dc6e386df Fix generated patch code for ref and subclasses 2022-04-22 21:01:47 +10:00
ff882296fd Fix for GenericConstructorWidget, adjust UI 2022-04-22 21:01:09 +10:00
ecc33927ee Make GenericConstructorWidget reusable 2022-04-22 21:00:18 +10:00
6e91f2a792 Hooks: Add support for generic classes and methods 2022-04-22 09:15:51 +10:00
97cb14d6fc HookInstance: Clean up generated patch code 2022-04-22 09:12:38 +10:00
8b861f7c77 Log Panel: Remove a todo 2022-04-22 09:12:22 +10:00
9379e0f813 Fix constraints on AddComponent TypeCompleter 2022-04-22 09:08:49 +10:00
bdda12a040 Remove redundant reference to EvaluateWidget 2022-04-22 09:08:17 +10:00
75bd654a94 TypeCompleter: Allow generics, support shorthand names 2022-04-22 09:07:51 +10:00
f174c7543a C# Console: Fix autocomplete caret deselection 2022-04-22 09:06:52 +10:00
08f2c6035e Bump version 2022-04-20 18:47:23 +10:00
475e24a66a Update editor package 2022-04-20 18:47:19 +10:00
c62b93535d Update position input when Reset button clicked 2022-04-20 18:47:12 +10:00
374d0b3bae Bump UniverseLib 2022-04-20 18:46:48 +10:00
29 changed files with 978 additions and 578 deletions

View File

@ -115,7 +115,7 @@ The inspector is used to see detailed information on objects of any type and man
### Hook Manager
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
* Simply enter any class and hook the methods you want from the menu.
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
### Mouse-Inspect
@ -124,6 +124,13 @@ The inspector is used to see detailed information on objects of any type and man
* <b>World</b>: uses Physics.Raycast to look for Colliders
* <b>UI</b>: uses GraphicRaycasters to find UI objects
### Freecam
* UnityExplorer provides a basic Free Camera which you can control with your keyboard and mouse.
* Unlike all other features of UnityExplorer, you can still use Freecam while UnityExplorer's menu is hidden.
* Supports using the game's main Camera or a separate custom Camera.
* See the Freecam panel for further instructions and details.
### Clipboard
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`)

View File

@ -390,16 +390,11 @@ namespace UnityExplorer.CSConsole
color.a = 0f;
Input.Component.selectionColor = color;
EventSystemHelper.SetSelectedGameObject(null);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
EventSystemHelper.SetSelectedGameObject(null);
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
EventSystemHelper.SetSelectionGuard(false);
Input.Component.Select();
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
Input.Component.caretPosition = caretPosition;
Input.Component.selectionFocusPosition = caretPosition;
LastCaretPosition = Input.Component.caretPosition;

View File

@ -29,8 +29,8 @@ namespace UnityExplorer.CacheObject
this.Owner = inspector;
this.NameLabelText = this switch
{
CacheMethod => SignatureHighlighter.HighlightMethod(member as MethodInfo),
CacheConstructor => SignatureHighlighter.HighlightConstructor(member as ConstructorInfo),
CacheMethod => SignatureHighlighter.ParseMethod(member as MethodInfo),
CacheConstructor => SignatureHighlighter.ParseConstructor(member as ConstructorInfo),
_ => SignatureHighlighter.Parse(member.DeclaringType, false, member),
};

View File

@ -14,7 +14,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.7.4";
public const string VERSION = "4.7.6";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";

View File

@ -16,14 +16,13 @@ namespace UnityExplorer.Hooks
public float DefaultHeight => 30;
public Text MethodNameLabel;
public Text HookedLabel;
public ButtonRef HookButton;
public int CurrentDisplayedIndex;
private void OnHookClicked()
{
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
HookCreator.AddHookClicked(CurrentDisplayedIndex);
}
public void Enable()
@ -44,9 +43,6 @@ namespace UnityExplorer.Hooks
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;

View File

@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
@ -24,17 +25,18 @@ namespace UnityExplorer.Hooks
private void OnToggleActiveClicked()
{
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
HookList.EnableOrDisableHookClicked(CurrentDisplayedIndex);
}
private void OnDeleteClicked()
{
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
HookList.DeleteHookClicked(CurrentDisplayedIndex);
HookCreator.AddHooksScrollPool.Refresh(true, false);
}
private void OnEditPatchClicked()
{
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
HookList.EditPatchClicked(CurrentDisplayedIndex);
}
public GameObject CreateContent(GameObject parent)
@ -48,18 +50,18 @@ namespace UnityExplorer.Hooks
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 = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "On", new Color(0.15f, 0.2f, 0.15f));
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 35);
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", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 35);
EditPatchButton.OnClick += OnEditPatchClicked;
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "X", new Color(0.2f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 35);
DeleteButton.OnClick += OnDeleteClicked;
return UIRoot;
}

340
src/Hooks/HookCreator.cs Normal file
View File

@ -0,0 +1,340 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CSConsole;
using UnityExplorer.Runtime;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookCreator : ICellPoolDataSource<AddHookCell>
{
public int ItemCount => filteredEligibleMethods.Count;
static readonly List<MethodInfo> currentAddEligibleMethods = new();
static readonly List<MethodInfo> filteredEligibleMethods = new();
static readonly List<string> currentEligibleNamesForFiltering = new();
// hook editor
static readonly LexerBuilder Lexer = new();
internal static HookInstance CurrentEditedHook;
// Add Hooks UI
internal static GameObject AddHooksRoot;
internal static ScrollPool<AddHookCell> AddHooksScrollPool;
internal static Text AddHooksLabel;
internal static InputFieldRef AddHooksMethodFilterInput;
internal static InputFieldRef ClassSelectorInputField;
internal static Type pendingGenericDefinition;
internal static MethodInfo pendingGenericMethod;
// Hook Source Editor UI
public static GameObject EditorRoot { get; private set; }
public static Text EditingHookLabel { get; private set; }
public static InputFieldScroller EditorInputScroller { get; private set; }
public static InputFieldRef EditorInput => EditorInputScroller.InputField;
public static Text EditorInputText { get; private set; }
public static Text EditorHighlightText { get; private set; }
// ~~~~~~ New hook method selector ~~~~~~~
public void OnClassSelectedForHooks(string typeFullName)
{
Type type = ReflectionUtility.GetTypeByName(typeFullName);
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
if (type.IsGenericType)
{
pendingGenericDefinition = type;
HookManagerPanel.genericArgsHandler.Show(OnGenericClassChosen, OnGenericClassCancel, type);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
return;
}
ShowMethodsForType(type);
}
void ShowMethodsForType(Type type)
{
SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
AddHooksMethodFilterInput.Text = string.Empty;
filteredEligibleMethods.Clear();
currentAddEligibleMethods.Clear();
currentEligibleNamesForFiltering.Clear();
foreach (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (UERuntimeHelper.IsBlacklisted(method))
continue;
currentAddEligibleMethods.Add(method);
currentEligibleNamesForFiltering.Add(SignatureHighlighter.RemoveHighlighting(SignatureHighlighter.ParseMethod(method)));
filteredEligibleMethods.Add(method);
}
AddHooksScrollPool.Refresh(true, true);
}
void OnGenericClassChosen(Type[] genericArgs)
{
Type generic = pendingGenericDefinition.MakeGenericType(genericArgs);
ShowMethodsForType(generic);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
void OnGenericClassCancel()
{
pendingGenericDefinition = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
public void SetAddHooksLabelType(string typeText)
{
AddHooksLabel.text = $"Adding hooks to: {typeText}";
AddHooksMethodFilterInput.GameObject.SetActive(true);
AddHooksScrollPool.UIRoot.SetActive(true);
}
public static void AddHookClicked(int index)
{
if (index >= filteredEligibleMethods.Count)
return;
MethodInfo method = filteredEligibleMethods[index];
if (!method.IsGenericMethod && HookList.hookedSignatures.Contains(method.FullDescription()))
{
ExplorerCore.Log($"Non-generic methods can only be hooked once.");
return;
}
else if (method.IsGenericMethod)
{
pendingGenericMethod = method;
HookManagerPanel.genericArgsHandler.Show(OnGenericMethodChosen, OnGenericMethodCancel, method);
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.GenericArgsSelector);
return;
}
AddHook(filteredEligibleMethods[index]);
}
static void OnGenericMethodChosen(Type[] arguments)
{
MethodInfo generic = pendingGenericMethod.MakeGenericMethod(arguments);
AddHook(generic);
}
static void OnGenericMethodCancel()
{
pendingGenericMethod = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
public static void AddHook(MethodInfo method)
{
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
string sig = method.FullDescription();
if (HookList.hookedSignatures.Contains(sig))
{
ExplorerCore.LogWarning($"Method is already hooked!");
return;
}
HookInstance hook = new(method);
if (hook.Enabled)
{
HookList.hookedSignatures.Add(sig);
HookList.currentHooks.Add(sig, hook);
}
AddHooksScrollPool.Refresh(true, false);
HookList.HooksScrollPool.Refresh(true, false);
}
public void OnAddHookFilterInputChanged(string input)
{
filteredEligibleMethods.Clear();
if (string.IsNullOrEmpty(input))
filteredEligibleMethods.AddRange(currentAddEligibleMethods);
else
{
for (int i = 0; i < currentAddEligibleMethods.Count; i++)
{
MethodInfo eligible = currentAddEligibleMethods[i];
string sig = currentEligibleNamesForFiltering[i];
if (sig.ContainsIgnoreCase(input))
filteredEligibleMethods.Add(eligible);
}
}
AddHooksScrollPool.Refresh(true, true);
}
// Set eligible method cell
public void OnCellBorrowed(AddHookCell cell) { }
public void SetCell(AddHookCell cell, int index)
{
if (index >= filteredEligibleMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
MethodInfo method = filteredEligibleMethods[index];
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(method);
}
// ~~~~~~~~ Hook source editor ~~~~~~~~
internal static void SetEditedHook(HookInstance hook)
{
CurrentEditedHook = hook;
EditingHookLabel.text = $"Editing: {SignatureHighlighter.Parse(hook.TargetMethod.DeclaringType, false, hook.TargetMethod)}";
EditorInput.Text = hook.PatchSourceCode;
}
internal static void OnEditorInputChanged(string value)
{
EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0, EditorInput.Component.caretPosition, out _);
}
internal static void EditorInputCancel()
{
CurrentEditedHook = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
internal static void EditorInputSave()
{
string input = EditorInput.Text;
bool wasEnabled = CurrentEditedHook.Enabled;
if (CurrentEditedHook.CompileAndGenerateProcessor(input))
{
if (wasEnabled)
CurrentEditedHook.Patch();
CurrentEditedHook.PatchSourceCode = input;
CurrentEditedHook = null;
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
}
}
// UI Construction
internal void ConstructAddHooksView(GameObject rightGroup)
{
AddHooksRoot = UIFactory.CreateUIObject("AddHooksPanel", rightGroup);
UIFactory.SetLayoutElement(AddHooksRoot, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(AddHooksRoot, false, false, true, true);
GameObject addRow = UIFactory.CreateHorizontalGroup(AddHooksRoot, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
ClassSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(ClassSelectorInputField.Component.gameObject, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
TypeCompleter completer = new(typeof(object), ClassSelectorInputField, true, false, true);
//completer.AllTypes = true;
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "View Methods");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 110, minHeight: 25);
addButton.OnClick += () => { OnClassSelectedForHooks(ClassSelectorInputField.Text); };
AddHooksLabel = UIFactory.CreateLabel(AddHooksRoot, "AddLabel", "Choose a class to begin...", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(AddHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
AddHooksMethodFilterInput = UIFactory.CreateInputField(AddHooksRoot, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
AddHooksMethodFilterInput.OnValueChanged += OnAddHookFilterInputChanged;
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(AddHooksRoot, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
AddHooksScrollPool.Initialize(this);
AddHooksMethodFilterInput.GameObject.SetActive(false);
AddHooksScrollPool.UIRoot.SetActive(false);
}
public void ConstructEditor(GameObject parent)
{
EditorRoot = UIFactory.CreateUIObject("HookSourceEditor", parent);
UIFactory.SetLayoutElement(EditorRoot, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(EditorRoot, true, true, true, true, 2, 3, 3, 3, 3);
EditingHookLabel = UIFactory.CreateLabel(EditorRoot, "EditingHookLabel", "NOT SET", TextAnchor.MiddleCenter);
EditingHookLabel.fontStyle = FontStyle.Bold;
UIFactory.SetLayoutElement(EditingHookLabel.gameObject, flexibleWidth: 9999, minHeight: 25);
Text editorLabel = UIFactory.CreateLabel(EditorRoot,
"EditorLabel",
"* Accepted method names are <b>Prefix</b>, <b>Postfix</b>, <b>Finalizer</b> and <b>Transpiler</b> (can define multiple).\n" +
"* Your patch methods must be static.\n" +
"* Hooks are temporary! 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);
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(EditorRoot, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef 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 += EditorInputSave;
ButtonRef 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 += EditorInputCancel;
int fontSize = 16;
GameObject inputObj = UIFactory.CreateScrollInputField(EditorRoot, "EditorInput", "", out InputFieldScroller inputScroller, fontSize);
EditorInputScroller = inputScroller;
EditorInput.OnValueChanged += 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
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
RectTransform 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 = UniversalUI.ConsoleFont;
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
EditorHighlightText.font = UniversalUI.ConsoleFont;
}
}
}

View File

@ -1,6 +1,7 @@
using HarmonyLib;
using Mono.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
@ -14,12 +15,14 @@ namespace UnityExplorer.Hooks
{
// Static
private static readonly StringBuilder evalOutput = new();
private static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evalOutput));
//static readonly StringBuilder evalOutput = new();
static readonly StringBuilder evaluatorOutput;
static readonly ScriptEvaluator scriptEvaluator = new(new StringWriter(evaluatorOutput = new StringBuilder()));
static HookInstance()
{
scriptEvaluator.Run("using System;");
scriptEvaluator.Run("using System.Text;");
scriptEvaluator.Run("using System.Reflection;");
scriptEvaluator.Run("using System.Collections;");
scriptEvaluator.Run("using System.Collections.Generic;");
@ -42,7 +45,7 @@ namespace UnityExplorer.Hooks
public HookInstance(MethodInfo targetMethod)
{
this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
this.shortSignature = TargetMethod.FullDescription();
GenerateDefaultPatchSourceCode(targetMethod);
@ -59,15 +62,15 @@ namespace UnityExplorer.Hooks
{
Unpatch();
StringBuilder codeBuilder = new();
try
{
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method
StringBuilder codeBuilder = new();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine($"static class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(patchSource);
codeBuilder.AppendLine("}");
@ -107,30 +110,59 @@ namespace UnityExplorer.Hooks
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
if (ex is FormatException)
{
string output = scriptEvaluator._textWriter.ToString();
string[] outputSplit = output.Split('\n');
if (outputSplit.Length >= 2)
output = outputSplit[outputSplit.Length - 2];
evaluatorOutput.Clear();
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
ExplorerCore.LogWarning($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
else
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
}
else
ExplorerCore.LogWarning($"Exception generating patch source code: {ex}");
// ExplorerCore.Log(codeBuilder.ToString());
return false;
}
}
static string FullDescriptionClean(Type type)
{
string description = type.FullDescription().Replace("+", ".");
if (description.EndsWith("&"))
description = $"ref {description.Substring(0, description.Length - 1)}";
return description;
}
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{
StringBuilder codeBuilder = new();
// Arguments
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
codeBuilder.Append("static void Postfix("); // System.Reflection.MethodBase __originalMethod
if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
bool isStatic = targetMethod.IsStatic;
if (!isStatic)
codeBuilder.Append($"{FullDescriptionClean(targetMethod.DeclaringType)} __instance");
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
{
if (!isStatic)
codeBuilder.Append(", ");
codeBuilder.Append($"{FullDescriptionClean(targetMethod.ReturnType)} __result");
}
ParameterInfo[] parameters = targetMethod.GetParameters();
int paramIdx = 0;
foreach (ParameterInfo param in parameters)
{
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
codeBuilder.Append($", {FullDescriptionClean(param.ParameterType)} __{paramIdx}");
paramIdx++;
}
@ -139,42 +171,39 @@ namespace UnityExplorer.Hooks
// Patch body
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {");
// Log message
StringBuilder logMessage = new();
logMessage.Append($"Patch called: {shortSignature}\\n");
codeBuilder.AppendLine(" StringBuilder sb = new StringBuilder();");
codeBuilder.AppendLine($" sb.AppendLine(\"---- Patched called ----\");");
codeBuilder.AppendLine($" sb.AppendLine(\"{shortSignature}\");");
if (!targetMethod.IsStatic)
logMessage.Append("__instance: {__instance.ToString()}\\n");
codeBuilder.AppendLine($" sb.Append(\"- __instance: \").AppendLine(__instance.ToString());");
paramIdx = 0;
foreach (ParameterInfo param in parameters)
{
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
codeBuilder.Append($" sb.Append(\"- Parameter {paramIdx} '{param.Name}': \")");
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType)
logMessage.Append($"{{__{paramIdx}.ToString()}}");
codeBuilder.AppendLine($".AppendLine(__{paramIdx}.ToString());");
else
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
logMessage.Append("\\n");
codeBuilder.AppendLine($".AppendLine(__{paramIdx}?.ToString() ?? \"null\");");
paramIdx++;
}
if (targetMethod.ReturnType != typeof(void))
{
logMessage.Append("Return value: ");
codeBuilder.Append(" sb.Append(\"- Return value: \")");
if (targetMethod.ReturnType.IsValueType)
logMessage.Append("{__result.ToString()}");
codeBuilder.AppendLine(".AppendLine(__result.ToString());");
else
logMessage.Append("{__result?.ToString() ?? \"null\"}");
logMessage.Append("\\n");
codeBuilder.AppendLine(".AppendLine(__result?.ToString() ?? \"null\");");
}
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log(sb.ToString());");
codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");

94
src/Hooks/HookList.cs Normal file
View File

@ -0,0 +1,94 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookList : ICellPoolDataSource<HookCell>
{
public int ItemCount => currentHooks.Count;
internal static readonly HashSet<string> hookedSignatures = new();
internal static readonly OrderedDictionary currentHooks = new();
internal static GameObject UIRoot;
internal static ScrollPool<HookCell> HooksScrollPool;
public static void EnableOrDisableHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
HooksScrollPool.Refresh(true, false);
}
public static void DeleteHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
if (HookCreator.CurrentEditedHook == hook)
HookCreator.EditorInputCancel();
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
HooksScrollPool.Refresh(true, false);
}
public static void EditPatchClicked(int index)
{
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor);
HookInstance hook = (HookInstance)currentHooks[index];
HookCreator.SetEditedHook(hook);
}
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
HookInstance hook = (HookInstance)currentHooks[index];
cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "On" : "Off";
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// UI
internal void ConstructUI(GameObject leftGroup)
{
UIRoot = UIFactory.CreateUIObject("CurrentHooksPanel", leftGroup);
UIFactory.SetLayoutElement(UIRoot, preferredHeight: 150, flexibleHeight: 0, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, true, true, true, true);
Text hooksLabel = UIFactory.CreateLabel(UIRoot, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(UIRoot, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(this);
}
}
}

View File

@ -1,227 +0,0 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using UnityEngine;
using UnityExplorer.CSConsole;
using UnityExplorer.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
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);
// 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;
// current hooks
private readonly HashSet<string> hookedSignatures = new();
private readonly OrderedDictionary currentHooks = new();
// adding hooks
private readonly List<MethodInfo> currentAddEligableMethods = new();
private readonly List<MethodInfo> filteredEligableMethods = new();
// hook editor
private readonly LexerBuilder Lexer = new();
private HookInstance currentEditedHook;
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
public void EnableOrDisableHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
Panel.HooksScrollPool.Refresh(true, false);
}
public void DeleteHookClicked(int index)
{
HookInstance hook = (HookInstance)currentHooks[index];
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
Panel.HooksScrollPool.Refresh(true, false);
}
public void EditPatchClicked(int index)
{
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
HookInstance hook = (HookInstance)currentHooks[index];
currentEditedHook = hook;
Panel.EditorInput.Text = hook.PatchSourceCode;
}
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
HookInstance hook = (HookInstance)this.currentHooks[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
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)
{
Type 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 (MethodInfo method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod || UERuntimeHelper.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)
{
string sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
return;
HookInstance hook = new(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 (MethodInfo method in currentAddEligableMethods)
{
if (method.Name.ContainsIgnoreCase(input))
filteredEligableMethods.Add(method);
}
}
Panel.AddHooksScrollPool.Refresh(true, true);
}
// Set eligable method cell
public void OnCellBorrowed(AddHookCell cell) { }
public void SetCell(AddHookCell cell, int index)
{
if (index >= this.filteredEligableMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
MethodInfo method = this.filteredEligableMethods[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(method);
string 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);
}
}
// ~~~~~~~~~~~ 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()
{
string 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);
}
}
}
}

View File

@ -333,7 +333,7 @@ namespace UnityExplorer.Inspectors
addCompButton.OnClick += () => { OnAddComponentClicked(addCompInput.Text); };
// comp autocompleter
new TypeCompleter(typeof(Component), addCompInput);
new TypeCompleter(typeof(Component), addCompInput, false, false, false);
// Component List

View File

@ -1,6 +1,8 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.Inspectors
@ -9,6 +11,7 @@ namespace UnityExplorer.Inspectors
{
public bool IsActive { get; internal set; }
public object Target { get; set; }
public Type TargetType { get; protected set; }
public InspectorTab Tab { get; internal set; }
@ -24,6 +27,8 @@ namespace UnityExplorer.Inspectors
public virtual void OnBorrowedFromPool(object target)
{
this.Target = target;
this.TargetType = target is Type type ? type : target.GetActualType();
Tab = Pool<InspectorTab>.Borrow();
Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false);

View File

@ -23,7 +23,7 @@ namespace UnityExplorer
public static event Action OnInspectedTabsChanged;
public static void Inspect(object obj, CacheObjectBase sourceCache = null)
public static void Inspect(object obj, CacheObjectBase parent = null)
{
if (obj.IsNullOrDestroyed())
return;
@ -36,19 +36,34 @@ namespace UnityExplorer
if (obj is GameObject)
CreateInspector<GameObjectInspector>(obj);
else
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
CreateInspector<ReflectionInspector>(obj, false, parent);
}
public static void Inspect(Type type)
{
if (TryFocusActiveInspector(type))
return;
CreateInspector<ReflectionInspector>(type, true);
}
private static bool TryFocusActiveInspector(object target)
static bool TryFocusActiveInspector(object target)
{
foreach (InspectorBase inspector in Inspectors)
{
if (inspector.Target.ReferenceEqual(target))
bool shouldFocus = false;
if (target is Type targetAsType)
{
if (inspector.TargetType.FullName == targetAsType.FullName)
shouldFocus = true;
}
else if(inspector.Target.ReferenceEqual(target))
{
shouldFocus = true;
}
if (shouldFocus)
{
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
SetInspectorActive(inspector);
@ -76,7 +91,7 @@ namespace UnityExplorer
}
}
internal static void CloseAllTabs()
public static void CloseAllTabs()
{
if (Inspectors.Any())
{
@ -89,18 +104,17 @@ namespace UnityExplorer
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
}
private static void CreateInspector<T>(object target, bool staticReflection = false,
CacheObjectBase parentObject = null) where T : InspectorBase
static void CreateInspector<T>(object target, bool staticReflection = false, CacheObjectBase parent = null) where T : InspectorBase
{
T inspector = Pool<T>.Borrow();
Inspectors.Add(inspector);
inspector.Target = target;
if (parentObject != null && parentObject.CanWrite)
if (parent != null && parent.CanWrite)
{
// only set parent cache object if we are inspecting a struct, otherwise there is no point.
if (target.GetType().IsValueType && inspector is ReflectionInspector ri)
ri.ParentCacheObject = parentObject;
ri.ParentCacheObject = parent;
}
UIManager.SetPanelActive(UIManager.Panels.Inspector, true);
@ -115,7 +129,7 @@ namespace UnityExplorer
OnInspectedTabsChanged?.Invoke();
}
internal static void ReleaseInspector<T>(T inspector) where T : InspectorBase
public static void ReleaseInspector<T>(T inspector) where T : InspectorBase
{
if (lastActiveInspector == inspector)
lastActiveInspector = null;

View File

@ -33,54 +33,52 @@ namespace UnityExplorer.Inspectors
public class ReflectionInspector : InspectorBase, ICellPoolDataSource<CacheMemberCell>, ICacheObjectController
{
public CacheObjectBase ParentCacheObject { get; set; }
public Type TargetType { get; private set; }
//public Type TargetType { get; private set; }
public bool StaticOnly { get; internal set; }
public bool CanWrite => true;
public bool AutoUpdateWanted => autoUpdateToggle.isOn;
private List<CacheMember> members = new();
private readonly List<CacheMember> filteredMembers = new();
List<CacheMember> members = new();
readonly List<CacheMember> filteredMembers = new();
private BindingFlags scopeFlagsFilter;
private string nameFilter;
private MemberFilter MemberFilter = MemberFilter.All;
string nameFilter;
BindingFlags scopeFlagsFilter;
MemberFilter memberFilter = MemberFilter.All;
// Updating
private bool refreshWanted;
private string lastNameFilter;
private BindingFlags lastFlagsFilter;
private MemberFilter lastMemberFilter = MemberFilter.All;
private float timeOfLastAutoUpdate;
bool refreshWanted;
string lastNameFilter;
BindingFlags lastFlagsFilter;
MemberFilter lastMemberFilter = MemberFilter.All;
float timeOfLastAutoUpdate;
// UI
internal GameObject mainContentHolder;
private static int LeftGroupWidth { get; set; }
private static int RightGroupWidth { get; set; }
static int LeftGroupWidth { get; set; }
static int RightGroupWidth { get; set; }
static readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
static readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
public GameObject ContentRoot { get; private set; }
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
public int ItemCount => filteredMembers.Count;
public UnityObjectWidget UnityWidget { get; private set; }
public string TabButtonText { get; set; }
public UnityObjectWidget UnityWidget;
InputFieldRef hiddenNameText;
Text nameText;
Text assemblyText;
Toggle autoUpdateToggle;
public InputFieldRef HiddenNameText;
public Text NameText;
public Text AssemblyText;
private Toggle autoUpdateToggle;
ButtonRef makeGenericButton;
GenericConstructorWidget genericConstructor;
internal string currentBaseTabText;
private readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
private readonly List<Toggle> memberTypeToggles = new();
private InputFieldRef filterInputField;
// const
private readonly Color disabledButtonColor = new(0.24f, 0.24f, 0.24f);
private readonly Color enabledButtonColor = new(0.2f, 0.27f, 0.2f);
InputFieldRef filterInputField;
readonly List<Toggle> memberTypeToggles = new();
readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new();
// Setup
@ -125,6 +123,8 @@ namespace UnityExplorer.Inspectors
this.UnityWidget = null;
}
genericConstructor?.Cancel();
base.OnReturnToPool();
}
@ -138,6 +138,8 @@ namespace UnityExplorer.Inspectors
Target = null;
TargetType = target as Type;
prefix = "[S]";
makeGenericButton.GameObject.SetActive(TargetType.IsGenericTypeDefinition);
}
else
{
@ -146,17 +148,17 @@ namespace UnityExplorer.Inspectors
}
// Setup main labels and tab text
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
Tab.TabText.text = currentBaseTabText;
NameText.text = SignatureHighlighter.Parse(TargetType, true);
HiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(NameText.text);
TabButtonText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
Tab.TabText.text = TabButtonText;
nameText.text = SignatureHighlighter.Parse(TargetType, true);
hiddenNameText.Text = SignatureHighlighter.RemoveHighlighting(nameText.text);
string asmText;
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
asmText = $"{TargetType.Assembly.GetName().Name} <color=grey><i>(in memory)</i></color>";
else
asmText = Path.GetFileName(TargetType.Assembly.Location);
AssemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
assemblyText.text = $"<color=grey>Assembly:</color> {asmText}";
// Unity object helper widget
@ -195,11 +197,11 @@ namespace UnityExplorer.Inspectors
}
// check filter changes or force-refresh
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != MemberFilter)
if (refreshWanted || nameFilter != lastNameFilter || scopeFlagsFilter != lastFlagsFilter || lastMemberFilter != memberFilter)
{
lastNameFilter = nameFilter;
lastFlagsFilter = scopeFlagsFilter;
lastMemberFilter = MemberFilter;
lastMemberFilter = memberFilter;
FilterMembers();
MemberScrollPool.Refresh(true, true);
@ -219,17 +221,8 @@ namespace UnityExplorer.Inspectors
}
}
public void UpdateClicked()
{
UpdateDisplayedMembers();
}
// Filtering
public void SetFilter(string name) => SetFilter(name, scopeFlagsFilter);
public void SetFilter(BindingFlags flags) => SetFilter(nameFilter, flags);
public void SetFilter(string name, BindingFlags flags)
{
this.nameFilter = name;
@ -245,15 +238,7 @@ namespace UnityExplorer.Inspectors
}
}
private void OnMemberTypeToggled(MemberFilter flag, bool val)
{
if (!val)
MemberFilter &= ~flag;
else
MemberFilter |= flag;
}
private void FilterMembers()
void FilterMembers()
{
filteredMembers.Clear();
@ -268,10 +253,10 @@ namespace UnityExplorer.Inspectors
continue;
}
if ((member is CacheMethod && !MemberFilter.HasFlag(MemberFilter.Method))
|| (member is CacheField && !MemberFilter.HasFlag(MemberFilter.Field))
|| (member is CacheProperty && !MemberFilter.HasFlag(MemberFilter.Property))
|| (member is CacheConstructor && !MemberFilter.HasFlag(MemberFilter.Constructor)))
if ((member is CacheMethod && !memberFilter.HasFlag(MemberFilter.Method))
|| (member is CacheField && !memberFilter.HasFlag(MemberFilter.Field))
|| (member is CacheProperty && !memberFilter.HasFlag(MemberFilter.Property))
|| (member is CacheConstructor && !memberFilter.HasFlag(MemberFilter.Constructor)))
continue;
if (!string.IsNullOrEmpty(nameFilter) && !member.NameForFiltering.ContainsIgnoreCase(nameFilter))
@ -281,7 +266,7 @@ namespace UnityExplorer.Inspectors
}
}
private void UpdateDisplayedMembers()
void UpdateDisplayedMembers()
{
bool shouldRefresh = false;
foreach (CacheMemberCell cell in MemberScrollPool.CellPool)
@ -320,13 +305,13 @@ namespace UnityExplorer.Inspectors
SetCellLayout(cell);
}
private void CalculateLayouts()
void CalculateLayouts()
{
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);
RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65);
}
private void SetCellLayout(CacheObjectCell cell)
void SetCellLayout(CacheObjectCell cell)
{
cell.NameLayout.minWidth = LeftGroupWidth;
cell.RightGroupLayout.minWidth = RightGroupWidth;
@ -335,11 +320,66 @@ namespace UnityExplorer.Inspectors
cell.Occupant.IValue.SetLayout();
}
private void OnCopyClicked()
// UI listeners
void OnUpdateClicked()
{
UpdateDisplayedMembers();
}
public void OnSetNameFilter(string name)
{
SetFilter(name, scopeFlagsFilter);
}
public void OnSetFlags(BindingFlags flags)
{
SetFilter(nameFilter, flags);
}
void OnMemberTypeToggled(MemberFilter flag, bool val)
{
if (!val)
memberFilter &= ~flag;
else
memberFilter |= flag;
}
void OnCopyClicked()
{
ClipboardPanel.Copy(this.Target ?? this.TargetType);
}
void OnMakeGenericClicked()
{
ContentRoot.SetActive(false);
if (genericConstructor == null)
{
genericConstructor = new();
genericConstructor.ConstructUI(UIRoot);
}
genericConstructor.UIRoot.SetActive(true);
genericConstructor.Show(OnGenericSubmit, OnGenericCancel, TargetType);
}
void OnGenericSubmit(Type[] args)
{
ContentRoot.SetActive(true);
genericConstructor.UIRoot.SetActive(false);
Type newType = TargetType.MakeGenericType(args);
InspectorManager.Inspect(newType);
//InspectorManager.ReleaseInspector(this);
}
void OnGenericCancel()
{
ContentRoot.SetActive(true);
genericConstructor.UIRoot.SetActive(false);
}
// UI Construction
public override GameObject CreateContent(GameObject parent)
@ -349,51 +389,57 @@ namespace UnityExplorer.Inspectors
// Class name, assembly
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default, new(1, 1, 1, 0), TextAnchor.MiddleLeft);
GameObject topRow = UIFactory.CreateHorizontalGroup(UIRoot, "TopRow", false, false, true, true, 4, default,
new(0.1f, 0.1f, 0.1f), TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(topRow, minHeight: 25, flexibleWidth: 9999);
GameObject titleHolder = UIFactory.CreateUIObject("TitleHolder", topRow);
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
RectTransform namerect = NameText.GetComponent<RectTransform>();
nameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
RectTransform namerect = nameText.GetComponent<RectTransform>();
namerect.anchorMin = new Vector2(0, 0);
namerect.anchorMax = new Vector2(1, 1);
NameText.fontSize = 17;
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
nameText.fontSize = 17;
UIFactory.SetLayoutElement(nameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
RectTransform hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
hiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
RectTransform hiddenrect = hiddenNameText.Component.gameObject.GetComponent<RectTransform>();
hiddenrect.anchorMin = new Vector2(0, 0);
hiddenrect.anchorMax = new Vector2(1, 1);
HiddenNameText.Component.readOnly = true;
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
HiddenNameText.Component.textComponent.fontSize = 17;
HiddenNameText.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
hiddenNameText.Component.readOnly = true;
hiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
hiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
hiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
hiddenNameText.Component.textComponent.fontSize = 17;
hiddenNameText.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(hiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
makeGenericButton = UIFactory.CreateButton(topRow, "MakeGenericButton", "Construct Generic", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(makeGenericButton.GameObject, minWidth: 140, minHeight: 25);
makeGenericButton.OnClick += OnMakeGenericClicked;
makeGenericButton.GameObject.SetActive(false);
ButtonRef copyButton = UIFactory.CreateButton(topRow, "CopyButton", "Copy to Clipboard", new Color(0.2f, 0.2f, 0.2f, 1));
copyButton.ButtonText.color = Color.yellow;
UIFactory.SetLayoutElement(copyButton.Component.gameObject, minHeight: 25, minWidth: 120, flexibleWidth: 0);
copyButton.OnClick += OnCopyClicked;
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
assemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(assemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
ContentRoot = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
new Color(0.12f, 0.12f, 0.12f));
UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutElement(ContentRoot, flexibleWidth: 9999, flexibleHeight: 9999);
ConstructFirstRow(mainContentHolder);
ConstructFirstRow(ContentRoot);
ConstructSecondRow(mainContentHolder);
ConstructSecondRow(ContentRoot);
// Member scroll pool
GameObject memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2, 2, 2, 2),
bgColor: new Color(0.05f, 0.05f, 0.05f));
GameObject memberBorder = UIFactory.CreateVerticalGroup(ContentRoot, "ScrollPoolHolder", false, false, true, true,
padding: new Vector4(2, 2, 2, 2), bgColor: new Color(0.05f, 0.05f, 0.05f));
UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999);
MemberScrollPool = UIFactory.CreateScrollPool<CacheMemberCell>(memberBorder, "MemberList", out GameObject scrollObj,
@ -411,7 +457,7 @@ namespace UnityExplorer.Inspectors
// First row
private void ConstructFirstRow(GameObject parent)
void ConstructFirstRow(GameObject parent)
{
GameObject rowObj = UIFactory.CreateUIObject("FirstRow", parent);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, true, true, true, true, 5, 2, 2, 2, 2);
@ -422,7 +468,7 @@ namespace UnityExplorer.Inspectors
filterInputField = UIFactory.CreateInputField(rowObj, "NameFilterInput", "...");
UIFactory.SetLayoutElement(filterInputField.UIRoot, minHeight: 25, flexibleWidth: 300);
filterInputField.OnValueChanged += (string val) => { SetFilter(val); };
filterInputField.OnValueChanged += (string val) => { OnSetNameFilter(val); };
GameObject spacer = UIFactory.CreateUIObject("Spacer", rowObj);
UIFactory.SetLayoutElement(spacer, minWidth: 25);
@ -431,7 +477,7 @@ namespace UnityExplorer.Inspectors
ButtonRef updateButton = UIFactory.CreateButton(rowObj, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f));
UIFactory.SetLayoutElement(updateButton.Component.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0);
updateButton.OnClick += UpdateClicked;
updateButton.OnClick += OnUpdateClicked;
GameObject toggleObj = UIFactory.CreateToggle(rowObj, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText);
UIFactory.SetLayoutElement(toggleObj, minWidth: 125, minHeight: 25);
@ -441,7 +487,7 @@ namespace UnityExplorer.Inspectors
// Second row
private void ConstructSecondRow(GameObject parent)
void ConstructSecondRow(GameObject parent)
{
GameObject rowObj = UIFactory.CreateUIObject("SecondRow", parent);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 2, 2, 2, 2);
@ -466,7 +512,7 @@ namespace UnityExplorer.Inspectors
AddMemberTypeToggle(rowObj, MemberTypes.Constructor, 110);
}
private void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
void AddScopeFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false)
{
string lbl = flags == BindingFlags.Default ? "All" : flags.ToString();
Color color = setAsActive ? enabledButtonColor : disabledButtonColor;
@ -475,10 +521,10 @@ namespace UnityExplorer.Inspectors
UIFactory.SetLayoutElement(button.Component.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 70, flexibleWidth: 0);
scopeFilterButtons.Add(flags, button);
button.OnClick += () => { SetFilter(flags); };
button.OnClick += () => { OnSetFlags(flags); };
}
private void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
void AddMemberTypeToggle(GameObject parent, MemberTypes type, int width)
{
GameObject toggleObj = UIFactory.CreateToggle(parent, "Toggle_" + type, out Toggle toggle, out Text toggleText);
UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: width);

View File

@ -59,7 +59,7 @@ namespace UnityExplorer.Loader.BIE
public override void SaveConfig()
{
// not required
Config.Save();
}
}
}

View File

@ -294,6 +294,8 @@ namespace UnityExplorer.UI.Panels
ourCamera.transform.position = (Vector3)currentUserCameraPosition;
ourCamera.transform.rotation = (Quaternion)currentUserCameraRotation;
}
positionInput.Text = ParseUtility.ToStringForInput<Vector3>(originalCameraPosition);
}
void PositionInput_OnEndEdit(string input)

View File

@ -1,7 +1,10 @@
using UnityEngine;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
@ -11,76 +14,58 @@ namespace UnityExplorer.UI.Panels
{
public class HookManagerPanel : UEPanel
{
public static HookManagerPanel Instance { get; private set; }
public enum Pages
{
CurrentHooks,
ClassMethodSelector,
HookSourceEditor
HookSourceEditor,
GenericArgsSelector,
}
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public static HookCreator hookCreator;
public static HookList hookList;
public static GenericConstructorWidget genericArgsHandler;
// Panel
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks";
public override bool ShowByDefault => false;
public override int MinWidth => 500;
public override int MinHeight => 600;
public override int MinWidth => 400;
public override int MinHeight => 400;
public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f);
public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f);
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
private GameObject currentHooksPanel;
public ScrollPool<HookCell> HooksScrollPool;
private InputFieldRef classSelectorInputField;
private GameObject addHooksPanel;
public ScrollPool<AddHookCell> AddHooksScrollPool;
private Text addHooksLabel;
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 Pages CurrentPage { get; private set; } = Pages.ClassMethodSelector;
public HookManagerPanel(UIBase owner) : base(owner)
{
}
private void OnClassInputAddClicked()
{
HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
}
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
public void SetPage(Pages page)
{
switch (page)
{
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);
HookCreator.AddHooksRoot.SetActive(true);
HookCreator.EditorRoot.SetActive(false);
genericArgsHandler.UIRoot.SetActive(false);
break;
case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(false);
editorPanel.SetActive(true);
HookCreator.AddHooksRoot.SetActive(false);
HookCreator.EditorRoot.SetActive(true);
genericArgsHandler.UIRoot.SetActive(false);
break;
case Pages.GenericArgsSelector:
HookCreator.AddHooksRoot.SetActive(false);
HookCreator.EditorRoot.SetActive(false);
genericArgsHandler.UIRoot.SetActive(true);
break;
}
}
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void SetDefaultSizeAndPosition()
{
base.SetDefaultSizeAndPosition();
@ -91,115 +76,35 @@ namespace UnityExplorer.UI.Panels
protected override void ConstructPanelContent()
{
// ~~~~~~~~~ Active hooks scroll pool
Instance = this;
hookList = new();
hookCreator = new();
genericArgsHandler = new();
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.ContentRoot);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ContentRoot, true, false);
GameObject 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);
// GameObject baseHoriGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "HoriGroup", true, true, true, true);
// UIFactory.SetLayoutElement(baseHoriGroup, flexibleWidth: 9999, flexibleHeight: 9999);
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
// // Left Group
ButtonRef addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
addButton.OnClick += OnClassInputAddClicked;
//GameObject leftGroup = UIFactory.CreateVerticalGroup(ContentRoot, "LeftGroup", true, true, true, true);
UIFactory.SetLayoutElement(ContentRoot.gameObject, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
Text hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
hookList.ConstructUI(ContentRoot);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// // Right Group
// ~~~~~~~~~ Add hooks panel
//GameObject rightGroup = UIFactory.CreateVerticalGroup(ContentRoot, "RightGroup", true, true, true, true);
UIFactory.SetLayoutElement(ContentRoot, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999);
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.ContentRoot);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
hookCreator.ConstructAddHooksView(ContentRoot);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
hookCreator.ConstructEditor(ContentRoot);
HookCreator.EditorRoot.SetActive(false);
GameObject buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef 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.DoneAddingHooks;
AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
AddHooksScrollPool.Initialize(HookManager.Instance);
addHooksPanel.gameObject.SetActive(false);
// ~~~~~~~~~ Hook source editor panel
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.ContentRoot);
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
Text 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);
GameObject editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
ButtonRef 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;
ButtonRef 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;
GameObject inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out InputFieldScroller 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
GameObject highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
RectTransform 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 = UniversalUI.ConsoleFont;
EditorInput.PlaceholderText.font = UniversalUI.ConsoleFont;
EditorHighlightText.font = UniversalUI.ConsoleFont;
editorPanel.SetActive(false);
genericArgsHandler.ConstructUI(ContentRoot);
genericArgsHandler.UIRoot.SetActive(false);
}
}
}

View File

@ -14,8 +14,6 @@ using UniverseLib.Utility;
namespace UnityExplorer.UI.Panels
{
// TODO move the logic out of this class into a LogUtil class (also move ExplorerCore.Log into that)
public class LogPanel : UEPanel, ICellPoolDataSource<ConsoleLogCell>
{
public struct LogInfo

View File

@ -28,59 +28,114 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
public Type[] GenericConstraints { get; set; }
public bool AllTypes { get; set; }
private readonly bool allowAbstract;
private readonly bool allowEnum;
public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false;
private readonly List<Suggestion> suggestions = new();
private readonly HashSet<string> suggestedNames = new();
readonly bool allowAbstract;
readonly bool allowEnum;
readonly bool allowGeneric;
private HashSet<Type> allowedTypes;
readonly List<Suggestion> suggestions = new();
readonly HashSet<string> suggestedNames = new();
private string chosenSuggestion;
bool ISuggestionProvider.AllowNavigation => false;
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true) { }
static readonly Dictionary<string, Type> shorthandToType = new()
{
{ "object", typeof(object) },
{ "string", typeof(string) },
{ "bool", typeof(bool) },
{ "byte", typeof(byte) },
{ "sbyte", typeof(sbyte) },
{ "char", typeof(char) },
{ "decimal", typeof(decimal) },
{ "double", typeof(double) },
{ "float", typeof(float) },
{ "int", typeof(int) },
{ "uint", typeof(uint) },
{ "long", typeof(long) },
{ "ulong", typeof(ulong) },
{ "short", typeof(short) },
{ "ushort", typeof(ushort) },
{ "void", typeof(void) },
};
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum)
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true, true) { }
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum, bool allowGeneric)
{
BaseType = baseType;
InputField = inputField;
this.allowAbstract = allowAbstract;
this.allowEnum = allowEnum;
this.allowGeneric = allowGeneric;
inputField.OnValueChanged += OnInputFieldChanged;
if (BaseType != null)
if (BaseType != null || AllTypes)
CacheTypes();
}
public void CacheTypes()
{
if (!AllTypes)
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowEnum, false);
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowGeneric, allowEnum);
else
allowedTypes = GetAllAllowedTypes();
// Check generic parameter constraints
if (GenericConstraints != null && GenericConstraints.Any())
{
allowedTypes = new();
foreach (KeyValuePair<string, Type> entry in ReflectionUtility.AllTypes)
List<Type> typesToRemove = new();
foreach (Type type in allowedTypes)
{
// skip <PrivateImplementationDetails> and <AnonymousClass> classes
Type type = entry.Value;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
bool allowed = true;
foreach (Type constraint in GenericConstraints)
{
continue;
if (!constraint.IsAssignableFrom(type))
{
allowed = false;
break;
}
}
allowedTypes.Add(type);
if (!allowed)
typesToRemove.Add(type);
}
foreach (Type type in typesToRemove)
allowedTypes.Remove(type);
}
}
HashSet<Type> GetAllAllowedTypes()
{
HashSet<Type> allAllowedTypes = new();
foreach (KeyValuePair<string, Type> entry in ReflectionUtility.AllTypes)
{
Type type = entry.Value;
if ((!allowAbstract && type.IsAbstract)
|| (!allowGeneric && type.IsGenericType)
|| (!allowEnum && type.IsEnum))
continue;
// skip <PrivateImplementationDetails> and <AnonymousClass> classes
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
{
continue;
}
allAllowedTypes.Add(type);
}
return allAllowedTypes;
}
public void OnSuggestionClicked(Suggestion suggestion)
{
InputField.Text = suggestion.UnderlyingValue;
@ -110,24 +165,34 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
}
}
private void GetSuggestions(string value)
private void GetSuggestions(string input)
{
suggestions.Clear();
suggestedNames.Clear();
if (BaseType == null)
if (!AllTypes && BaseType == null)
{
ExplorerCore.LogWarning("Autocompleter Base type is null!");
return;
}
// shorthand types all inherit from System.Object
if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand))
AddSuggestion(shorthand);
foreach (KeyValuePair<string, Type> entry in shorthandToType)
{
if (allowedTypes.Contains(entry.Value) && entry.Key.StartsWith(input, StringComparison.InvariantCultureIgnoreCase))
AddSuggestion(entry.Value);
}
// Check for exact match first
if (ReflectionUtility.GetTypeByName(value) is Type t && allowedTypes.Contains(t))
if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(t))
AddSuggestion(t);
foreach (Type entry in allowedTypes)
{
if (entry.FullName.ContainsIgnoreCase(value))
if (entry.FullName.ContainsIgnoreCase(input))
AddSuggestion(entry);
}
}

View File

@ -9,8 +9,6 @@ namespace UnityExplorer.UI.Widgets
{
public abstract class BaseArgumentHandler : IPooledObject
{
protected EvaluateWidget evaluator;
internal Text argNameLabel;
internal InputFieldRef inputField;
internal TypeCompleter typeCompleter;

View File

@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets
GenericArgumentHandler holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.genericArgumentsHolder.transform, false);
holder.OnBorrowed(this, type);
holder.OnBorrowed(type);
}
}
@ -122,7 +122,7 @@ namespace UnityExplorer.UI.Widgets
ParameterHandler holder = paramHandlers[i] = Pool<ParameterHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.parametersHolder.transform, false);
holder.OnBorrowed(this, param);
holder.OnBorrowed(param);
}
}

View File

@ -7,21 +7,21 @@ namespace UnityExplorer.UI.Widgets
{
public class GenericArgumentHandler : BaseArgumentHandler
{
private Type genericType;
private Type genericArgument;
public void OnBorrowed(EvaluateWidget evaluator, Type genericConstraint)
public void OnBorrowed(Type genericArgument)
{
this.evaluator = evaluator;
this.genericType = genericConstraint;
this.genericArgument = genericArgument;
typeCompleter.Enabled = true;
typeCompleter.BaseType = genericType;
typeCompleter.CacheTypes();
typeCompleter.BaseType = this.genericArgument;
Type[] constraints = genericType.GetGenericParameterConstraints();
Type[] constraints = this.genericArgument.GetGenericParameterConstraints();
typeCompleter.GenericConstraints = constraints;
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{genericType.Name}</color>");
typeCompleter.CacheTypes();
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{this.genericArgument.Name}</color>");
for (int j = 0; j < constraints.Length; j++)
{
@ -39,8 +39,7 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned()
{
this.evaluator = null;
this.genericType = null;
this.genericArgument = null;
this.typeCompleter.Enabled = false;

View File

@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Panels;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.ObjectPool;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Widgets
{
public class GenericConstructorWidget
{
GenericArgumentHandler[] handlers;
Type[] currentGenericParameters;
Action<Type[]> currentOnSubmit;
Action currentOnCancel;
public GameObject UIRoot;
Text Title;
GameObject ArgsHolder;
public void Show(Action<Type[]> onSubmit, Action onCancel, Type genericTypeDefinition)
{
Title.text = $"Setting generic arguments for {SignatureHighlighter.Parse(genericTypeDefinition, false)}...";
OnShow(onSubmit, onCancel, genericTypeDefinition.GetGenericArguments());
}
public void Show(Action<Type[]> onSubmit, Action onCancel, MethodInfo genericMethodDefinition)
{
Title.text = $"Setting generic arguments for {SignatureHighlighter.ParseMethod(genericMethodDefinition)}...";
OnShow(onSubmit, onCancel, genericMethodDefinition.GetGenericArguments());
}
void OnShow(Action<Type[]> onSubmit, Action onCancel, Type[] genericParameters)
{
currentOnSubmit = onSubmit;
currentOnCancel = onCancel;
SetGenericParameters(genericParameters);
}
void SetGenericParameters(Type[] genericParameters)
{
currentGenericParameters = genericParameters;
handlers = new GenericArgumentHandler[genericParameters.Length];
for (int i = 0; i < genericParameters.Length; i++)
{
Type type = genericParameters[i];
GenericArgumentHandler holder = handlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(ArgsHolder.transform, false);
holder.OnBorrowed(type);
}
}
public void TrySubmit()
{
Type[] args = new Type[currentGenericParameters.Length];
for (int i = 0; i < args.Length; i++)
{
GenericArgumentHandler handler = handlers[i];
Type arg;
try
{
arg = handler.Evaluate();
if (arg == null) throw new Exception();
}
catch
{
ExplorerCore.LogWarning($"Generic argument '{handler.inputField.Text}' is not a valid type.");
return;
}
args[i] = arg;
}
OnClose();
currentOnSubmit(args);
}
public void Cancel()
{
OnClose();
currentOnCancel?.Invoke();
}
void OnClose()
{
if (handlers != null)
{
foreach (GenericArgumentHandler widget in handlers)
{
widget.OnReturned();
Pool<GenericArgumentHandler>.Return(widget);
}
handlers = null;
}
}
// UI Construction
internal void ConstructUI(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "GenericArgsHandler", false, false, true, true, 5, new Vector4(5, 5, 5, 5),
childAlignment: TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, flexibleHeight: 9999);
ButtonRef submitButton = UIFactory.CreateButton(UIRoot, "SubmitButton", "Submit", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(submitButton.GameObject, minHeight: 25, minWidth: 200);
submitButton.OnClick += TrySubmit;
ButtonRef cancelButton = UIFactory.CreateButton(UIRoot, "CancelButton", "Cancel", new Color(0.3f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(cancelButton.GameObject, minHeight: 25, minWidth: 200);
cancelButton.OnClick += Cancel;
Title = UIFactory.CreateLabel(UIRoot, "Title", "Generic Arguments", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(Title.gameObject, minHeight: 25, flexibleWidth: 9999);
GameObject scrollview = UIFactory.CreateScrollView(UIRoot, "GenericArgsScrollView", out ArgsHolder, out _, new(0.1f, 0.1f, 0.1f));
UIFactory.SetLayoutElement(scrollview, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(ArgsHolder, padTop: 5, padLeft: 5, padBottom: 5, padRight: 5);
}
}
}

View File

@ -26,9 +26,8 @@ namespace UnityExplorer.UI.Widgets
private Text basicLabel;
private ButtonRef pasteButton;
public void OnBorrowed(EvaluateWidget evaluator, ParameterInfo paramInfo)
public void OnBorrowed(ParameterInfo paramInfo)
{
this.evaluator = evaluator;
this.paramInfo = paramInfo;
this.paramType = paramInfo.ParameterType;
@ -85,7 +84,6 @@ namespace UnityExplorer.UI.Widgets
public void OnReturned()
{
this.evaluator = null;
this.paramInfo = null;
this.enumCompleter.Enabled = false;

View File

@ -71,7 +71,7 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(false);
toggleButton.ButtonText.text = "View Texture";
ParentInspector.mainContentHolder.SetActive(true);
ParentInspector.ContentRoot.SetActive(true);
}
else
{
@ -85,7 +85,7 @@ namespace UnityExplorer.UI.Widgets
textureViewerRoot.SetActive(true);
toggleButton.ButtonText.text = "Hide Texture";
ParentInspector.mainContentHolder.gameObject.SetActive(false);
ParentInspector.ContentRoot.gameObject.SetActive(false);
}
}

View File

@ -80,7 +80,7 @@ namespace UnityExplorer.UI.Widgets
if (this.UnityObjectRef)
{
nameInput.Text = UnityObjectRef.name;
ParentInspector.Tab.TabText.text = $"{ParentInspector.currentBaseTabText} \"{UnityObjectRef.name}\"";
ParentInspector.Tab.TabText.text = $"{ParentInspector.TabButtonText} \"{UnityObjectRef.name}\"";
}
}

View File

@ -79,11 +79,11 @@
<!-- il2cpp nuget -->
<ItemGroup Condition="'$(Configuration)'=='ML_Cpp_net6' or '$(Configuration)'=='ML_Cpp_net472' or '$(Configuration)'=='STANDALONE_Cpp' or '$(Configuration)'=='BIE_Cpp'">
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" IncludeAssets="compile" />
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.6" />
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.8" />
</ItemGroup>
<!-- mono nuget -->
<ItemGroup Condition="'$(Configuration)'=='BIE6_Mono' or '$(Configuration)'=='BIE5_Mono' or '$(Configuration)'=='ML_Mono' or '$(Configuration)'=='STANDALONE_Mono'">
<PackageReference Include="UniverseLib.Mono" Version="1.3.6" />
<PackageReference Include="UniverseLib.Mono" Version="1.3.8" />
</ItemGroup>
<!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ -->