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 --- docs/release-notes.md | 4 ++++ .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3f7d5198..efab21d5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -25,6 +25,10 @@ * 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. + * `FieldToPropertyRewriter` now auto-rewrites broken field references into properties if possible, so we no longer need to map fields manually. + * `FieldReplaceRewriter` now supports mapping to a different target type. * Internal refactoring to simplify future game updates. ## 3.6.2 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 --- docs/release-notes.md | 5 +- .../MethodWithMissingOptionalParameterRewriter.cs | 113 +++++++++++++++++++++ src/SMAPI/Metadata/InstructionMetadata.cs | 7 +- 3 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/MethodWithMissingOptionalParameterRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/docs/release-notes.md b/docs/release-notes.md index efab21d5..7e928aed 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,7 +9,7 @@ ## Upcoming release * For players: - * Added heuristic compatibility rewrites for some common cases. That fixes some mods which previously broke on Android or in newer game versions. + * Added heuristic compatibility rewrites, which fix some mods previously incompatible with Android or 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. @@ -27,7 +27,8 @@ * 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. - * `FieldToPropertyRewriter` now auto-rewrites broken field references into properties if possible, so we no longer need to map fields manually. + * 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. 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; + } + } +} diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index fca809f8..972ed91d 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -31,9 +31,6 @@ 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)); @@ -41,6 +38,10 @@ namespace StardewModdingAPI.Metadata // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); + // generic rewrites + yield return new FieldToPropertyRewriter(this.ValidateReferencesToAssemblies); + yield return new MethodWithMissingOptionalParameterRewriter(this.ValidateReferencesToAssemblies); + #if HARMONY_2 // rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update) yield return new Harmony1AssemblyRewriter(); -- 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 --- src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 7 +------ src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs | 7 +------ .../Framework/ModLoading/Finders/MethodFinder.cs | 7 +------ .../Framework/ModLoading/Finders/PropertyFinder.cs | 7 +------ .../ReferenceToMemberWithUnexpectedTypeFinder.cs | 7 +------ .../Finders/ReferenceToMissingMemberFinder.cs | 7 +------ .../ModLoading/Finders/TypeAssemblyFinder.cs | 6 +----- src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs | 6 +----- .../ModLoading/Framework/BaseInstructionHandler.cs | 19 +++++-------------- .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 7 +------ .../ModLoading/Rewriters/FieldToPropertyRewriter.cs | 7 +------ .../ModLoading/Rewriters/Harmony1AssemblyRewriter.cs | 13 ++----------- .../ModLoading/Rewriters/MethodParentRewriter.cs | 7 +------ .../MethodWithMissingOptionalParameterRewriter.cs | 7 +------ .../Rewriters/StaticFieldToConstantRewriter.cs | 7 +------ .../ModLoading/Rewriters/TypeReferenceRewriter.cs | 6 +----- 16 files changed, 21 insertions(+), 106 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index e1476b73..3d23da87 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -36,12 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.Result = result; } - /// 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) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index c157ed9b..b4063078 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -36,12 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.Result = result; } - /// 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) { if (!this.Flags.Contains(this.Result) && RewriteHelper.IsFieldReferenceTo(instruction, this.FullTypeName, this.FieldName)) diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 82c93a7c..187bdefc 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -36,12 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.Result = result; } - /// 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) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index c96d61a2..d14058dc 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -36,12 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.Result = result; } - /// 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) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index a67cfa4f..2402bf48 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -29,12 +29,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.ValidateReferencesToAssemblies = new HashSet(validateReferencesToAssemblies); } - /// 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) { // field reference diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index ebb62948..f97783da 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -29,12 +29,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders this.ValidateReferencesToAssemblies = new HashSet(validateReferencesToAssemblies); } - /// 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) { // field reference diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index a1ade536..24ab2eca 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -35,11 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders 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) { if (type.Scope.Name == this.AssemblyName && this.ShouldIgnore?.Invoke(type) != true) diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index c285414a..bbd081e8 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -35,11 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders 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) { if (type.FullName == this.FullTypeName && this.ShouldIgnore?.Invoke(type) != true) diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index 611b2cf2..ef619761 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -11,35 +11,26 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /********* ** Accessors *********/ - /// A brief noun phrase indicating what the handler matches, used if is empty. + /// public string DefaultPhrase { get; } - /// The rewrite flags raised for the current module. + /// public ISet Flags { get; } = new HashSet(); - /// The brief noun phrases indicating what the handler matched for the current module. + /// public ISet Phrases { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); /********* ** Public methods *********/ - /// 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 virtual bool Handle(ModuleDefinition module, TypeReference type, Action replaceWith) { 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 virtual bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) { return false; 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/AssemblyLoader.cs | 4 ++-- src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 3 +-- src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs | 3 +-- src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs | 3 +-- src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs | 3 +-- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 3 +-- .../ModLoading/Finders/ReferenceToMissingMemberFinder.cs | 3 +-- .../Framework/ModLoading/Framework/BaseInstructionHandler.cs | 2 +- src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 10 ++-------- src/SMAPI/Framework/ModLoading/IInstructionHandler.cs | 3 +-- .../Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 2 +- .../Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs | 3 +-- .../Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 2 +- .../Rewriters/MethodWithMissingOptionalParameterRewriter.cs | 3 +-- .../ModLoading/Rewriters/StaticFieldToConstantRewriter.cs | 2 +- 15 files changed, 17 insertions(+), 32 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index c8c1ca08..cfe4c747 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -318,11 +318,11 @@ namespace StardewModdingAPI.Framework.ModLoading rewritten |= handler.Handle(module, type, replaceWith); return rewritten; }, - rewriteInstruction: (ref Instruction instruction, ILProcessor cil, Action replaceWith) => + rewriteInstruction: (ref Instruction instruction, ILProcessor cil) => { bool rewritten = false; foreach (IInstructionHandler handler in handlers) - rewritten |= handler.Handle(module, cil, instruction, replaceWith); + rewritten |= handler.Handle(module, cil, instruction); return rewritten; } ); diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 3d23da87..01ed153b 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,4 +1,3 @@ -using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -37,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) this.MarkFlag(this.Result); diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index b4063078..2c062243 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,4 +1,3 @@ -using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -37,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { if (!this.Flags.Contains(this.Result) && RewriteHelper.IsFieldReferenceTo(instruction, this.FullTypeName, this.FieldName)) this.MarkFlag(this.Result); diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 187bdefc..d2340f01 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,4 +1,3 @@ -using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -37,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) this.MarkFlag(this.Result); diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index d14058dc..99344848 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,4 +1,3 @@ -using System; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -37,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction)) this.MarkFlag(this.Result); diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 2402bf48..b01a3240 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -30,7 +29,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index f97783da..9afd1de0 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -30,7 +29,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index ef619761..624113b3 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -31,7 +31,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework } /// - public virtual bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith) + public virtual bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { return false; } diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index fb651465..ea29550a 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -22,9 +22,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Rewrite a CIL instruction in the assembly code. /// The current CIL instruction. /// The CIL instruction processor. - /// Replaces the CIL instruction with the given instruction. /// Returns whether the instruction was changed. - public delegate bool RewriteInstructionDelegate(ref Instruction instruction, ILProcessor cil, Action replaceWith); + public delegate bool RewriteInstructionDelegate(ref Instruction instruction, ILProcessor cil); /********* @@ -161,12 +160,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework // instruction itself // (should be done after the above type rewrites to ensure valid types) - rewritten |= this.RewriteInstructionImpl(ref instruction, cil, newInstruction => - { - rewritten = true; - cil.Replace(instruction, newInstruction); - instruction = newInstruction; - }); + rewritten |= this.RewriteInstructionImpl(ref instruction, cil); return rewritten; } diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs index e6de6785..17c9ba68 100644 --- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs @@ -35,8 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// 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. - bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action replaceWith); + bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction); } } 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 --------------------- src/SMAPI/Metadata/InstructionMetadata.cs | 6 +- 5 files changed, 199 insertions(+), 199 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 - }; - } - } -} diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 972ed91d..86e16e1e 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -38,9 +38,9 @@ namespace StardewModdingAPI.Metadata // rewrite for Stardew Valley 1.3 yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize); - // generic rewrites - yield return new FieldToPropertyRewriter(this.ValidateReferencesToAssemblies); - yield return new MethodWithMissingOptionalParameterRewriter(this.ValidateReferencesToAssemblies); + // heuristic rewrites + yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); + yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies); #if HARMONY_2 // rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update) -- 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 --- docs/release-notes.md | 12 ++-- .../ModLoading/Framework/RewriteHelper.cs | 18 ++++++ .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 65 +++++++++++++++++----- .../Rewriters/HeuristicMethodRewriter.cs | 20 +------ .../Rewriters/StaticFieldToConstantRewriter.cs | 65 ---------------------- src/SMAPI/Metadata/InstructionMetadata.cs | 3 - 6 files changed, 76 insertions(+), 107 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs (limited to 'src/SMAPI/Framework/ModLoading/Rewriters') 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; } + /// 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. + 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 + }; + } + /// Get whether a type matches a type reference. /// The defined type. /// The type reference. 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; + 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; } - - /// 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/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 -{ - /// Rewrites static field references into constant values. - /// The constant value type. - internal class StaticFieldToConstantRewriter : 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 constant value to replace with. - private readonly TValue Value; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type whose field to which references should be rewritten. - /// The field name to rewrite. - /// The constant value to replace with. - public StaticFieldToConstantRewriter(Type type, string fieldName, TValue value) - : base(defaultPhrase: $"{type.FullName}.{fieldName} field") - { - this.Type = type; - this.FromFieldName = fieldName; - this.Value = value; - } - - /// - 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(typeof(Game1), "tileSize", Game1.tileSize); - // heuristic rewrites yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies); -- cgit