Compare commits

..

14 Commits
4.2.1 ... 4.3.1

Author SHA1 Message Date
93181f02be Update README.md 2021-09-07 17:33:01 +10:00
371054d6df Add hook source editor for custom dynamic hooks 2021-09-07 17:23:20 +10:00
427f23b80a Bump version 2021-09-07 03:40:14 +10:00
a0d5ab8792 Fix ByRef parameters breaking generated patch code 2021-09-07 03:40:09 +10:00
297034e38b Cleanup 2021-09-07 03:19:40 +10:00
57f59d1295 Finalize hook manager update 2021-09-06 23:10:10 +10:00
fbdb84eefa Implement HookManager UI and logic 2021-09-06 23:04:40 +10:00
6989ea1b19 Misc cleanups 2021-09-06 23:03:55 +10:00
fcdfeb2dec Redesign mouse inspector class, add UI results panel 2021-09-06 17:10:01 +10:00
a1d0b6432e Cleanup 2021-09-06 15:51:40 +10:00
0b84405e57 Fix incorrect startup delay logic 2021-09-02 18:29:21 +10:00
c31e0949d3 Update ExplorerMelonMod.cs 2021-09-02 17:30:56 +10:00
5f0495a7ea Update README.md 2021-08-24 18:42:22 +10:00
28de6779d8 Fix for Canvas.renderingDisplaySize crash 2021-08-24 17:47:42 +10:00
29 changed files with 1290 additions and 317 deletions

View File

@ -8,14 +8,13 @@
<p align="center">
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
</p>
<p align="center">
⚡ UnityExplorer is on <a href="https://thunderstore.io/package/sinai-dev/UnityExplorer/">Thunderstore</a>! (and as <a href="https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP/">IL2CPP</a>)
</p>
# Releases [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases)
[![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=version)](../../releases/latest) [![](https://img.shields.io/github/workflow/status/sinai-dev/UnityExplorer/Build%20UnityExplorer)](https://github.com/sinai-dev/UnityExplorer/actions) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../releases/latest)
⚡ Thunderstore releases: [BepInEx Mono](https://thunderstore.io/package/sinai-dev/UnityExplorer) | [BepInEx IL2CPP](https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP) | [MelonLoader IL2CPP](https://boneworks.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP_ML)
## BepInEx
| Release | IL2CPP | Mono |
@ -85,6 +84,12 @@ The inspector is used to see detailed information on objects of any type and man
* You can execute a script automatically on startup by naming it `startup.cs` and placing it in the `UnityExplorer\Scripts\` folder (this folder will be created where you placed the DLL file).
* See the "Help" dropdown in the C# console menu for more detailed information.
### 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.
* 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
* The "Mouse Inspect" dropdown on the main UnityExplorer navbar allows you to inspect objects under the mouse.
@ -100,12 +105,6 @@ The inspector is used to see detailed information on objects of any type and man
# Building
If you fork the repository on GitHub you can build using the [dotnet workflow](https://github.com/sinai-dev/UnityExplorer/blob/master/.github/workflows/dotnet.yml):
0. Click on the Actions tab and enable workflows in your repository
1. Click on the "Build UnityExplorer" workflow, then click "Run Workflow" and run it manually, or make a new commit to trigger the workflow.
2. Take the artifact from the completed run.
For Visual Studio:
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
@ -113,6 +112,12 @@ For Visual Studio:
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`), and if using IL2CPP then build `Il2CppAssemblyUnhollower` (Release/AnyCPU) as well.
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
If you fork the repository on GitHub you can build using the [dotnet workflow](https://github.com/sinai-dev/UnityExplorer/blob/master/.github/workflows/dotnet.yml):
0. Click on the Actions tab and enable workflows in your repository
1. Click on the "Build UnityExplorer" workflow, then click "Run Workflow" and run it manually, or make a new commit to trigger the workflow.
2. Take the artifact from the completed run.
# Acknowledgments
* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], the ScriptEvaluator from RUE's REPL console was used as the base for UnityExplorer's C# console.

View File

@ -202,7 +202,7 @@ namespace UnityExplorer.CSConsole
{
// The compiled code was not REPL, so it was a using directive or it defined classes.
string output = ScriptEvaluator._textWriter.ToString();
string output = Evaluator._textWriter.ToString();
var outputSplit = output.Split('\n');
if (outputSplit.Length >= 2)
output = outputSplit[outputSplit.Length - 2];

View File

@ -16,7 +16,7 @@ namespace UnityExplorer.CSConsole
"mscorlib", "System.Core", "System", "System.Xml"
};
internal static TextWriter _textWriter;
internal TextWriter _textWriter;
internal static StreamReportPrinter _reportPrinter;
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
@ -51,8 +51,13 @@ namespace UnityExplorer.CSConsole
ReferenceAssembly(asm);
}
private static CompilerContext context;
private static CompilerContext BuildContext(TextWriter tw)
{
if (context != null)
return context;
_reportPrinter = new StreamReportPrinter(tw);
var settings = new CompilerSettings
@ -65,7 +70,7 @@ namespace UnityExplorer.CSConsole
EnhancedWarnings = false
};
return new CompilerContext(settings, _reportPrinter);
return context = new CompilerContext(settings, _reportPrinter);
}
private static void ImportAppdomainAssemblies(Action<Assembly> import)

View File

