rewrite fallback assembly resolution

* SMAPI now also searches the root game folder for unresolved assemblies. This fixes an issue resolving the game DLL when the player's DLL version doesn't match the one used to compile SMAPI.
* The DLL search folders are now scanned once and cached to avoid repeated iterations on startup.
This commit is contained in:
Jesse Plamondon-Willard 2022-01-16 22:56:48 -05:00
parent 85f8631bee
commit 95f4513727
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
2 changed files with 36 additions and 9 deletions

View File

@ -1,6 +1,9 @@
← [README](README.md) ← [README](README.md)
# Release notes # Release notes
## Upcoming release
* Fixed Linux/macOS launch error in 3.13.3.
## 3.13.3 ## 3.13.3
Released 16 January 2022 for Stardew Valley 1.5.6 or later. Released 16 January 2022 for Stardew Valley 1.5.6 or later.

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -16,7 +17,10 @@ namespace StardewModdingAPI
** Fields ** Fields
*********/ *********/
/// <summary>The absolute path to search for SMAPI's internal DLLs.</summary> /// <summary>The absolute path to search for SMAPI's internal DLLs.</summary>
internal static readonly string DllSearchPath = EarlyConstants.InternalFilesPath; private static readonly string DllSearchPath = EarlyConstants.InternalFilesPath;
/// <summary>The assembly paths in the search folders indexed by assembly name.</summary>
private static Dictionary<string, string> AssemblyPathsByName;
/********* /*********
@ -57,16 +61,36 @@ namespace StardewModdingAPI
/// <param name="e">The event arguments.</param> /// <param name="e">The event arguments.</param>
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
{ {
// cache assembly paths by name
if (Program.AssemblyPathsByName == null)
{
Program.AssemblyPathsByName = new(StringComparer.OrdinalIgnoreCase);
foreach (string searchPath in new[] { EarlyConstants.ExecutionPath, Program.DllSearchPath })
{
foreach (string dllPath in Directory.EnumerateFiles(searchPath, "*.dll"))
{
try
{
string curName = AssemblyName.GetAssemblyName(dllPath).Name;
if (curName != null)
Program.AssemblyPathsByName[curName] = dllPath;
}
catch
{
continue;
}
}
}
}
// resolve
try try
{ {
AssemblyName name = new AssemblyName(e.Name); string searchName = new AssemblyName(e.Name).Name;
foreach (FileInfo dll in new DirectoryInfo(Program.DllSearchPath).EnumerateFiles("*.dll")) return searchName != null && Program.AssemblyPathsByName.TryGetValue(searchName, out string assemblyPath)
{ ? Assembly.LoadFrom(assemblyPath)
if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) : null;
return Assembly.LoadFrom(dll.FullName);
}
return null;
} }
catch (Exception ex) catch (Exception ex)
{ {