From 4eb68e96ed2b986cf2db621b24f4ebbdd0cf83f1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 4 May 2020 17:41:45 -0400 Subject: fix asset propagation for Gil's portraits --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 0a14086b..fa6541cb 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -816,9 +816,18 @@ namespace StardewModdingAPI.Metadata where key != null && lookup.Contains(key) select new { Npc = npc, Key = key } ) - .ToArray(); - if (!characters.Any()) - return; + .ToList(); + + // special case: Gil is a private NPC field on the AdventureGuild class (only used for the portrait) + { + string gilKey = this.NormalizeAssetNameIgnoringEmpty("Portraits/Gil"); + if (lookup.Contains(gilKey)) + { + GameLocation adventureGuild = Game1.getLocationFromName("AdventureGuild"); + if (adventureGuild != null) + characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), Key = gilKey }); + } + } // update portrait foreach (var target in characters) -- cgit From 9728fe3f347328323ff79c6c93df2ab390f6070e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 4 May 2020 17:53:48 -0400 Subject: add Multiplayer.PeerConnected event --- src/SMAPI/Events/IMultiplayerEvents.cs | 5 ++++- src/SMAPI/Events/PeerConnectedEventArgs.cs | 25 ++++++++++++++++++++++ src/SMAPI/Framework/Events/EventManager.cs | 6 +++++- src/SMAPI/Framework/Events/ModMultiplayerEvents.cs | 9 +++++++- src/SMAPI/Framework/SMultiplayer.cs | 4 ++++ 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/SMAPI/Events/PeerConnectedEventArgs.cs (limited to 'src') diff --git a/src/SMAPI/Events/IMultiplayerEvents.cs b/src/SMAPI/Events/IMultiplayerEvents.cs index 4a31f48e..af9b5f17 100644 --- a/src/SMAPI/Events/IMultiplayerEvents.cs +++ b/src/SMAPI/Events/IMultiplayerEvents.cs @@ -5,9 +5,12 @@ namespace StardewModdingAPI.Events /// Events raised for multiplayer messages and connections. public interface IMultiplayerEvents { - /// Raised after the mod context for a peer is received. This happens before the game approves the connection, so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. + /// Raised after the mod context for a peer is received. This happens before the game approves the connection (), so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. event EventHandler PeerContextReceived; + /// Raised after a peer connection is approved by the game. + event EventHandler PeerConnected; + /// Raised after a mod message is received over the network. event EventHandler ModMessageReceived; diff --git a/src/SMAPI/Events/PeerConnectedEventArgs.cs b/src/SMAPI/Events/PeerConnectedEventArgs.cs new file mode 100644 index 00000000..bfaa2bd3 --- /dev/null +++ b/src/SMAPI/Events/PeerConnectedEventArgs.cs @@ -0,0 +1,25 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class PeerConnectedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The peer whose metadata was received. + public IMultiplayerPeer Peer { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The peer whose metadata was received. + internal PeerConnectedEventArgs(IMultiplayerPeer peer) + { + this.Peer = peer; + } + } +} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index a9dfda97..cc718d2c 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -109,9 +109,12 @@ namespace StardewModdingAPI.Framework.Events /**** ** Multiplayer ****/ - /// Raised after the mod context for a peer is received. This happens before the game approves the connection, so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. + /// Raised after the mod context for a peer is received. This happens before the game approves the connection (), so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. public readonly ManagedEvent PeerContextReceived; + /// Raised after a peer connection is approved by the game. + public readonly ManagedEvent PeerConnected; + /// Raised after a mod message is received over the network. public readonly ManagedEvent ModMessageReceived; @@ -218,6 +221,7 @@ namespace StardewModdingAPI.Framework.Events this.MouseWheelScrolled = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.MouseWheelScrolled)); this.PeerContextReceived = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerContextReceived)); + this.PeerConnected = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerConnected)); this.ModMessageReceived = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.ModMessageReceived)); this.PeerDisconnected = ManageEventOf(nameof(IModEvents.Multiplayer), nameof(IMultiplayerEvents.PeerDisconnected)); diff --git a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs index 152c4e0c..9f76511e 100644 --- a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs @@ -9,13 +9,20 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ - /// Raised after the mod context for a peer is received. This happens before the game approves the connection, so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. + /// Raised after the mod context for a peer is received. This happens before the game approves the connection (), so the player doesn't yet exist in the game. This is the earliest point where messages can be sent to the peer via SMAPI. public event EventHandler PeerContextReceived { add => this.EventManager.PeerContextReceived.Add(value); remove => this.EventManager.PeerContextReceived.Remove(value); } + /// Raised after a peer connection is approved by the game. + public event EventHandler PeerConnected + { + add => this.EventManager.PeerConnected.Add(value); + remove => this.EventManager.PeerConnected.Remove(value); + } + /// Raised after a mod message is received over the network. public event EventHandler ModMessageReceived { diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 821c343f..8c444e45 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -231,7 +231,11 @@ namespace StardewModdingAPI.Framework this.AddPeer(peer, canBeHost: false); } + // let game handle connection resume(); + + // raise event + this.EventManager.PeerConnected.Raise(new PeerConnectedEventArgs(this.Peers[message.FarmerID])); break; // handle mod message -- cgit From e08979acd301792435661d2c0008469a800f8dbb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 May 2020 20:49:05 -0400 Subject: migrate to Harmony 2.0 (#711) --- src/SMAPI/Framework/Patching/GamePatcher.cs | 4 ++-- src/SMAPI/Framework/Patching/IHarmonyPatch.cs | 4 ++-- src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs | 3 +-- src/SMAPI/Patches/DialogueErrorPatch.cs | 4 ++-- src/SMAPI/Patches/EventErrorPatch.cs | 4 ++-- src/SMAPI/Patches/LoadContextPatch.cs | 4 ++-- src/SMAPI/Patches/LoadErrorPatch.cs | 4 ++-- src/SMAPI/Patches/ObjectErrorPatch.cs | 4 ++-- src/SMAPI/Patches/ScheduleErrorPatch.cs | 4 ++-- src/SMAPI/SMAPI.csproj | 2 +- 10 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs index f82159d0..cdb54453 100644 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ b/src/SMAPI/Framework/Patching/GamePatcher.cs @@ -1,5 +1,5 @@ using System; -using Harmony; +using HarmonyLib; namespace StardewModdingAPI.Framework.Patching { @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Framework.Patching /// The patches to apply. public void Apply(params IHarmonyPatch[] patches) { - HarmonyInstance harmony = HarmonyInstance.Create("io.smapi"); + Harmony harmony = new Harmony("io.smapi"); foreach (IHarmonyPatch patch in patches) { try diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs index cb42f40e..7d5eb3d4 100644 --- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs +++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs @@ -1,4 +1,4 @@ -using Harmony; +using HarmonyLib; namespace StardewModdingAPI.Framework.Patching { @@ -10,6 +10,6 @@ namespace StardewModdingAPI.Framework.Patching /// Apply the Harmony patch. /// The Harmony instance. - void Apply(HarmonyInstance harmony); + void Apply(Harmony harmony); } } diff --git a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs index 3cf668ee..42825999 100644 --- a/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs +++ b/src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Harmony; namespace StardewModdingAPI.Framework.PerformanceMonitoring { @@ -57,7 +56,7 @@ namespace StardewModdingAPI.Framework.PerformanceMonitoring // add entry if (this.Entries.Count > this.MaxEntries) this.Entries.Pop(); - this.Entries.Add(entry); + this.Entries.Push(entry); // update metrics if (this.PeakPerformanceCounterEntry == null || entry.ElapsedMilliseconds > this.PeakPerformanceCounterEntry.Value.ElapsedMilliseconds) diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index 1e49826d..80029540 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; using StardewValley; @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { harmony.Patch( original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }), diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs index 504d1d2e..a2b94e8b 100644 --- a/src/SMAPI/Patches/EventErrorPatch.cs +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { harmony.Patch( original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"), diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 0cc8c8eb..9c707676 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { // detect CreatedBasicInfo harmony.Patch( diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs index 77415ff2..f8ad6693 100644 --- a/src/SMAPI/Patches/LoadErrorPatch.cs +++ b/src/SMAPI/Patches/LoadErrorPatch.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -49,7 +49,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { harmony.Patch( original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index d3b8800a..b9655043 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; using StardewValley.Menus; @@ -27,7 +27,7 @@ namespace StardewModdingAPI.Patches *********/ /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { // object.getDescription harmony.Patch( diff --git a/src/SMAPI/Patches/ScheduleErrorPatch.cs b/src/SMAPI/Patches/ScheduleErrorPatch.cs index 799fcb40..386230a6 100644 --- a/src/SMAPI/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI/Patches/ScheduleErrorPatch.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Harmony; +using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -39,7 +39,7 @@ namespace StardewModdingAPI.Patches /// Apply the Harmony patch. /// The Harmony instance. - public void Apply(HarmonyInstance harmony) + public void Apply(Harmony harmony) { harmony.Patch( original: AccessTools.Method(typeof(NPC), "parseMasterSchedule"), diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 5f41387b..1755b9e7 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -16,7 +16,7 @@ - + -- cgit From 2d37fe6819dd15a6e995ea55d625179106c22cd7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 May 2020 20:54:25 -0400 Subject: rename files for upcoming change (#711) --- .../Framework/ModLoading/Finders/TypeFinder.cs | 139 ------------------- .../ModLoading/Framework/BaseTypeFinder.cs | 139 +++++++++++++++++++ .../Framework/BaseTypeReferenceRewriter.cs | 151 ++++++++++++++++++++ .../ModLoading/Rewriters/TypeReferenceRewriter.cs | 152 --------------------- src/SMAPI/Metadata/InstructionMetadata.cs | 1 + 5 files changed, 291 insertions(+), 291 deletions(-) delete mode 100644 src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs create mode 100644 src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs create mode 100644 src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs delete mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs deleted file mode 100644 index 701b15f2..00000000 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; - -namespace StardewModdingAPI.Framework.ModLoading.Finders -{ - /// Finds incompatible CIL instructions that reference a given type. - internal class TypeFinder : IInstructionHandler - { - /********* - ** Accessors - *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; - - /// The result to return for matching instructions. - private readonly InstructionHandleResult Result; - - /// A lambda which overrides a matched type. - protected readonly Func ShouldIgnore; - - - /********* - ** Accessors - *********/ - /// A brief noun phrase indicating what the instruction finder matches. - public string NounPhrase { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name to match. - /// The result to return for matching instructions. - /// A lambda which overrides a matched type. - public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) - { - this.FullTypeName = fullTypeName; - this.Result = result; - this.NounPhrase = $"{fullTypeName} type"; - this.ShouldIgnore = shouldIgnore ?? (p => false); - } - - /// Perform the predefined logic for a method if applicable. - /// The assembly module containing the instruction. - /// The method definition containing the instruction. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) - { - return this.IsMatch(method) - ? this.Result - : InstructionHandleResult.None; - } - - /// Perform the predefined logic for an instruction if applicable. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) - { - return this.IsMatch(instruction) - ? this.Result - : InstructionHandleResult.None; - } - - - /********* - ** Protected methods - *********/ - /// Get whether a CIL instruction matches. - /// The method definition. - protected bool IsMatch(MethodDefinition method) - { - if (this.IsMatch(method.ReturnType)) - return true; - - foreach (VariableDefinition variable in method.Body.Variables) - { - if (this.IsMatch(variable.VariableType)) - return true; - } - - return false; - } - - /// Get whether a CIL instruction matches. - /// The IL instruction. - protected bool IsMatch(Instruction instruction) - { - // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (fieldRef != null) - { - return - this.IsMatch(fieldRef.DeclaringType) // field on target class - || this.IsMatch(fieldRef.FieldType); // field value is target class - } - - // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); - if (methodRef != null) - { - return - this.IsMatch(methodRef.DeclaringType) // method on target class - || this.IsMatch(methodRef.ReturnType) // method returns target class - || methodRef.Parameters.Any(p => this.IsMatch(p.ParameterType)); // method parameters - } - - return false; - } - - /// Get whether a type reference matches the expected type. - /// The type to check. - protected bool IsMatch(TypeReference type) - { - // root type - if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type)) - return true; - - // generic arguments - if (type is GenericInstanceType genericType) - { - if (genericType.GenericArguments.Any(this.IsMatch)) - return true; - } - - // generic parameters (e.g. constraints) - if (type.GenericParameters.Any(this.IsMatch)) - return true; - - return false; - } - } -} diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs new file mode 100644 index 00000000..170bbb48 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs @@ -0,0 +1,139 @@ +using System; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.Framework.ModLoading.Framework +{ + /// Finds incompatible CIL instructions that reference a given type. + internal class TypeFinder : IInstructionHandler + { + /********* + ** Accessors + *********/ + /// The full type name for which to find references. + private readonly string FullTypeName; + + /// The result to return for matching instructions. + private readonly InstructionHandleResult Result; + + /// A lambda which overrides a matched type. + protected readonly Func ShouldIgnore; + + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the instruction finder matches. + public string NounPhrase { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to match. + /// The result to return for matching instructions. + /// A lambda which overrides a matched type. + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) + { + this.FullTypeName = fullTypeName; + this.Result = result; + this.NounPhrase = $"{fullTypeName} type"; + this.ShouldIgnore = shouldIgnore ?? (p => false); + } + + /// Perform the predefined logic for a method if applicable. + /// The assembly module containing the instruction. + /// The method definition containing the instruction. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return this.IsMatch(method) + ? this.Result + : InstructionHandleResult.None; + } + + /// Perform the predefined logic for an instruction if applicable. + /// The assembly module containing the instruction. + /// The CIL processor. + /// The instruction to handle. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return this.IsMatch(instruction) + ? this.Result + : InstructionHandleResult.None; + } + + + /********* + ** Protected methods + *********/ + /// Get whether a CIL instruction matches. + /// The method definition. + protected bool IsMatch(MethodDefinition method) + { + if (this.IsMatch(method.ReturnType)) + return true; + + foreach (VariableDefinition variable in method.Body.Variables) + { + if (this.IsMatch(variable.VariableType)) + return true; + } + + return false; + } + + /// Get whether a CIL instruction matches. + /// The IL instruction. + protected bool IsMatch(Instruction instruction) + { + // field reference + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + if (fieldRef != null) + { + return + this.IsMatch(fieldRef.DeclaringType) // field on target class + || this.IsMatch(fieldRef.FieldType); // field value is target class + } + + // method reference + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef != null) + { + return + this.IsMatch(methodRef.DeclaringType) // method on target class + || this.IsMatch(methodRef.ReturnType) // method returns target class + || methodRef.Parameters.Any(p => this.IsMatch(p.ParameterType)); // method parameters + } + + return false; + } + + /// Get whether a type reference matches the expected type. + /// The type to check. + protected bool IsMatch(TypeReference type) + { + // root type + if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type)) + return true; + + // generic arguments + if (type is GenericInstanceType genericType) + { + if (genericType.GenericArguments.Any(this.IsMatch)) + return true; + } + + // generic parameters (e.g. constraints) + if (type.GenericParameters.Any(this.IsMatch)) + return true; + + return false; + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs new file mode 100644 index 00000000..8c2d11c8 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs @@ -0,0 +1,151 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace StardewModdingAPI.Framework.ModLoading.Framework +{ + /// Rewrites all references to a type. + internal class TypeReferenceRewriter : TypeFinder + { + /********* + ** Fields + *********/ + /// The full type name to which to find references. + private readonly string FromTypeName; + + /// The new type to reference. + private readonly Type ToType; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to which to find references. + /// The new type to reference. + /// A lambda which overrides a matched type. + public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldIgnore = null) + : base(fromTypeFullName, InstructionHandleResult.None, shouldIgnore) + { + this.FromTypeName = fromTypeFullName; + this.ToType = toType; + } + + /// Perform the predefined logic for a method if applicable. + /// The assembly module containing the instruction. + /// The method definition containing the instruction. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + public override InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + bool rewritten = false; + + // return type + if (this.IsMatch(method.ReturnType)) + { + this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType); + rewritten = true; + } + + // parameters + foreach (ParameterDefinition parameter in method.Parameters) + { + if (this.IsMatch(parameter.ParameterType)) + { + this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); + rewritten = true; + } + } + + // generic parameters + for (int i = 0; i < method.GenericParameters.Count; i++) + { + var parameter = method.GenericParameters[i]; + if (this.IsMatch(parameter)) + { + this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType)); + rewritten = true; + } + } + + // local variables + foreach (VariableDefinition variable in method.Body.Variables) + { + if (this.IsMatch(variable.VariableType)) + { + this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType); + rewritten = true; + } + } + + return rewritten + ? InstructionHandleResult.Rewritten + : InstructionHandleResult.None; + } + + /// Perform the predefined logic for an instruction if applicable. + /// The assembly module containing the instruction. + /// The CIL processor. + /// The instruction to handle. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(instruction)) + return InstructionHandleResult.None; + + // field reference + FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + if (fieldRef != null) + { + this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); + this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType); + } + + // method reference + MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + if (methodRef != null) + { + this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType); + this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType); + foreach (var parameter in methodRef.Parameters) + this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); + } + + // type reference + if (instruction.Operand is TypeReference typeRef) + this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType))); + + return InstructionHandleResult.Rewritten; + } + + /********* + ** Private methods + *********/ + /// Change a type reference if needed. + /// The assembly module containing the instruction. + /// The type to replace if it matches. + /// Assign the new type reference. + private void RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action set) + { + // current type + if (type.FullName == this.FromTypeName) + { + if (!this.ShouldIgnore(type)) + set(module.ImportReference(this.ToType)); + return; + } + + // recurse into generic arguments + if (type is GenericInstanceType genericType) + { + for (int i = 0; i < genericType.GenericArguments.Count; i++) + this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef); + } + + // recurse into generic parameters (e.g. constraints) + for (int i = 0; i < type.GenericParameters.Count; i++) + this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef)); + } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs deleted file mode 100644 index fade082b..00000000 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using Mono.Cecil; -using Mono.Cecil.Cil; -using StardewModdingAPI.Framework.ModLoading.Finders; - -namespace StardewModdingAPI.Framework.ModLoading.Rewriters -{ - /// Rewrites all references to a type. - internal class TypeReferenceRewriter : TypeFinder - { - /********* - ** Fields - *********/ - /// The full type name to which to find references. - private readonly string FromTypeName; - - /// The new type to reference. - private readonly Type ToType; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The full type name to which to find references. - /// The new type to reference. - /// A lambda which overrides a matched type. - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldIgnore = null) - : base(fromTypeFullName, InstructionHandleResult.None, shouldIgnore) - { - this.FromTypeName = fromTypeFullName; - this.ToType = toType; - } - - /// Perform the predefined logic for a method if applicable. - /// The assembly module containing the instruction. - /// The method definition containing the instruction. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) - { - bool rewritten = false; - - // return type - if (this.IsMatch(method.ReturnType)) - { - this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType); - rewritten = true; - } - - // parameters - foreach (ParameterDefinition parameter in method.Parameters) - { - if (this.IsMatch(parameter.ParameterType)) - { - this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); - rewritten = true; - } - } - - // generic parameters - for (int i = 0; i < method.GenericParameters.Count; i++) - { - var parameter = method.GenericParameters[i]; - if (this.IsMatch(parameter)) - { - this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType)); - rewritten = true; - } - } - - // local variables - foreach (VariableDefinition variable in method.Body.Variables) - { - if (this.IsMatch(variable.VariableType)) - { - this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType); - rewritten = true; - } - } - - return rewritten - ? InstructionHandleResult.Rewritten - : InstructionHandleResult.None; - } - - /// Perform the predefined logic for an instruction if applicable. - /// The assembly module containing the instruction. - /// The CIL processor. - /// The instruction to handle. - /// Metadata for mapping assemblies to the current platform. - /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) - { - if (!this.IsMatch(instruction)) - return InstructionHandleResult.None; - - // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (fieldRef != null) - { - this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); - this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType); - } - - // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); - if (methodRef != null) - { - this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType); - this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType); - foreach (var parameter in methodRef.Parameters) - this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); - } - - // type reference - if (instruction.Operand is TypeReference typeRef) - this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType))); - - return InstructionHandleResult.Rewritten; - } - - /********* - ** Private methods - *********/ - /// Change a type reference if needed. - /// The assembly module containing the instruction. - /// The type to replace if it matches. - /// Assign the new type reference. - private void RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action set) - { - // current type - if (type.FullName == this.FromTypeName) - { - if (!this.ShouldIgnore(type)) - set(module.ImportReference(this.ToType)); - return; - } - - // recurse into generic arguments - if (type is GenericInstanceType genericType) - { - for (int i = 0; i < genericType.GenericArguments.Count; i++) - this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef); - } - - // recurse into generic parameters (e.g. constraints) - for (int i = 0; i < type.GenericParameters.Count; i++) - this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef)); - } - } -} diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index eee5c235..f3d6e6db 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -3,6 +3,7 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.ModLoading.Finders; +using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Framework.ModLoading.Rewriters; using StardewModdingAPI.Framework.RewriteFacades; using StardewValley; -- cgit From f4192663d78c7a45418f07f0bf4acb67b11291fe Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 May 2020 20:53:02 -0400 Subject: add Harmony 2.0 rewriters (#711) --- .../ModLoading/Finders/TypeAssemblyFinder.cs | 25 ++++++ .../Framework/ModLoading/Finders/TypeFinder.cs | 25 ++++++ .../ModLoading/Framework/BaseTypeFinder.cs | 34 +++----- .../Framework/BaseTypeReferenceRewriter.cs | 97 ++++++++-------------- src/SMAPI/Framework/ModLoading/RewriteHelper.cs | 2 +- .../Rewriters/Harmony1AssemblyRewriter.cs | 77 +++++++++++++++++ .../ModLoading/Rewriters/MethodParentRewriter.cs | 18 ++-- .../ModLoading/Rewriters/TypeReferenceRewriter.cs | 68 +++++++++++++++ .../RewriteFacades/HarmonyInstanceMethods.cs | 33 ++++++++ src/SMAPI/Metadata/InstructionMetadata.cs | 5 +- 10 files changed, 295 insertions(+), 89 deletions(-) create mode 100644 src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs create mode 100644 src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs create mode 100644 src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs create mode 100644 src/SMAPI/Framework/RewriteFacades/HarmonyInstanceMethods.cs (limited to 'src') diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs new file mode 100644 index 00000000..5301186b --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -0,0 +1,25 @@ +using System; +using Mono.Cecil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Finders +{ + /// Finds incompatible CIL instructions that reference types in a given assembly. + internal class TypeAssemblyFinder : BaseTypeFinder + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full assembly name to which to find references. + /// The result to return for matching instructions. + /// A lambda which overrides a matched type. + public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func shouldIgnore = null) + : base( + isMatch: type => type.Scope.Name == assemblyName && (shouldIgnore == null || !shouldIgnore(type)), + result: result, + nounPhrase: $"{assemblyName} assembly" + ) + { } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs new file mode 100644 index 00000000..3adc31c7 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -0,0 +1,25 @@ +using System; +using Mono.Cecil; +using StardewModdingAPI.Framework.ModLoading.Framework; + +namespace StardewModdingAPI.Framework.ModLoading.Finders +{ + /// Finds incompatible CIL instructions that reference a given type. + internal class TypeFinder : BaseTypeFinder + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The full type name to match. + /// The result to return for matching instructions. + /// A lambda which overrides a matched type. + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) + : base( + isMatch: type => type.FullName == fullTypeName && (shouldIgnore == null || !shouldIgnore(type)), + result: result, + nounPhrase: $"{fullTypeName} type" + ) + { } + } +} diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs index 170bbb48..b1547334 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs @@ -5,21 +5,18 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { - /// Finds incompatible CIL instructions that reference a given type. - internal class TypeFinder : IInstructionHandler + /// Finds incompatible CIL type reference instructions. + internal abstract class BaseTypeFinder : IInstructionHandler { /********* ** Accessors *********/ - /// The full type name for which to find references. - private readonly string FullTypeName; + /// Matches the type references to handle. + private readonly Func IsMatchImpl; /// The result to return for matching instructions. private readonly InstructionHandleResult Result; - /// A lambda which overrides a matched type. - protected readonly Func ShouldIgnore; - /********* ** Accessors @@ -32,15 +29,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework ** Public methods *********/ /// Construct an instance. - /// The full type name to match. + /// Matches the type references to handle. /// The result to return for matching instructions. - /// A lambda which overrides a matched type. - public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) + /// A brief noun phrase indicating what the instruction finder matches. + public BaseTypeFinder(Func isMatch, InstructionHandleResult result, string nounPhrase) { - this.FullTypeName = fullTypeName; + this.IsMatchImpl = isMatch; this.Result = result; - this.NounPhrase = $"{fullTypeName} type"; - this.ShouldIgnore = shouldIgnore ?? (p => false); + this.NounPhrase = nounPhrase; } /// Perform the predefined logic for a method if applicable. @@ -68,13 +64,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework : InstructionHandleResult.None; } - - /********* - ** Protected methods - *********/ /// Get whether a CIL instruction matches. /// The method definition. - protected bool IsMatch(MethodDefinition method) + public bool IsMatch(MethodDefinition method) { if (this.IsMatch(method.ReturnType)) return true; @@ -90,7 +82,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get whether a CIL instruction matches. /// The IL instruction. - protected bool IsMatch(Instruction instruction) + public bool IsMatch(Instruction instruction) { // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); @@ -116,10 +108,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get whether a type reference matches the expected type. /// The type to check. - protected bool IsMatch(TypeReference type) + public bool IsMatch(TypeReference type) { // root type - if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type)) + if (this.IsMatchImpl(type)) return true; // generic arguments diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs index 8c2d11c8..55ce6b5a 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs @@ -5,30 +5,32 @@ using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { /// Rewrites all references to a type. - internal class TypeReferenceRewriter : TypeFinder + internal abstract class BaseTypeReferenceRewriter : IInstructionHandler { /********* ** Fields *********/ - /// The full type name to which to find references. - private readonly string FromTypeName; + /// The type finder which matches types to rewrite. + private readonly BaseTypeFinder Finder; - /// The new type to reference. - private readonly Type ToType; + + /********* + ** Accessors + *********/ + /// A brief noun phrase indicating what the handler matches. + public string NounPhrase { get; } /********* ** Public methods *********/ /// Construct an instance. - /// The full type name to which to find references. - /// The new type to reference. - /// A lambda which overrides a matched type. - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldIgnore = null) - : base(fromTypeFullName, InstructionHandleResult.None, shouldIgnore) + /// The type finder which matches types to rewrite. + /// A brief noun phrase indicating what the instruction finder matches. + public BaseTypeReferenceRewriter(BaseTypeFinder finder, string nounPhrase) { - this.FromTypeName = fromTypeFullName; - this.ToType = toType; + this.Finder = finder; + this.NounPhrase = nounPhrase; } /// Perform the predefined logic for a method if applicable. @@ -36,46 +38,36 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The method definition containing the instruction. /// Metadata for mapping assemblies to the current platform. /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + public InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) { bool rewritten = false; // return type - if (this.IsMatch(method.ReturnType)) + if (this.Finder.IsMatch(method.ReturnType)) { - this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType); - rewritten = true; + rewritten |= this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType); } // parameters foreach (ParameterDefinition parameter in method.Parameters) { - if (this.IsMatch(parameter.ParameterType)) - { - this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); - rewritten = true; - } + if (this.Finder.IsMatch(parameter.ParameterType)) + rewritten |= this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); } // generic parameters for (int i = 0; i < method.GenericParameters.Count; i++) { var parameter = method.GenericParameters[i]; - if (this.IsMatch(parameter)) - { - this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType)); - rewritten = true; - } + if (this.Finder.IsMatch(parameter)) + rewritten |= this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType)); } // local variables foreach (VariableDefinition variable in method.Body.Variables) { - if (this.IsMatch(variable.VariableType)) - { - this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType); - rewritten = true; - } + if (this.Finder.IsMatch(variable.VariableType)) + rewritten |= this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType); } return rewritten @@ -89,34 +81,37 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The instruction to handle. /// Metadata for mapping assemblies to the current platform. /// Whether the mod was compiled on a different platform. - public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + public InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction)) + if (!this.Finder.IsMatch(instruction)) return InstructionHandleResult.None; + bool rewritten = false; // field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { - this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); - this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType); + rewritten |= this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); + rewritten |= this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType); } // method reference MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null) { - this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType); - this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType); + rewritten |= this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType); + rewritten |= this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType); foreach (var parameter in methodRef.Parameters) - this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); + rewritten |= this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType); } // type reference if (instruction.Operand is TypeReference typeRef) - this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType))); + rewritten |= this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType))); - return InstructionHandleResult.Rewritten; + return rewritten + ? InstructionHandleResult.Rewritten + : InstructionHandleResult.None; } /********* @@ -126,26 +121,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The assembly module containing the instruction. /// The type to replace if it matches. /// Assign the new type reference. - private void RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action set) - { - // current type - if (type.FullName == this.FromTypeName) - { - if (!this.ShouldIgnore(type)) - set(module.ImportReference(this.ToType)); - return; - } - - // recurse into generic arguments - if (type is GenericInstanceType genericType) - { - for (int i = 0; i < genericType.GenericArguments.Count; i++) - this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef); - } - - // recurse into generic parameters (e.g. constraints) - for (int i = 0; i < type.GenericParameters.Count; i++) - this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef)); - } +