Compare commits

..

43 Commits
4.3.1 ... 4.4.2

Author SHA1 Message Date
0dd19345ed Fix FPS drop with MelonLoader 2021-12-28 23:24:44 +11:00
0050cff185 bump version 2021-12-24 15:17:53 +11:00
5f1d635eb3 Only filter transform tree on end edit 2021-12-24 15:17:48 +11:00
e0f7fad00e Cleanup 2021-12-24 15:17:35 +11:00
f92c215a88 update libs 2021-12-24 15:17:28 +11:00
d58a5df82c Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-12-24 15:17:23 +11:00
12dd85ecfd Update README.md 2021-12-03 01:06:57 +11:00
03fb165967 Update README (moved to UniverseLib) -noci 2021-12-02 21:11:25 +11:00
834f4b7c4c Update README.md 2021-12-02 20:27:51 +11:00
3415eb8e77 Bump version 2021-12-02 20:25:09 +11:00
6c1a5b8ae2 Update README.md 2021-12-02 18:36:33 +11:00
3334549902 use UniverseLib 2021-12-02 18:35:46 +11:00
077a2b434a Null ptr check on AssetBundle loading 2021-11-30 17:31:49 +11:00
8123ec2412 Revert "Use PatchProcessor ctor directly instead of Harmony.CreatePatchProcessor"
This reverts commit 9c1d459655.
2021-11-20 18:57:53 +11:00
3f31db8dd8 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-11-20 17:47:14 +11:00
9c1d459655 Use PatchProcessor ctor directly instead of Harmony.CreatePatchProcessor 2021-11-20 17:47:01 +11:00
2cb510a82e Cleanup readme
"Common issues" probably not that common anymore, removing to reduce clutter.
2021-11-18 05:12:04 +11:00
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
128 changed files with 693 additions and 7811 deletions

View File

