summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/ModLoading
diff options
context:
space:
mode:
Diffstat (limited to 'src/StardewModdingAPI/Framework/ModLoading')
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs61
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs15
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs333
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs36
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Finders/EventFinder.cs82
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Finders/FieldFinder.cs82
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Finders/MethodFinder.cs82
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Finders/PropertyFinder.cs82
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Finders/TypeFinder.cs133
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/IInstructionHandler.cs34
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/IncompatibleInstructionException.cs35
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/InstructionHandleResult.cs24
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs14
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs18
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs68
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs12
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs366
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Platform.cs12
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/PlatformAssemblyMap.cs55
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/RewriteHelper.cs94
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs50
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs51
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs88
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs154
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/Rewriters/VirtualEntryCallRemover.cs90
25 files changed, 0 insertions, 2071 deletions
diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
deleted file mode 100644
index 4378798c..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.Collections.Generic;
-using Mono.Cecil;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary>
- internal class AssemblyDefinitionResolver : DefaultAssemblyResolver
- {
- /*********
- ** Properties
- *********/
- /// <summary>The known assemblies.</summary>
- private readonly IDictionary<string, AssemblyDefinition> Loaded = new Dictionary<string, AssemblyDefinition>();
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Add known assemblies to the resolver.</summary>
- /// <param name="assemblies">The known assemblies.</param>
- public void Add(params AssemblyDefinition[] assemblies)
- {
- foreach (AssemblyDefinition assembly in assemblies)
- {
- this.Loaded[assembly.Name.Name] = assembly;
- this.Loaded[assembly.Name.FullName] = assembly;
- }
- }
-
- /// <summary>Resolve an assembly reference.</summary>
- /// <param name="name">The assembly name.</param>
- public override AssemblyDefinition Resolve(AssemblyNameReference name) => this.ResolveName(name.Name) ?? base.Resolve(name);
-
- /// <summary>Resolve an assembly reference.</summary>
- /// <param name="name">The assembly name.</param>
- /// <param name="parameters">The assembly reader parameters.</param>
- public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) => this.ResolveName(name.Name) ?? base.Resolve(name, parameters);
-
- /// <summary>Resolve an assembly reference.</summary>
- /// <param name="fullName">The assembly full name (including version, etc).</param>
- public override AssemblyDefinition Resolve(string fullName) => this.ResolveName(fullName) ?? base.Resolve(fullName);
-
- /// <summary>Resolve an assembly reference.</summary>
- /// <param name="fullName">The assembly full name (including version, etc).</param>
- /// <param name="parameters">The assembly reader parameters.</param>
- public override AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) => this.ResolveName(fullName) ?? base.Resolve(fullName, parameters);
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Resolve a known assembly definition based on its short or full name.</summary>
- /// <param name="name">The assembly's short or full name.</param>
- private AssemblyDefinition ResolveName(string name)
- {
- return this.Loaded.ContainsKey(name)
- ? this.Loaded[name]
- : null;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs
deleted file mode 100644
index 11be19fc..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Indicates the result of an assembly load.</summary>
- internal enum AssemblyLoadStatus
- {
- /// <summary>The assembly was loaded successfully.</summary>
- Okay = 1,
-
- /// <summary>The assembly could not be loaded.</summary>
- Failed = 2,
-
- /// <summary>The assembly is already loaded.</summary>
- AlreadyLoaded = 3
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs
deleted file mode 100644
index 1e3c4a05..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs
+++ /dev/null
@@ -1,333 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using StardewModdingAPI.Framework.Exceptions;
-using StardewModdingAPI.Metadata;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Preprocesses and loads mod assemblies.</summary>
- internal class AssemblyLoader
- {
- /*********
- ** Properties
- *********/
- /// <summary>Metadata for mapping assemblies to the current platform.</summary>
- private readonly PlatformAssemblyMap AssemblyMap;
-
- /// <summary>A type => assembly lookup for types which should be rewritten.</summary>
- private readonly IDictionary<string, Assembly> TypeAssemblies;
-
- /// <summary>Encapsulates monitoring and logging.</summary>
- private readonly IMonitor Monitor;
-
- /// <summary>Whether to enable developer mode logging.</summary>
- private readonly bool IsDeveloperMode;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="targetPlatform">The current game platform.</param>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- /// <param name="isDeveloperMode">Whether to enable developer mode logging.</param>
- public AssemblyLoader(Platform targetPlatform, IMonitor monitor, bool isDeveloperMode)
- {
- this.Monitor = monitor;
- this.IsDeveloperMode = isDeveloperMode;
- this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform);
-
- // generate type => assembly lookup for types which should be rewritten
- this.TypeAssemblies = new Dictionary<string, Assembly>();
- foreach (Assembly assembly in this.AssemblyMap.Targets)
- {
- ModuleDefinition module = this.AssemblyMap.TargetModules[assembly];
- foreach (TypeDefinition type in module.GetTypes())
- {
- if (!type.IsPublic)
- continue; // no need to rewrite
- if (type.Namespace.Contains("<"))
- continue; // ignore assembly metadata
- this.TypeAssemblies[type.FullName] = assembly;
- }
- }
- }
-
- /// <summary>Preprocess and load an assembly.</summary>
- /// <param name="mod">The mod for which the assembly is being loaded.</param>
- /// <param name="assemblyPath">The assembly file path.</param>
- /// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
- /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
- /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
- public Assembly Load(IModMetadata mod, string assemblyPath, bool assumeCompatible)
- {
- // get referenced local assemblies
- AssemblyParseResult[] assemblies;
- {
- AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver();
- HashSet<string> visitedAssemblyNames = new HashSet<string>(AppDomain.CurrentDomain.GetAssemblies().Select(p => p.GetName().Name)); // don't try loading assemblies that are already loaded
- assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), visitedAssemblyNames, resolver).ToArray();
- }
-
- // validate load
- if (!assemblies.Any() || assemblies[0].Status == AssemblyLoadStatus.Failed)
- {
- throw new SAssemblyLoadFailedException(!File.Exists(assemblyPath)
- ? $"Could not load '{assemblyPath}' because it doesn't exist."
- : $"Could not load '{assemblyPath}'."
- );
- }
- if (assemblies.Last().Status == AssemblyLoadStatus.AlreadyLoaded) // mod assembly is last in dependency order
- throw new SAssemblyLoadFailedException($"Could not load '{assemblyPath}' because it was already loaded. Do you have two copies of this mod?");
-
- // rewrite & load assemblies in leaf-to-root order
- bool oneAssembly = assemblies.Length == 1;
- Assembly lastAssembly = null;
- HashSet<string> loggedMessages = new HashSet<string>();
- foreach (AssemblyParseResult assembly in assemblies)
- {
- if (assembly.Status == AssemblyLoadStatus.AlreadyLoaded)
- continue;
-
- bool changed = this.RewriteAssembly(mod, assembly.Definition, assumeCompatible, loggedMessages, logPrefix: " ");
- if (changed)
- {
- if (!oneAssembly)
- this.Monitor.Log($" Loading {assembly.File.Name} (rewritten in memory)...", LogLevel.Trace);
- using (MemoryStream outStream = new MemoryStream())
- {
- assembly.Definition.Write(outStream);
- byte[] bytes = outStream.ToArray();
- lastAssembly = Assembly.Load(bytes);
- }
- }
- else
- {
- if (!oneAssembly)
- this.Monitor.Log($" Loading {assembly.File.Name}...", LogLevel.Trace);
- lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName);
- }
- }
-
- // last assembly loaded is the root
- return lastAssembly;
- }
-
- /// <summary>Resolve an assembly by its name.</summary>
- /// <param name="name">The assembly name.</param>
- /// <remarks>
- /// This implementation returns the first loaded assembly which matches the short form of
- /// the assembly name, to resolve assembly resolution issues when rewriting
- /// assemblies (especially with Mono). Since this is meant to be called on <see cref="AppDomain.AssemblyResolve"/>,
- /// the implicit assumption is that loading the exact assembly failed.
- /// </remarks>
- public Assembly ResolveAssembly(string name)
- {
- string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture)
- return AppDomain.CurrentDomain
- .GetAssemblies()
- .FirstOrDefault(p => p.GetName().Name == shortName);
- }
-
-
- /*********
- ** Private methods
- *********/
- /****
- ** Assembly parsing
- ****/
- /// <summary>Get a list of referenced local assemblies starting from the mod assembly, ordered from leaf to root.</summary>
- /// <param name="file">The assembly file to load.</param>
- /// <param name="visitedAssemblyNames">The assembly names that should be skipped.</param>
- /// <param name="assemblyResolver">A resolver which resolves references to known assemblies.</param>
- /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
- private IEnumerable<AssemblyParseResult> GetReferencedLocalAssemblies(FileInfo file, HashSet<string> visitedAssemblyNames, IAssemblyResolver assemblyResolver)
- {
- // validate
- if (file.Directory == null)
- throw new InvalidOperationException($"Could not get directory from file path '{file.FullName}'.");
- if (!file.Exists)
- yield break; // not a local assembly
-
- // read assembly
- byte[] assemblyBytes = File.ReadAllBytes(file.FullName);
- AssemblyDefinition assembly;
- using (Stream readStream = new MemoryStream(assemblyBytes))
- assembly = AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver });
-
- // skip if already visited
- if (visitedAssemblyNames.Contains(assembly.Name.Name))
- yield return new AssemblyParseResult(file, null, AssemblyLoadStatus.AlreadyLoaded);
- visitedAssemblyNames.Add(assembly.Name.Name);
-
- // yield referenced assemblies
- foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences)
- {
- FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll"));
- foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyNames, assemblyResolver))
- yield return result;
- }
-
- // yield assembly
- yield return new AssemblyParseResult(file, assembly, AssemblyLoadStatus.Okay);
- }
-
- /****
- ** Assembly rewriting
- ****/
- /// <summary>Rewrite the types referenced by an assembly.</summary>
- /// <param name="mod">The mod for which the assembly is being loaded.</param>
- /// <param name="assembly">The assembly to rewrite.</param>
- /// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
- /// <param name="loggedMessages">The messages that have already been logged for this mod.</param>
- /// <param name="logPrefix">A string to prefix to log messages.</param>
- /// <returns>Returns whether the assembly was modified.</returns>
- /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
- private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, bool assumeCompatible, HashSet<string> loggedMessages, string logPrefix)
- {
- ModuleDefinition module = assembly.MainModule;
- string filename = $"{assembly.Name.Name}.dll";
-
- // swap assembly references if needed (e.g. XNA => MonoGame)
- bool platformChanged = false;
- for (int i = 0; i < module.AssemblyReferences.Count; i++)
- {
- // remove old assembly reference
- if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
- {
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewriting {filename} for OS...");
- platformChanged = true;
- module.AssemblyReferences.RemoveAt(i);
- i--;
- }
- }
- if (platformChanged)
- {
- // add target assembly references
- foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
- module.AssemblyReferences.Add(target);
-
- // rewrite type scopes to use target assemblies
- IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
- foreach (TypeReference type in typeReferences)
- this.ChangeTypeScope(type);
- }
-
- // find (and optionally rewrite) incompatible instructions
- bool anyRewritten = false;
- IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers().ToArray();
- foreach (MethodDefinition method in this.GetMethods(module))
- {
- // check method definition
- foreach (IInstructionHandler handler in handlers)
- {
- InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename);
- if (result == InstructionHandleResult.Rewritten)
- anyRewritten = true;
- }
-
- // check CIL instructions
- ILProcessor cil = method.Body.GetILProcessor();
- foreach (Instruction instruction in cil.Body.Instructions.ToArray())
- {
- foreach (IInstructionHandler handler in handlers)
- {
- InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename);
- if (result == InstructionHandleResult.Rewritten)
- anyRewritten = true;
- }
- }
- }
-
- return platformChanged || anyRewritten;
- }
-
- /// <summary>Process the result from an instruction handler.</summary>
- /// <param name="mod">The mod being analysed.</param>
- /// <param name="handler">The instruction handler.</param>
- /// <param name="result">The result returned by the handler.</param>
- /// <param name="loggedMessages">The messages already logged for the current mod.</param>
- /// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
- /// <param name="logPrefix">A string to prefix to log messages.</param>
- /// <param name="filename">The assembly filename for log messages.</param>
- private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet<string> loggedMessages, string logPrefix, bool assumeCompatible, string filename)
- {
- switch (result)
- {
- case InstructionHandleResult.Rewritten:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewrote {filename} to fix {handler.NounPhrase}...");
- break;
-
- case InstructionHandleResult.NotCompatible:
- if (!assumeCompatible)
- throw new IncompatibleInstructionException(handler.NounPhrase, $"Found an incompatible CIL instruction ({handler.NounPhrase}) while loading assembly {filename}.");
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Found an incompatible CIL instruction ({handler.NounPhrase}) while loading assembly {filename}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn);
- break;
-
- case InstructionHandleResult.DetectedGamePatch:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected game patcher ({handler.NounPhrase}) in assembly {filename}.");
- this.Monitor.LogOnce(loggedMessages, $"{mod.DisplayName} patches the game, which may impact game stability. If you encounter problems, try removing this mod first.", LogLevel.Warn);
- break;
-
- case InstructionHandleResult.DetectedSaveSerialiser:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected possible save serialiser change ({handler.NounPhrase}) in assembly {filename}.");
- this.Monitor.LogOnce(loggedMessages, $"{mod.DisplayName} seems to change the save serialiser. It may change your saves in such a way that they won't work without this mod in the future.", LogLevel.Warn);
- break;
-
- case InstructionHandleResult.DetectedDynamic:
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected 'dynamic' keyword ({handler.NounPhrase}) in assembly {filename}.");
- this.Monitor.LogOnce(loggedMessages, $"{mod.DisplayName} uses the 'dynamic' keyword, which isn't compatible with Stardew Valley on Linux or Mac.",
-#if SMAPI_FOR_WINDOWS
- this.IsDeveloperMode ? LogLevel.Warn : LogLevel.Debug
-#else
- LogLevel.Warn
-#endif
- );
- break;
-
- case InstructionHandleResult.None:
- break;
-
- default:
- throw new NotSupportedException($"Unrecognised instruction handler result '{result}'.");
- }
- }
-
- /// <summary>Get the correct reference to use for compatibility with the current platform.</summary>
- /// <param name="type">The type reference to rewrite.</param>
- private void ChangeTypeScope(TypeReference type)
- {
- // check skip conditions
- if (type == null || type.FullName.StartsWith("System."))
- return;
-
- // get assembly
- if (!this.TypeAssemblies.TryGetValue(type.FullName, out Assembly assembly))
- return;
-
- // replace scope
- 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/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs
deleted file mode 100644
index b56a776c..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.IO;
-using Mono.Cecil;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Metadata about a parsed assembly definition.</summary>
- internal class AssemblyParseResult
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The original assembly file.</summary>
- public readonly FileInfo File;
-
- /// <summary>The assembly definition.</summary>
- public readonly AssemblyDefinition Definition;
-
- /// <summary>The result of the assembly load.</summary>
- public AssemblyLoadStatus Status;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="file">The original assembly file.</param>
- /// <param name="assembly">The assembly definition.</param>
- /// <param name="status">The result of the assembly load.</param>
- public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly, AssemblyLoadStatus status)
- {
- this.File = file;
- this.Definition = assembly;
- this.Status = status;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/StardewModdingAPI/Framework/ModLoading/Finders/EventFinder.cs
deleted file mode 100644
index e4beb7a9..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Finders/EventFinder.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// <summary>Finds incompatible CIL instructions that reference a given event.</summary>
- internal class EventFinder : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
-
- /// <summary>The event name for which to find references.</summary>
- private readonly string EventName;
-
- /// <summary>The result to return for matching instructions.</summary>
- private readonly InstructionHandleResult Result;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name for which to find references.</param>
- /// <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)
- {
- 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>
- /// <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)
- {
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
- {
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- return
- methodRef != null
- && methodRef.DeclaringType.FullName == this.FullTypeName
- && (methodRef.Name == "add_" + this.EventName || methodRef.Name == "remove_" + this.EventName);
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/StardewModdingAPI/Framework/ModLoading/Finders/FieldFinder.cs
deleted file mode 100644
index 00805815..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Finders/FieldFinder.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// <summary>Finds incompatible CIL instructions that reference a given field.</summary>
- internal class FieldFinder : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
-
- /// <summary>The field name for which to find references.</summary>
- private readonly string FieldName;
-
- /// <summary>The result to return for matching instructions.</summary>
- private readonly InstructionHandleResult Result;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name for which to find references.</param>
- /// <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)
- {
- 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>
- /// <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)
- {
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
-
- /*********
- ** 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;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/StardewModdingAPI/Framework/ModLoading/Finders/MethodFinder.cs
deleted file mode 100644
index 5358f181..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Finders/MethodFinder.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// <summary>Finds incompatible CIL instructions that reference a given method.</summary>
- internal class MethodFinder : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
-
- /// <summary>The method name for which to find references.</summary>
- private readonly string MethodName;
-
- /// <summary>The result to return for matching instructions.</summary>
- private readonly InstructionHandleResult Result;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name for which to find references.</param>
- /// <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)
- {
- 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>
- /// <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)
- {
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
- {
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- return
- methodRef != null
- && methodRef.DeclaringType.FullName == this.FullTypeName
- && methodRef.Name == this.MethodName;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/StardewModdingAPI/Framework/ModLoading/Finders/PropertyFinder.cs
deleted file mode 100644
index e54c86cf..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Finders/PropertyFinder.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// <summary>Finds incompatible CIL instructions that reference a given property.</summary>
- internal class PropertyFinder : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
-
- /// <summary>The property name for which to find references.</summary>
- private readonly string PropertyName;
-
- /// <summary>The result to return for matching instructions.</summary>
- private readonly InstructionHandleResult Result;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name for which to find references.</param>
- /// <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)
- {
- 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>
- /// <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)
- {
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
- {
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- return
- methodRef != null
- && methodRef.DeclaringType.FullName == this.FullTypeName
- && (methodRef.Name == "get_" + this.PropertyName || methodRef.Name == "set_" + this.PropertyName);
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/StardewModdingAPI/Framework/ModLoading/Finders/TypeFinder.cs
deleted file mode 100644
index 45349def..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Finders/TypeFinder.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System.Linq;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// <summary>Finds incompatible CIL instructions that reference a given type.</summary>
- internal class TypeFinder : IInstructionHandler
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
-
- /// <summary>The result to return for matching instructions.</summary>
- private readonly InstructionHandleResult Result;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name to match.</param>
- /// <param name="result">The result to return for matching instructions.</param>
- public TypeFinder(string fullTypeName, InstructionHandleResult result)
- {
- this.FullTypeName = fullTypeName;
- this.Result = result;
- this.NounPhrase = $"{fullTypeName} type";
- }
-
- /// <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 this.IsMatch(method)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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)
- {
- return this.IsMatch(instruction)
- ? this.Result
- : InstructionHandleResult.None;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="method">The method deifnition.</param>
- 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;
- }
-
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- 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;
- }
-
- /// <summary>Get whether a type reference matches the expected type.</summary>
- /// <param name="type">The type to check.</param>
- protected bool IsMatch(TypeReference type)
- {
- // root type
- if (type.FullName == this.FullTypeName)
- 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/StardewModdingAPI/Framework/ModLoading/IInstructionHandler.cs b/src/StardewModdingAPI/Framework/ModLoading/IInstructionHandler.cs
deleted file mode 100644
index 8830cc74..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/IInstructionHandler.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Performs predefined logic for detected CIL instructions.</summary>
- internal interface IInstructionHandler
- {
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the handler matches.</summary>
- string NounPhrase { get; }
-
-
- /*********
- ** Methods
- *********/
- /// <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>
- InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged);
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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>
- InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged);
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/IncompatibleInstructionException.cs b/src/StardewModdingAPI/Framework/ModLoading/IncompatibleInstructionException.cs
deleted file mode 100644
index 17ec24b1..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/IncompatibleInstructionException.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>An exception raised when an incompatible instruction is found while loading a mod assembly.</summary>
- internal class IncompatibleInstructionException : Exception
- {
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase which describes the incompatible instruction that was found.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="nounPhrase">A brief noun phrase which describes the incompatible instruction that was found.</param>
- public IncompatibleInstructionException(string nounPhrase)
- : base($"Found an incompatible CIL instruction ({nounPhrase}).")
- {
- this.NounPhrase = nounPhrase;
- }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="nounPhrase">A brief noun phrase which describes the incompatible instruction that was found.</param>
- /// <param name="message">A message which describes the error.</param>
- public IncompatibleInstructionException(string nounPhrase, string message)
- : base(message)
- {
- this.NounPhrase = nounPhrase;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/StardewModdingAPI/Framework/ModLoading/InstructionHandleResult.cs
deleted file mode 100644
index 0ae598fc..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/InstructionHandleResult.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Indicates how an instruction was handled.</summary>
- internal enum InstructionHandleResult
- {
- /// <summary>No special handling is needed.</summary>
- None,
-
- /// <summary>The instruction was successfully rewritten for compatibility.</summary>
- Rewritten,
-
- /// <summary>The instruction is not compatible and can't be rewritten for compatibility.</summary>
- NotCompatible,
-
- /// <summary>The instruction is compatible, but patches the game in a way that may impact stability.</summary>
- DetectedGamePatch,
-
- /// <summary>The instruction is compatible, but affects the save serializer in a way that may make saves unloadable without the mod.</summary>
- DetectedSaveSerialiser,
-
- /// <summary>The instruction is compatible, but uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
- DetectedDynamic
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs b/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs
deleted file mode 100644
index 075e237a..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/InvalidModStateException.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>An exception which indicates that something went seriously wrong while loading mods, and SMAPI should abort outright.</summary>
- internal class InvalidModStateException : Exception
- {
- /// <summary>Construct an instance.</summary>
- /// <param name="message">The error message.</param>
- /// <param name="ex">The underlying exception, if any.</param>
- public InvalidModStateException(string message, Exception ex = null)
- : base(message, ex) { }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs
deleted file mode 100644
index 0774b487..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/ModDependencyStatus.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>The status of a given mod in the dependency-sorting algorithm.</summary>
- internal enum ModDependencyStatus
- {
- /// <summary>The mod hasn't been visited yet.</summary>
- Queued,
-
- /// <summary>The mod is currently being analysed as part of a dependency chain.</summary>
- Checking,
-
- /// <summary>The mod has already been sorted.</summary>
- Sorted,
-
- /// <summary>The mod couldn't be sorted due to a metadata issue (e.g. missing dependencies).</summary>
- Failed
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs b/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs
deleted file mode 100644
index 5055da75..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/ModMetadata.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using StardewModdingAPI.Framework.Models;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Metadata for a mod.</summary>
- internal class ModMetadata : IModMetadata
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod's display name.</summary>
- public string DisplayName { get; }
-
- /// <summary>The mod's full directory path.</summary>
- public string DirectoryPath { get; }
-
- /// <summary>The mod manifest.</summary>
- public IManifest Manifest { get; }
-
- /// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary>
- public ModDataRecord DataRecord { get; }
-
- /// <summary>The metadata resolution status.</summary>
- public ModMetadataStatus Status { get; private set; }
-
- /// <summary>The reason the metadata is invalid, if any.</summary>
- public string Error { get; private set; }
-
- /// <summary>The mod instance (if it was loaded).</summary>
- public IMod Mod { get; private set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="displayName">The mod's display name.</param>
- /// <param name="directoryPath">The mod's full directory path.</param>
- /// <param name="manifest">The mod manifest.</param>
- /// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param>
- public ModMetadata(string displayName, string directoryPath, IManifest manifest, ModDataRecord dataRecord)
- {
- this.DisplayName = displayName;
- this.DirectoryPath = directoryPath;
- this.Manifest = manifest;
- this.DataRecord = dataRecord;
- }
-
- /// <summary>Set the mod status.</summary>
- /// <param name="status">The metadata resolution status.</param>
- /// <param name="error">The reason the metadata is invalid, if any.</param>
- /// <returns>Return the instance for chaining.</returns>
- public IModMetadata SetStatus(ModMetadataStatus status, string error = null)
- {
- this.Status = status;
- this.Error = error;
- return this;
- }
-
- /// <summary>Set the mod instance.</summary>
- /// <param name="mod">The mod instance to set.</param>
- public IModMetadata SetMod(IMod mod)
- {
- this.Mod = mod;
- return this;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs
deleted file mode 100644
index ab65f7b4..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/ModMetadataStatus.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Indicates the status of a mod's metadata resolution.</summary>
- internal enum ModMetadataStatus
- {
- /// <summary>The mod has been found, but hasn't been processed yet.</summary>
- Found,
-
- /// <summary>The mod cannot be loaded.</summary>
- Failed
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs
deleted file mode 100644
index d0ef1b08..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs
+++ /dev/null
@@ -1,366 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using StardewModdingAPI.Framework.Exceptions;
-using StardewModdingAPI.Framework.Models;
-using StardewModdingAPI.Framework.Serialisation;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Finds and processes mod metadata.</summary>
- internal class ModResolver
- {
- /*********
- ** Public methods
- *********/
- /// <summary>Get manifest metadata for each folder in the given root path.</summary>
- /// <param name="rootPath">The root path to search for mods.</param>
- /// <param name="jsonHelper">The JSON helper with which to read manifests.</param>
- /// <param name="dataRecords">Metadata about mods from SMAPI's internal data.</param>
- /// <returns>Returns the manifests by relative folder.</returns>
- public IEnumerable<IModMetadata> ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable<ModDataRecord> dataRecords)
- {
- dataRecords = dataRecords.ToArray();
-
- foreach (DirectoryInfo modDir in this.GetModFolders(rootPath))
- {
- // read file
- Manifest manifest = null;
- string path = Path.Combine(modDir.FullName, "manifest.json");
- string error = null;
- try
- {
- // read manifest
- manifest = jsonHelper.ReadJsonFile<Manifest>(path);
-
- // validate
- if (manifest == null)
- {
- error = File.Exists(path)
- ? "its manifest is invalid."
- : "it doesn't have a manifest.";
- }
- else if (string.IsNullOrWhiteSpace(manifest.EntryDll))
- error = "its manifest doesn't set an entry DLL.";
- }
- catch (SParseException ex)
- {
- error = $"parsing its manifest failed: {ex.Message}";
- }
- catch (Exception ex)
- {
- error = $"parsing its manifest failed:\n{ex.GetLogSummary()}";
- }
-
- // get internal data record (if any)
- ModDataRecord dataRecord = null;
- if (manifest != null)
- {
- string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll;
- dataRecord = dataRecords.FirstOrDefault(record => record.ID.Matches(key, manifest));
- }
-
- // add default update keys
- if (manifest != null && manifest.UpdateKeys == null && dataRecord?.UpdateKeys != null)
- manifest.UpdateKeys = dataRecord.UpdateKeys;
-
- // build metadata
- string displayName = !string.IsNullOrWhiteSpace(manifest?.Name)
- ? manifest.Name
- : modDir.FullName.Replace(rootPath, "").Trim('/', '\\');
- ModMetadataStatus status = error == null
- ? ModMetadataStatus.Found
- : ModMetadataStatus.Failed;
-
- yield return new ModMetadata(displayName, modDir.FullName, manifest, dataRecord).SetStatus(status, error);
- }
- }
-
- /// <summary>Validate manifest metadata.</summary>
- /// <param name="mods">The mod manifests to validate.</param>
- /// <param name="apiVersion">The current SMAPI version.</param>
- /// <param name="vendorModUrls">Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID).</param>
- public void ValidateManifests(IEnumerable<IModMetadata> mods, ISemanticVersion apiVersion, IDictionary<string, string> vendorModUrls)
- {
- mods = mods.ToArray();
-
- // validate each manifest
- foreach (IModMetadata mod in mods)
- {
- // skip if already failed
- if (mod.Status == ModMetadataStatus.Failed)
- continue;
-
- // validate compatibility
- ModCompatibility compatibility = mod.DataRecord?.GetCompatibility(mod.Manifest.Version);
- switch (compatibility?.Status)
- {
- case ModStatus.Obsolete:
- mod.SetStatus(ModMetadataStatus.Failed, $"it's obsolete: {compatibility.ReasonPhrase}");
- continue;
-
- case ModStatus.AssumeBroken:
- {
- // get reason
- string reasonPhrase = compatibility.ReasonPhrase ?? "it's no longer compatible";
-
- // get update URLs
- List<string> updateUrls = new List<string>();
- foreach (string key in mod.Manifest.UpdateKeys ?? new string[0])
- {
- string[] parts = key.Split(new[] { ':' }, 2);
- if (parts.Length != 2)
- continue;
-
- string vendorKey = parts[0].Trim();
- string modID = parts[1].Trim();
-
- if (vendorModUrls.TryGetValue(vendorKey, out string urlTemplate))
- updateUrls.Add(string.Format(urlTemplate, modID));
- }
- if (mod.DataRecord.AlternativeUrl != null)
- updateUrls.Add(mod.DataRecord.AlternativeUrl);
-
- // build error
- string error = $"{reasonPhrase}. Please check for a ";
- if (mod.Manifest.Version.Equals(compatibility.UpperVersion))
- error += "newer version";
- else
- error += $"version newer than {compatibility.UpperVersion}";
- error += " at " + string.Join(" or ", updateUrls);
-
- mod.SetStatus(ModMetadataStatus.Failed, error);
- }
- continue;
- }
-
- // validate SMAPI version
- if (mod.Manifest.MinimumApiVersion?.IsNewerThan(apiVersion) == true)
- {
- mod.SetStatus(ModMetadataStatus.Failed, $"it needs SMAPI {mod.Manifest.MinimumApiVersion} or later. Please update SMAPI to the latest version to use this mod.");
- continue;
- }
-
- // validate DLL path
- string assemblyPath = Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll);
- if (!File.Exists(assemblyPath))
- {
- mod.SetStatus(ModMetadataStatus.Failed, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist.");
- continue;
- }
-
- // validate required fields
- {
- List<string> missingFields = new List<string>(3);
-
- if (string.IsNullOrWhiteSpace(mod.Manifest.Name))
- missingFields.Add(nameof(IManifest.Name));
- if (mod.Manifest.Version == null || mod.Manifest.Version.ToString() == "0.0")
- missingFields.Add(nameof(IManifest.Version));
- if (string.IsNullOrWhiteSpace(mod.Manifest.UniqueID))
- missingFields.Add(nameof(IManifest.UniqueID));
-
- if (missingFields.Any())
- mod.SetStatus(ModMetadataStatus.Failed, $"its manifest is missing required fields ({string.Join(", ", missingFields)}).");
- }
- }
-
- // validate IDs are unique
- {
- var duplicatesByID = mods
- .GroupBy(mod => mod.Manifest?.UniqueID?.Trim(), mod => mod, StringComparer.InvariantCultureIgnoreCase)
- .Where(p => p.Count() > 1);
- foreach (var group in duplicatesByID)
- {
- foreach (IModMetadata mod in group)
- {
- if (mod.Status == ModMetadataStatus.Failed)
- continue; // don't replace metadata error
- mod.SetStatus(ModMetadataStatus.Failed, $"its unique ID '{mod.Manifest.UniqueID}' is used by multiple mods ({string.Join(", ", group.Select(p => p.DisplayName))}).");
- }
- }
- }
- }
-
- /// <summary>Sort the given mods by the order they should be loaded.</summary>
- /// <param name="mods">The mods to process.</param>
- public IEnumerable<IModMetadata> ProcessDependencies(IEnumerable<IModMetadata> mods)
- {
- // initialise metadata
- mods = mods.ToArray();
- var sortedMods = new Stack<IModMetadata>();
- var states = mods.ToDictionary(mod => mod, mod => ModDependencyStatus.Queued);
-
- // handle failed mods
- foreach (IModMetadata mod in mods.Where(m => m.Status == ModMetadataStatus.Failed))
- {
- states[mod] = ModDependencyStatus.Failed;
- sortedMods.Push(mod);
- }
-
- // sort mods
- foreach (IModMetadata mod in mods)
- this.ProcessDependencies(mods.ToArray(), mod, states, sortedMods, new List<IModMetadata>());
-
- return sortedMods.Reverse();
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Sort a mod's dependencies by the order they should be loaded, and remove any mods that can't be loaded due to missing or conflicting dependencies.</summary>
- /// <param name="mods">The full list of mods being validated.</param>
- /// <param name="mod">The mod whose dependencies to process.</param>
- /// <param name="states">The dependency state for each mod.</param>
- /// <param name="sortedMods">The list in which to save mods sorted by dependency order.</param>
- /// <param name="currentChain">The current change of mod dependencies.</param>
- /// <returns>Returns the mod dependency status.</returns>
- private ModDependencyStatus ProcessDependencies(IModMetadata[] mods, IModMetadata mod, IDictionary<IModMetadata, ModDependencyStatus> states, Stack<IModMetadata> sortedMods, ICollection<IModMetadata> currentChain)
- {
- // check if already visited
- switch (states[mod])
- {
- // already sorted or failed
- case ModDependencyStatus.Sorted:
- case ModDependencyStatus.Failed:
- return states[mod];
-
- // dependency loop
- case ModDependencyStatus.Checking:
- // This should never happen. The higher-level mod checks if the dependency is
- // already being checked, so it can fail without visiting a mod twice. If this
- // case is hit, that logic didn't catch the dependency loop for some reason.
- throw new InvalidModStateException($"A dependency loop was not caught by the calling iteration ({string.Join(" => ", currentChain.Select(p => p.DisplayName))} => {mod.DisplayName})).");
-
- // not visited yet, start processing
- case ModDependencyStatus.Queued:
- break;
-
- // sanity check
- default:
- throw new InvalidModStateException($"Unknown dependency status '{states[mod]}'.");
- }
-
- // no dependencies, mark sorted
- if (mod.Manifest.Dependencies == null || !mod.Manifest.Dependencies.Any())
- {
- sortedMods.Push(mod);
- return states[mod] = ModDependencyStatus.Sorted;
- }
-
- // get dependencies
- var dependencies =
- (
- from entry in mod.Manifest.Dependencies
- let dependencyMod = mods.FirstOrDefault(m => string.Equals(m.Manifest?.UniqueID, entry.UniqueID, StringComparison.InvariantCultureIgnoreCase))
- orderby entry.UniqueID
- select new
- {
- ID = entry.UniqueID,
- MinVersion = entry.MinimumVersion,
- Mod = dependencyMod,
- IsRequired = entry.IsRequired
- }
- )
- .ToArray();
-
- // missing required dependencies, mark failed
- {
- string[] failedIDs = (from entry in dependencies where entry.IsRequired && entry.Mod == null select entry.ID).ToArray();
- if (failedIDs.Any())
- {
- sortedMods.Push(mod);
- mod.SetStatus(ModMetadataStatus.Failed, $"it requires mods which aren't installed ({string.Join(", ", failedIDs)}).");
- return states[mod] = ModDependencyStatus.Failed;
- }
- }
-
- // dependency min version not met, mark failed
- {
- string[] failedLabels =
- (
- from entry in dependencies
- where entry.Mod != null && entry.MinVersion != null && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version)
- select $"{entry.Mod.DisplayName} (needs {entry.MinVersion} or later)"
- )
- .ToArray();
- if (failedLabels.Any())
- {
- sortedMods.Push(mod);
- mod.SetStatus(ModMetadataStatus.Failed, $"it needs newer versions of some mods: {string.Join(", ", failedLabels)}.");
- return states[mod] = ModDependencyStatus.Failed;
- }
- }
-
- // process dependencies
- {
- states[mod] = ModDependencyStatus.Checking;
-
- // recursively sort dependencies
- foreach (var dependency in dependencies)
- {
- IModMetadata requiredMod = dependency.Mod;
- var subchain = new List<IModMetadata>(currentChain) { mod };
-
- // ignore missing optional dependency
- if (!dependency.IsRequired && requiredMod == null)
- continue;
-
- // detect dependency loop
- if (states[requiredMod] == ModDependencyStatus.Checking)
- {
- sortedMods.Push(mod);
- mod.SetStatus(ModMetadataStatus.Failed, $"its dependencies have a circular reference: {string.Join(" => ", subchain.Select(p => p.DisplayName))} => {requiredMod.DisplayName}).");
- return states[mod] = ModDependencyStatus.Failed;
- }
-
- // recursively process each dependency
- var substatus = this.ProcessDependencies(mods, requiredMod, states, sortedMods, subchain);
- switch (substatus)
- {
- // sorted successfully
- case ModDependencyStatus.Sorted:
- break;
-
- // failed, which means this mod can't be loaded either
- case ModDependencyStatus.Failed:
- sortedMods.Push(mod);
- mod.SetStatus(ModMetadataStatus.Failed, $"it needs the '{requiredMod.DisplayName}' mod, which couldn't be loaded.");
- return states[mod] = ModDependencyStatus.Failed;
-
- // unexpected status
- case ModDependencyStatus.Queued:
- case ModDependencyStatus.Checking:
- throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{substatus}' status.");
-
- // sanity check
- default:
- throw new InvalidModStateException($"Unknown dependency status '{states[mod]}'.");
- }
- }
-
- // all requirements sorted successfully
- sortedMods.Push(mod);
- return states[mod] = ModDependencyStatus.Sorted;
- }
- }
-
- /// <summary>Get all mod folders in a root folder, passing through empty folders as needed.</summary>
- /// <param name="rootPath">The root folder path to search.</param>
- private IEnumerable<DirectoryInfo> GetModFolders(string rootPath)
- {
- foreach (string modRootPath in Directory.GetDirectories(rootPath))
- {
- DirectoryInfo directory = new DirectoryInfo(modRootPath);
-
- // if a folder only contains another folder, check the inner folder instead
- while (!directory.GetFiles().Any() && directory.GetDirectories().Length == 1)
- directory = directory.GetDirectories().First();
-
- yield return directory;
- }
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Platform.cs b/src/StardewModdingAPI/Framework/ModLoading/Platform.cs
deleted file mode 100644
index 45e881c4..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Platform.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>The game's platform version.</summary>
- internal enum Platform
- {
- /// <summary>The Linux/Mac version of the game.</summary>
- Mono,
-
- /// <summary>The Windows version of the game.</summary>
- Windows
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/StardewModdingAPI/Framework/ModLoading/PlatformAssemblyMap.cs
deleted file mode 100644
index 463f45e8..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/PlatformAssemblyMap.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Mono.Cecil;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Metadata for mapping assemblies to the current <see cref="Platform"/>.</summary>
- internal class PlatformAssemblyMap
- {
- /*********
- ** Accessors
- *********/
- /****
- ** Data
- ****/
- /// <summary>The target game platform.</summary>
- public readonly Platform TargetPlatform;
-
- /// <summary>The short assembly names to remove as assembly reference, and replace with the <see cref="Targets"/>. These should be short names (like "Stardew Valley").</summary>
- public readonly string[] RemoveNames;
-
- /****
- ** Metadata
- ****/
- /// <summary>The assemblies to target. Equivalent types should be rewritten to use these assemblies.</summary>
- public readonly Assembly[] Targets;
-
- /// <summary>An assembly => reference cache.</summary>
- public readonly IDictionary<Assembly, AssemblyNameReference> TargetReferences;
-
- /// <summary>An assembly => module cache.</summary>
- public readonly IDictionary<Assembly, ModuleDefinition> TargetModules;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="targetPlatform">The target game platform.</param>
- /// <param name="removeAssemblyNames">The assembly short names to remove (like <c>Stardew Valley</c>).</param>
- /// <param name="targetAssemblies">The assemblies to target.</param>
- public PlatformAssemblyMap(Platform targetPlatform, string[] removeAssemblyNames, Assembly[] targetAssemblies)
- {
- // save data
- this.TargetPlatform = targetPlatform;
- this.RemoveNames = removeAssemblyNames;
-
- // cache assembly metadata
- this.Targets = targetAssemblies;
- this.TargetReferences = this.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName));
- this.TargetModules = this.Targets.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.Modules.Single().FullyQualifiedName));
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/RewriteHelper.cs b/src/StardewModdingAPI/Framework/ModLoading/RewriteHelper.cs
deleted file mode 100644
index 56a60a72..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/RewriteHelper.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Linq;
-using System.Reflection;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading
-{
- /// <summary>Provides helper methods for field rewriters.</summary>
- internal static class RewriteHelper
- {
- /*********
- ** Public methods
- *********/
- /// <summary>Get the field reference from an instruction if it matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- public static FieldReference AsFieldReference(Instruction instruction)
- {
- return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld
- ? (FieldReference)instruction.Operand
- : null;
- }
-
- /// <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)
- {
- return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt
- ? (MethodReference)instruction.Operand
- : null;
- }
-
- /// <summary>Get whether a type matches a type reference.</summary>
- /// <param name="type">The defined type.</param>
- /// <param name="reference">The type reference.</param>
- public static bool IsSameType(Type type, TypeReference reference)
- {
- // same namespace & name
- if (type.Namespace != reference.Namespace || type.Name != reference.Name)
- return false;
-
- // same generic parameters
- if (type.IsGenericType)
- {
- if (!reference.IsGenericInstance)
- return false;
-
- Type[] defGenerics = type.GetGenericArguments();
- TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray();
- if (defGenerics.Length != refGenerics.Length)
- return false;
- for (int i = 0; i < defGenerics.Length; i++)
- {
- if (!RewriteHelper.IsSameType(defGenerics[i], refGenerics[i]))
- return false;
- }
- }
-
- return true;
- }
-
- /// <summary>Get whether a method definition matches the signature expected by a method reference.</summary>
- /// <param name="definition">The method definition.</param>
- /// <param name="reference">The method reference.</param>
- public static bool HasMatchingSignature(MethodInfo definition, MethodReference reference)
- {
- // same name
- if (definition.Name != reference.Name)
- return false;
-
- // same arguments
- ParameterInfo[] definitionParameters = definition.GetParameters();
- ParameterDefinition[] referenceParameters = reference.Parameters.ToArray();
- if (referenceParameters.Length != definitionParameters.Length)
- return false;
- for (int i = 0; i < referenceParameters.Length; i++)
- {
- if (!RewriteHelper.IsSameType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType))
- return false;
- }
- return true;
- }
-
- /// <summary>Get whether a type has a method whose signature matches the one expected by a method reference.</summary>
- /// <param name="type">The type to check.</param>
- /// <param name="reference">The method reference.</param>
- public static bool HasMatchingSignature(Type type, MethodReference reference)
- {
- return type
- .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public)
- .Any(method => RewriteHelper.HasMatchingSignature(method, reference));
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs
deleted file mode 100644
index 63358b39..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Reflection;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using StardewModdingAPI.Framework.ModLoading.Finders;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites references to one field with another.</summary>
- internal class FieldReplaceRewriter : FieldFinder
- {
- /*********
- ** Properties
- *********/
- /// <summary>The new field to reference.</summary>
- private readonly FieldInfo ToField;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="type">The type whose field to which references should be rewritten.</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)
- : base(type.FullName, fromFieldName, InstructionHandleResult.None)
- {
- this.ToField = type.GetField(toFieldName);
- if (this.ToField == null)
- throw new InvalidOperationException($"The {type.FullName} class doesn't have a {toFieldName} field.");
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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 override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- if (!this.IsMatch(instruction))
- return InstructionHandleResult.None;
-
- FieldReference newRef = module.Import(this.ToField);
- cil.Replace(instruction, cil.Create(instruction.OpCode, newRef));
- return InstructionHandleResult.Rewritten;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs
deleted file mode 100644
index a20b8bee..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using StardewModdingAPI.Framework.ModLoading.Finders;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites field references into property references.</summary>
- internal class FieldToPropertyRewriter : FieldFinder
- {
- /*********
- ** Properties
- *********/
- /// <summary>The type whose field to which references should be rewritten.</summary>
- private readonly Type Type;
-
- /// <summary>The field name to rewrite.</summary>
- private readonly string FieldName;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="type">The type whose field to which references should be rewritten.</param>
- /// <param name="fieldName">The field name to rewrite.</param>
- public FieldToPropertyRewriter(Type type, string fieldName)
- : base(type.FullName, fieldName, InstructionHandleResult.None)
- {
- this.Type = type;
- this.FieldName = fieldName;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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 override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- if (!this.IsMatch(instruction))
- return InstructionHandleResult.None;
-
- string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set";
- MethodReference propertyRef = module.Import(this.Type.GetMethod($"{methodPrefix}_{this.FieldName}"));
- cil.Replace(instruction, cil.Create(OpCodes.Call, propertyRef));
- return InstructionHandleResult.Rewritten;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs
deleted file mode 100644
index 974fcf4c..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites method references from one parent type to another if the signatures match.</summary>
- internal class MethodParentRewriter : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The type whose methods to remap.</summary>
- private readonly Type FromType;
-
- /// <summary>The type with methods to map to.</summary>
- private readonly Type ToType;
-
- /// <summary>Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</summary>
- private readonly bool OnlyIfPlatformChanged;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fromType">The type whose methods to remap.</param>
- /// <param name="toType">The type with methods to map to.</param>
- /// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param>
- public MethodParentRewriter(Type fromType, Type toType, bool onlyIfPlatformChanged = false)
- {
- this.FromType = fromType;
- this.ToType = toType;
- this.NounPhrase = $"{fromType.Name} methods";
- this.OnlyIfPlatformChanged = onlyIfPlatformChanged;
- }
-
- /// <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 InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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)
- {
- if (!this.IsMatch(instruction, platformChanged))
- return InstructionHandleResult.None;
-
- MethodReference methodRef = (MethodReference)instruction.Operand;
- methodRef.DeclaringType = module.Import(this.ToType);
- return InstructionHandleResult.Rewritten;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- protected bool IsMatch(Instruction instruction, bool platformChanged)
- {
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- return
- methodRef != null
- && (platformChanged || !this.OnlyIfPlatformChanged)
- && methodRef.DeclaringType.FullName == this.FromType.FullName
- && RewriteHelper.HasMatchingSignature(this.ToType, methodRef);
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/StardewModdingAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs
deleted file mode 100644
index 74f2fcdd..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs
+++ /dev/null
@@ -1,154 +0,0 @@
-using System;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using StardewModdingAPI.Framework.ModLoading.Finders;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites all references to a type.</summary>
- internal class TypeReferenceRewriter : TypeFinder
- {
- /*********
- ** Properties
- *********/
- /// <summary>The full type name to which to find references.</summary>
- private readonly string FromTypeName;
-
- /// <summary>The new type to reference.</summary>
- private readonly Type ToType;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="fromTypeFullName">The full type name to which to find references.</param>
- /// <param name="toType">The new type to reference.</param>
- public TypeReferenceRewriter(string fromTypeFullName, Type toType)
- : base(fromTypeFullName, InstructionHandleResult.None)
- {
- this.FromTypeName = fromTypeFullName;
- this.ToType = toType;
- }
-
- /// <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 override InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- bool rewritten = false;
-
- // return type
- if (this.IsMatch(method.ReturnType))
- {
- method.ReturnType = this.RewriteIfNeeded(module, method.ReturnType);
- rewritten = true;
- }
-
- // parameters
- foreach (ParameterDefinition parameter in method.Parameters)
- {
- if (this.IsMatch(parameter.ParameterType))
- {
- parameter.ParameterType = this.RewriteIfNeeded(module, parameter.ParameterType);
- rewritten = true;
- }
- }
-
- // generic parameters
- for (int i = 0; i < method.GenericParameters.Count; i++)
- {
- var parameter = method.GenericParameters[i];
- if (this.IsMatch(parameter))
- {
- TypeReference newType = this.RewriteIfNeeded(module, parameter);
- if (newType != parameter)
- method.GenericParameters[i] = new GenericParameter(parameter.Name, newType);
- rewritten = true;
- }
- }
-
- // local variables
- foreach (VariableDefinition variable in method.Body.Variables)
- {
- if (this.IsMatch(variable.VariableType))
- {
- variable.VariableType = this.RewriteIfNeeded(module, variable.VariableType);
- rewritten = true;
- }
- }
-
- return rewritten
- ? InstructionHandleResult.Rewritten
- : InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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 override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- if (!this.IsMatch(instruction) && !instruction.ToString().Contains(this.FromTypeName))
- return InstructionHandleResult.None;
-
- // field reference
- FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
- if (fieldRef != null)
- {
- fieldRef.DeclaringType = this.RewriteIfNeeded(module, fieldRef.DeclaringType);
- fieldRef.FieldType = this.RewriteIfNeeded(module, fieldRef.FieldType);
- }
-
- // method reference
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- if (methodRef != null)
- {
- methodRef.DeclaringType = this.RewriteIfNeeded(module, methodRef.DeclaringType);
- methodRef.ReturnType = this.RewriteIfNeeded(module, methodRef.ReturnType);
- foreach (var parameter in methodRef.Parameters)
- parameter.ParameterType = this.RewriteIfNeeded(module, parameter.ParameterType);
- }
-
- // type reference
- if (instruction.Operand is TypeReference typeRef)
- {
- TypeReference newRef = this.RewriteIfNeeded(module, typeRef);
- if (typeRef != newRef)
- cil.Replace(instruction, cil.Create(instruction.OpCode, newRef));
- }
-
- return InstructionHandleResult.Rewritten;
- }
-
- /*********
- ** Private methods
- *********/
- /// <summary>Get the adjusted type reference if it matches, else the same value.</summary>
- /// <param name="module">The assembly module containing the instruction.</param>
- /// <param name="type">The type to replace if it matches.</param>
- private TypeReference RewriteIfNeeded(ModuleDefinition module, TypeReference type)
- {
- // root type
- if (type.FullName == this.FromTypeName)
- return module.Import(this.ToType);
-
- // generic arguments
- if (type is GenericInstanceType genericType)
- {
- for (int i = 0; i < genericType.GenericArguments.Count; i++)
- genericType.GenericArguments[i] = this.RewriteIfNeeded(module, genericType.GenericArguments[i]);
- }
-
- // generic parameters (e.g. constraints)
- for (int i = 0; i < type.GenericParameters.Count; i++)
- type.GenericParameters[i] = new GenericParameter(this.RewriteIfNeeded(module, type.GenericParameters[i]));
-
- return type;
- }
- }
-}
diff --git a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/VirtualEntryCallRemover.cs b/src/StardewModdingAPI/Framework/ModLoading/Rewriters/VirtualEntryCallRemover.cs
deleted file mode 100644
index 322a7df1..00000000
--- a/src/StardewModdingAPI/Framework/ModLoading/Rewriters/VirtualEntryCallRemover.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-
-namespace StardewModdingAPI.Framework.ModLoading.Rewriters
-{
- /// <summary>Rewrites virtual calls to the <see cref="Mod.Entry"/> method.</summary>
- internal class VirtualEntryCallRemover : IInstructionHandler
- {
- /*********
- ** Properties
- *********/
- /// <summary>The type containing the method.</summary>
- private readonly Type ToType;
-
- /// <summary>The name of the method.</summary>
- private readonly string MethodName;
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>A brief noun phrase indicating what the instruction finder matches.</summary>
- public string NounPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public VirtualEntryCallRemover()
- {
- this.ToType = typeof(Mod);
- this.MethodName = nameof(Mod.Entry);
- this.NounPhrase = $"{this.ToType.Name}::{this.MethodName}";
- }
-
- /// <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 InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
- {
- return InstructionHandleResult.None;
- }
-
- /// <summary>Perform the predefined logic for an instruction if applicable.</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)
- {
- if (!this.IsMatch(instruction))
- return InstructionHandleResult.None;
-
- // get instructions comprising method call
- int index = cil.Body.Instructions.IndexOf(instruction);
- Instruction loadArg0 = cil.Body.Instructions[index - 2];
- Instruction loadArg1 = cil.Body.Instructions[index - 1];
- if (loadArg0.OpCode != OpCodes.Ldarg_0)
- throw new InvalidOperationException($"Unexpected instruction sequence while removing virtual {this.ToType.Name}.{this.MethodName} call: found {loadArg0.OpCode.Name} instead of {OpCodes.Ldarg_0.Name}");
- if (loadArg1.OpCode != OpCodes.Ldarg_1)
- throw new InvalidOperationException($"Unexpected instruction sequence while removing virtual {this.ToType.Name}.{this.MethodName} call: found {loadArg1.OpCode.Name} instead of {OpCodes.Ldarg_1.Name}");
-
- // remove method call
- cil.Remove(loadArg0);
- cil.Remove(loadArg1);
- cil.Remove(instruction);
- return InstructionHandleResult.Rewritten;
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Get whether a CIL instruction matches.</summary>
- /// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
- {
- MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
- return
- methodRef != null
- && methodRef.DeclaringType.FullName == this.ToType.FullName
- && methodRef.Name == this.MethodName;
- }
- }
-}