mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 16:42:38 +08:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
57f59d1295 | |||
fbdb84eefa | |||
6989ea1b19 | |||
fcdfeb2dec | |||
a1d0b6432e | |||
0b84405e57 | |||
c31e0949d3 | |||
5f0495a7ea | |||
28de6779d8 | |||
fae85c2968 | |||
fc8fa9aa7a | |||
bcf9a801a9 | |||
20298aa47b | |||
e2c1c186c3 | |||
9ce6508828 | |||
506e75c5fe | |||
a2f22051f0 | |||
4e3203a91b | |||
a411ce2dba | |||
48132b3d46 | |||
e49ed3028f | |||
31dd54d25c | |||
3cfdd6fa43 | |||
1fa1283a68 | |||
afa4135b67 | |||
48d1cf574d | |||
df0abbc847 | |||
5c9dcb1d43 | |||
602770d980 | |||
f26371f95f | |||
a673c39f4a | |||
40583cae3d | |||
20018a9ba9 | |||
dabf92a1a5 | |||
b7e275f02c | |||
f393e0d706 | |||
ab8acc9e84 | |||
66c30ee70e | |||
ddd271c00d | |||
30fe9e4dde | |||
fa3a436037 | |||
ca27d2b20f | |||
4315e0c547 | |||
3e19e74329 | |||
5a3ffebadc | |||
6e6b6239d8 | |||
03c8e5a8bd | |||
44f7209843 | |||
734e45cf9f | |||
97838e0b3a |
6
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -24,10 +24,8 @@ body:
|
||||
- BepInEx IL2CPP
|
||||
- BepInEx 6.X Mono
|
||||
- BepInEx 5.X Mono
|
||||
- MelonLoader 0.4+ IL2CPP
|
||||
- MelonLoader 0.4+ Mono
|
||||
- MelonLoader 0.3 IL2CPP
|
||||
- MelonLoader 0.3 Mono
|
||||
- MelonLoader IL2CPP
|
||||
- MelonLoader Mono
|
||||
- Standalone IL2CPP
|
||||
- Standalone Mono
|
||||
validations:
|
||||
|
16
.github/workflows/dotnet.yml
vendored
16
.github/workflows/dotnet.yml
vendored
@ -79,22 +79,6 @@ jobs:
|
||||
name: UnityExplorer.MelonLoader.Mono
|
||||
path: ./Release/UnityExplorer.MelonLoader.Mono/*
|
||||
|
||||
# MelonLoader 0.3.0 Il2Cpp
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Cpp
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.MelonLoader_Legacy.Il2Cpp
|
||||
path: ./Release/UnityExplorer.MelonLoader_Legacy.Il2Cpp/*
|
||||
|
||||
# MelonLoader 0.3.0 Mono
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Mono
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.MelonLoader_Legacy.Mono
|
||||
path: ./Release/UnityExplorer.MelonLoader_Legacy.Mono/*
|
||||
|
||||
# Standalone Il2Cpp
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Cpp
|
||||
|
||||
|
24
README.md
24
README.md
@ -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 [](../../releases)
|
||||
|
||||
[](../../releases/latest) [](https://github.com/sinai-dev/UnityExplorer/actions) [](../../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 |
|
||||
@ -33,7 +32,6 @@
|
||||
| Release | IL2CPP | Mono |
|
||||
| ------- | ------ | ---- |
|
||||
| ML 0.4+ | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
|
||||
| ML 0.3 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Mono.zip) |
|
||||
|
||||
1. Take the `UnityExplorer.ML.[version].dll` file and put it in the `Mods\` folder created by MelonLoader.
|
||||
|
||||
@ -86,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.
|
||||
@ -101,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.
|
||||
@ -114,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.
|
||||
|
Binary file not shown.
Submodule lib/Il2CppAssemblyUnhollower updated: 0911fdaca6...0099c25069
Binary file not shown.
Binary file not shown.
@ -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];
|
||||
|
@ -121,16 +121,15 @@ namespace UnityExplorer.CSConsole
|
||||
{
|
||||
while (input.Length - 1 >= matchEndIdx)
|
||||
{
|
||||
matchEndIdx++;
|
||||
if (IsNewLine(input[matchEndIdx]))
|
||||
break;
|
||||
matchEndIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// check caretIdx to determine inStringOrComment state
|
||||
if (caretIdx >= match.startIndex && (caretIdx <= matchEndIdx || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
|
||||
if (caretIdx >= match.startIndex && (caretIdx <= (matchEndIdx+1) || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
|
||||
caretInStringOrComment = match.isStringOrComment;
|
||||
|
||||
}
|
||||
|
||||
// Append trailing unhighlighted input
|
||||
|
@ -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)
|
||||
|
@ -12,11 +12,11 @@ namespace UnityExplorer.CacheObject
|
||||
public CacheConfigEntry(IConfigElement configElement)
|
||||
{
|
||||
this.RefConfigElement = configElement;
|
||||
this.FallbackType = configElement.ElementType;
|
||||
|
||||
this.NameLabelText = $"<color=cyan>{configElement.Name}</color>" +
|
||||
$"\r\n<color=grey><i>{configElement.Description}</i></color>";
|
||||
|
||||
this.FallbackType = configElement.ElementType;
|
||||
this.NameLabelTextRaw = string.Empty;
|
||||
|
||||
configElement.OnValueChangedNotify += UpdateValueFromSource;
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ namespace UnityExplorer.CacheObject
|
||||
var kvpCell = cell as CacheKeyValuePairCell;
|
||||
|
||||
kvpCell.NameLabel.text = $"{DictIndex}:";
|
||||
kvpCell.HiddenNameLabel.Text = "";
|
||||
kvpCell.Image.color = DictIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
|
||||
|
||||
if (KeyInputWanted)
|
||||
|
@ -28,6 +28,7 @@ namespace UnityExplorer.CacheObject
|
||||
var listCell = cell as CacheListEntryCell;
|
||||
|
||||
listCell.NameLabel.text = $"{ListIndex}:";
|
||||
listCell.HiddenNameLabel.Text = "";
|
||||
listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
|
||||
}
|
||||
|
||||
|
@ -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)));
|
||||
@ -34,6 +31,7 @@ namespace UnityExplorer.CacheObject
|
||||
this.Owner = inspector;
|
||||
this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member);
|
||||
this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
this.NameLabelTextRaw = NameForFiltering;
|
||||
}
|
||||
|
||||
public override void ReleasePooledObjects()
|
||||
@ -60,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();
|
||||
@ -67,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);
|
||||
@ -100,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)
|
||||
{
|
||||
@ -119,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)
|
||||
{
|
||||
@ -139,7 +130,6 @@ namespace UnityExplorer.CacheObject
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void OnEvaluateClicked()
|
||||
{
|
||||
if (!HasArguments)
|
||||
@ -245,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;
|
||||
@ -310,7 +300,6 @@ namespace UnityExplorer.CacheObject
|
||||
|
||||
cachedSigs.Add(sig);
|
||||
|
||||
//cached.Initialize(_inspector, declaringType, member, returnType);
|
||||
cached.SetFallbackType(returnType);
|
||||
cached.SetInspectorOwner(_inspector, member);
|
||||
|
||||
|
@ -30,15 +30,14 @@ namespace UnityExplorer.CacheObject
|
||||
try
|
||||
{
|
||||
var methodInfo = MethodInfo;
|
||||
|
||||
if (methodInfo.IsGenericMethod)
|
||||
methodInfo = MethodInfo.MakeGenericMethod(Evaluator.TryParseGenericArguments());
|
||||
|
||||
if (Arguments.Length > 0)
|
||||
return methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
|
||||
|
||||
var ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
|
||||
|
||||
object ret;
|
||||
if (HasArguments)
|
||||
ret = methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
|
||||
else
|
||||
ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
|
||||
HadException = false;
|
||||
LastException = null;
|
||||
return ret;
|
||||
|
@ -47,6 +47,7 @@ namespace UnityExplorer.CacheObject
|
||||
public bool SubContentShowWanted { get; private set; }
|
||||
|
||||
public string NameLabelText { get; protected set; }
|
||||
public string NameLabelTextRaw { get; protected set; }
|
||||
public string ValueLabelText { get; protected set; }
|
||||
|
||||
public abstract bool ShouldAutoEvaluate { get; }
|
||||
@ -260,6 +261,8 @@ namespace UnityExplorer.CacheObject
|
||||
public virtual void SetDataToCell(CacheObjectCell cell)
|
||||
{
|
||||
cell.NameLabel.text = NameLabelText;
|
||||
if (cell.HiddenNameLabel != null)
|
||||
cell.HiddenNameLabel.Text = NameLabelTextRaw;
|
||||
cell.ValueLabel.gameObject.SetActive(true);
|
||||
|
||||
cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -45,6 +45,7 @@ namespace UnityExplorer.CacheObject.Views
|
||||
public LayoutElement RightGroupLayout;
|
||||
|
||||
public Text NameLabel;
|
||||
public InputFieldRef HiddenNameLabel;
|
||||
public Text TypeLabel;
|
||||
public Text ValueLabel;
|
||||
public Toggle Toggle;
|
||||
@ -116,8 +117,19 @@ namespace UnityExplorer.CacheObject.Views
|
||||
|
||||
NameLabel = UIFactory.CreateLabel(horiRow, "NameLabel", "<notset>", TextAnchor.MiddleLeft);
|
||||
NameLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
|
||||
NameLayout = NameLabel.GetComponent<LayoutElement>();
|
||||
NameLayout = UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(NameLabel.gameObject, true, true, true, true);
|
||||
|
||||
HiddenNameLabel = UIFactory.CreateInputField(NameLabel.gameObject, "HiddenNameLabel", "");
|
||||
var hiddenRect = HiddenNameLabel.Component.GetComponent<RectTransform>();
|
||||
hiddenRect.anchorMin = Vector2.zero;
|
||||
hiddenRect.anchorMax = Vector2.one;
|
||||
HiddenNameLabel.Component.readOnly = true;
|
||||
HiddenNameLabel.Component.lineType = UnityEngine.UI.InputField.LineType.MultiLineNewline;
|
||||
HiddenNameLabel.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
HiddenNameLabel.Component.gameObject.GetComponent<Image>().color = Color.clear;
|
||||
HiddenNameLabel.Component.textComponent.color = Color.clear;
|
||||
UIFactory.SetLayoutElement(HiddenNameLabel.Component.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
|
||||
|
||||
// Right vertical group
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
@ -6,8 +7,6 @@ using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
@ -149,18 +148,97 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
// Patches
|
||||
|
||||
private static void SetupPatches()
|
||||
public static void SetupPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
ExplorerCore.Loader.SetupCursorPatches();
|
||||
PrefixPropertySetter(typeof(Cursor),
|
||||
"lockState",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
|
||||
|
||||
PrefixPropertySetter(typeof(Cursor),
|
||||
"visible",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
|
||||
|
||||
PrefixPropertySetter(typeof(EventSystem),
|
||||
"current",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
|
||||
|
||||
PrefixMethod(typeof(EventSystem),
|
||||
"SetSelectedGameObject",
|
||||
// some games use a modified version of uGUI that includes this extra int argument on this method.
|
||||
new Type[] { typeof(GameObject), typeof(BaseEventData), typeof(int) },
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_SetSelectedGameObject))),
|
||||
// most games use these arguments, we'll use them as our "backup".
|
||||
new Type[] { typeof(GameObject), typeof(BaseEventData) });
|
||||
|
||||
//// Not sure if this one is needed.
|
||||
//PrefixMethod(typeof(PointerInputModule),
|
||||
// "ClearSelection",
|
||||
// new Type[0],
|
||||
// new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_PointerInputModule_ClearSelection))));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrefixMethod(Type type, string method, Type[] arguments, HarmonyMethod prefix, Type[] backupArgs = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, arguments, null);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
if (backupArgs != null)
|
||||
methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, backupArgs, null);
|
||||
|
||||
if (methodInfo == null)
|
||||
throw new MissingMethodException($"Could not find method for patching - '{type.FullName}.{method}'!");
|
||||
}
|
||||
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(methodInfo);
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
|
||||
ExplorerCore.LogWarning($"Unable to patch {type.Name}.{method}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrefixPropertySetter(Type type, string property, HarmonyMethod prefix)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(type.GetProperty(property, ReflectionUtility.FLAGS).GetSetMethod());
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent setting non-UnityExplorer objects as selected when menu is open
|
||||
|
||||
public static bool Prefix_EventSystem_SetSelectedGameObject(GameObject __0)
|
||||
{
|
||||
if (!UIManager.ShowMenu || !UIManager.CanvasRoot)
|
||||
return true;
|
||||
|
||||
return __0 && __0.transform.root.gameObject.GetInstanceID() == UIManager.CanvasRoot.GetInstanceID();
|
||||
}
|
||||
|
||||
//public static bool Prefix_PointerInputModule_ClearSelection()
|
||||
//{
|
||||
// return !(UIManager.ShowMenu && UIManager.CanvasRoot);
|
||||
//}
|
||||
|
||||
// Force EventSystem.current to be UnityExplorer's when menu is open
|
||||
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
if (!settingEventSystem && value)
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
@ -134,6 +133,9 @@ namespace UnityExplorer
|
||||
var type = obj.GetType();
|
||||
try
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
return type;
|
||||
|
||||
if (IsString(obj))
|
||||
return typeof(string);
|
||||
|
||||
@ -178,7 +180,8 @@ namespace UnityExplorer
|
||||
if (fullname.StartsWith("System."))
|
||||
fullname = $"Il2Cpp{fullname}";
|
||||
|
||||
AllTypes.TryGetValue(fullname, out Type monoType);
|
||||
if (!AllTypes.TryGetValue(fullname, out Type monoType))
|
||||
ExplorerCore.LogWarning($"Failed to get type by name '{fullname}'!");
|
||||
return monoType;
|
||||
}
|
||||
|
||||
@ -477,59 +480,38 @@ namespace UnityExplorer
|
||||
|
||||
#region Force-loading game modules
|
||||
|
||||
internal static string UnhollowedFolderPath => Path.GetFullPath(
|
||||
#if ML
|
||||
Path.Combine("MelonLoader", "Managed")
|
||||
#elif BIE
|
||||
Path.Combine(BepInEx.Paths.BepInExRootPath, "unhollowed")
|
||||
#else
|
||||
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
|
||||
#endif
|
||||
);
|
||||
|
||||
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
|
||||
|
||||
// Force loading all il2cpp modules
|
||||
|
||||
internal void TryLoadGameModules()
|
||||
{
|
||||
if (Directory.Exists(UnhollowedFolderPath))
|
||||
var dir = ExplorerCore.Loader.UnhollowedModulesFolder;
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
var files = Directory.GetFiles(UnhollowedFolderPath);
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
DoLoadModule(filePath, true);
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
foreach (var filePath in Directory.GetFiles(dir, "*.dll"))
|
||||
DoLoadModule(filePath);
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
|
||||
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{dir}'. " +
|
||||
$"If you are using the standalone release, you can specify the Unhollowed modules path when you call CreateInstance().");
|
||||
}
|
||||
|
||||
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
|
||||
internal bool DoLoadModule(string fullPath)
|
||||
{
|
||||
if (!File.Exists(fullPath))
|
||||
if (string.IsNullOrEmpty(fullPath) || !File.Exists(fullPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
Assembly.LoadFile(fullPath);
|
||||
//Assembly.Load(File.ReadAllBytes(fullPath));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch //(Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
|
||||
//ExplorerCore.LogWarning($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -579,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",
|
||||
|
57
src/Core/Reflection/Patches.cs
Normal file
57
src/Core/Reflection/Patches.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ReflectionPatches
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = typeof(Assembly).GetMethod(nameof(Assembly.GetTypes), new Type[0]);
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(method);
|
||||
processor.AddFinalizer(typeof(ReflectionPatches).GetMethod(nameof(Assembly_GetTypes)));
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up Reflection patch: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Type[] emptyTypes = new Type[0];
|
||||
|
||||
public static Exception Assembly_GetTypes(Assembly __instance, Exception __exception, ref Type[] __result)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
__result = __instance.GetExportedTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
__result = e.Types.Where(it => it != null).ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
__result = emptyTypes;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
__result = emptyTypes;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ namespace UnityExplorer
|
||||
new ReflectionUtility();
|
||||
#endif
|
||||
Instance.Initialize();
|
||||
|
||||
ReflectionPatches.Init();
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
@ -249,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))
|
||||
{
|
||||
@ -285,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")
|
||||
|
@ -32,16 +32,6 @@ namespace UnityExplorer
|
||||
return new AssetBundle(ptr);
|
||||
}
|
||||
|
||||
// static void UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
|
||||
internal delegate void d_UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
|
||||
public static void UnloadAllAssetBundles(bool unloadAllObjects)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_UnloadAllAssetBundles>("UnityEngine.AssetBundle::UnloadAllAssetBundles");
|
||||
iCall.Invoke(unloadAllObjects);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
|
||||
|
||||
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
|
||||
@ -78,7 +68,8 @@ namespace UnityExplorer
|
||||
return new UnityEngine.Object(ptr).TryCast<T>();
|
||||
}
|
||||
|
||||
// public extern void Unload(bool unloadAllLoadedObjects);
|
||||
// Unload(bool unloadAllLoadedObjects);
|
||||
|
||||
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
|
||||
|
||||
public void Unload(bool unloadAssets = true)
|
||||
|
@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||
public static class ICallManager
|
||||
{
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
|
@ -6,8 +6,8 @@ using System.Linq;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
// CREDIT HerpDerpenstine
|
||||
// https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
|
||||
// Credit to HerpDerpenstine and knah
|
||||
// https://github.com/LavaGang/MelonLoader/blob/master/SM_Il2Cpp/Coroutines.cs
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
|
@ -73,6 +73,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
return ScriptableObject.CreateInstance(Il2CppType.From(type));
|
||||
}
|
||||
|
||||
// Pretty disgusting but couldn't figure out a cleaner way yet unfortunately
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
|
||||
@ -97,15 +98,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
|
||||
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
|
||||
|
||||
internal static readonly string[] findObjectsOfTypeAllSignatures = new[]
|
||||
{
|
||||
"UnityEngine.Resources::FindObjectsOfTypeAll",
|
||||
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
|
||||
};
|
||||
|
||||
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
|
||||
{
|
||||
var iCall = ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(new[]
|
||||
{
|
||||
"UnityEngine.Resources::FindObjectsOfTypeAll",
|
||||
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
|
||||
});
|
||||
|
||||
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(Il2CppType.From(type).Pointer));
|
||||
return new Il2CppReferenceArray<UnityEngine.Object>(
|
||||
ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(findObjectsOfTypeAllSignatures)
|
||||
.Invoke(Il2CppType.From(type).Pointer));
|
||||
}
|
||||
|
||||
// Scene.GetRootGameObjects();
|
||||
@ -117,22 +120,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
if (!scene.isLoaded)
|
||||
return new GameObject[0];
|
||||
|
||||
int handle = scene.handle;
|
||||
|
||||
if (handle == -1)
|
||||
if (scene.handle == -1)
|
||||
return new GameObject[0];
|
||||
|
||||
int count = GetRootCount(handle);
|
||||
int count = GetRootCount(scene.handle);
|
||||
|
||||
if (count < 1)
|
||||
return new GameObject[0];
|
||||
|
||||
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
|
||||
|
||||
var iCall = ICallManager.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
|
||||
|
||||
iCall.Invoke(handle, list.Pointer);
|
||||
|
||||
iCall.Invoke(scene.handle, list.Pointer);
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
|
@ -38,19 +38,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
return new Il2CppStructArray<byte>(ptr);
|
||||
}
|
||||
|
||||
// bool ImageConversion.LoadImage(this Texture2D tex, byte[] data, bool markNonReadable);
|
||||
|
||||
internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
|
||||
|
||||
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
var il2cppArray = (Il2CppStructArray<byte>)data;
|
||||
|
||||
var iCall = ICallManager.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
|
||||
|
||||
return iCall.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
|
||||
}
|
||||
|
||||
// Sprite Sprite.Create
|
||||
|
||||
public override Sprite CreateSprite(Texture2D texture)
|
||||
|
@ -10,6 +10,7 @@ using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Mono
|
||||
{
|
||||
@ -18,7 +19,6 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
public override void Initialize()
|
||||
{
|
||||
ExplorerCore.Context = RuntimeContext.Mono;
|
||||
//Reflection = new MonoReflection();
|
||||
TextureUtil = new MonoTextureUtil();
|
||||
}
|
||||
|
||||
@ -28,60 +28,35 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
}
|
||||
|
||||
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
|
||||
{
|
||||
ExplorerCore.LogUnity(condition, type);
|
||||
}
|
||||
=> ExplorerCore.LogUnity(condition, type);
|
||||
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
{
|
||||
ExplorerBehaviour.Instance.StartCoroutine(routine);
|
||||
}
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
=> ExplorerBehaviour.Instance.StartCoroutine(routine);
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override T AddComponent<T>(GameObject obj, Type type)
|
||||
{
|
||||
return (T)obj.AddComponent(type);
|
||||
}
|
||||
public override T AddComponent<T>(GameObject obj, Type type)
|
||||
=> (T)obj.AddComponent(type);
|
||||
|
||||
public override ScriptableObject CreateScriptable(Type type)
|
||||
{
|
||||
return ScriptableObject.CreateInstance(type);
|
||||
}
|
||||
public override ScriptableObject CreateScriptable(Type type)
|
||||
=> ScriptableObject.CreateInstance(type);
|
||||
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
raycaster.Raycast(data, list);
|
||||
}
|
||||
=> raycaster.Raycast(data, list);
|
||||
|
||||
public override string LayerToName(int layer)
|
||||
public override string LayerToName(int layer)
|
||||
=> LayerMask.LayerToName(layer);
|
||||
|
||||
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
|
||||
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
|
||||
=> Resources.FindObjectsOfTypeAll(type);
|
||||
|
||||
//private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionUtility.AllFlags);
|
||||
public override GameObject[] GetRootGameObjects(Scene scene)
|
||||
=> scene.isLoaded ? scene.GetRootGameObjects() : new GameObject[0];
|
||||
|
||||
//public override int GetSceneHandle(Scene scene)
|
||||
//{
|
||||
// return (int)fi_Scene_handle.GetValue(scene);
|
||||
//}
|
||||
|
||||
public override GameObject[] GetRootGameObjects(Scene scene)
|
||||
{
|
||||
if (!scene.isLoaded)
|
||||
return new GameObject[0];
|
||||
|
||||
return scene.GetRootGameObjects();
|
||||
}
|
||||
|
||||
public override int GetRootCount(Scene scene)
|
||||
{
|
||||
return scene.rootCount;
|
||||
}
|
||||
public override int GetRootCount(Scene scene)
|
||||
=> scene.rootCount;
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
Color? disabled = null)
|
||||
@ -103,59 +78,42 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
SetColorBlock(selectable, colors);
|
||||
}
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
|
||||
{
|
||||
selectable.colors = colors;
|
||||
}
|
||||
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
|
||||
=> selectable.colors = colors;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MonoExtensions
|
||||
{
|
||||
// Helpers to use the same style of AddListener that IL2CPP uses.
|
||||
|
||||
public static void AddListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction(listener));
|
||||
}
|
||||
=> _event.AddListener(new UnityAction(listener));
|
||||
|
||||
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction<T>(listener));
|
||||
}
|
||||
=> _event.AddListener(new UnityAction<T>(listener));
|
||||
|
||||
public static void RemoveListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction(listener));
|
||||
}
|
||||
=> _event.RemoveListener(new UnityAction(listener));
|
||||
|
||||
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction<T>(listener));
|
||||
}
|
||||
=> _event.RemoveListener(new UnityAction<T>(listener));
|
||||
|
||||
public static void Clear(this StringBuilder sb)
|
||||
{
|
||||
sb.Remove(0, sb.Length);
|
||||
}
|
||||
// Doesn't exist in NET 3.5
|
||||
|
||||
private static PropertyInfo pi_childControlHeight;
|
||||
public static void Clear(this StringBuilder sb)
|
||||
=> sb.Remove(0, sb.Length);
|
||||
|
||||
// These properties don't exist in some earlier games, so null check before trying to set them.
|
||||
|
||||
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
|
||||
{
|
||||
if (pi_childControlHeight == null)
|
||||
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
|
||||
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlHeight")
|
||||
?.SetValue(group, value, null);
|
||||
|
||||
pi_childControlHeight?.SetValue(group, value, null);
|
||||
}
|
||||
|
||||
private static PropertyInfo pi_childControlWidth;
|
||||
|
||||
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
|
||||
{
|
||||
if (pi_childControlWidth == null)
|
||||
pi_childControlWidth = group.GetType().GetProperty("childControlWidth");
|
||||
|
||||
pi_childControlWidth?.SetValue(group, value, null);
|
||||
}
|
||||
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlWidth")
|
||||
?.SetValue(group, value, null);
|
||||
}
|
||||
|
||||
#endif
|
@ -12,41 +12,28 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
public class MonoTextureUtil : TextureUtilProvider
|
||||
{
|
||||
public override void Blit(Texture2D tex, RenderTexture rt)
|
||||
{
|
||||
Graphics.Blit(tex, rt);
|
||||
}
|
||||
=> Graphics.Blit(tex, rt);
|
||||
|
||||
public override Sprite CreateSprite(Texture2D texture)
|
||||
{
|
||||
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
|
||||
}
|
||||
=> Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
|
||||
|
||||
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
return tex.LoadImage(data, markNonReadable);
|
||||
}
|
||||
//public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
// => tex.LoadImage(data, markNonReadable);
|
||||
|
||||
public override Texture2D NewTexture2D(int width, int height)
|
||||
{
|
||||
return new Texture2D(width, height);
|
||||
}
|
||||
=> new Texture2D(width, height);
|
||||
|
||||
public override byte[] EncodeToPNG(Texture2D tex)
|
||||
{
|
||||
return EncodeToPNGSafe(tex);
|
||||
}
|
||||
=> EncodeToPNGSafe(tex);
|
||||
|
||||
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
|
||||
private static MethodInfo m_encodeToPNGMethod;
|
||||
|
||||
public static byte[] EncodeToPNGSafe(Texture2D tex)
|
||||
{
|
||||
var method = EncodeToPNGMethod;
|
||||
|
||||
if (method.IsStatic)
|
||||
return (byte[])method.Invoke(null, new object[] { tex });
|
||||
else
|
||||
return (byte[])method.Invoke(tex, ArgumentUtility.EmptyArgs);
|
||||
return EncodeToPNGMethod.IsStatic
|
||||
? (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex })
|
||||
: (byte[])EncodeToPNGMethod.Invoke(tex, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
|
||||
private static MethodInfo GetEncodeToPNGMethod()
|
||||
|
@ -40,8 +40,6 @@ namespace UnityExplorer
|
||||
|
||||
public abstract void Update();
|
||||
|
||||
//public virtual bool IsReferenceEqual(object a, object b) => ReferenceEquals(a, b);
|
||||
|
||||
// Unity API handlers
|
||||
|
||||
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
|
||||
@ -54,12 +52,13 @@ namespace UnityExplorer
|
||||
|
||||
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
|
||||
|
||||
//public abstract int GetSceneHandle(Scene scene);
|
||||
|
||||
public abstract GameObject[] GetRootGameObjects(Scene scene);
|
||||
|
||||
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,
|
||||
|
@ -22,7 +22,7 @@ namespace UnityExplorer.Core.Runtime
|
||||
|
||||
public abstract void Blit(Texture2D tex, RenderTexture rt);
|
||||
|
||||
public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
|
||||
//public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
|
||||
|
||||
public abstract Sprite CreateSprite(Texture2D texture);
|
||||
|
||||
@ -43,27 +43,22 @@ namespace UnityExplorer.Core.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
|
||||
}
|
||||
//public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
|
||||
//{
|
||||
// if (!File.Exists(filePath))
|
||||
// return false;
|
||||
//
|
||||
// return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
|
||||
//}
|
||||
|
||||
public static Texture2D Copy(Texture2D orig, Rect rect)
|
||||
{
|
||||
Color[] pixels;
|
||||
|
||||
if (!IsReadable(orig))
|
||||
orig = ForceReadTexture(orig);
|
||||
|
||||
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
|
||||
Color[] pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
|
||||
|
||||
newTex.SetPixels(pixels);
|
||||
|
||||
return newTex;
|
||||
}
|
||||
|
||||
@ -92,7 +87,7 @@ namespace UnityExplorer.Core.Runtime
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
|
||||
ExplorerCore.Log($"Exception on ForceReadTexture: {e.ToString()}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@ -103,13 +98,11 @@ namespace UnityExplorer.Core.Runtime
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
byte[] data;
|
||||
string savepath = dir + @"\" + name + ".png";
|
||||
string savepath = $@"{dir}\{name}.png";
|
||||
|
||||
// Make sure we can EncodeToPNG it.
|
||||
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
|
||||
{
|
||||
tex = ForceReadTexture(tex);
|
||||
}
|
||||
|
||||
if (isDTXnmNormal)
|
||||
{
|
||||
@ -120,13 +113,9 @@ namespace UnityExplorer.Core.Runtime
|
||||
data = Instance.EncodeToPNG(tex);
|
||||
|
||||
if (data == null || !data.Any())
|
||||
{
|
||||
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllBytes(savepath, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
|
||||
|
@ -14,112 +14,31 @@ using UnhollowerBaseLib;
|
||||
|
||||
namespace UnityExplorer.Tests
|
||||
{
|
||||
public class TestIndexer : IList<int>
|
||||
{
|
||||
private readonly List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
|
||||
|
||||
public int Count => list.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
int IList<int>.this[int index]
|
||||
{
|
||||
get => list[index];
|
||||
set => list[index] = value;
|
||||
}
|
||||
|
||||
public int IndexOf(int item) => list.IndexOf(item);
|
||||
public bool Contains(int item) => list.Contains(item);
|
||||
|
||||
public void Add(int item) => list.Add(item);
|
||||
public void Insert(int index, int item) => list.Insert(index, item);
|
||||
|
||||
public bool Remove(int item) => list.Remove(item);
|
||||
public void RemoveAt(int index) => list.RemoveAt(index);
|
||||
|
||||
public void Clear() => list.Clear();
|
||||
|
||||
public void CopyTo(int[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<int> GetEnumerator() => list.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
|
||||
}
|
||||
|
||||
public static class TestClass
|
||||
{
|
||||
public static readonly TestIndexer AAAAATest = new TestIndexer();
|
||||
|
||||
public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue)
|
||||
static TestClass()
|
||||
{
|
||||
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");
|
||||
Init_Mono();
|
||||
#if CPP
|
||||
Init_IL2CPP();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static List<int> AWritableList = new List<int> { 1, 2, 3, 4, 5 };
|
||||
public static Dictionary<string, int> AWritableDict = new Dictionary<string, int> { { "one", 1 }, { "two", 2 } };
|
||||
|
||||
public static IEnumerable ANestedList = new List<List<List<string>>>
|
||||
{
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
"three",
|
||||
"four",
|
||||
}
|
||||
},
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"five"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static IDictionary ARandomDictionary = new Dictionary<object, object>
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ "one", "two" },
|
||||
{ true, false },
|
||||
{ new Vector3(0,1,2), new Vector3(1,2,3) },
|
||||
{ CameraClearFlags.Depth, CameraClearFlags.Color },
|
||||
{ "################################################\r\n##########", null },
|
||||
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
|
||||
};
|
||||
|
||||
public static Hashtable TestHashtable = new Hashtable
|
||||
{
|
||||
{ "one", "value" },
|
||||
{ "two", "value" },
|
||||
{ "three", "value" },
|
||||
};
|
||||
|
||||
public const int ConstantInt = 5;
|
||||
|
||||
public static Color AColor = Color.magenta;
|
||||
public static Color32 AColor32 = Color.red;
|
||||
|
||||
// Test enumerables
|
||||
public static List<object> ListOfInts;
|
||||
public static List<List<List<string>>> NestedList;
|
||||
public static IDictionary MixedDictionary;
|
||||
public static Hashtable Hashtable;
|
||||
public static byte[] ByteArray = new byte[16];
|
||||
public static string LongString = new string('#', 10000);
|
||||
public static List<string> BigList = new List<string>(10000);
|
||||
public static List<short> ABigList = new List<short>(10000);
|
||||
|
||||
// Test const behaviour (should be a readonly field)
|
||||
public const int ConstantInt5 = 5;
|
||||
|
||||
// Testing other InteractiveValues
|
||||
public static Color Color = Color.magenta;
|
||||
public static Color32 Color32 = Color.red;
|
||||
public static string ALongString = new string('#', 10000);
|
||||
|
||||
public static List<object> RandomList
|
||||
{
|
||||
@ -133,25 +52,7 @@ namespace UnityExplorer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestGeneric<T>()
|
||||
{
|
||||
ExplorerCore.Log("Test1 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestGenericClass<T>() where T : class
|
||||
{
|
||||
ExplorerCore.Log("Test2 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestComponent<T>() where T : Component
|
||||
{
|
||||
ExplorerCore.Log("Test3 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestStruct<T>() where T : struct
|
||||
{
|
||||
ExplorerCore.Log("Test3 " + typeof(T).FullName);
|
||||
}
|
||||
// Test methods
|
||||
|
||||
private static object GetRandomObject()
|
||||
{
|
||||
@ -165,109 +66,133 @@ namespace UnityExplorer.Tests
|
||||
case 2: return true;
|
||||
case 3: return "hello";
|
||||
case 4: return 50.5f;
|
||||
case 5: return UnityEngine.CameraClearFlags.Color;
|
||||
case 6: return new List<string> { "sub list", "lol" };
|
||||
case 5: return CameraClearFlags.Color;
|
||||
case 6: return new List<string> { "one", "two" };
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void TestComponent<T>() where T : Component
|
||||
{
|
||||
ExplorerCore.Log($"Test3 {typeof(T).FullName}");
|
||||
}
|
||||
|
||||
public static void TestArgumentParse(string s, int i, Color color, CameraClearFlags flags, Vector3 vector, Quaternion quaternion)
|
||||
{
|
||||
ExplorerCore.Log($"{s}, {i}, {color.ToString()}, {flags}, {vector.ToString()}, {quaternion.ToString()}");
|
||||
}
|
||||
|
||||
private static void Init_Mono()
|
||||
{
|
||||
ExplorerCore.Log($"1: Basic list");
|
||||
ListOfInts = new List<object> { 1, 2, 3, 4, 5 };
|
||||
|
||||
ExplorerCore.Log($"2: Nested list");
|
||||
NestedList = new List<List<List<string>>>
|
||||
{
|
||||
new List<List<string>> {
|
||||
new List<string> { "1", "2", "3" },
|
||||
new List<string> { "4", "5", "6" },
|
||||
},
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string> { "7", "8", "9" }
|
||||
}
|
||||
};
|
||||
|
||||
ExplorerCore.Log($"3: Dictionary");
|
||||
MixedDictionary = new Dictionary<object, object>
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ "one", "two" },
|
||||
{ true, false },
|
||||
{ new Vector3(0,1,2), new Vector3(1,2,3) },
|
||||
{ CameraClearFlags.Depth, CameraClearFlags.Color },
|
||||
{ "################################################\r\n##########", null },
|
||||
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
|
||||
};
|
||||
|
||||
ExplorerCore.Log($"4: Hashtable");
|
||||
Hashtable = new Hashtable { { "One", 1 }, { "Two", 2 } };
|
||||
|
||||
ExplorerCore.Log($"5: Big list");
|
||||
for (int i = 0; i < ABigList.Capacity; i++)
|
||||
ABigList.Add((short)UnityEngine.Random.Range(0, short.MaxValue));
|
||||
|
||||
ExplorerCore.Log("Finished TestClass Init_Mono");
|
||||
}
|
||||
|
||||
#if CPP
|
||||
|
||||
public static Il2CppSystem.Collections.IList IL2CPP_IList;
|
||||
public static Il2CppSystem.Collections.Generic.List<string> IL2CPP_ListString;
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
|
||||
|
||||
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
|
||||
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
|
||||
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
|
||||
|
||||
public static string IL2CPP_systemString = "Test";
|
||||
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
|
||||
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
|
||||
public static string nullString = null;
|
||||
|
||||
public static List<Il2CppSystem.Object> IL2CPP_listOfBoxedObjects;
|
||||
public static Il2CppStructArray<int> IL2CPP_structArray;
|
||||
public static Il2CppStringArray IL2CPP_stringArray;
|
||||
public static Il2CppReferenceArray<Il2CppSystem.Object> IL2CPP_ReferenceArray;
|
||||
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
|
||||
public static Il2CppSystem.Collections.IList IL2CPP_IList;
|
||||
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
|
||||
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
|
||||
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
|
||||
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
|
||||
public static Il2CppSystem.Object cppBoxedInt;
|
||||
public static Il2CppSystem.Int32 cppInt;
|
||||
public static Il2CppSystem.Decimal cppDecimal;
|
||||
public static Il2CppSystem.Object cppDecimalBoxed;
|
||||
public static Il2CppSystem.Object cppVector3Boxed;
|
||||
public static string IL2CPP_systemString = "Test";
|
||||
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
|
||||
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
|
||||
public static string nullString = null;
|
||||
|
||||
public static Il2CppSystem.Object RandomBoxedColor
|
||||
private static void Init_IL2CPP()
|
||||
{
|
||||
get
|
||||
{
|
||||
int ran = UnityEngine.Random.Range(0, 3);
|
||||
switch (ran)
|
||||
{
|
||||
case 1: return new Color32().BoxIl2CppObject();
|
||||
case 2: return Color.magenta.BoxIl2CppObject();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Il2CppSystem.Collections.Hashtable cppHashset;
|
||||
|
||||
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
|
||||
|
||||
#endif
|
||||
|
||||
static TestClass()
|
||||
{
|
||||
for (int i = 0; i < BigList.Capacity; i++)
|
||||
BigList.Add(i.ToString());
|
||||
|
||||
#if CPP
|
||||
ExplorerCore.Log($"IL2CPP 1: Il2Cpp Dictionary<string, string>");
|
||||
IL2CPP_Dict = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
|
||||
IL2CPP_Dict.Add("key1", "value1");
|
||||
IL2CPP_Dict.Add("key2", "value2");
|
||||
IL2CPP_Dict.Add("key3", "value3");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 2: Il2Cpp Hashtable");
|
||||
IL2CPP_HashTable = new Il2CppSystem.Collections.Hashtable();
|
||||
IL2CPP_HashTable.Add("key1", "value1");
|
||||
IL2CPP_HashTable.Add("key2", "value2");
|
||||
IL2CPP_HashTable.Add("key3", "value3");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 3: Il2Cpp IDictionary");
|
||||
var dict2 = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
|
||||
dict2.Add("key1", "value1");
|
||||
IL2CPP_IDict = dict2.TryCast<Il2CppSystem.Collections.IDictionary>();
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 4: Il2Cpp List of Il2Cpp Object");
|
||||
var list = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object>(5);
|
||||
list.Add("one");
|
||||
list.Add("two");
|
||||
IL2CPP_IList = list.TryCast<Il2CppSystem.Collections.IList>();
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 5: Il2Cpp List of strings");
|
||||
IL2CPP_ListString = new Il2CppSystem.Collections.Generic.List<string>();
|
||||
IL2CPP_ListString.Add("hello,");
|
||||
IL2CPP_ListString.Add("world!");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 6: Il2Cpp HashSet of strings");
|
||||
IL2CPP_HashSet = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||
IL2CPP_HashSet.Add("one");
|
||||
IL2CPP_HashSet.Add("two");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 7: Dictionary of Il2Cpp String and Il2Cpp Object");
|
||||
CppBoxedDict = new Dictionary<Il2CppSystem.String, Il2CppSystem.Object>();
|
||||
CppBoxedDict.Add("1", new Il2CppSystem.Int32 { m_value = 1 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("2", new Il2CppSystem.Int32 { m_value = 2 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("3", new Il2CppSystem.Int32 { m_value = 3 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("4", new Il2CppSystem.Int32 { m_value = 4 }.BoxIl2CppObject());
|
||||
|
||||
cppDecimal = new Il2CppSystem.Decimal(1f);
|
||||
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
|
||||
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
|
||||
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 8: List of boxed Il2Cpp Objects");
|
||||
IL2CPP_listOfBoxedObjects = new List<Il2CppSystem.Object>();
|
||||
IL2CPP_listOfBoxedObjects.Add((Il2CppSystem.String)"boxedString");
|
||||
IL2CPP_listOfBoxedObjects.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject());
|
||||
IL2CPP_listOfBoxedObjects.Add(Color.red.BoxIl2CppObject());
|
||||
|
||||
// boxed enum test
|
||||
try
|
||||
{
|
||||
var cppType = Il2CppType.Of<CameraClearFlags>();
|
||||
@ -283,9 +208,10 @@ namespace UnityExplorer.Tests
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Test fail: {ex}");
|
||||
ExplorerCore.LogWarning($"Boxed enum test fail: {ex}");
|
||||
}
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 9: Il2Cpp struct array of ints");
|
||||
IL2CPP_structArray = new UnhollowerBaseLib.Il2CppStructArray<int>(5);
|
||||
IL2CPP_structArray[0] = 0;
|
||||
IL2CPP_structArray[1] = 1;
|
||||
@ -293,24 +219,21 @@ namespace UnityExplorer.Tests
|
||||
IL2CPP_structArray[3] = 3;
|
||||
IL2CPP_structArray[4] = 4;
|
||||
|
||||
IL2CPP_stringArray = new UnhollowerBaseLib.Il2CppStringArray(2);
|
||||
IL2CPP_stringArray[0] = "hello, ";
|
||||
IL2CPP_stringArray[1] = "world!";
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 10: Il2Cpp reference array of boxed objects");
|
||||
IL2CPP_ReferenceArray = new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(3);
|
||||
IL2CPP_ReferenceArray[0] = new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject();
|
||||
IL2CPP_ReferenceArray[1] = null;
|
||||
IL2CPP_ReferenceArray[2] = (Il2CppSystem.String)"whats up";
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 11: Misc il2cpp members");
|
||||
cppBoxedInt = new Il2CppSystem.Int32() { m_value = 5 }.BoxIl2CppObject();
|
||||
cppInt = new Il2CppSystem.Int32 { m_value = 420 };
|
||||
cppDecimal = new Il2CppSystem.Decimal(1f);
|
||||
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
|
||||
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
|
||||
|
||||
cppHashset = new Il2CppSystem.Collections.Hashtable();
|
||||
cppHashset.Add("key1", "itemOne");
|
||||
cppHashset.Add("key2", "itemTwo");
|
||||
cppHashset.Add("key3", "itemThree");
|
||||
|
||||
#endif
|
||||
ExplorerCore.Log($"Finished Init_Il2Cpp");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace UnityExplorer
|
||||
/// </summary>
|
||||
public static bool ContainsIgnoreCase(this string _this, string s)
|
||||
{
|
||||
return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
|
||||
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,8 +10,6 @@ namespace UnityExplorer
|
||||
{
|
||||
public static class ParseUtility
|
||||
{
|
||||
public static CultureInfo en_US = new CultureInfo("en-US");
|
||||
|
||||
private static readonly HashSet<Type> nonPrimitiveTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(string),
|
||||
@ -19,20 +17,18 @@ namespace UnityExplorer
|
||||
typeof(DateTime),
|
||||
};
|
||||
|
||||
public const string NUMBER_FORMAT = "0.####";
|
||||
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
|
||||
// And also for formatting a sequence of those numbers, ie a Vector3, Color etc
|
||||
|
||||
public static readonly string NumberFormatString = $"0.####";
|
||||
private static readonly Dictionary<int, string> numSequenceStrings = new Dictionary<int, string>();
|
||||
|
||||
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
|
||||
public static string FormatDecimalSequence(params object[] numbers)
|
||||
{
|
||||
if (numbers.Length <= 0)
|
||||
return null;
|
||||
|
||||
int count = numbers.Length;
|
||||
var formatString = GetSequenceFormatString(count);
|
||||
|
||||
return string.Format(en_US, formatString, numbers);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetSequenceFormatString(numbers.Length), numbers);
|
||||
}
|
||||
|
||||
public static string GetSequenceFormatString(int count)
|
||||
@ -46,19 +42,19 @@ namespace UnityExplorer
|
||||
string[] strings = new string[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
strings[i] = $"{{{i}:{NUMBER_FORMAT}}}";
|
||||
strings[i] = $"{{{i}:{NumberFormatString}}}";
|
||||
|
||||
string s = string.Join(", ", strings);
|
||||
|
||||
numSequenceStrings.Add(count, s);
|
||||
return s;
|
||||
string ret = string.Join(" ", strings);
|
||||
numSequenceStrings.Add(count, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Main parsing API
|
||||
|
||||
public static bool CanParse(Type type)
|
||||
{
|
||||
if (string.IsNullOrEmpty(type.FullName))
|
||||
return false;
|
||||
return type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName);
|
||||
return !string.IsNullOrEmpty(type?.FullName)
|
||||
&& (type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName));
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
|
||||
@ -143,7 +139,7 @@ namespace UnityExplorer
|
||||
else if (formattedTypes.Contains(type))
|
||||
{
|
||||
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
|
||||
.Invoke(obj, new object[] { NUMBER_FORMAT, en_US })
|
||||
.Invoke(obj, new object[] { NumberFormatString, CultureInfo.CurrentCulture })
|
||||
as string;
|
||||
}
|
||||
else
|
||||
@ -166,9 +162,7 @@ namespace UnityExplorer
|
||||
try
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = Activator.CreateInstance(type);
|
||||
@ -222,10 +216,10 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector2 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -244,11 +238,11 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -267,12 +261,12 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector4 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.w = float.Parse(split[3].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -291,22 +285,22 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
if (split.Length == 4)
|
||||
{
|
||||
Quaternion quat = default;
|
||||
quat.x = float.Parse(split[0].Trim(), en_US);
|
||||
quat.y = float.Parse(split[1].Trim(), en_US);
|
||||
quat.z = float.Parse(split[2].Trim(), en_US);
|
||||
quat.w = float.Parse(split[3].Trim(), en_US);
|
||||
quat.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
return quat;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
return Quaternion.Euler(vector);
|
||||
}
|
||||
}
|
||||
@ -327,12 +321,12 @@ namespace UnityExplorer
|
||||
{
|
||||
Rect rect = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
rect.x = float.Parse(split[0].Trim(), en_US);
|
||||
rect.y = float.Parse(split[1].Trim(), en_US);
|
||||
rect.width = float.Parse(split[2].Trim(), en_US);
|
||||
rect.height = float.Parse(split[3].Trim(), en_US);
|
||||
rect.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.width = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.height = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return rect;
|
||||
}
|
||||
@ -351,13 +345,13 @@ namespace UnityExplorer
|
||||
{
|
||||
Color color = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
color.r = float.Parse(split[0].Trim(), en_US);
|
||||
color.g = float.Parse(split[1].Trim(), en_US);
|
||||
color.b = float.Parse(split[2].Trim(), en_US);
|
||||
color.r = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
color.g = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
color.b = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
if (split.Length > 3)
|
||||
color.a = float.Parse(split[3].Trim(), en_US);
|
||||
color.a = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
else
|
||||
color.a = 1;
|
||||
|
||||
@ -378,13 +372,13 @@ namespace UnityExplorer
|
||||
{
|
||||
Color32 color = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
color.r = byte.Parse(split[0].Trim(), en_US);
|
||||
color.g = byte.Parse(split[1].Trim(), en_US);
|
||||
color.b = byte.Parse(split[2].Trim(), en_US);
|
||||
color.r = byte.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
color.g = byte.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
color.b = byte.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
if (split.Length > 3)
|
||||
color.a = byte.Parse(split[3].Trim(), en_US);
|
||||
color.a = byte.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
else
|
||||
color.a = 255;
|
||||
|
||||
@ -397,7 +391,7 @@ namespace UnityExplorer
|
||||
return null;
|
||||
|
||||
// ints, this is fine
|
||||
return $"{color.r}, {color.g}, {color.b}, {color.a}";
|
||||
return $"{color.r} {color.g} {color.b} {color.a}";
|
||||
}
|
||||
|
||||
// Layermask (Int32)
|
||||
|
@ -20,13 +20,15 @@ namespace UnityExplorer
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "4.1.11";
|
||||
public const string VERSION = "4.3.0";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.unityexplorer";
|
||||
|
||||
public static IExplorerLoader Loader { get; private set; }
|
||||
public static RuntimeContext Context { get; internal set; }
|
||||
|
||||
public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize UnityExplorer with the provided Loader implementation.
|
||||
/// </summary>
|
||||
@ -64,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
63
src/Hooks/AddHookCell.cs
Normal 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
79
src/Hooks/HookCell.cs
Normal 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
193
src/Hooks/HookInstance.cs
Normal 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
233
src/Hooks/HookManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,20 +8,10 @@
|
||||
<InputAssemblies Include="..\lib\mcs-unity\mcs\bin\Release\mcs.dll" />
|
||||
<InputAssemblies Include="packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- MonoMod for MelonLoader 0.3.0 -->
|
||||
<ItemGroup Condition="'$(IsMelonLoaderLegacy)'=='true'">
|
||||
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll" />
|
||||
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Mdb.dll" />
|
||||
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Pdb.dll" />
|
||||
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll" />
|
||||
<InputAssemblies Include="packages\MonoMod.RuntimeDetour.20.1.1.4\lib\net35\MonoMod.RuntimeDetour.dll" />
|
||||
<InputAssemblies Include="packages\MonoMod.Utils.20.1.1.4\lib\net35\MonoMod.Utils.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Required references for ILRepack -->
|
||||
<ItemGroup>
|
||||
<ReferenceFolders Include="packages\HarmonyX.2.4.2\lib\net35\" />
|
||||
<ReferenceFolders Include="packages\HarmonyX.2.5.2\lib\net35\" />
|
||||
<ReferenceFolders Include="..\lib\BepInEx.6.IL2CPP\" />
|
||||
<ReferenceFolders Include="..\lib\BepInEx.6.Mono\" />
|
||||
<ReferenceFolders Include="..\lib\BepInEx.5\" />
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
21
src/Inspectors/MouseInspectors/MouseInspectorBase.cs
Normal file
21
src/Inspectors/MouseInspectors/MouseInspectorBase.cs
Normal 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();
|
||||
}
|
||||
}
|
142
src/Inspectors/MouseInspectors/UiInspector.cs
Normal file
142
src/Inspectors/MouseInspectors/UiInspector.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
61
src/Inspectors/MouseInspectors/WorldInspector.cs
Normal file
61
src/Inspectors/MouseInspectors/WorldInspector.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
|
||||
|
||||
public InputFieldRef HiddenNameText;
|
||||
public Text NameText;
|
||||
public Text AssemblyText;
|
||||
private Toggle autoUpdateToggle;
|
||||
@ -125,6 +126,7 @@ namespace UnityExplorer.Inspectors
|
||||
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
|
||||
Tab.TabText.text = currentBaseTabText;
|
||||
NameText.text = SignatureHighlighter.Parse(TargetType, true);
|
||||
HiddenNameText.Text = TargetType.FullName;
|
||||
|
||||
string asmText;
|
||||
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
|
||||
@ -332,8 +334,27 @@ namespace UnityExplorer.Inspectors
|
||||
|
||||
// Class name, assembly
|
||||
|
||||
NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 17);
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
var titleHolder = UIFactory.CreateUIObject("TitleHolder", UIRoot);
|
||||
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
|
||||
var namerect = NameText.GetComponent<RectTransform>();
|
||||
namerect.anchorMin = new Vector2(0, 0);
|
||||
namerect.anchorMax = new Vector2(1, 1);
|
||||
NameText.fontSize = 17;
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
|
||||
|
||||
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
|
||||
var hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
|
||||
hiddenrect.anchorMin = new Vector2(0, 0);
|
||||
hiddenrect.anchorMax = new Vector2(1, 1);
|
||||
HiddenNameText.Component.readOnly = true;
|
||||
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
|
||||
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
HiddenNameText.Component.textComponent.fontSize = 17;
|
||||
HiddenNameText.Component.textComponent.color = Color.clear;
|
||||
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
@ -39,6 +39,8 @@ namespace UnityExplorer
|
||||
=> Log;
|
||||
#endif
|
||||
|
||||
public string UnhollowedModulesFolder => Path.Combine(Paths.BepInExRootPath, "unhollowed");
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
private BepInExConfigHandler _configHandler;
|
||||
|
||||
@ -51,8 +53,7 @@ namespace UnityExplorer
|
||||
public Action<object> OnLogWarning => LogSource.LogWarning;
|
||||
public Action<object> OnLogError => LogSource.LogError;
|
||||
|
||||
// Init common to Mono and Il2Cpp
|
||||
internal void UniversalInit()
|
||||
private void Init()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new BepInExConfigHandler();
|
||||
@ -62,57 +63,15 @@ namespace UnityExplorer
|
||||
#if MONO // Mono
|
||||
internal void Awake()
|
||||
{
|
||||
UniversalInit();
|
||||
Init();
|
||||
}
|
||||
|
||||
#else // Il2Cpp
|
||||
public override void Load()
|
||||
{
|
||||
UniversalInit();
|
||||
Init();
|
||||
}
|
||||
#endif
|
||||
|
||||
public void SetupCursorPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HarmonyInstance.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
|
||||
public class PATCH_EventSystem_current
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
|
||||
public class PATCH_Cursor_lockState
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_lockState(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
|
||||
public class PATCH_Cursor_visible
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_visible(ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -9,13 +9,12 @@ namespace UnityExplorer
|
||||
public interface IExplorerLoader
|
||||
{
|
||||
string ExplorerFolder { get; }
|
||||
string UnhollowedModulesFolder { get; }
|
||||
|
||||
ConfigHandler ConfigHandler { get; }
|
||||
|
||||
Action<object> OnLogMessage { get; }
|
||||
Action<object> OnLogWarning { get; }
|
||||
Action<object> OnLogError { get; }
|
||||
|
||||
void SetupCursorPatches();
|
||||
}
|
||||
}
|
||||
|
@ -1,86 +0,0 @@
|
||||
#if ML
|
||||
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;
|
||||
#if ML_LEGACY
|
||||
using Harmony;
|
||||
#else
|
||||
using HarmonyLib;
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
|
||||
#endif
|
||||
|
||||
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||
[assembly: MelonGame(null, null)]
|
||||
[assembly: MelonColor(ConsoleColor.DarkCyan)]
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ExplorerMelonMod : MelonMod, IExplorerLoader
|
||||
{
|
||||
public static ExplorerMelonMod Instance;
|
||||
|
||||
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
public MelonLoaderConfigHandler _configHandler;
|
||||
|
||||
public Action<object> OnLogMessage => MelonLogger.Msg;
|
||||
public Action<object> OnLogWarning => MelonLogger.Warning;
|
||||
public Action<object> OnLogError => MelonLogger.Error;
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new MelonLoaderConfigHandler();
|
||||
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
|
||||
public void SetupCursorPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
PrefixProperty(typeof(Cursor),
|
||||
"lockState",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
|
||||
|
||||
PrefixProperty(typeof(Cursor),
|
||||
"visible",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
|
||||
|
||||
PrefixProperty(typeof(EventSystem),
|
||||
"current",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void PrefixProperty(Type type, string property, HarmonyMethod prefix)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prop = type.GetProperty(property);
|
||||
#if ML_LEGACY
|
||||
this.Harmony.Patch(prop.GetSetMethod(), prefix: prefix);
|
||||
#else
|
||||
HarmonyInstance.Patch(prop.GetSetMethod(), prefix: prefix);
|
||||
#endif
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,206 +0,0 @@
|
||||
#if ML
|
||||
|
||||
#if !ML_LEGACY // ML 0.3.1+ config handler
|
||||
|
||||
using MelonLoader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
|
||||
namespace UnityExplorer.Loader.ML
|
||||
{
|
||||
public class MelonLoaderConfigHandler : ConfigHandler
|
||||
{
|
||||
internal const string CTG_NAME = "UnityExplorer";
|
||||
|
||||
internal MelonPreferences_Category prefCategory;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings", false, true);
|
||||
}
|
||||
|
||||
public override void LoadConfig()
|
||||
{
|
||||
foreach (var entry in ConfigManager.ConfigElements)
|
||||
{
|
||||
var key = entry.Key;
|
||||
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
|
||||
{
|
||||
var config = entry.Value;
|
||||
config.BoxedValue = config.GetLoaderConfigValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> config)
|
||||
{
|
||||
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.Description, config.IsInternal, false);
|
||||
|
||||
entry.OnValueChangedUntyped += () =>
|
||||
{
|
||||
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
|
||||
return;
|
||||
|
||||
config.Value = entry.Value;
|
||||
};
|
||||
}
|
||||
|
||||
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
{
|
||||
entry.Value = value;
|
||||
//entry.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public override T GetConfigValue<T>(ConfigElement<T> config)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
return entry.Value;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void OnAnyConfigChanged()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SaveConfig()
|
||||
{
|
||||
MelonPreferences.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else // ML 0.3.0 config handler
|
||||
|
||||
using MelonLoader;
|
||||
using MelonLoader.Tomlyn.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
|
||||
namespace UnityExplorer.Loader.ML
|
||||
{
|
||||
public class MelonLoaderConfigHandler : ConfigHandler
|
||||
{
|
||||
internal const string CTG_NAME = "UnityExplorer";
|
||||
|
||||
internal MelonPreferences_Category prefCategory;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings");
|
||||
|
||||
try { MelonPreferences.Mapper.RegisterMapper(KeycodeReader, KeycodeWriter); } catch { }
|
||||
try { MelonPreferences.Mapper.RegisterMapper(AnchorReader, AnchorWriter); } catch { }
|
||||
}
|
||||
|
||||
public override void LoadConfig()
|
||||
{
|
||||
foreach (var entry in ConfigManager.ConfigElements)
|
||||
{
|
||||
var key = entry.Key;
|
||||
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
|
||||
{
|
||||
var config = entry.Value;
|
||||
config.BoxedValue = config.GetLoaderConfigValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> config)
|
||||
{
|
||||
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.IsInternal) as MelonPreferences_Entry<T>;
|
||||
|
||||
entry.OnValueChangedUntyped += () =>
|
||||
{
|
||||
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
|
||||
return;
|
||||
|
||||
config.Value = entry.Value;
|
||||
};
|
||||
}
|
||||
|
||||
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
{
|
||||
entry.Value = value;
|
||||
entry.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public override T GetConfigValue<T>(ConfigElement<T> config)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
return entry.Value;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void OnAnyConfigChanged()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SaveConfig()
|
||||
{
|
||||
MelonPreferences.Save();
|
||||
}
|
||||
|
||||
// Enum config handlers
|
||||
|
||||
public static KeyCode KeycodeReader(TomlObject value)
|
||||
{
|
||||
try
|
||||
{
|
||||
KeyCode kc = (KeyCode)Enum.Parse(typeof(KeyCode), (value as TomlString).Value);
|
||||
|
||||
if (kc == default)
|
||||
throw new Exception();
|
||||
|
||||
return kc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return KeyCode.F7;
|
||||
}
|
||||
}
|
||||
|
||||
public static TomlObject KeycodeWriter(KeyCode value)
|
||||
{
|
||||
return MelonPreferences.Mapper.ToToml(value.ToString());
|
||||
}
|
||||
|
||||
public static UI.UIManager.VerticalAnchor AnchorReader(TomlObject value)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (UI.UIManager.VerticalAnchor)Enum.Parse(typeof(UI.UIManager.VerticalAnchor), (value as TomlString).Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return UI.UIManager.VerticalAnchor.Top;
|
||||
}
|
||||
}
|
||||
|
||||
public static TomlObject AnchorWriter(UI.UIManager.VerticalAnchor anchor)
|
||||
{
|
||||
return MelonPreferences.Mapper.ToToml(anchor.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
42
src/Loader/MelonLoader/ExplorerMelonMod.cs
Normal file
42
src/Loader/MelonLoader/ExplorerMelonMod.cs
Normal file
@ -0,0 +1,42 @@
|
||||
#if ML
|
||||
using System;
|
||||
using System.IO;
|
||||
using MelonLoader;
|
||||
using UnityExplorer;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Loader.ML;
|
||||
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
|
||||
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||
[assembly: MelonGame(null, null)]
|
||||
[assembly: MelonColor(ConsoleColor.DarkCyan)]
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ExplorerMelonMod : MelonMod, IExplorerLoader
|
||||
{
|
||||
public static ExplorerMelonMod Instance;
|
||||
|
||||
public string ExplorerFolder => Path.Combine(MelonHandler.ModsDirectory, ExplorerCore.NAME);
|
||||
|
||||
public string UnhollowedModulesFolder => Path.Combine(
|
||||
Path.GetDirectoryName(MelonHandler.ModsDirectory),
|
||||
Path.Combine("MelonLoader", "Managed"));
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
public MelonLoaderConfigHandler _configHandler;
|
||||
|
||||
public Action<object> OnLogMessage => MelonLogger.Msg;
|
||||
public Action<object> OnLogWarning => MelonLogger.Warning;
|
||||
public Action<object> OnLogError => MelonLogger.Error;
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new MelonLoaderConfigHandler();
|
||||
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
94
src/Loader/MelonLoader/MelonLoaderConfigHandler.cs
Normal file
94
src/Loader/MelonLoader/MelonLoaderConfigHandler.cs
Normal file
@ -0,0 +1,94 @@
|
||||
#if ML
|
||||
using MelonLoader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
|
||||
namespace UnityExplorer.Loader.ML
|
||||
{
|
||||
public class MelonLoaderConfigHandler : ConfigHandler
|
||||
{
|
||||
internal const string CTG_NAME = "UnityExplorer";
|
||||
|
||||
internal MelonPreferences_Category prefCategory;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings", false, true);
|
||||
}
|
||||
|
||||
public override void LoadConfig()
|
||||
{
|
||||
foreach (var entry in ConfigManager.ConfigElements)
|
||||
{
|
||||
var key = entry.Key;
|
||||
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
|
||||
{
|
||||
var config = entry.Value;
|
||||
config.BoxedValue = config.GetLoaderConfigValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This wrapper exists to handle the arbitrary "LemonAction" delegates which ML now uses in 0.4.4+.
|
||||
// Reflection is required since the delegate type changed between 0.4.3 and 0.4.4.
|
||||
// A wrapper class is required to link the MelonPreferences_Entry and the delegate instance.
|
||||
public class EntryDelegateWrapper<T>
|
||||
{
|
||||
public MelonPreferences_Entry<T> entry;
|
||||
public ConfigElement<T> config;
|
||||
|
||||
public EntryDelegateWrapper(MelonPreferences_Entry<T> entry, ConfigElement<T> config)
|
||||
{
|
||||
this.entry = entry;
|
||||
this.config = config;
|
||||
var evt = entry.GetType().GetEvent("OnValueChangedUntyped");
|
||||
evt.AddEventHandler(entry, Delegate.CreateDelegate(evt.EventHandlerType, this, GetType().GetMethod("OnChanged")));
|
||||
}
|
||||
|
||||
public void OnChanged()
|
||||
{
|
||||
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
|
||||
return;
|
||||
config.Value = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> config)
|
||||
{
|
||||
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.Description, config.IsInternal, false);
|
||||
new EntryDelegateWrapper<T>(entry, config);
|
||||
}
|
||||
|
||||
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
{
|
||||
entry.Value = value;
|
||||
//entry.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public override T GetConfigValue<T>(ConfigElement<T> config)
|
||||
{
|
||||
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
|
||||
return entry.Value;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public override void OnAnyConfigChanged()
|
||||
{
|
||||
}
|
||||
|
||||
public override void SaveConfig()
|
||||
{
|
||||
MelonPreferences.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -18,26 +18,43 @@ namespace UnityExplorer
|
||||
public class ExplorerStandalone : IExplorerLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Call this to initialize UnityExplorer without adding a log listener.
|
||||
/// Call this to initialize UnityExplorer without adding a log listener or Unhollowed modules path.
|
||||
/// The default Unhollowed path "UnityExplorer\Modules\" will be used.
|
||||
/// </summary>
|
||||
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
|
||||
public static ExplorerStandalone CreateInstance()
|
||||
=> CreateInstance(null);
|
||||
public static ExplorerStandalone CreateInstance() => CreateInstance(null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages.
|
||||
/// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages, without specifying an Unhollowed modules path.
|
||||
/// The default Unhollowed path "UnityExplorer\Modules\" will be used.
|
||||
/// </summary>
|
||||
/// <param name="logListener">Your log listener to handle UnityExplorer logs.</param>
|
||||
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
|
||||
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener)
|
||||
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener) => CreateInstance(logListener, null);
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize UnityExplorer with the provided log listener and Unhollowed modules path.
|
||||
/// </summary>
|
||||
/// <param name="logListener">Your log listener to handle UnityExplorer logs.</param>
|
||||
/// <param name="unhollowedModulesPath">The path of the Unhollowed modules, either relative or absolute.</param>
|
||||
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
|
||||
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener, string unhollowedModulesPath)
|
||||
{
|
||||
if (Instance != null)
|
||||
return Instance;
|
||||
|
||||
OnLog += logListener;
|
||||
|
||||
var instance = new ExplorerStandalone();
|
||||
instance.Init();
|
||||
instance.CheckExplorerFolder();
|
||||
|
||||
if (logListener != null)
|
||||
OnLog += logListener;
|
||||
|
||||
if (string.IsNullOrEmpty(unhollowedModulesPath) || !Directory.Exists(unhollowedModulesPath))
|
||||
instance._unhollowedPath = Path.Combine(instance.ExplorerFolder, "Modules");
|
||||
else
|
||||
instance._unhollowedPath = unhollowedModulesPath;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -48,8 +65,8 @@ namespace UnityExplorer
|
||||
/// </summary>
|
||||
public static event Action<string, LogType> OnLog;
|
||||
|
||||
public Harmony HarmonyInstance => s_harmony;
|
||||
public static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
|
||||
public string UnhollowedModulesFolder => _unhollowedPath;
|
||||
private string _unhollowedPath;
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
private StandaloneConfigHandler _configHandler;
|
||||
@ -58,19 +75,7 @@ namespace UnityExplorer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_explorerFolder == null)
|
||||
{
|
||||
s_explorerFolder =
|
||||
Path.Combine(
|
||||
Path.GetDirectoryName(
|
||||
Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase)
|
||||
.AbsolutePath)),
|
||||
"UnityExplorer");
|
||||
|
||||
if (!Directory.Exists(s_explorerFolder))
|
||||
Directory.CreateDirectory(s_explorerFolder);
|
||||
}
|
||||
|
||||
CheckExplorerFolder();
|
||||
return s_explorerFolder;
|
||||
}
|
||||
}
|
||||
@ -88,45 +93,18 @@ namespace UnityExplorer
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
|
||||
public void SetupCursorPatches()
|
||||
private void CheckExplorerFolder()
|
||||
{
|
||||
try
|
||||
if (s_explorerFolder == null)
|
||||
{
|
||||
this.HarmonyInstance.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
s_explorerFolder =
|
||||
Path.Combine(
|
||||
Path.GetDirectoryName(
|
||||
Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath)),
|
||||
"UnityExplorer");
|
||||
|
||||
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
|
||||
public class PATCH_EventSystem_current
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
|
||||
public class PATCH_Cursor_lockState
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_lockState(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
|
||||
public class PATCH_Cursor_visible
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_visible(ref value);
|
||||
if (!Directory.Exists(s_explorerFolder))
|
||||
Directory.CreateDirectory(s_explorerFolder);
|
||||
}
|
||||
}
|
||||
}
|
@ -56,13 +56,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
else if (m_context == SearchContext.Class)
|
||||
currentResults = SearchProvider.ClassSearch(nameInputField.Text);
|
||||
else
|
||||
{
|
||||
string compType = "";
|
||||
if (m_context == SearchContext.UnityObject)
|
||||
compType = this.desiredTypeInput;
|
||||
|
||||
currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, compType, m_context, m_childFilter, m_sceneFilter);
|
||||
}
|
||||
currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, desiredTypeInput, m_context, m_childFilter, m_sceneFilter);
|
||||
|
||||
dataHandler.RefreshData();
|
||||
resultsScrollPool.Refresh(true);
|
||||
|
@ -40,14 +40,12 @@ namespace UnityExplorer.ObjectExplorer
|
||||
/// <summary>
|
||||
/// The names of all scenes in the build settings, if they could be retrieved.
|
||||
/// </summary>
|
||||
public static ReadOnlyCollection<string> AllSceneNames => new ReadOnlyCollection<string>(allScenesInBuild);
|
||||
private static readonly List<string> allScenesInBuild = new List<string>();
|
||||
public static readonly List<string> AllSceneNames = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not we successfuly retrieved the names of the scenes in the build settings.
|
||||
/// </summary>
|
||||
public static bool WasAbleToGetScenesInBuild => gotAllScenesInBuild;
|
||||
private static bool gotAllScenesInBuild = true;
|
||||
public static bool WasAbleToGetScenesInBuild { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the currently inspected Scene changes. The argument is the new scene.
|
||||
@ -97,12 +95,12 @@ namespace UnityExplorer.ObjectExplorer
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
var scenePath = (string)method.Invoke(null, new object[] { i });
|
||||
allScenesInBuild.Add(scenePath);
|
||||
AllSceneNames.Add(scenePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
gotAllScenesInBuild = false;
|
||||
WasAbleToGetScenesInBuild = false;
|
||||
ExplorerCore.LogWarning($"Unable to generate list of all Scenes in the build: {ex}");
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
120
src/UI/Panels/HookManagerPanel.cs
Normal file
120
src/UI/Panels/HookManagerPanel.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
@ -209,6 +207,10 @@ namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
|
||||
SetTransformDefaults();
|
||||
UIManager.Initializing = false;
|
||||
DoSaveToConfigElement();
|
||||
ConfigManager.InternalHandler.SaveConfig();
|
||||
UIManager.Initializing = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +337,7 @@ namespace UnityExplorer.UI.Panels
|
||||
if (!rect)
|
||||
throw new ArgumentNullException("rect");
|
||||
|
||||
return string.Format(ParseUtility.en_US, "{0},{1},{2},{3}", new object[]
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", new object[]
|
||||
{
|
||||
rect.anchorMin.x,
|
||||
rect.anchorMin.y,
|
||||
@ -349,16 +351,20 @@ namespace UnityExplorer.UI.Panels
|
||||
if (string.IsNullOrEmpty(stringAnchors))
|
||||
throw new ArgumentNullException("stringAnchors");
|
||||
|
||||
if (stringAnchors.Contains(" "))
|
||||
// outdated save data, not worth recovering just reset it.
|
||||
throw new Exception("invalid save data, resetting.");
|
||||
|
||||
var split = stringAnchors.Split(',');
|
||||
|
||||
if (split.Length != 4)
|
||||
throw new Exception($"stringAnchors split is unexpected length: {split.Length}");
|
||||
|
||||
Vector4 anchors;
|
||||
anchors.x = float.Parse(split[0], ParseUtility.en_US);
|
||||
anchors.y = float.Parse(split[1], ParseUtility.en_US);
|
||||
anchors.z = float.Parse(split[2], ParseUtility.en_US);
|
||||
anchors.w = float.Parse(split[3], ParseUtility.en_US);
|
||||
anchors.x = float.Parse(split[0], CultureInfo.InvariantCulture);
|
||||
anchors.y = float.Parse(split[1], CultureInfo.InvariantCulture);
|
||||
anchors.z = float.Parse(split[2], CultureInfo.InvariantCulture);
|
||||
anchors.w = float.Parse(split[3], CultureInfo.InvariantCulture);
|
||||
|
||||
panel.anchorMin = new Vector2(anchors.x, anchors.y);
|
||||
panel.anchorMax = new Vector2(anchors.z, anchors.w);
|
||||
@ -369,7 +375,7 @@ namespace UnityExplorer.UI.Panels
|
||||
if (!rect)
|
||||
throw new ArgumentNullException("rect");
|
||||
|
||||
return string.Format(ParseUtility.en_US, "{0},{1}", new object[]
|
||||
return string.Format(CultureInfo.InvariantCulture, "{0},{1}", new object[]
|
||||
{
|
||||
rect.localPosition.x, rect.localPosition.y
|
||||
});
|
||||
@ -377,14 +383,21 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
internal static void SetPositionFromString(this RectTransform rect, string stringPosition)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stringPosition))
|
||||
throw new ArgumentNullException(stringPosition);
|
||||
|
||||
if (stringPosition.Contains(" "))
|
||||
// outdated save data, not worth recovering just reset it.
|
||||
throw new Exception("invalid save data, resetting.");
|
||||
|
||||
var split = stringPosition.Split(',');
|
||||
|
||||
if (split.Length != 2)
|
||||
throw new Exception($"stringPosition split is unexpected length: {split.Length}");
|
||||
|
||||
Vector3 vector = rect.localPosition;
|
||||
vector.x = float.Parse(split[0], ParseUtility.en_US);
|
||||
vector.y = float.Parse(split[1], ParseUtility.en_US);
|
||||
vector.x = float.Parse(split[0], CultureInfo.InvariantCulture);
|
||||
vector.y = float.Parse(split[1], CultureInfo.InvariantCulture);
|
||||
rect.localPosition = vector;
|
||||
}
|
||||
}
|
||||
|
76
src/UI/Panels/UiInspectorResultsPanel.cs
Normal file
76
src/UI/Panels/UiInspectorResultsPanel.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -16,12 +16,6 @@ namespace UnityExplorer.UI
|
||||
internal static Vector2 _largeElementSize = new Vector2(100, 30);
|
||||
internal static Vector2 _smallElementSize = new Vector2(25, 25);
|
||||
internal static Color _defaultTextColor = Color.white;
|
||||
internal static Font _defaultFont;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
_defaultFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
}
|
||||
|
||||
public static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
|
||||
{
|
||||
@ -48,7 +42,7 @@ namespace UnityExplorer.UI
|
||||
internal static void SetDefaultTextValues(Text text)
|
||||
{
|
||||
text.color = _defaultTextColor;
|
||||
text.font = _defaultFont;
|
||||
text.font = UIManager.DefaultFont;
|
||||
text.fontSize = 14;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,9 @@ namespace UnityExplorer.UI
|
||||
Options,
|
||||
ConsoleLog,
|
||||
AutoCompleter,
|
||||
MouseInspector
|
||||
MouseInspector,
|
||||
UIInspectorResults,
|
||||
HookManager,
|
||||
}
|
||||
|
||||
public enum VerticalAnchor
|
||||
@ -36,7 +38,7 @@ namespace UnityExplorer.UI
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static bool Initializing { get; private set; } = true;
|
||||
public static bool Initializing { get; internal set; } = true;
|
||||
|
||||
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
|
||||
|
||||
@ -52,6 +54,7 @@ namespace UnityExplorer.UI
|
||||
internal static GameObject PanelHolder { get; private set; }
|
||||
|
||||
internal static Font ConsoleFont { get; private set; }
|
||||
internal static Font DefaultFont { get; private set; }
|
||||
internal static Shader BackupShader { get; private set; }
|
||||
|
||||
public static RectTransform NavBarRect;
|
||||
@ -92,8 +95,6 @@ namespace UnityExplorer.UI
|
||||
{
|
||||
LoadBundle();
|
||||
|
||||
UIFactory.Init();
|
||||
|
||||
CreateRootCanvas();
|
||||
|
||||
// Global UI Pool Holder
|
||||
@ -107,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)
|
||||
@ -241,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;
|
||||
}
|
||||
}
|
||||
@ -423,11 +426,11 @@ namespace UnityExplorer.UI
|
||||
int minor = int.Parse(split[1]);
|
||||
|
||||
// Use appropriate AssetBundle for Unity version
|
||||
// >= 2017.3
|
||||
if (major > 2017 || (major == 2017 && minor >= 3))
|
||||
// >= 2017
|
||||
if (major >= 2017)
|
||||
bundle = LoadBundle("modern");
|
||||
// 5.6.0 to 2017.3
|
||||
else if (major == 2017 || (major == 5 && minor >= 6))
|
||||
// 5.6.0 to <2017
|
||||
else if (major == 5 && minor >= 6)
|
||||
bundle = LoadBundle("legacy.5.6");
|
||||
// < 5.6.0
|
||||
else
|
||||
@ -452,11 +455,15 @@ namespace UnityExplorer.UI
|
||||
{
|
||||
ExplorerCore.LogWarning("Could not load the ExplorerUI Bundle!");
|
||||
ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
DefaultFont = ConsoleFont;
|
||||
return;
|
||||
}
|
||||
|
||||
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
|
||||
// Bundle loaded
|
||||
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||
DefaultFont = bundle.LoadAsset<Font>("arial");
|
||||
|
||||
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
|
||||
// Fix for games which don't ship with 'UI/Default' shader.
|
||||
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
|
||||
{
|
||||
@ -466,7 +473,6 @@ namespace UnityExplorer.UI
|
||||
else
|
||||
BackupShader = Graphic.defaultGraphicMaterial.shader;
|
||||
|
||||
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||
}
|
||||
|
||||
private static byte[] ReadFully(Stream input)
|
||||
|
@ -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)
|
||||
|
@ -93,24 +93,6 @@
|
||||
<IsCpp>true</IsCpp>
|
||||
<IsStandalone>true</IsStandalone>
|
||||
</PropertyGroup>
|
||||
<!-- ML 0.3.0 CPP -->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_MLLegacy_Cpp|AnyCPU'">
|
||||
<OutputPath>..\Release\UnityExplorer.MelonLoader_Legacy.Il2Cpp\</OutputPath>
|
||||
<DefineConstants>CPP,ML,ML_LEGACY</DefineConstants>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<AssemblyName>UnityExplorer.MLLEGACY.IL2CPP</AssemblyName>
|
||||
<IsCpp>true</IsCpp>
|
||||
<IsMelonLoaderLegacy>true</IsMelonLoaderLegacy>
|
||||
</PropertyGroup>
|
||||
<!-- ML 0.3.0 Mono -->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_MLLegacy_Mono|AnyCPU'">
|
||||
<OutputPath>..\Release\UnityExplorer.MelonLoader_Legacy.Mono\</OutputPath>
|
||||
<DefineConstants>MONO,ML,ML_LEGACY</DefineConstants>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<AssemblyName>UnityExplorer.MLLEGACY.Mono</AssemblyName>
|
||||
<IsCpp>false</IsCpp>
|
||||
<IsMelonLoaderLegacy>true</IsMelonLoaderLegacy>
|
||||
</PropertyGroup>
|
||||
<!-- Global refs, Mono and Il2Cpp -->
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@ -135,17 +117,10 @@
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!-- MelonLoader Legacy refs -->
|
||||
<ItemGroup Condition="'$(IsMelonLoaderLegacy)'=='true'">
|
||||
<Reference Include="MelonLoader">
|
||||
<HintPath>..\lib\MelonLoader_Legacy\MelonLoader.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!-- BepInEx universal refs -->
|
||||
<ItemGroup Condition="'$(IsBepInEx)'=='true'">
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>packages\HarmonyX.2.4.2\lib\net35\0Harmony.dll</HintPath>
|
||||
<HintPath>packages\HarmonyX.2.5.2\lib\net35\0Harmony.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
@ -181,7 +156,7 @@
|
||||
<!-- Standalone refs -->
|
||||
<ItemGroup Condition="'$(IsStandalone)'=='true'">
|
||||
<Reference Include="0Harmony">
|
||||
<HintPath>packages\HarmonyX.2.4.2\lib\net35\0Harmony.dll</HintPath>
|
||||
<HintPath>packages\HarmonyX.2.5.2\lib\net35\0Harmony.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
@ -240,9 +215,13 @@
|
||||
</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" />
|
||||
<Compile Include="Core\Reflection\Patches.cs" />
|
||||
<Compile Include="CSConsole\CSAutoCompleter.cs" />
|
||||
<Compile Include="CSConsole\LexerBuilder.cs" />
|
||||
<Compile Include="CSConsole\Lexers\CommentLexer.cs" />
|
||||
@ -259,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" />
|
||||
@ -287,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" />
|
||||
@ -323,13 +308,13 @@
|
||||
<Compile Include="Core\Tests\TestClass.cs" />
|
||||
<Compile Include="Core\Utility\UnityHelpers.cs" />
|
||||
<Compile Include="ExplorerCore.cs" />
|
||||
<Compile Include="Loader\BIE\BepInExConfigHandler.cs" />
|
||||
<Compile Include="Loader\BIE\ExplorerBepInPlugin.cs" />
|
||||
<Compile Include="Loader\BepInEx\BepInExConfigHandler.cs" />
|
||||
<Compile Include="Loader\BepInEx\ExplorerBepInPlugin.cs" />
|
||||
<Compile Include="Loader\IExplorerLoader.cs" />
|
||||
<Compile Include="Loader\ML\ExplorerMelonMod.cs" />
|
||||
<Compile Include="Loader\ML\MelonLoaderConfigHandler.cs" />
|
||||
<Compile Include="Loader\STANDALONE\ExplorerStandalone.cs" />
|
||||
<Compile Include="Loader\STANDALONE\StandaloneConfigHandler.cs" />
|
||||
<Compile Include="Loader\MelonLoader\ExplorerMelonMod.cs" />
|
||||
<Compile Include="Loader\MelonLoader\MelonLoaderConfigHandler.cs" />
|
||||
<Compile Include="Loader\Standalone\ExplorerStandalone.cs" />
|
||||
<Compile Include="Loader\Standalone\StandaloneConfigHandler.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UI\Models\UIBehaviourModel.cs" />
|
||||
<Compile Include="UI\Models\UIModel.cs" />
|
||||
|
@ -16,8 +16,6 @@ Global
|
||||
Release_BIE6_Mono|Any CPU = Release_BIE6_Mono|Any CPU
|
||||
Release_ML_Cpp|Any CPU = Release_ML_Cpp|Any CPU
|
||||
Release_ML_Mono|Any CPU = Release_ML_Mono|Any CPU
|
||||
Release_MLLegacy_Cpp|Any CPU = Release_MLLegacy_Cpp|Any CPU
|
||||
Release_MLLegacy_Mono|Any CPU = Release_MLLegacy_Mono|Any CPU
|
||||
Release_STANDALONE_Cpp|Any CPU = Release_STANDALONE_Cpp|Any CPU
|
||||
Release_STANDALONE_Mono|Any CPU = Release_STANDALONE_Mono|Any CPU
|
||||
EndGlobalSection
|
||||
@ -32,10 +30,6 @@ Global
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -50,10 +44,6 @@ Global
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU
|
||||
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -68,10 +58,6 @@ Global
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.Build.0 = Release_ML_Cpp|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.ActiveCfg = Release_ML_Mono|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.Build.0 = Release_ML_Mono|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release_MLLegacy_Cpp|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release_MLLegacy_Cpp|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release_MLLegacy_Mono|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release_MLLegacy_Mono|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release_STANDALONE_Cpp|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release_STANDALONE_Cpp|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release_STANDALONE_Mono|Any CPU
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="HarmonyX" version="2.4.2" targetFramework="net35" />
|
||||
<package id="HarmonyX" version="2.5.2" targetFramework="net35" />
|
||||
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.2" targetFramework="net35" />
|
||||
<package id="ini-parser" version="2.5.2" targetFramework="net35" />
|
||||
<package id="Mono.Cecil" version="0.10.4" targetFramework="net35" />
|
||||
|
Reference in New Issue
Block a user