1.4.5 (pre-release)

* Added MethodInfo support for basic methods with no arguments.
* Added support for missing primitive types (char, short, byte)
* Added CacheDictionary class (currently unsupported)
* Cleaned up some stuff, using System.Reflection.MemberType instead of a custom enum.
This commit is contained in:
sinaioutlander 2020-08-29 21:15:54 +10:00
parent 6e644b4f50
commit 92fe1dc704
17 changed files with 418 additions and 182 deletions

View File

@ -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. 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 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). * 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 ## Images

View File

@ -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("<color=red>Dictionary (unsupported)</color>", null);
}
}
}

View File

@ -8,7 +8,7 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CacheEnum : CacheObject public class CacheEnum : CacheObjectBase
{ {
public Type EnumType; public Type EnumType;
public string[] EnumNames; public string[] EnumNames;

View File

@ -8,7 +8,7 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CacheGameObject : CacheObject public class CacheGameObject : CacheObjectBase
{ {
private GameObject GameObj private GameObject GameObj
{ {

View File

@ -3,34 +3,40 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using Mono.CSharp;
using UnityEngine; using UnityEngine;
namespace Explorer namespace Explorer
{ {
public partial class CacheList : CacheObject public partial class CacheList : CacheObjectBase
{ {
public bool IsExpanded { get; set; } public bool IsExpanded { get; set; }
public int ArrayOffset { get; set; } public int ArrayOffset { get; set; }
public int ArrayLimit { get; set; } = 20; public int ArrayLimit { get; set; } = 20;
public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f;
public Type EntryType public Type EntryType
{ {
get get
{ {
if (m_entryType == null) if (m_entryType == null)
{ {
switch (this.MemberInfoType) if (this.MemberInfo != null)
{ {
case ReflectionWindow.MemberInfoType.Field: switch (this.MemberInfoType)
m_entryType = (MemberInfo as FieldInfo).FieldType.GetGenericArguments()[0]; {
break; case MemberTypes.Field:
case ReflectionWindow.MemberInfoType.Property: m_entryType = (MemberInfo as FieldInfo).FieldType.GetGenericArguments()[0];
m_entryType = (MemberInfo as PropertyInfo).PropertyType.GetGenericArguments()[0]; break;
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; return m_entryType;
@ -55,7 +61,7 @@ namespace Explorer
} }
private IEnumerable m_enumerable; private IEnumerable m_enumerable;
private CacheObject[] m_cachedEntries; private CacheObjectBase[] m_cachedEntries;
public MethodInfo GenericToArrayMethod public MethodInfo GenericToArrayMethod
{ {
@ -97,7 +103,7 @@ namespace Explorer
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>"; string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
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 _); WindowManager.InspectObject(Value, out bool _);
} }
@ -107,8 +113,12 @@ namespace Explorer
if (IsExpanded) if (IsExpanded)
{ {
float whitespace = 215; float whitespace = WhiteSpace;
ClampLabelWidth(window, ref whitespace);
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
if (count > ArrayLimit) if (count > ArrayLimit)
{ {
@ -120,11 +130,11 @@ namespace Explorer
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1; int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) }); GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons // prev/next page buttons
if (GUILayout.Button("< Prev", null)) if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset > 0) ArrayOffset--; if (ArrayOffset > 0) ArrayOffset--;
} }
if (GUILayout.Button("Next >", null)) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset < maxOffset) ArrayOffset++; if (ArrayOffset < maxOffset) ArrayOffset++;
} }
@ -186,7 +196,7 @@ namespace Explorer
if (enumerator == null) return; if (enumerator == null) return;
var list = new List<CacheObject>(); var list = new List<CacheObjectBase>();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
list.Add(GetCacheObject(enumerator.Current, null, null, this.EntryType)); list.Add(GetCacheObject(enumerator.Current, null, null, this.EntryType));

View File

@ -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($"<color=yellow>{ValueType}</color>", 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($"<color=grey><i>Not yet evaluated</i></color>", null);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
}
}

View File

@ -10,14 +10,13 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public abstract class CacheObject public abstract class CacheObjectBase
{ {
public object Value; public object Value;
public string ValueType; public string ValueType;
// Reflection window only // Reflection window only
public MemberInfo MemberInfo { get; set; } public MemberInfo MemberInfo { get; set; }
// public ReflectionWindow.MemberInfoType MemberInfoType { get; set; }
public Type DeclaringType { get; set; } public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; } public object DeclaringInstance { get; set; }
public string FullName => $"{MemberInfo.DeclaringType.Name}.{MemberInfo.Name}"; public string FullName => $"{MemberInfo.DeclaringType.Name}.{MemberInfo.Name}";
@ -42,14 +41,14 @@ namespace Explorer
} }
} }
public ReflectionWindow.MemberInfoType MemberInfoType public MemberTypes MemberInfoType
{ {
get get
{ {
if (MemberInfo is FieldInfo) return ReflectionWindow.MemberInfoType.Field; if (MemberInfo is FieldInfo) return MemberTypes.Field;
if (MemberInfo is PropertyInfo) return ReflectionWindow.MemberInfoType.Property; if (MemberInfo is PropertyInfo) return MemberTypes.Property;
if (MemberInfo is MethodInfo) return ReflectionWindow.MemberInfoType.Method; if (MemberInfo is MethodInfo) return MemberTypes.Method;
return ReflectionWindow.MemberInfoType.All; return MemberTypes.All;
} }
} }
@ -57,7 +56,7 @@ namespace Explorer
public virtual void Init() { } public virtual void Init() { }
public abstract void DrawValue(Rect window, float width); 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); return GetCacheObject(obj, null, null);
} }
@ -69,13 +68,39 @@ namespace Explorer
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param> /// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param>
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param> /// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <returns></returns> /// <returns></returns>
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) if (type == null)
{ {
MelonLogger.Log("Could not get type for object or memberinfo!"); MelonLogger.Log("Could not get type for object or memberinfo!");
if (memberInfo is MethodInfo)
{
MelonLogger.Log("is it void?");
}
return null; return null;
} }
@ -90,12 +115,22 @@ namespace Explorer
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param> /// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <param name="valueType">The type of the object or MemberInfo value.</param> /// <param name="valueType">The type of the object or MemberInfo value.</param>
/// <returns></returns> /// <returns></returns>
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)) if (memberInfo is MethodInfo mi)
&& (valueType.FullName.Contains("UnityEngine.GameObject") || valueType.FullName.Contains("UnityEngine.Transform"))) {
if (CacheMethod.CanEvaluate(mi))
{
holder = new CacheMethod();
}
else
{
return null;
}
}
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{ {
holder = new CacheGameObject(); holder = new CacheGameObject();
} }
@ -107,10 +142,14 @@ namespace Explorer
{ {
holder = new CacheEnum(); 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(); holder = new CacheList();
} }
else if (ReflectionHelpers.IsDictionary(valueType))
{
holder = new CacheDictionary();
}
else else
{ {
holder = new CacheOther(); holder = new CacheOther();
@ -163,7 +202,7 @@ namespace Explorer
{ {
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null); GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
} }
else if (Value == null) else if (Value == null && MemberInfoType != MemberTypes.Method)
{ {
GUILayout.Label("<i>null (" + ValueType + ")</i>", null); GUILayout.Label("<i>null (" + ValueType + ")</i>", null);
} }

View File

@ -9,7 +9,7 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CacheOther : CacheObject public class CacheOther : CacheObjectBase
{ {
private MethodInfo m_toStringMethod; private MethodInfo m_toStringMethod;
private bool m_triedToGetMethod; private bool m_triedToGetMethod;

View File

@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using MelonLoader; using MelonLoader;
using UnityEngine; using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CachePrimitive : CacheObject public class CachePrimitive : CacheObjectBase
{ {
public enum PrimitiveTypes public enum PrimitiveTypes
{ {
@ -13,7 +14,8 @@ namespace Explorer
Double, Double,
Float, Float,
Int, Int,
String String,
Char
} }
private string m_valueToString; private string m_valueToString;
@ -37,10 +39,10 @@ namespace Explorer
t = typeof(float); break; t = typeof(float); break;
case PrimitiveTypes.Int: case PrimitiveTypes.Int:
t = typeof(int); break; t = typeof(int); break;
case PrimitiveTypes.String: case PrimitiveTypes.Char:
t = typeof(string); break; 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; return m_parseMethod;
} }
@ -52,7 +54,7 @@ namespace Explorer
{ {
if (Value == null) 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; PrimitiveType = PrimitiveTypes.String;
return; return;
} }
@ -72,21 +74,39 @@ namespace Explorer
{ {
PrimitiveType = PrimitiveTypes.Float; 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; PrimitiveType = PrimitiveTypes.Int;
} }
else if (type == typeof(char))
{
PrimitiveType = PrimitiveTypes.Char;
}
else else
{ {
if (type != typeof(string))
{
MelonLogger.Log("Unsupported primitive: " + type);
}
PrimitiveType = PrimitiveTypes.String; 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<Type> _integerTypes = new HashSet<Type>
{
typeof(int),
typeof(uint),
typeof(short),
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(byte),
typeof(sbyte),
typeof(IntPtr)
};
public override void UpdateValue() public override void UpdateValue()
{ {
base.UpdateValue(); base.UpdateValue();
@ -99,21 +119,26 @@ namespace Explorer
if (PrimitiveType == PrimitiveTypes.Bool) if (PrimitiveType == PrimitiveTypes.Bool)
{ {
var b = (bool)Value; var b = (bool)Value;
var color = "<color=" + (b ? "lime>" : "red>"); var color = $"<color={(b ? "lime>" : "red>")}";
b = GUILayout.Toggle(b, color + b.ToString() + "</color>", null); var label = $"{color}{b}</color>";
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 else
{ {
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) }); GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", 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); int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f; var maxwidth = window.width - 300f;
if (CanWrite) maxwidth -= 60; if (CanWrite) maxwidth -= 60;

View File

@ -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)] //// Make it appear as though UnlockMouse is disabled to the rest of the application.
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)] //[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
public class Cursor_get_lockState //public class Cursor_get_visible
{ //{
[HarmonyPostfix] // [HarmonyPostfix]
public static void Postfix(ref CursorLockMode __result) // public static void Postfix(ref bool __result)
{ // {
if (ShouldForceMouse) // if (ShouldForceMouse)
{ // {
__result = m_lastLockMode; // __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

@ -120,11 +120,13 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CachedObjects\CacheDictionary.cs" />
<Compile Include="CachedObjects\CacheEnum.cs" /> <Compile Include="CachedObjects\CacheEnum.cs" />
<Compile Include="CachedObjects\CacheGameObject.cs" /> <Compile Include="CachedObjects\CacheGameObject.cs" />
<Compile Include="CachedObjects\CacheList.cs" /> <Compile Include="CachedObjects\CacheList.cs" />
<Compile Include="CachedObjects\CachePrimitive.cs" /> <Compile Include="CachedObjects\CachePrimitive.cs" />
<Compile Include="CachedObjects\CacheOther.cs" /> <Compile Include="CachedObjects\CacheOther.cs" />
<Compile Include="CachedObjects\CacheMethod.cs" />
<Compile Include="CppExplorer.cs" /> <Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" /> <Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Extensions\UnityExtensions.cs" /> <Compile Include="Extensions\UnityExtensions.cs" />
@ -132,7 +134,7 @@
<Compile Include="Helpers\UIHelpers.cs" /> <Compile Include="Helpers\UIHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" /> <Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="MainMenu\InspectUnderMouse.cs" /> <Compile Include="MainMenu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObject.cs" /> <Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="Windows\ResizeDrag.cs" /> <Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\UIWindow.cs" /> <Compile Include="Windows\UIWindow.cs" />
<Compile Include="MainMenu\Pages\ConsolePage.cs" /> <Compile Include="MainMenu\Pages\ConsolePage.cs" />

View File

@ -72,6 +72,11 @@ namespace Explorer
return false; return false;
} }
public static bool IsArray(Type t)
{
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
}
public static bool IsList(Type t) public static bool IsList(Type t)
{ {
return t.IsGenericType return t.IsGenericType
@ -79,6 +84,13 @@ namespace Explorer
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))); && (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) public static Type GetTypeByName(string typeName)
{ {
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())

View File

@ -121,7 +121,9 @@ MelonLogger.Log(""hello world"");";
{ {
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null); GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", 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) }); MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null)) if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
@ -147,10 +149,7 @@ MelonLogger.Log(""hello world"");";
} }
GUILayout.Label("<b>Using directives:</b>", null); GUILayout.Label("<b>Using directives:</b>", null);
foreach (var asm in UsingDirectives)
{
GUILayout.Label(AsmToUsing(asm, true), null);
}
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) }); GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) }); UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
@ -163,6 +162,11 @@ MelonLogger.Log(""hello world"");";
ResetConsole(); ResetConsole();
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
foreach (var asm in UsingDirectives)
{
GUILayout.Label(AsmToUsing(asm, true), null);
}
} }
public override void Update() { } public override void Update() { }

