From 0c0f7898f4ff3b2e4afdbdc438674b30be7dacc7 Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 28 Jun 2022 16:37:58 -0700 Subject: Search assembly directory for dependencies --- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/SMAPI/Framework/ModLoading') diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index eb940c41..bd29a159 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -264,8 +264,18 @@ namespace StardewModdingAPI.Framework.ModLoading if (!file.Exists) yield break; // not a local assembly + // add the assembly's directory temporarily if needed + // this is needed by F# mods which bundle FSharp.Core.dll, for example + string? temporarySearchDir = null; + if (file.DirectoryName is not null && !this.AssemblyDefinitionResolver.GetSearchDirectories().Contains(file.DirectoryName)) + { + this.AssemblyDefinitionResolver.AddSearchDirectory(file.DirectoryName); + temporarySearchDir = file.DirectoryName; + } + // read assembly AssemblyDefinition assembly; + try { byte[] assemblyBytes = File.ReadAllBytes(file.FullName); Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes)); @@ -286,6 +296,12 @@ namespace StardewModdingAPI.Framework.ModLoading assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true })); } } + finally + { + // clean up temporary search directory + if (temporarySearchDir is not null) + this.AssemblyDefinitionResolver.RemoveSearchDirectory(temporarySearchDir); + } // skip if already visited if (visitedAssemblyNames.Contains(assembly.Name.Name)) -- cgit From 1b3a1a48d0d0d7e2423db54f3266cabd80a5a9b3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 8 Jul 2022 19:02:33 -0400 Subject: refactor assembly resolver to avoid repeatedly copying search directory list --- src/SMAPI/Constants.cs | 4 +- .../ModLoading/AssemblyDefinitionResolver.cs | 70 ++++++++++++++++++++-- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 5 +- 3 files changed, 67 insertions(+), 12 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 33468717..442f2ec8 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -244,8 +244,8 @@ namespace StardewModdingAPI internal static void ConfigureAssemblyResolver(AssemblyDefinitionResolver resolver) { // add search paths - resolver.AddSearchDirectory(Constants.GamePath); - resolver.AddSearchDirectory(Constants.InternalFilesPath); + resolver.TryAddSearchDirectory(Constants.GamePath); + resolver.TryAddSearchDirectory(Constants.InternalFilesPath); // add SMAPI explicitly // Normally this would be handled automatically by the search paths, but for some reason there's a specific diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index b3378ad1..5a850255 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -4,18 +4,31 @@ using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { /// A minimal assembly definition resolver which resolves references to known assemblies. - internal class AssemblyDefinitionResolver : DefaultAssemblyResolver + internal class AssemblyDefinitionResolver : IAssemblyResolver { /********* ** Fields *********/ + /// The underlying assembly resolver. + private readonly DefaultAssemblyResolverWrapper Resolver = new(); + /// The known assemblies. private readonly IDictionary Lookup = new Dictionary(); + /// The directory paths to search for assemblies. + private readonly HashSet SearchPaths = new(); + /********* ** Public methods *********/ + /// Construct an instance. + public AssemblyDefinitionResolver() + { + foreach (string path in this.Resolver.GetSearchDirectories()) + this.SearchPaths.Add(path); + } + /// Add known assemblies to the resolver. /// The known assemblies. public void Add(params AssemblyDefinition[] assemblies) @@ -29,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// The assembly names for which it should be returned. public void AddWithExplicitNames(AssemblyDefinition assembly, params string[] names) { - this.RegisterAssembly(assembly); + this.Resolver.AddAssembly(assembly); foreach (string name in names) this.Lookup[name] = assembly; } @@ -37,18 +50,52 @@ namespace StardewModdingAPI.Framework.ModLoading /// Resolve an assembly reference. /// The assembly name. /// The assembly can't be resolved. - public override AssemblyDefinition Resolve(AssemblyNameReference name) + public AssemblyDefinition Resolve(AssemblyNameReference name) { - return this.ResolveName(name.Name) ?? base.Resolve(name); + return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name); } /// Resolve an assembly reference. /// The assembly name. /// The assembly reader parameters. /// The assembly can't be resolved. - public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { - return this.ResolveName(name.Name) ?? base.Resolve(name, parameters); + return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name, parameters); + } + + /// Add a directory path to search for assemblies, if it's non-null and not already added. + /// The path to search. + /// Returns whether the path was successfully added. + public bool TryAddSearchDirectory(string? path) + { + if (path is not null && this.SearchPaths.Add(path)) + { + this.Resolver.AddSearchDirectory(path); + return true; + } + + return false; + } + + /// Remove a directory path to search for assemblies, if it's non-null. + /// The path to remove. + /// Returns whether the path was in the list and removed. + public bool RemoveSearchDirectory(string? path) + { + if (path is not null && this.SearchPaths.Remove(path)) + { + this.Resolver.RemoveSearchDirectory(path); + return true; + } + + return false; + } + + /// + public void Dispose() + { + this.Resolver.Dispose(); } @@ -63,5 +110,16 @@ namespace StardewModdingAPI.Framework.ModLoading ? match : null; } + + /// An internal wrapper around to allow access to its protected methods. + private class DefaultAssemblyResolverWrapper : DefaultAssemblyResolver + { + /// Add an assembly to the resolver. + /// The assembly to add. + public void AddAssembly(AssemblyDefinition assembly) + { + this.RegisterAssembly(assembly); + } + } } } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index bd29a159..01037870 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -267,11 +267,8 @@ namespace StardewModdingAPI.Framework.ModLoading // add the assembly's directory temporarily if needed // this is needed by F# mods which bundle FSharp.Core.dll, for example string? temporarySearchDir = null; - if (file.DirectoryName is not null && !this.AssemblyDefinitionResolver.GetSearchDirectories().Contains(file.DirectoryName)) - { - this.AssemblyDefinitionResolver.AddSearchDirectory(file.DirectoryName); + if (this.AssemblyDefinitionResolver.TryAddSearchDirectory(file.DirectoryName)) temporarySearchDir = file.DirectoryName; - } // read assembly AssemblyDefinition assembly; -- cgit From 352fa4759e0b52ad25bd5d210639f57eabad6c89 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Aug 2022 19:54:07 -0400 Subject: fix error when a mod is both duplicated and missing the DLL --- docs/release-notes.md | 1 + src/SMAPI/Framework/ModLoading/ModResolver.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework/ModLoading') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3591c375..080a4f20 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,7 @@ * Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!). * Fixed Linux/macOS installer's color scheme question partly unreadable if the terminal background is dark. * Fixed error message when a mod loads an invalid PNG file (thanks to atravita!). + * Fixed error message when a mod is duplicated, but one of the copies is also missing the DLL file. This now shows the duplicate-mod message instead of the missing-DLL message. * Fixed macOS launcher using Terminal regardless of the system's default terminal (thanks to ishan!). * Fixed best practices in Linux/macOS launcher scripts (thanks to ishan!). * Improved translations. Thanks to KediDili (updated Turkish)! diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index abc46d47..a487ba28 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModLoading IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); - else + else if (metadata.Status == ModMetadataStatus.Failed) metadata.SetStatus(status, ModFailReason.InvalidManifest, folder.ManifestParseErrorText); yield return metadata; @@ -223,8 +223,8 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (IModMetadata mod in group) { - if (mod.Status == ModMetadataStatus.Failed) - continue; // don't replace metadata error + if (mod.Status == ModMetadataStatus.Failed && mod.FailReason != ModFailReason.InvalidManifest) + continue; string folderList = string.Join(", ", group.Select(p => p.GetRelativePathWithRoot()).OrderBy(p => p)); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Duplicate, $"you have multiple copies of this mod installed. To fix this, delete these folders and reinstall the mod: {folderList}."); -- cgit From f780d140f0c87de9f10d6f55d501c958316548d0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Aug 2022 16:36:15 -0400 Subject: fix early mod load errors incorrectly suppressed --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModLoading') diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index a487ba28..c5648c74 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModLoading IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); - else if (metadata.Status == ModMetadataStatus.Failed) + else if (status == ModMetadataStatus.Failed) metadata.SetStatus(status, ModFailReason.InvalidManifest, folder.ManifestParseErrorText); yield return metadata; -- cgit