summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
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/ModAssemblyLoader.cs
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/ModAssemblyLoader.cs')
-rw-r--r--src/StardewModdingAPI/Framework/ModAssemblyLoader.cs143
1 files changed, 0 insertions, 143 deletions
diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
deleted file mode 100644
index e4760398..00000000
--- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Security.Cryptography;
-using Mono.Cecil;
-using Newtonsoft.Json;
-using StardewModdingAPI.AssemblyRewriters;
-using StardewModdingAPI.Framework.AssemblyRewriting;
-
-namespace StardewModdingAPI.Framework
-{
- /// <summary>Preprocesses and loads mod assemblies.</summary>
- internal class ModAssemblyLoader
- {
- /*********
- ** Properties
- *********/
- /// <summary>The name of the directory containing a mod's cached data.</summary>
- private readonly string CacheDirName;
-
- /// <summary>Metadata for mapping assemblies to the current <see cref="Platform"/>.</summary>
- private readonly PlatformAssemblyMap AssemblyMap;
-
- /// <summary>Rewrites assembly types to match the current platform.</summary>
- private readonly AssemblyTypeRewriter AssemblyTypeRewriter;
-
- /// <summary>Encapsulates monitoring and logging.</summary>
- private readonly IMonitor Monitor;
-
- /// <summary>The current game platform.</summary>
- private readonly Platform TargetPlatform;
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="cacheDirName">The name of the directory containing a mod's cached data.</param>
- /// <param name="targetPlatform">The current game platform.</param>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- public ModAssemblyLoader(string cacheDirName, Platform targetPlatform, IMonitor monitor)
- {
- this.CacheDirName = cacheDirName;
- this.TargetPlatform = targetPlatform;
- this.Monitor = monitor;
- this.AssemblyMap = Constants.GetAssemblyMap(targetPlatform);
- this.AssemblyTypeRewriter = new AssemblyTypeRewriter(this.AssemblyMap, monitor);
- }
-
- /// <summary>Preprocess an assembly unless the cache is up to date.</summary>
- /// <param name="assemblyPath">The assembly file path.</param>
- /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
- public RewriteResult ProcessAssemblyUnlessCached(string assemblyPath)
- {
- // read assembly data
- byte[] assemblyBytes = File.ReadAllBytes(assemblyPath);
- string hash = string.Join("", MD5.Create().ComputeHash(assemblyBytes).Select(p => p.ToString("X2")));
-
- // get cached result if current
- CachePaths cachePaths = this.GetCachePaths(assemblyPath);
- {
- CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject<CacheEntry>(File.ReadAllText(cachePaths.Metadata)) : null;
- if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion, this.TargetPlatform, Environment.MachineName))
- return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed
- }
- this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace);
-
- // rewrite assembly
- AssemblyDefinition assembly;
- using (Stream readStream = new MemoryStream(assemblyBytes))
- assembly = AssemblyDefinition.ReadAssembly(readStream);
- bool modified = this.AssemblyTypeRewriter.RewriteAssembly(assembly);
- using (MemoryStream outStream = new MemoryStream())
- {
- assembly.Write(outStream);
- byte[] outBytes = outStream.ToArray();
- return new RewriteResult(assemblyPath, cachePaths, outBytes, hash, useCachedAssembly: modified, isNewerThanCache: true);
- }
- }
-
- /// <summary>Write rewritten assembly metadata to the cache for a mod.</summary>
- /// <param name="results">The rewrite results.</param>
- /// <param name="forceCacheAssemblies">Whether to write all assemblies to the cache, even if they weren't modified.</param>
- /// <exception cref="InvalidOperationException">There are no results to write, or the results are not all for the same directory.</exception>
- public void WriteCache(IEnumerable<RewriteResult> results, bool forceCacheAssemblies)
- {
- results = results.ToArray();
-
- // get cache directory
- if (!results.Any())
- throw new InvalidOperationException("There are no assemblies to cache.");
- if (results.Select(p => p.CachePaths.Directory).Distinct().Count() > 1)
- throw new InvalidOperationException("The assemblies can't be cached together because they have different source directories.");
- string cacheDir = results.Select(p => p.CachePaths.Directory).First();
-
- // reset cache
- if (Directory.Exists(cacheDir))
- Directory.Delete(cacheDir, recursive: true);
- Directory.CreateDirectory(cacheDir);
-
- // cache all results
- foreach (RewriteResult result in results)
- {
- CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), this.TargetPlatform, Environment.MachineName, forceCacheAssemblies || result.UseCachedAssembly);
- File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry));
- if (forceCacheAssemblies || result.UseCachedAssembly)
- File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes);
- }
- }
-
- /// <summary>Resolve an assembly from 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
- *********/
- /// <summary>Get the cache details for an assembly.</summary>
- /// <param name="assemblyPath">The assembly file path.</param>
- private CachePaths GetCachePaths(string assemblyPath)
- {
- string fileName = Path.GetFileName(assemblyPath);
- string dirPath = Path.Combine(Path.GetDirectoryName(assemblyPath), this.CacheDirName);
- string cacheAssemblyPath = Path.Combine(dirPath, fileName);
- string metadataPath = Path.Combine(dirPath, $"{fileName}.json");
- return new CachePaths(dirPath, cacheAssemblyPath, metadataPath);
- }
- }
-}