mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-28 10:52:44 +08:00
1.4.0
- Wrote the CacheObject class to replace MemberInfoHolder, resulting code is better perfomance and much easier to read. - Added pages to Object Reflection window, now limited to 20 members per page to improve performance further.
This commit is contained in:
69
src/CachedObjects/CacheEnum.cs
Normal file
69
src/CachedObjects/CacheEnum.cs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheEnum : CacheObject
|
||||||
|
{
|
||||||
|
private readonly Type m_enumType;
|
||||||
|
private readonly string[] m_names;
|
||||||
|
|
||||||
|
public CacheEnum(object obj)
|
||||||
|
{
|
||||||
|
m_enumType = obj.GetType();
|
||||||
|
m_names = Enum.GetNames(obj.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
if (MemberInfo != null)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||||
|
{
|
||||||
|
SetEnum(ref Value, -1);
|
||||||
|
SetValue();
|
||||||
|
}
|
||||||
|
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||||
|
{
|
||||||
|
SetEnum(ref Value, 1);
|
||||||
|
SetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label(Value.ToString(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
if (MemberInfo == null)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Enum.Parse(m_enumType, Value.ToString()) is object enumValue && enumValue != null)
|
||||||
|
{
|
||||||
|
Value = enumValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetValue(Value, MemberInfo, DeclaringInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEnum(ref object value, int change)
|
||||||
|
{
|
||||||
|
var names = m_names.ToList();
|
||||||
|
|
||||||
|
int newindex = names.IndexOf(value.ToString()) + change;
|
||||||
|
|
||||||
|
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
||||||
|
{
|
||||||
|
value = Enum.Parse(m_enumType, names[newindex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/CachedObjects/CacheGameObject.cs
Normal file
53
src/CachedObjects/CacheGameObject.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheGameObject : CacheObject
|
||||||
|
{
|
||||||
|
private GameObject m_gameObject;
|
||||||
|
|
||||||
|
public CacheGameObject(object obj)
|
||||||
|
{
|
||||||
|
if (obj != null)
|
||||||
|
m_gameObject = GetGameObject(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject GetGameObject(object obj)
|
||||||
|
{
|
||||||
|
if (obj is Il2CppSystem.Object ilObj)
|
||||||
|
{
|
||||||
|
var ilType = ilObj.GetIl2CppType();
|
||||||
|
|
||||||
|
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
|
||||||
|
{
|
||||||
|
return ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
UIHelpers.GameobjButton(m_gameObject, null, false, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue(object obj)
|
||||||
|
{
|
||||||
|
base.UpdateValue(obj);
|
||||||
|
|
||||||
|
m_gameObject = GetGameObject(Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/CachedObjects/CacheIl2CppObject.cs
Normal file
37
src/CachedObjects/CacheIl2CppObject.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheIl2CppObject : CacheObject
|
||||||
|
{
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
var label = ValueType ?? Value.ToString();
|
||||||
|
if (!label.Contains(ValueType))
|
||||||
|
{
|
||||||
|
label += $" ({ValueType})";
|
||||||
|
}
|
||||||
|
if (Value is UnityEngine.Object unityObj)
|
||||||
|
{
|
||||||
|
label = unityObj.name + " | " + label;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(width) }))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
|
}
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("TODO");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
src/CachedObjects/CacheList.cs
Normal file
153
src/CachedObjects/CacheList.cs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public partial class CacheList : CacheObject
|
||||||
|
{
|
||||||
|
public bool IsExpanded { get; set; }
|
||||||
|
public int ArrayOffset { get; set; }
|
||||||
|
public Type EntryType { get; set; }
|
||||||
|
|
||||||
|
private IEnumerable m_enumerable;
|
||||||
|
private CacheObject[] m_cachedEntries;
|
||||||
|
|
||||||
|
public CacheList(object obj)
|
||||||
|
{
|
||||||
|
GetEnumerable(obj);
|
||||||
|
EntryType = m_enumerable.GetType().GetGenericArguments()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetEnumerable(object obj)
|
||||||
|
{
|
||||||
|
if (obj is IEnumerable isEnumerable)
|
||||||
|
{
|
||||||
|
m_enumerable = isEnumerable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var listValueType = obj.GetType().GetGenericArguments()[0];
|
||||||
|
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
|
||||||
|
var method = listType.GetMethod("ToArray");
|
||||||
|
m_enumerable = (IEnumerable)method.Invoke(obj, new object[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
int count = m_cachedEntries.Length;
|
||||||
|
|
||||||
|
if (!IsExpanded)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||||
|
{
|
||||||
|
IsExpanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||||
|
{
|
||||||
|
IsExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
|
||||||
|
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) }))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
|
}
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
if (IsExpanded)
|
||||||
|
{
|
||||||
|
if (count > CppExplorer.ArrayLimit)
|
||||||
|
{
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Space(190);
|
||||||
|
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
||||||
|
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||||
|
// prev/next page buttons
|
||||||
|
if (GUILayout.Button("< Prev", null))
|
||||||
|
{
|
||||||
|
if (ArrayOffset > 0) ArrayOffset--;
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Next >", null))
|
||||||
|
{
|
||||||
|
if (ArrayOffset < maxOffset) ArrayOffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = ArrayOffset * CppExplorer.ArrayLimit;
|
||||||
|
|
||||||
|
if (offset >= count) offset = 0;
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
||||||
|
{
|
||||||
|
var entry = m_cachedEntries[i];
|
||||||
|
|
||||||
|
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Space(190);
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(30) });
|
||||||
|
|
||||||
|
entry.DrawValue(window, window.width - 250);
|
||||||
|
|
||||||
|
//var lbl = i + ": <color=cyan>" + obj.Value.ToString() + "</color>";
|
||||||
|
|
||||||
|
//if (EntryType.IsPrimitive || typeof(string).IsAssignableFrom(EntryType))
|
||||||
|
//{
|
||||||
|
// GUILayout.Label(lbl, null);
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
// if (GUILayout.Button(lbl, null))
|
||||||
|
// {
|
||||||
|
// WindowManager.InspectObject(obj, out _);
|
||||||
|
// }
|
||||||
|
// GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue(object obj)
|
||||||
|
{
|
||||||
|
GetEnumerable(Value);
|
||||||
|
|
||||||
|
var list = new List<CacheObject>();
|
||||||
|
|
||||||
|
var enumerator = m_enumerable.GetEnumerator();
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
list.Add(GetCacheObject(enumerator.Current));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cachedEntries = list.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
src/CachedObjects/CacheObject.cs
Normal file
194
src/CachedObjects/CacheObject.cs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public abstract class CacheObject
|
||||||
|
{
|
||||||
|
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 abstract void DrawValue(Rect window, float width);
|
||||||
|
public abstract void SetValue();
|
||||||
|
|
||||||
|
public static CacheObject GetCacheObject(object obj)
|
||||||
|
{
|
||||||
|
return GetCacheObject(obj, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
|
||||||
|
{
|
||||||
|
CacheObject holder;
|
||||||
|
|
||||||
|
var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
|
||||||
|
|
||||||
|
if (obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var name = type.FullName;
|
||||||
|
if (name == "UnityEngine.GameObject" || name == "UnityEngine.Transform")
|
||||||
|
{
|
||||||
|
holder = new CacheGameObject(obj);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder = new CacheIl2CppObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (type.IsPrimitive || type == typeof(string))
|
||||||
|
{
|
||||||
|
holder = new CachePrimitive(obj);
|
||||||
|
}
|
||||||
|
else if (type.IsEnum)
|
||||||
|
{
|
||||||
|
holder = new CacheEnum(obj);
|
||||||
|
}
|
||||||
|
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) || ReflectionHelpers.IsList(type))
|
||||||
|
{
|
||||||
|
holder = new CacheList(obj);
|
||||||
|
}
|
||||||
|
else if (type.IsValueType)
|
||||||
|
{
|
||||||
|
holder = new CacheStruct(obj);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder = new CacheOther();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holder == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberInfo != null)
|
||||||
|
{
|
||||||
|
holder.MemberInfo = memberInfo;
|
||||||
|
holder.DeclaringType = memberInfo.DeclaringType;
|
||||||
|
|
||||||
|
if (declaringInstance is Il2CppSystem.Object ilInstance && ilInstance.GetType() != memberInfo.DeclaringType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
holder.DeclaringInstance = ilInstance.Il2CppCast(holder.DeclaringType);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
holder.DeclaringInstance = declaringInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder.DeclaringInstance = declaringInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberInfo.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Field;
|
||||||
|
}
|
||||||
|
else if (memberInfo.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Property;
|
||||||
|
}
|
||||||
|
else if (memberInfo.MemberType == MemberTypes.Method)
|
||||||
|
{
|
||||||
|
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.Value = obj;
|
||||||
|
holder.ValueType = type.FullName;
|
||||||
|
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(Rect window, float labelWidth = 180f)
|
||||||
|
{
|
||||||
|
if (MemberInfo != null)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<color=cyan>" + MemberInfo.Name + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Space(labelWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Value == null)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<i>null (" + this.ValueType + ")</i>", null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawValue(window, window.width - labelWidth - 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void UpdateValue(object obj)
|
||||||
|
{
|
||||||
|
if (MemberInfo == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (MemberInfo.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
var fi = MemberInfo as FieldInfo;
|
||||||
|
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||||
|
}
|
||||||
|
else if (MemberInfo.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
var pi = MemberInfo as PropertyInfo;
|
||||||
|
Value = pi.GetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch //(Exception e)
|
||||||
|
{
|
||||||
|
//MelonLogger.Log($"Error updating MemberInfo value | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(object value, MemberInfo memberInfo, object declaringInstance)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (memberInfo.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
var fi = memberInfo as FieldInfo;
|
||||||
|
if (!(fi.IsLiteral && !fi.IsInitOnly))
|
||||||
|
{
|
||||||
|
fi.SetValue(fi.IsStatic ? null : declaringInstance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (memberInfo.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
var pi = memberInfo as PropertyInfo;
|
||||||
|
if (pi.CanWrite)
|
||||||
|
{
|
||||||
|
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : declaringInstance, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
src/CachedObjects/CacheOther.cs
Normal file
49
src/CachedObjects/CacheOther.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheOther : CacheObject
|
||||||
|
{
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
string label;
|
||||||
|
if (Value is UnityEngine.Object uObj)
|
||||||
|
{
|
||||||
|
label = uObj.name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label = Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
string typeLabel = Value.GetType().FullName;
|
||||||
|
|
||||||
|
if (!label.Contains(typeLabel))
|
||||||
|
{
|
||||||
|
label += $" ({typeLabel})";
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
|
}
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue(object obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
src/CachedObjects/CachePrimitive.cs
Normal file
105
src/CachedObjects/CachePrimitive.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CachePrimitive : CacheObject
|
||||||
|
{
|
||||||
|
public enum PrimitiveType
|
||||||
|
{
|
||||||
|
Bool,
|
||||||
|
Double,
|
||||||
|
Float,
|
||||||
|
Int,
|
||||||
|
String
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly PrimitiveType m_primitiveType;
|
||||||
|
|
||||||
|
public CachePrimitive(object obj)
|
||||||
|
{
|
||||||
|
if (obj is bool)
|
||||||
|
{
|
||||||
|
m_primitiveType = PrimitiveType.Bool;
|
||||||
|
}
|
||||||
|
else if (obj is double)
|
||||||
|
{
|
||||||
|
m_primitiveType = PrimitiveType.Double;
|
||||||
|
}
|
||||||
|
else if (obj is float)
|
||||||
|
{
|
||||||
|
m_primitiveType = PrimitiveType.Float;
|
||||||
|
}
|
||||||
|
else if (obj is int)
|
||||||
|
{
|
||||||
|
m_primitiveType = PrimitiveType.Int;
|
||||||
|
}
|
||||||
|
else if (obj is string)
|
||||||
|
{
|
||||||
|
m_primitiveType = PrimitiveType.String;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
if (m_primitiveType == PrimitiveType.Bool && Value is bool b)
|
||||||
|
{
|
||||||
|
var color = "<color=" + (b ? "lime>" : "red>");
|
||||||
|
Value = GUILayout.Toggle((bool)Value, color + Value.ToString() + "</color>", null);
|
||||||
|
|
||||||
|
if (b != (bool)Value)
|
||||||
|
{
|
||||||
|
SetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var toString = Value.ToString();
|
||||||
|
if (toString.Length > 37)
|
||||||
|
{
|
||||||
|
Value = GUILayout.TextArea(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Value = GUILayout.TextField(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MemberInfo != null)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||||
|
{
|
||||||
|
SetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
if (MemberInfo == null)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_primitiveType)
|
||||||
|
{
|
||||||
|
case PrimitiveType.Bool:
|
||||||
|
SetValue(bool.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
||||||
|
case PrimitiveType.Double:
|
||||||
|
SetValue(double.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
||||||
|
case PrimitiveType.Float:
|
||||||
|
SetValue(float.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
||||||
|
case PrimitiveType.Int:
|
||||||
|
SetValue(int.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
||||||
|
case PrimitiveType.String:
|
||||||
|
SetValue(Value.ToString(), MemberInfo, DeclaringInstance); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/CachedObjects/CacheStruct.cs
Normal file
71
src/CachedObjects/CacheStruct.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheStruct : CacheObject
|
||||||
|
{
|
||||||
|
public MethodInfo ToStringMethod { get; private set; }
|
||||||
|
private static readonly MethodInfo m_defaultToString = typeof(object).GetMethod("ToString");
|
||||||
|
|
||||||
|
public CacheStruct(object obj)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var methods = obj.GetType().GetMethods(ReflectionHelpers.CommonFlags).Where(x => x.Name == "ToString");
|
||||||
|
var enumerator = methods.GetEnumerator();
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
ToStringMethod = enumerator.Current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ToStringMethod = m_defaultToString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
string label;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
label = (string)ToStringMethod.Invoke(Value, null);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
label = Value.ToString();
|
||||||
|
}
|
||||||
|
string typeLabel = Value.GetType().FullName;
|
||||||
|
|
||||||
|
if (!label.Contains(typeLabel))
|
||||||
|
{
|
||||||
|
label += $" ({typeLabel})";
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
|
}
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetValue()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
//public override void UpdateValue(object obj)
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,10 @@ namespace Explorer
|
|||||||
// consts
|
// consts
|
||||||
|
|
||||||
public const string ID = "com.sinai.cppexplorer";
|
public const string ID = "com.sinai.cppexplorer";
|
||||||
public const string VERSION = "1.3.5";
|
public const string VERSION = "1.4.0";
|
||||||
public const string AUTHOR = "Sinai";
|
public const string AUTHOR = "Sinai";
|
||||||
|
|
||||||
public const string NAME = "IL2CPP Runtime Explorer"
|
public const string NAME = "CppExplorer"
|
||||||
#if Release_Unity2018
|
#if Release_Unity2018
|
||||||
+ " (Unity 2018)"
|
+ " (Unity 2018)"
|
||||||
#endif
|
#endif
|
||||||
|
@ -136,6 +136,13 @@
|
|||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="CachedObjects\CacheEnum.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheGameObject.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheList.cs" />
|
||||||
|
<Compile Include="CachedObjects\CachePrimitive.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheIl2CppObject.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheStruct.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheOther.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" />
|
||||||
@ -143,9 +150,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="Windows\Reflection\FieldInfoHolder.cs" />
|
<Compile Include="CachedObjects\CacheObject.cs" />
|
||||||
<Compile Include="Windows\Reflection\MemberInfoHolder.cs" />
|
|
||||||
<Compile Include="Windows\Reflection\PropertyInfoHolder.cs" />
|
|
||||||
<Compile Include="Windows\UIWindow.cs" />
|
<Compile Include="Windows\UIWindow.cs" />
|
||||||
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
||||||
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
||||||
|
@ -56,6 +56,8 @@ namespace Explorer
|
|||||||
|
|
||||||
public static Type GetActualType(object m_object)
|
public static Type GetActualType(object m_object)
|
||||||
{
|
{
|
||||||
|
if (m_object == null) return null;
|
||||||
|
|
||||||
if (m_object is Il2CppSystem.Object ilObject)
|
if (m_object is Il2CppSystem.Object ilObject)
|
||||||
{
|
{
|
||||||
var iltype = ilObject.GetIl2CppType();
|
var iltype = ilObject.GetIl2CppType();
|
||||||
|
@ -5,7 +5,6 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
@ -103,311 +102,5 @@ namespace Explorer
|
|||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DrawMember(ref object value, ref bool isExpanded, ref int arrayOffset, MemberInfo memberInfo, Rect rect, object setTarget = null, Action<object> setAction = null, float labelWidth = 180, bool autoSet = false)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=cyan>" + memberInfo.Name + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
|
||||||
|
|
||||||
string valueType = "";
|
|
||||||
bool canWrite = true;
|
|
||||||
if (memberInfo is FieldInfo fi)
|
|
||||||
{
|
|
||||||
valueType = fi.FieldType.Name;
|
|
||||||
canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
|
|
||||||
}
|
|
||||||
else if (memberInfo is PropertyInfo pi)
|
|
||||||
{
|
|
||||||
valueType = pi.PropertyType.Name;
|
|
||||||
canWrite = pi.CanWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawValue(ref value, ref isExpanded, ref arrayOffset, rect, valueType, (canWrite ? setTarget : null), (canWrite ? setAction : null), autoSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DrawValue(ref object value, ref bool isExpanded, ref int arrayOffset, Rect rect, string nullValueType = null, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i>null (" + nullValueType + ")</i>", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var valueType = value.GetType();
|
|
||||||
|
|
||||||
Il2CppSystem.Type ilType = null;
|
|
||||||
if (value is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
ilType = ilObject.GetIl2CppType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueType.IsPrimitive || value.GetType() == typeof(string))
|
|
||||||
{
|
|
||||||
DrawPrimitive(ref value, rect, setTarget, setAction);
|
|
||||||
}
|
|
||||||
else if (ilType != null && ilType == ReflectionHelpers.GameObjectType || ReflectionHelpers.TransformType.IsAssignableFrom(ilType))
|
|
||||||
{
|
|
||||||
GameObject go;
|
|
||||||
var ilObj = value as Il2CppSystem.Object;
|
|
||||||
if (ilType == ReflectionHelpers.GameObjectType)
|
|
||||||
{
|
|
||||||
go = ilObj.TryCast<GameObject>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
go = ilObj.TryCast<Transform>().gameObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameobjButton(go, null, false, rect.width - 250);
|
|
||||||
}
|
|
||||||
else if (valueType.IsEnum)
|
|
||||||
{
|
|
||||||
if (setAction != null)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
SetEnum(ref value, -1);
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
SetEnum(ref value, 1);
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label(value.ToString(), null);
|
|
||||||
}
|
|
||||||
else if (value is System.Collections.IEnumerable || ReflectionHelpers.IsList(valueType))
|
|
||||||
{
|
|
||||||
System.Collections.IEnumerable enumerable;
|
|
||||||
|
|
||||||
if (value is System.Collections.IEnumerable isEnumerable)
|
|
||||||
{
|
|
||||||
enumerable = isEnumerable;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var listValueType = value.GetType().GetGenericArguments()[0];
|
|
||||||
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
|
|
||||||
var method = listType.GetMethod("ToArray");
|
|
||||||
enumerable = (System.Collections.IEnumerable)method.Invoke(value, new object[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = enumerable.Cast<object>().Count();
|
|
||||||
|
|
||||||
if (!isExpanded)
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
isExpanded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
|
||||||
{
|
|
||||||
isExpanded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
string btnLabel = "<color=yellow>[" + count + "] " + valueType + "</color>";
|
|
||||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 260) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
|
|
||||||
if (isExpanded)
|
|
||||||
{
|
|
||||||
if (count > CppExplorer.ArrayLimit)
|
|
||||||
{
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Space(190);
|
|
||||||
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
|
||||||
GUILayout.Label($"Page {arrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
|
||||||
// prev/next page buttons
|
|
||||||
if (GUILayout.Button("< Prev", null))
|
|
||||||
{
|
|
||||||
if (arrayOffset > 0) arrayOffset--;
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("Next >", null))
|
|
||||||
{
|
|
||||||
if (arrayOffset < maxOffset) arrayOffset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int offset = arrayOffset * CppExplorer.ArrayLimit;
|
|
||||||
|
|
||||||
if (offset >= count) offset = 0;
|
|
||||||
|
|
||||||
var enumerator = enumerable.GetEnumerator();
|
|
||||||
if (enumerator != null)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
int preiterated = 0;
|
|
||||||
while (enumerator.MoveNext())
|
|
||||||
{
|
|
||||||
if (offset > 0 && preiterated < offset)
|
|
||||||
{
|
|
||||||
preiterated++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = enumerator.Current;
|
|
||||||
|
|
||||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Space(190);
|
|
||||||
|
|
||||||
if (i > CppExplorer.ArrayLimit - 1)
|
|
||||||
{
|
|
||||||
//GUILayout.Label($"<i><color=red>{count - CppExplorer.ArrayLimit} results omitted, array is too long!</color></i>", null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var type = obj.GetType();
|
|
||||||
|
|
||||||
var lbl = (i + offset) + ": <color=cyan>" + obj.ToString() + "</color>";
|
|
||||||
|
|
||||||
if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
GUILayout.Label(lbl, null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button(lbl, null))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
//var type = obj.GetType();
|
|
||||||
//DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var label = value.ToString();
|
|
||||||
|
|
||||||
if (valueType == typeof(Object))
|
|
||||||
{
|
|
||||||
label = (value as Object).name;
|
|
||||||
}
|
|
||||||
else if (value is Vector4 vec4)
|
|
||||||
{
|
|
||||||
label = vec4.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Vector3 vec3)
|
|
||||||
{
|
|
||||||
label = vec3.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Vector2 vec2)
|
|
||||||
{
|
|
||||||
label = vec2.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Rect rec)
|
|
||||||
{
|
|
||||||
label = rec.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Matrix4x4 matrix)
|
|
||||||
{
|
|
||||||
label = matrix.ToString();
|
|
||||||
}
|
|
||||||
else if (value is Color col)
|
|
||||||
{
|
|
||||||
label = col.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
string typeLabel;
|
|
||||||
if (ilType != null)
|
|
||||||
{
|
|
||||||
typeLabel = ilType.FullName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
typeLabel = value.GetType().FullName;
|
|
||||||
}
|
|
||||||
if (!label.Contains(typeLabel))
|
|
||||||
{
|
|
||||||
label += $" ({typeLabel})";
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for drawing primitive values (with Apply button)
|
|
||||||
|
|
||||||
public static void DrawPrimitive(ref object value, Rect m_rect, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
|
||||||
{
|
|
||||||
bool allowSet = setAction != null;
|
|
||||||
|
|
||||||
if (value.GetType() == typeof(bool))
|
|
||||||
{
|
|
||||||
bool b = (bool)value;
|
|
||||||
var color = "<color=" + (b ? "lime>" : "red>");
|
|
||||||
|
|
||||||
if (allowSet)
|
|
||||||
{
|
|
||||||
value = GUILayout.Toggle((bool)value, color + value.ToString() + "</color>", null);
|
|
||||||
|
|
||||||
if (b != (bool)value)
|
|
||||||
{
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (value.ToString().Length > 37)
|
|
||||||
{
|
|
||||||
value = GUILayout.TextArea(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value = GUILayout.TextField(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoSet || (allowSet && GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) })))
|
|
||||||
{
|
|
||||||
setAction.Invoke(setTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for setting an enum
|
|
||||||
|
|
||||||
public static void SetEnum(ref object value, int change)
|
|
||||||
{
|
|
||||||
var type = value.GetType();
|
|
||||||
var names = Enum.GetNames(type).ToList();
|
|
||||||
|
|
||||||
int newindex = names.IndexOf(value.ToString()) + change;
|
|
||||||
|
|
||||||
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
|
||||||
{
|
|
||||||
value = Enum.Parse(type, names[newindex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -22,9 +23,11 @@ namespace Explorer
|
|||||||
private string m_typeInput = "";
|
private string m_typeInput = "";
|
||||||
private int m_limit = 20;
|
private int m_limit = 20;
|
||||||
private int m_pageOffset = 0;
|
private int m_pageOffset = 0;
|
||||||
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>();
|
||||||
|
|
||||||
public SceneFilter SceneMode = SceneFilter.Any;
|
public SceneFilter SceneMode = SceneFilter.Any;
|
||||||
public TypeFilter TypeMode = TypeFilter.Object;
|
public TypeFilter TypeMode = TypeFilter.Object;
|
||||||
|
|
||||||
@ -59,6 +62,24 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CacheResults(IEnumerable results)
|
||||||
|
{
|
||||||
|
m_searchResults = new List<CacheObject>();
|
||||||
|
|
||||||
|
foreach (var obj in results)
|
||||||
|
{
|
||||||
|
var toCache = obj;
|
||||||
|
|
||||||
|
if (toCache is Il2CppSystem.Object ilObject)
|
||||||
|
{
|
||||||
|
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache = CacheObject.GetCacheObject(toCache);
|
||||||
|
m_searchResults.Add(cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void DrawWindow()
|
public override void DrawWindow()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -68,7 +89,8 @@ namespace Explorer
|
|||||||
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||||
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||||
{
|
{
|
||||||
m_searchResults = GetInstanceClassScanner().ToList();
|
//m_searchResults = GetInstanceClassScanner().ToList();
|
||||||
|
CacheResults(GetInstanceClassScanner());
|
||||||
m_pageOffset = 0;
|
m_pageOffset = 0;
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
@ -111,28 +133,12 @@ namespace Explorer
|
|||||||
if (m_searchResults.Count > 0)
|
if (m_searchResults.Count > 0)
|
||||||
{
|
{
|
||||||
int offset = m_pageOffset * this.m_limit;
|
int offset = m_pageOffset * this.m_limit;
|
||||||
int preiterated = 0;
|
|
||||||
|
|
||||||
if (offset >= count) m_pageOffset = 0;
|
if (offset >= count) m_pageOffset = 0;
|
||||||
|
|
||||||
for (int i = 0; i < m_searchResults.Count; i++)
|
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
||||||
{
|
{
|
||||||
if (offset > 0 && preiterated < offset)
|
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||||
{
|
//m_searchResults[i].DrawValue(MainMenu.MainRect);
|
||||||
preiterated++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i - offset > this.m_limit - 1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = m_searchResults[i];
|
|
||||||
|
|
||||||
bool _ = false;
|
|
||||||
int __ = 0;
|
|
||||||
UIHelpers.DrawValue(ref obj, ref _, ref __, _temprect);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -141,7 +147,6 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
GUILayout.EndScrollView();
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -252,7 +257,7 @@ namespace Explorer
|
|||||||
private void Search()
|
private void Search()
|
||||||
{
|
{
|
||||||
m_pageOffset = 0;
|
m_pageOffset = 0;
|
||||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class FieldInfoHolder : MemberInfoHolder
|
|
||||||
{
|
|
||||||
public FieldInfo fieldInfo;
|
|
||||||
public object m_value;
|
|
||||||
|
|
||||||
public FieldInfoHolder(Type _type, FieldInfo _fieldInfo)
|
|
||||||
{
|
|
||||||
classType = _type;
|
|
||||||
fieldInfo = _fieldInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (obj is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var declaringType = this.fieldInfo.DeclaringType;
|
|
||||||
|
|
||||||
//var cast = ReflectionHelpers.Il2CppCast(obj, declaringType);
|
|
||||||
var cast = obj.Il2CppCast(declaringType);
|
|
||||||
m_value = this.fieldInfo.GetValue(fieldInfo.IsStatic ? null : cast);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_value = this.fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log($"Error updating FieldInfoHolder | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw(ReflectionWindow window)
|
|
||||||
{
|
|
||||||
UIHelpers.DrawMember(ref m_value, ref this.IsExpanded, ref this.arrayOffset, this.fieldInfo, window.m_rect, window.Target, SetValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (fieldInfo.FieldType.IsEnum)
|
|
||||||
{
|
|
||||||
if (Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null)
|
|
||||||
{
|
|
||||||
m_value = enumValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType.IsPrimitive)
|
|
||||||
{
|
|
||||||
if (fieldInfo.FieldType == typeof(float))
|
|
||||||
{
|
|
||||||
if (float.TryParse(m_value.ToString(), out float f))
|
|
||||||
{
|
|
||||||
m_value = f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType == typeof(double))
|
|
||||||
{
|
|
||||||
if (double.TryParse(m_value.ToString(), out double d))
|
|
||||||
{
|
|
||||||
m_value = d;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (fieldInfo.FieldType != typeof(bool))
|
|
||||||
{
|
|
||||||
if (int.TryParse(m_value.ToString(), out int i))
|
|
||||||
{
|
|
||||||
m_value = i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + fieldInfo.FieldType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Unsupported primitive field type: " + fieldInfo.FieldType.FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var declaringType = this.fieldInfo.DeclaringType;
|
|
||||||
|
|
||||||
//var cast = ReflectionHelpers.Il2CppCast(obj, declaringType);
|
|
||||||
var cast = obj.Il2CppCast(declaringType);
|
|
||||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : cast, m_value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log($"Error setting FieldInfoHolder | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public abstract class MemberInfoHolder
|
|
||||||
{
|
|
||||||
public Type classType;
|
|
||||||
public bool IsExpanded = false;
|
|
||||||
public int arrayOffset = 0;
|
|
||||||
|
|
||||||
public abstract void Draw(ReflectionWindow window);
|
|
||||||
public abstract void UpdateValue(object obj);
|
|
||||||
public abstract void SetValue(object obj);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class PropertyInfoHolder : MemberInfoHolder
|
|
||||||
{
|
|
||||||
public PropertyInfo propInfo;
|
|
||||||
public object m_value;
|
|
||||||
|
|
||||||
public PropertyInfoHolder(Type _type, PropertyInfo _propInfo)
|
|
||||||
{
|
|
||||||
classType = _type;
|
|
||||||
propInfo = _propInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw(ReflectionWindow window)
|
|
||||||
{
|
|
||||||
UIHelpers.DrawMember(ref m_value, ref this.IsExpanded, ref this.arrayOffset, this.propInfo, window.m_rect, window.Target, SetValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (obj is Il2CppSystem.Object ilObject)
|
|
||||||
{
|
|
||||||
var declaringType = this.propInfo.DeclaringType;
|
|
||||||
|
|
||||||
if (declaringType == typeof(Il2CppObjectBase))
|
|
||||||
{
|
|
||||||
m_value = ilObject.Pointer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//var cast = ReflectionHelpers.Il2CppCast(obj, declaringType);
|
|
||||||
var cast = obj.Il2CppCast(declaringType);
|
|
||||||
m_value = this.propInfo.GetValue(this.propInfo.GetAccessors()[0].IsStatic ? null : cast, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_value = this.propInfo.GetValue(obj, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch //(Exception e)
|
|
||||||
{
|
|
||||||
//MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name);
|
|
||||||
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
|
||||||
|
|
||||||
//var inner = e.InnerException;
|
|
||||||
//while (inner != null)
|
|
||||||
//{
|
|
||||||
// MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message);
|
|
||||||
// inner = inner.InnerException;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//m_value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (propInfo.PropertyType.IsEnum)
|
|
||||||
{
|
|
||||||
if (Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null)
|
|
||||||
{
|
|
||||||
m_value = enumValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType.IsPrimitive)
|
|
||||||
{
|
|
||||||
if (propInfo.PropertyType == typeof(float))
|
|
||||||
{
|
|
||||||
if (float.TryParse(m_value.ToString(), out float f))
|
|
||||||
{
|
|
||||||
m_value = f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType == typeof(double))
|
|
||||||
{
|
|
||||||
if (double.TryParse(m_value.ToString(), out double d))
|
|
||||||
{
|
|
||||||
m_value = d;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (propInfo.PropertyType != typeof(bool))
|
|
||||||
{
|
|
||||||
if (int.TryParse(m_value.ToString(), out int i))
|
|
||||||
{
|
|
||||||
m_value = i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + propInfo.PropertyType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cast = obj.Il2CppCast(propInfo.DeclaringType);
|
|
||||||
propInfo.SetValue(propInfo.GetAccessors()[0].IsStatic ? null : cast, m_value, null);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//MelonLogger.Log("Exception trying to set property " + this.propInfo.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,20 +16,21 @@ namespace Explorer
|
|||||||
public override string Name { get => "Object Reflection"; set => Name = value; }
|
public override string Name { get => "Object Reflection"; set => Name = value; }
|
||||||
|
|
||||||
public Type ObjectType;
|
public Type ObjectType;
|
||||||
//public object Target;
|
|
||||||
|
|
||||||
private FieldInfoHolder[] m_FieldInfos;
|
private CacheObject[] m_cachedMembers;
|
||||||
private PropertyInfoHolder[] m_PropertyInfos;
|
private CacheObject[] m_cachedMemberFiltered;
|
||||||
|
private int m_pageOffset;
|
||||||
|
|
||||||
private bool m_autoUpdate = false;
|
private bool m_autoUpdate = false;
|
||||||
private string m_search = "";
|
private string m_search = "";
|
||||||
public MemberFilter m_filter = MemberFilter.Property;
|
public MemberInfoType m_filter = MemberInfoType.Property;
|
||||||
|
|
||||||
public enum MemberFilter
|
public enum MemberInfoType
|
||||||
{
|
{
|
||||||
Both,
|
Field,
|
||||||
Property,
|
Property,
|
||||||
Field
|
Method,
|
||||||
|
All
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Init()
|
public override void Init()
|
||||||
@ -44,119 +45,114 @@ namespace Explorer
|
|||||||
ObjectType = type;
|
ObjectType = type;
|
||||||
|
|
||||||
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
||||||
|
CacheMembers(types);
|
||||||
|
|
||||||
CacheFields(types);
|
m_filter = MemberInfoType.All;
|
||||||
CacheProperties(types);
|
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||||
|
UpdateValues();
|
||||||
UpdateValues(true);
|
m_filter = MemberInfoType.Property;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
|
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||||
|
|
||||||
if (m_autoUpdate)
|
if (m_autoUpdate)
|
||||||
{
|
{
|
||||||
UpdateValues();
|
UpdateValues();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateValues(bool forceAll = false)
|
private void UpdateValues()
|
||||||
{
|
{
|
||||||
UpdateMemberList(forceAll, this.m_FieldInfos, MemberFilter.Field);
|
UpdateMembers();
|
||||||
UpdateMemberList(forceAll, this.m_PropertyInfos, MemberFilter.Property);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMemberList(bool forceAll, MemberInfoHolder[] list, MemberFilter filter)
|
private void UpdateMembers()
|
||||||
{
|
{
|
||||||
if (forceAll || m_filter == MemberFilter.Both || m_filter == filter)
|
foreach (var member in m_cachedMemberFiltered)
|
||||||
{
|
{
|
||||||
foreach (var holder in list)
|
member.UpdateValue(Target);
|
||||||
{
|
|
||||||
if (forceAll || ShouldUpdateMemberInfo(holder))
|
|
||||||
{
|
|
||||||
holder.UpdateValue(Target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldUpdateMemberInfo(MemberInfoHolder holder)
|
private bool ShouldProcessMember(CacheObject holder)
|
||||||
{
|
{
|
||||||
var memberName = holder is FieldInfoHolder ?
|
if (m_filter != MemberInfoType.All && m_filter != holder.MemberInfoType) return false;
|
||||||
(holder as FieldInfoHolder).fieldInfo.Name :
|
|
||||||
(holder as PropertyInfoHolder).propInfo.Name;
|
|
||||||
|
|
||||||
return m_search == "" || memberName.ToLower().Contains(m_search.ToLower());
|
if (m_search == "" || holder.MemberInfo == null) return true;
|
||||||
|
|
||||||
|
return holder.MemberInfo.Name
|
||||||
|
.ToLower()
|
||||||
|
.Contains(m_search.ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CacheProperties(Type[] types, List<string> names = null)
|
private void CacheMembers(Type[] types, List<string> names = null)
|
||||||
{
|
{
|
||||||
if (names == null)
|
if (names == null)
|
||||||
{
|
{
|
||||||
names = new List<string>();
|
names = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<PropertyInfoHolder>();
|
var list = new List<CacheObject>();
|
||||||
|
|
||||||
foreach (var type in types)
|
foreach (var type in types)
|
||||||
{
|
{
|
||||||
PropertyInfo[] propInfos = new PropertyInfo[0];
|
MemberInfo[] infos;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
propInfos = type.GetProperties(ReflectionHelpers.CommonFlags);
|
infos = type.GetMembers(ReflectionHelpers.CommonFlags);
|
||||||
}
|
}
|
||||||
catch (TypeLoadException)
|
catch
|
||||||
{
|
|
||||||
MelonLogger.Log($"Couldn't get Properties for Type '{type.Name}', it may not support Il2Cpp Reflection at the moment.");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pi in propInfos)
|
|
||||||
{
|
|
||||||
// this member causes a crash when inspected, so just skipping it for now.
|
|
||||||
if (pi.Name == "Il2CppType")
|
|
||||||
{
|
{
|
||||||
|
MelonLogger.Log("Exception getting members for type: " + type.Name);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (names.Contains(pi.Name))
|
foreach (var member in infos)
|
||||||
{
|
{
|
||||||
continue;
|
try
|
||||||
}
|
|
||||||
names.Add(pi.Name);
|
|
||||||
|
|
||||||
var piHolder = new PropertyInfoHolder(type, pi);
|
|
||||||
list.Add(piHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_PropertyInfos = list.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CacheFields(Type[] types, List<string> names = null)
|
|
||||||
{
|
{
|
||||||
if (names == null)
|
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
|
||||||
{
|
{
|
||||||
names = new List<string>();
|
if (member.Name == "Il2CppType") continue;
|
||||||
|
|
||||||
|
if (names.Contains(member.Name)) continue;
|
||||||
|
names.Add(member.Name);
|
||||||
|
|
||||||
|
object value = null;
|
||||||
|
object target = Target;
|
||||||
|
|
||||||
|
if (target is Il2CppSystem.Object ilObject)
|
||||||
|
{
|
||||||
|
if (member.DeclaringType == typeof(Il2CppObjectBase)) continue;
|
||||||
|
|
||||||
|
target = ilObject.Il2CppCast(member.DeclaringType);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<FieldInfoHolder>();
|
if (member is FieldInfo)
|
||||||
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
{
|
||||||
foreach (var fi in type.GetFields(ReflectionHelpers.CommonFlags))
|
value = (member as FieldInfo).GetValue(target);
|
||||||
{
|
}
|
||||||
if (names.Contains(fi.Name))
|
else if (member is PropertyInfo)
|
||||||
{
|
{
|
||||||
continue;
|
value = (member as PropertyInfo).GetValue(target);
|
||||||
}
|
}
|
||||||
names.Add(fi.Name);
|
|
||||||
|
|
||||||
var fiHolder = new FieldInfoHolder(type, fi);
|
list.Add(CacheObject.GetCacheObject(value, member, Target));
|
||||||
list.Add(fiHolder);
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Exception caching member " + member.Name + "!");
|
||||||
|
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||||
|
MelonLogger.Log(e.StackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_FieldInfos = list.ToArray();
|
m_cachedMembers = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========== GUI DRAW =========== //
|
// =========== GUI DRAW =========== //
|
||||||
@ -211,9 +207,9 @@ 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(MemberFilter.Both, "Both");
|
FilterToggle(MemberInfoType.All, "All");
|
||||||
FilterToggle(MemberFilter.Property, "Properties");
|
FilterToggle(MemberInfoType.Property, "Properties");
|
||||||
FilterToggle(MemberFilter.Field, "Fields");
|
FilterToggle(MemberInfoType.Field, "Fields");
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
@ -229,19 +225,32 @@ namespace Explorer
|
|||||||
|
|
||||||
GUILayout.Space(10);
|
GUILayout.Space(10);
|
||||||
|
|
||||||
|
int count = m_cachedMemberFiltered.Length;
|
||||||
|
|
||||||
|
if (count > 20)
|
||||||
|
{
|
||||||
|
// prev/next page buttons
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
int maxOffset = (int)Mathf.Ceil(count / 20);
|
||||||
|
if (GUILayout.Button("< Prev", null))
|
||||||
|
{
|
||||||
|
if (m_pageOffset > 0) m_pageOffset--;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||||
|
|
||||||
|
if (GUILayout.Button("Next >", null))
|
||||||
|
{
|
||||||
|
if (m_pageOffset < maxOffset) m_pageOffset++;
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
||||||
|
|
||||||
GUILayout.Space(10);
|
GUILayout.Space(10);
|
||||||
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
DrawMembers(this.m_cachedMemberFiltered);
|
||||||
{
|
|
||||||
DrawMembers(this.m_FieldInfos, "Fields");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
|
||||||
{
|
|
||||||
DrawMembers(this.m_PropertyInfos, "Properties");
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
GUILayout.EndScrollView();
|
||||||
|
|
||||||
@ -257,28 +266,59 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMembers(MemberInfoHolder[] members, string title)
|
private void DrawMembers(CacheObject[] 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMembersInternal(string title, MemberInfoType filter, CacheObject[] members, ref int index)
|
||||||
|
{
|
||||||
|
if (m_filter != filter && m_filter != MemberInfoType.All)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
UIStyles.HorizontalLine(Color.grey);
|
UIStyles.HorizontalLine(Color.grey);
|
||||||
|
|
||||||
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
|
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
|
||||||
|
|
||||||
foreach (var holder in members)
|
int offset = (m_pageOffset * 20) + index;
|
||||||
{
|
|
||||||
var memberName = (holder as FieldInfoHolder)?.fieldInfo.Name ?? (holder as PropertyInfoHolder)?.propInfo.Name;
|
|
||||||
|
|
||||||
if (m_search != "" && !memberName.ToLower().Contains(m_search.ToLower()))
|
if (offset >= m_cachedMemberFiltered.Length)
|
||||||
{
|
{
|
||||||
continue;
|
m_pageOffset = 0;
|
||||||
|
offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int j = offset; j < offset + 20 && j < members.Length; j++)
|
||||||
|
{
|
||||||
|
var holder = members[j];
|
||||||
|
|
||||||
|
if (holder.MemberInfoType != filter || !ShouldProcessMember(holder)) continue;
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||||
holder.Draw(this);
|
try
|
||||||
|
{
|
||||||
|
holder.Draw(this.m_rect, 180f);
|
||||||
|
}
|
||||||
|
catch // (Exception e)
|
||||||
|
{
|
||||||
|
//MelonLogger.Log("Exception drawing member " + holder.MemberInfo.Name);
|
||||||
|
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||||
|
//MelonLogger.Log(e.StackTrace);
|
||||||
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (index >= 20) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FilterToggle(MemberFilter mode, string label)
|
private void FilterToggle(MemberInfoType mode, string label)
|
||||||
{
|
{
|
||||||
if (m_filter == mode)
|
if (m_filter == mode)
|
||||||
{
|
{
|
||||||
|
@ -23,9 +23,12 @@ namespace Explorer
|
|||||||
|
|
||||||
public Vector2 scroll = Vector2.zero;
|
public Vector2 scroll = Vector2.zero;
|
||||||
|
|
||||||
|
public abstract void Init();
|
||||||
|
public abstract void WindowFunction(int windowID);
|
||||||
|
public abstract void Update();
|
||||||
|
|
||||||
public static UIWindow CreateWindow<T>(object target) where T : UIWindow
|
public static UIWindow CreateWindow<T>(object target) where T : UIWindow
|
||||||
{
|
{
|
||||||
//var component = (UIWindow)AddToGameObject<T>(Instance.gameObject);
|
|
||||||
var window = Activator.CreateInstance<T>();
|
var window = Activator.CreateInstance<T>();
|
||||||
|
|
||||||
window.Target = target;
|
window.Target = target;
|
||||||
@ -50,13 +53,8 @@ namespace Explorer
|
|||||||
MelonLogger.Log("Exception removing Window from WindowManager.Windows list!");
|
MelonLogger.Log("Exception removing Window from WindowManager.Windows list!");
|
||||||
MelonLogger.Log($"{e.GetType()} : {e.Message}\r\n{e.StackTrace}");
|
MelonLogger.Log($"{e.GetType()} : {e.Message}\r\n{e.StackTrace}");
|
||||||
}
|
}
|
||||||
//Destroy(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void Init();
|
|
||||||
public abstract void WindowFunction(int windowID);
|
|
||||||
public abstract void Update();
|
|
||||||
|
|
||||||
public void OnGUI()
|
public void OnGUI()
|
||||||
{
|
{
|
||||||
if (CppExplorer.ShowMenu)
|
if (CppExplorer.ShowMenu)
|
||||||
|
Reference in New Issue
Block a user