From 94b8262692d2452e77d57fa22046dded231cdb0a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Aug 2020 20:11:56 -0400 Subject: add heuristic field-to-property rewriter --- .../Rewriters/FieldToPropertyRewriter.cs | 63 ++++++++++++---------- 1 file changed, 36 insertions(+), 27 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') 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 { - /// Rewrites field references into property references. + /// Rewrites references to fields which no longer exist, but which have an equivalent property with the exact same name. internal class FieldToPropertyRewriter : BaseInstructionHandler { /********* ** Fields *********/ - /// The type containing the field to which references should be rewritten. - private readonly Type Type; - - /// The field name to which references should be rewritten. - private readonly string FromFieldName; - - /// The new property name. - private readonly string ToPropertyName; + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; /********* ** Public methods *********/ /// Construct an instance. - /// The type whose field to which references should be rewritten. - /// The field name to rewrite. - /// The property name (if different). - public FieldToPropertyRewriter(Type type, string fieldName, string propertyName) - : base(defaultPhrase: $"{type.FullName}.{fieldName} field") + /// The assembly names to which to rewrite broken references. + 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(rewriteReferencesToAssemblies); } - /// Construct an instance. - /// The type whose field to which references should be rewritten. - /// The field name to rewrite. - public FieldToPropertyRewriter(Type type, string fieldName) - : this(type, fieldName, fieldName) { } - /// Rewrite a CIL instruction reference if needed. /// The assembly module containing the instruction. /// The CIL processor. @@ -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 + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } } } -- cgit From 1bd67baae116b0307b351222b056a0615107eb3c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Aug 2020 21:39:50 -0400 Subject: support mapping fields to a different type in FieldReplaceRewriter --- .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 8043b13a..9166ab86 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -26,17 +26,27 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters ** Public methods *********/ /// Construct an instance. - /// The type whose field to rewrite. + /// The type whose field to rewrite. /// The field name to rewrite. + /// The new type which will have the field. /// The new field name to reference. - public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName) - : base(defaultPhrase: $"{type.FullName}.{fromFieldName} field") + public FieldReplaceRewriter(Type fromType, string fromFieldName, Type toType, string toFieldName) + : base(defaultPhrase: $"{fromType.FullName}.{fromFieldName} field") { - this.Type = type; + this.Type = fromType; this.FromFieldName = fromFieldName; - this.ToField = type.GetField(toFieldName); + this.ToField = toType.GetField(toFieldName); if (this.ToField == null) - throw new InvalidOperationException($"The {type.FullName} class doesn't have a {toFieldName} field."); + throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field."); + } + + /// Construct an instance. + /// The type whose field to rewrite. + /// The field name to rewrite. + /// The new field name to reference. + public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName) + : this(type, fromFieldName, type, toFieldName) + { } /// Rewrite a CIL instruction reference if needed. -- cgit From 3a890408760d0d38a418d9830374262043e2ba13 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Aug 2020 22:16:48 -0400 Subject: add rewriter for method references with missing optional parameters --- .../MethodWithMissingOptionalParameterRewriter.cs | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs new file mode 100644 index 00000000..9db3c3fd --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -0,0 +1,113 @@ +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 +{ + /// Rewrites references to methods which only broke because the definition has new optional parameters. + internal class MethodWithMissingOptionalParameterRewriter : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly names to which to rewrite broken references. + public MethodWithMissingOptionalParameterRewriter(string[] rewriteReferencesToAssemblies) + : base(defaultPhrase: "methods with missing parameters") // ignored since we specify phrases + { + this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); + } + + /// Rewrite a CIL instruction reference if needed. + /// The assembly module containing the instruction. + /// The CIL processor. + /// The CIL instruction to handle. + /// Replaces the CIL instruction with a new one. + /// Returns whether the instruction was changed. + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + { + // get method ref + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) + return false; + + // skip if not broken + if (methodRef.Resolve() != null) + return false; + + // get type + var type = methodRef.DeclaringType.Resolve(); + if (type == null) + return false; + + // get method definition + MethodDefinition method = null; + foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + { + // reference matches initial parameters of definition + if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) + continue; + + // all remaining parameters in definition are optional + if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) + continue; + + method = match; + break; + } + if (method == null) + return false; + + // add extra parameters + foreach (ParameterDefinition parameter in method.Parameters.Skip(methodRef.Parameters.Count)) + { + methodRef.Parameters.Add(new ParameterDefinition( + name: parameter.Name, + attributes: parameter.Attributes, + parameterType: module.ImportReference(parameter.ParameterType) + )); + } + + this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); + return this.MarkRewritten(); + } + + + /********* + ** Private methods + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + + /// Get whether every parameter in the method reference matches the exact order and type of the parameters in the method definition. This ignores extra parameters in the definition. + /// The method reference whose parameters to check. + /// The method definition whose parameters to check against. + private bool InitialParametersMatch(MethodReference methodRef, MethodDefinition method) + { + if (methodRef.Parameters.Count > method.Parameters.Count) + return false; + + for (int i = 0; i < methodRef.Parameters.Count; i++) + { + if (!RewriteHelper.IsSameType(methodRef.Parameters[i].ParameterType, method.Parameters[i].ParameterType)) + return false; + } + + return true; + } + } +} -- cgit From b9a9fe36bbaa1357b98a117400a62fecc0fc56cb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 Aug 2020 22:05:27 -0400 Subject: fix missing-parameter rewriter not loading default values onto stack --- .../MethodWithMissingOptionalParameterRewriter.cs | 40 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs index 9db3c3fd..75182890 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -68,14 +68,28 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (method == null) return false; - // add extra parameters - foreach (ParameterDefinition parameter in method.Parameters.Skip(methodRef.Parameters.Count)) + // get instructions to inject + var injectables = method.Parameters.Skip(methodRef.Parameters.Count) + .Select(p => new { Parameter = p, LoadValueInstruction = this.GetLoadValueInstruction(p.Constant) }) + .ToArray(); + if (injectables.Any(p => p.LoadValueInstruction == null)) + return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized + + // inject new parameters + foreach (var entry in injectables) { - methodRef.Parameters.Add(new ParameterDefinition( + // load value onto stack + cil.InsertBefore(instruction, entry.LoadValueInstruction); + + // add parameter + ParameterDefinition parameter = entry.Parameter; + var newParameter = new ParameterDefinition( name: parameter.Name, attributes: parameter.Attributes, parameterType: module.ImportReference(parameter.ParameterType) - )); + ); + newParameter.Constant = parameter.Constant; + methodRef.Parameters.Add(newParameter); } this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); @@ -109,5 +123,23 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return true; } + + /// Get the CIL instruction to load a value onto the stack. + /// The constant value to inject. + /// Returns the instruction, or null if the value type isn't supported. + 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 + }; + } } } -- cgit From abfe40bf691e4d384d444bf6f001de8d959b12bb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 Aug 2020 22:49:00 -0400 Subject: fix some method references only partially rewritten Thanks to Bepis on Discord for helping find the issue! --- .../MethodWithMissingOptionalParameterRewriter.cs | 28 +++++++--------------- 1 file changed, 8 insertions(+), 20 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs index 75182890..87ccf941 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -68,29 +68,17 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (method == null) return false; - // get instructions to inject - var injectables = method.Parameters.Skip(methodRef.Parameters.Count) - .Select(p => new { Parameter = p, LoadValueInstruction = this.GetLoadValueInstruction(p.Constant) }) + // get instructions to inject parameter values + var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) + .Select(p => this.GetLoadValueInstruction(p.Constant)) .ToArray(); - if (injectables.Any(p => p.LoadValueInstruction == null)) + 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 - // inject new parameters - foreach (var entry in injectables) - { - // load value onto stack - cil.InsertBefore(instruction, entry.LoadValueInstruction); - - // add parameter - ParameterDefinition parameter = entry.Parameter; - var newParameter = new ParameterDefinition( - name: parameter.Name, - attributes: parameter.Attributes, - parameterType: module.ImportReference(parameter.ParameterType) - ); - newParameter.Constant = parameter.Constant; - methodRef.Parameters.Add(newParameter); - } + // rewrite method reference + foreach (Instruction loadInstruction in loadInstructions) + cil.InsertBefore(instruction, loadInstruction); + instruction.Operand = module.ImportReference(method); this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); return this.MarkRewritten(); -- cgit From ec4b81819aeaaeba5f5f3edf28e72b0e6f0430c4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 Aug 2020 22:59:08 -0400 Subject: use inheritdoc in rewriters --- .../Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 7 +------ .../ModLoading/Rewriters/FieldToPropertyRewriter.cs | 7 +------ .../ModLoading/Rewriters/Harmony1AssemblyRewriter.cs | 13 ++----------- .../Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 7 +------ .../Rewriters/MethodWithMissingOptionalParameterRewriter.cs | 7 +------ .../ModLoading/Rewriters/StaticFieldToConstantRewriter.cs | 7 +------ .../Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs | 6 +----- 7 files changed, 8 insertions(+), 46 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 9166ab86..2b8fbe06 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -49,12 +49,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters { } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // get field reference diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs index 514691cf..de123cc2 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs @@ -28,12 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // get field ref diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index b30d686e..4b3675bc 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -25,11 +25,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public Harmony1AssemblyRewriter() : base(defaultPhrase: "Harmony 1.x") { } - /// Rewrite a type reference if needed. - /// The assembly module containing the instruction. - /// The type definition to handle. - /// Replaces the type reference with a new one. - /// Returns whether the type was changed. + /// public override bool Handle(ModuleDefinition module, TypeReference type, Action replaceWith) { // rewrite Harmony 1.x type to Harmony 2.0 type @@ -45,12 +41,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // rewrite Harmony 1.x methods to Harmony 2.0 diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index b8e53f40..dc04478f 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -40,12 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public MethodParentRewriter(Type fromType, Type toType, string nounPhrase = null) : this(fromType.FullName, toType, nounPhrase) { } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // get method ref diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs index 87ccf941..e6e7a847 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -28,12 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // get method ref diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs index 6ef18b26..6ea59d1c 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs @@ -37,12 +37,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters this.Value = value; } - /// Rewrite a CIL instruction reference if needed. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The CIL instruction to handle. - /// Replaces the CIL instruction with a new one. - /// Returns whether the instruction was changed. + /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { // get field reference diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index c2120444..ad5cb96f 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -35,11 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters this.ShouldIgnore = shouldIgnore; } - /// Rewrite a type reference if needed. - /// The assembly module containing the instruction. - /// The type definition to handle. - /// Replaces the type reference with a new one. - /// Returns whether the type was changed. + /// public override bool Handle(ModuleDefinition module, TypeReference type, Action replaceWith) { // check type reference -- cgit From fd6835555ccdfecc09d87aab29b9c5cec3820720 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 21:55:04 -0400 Subject: fix InvalidProgramException when replacing CIL instructions in some cases --- .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 4 +-- .../Rewriters/FieldToPropertyRewriter.cs | 5 ++-- .../Rewriters/StaticFieldToConstantRewriter.cs | 30 ++++++++++------------ 3 files changed, 18 insertions(+), 21 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 2b8fbe06..c251a30c 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -58,8 +58,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // replace with new field - FieldReference newRef = module.ImportReference(this.ToField); - replaceWith(cil.Create(instruction.OpCode, newRef)); + instruction.Operand = module.ImportReference(this.ToField); + return this.MarkRewritten(); } } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs index de123cc2..0a99bde0 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs @@ -49,8 +49,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // rewrite field to property - MethodReference propertyRef = module.ImportReference(method); - replaceWith(cil.Create(OpCodes.Call, propertyRef)); + instruction.OpCode = OpCodes.Call; + instruction.Operand = module.ImportReference(method); + this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)"); return this.MarkRewritten(); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs index 6ea59d1c..2f1122b4 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs @@ -46,24 +46,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // rewrite to constant - replaceWith(this.CreateConstantInstruction(cil, this.Value)); - return this.MarkRewritten(); - } - - - /********* - ** Private methods - *********/ - /// Create a CIL constant value instruction. - /// The CIL processor. - /// The constant value to set. - private Instruction CreateConstantInstruction(ILProcessor cil, object value) - { if (typeof(TValue) == typeof(int)) - return cil.Create(OpCodes.Ldc_I4, (int)value); - if (typeof(TValue) == typeof(string)) - return cil.Create(OpCodes.Ldstr, (string)value); - throw new NotSupportedException($"Rewriting to constant values of type {typeof(TValue)} isn't currently supported."); + { + 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(); } } } -- cgit From 16161a214fddea19b908d7ca3dc0d39f81c259c8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 21:55:08 -0400 Subject: remove now-unused instruction replace callback --- src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 2 +- src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs | 3 +-- src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 2 +- .../ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs | 3 +-- .../Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index c251a30c..0b679e9d 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs index 0a99bde0..aaf04b79 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -29,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field ref FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index dc04478f..9933e2ca 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -41,7 +41,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters : this(fromType.FullName, toType, nounPhrase) { } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs index e6e7a847..89c8ede7 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -29,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs index 2f1122b4..f34d4943 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); -- cgit From d3c5fe0764806684cc71508abf009473b9d7bc0a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 22:14:25 -0400 Subject: rename new heuristic rewriters for clarity --- .../Rewriters/FieldToPropertyRewriter.cs | 69 ----------- .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 69 +++++++++++ .../Rewriters/HeuristicMethodRewriter.cs | 127 +++++++++++++++++++++ .../MethodWithMissingOptionalParameterRewriter.cs | 127 --------------------- 4 files changed, 196 insertions(+), 196 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs deleted file mode 100644 index aaf04b79..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Framework; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites references to fields which no longer exist, but which have an equivalent property with the exact same name. - internal class FieldToPropertyRewriter : BaseInstructionHandler - { - /********* - ** Fields - *********/ - /// The assembly names to which to rewrite broken references. - private readonly HashSet RewriteReferencesToAssemblies; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The assembly names to which to rewrite broken references. - public FieldToPropertyRewriter(string[] rewriteReferencesToAssemblies) - : base(defaultPhrase: "field changed to property") // ignored since we specify phrases - { - this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); - } - - /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) - { - // get field ref - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - 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; - - // 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(); - } - - - /********* - ** Private methods - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) - { - return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs new file mode 100644 index 00000000..5a088ed8 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Rewriters +{ + /// Automatically fix references to fields that have been replaced by a property. + internal class HeuristicFieldRewriter : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly names to which to rewrite broken references. + public HeuristicFieldRewriter(string[] rewriteReferencesToAssemblies) + : base(defaultPhrase: "field changed to property") // ignored since we specify phrases + { + this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); + } + + /// + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) + { + // get field ref + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + 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; + + // 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(); + } + + + /********* + ** Private methods + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs new file mode 100644 index 00000000..21b42e12 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Rewriters +{ + /// Automatically fix references to methods that had extra optional parameters added. + internal class HeuristicMethodRewriter : BaseInstructionHandler + { + /********* + ** Fields + *********/ + /// The assembly names to which to rewrite broken references. + private readonly HashSet RewriteReferencesToAssemblies; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly names to which to rewrite broken references. + public HeuristicMethodRewriter(string[] rewriteReferencesToAssemblies) + : base(defaultPhrase: "methods with missing parameters") // ignored since we specify phrases + { + this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); + } + + /// + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) + { + // get method ref + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) + return false; + + // skip if not broken + if (methodRef.Resolve() != null) + return false; + + // get type + var type = methodRef.DeclaringType.Resolve(); + if (type == null) + return false; + + // get method definition + MethodDefinition method = null; + foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + { + // reference matches initial parameters of definition + if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) + continue; + + // all remaining parameters in definition are optional + if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) + continue; + + method = match; + break; + } + if (method == null) + return false; + + // get instructions to inject parameter values + var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) + .Select(p => this.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 + + // rewrite method reference + foreach (Instruction loadInstruction in loadInstructions) + cil.InsertBefore(instruction, loadInstruction); + instruction.Operand = module.ImportReference(method); + + this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); + return this.MarkRewritten(); + } + + + /********* + ** Private methods + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + + /// Get whether every parameter in the method reference matches the exact order and type of the parameters in the method definition. This ignores extra parameters in the definition. + /// The method reference whose parameters to check. + /// The method definition whose parameters to check against. + private bool InitialParametersMatch(MethodReference methodRef, MethodDefinition method) + { + if (methodRef.Parameters.Count > method.Parameters.Count) + return false; + + for (int i = 0; i < methodRef.Parameters.Count; i++) + { + if (!RewriteHelper.IsSameType(methodRef.Parameters[i].ParameterType, method.Parameters[i].ParameterType)) + return false; + } + + return true; + } + + /// Get the CIL instruction to load a value onto the stack. + /// The constant value to inject. + /// Returns the instruction, or null if the value type isn't supported. + 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/MethodWithMissingOptionalParameterRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs deleted file mode 100644 index 89c8ede7..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Framework; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites references to methods which only broke because the definition has new optional parameters. - internal class MethodWithMissingOptionalParameterRewriter : BaseInstructionHandler - { - /********* - ** Fields - *********/ - /// The assembly names to which to rewrite broken references. - private readonly HashSet RewriteReferencesToAssemblies; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The assembly names to which to rewrite broken references. - public MethodWithMissingOptionalParameterRewriter(string[] rewriteReferencesToAssemblies) - : base(defaultPhrase: "methods with missing parameters") // ignored since we specify phrases - { - this.RewriteReferencesToAssemblies = new HashSet(rewriteReferencesToAssemblies); - } - - /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) - { - // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); - if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) - return false; - - // skip if not broken - if (methodRef.Resolve() != null) - return false; - - // get type - var type = methodRef.DeclaringType.Resolve(); - if (type == null) - return false; - - // get method definition - MethodDefinition method = null; - foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) - { - // reference matches initial parameters of definition - if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) - continue; - - // all remaining parameters in definition are optional - if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) - continue; - - method = match; - break; - } - if (method == null) - return false; - - // get instructions to inject parameter values - var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) - .Select(p => this.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 - - // rewrite method reference - foreach (Instruction loadInstruction in loadInstructions) - cil.InsertBefore(instruction, loadInstruction); - instruction.Operand = module.ImportReference(method); - - this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); - return this.MarkRewritten(); - } - - - /********* - ** Private methods - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) - { - return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); - } - - /// Get whether every parameter in the method reference matches the exact order and type of the parameters in the method definition. This ignores extra parameters in the definition. - /// The method reference whose parameters to check. - /// The method definition whose parameters to check against. - private bool InitialParametersMatch(MethodReference methodRef, MethodDefinition method) - { - if (methodRef.Parameters.Count > method.Parameters.Count) - return false; - - for (int i = 0; i < methodRef.Parameters.Count; i++) - { - if (!RewriteHelper.IsSameType(methodRef.Parameters[i].ParameterType, method.Parameters[i].ParameterType)) - return false; - } - - return true; - } - - /// Get the CIL instruction to load a value onto the stack. - /// The constant value to inject. - /// Returns the instruction, or null if the value type isn't supported. - 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 - }; - } - } -} -- cgit From 0bf692addc3e309a8448de9ffb2a41cb701cfddf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 26 Aug 2020 23:11:41 -0400 Subject: add heuristic rewrite for field => const changes --- .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 65 +++++++++++++++++----- .../Rewriters/HeuristicMethodRewriter.cs | 20 +------ .../Rewriters/StaticFieldToConstantRewriter.cs | 65 ---------------------- 3 files changed, 52 insertions(+), 98 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') 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 { - /// Automatically fix references to fields that have been replaced by a property. + /// Automatically fix references to fields that have been replaced by a property or const field. 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 + *********/ + /// Whether references to the given type should be validated. + /// The type reference. + private bool ShouldValidate(TypeReference type) + { + return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); + } + + /// Try rewriting the field into a matching property. + /// The assembly module containing the instruction. + /// The CIL instruction to rewrite. + /// The field reference. + /// The type on which the field was defined. + /// Whether the field is being read; else it's being written to. + 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 - *********/ - /// Whether references to the given type should be validated. - /// The type reference. - private bool ShouldValidate(TypeReference type) + /// Try rewriting the field into a matching const field. + /// The CIL instruction to rewrite. + /// The field definition. + 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; +