@ -14,9 +14,6 @@ namespace UnityExplorer.CacheObject
{
public abstract class CacheMember : CacheObjectBase
{
//public ReflectionInspector ParentInspector { get; internal set; }
//public bool AutoUpdateWanted { get; internal set; }
public abstract Type DeclaringType { get; }
public string NameForFiltering { get; protected set; }
public object DeclaringInstance => IsStatic ? null : (m_declaringInstance ?? (m_declaringInstance = Owner.Target.TryCast(DeclaringType)));
@ -61,6 +58,17 @@ namespace UnityExplorer.CacheObject
protected abstract void TrySetValue(object value);
/// <summary>
/// Evaluate is called when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
/// </summary>
public void Evaluate()
{
SetValueFromSource(TryEvaluate());
}
/// <summary>
/// Called when user presses the Evaluate button.
/// </summary>
public void EvaluateAndSetCell()
{
Evaluate();
@ -68,27 +76,15 @@ namespace UnityExplorer.CacheObject
SetDataToCell(CellView);
}
/// <summary>
/// Evaluate when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
/// </summary>
public void Evaluate()
{
SetValueFromSource(TryEvaluate());
}
public override void TrySetUserValue(object value)
{
TrySetValue(value);
Evaluate();
}
protected override void SetValueState(CacheObjectCell cell, ValueStateArgs args)
{
base.SetValueState(cell, args);
//var memCell = cell as CacheMemberCell;
//memCell.UpdateToggle.gameObject.SetActive(ShouldAutoEvaluate);
}
private static readonly Color evalEnabledColor = new Color(0.15f, 0.25f, 0.15f);
@ -101,7 +97,6 @@ namespace UnityExplorer.CacheObject
cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate);
if (!ShouldAutoEvaluate)
{
//cell.UpdateToggle.gameObject.SetActive(false);
cell.EvaluateButton.Component.gameObject.SetActive(true);
if (HasArguments)
{
@ -120,11 +115,6 @@ namespace UnityExplorer.CacheObject
if (!Evaluating)
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
}
//else
//{
// cell.UpdateToggle.gameObject.SetActive(true);
// cell.UpdateToggle.isOn = AutoUpdateWanted;
//}
if (State == ValueState.NotEvaluated && !ShouldAutoEvaluate)
{
@ -140,7 +130,6 @@ namespace UnityExplorer.CacheObject
return false;
}
public void OnEvaluateClicked()
{
if (!HasArguments)
@ -246,7 +235,7 @@ namespace UnityExplorer.CacheObject
var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log($"Trying to cache member {sig}... ({member.MemberType})");
CacheMember cached;
Type returnType;
@ -311,7 +300,6 @@ namespace UnityExplorer.CacheObject
cachedSigs.Add(sig);
//cached.Initialize(_inspector, declaringType, member, returnType);
cached.SetFallbackType(returnType);
cached.SetInspectorOwner(_inspector, member);

View File

@ -169,7 +169,6 @@ namespace UnityExplorer.CacheObject.IValues
}
public int AdjustedWidth => (int)UIRect.rect.width - 80;
//public int AdjustedKeyWidth => HalfWidth - 50;
public override void SetLayout()
{

View File

@ -37,6 +37,7 @@ namespace UnityExplorer.Core.Config
public static ConfigElement<string> CSConsoleData;
public static ConfigElement<string> OptionsPanelData;
public static ConfigElement<string> ConsoleLogData;
public static ConfigElement<string> HookManagerData;
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
@ -126,6 +127,7 @@ namespace UnityExplorer.Core.Config
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
HookManagerData = new ConfigElement<string>("HookManager", "", "", true);
}
}
}

View File

@ -31,9 +31,6 @@ namespace UnityExplorer
}
}
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
=> ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
// ------- Misc extensions --------
/// <summary>

View File

@ -14,7 +14,6 @@ using System.Diagnostics.CodeAnalysis;
using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnhollowerBaseLib.Attributes;
using UnityEngine;
@ -562,6 +561,7 @@ namespace UnityExplorer
"UnityEngine.Audio.AudioMixerPlayable.Create",
"UnityEngine.BoxcastCommand.ScheduleBatch",
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
"UnityEngine.Canvas.renderingDisplaySize",
"UnityEngine.CapsulecastCommand.ScheduleBatch",
"UnityEngine.Collider2D.Cast",
"UnityEngine.Collider2D.Raycast",

View File

@ -251,27 +251,25 @@ namespace UnityExplorer
/// </summary>
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
/// <returns>All implementations of the type in the current AppDomain.</returns>
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowRecursive = true)
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowEnum, bool allowRecursive = true)
{
var key = GetImplementationKey(baseType);
int count = AllTypes.Count;
HashSet<Type> ret;
if (!baseType.IsGenericParameter)
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric);
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric, allowEnum);
else
ret = GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric);
// types were resolved during the parse, do it again if we're not already rebuilding.
if (allowRecursive && AllTypes.Count != count)
{
ret = GetImplementationsOf(baseType, allowAbstract, allowGeneric, false);
}
return ret;
}
private static HashSet<Type> GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
private static HashSet<Type> GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric, bool allowEnum)
{
if (!typeInheritance.ContainsKey(key))
{
@ -287,7 +285,8 @@ namespace UnityExplorer
if (set.Contains(type)
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))
|| (!allowEnum && type.IsEnum))
continue;
if (type.FullName.Contains("PrivateImplementationDetails")

View File

@ -56,6 +56,9 @@ namespace UnityExplorer
public abstract int GetRootCount(Scene scene);
public void SetColorBlockAuto(Selectable selectable, Color baseColor)
=> SetColorBlock(selectable, baseColor, baseColor * 1.2f, baseColor * 0.8f);
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,

View File

@ -20,7 +20,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.2.1";
public const string VERSION = "4.3.1";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
@ -66,14 +66,13 @@ namespace UnityExplorer
private static IEnumerator SetupCoroutine()
{
yield return null;
float start = Time.realtimeSinceStartup;
float prevRealTime = Time.realtimeSinceStartup;
float delay = ConfigManager.Startup_Delay_Time.Value;
while (delay > 0)
{
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - start);
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - prevRealTime);
delay -= diff;
prevRealTime = Time.realtimeSinceStartup;
yield return null;
}

