Skip to content
This repository was archived by the owner on May 9, 2023. It is now read-only.

Commit fbdb84e

Browse files
committed
Implement HookManager UI and logic
1 parent 6989ea1 commit fbdb84e

File tree

10 files changed

+701
-4
lines changed

10 files changed

+701
-4
lines changed

src/CSConsole/ConsoleController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public static void Evaluate(string input, bool supressLog = false)
202202
{
203203
// The compiled code was not REPL, so it was a using directive or it defined classes.
204204

205-
string output = ScriptEvaluator._textWriter.ToString();
205+
string output = Evaluator._textWriter.ToString();
206206
var outputSplit = output.Split('\n');
207207
if (outputSplit.Length >= 2)
208208
output = outputSplit[outputSplit.Length - 2];

src/Core/Config/ConfigManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static class ConfigManager
3737
public static ConfigElement<string> CSConsoleData;
3838
public static ConfigElement<string> OptionsPanelData;
3939
public static ConfigElement<string> ConsoleLogData;
40+
public static ConfigElement<string> HookManagerData;
4041

4142
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
4243
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
@@ -126,6 +127,7 @@ private static void CreateConfigElements()
126127
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
127128
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
128129
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
130+
HookManagerData = new ConfigElement<string>("HookManager", "", "", true);
129131
}
130132
}
131133
}

src/ExplorerCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace UnityExplorer
2020
public static class ExplorerCore
2121
{
2222
public const string NAME = "UnityExplorer";
23-
public const string VERSION = "4.2.1";
23+
public const string VERSION = "4.3.0";
2424
public const string AUTHOR = "Sinai";
2525
public const string GUID = "com.sinai.unityexplorer";
2626

src/Hooks/AddHookCell.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using UnityEngine;
6+
using UnityEngine.UI;
7+
using UnityExplorer.UI;
8+
using UnityExplorer.UI.Widgets;
9+
10+
namespace UnityExplorer.Hooks
11+
{
12+
public class AddHookCell : ICell
13+
{
14+
public bool Enabled => UIRoot.activeSelf;
15+
16+
public RectTransform Rect { get; set; }
17+
public GameObject UIRoot { get; set; }
18+
19+
public float DefaultHeight => 30;
20+
21+
public Text MethodNameLabel;
22+
public Text HookedLabel;
23+
public ButtonRef HookButton;
24+
25+
public int CurrentDisplayedIndex;
26+
27+
private void OnHookClicked()
28+
{
29+
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
30+
}
31+
32+
public void Enable()
33+
{
34+
this.UIRoot.SetActive(true);
35+
}
36+
37+
public void Disable()
38+
{
39+
this.UIRoot.SetActive(false);
40+
}
41+
42+
public GameObject CreateContent(GameObject parent)
43+
{
44+
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
45+
Rect = UIRoot.GetComponent<RectTransform>();
46+
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft);
47+
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
48+
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
49+
50+
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
51+
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
52+
53+
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
54+
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
55+
HookButton.OnClick += OnHookClicked;
56+
57+
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
58+
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
59+
60+
return UIRoot;
61+
}
62+
}
63+
}

src/Hooks/HookCell.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using UnityEngine;
6+
using UnityEngine.UI;
7+
using UnityExplorer.UI;
8+
using UnityExplorer.UI.Widgets;
9+
10+
namespace UnityExplorer.Hooks
11+
{
12+
public class HookCell : ICell
13+
{
14+
public bool Enabled => UIRoot.activeSelf;
15+
16+
public RectTransform Rect { get; set; }
17+
public GameObject UIRoot { get; set; }
18+
19+
public float DefaultHeight => 30;
20+
21+
public Text MethodNameLabel;
22+
public ButtonRef EditPatchButton;
23+
public ButtonRef ToggleActiveButton;
24+
public ButtonRef DeleteButton;
25+
26+
public int CurrentDisplayedIndex;
27+
28+
private void OnToggleActiveClicked()
29+
{
30+
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
31+
}
32+
33+
private void OnDeleteClicked()
34+
{
35+
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
36+
}
37+
38+
private void OnEditPatchClicked()
39+
{
40+
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
41+
}
42+
43+
public GameObject CreateContent(GameObject parent)
44+
{
45+
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
46+
Rect = UIRoot.GetComponent<RectTransform>();
47+
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
48+
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
49+
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
50+
51+
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
52+
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
53+
54+
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
55+
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
56+
ToggleActiveButton.OnClick += OnToggleActiveClicked;
57+
58+
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
59+
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
60+
DeleteButton.OnClick += OnDeleteClicked;
61+
62+
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Log Patch Source", new Color(0.15f, 0.15f, 0.15f));
63+
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
64+
EditPatchButton.OnClick += OnEditPatchClicked;
65+
66+
return UIRoot;
67+
}
68+
69+
public void Disable()
70+
{
71+
UIRoot.SetActive(false);
72+
}
73+
74+
public void Enable()
75+
{
76+
UIRoot.SetActive(true);
77+
}
78+
}
79+
}

