using System; using System.Collections.Generic; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters { /// Rewrites references to one field with another. internal class FieldReplaceRewriter : BaseInstructionHandler { /********* ** Fields *********/ /// The new fields to reference indexed by the old field/type names. private readonly Dictionary> FieldMaps = new(); /********* ** Public methods *********/ /// Construct an instance. public FieldReplaceRewriter() : base(defaultPhrase: "field replacement") { } // will be overridden when a field is replaced /// Add a field to replace. /// 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 AddField(Type fromType, string fromFieldName, Type toType, string toFieldName) { // validate parameters if (fromType == null) throw new InvalidOperationException("Can't replace a field on a null source type."); if (toType == null) throw new InvalidOperationException("Can't replace a field on a null target type."); // get full type name string? fromTypeName = fromType.FullName; if (fromTypeName == null) throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}."); // get target field FieldInfo? toField = toType.GetField(toFieldName); if (toField == null) throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field."); // add mapping if (!this.FieldMaps.TryGetValue(fromTypeName, out var fieldMap)) this.FieldMaps[fromTypeName] = fieldMap = new(); fieldMap[fromFieldName] = toField; return this; } /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); string? declaringType = fieldRef?.DeclaringType?.FullName; // get mapped field if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef!.Name, out FieldInfo? toField)) return false; // replace with new field this.Phrases.Add($"{fieldRef.DeclaringType!.Name}.{fieldRef.Name} field"); instruction.Operand = module.ImportReference(toField); return this.MarkRewritten(); } } }