63
src/Hooks/AddHookCell.cs Normal file
View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.Hooks
{
public class AddHookCell : ICell
{
public bool Enabled => UIRoot.activeSelf;
public RectTransform Rect { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => 30;
public Text MethodNameLabel;
public Text HookedLabel;
public ButtonRef HookButton;
public int CurrentDisplayedIndex;
private void OnHookClicked()
{
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
}
public void Enable()
{
this.UIRoot.SetActive(true);
}
public void Disable()
{
this.UIRoot.SetActive(false);
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
Rect = UIRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
HookButton.OnClick += OnHookClicked;
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
return UIRoot;
}
}
}

79
src/Hooks/HookCell.cs Normal file
View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.Hooks
{
public class HookCell : ICell
{
public bool Enabled => UIRoot.activeSelf;
public RectTransform Rect { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => 30;
public Text MethodNameLabel;
public ButtonRef EditPatchButton;
public ButtonRef ToggleActiveButton;
public ButtonRef DeleteButton;
public int CurrentDisplayedIndex;
private void OnToggleActiveClicked()
{
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
}
private void OnDeleteClicked()
{
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
}
private void OnEditPatchClicked()
{
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
Rect = UIRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
ToggleActiveButton.OnClick += OnToggleActiveClicked;
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
DeleteButton.OnClick += OnDeleteClicked;
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
EditPatchButton.OnClick += OnEditPatchClicked;
return UIRoot;
}
public void Disable()
{
UIRoot.SetActive(false);
}
public void Enable()
{
UIRoot.SetActive(true);
}
}
}

225
src/Hooks/HookInstance.cs Normal file
View File

@ -0,0 +1,225 @@
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using Mono.CSharp;
using UnityExplorer.CSConsole;
namespace UnityExplorer.Hooks
{
public class HookInstance
{
// Static
private static readonly StringBuilder evalOutput = new StringBuilder();
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
// Instance
public bool Enabled;
public MethodInfo TargetMethod;
public string PatchSourceCode;
private readonly string shortSignature;
private PatchProcessor patchProcessor;
private MethodInfo postfix;
private MethodInfo prefix;
private MethodInfo finalizer;
private MethodInfo transpiler;
public HookInstance(MethodInfo targetMethod)
{
this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
GenerateDefaultPatchSourceCode(targetMethod);
if (CompileAndGenerateProcessor(PatchSourceCode))
Patch();
}
// Evaluator.source_file
private static readonly FieldInfo fi_sourceFile = ReflectionUtility.GetFieldInfo(typeof(Evaluator), "source_file");
// TypeDefinition.Definition
private static readonly PropertyInfo pi_Definition = ReflectionUtility.GetPropertyInfo(typeof(TypeDefinition), "Definition");
public bool CompileAndGenerateProcessor(string patchSource)
{
Unpatch();
try
{
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method
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)
throw new FormatException($"Unable to compile the generated patch!");
// TODO: Publicize MCS to avoid this reflection
// Get the most recent Patch type in the source file
var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator))
.Containers
.Last(it => it.MemberName.Name.StartsWith("DynamicPatch_"));
// Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type)
var patchClass = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)).GetMetaInfo();
// Create the harmony patches as defined
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;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
return false;
}
}
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{
var codeBuilder = new StringBuilder();
// Arguments
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
int paramIdx = 0;
var parameters = targetMethod.GetParameters();
foreach (var param in parameters)
{
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
codeBuilder.Append($", {pType.FullName} __{paramIdx}");
paramIdx++;
}
codeBuilder.Append(")\n");
// Patch body
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {");
// Log message
var logMessage = new StringBuilder();
logMessage.Append($"Patch called: {shortSignature}\\n");
if (!targetMethod.IsStatic)
logMessage.Append("__instance: {__instance.ToString()}\\n");
paramIdx = 0;
foreach (var param in parameters)
{
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType)
logMessage.Append($"{{__{paramIdx}.ToString()}}");
else
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
logMessage.Append("\\n");
paramIdx++;
}
if (targetMethod.ReturnType != typeof(void))
{
logMessage.Append("Return value: ");
if (targetMethod.ReturnType.IsValueType)
logMessage.Append("{__result.ToString()}");
else
logMessage.Append("{__result?.ToString() ?? \"null\"}");
logMessage.Append("\\n");
}
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
codeBuilder.AppendLine(" }");
// End patch body
codeBuilder.AppendLine("}");
return PatchSourceCode = codeBuilder.ToString();
}
public void TogglePatch()
{
Enabled = !Enabled;
if (Enabled)
Patch();
else
Unpatch();
}
public void Patch()
{
try
{
patchProcessor.Patch();
Enabled = true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
}
}
public void Unpatch()
{
try
{
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;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
}
}
}
}

272
src/Hooks/HookManager.cs Normal file
View File

