diff options
-rw-r--r-- | src/StardewModdingAPI/Framework/ModAssemblyLoader.cs | 82 | ||||
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 34 |
2 files changed, 94 insertions, 22 deletions
diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs index 2615e1f7..6c0f0cdf 100644 --- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; @@ -6,7 +7,7 @@ using Mono.Cecil; namespace StardewModdingAPI.Framework { - /// <summary>Loads mod assemblies.</summary> + /// <summary>Preprocesses and loads mod assemblies.</summary> internal class ModAssemblyLoader { /********* @@ -26,19 +27,17 @@ namespace StardewModdingAPI.Framework this.CacheDirPath = cacheDirPath; } - /// <summary>Read an assembly from the given path.</summary> + /// <summary>Preprocess an assembly and cache the modified version.</summary> /// <param name="assemblyPath">The assembly file path.</param> - public Assembly ProcessAssembly(string assemblyPath) + public void ProcessAssembly(string assemblyPath) { // read assembly data byte[] assemblyBytes = File.ReadAllBytes(assemblyPath); byte[] hash = MD5.Create().ComputeHash(assemblyBytes); - // get cache data - string key = Path.GetFileNameWithoutExtension(assemblyPath); - string cachePath = Path.Combine(this.CacheDirPath, $"{key}.dll"); - string cacheHashPath = Path.Combine(this.CacheDirPath, $"{key}-hash.txt"); - bool canUseCache = File.Exists(cachePath) && File.Exists(cacheHashPath) && hash.SequenceEqual(File.ReadAllBytes(cacheHashPath)); + // check cache + CachePaths cachePaths = this.GetCacheInfo(assemblyPath); + bool canUseCache = File.Exists(cachePaths.Assembly) && File.Exists(cachePaths.Hash) && hash.SequenceEqual(File.ReadAllBytes(cachePaths.Hash)); // process assembly if not cached if (!canUseCache) @@ -53,13 +52,70 @@ namespace StardewModdingAPI.Framework { definition.Write(outStream); byte[] outBytes = outStream.ToArray(); - File.WriteAllBytes(cachePath, outBytes); - File.WriteAllBytes(cacheHashPath, hash); + Directory.CreateDirectory(cachePaths.Directory); + File.WriteAllBytes(cachePaths.Assembly, outBytes); + File.WriteAllBytes(cachePaths.Hash, hash); } } + } + + /// <summary>Load a preprocessed assembly.</summary> + /// <param name="assemblyPath">The assembly file path.</param> + public Assembly LoadCachedAssembly(string assemblyPath) + { + CachePaths cachePaths = this.GetCacheInfo(assemblyPath); + if (!File.Exists(cachePaths.Assembly)) + throw new InvalidOperationException($"The assembly {assemblyPath} doesn't exist in the preprocessed cache."); + return Assembly.UnsafeLoadFrom(cachePaths.Assembly); + } - // load assembly - return Assembly.UnsafeLoadFrom(cachePath); + + /********* + ** Private methods + *********/ + /// <summary>Get the cache details for an assembly.</summary> + /// <param name="assemblyPath">The assembly file path.</param> + private CachePaths GetCacheInfo(string assemblyPath) + { + string key = Path.GetFileNameWithoutExtension(assemblyPath); + string dirPath = Path.Combine(this.CacheDirPath, new DirectoryInfo(Path.GetDirectoryName(assemblyPath)).Name); + string cacheAssemblyPath = Path.Combine(dirPath, $"{key}.dll"); + string cacheHashPath = Path.Combine(dirPath, $"{key}.hash"); + return new CachePaths(dirPath, cacheAssemblyPath, cacheHashPath); + } + + /********* + ** Private objects + *********/ + /// <summary>Contains the paths for an assembly's cached data.</summary> + private 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 MD5 hash for the assembly.</summary> + public string Hash { 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="hash">The file path containing the MD5 hash for the assembly.</param> + public CachePaths(string directory, string assembly, string hash) + { + this.Directory = directory; + this.Assembly = assembly; + this.Hash = hash; + } } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 255ad365..f8920896 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -301,6 +301,10 @@ namespace StardewModdingAPI ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CachePath); foreach (string directory in Directory.GetDirectories(Program.ModPath)) { + // ignore internal directory + if (new DirectoryInfo(directory).Name == ".cache") + continue; + // check for cancellation if (Program.CancellationTokenSource.IsCancellationRequested) { @@ -388,20 +392,32 @@ namespace StardewModdingAPI } } + // preprocess mod assemblies + { + bool succeeded = true; + foreach (string assemblyPath in Directory.GetFiles(directory, "*.dll")) + { + try + { + modAssemblyLoader.ProcessAssembly(assemblyPath); + } + catch (Exception ex) + { + Program.Monitor.Log($"{errorPrefix}: an error occurred while preprocessing '{assemblyPath}'.\n{ex}", LogLevel.Error); + succeeded = false; + break; + } + } + if (!succeeded) + continue; + } + // load assembly Assembly modAssembly; try { - // get assembly path string assemblyPath = Path.Combine(directory, manifest.EntryDll); - if (!File.Exists(assemblyPath)) - { - Program.Monitor.Log($"{errorPrefix}: target DLL '{assemblyPath}' does not exist.", LogLevel.Error); - continue; - } - - // read assembly - modAssembly = modAssemblyLoader.ProcessAssembly(assemblyPath); + modAssembly = modAssemblyLoader.LoadCachedAssembly(assemblyPath); if (modAssembly.DefinedTypes.Count(x => x.BaseType == typeof(Mod)) == 0) { Program.Monitor.Log($"{errorPrefix}: the mod DLL does not contain an implementation of the 'Mod' class.", LogLevel.Error); |