summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/AssemblyRewriting
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-02-02 23:22:54 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-02-02 23:22:54 -0500
commit6ee14ecfbfd4abcb78b2c8db6ac220981e019f32 (patch)
tree92d8a6dedd490c72ea5c57880e18ac6ff9ecca13 /src/StardewModdingAPI/Framework/AssemblyRewriting
parentae7d9d6bc484bd27922e6652d116ce7ddd4b8104 (diff)
downloadSMAPI-6ee14ecfbfd4abcb78b2c8db6ac220981e019f32.tar.gz
SMAPI-6ee14ecfbfd4abcb78b2c8db6ac220981e019f32.tar.bz2
SMAPI-6ee14ecfbfd4abcb78b2c8db6ac220981e019f32.zip
rewrite mod assembly loading (#229)
This greatly simplifies mod loading, eliminates the .cache folders by loading assemblies in memory, ensures DLLs are loaded in leaf-to-root order, and reduces log verbosity. These changes should address a range of issues, notably #221 and #226.
Diffstat (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting')
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs162
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs61
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs33
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs49
4 files changed, 0 insertions, 305 deletions
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
deleted file mode 100644
index 9d4d6b11..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Mono.Cecil;
-using Mono.Cecil.Cil;
-using Mono.Cecil.Rocks;
-using StardewModdingAPI.AssemblyRewriters;
-
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Rewrites type references.</summary>
- internal class AssemblyTypeRewriter
- {
- /*********
- ** Properties
- *********/
- /// <summary>Metadata for mapping assemblies to the current <see cref="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;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="assemblyMap">Metadata for mapping assemblies to the current <see cref="Platform"/>.</param>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- public AssemblyTypeRewriter(PlatformAssemblyMap assemblyMap, IMonitor monitor)
- {
- // save config
- this.AssemblyMap = assemblyMap;
- this.Monitor = monitor;
-
- // collect type => assembly lookup
- this.TypeAssemblies = new Dictionary<string, Assembly>();
- foreach (Assembly assembly in 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>Rewrite the types referenced by an assembly.</summary>
- /// <param name="assembly">The assembly to rewrite.</param>
- /// <returns>Returns whether the assembly was modified.</returns>
- public bool RewriteAssembly(AssemblyDefinition assembly)
- {
- ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module
-
- // remove old assembly references
- bool shouldRewrite = false;
- for (int i = 0; i < module.AssemblyReferences.Count; i++)
- {
- if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
- {
- this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace);
- shouldRewrite = true;
- module.AssemblyReferences.RemoveAt(i);
- i--;
- }
- }
- if (!shouldRewrite)
- return false;
-
- // add target assembly references
- foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
- {
- this.Monitor.Log($" adding reference to {target}", LogLevel.Trace);
- module.AssemblyReferences.Add(target);
- }
-
- // rewrite type scopes to use target assemblies
- IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
- string lastTypeLogged = null;
- foreach (TypeReference type in typeReferences)
- {
- this.ChangeTypeScope(type, shouldLog: type.FullName != lastTypeLogged);
- lastTypeLogged = type.FullName;
- }
-
- // rewrite incompatible methods
- IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray();
- foreach (MethodDefinition method in this.GetMethods(module))
- {
- // skip methods with no rewritable method
- bool hasMethodToRewrite = method.Body.Instructions.Any(op => (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) && methodRewriters.Any(rewriter => rewriter.ShouldRewrite((MethodReference)op.Operand)));
- if (!hasMethodToRewrite)
- continue;
-
- // rewrite method references
- method.Body.SimplifyMacros();
- ILProcessor cil = method.Body.GetILProcessor();
- Instruction[] instructions = cil.Body.Instructions.ToArray();
- foreach (Instruction op in instructions)
- {
- if (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt)
- {
- IMethodRewriter rewriter = methodRewriters.FirstOrDefault(p => p.ShouldRewrite((MethodReference)op.Operand));
- if (rewriter != null)
- {
- MethodReference methodRef = (MethodReference)op.Operand;
- this.Monitor.Log($"rewriting method reference {methodRef.DeclaringType.FullName}.{methodRef.Name}", LogLevel.Trace);
- rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap);
- }
- }
- }
- method.Body.OptimizeMacros();
- }
- return true;
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Get the correct reference to use for compatibility with the current platform.</summary>
- /// <param name="type">The type reference to rewrite.</param>
- /// <param name="shouldLog">Whether to log a message.</param>
- private void ChangeTypeScope(TypeReference type, bool shouldLog)
- {
- // check skip conditions
- if (type == null || type.FullName.StartsWith("System."))
- return;
-
- // get assembly
- Assembly assembly;
- if (!this.TypeAssemblies.TryGetValue(type.FullName, out assembly))
- return;
-
- // replace scope
- AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly];
- if (shouldLog)
- this.Monitor.Log($"redirecting {type.FullName} from {type.Scope.Name} to {assemblyRef.Name}", LogLevel.Trace);
- 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/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
deleted file mode 100644
index 4c3b86fe..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.IO;
-using StardewModdingAPI.AssemblyRewriters;
-
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Represents cached metadata for a rewritten assembly.</summary>
- internal class CacheEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The MD5 hash for the original assembly.</summary>
- public readonly string Hash;
-
- /// <summary>The SMAPI version used to rewrite the assembly.</summary>
- public readonly string ApiVersion;
-
- /// <summary>The target platform.</summary>
- public readonly Platform Platform;
-
- /// <summary>The <see cref="System.Environment.MachineName"/> value for the machine used to rewrite the assembly.</summary>
- public readonly string MachineName;
-
- /// <summary>Whether to use the cached assembly instead of the original assembly.</summary>
- public readonly bool UseCachedAssembly;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="hash">The MD5 hash for the original assembly.</param>
- /// <param name="apiVersion">The SMAPI version used to rewrite the assembly.</param>
- /// <param name="platform">The target platform.</param>
- /// <param name="machineName">The <see cref="System.Environment.MachineName"/> value for the machine used to rewrite the assembly.</param>
- /// <param name="useCachedAssembly">Whether to use the cached assembly instead of the original assembly.</param>
- public CacheEntry(string hash, string apiVersion, Platform platform, string machineName, bool useCachedAssembly)
- {
- this.Hash = hash;
- this.ApiVersion = apiVersion;
- this.Platform = platform;
- this.MachineName = machineName;
- this.UseCachedAssembly = useCachedAssembly;
- }
-
- /// <summary>Get whether the cache entry is up-to-date for the given assembly hash.</summary>
- /// <param name="paths">The paths for the cached assembly.</param>
- /// <param name="hash">The MD5 hash of the original assembly.</param>
- /// <param name="currentVersion">The current SMAPI version.</param>
- /// <param name="platform">The target platform.</param>
- /// <param name="machineName">The <see cref="System.Environment.MachineName"/> value for the machine reading the assembly.</param>
- public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion, Platform platform, string machineName)
- {
- return hash == this.Hash
- && this.ApiVersion == currentVersion.ToString()
- && this.Platform == platform
- && this.MachineName == machineName
- && (!this.UseCachedAssembly || File.Exists(paths.Assembly));
- }
- }
-} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs
deleted file mode 100644
index 18861873..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Contains the paths for an assembly's cached data.</summary>
- internal struct CachePaths
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The directory path which contains the assembly.</summary>
- public string Directory { get; }
-
- /// <summary>The file path of the assembly file.</summary>
- public string Assembly { get; }
-
- /// <summary>The file path containing the assembly metadata.</summary>
- public string Metadata { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="directory">The directory path which contains the assembly.</param>
- /// <param name="assembly">The file path of the assembly file.</param>
- /// <param name="metadata">The file path containing the assembly metadata.</param>
- public CachePaths(string directory, string assembly, string metadata)
- {
- this.Directory = directory;
- this.Assembly = assembly;
- this.Metadata = metadata;
- }
- }
-} \ No newline at end of file
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs
deleted file mode 100644
index 8f34bb20..00000000
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-namespace StardewModdingAPI.Framework.AssemblyRewriting
-{
- /// <summary>Metadata about a preprocessed assembly.</summary>
- internal class RewriteResult
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The original assembly path.</summary>
- public readonly string OriginalAssemblyPath;
-
- /// <summary>The cache paths.</summary>
- public readonly CachePaths CachePaths;
-
- /// <summary>The rewritten assembly bytes.</summary>
- public readonly byte[] AssemblyBytes;
-
- /// <summary>The MD5 hash for the original assembly.</summary>
- public readonly string Hash;
-
- /// <summary>Whether to use the cached assembly instead of the original assembly.</summary>
- public readonly bool UseCachedAssembly;
-
- /// <summary>Whether this data is newer than the cache.</summary>
- public readonly bool IsNewerThanCache;
-
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="originalAssemblyPath"></param>
- /// <param name="cachePaths">The cache paths.</param>
- /// <param name="assemblyBytes">The rewritten assembly bytes.</param>
- /// <param name="hash">The MD5 hash for the original assembly.</param>
- /// <param name="useCachedAssembly">Whether to use the cached assembly instead of the original assembly.</param>
- /// <param name="isNewerThanCache">Whether this data is newer than the cache.</param>
- public RewriteResult(string originalAssemblyPath, CachePaths cachePaths, byte[] assemblyBytes, string hash, bool useCachedAssembly, bool isNewerThanCache)
- {
- this.OriginalAssemblyPath = originalAssemblyPath;
- this.CachePaths = cachePaths;
- this.Hash = hash;
- this.AssemblyBytes = assemblyBytes;
- this.UseCachedAssembly = useCachedAssembly;
- this.IsNewerThanCache = isNewerThanCache;
- }
- }
-}