using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using Mono.Cecil; namespace StardewModdingAPI.Framework { /// Loads mod assemblies. internal class ModAssemblyLoader { /********* ** Properties *********/ /// The directory in which to cache data. private readonly string CacheDirPath; /********* ** Public methods *********/ /// Construct an instance. /// The cache directory. public ModAssemblyLoader(string cacheDirPath) { this.CacheDirPath = cacheDirPath; } /// Read an assembly from the given path. /// The assembly file path. public Assembly 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)); // process assembly if not cached if (!canUseCache) { // read assembly definition AssemblyDefinition definition; using (Stream readStream = new MemoryStream(assemblyBytes)) definition = AssemblyDefinition.ReadAssembly(readStream); // write cache using (MemoryStream outStream = new MemoryStream()) { definition.Write(outStream); byte[] outBytes = outStream.ToArray(); File.WriteAllBytes(cachePath, outBytes); File.WriteAllBytes(cacheHashPath, hash); } } // load assembly return Assembly.UnsafeLoadFrom(cachePath); } } }