Add support for writing to IList<T>'s which don't implement IList

This commit is contained in:
Sinai 2021-05-18 20:43:51 +10:00
parent 5aef8ddc99
commit b062924af7
3 changed files with 93 additions and 4 deletions

View File

@ -14,8 +14,40 @@ using UnhollowerBaseLib;
namespace UnityExplorer.Tests namespace UnityExplorer.Tests
{ {
public class TestIndexer : IList<int>
{
private readonly List<int> list = new List<int>() { 1,2,3,4,5 };
public int Count => list.Count;
public bool IsReadOnly => false;
int IList<int>.this[int index]
{
get => list[index];
set => list[index] = value;
}
public int IndexOf(int item) => list.IndexOf(item);
public bool Contains(int item) => list.Contains(item);
public void Add(int item) => list.Add(item);
public void Insert(int index, int item) => list.Insert(index, item);
public bool Remove(int item) => list.Remove(item);
public void RemoveAt(int index) => list.RemoveAt(index);
public void Clear() => list.Clear();
public void CopyTo(int[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
public IEnumerator<int> GetEnumerator() => list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
}
public static class TestClass public static class TestClass
{ {
public static readonly TestIndexer AAAAATest = new TestIndexer();
public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue) public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue)
{ {
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}"); ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");

View File

@ -19,7 +19,7 @@ namespace UnityExplorer
public static class ExplorerCore public static class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; public const string NAME = "UnityExplorer";
public const string VERSION = "4.0.0"; public const string VERSION = "4.0.1";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";

View File

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityExplorer.UI.CacheObject; using UnityExplorer.UI.CacheObject;
@ -19,11 +20,14 @@ namespace UnityExplorer.UI.IValues
object ICacheObjectController.Target => this.CurrentOwner.Value; object ICacheObjectController.Target => this.CurrentOwner.Value;
public Type TargetType { get; private set; } public Type TargetType { get; private set; }
public override bool CanWrite => base.CanWrite && RefIList != null && !RefIList.IsReadOnly; public override bool CanWrite => base.CanWrite && ((RefIList != null && !RefIList.IsReadOnly) || IsWritableGenericIList);
public Type EntryType; public Type EntryType;
public IList RefIList; public IList RefIList;
private bool IsWritableGenericIList;
private PropertyInfo genericIndexer;
public int ItemCount => values.Count; public int ItemCount => values.Count;
private readonly List<object> values = new List<object>(); private readonly List<object> values = new List<object>();
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>(); private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
@ -92,6 +96,12 @@ namespace UnityExplorer.UI.IValues
{ {
RefIList = value as IList; RefIList = value as IList;
// Check if the type implements IList<T> but not IList (ie. Il2CppArrayBase)
if (RefIList == null)
CheckGenericIList(value);
else
IsWritableGenericIList = false;
values.Clear(); values.Clear();
int idx = 0; int idx = 0;
@ -141,14 +151,61 @@ namespace UnityExplorer.UI.IValues
} }
} }
private void CheckGenericIList(object value)
{
try
{
var type = value.GetType();
if (type.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(IList<>)))
IsWritableGenericIList = !(bool)type.GetProperty("IsReadOnly").GetValue(value, null);
else
IsWritableGenericIList = false;
if (IsWritableGenericIList)
{
// Find the "this[int index]" property.
// It might be a private implementation.
foreach (var prop in type.GetProperties(ReflectionUtility.FLAGS))
{
if ((prop.Name == "Item"
|| (prop.Name.StartsWith("System.Collections.Generic.IList<") && prop.Name.EndsWith(">.Item")))
&& prop.GetIndexParameters() is ParameterInfo[] parameters
&& parameters.Length == 1
&& parameters[0].ParameterType == typeof(int))
{
genericIndexer = prop;
break;
}
}
if (genericIndexer == null)
{
ExplorerCore.LogWarning($"Failed to find indexer property for IList<T> type '{type.FullName}'!");
IsWritableGenericIList = false;
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception processing IEnumerable for IList<T> check: {ex.ReflectionExToString()}");
IsWritableGenericIList = false;
}
}
// Setting the value of an index to the list // Setting the value of an index to the list
public void TrySetValueToIndex(object value, int index) public void TrySetValueToIndex(object value, int index)
{ {
try try
{ {
//value = value.TryCast(this.EntryType); if (!IsWritableGenericIList)
RefIList[index] = value; {
RefIList[index] = value;
}
else
{
genericIndexer.SetValue(CurrentOwner.Value, value, new object[] { index });
}
var entry = cachedEntries[index]; var entry = cachedEntries[index];
entry.SetValueFromSource(value); entry.SetValueFromSource(value);