using System; using System.Collections.Generic; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Internal; namespace StardewModdingAPI.Framework.ModHelpers { /// Provides metadata about installed mods. internal class ModRegistryHelper : BaseHelper, IModRegistry { /********* ** Fields *********/ /// The underlying mod registry. private readonly ModRegistry Registry; /// Encapsulates monitoring and logging for the mod. private readonly IMonitor Monitor; /// 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; /********* ** Public methods *********/ /// Construct an instance. /// The mod using this instance. /// The underlying mod registry. /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. public ModRegistryHelper(IModMetadata mod, ModRegistry registry, IInterfaceProxyFactory proxyFactory, IMonitor monitor) : base(mod) { this.Registry = registry; this.ProxyFactory = proxyFactory; this.Monitor = monitor; } /// public IEnumerable GetAll() { return this.Registry.GetAll(); } /// public IModInfo? Get(string uniqueID) { return this.Registry.Get(uniqueID); } /// public bool IsLoaded(string uniqueID) { return this.Registry.Get(uniqueID) != null; } /// public object? GetApi(string uniqueID) { // validate ready if (!this.Registry.AreAllModsInitialized) { this.Monitor.Log("Tried to access a mod-provided API before all mods were initialized.", LogLevel.Error); return null; } // get our cached API if one is available IModMetadata? mod = this.Registry.Get(uniqueID); 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; } /// public TInterface? GetApi(string uniqueID) where TInterface : class { // get raw API object? api = this.GetApi(uniqueID); if (api == null) return null; // validate mapping if (!typeof(TInterface).IsInterface) { this.Monitor.Log($"Tried to map a mod-provided API to class '{typeof(TInterface).FullName}'; must be a public interface.", LogLevel.Error); return null; } if (!typeof(TInterface).IsPublic) { this.Monitor.Log($"Tried to map a mod-provided API to non-public interface '{typeof(TInterface).FullName}'; must be a public interface.", LogLevel.Error); return null; } // get API of type return api is TInterface castApi ? castApi : this.ProxyFactory.CreateProxy(api, sourceModID: this.ModID, targetModID: uniqueID); } } }