add heuristic rewrite for field => const changes
This commit is contained in:
parent
54e7fb7a0b
commit
0bf692addc
|
@ -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.
|
||||
* 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.
|
||||
* Internal refactoring to simplify future game updates.
|
||||
|
||||
## 3.6.2
|
||||
Released 02 August 2020 for Stardew Valley 1.4.1 or later.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,23 +36,16 @@ 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// rewrite field to property
|
||||
instruction.OpCode = OpCodes.Call;
|
||||
instruction.Operand = module.ImportReference(method);
|
||||
|
||||
this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)");
|
||||
return this.MarkRewritten();
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -65,5 +58,49 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
|
|||
{
|
||||
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 = declaringType.Properties.FirstOrDefault(p => p.Name == fieldRef.Name);
|
||||
MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod;
|
||||
if (method == null)
|
||||
return false;
|
||||
|
||||
// rewrite field to property
|
||||
instruction.OpCode = OpCodes.Call;
|
||||
instruction.Operand = module.ImportReference(method);
|
||||
|
||||
this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)");
|
||||
return this.MarkRewritten();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue