diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-02-02 23:22:54 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-02-02 23:22:54 -0500 |
commit | 6ee14ecfbfd4abcb78b2c8db6ac220981e019f32 (patch) | |
tree | 92d8a6dedd490c72ea5c57880e18ac6ff9ecca13 /src/StardewModdingAPI/Framework/AssemblyRewriting | |
parent | ae7d9d6bc484bd27922e6652d116ce7ddd4b8104 (diff) | |
download | SMAPI-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')
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; - } - } -} |