@ -0,0 +1,272 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using UnityEngine;
using UnityExplorer.CSConsole;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.Hooks
{
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
{
private static HookManager s_instance;
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
// 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 HashSet<string>();
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
// adding hooks
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
private readonly List<MethodInfo> filteredEligableMethods = new List<MethodInfo>();
// hook editor
private readonly LexerBuilder Lexer = new LexerBuilder();
private HookInstance currentEditedHook;
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
public void EnableOrDisableHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
Panel.HooksScrollPool.Refresh(true, false);
}
public void DeleteHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
Panel.HooksScrollPool.Refresh(true, false);
}
public void EditPatchClicked(int index)
{
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
var hook = (HookInstance)currentHooks[index];
currentEditedHook = hook;
Panel.EditorInput.Text = hook.PatchSourceCode;
}
// Set current hook cell
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var hook = (HookInstance)this.currentHooks[index];
cell.MethodNameLabel.text = HighlightMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
RuntimeProvider.Instance.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// ~~~~~~~~~~~ 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
public void SetCell(AddHookCell cell, int index)
{
if (index >= this.filteredEligableMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var method = this.filteredEligableMethods[index];
cell.MethodNameLabel.text = HighlightMethod(method);
var sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
{
cell.HookButton.Component.gameObject.SetActive(false);
cell.HookedLabel.gameObject.SetActive(true);
}
else
{
cell.HookButton.Component.gameObject.SetActive(true);
cell.HookedLabel.gameObject.SetActive(false);
}
}
// private static readonly string VOID_HIGHLIGHT = $"<color=#{SignatureHighlighter.keywordBlueHex}>void</color> ";
private static readonly Dictionary<string, string> highlightedMethods = new Dictionary<string, string>();
private string HighlightMethod(MethodInfo method)
{
var sig = method.FullDescription();
if (highlightedMethods.ContainsKey(sig))
return highlightedMethods[sig];
var sb = new StringBuilder();
// declaring type
sb.Append(SignatureHighlighter.Parse(method.DeclaringType, false));
sb.Append('.');
// method name
var color = !method.IsStatic
? SignatureHighlighter.METHOD_INSTANCE
: SignatureHighlighter.METHOD_STATIC;
sb.Append($"<color={color}>{method.Name}</color>");
// arguments
sb.Append('(');
var args = method.GetParameters();
if (args != null && args.Any())
{
int i = 0;
foreach (var param in args)
{
sb.Append(SignatureHighlighter.Parse(param.ParameterType, false));
sb.Append(' ');
sb.Append($"<color={SignatureHighlighter.LOCAL_ARG}>{param.Name}</color>");
i++;
if (i < args.Length)
sb.Append(", ");
}
}
sb.Append(')');
var ret = sb.ToString();
highlightedMethods.Add(sig, ret);
return ret;
}
}
}

View File

