summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI/Framework/ModRegistry.cs
blob: 233deb3c5efc663afc1a0d20a8f930d3b02fb245 (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
139
140
141
142
143
144
145
146
147
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using StardewModdingAPI.Framework.Models;

namespace StardewModdingAPI.Framework
{
    /// <summary>Tracks the installed mods.</summary>
    internal class ModRegistry : IModRegistry
    {
        /*********
        ** Properties
        *********/
        /// <summary>The registered mod data.</summary>
        private readonly List<IMod> Mods = new List<IMod>();

        /// <summary>The friendly mod names treated as deprecation warning sources (assembly full name => mod name).</summary>
        private readonly IDictionary<string, string> ModNamesByAssembly = new Dictionary<string, string>();

        /// <summary>The mod versions which should be disabled due to incompatibility.</summary>
        private readonly IncompatibleMod[] IncompatibleMods;


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        /// <param name="incompatibleMods">The mod versions which should be disabled due to incompatibility.</param>
        public ModRegistry(IEnumerable<IncompatibleMod> incompatibleMods)
        {
            this.IncompatibleMods = incompatibleMods.ToArray();
        }


        /****
        ** IModRegistry
        ****/
        /// <summary>Get metadata for all loaded mods.</summary>
        public IEnumerable<IManifest> GetAll()
        {
            return this.Mods.Select(p => p.ModManifest);
        }

        /// <summary>Get metadata for a loaded mod.</summary>
        /// <param name="uniqueID">The mod's unique ID.</param>
        /// <returns>Returns the matching mod's metadata, or <c>null</c> if not found.</returns>
        public IManifest Get(string uniqueID)
        {
            return this.GetAll().FirstOrDefault(p => p.UniqueID == uniqueID);
        }

        /// <summary>Get whether a mod has been loaded.</summary>
        /// <param name="uniqueID">The mod's unique ID.</param>
        public bool IsLoaded(string uniqueID)
        {
            return this.GetAll().Any(p => p.UniqueID == uniqueID);
        }

        /****
        ** Internal methods
        ****/
        /// <summary>Register a mod as a possible source of deprecation warnings.</summary>
        /// <param name="mod">The mod instance.</param>
        public void Add(IMod mod)
        {
            this.Mods.Add(mod);
            this.ModNamesByAssembly[mod.GetType().Assembly.FullName] = mod.ModManifest.Name;
        }

        /// <summary>Get all enabled mods.</summary>
        public IEnumerable<IMod> GetMods()
        {
            return (from mod in this.Mods select mod);
        }

        /// <summary>Get the friendly mod name which handles a delegate.</summary>
        /// <param name="delegate">The delegate to follow.</param>
        /// <returns>Returns the mod name, or <c>null</c> if the delegate isn't implemented by a known mod.</returns>
        public string GetModFrom(Delegate @delegate)
        {
            return @delegate?.Target != null
                ? this.GetModFrom(@delegate.Target.GetType())
                : null;
        }

        /// <summary>Get the friendly mod name which defines a type.</summary>
        /// <param name="type">The type to check.</param>
        /// <returns>Returns the mod name, or <c>null</c> if the type isn't part of a known mod.</returns>
        public string GetModFrom(Type type)
        {
            // null
            if (type == null)
                return null;

            // known type
            string assemblyName = type.Assembly.FullName;
            if (this.ModNamesByAssembly.ContainsKey(assemblyName))
                return this.ModNamesByAssembly[assemblyName];

            // not found
            return null;
        }

        /// <summary>Get the friendly name for the closest assembly registered as a source of deprecation warnings.</summary>
        /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns>
        public string GetModFromStack()
        {
            // get stack frames
            StackTrace stack = new StackTrace();
            StackFrame[] frames = stack.GetFrames();
            if (frames == null)
                return null;

            // search stack for a source assembly
            foreach (StackFrame frame in frames)
            {
                MethodBase method = frame.GetMethod();
                string name = this.GetModFrom(method.ReflectedType);
                if (name != null)
                    return name;
            }

            // no known assembly found
            return null;
        }

        /// <summary>Get a record indicating why a mod is incompatible (if applicable).</summary>
        /// <param name="manifest">The mod manifest.</param>
        /// <returns>Returns the incompatibility record if applicable, else <c>null</c>.</returns>
        internal IncompatibleMod GetIncompatibilityRecord(IManifest manifest)
        {
            string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll;
            return (
                from mod in this.IncompatibleMods
                where
                    mod.ID == key
                    && (mod.LowerSemanticVersion == null || !manifest.Version.IsOlderThan(mod.LowerSemanticVersion))
                    && !manifest.Version.IsNewerThan(mod.UpperSemanticVersion)
                    && (string.IsNullOrWhiteSpace(mod.ForceCompatibleVersion) || !Regex.IsMatch(manifest.Version.ToString(), mod.ForceCompatibleVersion, RegexOptions.IgnoreCase))
                select mod
            ).FirstOrDefault();
        }
    }
}