Files
UnityExplorer_Fix/src/CachedObjects/Object/CacheDictionary.cs
sinaioutlander a927b5ed21 1.6.7
* Parameters (in Methods or Properties) with default values will now show these default values in the Inspector, and if you don't provide any input then this default value will be used as the argument.
* Removed an unnecessary update of cached members when you open a Reflection Inspector, should be a bit faster now.
* When entering arguments, the name of the argument is now white instead of cyan to avoid confusion with the Type name.
* A few clean ups
2020-09-10 18:02:41 +10:00

304 lines
9.8 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using System.Reflection;
using UnhollowerBaseLib;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase, IExpandHeight
{
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public PageHelper Pages = new PageHelper();
private CacheObjectBase[] m_cachedKeys;
private CacheObjectBase[] m_cachedValues;
public Type TypeOfKeys
{
get
{
if (m_keysType == null) GetGenericArguments();
return m_keysType;
}
}
private Type m_keysType;
public Type TypeOfValues
{
get
{
if (m_valuesType == null) GetGenericArguments();
return m_valuesType;
}
}
private Type m_valuesType;
public IDictionary IDict
{
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
set => m_iDictionary = value;
}
private IDictionary m_iDictionary;
// ========== Methods ==========
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
private IDictionary Il2CppDictionaryToMono()
{
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
// get keys and values
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
var values = ValueType.GetProperty("Values").GetValue(Value);
// create lists to hold them
var keyList = new List<object>();
var valueList = new List<object>();
// store entries with reflection
EnumerateWithReflection(keys, keyList);
EnumerateWithReflection(values, valueList);
// make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// finally iterate into dictionary
for (int i = 0; i < keyList.Count; i++)
{
dict.Add(keyList[i], valueList[i]);
}
return dict;
}
private void EnumerateWithReflection(object collection, List<object> list)
{
// invoke GetEnumerator
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
// get the type of it
var enumeratorType = enumerator.GetType();
// reflect MoveNext and Current
var moveNext = enumeratorType.GetMethod("MoveNext");
var current = enumeratorType.GetProperty("Current");
// iterate
while ((bool)moveNext.Invoke(enumerator, null))
{
list.Add(current.GetValue(enumerator));
}
}
private void GetGenericArguments()
{
if (this.MemInfo != null)
{
Type memberType = null;
switch (this.MemInfo.MemberType)
{
case MemberTypes.Field:
memberType = (MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (MemInfo as PropertyInfo).PropertyType;
break;
}
if (memberType != null && memberType.IsGenericType)
{
m_keysType = memberType.GetGenericArguments()[0];
m_valuesType = memberType.GetGenericArguments()[1];
}
}
else if (Value != null)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_keysType = type.GetGenericArguments()[0];
m_valuesType = type.GetGenericArguments()[1];
}
else
{
MelonLogger.Log("TODO? Dictionary is of type: " + Value.GetType().FullName);
}
}
}
public override void UpdateValue()
{
// first make sure we won't run into a TypeInitializationException.
if (!EnsureDictionaryIsSupported())
{
ReflectionException = "Dictionary Type not supported with Reflection!";
return;
}
base.UpdateValue();
// reset
IDict = null;
if (Value == null || IDict == null)
{
return;
}
var keys = new List<CacheObjectBase>();
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
keys.Add(cache);
}
var values = new List<CacheObjectBase>();
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
values.Add(cache);
}
m_cachedKeys = keys.ToArray();
m_cachedValues = values.ToArray();
}
private bool EnsureDictionaryIsSupported()
{
if (typeof(IDictionary).IsAssignableFrom(ValueType))
{
return true;
}
try
{
return Check(TypeOfKeys) && Check(TypeOfValues);
bool Check(Type type)
{
var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(type)
.GetField("NativeClassPtr")
.GetValue(null);
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
}
}
catch
{
return false;
}
}
// ============= GUI Draw =============
public override void DrawValue(Rect window, float width)
{
if (m_cachedKeys == null || m_cachedValues == null)
{
GUILayout.Label("Cached keys or values is null!", null);
return;
}
var whitespace = CalcWhitespace(window);
int count = m_cachedKeys.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;
}
}
var negativeWhitespace = window.width - (whitespace + 100f);
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = $"<color=#2df7b2>[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
if (IsExpanded)
{
Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
Pages.CurrentPageLabel();
// prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Left);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Right);
}
Pages.DrawLimitInputArea();
GUILayout.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{
var key = m_cachedKeys[i];
var val = m_cachedValues[i];
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
//GUILayout.Space(whitespace);
if (key == null || val == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
key.DrawValue(window, (window.width / 2) - 30f);
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
val.DrawValue(window, (window.width / 2) - 30f);
}
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
}
}
}