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 or const field. 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 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 = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; // rewrite field to property instruction.OpCode = OpCodes.Call; instruction.Operand = module.ImportReference(method); this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)"); return this.MarkRewritten(); } /// Try rewriting the field into a matching const field. /// The CIL instruction to rewrite. /// The field definition. private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field) { // must have been a static field read, and the new field must be const if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true) return false; // get opcode for value type Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); if (loadInstruction == null) return false; // rewrite to constant instruction.OpCode = loadInstruction.OpCode; instruction.Operand = loadInstruction.Operand; this.Phrases.Add($"{field.DeclaringType.Name}.{field.Name} (field => const)"); return this.MarkRewritten(); } } }