summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI/Constants.cs4
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs70
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs13
3 files changed, 79 insertions, 8 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index d733e61e..c59af612 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
{
/// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary>
- internal class AssemblyDefinitionResolver : DefaultAssemblyResolver
+ internal class AssemblyDefinitionResolver : IAssemblyResolver
{
/*********
** Fields
*********/
+ /// <summary>The underlying assembly resolver.</summary>
+ private readonly DefaultAssemblyResolverWrapper Resolver = new();
+
/// <summary>The known assemblies.</summary>
private readonly IDictionary<string, AssemblyDefinition> Lookup = new Dictionary<string, AssemblyDefinition>();
+ /// <summary>The directory paths to search for assemblies.</summary>
+ private readonly HashSet<string> SearchPaths = new();
+
/*********
** Public methods
*********/
+ /// <summary>Construct an instance.</summary>
+ public AssemblyDefinitionResolver()
+ {
+ foreach (string path in this.Resolver.GetSearchDirectories())
+ this.SearchPaths.Add(path);
+ }
+
/// <summary>Add known assemblies to the resolver.</summary>
/// <param name="assemblies">The known assemblies.</param>
public void Add(params AssemblyDefinition[] assemblies)
@@ -29,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="names">The assembly names for which it should be returned.</param>
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
/// <summary>Resolve an assembly reference.</summary>
/// <param name="name">The assembly name.</param>
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
- 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);
}
/// <summary>Resolve an assembly reference.</summary>
/// <param name="name">The assembly name.</param>
/// <param name="parameters">The assembly reader parameters.</param>
/// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
- 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);
+ }
+
+ /// <summary>Add a directory path to search for assemblies, if it's non-null and not already added.</summary>
+ /// <param name="path">The path to search.</param>
+ /// <returns>Returns whether the path was successfully added.</returns>
+ public bool TryAddSearchDirectory(string? path)
+ {
+ if (path is not null && this.SearchPaths.Add(path))
+ {
+ this.Resolver.AddSearchDirectory(path);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Remove a directory path to search for assemblies, if it's non-null.</summary>
+ /// <param name="path">The path to remove.</param>
+ /// <returns>Returns whether the path was in the list and removed.</returns>
+ public bool RemoveSearchDirectory(string? path)
+ {
+ if (path is not null && this.SearchPaths.Remove(path))
+ {
+ this.Resolver.RemoveSearchDirectory(path);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ this.Resolver.Dispose();
}
@@ -63,5 +110,16 @@ namespace StardewModdingAPI.Framework.ModLoading
? match
: null;
}
+
+ /// <summary>An internal wrapper around <see cref="DefaultAssemblyResolver"/> to allow access to its protected methods.</summary>
+ private class DefaultAssemblyResolverWrapper : DefaultAssemblyResolver
+ {
+ /// <summary>Add an assembly to the resolver.</summary>
+ /// <param name="assembly">The assembly to add.</param>
+ 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 eb940c41..01037870 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -264,8 +264,15 @@ 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 (this.AssemblyDefinitionResolver.TryAddSearchDirectory(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 +293,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))