Compare commits

...

9 Commits
4.2.1 ... 4.3.0

Author SHA1 Message Date
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 1136 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.
* To quickly copy the hook into your own project for further development, use the "Log Hook Source" button.
### 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.0";
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", "Log 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);
}
}
}

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

@ -0,0 +1,193 @@
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using Microsoft.CSharp;
using UnityExplorer.CSConsole;
namespace UnityExplorer.Hooks
{
public class HookInstance
{
private static readonly StringBuilder evalOutput = new StringBuilder();
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
// Instance
public bool Enabled;
public MethodInfo TargetMethod;
public string GeneratedSource;
private string shortSignature;
private PatchProcessor patchProcessor;
private HarmonyMethod patchDelegate;
private MethodInfo patchDelegateMethodInfo;
public HookInstance(MethodInfo targetMethod)
{
this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
GenerateProcessorAndDelegate();
Patch();
}
private void GenerateProcessorAndDelegate()
{
try
{
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method
scriptEvaluator.Run(GeneratePatchSourceCode(TargetMethod));
// Get the compiled method and check for errors
string output = scriptEvaluator._textWriter.ToString();
var outputSplit = output.Split('\n');
if (outputSplit.Length >= 2)
output = outputSplit[outputSplit.Length - 2];
evalOutput.Clear();
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
// Could publicize MCS to avoid this reflection, but not bothering for now
var source = (Mono.CSharp.CompilationSourceFile)ReflectionUtility.GetFieldInfo(typeof(Mono.CSharp.Evaluator), "source_file")
.GetValue(scriptEvaluator);
var type = (Mono.CSharp.Class)source.Containers.Last();
var systemType = ((Mono.CSharp.TypeSpec)ReflectionUtility.GetPropertyInfo(typeof(Mono.CSharp.TypeDefinition), "Definition")
.GetValue(type, null))
.GetMetaInfo();
this.patchDelegateMethodInfo = systemType.GetMethod("Patch", ReflectionUtility.FLAGS);
// Actually create the harmony patch
this.patchDelegate = new HarmonyMethod(patchDelegateMethodInfo);
patchProcessor.AddPostfix(patchDelegate);
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
}
}
private string GeneratePatchSourceCode(MethodInfo targetMethod)
{
var codeBuilder = new StringBuilder();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
// Arguments
codeBuilder.Append(" public static void Patch(System.Reflection.MethodBase __originalMethod");
if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
int paramIdx = 0;
var parameters = targetMethod.GetParameters();
foreach (var param in parameters)
{
codeBuilder.Append($", {param.ParameterType.FullName} __{paramIdx}");
paramIdx++;
}
codeBuilder.Append(")\n");
// Patch body
codeBuilder.AppendLine(" {");
codeBuilder.AppendLine(" try {");
// Log message
var logMessage = new StringBuilder();
logMessage.AppendLine($"$@\"Patch called: {shortSignature}");
if (!targetMethod.IsStatic)
logMessage.AppendLine("__instance: {__instance.ToString()}");
paramIdx = 0;
foreach (var param in parameters)
{
if (param.ParameterType.IsValueType)
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}.ToString()}}");
else
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}?.ToString() ?? \"null\"}}");
paramIdx++;
}
if (targetMethod.ReturnType != typeof(void))
{
if (targetMethod.ReturnType.IsValueType)
logMessage.AppendLine("Return value: {__result.ToString()}");
else
logMessage.AppendLine("Return value: {__result?.ToString() ?? \"null\"}");
}
logMessage.Append('"');
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log({logMessage});");
codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
codeBuilder.AppendLine(" }");
// End patch body
codeBuilder.AppendLine(" }");
// End class
codeBuilder.AppendLine("}");
return GeneratedSource = codeBuilder.ToString();
}
public void TogglePatch()
{
Enabled = !Enabled;
if (Enabled)
Patch();
else
Unpatch();
}
public void Patch()
{
try
{
patchProcessor.Patch();
Enabled = true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
}
}
public void Unpatch()
{
if (!Enabled)
return;
try
{
this.patchProcessor.Unpatch(patchDelegateMethodInfo);
Enabled = false;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
}
}
}
}

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

