diff options
Diffstat (limited to 'src/SMAPI/Framework')
15 files changed, 523 insertions, 243 deletions
diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 10c41d08..4508e641 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -57,14 +57,14 @@ namespace StardewModdingAPI.Framework.Content public ContentCache(LocalizedContentManager contentManager, Reflector reflection, char[] possiblePathSeparators, string preferredPathSeparator) { // init - this.Cache = reflection.GetPrivateField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue(); + this.Cache = reflection.GetField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue(); this.PossiblePathSeparators = possiblePathSeparators; this.PreferredPathSeparator = preferredPathSeparator; // get key normalisation logic if (Constants.TargetPlatform == Platform.Windows) { - IPrivateMethod method = reflection.GetPrivateMethod(typeof(TitleContainer), "GetCleanPath"); + IReflectedMethod method = reflection.GetMethod(typeof(TitleContainer), "GetCleanPath"); this.NormaliseAssetNameForPlatform = path => method.Invoke<string>(path); } else diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index b07c6c7d..7a824a05 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework /// <param name="severity">How deprecated the code is.</param> public void Warn(string nounPhrase, string version, DeprecationLevel severity) { - this.Warn(this.ModRegistry.GetModFromStack(), nounPhrase, version, severity); + this.Warn(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version, severity); } /// <summary>Log a deprecation warning.</summary> @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Framework return; // build message - string message = $"{source ?? "An unknown mod"} uses deprecated code ({nounPhrase})."; + string message = $"{source ?? "An unknown mod"} uses deprecated code ({nounPhrase} is deprecated since SMAPI {version})."; if (source == null) message += $"{Environment.NewLine}{Environment.StackTrace}"; @@ -82,7 +82,7 @@ namespace StardewModdingAPI.Framework /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns> public bool MarkWarned(string nounPhrase, string version) { - return this.MarkWarned(this.ModRegistry.GetModFromStack(), nounPhrase, version); + return this.MarkWarned(this.ModRegistry.GetFromStack()?.DisplayName, nounPhrase, version); } /// <summary>Mark a deprecation warning as already logged.</summary> diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index c21734a7..a36994fd 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -30,6 +30,9 @@ namespace StardewModdingAPI.Framework /// <summary>The mod instance (if it was loaded).</summary> IMod Mod { get; } + /// <summary>The mod-provided API (if any).</summary> + object Api { get; } + /********* ** Public methods @@ -43,5 +46,9 @@ namespace StardewModdingAPI.Framework /// <summary>Set the mod instance.</summary> /// <param name="mod">The mod instance to set.</param> IModMetadata SetMod(IMod mod); + + /// <summary>Set the mod-provided API instance.</summary> + /// <param name="api">The mod-provided API.</param> + IModMetadata SetApi(object api); } } diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index 3709e05d..bec6c183 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -108,6 +108,15 @@ namespace StardewModdingAPI.Framework } } + /// <summary>Get the lowest exception in an exception stack.</summary> + /// <param name="exception">The exception from which to search.</param> + public static Exception GetInnermostException(this Exception exception) + { + while (exception.InnerException != null) + exception = exception.InnerException; + return exception; + } + /**** ** Sprite batch ****/ @@ -125,7 +134,7 @@ namespace StardewModdingAPI.Framework #endif // get result - return reflection.GetPrivateField<bool>(Game1.spriteBatch, fieldName).GetValue(); + return reflection.GetField<bool>(Game1.spriteBatch, fieldName).GetValue(); } } } diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 9e824694..ea0dbb38 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; +using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers { @@ -11,6 +13,15 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <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 mod IDs for APIs accessed by this instanced.</summary> + private readonly HashSet<string> AccessedModApis = new HashSet<string>(); + + /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary> + private readonly InterfaceProxyBuilder ProxyBuilder; + /********* ** Public methods @@ -18,16 +29,20 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>Construct an instance.</summary> /// <param name="modID">The unique ID of the relevant mod.</param> /// <param name="registry">The underlying mod registry.</param> - public ModRegistryHelper(string modID, ModRegistry registry) + /// <param name="proxyBuilder">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(string modID, ModRegistry registry, InterfaceProxyBuilder proxyBuilder, IMonitor monitor) : base(modID) { this.Registry = registry; + this.ProxyBuilder = proxyBuilder; + this.Monitor = monitor; } /// <summary>Get metadata for all loaded mods.</summary> public IEnumerable<IManifest> GetAll() { - return this.Registry.GetAll(); + return this.Registry.GetAll().Select(p => p.Manifest); } /// <summary>Get metadata for a loaded mod.</summary> @@ -35,14 +50,56 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <returns>Returns the matching mod's metadata, or <c>null</c> if not found.</returns> public IManifest Get(string uniqueID) { - return this.Registry.Get(uniqueID); + return this.Registry.Get(uniqueID)?.Manifest; } /// <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.Registry.IsLoaded(uniqueID); + return this.Registry.Get(uniqueID) != null; + } + + /// <summary>Get the API provided by a mod, or <c>null</c> if it has none. This signature requires using the <see cref="IModHelper.Reflection"/> API to access the API's properties and methods.</summary> + public object GetApi(string uniqueID) + { + 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}.", LogLevel.Trace); + return mod?.Api; + } + + /// <summary>Get the API provided by a mod, mapped to a given interface which specifies the expected properties and methods. If the mod has no API or it's not compatible with the given interface, get <c>null</c>.</summary> + /// <typeparam name="TInterface">The interface which matches the properties and methods you intend to access.</typeparam> + /// <param name="uniqueID">The mod's unique ID.</param> + public TInterface GetApi<TInterface>(string uniqueID) where TInterface : class + { + // validate + if (!this.Registry.AreAllModsInitialised) + { + this.Monitor.Log("Tried to access a mod-provided API before all mods were initialised.", LogLevel.Error); + return null; + } + if (!typeof(TInterface).IsInterface) + { + this.Monitor.Log("Tried to map a mod-provided API to a class; must be a public interface.", LogLevel.Error); + return null; + } + if (!typeof(TInterface).IsPublic) + { + this.Monitor.Log("Tried to map a mod-provided API to a non-public interface; must be a public interface.", LogLevel.Error); + return null; + } + + // get raw API + object api = this.GetApi(uniqueID); + if (api == null) + return null; + + // get API of type + if (api is TInterface castApi) + return castApi; + return this.ProxyBuilder.CreateProxy<TInterface>(api, this.ModID, uniqueID); } } } diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 8788b142..81453003 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -17,6 +17,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>The mod name for error messages.</summary> private readonly string ModName; + /// <summary>Manages deprecation warnings.</summary> + private readonly DeprecationManager DeprecationManager; + /********* ** Public methods @@ -25,15 +28,88 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="modID">The unique ID of the relevant mod.</param> /// <param name="modName">The mod name for error messages.</param> /// <param name="reflector">The underlying reflection helper.</param> - public ReflectionHelper(string modID, string modName, Reflector reflector) + /// <param name="deprecationManager">Manages deprecation warnings.</param> + public ReflectionHelper(string modID, string modName, Reflector reflector, DeprecationManager deprecationManager) : base(modID) { this.ModName = modName; this.Reflector = reflector; + this.DeprecationManager = deprecationManager; + } + + /// <summary>Get an instance field.</summary> + /// <typeparam name="TValue">The field type.</typeparam> + /// <param name="obj">The object which has the field.</param> + /// <param name="name">The field name.</param> + /// <param name="required">Whether to throw an exception if the field is not found.</param> + public IReflectedField<TValue> GetField<TValue>(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField<TValue>(obj, name, required) + ); + } + + /// <summary>Get a static field.</summary> + /// <typeparam name="TValue">The field type.</typeparam> + /// <param name="type">The type which has the field.</param> + /// <param name="name">The field name.</param> + /// <param name="required">Whether to throw an exception if the field is not found.</param> + public IReflectedField<TValue> GetField<TValue>(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField<TValue>(type, name, required) + ); + } + + /// <summary>Get an instance property.</summary> + /// <typeparam name="TValue">The property type.</typeparam> + /// <param name="obj">The object which has the property.</param> + /// <param name="name">The property name.</param> + /// <param name="required">Whether to throw an exception if the property is not found.</param> + public IReflectedProperty<TValue> GetProperty<TValue>(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty<TValue>(obj, name, required) + ); + } + + /// <summary>Get a static property.</summary> + /// <typeparam name="TValue">The property type.</typeparam> + /// <param name="type">The type which has the property.</param> + /// <param name="name">The property name.</param> + /// <param name="required">Whether to throw an exception if the property is not found.</param> + public IReflectedProperty<TValue> GetProperty<TValue>(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty<TValue>(type, name, required) + ); + } + + /// <summary>Get an instance method.</summary> + /// <param name="obj">The object which has the method.</param> + /// <param name="name">The field name.</param> + /// <param name="required">Whether to throw an exception if the field is not found.</param> + public IReflectedMethod GetMethod(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(obj, name, required) + ); + } + + /// <summary>Get a static method.</summary> + /// <param name="type">The type which has the method.</param> + /// <param name="name">The field name.</param> + /// <param name="required">Whether to throw an exception if the field is not found.</param> + public IReflectedMethod GetMethod(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(type, name, required) + ); } + /**** - ** Fields + ** Obsolete ****/ /// <summary>Get a private instance field.</summary> /// <typeparam name="TValue">The field type.</typeparam> @@ -41,11 +117,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="name">The field name.</param> /// <param name="required">Whether to throw an exception if the private field is not found.</param> /// <returns>Returns the field wrapper, or <c>null</c> if the field doesn't exist and <paramref name="required"/> is <c>false</c>.</returns> + [Obsolete] public IPrivateField<TValue> GetPrivateField<TValue>(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField<TValue>(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField<TValue>)this.GetField<TValue>(obj, name, required); } /// <summary>Get a private static field.</summary> @@ -53,26 +129,23 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="type">The type which has the field.</param> /// <param name="name">The field name.</param> /// <param name="required">Whether to throw an exception if the private field is not found.</param> + [Obsolete] public IPrivateField<TValue> GetPrivateField<TValue>(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField<TValue>(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField<TValue>)this.GetField<TValue>(type, name, required); } - /**** - ** Properties - ****/ /// <summary>Get a private instance property.</summary> /// <typeparam name="TValue">The property type.</typeparam> /// <param name="obj">The object which has the property.</param> /// <param name="name">The property name.</param> /// <param name="required">Whether to throw an exception if the private property is not found.</param> + [Obsolete] public IPrivateProperty<TValue> GetPrivateProperty<TValue>(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty<TValue>(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty<TValue>)this.GetProperty<TValue>(obj, name, required); } /// <summary>Get a private static property.</summary> @@ -80,17 +153,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="type">The type which has the property.</param> /// <param name="name">The property name.</param> /// <param name="required">Whether to throw an exception if the private property is not found.</param> + [Obsolete] public IPrivateProperty<TValue> GetPrivateProperty<TValue>(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty<TValue>(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty<TValue>)this.GetProperty<TValue>(type, name, required); } - /**** - ** Field values - ** (shorthand since this is the most common case) - ****/ /// <summary>Get the value of a private instance field.</summary> /// <typeparam name="TValue">The field type.</typeparam> /// <param name="obj">The object which has the field.</param> @@ -101,9 +170,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for <see cref="GetPrivateField{TValue}(object,string,bool)"/> followed by <see cref="IPrivateField{TValue}.GetValue"/>. /// When <paramref name="required" /> is false, this will return the default value if reflection fails. If you need to check whether the field exists, use <see cref="GetPrivateField{TValue}(object,string,bool)" /> instead. /// </remarks> + [Obsolete] public TValue GetPrivateValue<TValue>(object obj, string name, bool required = true) { - IPrivateField<TValue> field = this.GetPrivateField<TValue>(obj, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField<TValue> field = (IPrivateField<TValue>)this.GetField<TValue>(obj, name, required); return field != null ? field.GetValue() : default(TValue); @@ -119,64 +190,36 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for <see cref="GetPrivateField{TValue}(Type,string,bool)"/> followed by <see cref="IPrivateField{TValue}.GetValue"/>. /// When <paramref name="required" /> is false, this will return the default value if reflection fails. If you need to check whether the field exists, use <see cref="GetPrivateField{TValue}(Type,string,bool)" /> instead. /// </remarks> + [Obsolete] public TValue GetPrivateValue<TValue>(Type type, string name, bool required = true) { - IPrivateField<TValue> field = this.GetPrivateField<TValue>(type, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField<TValue> field = (IPrivateField<TValue>)this.GetField<TValue>(type, name, required); return field != null ? field.GetValue() : default(TValue); } - /**** - ** Methods - ****/ /// <summary>Get a private instance method.</summary> /// <param name="obj">The object which has the method.</param> /// <param name="name">The field name.</param> /// <param name="required">Whether to throw an exception if the private field is not found.</param> + [Obsolete] public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateMethod)this.GetMethod(obj, name, required); } /// <summary>Get a private static method.</summary> /// <param name="type">The type which has the method.</param> /// <param name="name">The field name.</param> /// <param name="required">Whether to throw an exception if the private field is not found.</param> + [Obsolete] public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(type, name, required) - ); - } - - /**** - ** Methods by signature - ****/ - /// <summary>Get a private instance method.</summary> - /// <param name="obj">The object which has the method.</param> - /// <param name="name">The field name.</param> - /// <param name="argumentTypes">The argument types of the method signature to find.</param> - /// <param name="required">Whether to throw an exception if the private field is not found.</param> - public IPrivateMethod GetPrivateMethod(object obj, string name, Type[] argumentTypes, bool required = true) - { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(obj, name, argumentTypes, required) - ); - } - - /// <summary>Get a private static method.</summary> - /// <param name="type">The type which has the method.</param> - /// <param name="name">The field name.</param> - /// <param name="argumentTypes">The argument types of the method signature to find.</param> - /// <param name="required">Whether to throw an exception if the private field is not found.</param> - public IPrivateMethod GetPrivateMethod(Type type, string name, Type[] argumentTypes, bool required = true) - { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(type, name, argumentTypes, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateMethod)this.GetMethod(type, name, required); } @@ -187,7 +230,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <typeparam name="T">The field value type.</typeparam> /// <param name="field">The field being accessed.</param> /// <returns>Returns the same field instance for convenience.</returns> - private IPrivateField<T> AssertAccessAllowed<T>(IPrivateField<T> field) + private IReflectedField<T> AssertAccessAllowed<T>(IReflectedField<T> field) { this.AssertAccessAllowed(field?.FieldInfo); return field; @@ -197,7 +240,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <typeparam name="T">The property value type.</typeparam> /// <param name="property">The property being accessed.</param> /// <returns>Returns the same property instance for convenience.</returns> - private IPrivateProperty<T> AssertAccessAllowed<T>(IPrivateProperty<T> property) + private IReflectedProperty<T> AssertAccessAllowed<T>(IReflectedProperty<T> property) { this.AssertAccessAllowed(property?.PropertyInfo); return property; @@ -206,7 +249,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>Assert that mods can use the reflection helper to access the given member.</summary> /// <param name="method">The method being accessed.</param> /// <returns>Returns the same method instance for convenience.</returns> - private IPrivateMethod AssertAccessAllowed(IPrivateMethod method) + private IReflectedMethod AssertAccessAllowed(IReflectedMethod method) { this.AssertAccessAllowed(method?.MethodInfo); return method; diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 5055da75..30fe211b 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -29,6 +29,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>The mod instance (if it was loaded).</summary> public IMod Mod { get; private set; } + /// <summary>The mod-provided API (if any).</summary> + public object Api { get; private set; } + /********* ** Public methods @@ -64,5 +67,13 @@ namespace StardewModdingAPI.Framework.ModLoading this.Mod = mod; return this; } + + /// <summary>Set the mod-provided API instance.</summary> + /// <param name="api">The mod-provided API.</param> + public IModMetadata SetApi(object api) + { + this.Api = api; + return this; + } } } diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index 9dde7a20..453d2868 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -15,26 +15,34 @@ namespace StardewModdingAPI.Framework /// <summary>The registered mod data.</summary> private readonly List<IModMetadata> Mods = new List<IModMetadata>(); - /// <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>An assembly full name => mod lookup.</summary> + private readonly IDictionary<string, IModMetadata> ModNamesByAssembly = new Dictionary<string, IModMetadata>(); + + /// <summary>Whether all mods have been initialised and their <see cref="IMod.Entry"/> method called.</summary> + public bool AreAllModsInitialised { get; set; } /********* ** Public methods *********/ - /**** - ** Basic metadata - ****/ + /// <summary>Register a mod as a possible source of deprecation warnings.</summary> + /// <param name="metadata">The mod metadata.</param> + public void Add(IModMetadata metadata) + { + this.Mods.Add(metadata); + this.ModNamesByAssembly[metadata.Mod.GetType().Assembly.FullName] = metadata; + } + /// <summary>Get metadata for all loaded mods.</summary> - public IEnumerable<IManifest> GetAll() + public IEnumerable<IModMetadata> GetAll() { - return this.Mods.Select(p => p.Manifest); + return this.Mods.Select(p => p); } /// <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) + public IModMetadata Get(string uniqueID) { // normalise search ID if (string.IsNullOrWhiteSpace(uniqueID)) @@ -42,37 +50,13 @@ namespace StardewModdingAPI.Framework uniqueID = uniqueID.Trim(); // find match - return this.GetAll().FirstOrDefault(p => p.UniqueID.Trim().Equals(uniqueID, StringComparison.InvariantCultureIgnoreCase)); - } - - /// <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.Get(uniqueID) != null; - } - - /**** - ** Mod data - ****/ - /// <summary>Register a mod as a possible source of deprecation warnings.</summary> - /// <param name="metadata">The mod metadata.</param> - public void Add(IModMetadata metadata) - { - this.Mods.Add(metadata); - this.ModNamesByAssembly[metadata.Mod.GetType().Assembly.FullName] = metadata.DisplayName; - } - - /// <summary>Get all enabled mods.</summary> - public IEnumerable<IModMetadata> GetMods() - { - return (from mod in this.Mods select mod); + return this.GetAll().FirstOrDefault(p => p.Manifest.UniqueID.Trim().Equals(uniqueID, StringComparison.InvariantCultureIgnoreCase)); } - /// <summary>Get the friendly mod name which defines a type.</summary> + /// <summary>Get the mod metadata from one of its assemblies.</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) + public IModMetadata GetFrom(Type type) { // null if (type == null) @@ -89,7 +73,7 @@ namespace StardewModdingAPI.Framework /// <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() + public IModMetadata GetFromStack() { // get stack frames StackTrace stack = new StackTrace(); @@ -101,9 +85,9 @@ namespace StardewModdingAPI.Framework foreach (StackFrame frame in frames) { MethodBase method = frame.GetMethod(); - string name = this.GetModFrom(method.ReflectedType); - if (name != null) - return name; + IModMetadata mod = this.GetFrom(method.ReflectedType); + if (mod != null) + return mod; } // no known assembly found diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs new file mode 100644 index 00000000..5abebc18 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// <summary>Generates proxy classes to access mod APIs through an arbitrary interface.</summary> + internal class InterfaceProxyBuilder + { + /********* + ** Properties + *********/ + /// <summary>The CLR module in which to create proxy classes.</summary> + private readonly ModuleBuilder ModuleBuilder; + + /// <summary>The generated proxy types.</summary> + private readonly IDictionary<string, Type> GeneratedTypes = new Dictionary<string, Type>(); + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public InterfaceProxyBuilder() + { + AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); + this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); + } + + /// <summary>Create an API proxy.</summary> + /// <typeparam name="TInterface">The interface through which to access the API.</typeparam> + /// <param name="instance">The API instance to access.</param> + /// <param name="sourceModID">The unique ID of the mod consuming the API.</param> + /// <param name="targetModID">The unique ID of the mod providing the API.</param> + public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID) + where TInterface : class + { + // validate + if (instance == null) + throw new InvalidOperationException("Can't proxy access to a null API."); + if (!typeof(TInterface).IsInterface) + throw new InvalidOperationException("The proxy type must be an interface, not a class."); + + // get proxy type + Type targetType = instance.GetType(); + string proxyTypeName = $ |
