Compare commits

..

40 Commits
4.2.1 ... 4.3.5

Author SHA1 Message Date
6aa9b3aa15 Bump version 2021-10-26 15:12:56 +11:00
587842f46b Add search filter to scene loader dropdown 2021-10-26 15:12:52 +11:00
b9c641f170 Fix scene loader not showing 2021-10-26 15:12:44 +11:00
1e68529430 Don't patch stripped UnloadAllAssetBundles 2021-10-22 15:54:14 +11:00
e84e96a63c Bump version 2021-10-21 20:04:48 +11:00
ec8c88ab9b Actually fix Logs folder 2021-10-19 16:34:46 +11:00
0eae78eeb2 Update LogPanel.cs 2021-10-19 16:32:33 +11:00
a07ead2142 Fix log folder not being created 2021-10-19 04:30:35 +11:00
b93567c7ea Bump version 2021-10-16 18:11:08 +11:00
957d80c7ec Fix IOUtility creating folders for file paths 2021-10-16 18:10:23 +11:00
d530d10798 Exceptions is IsNullOrDestroyed should return true 2021-10-12 20:27:32 +11:00
66daabf770 Add try/catch to IsNullOrDestroyed 2021-10-12 20:20:08 +11:00
9fe998aa22 Cleanup 2021-10-01 17:21:54 +10:00
28b6db80f9 Fix InputSystem EventSystem bug 2021-10-01 17:21:50 +10:00
22effbb399 Update README.md 2021-09-30 21:13:29 +10:00
50f0c31e98 Add patch for asset bundle unloading to prevent UnityExplorer's bundle being unloaded 2021-09-30 21:09:27 +10:00
911522457e Clarify "Classes" help in C# Console 2021-09-30 21:09:04 +10:00
d5d1841109 Include Unhollower in ILRepack references 2021-09-30 21:08:45 +10:00
239534e09c Add GetAllLoadedAssetBundles 2021-09-30 21:08:29 +10:00
bc5d16051f Fix issue with InputSystem 2021-09-30 21:08:08 +10:00
f81822f219 bump lib versions 2021-09-30 21:06:38 +10:00
f159cf5ea7 Attempt fix at bundle unloading 2021-09-23 16:40:47 +10:00
1e6bacb32b manual unity libs no longer required for BepInEx 2021-09-23 16:21:47 +10:00
a80cef4c1d Bump version 2021-09-07 19:11:58 +10:00
450bb77f2e Use Harmony's FullDescription for hook parameter types 2021-09-07 19:11:19 +10:00
0479102db6 Cleanup toggle Enabled logic 2021-09-07 18:06:38 +10:00
93181f02be Update README.md 2021-09-07 17:33:01 +10:00
371054d6df Add hook source editor for custom dynamic hooks 2021-09-07 17:23:20 +10:00
427f23b80a Bump version 2021-09-07 03:40:14 +10:00
a0d5ab8792 Fix ByRef parameters breaking generated patch code 2021-09-07 03:40:09 +10:00
297034e38b Cleanup 2021-09-07 03:19:40 +10:00
57f59d1295 Finalize hook manager update 2021-09-06 23:10:10 +10:00
fbdb84eefa Implement HookManager UI and logic 2021-09-06 23:04:40 +10:00
6989ea1b19 Misc cleanups 2021-09-06 23:03:55 +10:00
fcdfeb2dec Redesign mouse inspector class, add UI results panel 2021-09-06 17:10:01 +10:00
a1d0b6432e Cleanup 2021-09-06 15:51:40 +10:00
0b84405e57 Fix incorrect startup delay logic 2021-09-02 18:29:21 +10:00
c31e0949d3 Update ExplorerMelonMod.cs 2021-09-02 17:30:56 +10:00
5f0495a7ea Update README.md 2021-08-24 18:42:22 +10:00
28de6779d8 Fix for Canvas.renderingDisplaySize crash 2021-08-24 17:47:42 +10:00
60 changed files with 1574 additions and 438 deletions

View File

