merge field rewriters to reduce rewrite iterations

This commit is contained in:
Jesse Plamondon-Willard 2022-01-16 17:08:08 -05:00
parent 1ea8d75235
commit 230f119205
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
4 changed files with 36 additions and 54 deletions

View File

@ -8,6 +8,7 @@
* Added automatic save recovery when the custom farm type isn't available anymore.
* Added the new game build number to the SMAPI console + log.
* The installer now detects Xbox app game folders.
* Reduced mod loading time a bit.
* Fixed extra newlines shown in the console in non-developer mode.
* Fixed macOS launch issue when using some terminals (thanks to bruce2409!).
* Fixed Linux/macOS terminal ignoring backspace in Stardew Valley 1.5.5+.

View File

@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
** Fields
*********/
/// <summary>The comparer which heuristically compares type definitions.</summary>
private static readonly TypeReferenceComparer TypeDefinitionComparer = new TypeReferenceComparer();
private static readonly TypeReferenceComparer TypeDefinitionComparer = new();
/*********
@ -28,28 +28,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
: null;
}
/// <summary>Get whether the field is a reference to the expected type and field.</summary>
/// <param name="instruction">The IL instruction.</param>
/// <param name="fullTypeName">The full type name containing the expected field.</param>
/// <param name="fieldName">The name of the expected field.</param>
public static bool IsFieldReferenceTo(Instruction instruction, string fullTypeName, string fieldName)
{
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
return RewriteHelper.IsFieldReferenceTo(fieldRef, fullTypeName, fieldName);
}
/// <summary>Get whether the field is a reference to the expected type and field.</summary>
/// <param name="fieldRef">The field reference to check.</param>
/// <param name="fullTypeName">The full type name containing the expected field.</param>
/// <param name="fieldName">The name of the expected field.</param>
public static bool IsFieldReferenceTo(FieldReference fieldRef, string fullTypeName, string fieldName)
{
return
fieldRef != null
&& fieldRef.DeclaringType.FullName == fullTypeName
&& fieldRef.Name == fieldName;
}
/// <summary>Get the method reference from an instruction if it matches.</summary>
/// <param name="instruction">The IL instruction.</param>
public static MethodReference AsMethodReference(Instruction instruction)

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
@ -12,54 +13,55 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/*********
** Fields
*********/
/// <summary>The type containing the field to which references should be rewritten.</summary>
private readonly Type Type;
/// <summary>The field name to which references should be rewritten.</summary>
private readonly string FromFieldName;
/// <summary>The new field to reference.</summary>
private readonly FieldInfo ToField;
/// <summary>The new fields to reference indexed by the old field/type names.</summary>
private readonly Dictionary<string, Dictionary<string, FieldInfo>> FieldMaps = new();
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
public FieldReplaceRewriter()
: base(defaultPhrase: "field replacement") { } // will be overridden when a field is replaced
/// <summary>Add a field to replace.</summary>
/// <param name="fromType">The type whose field to rewrite.</param>
/// <param name="fromFieldName">The field name to rewrite.</param>
/// <param name="toType">The new type which will have the field.</param>
/// <param name="toFieldName">The new field name to reference.</param>
public FieldReplaceRewriter(Type fromType, string fromFieldName, Type toType, string toFieldName)
: base(defaultPhrase: $"{fromType.FullName}.{fromFieldName} field")
public FieldReplaceRewriter AddField(Type fromType, string fromFieldName, Type toType, string toFieldName)
{
this.Type = fromType;
this.FromFieldName = fromFieldName;
this.ToField = toType.GetField(toFieldName);
if (this.ToField == null)
throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field.");
}
// get full type name
string fromTypeName = fromType?.FullName;
if (fromTypeName == null)
throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}.");
/// <summary>Construct an instance.</summary>
/// <param name="type">The type whose field to rewrite.</param>
/// <param name="fromFieldName">The field name to rewrite.</param>
/// <param name="toFieldName">The new field name to reference.</param>
public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName)
: this(type, fromFieldName, type, toFieldName)
{
// 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;
}
/// <inheritdoc />
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))
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
instruction.Operand = module.ImportReference(this.ToField);
this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} field");
instruction.Operand = module.ImportReference(toField);
return this.MarkRewritten();
}
}

View File

@ -37,9 +37,10 @@ namespace StardewModdingAPI.Metadata
if (rewriteMods)
{
// rewrite for Stardew Valley 1.5
yield return new FieldReplaceRewriter(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture));
yield return new FieldReplaceRewriter(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
yield return new FieldReplaceRewriter(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
yield return new FieldReplaceRewriter()
.AddField(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture))
.AddField(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps))
.AddField(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
// heuristic rewrites
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);