diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
index 348ba225..9ad3e3ae 100644
--- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
@@ -1,5 +1,7 @@
+using System;
using System.Collections.Generic;
using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Internal;
namespace StardewModdingAPI.Framework.ModHelpers
{
@@ -15,8 +17,8 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// Encapsulates monitoring and logging for the mod.
private readonly IMonitor Monitor;
- /// The mod IDs for APIs accessed by this instanced.
- private readonly HashSet AccessedModApis = new();
+ /// The APIs accessed by this instance.
+ private readonly Dictionary AccessedModApis = new();
/// Generates proxy classes to access mod APIs through an arbitrary interface.
private readonly IInterfaceProxyFactory ProxyFactory;
@@ -66,11 +68,50 @@ namespace StardewModdingAPI.Framework.ModHelpers
return null;
}
- // get raw API
+ // get our cached API if one is available
IModMetadata? mod = this.Registry.Get(uniqueID);
- if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID))
- this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.");
- return mod?.Api;
+ if (mod == null)
+ return null;
+
+ if (this.AccessedModApis.ContainsKey(mod.Manifest.UniqueID))
+ {
+ return this.AccessedModApis[mod.Manifest.UniqueID];
+ }
+
+ object? api;
+
+ // safely request a specific API instance
+ try
+ {
+ api = mod.Mod?.GetApi(this.Mod.Manifest);
+ if (api != null && !api.GetType().IsPublic)
+ {
+ api = null;
+ this.Monitor.Log($"{mod.DisplayName} provided a specific API instance with a non-public type. This isn't currently supported, so the specific API won't be available to the requesting mod.", LogLevel.Warn);
+ }
+
+ if (api != null)
+ this.Monitor.Log($"Accessed specific mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}.");
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"Failed loading specific mod-provided API for {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
+ api = null;
+ }
+
+ // fall back to the generic API instance
+ if (api == null)
+ {
+ api = mod.Api;
+ if (api != null)
+ {
+ this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.");
+ }
+ }
+
+ // cache the API instance and return it
+ this.AccessedModApis[mod.Manifest.UniqueID] = api;
+ return api;
}
///
diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs
index b81ba0e3..6041bf66 100644
--- a/src/SMAPI/IMod.cs
+++ b/src/SMAPI/IMod.cs
@@ -25,5 +25,11 @@ namespace StardewModdingAPI
/// Get an API that other mods can access. This is always called after .
object? GetApi();
+
+ /// Get an API that a specific other mod can access. This method is called the first time the other mod calls for this mod.
+ /// The other mod's manifest.
+ /// Returns an API for another mod, or null if the other mod should use the general API returned from .
+ object? GetApi(IManifest manifest);
+
}
}
diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs
index f764752b..1a5f5594 100644
--- a/src/SMAPI/Mod.cs
+++ b/src/SMAPI/Mod.cs
@@ -30,6 +30,12 @@ namespace StardewModdingAPI
return null;
}
+ ///
+ public virtual object? GetApi(IManifest manifest)
+ {
+ return null;
+ }
+
/// Release or reset unmanaged resources.
public void Dispose()
{