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.
|
// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reflection;
|
|
||||||
using System.Reflection.Emit;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author.
|
// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author.
|
||||||
namespace MonoMod.Utils
|
namespace MonoMod.Utils
|
||||||
|
@ -24,38 +24,38 @@ namespace MonoMod.Utils
|
||||||
{
|
{
|
||||||
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
|
// .NET Framework can break member ordering if using Module.Resolve* on certain members.
|
||||||
|
|
||||||
private static readonly object[] _NoArgs = new object[0];
|
private static object[] _NoArgs = new object[0];
|
||||||
private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
|
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
|
typeof(Module).Assembly
|
||||||
.GetType("System.Reflection.RuntimeModule");
|
.GetType("System.Reflection.RuntimeModule");
|
||||||
|
|
||||||
private static readonly PropertyInfo p_RuntimeModule_RuntimeType =
|
private static PropertyInfo p_RuntimeModule_RuntimeType =
|
||||||
typeof(Module).Assembly
|
typeof(Module).Assembly
|
||||||
.GetType("System.Reflection.RuntimeModule")
|
.GetType("System.Reflection.RuntimeModule")
|
||||||
?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
private static readonly Type t_RuntimeType =
|
private static Type t_RuntimeType =
|
||||||
typeof(Type).Assembly
|
typeof(Type).Assembly
|
||||||
.GetType("System.RuntimeType");
|
.GetType("System.RuntimeType");
|
||||||
|
|
||||||
private static readonly PropertyInfo p_RuntimeType_Cache =
|
private static PropertyInfo p_RuntimeType_Cache =
|
||||||
typeof(Type).Assembly
|
typeof(Type).Assembly
|
||||||
.GetType("System.RuntimeType")
|
.GetType("System.RuntimeType")
|
||||||
?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList =
|
private static MethodInfo m_RuntimeTypeCache_GetFieldList =
|
||||||
typeof(Type).Assembly
|
typeof(Type).Assembly
|
||||||
.GetType("System.RuntimeType+RuntimeTypeCache")
|
.GetType("System.RuntimeType+RuntimeTypeCache")
|
||||||
?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList =
|
private static MethodInfo m_RuntimeTypeCache_GetPropertyList =
|
||||||
typeof(Type).Assembly
|
typeof(Type).Assembly
|
||||||
.GetType("System.RuntimeType+RuntimeTypeCache")
|
.GetType("System.RuntimeType+RuntimeTypeCache")
|
||||||
?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
?.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()
|
public static void Apply()
|
||||||
{
|
{
|
||||||
|
@ -143,24 +143,80 @@ namespace MonoMod.Utils
|
||||||
if (!t_RuntimeType.IsInstanceOfType(type))
|
if (!t_RuntimeType.IsInstanceOfType(type))
|
||||||
continue;
|
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);
|
// All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null.
|
||||||
_FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList);
|
entryNew.Cache = cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs);
|
||||||
_FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void _FixReflectionCacheOrder<T>(object cache, MethodInfo getter) where T : MemberInfo
|
entry.NeedsVerify = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// Get and discard once, otherwise we might not be getting the actual backing array.
|
||||||
getter.Invoke(cache, _CacheGetterArgs);
|
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.
|
// Sort using a short-lived list.
|
||||||
List<T> list = new List<T>(orig.Length);
|
List<T> list = new List<T>(orig.Length);
|
||||||
for (int i = 0; i < orig.Length; i++)
|
for (int i = 0; i < orig.Length; i++)
|
||||||
|
@ -172,5 +228,12 @@ namespace MonoMod.Utils
|
||||||
orig.SetValue(list[i], i);
|
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