View File

@ -26,7 +26,7 @@ namespace Explorer
//private List<object> m_searchResults = new List<object>(); //private List<object> m_searchResults = new List<object>();
private Vector2 resultsScroll = Vector2.zero; private Vector2 resultsScroll = Vector2.zero;
private List<CacheObject> m_searchResults = new List<CacheObject>(); private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
public SceneFilter SceneMode = SceneFilter.Any; public SceneFilter SceneMode = SceneFilter.Any;
public TypeFilter TypeMode = TypeFilter.Object; public TypeFilter TypeMode = TypeFilter.Object;
@ -64,7 +64,7 @@ namespace Explorer
private void CacheResults(IEnumerable results) private void CacheResults(IEnumerable results)
{ {
m_searchResults = new List<CacheObject>(); m_searchResults = new List<CacheObjectBase>();
foreach (var obj in results) foreach (var obj in results)
{ {
@ -75,7 +75,7 @@ namespace Explorer
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject; toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
} }
var cache = CacheObject.GetCacheObject(toCache); var cache = CacheObjectBase.GetCacheObject(toCache);
m_searchResults.Add(cache); m_searchResults.Add(cache);
} }
} }

View File

@ -6,7 +6,6 @@ using MelonLoader;
using UnhollowerRuntimeLib; using UnhollowerRuntimeLib;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using ComponentList = Il2CppSystem.Collections.Generic.List<UnityEngine.Component>;
namespace Explorer namespace Explorer
{ {
@ -22,7 +21,7 @@ namespace Explorer
private Vector2 m_transformScroll = Vector2.zero; private Vector2 m_transformScroll = Vector2.zero;
private Transform[] m_children; private Transform[] m_children;
private ComponentList m_components; private Component[] m_components;
private Vector2 m_compScroll = Vector2.zero; private Vector2 m_compScroll = Vector2.zero;
@ -69,11 +68,9 @@ namespace Explorer
} }
m_name = m_object.name; m_name = m_object.name;
m_scene = m_object.scene == null ? "null" : m_object.scene.name; m_scene = string.IsNullOrEmpty(m_object.scene.name)
? "None"
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>(); : m_object.scene.name;
//m_object.GetComponents(listComps);
//m_components = listComps.ToArray();
var list = new List<Transform>(); var list = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++) for (int i = 0; i < m_object.transform.childCount; i++)
@ -92,8 +89,12 @@ namespace Explorer
throw new Exception("Object is null!"); throw new Exception("Object is null!");
} }
m_components = new Il2CppSystem.Collections.Generic.List<Component>(); var list = new List<Component>();
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, false, false, true, false, m_components); foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
{
list.Add(comp);
}
m_components = list.ToArray();
} }
catch (Exception e) catch (Exception e)
{ {
@ -255,34 +256,37 @@ namespace Explorer
m_cachedDestroyList.Clear(); m_cachedDestroyList.Clear();
} }
foreach (var component in m_components) if (m_components != null)
{ {
if (!component) continue; foreach (var component in m_components)
{
if (!component) continue;
var ilType = component.GetIl2CppType(); var ilType = component.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType) if (ilType == ReflectionHelpers.TransformType)
{ {
continue; continue;
} }
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType)) if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
{ {
BehaviourEnabledBtn(component.TryCast<Behaviour>()); BehaviourEnabledBtn(component.TryCast<Behaviour>());
}
else
{
GUILayout.Space(26);
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
{
ReflectObject(component);
}
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
{
m_cachedDestroyList.Add(component);
}
GUILayout.EndHorizontal();
} }
else
{
GUILayout.Space(26);
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
{
ReflectObject(component);
}
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
{
m_cachedDestroyList.Add(component);
}
GUILayout.EndHorizontal();
} }
GUI.skin.button.alignment = TextAnchor.MiddleCenter; GUI.skin.button.alignment = TextAnchor.MiddleCenter;

