summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md12
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs18
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs65
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs20
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs65
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs3
6 files changed, 76 insertions, 107 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 21f9d213..554b9518 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -26,12 +26,12 @@
* For SMAPI developers:
* The web API now returns an update alert in two new cases: any newer unofficial update (previously only shown if the mod was incompatible), and a newer prerelease version if the installed non-prerelease version is broken (previously only shown if the installed version was prerelease).
- * Internal refactoring to simplify game updates:
- * Reorganised SMAPI core to reduce coupling to `Game1` and make it easier to navigate.
- * Added rewriter for any method broken due to new optional parameters.
- * Added rewriter for any field which was replaced by a property.
- * `FieldReplaceRewriter` now supports mapping to a different target type.
- * Internal refactoring to simplify future game updates.
+ * Reorganised SMAPI core to reduce coupling to `Game1`, make it easier to navigate, and simplify future game updates.
+ * SMAPI now automatically fixes code broken by these changes in game code, so manual rewriters are no longer needed:
+ * reference to a method with new optional parameters;
+ * reference to a field replaced by a property;
+ * reference to a field replaced by a `const` field.
+ * `FieldReplaceRewriter` now supports mapping to a different target type.
## 3.6.2
Released 02 August 2020 for Stardew Valley 1.4.1 or later.
diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs
index 36058b86..4b88148f 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs
@@ -59,6 +59,24 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
: null;
}
+ /// <summary>Get the CIL instruction to load a value onto the stack.</summary>
+ /// <param name="rawValue">The constant value to inject.</param>
+ /// <returns>Returns the instruction, or <c>null</c> if the value type isn't supported.</returns>
+ public static Instruction GetLoadValueInstruction(object rawValue)
+ {
+ return rawValue switch
+ {
+ null => Instruction.Create(OpCodes.Ldnull),
+ bool value => Instruction.Create(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0),
+ int value => Instruction.Create(OpCodes.Ldc_I4, value), // int32
+ long value => Instruction.Create(OpCodes.Ldc_I8, value), // int64
+ float value => Instruction.Create(OpCodes.Ldc_R4, value), // float32
+ double value => Instruction.Create(OpCodes.Ldc_R8, value), // float64
+ string value => Instruction.Create(OpCodes.Ldstr, value),
+ _ => null
+ };
+ }
+
/// <summary>Get whether a type matches a type reference.</summary>
/// <param name="type">The defined type.</param>
/// <param name="reference">The type reference.</param>
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs
index 5a088ed8..ca04205c 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs
@@ -6,7 +6,7 @@ using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Rewriters
{
- /// <summary>Automatically fix references to fields that have been replaced by a property.</summary>
+ /// <summary>Automatically fix references to fields that have been replaced by a property or const field.</summary>
internal class HeuristicFieldRewriter : BaseInstructionHandler
{
/*********
@@ -36,14 +36,40 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
return false;
// skip if not broken
- if (fieldRef.Resolve() != null)
+ FieldDefinition fieldDefinition = fieldRef.Resolve();
+ if (fieldDefinition != null && !fieldDefinition.HasConstant)
return false;
+ // rewrite if possible
+ TypeDefinition declaringType = fieldRef.DeclaringType.Resolve();
+ bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld;
+ return
+ this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead)
+ || this.TryRewriteToConstField(instruction, fieldDefinition);
+ }
+
+
+ /*********
+ ** 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);
+ }
+
+ /// <summary>Try rewriting the field into a matching property.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="instruction">The CIL instruction to rewrite.</param>
+ /// <param name="fieldRef">The field reference.</param>
+ /// <param name="declaringType">The type on which the field was defined.</param>
+ /// <param name="isRead">Whether the field is being read; else it's being written to.</param>
+ private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead)
+ {
// 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;
+ PropertyDefinition property = declaringType.Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
+ MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod;
if (method == null)
return false;
@@ -55,15 +81,26 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
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)
+ /// <summary>Try rewriting the field into a matching const field.</summary>
+ /// <param name="instruction">The CIL instruction to rewrite.</param>
+ /// <param name="field">The field definition.</param>
+ private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field)
{
- return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name);
+ // must have been a static field read, and the new field must be const
+ if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true)
+ return false;
+
+ // get opcode for value type
+ Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant);
+ if (loadInstruction == null)
+ return false;
+
+ // rewrite to constant
+ instruction.OpCode = loadInstruction.OpCode;
+ instruction.Operand = loadInstruction.Operand;
+
+ this.Phrases.Add($"{field.DeclaringType.Name}.{field.Name} (field => const)");
+ return this.MarkRewritten();
}
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs
index 21b42e12..e133b6fa 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs
@@ -64,7 +64,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
// get instructions to inject parameter values
var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count)
- .Select(p => this.GetLoadValueInstruction(p.Constant))
+ .Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant))
.ToArray();
if (loadInstructions.Any(p => p == null))
return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized
@@ -105,23 +105,5 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
return true;
}
-
- /// <summary>Get the CIL instruction to load a value onto the stack.</summary>
- /// <param name="rawValue">The constant value to inject.</param>
- /// <returns>Returns the instruction, or <c>null</c> if the value type isn't supported.</returns>
- private Instruction GetLoadValueInstruction(object rawValue)
- {
- return rawValue switch
- {
- null => Instruction.Create(OpCodes.Ldnull),
- bool value => Instruction.Create(value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0),
- int value => Instruction.Create(OpCodes.Ldc_I4, value), // int32
- long value => Instruction.Create(OpCodes.Ldc_I8, value), // int64
- float value => Instruction.Create(OpCodes.Ldc_R4, value), // float32
- double value => Instruction.Create(OpCodes.Ldc_R8, value), // float64
- string value => Instruction.Create(OpCodes.Ldstr, value),
- _ => null
- };
- }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs
deleted file mode 100644
index f34d4943..00000000
--- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using StardewModdingAPI.Framework.ModLoading.Framework;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites static field references into constant values.</summary>
- /// <typeparam name="TValue">The constant value type.</typeparam>
- internal class StaticFieldToConstantRewriter<TValue> : 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 constant value to replace with.</summary>
- private readonly TValue Value;
-
-
- /*********
- ** 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="value">The constant value to replace with.</param>
- public StaticFieldToConstantRewriter(Type type, string fieldName, TValue value)
- : base(defaultPhrase: $"{type.FullName}.{fieldName} field")
- {
- this.Type = type;
- this.FromFieldName = fieldName;
- this.Value = value;
- }
-
- /// <inheritdoc />
- public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
- {
- // get field reference
- FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
- if (!RewriteHelper.IsFieldReferenceTo(fieldRef, this.Type.FullName, this.FromFieldName))
- return false;
-
- // rewrite to constant
- if (typeof(TValue) == typeof(int))
- {
- instruction.OpCode = OpCodes.Ldc_I4;
- instruction.Operand = this.Value;
- }
- else if (typeof(TValue) == typeof(string))
- {
- instruction.OpCode = OpCodes.Ldstr;
- instruction.Operand = this.Value;
- }
- else
- throw new NotSupportedException($"Rewriting to constant values of type {typeof(TValue)} isn't currently supported.");
-
- return this.MarkRewritten();
- }
- }
-}
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index 86e16e1e..09a199f9 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -35,9 +35,6 @@ namespace StardewModdingAPI.Metadata
if (platformChanged)
yield return new MethodParentRewriter(typeof(SpriteBatch), typeof(SpriteBatchFacade));
- // rewrite for Stardew Valley 1.3
- yield return new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize);
-
// heuristic rewrites
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);
yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies);