summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-08-21 14:22:19 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-08-21 14:22:19 -0400
commit80fe706f19d95ef2d00887344bcbc2a064a04541 (patch)
treee80dc30e52b2680391aaff9ee65c02ede2f5ee64
parent723ddc255e1c2b399dfb734306fd00912a741e62 (diff)
downloadSMAPI-80fe706f19d95ef2d00887344bcbc2a064a04541.tar.gz
SMAPI-80fe706f19d95ef2d00887344bcbc2a064a04541.tar.bz2
SMAPI-80fe706f19d95ef2d00887344bcbc2a064a04541.zip
show friendlier error when players have two copies of a mod
-rw-r--r--release-notes.md1
-rw-r--r--src/StardewModdingAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs16
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyLoadStatus.cs15
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs24
-rw-r--r--src/StardewModdingAPI/Framework/ModLoading/AssemblyParseResult.cs9
-rw-r--r--src/StardewModdingAPI/Program.cs6
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj2
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" />