From f7b8879011873fa8f7a3d5dd7db27254bfc90469 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Nov 2016 15:56:47 -0500 Subject: supplement assembly resolution for Mono (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 21 +++++-------- .../AssemblyRewriting/PlatformAssemblyMap.cs | 35 ++++++++++++++++++++++ .../Framework/ModAssemblyLoader.cs | 20 ++++++++++--- src/StardewModdingAPI/Program.cs | 4 ++- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 5 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs (limited to 'src') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 7081df15..66c36c03 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -11,11 +11,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /********* ** Properties *********/ - /// The assemblies to target. Equivalent types will be rewritten to use these assemblies. - private readonly Assembly[] TargetAssemblies; - - /// >The short assembly names to remove as assembly reference, and replace with the . - private readonly string[] RemoveAssemblyNames; + /// Metadata for mapping assemblies to the current . + private readonly PlatformAssemblyMap AssemblyMap; /// A type => assembly lookup for types which should be rewritten. private readonly IDictionary TypeAssemblies; @@ -31,22 +28,20 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting ** Public methods *********/ /// Construct an instance. - /// The assembly filenames to target. Equivalent types will be rewritten to use these assemblies. - /// The short assembly names to remove as assembly reference, and replace with the . + /// Metadata for mapping assemblies to the current . /// Encapsulates monitoring and logging. - public AssemblyTypeRewriter(Assembly[] targetAssemblies, string[] removeAssemblyNames, IMonitor monitor) + public AssemblyTypeRewriter(PlatformAssemblyMap assemblyMap, IMonitor monitor) { // save config - this.TargetAssemblies = targetAssemblies; - this.RemoveAssemblyNames = removeAssemblyNames; + this.AssemblyMap = assemblyMap; this.Monitor = monitor; // cache assembly metadata - this.AssemblyNameReferences = targetAssemblies.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); + this.AssemblyNameReferences = assemblyMap.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); // collect type => assembly lookup this.TypeAssemblies = new Dictionary(); - foreach (Assembly assembly in targetAssemblies) + foreach (Assembly assembly in assemblyMap.Targets) { foreach (Module assemblyModule in assembly.Modules) { @@ -73,7 +68,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting // remove old assembly references for (int i = 0; i < module.AssemblyReferences.Count; i++) { - bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name); + bool shouldRemove = this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name); if (shouldRemove) { this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace); diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs new file mode 100644 index 00000000..3e6cec8a --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs @@ -0,0 +1,35 @@ +using System.Reflection; + +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Metadata for mapping assemblies to the current . + internal class PlatformAssemblyMap + { + /********* + ** Accessors + *********/ + /// The target game platform. + public readonly Platform TargetPlatform; + + /// The short assembly names to remove as assembly reference, and replace with the . These should be short names (like "Stardew Valley"). + public readonly string[] RemoveNames; + + /// The assembly filenames to target. Equivalent types should be rewritten to use these assemblies. + public readonly Assembly[] Targets; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The target game platform. + /// The assembly short names to remove (like Stardew Valley). + /// The assemblies to target. + public PlatformAssemblyMap(Platform targetPlatform, string[] removeAssemblyNames, Assembly[] targetAssemblies) + { + this.TargetPlatform = targetPlatform; + this.RemoveNames = removeAssemblyNames; + this.Targets = targetAssemblies; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs index 7de48649..3d08ec64 100644 --- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs @@ -17,6 +17,9 @@ namespace StardewModdingAPI.Framework /// The directory in which to cache data. private readonly string CacheDirPath; + /// Metadata for mapping assemblies to the current . + private readonly PlatformAssemblyMap AssemblyMap; + /// Rewrites assembly types to match the current platform. private readonly AssemblyTypeRewriter AssemblyTypeRewriter; @@ -35,7 +38,8 @@ namespace StardewModdingAPI.Framework { this.CacheDirPath = cacheDirPath; this.Monitor = monitor; - this.AssemblyTypeRewriter = this.GetAssemblyRewriter(targetPlatform); + this.AssemblyMap = this.GetAssemblyMap(targetPlatform); + this.AssemblyTypeRewriter = new AssemblyTypeRewriter(this.AssemblyMap, monitor); } /// Preprocess an assembly and cache the modified version. @@ -97,6 +101,14 @@ namespace StardewModdingAPI.Framework return Assembly.UnsafeLoadFrom(cachePaths.Assembly); // unsafe load allows DLLs downloaded from the Internet without the user needing to 'unblock' them } + /// Resolve an assembly from its name. + /// The assembly name. + public Assembly ResolveAssembly(string name) + { + string shortName = name.Split(new[] { ',' }, 2).First(); + return this.AssemblyMap.Targets.FirstOrDefault(p => p.GetName().Name == shortName); + } + /********* ** Private methods @@ -112,9 +124,9 @@ namespace StardewModdingAPI.Framework return new CachePaths(dirPath, cacheAssemblyPath, cacheHashPath); } - /// Get an assembly rewriter for the target platform. + /// Get metadata for mapping assemblies to the current platform. /// The target game platform. - private AssemblyTypeRewriter GetAssemblyRewriter(Platform targetPlatform) + private PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform) { // get assembly changes needed for platform string[] removeAssemblyReferences; @@ -155,7 +167,7 @@ namespace StardewModdingAPI.Framework throw new InvalidOperationException($"Unknown target platform '{targetPlatform}'."); } - return new AssemblyTypeRewriter(targetAssemblies, removeAssemblyReferences, this.Monitor); + return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index eba89981..bed4cafc 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -303,6 +303,8 @@ namespace StardewModdingAPI Program.Monitor.Log("Loading mods..."); ModAssemblyLoader modAssemblyLoader = new ModAssemblyLoader(Program.CachePath, Program.TargetPlatform, Program.Monitor); + AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); // supplement Mono's assembly resolution which doesn't handle assembly rewrites very well + foreach (string directory in Directory.GetDirectories(Program.ModPath)) { // ignore internal directory @@ -391,7 +393,7 @@ namespace StardewModdingAPI } catch (Exception ex) { - Program.Monitor.Log($"{errorPrefix}: couldm't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); + Program.Monitor.Log($"{errorPrefix}: couldn't create the per-save configuration directory ('psconfigs') requested by this mod.\n{ex.GetLogSummary()}", LogLevel.Error); continue; } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 2abcdc23..fd02f802 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -215,6 +215,7 @@ + -- cgit