diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-07-28 18:03:49 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-07-28 18:03:49 -0400 |
commit | e8ad5d0a2404720ec4f8cc75117e62bcc72cd068 (patch) | |
tree | 34d02dd1d5c1da317bbb6b2279da6a4b2ed3a17c | |
parent | b4f307e1ba88df072efed3b94975fa45789ae1ef (diff) | |
download | SMAPI-e8ad5d0a2404720ec4f8cc75117e62bcc72cd068.tar.gz SMAPI-e8ad5d0a2404720ec4f8cc75117e62bcc72cd068.tar.bz2 SMAPI-e8ad5d0a2404720ec4f8cc75117e62bcc72cd068.zip |
fix Data\Movies error regression when patching dictionary (#711)
-rw-r--r-- | src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs | 101 |
1 files changed, 82 insertions, 19 deletions
diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 9f5819d7..9d63ab2c 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -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; + + // 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); - object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); - _FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList); - _FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList); + _FixReflectionCacheOrder<PropertyInfo>(properties); + _FixReflectionCacheOrder<FieldInfo>(fields); - return new object(); + 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; + } } } |