@ -8,6 +8,7 @@ using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Inspectors.MouseInspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
@ -23,17 +24,27 @@ namespace UnityExplorer.Inspectors
{
public static InspectUnderMouse Instance { get; private set; }
public InspectUnderMouse() { Instance = this; }
private readonly WorldInspector worldInspector;
private readonly UiInspector uiInspector;
public static void OnDropdownSelect(int index)
public static bool Inspecting { get; set; }
public static MouseInspectMode Mode { get; set; }
private static Vector3 lastMousePos;
public MouseInspectorBase CurrentInspector
{
switch (index)
get
{
case 0: return;
case 1: Instance.StartInspect(MouseInspectMode.World); break;
case 2: Instance.StartInspect(MouseInspectMode.UI); break;
switch (Mode)
{
case MouseInspectMode.UI:
return uiInspector;
case MouseInspectMode.World:
return worldInspector;
}
return null;
}
UIManager.MouseInspectDropdown.value = 0;
}
// UIPanel
@ -46,56 +57,54 @@ namespace UnityExplorer.Inspectors
public override bool ShouldSaveActiveState => false;
public override bool ShowByDefault => false;
internal static Text objNameLabel;
internal static Text objPathLabel;
internal static Text mousePosLabel;
internal Text objNameLabel;
internal Text objPathLabel;
internal Text mousePosLabel;
// Mouse Inspector
public static bool Inspecting { get; set; }
public static MouseInspectMode Mode { get; set; }
public InspectUnderMouse()
{
Instance = this;
worldInspector = new WorldInspector();
uiInspector = new UiInspector();
}
private static GameObject lastHitObject;
private static Vector3 lastMousePos;
private static readonly List<Graphic> wasDisabledGraphics = new List<Graphic>();
private static readonly List<CanvasGroup> wasDisabledCanvasGroups = new List<CanvasGroup>();
private static readonly List<GameObject> objectsAddedCastersTo = new List<GameObject>();
internal static Camera MainCamera;
internal static GraphicRaycaster[] graphicRaycasters;
public static void OnDropdownSelect(int index)
{
switch (index)
{
case 0: return;
case 1: Instance.StartInspect(MouseInspectMode.World); break;
case 2: Instance.StartInspect(MouseInspectMode.UI); break;
}
UIManager.MouseInspectDropdown.value = 0;
}
public void StartInspect(MouseInspectMode mode)
{
MainCamera = Camera.main;
if (!MainCamera && mode == MouseInspectMode.World)
{
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
return;
}
PanelDragger.ForceEnd();
Mode = mode;
Inspecting = true;
CurrentInspector.OnBeginMouseInspect();
PanelDragger.ForceEnd();
UIManager.NavBarRect.gameObject.SetActive(false);
UIManager.PanelHolder.SetActive(false);
UIRoot.SetActive(true);
if (mode == MouseInspectMode.UI)
SetupUIRaycast();
}
internal void ClearHitData()
{
lastHitObject = null;
CurrentInspector.ClearHitData();
objNameLabel.text = "No hits...";
objPathLabel.text = "";
}
public void StopInspect()
{
CurrentInspector.OnEndInspect();
ClearHitData();
Inspecting = false;
UIManager.NavBarRect.gameObject.SetActive(true);
@ -106,11 +115,6 @@ namespace UnityExplorer.Inspectors
drop.DestroyDropdownList(list.gameObject);
UIRoot.SetActive(false);
if (Mode == MouseInspectMode.UI)
StopUIInspect();
ClearHitData();
}
private static float timeOfLastRaycast;
@ -123,33 +127,22 @@ namespace UnityExplorer.Inspectors
return;
}
if (lastHitObject && InputManager.GetMouseButtonDown(0))
if (InputManager.GetMouseButtonDown(0))
{
var target = lastHitObject;
CurrentInspector.OnSelectMouseInspect();
StopInspect();
InspectorManager.Inspect(target);
return;
}
var mousePos = InputManager.MousePosition;
if (mousePos != lastMousePos)
UpdatePosition(mousePos);
if (!timeOfLastRaycast.OccuredEarlierThan(0.1f))
return;
timeOfLastRaycast = Time.realtimeSinceStartup;
// actual inspect raycast
switch (Mode)
{
case MouseInspectMode.UI:
RaycastUI(mousePos); break;
case MouseInspectMode.World:
RaycastWorld(mousePos); break;
}
CurrentInspector.UpdateMouseInspect(mousePos);
}
internal void UpdatePosition(Vector2 mousePos)
@ -171,181 +164,9 @@ namespace UnityExplorer.Inspectors
// calculate and set our UI position
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
UIRoot.transform.localPosition = new Vector3(inversePos.x, inversePos.y, 0);
}
internal void OnHitGameObject(GameObject obj)
{
if (obj != lastHitObject)
{
lastHitObject = obj;
objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
}
}
// Collider raycasting
internal void RaycastWorld(Vector2 mousePos)
{
var ray = MainCamera.ScreenPointToRay(mousePos);
Physics.Raycast(ray, out RaycastHit hit, 1000f);
if (hit.transform)
{
var obj = hit.transform.gameObject;
OnHitGameObject(obj);
}
else
{
if (lastHitObject)
ClearHitData();
}
}
// UI Graphic raycasting
private static void SetupUIRaycast()
{
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas)))
{
var canvas = obj.TryCast<Canvas>();
if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy)
continue;
if (!canvas.GetComponent<GraphicRaycaster>())
{
canvas.gameObject.AddComponent<GraphicRaycaster>();
//ExplorerCore.Log("Added raycaster to " + canvas.name);
objectsAddedCastersTo.Add(canvas.gameObject);
}
}
// recache Graphic Raycasters each time we start
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
graphicRaycasters = new GraphicRaycaster[casters.Length];
for (int i = 0; i < casters.Length; i++)
{
graphicRaycasters[i] = casters[i].TryCast<GraphicRaycaster>();
}
// enable raycastTarget on Graphics
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic)))
{
var graphic = obj.TryCast<Graphic>();
if (!graphic || !graphic.enabled || graphic.raycastTarget || !graphic.gameObject.activeInHierarchy)
continue;
graphic.raycastTarget = true;
//ExplorerCore.Log("Enabled raycastTarget on " + graphic.name);
wasDisabledGraphics.Add(graphic);
}
// enable blocksRaycasts on CanvasGroups
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(CanvasGroup)))
{
var canvas = obj.TryCast<CanvasGroup>();
if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts)
continue;
canvas.blocksRaycasts = true;
//ExplorerCore.Log("Enabled raycasts on " + canvas.name);
wasDisabledCanvasGroups.Add(canvas);
}
}
internal void RaycastUI(Vector2 mousePos)
{
var ped = new PointerEventData(null)
{
position = mousePos
};
//ExplorerCore.Log("~~~~~~~~~ begin raycast ~~~~~~~~");
GameObject hitObject = null;
int highestLayer = int.MinValue;
int highestOrder = int.MinValue;
int highestDepth = int.MinValue;
foreach (var gr in graphicRaycasters)
{
if (!gr || !gr.canvas)
continue;
var list = new List<RaycastResult>();
RuntimeProvider.Instance.GraphicRaycast(gr, ped, list);
if (list.Count > 0)
{
foreach (var hit in list)
{
// Manualy trying to determine which object is "on top".
// Could be improved, but seems to work pretty well and isn't as laggy as you would expect.
if (!hit.gameObject)
continue;
if (hit.gameObject.GetComponent<CanvasGroup>() is CanvasGroup group && group.alpha == 0)
continue;
if (hit.gameObject.GetComponent<Graphic>() is Graphic graphic && graphic.color.a == 0f)
continue;
if (hit.sortingLayer < highestLayer)
continue;
if (hit.sortingLayer > highestLayer)
{
highestLayer = hit.sortingLayer;
highestDepth = int.MinValue;
}
if (hit.depth < highestDepth)
continue;
if (hit.depth > highestDepth)
{
highestDepth = hit.depth;
highestOrder = int.MinValue;
}
if (hit.sortingOrder <= highestOrder)
continue;
highestOrder = hit.sortingOrder;
hitObject = hit.gameObject;
}
}
else
{
if (lastHitObject)
ClearHitData();
}
}
if (hitObject)
OnHitGameObject(hitObject);
//ExplorerCore.Log("~~~~~~~~~ end raycast ~~~~~~~~");
}
private static void StopUIInspect()
{
foreach (var obj in objectsAddedCastersTo)
{
if (obj.GetComponent<GraphicRaycaster>() is GraphicRaycaster raycaster)
GameObject.Destroy(raycaster);
}
foreach (var graphic in wasDisabledGraphics)
graphic.raycastTarget = false;
foreach (var canvas in wasDisabledCanvasGroups)
canvas.blocksRaycasts = false;
objectsAddedCastersTo.Clear();
wasDisabledCanvasGroups.Clear();
wasDisabledGraphics.Clear();
}
// UI Construction
protected internal override void DoSetDefaultPosAndAnchors()
@ -367,7 +188,10 @@ namespace UnityExplorer.Inspectors
// Title text
var title = UIFactory.CreateLabel(inspectContent, "InspectLabel", "<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)", TextAnchor.MiddleCenter);
var title = UIFactory.CreateLabel(inspectContent,
"InspectLabel",
"<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)",
TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(title.gameObject, flexibleWidth: 9999);
mousePosLabel = UIFactory.CreateLabel(inspectContent, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter);

View File

@ -21,19 +21,6 @@ namespace UnityExplorer
public static float PanelWidth;
internal static void CloseAllTabs()
{
if (Inspectors.Any())
{
for (int i = Inspectors.Count - 1; i >= 0; i--)
Inspectors[i].CloseInspector();
Inspectors.Clear();
}
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
}
public static void Inspect(object obj, CacheObjectBase sourceCache = null)
{
if (obj.IsNullOrDestroyed())
@ -50,6 +37,11 @@ namespace UnityExplorer
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
}
public static void Inspect(Type type)
{
CreateInspector<ReflectionInspector>(type, true);
}
private static bool TryFocusActiveInspector(object target)
{
foreach (var inspector in Inspectors)
@ -64,11 +56,6 @@ namespace UnityExplorer
return false;
}
public static void Inspect(Type type)
{
CreateInspector<ReflectionInspector>(type, true);
}
public static void SetInspectorActive(InspectorBase inspector)
{
UnsetActiveInspector();
@ -87,6 +74,19 @@ namespace UnityExplorer
}
}
internal static void CloseAllTabs()
{
if (Inspectors.Any())
{
for (int i = Inspectors.Count - 1; i >= 0; i--)
Inspectors[i].CloseInspector();
Inspectors.Clear();
}
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
}
private static void CreateInspector<T>(object target, bool staticReflection = false,
CacheObjectBase sourceCache = null) where T : InspectorBase
{

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.Inspectors.MouseInspectors
{
public abstract class MouseInspectorBase
{
public abstract void OnBeginMouseInspect();
public abstract void UpdateMouseInspect(Vector2 mousePos);
public abstract void OnSelectMouseInspect();
public abstract void ClearHitData();
public abstract void OnEndInspect();
}
}

View File

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
namespace UnityExplorer.Inspectors.MouseInspectors
{
public class UiInspector : MouseInspectorBase
{
public static readonly List<GameObject> LastHitObjects = new List<GameObject>();
private static GraphicRaycaster[] graphicRaycasters;
private static readonly List<GameObject> currentHitObjects = new List<GameObject>();
private static readonly List<Graphic> wasDisabledGraphics = new List<Graphic>();
private static readonly List<CanvasGroup> wasDisabledCanvasGroups = new List<CanvasGroup>();
private static readonly List<GameObject> objectsAddedCastersTo = new List<GameObject>();
public override void OnBeginMouseInspect()
{
SetupUIRaycast();
InspectUnderMouse.Instance.objPathLabel.text = "";
}
public override void ClearHitData()
{
currentHitObjects.Clear();
}
public override void OnSelectMouseInspect()
{
LastHitObjects.Clear();
LastHitObjects.AddRange(currentHitObjects);
var panel = UIManager.GetPanel<UiInspectorResultsPanel>(UIManager.Panels.UIInspectorResults);
panel.SetActive(true);
panel.ShowResults();
}
public override void UpdateMouseInspect(Vector2 mousePos)
{
currentHitObjects.Clear();
var ped = new PointerEventData(null)
{
position = mousePos
};
foreach (var gr in graphicRaycasters)
{
if (!gr || !gr.canvas)
continue;
var list = new List<RaycastResult>();
RuntimeProvider.Instance.GraphicRaycast(gr, ped, list);
if (list.Count > 0)
{
foreach (var hit in list)
{
if (hit.gameObject)
currentHitObjects.Add(hit.gameObject);
}
}
}
if (currentHitObjects.Any())
InspectUnderMouse.Instance.objNameLabel.text = $"Click to view UI Objects under mouse: {currentHitObjects.Count}";
else
InspectUnderMouse.Instance.objNameLabel.text = $"No UI objects under mouse.";
}
private static void SetupUIRaycast()
{
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas)))
{
var canvas = obj.TryCast<Canvas>();
if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy)
continue;
if (!canvas.GetComponent<GraphicRaycaster>())
{
canvas.gameObject.AddComponent<GraphicRaycaster>();
//ExplorerCore.Log("Added raycaster to " + canvas.name);
objectsAddedCastersTo.Add(canvas.gameObject);
}
}
// recache Graphic Raycasters each time we start
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
graphicRaycasters = new GraphicRaycaster[casters.Length];
for (int i = 0; i < casters.Length; i++)
{
graphicRaycasters[i] = casters[i].TryCast<GraphicRaycaster>();
}
// enable raycastTarget on Graphics
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic)))
{
var graphic = obj.TryCast<Graphic>();
if (!graphic || !graphic.enabled || graphic.raycastTarget || !graphic.gameObject.activeInHierarchy)
continue;
graphic.raycastTarget = true;
//ExplorerCore.Log("Enabled raycastTarget on " + graphic.name);
wasDisabledGraphics.Add(graphic);
}
// enable blocksRaycasts on CanvasGroups
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(CanvasGroup)))
{
var canvas = obj.TryCast<CanvasGroup>();
if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts)
continue;
canvas.blocksRaycasts = true;
//ExplorerCore.Log("Enabled raycasts on " + canvas.name);
wasDisabledCanvasGroups.Add(canvas);
}
}
public override void OnEndInspect()
{
foreach (var obj in objectsAddedCastersTo)
{
if (obj.GetComponent<GraphicRaycaster>() is GraphicRaycaster raycaster)
GameObject.Destroy(raycaster);
}
foreach (var graphic in wasDisabledGraphics)
graphic.raycastTarget = false;
foreach (var canvas in wasDisabledCanvasGroups)
canvas.blocksRaycasts = false;
objectsAddedCastersTo.Clear();
wasDisabledCanvasGroups.Clear();
wasDisabledGraphics.Clear();
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.Inspectors.MouseInspectors
{
public class WorldInspector : MouseInspectorBase
{
private static Camera MainCamera;
private static GameObject lastHitObject;
public override void OnBeginMouseInspect()
{
MainCamera = Camera.main;
if (!MainCamera)
{
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
return;
}
}
public override void ClearHitData()
{
lastHitObject = null;
}
public override void OnSelectMouseInspect()
{
InspectorManager.Inspect(lastHitObject);
}
public override void UpdateMouseInspect(Vector2 mousePos)
{
var ray = MainCamera.ScreenPointToRay(mousePos);
Physics.Raycast(ray, out RaycastHit hit, 1000f);
if (hit.transform)
OnHitGameObject(hit.transform.gameObject);
else if (lastHitObject)
InspectUnderMouse.Instance.ClearHitData();
}
internal void OnHitGameObject(GameObject obj)
{
if (obj != lastHitObject)
{
lastHitObject = obj;
InspectUnderMouse.Instance.objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
InspectUnderMouse.Instance.objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
}
}
public override void OnEndInspect()
{
// not needed
}
}
}

View File

@ -2,16 +2,11 @@
using System;
using System.IO;
using MelonLoader;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.ML;
using HarmonyLib;
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
[assembly: MelonColor(ConsoleColor.DarkCyan)]
@ -22,7 +17,7 @@ namespace UnityExplorer
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string ExplorerFolder => Path.Combine(MelonHandler.ModsDirectory, ExplorerCore.NAME);
public string UnhollowedModulesFolder => Path.Combine(
Path.GetDirectoryName(MelonHandler.ModsDirectory),

View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
namespace UnityExplorer.UI.Panels
{
public class HookManagerPanel : UIPanel
{
public enum Pages
{
CurrentHooks,
ClassMethodSelector,
HookSourceEditor
}
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks";
public override int MinWidth => 500;
public override int MinHeight => 600;
public override bool ShowByDefault => false;
public 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 override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value;
public override void DoSaveToConfigElement() => ConfigManager.HookManagerData.Value = this.ToSaveData();
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);
break;
case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(false);
editorPanel.SetActive(true);
break;
}
}
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void ConstructPanelContent()
{
// ~~~~~~~~~ Active hooks scroll pool
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
var addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
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);
var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
addButton.OnClick += OnClassInputAddClicked;
var hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// ~~~~~~~~~ Add hooks panel
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
var buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
doneButton.OnClick += HookManager.Instance.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.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()
{
this.Rect.anchorMin = new Vector2(0.5f, 0.5f);
this.Rect.anchorMax = new Vector2(0.5f, 0.5f);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
}
}

