diff --git a/README.md b/README.md index 640d71a..6f7b28d 100644 --- a/README.md +++ b/README.md @@ -19,24 +19,24 @@ ### BepInEx -0. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6). -1. Download the UnityExplorer release for BepInEx IL2CPP or Mono above. -2. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\` -3. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder. +1. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6). +2. Download the UnityExplorer release for BepInEx IL2CPP or Mono above. +3. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\` +4. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder. ### MelonLoader -0. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3+ for your game. Version 0.3 is currently in pre-release, so you must "Enable ALPHA Releases" in your MelonLoader Installer settings to see the option for it. -1. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above. -2. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder. +1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3+ for your game. Version 0.3 is currently in pre-release, so you must "Enable ALPHA Releases" in your MelonLoader Installer settings to see the option for it. +2. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above. +3. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder. ### Standalone -The standalone release is based on the BepInEx build, so it requires Harmony 2.0 (or HarmonyX) to function properly. +The standalone release requires you to also load `0Harmony.dll` (HarmonyX, from the `lib\` folder) to function properly, and the IL2CPP also requires `UnhollowerBaseLib.dll` as well. The Mono release should be fairly easy to use with any loader, but the IL2CPP one may be tricky, I'd recommend just using BepInEx or MelonLoader for IL2CPP. -0. Load the DLL from your mod or inject it. You must also make sure `0Harmony.dll` is loaded, and `UnhollowerBaseLib.dll` for IL2CPP as well. -1. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();` -2. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish. +1. Load the UnityExplorer DLL from your mod or inject it, as well as `0Harmony.dll` and `UnhollowerBaseLib.dll` as required. +2. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();` +3. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish. ## Issues and contributions @@ -124,31 +124,6 @@ Depending on the release you are using, the config file will be found at: * MelonLoader: `UserData\MelonPreferences.cfg` * Standalone `{DLL_location}\UnityExplorer\config.ini` -`Main Menu Toggle` (KeyCode) -* Default: `F7` -* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes. - -`Force Unlock Mouse` (bool) -* Default: `true` -* Forces the cursor to be unlocked and visible while the UnityExplorer menu is open, and prevents anything else taking control. - -`Default Page Limit` (int) -* Default: `25` -* Sets the default items per page when viewing lists or search results. -* Requires a restart to take effect, apart from Reflection Inspector tabs. - -`Default Output Path` (string) -* Default: `Mods\UnityExplorer` -* Where output is generated to, by default (for Texture PNG saving, etc). - -`Log Unity Debug` (bool) -* Default: `false` -* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log. - -`Hide on Startup` (bool) -* Default: `false` -* If true, UnityExplorer will be hidden when you start the game, you must open it via the keybind. - ## Building Building the project should be straight-forward, the references are all inside the `lib\` folder. diff --git a/lib/BepInEx.dll b/lib/BepInEx.dll index 6bd8fc3..4f11349 100644 Binary files a/lib/BepInEx.dll and b/lib/BepInEx.dll differ diff --git a/src/Core/Config/ConfigElement.cs b/src/Core/Config/ConfigElement.cs index 7795752..b3510cc 100644 --- a/src/Core/Config/ConfigElement.cs +++ b/src/Core/Config/ConfigElement.cs @@ -46,7 +46,7 @@ namespace UnityExplorer.Core.Config private void SetValue(T value) { - if ((m_value == null && value == null) || m_value.Equals(value)) + if ((m_value == null && value == null) || (m_value != null && m_value.Equals(value))) return; m_value = value; diff --git a/src/Core/Input/CursorUnlocker.cs b/src/Core/Input/CursorUnlocker.cs index 5027955..419912f 100644 --- a/src/Core/Input/CursorUnlocker.cs +++ b/src/Core/Input/CursorUnlocker.cs @@ -46,86 +46,21 @@ namespace UnityExplorer.Core.Input UpdateCursorControl(); - Unlock = true; + Unlock = ConfigManager.Force_Unlock_Mouse.Value; ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; }; } - private static void SetupPatches() - { - try - { - if (CursorType == null) - { - throw new Exception("Could not find Type 'UnityEngine.Cursor'!"); - } - - // Get current cursor state and enable cursor - try - { - m_lastLockMode = (CursorLockMode?)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null) - ?? CursorLockMode.None; - - m_lastVisibleState = (bool?)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null) - ?? false; - } - catch { } - - // Setup Harmony Patches - TryPatch(typeof(Cursor), - "lockState", - new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_lockState))), - true); - - TryPatch(typeof(Cursor), - "visible", - new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_set_visible))), - true); - - TryPatch(typeof(EventSystem), - "current", - new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(Prefix_EventSystem_set_current))), - true); - } - catch (Exception e) - { - ExplorerCore.Log($"Exception on ForceUnlockCursor.Init! {e.GetType()}, {e.Message}"); - } - } - - private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter) - { - try - { - var harmony = ExplorerCore.Loader.HarmonyInstance; - - var prop = type.GetProperty(property); - - if (setter) // setter is prefix - { - harmony.Patch(prop.GetSetMethod(), prefix: patch); - } - else // getter is postfix - { - harmony.Patch(prop.GetGetMethod(), postfix: patch); - } - } - catch (Exception e) - { - string suf = setter ? "set_" : "get_"; - ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}"); - } - } - public static void UpdateCursorControl() { try { m_currentlySettingCursor = true; + if (ShouldActuallyUnlock) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; - + if (UIManager.EventSys) SetEventSystem(); } @@ -137,6 +72,7 @@ namespace UnityExplorer.Core.Input if (UIManager.EventSys) ReleaseEventSystem(); } + m_currentlySettingCursor = false; } catch (Exception e) @@ -163,9 +99,7 @@ namespace UnityExplorer.Core.Input if (!m_lastEventSystem) m_lastEventSystem = EventSystem.current; - //ExplorerCore.Log("Disabling current event system..."); m_lastEventSystem.enabled = false; - //m_lastEventSystem.gameObject.SetActive(false); } // Set to our current system @@ -184,7 +118,6 @@ namespace UnityExplorer.Core.Input if (m_lastEventSystem) { m_lastEventSystem.enabled = true; - //m_lastEventSystem.gameObject.SetActive(true); m_settingEventSystem = true; EventSystem.current = m_lastEventSystem; @@ -193,7 +126,30 @@ namespace UnityExplorer.Core.Input } } - [HarmonyPrefix] + // Patches + + private static void SetupPatches() + { + try + { + if (CursorType == null) + throw new Exception("Could not load Type 'UnityEngine.Cursor'!"); + + // Get current cursor state and enable cursor + m_lastLockMode = (CursorLockMode?)CursorType.GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null) + ?? CursorLockMode.None; + + m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null) + ?? false; + + ExplorerCore.Loader.SetupPatches(); + } + catch (Exception e) + { + ExplorerCore.Log($"Error on CursorUnlocker.Init! {e.GetType()}, {e.Message}"); + } + } + public static void Prefix_EventSystem_set_current(ref EventSystem value) { if (!m_settingEventSystem) @@ -210,7 +166,6 @@ namespace UnityExplorer.Core.Input // 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. - [HarmonyPrefix] public static void Prefix_set_lockState(ref CursorLockMode value) { if (!m_currentlySettingCursor) @@ -222,7 +177,6 @@ namespace UnityExplorer.Core.Input } } - [HarmonyPrefix] public static void Prefix_set_visible(ref bool value) { if (!m_currentlySettingCursor) diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index a039828..76e4d19 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -12,7 +12,7 @@ namespace UnityExplorer public class ExplorerCore { public const string NAME = "UnityExplorer"; - public const string VERSION = "3.3.3"; + public const string VERSION = "3.3.4"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.unityexplorer"; diff --git a/src/Loader/BIE/ExplorerBepInPlugin.cs b/src/Loader/BIE/ExplorerBepInPlugin.cs index cc56a8f..82082b2 100644 --- a/src/Loader/BIE/ExplorerBepInPlugin.cs +++ b/src/Loader/BIE/ExplorerBepInPlugin.cs @@ -10,6 +10,9 @@ using System.Text; using UnityExplorer.Core.Config; using UnityExplorer.Loader.BIE; using UnityEngine; +using UnityExplorer.Core; +using UnityEngine.EventSystems; +using UnityExplorer.Core.Input; #if CPP using BepInEx.IL2CPP; using UnhollowerRuntimeLib; @@ -75,10 +78,8 @@ namespace UnityExplorer ClassInjector.RegisterTypeInIl2Cpp(); - var obj = new GameObject( - "ExplorerBehaviour", - new Il2CppSystem.Type[] { Il2CppType.Of() } - ); + var obj = new GameObject("ExplorerBehaviour"); + obj.AddComponent(); obj.hideFlags = HideFlags.HideAndDontSave; GameObject.DontDestroyOnLoad(obj); @@ -101,6 +102,48 @@ namespace UnityExplorer } } #endif + + public void SetupPatches() + { + try + { + this.HarmonyInstance.PatchAll(); + } + catch (Exception ex) + { + ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}"); + } + } + + [HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)] + public class PATCH_EventSystem_current + { + [HarmonyPrefix] + public static void Prefix_EventSystem_set_current(ref EventSystem value) + { + CursorUnlocker.Prefix_EventSystem_set_current(ref value); + } + } + + [HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)] + public class PATCH_Cursor_lockState + { + [HarmonyPrefix] + public static void Prefix_set_lockState(ref CursorLockMode value) + { + CursorUnlocker.Prefix_set_lockState(ref value); + } + } + + [HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)] + public class PATCH_Cursor_visible + { + [HarmonyPrefix] + public static void Prefix_set_visible(ref bool value) + { + CursorUnlocker.Prefix_set_visible(ref value); + } + } } } #endif \ No newline at end of file diff --git a/src/Loader/IExplorerLoader.cs b/src/Loader/IExplorerLoader.cs index f8b8e18..78a17b4 100644 --- a/src/Loader/IExplorerLoader.cs +++ b/src/Loader/IExplorerLoader.cs @@ -17,10 +17,6 @@ namespace UnityExplorer Action OnLogWarning { get; } Action OnLogError { get; } -#if ML - Harmony.HarmonyInstance HarmonyInstance { get; } -#else - HarmonyLib.Harmony HarmonyInstance { get; } -#endif + void SetupPatches(); } } diff --git a/src/Loader/ML/ExplorerMelonMod.cs b/src/Loader/ML/ExplorerMelonMod.cs index ae40b77..47f6db7 100644 --- a/src/Loader/ML/ExplorerMelonMod.cs +++ b/src/Loader/ML/ExplorerMelonMod.cs @@ -1,10 +1,14 @@ #if ML using System; using System.IO; +using Harmony; using MelonLoader; using UnityEngine; +using UnityEngine.EventSystems; using UnityExplorer; +using UnityExplorer.Core; using UnityExplorer.Core.Config; +using UnityExplorer.Core.Input; using UnityExplorer.Loader.ML; [assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)] @@ -40,6 +44,41 @@ namespace UnityExplorer { ExplorerCore.Update(); } + + public void SetupPatches() + { + try + { + PrefixProperty(typeof(Cursor), + "lockState", + new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState)))); + + PrefixProperty(typeof(Cursor), + "visible", + new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible)))); + + PrefixProperty(typeof(EventSystem), + "current", + new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current)))); + } + catch (Exception ex) + { + ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}"); + } + } + + private void PrefixProperty(Type type, string property, HarmonyMethod prefix) + { + try + { + var prop = type.GetProperty(property); + this.Harmony.Patch(prop.GetSetMethod(), prefix: prefix); + } + catch (Exception e) + { + ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}"); + } + } } } #endif \ No newline at end of file diff --git a/src/Loader/STANDALONE/ExplorerStandalone.cs b/src/Loader/STANDALONE/ExplorerStandalone.cs index 1657040..9a2ab59 100644 --- a/src/Loader/STANDALONE/ExplorerStandalone.cs +++ b/src/Loader/STANDALONE/ExplorerStandalone.cs @@ -6,6 +6,9 @@ using System.Reflection; using UnityEngine; using UnityExplorer.Core.Config; using UnityExplorer.Loader.STANDALONE; +using UnityEngine.EventSystems; +using UnityExplorer.Core.Input; +using UnityExplorer.Core; #if CPP using UnhollowerRuntimeLib; #endif @@ -86,17 +89,9 @@ namespace UnityExplorer #if CPP ClassInjector.RegisterTypeInIl2Cpp(); - - var obj = new GameObject( - "ExplorerBehaviour", - new Il2CppSystem.Type[] { Il2CppType.Of() } - ); -#else - var obj = new GameObject( - "ExplorerBehaviour", - new Type[] { typeof(ExplorerBehaviour) } - ); #endif + var obj = new GameObject("ExplorerBehaviour"); + obj.AddComponent(); GameObject.DontDestroyOnLoad(obj); obj.hideFlags = HideFlags.HideAndDontSave; @@ -114,6 +109,48 @@ namespace UnityExplorer ExplorerCore.Update(); } } + + public void SetupPatches() + { + try + { + this.HarmonyInstance.PatchAll(); + } + catch (Exception ex) + { + ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}"); + } + } + + [HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)] + public class PATCH_EventSystem_current + { + [HarmonyPrefix] + public static void Prefix_EventSystem_set_current(ref EventSystem value) + { + CursorUnlocker.Prefix_EventSystem_set_current(ref value); + } + } + + [HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)] + public class PATCH_Cursor_lockState + { + [HarmonyPrefix] + public static void Prefix_set_lockState(ref CursorLockMode value) + { + CursorUnlocker.Prefix_set_lockState(ref value); + } + } + + [HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)] + public class PATCH_Cursor_visible + { + [HarmonyPrefix] + public static void Prefix_set_visible(ref bool value) + { + CursorUnlocker.Prefix_set_visible(ref value); + } + } } } #endif \ No newline at end of file