1.4.5 (pre-release)

* Pre-release. Will be released once MelonLoader bumps to Unhollower 0.4.9.0
* Added global "Force Unlock Mouse" option, should work on almost all games. Has smart behaviour and will maintain the previous value (or the value which should be set).
* Improve performacne of CacheList casting List ->IEnumerable
* Fix a bug causing some Components to not show the GameObject button in the Reflection Window (top-right corner).
* Fix a bug making the Window Manager think that two of the same Il2Cpp Object are not ReferenceEquals.
* Added logging when C# Console fails to compile anything
* Improve display of Reflection Window member name label, now expands with window resize.
This commit is contained in:
sinaioutlander 2020-08-27 18:05:55 +10:00
parent e567c16221
commit 535e88be9a
10 changed files with 222 additions and 67 deletions

View File

@ -14,8 +14,10 @@
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
</p>
### Note
Most games running on Unity 2017 to 2019 should be supported. If you find that the GUI does not display properly and you get errors in the MelonLoader console about it, then this is likely due to a bug with Il2CppAssemblyUnhollower's unstripping. This bug is known by the developer of the tool and they will fix it as soon as they are able to.
### Known issue
Some games are experiencing `MissingMethodException`s or exceptions about failed unstripping, which prevent the CppExplorer menu from showing properly or at all. This is a bug with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower) and there isn't much I can do about it myself.
If you're familiar with C# and Unity, one possibility for now is making a fork of this repo and manually fixing all the broken methods to ones which aren't broken (if possible). There may be another overload of the same method which wasn't stripped or was unstripped successfully, which you can use instead.
## Features
* Scene hierarchy explorer
@ -38,15 +40,13 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
* Press F7 to show or hide the menu.
* Simply browse through the scene, search for objects, etc, it's pretty self-explanatory.
### Help! I can't use the mouse!
### Mouse Control
It is fairly common for games to override mouse control with their own mouse behaviour. Unfortunately, it's not feasible for CppExplorer to handle this due to how differently every game will go about it.
CppExplorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). However, you may also want to prevent the mouse clicking-through onto the game behind CppExplorer, this is possible but it requires specific patches for that game.
In order to fix this problem, you can:
* Use [VRCExplorerMouseControl](https://github.com/sinaioutlander/VRCExplorerMouseControl) (for VRChat)
* Use [HPExplorerMouseControl](https://github.com/sinaioutlander/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl) (for Hellpoint)
* In general, pressing Escape (to open a menu) will usually give you temporary control over the mouse.
* Create your own mini-plugin using one of the two plugins above as an example. Usually only 1 or 2 simple Harmony patches are needed to fix the problem.
* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinaioutlander/VRCExplorerMouseControl)
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinaioutlander/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
* You can create your own mini-plugin using one of the two plugins above as an example. Usually only 1 or 2 simple Harmony patches are needed to fix the problem (if you want to submit that here, feel free to make a PR to this Readme).
## Images

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using Mono.CSharp;
using UnityEngine;
@ -22,7 +23,15 @@ namespace Explorer
{
if (m_entryType == null)
{
m_entryType = Value?.GetType().GetGenericArguments()[0];
switch (this.MemberInfoType)
{
case ReflectionWindow.MemberInfoType.Field:
m_entryType = (MemberInfo as FieldInfo).FieldType.GetGenericArguments()[0];
break;
case ReflectionWindow.MemberInfoType.Property:
m_entryType = (MemberInfo as PropertyInfo).PropertyType.GetGenericArguments()[0];
break;
}
}
return m_entryType;
}
@ -39,7 +48,7 @@ namespace Explorer
{
if (m_enumerable == null && Value != null)
{
m_enumerable = Value as IEnumerable ?? CppListToEnumerable(Value);
m_enumerable = Value as IEnumerable ?? CastValueFromList();
}
return m_enumerable;
}
@ -48,14 +57,23 @@ namespace Explorer
private IEnumerable m_enumerable;
private CacheObject[] m_cachedEntries;
private IEnumerable CppListToEnumerable(object list)
public MethodInfo GenericToArrayMethod
{
if (EntryType == null) return null;
get
{
if (EntryType == null) return null;
return (IEnumerable)typeof(Il2CppSystem.Collections.Generic.List<>)
.MakeGenericType(new Type[] { EntryType })
.GetMethod("ToArray")
.Invoke(list, new object[0]);
return m_genericToArray ??
(m_genericToArray = typeof(Il2CppSystem.Collections.Generic.List<>)
.MakeGenericType(new Type[] { this.EntryType })
.GetMethod("ToArray"));
}
}
private MethodInfo m_genericToArray;
private IEnumerable CastValueFromList()
{
return (Value == null) ? null : (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
}
public override void DrawValue(Rect window, float width)

View File

@ -132,8 +132,18 @@ namespace Explorer
return holder;
}
private const float MAX_WIDTH = 400f;
public void Draw(Rect window, float labelWidth = 215f)
{
if (labelWidth > 0)
{
float min = (window.width * 0.37f);
if (min > MAX_WIDTH) min = MAX_WIDTH;
labelWidth = Mathf.Clamp(labelWidth, min, MAX_WIDTH);
}
if (MemberInfo != null)
{
GUILayout.Label("<color=cyan>" + FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });

View File

@ -1,19 +1,18 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
{
public class CppExplorer : MelonMod
{
// consts
public const string ID = "com.sinai.cppexplorer";
public const string VERSION = "1.4.2";
public const string GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.4.5";
public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer"
@ -22,26 +21,52 @@ namespace Explorer
#endif
;
// fields
public static CppExplorer Instance { get; private set; }
public static CppExplorer Instance;
public static bool ShowMenu
{
get => m_showMenu;
set => SetShowMenu(value);
}
private static bool m_showMenu;
// props
public static bool ForceUnlockMouse
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShowMenu { get; set; } = false;
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
// methods
private static void SetShowMenu(bool show)
{
m_showMenu = show;
UpdateCursorControl();
}
// ========== MonoBehaviour methods ==========
public override void OnApplicationStart()
{
base.OnApplicationStart();
Instance = this;
new MainMenu();
new WindowManager();
ShowMenu = true;
// Get current cursor state and enable cursor
m_lastLockMode = Cursor.lockState;
m_lastVisibleState = Cursor.visible;
// Enable ShowMenu and ForceUnlockMouse
// (set m_showMenu to not call UpdateCursorState twice)
m_showMenu = true;
SetForceUnlock(true);
MelonLogger.Log($"CppExplorer {VERSION} initialized.");
}
public override void OnLevelWasLoaded(int level)
@ -52,6 +77,7 @@ namespace Explorer
public override void OnUpdate()
{
// Check main toggle key input
if (Input.GetKeyDown(KeyCode.F7))
{
ShowMenu = !ShowMenu;
@ -59,15 +85,14 @@ namespace Explorer
if (ShowMenu)
{
if (!Cursor.visible)
// Check Force-Unlock input
if (Input.GetKeyDown(KeyCode.LeftAlt))
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
ForceUnlockMouse = !ForceUnlockMouse;
}
MainMenu.Instance.Update();
WindowManager.Instance.Update();
InspectUnderMouse.Update();
}
}
@ -78,8 +103,99 @@ namespace Explorer
MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI();
}
// =========== Cursor control ===========
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
private static void UpdateCursorControl()
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
}
m_currentlySettingCursor = false;
}
// 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.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)]
public class Cursor_lockState
{
[HarmonyPrefix]
public static void Prefix(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
{
m_lastLockMode = value;
if (ShouldForceMouse)
{
value = CursorLockMode.None;
}
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)]
public class Cursor_set_visible
{
[HarmonyPrefix]
public static void Prefix(ref bool value)
{
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
value = true;
}
}
}
}
// Make it appear as though UnlockMouse is disabled to the rest of the application.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
public class Cursor_get_visible
{
[HarmonyPostfix]
public static void Postfix(ref bool __result)
{
if (ShouldForceMouse)
{
__result = m_lastVisibleState;
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)]
public class Cursor_get_lockState
{
[HarmonyPostfix]
public static void Postfix(ref CursorLockMode __result)
{
if (ShouldForceMouse)
{
__result = m_lastLockMode;
}
}
}
}
}

