diff --git a/README.md b/README.md
index 00378d3..2006762 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,6 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
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.
* 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
diff --git a/src/CachedObjects/CacheDictionary.cs b/src/CachedObjects/CacheDictionary.cs
new file mode 100644
index 0000000..6454bcc
--- /dev/null
+++ b/src/CachedObjects/CacheDictionary.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MelonLoader;
+using UnityEngine;
+
+namespace Explorer
+{
+ public class CacheDictionary : CacheObjectBase
+ {
+
+
+ public override void Init()
+ {
+ //base.Init();
+
+ Value = "Unsupported";
+ }
+
+ public override void UpdateValue()
+ {
+ //base.UpdateValue();
+
+
+ }
+
+ public override void DrawValue(Rect window, float width)
+ {
+ GUILayout.Label("Dictionary (unsupported)", null);
+ }
+ }
+}
diff --git a/src/CachedObjects/CacheEnum.cs b/src/CachedObjects/CacheEnum.cs
index d8a182b..00f3d09 100644
--- a/src/CachedObjects/CacheEnum.cs
+++ b/src/CachedObjects/CacheEnum.cs
@@ -8,7 +8,7 @@ using UnityEngine;
namespace Explorer
{
- public class CacheEnum : CacheObject
+ public class CacheEnum : CacheObjectBase
{
public Type EnumType;
public string[] EnumNames;
diff --git a/src/CachedObjects/CacheGameObject.cs b/src/CachedObjects/CacheGameObject.cs
index 417093b..34bf0af 100644
--- a/src/CachedObjects/CacheGameObject.cs
+++ b/src/CachedObjects/CacheGameObject.cs
@@ -8,7 +8,7 @@ using UnityEngine;
namespace Explorer
{
- public class CacheGameObject : CacheObject
+ public class CacheGameObject : CacheObjectBase
{
private GameObject GameObj
{
diff --git a/src/CachedObjects/CacheList.cs b/src/CachedObjects/CacheList.cs
index dfb9270..7bdd720 100644
--- a/src/CachedObjects/CacheList.cs
+++ b/src/CachedObjects/CacheList.cs
@@ -3,34 +3,40 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using MelonLoader;
-using Mono.CSharp;
using UnityEngine;
namespace Explorer
{
- public partial class CacheList : CacheObject
+ public partial class CacheList : CacheObjectBase
{
public bool IsExpanded { get; set; }
public int ArrayOffset { get; set; }
public int ArrayLimit { get; set; } = 20;
-
+
+ public float WhiteSpace = 215f;
+ public float ButtonWidthOffset = 290f;
+
public Type EntryType
{
get
{
if (m_entryType == null)
{
- switch (this.MemberInfoType)
+ if (this.MemberInfo != null)
{
- 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;
+ switch (this.MemberInfoType)
+ {
+ case MemberTypes.Field:
+ m_entryType = (MemberInfo as FieldInfo).FieldType.GetGenericArguments()[0];
+ break;
+ case MemberTypes.Property:
+ m_entryType = (MemberInfo as PropertyInfo).PropertyType.GetGenericArguments()[0];
+ break;
+ }
+ }
+ else if (Value != null)
+ {
+ m_entryType = Value.GetType().GetGenericArguments()[0];
}
}
return m_entryType;
@@ -55,7 +61,7 @@ namespace Explorer
}
private IEnumerable m_enumerable;
- private CacheObject[] m_cachedEntries;
+ private CacheObjectBase[] m_cachedEntries;
public MethodInfo GenericToArrayMethod
{
@@ -97,7 +103,7 @@ namespace Explorer
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = "[" + count + "] " + EntryType + "";
- if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) }))
+ if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
{
WindowManager.InspectObject(Value, out bool _);
}
@@ -107,8 +113,12 @@ namespace Explorer
if (IsExpanded)
{
- float whitespace = 215;
- ClampLabelWidth(window, ref whitespace);
+ float whitespace = WhiteSpace;
+
+ if (whitespace > 0)
+ {
+ ClampLabelWidth(window, ref whitespace);
+ }
if (count > ArrayLimit)
{
@@ -120,11 +130,11 @@ namespace Explorer
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons
- if (GUILayout.Button("< Prev", null))
+ if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{
if (ArrayOffset > 0) ArrayOffset--;
}
- if (GUILayout.Button("Next >", null))
+ if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{
if (ArrayOffset < maxOffset) ArrayOffset++;
}
@@ -186,7 +196,7 @@ namespace Explorer
if (enumerator == null) return;
- var list = new List();
+ var list = new List();
while (enumerator.MoveNext())
{
list.Add(GetCacheObject(enumerator.Current, null, null, this.EntryType));
diff --git a/src/CachedObjects/CacheMethod.cs b/src/CachedObjects/CacheMethod.cs
new file mode 100644
index 0000000..30d4518
--- /dev/null
+++ b/src/CachedObjects/CacheMethod.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Reflection;
+using UnityEngine;
+using MelonLoader;
+
+namespace Explorer
+{
+ public class CacheMethod : CacheObjectBase
+ {
+ private bool m_evaluated = false;
+ private CacheObjectBase m_cachedReturnValue;
+
+ public static bool CanEvaluate(MethodInfo mi)
+ {
+ if (mi.GetParameters().Length > 0 || mi.GetGenericArguments().Length > 0)
+ {
+ // Currently methods with arguments are not supported (no good way to input them).
+ return false;
+ }
+
+ return true;
+ }
+
+ public override void Init()
+ {
+ base.Init();
+ }
+
+ public override void UpdateValue()
+ {
+ base.UpdateValue();
+ }
+
+ private void Evaluate()
+ {
+ m_evaluated = true;
+
+ var mi = MemberInfo as MethodInfo;
+ var ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
+
+ if (ret != null)
+ {
+ m_cachedReturnValue = GetCacheObject(ret);
+ if (m_cachedReturnValue is CacheList cacheList)
+ {
+ cacheList.WhiteSpace = 0f;
+ cacheList.ButtonWidthOffset += 70f;
+ }
+ m_cachedReturnValue.UpdateValue();
+ }
+ else
+ {
+ m_cachedReturnValue = null;
+ }
+ }
+
+ public override void DrawValue(Rect window, float width)
+ {
+ GUILayout.BeginVertical(null);
+
+ GUILayout.BeginHorizontal(null);
+ if (GUILayout.Button("Evaluate", new GUILayoutOption[] { GUILayout.Width(70) }))
+ {
+ Evaluate();
+ }
+ GUI.skin.label.wordWrap = false;
+ GUILayout.Label($"{ValueType}", null);
+ GUI.skin.label.wordWrap = true;
+ GUILayout.EndHorizontal();
+
+ GUILayout.BeginHorizontal(null);
+ if (m_evaluated)
+ {
+ if (m_cachedReturnValue != null)
+ {
+ try
+ {
+ m_cachedReturnValue.DrawValue(window, width);
+ }
+ catch (Exception e)
+ {
+ MelonLogger.Log("Exception drawing m_cachedReturnValue!");
+ MelonLogger.Log(e.ToString());
+ }
+ }
+ else
+ {
+ GUILayout.Label($"null", null);
+ }
+ }
+ else
+ {
+ GUILayout.Label($"Not yet evaluated", null);
+ }
+ GUILayout.EndHorizontal();
+
+ GUILayout.EndVertical();
+ }
+ }
+}
diff --git a/src/CachedObjects/CacheObject.cs b/src/CachedObjects/CacheObjectBase.cs
similarity index 74%
rename from src/CachedObjects/CacheObject.cs
rename to src/CachedObjects/CacheObjectBase.cs
index 54cb20d..2bdd936 100644
--- a/src/CachedObjects/CacheObject.cs
+++ b/src/CachedObjects/CacheObjectBase.cs
@@ -10,14 +10,13 @@ using UnityEngine;
namespace Explorer
{
- public abstract class CacheObject
+ public abstract class CacheObjectBase
{
public object Value;
public string ValueType;
// Reflection window only
public MemberInfo MemberInfo { get; set; }
- // public ReflectionWindow.MemberInfoType MemberInfoType { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public string FullName => $"{MemberInfo.DeclaringType.Name}.{MemberInfo.Name}";
@@ -42,14 +41,14 @@ namespace Explorer
}
}
- public ReflectionWindow.MemberInfoType MemberInfoType
+ public MemberTypes MemberInfoType
{
get
{
- if (MemberInfo is FieldInfo) return ReflectionWindow.MemberInfoType.Field;
- if (MemberInfo is PropertyInfo) return ReflectionWindow.MemberInfoType.Property;
- if (MemberInfo is MethodInfo) return ReflectionWindow.MemberInfoType.Method;
- return ReflectionWindow.MemberInfoType.All;
+ if (MemberInfo is FieldInfo) return MemberTypes.Field;
+ if (MemberInfo is PropertyInfo) return MemberTypes.Property;
+ if (MemberInfo is MethodInfo) return MemberTypes.Method;
+ return MemberTypes.All;
}
}
@@ -57,7 +56,7 @@ namespace Explorer
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
- public static CacheObject GetCacheObject(object obj)
+ public static CacheObjectBase GetCacheObject(object obj)
{
return GetCacheObject(obj, null, null);
}
@@ -69,13 +68,39 @@ namespace Explorer
/// The MemberInfo (can be null if obj is not null)
/// If MemberInfo is not null, the declaring class instance. Can be null if static.
///
- public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
+ public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
{
- var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
+ //var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
+
+ Type type = null;
+
+ if (obj != null)
+ {
+ type = ReflectionHelpers.GetActualType(obj);
+ }
+ else if (memberInfo != null)
+ {
+ if (memberInfo is FieldInfo fi)
+ {
+ type = fi.FieldType;
+ }
+ else if (memberInfo is PropertyInfo pi)
+ {
+ type = pi.PropertyType;
+ }
+ else if (memberInfo is MethodInfo mi)
+ {
+ type = mi.ReturnType;
+ }
+ }
if (type == null)
{
MelonLogger.Log("Could not get type for object or memberinfo!");
+ if (memberInfo is MethodInfo)
+ {
+ MelonLogger.Log("is it void?");
+ }
return null;
}
@@ -90,12 +115,22 @@ namespace Explorer
/// If MemberInfo is not null, the declaring class instance. Can be null if static.
/// The type of the object or MemberInfo value.
///
- public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
+ public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
{
- CacheObject holder;
+ CacheObjectBase holder;
- if ((obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(valueType))
- && (valueType.FullName.Contains("UnityEngine.GameObject") || valueType.FullName.Contains("UnityEngine.Transform")))
+ if (memberInfo is MethodInfo mi)
+ {
+ if (CacheMethod.CanEvaluate(mi))
+ {
+ holder = new CacheMethod();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{
holder = new CacheGameObject();
}
@@ -107,10 +142,14 @@ namespace Explorer
{
holder = new CacheEnum();
}
- else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(valueType) || ReflectionHelpers.IsList(valueType))
+ else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType))
{
holder = new CacheList();
}
+ else if (ReflectionHelpers.IsDictionary(valueType))
+ {
+ holder = new CacheDictionary();
+ }
else
{
holder = new CacheOther();
@@ -163,7 +202,7 @@ namespace Explorer
{
GUILayout.Label("Reflection failed! (" + ReflectionException + ")", null);
}
- else if (Value == null)
+ else if (Value == null && MemberInfoType != MemberTypes.Method)
{
GUILayout.Label("null (" + ValueType + ")", null);
}
diff --git a/src/CachedObjects/CacheOther.cs b/src/CachedObjects/CacheOther.cs
index f101b96..a4d5044 100644
--- a/src/CachedObjects/CacheOther.cs
+++ b/src/CachedObjects/CacheOther.cs
@@ -9,7 +9,7 @@ using UnityEngine;
namespace Explorer
{
- public class CacheOther : CacheObject
+ public class CacheOther : CacheObjectBase
{
private MethodInfo m_toStringMethod;
private bool m_triedToGetMethod;
diff --git a/src/CachedObjects/CachePrimitive.cs b/src/CachedObjects/CachePrimitive.cs
index 4d274cf..7b3190a 100644
--- a/src/CachedObjects/CachePrimitive.cs
+++ b/src/CachedObjects/CachePrimitive.cs
@@ -1,11 +1,12 @@
using System;
+using System.Collections.Generic;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
- public class CachePrimitive : CacheObject
+ public class CachePrimitive : CacheObjectBase
{
public enum PrimitiveTypes
{
@@ -13,7 +14,8 @@ namespace Explorer
Double,
Float,
Int,
- String
+ String,
+ Char
}
private string m_valueToString;
@@ -37,10 +39,10 @@ namespace Explorer
t = typeof(float); break;
case PrimitiveTypes.Int:
t = typeof(int); break;
- case PrimitiveTypes.String:
- t = typeof(string); break;
+ case PrimitiveTypes.Char:
+ t = typeof(char); break;
}
- m_parseMethod = t.GetMethod("Parse", new Type[] { typeof(string) });
+ m_parseMethod = t?.GetMethod("Parse", new Type[] { typeof(string) });
}
return m_parseMethod;
}
@@ -52,7 +54,7 @@ namespace Explorer
{
if (Value == null)
{
- // this must mean it is a string? no other primitive type should be nullable
+ // this must mean it is a string. No other primitive type should be nullable.
PrimitiveType = PrimitiveTypes.String;
return;
}
@@ -72,21 +74,39 @@ namespace Explorer
{
PrimitiveType = PrimitiveTypes.Float;
}
- else if (type == typeof(int) || type == typeof(long) || type == typeof(uint) || type == typeof(ulong) || type == typeof(IntPtr))
+ else if (IsInteger(type))
{
PrimitiveType = PrimitiveTypes.Int;
}
+ else if (type == typeof(char))
+ {
+ PrimitiveType = PrimitiveTypes.Char;
+ }
else
{
- if (type != typeof(string))
- {
- MelonLogger.Log("Unsupported primitive: " + type);
- }
-
PrimitiveType = PrimitiveTypes.String;
}
}
+ private static bool IsInteger(Type type)
+ {
+ // For our purposes, all types of int can be treated the same, including IntPtr.
+ return _integerTypes.Contains(type);
+ }
+
+ private static readonly HashSet _integerTypes = new HashSet
+ {
+ typeof(int),
+ typeof(uint),
+ typeof(short),
+ typeof(ushort),
+ typeof(long),
+ typeof(ulong),
+ typeof(byte),
+ typeof(sbyte),
+ typeof(IntPtr)
+ };
+
public override void UpdateValue()
{
base.UpdateValue();
@@ -99,21 +119,26 @@ namespace Explorer
if (PrimitiveType == PrimitiveTypes.Bool)
{
var b = (bool)Value;
- var color = "" : "red>");
- b = GUILayout.Toggle(b, color + b.ToString() + "", null);
+ var color = $"" : "red>")}";
+ var label = $"{color}{b}";
- if (b != (bool)Value)
+ if (CanWrite)
{
- SetValue(m_valueToString);
+ b = GUILayout.Toggle(b, label, null);
+ if (b != (bool)Value)
+ {
+ SetValue(m_valueToString);
+ }
+ }
+ else
+ {
+ GUILayout.Label(label, null);
}
}
else
{
GUILayout.Label("" + PrimitiveType + "", new GUILayoutOption[] { GUILayout.Width(50) });
- //var content = new GUIContent(m_valueToString);
- //var contentSize = GUI.skin.textField.CalcSize(content);
-
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f;
if (CanWrite) maxwidth -= 60;
diff --git a/src/CppExplorer.cs b/src/CppExplorer.cs
index 69a71d2..bd66eb3 100644
--- a/src/CppExplorer.cs
+++ b/src/CppExplorer.cs
@@ -170,32 +170,34 @@ namespace Explorer
}
}
- // Make it appear as though UnlockMouse is disabled to the rest of the application.
+ // Temporarily disabled this because I don't think it's actually useful, and may in fact cause problems instead
- [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;
- }
- }
- }
+ //// Make it appear as though UnlockMouse is disabled to the rest of the application.
- [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;
- }
- }
- }
+ //[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;
+ // }
+ // }
+ //}
}
}
diff --git a/src/CppExplorer.csproj b/src/CppExplorer.csproj
index fdde414..4b446cd 100644
--- a/src/CppExplorer.csproj
+++ b/src/CppExplorer.csproj
@@ -120,11 +120,13 @@
+
+
@@ -132,7 +134,7 @@
-
+
diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs
index 2b416f9..b0912a1 100644
--- a/src/Helpers/ReflectionHelpers.cs
+++ b/src/Helpers/ReflectionHelpers.cs
@@ -72,6 +72,11 @@ namespace Explorer
return false;
}
+ public static bool IsArray(Type t)
+ {
+ return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
+ }
+
public static bool IsList(Type t)
{
return t.IsGenericType
@@ -79,6 +84,13 @@ namespace Explorer
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>)));
}
+ public static bool IsDictionary(Type t)
+ {
+ return t.IsGenericType
+ && t.GetGenericTypeDefinition() is Type typeDef
+ && typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
+ }
+
public static Type GetTypeByName(string typeName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
diff --git a/src/MainMenu/Pages/ConsolePage.cs b/src/MainMenu/Pages/ConsolePage.cs
index 7c5993b..122778e 100644
--- a/src/MainMenu/Pages/ConsolePage.cs
+++ b/src/MainMenu/Pages/ConsolePage.cs
@@ -121,7 +121,9 @@ MelonLogger.Log(""hello world"");";
{
GUILayout.Label("C# REPL Console", null);
- GUILayout.Label("Method:", null);
+ GUI.skin.label.alignment = TextAnchor.UpperLeft;
+
+ GUILayout.Label("Enter code here as though it is a method body:", null);
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
if (GUILayout.Button("Execute", null))
@@ -147,10 +149,7 @@ MelonLogger.Log(""hello world"");";
}
GUILayout.Label("Using directives:", null);
- foreach (var asm in UsingDirectives)
- {
- GUILayout.Label(AsmToUsing(asm, true), null);
- }
+
GUILayout.BeginHorizontal(null);
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
@@ -163,6 +162,11 @@ MelonLogger.Log(""hello world"");";
ResetConsole();
}
GUILayout.EndHorizontal();
+
+ foreach (var asm in UsingDirectives)
+ {
+ GUILayout.Label(AsmToUsing(asm, true), null);
+ }
}
public override void Update() { }
diff --git a/src/MainMenu/Pages/SearchPage.cs b/src/MainMenu/Pages/SearchPage.cs
index e978a20..7011131 100644
--- a/src/MainMenu/Pages/SearchPage.cs
+++ b/src/MainMenu/Pages/SearchPage.cs
@@ -26,7 +26,7 @@ namespace Explorer
//private List