@ -8,6 +8,9 @@
<p align="center">
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
</p>
<p align="center">
✨ Powered by <a href="https://github.com/sinai-dev/UniverseLib">UniverseLib</a>
</p>
# Releases [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases)
@ -22,8 +25,7 @@
| BIE 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| 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.
1. Take the `UnityExplorer.BIE.[version].dll` and the `UniverseLib.[version].dll` files and put them in `BepInEx\plugins\`
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
@ -33,7 +35,7 @@
| ------- | ------ | ---- |
| ML 0.4+ | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
1. Take the `UnityExplorer.ML.[version].dll` file and put it in the `Mods\` folder created by MelonLoader.
1. Take the `UnityExplorer.ML.[version].dll` and the `UniverseLib.[version].dll` files and put them in the `Mods\` folder created by MelonLoader.
## Standalone
@ -43,11 +45,26 @@
The standalone release can be used with any injector or loader of your choice, but it requires you to load the dependencies manually: HarmonyX, and the IL2CPP version also requires that you set up an [Il2CppAssemblyUnhollower runtime](https://github.com/knah/Il2CppAssemblyUnhollower#required-external-setup).
1. Load the required libs - HarmonyX, and Il2CppAssemblyUnhollower if IL2CPP
1. Load the required libs - UniverseLib, HarmonyX, and Il2CppAssemblyUnhollower if IL2CPP
2. Load the UnityExplorer DLL
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 and solutions
Although UnityExplorer should work out of the box for most Unity games, in some cases you may need to tweak the settings for it to work properly.
To adjust the settings, open the config file:
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
* MelonLoader: `UserData\MelonPreferences.cfg`
* Standalone: `UnityExplorer\config.ini`
Try adjusting the following settings and see if it fixes your issues:
* `Startup_Delay_Time` - increase to 5-10 seconds (or more as needed), can fix issues with UnityExplorer being destroyed or corrupted during startup.
* `Disable_EventSystem_Override` - if input is not working properly, try setting this to `true`.
If these fixes do not work, please create an issue in this repo and I'll do my best to look into it.
# Features
<p align="center">
@ -122,7 +139,6 @@ If you fork the repository on GitHub you can build using the [dotnet workflow](h
* [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.
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
* [HerpDerpenstine](https://github.com/HerpDerpinstine) for [MelonCoroutines](https://github.com/LavaGang/MelonLoader/blob/6cc958ec23b5e2e8453a73bc2e0d5aa353d4f0d1/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs) \[[license](THIRDPARTY_LICENSES.md#melonloader-license)\], they were included for standalone IL2CPP coroutine support.
### Disclaimer

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

@ -6,6 +6,8 @@ using UnityEngine;
using UnityExplorer.CSConsole.Lexers;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CSConsole
{

View File

@ -9,11 +9,13 @@ using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.CSConsole;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.CSConsole
{
@ -104,7 +106,6 @@ namespace UnityExplorer.CSConsole
}
}
#region UI Listeners and options
// TODO save
@ -659,7 +660,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

@ -6,6 +6,7 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CSConsole.Lexers;
using UniverseLib;
namespace UnityExplorer.CSConsole
{

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UniverseLib;
namespace UnityExplorer.CSConsole.Lexers
{

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Core.Runtime;
using UniverseLib;
namespace UnityExplorer.CSConsole
{

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using UnityExplorer.CacheObject.IValues;
using UnityExplorer.CacheObject.Views;
using UniverseLib;
namespace UnityExplorer.CacheObject
{

View File

@ -7,8 +7,10 @@ using UnityEngine;
using UnityExplorer.Core.Runtime;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject
{
@ -230,7 +232,7 @@ namespace UnityExplorer.CacheObject
{
try
{
if (ReflectionUtility.IsBlacklisted(member))
if (RuntimeHelper.IsBlacklisted(member))
return;
var sig = GetSig(member);

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using UnityExplorer.Inspectors;
using UniverseLib;
namespace UnityExplorer.CacheObject
{

View File

@ -9,8 +9,10 @@ using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
using UnityExplorer.CacheObject.IValues;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject
{
@ -218,10 +220,7 @@ namespace UnityExplorer.CacheObject
// string wants it trimmed to max 200 chars
case ValueState.String:
if (!LastValueWasNull)
{
string s = Value as string;
return $"\"{ToStringUtility.PruneString(s, 200, 5)}\"";
}
return $"\"{ToStringUtility.PruneString(Value as string, 200, 5)}\"";
break;
// try to prefix the count of the collection for lists and dicts
@ -442,7 +441,7 @@ namespace UnityExplorer.CacheObject
{
inactiveIValueHolder = new GameObject("Temp_IValue_Holder");
GameObject.DontDestroyOnLoad(inactiveIValueHolder);
inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform;
inactiveIValueHolder.transform.parent = UniversalUI.PoolHolder.transform;
inactiveIValueHolder.SetActive(false);
}
return inactiveIValueHolder;

View File

@ -6,6 +6,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -11,6 +11,9 @@ using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -7,6 +7,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -11,6 +11,9 @@ using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -9,6 +9,8 @@ using UnityExplorer.Core.Config;
using UnityExplorer.CacheObject;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.CacheObject.IValues
{
@ -38,7 +40,7 @@ namespace UnityExplorer.CacheObject.IValues
if (s == null)
return false;
return s.Length >= UIManager.MAX_INPUTFIELD_CHARS;
return s.Length >= UniversalUI.MAX_INPUTFIELD_CHARS;
}
public override void SetValue(object value)
@ -80,7 +82,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

@ -6,7 +6,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI;
using UniverseLib.UI.Models;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -7,6 +7,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.IValues
{

View File

@ -5,6 +5,8 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.Views
{

View File

@ -8,6 +8,7 @@ using UnityExplorer.CacheObject.IValues;
using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.Views
{

View File

@ -6,6 +6,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.Views
{

View File

@ -8,6 +8,9 @@ using UnityExplorer.CacheObject.IValues;
using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.CacheObject.Views
{

View File

@ -6,8 +6,10 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.CacheObject.Views
{

View File

@ -89,18 +89,22 @@ namespace UnityExplorer.Core.Config
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
Force_Unlock_Mouse.OnValueChanged += (bool value) =>
{
UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = value;
};
Force_Unlock_Toggle = new ConfigElement<KeyCode>("Force Unlock Toggle Key",
"The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.",
KeyCode.None);
Aggressive_Mouse_Unlock = new ConfigElement<bool>("Aggressive Mouse Unlock",
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked.\n<b>Requires restart to take effect.</b>",
false);
Disable_EventSystem_Override = new ConfigElement<bool>("Disable EventSystem override",
"If enabled, UnityExplorer will not override the EventSystem from the game.\n<b>May require restart to take effect.</b>",
false);
Disable_EventSystem_Override.OnValueChanged += (bool value) =>
{
UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value;
};
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",

View File

@ -32,53 +32,9 @@ namespace UnityExplorer
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
#endif
private static bool onPostRenderFailed;
internal void Awake()
{
try
{
#if CPP
Camera.onPostRender = Camera.onPostRender == null
? new Action<Camera>(OnPostRender)
: Il2CppSystem.Delegate.Combine(Camera.onPostRender,
(Camera.CameraCallback)new Action<Camera>(OnPostRender)).Cast<Camera.CameraCallback>();
if (Camera.onPostRender == null || Camera.onPostRender.delegates == null)
{
ExplorerCore.LogWarning("Failed to add Camera.onPostRender listener, falling back to LateUpdate instead!");
onPostRenderFailed = true;
}
#else
Camera.onPostRender += OnPostRender;
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception adding onPostRender listener: {ex.ReflectionExToString()}\r\nFalling back to LateUpdate!");
onPostRenderFailed = true;
}
}
internal void Update()
{
ExplorerCore.Update();
}
internal void FixedUpdate()
{
ExplorerCore.FixedUpdate();
}
internal void LateUpdate()
{
if (onPostRenderFailed)
OnPostRender(null);
}
internal static void OnPostRender(Camera _)
{
ExplorerCore.OnPostRender();
}
}
}

View File

@ -1,286 +0,0 @@
using HarmonyLib;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Input
{
public class CursorUnlocker
{
public static bool Unlock
{
get => m_forceUnlock;
set
{
m_forceUnlock = value;
UpdateCursorControl();
}
}
private static bool m_forceUnlock;
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
private static CursorLockMode lastLockMode;
private static bool lastVisibleState;
private static bool currentlySettingCursor = false;
public static void Init()
{
lastLockMode = Cursor.lockState;
lastVisibleState = Cursor.visible;
SetupPatches();
UpdateCursorControl();
// Hook up config values
// Force Unlock Mouse
Unlock = ConfigManager.Force_Unlock_Mouse.Value;
ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; };
// Aggressive Mouse Unlock
if (ConfigManager.Aggressive_Mouse_Unlock.Value)
SetupAggressiveUnlock();
}
public static void SetupAggressiveUnlock()
{
try
{
RuntimeProvider.Instance.StartCoroutine(AggressiveUnlockCoroutine());
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up Aggressive Mouse Unlock: {ex}");
}
}
private static WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
private static IEnumerator AggressiveUnlockCoroutine()
{
while (true)
{
yield return _waitForEndOfFrame ?? (_waitForEndOfFrame = new WaitForEndOfFrame());
if (UIManager.ShowMenu)
UpdateCursorControl();
}
}
public static void UpdateCursorControl()
{
try
{
currentlySettingCursor = true;
if (ShouldActuallyUnlock)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
SetEventSystem();
}
else
{
Cursor.lockState = lastLockMode;
Cursor.visible = lastVisibleState;
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
ReleaseEventSystem();
}
currentlySettingCursor = false;
}
catch (Exception e)
{
ExplorerCore.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
}
}
// Event system overrides
private static bool settingEventSystem;
private static EventSystem lastEventSystem;
private static BaseInputModule lastInputModule;
public static void SetEventSystem()
{
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
{
lastEventSystem = EventSystem.current;
lastEventSystem.enabled = false;
}
// Set to our current system
settingEventSystem = true;
UIManager.EventSys.enabled = true;
EventSystem.current = UIManager.EventSys;
InputManager.ActivateUIModule();
settingEventSystem = false;
}
public static void ReleaseEventSystem()
{
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
{
lastEventSystem.enabled = true;
settingEventSystem = true;
EventSystem.current = lastEventSystem;
lastInputModule?.ActivateModule();
settingEventSystem = false;
}
}
// Patches
public static void SetupPatches()
{
try
{
PrefixPropertySetter(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
PrefixPropertySetter(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
PrefixPropertySetter(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
PrefixMethod(typeof(EventSystem),
"SetSelectedGameObject",
// some games use a modified version of uGUI that includes this extra int argument on this method.
new Type[] { typeof(GameObject), typeof(BaseEventData), typeof(int) },
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_SetSelectedGameObject))),
// most games use these arguments, we'll use them as our "backup".
new Type[] { typeof(GameObject), typeof(BaseEventData) });
//// Not sure if this one is needed.
//PrefixMethod(typeof(PointerInputModule),
// "ClearSelection",
// new Type[0],
// new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_PointerInputModule_ClearSelection))));
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
private static void PrefixMethod(Type type, string method, Type[] arguments, HarmonyMethod prefix, Type[] backupArgs = null)
{
try
{
var methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, arguments, null);
if (methodInfo == null)
{
if (backupArgs != null)
methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, backupArgs, null);
if (methodInfo == null)
throw new MissingMethodException($"Could not find method for patching - '{type.FullName}.{method}'!");
}
var processor = ExplorerCore.Harmony.CreateProcessor(methodInfo);
processor.AddPrefix(prefix);
processor.Patch();
}
catch (Exception e)
{
ExplorerCore.LogWarning($"Unable to patch {type.Name}.{method}: {e.Message}");
}
}
private static void PrefixPropertySetter(Type type, string property, HarmonyMethod prefix)
{
try
{
var processor = ExplorerCore.Harmony.CreateProcessor(type.GetProperty(property, ReflectionUtility.FLAGS).GetSetMethod());
processor.AddPrefix(prefix);
processor.Patch();
}
catch (Exception e)
{
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
}
}
// Prevent setting non-UnityExplorer objects as selected when menu is open
public static bool Prefix_EventSystem_SetSelectedGameObject(GameObject __0)
{
if (!UIManager.ShowMenu || !UIManager.CanvasRoot)
return true;
return __0 && __0.transform.root.gameObject.GetInstanceID() == UIManager.CanvasRoot.GetInstanceID();
}
//public static bool Prefix_PointerInputModule_ClearSelection()
//{
// return !(UIManager.ShowMenu && UIManager.CanvasRoot);
//}
// Force EventSystem.current to be UnityExplorer's when menu is open
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
if (!settingEventSystem && value)
{
lastEventSystem = value;
lastInputModule = value.currentInputModule;
}
if (!UIManager.EventSys)
return;
if (!settingEventSystem && ShouldActuallyUnlock && !ConfigManager.Disable_EventSystem_Override.Value)
{
value = UIManager.EventSys;
value.enabled = true;
}
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!currentlySettingCursor)
{
lastLockMode = value;
if (ShouldActuallyUnlock)
value = CursorLockMode.None;
}
}
public static void Prefix_set_visible(ref bool value)
{
if (!currentlySettingCursor)
{
lastVisibleState = value;
if (ShouldActuallyUnlock)
value = true;
}
}
}
}

View File

@ -1,22 +0,0 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
public interface IHandleInput
{
Vector2 MousePosition { get; }
Vector2 MouseScrollDelta { get; }
bool GetKeyDown(KeyCode key);
bool GetKey(KeyCode key);
bool GetMouseButtonDown(int btn);
bool GetMouseButton(int btn);
BaseInputModule UIModule { get; }
void AddUIInputModule();
void ActivateModule();
}
}

View File

@ -1,103 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
public enum InputType
{
InputSystem,
Legacy,
None
}
public static class InputManager
{
public static InputType CurrentType { get; private set; }
private static IHandleInput m_inputModule;
public static Vector3 MousePosition => m_inputModule.MousePosition;
public static bool GetKeyDown(KeyCode key)
{
if (key == KeyCode.None)
return false;
return m_inputModule.GetKeyDown(key);
}
public static bool GetKey(KeyCode key)
{
if (key == KeyCode.None)
return false;
return m_inputModule.GetKey(key);
}
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
public static BaseInputModule UIInput => m_inputModule.UIModule;
public static Vector2 MouseScrollDelta => m_inputModule.MouseScrollDelta;
public static void ActivateUIModule() => m_inputModule.ActivateModule();
public static void AddUIModule()
{
m_inputModule.AddUIInputModule();
ActivateUIModule();
}
public static void Init()
{
InitHandler();
CursorUnlocker.Init();
}
private static void InitHandler()
{
// First, just try to use the legacy input, see if its working.
// The InputSystem package may be present but not actually activated, so we can find out this way.
if (LegacyInput.TInput != null)
{
try
{
m_inputModule = new LegacyInput();
CurrentType = InputType.Legacy;
// make sure its working
GetKeyDown(KeyCode.F5);
ExplorerCore.Log("Initialized Legacy Input support");
return;
}
catch
{
// It's not working, we'll fall back to InputSystem.
}
}
if (InputSystem.TKeyboard != null)
{
try
{
m_inputModule = new InputSystem();
CurrentType = InputType.InputSystem;
ExplorerCore.Log("Initialized new InputSystem support.");
return;
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
ExplorerCore.LogWarning("Could not find any Input Module Type!");
m_inputModule = new NoInput();
CurrentType = InputType.None;
}
}
}

View File

@ -1,269 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Input
{
public class InputSystem : IHandleInput
{
public InputSystem()
{
SetupSupportedDevices();
m_kbCurrentProp = TKeyboard.GetProperty("current");
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
var btnControl = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
m_mouseCurrentProp = TMouse.GetProperty("current");
m_leftButtonProp = TMouse.GetProperty("leftButton");
m_rightButtonProp = TMouse.GetProperty("rightButton");
m_scrollDeltaProp = TMouse.GetProperty("scroll");
m_positionProp = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Pointer")
.GetProperty("position");
ReadV2ControlMethod = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
.MakeGenericType(typeof(Vector2))
.GetMethod("ReadValue");
}
internal static void SetupSupportedDevices()
{
try
{
// typeof(InputSystem)
Type TInputSystem = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputSystem");
// InputSystem.settings
var settings = TInputSystem.GetProperty("settings", BindingFlags.Public | BindingFlags.Static).GetValue(null, null);
// typeof(InputSettings)
Type TSettings = settings.GetActualType();
// InputSettings.supportedDevices
PropertyInfo supportedProp = TSettings.GetProperty("supportedDevices", BindingFlags.Public | BindingFlags.Instance);
var supportedDevices = supportedProp.GetValue(settings, null);
// An empty supportedDevices list means all devices are supported.
#if CPP
// weird hack for il2cpp, use the implicit operator and cast Il2CppStringArray to ReadOnlyArray<string>
var args = new object[] { new UnhollowerBaseLib.Il2CppStringArray(0) };
var method = supportedDevices.GetActualType().GetMethod("op_Implicit", BindingFlags.Static | BindingFlags.Public);
supportedProp.SetValue(settings, method.Invoke(null, args), null);
#else
supportedProp.SetValue(settings, Activator.CreateInstance(supportedDevices.GetActualType(), new object[] { new string[0] }), null);
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up InputSystem.settings.supportedDevices list!");
ExplorerCore.Log(ex);
}
}
#region reflection cache
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type m_tKeyboard;
public static Type TMouse => m_tMouse ?? (m_tMouse = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Mouse"));
private static Type m_tMouse;
public static Type TKey => m_tKey ?? (m_tKey = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Key"));
private static Type m_tKey;
private static PropertyInfo m_btnIsPressedProp;
private static PropertyInfo m_btnWasPressedProp;
private static object CurrentKeyboard => m_currentKeyboard ?? (m_currentKeyboard = m_kbCurrentProp.GetValue(null, null));
private static object m_currentKeyboard;
private static PropertyInfo m_kbCurrentProp;
private static PropertyInfo m_kbIndexer;
private static object CurrentMouse => m_currentMouse ?? (m_currentMouse = m_mouseCurrentProp.GetValue(null, null));
private static object m_currentMouse;
private static PropertyInfo m_mouseCurrentProp;
private static object LeftMouseButton => m_lmb ?? (m_lmb = m_leftButtonProp.GetValue(CurrentMouse, null));
private static object m_lmb;
private static PropertyInfo m_leftButtonProp;
private static object RightMouseButton => m_rmb ?? (m_rmb = m_rightButtonProp.GetValue(CurrentMouse, null));
private static object m_rmb;
private static PropertyInfo m_rightButtonProp;
private static MethodInfo ReadV2ControlMethod;
private static object MousePositionInfo => m_pos ?? (m_pos = m_positionProp.GetValue(CurrentMouse, null));
private static object m_pos;
private static PropertyInfo m_positionProp;
private static object MouseScrollInfo => m_scrollInfo ?? (m_scrollInfo = m_scrollDeltaProp.GetValue(CurrentMouse, null));
private static object m_scrollInfo;
private static PropertyInfo m_scrollDeltaProp;
#endregion
public Vector2 MousePosition
{
get
{
try
{
return (Vector2)ReadV2ControlMethod.Invoke(MousePositionInfo, ArgumentUtility.EmptyArgs);
}
catch { return Vector2.zero; }
}
}
public Vector2 MouseScrollDelta
{
get
{
try
{
return (Vector2)ReadV2ControlMethod.Invoke(MouseScrollInfo, ArgumentUtility.EmptyArgs);
}
catch { return Vector2.zero; }
}
}
internal static Dictionary<KeyCode, object> ActualKeyDict = new Dictionary<KeyCode, object>();
internal static Dictionary<string, string> enumNameFixes = new Dictionary<string, string>
{
{ "Control", "Ctrl" },
{ "Return", "Enter" },
{ "Alpha", "Digit" },
{ "Keypad", "Numpad" },
{ "Numlock", "NumLock" },
{ "Print", "PrintScreen" },
{ "BackQuote", "Backquote" }
};
internal object GetActualKey(KeyCode key)
{
if (!ActualKeyDict.ContainsKey(key))
{
var s = key.ToString();
try
{
if (enumNameFixes.First(it => s.Contains(it.Key)) is KeyValuePair<string, string> entry)
s = s.Replace(entry.Key, entry.Value);
}
catch { }
var parsed = Enum.Parse(TKey, s);
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
ActualKeyDict.Add(key, actualKey);
}
return ActualKeyDict[key];
}
public bool GetKeyDown(KeyCode key) => (bool)m_btnWasPressedProp.GetValue(GetActualKey(key), null);
public bool GetKey(KeyCode key) => (bool)m_btnIsPressedProp.GetValue(GetActualKey(key), null);
public bool GetMouseButtonDown(int btn)
{
if (CurrentMouse == null)
return false;
switch (btn)
{
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)m_btnWasPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnWasPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
public bool GetMouseButton(int btn)
{
if (CurrentMouse == null)
return false;
switch (btn)
{
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)m_btnIsPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnIsPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
// UI Input
public Type TInputSystemUIInputModule
=> m_tUIInputModule
?? (m_tUIInputModule = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
internal Type m_tUIInputModule;
public BaseInputModule UIModule => m_newInputModule;
internal BaseInputModule m_newInputModule;
public void AddUIInputModule()
{
if (TInputSystemUIInputModule == null)
{
ExplorerCore.LogWarning("Unable to find UI Input Module Type, Input will not work!");
return;
}
var assetType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionAsset");
m_newInputModule = RuntimeProvider.Instance.AddComponent<BaseInputModule>(UIManager.CanvasRoot, TInputSystemUIInputModule);
var asset = RuntimeProvider.Instance.CreateScriptable(assetType)
.TryCast(assetType);
inputExtensions = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionSetupExtensions");
var addMap = inputExtensions.GetMethod("AddActionMap", new Type[] { assetType, typeof(string) });
var map = addMap.Invoke(null, new object[] { asset, "UI" })
.TryCast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
CreateAction(map, "point", new[] { "<Mouse>/position" }, "point");
CreateAction(map, "click", new[] { "<Mouse>/leftButton" }, "leftClick");
CreateAction(map, "rightClick", new[] { "<Mouse>/rightButton" }, "rightClick");
CreateAction(map, "scrollWheel", new[] { "<Mouse>/scroll" }, "scrollWheel");
UI_Enable = map.GetType().GetMethod("Enable");
UI_Enable.Invoke(map, ArgumentUtility.EmptyArgs);
UI_ActionMap = map;
}
private Type inputExtensions;
private object UI_ActionMap;
private MethodInfo UI_Enable;
private void CreateAction(object map, string actionName, string[] bindings, string propertyName)
{
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 })
.TryCast(inputActionType);
var addBinding = inputExtensions.GetMethod("AddBinding",
new Type[] { inputActionType, typeof(string), typeof(string), typeof(string), typeof(string) });
foreach (string binding in bindings)
addBinding.Invoke(null, new object[] { action.TryCast(inputActionType), binding, null, null, null });
var refType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionReference");
var inputRef = refType.GetMethod("Create")
.Invoke(null, new object[] { action })
.TryCast(refType);
TInputSystemUIInputModule
.GetProperty(propertyName)
.SetValue(m_newInputModule.TryCast(TInputSystemUIInputModule), inputRef, null);
}
public void ActivateModule()
{
m_newInputModule.ActivateModule();
UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
}
}
}

View File

@ -1,58 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Input
{
public class LegacyInput : IHandleInput
{
public LegacyInput()
{
m_mousePositionProp = TInput.GetProperty("mousePosition");
m_mouseDeltaProp = TInput.GetProperty("mouseScrollDelta");
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
}
public static Type TInput => m_tInput ?? (m_tInput = ReflectionUtility.GetTypeByName("UnityEngine.Input"));
private static Type m_tInput;
private static PropertyInfo m_mousePositionProp;
private static PropertyInfo m_mouseDeltaProp;
private static MethodInfo m_getKeyMethod;
private static MethodInfo m_getKeyDownMethod;
private static MethodInfo m_getMouseButtonMethod;
private static MethodInfo m_getMouseButtonDownMethod;
public Vector2 MousePosition => (Vector3)m_mousePositionProp.GetValue(null, null);
public Vector2 MouseScrollDelta => (Vector2)m_mouseDeltaProp.GetValue(null, null);
public bool GetKey(KeyCode key) => (bool)m_getKeyMethod.Invoke(null, new object[] { key });
public bool GetKeyDown(KeyCode key) => (bool)m_getKeyDownMethod.Invoke(null, new object[] { key });
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
// UI Input module
public BaseInputModule UIModule => m_inputModule;
internal StandaloneInputModule m_inputModule;
public void AddUIInputModule()
{
m_inputModule = UIManager.CanvasRoot.gameObject.AddComponent<StandaloneInputModule>();
}
public void ActivateModule()
{
m_inputModule.ActivateModule();
}
}
}

View File

@ -1,23 +0,0 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
// Just a stub for games where no Input module was able to load at all.
public class NoInput : IHandleInput
{
public Vector2 MousePosition => Vector2.zero;
public Vector2 MouseScrollDelta => Vector2.zero;
public bool GetKey(KeyCode key) => false;
public bool GetKeyDown(KeyCode key) => false;
public bool GetMouseButton(int btn) => false;
public bool GetMouseButtonDown(int btn) => false;
public BaseInputModule UIModule => null;
public void ActivateModule() { }
public void AddUIInputModule() { }
}
}

View File

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UnityExplorer
{
public static class ReflectionExtensions
{
// ReflectionUtility extensions
public static Type GetActualType(this object obj)
=> ReflectionUtility.Instance.Internal_GetActualType(obj);
public static object TryCast(this object obj)
=> ReflectionUtility.Instance.Internal_TryCast(obj, ReflectionUtility.Instance.Internal_GetActualType(obj));
public static object TryCast(this object obj, Type castTo)
=> ReflectionUtility.Instance.Internal_TryCast(obj, castTo);
public static T TryCast<T>(this object obj)
{
try
{
return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T));
}
catch
{
return default;
}
}
// ------- Misc extensions --------
/// <summary>
/// Safely try to get all Types inside an Assembly.
/// </summary>
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
{
try
{
return asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
try
{
return asm.GetExportedTypes();
}
catch
{
return e.Types.Where(t => t != null);
}
}
catch
{
return Enumerable.Empty<Type>();
}
}
/// <summary>
/// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality.
/// </summary>
public static bool ReferenceEqual(this object objA, object objB)
{
if (object.ReferenceEquals(objA, objB))
return true;
if (objA is UnityEngine.Object unityA && objB is UnityEngine.Object unityB)
{
if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr)
return true;
}
#if CPP
if (objA is Il2CppSystem.Object cppA && objB is Il2CppSystem.Object cppB
&& cppA.Pointer == cppB.Pointer)
return true;
#endif
return false;
}
/// <summary>
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
/// </summary>
public static string ReflectionExToString(this Exception e, bool innerMost = true)
{
if (innerMost)
e = e.GetInnerMostException();
return $"{e.GetType()}: {e.Message}";
}
public static Exception GetInnerMostException(this Exception e)
{
while (e != null)
{
if (e.InnerException == null)
break;
#if CPP
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break;
#endif
e = e.InnerException;
}
return e;
}
}
}

View File

@ -1,956 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
using UnhollowerBaseLib.Attributes;
using UnityEngine;
namespace UnityExplorer
{
public class Il2CppReflection : ReflectionUtility
{
protected override void Initialize()
{
base.Initialize();
float start = Time.realtimeSinceStartup;
TryLoadGameModules();
ExplorerCore.Log($"Loaded Unhollowed modules in {Time.realtimeSinceStartup - start} seconds");
start = Time.realtimeSinceStartup;
BuildDeobfuscationCache();
OnTypeLoaded += TryCacheDeobfuscatedType;
ExplorerCore.Log($"Setup IL2CPP reflection in {Time.realtimeSinceStartup - start} seconds, " +
$"deobfuscated types count: {DeobfuscatedTypes.Count}");
}
#region IL2CPP Extern and pointers
// Extern C++ methods
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (!cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
{
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
}
return il2cppPtr != IntPtr.Zero;
}
#endregion
#region Deobfuscation cache
private static readonly Dictionary<string, Type> DeobfuscatedTypes = new Dictionary<string, Type>();
private static readonly Dictionary<string, string> reverseDeobCache = new Dictionary<string, string>();
private static void BuildDeobfuscationCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
}
private static void TryCacheDeobfuscatedType(Type type)
{
try
{
if (!type.CustomAttributes.Any())
return;
foreach (var att in type.CustomAttributes)
{
// Thanks to Slaynash for this
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
{
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
DeobfuscatedTypes.Add(obfuscatedName, type);
reverseDeobCache.Add(type.FullName, obfuscatedName);
}
}
}
catch { }
}
internal override string Internal_ProcessTypeInString(string theString, Type type)
{
if (reverseDeobCache.TryGetValue(type.FullName, out string obName))
return theString.Replace(obName, type.FullName);
return theString;
}
#endregion
// Get type by name
internal override Type Internal_GetTypeByName(string fullName)
{
if (DeobfuscatedTypes.TryGetValue(fullName, out Type deob))
return deob;
return base.Internal_GetTypeByName(fullName);
}
#region Get actual type
internal override Type Internal_GetActualType(object obj)
{
if (obj == null)
return null;
var type = obj.GetType();
try
{
if (type.IsGenericType)
return type;
if (IsString(obj))
return typeof(string);
if (IsIl2CppPrimitive(type))
return il2cppPrimitivesToMono[type.FullName];
if (obj is Il2CppSystem.Object cppObject)
{
var cppType = cppObject.GetIl2CppType();
// check if type is injected
IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
if (RuntimeSpecificsStore.IsInjected(classPtr))
{
// Note: This will fail on injected subclasses.
// - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected.
// Not sure on solution yet.
return GetTypeByName(cppType.FullName) ?? type;
}
if (AllTypes.TryGetValue(cppType.FullName, out Type primitive) && primitive.IsPrimitive)
return primitive;
return GetUnhollowedType(cppType) ?? type;
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception in IL2CPP GetActualType: " + ex);
}
return type;
}
public static Type GetUnhollowedType(CppType cppType)
{
var fullname = cppType.FullName;
if (DeobfuscatedTypes.TryGetValue(fullname, out Type deob))
return deob;
if (fullname.StartsWith("System."))
fullname = $"Il2Cpp{fullname}";
if (!AllTypes.TryGetValue(fullname, out Type monoType))
ExplorerCore.LogWarning($"Failed to get type by name '{fullname}'!");
return monoType;
}
#endregion
#region Casting
private static readonly Dictionary<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
internal override object Internal_TryCast(object obj, Type castTo)
{
if (obj == null)
return null;
var type = obj.GetType();
if (type == castTo)
return obj;
// from structs
if (type.IsValueType)
{
// from il2cpp primitive to system primitive
if (IsIl2CppPrimitive(type) && castTo.IsPrimitive)
{
return MakeMonoPrimitive(obj);
}
// from system primitive to il2cpp primitive
else if (IsIl2CppPrimitive(castTo))
{
return MakeIl2CppPrimitive(castTo, obj);
}
// from other structs to il2cpp object
else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{
return BoxIl2CppObject(obj).TryCast(castTo);
}
else
return obj;
}
// from string to il2cpp.Object / il2cpp.String
if (obj is string && typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{
return BoxStringToType(obj, castTo);
}
// from il2cpp objects...
if (!(obj is Il2CppObjectBase cppObj))
return obj;
// from Il2CppSystem.Object to a struct
if (castTo.IsValueType)
return UnboxCppObject(cppObj, castTo);
// or to system string
else if (castTo == typeof(string))
return UnboxString(obj);
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
return obj;
// Casting from il2cpp object to il2cpp object...
IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
return null;
if (RuntimeSpecificsStore.IsInjected(castToPtr))
{
var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
return injectedObj ?? obj;
}
try
{
return Activator.CreateInstance(castTo, cppObj.Pointer);
}
catch
{
return obj;
}
}
//private static bool IsAssignableFrom(Type thisType, Type fromType)
//{
// if (!Il2CppTypeNotNull(fromType, out IntPtr fromTypePtr)
// || !Il2CppTypeNotNull(thisType, out IntPtr thisTypePtr))
// {
// // one or both of the types are not Il2Cpp types, use normal check
// return thisType.IsAssignableFrom(fromType);
// }
//
// return il2cpp_class_is_assignable_from(thisTypePtr, fromTypePtr);
//}
#endregion
#region Boxing and unboxing ValueTypes
// cached il2cpp unbox methods
internal static readonly Dictionary<string, MethodInfo> unboxMethods = new Dictionary<string, MethodInfo>();
// Unbox an il2cpp object to a struct or System primitive.
public object UnboxCppObject(Il2CppObjectBase cppObj, Type toType)
{
if (!toType.IsValueType)
return null;
try
{
if (toType.IsEnum)
{
// Check for nullable enums
var type = cppObj.GetType();
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Il2CppSystem.Nullable<>))
{
var nullable = cppObj.TryCast(type);
var nullableHasValueProperty = type.GetProperty("HasValue");
if ((bool)nullableHasValueProperty.GetValue(nullable, null))
{
// nullable has a value.
var nullableValueProperty = type.GetProperty("Value");
return Enum.Parse(toType, nullableValueProperty.GetValue(nullable, null).ToString());
}
// nullable and no current value.
return cppObj;
}
return Enum.Parse(toType, cppObj.ToString());
}
// Not enum, unbox with Il2CppObjectBase.Unbox
var name = toType.AssemblyQualifiedName;
if (!unboxMethods.ContainsKey(name))
{
unboxMethods.Add(name, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(toType));
}
return unboxMethods[name].Invoke(cppObj, ArgumentUtility.EmptyArgs);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception Unboxing Il2Cpp object to struct: " + ex);
return null;
}
}
private static Il2CppSystem.Object BoxIl2CppObject(object cppStruct, Type structType)
{
return GetMethodInfo(structType, "BoxIl2CppObject", ArgumentUtility.EmptyTypes)
.Invoke(cppStruct, ArgumentUtility.EmptyArgs)
as Il2CppSystem.Object;
}
public Il2CppSystem.Object BoxIl2CppObject(object value)
{
if (value == null)
return null;
try
{
var type = value.GetType();
if (!type.IsValueType)
return null;
if (type.IsEnum)
return Il2CppSystem.Enum.Parse(Il2CppType.From(type), value.ToString());
if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType))
return BoxIl2CppObject(MakeIl2CppPrimitive(cppType, value), cppType);
return BoxIl2CppObject(value, type);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception in BoxIl2CppObject: " + ex);
return null;
}
}
// Helpers for Il2Cpp primitive <-> Mono
internal static readonly Dictionary<string, Type> il2cppPrimitivesToMono = new Dictionary<string, Type>
{
{ "Il2CppSystem.Boolean", typeof(bool) },
{ "Il2CppSystem.Byte", typeof(byte) },
{ "Il2CppSystem.SByte", typeof(sbyte) },
{ "Il2CppSystem.Char", typeof(char) },
{ "Il2CppSystem.Double", typeof(double) },
{ "Il2CppSystem.Single", typeof(float) },
{ "Il2CppSystem.Int32", typeof(int) },
{ "Il2CppSystem.UInt32", typeof(uint) },
{ "Il2CppSystem.Int64", typeof(long) },
{ "Il2CppSystem.UInt64", typeof(ulong) },
{ "Il2CppSystem.Int16", typeof(short) },
{ "Il2CppSystem.UInt16", typeof(ushort) },
{ "Il2CppSystem.IntPtr", typeof(IntPtr) },
{ "Il2CppSystem.UIntPtr", typeof(UIntPtr) }
};
public static bool IsIl2CppPrimitive(object obj) => IsIl2CppPrimitive(obj.GetType());
public static bool IsIl2CppPrimitive(Type type) => il2cppPrimitivesToMono.ContainsKey(type.FullName);
public object MakeMonoPrimitive(object cppPrimitive)
{
return GetFieldInfo(cppPrimitive.GetType(), "m_value").GetValue(cppPrimitive);
}
public object MakeIl2CppPrimitive(Type cppType, object monoValue)
{
var cppStruct = Activator.CreateInstance(cppType);
GetFieldInfo(cppType, "m_value").SetValue(cppStruct, monoValue);
return cppStruct;
}
#endregion
#region String boxing/unboxing
private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
private const string STRING_FULLNAME = "System.String";
public bool IsString(object obj)
{
if (obj is string || obj is Il2CppSystem.String)
return true;
if (obj is Il2CppSystem.Object cppObj)
{
var type = cppObj.GetIl2CppType();
return type.FullName == IL2CPP_STRING_FULLNAME || type.FullName == STRING_FULLNAME;
}
return false;
}
public object BoxStringToType(object value, Type castTo)
{
if (castTo == typeof(Il2CppSystem.String))
return (Il2CppSystem.String)(value as string);
else
return (Il2CppSystem.Object)(value as string);
}
public string UnboxString(object value)
{
if (value is string s)
return s;
s = null;
if (value is Il2CppSystem.Object cppObject)
s = cppObject.ToString();
else if (value is Il2CppSystem.String cppString)
s = cppString;
return s;
}
#endregion
#region Singleton finder
internal override void Internal_FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
{
PropertyInfo pi;
foreach (var name in possibleNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
base.Internal_FindSingleton(possibleNames, type, flags, instances);
}
#endregion
#region Force-loading game modules
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
// Force loading all il2cpp modules
internal void TryLoadGameModules()
{
var dir = ExplorerCore.Loader.UnhollowedModulesFolder;
if (Directory.Exists(dir))
{
foreach (var filePath in Directory.GetFiles(dir, "*.dll"))
DoLoadModule(filePath);
}
else
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{dir}'. " +
$"If you are using the standalone release, you can specify the Unhollowed modules path when you call CreateInstance().");
}
internal bool DoLoadModule(string fullPath)
{
if (string.IsNullOrEmpty(fullPath) || !File.Exists(fullPath))
return false;
try
{
Assembly.LoadFile(fullPath);
return true;
}
catch //(Exception e)
{
//ExplorerCore.LogWarning($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
return false;
}
}
#endregion
#region Il2cpp reflection blacklist
public override string[] DefaultReflectionBlacklist => defaultIl2CppBlacklist.ToArray();
// These methods currently cause a crash in most il2cpp games,
// even from doing "GetParameters()" on the MemberInfo.
// Blacklisting until the issue is fixed in Unhollower.
public static HashSet<string> defaultIl2CppBlacklist = new HashSet<string>
{
// These were deprecated a long time ago, still show up in some IL2CPP games for some reason
"UnityEngine.MonoBehaviour.allowPrefabModeInPlayMode",
"UnityEngine.MonoBehaviour.runInEditMode",
"UnityEngine.Component.animation",
"UnityEngine.Component.audio",
"UnityEngine.Component.camera",
"UnityEngine.Component.collider",
"UnityEngine.Component.collider2D",
"UnityEngine.Component.constantForce",
"UnityEngine.Component.hingeJoint",
"UnityEngine.Component.light",
"UnityEngine.Component.networkView",
"UnityEngine.Component.particleSystem",
"UnityEngine.Component.renderer",
"UnityEngine.Component.rigidbody",
"UnityEngine.Component.rigidbody2D",
"UnityEngine.Light.flare",
// These can cause a crash in IL2CPP
"Il2CppSystem.Type.DeclaringMethod",
"Il2CppSystem.RuntimeType.DeclaringMethod",
"Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData",
"Unity.Profiling.ProfilerRecorder.CopyTo",
"Unity.Profiling.ProfilerRecorder.StartNew",
"UnityEngine.Analytics.Analytics.RegisterEvent",
"UnityEngine.Analytics.Analytics.SendEvent",
"UnityEngine.Analytics.ContinuousEvent+ConfigureEventDelegate.Invoke",
"UnityEngine.Analytics.ContinuousEvent.ConfigureEvent",
"UnityEngine.Animations.AnimationLayerMixerPlayable.Create",
"UnityEngine.Animations.AnimationLayerMixerPlayable.CreateHandle",
"UnityEngine.Animations.AnimationMixerPlayable.Create",
"UnityEngine.Animations.AnimationMixerPlayable.CreateHandle",
"UnityEngine.AssetBundle.RecompressAssetBundleAsync",
"UnityEngine.Audio.AudioMixerPlayable.Create",
"UnityEngine.BoxcastCommand.ScheduleBatch",
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
"UnityEngine.Canvas.renderingDisplaySize",
"UnityEngine.CapsulecastCommand.ScheduleBatch",
"UnityEngine.Collider2D.Cast",
"UnityEngine.Collider2D.Raycast",
"UnityEngine.ComputeBuffer+BeginBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer+EndBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer.BeginBufferWrite",
"UnityEngine.ComputeBuffer.EndBufferWrite",
"UnityEngine.Cubemap+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Cubemap+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Cubemap.SetPixelDataImpl",
"UnityEngine.Cubemap.SetPixelDataImplArray",
"UnityEngine.CubemapArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.CubemapArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.CubemapArray.SetPixelDataImpl",
"UnityEngine.CubemapArray.SetPixelDataImplArray",
"UnityEngine.Experimental.Playables.MaterialEffectPlayable.Create",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstanceDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstance_Procedural_InjectedDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural_Injected",
"UnityEngine.Experimental.Rendering.RayTracingShader+DispatchDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingShader.Dispatch",
"UnityEngine.Experimental.Rendering.RenderPassAttachment.Clear",
"UnityEngine.GUI.DoButtonGrid",
"UnityEngine.GUI.Slider",
"UnityEngine.GUI.Toolbar",
"UnityEngine.Graphics.DrawMeshInstancedIndirect",
"UnityEngine.Graphics.DrawMeshInstancedProcedural",
"UnityEngine.Graphics.DrawProcedural",
"UnityEngine.Graphics.DrawProceduralIndirect",
"UnityEngine.Graphics.DrawProceduralIndirectNow",
"UnityEngine.Graphics.DrawProceduralNow",
"UnityEngine.LineRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.LineRenderer.BakeMesh",
"UnityEngine.Mesh.GetIndices",
"UnityEngine.Mesh.GetTriangles",
"UnityEngine.Mesh.SetIndices",
"UnityEngine.Mesh.SetTriangles",
"UnityEngine.Physics2D.BoxCast",
"UnityEngine.Physics2D.CapsuleCast",
"UnityEngine.Physics2D.CircleCast",
"UnityEngine.PhysicsScene.BoxCast",
"UnityEngine.PhysicsScene.CapsuleCast",
"UnityEngine.PhysicsScene.OverlapBox",
"UnityEngine.PhysicsScene.OverlapCapsule",
"UnityEngine.PhysicsScene.SphereCast",
"UnityEngine.PhysicsScene2D.BoxCast",
"UnityEngine.PhysicsScene2D.CapsuleCast",
"UnityEngine.PhysicsScene2D.CircleCast",
"UnityEngine.PhysicsScene2D.GetRayIntersection",
"UnityEngine.PhysicsScene2D.Linecast",
"UnityEngine.PhysicsScene2D.OverlapArea",
"UnityEngine.PhysicsScene2D.OverlapBox",
"UnityEngine.PhysicsScene2D.OverlapCapsule",
"UnityEngine.PhysicsScene2D.OverlapCircle",
"UnityEngine.PhysicsScene2D.OverlapCollider",
"UnityEngine.PhysicsScene2D.OverlapPoint",
"UnityEngine.PhysicsScene2D.Raycast",
"UnityEngine.Playables.Playable.Create",
"UnityEngine.Profiling.CustomSampler.Create",
"UnityEngine.RaycastCommand.ScheduleBatch",
"UnityEngine.RemoteConfigSettings+QueueConfigDelegate.Invoke",
"UnityEngine.RemoteConfigSettings.QueueConfig",
"UnityEngine.RenderTexture.GetTemporaryImpl",
"UnityEngine.Rendering.AsyncGPUReadback.Request",
"UnityEngine.Rendering.AttachmentDescriptor.ConfigureClear",
"UnityEngine.Rendering.BatchRendererGroup+AddBatch_InjectedDelegate.Invoke",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch_Injected",
"UnityEngine.Rendering.CommandBuffer+Internal_DispatchRaysDelegate.Invoke",
"UnityEngine.Rendering.CommandBuffer.DispatchRays",
"UnityEngine.Rendering.CommandBuffer.DrawMeshInstancedProcedural",
"UnityEngine.Rendering.CommandBuffer.Internal_DispatchRays",
"UnityEngine.Rendering.CommandBuffer.ResolveAntiAliasedSurface",
"UnityEngine.Rendering.ScriptableRenderContext.BeginRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.SetupCameraProperties",
"UnityEngine.Rigidbody2D.Cast",
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
"UnityEngine.Scripting.GarbageCollector.CollectIncremental",
"UnityEngine.SpherecastCommand.ScheduleBatch",
"UnityEngine.Texture.GetPixelDataSize",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2D.SetPixelDataImpl",
"UnityEngine.Texture2D.SetPixelDataImplArray",
"UnityEngine.Texture2DArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2DArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2DArray.SetPixelDataImpl",
"UnityEngine.Texture2DArray.SetPixelDataImplArray",
"UnityEngine.Texture3D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture3D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture3D.SetPixelDataImpl",
"UnityEngine.Texture3D.SetPixelDataImplArray",
"UnityEngine.TrailRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.TrailRenderer.BakeMesh",
"UnityEngine.WWW.LoadFromCacheOrDownload",
"UnityEngine.XR.InputDevice.SendHapticImpulse",
};
#endregion
#region IL2CPP IEnumerable and IDictionary
protected override bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for system types (not unhollowed)
if (base.Internal_TryGetEntryType(enumerableType, out type))
return true;
// Type is either an IL2CPP enumerable, or its not generic.
if (type.IsGenericType)
{
// Temporary naive solution until IL2CPP interface support improves.
// This will work fine for most cases, but there are edge cases which would not work.
type = type.GetGenericArguments()[0];
return true;
}
// Unable to determine entry type
type = typeof(object);
return false;
}
protected override bool Internal_TryGetEntryTypes(Type type, out Type keys, out Type values)
{
if (base.Internal_TryGetEntryTypes(type, out keys, out values))
return true;
// Type is either an IL2CPP dictionary, or its not generic.
if (type.IsGenericType)
{
// Naive solution until IL2CPP interfaces improve.
var args = type.GetGenericArguments();
if (args.Length == 2)
{
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
// Temp fix until Unhollower interface support improves
internal static readonly Dictionary<string, MethodInfo> getEnumeratorMethods = new Dictionary<string, MethodInfo>();
internal static readonly Dictionary<string, EnumeratorInfo> enumeratorInfos = new Dictionary<string, EnumeratorInfo>();
internal static readonly HashSet<string> notSupportedTypes = new HashSet<string>();
// IEnumerables
internal static IntPtr cppIEnumerablePointer;
protected override bool Internal_IsEnumerable(Type type)
{
if (base.Internal_IsEnumerable(type))
return true;
try
{
if (cppIEnumerablePointer == IntPtr.Zero)
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out cppIEnumerablePointer);
if (cppIEnumerablePointer != IntPtr.Zero
&& Il2CppTypeNotNull(type, out IntPtr assignFromPtr)
&& il2cpp_class_is_assignable_from(cppIEnumerablePointer, assignFromPtr))
{
return true;
}
}
catch { }
return false;
}
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
protected override bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
{
if (list is IEnumerable)
return base.Internal_TryGetEnumerator(list, out enumerator);
try
{
PrepareCppEnumerator(list, out object cppEnumerator, out EnumeratorInfo info);
enumerator = EnumerateCppList(info, cppEnumerator);
return true;
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Exception enumerating IEnumerable: {ex.ReflectionExToString()}");
enumerator = null;
return false;
}
}
private static void PrepareCppEnumerator(object list, out object cppEnumerator, out EnumeratorInfo info)
{
info = null;
cppEnumerator = null;
if (list == null)
throw new ArgumentNullException("list");
// Some ugly reflection to use the il2cpp interface for the instance type
var type = list.GetActualType();
var key = type.AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(key))
{
var method = type.GetMethod("GetEnumerator")
?? type.GetMethod("System_Collections_IEnumerable_GetEnumerator", FLAGS);
getEnumeratorMethods.Add(key, method);
// ensure the enumerator type is supported
try
{
var test = getEnumeratorMethods[key].Invoke(list, null);
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
}
catch (Exception ex)
{
ExplorerCore.Log($"IEnumerable failed to enumerate: {ex}");
notSupportedTypes.Add(key);
}
}
if (notSupportedTypes.Contains(key))
throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext.");
cppEnumerator = getEnumeratorMethods[key].Invoke(list, null);
var enumeratorType = cppEnumerator.GetActualType();
var enumInfoKey = enumeratorType.AssemblyQualifiedName;
if (!enumeratorInfos.ContainsKey(enumInfoKey))
{
enumeratorInfos.Add(enumInfoKey, new EnumeratorInfo
{
current = enumeratorType.GetProperty("Current"),
moveNext = enumeratorType.GetMethod("MoveNext"),
});
}
info = enumeratorInfos[enumInfoKey];
}
internal static IEnumerator EnumerateCppList(EnumeratorInfo info, object enumerator)
{
// Yield and return the actual entries
while ((bool)info.moveNext.Invoke(enumerator, null))
yield return info.current.GetValue(enumerator);
}
// IDictionary
internal static IntPtr cppIDictionaryPointer;
protected override bool Internal_IsDictionary(Type type)
{
if (base.Internal_IsDictionary(type))
return true;
try
{
if (cppIDictionaryPointer == IntPtr.Zero)
if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out cppIDictionaryPointer))
return false;
if (Il2CppTypeNotNull(type, out IntPtr classPtr)
&& il2cpp_class_is_assignable_from(cppIDictionaryPointer, classPtr))
return true;
}
catch { }
return false;
}
protected override bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
{
if (dictionary is IDictionary)
return base.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
try
{
var type = dictionary.GetActualType();
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
{
dictEnumerator = EnumerateCppHashTable(dictionary.TryCast<Il2CppSystem.Collections.Hashtable>());
return true;
}
var keys = type.GetProperty("Keys").GetValue(dictionary, null);
var keyCollType = keys.GetActualType();
var cacheKey = keyCollType.AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(cacheKey))
{
var method = keyCollType.GetMethod("GetEnumerator")
?? keyCollType.GetMethod("System_Collections_IDictionary_GetEnumerator", FLAGS);
getEnumeratorMethods.Add(cacheKey, method);
// test support
try
{
var test = getEnumeratorMethods[cacheKey].Invoke(keys, null);
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
}
catch (Exception ex)
{
ExplorerCore.Log($"IDictionary failed to enumerate: {ex}");
notSupportedTypes.Add(cacheKey);
}
}
if (notSupportedTypes.Contains(cacheKey))
throw new Exception($"The IDictionary type '{type.FullName}' does not support MoveNext.");
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
var keyInfo = new EnumeratorInfo
{
current = keyEnumerator.GetActualType().GetProperty("Current"),
moveNext = keyEnumerator.GetActualType().GetMethod("MoveNext"),
};
var values = type.GetProperty("Values").GetValue(dictionary, null);
var valueEnumerator = values.GetActualType().GetMethod("GetEnumerator").Invoke(values, null);
var valueInfo = new EnumeratorInfo
{
current = valueEnumerator.GetActualType().GetProperty("Current"),
moveNext = valueEnumerator.GetActualType().GetMethod("MoveNext"),
};
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);
return true;
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Exception enumerating IDictionary: {ex.ReflectionExToString()}");
dictEnumerator = null;
return false;
}
}
internal static IEnumerator<DictionaryEntry> EnumerateCppDict(EnumeratorInfo keyInfo, object keyEnumerator,
EnumeratorInfo valueInfo, object valueEnumerator)
{
while ((bool)keyInfo.moveNext.Invoke(keyEnumerator, null))
{
valueInfo.moveNext.Invoke(valueEnumerator, null);
var key = keyInfo.current.GetValue(keyEnumerator, null);
var value = valueInfo.current.GetValue(valueEnumerator, null);
yield return new DictionaryEntry(key, value);
}
}
internal static IEnumerator<DictionaryEntry> EnumerateCppHashTable(Il2CppSystem.Collections.Hashtable hashtable)
{
for (int i = 0; i < hashtable.buckets.Count; i++)
{
var bucket = hashtable.buckets[i];
if (bucket == null || bucket.key == null)
continue;
yield return new DictionaryEntry(bucket.key, bucket.val);
}
}
#endregion
}
}
#endif

View File

@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
namespace UnityExplorer
{
public static class ReflectionPatches
{
public static void Init()
{
try
{
var method = typeof(Assembly).GetMethod(nameof(Assembly.GetTypes), new Type[0]);
var processor = ExplorerCore.Harmony.CreateProcessor(method);
processor.AddFinalizer(typeof(ReflectionPatches).GetMethod(nameof(Assembly_GetTypes)));
processor.Patch();
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up Reflection patch: {ex}");
}
}
private static readonly Type[] emptyTypes = new Type[0];
public static Exception Assembly_GetTypes(Assembly __instance, Exception __exception, ref Type[] __result)
{
if (__exception != null)
{
try
{
__result = __instance.GetExportedTypes();
}
catch (ReflectionTypeLoadException e)
{
try
{
__result = e.Types.Where(it => it != null).ToArray();
}
catch
{
__result = emptyTypes;
}
}
catch
{
__result = emptyTypes;
}
}
return null;
}
}
}

View File

@ -1,597 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Runtime;
using BF = System.Reflection.BindingFlags;
namespace UnityExplorer
{
public class ReflectionUtility
{
public const BF FLAGS = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
internal static ReflectionUtility Instance;
public static void Init()
{
Instance =
#if CPP
new Il2CppReflection();
#else
new ReflectionUtility();
#endif
Instance.Initialize();
ReflectionPatches.Init();
}
protected virtual void Initialize()
{
SetupTypeCache();
LoadBlacklistString(ConfigManager.Reflection_Signature_Blacklist.Value);
ConfigManager.Reflection_Signature_Blacklist.OnValueChanged += LoadBlacklistString;
}
#region Type cache
public static Action<Type> OnTypeLoaded;
/// <summary>Key: Type.FullName</summary>
protected static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public static readonly List<string> AllNamespaces = new List<string>();
private static readonly HashSet<string> uniqueNamespaces = new HashSet<string>();
private static string[] allTypesArray;
public static string[] GetTypeNameArray()
{
if (allTypesArray == null || allTypesArray.Length != AllTypes.Count)
{
allTypesArray = new string[AllTypes.Count];
int i = 0;
foreach (var name in AllTypes.Keys)
{
allTypesArray[i] = name;
i++;
}
}
return allTypesArray;
}
private static void SetupTypeCache()
{
float start = Time.realtimeSinceStartup;
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
CacheTypes(asm);
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
ExplorerCore.Log($"Cached AppDomain assemblies in {Time.realtimeSinceStartup - start} seconds");
}
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
if (args.LoadedAssembly == null || args.LoadedAssembly.GetName().Name == "completions")
return;
CacheTypes(args.LoadedAssembly);
}
private static void CacheTypes(Assembly asm)
{
foreach (var type in asm.TryGetTypes())
{
// Cache namespace if there is one
if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace))
{
uniqueNamespaces.Add(type.Namespace);
int i = 0;
while (i < AllNamespaces.Count)
{
if (type.Namespace.CompareTo(AllNamespaces[i]) < 0)
break;
i++;
}
AllNamespaces.Insert(i, type.Namespace);
}
// Cache the type. Overwrite type if one exists with the full name
if (AllTypes.ContainsKey(type.FullName))
AllTypes[type.FullName] = type;
else
AllTypes.Add(type.FullName, type);
// Invoke listener
OnTypeLoaded?.Invoke(type);
// Check type inheritance cache, add this to any lists it should be in
foreach (var key in typeInheritance.Keys)
{
try
{
var baseType = AllTypes[key];
if (baseType.IsAssignableFrom(type) && !typeInheritance[key].Contains(type))
typeInheritance[key].Add(type);
}
catch { }
}
}
}
#endregion
/// <summary>
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
/// </summary>
/// <param name="fullName">The <see cref="Type.FullName"/> you want to search for - case sensitive and full matches only.</param>
/// <returns>The Type if found, otherwise null.</returns>
public static Type GetTypeByName(string fullName)
=> Instance.Internal_GetTypeByName(fullName);
internal virtual Type Internal_GetTypeByName(string fullName)
{
AllTypes.TryGetValue(fullName, out Type type);
return type;
}
// Getting the actual type of an object
internal virtual Type Internal_GetActualType(object obj)
=> obj?.GetType();
// Force-casting an object to a type
internal virtual object Internal_TryCast(object obj, Type castTo)
=> obj;
// Processing deobfuscated type names in strings
public static string ProcessTypeInString(Type type, string theString)
=> Instance.Internal_ProcessTypeInString(theString, type);
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
=> theString;
// Singleton finder
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
internal virtual void Internal_FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
{
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in possibleNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
}
// Universal helpers
#region Type inheritance cache
// cache for GetBaseTypes
internal static readonly Dictionary<string, Type[]> baseTypes = new Dictionary<string, Type[]>();
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType());
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
var name = type.AssemblyQualifiedName;
if (baseTypes.TryGetValue(name, out Type[] ret))
return ret;
List<Type> list = new List<Type>();
while (type != null)
{
list.Add(type);
type = type.BaseType;
}
ret = list.ToArray();
baseTypes.Add(name, ret);
return ret;
}
#endregion
#region Type and Generic Parameter implementation cache
// cache for GetImplementationsOf
internal static readonly Dictionary<string, HashSet<Type>> typeInheritance = new Dictionary<string, HashSet<Type>>();
internal static readonly Dictionary<string, HashSet<Type>> genericParameterInheritance = new Dictionary<string, HashSet<Type>>();
public static string GetImplementationKey(Type type)
{
if (!type.IsGenericParameter)
return type.FullName;
else
{
var sb = new StringBuilder();
sb.Append(type.GenericParameterAttributes)
.Append('|');
foreach (var c in type.GetGenericParameterConstraints())
sb.Append(c.FullName).Append(',');
return sb.ToString();
}
}
/// <summary>
/// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain.
/// Also works for generic parameters by analyzing the constraints.
/// </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 allowEnum, bool allowRecursive = true)
{
var key = GetImplementationKey(baseType);
int count = AllTypes.Count;
HashSet<Type> ret;
if (!baseType.IsGenericParameter)
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, bool allowEnum)
{
if (!typeInheritance.ContainsKey(key))
{
var set = new HashSet<Type>();
var names = GetTypeNameArray();
for (int i = 0; i < names.Length; i++)
{
var name = names[i];
try
{
var type = AllTypes[name];
if (set.Contains(type)
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))
|| (!allowEnum && type.IsEnum))
continue;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
continue;
if (baseType.IsAssignableFrom(type) && !set.Contains(type))
set.Add(type);
}
catch { }
}
//set.
typeInheritance.Add(key, set);
}
return typeInheritance[key];
}
private static HashSet<Type> GetGenericParameterImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
{
if (!genericParameterInheritance.ContainsKey(key))
{
var set = new HashSet<Type>();
var names = GetTypeNameArray();
for (int i = 0; i < names.Length; i++)
{
var name = names[i];
try
{
var type = AllTypes[name];
if (set.Contains(type)
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
continue;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
continue;
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)
&& type.IsClass)
continue;
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)
&& type.IsValueType)
continue;
if (baseType.GetGenericParameterConstraints().Any(it => !it.IsAssignableFrom(type)))
continue;
set.Add(type);
}
catch { }
}
genericParameterInheritance.Add(key, set);
}
return genericParameterInheritance[key];
}
#endregion
#region Internal MemberInfo Cache
internal static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
public static FieldInfo GetFieldInfo(Type type, string fieldName)
{
if (!fieldInfos.ContainsKey(type))
fieldInfos.Add(type, new Dictionary<string, FieldInfo>());
if (!fieldInfos[type].ContainsKey(fieldName))
fieldInfos[type].Add(fieldName, type.GetField(fieldName, FLAGS));
return fieldInfos[type][fieldName];
}
internal static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfos = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
if (!propertyInfos.ContainsKey(type))
propertyInfos.Add(type, new Dictionary<string, PropertyInfo>());
if (!propertyInfos[type].ContainsKey(propertyName))
propertyInfos[type].Add(propertyName, type.GetProperty(propertyName, FLAGS));
return propertyInfos[type][propertyName];
}
internal static Dictionary<Type, Dictionary<string, MethodInfo>> methodInfos = new Dictionary<Type, Dictionary<string, MethodInfo>>();
public static MethodInfo GetMethodInfo(Type type, string methodName)
=> GetMethodInfo(type, methodName, ArgumentUtility.EmptyTypes, false);
public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes, bool cacheAmbiguous = false)
{
if (!methodInfos.ContainsKey(type))
methodInfos.Add(type, new Dictionary<string, MethodInfo>());
if (cacheAmbiguous)
{
methodName += "|";
foreach (var arg in argumentTypes)
methodName += arg.FullName + ",";
}
try
{
if (!methodInfos[type].ContainsKey(methodName))
{
if (argumentTypes != null)
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS, null, argumentTypes, null));
else
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS));
}
return methodInfos[type][methodName];
}
catch (AmbiguousMatchException)
{
ExplorerCore.LogWarning($"AmbiguousMatchException trying to get method '{methodName}'");
return null;
}
catch (Exception e)
{
ExplorerCore.LogWarning($"{e.GetType()} trying to get method '{methodName}': {e.Message}\r\n{e.StackTrace}");
return null;
}
}
#endregion
#region Reflection Blacklist
public virtual string[] DefaultReflectionBlacklist => new string[0];
public static void LoadBlacklistString(string blacklist)
{
try
{
if (string.IsNullOrEmpty(blacklist) && !Instance.DefaultReflectionBlacklist.Any())
return;
try
{
var sigs = blacklist.Split(';');
foreach (var sig in sigs)
{
var s = sig.Trim();
if (string.IsNullOrEmpty(s))
continue;
if (!currentBlacklist.Contains(s))
currentBlacklist.Add(s);
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception parsing blacklist string: {ex.ReflectionExToString()}");
}
foreach (var sig in Instance.DefaultReflectionBlacklist)
{
if (!currentBlacklist.Contains(sig))
currentBlacklist.Add(sig);
}
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}");
}
}
public static bool IsBlacklisted(MemberInfo member)
{
if (string.IsNullOrEmpty(member.DeclaringType?.Namespace))
return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig);
}
private static readonly HashSet<string> currentBlacklist = new HashSet<string>();
#endregion
// Temp fix for IL2CPP until interface support improves
// IsEnumerable
public static bool IsEnumerable(Type type) => Instance.Internal_IsEnumerable(type);
protected virtual bool Internal_IsEnumerable(Type type)
{
return typeof(IEnumerable).IsAssignableFrom(type);
}
// TryGetEnumerator (list)
public static bool TryGetEnumerator(object list, out IEnumerator enumerator)
=> Instance.Internal_TryGetEnumerator(list, out enumerator);
protected virtual bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
{
enumerator = (list as IEnumerable).GetEnumerator();
return true;
}
// TryGetEntryType
public static bool TryGetEntryType(Type enumerableType, out Type type)
=> Instance.Internal_TryGetEntryType(enumerableType, out type);
protected virtual bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for arrays
if (enumerableType.IsArray)
{
type = enumerableType.GetElementType();
return true;
}
// Check for implementation of IEnumerable<T>, IList<T> or ICollection<T>
foreach (var t in enumerableType.GetInterfaces())
{
if (t.IsGenericType)
{
var typeDef = t.GetGenericTypeDefinition();
if (typeDef == typeof(IEnumerable<>) || typeDef == typeof(IList<>) || typeDef == typeof(ICollection<>))
{
type = t.GetGenericArguments()[0];
return true;
}
}
}
// Unable to determine any generic element type, just use object.
type = typeof(object);
return false;
}
// IsDictionary
public static bool IsDictionary(Type type) => Instance.Internal_IsDictionary(type);
protected virtual bool Internal_IsDictionary(Type type)
{
return typeof(IDictionary).IsAssignableFrom(type);
}
// TryGetEnumerator (dictionary)
public static bool TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
=> Instance.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
protected virtual bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
{
dictEnumerator = EnumerateDictionary((IDictionary)dictionary);
return true;
}
private IEnumerator<DictionaryEntry> EnumerateDictionary(IDictionary dict)
{
var enumerator = dict.GetEnumerator();
while (enumerator.MoveNext())
{
yield return new DictionaryEntry(enumerator.Key, enumerator.Value);
}
}
// TryGetEntryTypes
public static bool TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
=> Instance.Internal_TryGetEntryTypes(dictionaryType, out keys, out values);
protected virtual bool Internal_TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
{
foreach (var t in dictionaryType.GetInterfaces())
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
var args = t.GetGenericArguments();
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
}
}

View File

@ -1,82 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityExplorer.Core.Runtime.Il2Cpp;
namespace UnityExplorer
{
public class AssetBundle
{
// ~~~~~~~~~~~~ Static ~~~~~~~~~~~~
internal delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset);
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);
return new AssetBundle(ptr);
}
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
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);
return new AssetBundle(ptr);
}
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
public AssetBundle(IntPtr ptr) { m_bundlePtr = ptr; }
// LoadAllAssets()
internal delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr _this, IntPtr name, IntPtr type);
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);
if (ptr == IntPtr.Zero)
return new UnityEngine.Object[0];
return new Il2CppReferenceArray<UnityEngine.Object>(ptr);
}
// LoadAsset<T>(string name, Type type)
internal delegate IntPtr d_LoadAsset_Internal(IntPtr _this, IntPtr name, IntPtr type);
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);
if (ptr == IntPtr.Zero)
return null;
return new UnityEngine.Object(ptr).TryCast<T>();
}
// Unload(bool unloadAllLoadedObjects);
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
public void Unload(bool unloadAssets = true)
{
var iCall = ICallManager.GetICall<d_Unload>("UnityEngine.AssetBundle::Unload");
iCall.Invoke(this.m_bundlePtr, unloadAssets);
}
}
}
#endif

View File

@ -1,71 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public static class ICallManager
{
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
/// <summary>
/// Helper to get and cache an iCall by providing the signature (eg. "UnityEngine.Resources::FindObjectsOfTypeAll").
/// </summary>
/// <typeparam name="T">The Type of Delegate to provide for the iCall.</typeparam>
/// <param name="signature">The signature of the iCall you want to get.</param>
/// <returns>The <typeparamref name="T"/> delegate if successful.</returns>
/// <exception cref="MissingMethodException">If the iCall could not be found.</exception>
public static T GetICall<T>(string signature) where T : Delegate
{
if (iCallCache.ContainsKey(signature))
return (T)iCallCache[signature];
IntPtr ptr = il2cpp_resolve_icall(signature);
if (ptr == IntPtr.Zero)
throw new MissingMethodException($"Could not find any iCall with the signature '{signature}'!");
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
iCallCache.Add(signature, iCall);
return (T)iCall;
}
private static readonly Dictionary<string, Delegate> s_unreliableCache = new Dictionary<string, Delegate>();
/// <summary>
/// Get an iCall which may be one of multiple different signatures (ie, it changed in different Unity versions).
/// Each possible signature must have the same Type pattern, it can only vary by name.
/// </summary>
public static T GetICallUnreliable<T>(IEnumerable<string> possibleSignatures) where T : Delegate
{
// use the first possible signature as the 'key'.
string key = possibleSignatures.First();
if (s_unreliableCache.ContainsKey(key))
return (T)s_unreliableCache[key];
T iCall;
IntPtr ptr;
foreach (var sig in possibleSignatures)
{
ptr = il2cpp_resolve_icall(sig);
if (ptr != IntPtr.Zero)
{
iCall = (T)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
s_unreliableCache.Add(key, iCall);
return iCall;
}
}
throw new MissingMethodException($"Could not find any iCall from list of provided signatures starting with '{key}'!");
}
}
}
#endif

View File

@ -1,159 +0,0 @@
#if CPP
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnhollowerBaseLib;
using UnityEngine;
// Credit to HerpDerpenstine and knah
// https://github.com/LavaGang/MelonLoader/blob/master/SM_Il2Cpp/Coroutines.cs
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public static class Il2CppCoroutine
{
private struct CoroTuple
{
public object WaitCondition;
public IEnumerator Coroutine;
}
private static readonly List<CoroTuple> ourCoroutinesStore = new List<CoroTuple>();
private static readonly List<IEnumerator> ourNextFrameCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> ourWaitForFixedUpdateCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> ourWaitForEndOfFrameCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> tempList = new List<IEnumerator>();
internal static object Start(IEnumerator routine)
{
if (routine != null) ProcessNextOfCoroutine(routine);
return routine;
}
internal static void Stop(IEnumerator enumerator)
{
if (ourNextFrameCoroutines.Contains(enumerator)) // the coroutine is running itself
ourNextFrameCoroutines.Remove(enumerator);
else
{
int coroTupleIndex = ourCoroutinesStore.FindIndex(c => c.Coroutine == enumerator);
if (coroTupleIndex != -1) // the coroutine is waiting for a subroutine
{
object waitCondition = ourCoroutinesStore[coroTupleIndex].WaitCondition;
if (waitCondition is IEnumerator waitEnumerator)
Stop(waitEnumerator);
ourCoroutinesStore.RemoveAt(coroTupleIndex);
}
}
}
private static void ProcessCoroList(List<IEnumerator> target)
{
if (target.Count == 0) return;
// use a temp list to make sure waits made during processing are not handled by same processing invocation
// additionally, a temp list reduces allocations compared to an array
tempList.AddRange(target);
target.Clear();
foreach (var enumerator in tempList) ProcessNextOfCoroutine(enumerator);
tempList.Clear();
}
internal static void Process()
{
for (var i = ourCoroutinesStore.Count - 1; i >= 0; i--)
{
var tuple = ourCoroutinesStore[i];
if (tuple.WaitCondition is WaitForSeconds waitForSeconds)
{
if ((waitForSeconds.m_Seconds -= Time.deltaTime) <= 0)
{
ourCoroutinesStore.RemoveAt(i);
ProcessNextOfCoroutine(tuple.Coroutine);
}
}
}
ProcessCoroList(ourNextFrameCoroutines);
}
internal static void ProcessWaitForFixedUpdate() => ProcessCoroList(ourWaitForFixedUpdateCoroutines);
internal static void ProcessWaitForEndOfFrame() => ProcessCoroList(ourWaitForEndOfFrameCoroutines);
private static void ProcessNextOfCoroutine(IEnumerator enumerator)
{
try
{
if (!enumerator.MoveNext()) // Run the next step of the coroutine. If it's done, restore the parent routine
{
var indices = ourCoroutinesStore.Select((it, idx) => (idx, it)).Where(it => it.it.WaitCondition == enumerator).Select(it => it.idx).ToList();
for (var i = indices.Count - 1; i >= 0; i--)
{
var index = indices[i];
ourNextFrameCoroutines.Add(ourCoroutinesStore[index].Coroutine);
ourCoroutinesStore.RemoveAt(index);
}
return;
}
}
catch (Exception e)
{
ExplorerCore.LogError(e.ToString());
Stop(FindOriginalCoro(enumerator)); // We want the entire coroutine hierachy to stop when an error happen
}
var next = enumerator.Current;
switch (next)
{
case null:
ourNextFrameCoroutines.Add(enumerator);
return;
case WaitForFixedUpdate _:
ourWaitForFixedUpdateCoroutines.Add(enumerator);
return;
case WaitForEndOfFrame _:
ourWaitForEndOfFrameCoroutines.Add(enumerator);
return;
case WaitForSeconds _:
break; // do nothing, this one is supported in Process
case Il2CppObjectBase il2CppObjectBase:
var nextAsEnumerator = il2CppObjectBase.TryCast<Il2CppSystem.Collections.IEnumerator>();
if (nextAsEnumerator != null) // il2cpp IEnumerator also handles CustomYieldInstruction
next = new Il2CppEnumeratorWrapper(nextAsEnumerator);
else
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{il2CppObjectBase}' for coroutine '{enumerator}'");
return;
default:
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{next}' for coroutine '{enumerator}'");
return;
}
ourCoroutinesStore.Add(new CoroTuple { WaitCondition = next, Coroutine = enumerator });
if (next is IEnumerator nextCoro)
ProcessNextOfCoroutine(nextCoro);
}
private static IEnumerator FindOriginalCoro(IEnumerator enumerator)
{
int index = ourCoroutinesStore.FindIndex(ct => ct.WaitCondition == enumerator);
if (index == -1)
return enumerator;
return FindOriginalCoro(ourCoroutinesStore[index].Coroutine);
}
private class Il2CppEnumeratorWrapper : IEnumerator
{
private readonly Il2CppSystem.Collections.IEnumerator il2cppEnumerator;
public Il2CppEnumeratorWrapper(Il2CppSystem.Collections.IEnumerator il2CppEnumerator) => il2cppEnumerator = il2CppEnumerator;
public bool MoveNext() => il2cppEnumerator.MoveNext();
public void Reset() => il2cppEnumerator.Reset();
public object Current => il2cppEnumerator.Current;
}
}
}
#endif

View File

@ -1,268 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BF = System.Reflection.BindingFlags;
using System.Text;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public class Il2CppProvider : RuntimeProvider
{
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.IL2CPP;
TextureUtil = new Il2CppTextureUtil();
}
public override void SetupEvents()
{
try
{
Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
ExplorerCore.Log(ex);
}
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.LogUnity(condition, type);
}
public override void Update()
{
Il2CppCoroutine.Process();
}
internal override void ProcessOnPostRender()
{
Il2CppCoroutine.ProcessWaitForEndOfFrame();
}
internal override void ProcessFixedUpdate()
{
Il2CppCoroutine.ProcessWaitForFixedUpdate();
}
public override void StartCoroutine(IEnumerator routine)
{
Il2CppCoroutine.Start(routine);
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return obj.AddComponent(Il2CppType.From(type)).TryCast<T>();
}
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(Il2CppType.From(type));
}
// Pretty disgusting but couldn't figure out a cleaner way yet unfortunately
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
raycaster.Raycast(data, il2cppList);
if (il2cppList.Count > 0)
list.AddRange(il2cppList.ToArray());
}
// LayerMask.LayerToName
internal delegate IntPtr d_LayerToName(int layer);
public override string LayerToName(int layer)
{
var iCall = ICallManager.GetICall<d_LayerToName>("UnityEngine.LayerMask::LayerToName");
return IL2CPP.Il2CppStringToManaged(iCall.Invoke(layer));
}
// Resources.FindObjectsOfTypeAll
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
internal static readonly string[] findObjectsOfTypeAllSignatures = new[]
{
"UnityEngine.Resources::FindObjectsOfTypeAll",
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
};
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
{
return new Il2CppReferenceArray<UnityEngine.Object>(
ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(findObjectsOfTypeAllSignatures)
.Invoke(Il2CppType.From(type).Pointer));
}
// Scene.GetRootGameObjects();
internal delegate void d_GetRootGameObjects(int handle, IntPtr list);
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
if (scene.handle == -1)
return new GameObject[0];
int count = GetRootCount(scene.handle);
if (count < 1)
return new GameObject[0];
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
var iCall = ICallManager.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
iCall.Invoke(scene.handle, list.Pointer);
return list.ToArray();
}
// Scene.rootCount
internal delegate int d_GetRootCountInternal(int handle);
public override int GetRootCount(Scene scene) => GetRootCount(scene.handle);
public static int GetRootCount(int handle)
{
return ICallManager.GetICall<d_GetRootCountInternal>("UnityEngine.SceneManagement.Scene::GetRootCountInternal")
.Invoke(handle);
}
internal static bool triedToGetColorBlockProps;
internal static PropertyInfo _normalColorProp;
internal static PropertyInfo _highlightColorProp;
internal static PropertyInfo _pressedColorProp;
internal static PropertyInfo _disabledColorProp;
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
colors.colorMultiplier = 1;
object boxed = (object)colors;
if (!triedToGetColorBlockProps)
{
triedToGetColorBlockProps = true;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "normalColor") is PropertyInfo norm && norm.CanWrite)
_normalColorProp = norm;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "highlightedColor") is PropertyInfo high && high.CanWrite)
_highlightColorProp = high;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "pressedColor") is PropertyInfo pres && pres.CanWrite)
_pressedColorProp = pres;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "disabledColor") is PropertyInfo disa && disa.CanWrite)
_disabledColorProp = disa;
}
try
{
if (normal != null)
{
if (_normalColorProp != null)
_normalColorProp.SetValue(boxed, (Color)normal);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_NormalColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)normal);
}
if (highlighted != null)
{
if (_highlightColorProp != null)
_highlightColorProp.SetValue(boxed, (Color)highlighted);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_HighlightedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)highlighted);
}
if (pressed != null)
{
if (_pressedColorProp != null)
_pressedColorProp.SetValue(boxed, (Color)pressed);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_PressedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)pressed);
}
if (disabled != null)
{
if (_disabledColorProp != null)
_disabledColorProp.SetValue(boxed, (Color)disabled);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_DisabledColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)disabled);
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
colors = (ColorBlock)boxed;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock _colorBlock)
{
try
{
selectable = selectable.TryCast<Selectable>();
ReflectionUtility.GetPropertyInfo(typeof(Selectable), "m_Colors")
.SetValue(selectable, _colorBlock, null);
ReflectionUtility.GetMethodInfo(typeof(Selectable), "OnSetProperty")
.Invoke(selectable, ArgumentUtility.EmptyArgs);
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
}
}
public static class Il2CppExtensions
{
public static void AddListener(this UnityEvent action, Action listener)
{
action.AddListener(listener);
}
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.AddListener(listener);
}
public static void RemoveListener(this UnityEvent action, Action listener)
{
action.RemoveListener(listener);
}
public static void RemoveListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.RemoveListener(listener);
}
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlHeight = value;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlWidth = value;
}
#endif

View File

@ -1,64 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityEngine;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public class Il2CppTextureUtil : TextureUtilProvider
{
public override Texture2D NewTexture2D(int width, int height)
=> new Texture2D((int)width, (int)height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
internal delegate void d_Blit2(IntPtr source, IntPtr dest);
public override void Blit(Texture2D tex, RenderTexture rt)
{
var iCall = ICallManager.GetICall<d_Blit2>("UnityEngine.Graphics::Blit2");
iCall.Invoke(tex.Pointer, rt.Pointer);
}
// byte[] ImageConversion.EncodeToPNG(this Texture2D image);
internal delegate IntPtr d_EncodeToPNG(IntPtr tex);
public override byte[] EncodeToPNG(Texture2D tex)
{
var iCall = ICallManager.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG");
IntPtr ptr = iCall.Invoke(tex.Pointer);
if (ptr == IntPtr.Zero)
return null;
return new Il2CppStructArray<byte>(ptr);
}
// Sprite Sprite.Create
public override Sprite CreateSprite(Texture2D texture)
{
return CreateSpriteImpl(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero, 100f, 0u, Vector4.zero);
}
internal delegate IntPtr d_CreateSprite(IntPtr texture, ref Rect rect, ref Vector2 pivot, float pixelsPerUnit,
uint extrude, int meshType, ref Vector4 border, bool generateFallbackPhysicsShape);
public static Sprite CreateSpriteImpl(Texture texture, Rect rect, Vector2 pivot, float pixelsPerUnit, uint extrude, Vector4 border)
{
var iCall = ICallManager.GetICall<d_CreateSprite>("UnityEngine.Sprite::CreateSprite_Injected");
var ptr = iCall.Invoke(texture.Pointer, ref rect, ref pivot, pixelsPerUnit, extrude, 1, ref border, false);
if (ptr == IntPtr.Zero)
return null;
else
return new Sprite(ptr);
}
}
}
#endif

View File

@ -0,0 +1,189 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BF = System.Reflection.BindingFlags;
using System.Text;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Runtime
{
public class Il2CppProvider : RuntimeHelper
{
public override void SetupEvents()
{
try
{
Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
ExplorerCore.Log(ex);
}
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.LogUnity(condition, type);
}
public override string[] DefaultReflectionBlacklist => defaultIl2CppBlacklist.ToArray();
// These methods currently cause a crash in most il2cpp games,
// even from doing "GetParameters()" on the MemberInfo.
// Blacklisting until the issue is fixed in Unhollower.
public static HashSet<string> defaultIl2CppBlacklist = new HashSet<string>
{
// These were deprecated a long time ago, still show up in some IL2CPP games for some reason
"UnityEngine.MonoBehaviour.allowPrefabModeInPlayMode",
"UnityEngine.MonoBehaviour.runInEditMode",
"UnityEngine.Component.animation",
"UnityEngine.Component.audio",
"UnityEngine.Component.camera",
"UnityEngine.Component.collider",
"UnityEngine.Component.collider2D",
"UnityEngine.Component.constantForce",
"UnityEngine.Component.hingeJoint",
"UnityEngine.Component.light",
"UnityEngine.Component.networkView",
"UnityEngine.Component.particleSystem",
"UnityEngine.Component.renderer",
"UnityEngine.Component.rigidbody",
"UnityEngine.Component.rigidbody2D",
"UnityEngine.Light.flare",
// These can cause a crash in IL2CPP
"Il2CppSystem.Type.DeclaringMethod",
"Il2CppSystem.RuntimeType.DeclaringMethod",
"Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData",
"Unity.Profiling.ProfilerRecorder.CopyTo",
"Unity.Profiling.ProfilerRecorder.StartNew",
"UnityEngine.Analytics.Analytics.RegisterEvent",
"UnityEngine.Analytics.Analytics.SendEvent",
"UnityEngine.Analytics.ContinuousEvent+ConfigureEventDelegate.Invoke",
"UnityEngine.Analytics.ContinuousEvent.ConfigureEvent",
"UnityEngine.Animations.AnimationLayerMixerPlayable.Create",
"UnityEngine.Animations.AnimationLayerMixerPlayable.CreateHandle",
"UnityEngine.Animations.AnimationMixerPlayable.Create",
"UnityEngine.Animations.AnimationMixerPlayable.CreateHandle",
"UnityEngine.AssetBundle.RecompressAssetBundleAsync",
"UnityEngine.Audio.AudioMixerPlayable.Create",
"UnityEngine.BoxcastCommand.ScheduleBatch",
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
"UnityEngine.Canvas.renderingDisplaySize",
"UnityEngine.CapsulecastCommand.ScheduleBatch",
"UnityEngine.Collider2D.Cast",
"UnityEngine.Collider2D.Raycast",
"UnityEngine.ComputeBuffer+BeginBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer+EndBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer.BeginBufferWrite",
"UnityEngine.ComputeBuffer.EndBufferWrite",
"UnityEngine.Cubemap+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Cubemap+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Cubemap.SetPixelDataImpl",
"UnityEngine.Cubemap.SetPixelDataImplArray",
"UnityEngine.CubemapArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.CubemapArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.CubemapArray.SetPixelDataImpl",
"UnityEngine.CubemapArray.SetPixelDataImplArray",
"UnityEngine.Experimental.Playables.MaterialEffectPlayable.Create",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstanceDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstance_Procedural_InjectedDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural_Injected",
"UnityEngine.Experimental.Rendering.RayTracingShader+DispatchDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingShader.Dispatch",
"UnityEngine.Experimental.Rendering.RenderPassAttachment.Clear",
"UnityEngine.GUI.DoButtonGrid",
"UnityEngine.GUI.Slider",
"UnityEngine.GUI.Toolbar",
"UnityEngine.Graphics.DrawMeshInstancedIndirect",
"UnityEngine.Graphics.DrawMeshInstancedProcedural",
"UnityEngine.Graphics.DrawProcedural",
"UnityEngine.Graphics.DrawProceduralIndirect",
"UnityEngine.Graphics.DrawProceduralIndirectNow",
"UnityEngine.Graphics.DrawProceduralNow",
"UnityEngine.LineRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.LineRenderer.BakeMesh",
"UnityEngine.Mesh.GetIndices",
"UnityEngine.Mesh.GetTriangles",
"UnityEngine.Mesh.SetIndices",
"UnityEngine.Mesh.SetTriangles",
"UnityEngine.Physics2D.BoxCast",
"UnityEngine.Physics2D.CapsuleCast",
"UnityEngine.Physics2D.CircleCast",
"UnityEngine.PhysicsScene.BoxCast",
"UnityEngine.PhysicsScene.CapsuleCast",
"UnityEngine.PhysicsScene.OverlapBox",
"UnityEngine.PhysicsScene.OverlapCapsule",
"UnityEngine.PhysicsScene.SphereCast",
"UnityEngine.PhysicsScene2D.BoxCast",
"UnityEngine.PhysicsScene2D.CapsuleCast",
"UnityEngine.PhysicsScene2D.CircleCast",
"UnityEngine.PhysicsScene2D.GetRayIntersection",
"UnityEngine.PhysicsScene2D.Linecast",
"UnityEngine.PhysicsScene2D.OverlapArea",
"UnityEngine.PhysicsScene2D.OverlapBox",
"UnityEngine.PhysicsScene2D.OverlapCapsule",
"UnityEngine.PhysicsScene2D.OverlapCircle",
"UnityEngine.PhysicsScene2D.OverlapCollider",
"UnityEngine.PhysicsScene2D.OverlapPoint",
"UnityEngine.PhysicsScene2D.Raycast",
"UnityEngine.Playables.Playable.Create",
"UnityEngine.Profiling.CustomSampler.Create",
"UnityEngine.RaycastCommand.ScheduleBatch",
"UnityEngine.RemoteConfigSettings+QueueConfigDelegate.Invoke",
"UnityEngine.RemoteConfigSettings.QueueConfig",
"UnityEngine.RenderTexture.GetTemporaryImpl",
"UnityEngine.Rendering.AsyncGPUReadback.Request",
"UnityEngine.Rendering.AttachmentDescriptor.ConfigureClear",
"UnityEngine.Rendering.BatchRendererGroup+AddBatch_InjectedDelegate.Invoke",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch_Injected",
"UnityEngine.Rendering.CommandBuffer+Internal_DispatchRaysDelegate.Invoke",
"UnityEngine.Rendering.CommandBuffer.DispatchRays",
"UnityEngine.Rendering.CommandBuffer.DrawMeshInstancedProcedural",
"UnityEngine.Rendering.CommandBuffer.Internal_DispatchRays",
"UnityEngine.Rendering.CommandBuffer.ResolveAntiAliasedSurface",
"UnityEngine.Rendering.ScriptableRenderContext.BeginRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.SetupCameraProperties",
"UnityEngine.Rigidbody2D.Cast",
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
"UnityEngine.Scripting.GarbageCollector.CollectIncremental",
"UnityEngine.SpherecastCommand.ScheduleBatch",
"UnityEngine.Texture.GetPixelDataSize",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2D.SetPixelDataImpl",
"UnityEngine.Texture2D.SetPixelDataImplArray",
"UnityEngine.Texture2DArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2DArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2DArray.SetPixelDataImpl",
"UnityEngine.Texture2DArray.SetPixelDataImplArray",
"UnityEngine.Texture3D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture3D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture3D.SetPixelDataImpl",
"UnityEngine.Texture3D.SetPixelDataImplArray",
"UnityEngine.TrailRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.TrailRenderer.BakeMesh",
"UnityEngine.WWW.LoadFromCacheOrDownload",
"UnityEngine.XR.InputDevice.SendHapticImpulse",
};
}
}
#endif

View File

@ -1,119 +0,0 @@
#if MONO
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer;
namespace UnityExplorer.Core.Runtime.Mono
{
public class MonoProvider : RuntimeProvider
{
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.Mono;
TextureUtil = new MonoTextureUtil();
}
public override void SetupEvents()
{
Application.logMessageReceived += Application_logMessageReceived;
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
=> ExplorerCore.LogUnity(condition, type);
public override void StartCoroutine(IEnumerator routine)
=> ExplorerBehaviour.Instance.StartCoroutine(routine);
public override void Update()
{
}
public override T AddComponent<T>(GameObject obj, Type type)
=> (T)obj.AddComponent(type);
public override ScriptableObject CreateScriptable(Type type)
=> ScriptableObject.CreateInstance(type);
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
=> raycaster.Raycast(data, list);
public override string LayerToName(int layer)
=> LayerMask.LayerToName(layer);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
=> Resources.FindObjectsOfTypeAll(type);
public override GameObject[] GetRootGameObjects(Scene scene)
=> scene.isLoaded ? scene.GetRootGameObjects() : new GameObject[0];
public override int GetRootCount(Scene scene)
=> scene.rootCount;
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
if (normal != null)
colors.normalColor = (Color)normal;
if (highlighted != null)
colors.highlightedColor = (Color)highlighted;
if (pressed != null)
colors.pressedColor = (Color)pressed;
if (disabled != null)
colors.disabledColor = (Color)disabled;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
=> selectable.colors = colors;
}
}
public static class MonoExtensions
{
// Helpers to use the same style of AddListener that IL2CPP uses.
public static void AddListener(this UnityEvent _event, Action listener)
=> _event.AddListener(new UnityAction(listener));
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
=> _event.AddListener(new UnityAction<T>(listener));
public static void RemoveListener(this UnityEvent _event, Action listener)
=> _event.RemoveListener(new UnityAction(listener));
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
=> _event.RemoveListener(new UnityAction<T>(listener));
// Doesn't exist in NET 3.5
public static void Clear(this StringBuilder sb)
=> sb.Remove(0, sb.Length);
// These properties don't exist in some earlier games, so null check before trying to set them.
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlHeight")
?.SetValue(group, value, null);
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlWidth")
?.SetValue(group, value, null);
}
#endif

View File

@ -1,53 +0,0 @@
#if MONO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
namespace UnityExplorer.Core.Runtime.Mono
{
public class MonoTextureUtil : TextureUtilProvider
{
public override void Blit(Texture2D tex, RenderTexture rt)
=> Graphics.Blit(tex, rt);
public override Sprite CreateSprite(Texture2D texture)
=> Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
//public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
// => tex.LoadImage(data, markNonReadable);
public override Texture2D NewTexture2D(int width, int height)
=> new Texture2D(width, height);
public override byte[] EncodeToPNG(Texture2D tex)
=> EncodeToPNGSafe(tex);
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod;
public static byte[] EncodeToPNGSafe(Texture2D tex)
{
return EncodeToPNGMethod.IsStatic
? (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex })
: (byte[])EncodeToPNGMethod.Invoke(tex, ArgumentUtility.EmptyArgs);
}
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
if (method != null)
return m_encodeToPNGMethod = method;
ExplorerCore.Log("ERROR: Cannot get any EncodeToPNG method!");
return null;
}
}
}
#endif

View File

@ -0,0 +1,29 @@
#if MONO
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer;
namespace UnityExplorer.Core.Runtime
{
public class MonoProvider : RuntimeHelper
{
public override void SetupEvents()
{
Application.logMessageReceived += Application_logMessageReceived;
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
=> ExplorerCore.LogUnity(condition, type);
}
}
#endif

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Runtime
{
public enum RuntimeContext
{
Mono,
IL2CPP
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UniverseLib;
namespace UnityExplorer.Core.Runtime
{
public abstract class RuntimeHelper
{
public static RuntimeHelper Instance;
public static void Init()
{
#if CPP
Instance = new Il2CppProvider();
#else
Instance = new MonoProvider();
#endif
Instance.SetupEvents();
LoadBlacklistString(ConfigManager.Reflection_Signature_Blacklist.Value);
ConfigManager.Reflection_Signature_Blacklist.OnValueChanged += (string val) =>
{
LoadBlacklistString(val);
};
}
public abstract void SetupEvents();
#region Reflection Blacklist
private static readonly HashSet<string> currentBlacklist = new HashSet<string>();
public virtual string[] DefaultReflectionBlacklist => new string[0];
public static void LoadBlacklistString(string blacklist)
{
try
{
if (string.IsNullOrEmpty(blacklist) && !Instance.DefaultReflectionBlacklist.Any())
return;
try
{
var sigs = blacklist.Split(';');
foreach (var sig in sigs)
{
var s = sig.Trim();
if (string.IsNullOrEmpty(s))
continue;
if (!currentBlacklist.Contains(s))
currentBlacklist.Add(s);
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception parsing blacklist string: {ex.ReflectionExToString()}");
}
foreach (var sig in Instance.DefaultReflectionBlacklist)
{
if (!currentBlacklist.Contains(sig))
currentBlacklist.Add(sig);
}
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}");
}
}
public static bool IsBlacklisted(MemberInfo member)
{
if (string.IsNullOrEmpty(member.DeclaringType?.Namespace))
return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig);
}
#endregion
}
}

View File

@ -1,75 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
// Intentionally project-wide namespace so that its always easily accessible.
namespace UnityExplorer
{
public abstract class RuntimeProvider
{
public static RuntimeProvider Instance;
public TextureUtilProvider TextureUtil;
public RuntimeProvider()
{
Initialize();
SetupEvents();
}
public static void Init() =>
#if CPP
Instance = new Core.Runtime.Il2Cpp.Il2CppProvider();
#else
Instance = new Core.Runtime.Mono.MonoProvider();
#endif
public abstract void Initialize();
public abstract void SetupEvents();
public abstract void StartCoroutine(IEnumerator routine);
public abstract void Update();
// Unity API handlers
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
public abstract ScriptableObject CreateScriptable(Type type);
public abstract string LayerToName(int layer);
public abstract UnityEngine.Object[] FindObjectsOfTypeAll(Type type);
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
public abstract GameObject[] GetRootGameObjects(Scene scene);
public abstract int GetRootCount(Scene scene);
public void SetColorBlockAuto(Selectable selectable, Color baseColor)
=> SetColorBlock(selectable, baseColor, baseColor * 1.2f, baseColor * 0.8f);
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null);
internal virtual void ProcessOnPostRender()
{
}
internal virtual void ProcessFixedUpdate()
{
}
}
}

View File

@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.Core.Runtime
{
public abstract class TextureUtilProvider
{
public static TextureUtilProvider Instance;
public TextureUtilProvider()
{
Instance = this;
}
public abstract byte[] EncodeToPNG(Texture2D tex);
public abstract Texture2D NewTexture2D(int width, int height);
public abstract void Blit(Texture2D tex, RenderTexture rt);
//public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
public abstract Sprite CreateSprite(Texture2D texture);
public static bool IsReadable(Texture2D tex)
{
try
{
// This will cause an exception if it's not readable.
// Reason for doing it this way is not all Unity versions
// ship with the 'Texture.isReadable' property.
tex.GetPixel(0, 0);
return true;
}
catch
{
return false;
}
}
//public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
//{
// if (!File.Exists(filePath))
// return false;
//
// return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
//}
public static Texture2D Copy(Texture2D orig, Rect rect)
{
if (!IsReadable(orig))
orig = ForceReadTexture(orig);
Color[] pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
newTex.SetPixels(pixels);
return newTex;
}
public static Texture2D ForceReadTexture(Texture2D tex)
{
try
{
FilterMode origFilter = tex.filterMode;
tex.filterMode = FilterMode.Point;
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
rt.filterMode = FilterMode.Point;
RenderTexture.active = rt;
Instance.Blit(tex, rt);
var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
_newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
_newTex.Apply(false, false);
RenderTexture.active = null;
tex.filterMode = origFilter;
return _newTex;
}
catch (Exception e)
{
ExplorerCore.Log($"Exception on ForceReadTexture: {e.ToString()}");
return default;
}
}
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
{
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
byte[] data;
string savepath = $@"{dir}\{name}.png";
// Make sure we can EncodeToPNG it.
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
tex = ForceReadTexture(tex);
if (isDTXnmNormal)
{
tex = DTXnmToRGBA(tex);
tex.Apply(false, false);
}
data = Instance.EncodeToPNG(tex);
if (data == null || !data.Any())
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
else
File.WriteAllBytes(savepath, data);
}
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
public static Texture2D DTXnmToRGBA(Texture2D tex)
{
Color[] colors = tex.GetPixels();
for (int i = 0; i < colors.Length; i++)
{
var c = colors[i];
c.r = c.a * 2 - 1; // red <- alpha
c.g = c.g * 2 - 1; // green is always the same
var rg = new Vector2(c.r, c.g); //this is the red-green vector
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
colors[i] = new Color(
(c.r * 0.5f) + 0.5f,
(c.g * 0.5f) + 0.25f,
(c.b * 0.5f) + 0.5f
);
}
var newtex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
newtex.SetPixels(colors);
return newtex;
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class ArgumentUtility
{
public static readonly Type[] EmptyTypes = new Type[0];
public static readonly object[] EmptyArgs = new object[0];
public static readonly Type[] ParseArgs = new Type[] { typeof(string) };
}
}

View File

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class IOUtility
{
private static readonly char[] invalidDirectoryCharacters = Path.GetInvalidPathChars();
private static readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars();
public static string EnsureValidDirectory(string path)
{
path = string.Concat(path.Split(invalidDirectoryCharacters));
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
return path;
}
public static string EnsureValidFilename(string filename)
{
return string.Concat(filename.Split(invalidFilenameCharacters));
}
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class MiscUtility
{
/// <summary>
/// Check if a string contains another string, case-insensitive.
/// </summary>
public static bool ContainsIgnoreCase(this string _this, string s)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
}
/// <summary>
/// Just to allow Enum to do .HasFlag() in NET 3.5
/// </summary>
public static bool HasFlag(this Enum flags, Enum value)
{
ulong flag = Convert.ToUInt64(value);
return (Convert.ToUInt64(flags) & flag) == flag;
}
}
}

View File

@ -1,414 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace UnityExplorer
{
public static class ParseUtility
{
private static readonly HashSet<Type> nonPrimitiveTypes = new HashSet<Type>
{
typeof(string),
typeof(decimal),
typeof(DateTime),
};
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
// And also for formatting a sequence of those numbers, ie a Vector3, Color etc
public static readonly string NumberFormatString = $"0.####";
private static readonly Dictionary<int, string> numSequenceStrings = new Dictionary<int, string>();
public static string FormatDecimalSequence(params object[] numbers)
{
if (numbers.Length <= 0)
return null;
return string.Format(CultureInfo.CurrentCulture, GetSequenceFormatString(numbers.Length), numbers);
}
public static string GetSequenceFormatString(int count)
{
if (count <= 0)
return null;
if (numSequenceStrings.ContainsKey(count))
return numSequenceStrings[count];
string[] strings = new string[count];
for (int i = 0; i < count; i++)
strings[i] = $"{{{i}:{NumberFormatString}}}";
string ret = string.Join(" ", strings);
numSequenceStrings.Add(count, ret);
return ret;
}
// Main parsing API
public static bool CanParse(Type type)
{
return !string.IsNullOrEmpty(type?.FullName)
&& (type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName));
}
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
{
obj = null;
parseException = null;
if (type == null)
return false;
if (type == typeof(string))
{
obj = input;
return true;
}
if (type.IsEnum)
{
try
{
obj = Enum.Parse(type, input);
return true;
}
catch (Exception ex)
{
parseException = ex.GetInnerMostException();
return false;
}
}
try
{
if (customTypes.ContainsKey(type.FullName))
{
obj = customTypes[type.FullName].Invoke(input);
}
else
{
obj = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs)
.Invoke(null, new object[] { input });
}
return true;
}
catch (Exception ex)
{
ex = ex.GetInnerMostException();
parseException = ex;
}
return false;
}
private static readonly HashSet<Type> formattedTypes = new HashSet<Type>
{
typeof(float),
typeof(double),
typeof(decimal)
};
public static string ToStringForInput(object obj, Type type)
{
if (type == null || obj == null)
return null;
if (type == typeof(string))
return obj as string;
if (type.IsEnum)
{
return Enum.IsDefined(type, obj)
? Enum.GetName(type, obj)
: obj.ToString();
}
try
{
if (customTypes.ContainsKey(type.FullName))
{
return customTypesToString[type.FullName].Invoke(obj);
}
else if (formattedTypes.Contains(type))
{
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
.Invoke(obj, new object[] { NumberFormatString, CultureInfo.CurrentCulture })
as string;
}
else
return obj.ToString();
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception formatting object for input: {ex}");
return null;
}
}
private static readonly Dictionary<string, string> typeInputExamples = new Dictionary<string, string>();
public static string GetExampleInput(Type type)
{
if (!typeInputExamples.ContainsKey(type.AssemblyQualifiedName))
{
try
{
if (type.IsEnum)
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
else
{
var instance = Activator.CreateInstance(type);
typeInputExamples.Add(type.AssemblyQualifiedName, ToStringForInput(instance, type));
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception generating default instance for example input for '" + type.FullName + "'");
ExplorerCore.Log(ex);
return "";
}
}
return typeInputExamples[type.AssemblyQualifiedName];
}
#region Custom parse methods
internal delegate object ParseMethod(string input);
private static readonly Dictionary<string, ParseMethod> customTypes = new Dictionary<string, ParseMethod>
{
{ typeof(Vector2).FullName, TryParseVector2 },
{ typeof(Vector3).FullName, TryParseVector3 },
{ typeof(Vector4).FullName, TryParseVector4 },
{ typeof(Quaternion).FullName, TryParseQuaternion },
{ typeof(Rect).FullName, TryParseRect },
{ typeof(Color).FullName, TryParseColor },
{ typeof(Color32).FullName, TryParseColor32 },
{ typeof(LayerMask).FullName, TryParseLayerMask },
};
internal delegate string ToStringMethod(object obj);
private static readonly Dictionary<string, ToStringMethod> customTypesToString = new Dictionary<string, ToStringMethod>
{
{ typeof(Vector2).FullName, Vector2ToString },
{ typeof(Vector3).FullName, Vector3ToString },
{ typeof(Vector4).FullName, Vector4ToString },
{ typeof(Quaternion).FullName, QuaternionToString },
{ typeof(Rect).FullName, RectToString },
{ typeof(Color).FullName, ColorToString },
{ typeof(Color32).FullName, Color32ToString },
{ typeof(LayerMask).FullName, LayerMaskToString },
};
// Vector2
public static object TryParseVector2(string input)
{
Vector2 vector = default;
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
return vector;
}
public static string Vector2ToString(object obj)
{
if (!(obj is Vector2 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y);
}
// Vector3
public static object TryParseVector3(string input)
{
Vector3 vector = default;
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
return vector;
}
public static string Vector3ToString(object obj)
{
if (!(obj is Vector3 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y, vector.z);
}
// Vector4
public static object TryParseVector4(string input)
{
Vector4 vector = default;
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
vector.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return vector;
}
public static string Vector4ToString(object obj)
{
if (!(obj is Vector4 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y, vector.z, vector.w);
}
// Quaternion
public static object TryParseQuaternion(string input)
{
Vector3 vector = default;
var split = input.Split(' ');
if (split.Length == 4)
{
Quaternion quat = default;
quat.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
quat.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
quat.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
quat.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return quat;
}
else
{
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
return Quaternion.Euler(vector);
}
}
public static string QuaternionToString(object obj)
{
if (!(obj is Quaternion quaternion))
return null;
Vector3 vector = quaternion.eulerAngles;
return FormatDecimalSequence(vector.x, vector.y, vector.z);
}
// Rect
public static object TryParseRect(string input)
{
Rect rect = default;
var split = input.Split(' ');
rect.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
rect.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
rect.width = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
rect.height = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return rect;
}
public static string RectToString(object obj)
{
if (!(obj is Rect rect))
return null;
return FormatDecimalSequence(rect.x, rect.y, rect.width, rect.height);
}
// Color
public static object TryParseColor(string input)
{
Color color = default;
var split = input.Split(' ');
color.r = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
color.g = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
color.b = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
if (split.Length > 3)
color.a = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
else
color.a = 1;
return color;
}
public static string ColorToString(object obj)
{
if (!(obj is Color color))
return null;
return FormatDecimalSequence(color.r, color.g, color.b, color.a);
}
// Color32
public static object TryParseColor32(string input)
{
Color32 color = default;
var split = input.Split(' ');
color.r = byte.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
color.g = byte.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
color.b = byte.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
if (split.Length > 3)
color.a = byte.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
else
color.a = 255;
return color;
}
public static string Color32ToString(object obj)
{
if (!(obj is Color32 color))
return null;
// ints, this is fine
return $"{color.r} {color.g} {color.b} {color.a}";
}
// Layermask (Int32)
public static object TryParseLayerMask(string input)
{
return (LayerMask)int.Parse(input);
}
public static string LayerMaskToString(object obj)
{
if (!(obj is LayerMask mask))
return null;
return mask.value.ToString();
}
#endregion
}
}

View File

@ -1,294 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer
{
/// <summary>
/// Syntax-highlights a member's signature, by either the Type name or a Type and Member together.
/// </summary>
public static class SignatureHighlighter
{
public const string NAMESPACE = "#a8a8a8";
public const string CONST = "#92c470";
public const string CLASS_STATIC = "#3a8d71";
public const string CLASS_INSTANCE = "#2df7b2";
public const string STRUCT = "#0fba3a";
public const string INTERFACE = "#9b9b82";
public const string FIELD_STATIC = "#8d8dc6";
public const string FIELD_INSTANCE = "#c266ff";
public const string METHOD_STATIC = "#b55b02";
public const string METHOD_INSTANCE = "#ff8000";
public const string PROP_STATIC = "#588075";
public const string PROP_INSTANCE = "#55a38e";
public const string LOCAL_ARG = "#a6e9e9";
internal const string ARRAY_TOKEN = "[]";
internal const string OPEN_COLOR = "<color=";
internal const string CLOSE_COLOR = "</color>";
internal const string OPEN_ITALIC = "<i>";
internal const string CLOSE_ITALIC = "</i>";
public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f);
public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f);
public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f);
public static readonly string keywordBlueHex = KeywordBlue.ToHex();
public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f);
internal static string GetClassColor(Type type)
{
if (type.IsAbstract && type.IsSealed)
return CLASS_STATIC;
else if (type.IsEnum || type.IsGenericParameter)
return CONST;
else if (type.IsValueType)
return STRUCT;
else if (type.IsInterface)
return INTERFACE;
else
return CLASS_INSTANCE;
}
//private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
private static bool GetNamespace(Type type, out string ns)
{
var ret = !string.IsNullOrEmpty(ns = type.Namespace?.Trim());
return ret;
}
public static string Parse(Type type, bool includeNamespace, MemberInfo memberInfo = null)
{
if (type == null)
throw new ArgumentNullException("type");
var syntaxBuilder = new StringBuilder();
// Namespace
bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter);
if (!isGeneric)
{
if (includeNamespace && GetNamespace(type, out string ns))
syntaxBuilder.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.');
// Declaring type
var declaring = type.DeclaringType;
while (declaring != null)
{
syntaxBuilder.Append(HighlightType(declaring));
syntaxBuilder.Append('.');
declaring = declaring.DeclaringType;
}
}
// Highlight the type name
syntaxBuilder.Append(HighlightType(type));
// If memberInfo, highlight the member info
if (memberInfo != null)
{
syntaxBuilder.Append('.');
int start = syntaxBuilder.Length - 1;
syntaxBuilder.Append(OPEN_COLOR)
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
.Append('>')
.Append(memberInfo.Name)
.Append(CLOSE_COLOR);
if (isStatic)
{
syntaxBuilder.Insert(start, OPEN_ITALIC);
syntaxBuilder.Append(CLOSE_ITALIC);
}
if (memberInfo is MethodInfo method)
{
var args = method.GetGenericArguments();
if (args.Length > 0)
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
}
}
return syntaxBuilder.ToString();
}
private static readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
private static bool EndsWith(this StringBuilder sb, string _string)
{
int len = _string.Length;
if (sb.Length < len)
return false;
int stringpos = 0;
for (int i = sb.Length - len; i < sb.Length; i++, stringpos++)
{
if (sb[i] != _string[stringpos])
return false;
}
return true;
}
private static string HighlightType(Type type)
{
string key = type.ToString();
if (typeToRichType.ContainsKey(key))
return typeToRichType[key];
var sb = new StringBuilder(type.Name);
bool isArray = false;
if (sb.EndsWith(ARRAY_TOKEN))
{
isArray = true;
sb.Remove(sb.Length - 2, 2);
type = type.GetElementType();
}
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
{
sb.Insert(0, $"<color={CONST}>");
sb.Append(CLOSE_COLOR);
}
else
{
var args = type.GetGenericArguments();
if (args.Length > 0)
{
// remove the `N from the end of the type name
// this could actually be >9 in some cases, so get the length of the length string and use that.
// eg, if it was "List`15", we would remove the ending 3 chars
int suffixLen = 1 + args.Length.ToString().Length;
// make sure the typename actually has expected "`N" format.
if (sb[sb.Length - suffixLen] == '`')
sb.Remove(sb.Length - suffixLen, suffixLen);
}
// highlight the base name itself
// do this after removing the `N suffix, so only the name itself is in the color tags.
sb.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
sb.Append(CLOSE_COLOR);
// parse the generic args, if any
if (args.Length > 0)
{
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
}
}
if (isArray)
sb.Append('[').Append(']');
var ret = sb.ToString();
typeToRichType.Add(key, ret);
return ret;
}
public static string ParseGenericArgs(Type[] args, bool isGenericParams = false)
{
if (args.Length < 1)
return string.Empty;
var sb = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
if (i > 0)
sb.Append(',').Append(' ');
if (isGenericParams)
{
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
continue;
}
sb.Append(HighlightType(args[i]));
}
return sb.ToString();
}
public static string GetMemberInfoColor(MemberTypes type)
{
switch (type)
{
case MemberTypes.Method: return METHOD_INSTANCE;
case MemberTypes.Property: return PROP_INSTANCE;
case MemberTypes.Field: return FIELD_INSTANCE;
default: return null;
}
}
public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic)
{
isStatic = false;
if (memberInfo is FieldInfo fi)
{
if (fi.IsStatic)
{
isStatic = true;
return FIELD_STATIC;
}
return FIELD_INSTANCE;
}
else if (memberInfo is MethodInfo mi)
{
if (mi.IsStatic)
{
isStatic = true;
return METHOD_STATIC;
}
return METHOD_INSTANCE;
}
else if (memberInfo is PropertyInfo pi)
{
if (pi.GetAccessors(true)[0].IsStatic)
{
isStatic = true;
return PROP_STATIC;
}
return PROP_INSTANCE;
}
//else if (memberInfo is EventInfo ei)
//{
// if (ei.GetAddMethod().IsStatic)
// {
// isStatic = true;
// return EVENT_STATIC;
// }
// return EVENT_INSTANCE;
//}
throw new NotImplementedException(memberInfo.GetType().Name + " is not supported");
}
}
}

