mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-23 00:52:31 +08:00
Compare commits
81 Commits
Author | SHA1 | Date | |
---|---|---|---|
6aa9b3aa15 | |||
587842f46b | |||
b9c641f170 | |||
1e68529430 | |||
e84e96a63c | |||
ec8c88ab9b | |||
0eae78eeb2 | |||
a07ead2142 | |||
b93567c7ea | |||
957d80c7ec | |||
d530d10798 | |||
66daabf770 | |||
9fe998aa22 | |||
28b6db80f9 | |||
22effbb399 | |||
50f0c31e98 | |||
911522457e | |||
d5d1841109 | |||
239534e09c | |||
bc5d16051f | |||
f81822f219 | |||
f159cf5ea7 | |||
1e6bacb32b | |||
a80cef4c1d | |||
450bb77f2e | |||
0479102db6 | |||
93181f02be | |||
371054d6df | |||
427f23b80a | |||
a0d5ab8792 | |||
297034e38b | |||
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
|
||||
|
||||
|
43
README.md
43
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 |
|
||||
@ -24,7 +23,6 @@
|
||||
| BIE 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
|
||||
|
||||
1. Take the `UnityExplorer.BIE.[version].dll` file and put it in `BepInEx\plugins\`
|
||||
2. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version, create a folder `BepInEx\unity-libs\`, then extract the Unity libs into this folder.
|
||||
|
||||
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
|
||||
|
||||
@ -33,7 +31,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.
|
||||
|
||||
@ -50,6 +47,24 @@ The standalone release can be used with any injector or loader of your choice, b
|
||||
3. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
|
||||
4. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish
|
||||
|
||||
# Common issues
|
||||
|
||||
These are some common fixes to issues which are present in some games, please create an issue in this repository if these fixes don't work.
|
||||
|
||||
### Input not working properly
|
||||
|
||||
This can be caused by a number of issues, but most commonly by the Event System.
|
||||
|
||||
1. Open the UnityExplorer config file. See the "Settings" section below if you're unsure where it is.
|
||||
2. For the "Disable EventSystem Override" option, set the value to `true`
|
||||
3. Restart the game.
|
||||
|
||||
### UI not appearing or gets destroyed during startup
|
||||
|
||||
1. Open the UnityExplorer config file. See the "Settings" section below if you're unsure where it is.
|
||||
2. For the "Startup Delay" option, set the value to something higher, at least as long as it takes to reach the main menu of the game.
|
||||
3. Restart the game.
|
||||
|
||||
# Features
|
||||
|
||||
<p align="center">
|
||||
@ -86,6 +101,12 @@ The inspector is used to see detailed information on objects of any type and man
|
||||
* You can execute a script automatically on startup by naming it `startup.cs` and placing it in the `UnityExplorer\Scripts\` folder (this folder will be created where you placed the DLL file).
|
||||
* See the "Help" dropdown in the C# console menu for more detailed information.
|
||||
|
||||
### Hook Manager
|
||||
|
||||
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
|
||||
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
|
||||
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
|
||||
|
||||
### Mouse-Inspect
|
||||
|
||||
* The "Mouse Inspect" dropdown on the main UnityExplorer navbar allows you to inspect objects under the mouse.
|
||||
@ -101,12 +122,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 +129,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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule lib/Il2CppAssemblyUnhollower updated: 0911fdaca6...0099c25069
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lib/unhollowed/UnityEngine.AssetBundleModule.dll
Normal file
BIN
lib/unhollowed/UnityEngine.AssetBundleModule.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -104,7 +104,6 @@ namespace UnityExplorer.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region UI Listeners and options
|
||||
|
||||
// TODO save
|
||||
@ -202,7 +201,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];
|
||||
@ -659,7 +658,7 @@ var x = 5;
|
||||
// You can soft-overwrite a class by compiling it again with the same name. The old class will still technically exist in memory.
|
||||
|
||||
// Compiled classes can be accessed from both inside and outside this console.
|
||||
// Note: in IL2CPP, injecting these classes with ClassInjector may crash the game!
|
||||
// Note: in IL2CPP, you must declare a Namespace to inject these classes with ClassInjector or it will crash the game.
|
||||
|
||||
public class HelloWorld
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ namespace UnityExplorer.CacheObject.IValues
|
||||
return;
|
||||
}
|
||||
|
||||
var path = IOUtility.EnsureValidDirectory(SaveFilePath.Text);
|
||||
var path = IOUtility.EnsureValidFilePath(SaveFilePath.Text);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
@ -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
|
||||
{
|
||||
@ -114,9 +113,6 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public static void SetEventSystem()
|
||||
{
|
||||
if (InputManager.CurrentType == InputType.InputSystem)
|
||||
return;
|
||||
|
||||
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
|
||||
{
|
||||
lastEventSystem = EventSystem.current;
|
||||
@ -133,14 +129,12 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public static void ReleaseEventSystem()
|
||||
{
|
||||
if (InputManager.CurrentType == InputType.InputSystem)
|
||||
return;
|
||||
|
||||
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
|
||||
{
|
||||
lastEventSystem.enabled = true;
|
||||
|
||||
settingEventSystem = true;
|
||||
UIManager.EventSys.enabled = false;
|
||||
EventSystem.current = lastEventSystem;
|
||||
lastInputModule?.ActivateModule();
|
||||
settingEventSystem = false;
|
||||
@ -149,18 +143,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)
|
||||
|
@ -14,7 +14,7 @@ namespace UnityExplorer.Core.Input
|
||||
bool GetMouseButtonDown(int btn);
|
||||
bool GetMouseButton(int btn);
|
||||
|
||||
BaseInputModule UIModule { get; }
|
||||
BaseInputModule UIInputModule { get; }
|
||||
|
||||
void AddUIInputModule();
|
||||
void ActivateModule();
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
@ -16,39 +17,43 @@ namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public static InputType CurrentType { get; private set; }
|
||||
|
||||
private static IHandleInput m_inputModule;
|
||||
private static IHandleInput m_inputHandler;
|
||||
|
||||
public static Vector3 MousePosition => m_inputModule.MousePosition;
|
||||
public static Vector3 MousePosition => m_inputHandler.MousePosition;
|
||||
|
||||
public static bool GetKeyDown(KeyCode key)
|
||||
{
|
||||
if (key == KeyCode.None)
|
||||
return false;
|
||||
return m_inputModule.GetKeyDown(key);
|
||||
return m_inputHandler.GetKeyDown(key);
|
||||
}
|
||||
|
||||
public static bool GetKey(KeyCode key)
|
||||
{
|
||||
if (key == KeyCode.None)
|
||||
return false;
|
||||
return m_inputModule.GetKey(key);
|
||||
return m_inputHandler.GetKey(key);
|
||||
}
|
||||
|
||||
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
|
||||
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
|
||||
public static bool GetMouseButtonDown(int btn) => m_inputHandler.GetMouseButtonDown(btn);
|
||||
public static bool GetMouseButton(int btn) => m_inputHandler.GetMouseButton(btn);
|
||||
|
||||
public static BaseInputModule UIInput => m_inputModule.UIModule;
|
||||
public static BaseInputModule UIInput => m_inputHandler.UIInputModule;
|
||||
|
||||
public static Vector2 MouseScrollDelta => m_inputModule.MouseScrollDelta;
|
||||
|
||||
public static void ActivateUIModule() => m_inputModule.ActivateModule();
|
||||
public static Vector2 MouseScrollDelta => m_inputHandler.MouseScrollDelta;
|
||||
|
||||
public static void AddUIModule()
|
||||
{
|
||||
m_inputModule.AddUIInputModule();
|
||||
m_inputHandler.AddUIInputModule();
|
||||
ActivateUIModule();
|
||||
}
|
||||
|
||||
public static void ActivateUIModule()
|
||||
{
|
||||
UIManager.EventSys.m_CurrentInputModule = UIInput;
|
||||
m_inputHandler.ActivateModule();
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
InitHandler();
|
||||
@ -65,7 +70,7 @@ namespace UnityExplorer.Core.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
m_inputModule = new LegacyInput();
|
||||
m_inputHandler = new LegacyInput();
|
||||
CurrentType = InputType.Legacy;
|
||||
|
||||
// make sure its working
|
||||
@ -84,7 +89,7 @@ namespace UnityExplorer.Core.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
m_inputModule = new InputSystem();
|
||||
m_inputHandler = new InputSystem();
|
||||
CurrentType = InputType.InputSystem;
|
||||
ExplorerCore.Log("Initialized new InputSystem support.");
|
||||
return;
|
||||
@ -96,7 +101,7 @@ namespace UnityExplorer.Core.Input
|
||||
}
|
||||
|
||||
ExplorerCore.LogWarning("Could not find any Input Module Type!");
|
||||
m_inputModule = new NoInput();
|
||||
m_inputHandler = new NoInput();
|
||||
CurrentType = InputType.None;
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +201,7 @@ namespace UnityExplorer.Core.Input
|
||||
?? (m_tUIInputModule = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
|
||||
internal Type m_tUIInputModule;
|
||||
|
||||
public BaseInputModule UIModule => m_newInputModule;
|
||||
public BaseInputModule UIInputModule => m_newInputModule;
|
||||
internal BaseInputModule m_newInputModule;
|
||||
|
||||
public void AddUIInputModule()
|
||||
@ -239,6 +239,9 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
private void CreateAction(object map, string actionName, string[] bindings, string propertyName)
|
||||
{
|
||||
var disable = map.GetType().GetMethod("Disable");
|
||||
disable.Invoke(map, ArgumentUtility.EmptyArgs);
|
||||
|
||||
var inputActionType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputAction");
|
||||
var addAction = inputExtensions.GetMethod("AddAction");
|
||||
var action = addAction.Invoke(null, new object[] { map, actionName, default, null, null, null, null, null })
|
||||
@ -262,8 +265,16 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public void ActivateModule()
|
||||
{
|
||||
m_newInputModule.ActivateModule();
|
||||
UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
|
||||
try
|
||||
{
|
||||
m_newInputModule.m_EventSystem = UIManager.EventSys;
|
||||
m_newInputModule.ActivateModule();
|
||||
UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception enabling InputSystem UI Input Module: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,17 +42,25 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
// UI Input module
|
||||
|
||||
public BaseInputModule UIModule => m_inputModule;
|
||||
public BaseInputModule UIInputModule => m_inputModule;
|
||||
internal StandaloneInputModule m_inputModule;
|
||||
|
||||
public void AddUIInputModule()
|
||||
{
|
||||
m_inputModule = UIManager.CanvasRoot.gameObject.AddComponent<StandaloneInputModule>();
|
||||
m_inputModule.m_EventSystem = UIManager.EventSys;
|
||||
}
|
||||
|
||||
public void ActivateModule()
|
||||
{
|
||||
m_inputModule.ActivateModule();
|
||||
try
|
||||
{
|
||||
m_inputModule.ActivateModule();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception enabling StandaloneInputModule: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ namespace UnityExplorer.Core.Input
|
||||
public bool GetMouseButton(int btn) => false;
|
||||
public bool GetMouseButtonDown(int btn) => false;
|
||||
|
||||
public BaseInputModule UIModule => null;
|
||||
public BaseInputModule UIInputModule => null;
|
||||
public void ActivateModule() { }
|
||||
public void AddUIInputModule() { }
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -4,58 +4,69 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerBaseLib.Attributes;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime.Il2Cpp;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class AssetBundle
|
||||
public class AssetBundle : UnityEngine.Object
|
||||
{
|
||||
static AssetBundle()
|
||||
{
|
||||
ClassInjector.RegisterTypeInIl2Cpp<AssetBundle>();
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~ Static ~~~~~~~~~~~~
|
||||
|
||||
internal delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset);
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public static AssetBundle LoadFromFile(string path)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadFromFile>("UnityEngine.AssetBundle::LoadFromFile_Internal");
|
||||
var ptr = iCall.Invoke(IL2CPP.ManagedStringToIl2Cpp(path), 0u, 0UL);
|
||||
var ptr = iCall(IL2CPP.ManagedStringToIl2Cpp(path), 0u, 0UL);
|
||||
return new AssetBundle(ptr);
|
||||
}
|
||||
|
||||
|
||||
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadFromMemory>("UnityEngine.AssetBundle::LoadFromMemory_Internal");
|
||||
var ptr = iCall(((Il2CppStructArray<byte>) binary).Pointer, crc);
|
||||
var ptr = iCall(((Il2CppStructArray<byte>)binary).Pointer, crc);
|
||||
return new AssetBundle(ptr);
|
||||
}
|
||||
|
||||
// static void UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
public delegate IntPtr d_GetAllLoadedAssetBundles_Native();
|
||||
|
||||
internal delegate void d_UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
|
||||
public static void UnloadAllAssetBundles(bool unloadAllObjects)
|
||||
[HideFromIl2Cpp]
|
||||
public static AssetBundle[] GetAllLoadedAssetBundles()
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_UnloadAllAssetBundles>("UnityEngine.AssetBundle::UnloadAllAssetBundles");
|
||||
iCall.Invoke(unloadAllObjects);
|
||||
var iCall = ICallManager.GetICall<d_GetAllLoadedAssetBundles_Native>("UnityEngine.AssetBundle::GetAllLoadedAssetBundles_Native");
|
||||
var ptr = iCall();
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
return (AssetBundle[])new Il2CppReferenceArray<AssetBundle>(ptr);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
|
||||
|
||||
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
|
||||
public readonly IntPtr m_bundlePtr = IntPtr.Zero;
|
||||
|
||||
public AssetBundle(IntPtr ptr) { m_bundlePtr = ptr; }
|
||||
public AssetBundle(IntPtr ptr) : base(ptr) { m_bundlePtr = ptr; }
|
||||
|
||||
// LoadAllAssets()
|
||||
|
||||
internal delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr _this, IntPtr name, IntPtr type);
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public UnityEngine.Object[] LoadAllAssets()
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadAssetWithSubAssets_Internal>("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal");
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(""), Il2CppType.Of<UnityEngine.Object>().Pointer);
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(""), UnhollowerRuntimeLib.Il2CppType.Of<UnityEngine.Object>().Pointer);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return new UnityEngine.Object[0];
|
||||
@ -67,10 +78,11 @@ namespace UnityExplorer
|
||||
|
||||
internal delegate IntPtr d_LoadAsset_Internal(IntPtr _this, IntPtr name, IntPtr type);
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public T LoadAsset<T>(string name) where T : UnityEngine.Object
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadAsset_Internal>("UnityEngine.AssetBundle::LoadAsset_Internal");
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(name), Il2CppType.Of<T>().Pointer);
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(name), UnhollowerRuntimeLib.Il2CppType.Of<T>().Pointer);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
@ -78,13 +90,15 @@ 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)
|
||||
[HideFromIl2Cpp]
|
||||
public void Unload(bool unloadAllLoadedObjects)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_Unload>("UnityEngine.AssetBundle::Unload");
|
||||
iCall.Invoke(this.m_bundlePtr, unloadAssets);
|
||||
iCall.Invoke(this.m_bundlePtr, unloadAllLoadedObjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,15 @@ namespace UnityExplorer
|
||||
private static readonly char[] invalidDirectoryCharacters = Path.GetInvalidPathChars();
|
||||
private static readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars();
|
||||
|
||||
public static string EnsureValidDirectory(string path)
|
||||
public static string EnsureValidFilePath(string fullPathWithFile)
|
||||
{
|
||||
path = string.Concat(path.Split(invalidDirectoryCharacters));
|
||||
// Remove invalid path characters
|
||||
fullPathWithFile = string.Concat(fullPathWithFile.Split(invalidDirectoryCharacters));
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
// Create directory (does nothing if it exists)
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPathWithFile));
|
||||
|
||||
return path;
|
||||
return fullPathWithFile;
|
||||
}
|
||||
|
||||
public static string EnsureValidFilename(string filename)
|
||||
|
@ -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)
|
||||
|
@ -9,7 +9,6 @@ using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
// Project-wide namespace for accessibility
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class UnityHelpers
|
||||
@ -32,25 +31,28 @@ namespace UnityExplorer
|
||||
/// </summary>
|
||||
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
|
||||
{
|
||||
var unityObj = obj as Object;
|
||||
if (obj == null)
|
||||
try
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target instance is null!");
|
||||
if (obj == null)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target instance is null!");
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (obj is Object)
|
||||
{
|
||||
if (!unityObj)
|
||||
return true;
|
||||
}
|
||||
else if (obj is Object unityObj && !unityObj)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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.5";
|
||||
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", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
EditPatchButton.OnClick += OnEditPatchClicked;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
235
src/Hooks/HookInstance.cs
Normal file
235
src/Hooks/HookInstance.cs
Normal file
@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using Mono.CSharp;
|
||||
using UnityExplorer.CSConsole;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookInstance
|
||||
{
|
||||
// Static
|
||||
|
||||
private static readonly StringBuilder evalOutput = new StringBuilder();
|
||||
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
|
||||
|
||||
static HookInstance()
|
||||
{
|
||||
scriptEvaluator.Run("using System;");
|
||||
scriptEvaluator.Run("using System.Reflection;");
|
||||
scriptEvaluator.Run("using System.Collections;");
|
||||
scriptEvaluator.Run("using System.Collections.Generic;");
|
||||
}
|
||||
|
||||
// Instance
|
||||
|
||||
public bool Enabled;
|
||||
public MethodInfo TargetMethod;
|
||||
public string PatchSourceCode;
|
||||
|
||||
private readonly string shortSignature;
|
||||
private PatchProcessor patchProcessor;
|
||||
|
||||
private MethodInfo postfix;
|
||||
private MethodInfo prefix;
|
||||
private MethodInfo finalizer;
|
||||
private MethodInfo transpiler;
|
||||
|
||||
public HookInstance(MethodInfo targetMethod)
|
||||
{
|
||||
this.TargetMethod = targetMethod;
|
||||
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
|
||||
|
||||
GenerateDefaultPatchSourceCode(targetMethod);
|
||||
|
||||
if (CompileAndGenerateProcessor(PatchSourceCode))
|
||||
Patch();
|
||||
}
|
||||
|
||||
// Evaluator.source_file
|
||||
private static readonly FieldInfo fi_sourceFile = ReflectionUtility.GetFieldInfo(typeof(Evaluator), "source_file");
|
||||
// TypeDefinition.Definition
|
||||
private static readonly PropertyInfo pi_Definition = ReflectionUtility.GetPropertyInfo(typeof(TypeDefinition), "Definition");
|
||||
|
||||
public bool CompileAndGenerateProcessor(string patchSource)
|
||||
{
|
||||
Unpatch();
|
||||
|
||||
try
|
||||
{
|
||||
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
|
||||
|
||||
// Dynamically compile the patch method
|
||||
|
||||
var codeBuilder = new StringBuilder();
|
||||
|
||||
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
|
||||
codeBuilder.AppendLine("{");
|
||||
codeBuilder.AppendLine(patchSource);
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
scriptEvaluator.Run(codeBuilder.ToString());
|
||||
|
||||
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
|
||||
throw new FormatException($"Unable to compile the generated patch!");
|
||||
|
||||
// TODO: Publicize MCS to avoid this reflection
|
||||
// Get the most recent Patch type in the source file
|
||||
var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator))
|
||||
.Containers
|
||||
.Last(it => it.MemberName.Name.StartsWith("DynamicPatch_"));
|
||||
// Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type)
|
||||
var patchClass = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)).GetMetaInfo();
|
||||
|
||||
// Create the harmony patches as defined
|
||||
|
||||
postfix = patchClass.GetMethod("Postfix", ReflectionUtility.FLAGS);
|
||||
if (postfix != null)
|
||||
patchProcessor.AddPostfix(new HarmonyMethod(postfix));
|
||||
|
||||
prefix = patchClass.GetMethod("Prefix", ReflectionUtility.FLAGS);
|
||||
if (prefix != null)
|
||||
patchProcessor.AddPrefix(new HarmonyMethod(prefix));
|
||||
|
||||
finalizer = patchClass.GetMethod("Finalizer", ReflectionUtility.FLAGS);
|
||||
if (finalizer != null)
|
||||
patchProcessor.AddFinalizer(new HarmonyMethod(finalizer));
|
||||
|
||||
transpiler = patchClass.GetMethod("Transpiler", ReflectionUtility.FLAGS);
|
||||
if (transpiler != null)
|
||||
patchProcessor.AddTranspiler(new HarmonyMethod(transpiler));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
// Arguments
|
||||
|
||||
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
|
||||
|
||||
var parameters = targetMethod.GetParameters();
|
||||
|
||||
int paramIdx = 0;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
codeBuilder.Append(")\n");
|
||||
|
||||
// Patch body
|
||||
|
||||
codeBuilder.AppendLine("{");
|
||||
|
||||
codeBuilder.AppendLine(" try {");
|
||||
|
||||
// Log message
|
||||
|
||||
var logMessage = new StringBuilder();
|
||||
logMessage.Append($"Patch called: {shortSignature}\\n");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
logMessage.Append("__instance: {__instance.ToString()}\\n");
|
||||
|
||||
paramIdx = 0;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
|
||||
Type pType = param.ParameterType;
|
||||
if (pType.IsByRef) pType = pType.GetElementType();
|
||||
if (pType.IsValueType)
|
||||
logMessage.Append($"{{__{paramIdx}.ToString()}}");
|
||||
else
|
||||
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
|
||||
logMessage.Append("\\n");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
{
|
||||
logMessage.Append("Return value: ");
|
||||
if (targetMethod.ReturnType.IsValueType)
|
||||
logMessage.Append("{__result.ToString()}");
|
||||
else
|
||||
logMessage.Append("{__result?.ToString() ?? \"null\"}");
|
||||
logMessage.Append("\\n");
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
|
||||
codeBuilder.AppendLine(" }");
|
||||
codeBuilder.AppendLine(" catch (System.Exception ex) {");
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
|
||||
codeBuilder.AppendLine(" }");
|
||||
|
||||
// End patch body
|
||||
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
//ExplorerCore.Log(codeBuilder.ToString());
|
||||
|
||||
return PatchSourceCode = codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public void TogglePatch()
|
||||
{
|
||||
if (!Enabled)
|
||||
Patch();
|
||||
else
|
||||
Unpatch();
|
||||
}
|
||||
|
||||
public void Patch()
|
||||
{
|
||||
try
|
||||
{
|
||||
patchProcessor.Patch();
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Unpatch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (prefix != null)
|
||||
patchProcessor.Unpatch(prefix);
|
||||
if (postfix != null)
|
||||
patchProcessor.Unpatch(postfix);
|
||||
if (finalizer != null)
|
||||
patchProcessor.Unpatch(finalizer);
|
||||
if (transpiler != null)
|
||||
patchProcessor.Unpatch(transpiler);
|
||||
|
||||
Enabled = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
271
src/Hooks/HookManager.cs
Normal file
271
src/Hooks/HookManager.cs
Normal file
@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
|
||||
{
|
||||
private static HookManager s_instance;
|
||||
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
|
||||
|
||||
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
|
||||
|
||||
// This class acts as the data source for both current hooks and eligable methods when adding hooks.
|
||||
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
|
||||
// correct pool cells.
|
||||
private bool isAddingMethods;
|
||||
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
|
||||
|
||||
// current hooks
|
||||
private readonly HashSet<string> hookedSignatures = new HashSet<string>();
|
||||
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
|
||||
|
||||
// adding hooks
|
||||
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
|
||||
private readonly List<MethodInfo> filteredEligableMethods = new List<MethodInfo>();
|
||||
|
||||
// hook editor
|
||||
private readonly LexerBuilder Lexer = new LexerBuilder();
|
||||
private HookInstance currentEditedHook;
|
||||
|
||||
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void EnableOrDisableHookClicked(int index)
|
||||
{
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
hook.TogglePatch();
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void DeleteHookClicked(int index)
|
||||
{
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
hook.Unpatch();
|
||||
currentHooks.RemoveAt(index);
|
||||
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void EditPatchClicked(int index)
|
||||
{
|
||||
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
currentEditedHook = hook;
|
||||
Panel.EditorInput.Text = hook.PatchSourceCode;
|
||||
}
|
||||
|
||||
// Set current hook cell
|
||||
|
||||
public void OnCellBorrowed(HookCell cell) { }
|
||||
|
||||
public void SetCell(HookCell cell, int index)
|
||||
{
|
||||
if (index >= this.currentHooks.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
var hook = (HookInstance)this.currentHooks[index];
|
||||
|
||||
cell.MethodNameLabel.text = HighlightMethod(hook.TargetMethod);
|
||||
|
||||
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
|
||||
RuntimeProvider.Instance.SetColorBlockAuto(cell.ToggleActiveButton.Component,
|
||||
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void OnClassSelectedForHooks(string typeFullName)
|
||||
{
|
||||
var type = ReflectionUtility.GetTypeByName(typeFullName);
|
||||
if (type == null)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
|
||||
|
||||
Panel.ResetMethodFilter();
|
||||
filteredEligableMethods.Clear();
|
||||
currentAddEligableMethods.Clear();
|
||||
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if (method.IsGenericMethod /* || method.IsAbstract */ || ReflectionUtility.IsBlacklisted(method))
|
||||
continue;
|
||||
currentAddEligableMethods.Add(method);
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
|
||||
isAddingMethods = true;
|
||||
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
public void DoneAddingHooks()
|
||||
{
|
||||
isAddingMethods = false;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHookClicked(int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
return;
|
||||
|
||||
AddHook(filteredEligableMethods[index]);
|
||||
Panel.AddHooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHook(MethodInfo method)
|
||||
{
|
||||
var sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
return;
|
||||
|
||||
var hook = new HookInstance(method);
|
||||
if (hook.Enabled)
|
||||
{
|
||||
hookedSignatures.Add(sig);
|
||||
currentHooks.Add(sig, hook);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAddHookFilterInputChanged(string input)
|
||||
{
|
||||
filteredEligableMethods.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
filteredEligableMethods.AddRange(currentAddEligableMethods);
|
||||
else
|
||||
{
|
||||
foreach (var method in currentAddEligableMethods)
|
||||
{
|
||||
if (method.Name.ContainsIgnoreCase(input))
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
}
|
||||
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
// Set eligable method cell
|
||||
|
||||
public void OnCellBorrowed(AddHookCell cell) { }
|
||||
|
||||
public void SetCell(AddHookCell cell, int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
var method = this.filteredEligableMethods[index];
|
||||
|
||||
cell.MethodNameLabel.text = HighlightMethod(method);
|
||||
|
||||
var sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(false);
|
||||
cell.HookedLabel.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(true);
|
||||
cell.HookedLabel.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
|
||||
|
||||
public void OnEditorInputChanged(string value)
|
||||
{
|
||||
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
|
||||
Panel.EditorInput.Component.caretPosition, out _);
|
||||
}
|
||||
|
||||
public void EditorInputCancel()
|
||||
{
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
|
||||
public void EditorInputSave()
|
||||
{
|
||||
var input = Panel.EditorInput.Text;
|
||||
bool wasEnabled = currentEditedHook.Enabled;
|
||||
if (currentEditedHook.CompileAndGenerateProcessor(input))
|
||||
{
|
||||
if (wasEnabled)
|
||||
currentEditedHook.Patch();
|
||||
currentEditedHook.PatchSourceCode = input;
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~ Method syntax highlighting
|
||||
|
||||
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,24 +8,15 @@
|
||||
<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\" />
|
||||
<ReferenceFolders Include="..\lib\MelonLoader\" />
|
||||
<ReferenceFolders Include="..\lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\bin\Release\net4.7.2\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ILRepack
|
||||
|
@ -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);
|
||||
@ -623,7 +644,7 @@ namespace UnityExplorer.Inspectors
|
||||
var fitter = imageObj.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
textureImage = imageObj.AddComponent<Image>();
|
||||
textureImageLayout = UIFactory.SetLayoutElement(imageObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
textureImageLayout = UIFactory.SetLayoutElement(imageObj, flexibleWidth: 1, flexibleHeight: 1);
|
||||
|
||||
textureViewer.SetActive(false);
|
||||
}
|
||||
@ -643,6 +664,7 @@ namespace UnityExplorer.Inspectors
|
||||
textureImage.sprite = sprite;
|
||||
|
||||
textureImageLayout.preferredHeight = sprite.rect.height;
|
||||
// not really working, its always stretched horizontally for some reason.
|
||||
textureImageLayout.preferredWidth = sprite.rect.width;
|
||||
}
|
||||
|
||||
@ -667,7 +689,7 @@ namespace UnityExplorer.Inspectors
|
||||
return;
|
||||
}
|
||||
|
||||
path = IOUtility.EnsureValidDirectory(path);
|
||||
path = IOUtility.EnsureValidFilePath(path);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
@ -678,7 +700,6 @@ namespace UnityExplorer.Inspectors
|
||||
tex = TextureUtilProvider.ForceReadTexture(tex);
|
||||
|
||||
byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex);
|
||||
|
||||
File.WriteAllBytes(path, data);
|
||||
|
||||
if (tex != TextureRef)
|
||||
|
@ -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 "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);
|
||||
|
@ -42,6 +42,11 @@ namespace UnityExplorer.ObjectExplorer
|
||||
private Dropdown sceneDropdown;
|
||||
private readonly Dictionary<Scene, Dropdown.OptionData> sceneToDropdownOption = new Dictionary<Scene, Dropdown.OptionData>();
|
||||
|
||||
// scene loader
|
||||
private Dropdown allSceneDropdown;
|
||||
private ButtonRef loadButton;
|
||||
private ButtonRef loadAdditiveButton;
|
||||
|
||||
private IEnumerable<GameObject> GetRootEntries() => SceneHandler.CurrentRootObjects;
|
||||
|
||||
public void Update()
|
||||
@ -155,7 +160,7 @@ namespace UnityExplorer.ObjectExplorer
|
||||
|
||||
private void TryLoadScene(LoadSceneMode mode, Dropdown allSceneDrop)
|
||||
{
|
||||
var text = allSceneDrop.options[allSceneDrop.value].text;
|
||||
var text = allSceneDrop.captionText.text;
|
||||
|
||||
if (text == DEFAULT_LOAD_TEXT)
|
||||
return;
|
||||
@ -250,6 +255,38 @@ namespace UnityExplorer.ObjectExplorer
|
||||
|
||||
private const string DEFAULT_LOAD_TEXT = "[Select a scene]";
|
||||
|
||||
private void RefreshSceneLoaderOptions(string filter)
|
||||
{
|
||||
allSceneDropdown.options.Clear();
|
||||
allSceneDropdown.options.Add(new Dropdown.OptionData(DEFAULT_LOAD_TEXT));
|
||||
|
||||
foreach (var scene in SceneHandler.AllSceneNames)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filter) || scene.ContainsIgnoreCase(filter))
|
||||
allSceneDropdown.options.Add(new Dropdown.OptionData(Path.GetFileNameWithoutExtension(scene)));
|
||||
}
|
||||
|
||||
allSceneDropdown.RefreshShownValue();
|
||||
|
||||
if (loadButton != null)
|
||||
RefreshSceneLoaderButtons();
|
||||
}
|
||||
|
||||
private void RefreshSceneLoaderButtons()
|
||||
{
|
||||
var text = allSceneDropdown.captionText.text;
|
||||
if (text == DEFAULT_LOAD_TEXT)
|
||||
{
|
||||
loadButton.Component.interactable = false;
|
||||
loadAdditiveButton.Component.interactable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadButton.Component.interactable = true;
|
||||
loadAdditiveButton.Component.interactable = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConstructSceneLoader()
|
||||
{
|
||||
// Scene Loader
|
||||
@ -259,36 +296,41 @@ namespace UnityExplorer.ObjectExplorer
|
||||
{
|
||||
var sceneLoaderObj = UIFactory.CreateVerticalGroup(m_uiRoot, "SceneLoader", true, true, true, true);
|
||||
UIFactory.SetLayoutElement(sceneLoaderObj, minHeight: 25);
|
||||
//sceneLoaderObj.SetActive(false);
|
||||
|
||||
// Title
|
||||
|
||||
var loaderTitle = UIFactory.CreateLabel(sceneLoaderObj, "SceneLoaderLabel", "Scene Loader", TextAnchor.MiddleLeft, Color.white, true, 14);
|
||||
UIFactory.SetLayoutElement(loaderTitle.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
var allSceneDropObj = UIFactory.CreateDropdown(sceneLoaderObj, out Dropdown allSceneDrop, "", 14, null);
|
||||
// Search filter
|
||||
|
||||
var searchFilterObj = UIFactory.CreateInputField(sceneLoaderObj, "SearchFilterInput", "Filter scene names...");
|
||||
UIFactory.SetLayoutElement(searchFilterObj.UIRoot, minHeight: 25, flexibleHeight: 0);
|
||||
searchFilterObj.OnValueChanged += RefreshSceneLoaderOptions;
|
||||
|
||||
// Dropdown
|
||||
|
||||
var allSceneDropObj = UIFactory.CreateDropdown(sceneLoaderObj, out allSceneDropdown, "", 14, null);
|
||||
UIFactory.SetLayoutElement(allSceneDropObj, minHeight: 25, minWidth: 150, flexibleWidth: 0, flexibleHeight: 0);
|
||||
|
||||
allSceneDrop.options.Add(new Dropdown.OptionData(DEFAULT_LOAD_TEXT));
|
||||
RefreshSceneLoaderOptions(string.Empty);
|
||||
|
||||
foreach (var scene in SceneHandler.AllSceneNames)
|
||||
allSceneDrop.options.Add(new Dropdown.OptionData(Path.GetFileNameWithoutExtension(scene)));
|
||||
|
||||
allSceneDrop.value = 1;
|
||||
allSceneDrop.value = 0;
|
||||
// Button row
|
||||
|
||||
var buttonRow = UIFactory.CreateHorizontalGroup(sceneLoaderObj, "LoadButtons", true, true, true, true, 4);
|
||||
|
||||
var loadButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Single)", new Color(0.1f, 0.3f, 0.3f));
|
||||
loadButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Single)", new Color(0.1f, 0.3f, 0.3f));
|
||||
UIFactory.SetLayoutElement(loadButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
loadButton.OnClick += () =>
|
||||
{
|
||||
TryLoadScene(LoadSceneMode.Single, allSceneDrop);
|
||||
TryLoadScene(LoadSceneMode.Single, allSceneDropdown);
|
||||
};
|
||||
|
||||
var loadAdditiveButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Additive)", new Color(0.1f, 0.3f, 0.3f));
|
||||
loadAdditiveButton = UIFactory.CreateButton(buttonRow, "LoadSceneButton", "Load (Additive)", new Color(0.1f, 0.3f, 0.3f));
|
||||
UIFactory.SetLayoutElement(loadAdditiveButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
loadAdditiveButton.OnClick += () =>
|
||||
{
|
||||
TryLoadScene(LoadSceneMode.Additive, allSceneDrop);
|
||||
TryLoadScene(LoadSceneMode.Additive, allSceneDropdown);
|
||||
};
|
||||
|
||||
var disabledColor = new Color(0.24f, 0.24f, 0.24f);
|
||||
@ -298,19 +340,9 @@ namespace UnityExplorer.ObjectExplorer
|
||||
loadButton.Component.interactable = false;
|
||||
loadAdditiveButton.Component.interactable = false;
|
||||
|
||||
allSceneDrop.onValueChanged.AddListener((int val) =>
|
||||
allSceneDropdown.onValueChanged.AddListener((int val) =>
|
||||
{
|
||||
var text = allSceneDrop.options[val].text;
|
||||
if (text == DEFAULT_LOAD_TEXT)
|
||||
{
|
||||
loadButton.Component.interactable = false;
|
||||
loadAdditiveButton.Component.interactable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadButton.Component.interactable = true;
|
||||
loadAdditiveButton.Component.interactable = true;
|
||||
}
|
||||
RefreshSceneLoaderButtons();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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,14 @@ 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);
|
||||
}
|
||||
|
||||
WasAbleToGetScenesInBuild = true;
|
||||
}
|
||||
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.
203
src/UI/Panels/HookManagerPanel.cs
Normal file
203
src/UI/Panels/HookManagerPanel.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Hooks;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class HookManagerPanel : UIPanel
|
||||
{
|
||||
public enum Pages
|
||||
{
|
||||
CurrentHooks,
|
||||
ClassMethodSelector,
|
||||
HookSourceEditor
|
||||
}
|
||||
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
|
||||
|
||||
public override string Name => "Hooks";
|
||||
public override int MinWidth => 500;
|
||||
public override int MinHeight => 600;
|
||||
public override bool ShowByDefault => false;
|
||||
|
||||
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
|
||||
|
||||
private GameObject currentHooksPanel;
|
||||
public ScrollPool<HookCell> HooksScrollPool;
|
||||
private InputFieldRef classSelectorInputField;
|
||||
|
||||
private GameObject addHooksPanel;
|
||||
public ScrollPool<AddHookCell> AddHooksScrollPool;
|
||||
private Text addHooksLabel;
|
||||
private InputFieldRef AddHooksMethodFilterInput;
|
||||
|
||||
private GameObject editorPanel;
|
||||
public InputFieldScroller EditorInputScroller { get; private set; }
|
||||
public InputFieldRef EditorInput => EditorInputScroller.InputField;
|
||||
public Text EditorInputText { get; private set; }
|
||||
public Text EditorHighlightText { get; private set; }
|
||||
|
||||
public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value;
|
||||
|
||||
public override void DoSaveToConfigElement() => ConfigManager.HookManagerData.Value = this.ToSaveData();
|
||||
|
||||
private void OnClassInputAddClicked()
|
||||
{
|
||||
HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
|
||||
}
|
||||
|
||||
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
|
||||
|
||||
public void SetPage(Pages page)
|
||||
{
|
||||
switch (page)
|
||||
{
|
||||
case Pages.CurrentHooks:
|
||||
currentHooksPanel.SetActive(true);
|
||||
addHooksPanel.SetActive(false);
|
||||
editorPanel.SetActive(false);
|
||||
break;
|
||||
case Pages.ClassMethodSelector:
|
||||
currentHooksPanel.SetActive(false);
|
||||
addHooksPanel.SetActive(true);
|
||||
editorPanel.SetActive(false);
|
||||
break;
|
||||
case Pages.HookSourceEditor:
|
||||
currentHooksPanel.SetActive(false);
|
||||
addHooksPanel.SetActive(false);
|
||||
editorPanel.SetActive(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
|
||||
|
||||
public override void ConstructPanelContent()
|
||||
{
|
||||
// ~~~~~~~~~ Active hooks scroll pool
|
||||
|
||||
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content);
|
||||
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
|
||||
|
||||
var addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
|
||||
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
|
||||
|
||||
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
|
||||
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
|
||||
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
|
||||
|
||||
var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
|
||||
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
|
||||
addButton.OnClick += OnClassInputAddClicked;
|
||||
|
||||
var hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
|
||||
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
|
||||
out GameObject hooksScroll, out GameObject hooksContent);
|
||||
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
|
||||
HooksScrollPool.Initialize(HookManager.Instance);
|
||||
|
||||
// ~~~~~~~~~ Add hooks panel
|
||||
|
||||
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content);
|
||||
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
|
||||
|
||||
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
|
||||
|
||||
var buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
doneButton.OnClick += HookManager.Instance.DoneAddingHooks;
|
||||
|
||||
AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
|
||||
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
|
||||
AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
|
||||
|
||||
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
|
||||
out GameObject addScrollRoot, out GameObject addContent);
|
||||
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
|
||||
AddHooksScrollPool.Initialize(HookManager.Instance);
|
||||
|
||||
addHooksPanel.gameObject.SetActive(false);
|
||||
|
||||
// ~~~~~~~~~ Hook source editor panel
|
||||
|
||||
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.content);
|
||||
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
|
||||
|
||||
var editorLabel = UIFactory.CreateLabel(editorPanel,
|
||||
"EditorLabel",
|
||||
"Edit Harmony patch source as desired. Accepted method names are Prefix, Postfix, Finalizer and Transpiler (can define multiple).\n\n" +
|
||||
"Hooks are temporary! Please copy the source into your IDE to avoid losing work if you wish to keep it!",
|
||||
TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
var editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
|
||||
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
var editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorSaveButton.OnClick += HookManager.Instance.EditorInputSave;
|
||||
|
||||
var editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
|
||||
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
editorDoneButton.OnClick += HookManager.Instance.EditorInputCancel;
|
||||
|
||||
int fontSize = 16;
|
||||
var inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out var inputScroller, fontSize);
|
||||
EditorInputScroller = inputScroller;
|
||||
EditorInput.OnValueChanged += HookManager.Instance.OnEditorInputChanged;
|
||||
|
||||
EditorInputText = EditorInput.Component.textComponent;
|
||||
EditorInputText.supportRichText = false;
|
||||
EditorInputText.color = Color.clear;
|
||||
EditorInput.Component.customCaretColor = true;
|
||||
EditorInput.Component.caretColor = Color.white;
|
||||
EditorInput.PlaceholderText.fontSize = fontSize;
|
||||
|
||||
// Lexer highlight text overlay
|
||||
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
|
||||
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
|
||||
highlightTextRect.pivot = new Vector2(0, 1);
|
||||
highlightTextRect.anchorMin = Vector2.zero;
|
||||
highlightTextRect.anchorMax = Vector2.one;
|
||||
highlightTextRect.offsetMin = Vector2.zero;
|
||||
highlightTextRect.offsetMax = Vector2.zero;
|
||||
|
||||
EditorHighlightText = highlightTextObj.AddComponent<Text>();
|
||||
EditorHighlightText.color = Color.white;
|
||||
EditorHighlightText.supportRichText = true;
|
||||
EditorHighlightText.fontSize = fontSize;
|
||||
|
||||
// Set fonts
|
||||
EditorInputText.font = UIManager.ConsoleFont;
|
||||
EditorInput.PlaceholderText.font = UIManager.ConsoleFont;
|
||||
EditorHighlightText.font = UIManager.ConsoleFont;
|
||||
|
||||
editorPanel.SetActive(false);
|
||||
}
|
||||
|
||||
protected internal override void DoSetDefaultPosAndAnchors()
|
||||
{
|
||||
this.Rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
this.Rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
|
||||
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
|
||||
}
|
||||
}
|
||||
}
|
@ -61,8 +61,10 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
private void SetupIO()
|
||||
{
|
||||
var fileName = $"UnityExplorer {DateTime.Now:u}.txt";
|
||||
fileName = IOUtility.EnsureValidFilename(fileName);
|
||||
var path = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Logs");
|
||||
path = IOUtility.EnsureValidDirectory(path);
|
||||
CurrentStreamPath = IOUtility.EnsureValidFilePath(Path.Combine(path, fileName));
|
||||
|
||||
// clean old log(s)
|
||||
var files = Directory.GetFiles(path);
|
||||
@ -75,11 +77,6 @@ namespace UnityExplorer.UI.Panels
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
|
||||
var fileName = $"UnityExplorer {DateTime.Now:u}.txt";
|
||||
fileName = IOUtility.EnsureValidFilename(fileName);
|
||||
|
||||
CurrentStreamPath = Path.Combine(path, fileName);
|
||||
|
||||
File.WriteAllLines(CurrentStreamPath, Logs.Select(it => it.message).ToArray());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
@ -27,7 +30,9 @@ namespace UnityExplorer.UI
|
||||
Options,
|
||||
ConsoleLog,
|
||||
AutoCompleter,
|
||||
MouseInspector
|
||||
MouseInspector,
|
||||
UIInspectorResults,
|
||||
HookManager,
|
||||
}
|
||||
|
||||
public enum VerticalAnchor
|
||||
@ -36,22 +41,20 @@ namespace UnityExplorer.UI
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static bool Initializing { get; private set; } = true;
|
||||
|
||||
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
|
||||
public static bool Initializing { get; internal set; } = true;
|
||||
|
||||
public static VerticalAnchor NavbarAnchor = VerticalAnchor.Top;
|
||||
|
||||
// References
|
||||
public static GameObject CanvasRoot { get; private set; }
|
||||
public static Canvas Canvas { get; private set; }
|
||||
public static EventSystem EventSys { get; private set; }
|
||||
|
||||
internal static GameObject PoolHolder { get; private set; }
|
||||
|
||||
internal static GameObject PanelHolder { get; private set; }
|
||||
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
|
||||
|
||||
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)
|
||||
@ -193,15 +196,9 @@ namespace UnityExplorer.UI
|
||||
|
||||
// Panels
|
||||
|
||||
public static UIPanel GetPanel(Panels panel)
|
||||
{
|
||||
return UIPanels[panel];
|
||||
}
|
||||
public static UIPanel GetPanel(Panels panel) => UIPanels[panel];
|
||||
|
||||
public static T GetPanel<T>(Panels panel) where T : UIPanel
|
||||
{
|
||||
return (T)UIPanels[panel];
|
||||
}
|
||||
public static T GetPanel<T>(Panels panel) where T : UIPanel => (T)UIPanels[panel];
|
||||
|
||||
public static void TogglePanel(Panels panel)
|
||||
{
|
||||
@ -241,14 +238,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;
|
||||
}
|
||||
}
|
||||
@ -322,9 +319,14 @@ namespace UnityExplorer.UI
|
||||
CanvasRoot.layer = 5;
|
||||
CanvasRoot.transform.position = new Vector3(0f, 0f, 1f);
|
||||
|
||||
CanvasRoot.SetActive(false);
|
||||
|
||||
EventSys = CanvasRoot.AddComponent<EventSystem>();
|
||||
InputManager.AddUIModule();
|
||||
|
||||
EventSys.enabled = false;
|
||||
CanvasRoot.SetActive(true);
|
||||
|
||||
Canvas = CanvasRoot.AddComponent<Canvas>();
|
||||
Canvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||
Canvas.referencePixelsPerUnit = 100;
|
||||
@ -412,9 +414,12 @@ namespace UnityExplorer.UI
|
||||
|
||||
// UI AssetBundle
|
||||
|
||||
internal static AssetBundle ExplorerBundle;
|
||||
|
||||
private static void LoadBundle()
|
||||
{
|
||||
AssetBundle bundle;
|
||||
SetupAssetBundlePatches();
|
||||
|
||||
try
|
||||
{
|
||||
// Get the Major and Minor of the Unity version
|
||||
@ -423,40 +428,52 @@ namespace UnityExplorer.UI
|
||||
int minor = int.Parse(split[1]);
|
||||
|
||||
// Use appropriate AssetBundle for Unity version
|
||||
// >= 2017.3
|
||||
if (major > 2017 || (major == 2017 && minor >= 3))
|
||||
bundle = LoadBundle("modern");
|
||||
// 5.6.0 to 2017.3
|
||||
else if (major == 2017 || (major == 5 && minor >= 6))
|
||||
bundle = LoadBundle("legacy.5.6");
|
||||
// >= 2017
|
||||
if (major >= 2017)
|
||||
ExplorerBundle = LoadBundle("modern");
|
||||
// 5.6.0 to <2017
|
||||
else if (major == 5 && minor >= 6)
|
||||
ExplorerBundle = LoadBundle("legacy.5.6");
|
||||
// < 5.6.0
|
||||
else
|
||||
bundle = LoadBundle("legacy");
|
||||
ExplorerBundle = LoadBundle("legacy");
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception parsing Unity version, falling back to old AssetBundle load method...");
|
||||
bundle = LoadBundle("modern") ?? LoadBundle("legacy.5.6") ?? LoadBundle("legacy");
|
||||
ExplorerBundle = LoadBundle("modern") ?? LoadBundle("legacy.5.6") ?? LoadBundle("legacy");
|
||||
}
|
||||
|
||||
AssetBundle LoadBundle(string id)
|
||||
{
|
||||
ExplorerCore.Log($"Loading {id} bundle for Unity {Application.unityVersion}");
|
||||
|
||||
return AssetBundle.LoadFromMemory(ReadFully(typeof(ExplorerCore)
|
||||
var bundle = AssetBundle.LoadFromMemory(ReadFully(typeof(ExplorerCore)
|
||||
.Assembly
|
||||
.GetManifestResourceStream($"UnityExplorer.Resources.{id}.bundle")));
|
||||
if (bundle)
|
||||
ExplorerCore.Log($"Loaded {id} bundle for Unity {Application.unityVersion}");
|
||||
return bundle;
|
||||
}
|
||||
|
||||
if (bundle == null)
|
||||
if (ExplorerBundle == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Could not load the ExplorerUI Bundle!");
|
||||
ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
ExplorerCore.LogWarning("Could not load the UnityExplorer UI Bundle!");
|
||||
DefaultFont = ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
return;
|
||||
}
|
||||
|
||||
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
|
||||
// Bundle loaded
|
||||
|
||||
ConsoleFont = ExplorerBundle.LoadAsset<Font>("CONSOLA");
|
||||
ConsoleFont.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(ConsoleFont);
|
||||
|
||||
DefaultFont = ExplorerBundle.LoadAsset<Font>("arial");
|
||||
DefaultFont.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(DefaultFont);
|
||||
|
||||
BackupShader = ExplorerBundle.LoadAsset<Shader>("DefaultUI");
|
||||
BackupShader.hideFlags = HideFlags.HideAndDontSave;
|
||||
UnityEngine.Object.DontDestroyOnLoad(BackupShader);
|
||||
// Fix for games which don't ship with 'UI/Default' shader.
|
||||
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
|
||||
{
|
||||
@ -465,8 +482,6 @@ namespace UnityExplorer.UI
|
||||
}
|
||||
else
|
||||
BackupShader = Graphic.defaultGraphicMaterial.shader;
|
||||
|
||||
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
|
||||
}
|
||||
|
||||
private static byte[] ReadFully(Stream input)
|
||||
@ -480,5 +495,57 @@ namespace UnityExplorer.UI
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// AssetBundle patch
|
||||
|
||||
private static Type TypeofAssetBundle => ReflectionUtility.GetTypeByName("UnityEngine.AssetBundle");
|
||||
|
||||
private static void SetupAssetBundlePatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TypeofAssetBundle.GetMethod("UnloadAllAssetBundles", AccessTools.all) is MethodInfo unloadAllBundles)
|
||||
{
|
||||
#if CPP
|
||||
// if IL2CPP, ensure method wasn't stripped
|
||||
if (UnhollowerBaseLib.UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(unloadAllBundles) == null)
|
||||
return;
|
||||
#endif
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(unloadAllBundles);
|
||||
var prefix = new HarmonyMethod(typeof(UIManager).GetMethod(nameof(Prefix_UnloadAllAssetBundles), AccessTools.all));
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up AssetBundle.UnloadAllAssetBundles patch: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool Prefix_UnloadAllAssetBundles(bool unloadAllObjects)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = typeof(AssetBundle).GetMethod("GetAllLoadedAssetBundles", AccessTools.all);
|
||||
if (method == null)
|
||||
return true;
|
||||
var bundles = method.Invoke(null, ArgumentUtility.EmptyArgs) as AssetBundle[];
|
||||
foreach (var obj in bundles)
|
||||
{
|
||||
if (obj.m_CachedPtr == ExplorerBundle.m_CachedPtr)
|
||||
continue;
|
||||
|
||||
obj.Unload(unloadAllObjects);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception unloading AssetBundles: {ex}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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