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 automatic save recovery when the custom farm type isn't available anymore.
* Added the new game build number to the SMAPI console + log. * Added the new game build number to the SMAPI console + log.
* The installer now detects Xbox app game folders. * 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 extra newlines shown in the console in non-developer mode.
* Fixed macOS launch issue when using some terminals (thanks to bruce2409!). * Fixed macOS launch issue when using some terminals (thanks to bruce2409!).
* Fixed Linux/macOS terminal ignoring backspace in Stardew Valley 1.5.5+. * Fixed Linux/macOS terminal ignoring backspace in Stardew Valley 1.5.5+.

View File

@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
** Fields ** Fields
*********/ *********/
/// <summary>The comparer which heuristically compares type definitions.</summary> /// <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; : 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> /// <summary>Get the method reference from an instruction if it matches.</summary>
/// <param name="instruction">The IL instruction.</param> /// <param name="instruction">The IL instruction.</param>
public static MethodReference AsMethodReference(Instruction instruction) public static MethodReference AsMethodReference(Instruction instruction)

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
@ -12,54 +13,55 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/********* /*********
** Fields ** Fields
*********/ *********/
/// <summary>The type containing the field to which references should be rewritten.</summary> /// <summary>The new fields to reference indexed by the old field/type names.</summary>
private readonly Type Type; private readonly Dictionary<string, Dictionary<string, FieldInfo>> FieldMaps = new();
/// <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;
/********* /*********
** Public methods ** Public methods
*********/ *********/
/// <summary>Construct an instance.</summary> /// <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="fromType">The type whose field to rewrite.</param>
/// <param name="fromFieldName">The field name 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="toType">The new type which will have the field.</param>
/// <param name="toFieldName">The new field name to reference.</param> /// <param name="toFieldName">The new field name to reference.</param>
public FieldReplaceRewriter(Type fromType, string fromFieldName, Type toType, string toFieldName) public FieldReplaceRewriter AddField(Type fromType, string fromFieldName, Type toType, string toFieldName)
: base(defaultPhrase: $"{fromType.FullName}.{fromFieldName} field")
{ {
this.Type = fromType; // get full type name
this.FromFieldName = fromFieldName; string fromTypeName = fromType?.FullName;
this.ToField = toType.GetField(toFieldName); if (fromTypeName == null)
if (this.ToField == null) throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}.");
throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field.");
}
/// <summary>Construct an instance.</summary> // get target field
/// <param name="type">The type whose field to rewrite.</param> FieldInfo toField = toType.GetField(toFieldName);
/// <param name="fromFieldName">The field name to rewrite.</param> if (toField == null)
/// <param name="toFieldName">The new field name to reference.</param> throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field.");
public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName)
: this(type, fromFieldName, type, toFieldName) // add mapping
{ if (!this.FieldMaps.TryGetValue(fromTypeName, out var fieldMap))
this.FieldMaps[fromTypeName] = fieldMap = new();
fieldMap[fromFieldName] = toField;
return this;
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
{ {
// get field reference
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); 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; return false;
// replace with new field // 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(); return this.MarkRewritten();
} }
} }

View File

@ -37,9 +37,10 @@ namespace StardewModdingAPI.Metadata
if (rewriteMods) if (rewriteMods)
{ {
// rewrite for Stardew Valley 1.5 // rewrite for Stardew Valley 1.5
yield return new FieldReplaceRewriter(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture)); yield return new FieldReplaceRewriter()
yield return new FieldReplaceRewriter(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)); .AddField(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture))
yield return new FieldReplaceRewriter(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)); .AddField(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps))
.AddField(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps));
// heuristic rewrites // heuristic rewrites
yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies);
@ -87,7 +88,7 @@ namespace StardewModdingAPI.Metadata
typeof(System.IO.DirectoryInfo).FullName, typeof(System.IO.DirectoryInfo).FullName,
typeof(System.IO.DriveInfo).FullName, typeof(System.IO.DriveInfo).FullName,
typeof(System.IO.FileSystemWatcher).FullName typeof(System.IO.FileSystemWatcher).FullName
}, },
InstructionHandleResult.DetectedFilesystemAccess InstructionHandleResult.DetectedFilesystemAccess
); );