summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md3
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs63
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs3
4 files changed, 42 insertions, 29 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index a65db68c..3f7d5198 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -9,12 +9,13 @@
## Upcoming release
* For players:
- * Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent and unpredictable errors when enabled.
+ * Added heuristic compatibility rewrites for some common cases. That fixes some mods which previously broke on Android or in newer game versions.
* Tweaked the rules for showing update alerts (see _for SMAPI developers_ below for details).
* Fixed crossplatform compatibility for mods which use the `[HarmonyPatch(type)]` attribute (thanks to spacechase0!).
* Fixed broken URL in update alerts for unofficial versions.
* Fixed rare error when a mod adds/removes event handlers asynchronously.
* Fixed rare issue where the console showed incorrect colors when mods wrote to it asynchronously.
+ * Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent and unpredictable errors when enabled.
* For modders:
* You can now read/write `SDate` values to JSON (e.g. for `config.json`, network mod messages, etc).
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
index fde37d68..611b2cf2 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
@@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
** Protected methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="defaultPhrase">A brief noun phrase indicating what the handler matches.</param>
+ /// <param name="defaultPhrase">A brief noun phrase indicating what the handler matches, used if <see cref="Phrases"/> is empty.</param>
protected BaseInstructionHandler(string defaultPhrase)
{
this.DefaultPhrase = defaultPhrase;
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs
index c3b5854e..514691cf 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs
@@ -1,47 +1,33 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters
{
- /// <summary>Rewrites field references into property references.</summary>
+ /// <summary>Rewrites references to fields which no longer exist, but which have an equivalent property with the exact same name.</summary>
internal class FieldToPropertyRewriter : BaseInstructionHandler
{
/*********
** Fields
*********/
- /// <summary>The type containing the field to which references should be rewritten.</summary>
- private readonly Type Type;
-
- /// <summary>The field name to which references should be rewritten.</summary>
- private readonly string FromFieldName;
-
- /// <summary>The new property name.</summary>
- private readonly string ToPropertyName;
+ /// <summary>The assembly names to which to rewrite broken references.</summary>
+ private readonly HashSet<string> RewriteReferencesToAssemblies;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="type">The type whose field to which references should be rewritten.</param>
- /// <param name="fieldName">The field name to rewrite.</param>
- /// <param name="propertyName">The property name (if different).</param>
- public FieldToPropertyRewriter(Type type, string fieldName, string propertyName)
- : base(defaultPhrase: $"{type.FullName}.{fieldName} field")
+ /// <param name="rewriteReferencesToAssemblies">The assembly names to which to rewrite broken references.</param>
+ public FieldToPropertyRewriter(string[] rewriteReferencesToAssemblies)
+ : base(defaultPhrase: "field changed to property") // ignored since we specify phrases
{
- this.Type = type;
- this.FromFieldName = fieldName;
- this.ToPropertyName = propertyName;
+ this.RewriteReferencesToAssemblies = new HashSet<string>(rewriteReferencesToAssemblies);
}
- /// <summary>Construct an instance.</summary>
- /// <param name="type">The type whose field to which references should be rewritten.</param>
- /// <param name="fieldName">The field name to rewrite.</param>
- public FieldToPropertyRewriter(Type type, string fieldName)
- : this(type, fieldName, fieldName) { }
-
/// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
@@ -52,14 +38,37 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
{
// get field ref
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
- if (!RewriteHelper.IsFieldReferenceTo(fieldRef, this.Type.FullName, this.FromFieldName))
+ if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType))
+ return false;
+
+ // skip if not broken
+ if (fieldRef.Resolve() != null)
+ return false;
+
+ // get equivalent property
+ PropertyDefinition property = fieldRef.DeclaringType.Resolve().Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
+ MethodDefinition method = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld
+ ? property?.GetMethod
+ : property?.SetMethod;
+ if (method == null)
return false;
- // replace with property
- string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set";
- MethodReference propertyRef = module.ImportReference(this.Type.GetMethod($"{methodPrefix}_{this.ToPropertyName}"));
+ // rewrite field to property
+ MethodReference propertyRef = module.ImportReference(method);
replaceWith(cil.Create(OpCodes.Call, propertyRef));
+ this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)");
return this.MarkRewritten();
}
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Whether references to the given type should be validated.</summary>
+ /// <param name="type">The type reference.</param>
+ private bool ShouldValidate(TypeReference type)
+ {
+ return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name);
+ }
}
}
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index 79d7a7a8..fca809f8 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -31,6 +31,9 @@ namespace StardewModdingAPI.Metadata
/****
** rewrite CIL to fix incompatible code
****/
+ // generic rewrites
+ yield return new FieldToPropertyRewriter(this.ValidateReferencesToAssemblies);
+
// rewrite for crossplatform compatibility
if (platformChanged)
yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));