2020-08-22 00:16:05 +10:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
2020-08-31 18:23:19 +10:00
|
|
|
|
using MelonLoader;
|
2020-08-22 00:16:05 +10:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Explorer
|
|
|
|
|
{
|
2020-08-29 21:15:54 +10:00
|
|
|
|
public partial class CacheList : CacheObjectBase
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
public bool IsExpanded { get; set; }
|
|
|
|
|
public int ArrayOffset { get; set; }
|
2020-08-24 01:42:19 +10:00
|
|
|
|
public int ArrayLimit { get; set; } = 20;
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
|
|
|
|
public float WhiteSpace = 215f;
|
|
|
|
|
public float ButtonWidthOffset = 290f;
|
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
private CacheObjectBase[] m_cachedEntries;
|
|
|
|
|
|
|
|
|
|
// Type of Entries in the Array
|
2020-08-22 17:17:11 +10:00
|
|
|
|
public Type EntryType
|
2020-08-31 18:23:19 +10:00
|
|
|
|
{
|
|
|
|
|
get => GetEntryType();
|
|
|
|
|
set => m_entryType = value;
|
|
|
|
|
}
|
|
|
|
|
private Type m_entryType;
|
|
|
|
|
|
|
|
|
|
// Cached IEnumerable object
|
|
|
|
|
public IEnumerable Enumerable
|
|
|
|
|
{
|
|
|
|
|
get => GetEnumerable();
|
|
|
|
|
}
|
|
|
|
|
private IEnumerable m_enumerable;
|
|
|
|
|
|
|
|
|
|
// Generic Type Definition for Lists
|
|
|
|
|
public Type GenericTypeDef
|
|
|
|
|
{
|
|
|
|
|
get => GetGenericTypeDef();
|
|
|
|
|
}
|
|
|
|
|
private Type m_genericTypeDef;
|
|
|
|
|
|
|
|
|
|
// Cached ToArray method for Lists
|
|
|
|
|
public MethodInfo GenericToArrayMethod
|
|
|
|
|
{
|
|
|
|
|
get => GetGenericToArrayMethod();
|
|
|
|
|
}
|
|
|
|
|
private MethodInfo m_genericToArray;
|
|
|
|
|
|
|
|
|
|
// Cached Item Property for ILists
|
|
|
|
|
public PropertyInfo ItemProperty
|
|
|
|
|
{
|
|
|
|
|
get => GetItemProperty();
|
|
|
|
|
}
|
|
|
|
|
private PropertyInfo m_itemProperty;
|
|
|
|
|
|
|
|
|
|
// ========== Methods ==========
|
|
|
|
|
|
|
|
|
|
private IEnumerable GetEnumerable()
|
|
|
|
|
{
|
|
|
|
|
if (m_enumerable == null && Value != null)
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
m_enumerable = Value as IEnumerable ?? CastValueFromList();
|
|
|
|
|
}
|
|
|
|
|
return m_enumerable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Type GetGenericTypeDef()
|
|
|
|
|
{
|
|
|
|
|
if (m_genericTypeDef == null && Value != null)
|
|
|
|
|
{
|
|
|
|
|
var type = Value.GetType();
|
|
|
|
|
if (type.IsGenericType)
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
m_genericTypeDef = type.GetGenericTypeDefinition();
|
2020-08-22 17:17:11 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-31 18:23:19 +10:00
|
|
|
|
return m_genericTypeDef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private MethodInfo GetGenericToArrayMethod()
|
|
|
|
|
{
|
|
|
|
|
if (GenericTypeDef == null) return null;
|
|
|
|
|
|
|
|
|
|
if (m_genericToArray == null)
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
m_genericToArray = GenericTypeDef
|
|
|
|
|
.MakeGenericType(new Type[] { this.EntryType })
|
|
|
|
|
.GetMethod("ToArray");
|
|
|
|
|
}
|
|
|
|
|
return m_genericToArray;
|
2020-08-22 17:17:11 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
private PropertyInfo GetItemProperty()
|
|
|
|
|
{
|
|
|
|
|
if (m_itemProperty == null)
|
|
|
|
|
{
|
|
|
|
|
m_itemProperty = Value?.GetType().GetProperty("Item");
|
|
|
|
|
}
|
|
|
|
|
return m_itemProperty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable CastValueFromList()
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
if (Value == null) return null;
|
|
|
|
|
|
|
|
|
|
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
|
|
|
|
{
|
|
|
|
|
return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
|
|
|
|
|
}
|
|
|
|
|
else
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
return CastFromIList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IList CastFromIList()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.EntryType });
|
|
|
|
|
var list = (IList)Activator.CreateInstance(genericType);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; ; i++)
|
2020-08-22 17:17:11 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var itm = ItemProperty.GetValue(Value, new object[] { i });
|
|
|
|
|
list.Add(itm);
|
|
|
|
|
}
|
|
|
|
|
catch { break; }
|
2020-08-22 17:17:11 +10:00
|
|
|
|
}
|
2020-08-31 18:23:19 +10:00
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
MelonLogger.Log("Exception casting IList to Array: " + e.GetType() + ", " + e.Message);
|
|
|
|
|
return null;
|
2020-08-22 17:17:11 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-22 00:16:05 +10:00
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
private Type GetEntryType()
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
if (m_entryType == null)
|
2020-08-27 18:05:55 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
if (this.MemberInfo != null)
|
|
|
|
|
{
|
|
|
|
|
Type memberType = null;
|
|
|
|
|
switch (this.MemberInfo.MemberType)
|
|
|
|
|
{
|
|
|
|
|
case MemberTypes.Field:
|
|
|
|
|
memberType = (MemberInfo as FieldInfo).FieldType;
|
|
|
|
|
break;
|
|
|
|
|
case MemberTypes.Property:
|
|
|
|
|
memberType = (MemberInfo as PropertyInfo).PropertyType;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-08-27 18:05:55 +10:00
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
if (memberType != null && memberType.IsGenericType)
|
|
|
|
|
{
|
|
|
|
|
m_entryType = memberType.GetGenericArguments()[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (Value != null)
|
|
|
|
|
{
|
|
|
|
|
var type = Value.GetType();
|
|
|
|
|
if (type.IsGenericType)
|
|
|
|
|
{
|
|
|
|
|
m_entryType = type.GetGenericArguments()[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-27 18:05:55 +10:00
|
|
|
|
}
|
2020-08-31 18:23:19 +10:00
|
|
|
|
|
|
|
|
|
// IList probably won't be able to get any EntryType.
|
|
|
|
|
if (m_entryType == null)
|
|
|
|
|
{
|
|
|
|
|
m_entryType = typeof(object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return m_entryType;
|
2020-08-27 18:05:55 +10:00
|
|
|
|
}
|
2020-08-22 17:17:11 +10:00
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
public override void UpdateValue()
|
2020-08-27 18:05:55 +10:00
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
base.UpdateValue();
|
|
|
|
|
|
|
|
|
|
if (Value == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var enumerator = Enumerable?.GetEnumerator();
|
|
|
|
|
|
|
|
|
|
if (enumerator == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var list = new List<CacheObjectBase>();
|
|
|
|
|
while (enumerator.MoveNext())
|
|
|
|
|
{
|
|
|
|
|
var obj = enumerator.Current;
|
|
|
|
|
var type = ReflectionHelpers.GetActualType(obj);
|
|
|
|
|
|
|
|
|
|
if (obj is Il2CppSystem.Object iObj)
|
|
|
|
|
{
|
|
|
|
|
obj = iObj.Il2CppCast(type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cached = GetCacheObject(obj, null, null, type);
|
|
|
|
|
cached.UpdateValue();
|
|
|
|
|
|
|
|
|
|
list.Add(cached);
|
|
|
|
|
}
|
|
|
|
|
m_cachedEntries = list.ToArray();
|
2020-08-22 00:16:05 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-31 18:23:19 +10:00
|
|
|
|
// ============= GUI Draw =============
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
public override void DrawValue(Rect window, float width)
|
|
|
|
|
{
|
2020-08-31 18:23:19 +10:00
|
|
|
|
if (m_cachedEntries == null)
|
|
|
|
|
{
|
|
|
|
|
GUILayout.Label("m_cachedEntries is null!", null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
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>";
|
2020-08-29 21:15:54 +10:00
|
|
|
|
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
WindowManager.InspectObject(Value, out bool _);
|
|
|
|
|
}
|
|
|
|
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
|
|
|
|
2020-08-28 00:45:34 +10:00
|
|
|
|
GUILayout.Space(5);
|
|
|
|
|
|
2020-08-22 00:16:05 +10:00
|
|
|
|
if (IsExpanded)
|
|
|
|
|
{
|
2020-08-29 21:15:54 +10:00
|
|
|
|
float whitespace = WhiteSpace;
|
|
|
|
|
|
|
|
|
|
if (whitespace > 0)
|
|
|
|
|
{
|
|
|
|
|
ClampLabelWidth(window, ref whitespace);
|
|
|
|
|
}
|
2020-08-28 00:45:34 +10:00
|
|
|
|
|
2020-08-24 01:42:19 +10:00
|
|
|
|
if (count > ArrayLimit)
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
GUILayout.BeginHorizontal(null);
|
2020-08-28 00:45:34 +10:00
|
|
|
|
|
|
|
|
|
GUILayout.Space(whitespace);
|
|
|
|
|
|
2020-08-24 01:42:19 +10:00
|
|
|
|
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
|
2020-08-22 00:16:05 +10:00
|
|
|
|
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
|
|
|
|
// prev/next page buttons
|
2020-08-29 21:15:54 +10:00
|
|
|
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
if (ArrayOffset > 0) ArrayOffset--;
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
if (ArrayOffset < maxOffset) ArrayOffset++;
|
|
|
|
|
}
|
2020-08-24 01:42:19 +10:00
|
|
|
|
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
|
|
|
|
|
var limit = this.ArrayLimit.ToString();
|
|
|
|
|
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
|
|
|
|
|
if (limit != ArrayLimit.ToString() && int.TryParse(limit, out int i))
|
|
|
|
|
{
|
|
|
|
|
ArrayLimit = i;
|
|
|
|
|
}
|
2020-08-28 00:45:34 +10:00
|
|
|
|
|
|
|
|
|
GUILayout.Space(5);
|
2020-08-22 00:16:05 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-24 01:42:19 +10:00
|
|
|
|
int offset = ArrayOffset * ArrayLimit;
|
2020-08-22 00:16:05 +10:00
|
|
|
|
|
2020-08-28 00:45:34 +10:00
|
|
|
|
if (offset >= count)
|
|
|
|
|
{
|
|
|
|
|
offset = 0;
|
|
|
|
|
ArrayOffset = 0;
|
|
|
|
|
}
|
2020-08-22 00:16:05 +10:00
|
|
|
|
|
2020-08-24 01:42:19 +10:00
|
|
|
|
for (int i = offset; i < offset + ArrayLimit && i < count; i++)
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
|
|
|
|
var entry = m_cachedEntries[i];
|
|
|
|
|
|
|
|
|
|
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
GUILayout.BeginHorizontal(null);
|
2020-08-28 00:45:34 +10:00
|
|
|
|
|
|
|
|
|
GUILayout.Space(whitespace);
|
2020-08-22 00:16:05 +10:00
|
|
|
|
|
2020-08-22 17:17:11 +10:00
|
|
|
|
if (entry.Value == null)
|
2020-08-22 00:16:05 +10:00
|
|
|
|
{
|
2020-08-28 00:45:34 +10:00
|
|
|
|
GUILayout.Label(i + "<i><color=grey> (null)</color></i>", null);
|
2020-08-22 00:16:05 +10:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-08-28 00:45:34 +10:00
|
|
|
|
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
|
|
|
|
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
|
|
|
|
|
entry.DrawValue(window, window.width - (whitespace + 85));
|
2020-08-22 00:16:05 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-28 00:45:34 +10:00
|
|
|
|
|
|
|
|
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
2020-08-22 00:16:05 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|