fix Data\Movies error regression when patching dictionary (#711)
This commit is contained in:
parent
b4f307e1ba
commit
e8ad5d0a24
|
@ -8,12 +8,12 @@
|
|||
// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using HarmonyLib;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author.
|
||||
namespace MonoMod.Utils
|
||||
|
@ -24,38 +24,38 @@ namespace MonoMod.Utils
|
|||
{
|
||||
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
|
||||
|
||||
private static readonly object[] _NoArgs = new object[0];
|
||||
private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
|
||||
private static object[] _NoArgs = new object[0];
|
||||
private static object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
|
||||
|
||||
private static readonly Type t_RuntimeModule =
|
||||
private static Type t_RuntimeModule =
|
||||
typeof(Module).Assembly
|
||||
.GetType("System.Reflection.RuntimeModule");
|
||||
|
||||
private static readonly PropertyInfo p_RuntimeModule_RuntimeType =
|
||||
private static PropertyInfo p_RuntimeModule_RuntimeType =
|
||||
typeof(Module).Assembly
|
||||
.GetType("System.Reflection.RuntimeModule")
|
||||
?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
private static readonly Type t_RuntimeType =
|
||||
private static Type t_RuntimeType =
|
||||
typeof(Type).Assembly
|
||||
.GetType("System.RuntimeType");
|
||||
|
||||
private static readonly PropertyInfo p_RuntimeType_Cache =
|
||||
private static PropertyInfo p_RuntimeType_Cache =
|
||||
typeof(Type).Assembly
|
||||
.GetType("System.RuntimeType")
|
||||
?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList =
|
||||
private static MethodInfo m_RuntimeTypeCache_GetFieldList =
|
||||
typeof(Type).Assembly
|
||||
.GetType("System.RuntimeType+RuntimeTypeCache")
|
||||
?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList =
|
||||
private static MethodInfo m_RuntimeTypeCache_GetPropertyList =
|
||||
typeof(Type).Assembly
|
||||
.GetType("System.RuntimeType+RuntimeTypeCache")
|
||||
?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
private static readonly ConditionalWeakTable<Type, object> _CacheFixed = new ConditionalWeakTable<Type, object>();
|
||||
private static readonly ConditionalWeakTable<Type, CacheFixEntry> _CacheFixed = new ConditionalWeakTable<Type, CacheFixEntry>();
|
||||
|
||||
public static void Apply()
|
||||
{
|
||||
|
@ -143,24 +143,80 @@ namespace MonoMod.Utils
|
|||
if (!t_RuntimeType.IsInstanceOfType(type))
|
||||
continue;
|
||||
|
||||
_CacheFixed.GetValue(type, rt =>
|
||||
{
|
||||
CacheFixEntry entry = _CacheFixed.GetValue(type, rt => {
|
||||
CacheFixEntry entryNew = new CacheFixEntry();
|
||||
object cache;
|
||||
Array properties, fields;
|
||||
|
||||
object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs);
|
||||
_FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList);
|
||||
_FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList);
|
||||
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
|
||||
entryNew.Cache = cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs);
|
||||
entryNew.Properties = properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList);
|
||||
entryNew.Fields = fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList);
|
||||
|
||||
return new object();
|
||||
_FixReflectionCacheOrder<PropertyInfo>(properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(fields);
|
||||
|
||||
entryNew.NeedsVerify = false;
|
||||
return entryNew;
|
||||
});
|
||||
|
||||
if (entry.NeedsVerify && !_Verify(entry, type))
|
||||
{
|
||||
lock (entry)
|
||||
{
|
||||
_FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
|
||||
_FixReflectionCacheOrder<FieldInfo>(entry.Fields);
|
||||
}
|
||||
}
|
||||
|
||||
entry.NeedsVerify = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void _FixReflectionCacheOrder<T>(object cache, MethodInfo getter) where T : MemberInfo
|
||||
private static bool _Verify(CacheFixEntry entry, Type type)
|
||||
{
|
||||
object cache;
|
||||
Array properties, fields;
|
||||
|
||||
// The cache can sometimes be invalidated.
|
||||
// TODO: Figure out if only the arrays get replaced or if the entire cache object gets replaced!
|
||||
if (entry.Cache != (cache = p_RuntimeType_Cache.GetValue(type, _NoArgs)))
|
||||
{
|
||||
entry.Cache = cache;
|
||||
entry.Properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList);
|
||||
entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList);
|
||||
return false;
|
||||
|
||||
}
|
||||
else if (entry.Properties != (properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList)))
|
||||
{
|
||||
entry.Properties = properties;
|
||||
entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList);
|
||||
return false;
|
||||
|
||||
}
|
||||
else if (entry.Fields != (fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList)))
|
||||
{
|
||||
entry.Fields = fields;
|
||||
return false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache should still be the same, no re-fix necessary.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Array _GetArray(object cache, MethodInfo getter)
|
||||
{
|
||||
// Get and discard once, otherwise we might not be getting the actual backing array.
|
||||
getter.Invoke(cache, _CacheGetterArgs);
|
||||
Array orig = (Array)getter.Invoke(cache, _CacheGetterArgs);
|
||||
return (Array)getter.Invoke(cache, _CacheGetterArgs);
|
||||
}
|
||||
|
||||
private static void _FixReflectionCacheOrder<T>(Array orig) where T : MemberInfo
|
||||
{
|
||||
// Sort using a short-lived list.
|
||||
List<T> list = new List<T>(orig.Length);
|
||||
for (int i = 0; i < orig.Length; i++)
|
||||
|
@ -172,5 +228,12 @@ namespace MonoMod.Utils
|
|||
orig.SetValue(list[i], i);
|
||||
}
|
||||
|
||||
private class CacheFixEntry
|
||||
{
|
||||
public object Cache;
|
||||
public Array Properties;
|
||||
public Array Fields;
|
||||
public bool NeedsVerify;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue