summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs98
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs53
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs40
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs48
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs49
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs51
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs120
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs77
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs278
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs (renamed from src/SMAPI/Framework/ModLoading/RewriteHelper.cs)93
-rw-r--r--src/SMAPI/Framework/ModLoading/IInstructionHandler.cs32
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsMethods.cs32
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceMethods.cs55
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchMethods.cs (renamed from src/SMAPI/Framework/RewriteFacades/SpriteBatchMethods.cs)2
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs37
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs42
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs123
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs71
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs35
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs136
-rw-r--r--src/SMAPI/Framework/Patching/GamePatcher.cs4
-rw-r--r--src/SMAPI/Framework/Patching/IHarmonyPatch.cs4
-rw-r--r--src/SMAPI/Framework/Patching/PatchHelper.cs34
-rw-r--r--src/SMAPI/Framework/PerformanceMonitoring/PerformanceCounter.cs3
26 files changed, 1004 insertions, 593 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 8df492eb..5218938f 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -4,8 +4,8 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
-using Mono.Cecil.Cil;
using StardewModdingAPI.Framework.Exceptions;
+using StardewModdingAPI.Framework.ModLoading.Framework;
using StardewModdingAPI.Metadata;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Utilities;
@@ -49,6 +49,8 @@ namespace StardewModdingAPI.Framework.ModLoading
this.Monitor = monitor;
this.ParanoidMode = paranoidMode;
this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform));
+
+ // init resolver
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.ExecutionPath);
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.InternalFilesPath);
@@ -124,7 +126,7 @@ namespace StardewModdingAPI.Framework.ModLoading
if (changed)
{
if (!oneAssembly)
- this.Monitor.Log($" Loading {assembly.File.Name} (rewritten in memory)...", LogLevel.Trace);
+ this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace);
using MemoryStream outStream = new MemoryStream();
assembly.Definition.Write(outStream);
byte[] bytes = outStream.ToArray();
@@ -280,35 +282,32 @@ namespace StardewModdingAPI.Framework.ModLoading
this.ChangeTypeScope(type);
}
- // find (and optionally rewrite) incompatible instructions
- bool anyRewritten = false;
- IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray();
- foreach (MethodDefinition method in this.GetMethods(module))
- {
- // check method definition
- foreach (IInstructionHandler handler in handlers)
+ // find or rewrite code
+ IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged).ToArray();
+ RecursiveRewriter rewriter = new RecursiveRewriter(
+ module: module,
+ rewriteType: (type, replaceWith) =>
{
- InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
- if (result == InstructionHandleResult.Rewritten)
- anyRewritten = true;
- }
-
- // check CIL instructions
- ILProcessor cil = method.Body.GetILProcessor();
- var instructions = cil.Body.Instructions;
- // ReSharper disable once ForCanBeConvertedToForeach -- deliberate access by index so each handler sees replacements from previous handlers
- for (int offset = 0; offset < instructions.Count; offset++)
+ bool rewritten = false;
+ foreach (IInstructionHandler handler in handlers)
+ rewritten |= handler.Handle(module, type, replaceWith);
+ return rewritten;
+ },
+ rewriteInstruction: (instruction, cil, replaceWith) =>
{
+ bool rewritten = false;
foreach (IInstructionHandler handler in handlers)
- {
- Instruction instruction = instructions[offset];
- InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
- if (result == InstructionHandleResult.Rewritten)
- anyRewritten = true;
- }
+ rewritten |= handler.Handle(module, cil, instruction, replaceWith);
+ return rewritten;
}
+ );
+ bool anyRewritten = rewriter.RewriteModule();
+
+ // handle rewrite flags
+ foreach (IInstructionHandler handler in handlers)
+ {
+ foreach (var flag in handler.Flags)
+ this.ProcessInstructionHandleResult(mod, handler, flag, loggedMessages, logPrefix, filename);
}
return platformChanged || anyRewritten;
@@ -323,49 +322,52 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="filename">The assembly filename for log messages.</param>
private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet<string> loggedMessages, string logPrefix, string filename)
{
+ // get message template
+ // ($phrase is replaced with the noun phrase or messages)
+ string template = null;
switch (result)
{
case InstructionHandleResult.Rewritten:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewrote {filename} to fix {handler.NounPhrase}...");
+ template = $"{logPrefix}Rewrote {filename} to fix $phrase...";
break;
case InstructionHandleResult.NotCompatible:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Broken code in {filename}: {handler.NounPhrase}.");
+ template = $"{logPrefix}Broken code in {filename}: $phrase.";
mod.SetWarning(ModWarning.BrokenCodeLoaded);
break;
case InstructionHandleResult.DetectedGamePatch:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected game patcher ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected game patcher ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.PatchesGame);
break;
case InstructionHandleResult.DetectedSaveSerializer:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected possible save serializer change ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected possible save serializer change ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.ChangesSaveSerializer);
break;
case InstructionHandleResult.DetectedUnvalidatedUpdateTick:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected reference to {handler.NounPhrase} in assembly {filename}.");
+ template = $"{logPrefix}Detected reference to $phrase in assembly {filename}.";
mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick);
break;
case InstructionHandleResult.DetectedDynamic:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected 'dynamic' keyword ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected 'dynamic' keyword ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.UsesDynamic);
break;
case InstructionHandleResult.DetectedConsoleAccess:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected direct console access ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.AccessesConsole);
break;
case InstructionHandleResult.DetectedFilesystemAccess:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected filesystem access ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected filesystem access ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.AccessesFilesystem);
break;
case InstructionHandleResult.DetectedShellAccess:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected shell or process access ({handler.NounPhrase}) in assembly {filename}.");
+ template = $"{logPrefix}Detected shell or process access ($phrase) in assembly {filename}.";
mod.SetWarning(ModWarning.AccessesShell);
break;
@@ -375,6 +377,17 @@ namespace StardewModdingAPI.Framework.ModLoading
default:
throw new NotSupportedException($"Unrecognized instruction handler result '{result}'.");
}
+ if (template == null)
+ return;
+
+ // format messages
+ if (handler.Phrases.Any())
+ {
+ foreach (string message in handler.Phrases)
+ this.Monitor.LogOnce(template.Replace("$phrase", message));
+ }
+ else
+ this.Monitor.LogOnce(template.Replace("$phrase", handler.DefaultPhrase ?? handler.GetType().Name));
}
/// <summary>Get the correct reference to use for compatibility with the current platform.</summary>
@@ -393,18 +406,5 @@ namespace StardewModdingAPI.Framework.ModLoading
AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly];
type.Scope = assemblyRef;
}
-
- /// <summary>Get all methods in a module.</summary>
- /// <param name="module">The module to search.</param>
- private IEnumerable<MethodDefinition> GetMethods(ModuleDefinition module)
- {
- return (
- from type in module.GetTypes()
- where type.HasMethods
- from method in type.Methods
- where method.HasBody
- select method
- );
- }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
index 898bafb4..e1476b73 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs
@@ -1,10 +1,12 @@
+using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Finders
{
/// <summary>Finds incompatible CIL instructions that reference a given event.</summary>
- internal class EventFinder : IInstructionHandler
+ internal class EventFinder : BaseInstructionHandler
{
/*********
** Fields
@@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/// <param name="eventName">The event name for which to find references.</param>
/// <param name="result">The result to return for matching instructions.</param>
public EventFinder(string fullTypeName, string eventName, InstructionHandleResult result)
+ : base(defaultPhrase: $"{fullTypeName}.{eventName} event")
{
this.FullTypeName = fullTypeName;
this.EventName = eventName;
this.Result = result;
- this.NounPhrase = $"{fullTypeName}.{eventName} event";
}
- /// <summary>Perform the predefined logic for a method if applicable.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="method">The method definition containing the instruction.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+ /// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
- /// <param name="instruction">The instruction to handle.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ /// <param name="instruction">The CIL instruction to handle.</param>
+ /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param>
+ /// <returns>Returns whether the instruction was changed.</returns>
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
{
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
+ if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction))
+ this.MarkFlag(this.Result);
+
+ return false;
}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs
index 606ca8b7..c157ed9b 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs
@@ -1,10 +1,12 @@
+using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Finders
{
/// <summary>Finds incompatible CIL instructions that reference a given field.</summary>
- internal class FieldFinder : IInstructionHandler
+ internal class FieldFinder : BaseInstructionHandler
{
/*********
** Fields
@@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -34,49 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/// <param name="fieldName">The field name for which to find references.</param>
/// <param name="result">The result to return for matching instructions.</param>
public FieldFinder(string fullTypeName, string fieldName, InstructionHandleResult result)
+ : base(defaultPhrase: $"{fullTypeName}.{fieldName} field")
{
this.FullTypeName = fullTypeName;
this.FieldName = fieldName;
this.Result = result;
- this.NounPhrase = $"{fullTypeName}.{fieldName} field";
}
- /// <summary>Perform the predefined logic for a method if applicable.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="method">The method definition containing the instruction.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+ /// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
- /// <param name="instruction">The instruction to handle.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ /// <param name="instruction">The CIL instruction to handle.</param>
+ /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param>
+ /// <returns>Returns whether the instruction was changed.</returns>
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
{
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
+ if (!this.Flags.Contains(this.Result) && RewriteHelper.IsFieldReferenceTo(instruction, this.FullTypeName, this.FieldName))
+ this.MarkFlag(this.Result);
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
- {
- FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
- return
- fieldRef != null
- && fieldRef.DeclaringType.FullName == this.FullTypeName
- && fieldRef.Name == this.FieldName;
+ return false;
}
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs
index 9ca246ff..82c93a7c 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs
@@ -1,10 +1,12 @@
+using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Finders
{
/// <summary>Finds incompatible CIL instructions that reference a given method.</summary>
- internal class MethodFinder : IInstructionHandler
+ internal class MethodFinder : BaseInstructionHandler
{
/*********
** Fields
@@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/// <param name="methodName">The method name for which to find references.</param>
/// <param name="result">The result to return for matching instructions.</param>
public MethodFinder(string fullTypeName, string methodName, InstructionHandleResult result)
+ : base(defaultPhrase: $"{fullTypeName}.{methodName} method")
{
this.FullTypeName = fullTypeName;
this.MethodName = methodName;
this.Result = result;
- this.NounPhrase = $"{fullTypeName}.{methodName} method";
}
- /// <summary>Perform the predefined logic for a method if applicable.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="method">The method definition containing the instruction.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+ /// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
- /// <param name="instruction">The instruction to handle.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ /// <param name="instruction">The CIL instruction to handle.</param>
+ /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param>
+ /// <returns>Returns whether the instruction was changed.</returns>
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
{
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
+ if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction))
+ this.MarkFlag(this.Result);
+
+ return false;
}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs
index 0677aa88..c96d61a2 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs
@@ -1,10 +1,12 @@
+using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Finders
{
/// <summary>Finds incompatible CIL instructions that reference a given property.</summary>
- internal class PropertyFinder : IInstructionHandler
+ internal class PropertyFinder : BaseInstructionHandler
{
/*********
** Fields
@@ -20,13 +22,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -34,34 +29,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/// <param name="propertyName">The property name for which to find references.</param>
/// <param name="result">The result to return for matching instructions.</param>
public PropertyFinder(string fullTypeName, string propertyName, InstructionHandleResult result)
+ : base(defaultPhrase: $"{fullTypeName}.{propertyName} property")
{
this.FullTypeName = fullTypeName;
this.PropertyName = propertyName;
this.Result = result;
- this.NounPhrase = $"{fullTypeName}.{propertyName} property";
}
- /// <summary>Perform the predefined logic for a method if applicable.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="method">The method definition containing the instruction.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+ /// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
- /// <param name="instruction">The instruction to handle.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ /// <param name="instruction">The CIL instruction to handle.</param>
+ /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param>
+ /// <returns>Returns whether the instruction was changed.</returns>
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
{
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
+ if (!this.Flags.Contains(this.Result) && this.IsMatch(instruction))
+ this.MarkFlag(this.Result);
+
+ return false;
}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
index 459e3210..a67cfa4f 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
@@ -1,13 +1,15 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
namespace StardewModdingAPI.Framework.ModLoading.Finders
{
/// <summary>Finds references to a field, property, or method which returns a different type than the code expects.</summary>
/// <remarks>This implementation is purely heuristic. It should never return a false positive, but won't detect all cases.</remarks>
- internal class ReferenceToMemberWithUnexpectedTypeFinder : IInstructionHandler
+ internal class ReferenceToMemberWithUnexpectedTypeFinder : BaseInstructionHandler
{
/*********
** Fields
@@ -17,39 +19,23 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
/*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; private set; } = "";
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="validateReferencesToAssemblies">The assembly names to which to heuristically detect broken references.</param>
public ReferenceToMemberWithUnexpectedTypeFinder(string[] validateReferencesToAssemblies)
+ : base(defaultPhrase: "")
{
this.ValidateReferencesToAssemblies = new HashSet<string>(validateReferencesToAssemblies);
}
- /// <summary>Perform the predefined logic for a method if applicable.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="method">The method definition containing the instruction.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</summary>
+ /// <summary>Rewrite a CIL instruction reference if needed.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="cil">The CIL processor.</param>
- /// <param name="instruction">The instruction to handle.</param>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public virtual InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ /// <param name="instruction">The CIL instruction to handle.</param>
+ /// <param name="replaceWith">Replaces the CIL instruction with a new one.</param>
+ /// <returns>Returns whether the instruction was changed.</returns>
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
{
// field reference
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
@@ -58,13 +44,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
// get target field
FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name);
if (targetField == null)
- return InstructionHandleResult.None;
+ return false;
// validate return type
if (!RewriteHelper.LooksLikeSameType(fieldRef.FieldType, targetField.FieldType))
{
- this.NounPhrase = $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})";
- return InstructionHandleResult.NotCompatible;
+ this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})");
+ ret