View File

@ -1,170 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer
{
public static class ToStringUtility
{
internal static Dictionary<string, MethodInfo> toStringMethods = new Dictionary<string, MethodInfo>();
private const string nullString = "<color=grey>null</color>";
private const string nullUnknown = nullString + " (?)";
private const string destroyedString = "<color=red>Destroyed</color>";
private const string untitledString = "<i><color=grey>untitled</color></i>";
private const string eventSystemNamespace = "UnityEngine.EventSystem";
public static string PruneString(string s, int chars = 200, int lines = 5)
{
if (string.IsNullOrEmpty(s))
return s;
var sb = new StringBuilder(Math.Max(chars, s.Length));
int newlines = 0;
for (int i = 0; i < s.Length; i++)
{
if (newlines >= lines || i >= chars)
{
sb.Append("...");
break;
}
char c = s[i];
if (c == '\r' || c == '\n')
newlines++;
sb.Append(c);
}
return sb.ToString();
}
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
{
if (value.IsNullOrDestroyed() && fallbackType == null)
return nullUnknown;
Type type = value?.GetActualType() ?? fallbackType;
string richType = SignatureHighlighter.Parse(type, includeNamespace);
var sb = new StringBuilder();
if (value.IsNullOrDestroyed())
{
if (value == null)
{
sb.Append(nullString);
AppendRichType(sb, richType);
return sb.ToString();
}
else // destroyed unity object
{
sb.Append(destroyedString);
AppendRichType(sb, richType);
return sb.ToString();
}
}
if (value is UnityEngine.Object obj)
{
if (string.IsNullOrEmpty(obj.name))
sb.Append(untitledString);
else
{
sb.Append('"');
sb.Append(PruneString(obj.name, 50, 1));
sb.Append('"');
}
AppendRichType(sb, richType);
}
else if (type.FullName.StartsWith(eventSystemNamespace))
{
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
sb.Append(richType);
}
else
{
var toString = ToString(value);
if (type.IsGenericType
|| toString == type.FullName
|| toString == $"{type.FullName} {type.FullName}"
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
{
sb.Append(richType);
}
else // the ToString contains some actual implementation, use that value.
{
sb.Append(PruneString(toString, 200, 5));
AppendRichType(sb, richType);
}
}
return sb.ToString();
}
private static void AppendRichType(StringBuilder sb, string richType)
{
sb.Append(' ');
sb.Append('(');
sb.Append(richType);
sb.Append(')');
}
private static string ToString(object value)
{
if (value.IsNullOrDestroyed())
{
if (value == null)
return nullString;
else // destroyed unity object
return destroyedString;
}
var type = value.GetActualType();
// Find and cache the ToString method for this Type, if haven't already.
if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName))
{
var toStringMethod = type.GetMethod("ToString", ArgumentUtility.EmptyTypes);
toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod);
}
// Invoke the ToString method on the object
value = value.TryCast(type);
string toString;
try
{
toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, ArgumentUtility.EmptyArgs);
}
catch (Exception ex)
{
toString = ex.ReflectionExToString();
}
toString = ReflectionUtility.ProcessTypeInString(type, toString);
#if CPP
if (value is Il2CppSystem.Type cppType)
{
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
if (monoType != null)
toString = ReflectionUtility.ProcessTypeInString(monoType, toString);
}
#endif
return toString;
}
}
}

