From 1dfedd2d1a032d357bf42e9edbd7a797beb2e124 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 17 Feb 2017 01:44:19 -0500 Subject: fix issue where mod dependencies overrode SMAPI dependencies --- release-notes.md | 1 + src/StardewModdingAPI/Framework/AssemblyLoader.cs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/release-notes.md b/release-notes.md index 8d571514..6111776f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -9,6 +9,7 @@ For players: * Simple nested mod folders are now recognised by SMAPI (e.g. `ModName-1.0\ModName\manifest.json`). * Fixed game's debug output being shown in the console for all users. * Fixed installer errors for some players when deleting files. +* Fixed rare issue where mod dependencies would override SMAPI dependencies and cause unpredictable bugs. For mod developers: * Added `SaveEvents.AfterReturnToTitle` and `TimeEvents.AfterDayStarted` events. diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 0cf6e569..a932a47c 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -61,7 +61,8 @@ namespace StardewModdingAPI.Framework AssemblyParseResult[] assemblies; { AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver(); - assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), new HashSet(), resolver).ToArray(); + HashSet visitedAssemblyNames = new HashSet(AppDomain.CurrentDomain.GetAssemblies().Select(p => p.GetName().Name)); // don't try loading assemblies that are already loaded + assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), visitedAssemblyNames, resolver).ToArray(); if (!assemblies.Any()) throw new InvalidOperationException($"Could not load '{assemblyPath}' because it doesn't exist."); resolver.Add(assemblies.Select(p => p.Definition).ToArray()); @@ -118,18 +119,16 @@ namespace StardewModdingAPI.Framework ****/ /// Get a list of referenced local assemblies starting from the mod assembly, ordered from leaf to root. /// The assembly file to load. - /// The assembly paths that should be skipped. + /// The assembly names that should be skipped. + /// A resolver which resolves references to known assemblies. /// Returns the rewrite metadata for the preprocessed assembly. - private IEnumerable GetReferencedLocalAssemblies(FileInfo file, HashSet visitedAssemblyPaths, IAssemblyResolver assemblyResolver) + private IEnumerable GetReferencedLocalAssemblies(FileInfo file, HashSet visitedAssemblyNames, IAssemblyResolver assemblyResolver) { // validate if (file.Directory == null) throw new InvalidOperationException($"Could not get directory from file path '{file.FullName}'."); - if (visitedAssemblyPaths.Contains(file.FullName)) - yield break; // already visited if (!file.Exists) yield break; // not a local assembly - visitedAssemblyPaths.Add(file.FullName); // read assembly byte[] assemblyBytes = File.ReadAllBytes(file.FullName); @@ -137,11 +136,16 @@ namespace StardewModdingAPI.Framework using (Stream readStream = new MemoryStream(assemblyBytes)) assembly = AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); + // skip if already visited + if (visitedAssemblyNames.Contains(assembly.Name.Name)) + yield break; + visitedAssemblyNames.Add(assembly.Name.Name); + // yield referenced assemblies foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences) { FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); - foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyPaths, assemblyResolver)) + foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyNames, assemblyResolver)) yield return result; } -- cgit