View File

@ -42,10 +42,6 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath>
<Private>False</Private>
@ -64,10 +60,6 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerRuntimeLib">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- Unity 2019 build (InputLegacyModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
@ -97,10 +89,6 @@
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- Unity 2018 build (InputModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
@ -130,10 +118,6 @@
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CachedObjects\CacheEnum.cs" />

View File

@ -8,15 +8,12 @@ using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using BF = System.Reflection.BindingFlags;
using ILBF = Il2CppSystem.Reflection.BindingFlags;
using MelonLoader;
namespace Explorer
{
public class ReflectionHelpers
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
public static ILBF CommonFlags_IL = ILBF.Public | ILBF.NonPublic | ILBF.Instance | ILBF.Static;
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();

View File

@ -103,9 +103,15 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUI.color = Color.white;
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
bool mouseState = CppExplorer.ForceUnlockMouse;
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUI.color = Color.white;
}

View File

@ -86,11 +86,11 @@ MelonLogger.Log(""hello world"");";
if (!UsingDirectives.Contains(asm))
{
UsingDirectives.Add(asm);
Evaluate(AsmToUsing(asm));
Evaluate(AsmToUsing(asm), true);
}
}
public object Evaluate(string str)
public object Evaluate(string str, bool suppressWarning = false)
{
object ret = VoidType.Value;
@ -98,11 +98,19 @@ MelonLogger.Log(""hello world"");";
try
{
compiled?.Invoke(ref ret);
if (compiled == null)
{
throw new Exception("Mono.Csharp Service was unable to compile the code provided.");
}
compiled.Invoke(ref ret);
}
catch (Exception e)
{
MelonLogger.LogWarning(e.ToString());
if (!suppressWarning)
{
MelonLogger.LogWarning(e.GetType() + ", " + e.Message);
}
}
return ret;
@ -114,7 +122,7 @@ MelonLogger.Log(""hello world"");";
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null);
GUILayout.Label("Method:", null);
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(300) });
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
{
@ -144,7 +152,7 @@ MelonLogger.Log(""hello world"");";
GUILayout.Label(AsmToUsing(asm, true), null);
}
GUILayout.BeginHorizontal(null);
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) });
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
{

View File

@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using Mono.CSharp;
using UnhollowerBaseLib;
using UnityEngine;
@ -190,7 +189,9 @@ namespace Explorer
UIHelpers.InstantiateButton((UnityEngine.Object)Target);
if (Target is Component comp && comp.gameObject is GameObject obj)
var comp = (Target as Il2CppSystem.Object).TryCast<Component>();
if (comp && comp.gameObject is GameObject obj)
{
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("GameObject:", null);

View File

@ -102,12 +102,24 @@ namespace Explorer
{
createdNew = false;
UnityEngine.Object uObj = null;
if (obj is UnityEngine.Object)
{
uObj = obj as UnityEngine.Object;
}
foreach (var window in Windows)
{
if (ReferenceEquals(obj, window.Target))
bool equals;
equals = ReferenceEquals(obj, window.Target);
if (!equals && uObj != null && window.Target is UnityEngine.Object uTarget)
{
equals = uObj.m_CachedPtr == uTarget.m_CachedPtr;
}
if (equals)
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
return window;
}
}
@ -181,7 +193,10 @@ namespace Explorer
GUILayout.EndHorizontal();
}
catch { }
catch //(Exception e)
{
//MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
}
return _rect;
}