UnityExplorer/src/Windows/ReflectionWindow.cs

363 lines
13 KiB
C#
Raw Normal View History

2020-08-07 22:19:03 +10:00
using System;
using System.CodeDom;
2020-08-07 22:19:03 +10:00
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
{
public class ReflectionWindow : UIWindow
2020-08-07 22:19:03 +10:00
{
public override string Title => WindowManager.TabView
? $"<color=cyan>[R]</color> {TargetType.Name}"
: $"Reflection Inspector ({TargetType.Name})";
2020-08-07 22:19:03 +10:00
public Type TargetType;
2020-08-07 22:19:03 +10:00
private CacheObjectBase[] m_allCachedMembers;
private CacheObjectBase[] m_cachedMembersFiltered;
public PageHelper Pages = new PageHelper();
//private int m_pageOffset;
//private int m_limitPerPage = 20;
2020-08-07 22:19:03 +10:00
private bool m_autoUpdate = false;
private string m_search = "";
public MemberTypes m_filter = MemberTypes.Property;
private bool m_hideFailedReflection = false;
2020-08-07 22:19:03 +10:00
// some extra caching
private UnityEngine.Object m_uObj;
private Component m_component;
2020-08-07 22:19:03 +10:00
public override void Init()
{
var type = ReflectionHelpers.GetActualType(Target);
2020-08-07 22:19:03 +10:00
TargetType = type;
var types = ReflectionHelpers.GetAllBaseTypes(Target);
CacheMembers(types);
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;
2020-08-07 22:19:03 +10:00
}
public override void Update()
{
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
2020-08-07 22:19:03 +10:00
if (m_autoUpdate)
{
UpdateValues();
}
}
private void UpdateValues()
{
foreach (var member in m_cachedMembersFiltered)
2020-08-07 22:19:03 +10:00
{
member.UpdateValue();
2020-08-07 22:19:03 +10:00
}
}
private bool ShouldProcessMember(CacheObjectBase holder)
{
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
if (m_search == "" || holder.MemInfo == null) return true;
var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
2020-08-31 23:28:44 +10:00
return name.ToLower().Contains(m_search.ToLower());
}
private void CacheMembers(Type[] types)
{
var list = new List<CacheObjectBase>();
2020-08-07 22:19:03 +10:00
var names = new List<string>();
foreach (var declaringType in types)
2020-08-07 22:19:03 +10:00
{
MemberInfo[] infos;
string exception = null;
try
{
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
}
catch
{
MelonLogger.Log($"Exception getting members for type: {declaringType.FullName}");
continue;
}
object target = Target;
if (target is Il2CppSystem.Object ilObject)
2020-08-07 22:19:03 +10:00
{
try
2020-08-07 22:19:03 +10:00
{
target = ilObject.Il2CppCast(declaringType);
}
catch (Exception e)
{
exception = ReflectionHelpers.ExceptionToString(e);
}
}
foreach (var member in infos)
{
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
{
var name = $"{member.DeclaringType.Name}.{member.Name}";
// blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
{
continue;
}
2020-08-31 23:28:44 +10:00
if (member is MethodInfo mi)
{
name += " (";
foreach (var param in mi.GetParameters())
{
name += param.ParameterType.Name + ", ";
}
name += ")";
}
if (names.Contains(name))
{
2020-08-31 23:28:44 +10:00
continue;
}
2020-08-31 23:28:44 +10:00
try
{
var cached = CacheObjectBase.GetCacheObject(member, target);
if (cached != null)
{
2020-08-31 23:28:44 +10:00
names.Add(name);
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
2020-08-31 23:28:44 +10:00
MelonLogger.LogWarning($"Exception caching member {name}!");
MelonLogger.Log(e.ToString());
}
}
}
}
m_allCachedMembers = list.ToArray();
2020-08-07 22:19:03 +10:00
}
// =========== GUI DRAW =========== //
2020-08-07 22:19:03 +10:00
public override void WindowFunction(int windowID)
{
try
{
// ====== HEADER ======
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
2020-08-07 22:19:03 +10:00
if (!WindowManager.TabView)
{
Header();
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
}
2020-08-07 22:19:03 +10:00
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) });
if (m_uObj)
2020-08-07 22:19:03 +10:00
{
GUILayout.Label("Name: " + m_uObj.name, null);
2020-08-07 22:19:03 +10:00
}
GUILayout.EndHorizontal();
if (m_uObj)
2020-08-07 22:19:03 +10:00
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
UIHelpers.InstantiateButton(m_uObj);
if (m_component && m_component.gameObject is GameObject obj)
2020-08-07 22:19:03 +10:00
{
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) });
var charWidth = obj.name.Length * 15;
var maxWidth = rect.width - 350;
var labelWidth = charWidth < maxWidth ? charWidth : maxWidth;
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) }))
2020-08-07 22:19:03 +10:00
{
WindowManager.InspectObject(obj, out bool _);
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
GUILayout.EndHorizontal();
}
UIStyles.HorizontalLine(Color.grey);
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
m_search = GUILayout.TextField(m_search, null);
2020-08-07 22:19:03 +10:00
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
FilterToggle(MemberTypes.All, "All");
FilterToggle(MemberTypes.Property, "Properties");
FilterToggle(MemberTypes.Field, "Fields");
FilterToggle(MemberTypes.Method, "Methods");
2020-08-07 22:19:03 +10:00
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
{
UpdateValues();
}
GUI.color = m_autoUpdate ? Color.green : Color.red;
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
GUI.color = m_hideFailedReflection ? Color.green : Color.red;
m_hideFailedReflection = GUILayout.Toggle(m_hideFailedReflection, "Hide failed Reflection?", new GUILayoutOption[] { GUILayout.Width(150) });
2020-08-07 22:19:03 +10:00
GUI.color = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(10);
Pages.Count = m_cachedMembersFiltered.Length;
// prev/next page buttons
GUILayout.BeginHorizontal(null);
Pages.DrawLimitInputArea();
if (Pages.Count > Pages.PageLimit)
2020-08-07 22:19:03 +10:00
{
Pages.CalculateMaxOffset();
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.scroll);
}
2020-08-07 22:19:03 +10:00
Pages.CurrentPageLabel();
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Right, ref this.scroll);
}
2020-08-07 22:19:03 +10:00
}
GUILayout.EndHorizontal();
2020-08-07 22:19:03 +10:00
// ====== BODY ======
scroll = UIHelpers.BeginScrollView(scroll);
GUILayout.Space(10);
UIStyles.HorizontalLine(Color.grey);
GUILayout.BeginVertical(GUI.skin.box, null);
2020-08-07 22:19:03 +10:00
var members = this.m_cachedMembersFiltered;
int start = Pages.CalculateOffsetIndex();
2020-08-07 22:19:03 +10:00
for (int j = start; (j < start + Pages.PageLimit && j < members.Length); j++)
{
var holder = members[j];
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
try
{
holder.Draw(rect, 180f);
}
catch
{
GUILayout.EndHorizontal();
continue;
}
GUILayout.EndHorizontal();
// if not last element
if (!(j == (start + Pages.PageLimit - 1) || j == (members.Length - 1)))
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
}
GUILayout.EndVertical();
UIHelpers.EndScrollView();
if (!WindowManager.TabView)
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
}
}
catch (Il2CppException e)
{
if (!e.Message.Contains("in a group with only"))
{
throw;
}
}
catch (Exception e)
{
MelonLogger.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
DestroyWindow();
return;
}
2020-08-07 22:19:03 +10:00
}
private void FilterToggle(MemberTypes mode, string label)
2020-08-07 22:19:03 +10:00
{
if (m_filter == mode)
2020-08-07 22:19:03 +10:00
{
GUI.color = Color.green;
2020-08-07 22:19:03 +10:00
}
else
2020-08-07 22:19:03 +10:00
{
GUI.color = Color.white;
2020-08-07 22:19:03 +10:00
}
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
2020-08-07 22:19:03 +10:00
{
m_filter = mode;
Pages.PageOffset = 0;
scroll = Vector2.zero;
2020-08-07 22:19:03 +10:00
}
GUI.color = Color.white;
2020-08-07 22:19:03 +10:00
}
}
}