View File

@ -1,124 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
// Project-wide namespace for accessibility
namespace UnityExplorer
{
public static class UnityHelpers
{
// Time helpers, can't use Time.time since timeScale will affect it.
// default 10ms (one frame at 100fps)
public static bool OccuredEarlierThanDefault(this float time)
{
return Time.realtimeSinceStartup - 0.01f >= time;
}
public static bool OccuredEarlierThan(this float time, float secondsAgo)
{
return Time.realtimeSinceStartup - secondsAgo >= time;
}
/// <summary>
/// Check if an object is null, and if it's a UnityEngine.Object then also check if it was destroyed.
/// </summary>
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
{
var unityObj = obj as Object;
if (obj == null)
{
if (!suppressWarning)
ExplorerCore.LogWarning("The target instance is null!");
return true;
}
else if (obj is Object)
{
if (!unityObj)
{
if (!suppressWarning)
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
return true;
}
}
return false;
}
/// <summary>
/// Get the full Transform heirarchy path for this provided Transform.
/// </summary>
public static string GetTransformPath(this Transform transform, bool includeSelf = false)
{
var sb = new StringBuilder();
if (includeSelf)
sb.Append(transform.name);
while (transform.parent)
{
transform = transform.parent;
sb.Insert(0, '/');
sb.Insert(0, transform.name);
}
return sb.ToString();
}
/// <summary>
/// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000
/// </summary>
public static string ToHex(this Color color)
{
byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255);
byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255);
byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255);
return $"{r:X2}{g:X2}{b:X2}";
}
/// <summary>
/// Assumes the string is a 6-digit RGB Hex color code (with optional leading #) which it will parse into a UnityEngine.Color.
/// Eg, FF0000 -> RGBA(1,0,0,1)
/// </summary>
public static Color ToColor(this string _string)
{
_string = _string.Replace("#", "");
if (_string.Length != 6)
return Color.magenta;
var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber);
var color = new Color
{
r = (float)(r / (decimal)255),
g = (float)(g / (decimal)255),
b = (float)(b / (decimal)255),
a = 1
};
return color;
}
private static PropertyInfo onEndEdit;
public static UnityEvent<string> GetOnEndEdit(this InputField _this)
{
if (onEndEdit == null)
onEndEdit = typeof(InputField).GetProperty("onEndEdit")
?? throw new Exception("Could not get InputField.onEndEdit property!");
return onEndEdit.GetValue(_this, null).TryCast<UnityEvent<string>>();
}
}
}

