diff options
7 files changed, 65 insertions, 8 deletions
diff --git a/release-notes.md b/release-notes.md index 803f7354..7b7e250c 100644 --- a/release-notes.md +++ b/release-notes.md @@ -6,6 +6,7 @@ For players: * The SMAPI console is now much simpler and easier to read. * The SMAPI console now adjusts its colors when you have a light terminal background. * Updated compatibility list. +* Improved errors when a mod DLL can't be loaded. For mod developers: * Added new APIs to edit, inject, and reload XNB assets loaded by the game at any time. diff --git a/src/StardewModdingAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs b/src/StardewModdingAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs new file mode 100644 index 00000000..ec9279f1 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs @@ -0,0 +1,16 @@ +using System; + +namespace StardewModdingAPI.Framework.Exceptions +{ + /// <summary>An exception thrown when an assembly can't be loaded by SMAPI, with all the relevant details in the message.</summary> + internal class SAssemblyLoadFailedException : Exception + { + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="message">The error message.</param> + public SAssemblyLoadFailedException(string message) + : base(message) { } + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs new file mode 100644 index 00000000..11be19fc --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Indicates the result of an assembly load.</summary> + internal enum AssemblyLoadStatus + { + /// <summary>The assembly was loaded successfully.</summary> + Okay = 1, + + /// <summary>The assembly could not be loaded.</summary> + Failed = 2, + + /// <summary>The assembly is already loaded.</summary> + AlreadyLoaded = 3 + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs index 406d49e1..b14ae56f 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,6 +6,7 @@ using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.AssemblyRewriters; +using StardewModdingAPI.Framework.Exceptions; namespace StardewModdingAPI.Framework.ModLoading { @@ -65,16 +66,27 @@ namespace StardewModdingAPI.Framework.ModLoading AssemblyDefinitionResolver resolver = new AssemblyDefinitionResolver(); HashSet<string> visitedAssemblyNames = new HashSet<string>(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()); } + // validate load + if (!assemblies.Any() || assemblies[0].Status == AssemblyLoadStatus.Failed) + { + throw new SAssemblyLoadFailedException(!File.Exists(assemblyPath) + ? $"Could not load '{assemblyPath}' because it doesn't exist." + : $"Could not load '{assemblyPath}'." + ); + } + if (assemblies[0].Status == AssemblyLoadStatus.AlreadyLoaded) + throw new SAssemblyLoadFailedException($"Could not load '{assemblyPath}' because it was already loaded. Do you have two copies of this mod?"); + // rewrite & load assemblies in leaf-to-root order bool oneAssembly = assemblies.Length == 1; Assembly lastAssembly = null; foreach (AssemblyParseResult assembly in assemblies) { + if (assembly.Status == AssemblyLoadStatus.AlreadyLoaded) + continue; + bool changed = this.RewriteAssembly(assembly.Definition, assumeCompatible, logPrefix: " "); if (changed) { @@ -143,7 +155,7 @@ namespace StardewModdingAPI.Framework.ModLoading // skip if already visited if (visitedAssemblyNames.Contains(assembly.Name.Name)) - yield break; + yield return new AssemblyParseResult(file, null, AssemblyLoadStatus.AlreadyLoaded); visitedAssemblyNames.Add(assembly.Name.Name); // yield referenced assemblies @@ -155,7 +167,7 @@ namespace StardewModdingAPI.Framework.ModLoading } // yield assembly - yield return new AssemblyParseResult(file, assembly); + yield return new AssemblyParseResult(file, assembly, AssemblyLoadStatus.Okay); } /**** diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs index 69c99afe..b56a776c 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -15,6 +15,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>The assembly definition.</summary> public readonly AssemblyDefinition Definition; + /// <summary>The result of the assembly load.</summary> + public AssemblyLoadStatus Status; + /********* ** Public methods @@ -22,10 +25,12 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Construct an instance.</summary> /// <param name="file">The original assembly file.</param> /// <param name="assembly">The assembly definition.</param> - public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly) + /// <param name="status">The result of the assembly load.</param> + public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly, AssemblyLoadStatus status) { this.File = file; this.Definition = assembly; + this.Status = status; } } -}
\ No newline at end of file +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 57ff011f..108e9273 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -15,6 +15,7 @@ using Newtonsoft.Json; using StardewModdingAPI.AssemblyRewriters; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Logging; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModHelpers; @@ -655,6 +656,11 @@ namespace StardewModdingAPI #endif continue; } + catch (SAssemblyLoadFailedException ex) + { + TrackSkip(metadata, $"its DLL '{manifest.EntryDll}' couldn't be loaded: {ex.Message}"); + continue; + } catch (Exception ex) { TrackSkip(metadata, $"its DLL '{manifest.EntryDll}' couldn't be loaded:\n{ex.GetLogSummary()}"); diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 73112983..8c7279a1 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -91,6 +91,8 @@ <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> <Compile Include="Command.cs" /> + <Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" /> + <Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" /> <Compile Include="Framework\Utilities\ContextHash.cs" /> <Compile Include="Metadata\CoreAssets.cs" /> <Compile Include="ContentSource.cs" /> |