@ -8,14 +8,13 @@
<p align="center">
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
</p>
<p align="center">
⚡ UnityExplorer is on <a href="https://thunderstore.io/package/sinai-dev/UnityExplorer/">Thunderstore</a>! (and as <a href="https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP/">IL2CPP</a>)
</p>
# Releases [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases)
[![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=version)](../../releases/latest) [![](https://img.shields.io/github/workflow/status/sinai-dev/UnityExplorer/Build%20UnityExplorer)](https://github.com/sinai-dev/UnityExplorer/actions) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../releases/latest)
⚡ Thunderstore releases: [BepInEx Mono](https://thunderstore.io/package/sinai-dev/UnityExplorer) | [BepInEx IL2CPP](https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP) | [MelonLoader IL2CPP](https://boneworks.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP_ML)
## BepInEx
| Release | IL2CPP | Mono |
@ -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>
@ -49,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">
@ -85,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.
@ -100,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.
@ -113,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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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
{

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -113,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;
@ -132,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;

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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}");
}
}
}
}

View File

@ -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() { }
}

View File

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

View File

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

View File

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

View File

@ -4,48 +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);
}
public delegate IntPtr d_GetAllLoadedAssetBundles_Native();
[HideFromIl2Cpp]
public static AssetBundle[] GetAllLoadedAssetBundles()
{
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];
@ -57,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;
@ -72,10 +94,11 @@ namespace UnityExplorer
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);
}
}
}

View File

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

View File

@ -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)

View File

@ -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>

View File

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

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

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

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

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

235
src/Hooks/HookInstance.cs Normal file
View 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
View 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;
}
}
}

View File

@ -16,6 +16,7 @@
<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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -644,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);
}
@ -664,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;
}
@ -688,7 +689,7 @@ namespace UnityExplorer.Inspectors
return;
}
path = IOUtility.EnsureValidDirectory(path);
path = IOUtility.EnsureValidFilePath(path);
if (File.Exists(path))
File.Delete(path);
@ -699,7 +700,6 @@ namespace UnityExplorer.Inspectors
tex = TextureUtilProvider.ForceReadTexture(tex);
byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex);
File.WriteAllBytes(path, data);
if (tex != TextureRef)

View File

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

View File

@ -34,7 +34,7 @@ namespace UnityExplorer.Loader.ML
}
}
// This wrapper exists to handle the arbitrary "LemonAction" delegates which ML now uses in 0.4.4+.
// 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>

View File

@ -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();
});
}
}

View File