View File

@ -1,31 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Tests;
using UnityExplorer.UI;
using UnityExplorer.Inspectors;
using UnityExplorer.ObjectExplorer;
using UnityExplorer.UI.Panels;
using UnityExplorer.Core.Runtime;
using UniverseLib.Input;
namespace UnityExplorer
{
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.3.1";
public const string VERSION = "4.4.2";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
public static IExplorerLoader Loader { get; private set; }
public static RuntimeContext Context { get; internal set; }
public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID);
@ -43,38 +38,31 @@ namespace UnityExplorer
Log($"{NAME} {VERSION} initializing...");
ExplorerBehaviour.Setup();
if (!Directory.Exists(Loader.ExplorerFolder))
Directory.CreateDirectory(Loader.ExplorerFolder);
ConfigManager.Init(Loader.ConfigHandler);
ReflectionUtility.Init();
RuntimeProvider.Init();
RuntimeHelper.Init();
ExplorerBehaviour.Setup();
UniverseLib.Universe.Init(ConfigManager.Startup_Delay_Time.Value, LateInit, Log, new UniverseLib.Config.UUConfig
{
Disable_EventSystem_Override = ConfigManager.Disable_EventSystem_Override.Value,
Force_Unlock_Mouse = ConfigManager.Force_Unlock_Mouse.Value,
Unhollowed_Modules_Folder = loader.UnhollowedModulesFolder
});
SceneHandler.Init();
InputManager.Init();
RuntimeProvider.Instance.StartCoroutine(SetupCoroutine());
Log($"Finished core setup, waiting for UI setup...");
Log($"Finished core setup, waiting for late setup...");
}
// Do a delayed setup so that objects aren't destroyed instantly.
// This can happen for a multitude of reasons.
// Default delay is 1 second which is usually enough.
private static IEnumerator SetupCoroutine()
private static void LateInit()
{
yield return null;
float prevRealTime = Time.realtimeSinceStartup;
float delay = ConfigManager.Startup_Delay_Time.Value;
while (delay > 0)
{
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - prevRealTime);
delay -= diff;
prevRealTime = Time.realtimeSinceStartup;
yield return null;
}
Log($"Setting up late core features...");
SceneHandler.Init();
Log($"Creating UI...");
@ -88,19 +76,11 @@ namespace UnityExplorer
/// </summary>
public static void Update()
{
RuntimeProvider.Instance.Update();
UIManager.Update();
}
public static void FixedUpdate()
{
RuntimeProvider.Instance.ProcessFixedUpdate();
}
public static void OnPostRender()
{
RuntimeProvider.Instance.ProcessOnPostRender();
// check master toggle
if (InputManager.GetKeyDown(ConfigManager.Master_Toggle.Value))
UIManager.ShowMenu = !UIManager.ShowMenu;
}
#region LOGGING

