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() {