2020-08-29 21:15:54 +10:00
|
|
|
|
using System;
|
2020-09-06 21:33:09 +10:00
|
|
|
|
using System.Collections;
|
2020-08-29 21:15:54 +10:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MelonLoader;
|
|
|
|
|
using UnityEngine;
|
2020-09-06 21:33:09 +10:00
|
|
|
|
using System.Reflection;
|
2020-09-07 20:28:33 +10:00
|
|
|
|
using UnhollowerBaseLib;
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
|
|
|
|
namespace Explorer
|
|
|
|
|
{
|
2020-09-07 17:05:37 +10:00
|
|
|
|
public class CacheDictionary : CacheObjectBase, IExpandHeight
|
2020-08-29 21:15:54 +10:00
|
|
|
|
{
|
2020-09-06 21:33:09 +10:00
|
|
|
|
public bool IsExpanded { get; set; }
|
2020-09-07 17:05:37 +10:00
|
|
|
|
public float WhiteSpace { get; set; } = 215f;
|
|
|
|
|
public float ButtonWidthOffset { get; set; } = 290f;
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
2020-09-07 17:05:37 +10:00
|
|
|
|
public PageHelper Pages = new PageHelper();
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
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;
|
|
|
|
|
|
2020-09-07 17:05:37 +10:00
|
|
|
|
// ========== Methods ==========
|
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
// 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);
|
|
|
|
|
|
2020-09-07 03:25:43 +10:00
|
|
|
|
// create lists to hold them
|
|
|
|
|
var keyList = new List<object>();
|
2020-09-06 21:33:09 +10:00
|
|
|
|
var valueList = new List<object>();
|
|
|
|
|
|
2020-09-08 04:33:27 +10:00
|
|
|
|
// store entries with reflection
|
|
|
|
|
EnumerateWithReflection(keys, keyList);
|
|
|
|
|
EnumerateWithReflection(values, valueList);
|
2020-09-06 21:33:09 +10:00
|
|
|
|
|
2020-09-08 04:33:27 +10:00
|
|
|
|
// make actual mono dictionary
|
|
|
|
|
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
|
|
|
|
.MakeGenericType(TypeOfKeys, TypeOfValues));
|
2020-09-06 21:33:09 +10:00
|
|
|
|
|
2020-09-08 04:33:27 +10:00
|
|
|
|
// finally iterate into dictionary
|
2020-09-06 21:33:09 +10:00
|
|
|
|
for (int i = 0; i < keyList.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
dict.Add(keyList[i], valueList[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dict;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-08 04:33:27 +10:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
private void GetGenericArguments()
|
2020-08-29 21:15:54 +10:00
|
|
|
|
{
|
2020-09-07 17:05:37 +10:00
|
|
|
|
if (this.MemInfo != null)
|
2020-09-06 21:33:09 +10:00
|
|
|
|
{
|
2020-09-07 17:05:37 +10:00
|
|
|
|
Type memberType = null;
|
|
|
|
|
switch (this.MemInfo.MemberType)
|
2020-09-06 21:33:09 +10:00
|
|
|
|
{
|
2020-09-07 17:05:37 +10:00
|
|
|
|
case MemberTypes.Field:
|
|
|
|
|
memberType = (MemInfo as FieldInfo).FieldType;
|
|
|
|
|
break;
|
|
|
|
|
case MemberTypes.Property:
|
|
|
|
|
memberType = (MemInfo as PropertyInfo).PropertyType;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
2020-09-07 17:05:37 +10:00
|
|
|
|
if (memberType != null && memberType.IsGenericType)
|
|
|
|
|
{
|
|
|
|
|
m_keysType = memberType.GetGenericArguments()[0];
|
|
|
|
|
m_valuesType = memberType.GetGenericArguments()[1];
|
2020-09-06 21:33:09 +10:00
|
|
|
|
}
|
2020-09-07 17:05:37 +10:00
|
|
|
|
}
|
|
|
|
|
else if (Value != null)
|
|
|
|
|
{
|
|
|
|
|
var type = Value.GetType();
|
|
|
|
|
if (type.IsGenericType)
|
2020-09-06 21:33:09 +10:00
|
|
|
|
{
|
2020-09-07 17:05:37 +10:00
|
|
|
|
m_keysType = type.GetGenericArguments()[0];
|
|
|
|
|
m_valuesType = type.GetGenericArguments()[1];
|
2020-09-06 21:33:09 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void UpdateValue()
|
|
|
|
|
{
|
2020-09-07 20:28:33 +10:00
|
|
|
|
// first make sure we won't run into a TypeInitializationException.
|
|
|
|
|
if (!EnsureDictionaryIsSupported())
|
|
|
|
|
{
|
|
|
|
|
ReflectionException = "Dictionary Type not supported with Reflection!";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
base.UpdateValue();
|
|
|
|
|
|
|
|
|
|
// reset
|
|
|
|
|
IDict = null;
|
|
|
|
|
|
|
|
|
|
if (Value == null || IDict == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
var keys = new List<CacheObjectBase>();
|
|
|
|
|
foreach (var key in IDict.Keys)
|
|
|
|
|
{
|
|
|
|
|
var cache = GetCacheObject(key, TypeOfKeys);
|
|
|
|
|
cache.UpdateValue();
|
|
|
|
|
keys.Add(cache);
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
var values = new List<CacheObjectBase>();
|
|
|
|
|
foreach (var val in IDict.Values)
|
|
|
|
|
{
|
|
|
|
|
var cache = GetCacheObject(val, TypeOfValues);
|
|
|
|
|
cache.UpdateValue();
|
|
|
|
|
values.Add(cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_cachedKeys = keys.ToArray();
|
|
|
|
|
m_cachedValues = values.ToArray();
|
2020-08-29 21:15:54 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-07 20:28:33 +10:00
|
|
|
|
private bool EnsureDictionaryIsSupported()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
//var ilTypes = new List<Il2CppSystem.Type>();
|
|
|
|
|
var monoTypes = new Type[] { TypeOfKeys, TypeOfValues };
|
|
|
|
|
|
|
|
|
|
foreach (var type in monoTypes)
|
|
|
|
|
{
|
|
|
|
|
var generic = typeof(Il2CppClassPointerStore<>).MakeGenericType(type);
|
|
|
|
|
if (generic == null) return false;
|
|
|
|
|
|
|
|
|
|
var genericPtr = (IntPtr)generic.GetField("NativeClassPtr").GetValue(null);
|
|
|
|
|
if (genericPtr == null) return false;
|
|
|
|
|
|
|
|
|
|
var classPtr = IL2CPP.il2cpp_class_get_type(genericPtr);
|
|
|
|
|
if (classPtr == null) return false;
|
|
|
|
|
|
|
|
|
|
var internalType = Il2CppSystem.Type.internal_from_handle(classPtr);
|
|
|
|
|
if (internalType == null) return false;
|
|
|
|
|
|
|
|
|
|
//ilTypes.Add(internalType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Should be fine if we got this far, but I'll leave the rest below commented out just in case.
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Got both generic types, continuing...");
|
|
|
|
|
|
|
|
|
|
//var dictIlClass = IL2CPP.GetIl2CppClass("mscorlib.dll", "System.Collections.Generic", "Dictionary`2");
|
|
|
|
|
//if (dictIlClass == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Got base dictionary Il2Cpp type");
|
|
|
|
|
|
|
|
|
|
//var ilClassFromType = IL2CPP.il2cpp_class_get_type(dictIlClass);
|
|
|
|
|
//if (ilClassFromType == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("got IntPtr from base dictionary type");
|
|
|
|
|
|
|
|
|
|
//var internalHandle = Il2CppSystem.Type.internal_from_handle(ilClassFromType);
|
|
|
|
|
//if (internalHandle == null) return;
|
|
|
|
|
|
|
|
|
|
//var generic = internalHandle.MakeGenericType(new Il2CppReferenceArray<Il2CppSystem.Type>(new Il2CppSystem.Type[]
|
|
|
|
|
//{
|
|
|
|
|
// ilTypes[0], ilTypes[1]
|
|
|
|
|
//}));
|
|
|
|
|
//if (generic == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Made generic handle for our entry types");
|
|
|
|
|
|
|
|
|
|
//var nativeClassPtr = generic.TypeHandle.value;
|
|
|
|
|
//if (nativeClassPtr == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Got the actual nativeClassPtr for the handle");
|
|
|
|
|
|
|
|
|
|
//var dictType = typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).MakeGenericType(TypeOfKeys, TypeOfValues);
|
|
|
|
|
//if (dictType == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Made the generic type for the dictionary");
|
|
|
|
|
|
|
|
|
|
//var pointerStoreType = typeof(Il2CppClassPointerStore<>).MakeGenericType(dictType);
|
|
|
|
|
//if (pointerStoreType == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Made the generic PointerStoreType for our dict");
|
|
|
|
|
|
|
|
|
|
//var ptrToSet = IL2CPP.il2cpp_class_from_type(nativeClassPtr);
|
|
|
|
|
//if (ptrToSet == null) return;
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Got class from nativeClassPtr, setting value...");
|
|
|
|
|
|
|
|
|
|
//pointerStoreType.GetField("NativeClassPtr").SetValue(null, ptrToSet);
|
|
|
|
|
|
|
|
|
|
//MelonLogger.Log("Ok");
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-06 21:33:09 +10:00
|
|
|
|
// ============= GUI Draw =============
|
|
|
|
|
|
2020-08-29 21:15:54 +10:00
|
|
|
|
public override void DrawValue(Rect window, float width)
|
|
|
|
|
{
|
2020-09-06 21:33:09 +10:00
|
|
|
|
if (m_cachedKeys == null || m_cachedValues == null)
|
|
|
|
|
{
|
|
|
|
|
GUILayout.Label("Cached keys or values is null!", null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
|
|
|
string btnLabel = $"<color=yellow>[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
|
|
|
|
|
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
|
|
|
|
{
|
|
|
|
|
WindowManager.InspectObject(Value, out bool _);
|
|
|
|
|
}
|
|
|
|
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
|
|
|
|
|
|
|
|
GUILayout.Space(5);
|
|
|
|
|
|
|
|
|
|
if (IsExpanded)
|
|
|
|
|
{
|
|
|
|
|
float whitespace = WhiteSpace;
|
|
|
|
|
if (whitespace > 0)
|
|
|
|
|
{
|
|
|
|
|
ClampLabelWidth(window, ref whitespace);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2020-08-29 21:15:54 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|