src/Hooks/HookInstance.cs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using System;
2+
using System.CodeDom.Compiler;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
using HarmonyLib;
8+
using Microsoft.CSharp;
9+
using UnityExplorer.CSConsole;
10+
11+
namespace UnityExplorer.Hooks
12+
{
13+
public class HookInstance
14+
{
15+
private static readonly StringBuilder evalOutput = new StringBuilder();
16+
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
17+
18+
// Instance
19+
20+
public bool Enabled;
21+
public MethodInfo TargetMethod;
22+
public string GeneratedSource;
23+
24+
private string shortSignature;
25+
private PatchProcessor patchProcessor;
26+
private HarmonyMethod patchDelegate;
27+
private MethodInfo patchDelegateMethodInfo;
28+
29+
public HookInstance(MethodInfo targetMethod)
30+
{
31+
this.TargetMethod = targetMethod;
32+
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
33+
GenerateProcessorAndDelegate();
34+
Patch();
35+
}
36+
37+
private void GenerateProcessorAndDelegate()
38+
{
39+
try
40+
{
41+
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
42+
43+
// Dynamically compile the patch method
44+
45+
scriptEvaluator.Run(GeneratePatchSourceCode(TargetMethod));
46+
47+
// Get the compiled method and check for errors
48+
49+
string output = scriptEvaluator._textWriter.ToString();
50+
var outputSplit = output.Split('\n');
51+
if (outputSplit.Length >= 2)
52+
output = outputSplit[outputSplit.Length - 2];
53+
evalOutput.Clear();
54+
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
55+
throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
56+
57+
// Could publicize MCS to avoid this reflection, but not bothering for now
58+
var source = (Mono.CSharp.CompilationSourceFile)ReflectionUtility.GetFieldInfo(typeof(Mono.CSharp.Evaluator), "source_file")
59+
.GetValue(scriptEvaluator);
60+
var type = (Mono.CSharp.Class)source.Containers.Last();
61+
var systemType = ((Mono.CSharp.TypeSpec)ReflectionUtility.GetPropertyInfo(typeof(Mono.CSharp.TypeDefinition), "Definition")
62+
.GetValue(type, null))
63+
.GetMetaInfo();
64+
65+
this.patchDelegateMethodInfo = systemType.GetMethod("Patch", ReflectionUtility.FLAGS);
66+
67+
// Actually create the harmony patch
68+
this.patchDelegate = new HarmonyMethod(patchDelegateMethodInfo);
69+
patchProcessor.AddPostfix(patchDelegate);
70+
}
71+
catch (Exception ex)
72+
{
73+
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
74+
}
75+
}
76+
77+
private string GeneratePatchSourceCode(MethodInfo targetMethod)
78+
{
79+
var codeBuilder = new StringBuilder();
80+
81+
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
82+
codeBuilder.AppendLine("{");
83+
84+
// Arguments
85+
86+
codeBuilder.Append(" public static void Patch(System.Reflection.MethodBase __originalMethod");
87+
88+
if (!targetMethod.IsStatic)
89+
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
90+
91+
if (targetMethod.ReturnType != typeof(void))
92+
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
93+
94+
int paramIdx = 0;
95+
var parameters = targetMethod.GetParameters();
96+
foreach (var param in parameters)
97+
{
98+
codeBuilder.Append($", {param.ParameterType.FullName} __{paramIdx}");
99+
paramIdx++;
100+
}
101+
102+
codeBuilder.Append(")\n");
103+
104+
// Patch body
105+
106+
codeBuilder.AppendLine(" {");
107+
108+
codeBuilder.AppendLine(" try {");
109+
110+
// Log message
111+
112+
var logMessage = new StringBuilder();
113+
logMessage.AppendLine($"$@\"Patch called: {shortSignature}");
114+
115+
if (!targetMethod.IsStatic)
116+
logMessage.AppendLine("__instance: {__instance.ToString()}");
117+
118+
paramIdx = 0;
119+
foreach (var param in parameters)
120+
{
121+
if (param.ParameterType.IsValueType)
122+
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}.ToString()}}");
123+
else
124+
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}?.ToString() ?? \"null\"}}");
125+
paramIdx++;
126+
}
127+
128+
if (targetMethod.ReturnType != typeof(void))
129+
{
130+
if (targetMethod.ReturnType.IsValueType)
131+
logMessage.AppendLine("Return value: {__result.ToString()}");
132+
else
133+
logMessage.AppendLine("Return value: {__result?.ToString() ?? \"null\"}");
134+
}
135+
136+
logMessage.Append('"');
137+
138+
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log({logMessage});");
139+
codeBuilder.AppendLine(" }");
140+
codeBuilder.AppendLine(" catch (System.Exception ex) {");
141+
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
142+
codeBuilder.AppendLine(" }");
143+
144+
// End patch body
145+
146+
codeBuilder.AppendLine(" }");
147+
148+
// End class
149+
150+
codeBuilder.AppendLine("}");
151+
152+
return GeneratedSource = codeBuilder.ToString();
153+
}
154+
155+
public void TogglePatch()
156+
{
157+
Enabled = !Enabled;
158+
if (Enabled)
159+
Patch();
160+
else
161+
Unpatch();
162+
}
163+
164+
public void Patch()
165+
{
166+
try
167+
{
168+
patchProcessor.Patch();
169+
Enabled = true;
170+
}
171+
catch (Exception ex)
172+
{
173+
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
174+
}
175+
}
176+
177+
public void Unpatch()
178+
{
179+
if (!Enabled)
180+
return;
181+
182+
try
183+
{
184+
this.patchProcessor.Unpatch(patchDelegateMethodInfo);
185+
Enabled = false;
186+
}
187+
catch (Exception ex)
188+
{
189+
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
190+
}
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)