@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using UnityEngine;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.Hooks
{
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
{
private static HookManager s_instance;
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
public int ItemCount => addingMethods ? currentFilteredMethods.Count : currentHooks.Count;
private bool addingMethods;
private HashSet<string> hookedSignatures = new HashSet<string>();
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
private readonly List<MethodInfo> currentFilteredMethods = new List<MethodInfo>();
public void OnClassSelectedForHooks(string typeFullName)
{
var type = ReflectionUtility.GetTypeByName(typeFullName);
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
Panel.ResetMethodFilter();
currentFilteredMethods.Clear();
currentAddEligableMethods.Clear();
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod || method.IsAbstract || ReflectionUtility.IsBlacklisted(method))
continue;
currentAddEligableMethods.Add(method);
currentFilteredMethods.Add(method);
}
addingMethods = true;
Panel.SetAddPanelActive(true);
Panel.MethodResultsScrollPool.Refresh(true, true);
}
public void CloseAddHooks()
{
addingMethods = false;
Panel.SetAddPanelActive(false);
Panel.HooksScrollPool.Refresh(true, false);
}
public void OnHookAllClicked()
{
foreach (var method in currentAddEligableMethods)
AddHook(method);
Panel.MethodResultsScrollPool.Refresh(true, false);
}
public void AddHookClicked(int index)
{
if (index >= this.currentFilteredMethods.Count)
return;
AddHook(currentFilteredMethods[index]);
Panel.MethodResultsScrollPool.Refresh(true, false);
}
public void AddHook(MethodInfo method)
{
var sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
return;
var hook = new HookInstance(method);
if (hook.Enabled)
{
hookedSignatures.Add(sig);
currentHooks.Add(sig, hook);
}
}
public void EnableOrDisableHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
Panel.HooksScrollPool.Refresh(true, false);
}
public void DeleteHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
Panel.HooksScrollPool.Refresh(true, false);
}
public void EditPatchClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
ExplorerCore.Log(hook.GeneratedSource);
}
public void OnAddHookFilterInputChanged(string input)
{
currentFilteredMethods.Clear();
if (string.IsNullOrEmpty(input))
currentFilteredMethods.AddRange(currentAddEligableMethods);
else
{
foreach (var method in currentAddEligableMethods)
{
if (method.Name.ContainsIgnoreCase(input))
currentFilteredMethods.Add(method);
}
}
Panel.MethodResultsScrollPool.Refresh(true, true);
}
// OnBorrow methods not needed
public void OnCellBorrowed(HookCell cell) { }
public void OnCellBorrowed(AddHookCell cell) { }
// Set current hook cell
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var hook = (HookInstance)this.currentHooks[index];
cell.MethodNameLabel.text = HighlightMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
RuntimeProvider.Instance.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// Set eligable method cell
public void SetCell(AddHookCell cell, int index)
{
if (index >= this.currentFilteredMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var method = this.currentFilteredMethods[index];
cell.MethodNameLabel.text = HighlightMethod(method);
var sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
{
cell.HookButton.Component.gameObject.SetActive(false);
cell.HookedLabel.gameObject.SetActive(true);
}
else
{
cell.HookButton.Component.gameObject.SetActive(true);
cell.HookedLabel.gameObject.SetActive(false);
}
}
// private static readonly string VOID_HIGHLIGHT = $"<color=#{SignatureHighlighter.keywordBlueHex}>void</color> ";
private static readonly Dictionary<string, string> highlightedMethods = new Dictionary<string, string>();
private string HighlightMethod(MethodInfo method)
{
var sig = method.FullDescription();
if (highlightedMethods.ContainsKey(sig))
return highlightedMethods[sig];
var sb = new StringBuilder();
// declaring type
sb.Append(SignatureHighlighter.Parse(method.DeclaringType, false));
sb.Append('.');
// method name
var color = !method.IsStatic
? SignatureHighlighter.METHOD_INSTANCE
: SignatureHighlighter.METHOD_STATIC;
sb.Append($"<color={color}>{method.Name}</color>");
// arguments
sb.Append('(');
var args = method.GetParameters();
if (args != null && args.Any())
{
int i = 0;
foreach (var param in args)
{
sb.Append(SignatureHighlighter.Parse(param.ParameterType, false));
sb.Append(' ');
sb.Append($"<color={SignatureHighlighter.LOCAL_ARG}>{param.Name}</color>");
i++;
if (i < args.Length)
sb.Append(", ");
}
}
sb.Append(')');
var ret = sb.ToString();
highlightedMethods.Add(sig, ret);
return ret;
}
}
}

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,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
namespace UnityExplorer.UI.Panels
{
public class HookManagerPanel : UIPanel
{
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks";
public override int MinWidth => 500;
public override int MinHeight => 600;
public override bool ShowByDefault => false;
public ScrollPool<HookCell> HooksScrollPool;
public ScrollPool<AddHookCell> MethodResultsScrollPool;
private GameObject addHooksPanel;
private GameObject currentHooksPanel;
private InputFieldRef classInputField;
private Text addHooksLabel;
private InputFieldRef methodFilterInput;
public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value;
public override void DoSaveToConfigElement() => ConfigManager.HookManagerData.Value = this.ToSaveData();
private void OnClassInputAddClicked()
{
HookManager.Instance.OnClassSelectedForHooks(this.classInputField.Text);
}
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
public void SetAddPanelActive(bool show)
{
addHooksPanel.SetActive(show);
currentHooksPanel.SetActive(!show);
}
public void ResetMethodFilter() => methodFilterInput.Text = string.Empty;
public override void ConstructPanelContent()
{
// Active hooks scroll pool
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
var addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
classInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(classInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classInputField, false, false);
var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
addButton.OnClick += OnClassInputAddClicked;
var hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// Add hooks panel
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
var buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
doneButton.OnClick += HookManager.Instance.CloseAddHooks;
var patchAllButton = UIFactory.CreateButton(buttonRow, "PatchAllButton", "Hook ALL methods in class", new Color(0.3f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(patchAllButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
patchAllButton.OnClick += HookManager.Instance.OnHookAllClicked;
methodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(methodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
methodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
MethodResultsScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
MethodResultsScrollPool.Initialize(HookManager.Instance);
addHooksPanel.gameObject.SetActive(false);
}
protected internal override void DoSetDefaultPosAndAnchors()
{
this.Rect.anchorMin = new Vector2(0.5f, 0.5f);
this.Rect.anchorMax = new Vector2(0.5f, 0.5f);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
}
}

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" />