View File

@ -102,7 +102,6 @@ namespace UnityExplorer.UI.Panels
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.125f, 0.175f);
Rect.anchorMax = new Vector2(0.325f, 0.925f);
//mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 350);
}
public override void ConstructPanelContent()

View File

@ -11,8 +11,6 @@ using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
{
// TODO move the logic out of this class into ConfigManager
public class OptionsPanel : UIPanel, ICacheObjectController, ICellPoolDataSource<ConfigEntryCell>
{
public override string Name => "Options";

View File

@ -116,10 +116,8 @@ namespace UnityExplorer.UI.Panels
if (NavButtonWanted)
{
if (active)
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, UIManager.enabledButtonColor, UIManager.enabledButtonColor * 1.2f);
else
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, UIManager.disabledButtonColor, UIManager.disabledButtonColor * 1.2f);
var color = active ? UIManager.enabledButtonColor : UIManager.disabledButtonColor;
RuntimeProvider.Instance.SetColorBlock(NavButton.Component, color, color * 1.2f);
}
if (!active)

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Inspectors.MouseInspectors;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
{
public class UiInspectorResultsPanel : UIPanel
{
public override UIManager.Panels PanelType => UIManager.Panels.UIInspectorResults;
public override string Name => "UI Inspector Results";
public override int MinWidth => 500;
public override int MinHeight => 500;
public override bool CanDragAndResize => true;
public override bool NavButtonWanted => false;
public override bool ShouldSaveActiveState => false;
public override bool ShowByDefault => false;
private ButtonListHandler<GameObject, ButtonCell> dataHandler;
private ScrollPool<ButtonCell> buttonScrollPool;
public override void ConstructPanelContent()
{
dataHandler = new ButtonListHandler<GameObject, ButtonCell>(buttonScrollPool, GetEntries, SetCell, ShouldDisplayCell, OnCellClicked);
buttonScrollPool = UIFactory.CreateScrollPool<ButtonCell>(this.content, "ResultsList", out GameObject scrollObj,
out GameObject scrollContent);
buttonScrollPool.Initialize(dataHandler);
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
}
public void ShowResults()
{
dataHandler.RefreshData();
buttonScrollPool.Refresh(true, true);
}
private List<GameObject> GetEntries() => UiInspector.LastHitObjects;
private bool ShouldDisplayCell(object cell, string filter) => true;
private void OnCellClicked(int index)
{
if (index >= UiInspector.LastHitObjects.Count)
return;
InspectorManager.Inspect(UiInspector.LastHitObjects[index]);
}
private void SetCell(ButtonCell cell, int index)
{
if (index >= UiInspector.LastHitObjects.Count)
return;
var obj = UiInspector.LastHitObjects[index];
cell.Button.ButtonText.text = $"<color=cyan>{obj.name}</color> ({obj.transform.GetTransformPath(true)})";
}
protected internal override void DoSetDefaultPosAndAnchors()
{
this.Rect.anchorMin = new Vector2(0.5f, 0.5f);
this.Rect.anchorMax = new Vector2(0.5f, 0.5f);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 500f);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 500f);
}
public override void DoSaveToConfigElement() { }
public override string GetSaveDataFromConfigManager() => null;
}
}