@ -97,6 +97,8 @@ namespace UnityExplorer.ObjectExplorer
var scenePath = (string)method.Invoke(null, new object[] { i });
AllSceneNames.Add(scenePath);
}
WasAbleToGetScenesInBuild = true;
}
catch (Exception ex)
{

View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Hooks;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
namespace UnityExplorer.UI.Panels
{
public class HookManagerPanel : UIPanel
{
public enum Pages
{
CurrentHooks,
ClassMethodSelector,
HookSourceEditor
}
public override UIManager.Panels PanelType => UIManager.Panels.HookManager;
public override string Name => "Hooks";
public override int MinWidth => 500;
public override int MinHeight => 600;
public override bool ShowByDefault => false;
public Pages CurrentPage { get; private set; } = Pages.CurrentHooks;
private GameObject currentHooksPanel;
public ScrollPool<HookCell> HooksScrollPool;
private InputFieldRef classSelectorInputField;
private GameObject addHooksPanel;
public ScrollPool<AddHookCell> AddHooksScrollPool;
private Text addHooksLabel;
private InputFieldRef AddHooksMethodFilterInput;
private GameObject editorPanel;
public InputFieldScroller EditorInputScroller { get; private set; }
public InputFieldRef EditorInput => EditorInputScroller.InputField;
public Text EditorInputText { get; private set; }
public Text EditorHighlightText { get; private set; }
public override string GetSaveDataFromConfigManager() => ConfigManager.HookManagerData.Value;
public override void DoSaveToConfigElement() => ConfigManager.HookManagerData.Value = this.ToSaveData();
private void OnClassInputAddClicked()
{
HookManager.Instance.OnClassSelectedForHooks(this.classSelectorInputField.Text);
}
public void SetAddHooksLabelType(string typeText) => addHooksLabel.text = $"Adding hooks to: {typeText}";
public void SetPage(Pages page)
{
switch (page)
{
case Pages.CurrentHooks:
currentHooksPanel.SetActive(true);
addHooksPanel.SetActive(false);
editorPanel.SetActive(false);
break;
case Pages.ClassMethodSelector:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(true);
editorPanel.SetActive(false);
break;
case Pages.HookSourceEditor:
currentHooksPanel.SetActive(false);
addHooksPanel.SetActive(false);
editorPanel.SetActive(true);
break;
}
}
public void ResetMethodFilter() => AddHooksMethodFilterInput.Text = string.Empty;
public override void ConstructPanelContent()
{
// ~~~~~~~~~ Active hooks scroll pool
currentHooksPanel = UIFactory.CreateUIObject("CurrentHooksPanel", this.content);
UIFactory.SetLayoutElement(currentHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(currentHooksPanel, true, true, true, true);
var addRow = UIFactory.CreateHorizontalGroup(currentHooksPanel, "AddRow", false, true, true, true, 4,
new Vector4(2, 2, 2, 2), new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(addRow, minHeight: 30, flexibleWidth: 9999);
classSelectorInputField = UIFactory.CreateInputField(addRow, "ClassInput", "Enter a class to add hooks to...");
UIFactory.SetLayoutElement(classSelectorInputField.Component.gameObject, flexibleWidth: 9999);
new TypeCompleter(typeof(object), classSelectorInputField, true, false);
var addButton = UIFactory.CreateButton(addRow, "AddButton", "Add Hooks");
UIFactory.SetLayoutElement(addButton.Component.gameObject, minWidth: 100, minHeight: 25);
addButton.OnClick += OnClassInputAddClicked;
var hooksLabel = UIFactory.CreateLabel(currentHooksPanel, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999);
HooksScrollPool = UIFactory.CreateScrollPool<HookCell>(currentHooksPanel, "HooksScrollPool",
out GameObject hooksScroll, out GameObject hooksContent);
UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999);
HooksScrollPool.Initialize(HookManager.Instance);
// ~~~~~~~~~ Add hooks panel
addHooksPanel = UIFactory.CreateUIObject("AddHooksPanel", this.content);
UIFactory.SetLayoutElement(addHooksPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(addHooksPanel, true, true, true, true);
addHooksLabel = UIFactory.CreateLabel(addHooksPanel, "AddLabel", "NOT SET", TextAnchor.MiddleCenter);
UIFactory.SetLayoutElement(addHooksLabel.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
var buttonRow = UIFactory.CreateHorizontalGroup(addHooksPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(buttonRow, minHeight: 25, flexibleWidth: 9999);
var doneButton = UIFactory.CreateButton(buttonRow, "DoneButton", "Done", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(doneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
doneButton.OnClick += HookManager.Instance.DoneAddingHooks;
AddHooksMethodFilterInput = UIFactory.CreateInputField(addHooksPanel, "FilterInputField", "Filter method names...");
UIFactory.SetLayoutElement(AddHooksMethodFilterInput.Component.gameObject, minHeight: 30, flexibleWidth: 9999);
AddHooksMethodFilterInput.OnValueChanged += HookManager.Instance.OnAddHookFilterInputChanged;
AddHooksScrollPool = UIFactory.CreateScrollPool<AddHookCell>(addHooksPanel, "MethodAddScrollPool",
out GameObject addScrollRoot, out GameObject addContent);
UIFactory.SetLayoutElement(addScrollRoot, flexibleHeight: 9999);
AddHooksScrollPool.Initialize(HookManager.Instance);
addHooksPanel.gameObject.SetActive(false);
// ~~~~~~~~~ Hook source editor panel
editorPanel = UIFactory.CreateUIObject("HookSourceEditor", this.content);
UIFactory.SetLayoutElement(editorPanel, flexibleHeight: 9999, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(editorPanel, true, true, true, true);
var editorLabel = UIFactory.CreateLabel(editorPanel,
"EditorLabel",
"Edit Harmony patch source as desired. Accepted method names are Prefix, Postfix, Finalizer and Transpiler (can define multiple).\n\n" +
"Hooks are temporary! Please copy the source into your IDE to avoid losing work if you wish to keep it!",
TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(editorLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
var editorButtonRow = UIFactory.CreateHorizontalGroup(editorPanel, "ButtonRow", false, false, true, true, 5);
UIFactory.SetLayoutElement(editorButtonRow, minHeight: 25, flexibleWidth: 9999);
var editorSaveButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Save and Return", new Color(0.2f, 0.3f, 0.2f));
UIFactory.SetLayoutElement(editorSaveButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorSaveButton.OnClick += HookManager.Instance.EditorInputSave;
var editorDoneButton = UIFactory.CreateButton(editorButtonRow, "DoneButton", "Cancel and Return", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(editorDoneButton.Component.gameObject, minHeight: 25, flexibleWidth: 9999);
editorDoneButton.OnClick += HookManager.Instance.EditorInputCancel;
int fontSize = 16;
var inputObj = UIFactory.CreateScrollInputField(editorPanel, "EditorInput", "", out var inputScroller, fontSize);
EditorInputScroller = inputScroller;
EditorInput.OnValueChanged += HookManager.Instance.OnEditorInputChanged;
EditorInputText = EditorInput.Component.textComponent;
EditorInputText.supportRichText = false;
EditorInputText.color = Color.clear;
EditorInput.Component.customCaretColor = true;
EditorInput.Component.caretColor = Color.white;
EditorInput.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", EditorInputText.gameObject);
var highlightTextRect = highlightTextObj.GetComponent<RectTransform>();
highlightTextRect.pivot = new Vector2(0, 1);
highlightTextRect.anchorMin = Vector2.zero;
highlightTextRect.anchorMax = Vector2.one;
highlightTextRect.offsetMin = Vector2.zero;
highlightTextRect.offsetMax = Vector2.zero;
EditorHighlightText = highlightTextObj.AddComponent<Text>();
EditorHighlightText.color = Color.white;
EditorHighlightText.supportRichText = true;
EditorHighlightText.fontSize = fontSize;
// Set fonts
EditorInputText.font = UIManager.ConsoleFont;
EditorInput.PlaceholderText.font = UIManager.ConsoleFont;
EditorHighlightText.font = UIManager.ConsoleFont;
editorPanel.SetActive(false);
}
protected internal override void DoSetDefaultPosAndAnchors()
{
this.Rect.anchorMin = new Vector2(0.5f, 0.5f);
this.Rect.anchorMax = new Vector2(0.5f, 0.5f);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
}
}

View File

@ -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());
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -38,18 +43,15 @@ namespace UnityExplorer.UI
public static bool Initializing { get; internal set; } = true;
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
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; }
@ -106,8 +108,10 @@ namespace UnityExplorer.UI
UIPanels.Add(Panels.ObjectExplorer, new ObjectExplorerPanel());
UIPanels.Add(Panels.Inspector, new InspectorPanel());
UIPanels.Add(Panels.CSConsole, new CSConsolePanel());
UIPanels.Add(Panels.HookManager, new HookManagerPanel());
UIPanels.Add(Panels.ConsoleLog, new LogPanel());
UIPanels.Add(Panels.Options, new OptionsPanel());
UIPanels.Add(Panels.UIInspectorResults, new UiInspectorResultsPanel());
UIPanels.Add(Panels.MouseInspector, new InspectUnderMouse());
foreach (var panel in UIPanels.Values)
@ -192,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)
{
@ -240,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;
}
}
@ -321,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;
@ -411,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
@ -424,42 +430,50 @@ namespace UnityExplorer.UI
// Use appropriate AssetBundle for Unity version
// >= 2017
if (major >= 2017)
bundle = LoadBundle("modern");
ExplorerBundle = LoadBundle("modern");
// 5.6.0 to <2017
else if (major == 5 && minor >= 6)
bundle = LoadBundle("legacy.5.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");
DefaultFont = ConsoleFont;
ExplorerCore.LogWarning("Could not load the UnityExplorer UI Bundle!");
DefaultFont = ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
return;
}
// Bundle loaded
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
DefaultFont = bundle.LoadAsset<Font>("arial");
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
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")
{
@ -468,7 +482,6 @@ namespace UnityExplorer.UI
}
else
BackupShader = Graphic.defaultGraphicMaterial.shader;
}
private static byte[] ReadFully(Stream input)
@ -482,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;
}
}
}

View File

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

View File

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