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();
}
}
}