View File

@ -27,7 +27,9 @@ namespace UnityExplorer.UI
Options,
ConsoleLog,
AutoCompleter,
MouseInspector
MouseInspector,
UIInspectorResults,
HookManager,
}
public enum VerticalAnchor
@ -106,8 +108,10 @@ namespace UnityExplorer.UI
UIPanels.Add(Panels.ObjectExplorer, new ObjectExplorerPanel());
UIPanels.Add(Panels.Inspector, new InspectorPanel());
UIPanels.Add(Panels.CSConsole, new CSConsolePanel());
UIPanels.Add(Panels.HookManager, new HookManagerPanel());
UIPanels.Add(Panels.ConsoleLog, new LogPanel());
UIPanels.Add(Panels.Options, new OptionsPanel());
UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel());
UIPanels.Add(Panels.MouseInspector, new InspectUnderMouse());
foreach (var panel in UIPanels.Values)
@ -240,14 +244,14 @@ namespace UnityExplorer.UI
NavBarRect.anchorMin = new Vector2(0.5f, 1f);
NavBarRect.anchorMax = new Vector2(0.5f, 1f);
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 0);
NavBarRect.sizeDelta = new Vector2(1000f, 35f);
NavBarRect.sizeDelta = new Vector2(1080f, 35f);
break;
case VerticalAnchor.Bottom:
NavBarRect.anchorMin = new Vector2(0.5f, 0f);
NavBarRect.anchorMax = new Vector2(0.5f, 0f);
NavBarRect.anchoredPosition = new Vector2(NavBarRect.anchoredPosition.x, 35);
NavBarRect.sizeDelta = new Vector2(1000f, 35f);
NavBarRect.sizeDelta = new Vector2(1080f, 35f);
break;
}
}

