2020-08-07 22:19:03 +10:00
|
|
|
|
using System;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
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;
|
2020-08-08 22:45:06 +10:00
|
|
|
|
using Mono.CSharp;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
using UnhollowerBaseLib;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Explorer
|
|
|
|
|
{
|
2020-08-13 00:06:39 +10:00
|
|
|
|
public class ReflectionWindow : UIWindow
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-13 00:06:39 +10:00
|
|
|
|
public override string Name { get => "Object Reflection"; set => Name = value; }
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
public Type ObjectType;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private CacheObject[] m_cachedMembers;
|
|
|
|
|
private CacheObject[] m_cachedMemberFiltered;
|
|
|
|
|
private int m_pageOffset;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
|
|
|
|
private bool m_autoUpdate = false;
|
|
|
|
|
private string m_search = "";
|
2020-08-22 00:16:05 +10:00
|
|
|
|
public MemberInfoType m_filter = MemberInfoType.Property;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
public enum MemberInfoType
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
Field,
|
2020-08-07 22:19:03 +10:00
|
|
|
|
Property,
|
2020-08-22 00:16:05 +10:00
|
|
|
|
Method,
|
|
|
|
|
All
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Init()
|
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
var type = ReflectionHelpers.GetActualType(Target);
|
2020-08-07 22:19:03 +10:00
|
|
|
|
if (type == null)
|
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
MelonLogger.Log("Could not get underlying type for object. ToString(): " + Target.ToString());
|
2020-08-07 22:19:03 +10:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
ObjectType = type;
|
|
|
|
|
|
|
|
|
|
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
2020-08-22 00:16:05 +10:00
|
|
|
|
CacheMembers(types);
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
m_filter = MemberInfoType.All;
|
|
|
|
|
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
|
|
|
|
UpdateValues();
|
|
|
|
|
m_filter = MemberInfoType.Property;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Update()
|
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
|
|
|
|
|
2020-08-07 22:19:03 +10:00
|
|
|
|
if (m_autoUpdate)
|
|
|
|
|
{
|
|
|
|
|
UpdateValues();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private void UpdateValues()
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
UpdateMembers();
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private void UpdateMembers()
|
2020-08-18 17:11:58 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
foreach (var member in m_cachedMemberFiltered)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
member.UpdateValue(Target);
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private bool ShouldProcessMember(CacheObject holder)
|
2020-08-18 17:11:58 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (m_filter != MemberInfoType.All && m_filter != holder.MemberInfoType) return false;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (m_search == "" || holder.MemberInfo == null) return true;
|
|
|
|
|
|
|
|
|
|
return holder.MemberInfo.Name
|
|
|
|
|
.ToLower()
|
|
|
|
|
.Contains(m_search.ToLower());
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private void CacheMembers(Type[] types, List<string> names = null)
|
2020-08-18 17:11:58 +10:00
|
|
|
|
{
|
|
|
|
|
if (names == null)
|
|
|
|
|
{
|
|
|
|
|
names = new List<string>();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
var list = new List<CacheObject>();
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
foreach (var type in types)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
MemberInfo[] infos;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
infos = type.GetMembers(ReflectionHelpers.CommonFlags);
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
2020-08-22 00:16:05 +10:00
|
|
|
|
catch
|
2020-08-18 17:11:58 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
MelonLogger.Log("Exception getting members for type: " + type.Name);
|
|
|
|
|
continue;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
foreach (var member in infos)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
try
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
|
|
|
|
|
{
|
|
|
|
|
if (member.Name == "Il2CppType") continue;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (names.Contains(member.Name)) continue;
|
|
|
|
|
names.Add(member.Name);
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
object value = null;
|
|
|
|
|
object target = Target;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (target is Il2CppSystem.Object ilObject)
|
|
|
|
|
{
|
|
|
|
|
if (member.DeclaringType == typeof(Il2CppObjectBase)) continue;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
target = ilObject.Il2CppCast(member.DeclaringType);
|
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (member is FieldInfo)
|
|
|
|
|
{
|
|
|
|
|
value = (member as FieldInfo).GetValue(target);
|
|
|
|
|
}
|
|
|
|
|
else if (member is PropertyInfo)
|
|
|
|
|
{
|
|
|
|
|
value = (member as PropertyInfo).GetValue(target);
|
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
list.Add(CacheObject.GetCacheObject(value, member, Target));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
2020-08-18 17:11:58 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
MelonLogger.Log("Exception caching member " + member.Name + "!");
|
|
|
|
|
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
|
|
|
|
MelonLogger.Log(e.StackTrace);
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
m_cachedMembers = list.ToArray();
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
// =========== GUI DRAW =========== //
|
|
|
|
|
|
2020-08-07 22:19:03 +10:00
|
|
|
|
public override void WindowFunction(int windowID)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Header();
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginHorizontal(null);
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.Name + "</color>", null);
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
bool unityObj = Target is UnityEngine.Object;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
|
|
|
|
if (unityObj)
|
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUILayout.Label("Name: " + (Target as UnityEngine.Object).name, null);
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
if (unityObj)
|
|
|
|
|
{
|
|
|
|
|
GUILayout.BeginHorizontal(null);
|
|
|
|
|
|
|
|
|
|
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
UIHelpers.InstantiateButton((UnityEngine.Object)Target);
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
if (Target is Component comp && comp.gameObject is GameObject obj)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
|
|
|
|
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
|
|
|
|
GUILayout.Label("GameObject:", null);
|
|
|
|
|
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) }))
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
GUILayout.BeginHorizontal(null);
|
|
|
|
|
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
2020-08-22 00:16:05 +10:00
|
|
|
|
FilterToggle(MemberInfoType.All, "All");
|
|
|
|
|
FilterToggle(MemberInfoType.Property, "Properties");
|
|
|
|
|
FilterToggle(MemberInfoType.Field, "Fields");
|
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 = Color.white;
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(10);
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
int count = m_cachedMemberFiltered.Length;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (count > 20)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
// 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--;
|
|
|
|
|
}
|
2020-08-07 22:19:03 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
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();
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(10);
|
|
|
|
|
|
|
|
|
|
DrawMembers(this.m_cachedMemberFiltered);
|
|
|
|
|
|
2020-08-07 22:19:03 +10:00
|
|
|
|
GUILayout.EndScrollView();
|
|
|
|
|
|
|
|
|
|
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
|
|
|
|
|
|
|
|
|
GUILayout.EndArea();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message);
|
|
|
|
|
DestroyWindow();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
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)
|
2020-08-13 18:43:34 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (m_filter != filter && m_filter != MemberInfoType.All)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
UIStyles.HorizontalLine(Color.grey);
|
2020-08-13 18:43:34 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
|
2020-08-13 18:43:34 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
int offset = (m_pageOffset * 20) + index;
|
|
|
|
|
|
|
|
|
|
if (offset >= m_cachedMemberFiltered.Length)
|
2020-08-13 18:43:34 +10:00
|
|
|
|
{
|
2020-08-22 00:16:05 +10:00
|
|
|
|
m_pageOffset = 0;
|
|
|
|
|
offset = 0;
|
|
|
|
|
}
|
2020-08-13 18:43:34 +10:00
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
for (int j = offset; j < offset + 20 && j < members.Length; j++)
|
|
|
|
|
{
|
|
|
|
|
var holder = members[j];
|
|
|
|
|
|
|
|
|
|
if (holder.MemberInfoType != filter || !ShouldProcessMember(holder)) continue;
|
2020-08-13 18:43:34 +10:00
|
|
|
|
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
2020-08-22 00:16:05 +10:00
|
|
|
|
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);
|
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUILayout.EndHorizontal();
|
2020-08-22 00:16:05 +10:00
|
|
|
|
|
|
|
|
|
index++;
|
|
|
|
|
if (index >= 20) break;
|
2020-08-18 17:11:58 +10:00
|
|
|
|
}
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
private void FilterToggle(MemberInfoType mode, string label)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
if (m_filter == mode)
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUI.color = Color.green;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
else
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUI.color = Color.white;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
2020-08-07 22:19:03 +10:00
|
|
|
|
{
|
2020-08-18 17:11:58 +10:00
|
|
|
|
m_filter = mode;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
2020-08-18 17:11:58 +10:00
|
|
|
|
GUI.color = Color.white;
|
2020-08-07 22:19:03 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|