View File

@ -6,6 +6,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.Hooks
{

View File

@ -4,8 +4,8 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.Hooks
{

View File

@ -7,6 +7,7 @@ using System.Text;
using HarmonyLib;
using Mono.CSharp;
using UnityExplorer.CSConsole;
using UniverseLib;
namespace UnityExplorer.Hooks
{
@ -17,6 +18,14 @@ namespace UnityExplorer.Hooks
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;
@ -117,13 +126,12 @@ namespace UnityExplorer.Hooks
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
int paramIdx = 0;
var parameters = targetMethod.GetParameters();
int paramIdx = 0;
foreach (var param in parameters)
{
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
codeBuilder.Append($", {pType.FullName} __{paramIdx}");
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
paramIdx++;
}
@ -177,13 +185,14 @@ namespace UnityExplorer.Hooks
codeBuilder.AppendLine("}");
//ExplorerCore.Log(codeBuilder.ToString());
return PatchSourceCode = codeBuilder.ToString();
}
public void TogglePatch()
{
Enabled = !Enabled;
if (Enabled)
if (!Enabled)
Patch();
else
Unpatch();
@ -194,6 +203,7 @@ namespace UnityExplorer.Hooks
try
{
patchProcessor.Patch();
Enabled = true;
}
catch (Exception ex)
@ -214,6 +224,7 @@ namespace UnityExplorer.Hooks
patchProcessor.Unpatch(finalizer);
if (transpiler != null)
patchProcessor.Unpatch(transpiler);
Enabled = false;
}
catch (Exception ex)

View File

@ -6,10 +6,13 @@ using System.Reflection;
using System.Text;
using HarmonyLib;
using UnityEngine;
using UnityExplorer.Core.Runtime;
using UnityExplorer.CSConsole;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.Hooks
{
@ -68,6 +71,8 @@ namespace UnityExplorer.Hooks
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
@ -104,7 +109,7 @@ namespace UnityExplorer.Hooks
currentAddEligableMethods.Clear();
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod /* || method.IsAbstract */ || ReflectionUtility.IsBlacklisted(method))
if (method.IsGenericMethod /* || method.IsAbstract */ || RuntimeHelper.IsBlacklisted(method))
continue;
currentAddEligableMethods.Add(method);
filteredEligableMethods.Add(method);
@ -163,6 +168,36 @@ namespace UnityExplorer.Hooks
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)
@ -191,40 +226,7 @@ namespace UnityExplorer.Hooks
}
}
// OnBorrow methods not needed
public void OnCellBorrowed(HookCell cell) { }
public void OnCellBorrowed(AddHookCell cell) { }
// Set eligable method 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);
}
}
// private static readonly string VOID_HIGHLIGHT = $"<color=#{SignatureHighlighter.keywordBlueHex}>void</color> ";
// ~~~~~~~~~~ Method syntax highlighting
private static readonly Dictionary<string, string> highlightedMethods = new Dictionary<string, string>();

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

