summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs
blob: 93edd597bbdb28568a97e69fff8a70c55806a612 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Collections.Generic;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Internal;

namespace StardewModdingAPI.Framework.ModHelpers
{
    /// <summary>Provides metadata about installed mods.</summary>
    internal class ModRegistryHelper : BaseHelper, IModRegistry
    {
        /*********
        ** Fields
        *********/
        /// <summary>The underlying mod registry.</summary>
        private readonly ModRegistry Registry;

        /// <summary>Encapsulates monitoring and logging for the mod.</summary>
        private readonly IMonitor Monitor;

        /// <summary>The APIs accessed by this instance.</summary>
        private readonly Dictionary<string, object?> AccessedModApis = new();

        /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary>
        private readonly IInterfaceProxyFactory ProxyFactory;


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="mod">The mod using this instance.</param>
        /// <param name="registry">The underlying mod registry.</param>
        /// <param name="proxyFactory">Generates proxy classes to access mod APIs through an arbitrary interface.</param>
        /// <param name="monitor">Encapsulates monitoring and logging for the mod.</param>
        public ModRegistryHelper(IModMetadata mod, ModRegistry registry, IInterfaceProxyFactory proxyFactory, IMonitor monitor)
            : base(mod)
        {
            this.Registry = registry;
            this.ProxyFactory = proxyFactory;
            this.Monitor = monitor;
        }

        /// <inheritdoc />
        public IEnumerable<IModInfo> GetAll()
        {
            return this.Registry.GetAll();
        }

        /// <inheritdoc />
        public IModInfo? Get(string uniqueID)
        {
            return this.Registry.Get(uniqueID);
        }

        /// <inheritdoc />
        public bool IsLoaded(string uniqueID)
        {
            return this.Registry.Get(uniqueID) != null;
        }

        /// <inheritdoc />
        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 the target mod
            IModMetadata? mod = this.Registry.Get(uniqueID);
            if (mod == null)
                return null;

            // fetch API
            if (!this.AccessedModApis.TryGetValue(mod.Manifest.UniqueID, out object? api))
            {
                // if the target has a global API, this is mutually exclusive with per-mod APIs
                if (mod.Api != null)
                    api = mod.Api;

                // else try to get a per-mod API
                else
                {
                    try
                    {
                        api = mod.Mod?.GetApi(this.Mod);
                        if (api != null && !api.GetType().IsPublic)
                        {
                            api = null;
                            this.Monitor.Log($"{mod.DisplayName} provides a per-mod API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn);
                        }
                    }
                    catch (Exception ex)
                    {
                        this.Monitor.Log($"Failed loading the per-mod API instance from {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
                        api = null;
                    }
                }

                // cache & log API access
                this.AccessedModApis[mod.Manifest.UniqueID] = api;
                if (api != null)
                    this.Monitor.Log($"Accessed mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}.");
            }

            return api;
        }

        /// <inheritdoc />
        public TInterface? GetApi<TInterface>(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<TInterface>(api, sourceModID: this.ModID, targetModID: uniqueID);
        }
    }
}