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.
* 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

@ -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
{
public class CacheEnum : CacheObject
public class CacheEnum : CacheObjectBase
{
public Type EnumType;
public string[] EnumNames;

View File

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

View File

@ -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 = "<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 _);
}
@ -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<CacheObject>();
var list = new List<CacheObjectBase>();
while (enumerator.MoveNext())
{
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
{
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
/// <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>
/// <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)
{
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
/// <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>
/// <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))
&& (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("<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);
}

View File

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

View File

@ -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<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()
{
base.UpdateValue();
@ -99,21 +119,26 @@ namespace Explorer
if (PrimitiveType == PrimitiveTypes.Bool)
{
var b = (bool)Value;
var color = "<color=" + (b ? "lime>" : "red>");
b = GUILayout.Toggle(b, color + b.ToString() + "</color>", null);
var color = $"<color={(b ? "lime>" : "red>")}";
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
{
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);
var maxwidth = window.width - 300f;
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)]
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;
// }
// }
//}
}
}

View File

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

View File

@ -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())

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("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("<color=cyan><b>Execute</b></color>", null))
@ -147,10 +149,7 @@ MelonLogger.Log(""hello world"");";
}
GUILayout.Label("<b>Using directives:</b>", 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() { }

View File

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

View File

@ -6,7 +6,6 @@ using MelonLoader;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.SceneManagement;
using ComponentList = Il2CppSystem.Collections.Generic.List<UnityEngine.Component>;
namespace Explorer
{
@ -22,7 +21,7 @@ namespace Explorer
private Vector2 m_transformScroll = Vector2.zero;
private Transform[] m_children;
private ComponentList m_components;
private Component[] m_components;
private Vector2 m_compScroll = Vector2.zero;
@ -69,11 +68,9 @@ namespace Explorer
}
m_name = m_object.name;
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
//m_object.GetComponents(listComps);
//m_components = listComps.ToArray();
m_scene = string.IsNullOrEmpty(m_object.scene.name)
? "None"
: m_object.scene.name;
var list = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
@ -92,8 +89,12 @@ namespace Explorer
throw new Exception("Object is null!");
}
m_components = new Il2CppSystem.Collections.Generic.List<Component>();
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, false, false, true, false, m_components);
var list = new List<Component>();
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
{
list.Add(comp);
}
m_components = list.ToArray();
}
catch (Exception e)
{
@ -255,34 +256,37 @@ namespace Explorer
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();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
var ilType = component.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
{
BehaviourEnabledBtn(component.TryCast<Behaviour>());
GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
{
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;

View File

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

View File

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