@ -5,12 +5,15 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.Inspectors
{

View File

@ -6,6 +6,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.Inspectors
{
@ -32,7 +34,8 @@ namespace UnityExplorer.Inspectors
var root = base.CreateContent(parent);
// Add mask to button so text doesnt overlap on Close button
this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
//this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
this.Button.ButtonText.horizontalOverflow = HorizontalWrapMode.Wrap;
// Behaviour toggle

View File

@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI.Widgets;
namespace UnityExplorer.Inspectors
{

View File

@ -4,8 +4,10 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.UI;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.Inspectors
{

View File

@ -6,11 +6,13 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Inspectors.MouseInspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.Inspectors
{
@ -163,7 +165,7 @@ namespace UnityExplorer.Inspectors
mousePos.y -= 10;
// calculate and set our UI position
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
var inversePos = UIManager.UIRoot.transform.InverseTransformPoint(mousePos);
UIRoot.transform.localPosition = new Vector3(inversePos.x, inversePos.y, 0);
}
@ -181,7 +183,7 @@ namespace UnityExplorer.Inspectors
{
// hide title bar
this.titleBar.SetActive(false);
this.UIRoot.transform.SetParent(UIManager.CanvasRoot.transform, false);
this.UIRoot.transform.SetParent(UIManager.UIRoot.transform, false);
var inspectContent = UIFactory.CreateVerticalGroup(this.content, "InspectContent", true, true, true, true, 3, new Vector4(2, 2, 2, 2));
UIFactory.SetLayoutElement(inspectContent, flexibleWidth: 9999, flexibleHeight: 9999);

View File

@ -5,8 +5,9 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UniverseLib.UI;
namespace UnityExplorer.Inspectors
{

View File

@ -7,8 +7,10 @@ using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer
{

View File

@ -5,8 +5,10 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.Inspectors
{

View File

@ -7,6 +7,7 @@ using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UniverseLib;
namespace UnityExplorer.Inspectors.MouseInspectors
{

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UniverseLib;
namespace UnityExplorer.Inspectors.MouseInspectors
{

View File

@ -15,6 +15,10 @@ using UnityExplorer.CacheObject.Views;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI;
using UniverseLib.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
using UniverseLib.Runtime;
namespace UnityExplorer.Inspectors
{
@ -644,7 +648,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 +668,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 +693,7 @@ namespace UnityExplorer.Inspectors
return;
}
path = IOUtility.EnsureValidDirectory(path);
path = IOUtility.EnsureValidFilePath(path);
if (File.Exists(path))
File.Delete(path);
@ -699,7 +704,6 @@ namespace UnityExplorer.Inspectors
tex = TextureUtilProvider.ForceReadTexture(tex);
byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex);
File.WriteAllBytes(path, data);
if (tex != TextureRef)

View File

@ -11,7 +11,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.Loader.BIE;
#if CPP
using BepInEx.IL2CPP;

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

@ -7,7 +7,7 @@ using UnityEngine;
using UnityExplorer.Core.Config;
using UnityExplorer.Loader.STANDALONE;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
using UniverseLib.Input;
using UnityExplorer.Core;
#if CPP
using UnhollowerRuntimeLib;

View File

@ -5,10 +5,13 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.ObjectExplorer
{

View File

@ -9,9 +9,12 @@ using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
using System.Collections;
namespace UnityExplorer.ObjectExplorer
{
@ -42,6 +45,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 +163,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;
@ -203,12 +211,13 @@ namespace UnityExplorer.ObjectExplorer
UIFactory.SetLayoutElement(filterRow, minHeight: 25, flexibleHeight: 0);
//Filter input field
var inputField = UIFactory.CreateInputField(filterRow, "FilterInput", "Search...");
var inputField = UIFactory.CreateInputField(filterRow, "FilterInput", "Search and press enter...");
inputField.Component.targetGraphic.color = new Color(0.2f, 0.2f, 0.2f);
RuntimeProvider.Instance.SetColorBlock(inputField.Component, new Color(0.4f, 0.4f, 0.4f), new Color(0.2f, 0.2f, 0.2f),
new Color(0.08f, 0.08f, 0.08f));
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25);
inputField.OnValueChanged += OnFilterInput;
//inputField.OnValueChanged += OnFilterInput;
inputField.Component.GetOnEndEdit().AddListener(OnFilterInput);
// refresh row
@ -246,10 +255,57 @@ namespace UnityExplorer.ObjectExplorer
// Scene Loader
ConstructSceneLoader();
RuntimeProvider.Instance.StartCoroutine(TempFixCoro());
}
// To "fix" a strange FPS drop issue with MelonLoader.
private IEnumerator TempFixCoro()
{
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - start < 2.5f)
yield return null;
// Select "HideAndDontSave" and then go back to first scene.
this.sceneDropdown.value = sceneDropdown.options.Count - 1;
this.sceneDropdown.value = 0;
}
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 +315,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 +359,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

@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
using UniverseLib;
namespace UnityExplorer.ObjectExplorer
{
@ -97,6 +98,8 @@ namespace UnityExplorer.ObjectExplorer
var scenePath = (string)method.Invoke(null, new object[] { i });
AllSceneNames.Add(scenePath);
}
WasAbleToGetScenesInBuild = true;
}
catch (Exception ex)
{
@ -130,10 +133,11 @@ namespace UnityExplorer.ObjectExplorer
LoadedScenes.Add(scene);
}
bool anyChange = confirmedCount != LoadedScenes.Count;
LoadedScenes.Add(DontDestroyScene);
LoadedScenes.Add(default);
bool anyChange = confirmedCount != LoadedScenes.Count;
previousLoadedScenes = new HashSet<Scene>(LoadedScenes);
// Default to first scene if none selected or previous selection no longer exists.

View File

@ -7,6 +7,7 @@ using UnityEngine;
using UnityEngine.SceneManagement;
using UnityExplorer.Core;
using UnityExplorer.Core.Runtime;
using UniverseLib;
namespace UnityExplorer.ObjectExplorer
{
@ -101,7 +102,7 @@ namespace UnityExplorer.ObjectExplorer
if (go)
{
// hide unityexplorer objects
if (go.transform.root.name == "ExplorerCanvas")
if (go.transform.root.name == "UniverseLibCanvas")
continue;
if (shouldFilterGOs)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine.UI;
using UnityExplorer.UI.Models;
namespace UnityExplorer.UI
{
// A simple helper class to handle a button's OnClick more effectively.
public class ButtonRef
{
public Action OnClick;
public Button Component { get; }
public Text ButtonText { get; }
public ButtonRef(Button button)
{
this.Component = button;
this.ButtonText = button.GetComponentInChildren<Text>();
button.onClick.AddListener(() => { OnClick?.Invoke(); });
}
}
}

View File

@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Models;
namespace UnityExplorer.UI
{
public class InputFieldRef : UIModel
{
public static readonly HashSet<InputFieldRef> inputsPendingUpdate = new HashSet<InputFieldRef>();
public static void UpdateInstances()
{
if (inputsPendingUpdate.Any())
{
var array = inputsPendingUpdate.ToArray();
for (int i = array.Length - 1; i >= 0; i--)
{
var entry = array[i];
LayoutRebuilder.MarkLayoutForRebuild(entry.Rect);
entry.OnValueChanged?.Invoke(entry.Component.text);
}
inputsPendingUpdate.Clear();
}
}
public InputFieldRef(InputField component)
{
this.Component = component;
Rect = component.GetComponent<RectTransform>();
PlaceholderText = component.placeholder.TryCast<Text>();
component.onValueChanged.AddListener(OnInputChanged);
}
public event Action<string> OnValueChanged;
public InputField Component;
public Text PlaceholderText;
public RectTransform Rect;
public string Text
{
get => Component.text;
set => Component.text = value;
}
public TextGenerator TextGenerator => Component.cachedInputTextGenerator;
public bool ReachedMaxVerts => TextGenerator.vertexCount >= UIManager.MAX_TEXT_VERTS;
private void OnInputChanged(string value)
{
if (!inputsPendingUpdate.Contains(this))
inputsPendingUpdate.Add(this);
}
public override GameObject UIRoot => Component.gameObject;
public override void ConstructUI(GameObject parent)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.UI.Models
{
public abstract class UIBehaviourModel : UIModel
{
private static readonly List<UIBehaviourModel> Instances = new List<UIBehaviourModel>();
public static void UpdateInstances()
{
if (!Instances.Any())
return;
try
{
for (int i = Instances.Count - 1; i >= 0; i--)
{
var instance = Instances[i];
if (instance == null || !instance.UIRoot)
{
Instances.RemoveAt(i);
continue;
}
if (instance.Enabled)
instance.Update();
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
public UIBehaviourModel()
{
Instances.Add(this);
}
/// <summary>
/// Default empty method, override and implement if NeedsUpdateTick is true.
/// </summary>
public virtual void Update()
{
}
public override void Destroy()
{
if (Instances.Contains(this))
Instances.Remove(this);
base.Destroy();
}
}
}

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.UI.Models
{
public abstract class UIModel
{
public abstract GameObject UIRoot { get; }
public bool Enabled
{
get => UIRoot && UIRoot.activeInHierarchy;
set
{
if (!UIRoot || Enabled == value)
return;
UIRoot.SetActive(value);
OnToggleEnabled?.Invoke(value);
}
}
public event Action<bool> OnToggleEnabled;
public abstract void ConstructUI(GameObject parent);
public virtual void Toggle() => SetActive(!Enabled);
public virtual void SetActive(bool active)
{
UIRoot?.SetActive(active);
}
public virtual void Destroy()
{
if (UIRoot)
GameObject.Destroy(UIRoot);
}
}
}

Some files were not shown because too many files have changed in this diff Show More