View File

@ -16,30 +16,27 @@ namespace Explorer
public Type ObjectType; public Type ObjectType;
private CacheObject[] m_cachedMembers; private CacheObjectBase[] m_cachedMembers;
private CacheObject[] m_cachedMemberFiltered; private CacheObjectBase[] m_cachedMemberFiltered;
private int m_pageOffset; private int m_pageOffset;
private int m_limitPerPage = 20; private int m_limitPerPage = 20;
private bool m_autoUpdate = false; private bool m_autoUpdate = false;
private string m_search = ""; private string m_search = "";
public MemberInfoType m_filter = MemberInfoType.Property; public MemberTypes m_filter = MemberTypes.Property;
private bool m_hideFailedReflection = false; private bool m_hideFailedReflection = false;
public enum MemberInfoType // some extra caching
{ private UnityEngine.Object m_uObj;
Field, private Component m_component;
Property,
Method,
All
}
public override void Init() public override void Init()
{ {
var type = ReflectionHelpers.GetActualType(Target); var type = ReflectionHelpers.GetActualType(Target);
if (type == null) if (type == null)
{ {
MelonLogger.Log("Could not get underlying type for object. ToString(): " + Target.ToString()); MelonLogger.Log($"Could not get underlying type for object..? Type: {Target?.GetType().Name}, ToString: {Target?.ToString()}");
DestroyWindow();
return; return;
} }
@ -48,10 +45,27 @@ namespace Explorer
var types = ReflectionHelpers.GetAllBaseTypes(Target); var types = ReflectionHelpers.GetAllBaseTypes(Target);
CacheMembers(types); CacheMembers(types);
m_filter = MemberInfoType.All; if (Target is Il2CppSystem.Object ilObject)
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray(); {
UpdateValues(); var unityObj = ilObject.TryCast<UnityEngine.Object>();
m_filter = MemberInfoType.Property; if (unityObj)
{
m_uObj = unityObj;
var component = ilObject.TryCast<Component>();
if (component)
{
m_component = component;
}
}
}
m_filter = MemberTypes.All;
m_autoUpdate = true;
Update();
m_autoUpdate = false;
m_filter = MemberTypes.Property;
} }
public override void Update() public override void Update()
@ -65,11 +79,6 @@ namespace Explorer
} }
private void UpdateValues() private void UpdateValues()
{
UpdateMembers();
}
private void UpdateMembers()
{ {
foreach (var member in m_cachedMemberFiltered) foreach (var member in m_cachedMemberFiltered)
{ {
@ -77,9 +86,9 @@ namespace Explorer
} }
} }
private bool ShouldProcessMember(CacheObject holder) private bool ShouldProcessMember(CacheObjectBase holder)
{ {
if (m_filter != MemberInfoType.All && m_filter != holder.MemberInfoType) return false; if (m_filter != MemberTypes.All && m_filter != holder.MemberInfoType) return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false; if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
@ -92,16 +101,13 @@ namespace Explorer
private void CacheMembers(Type[] types) private void CacheMembers(Type[] types)
{ {
var list = new List<CacheObject>(); var list = new List<CacheObjectBase>();
var names = new List<string>(); var names = new List<string>();
foreach (var declaringType in types) foreach (var declaringType in types)
{ {
if (declaringType == typeof(Il2CppObjectBase)) continue;
MemberInfo[] infos; MemberInfo[] infos;
string exception = null; string exception = null;
try try
@ -114,8 +120,8 @@ namespace Explorer
continue; continue;
} }
//object value = null;
object target = Target; object target = Target;
if (target is Il2CppSystem.Object ilObject) if (target is Il2CppSystem.Object ilObject)
{ {
try try
@ -130,9 +136,10 @@ namespace Explorer
foreach (var member in infos) foreach (var member in infos)
{ {
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property) if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
{ {
if (member.Name == "Il2CppType") continue; if (member.Name.Contains("Il2CppType") || member.Name.StartsWith("get_"))
continue;
try try
{ {
@ -140,7 +147,7 @@ namespace Explorer
if (names.Contains(name)) continue; if (names.Contains(name)) continue;
names.Add(name); names.Add(name);
var cached = CacheObject.GetCacheObject(null, member, target); var cached = CacheObjectBase.GetCacheObject(null, member, target);
if (cached != null) if (cached != null)
{ {
list.Add(cached); list.Add(cached);
@ -149,9 +156,8 @@ namespace Explorer
} }
catch (Exception e) catch (Exception e)
{ {
MelonLogger.Log("Exception caching member!"); MelonLogger.LogWarning($"Exception caching member {declaringType.Name}.{member.Name}!");
MelonLogger.Log(e.GetType() + ", " + e.Message); MelonLogger.Log(e.ToString());
MelonLogger.Log(e.StackTrace);
} }
} }
} }
@ -172,26 +178,18 @@ namespace Explorer
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.FullName + "</color>", null); GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.FullName + "</color>", null);
if (m_uObj)
bool unityObj = Target is UnityEngine.Object;
if (unityObj)
{ {
GUILayout.Label("Name: " + (Target as UnityEngine.Object).name, null); GUILayout.Label("Name: " + m_uObj.name, null);
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
if (unityObj) if (m_uObj)
{ {
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) }); GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
UIHelpers.InstantiateButton(m_uObj);
UIHelpers.InstantiateButton((UnityEngine.Object)Target); if (m_component && m_component.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; GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("GameObject:", null); GUILayout.Label("GameObject:", null);
@ -201,7 +199,6 @@ namespace Explorer
} }
GUI.skin.label.alignment = TextAnchor.UpperLeft; GUI.skin.label.alignment = TextAnchor.UpperLeft;
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
@ -221,9 +218,10 @@ namespace Explorer
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) }); GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
FilterToggle(MemberInfoType.All, "All"); FilterToggle(MemberTypes.All, "All");
FilterToggle(MemberInfoType.Property, "Properties"); FilterToggle(MemberTypes.Property, "Properties");
FilterToggle(MemberInfoType.Field, "Fields"); FilterToggle(MemberTypes.Field, "Fields");
FilterToggle(MemberTypes.Method, "Methods");
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -284,18 +282,19 @@ namespace Explorer
} }
} }
private void DrawMembers(CacheObject[] members) private void DrawMembers(CacheObjectBase[] members)
{ {
// todo pre-cache list based on current search, otherwise this doesnt work. // todo pre-cache list based on current search, otherwise this doesnt work.
int i = 0; int i = 0;
DrawMembersInternal("Properties", MemberInfoType.Property, members, ref i); DrawMembersInternal("Properties", MemberTypes.Property, members, ref i);
DrawMembersInternal("Fields", MemberInfoType.Field, members, ref i); DrawMembersInternal("Fields", MemberTypes.Field, members, ref i);
DrawMembersInternal("Methods", MemberTypes.Method, members, ref i);
} }
private void DrawMembersInternal(string title, MemberInfoType filter, CacheObject[] members, ref int index) private void DrawMembersInternal(string title, MemberTypes filter, CacheObjectBase[] members, ref int index)
{ {
if (m_filter != filter && m_filter != MemberInfoType.All) if (m_filter != filter && m_filter != MemberTypes.All)
{ {
return; return;
} }
@ -336,7 +335,7 @@ namespace Explorer
} }
} }
private void FilterToggle(MemberInfoType mode, string label) private void FilterToggle(MemberTypes mode, string label)
{ {
if (m_filter == mode) if (m_filter == mode)
{ {

View File

@ -59,6 +59,8 @@ namespace Explorer
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message); MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
} }
GUI.skin.label.alignment = TextAnchor.UpperLeft;
return _rect; return _rect;
} }
} }