View File

@ -20,6 +20,8 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
public Type BaseType { get; set; }
public Type[] GenericConstraints { get; set; }
private bool allowAbstract;
private bool allowEnum;
public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false;
@ -33,11 +35,16 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
bool ISuggestionProvider.AllowNavigation => false;
public TypeCompleter(Type baseType, InputFieldRef inputField)
public TypeCompleter(Type baseType, InputFieldRef inputField) : this(baseType, inputField, true, true) { }
public TypeCompleter(Type baseType, InputFieldRef inputField, bool allowAbstract, bool allowEnum)
{
BaseType = baseType;
InputField = inputField;
this.allowAbstract = allowAbstract;
this.allowEnum = allowEnum;
inputField.OnValueChanged += OnInputFieldChanged;
if (BaseType != null)
@ -46,7 +53,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
public void CacheTypes()
{
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, true, false);
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowEnum, false);
}
public void OnSuggestionClicked(Suggestion suggestion)

View File

@ -215,6 +215,9 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Hooks\HookCell.cs" />
<Compile Include="Hooks\HookInstance.cs" />
<Compile Include="Hooks\HookManager.cs" />
<Compile Include="Core\Config\InternalConfigHandler.cs" />
<Compile Include="CacheObject\CacheConfigEntry.cs" />
<Compile Include="CacheObject\Views\CacheConfigCell.cs" />
@ -235,6 +238,7 @@
<Compile Include="Core\Utility\ArgumentUtility.cs" />
<Compile Include="Core\Utility\MiscUtility.cs" />
<Compile Include="Core\Utility\ParseUtility.cs" />
<Compile Include="Hooks\AddHookCell.cs" />
<Compile Include="Inspectors\GameObjectWidgets\ComponentCell.cs" />
<Compile Include="Inspectors\GameObjectWidgets\ComponentList.cs" />
<Compile Include="Inspectors\GameObjectWidgets\GameObjectControls.cs" />
@ -263,9 +267,14 @@
<Compile Include="CacheObject\IValues\InteractiveList.cs" />
<Compile Include="CacheObject\IValues\InteractiveString.cs" />
<Compile Include="CacheObject\IValues\InteractiveValue.cs" />
<Compile Include="Inspectors\MouseInspectors\MouseInspectorBase.cs" />
<Compile Include="Inspectors\MouseInspectors\UiInspector.cs" />
<Compile Include="Inspectors\MouseInspectors\WorldInspector.cs" />
<Compile Include="Inspectors\ReflectionInspector.cs" />
<Compile Include="CacheObject\IValues\InteractiveValueStruct.cs" />
<Compile Include="UI\Models\InputFieldRef.cs" />
<Compile Include="UI\Panels\HookManagerPanel.cs" />
<Compile Include="UI\Panels\UiInspectorResultsPanel.cs" />
<Compile Include="UI\Pool.cs" />
<Compile Include="UI\Panels\LogPanel.cs" />
<Compile Include="UI\Panels\CSConsolePanel.cs" />