summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-12-26 00:31:36 -0500
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-12-26 00:31:36 -0500
commit15d4b6310e3dd15c62f3faedbf1290b2db26fb59 (patch)
tree47d49a9c69628f0df1e688361f46bc5b46b3c0fd /src/SMAPI
parent5cc5f089b9645a60385ff293b5a7202f260bfc0f (diff)
parentf19cc3aac1a781bf2f2d20bc9577c2fe929b1e96 (diff)
downloadSMAPI-15d4b6310e3dd15c62f3faedbf1290b2db26fb59.tar.gz
SMAPI-15d4b6310e3dd15c62f3faedbf1290b2db26fb59.tar.bz2
SMAPI-15d4b6310e3dd15c62f3faedbf1290b2db26fb59.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI')
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Events/EventArgsInput.cs8
-rw-r--r--src/SMAPI/Events/GameEvents.cs10
-rw-r--r--src/SMAPI/Framework/Content/ContentCache.cs4
-rw-r--r--src/SMAPI/Framework/DeprecationManager.cs6
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs7
-rw-r--r--src/SMAPI/Framework/InternalExtensions.cs11
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs67
-rw-r--r--src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs167
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs11
-rw-r--r--src/SMAPI/Framework/ModRegistry.cs62
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs138
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedField.cs (renamed from src/SMAPI/Framework/Reflection/PrivateField.cs)16
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedMethod.cs (renamed from src/SMAPI/Framework/Reflection/PrivateMethod.cs)16
-rw-r--r--src/SMAPI/Framework/Reflection/ReflectedProperty.cs (renamed from src/SMAPI/Framework/Reflection/PrivateProperty.cs)42
-rw-r--r--src/SMAPI/Framework/Reflection/Reflector.cs106
-rw-r--r--src/SMAPI/Framework/SContentManager.cs10
-rw-r--r--src/SMAPI/Framework/SGame.cs103
-rw-r--r--src/SMAPI/IMod.cs7
-rw-r--r--src/SMAPI/IModRegistry.cs13
-rw-r--r--src/SMAPI/IPrivateField.cs6
-rw-r--r--src/SMAPI/IPrivateMethod.cs6
-rw-r--r--src/SMAPI/IPrivateProperty.cs6
-rw-r--r--src/SMAPI/IReflectedField.cs26
-rw-r--r--src/SMAPI/IReflectedMethod.cs27
-rw-r--r--src/SMAPI/IReflectedProperty.cs26
-rw-r--r--src/SMAPI/IReflectionHelper.cs55
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs1
-rw-r--r--src/SMAPI/Mod.cs3
-rw-r--r--src/SMAPI/Program.cs272
-rw-r--r--src/SMAPI/StardewModdingAPI.config.json10
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj14
32 files changed, 873 insertions, 385 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 41b79272..786ae32b 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -29,7 +29,7 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new SemanticVersion("2.2");
+ public static ISemanticVersion ApiVersion { get; } = new SemanticVersion("2.3");
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new SemanticVersion("1.2.30");
diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs
index 54ce9b53..a5325b76 100644
--- a/src/SMAPI/Events/EventArgsInput.cs
+++ b/src/SMAPI/Events/EventArgsInput.cs
@@ -16,7 +16,7 @@ namespace StardewModdingAPI.Events
public SButton Button { get; }
/// <summary>The current cursor position.</summary>
- public ICursorPosition Cursor { get; set; }
+ public ICursorPosition Cursor { get; }
/// <summary>Whether the input is considered a 'click' by the game for enabling action.</summary>
[Obsolete("Use " + nameof(EventArgsInput.IsActionButton) + " or " + nameof(EventArgsInput.IsUseToolButton) + " instead")] // deprecated in SMAPI 2.1
@@ -28,6 +28,9 @@ namespace StardewModdingAPI.Events
/// <summary>Whether the input should use tools on the affected tile.</summary>
public bool IsUseToolButton { get; }
+ /// <summary>Whether a mod has indicated the key was already handled.</summary>
+ public bool IsSuppressed { get; private set; }
+
/*********
** Public methods
@@ -55,6 +58,9 @@ namespace StardewModdingAPI.Events
/// <param name="button">The button to suppress.</param>
public void SuppressButton(SButton button)
{
+ if (button == this.Button)
+ this.IsSuppressed = true;
+
// keyboard
if (button.TryGetKeyboard(out Keys key))
Game1.oldKBState = new KeyboardState(Game1.oldKBState.GetPressedKeys().Union(new[] { key }).ToArray());
diff --git a/src/SMAPI/Events/GameEvents.cs b/src/SMAPI/Events/GameEvents.cs
index b477376e..3466470d 100644
--- a/src/SMAPI/Events/GameEvents.cs
+++ b/src/SMAPI/Events/GameEvents.cs
@@ -33,6 +33,9 @@ namespace StardewModdingAPI.Events
/// <summary>Raised every 60th tick (≈once per second).</summary>
public static event EventHandler OneSecondTick;
+ /// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
+ public static event EventHandler FirstUpdateTick;
+
/*********
** Internal methods
@@ -92,5 +95,12 @@ namespace StardewModdingAPI.Events
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.OneSecondTick)}", GameEvents.OneSecondTick?.GetInvocationList());
}
+
+ /// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary>
+ /// <param name="monitor">Encapsulates monitoring and logging.</param>
+ internal static void InvokeFirstUpdateTick(IMonitor monitor)
+ {
+ monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents.FirstUpdateTick?.GetInvocationList());
+ }
}
}
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 = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>";
+ if (!this.GeneratedTypes.TryGetValue(proxyTypeName, out Type type))
+ {
+ type = this.CreateProxyType(proxyTypeName, typeof(TInterface), targetType);
+ this.GeneratedTypes[proxyTypeName] = type;
+ }
+
+ // create instance
+ ConstructorInfo constructor = type.GetConstructor(new[] { targetType });
+ if (constructor == null)
+ throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{proxyTypeName}'."); // should never happen
+ return (TInterface)constructor.Invoke(new[] { instance });
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Define a class which proxies access to a target type through an interface.</summary>
+ /// <param name="proxyTypeName">The name of the proxy type to generate.</param>
+ /// <param name="interfaceType">The interface type through which to access the target.</param>
+ /// <param name="targetType">The target type to access.</param>
+ private Type CreateProxyType(string proxyTypeName, Type interfaceType, Type targetType)
+ {
+ // define proxy type
+ TypeBuilder proxyBuilder = this.ModuleBuilder.DefineType(proxyTypeName, TypeAttributes.Public | TypeAttributes.Class);
+ proxyBuilder.AddInterfaceImplementation(interfaceType);
+
+ // create field to store target instance
+ FieldBuilder field = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
+
+ // create constructor which accepts target instance
+ {
+ ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType });
+ ILGenerator il = constructor.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0); // this
+ // ReSharper disable once AssignNullToNotNullAttribute -- never null
+ il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); // call base constructor
+ il.Emit(OpCodes.Ldarg_0); // this
+ il.Emit(OpCodes.Ldarg_1); // load argument
+ il.Emit(OpCodes.Stfld, field); // set field to loaded argument
+ il.Emit(OpCodes.Ret);
+ }
+
+ // proxy methods
+ foreach (MethodInfo proxyMethod in interfaceType.GetMethods())
+ {
+ var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray());
+ if (targetMethod == null)
+ throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API.");
+
+ this.ProxyMethod(proxyBuilder, targetMethod, field);
+ }
+
+ // create type
+ return proxyBuilder.CreateType();
+ }
+
+ /// <summary>Define a method which proxies access to a method on the target.</summary>
+ /// <param name="proxyBuilder">The proxy type being generated.</param>
+ /// <param name="target">The target method.</param>
+ /// <param name="instanceField">The proxy field containing the API instance.</param>
+ private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField)
+ {
+ Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray();
+
+ // create method
+ MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
+ methodBuilder.SetParameters(argTypes);
+ methodBuilder.SetReturnType(target.ReturnType);
+
+ // create method body
+ {
+ ILGenerator il = methodBuilder.GetILGenerator();
+
+ // load target instance
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldfld, instanceField);
+
+ // invoke target method on instance
+ for (int i = 0; i < argTypes.Length; i++)
+ il.Emit(OpCodes.Ldarg, i + 1);
+ il.Emit(OpCodes.Call, target);
+
+ // return result
+ il.Emit(OpCodes.Ret);
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Reflection/PrivateField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs
index 0bf45969..ad1557bb 100644
--- a/src/SMAPI/Framework/Reflection/PrivateField.cs
+++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs
@@ -1,11 +1,11 @@
-using System;
+using System;
using System.Reflection;
namespace StardewModdingAPI.Framework.Reflection
{
- /// <summary>A private field obtained through reflection.</summary>
+ /// <summary>A field obtained through reflection.</summary>
/// <typeparam name="TValue">The field value type.</typeparam>
- internal class PrivateField<TValue> : IPrivateField<TValue>
+ internal class ReflectedField<TValue> : IPrivateField<TValue>, IReflectedField<TValue>
{
/*********
** Properties
@@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="isStatic">Whether the field is static.</param>
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="field"/> is null.</exception>
/// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static field, or not null for a static field.</exception>
- public PrivateField(Type parentType, object obj, FieldInfo field, bool isStatic)
+ public ReflectedField(Type parentType, object obj, FieldInfo field, bool isStatic)
{
// validate
if (parentType == null)
@@ -64,11 +64,11 @@ namespace StardewModdingAPI.Framework.Reflection
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"Can't convert the private {this.DisplayName} field from {this.FieldInfo.FieldType.FullName} to {typeof(TValue).FullName}.");
+ throw new InvalidCastException($"Can't convert the {this.DisplayName} field from {this.FieldInfo.FieldType.FullName} to {typeof(TValue).FullName}.");
}
catch (Exception ex)
{
- throw new Exception($"Couldn't get the value of the private {this.DisplayName} field", ex);
+ throw new Exception($"Couldn't get the value of the {this.DisplayName} field", ex);
}
}
@@ -82,11 +82,11 @@ namespace StardewModdingAPI.Framework.Reflection
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"Can't assign the private {this.DisplayName} field a {typeof(TValue).FullName} value, must be compatible with {this.FieldInfo.FieldType.FullName}.");
+ throw new InvalidCastException($"Can't assign the {this.DisplayName} field a {typeof(TValue).FullName} value, must be compatible with {this.FieldInfo.FieldType.FullName}.");
}
catch (Exception ex)
{
- throw new Exception($"Couldn't set the value of the private {this.DisplayName} field", ex);
+ throw new Exception($"Couldn't set the value of the {this.DisplayName} field", ex);
}
}
}
diff --git a/src/SMAPI/Framework/Reflection/PrivateMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs
index ba2374f4..376de869 100644
--- a/src/SMAPI/Framework/Reflection/PrivateMethod.cs
+++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs
@@ -3,8 +3,8 @@ using System.Reflection;
namespace StardewModdingAPI.Framework.Reflection
{
- /// <summary>A private method obtained through reflection.</summary>
- internal class PrivateMethod : IPrivateMethod
+ /// <summary>A method obtained through reflection.</summary>
+ internal class ReflectedMethod : IPrivateMethod, IReflectedMethod
{
/*********
** Properties
@@ -33,10 +33,10 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="parentType">The type that has the method.</param>
/// <param name="obj">The object that has the instance method(if applicable).</param>
/// <param name="method">The reflection metadata.</param>
- /// <param name="isStatic">Whether the field is static.</param>
+ /// <param name="isStatic">Whether the method is static.</param>
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="method"/> is null.</exception>
/// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static method, or not null for a static method.</exception>
- public PrivateMethod(Type parentType, object obj, MethodInfo method, bool isStatic)
+ public ReflectedMethod(Type parentType, object obj, MethodInfo method, bool isStatic)
{
// validate
if (parentType == null)
@@ -67,7 +67,7 @@ namespace StardewModdingAPI.Framework.Reflection
}
catch (Exception ex)
{
- throw new Exception($"Couldn't invoke the private {this.DisplayName} field", ex);
+ throw new Exception($"Couldn't invoke the {this.DisplayName} method", ex);
}
// cast return value
@@ -77,7 +77,7 @@ namespace StardewModdingAPI.Framework.Reflection
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"Can't convert the return value of the private {this.DisplayName} method from {this.MethodInfo.ReturnType.FullName} to {typeof(TValue).FullName}.");
+ throw new InvalidCastException($"Can't convert the return value of the {this.DisplayName} method from {this.MethodInfo.ReturnType.FullName} to {typeof(TValue).FullName}.");
}
}
@@ -92,8 +92,8 @@ namespace StardewModdingAPI.Framework.Reflection
}
catch (Exception ex)
{
- throw new Exception($"Couldn't invoke the private {this.DisplayName} field", ex);
+ throw new Exception($"Couldn't invoke the {this.DisplayName} method", ex);
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/Framework/Reflection/PrivateProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs
index be346d71..d6c964c1 100644
--- a/src/SMAPI/Framework/Reflection/PrivateProperty.cs
+++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs
@@ -3,9 +3,9 @@ using System.Reflection;
namespace StardewModdingAPI.Framework.Reflection
{
- /// <summary>A private property obtained through reflection.</summary>
+ /// <summary>A property obtained through reflection.</summary>
/// <typeparam name="TValue">The property value type.</typeparam>
- internal class PrivateProperty<TValue> : IPrivateProperty<TValue>
+ internal class ReflectedProperty<TValue> : IPrivateProperty<TValue>, IReflectedProperty<TValue>
{
/*********
** Properties
@@ -14,10 +14,10 @@ namespace StardewModdingAPI.Framework.Reflection
private readonly string DisplayName;
/// <summary>The underlying property getter.</summary>
- private readonly Func<TValue> GetterDelegate;
+ private readonly Func<TValue> GetMethod;
/// <summary>The underlying property setter.</summary>
- private readonly Action<TValue> SetterDelegate;
+ private readonly Action<TValue> SetMethod;
/*********
@@ -31,13 +31,13 @@ namespace StardewModdingAPI.Framework.Reflection
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="parentType">The type that has the field.</param>
- /// <param name="obj">The object that has the instance field (if applicable).</param>
+ /// <param name="parentType">The type that has the property.</param>
+ /// <param name="obj">The object that has the instance property (if applicable).</param>
/// <param name="property">The reflection metadata.</param>
- /// <param name="isStatic">Whether the field is static.</param>
+ /// <param name="isStatic">Whether the property is static.</param>
/// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="property"/> is null.</exception>
- /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static field, or not null for a static field.</exception>
- public PrivateProperty(Type parentType, object obj, PropertyInfo property, bool isStatic)
+ /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static property, or not null for a static property.</exception>
+ public ReflectedProperty(Type parentType, object obj, PropertyInfo property, bool isStatic)
{
// validate input
if (parentType == null)
@@ -55,24 +55,29 @@ namespace StardewModdingAPI.Framework.Reflection
this.DisplayName = $"{parentType.FullName}::{property.Name}";
this.PropertyInfo = property;
- this.GetterDelegate = (Func<TValue>)Delegate.CreateDelegate(typeof(Func<TValue>), obj, this.PropertyInfo.GetMethod);
- this.SetterDelegate = (Action<TValue>)Delegate.CreateDelegate(typeof(Action<TValue>), obj, this.PropertyInfo.SetMethod);
+ if (this.PropertyInfo.GetMethod != null)
+ this.GetMethod = (Func<TValue>)Delegate.CreateDelegate(typeof(Func<TValue>), obj, this.PropertyInfo.GetMethod);
+ if (this.PropertyInfo.SetMethod != null)
+ this.SetMethod = (Action<TValue>)Delegate.CreateDelegate(typeof(Action<TValue>), obj, this.PropertyInfo.SetMethod);
}
/// <summary>Get the property value.</summary>
public TValue GetValue()
{
+ if (this.GetMethod == null)
+ throw new InvalidOperationException($"The {this.DisplayName} property has no get method.");
+
try
{
- return this.GetterDelegate();
+ return this.GetMethod();
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"Can't convert the private {this.DisplayName} property from {this.PropertyInfo.PropertyType.FullName} to {typeof(TValue).FullName}.");
+ throw new InvalidCastException($"Can't convert the {this.DisplayName} property from {this.PropertyInfo.PropertyType.FullName} to {typeof(TValue).FullName}.");
}
catch (Exception ex)
{
- throw new Exception($"Couldn't get the value of the private {this.DisplayName} property", ex);
+ throw new Exception($"Couldn't get the value of the {this.DisplayName} property", ex);
}
}
@@ -80,17 +85,20 @@ namespace StardewModdingAPI.Framework.Reflection
//// <param name="value">The value to set.</param>
public void SetValue(TValue value)
{
+ if (this.SetMethod == null)
+ throw new InvalidOperationException($"The {this.DisplayName} property has no set method.");
+
try
{
- this.SetterDelegate(value);
+ this.SetMethod(value);
}
catch (InvalidCastException)
{
- throw new InvalidCastException($"Can't assign the private {this.DisplayName} property a {typeof(TValue).FullName} value, must be compatible with {this.PropertyInfo.PropertyType.FullName}.");
+ throw new InvalidCastException($"Can't assign the {this.DisplayName} property a {typeof(TValue).FullName} value, must be compatible with {this.PropertyInfo.PropertyType.FullName}.");
}
catch (Exception ex)
{
- throw new Exception($"Couldn't set the value of the private {this.DisplayName} property", ex);
+ throw new Exception($"Couldn't set the value of the {this.DisplayName} property", ex);
}
}
}
diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs
index 23a48505..910e3a54 100644
--- a/src/SMAPI/Framework/Reflection/Reflector.cs
+++ b/src/SMAPI/Framework/Reflection/Reflector.cs
@@ -5,7 +5,7 @@ using System.Runtime.Caching;
namespace StardewModdingAPI.Framework.Reflection
{
- /// <summary>Provides helper methods for accessing private game code.</summary>
+ /// <summary>Provides helper methods for accessing inaccessible code.</summary>
/// <remarks>This implementation searches up the type hierarchy, and caches the reflected fields and methods with a sliding expiry (to optimise performance without unnecessary memory usage).</remarks>
internal class Reflector
{
@@ -25,139 +25,139 @@ namespace StardewModdingAPI.Framework.Reflection
/****
** Fields
****/
- /// <summary>Get a private instance field.</summary>
+ /// <summary>Get a 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 private field is not found.</param>
+ /// <param name="required">Whether to throw an exception if the 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>
- public IPrivateField<TValue> GetPrivateField<TValue>(object obj, string name, bool required = true)
+ public IReflectedField<TValue> GetField<TValue>(object obj, string name, bool required = true)
{
// validate
if (obj == null)
- throw new ArgumentNullException(nameof(obj), "Can't get a private instance field from a null object.");
+ throw new ArgumentNullException(nameof(obj), "Can't get a instance field from a null object.");
// get field from hierarchy
- IPrivateField<TValue> field = this.GetFieldFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ IReflectedField<TValue> field = this.GetFieldFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (required && field == null)
- throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance field.");
+ throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance field.");
return field;
}
- /// <summary>Get a private static field.</summary>
+ /// <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 private field is not found.</param>
- public IPrivateField<TValue> GetPrivateField<TValue>(Type type, string name, bool required = true)
+ /// <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)
{
// get field from hierarchy
- IPrivateField<TValue> field = this.GetFieldFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public);
+ IReflectedField<TValue> field = this.GetFieldFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public);
if (required && field == null)
- throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static field.");
+ throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static field.");
return field;
}
/****
** Properties
****/
- /// <summary>Get a private instance property.</summary>
+ /// <summary>Get a 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>
- public IPrivateProperty<TValue> GetPrivateProperty<TValue>(object obj, string name, bool required = true)
+ /// <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)
{
// validate
if (obj == null)
- throw new ArgumentNullException(nameof(obj), "Can't get a private instance property from a null object.");
+ throw new ArgumentNullException(nameof(obj), "Can't get a instance property from a null object.");
// get property from hierarchy
- IPrivateProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ IReflectedProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (required && property == null)
- throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance property.");
+ throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance property.");
return property;
}
- /// <summary>Get a private static property.</summary>
+ /// <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 private property is not found.</param>
- public IPrivateProperty<TValue> GetPrivateProperty<TValue>(Type type, string name, bool required = true)
+ /// <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)
{
// get field from hierarchy
- IPrivateProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
+ IReflectedProperty<TValue> property = this.GetPropertyFromHierarchy<TValue>(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (required && property == null)
- throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static property.");
+ throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static property.");
return property;
}
/****
** Methods
****/
- /// <summary>Get a private instance method.</summary>
+ /// <summary>Get a 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>
- public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true)
+ /// <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)
{
// validate
if (obj == null)
- throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object.");
+ throw new ArgumentNullException(nameof(obj), "Can't get a instance method from a null object.");
// get method from hierarchy
- IPrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ IReflectedMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (required && method == null)
- throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method.");
+ throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance method.");
return method;
}
- /// <summary>Get a private static method.</summary>
+ /// <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 private field is not found.</param>
- public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true)
+ /// <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)
{
// get method from hierarchy
- IPrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
+ IReflectedMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (required && method == null)
- throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static method.");
+ throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static method.");
return method;
}
/****
** Methods by signature
****/
- /// <summary>Get a private instance method.</summary>
+ /// <summary>Get a 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)
+ /// <param name="required">Whether to throw an exception if the field is not found.</param>
+ public IReflectedMethod GetMethod(object obj, string name, Type[] argumentTypes, bool required = true)
{
// validate parent
if (obj == null)
- throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object.");
+ throw new ArgumentNullException(nameof(obj), "Can't get a instance method from a null object.");
// get method from hierarchy
- PrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, argumentTypes);
+ ReflectedMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, argumentTypes);
if (required && method == null)
- throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method with that signature.");
+ throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance method with that signature.");
return method;
}
- /// <summary>Get a private static method.</summary>
+ /// <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="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)
+ /// <param name="required">Whether to throw an exception if the field is not found.</param>
+ public IReflectedMethod GetMethod(Type type, string name, Type[] argumentTypes, bool required = true)
{
// get field from hierarchy
- PrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, argumentTypes);
+ ReflectedMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, argumentTypes);
if (required && method == null)
- throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static method with that signature.");
+ throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static method with that signature.");
return method;
}
@@ -171,7 +171,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="obj">The object which has the field.</param>
/// <param name="name">The field name.</param>
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of field to find.</param>
- private IPrivateField<TValue> GetFieldFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags)
+ private IReflectedField<TValue> GetFieldFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
FieldInfo field = this.GetCached<FieldInfo>($"field::{isStatic}::{type.FullName}::{name}", () =>
@@ -183,7 +183,7 @@ namespace StardewModdingAPI.Framework.Reflection
});
return field != null
- ? new PrivateField<TValue>(type, obj, field, isStatic)
+ ? new ReflectedField<TValue>(type, obj, field, isStatic)
: null;
}
@@ -193,7 +193,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="obj">The object which has the property.</param>
/// <param name="name">The property name.</param>
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of property to find.</param>
- private IPrivateProperty<TValue> GetPropertyFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags)
+ private IReflectedProperty<TValue> GetPropertyFromHierarchy<TValue>(Type type, object obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
PropertyInfo property = this.GetCached<PropertyInfo>($"property::{isStatic}::{type.FullName}::{name}", () =>
@@ -205,7 +205,7 @@ namespace StardewModdingAPI.Framework.Reflection
});
return property != null
- ? new PrivateProperty<TValue>(type, obj, property, isStatic)
+ ? new ReflectedProperty<TValue>(type, obj, property, isStatic)
: null;
}
@@ -214,7 +214,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="obj">The object which has the method.</param>
/// <param name="name">The method name.</param>
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of method to find.</param>
- private IPrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags)
+ private IReflectedMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () =>
@@ -226,7 +226,7 @@ namespace StardewModdingAPI.Framework.Reflection
});
return method != null
- ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
+ ? new ReflectedMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static))
: null;
}
@@ -236,7 +236,7 @@ namespace StardewModdingAPI.Framework.Reflection
/// <param name="name">The method name.</param>
/// <param name="bindingFlags">The reflection binding which flags which indicates what type of method to find.</param>
/// <param name="argumentTypes">The argument types of the method signature to find.</param>
- private PrivateMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags, Type[] argumentTypes)
+ private ReflectedMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags, Type[] argumentTypes)
{
bool isStatic = bindingFlags.HasFlag(BindingFlags.Static);
MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}({string.Join(",", argumentTypes.Select(p => p.FullName))})", () =>
@@ -247,7 +247,7 @@ namespace StardewModdingAPI.Framework.Reflection
return methodInfo;
});
return method != null
- ? new PrivateMethod(type, obj, method, isStatic)
+ ? new ReflectedMethod(type, obj, method, isStatic)
: null;
}
diff --git a/src/SMAPI/Framework/SContentManager.cs b/src/SMAPI/Framework/SContentManager.cs
index 524b2d17..ebea6c84 100644
--- a/src/SMAPI/Framework/SContentManager.cs
+++ b/src/SMAPI/Framework/SContentManager.cs
@@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework
private readonly ContentCache Cache;
/// <summary>The private <see cref="LocalizedContentManager"/> method which generates the locale portion of an asset name.</summary>
- private readonly IPrivateMethod GetKeyLocale;
+ private readonly IReflectedMethod GetKeyLocale;
/// <summary>The language codes used in asset keys.</summary>
private readonly IDictionary<string, LanguageCode> KeyLocales;
@@ -101,7 +101,7 @@ namespace StardewModdingAPI.Framework
// init
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Cache = new ContentCache(this, reflection, SContentManager.PossiblePathSeparators, SContentManager.PreferredPathSeparator);
- this.GetKeyLocale = reflection.GetPrivateMethod(this, "languageCode");
+ this.GetKeyLocale = reflection.GetMethod(this, "languageCode");
this.ModContentPrefix = this.GetAssetNameFromFilePath(Constants.ModPath);
// get asset data
@@ -205,7 +205,7 @@ namespace StardewModdingAPI.Framework
return this.LoadImpl<T>(assetName, instance);
// load mod content
- SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading content asset '{assetName}': {reasonPhrase}.");
+ SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading content asset '{assetName}': {reasonPhrase}");
try
{
return this.WithWriteLock(() =>
@@ -252,6 +252,8 @@ namespace StardewModdingAPI.Framework
}
catch (Exception ex) when (!(ex is SContentLoadException))
{
+ if (ex.GetInnermostException() is DllNotFoundException dllEx && dllEx.Message == "libgdiplus.dylib")
+ throw GetContentError("couldn't find libgdiplus, which is needed to load mod images. Make sure Mono is installed and you're running the game through the normal launcher.");
throw new SContentLoadException($"The content manager failed loading content asset '{assetName}'.", ex);
}
}
@@ -413,7 +415,7 @@ namespace StardewModdingAPI.Framework
private IDictionary<string, LanguageCode> GetKeyLocales(Reflector reflection)
{
// get the private code field directly to avoid changed-code logic
- IPrivateField<LanguageCode> codeField = reflection.GetPrivateField<LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
+ IReflectedField<LanguageCode> codeField = reflection.GetField<LanguageCode>(typeof(LocalizedContentManager), "_currentLangCode");
// remember previous settings
LanguageCode previousCode = codeField.GetValue();
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index c886a4b7..0a614f17 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -133,20 +133,20 @@ namespace StardewModdingAPI.Framework
// ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
/// <summary>Used to access private fields and methods.</summary>
- private static List<float> _fpsList => SGame.Reflection.GetPrivateField<List<float>>(typeof(Game1), nameof(_fpsList)).GetValue();
- private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField<Stopwatch>(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue();
+ private static List<float> _fpsList => SGame.Reflection.GetField<List<float>>(typeof(Game1), nameof(_fpsList)).GetValue();
+ private static Stopwatch _fpsStopwatch => SGame.Reflection.GetField<Stopwatch>(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue();
private static float _fps
{
- set => SGame.Reflection.GetPrivateField<float>(typeof(Game1), nameof(_fps)).SetValue(value);
+ set => SGame.Reflection.GetField<float>(typeof(Game1), nameof(_fps)).SetValue(value);
}
- private static Task _newDayTask => SGame.Reflection.GetPrivateField<Task>(typeof(Game1), nameof(_newDayTask)).GetValue();
- private Color bgColor => SGame.Reflection.GetPrivateField<Color>(this, nameof(bgColor)).GetValue();
- public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty<RenderTarget2D>(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop
- public BlendState lightingBlend => SGame.Reflection.GetPrivateField<BlendState>(this, nameof(lightingBlend)).GetValue();
- private readonly Action drawFarmBuildings = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawFarmBuildings)).Invoke();
- private readonly Action drawHUD = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawHUD)).Invoke();
- private readonly Action drawDialogueBox = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(drawDialogueBox)).Invoke();
- private readonly Action renderScreenBuffer = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke();
+ private static Task _newDayTask => SGame.Reflection.GetField<Task>(typeof(Game1), nameof(_newDayTask)).GetValue();
+ private Color bgColor => SGame.Reflection.GetField<Color>(this, nameof(bgColor)).GetValue();
+ public RenderTarget2D screenWrapper => SGame.Reflection.GetProperty<RenderTarget2D>(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop
+ public BlendState lightingBlend => SGame.Reflection.GetField<BlendState>(this, nameof(lightingBlend)).GetValue();
+ private readonly Action drawFarmBuildings = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawFarmBuildings)).Invoke();
+ private readonly Action drawHUD = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawHUD)).Invoke();
+ private readonly Action drawDialogueBox = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawDialogueBox)).Invoke();
+ private readonly Action renderScreenBuffer = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke();
// ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming
@@ -182,7 +182,7 @@ namespace StardewModdingAPI.Framework
this.SContentManager = new SContentManager(this.Content.ServiceProvider, this.Content.RootDirectory, Thread.CurrentThread.CurrentUICulture, null, this.Monitor, reflection);
this.Content = new ContentManagerShim(this.SContentManager, "SGame.Content");
Game1.content = new ContentManagerShim(this.SContentManager, "Game1.content");
- reflection.GetPrivateField<LocalizedContentManager>(typeof(Game1), "_temporaryContent").SetValue(new ContentManagerShim(this.SContentManager, "Game1._temporaryContent")); // regenerate value with new content manager
+ reflection.GetField<LocalizedContentManager>(typeof(Game1), "_temporaryContent").SetValue(new ContentManagerShim(this.SContentManager, "Game1._temporaryContent")); // regenerate value with new content manager
}
/****
@@ -557,9 +557,12 @@ namespace StardewModdingAPI.Framework
/*********
** Update events
*********/
- GameEvents.InvokeUpdateTick(this.Monitor);
if (this.FirstUpdate)
+ {
this.FirstUpdate = false;
+ GameEvents.InvokeFirstUpdateTick(this.Monitor);
+ }
+ GameEvents.InvokeUpdateTick(this.Monitor);
if (this.CurrentUpdateTick % 2 == 0)
GameEvents.InvokeSecondUpdateTick(this.Monitor);
if (this.CurrentUpdateTick % 4 == 0)
@@ -689,6 +692,7 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error);
activeClickableMenu.exitThisMenu();
}
+ this.RaisePostRender();
Game1.spriteBatch.End();
}
//base.Draw(gameTime);
@@ -712,6 +716,7 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error);
Game1.activeClickableMenu.exitThisMenu();
}
+ this.RaisePostRender();
Game1.spriteBatch.End();
if ((double)Game1.options.zoomLevel != 1.0)
{
@@ -721,11 +726,12 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
Game1.spriteBatch.End();
}
- if (Game1.overlayMenu == null)
- return;
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.overlayMenu.draw(Game1.spriteBatch);
- Game1.spriteBatch.End();
+ if (Game1.overlayMenu != null)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.overlayMenu.draw(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ }
}
else if ((int)Game1.gameMode == 11)
{
@@ -733,6 +739,7 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink);
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 0));
Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White);
+ this.RaisePostRender();
Game1.spriteBatch.End();
}
else if (Game1.currentMinigame != null)
@@ -744,6 +751,7 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Color.Black * ((int)Game1.gameMode == 0 ? 1f - Game1.fadeToBlackAlpha : Game1.fadeToBlackAlpha));
Game1.spriteBatch.End();
}
+ this.RaisePostRender(needsNewBatch: true);
if ((double)Game1.options.zoomLevel != 1.0)
{
this.GraphicsDevice.SetRenderTarget((RenderTarget2D)null);
@@ -752,11 +760,12 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
Game1.spriteBatch.End();
}
- if (Game1.overlayMenu == null)
- return;
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.overlayMenu.draw(Game1.spriteBatch);
- Game1.spriteBatch.End();
+ if (Game1.overlayMenu != null)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.overlayMenu.draw(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ }
}
else if (Game1.showingEndOfNightStuff)
{
@@ -775,6 +784,7 @@ namespace StardewModdingAPI.Framework
Game1.activeClickableMenu.exitThisMenu();
}
}
+ this.RaisePostRender();
Game1.spriteBatch.End();
if ((double)Game1.options.zoomLevel != 1.0)
{
@@ -784,11 +794,12 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
Game1.spriteBatch.End();
}
- if (Game1.overlayMenu == null)
- return;
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.overlayMenu.draw(Game1.spriteBatch);
- Game1.spriteBatch.End();
+ if (Game1.overlayMenu != null)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.overlayMenu.draw(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ }
}
else if ((int)Game1.gameMode == 6)
{
@@ -806,6 +817,7 @@ namespace StardewModdingAPI.Framework
int x = 64;
int y = Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - height;
SpriteText.drawString(Game1.spriteBatch, s, x, y, 999999, widthOfString, height, 1f, 0.88f, false, 0, str5, -1);
+ this.RaisePostRender();
Game1.spriteBatch.End();
if ((double)Game1.options.zoomLevel != 1.0)
{
@@ -815,11 +827,12 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Draw((Texture2D)this.screenWrapper, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(this.screenWrapper.Bounds), Color.White, 0.0f, Vector2.Zero, Game1.options.zoomLevel, SpriteEffects.None, 1f);
Game1.spriteBatch.End();
}
- if (Game1.overlayMenu == null)
- return;
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
- Game1.overlayMenu.draw(Game1.spriteBatch);
- Game1.spriteBatch.End();
+ if (Game1.overlayMenu != null)
+ {
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
+ Game1.overlayMenu.draw(Game1.spriteBatch);
+ Game1.spriteBatch.End();
+ }
}
else
{
@@ -1265,6 +1278,8 @@ namespace StardewModdingAPI.Framework
}
else if (Game1.farmEvent != null)
Game1.farmEvent.drawAboveEverything(Game1.spriteBatch);
+
+ this.RaisePostRender();
Game1.spriteBatch.End();
if (Game1.overlayMenu != null)
{
@@ -1272,14 +1287,6 @@ namespace StardewModdingAPI.Framework
Game1.overlayMenu.draw(Game1.spriteBatch);
Game1.spriteBatch.End();
}
-
- if (GraphicsEvents.HasPostRenderListeners())
- {
- Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
- GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor);
- Game1.spriteBatch.End();
- }
-
this.renderScreenBuffer();
}
}
@@ -1401,5 +1408,19 @@ namespace StardewModdingAPI.Framework
hash ^= v.GetHashCode();
return hash;
}
+
+ /// <summary>Raise the <see cref="GraphicsEvents.OnPostRenderEvent"/> if there are any listeners.</summary>
+ /// <param name="needsNewBatch">Whether to create a new sprite batch.</param>
+ private void RaisePostRender(bool needsNewBatch = false)
+ {
+ if (GraphicsEvents.HasPostRenderListeners())
+ {
+ if (needsNewBatch)
+ Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
+ GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor);
+ if (needsNewBatch)
+ Game1.spriteBatch.End();
+ }
+ }
}
}
diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs
index 35ac7c0f..44ef32c9 100644
--- a/src/SMAPI/IMod.cs
+++ b/src/SMAPI/IMod.cs
@@ -1,4 +1,4 @@
-namespace StardewModdingAPI
+namespace StardewModdingAPI
{
/// <summary>The implementation for a Stardew Valley mod.</summary>
public interface IMod
@@ -22,5 +22,8 @@
/// <summary>The mod entry point, called after the mod is first loaded.</summary>
/// <param name="helper">Provides simplified APIs for writing mods.</param>
void Entry(IModHelper helper);
+
+ /// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary>
+ object GetApi();
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs
index 5ef3fd65..a06e099e 100644
--- a/src/SMAPI/IModRegistry.cs
+++ b/src/SMAPI/IModRegistry.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace StardewModdingAPI
{
@@ -16,5 +16,14 @@ namespace StardewModdingAPI
/// <summary>Get whether a mod has been loaded.</summary>
/// <param name="uniqueID">The mod's unique ID.</param>
bool IsLoaded(string uniqueID);
+
+ /// <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>
+ /// <param name="uniqueID">The mod's unique ID.</param>
+ object GetApi(string uniqueID);
+
+ /// <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>
+ TInterface GetApi<TInterface>(string uniqueID) where TInterface : class;
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/IPrivateField.cs b/src/SMAPI/IPrivateField.cs
index 3e681c12..512bfdab 100644
--- a/src/SMAPI/IPrivateField.cs
+++ b/src/SMAPI/IPrivateField.cs
@@ -1,9 +1,11 @@
-using System.Reflection;
+using System;
+using System.Reflection;
namespace StardewModdingAPI
{
/// <summary>A private field obtained through reflection.</summary>
/// <typeparam name="TValue">The field value type.</typeparam>
+ [Obsolete("Use " + nameof(IReflectedField<TValue>) + " instead")]
public interface IPrivateField<TValue>
{
/*********
@@ -23,4 +25,4 @@ namespace StardewModdingAPI
//// <param name="value">The value to set.</param>
void SetValue(TValue value);
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/IPrivateMethod.cs b/src/SMAPI/IPrivateMethod.cs
index 67fc8b3c..b2fdaaeb 100644
--- a/src/SMAPI/IPrivateMethod.cs
+++ b/src/SMAPI/IPrivateMethod.cs
@@ -1,8 +1,10 @@
-using System.Reflection;
+using System;
+using System.Reflection;
namespace StardewModdingAPI
{
/// <summary>A private method obtained through reflection.</summary>
+ [Obsolete("Use " + nameof(IReflectedMethod) + " instead")]
public interface IPrivateMethod
{
/*********
@@ -24,4 +26,4 @@ namespace StardewModdingAPI
/// <param name="arguments">The method arguments to pass in.</param>
void Invoke(params object[] arguments);
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/IPrivateProperty.cs b/src/SMAPI/IPrivateProperty.cs
index 8d67fa7a..a24495dd 100644
--- a/src/SMAPI/IPrivateProperty.cs
+++ b/src/SMAPI/IPrivateProperty.cs
@@ -1,9 +1,11 @@
-using System.Reflection;
+using System;
+using System.Reflection;
namespace StardewModdingAPI
{
/// <summary>A private property obtained through reflection.</summary>
/// <typeparam name="TValue">The property value type.</typeparam>
+ [Obsolete("Use " + nameof(IPrivateProperty<TValue>) + " instead")]
public interface IPrivateProperty<TValue>
{
/*********
@@ -23,4 +25,4 @@ namespace StardewModdingAPI
//// <param name="value">The value to set.</param>
void SetValue(TValue value);
}
-} \ No newline at end of file
+}
diff --git a/src/SMAPI/IReflectedField.cs b/src/SMAPI/IReflectedField.cs
new file mode 100644
index 00000000..43ddad42
--- /dev/null
+++ b/src/SMAPI/IReflectedField.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+
+namespace StardewModdingAPI
+{
+ /// <summary>A field obtained through reflection.</summary>
+ /// <typeparam name="TValue">The field value type.</typeparam>
+ public interface IReflectedField<TValue>
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The reflection metadata.</summary>
+ FieldInfo FieldInfo { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get the field value.</summary>
+ TValue GetValue();
+
+ /// <summary>Set the field value.</summary>
+ //// <param name="value">The value to set.</param>
+ void SetValue(TValue value);
+ }
+} \ No newline at end of file
diff --git a/src/SMAPI/IReflectedMethod.cs b/src/SMAPI/IReflectedMethod.cs
new file mode 100644
index 00000000..de83b98c
--- /dev/null
+++ b/src/SMAPI/IReflectedMethod.cs
@@ -0,0 +1,27 @@
+using System.Reflection;
+
+namespace StardewModdingAPI
+{
+ /// <summary>A method obtained through reflection.</summary>
+ public interface IReflectedMethod
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The reflection metadata.</summary>
+ MethodInfo MethodInfo { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Invoke the method.</summary>
+ /// <typeparam name="TValue">The return type.</typeparam>
+ /// <param name="arguments">The method arguments to pass in.</param>
+ TValue Invoke<TValue>(params object[] arguments);
+
+ /// <summary>Invoke the method.</summary>
+ /// <param name="arguments">The method arguments to pass in.</param>
+ void Invoke(params object[] arguments);
+ }
+} \ No newline at end of file
diff --git a/src/SMAPI/IReflectedProperty.cs b/src/SMAPI/IReflectedProperty.cs
new file mode 100644
index 00000000..73ad9f30
--- /dev/null
+++ b/src/SMAPI/IReflectedProperty.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+
+namespace StardewModdingAPI
+{
+ /// <summary>A property obtained through reflection.</summary>
+ /// <typeparam name="TValue">The property value type.</typeparam>
+ public interface IReflectedProperty<TValue>
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The reflection metadata.</summary>
+ PropertyInfo PropertyInfo { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get the property value.</summary>
+ TValue GetValue();
+
+ /// <summary>Set the property value.</summary>
+ //// <param name="value">The value to set.</param>
+ void SetValue(TValue value);
+ }
+}
diff --git a/src/SMAPI/IReflectionHelper.cs b/src/SMAPI/IReflectionHelper.cs
index fb2c7861..fcebae42 100644
--- a/src/SMAPI/IReflectionHelper.cs
+++ b/src/SMAPI/IReflectionHelper.cs
@@ -1,18 +1,62 @@
-using System;
+using System;
namespace StardewModdingAPI
{
- /// <summary>Provides an API for accessing private game code.</summary>
+ /// <summary>Provides an API for accessing inaccessible code.</summary>
public interface IReflectionHelper : IModLinked
{
/*********
** Public methods
*********/
+ /// <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>
+ IReflectedField<TValue> GetField<TValue>(object obj, string name, bool required = true);
+
+ /// <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>
+ IReflectedField<TValue> GetField<TValue>(Type type, string name, bool required = true);
+
+ /// <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>
+ IReflectedProperty<TValue> GetProperty<TValue>(object obj, string name, bool required = true);
+
+ /// <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>
+ IReflectedProperty<TValue> GetProperty<TValue>(Type type, string name, bool required = true);
+
+ /// <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>
+ IReflectedMethod GetMethod(object obj, string name, bool required = true);
+
+ /// <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>
+ IReflectedMethod GetMethod(Type type, string name, bool required = true);
+
+ /*****
+ ** Obsolete
+ *****/
/// <summary>Get a private 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 private field is not found.</param>
+ [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " instead")]
IPrivateField<TValue> GetPrivateField<TValue>(object obj, string name, bool required = true);
/// <summary>Get a private static field.</summary>
@@ -20,6 +64,7 @@ namespace StardewModdingAPI
/// <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("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " instead")]
IPrivateField<TValue> GetPrivateField<TValue>(Type type, string name, bool required = true);
/// <summary>Get a private instance property.</summary>
@@ -27,6 +72,7 @@ namespace StardewModdingAPI
/// <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("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")]
IPrivateProperty<TValue> GetPrivateProperty<TValue>(object obj, string name, bool required = true);
/// <summary>Get a private static property.</summary>
@@ -34,6 +80,7 @@ namespace StardewModdingAPI
/// <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("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")]
IPrivateProperty<TValue> GetPrivateProperty<TValue>(Type type, string name, bool required = true);
/// <summary>Get the value of a private instance field.</summary>
@@ -42,6 +89,7 @@ namespace StardewModdingAPI
/// <param name="name">The field name.</param>
/// <param name="required">Whether to throw an exception if the private field is not found.</param>
/// <remarks>This is a shortcut for <see cref="GetPrivateField{TValue}(object,string,bool)"/> followed by <see cref="IPrivateField{TValue}.GetValue"/>.</remarks>
+ [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " or " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")]
TValue GetPrivateValue<TValue>(object obj, string name, bool required = true);
/// <summary>Get the value of a private static field.</summary>
@@ -50,18 +98,21 @@ namespace StardewModdingAPI
/// <param name="name">The field name.</param>
/// <param name="required">Whether to throw an exception if the private field is not found.</param>
/// <remarks>This is a shortcut for <see cref="GetPrivateField{TValue}(Type,string,bool)"/> followed by <see cref="IPrivateField{TValue}.GetValue"/>.</remarks>
+ [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " or " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")]
TValue GetPrivateValue<TValue>(Type type, string name, bool required = true);
/// <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("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetMethod) + " instead")]
IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true);
/// <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("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetMethod) + " instead")]
IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true);
}
}
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index 3346f1ac..f285764c 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -50,7 +50,6 @@ namespace StardewModdingAPI.Metadata
new EventFinder("StardewModdingAPI.Events.GameEvents", "Initialize", InstructionHandleResult.NotCompatible),
new EventFinder("StardewModdingAPI.Events.GameEvents", "LoadContent", InstructionHandleResult.NotCompatible),
new EventFinder("StardewModdingAPI.Events.GameEvents", "GameLoaded", InstructionHandleResult.NotCompatible),
- new EventFinder("StardewModdingAPI.Events.GameEvents", "FirstUpdateTick", InstructionHandleResult.NotCompatible),
new EventFinder("StardewModdingAPI.Events.PlayerEvents", "LoadedGame", InstructionHandleResult.NotCompatible),
new EventFinder("StardewModdingAPI.Events.PlayerEvents", "FarmerChanged", InstructionHandleResult.NotCompatible),
new EventFinder("StardewModdingAPI.Events.TimeEvents", "DayOfMonthChanged", InstructionHandleResult.NotCompatible),
diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs
index ee75ba54..3a753afc 100644
--- a/src/SMAPI/Mod.cs
+++ b/src/SMAPI/Mod.cs
@@ -25,6 +25,9 @@ namespace StardewModdingAPI
/// <param name="helper">Provides simplified APIs for writing mods.</param>
public abstract void Entry(IModHelper helper);
+ /// <summary>Get an API that other mods can access. This is always called after <see cref="Entry"/>.</summary>
+ public virtual object GetApi() => null;
+
/// <summary>Release or reset unmanaged resources.</summary>
public void Dispose()
{
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index 3ba35e43..7eda9c66 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -247,7 +247,7 @@ namespace StardewModdingAPI
this.IsDisposed = true;
// dispose mod data
- foreach (IModMetadata mod in this.ModRegistry.GetMods())
+ foreach (IModMetadata mod in this.ModRegistry.GetAll())
{
try
{
@@ -374,7 +374,7 @@ namespace StardewModdingAPI
}
// update window titles
- int modsLoaded = this.ModRegistry.GetMods().Count();
+ int modsLoaded = this.ModRegistry.GetAll().Count();
this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods";
Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods";
@@ -390,7 +390,7 @@ namespace StardewModdingAPI
LocalizedContentManager.LanguageCode languageCode = this.ContentManager.GetCurrentLanguage();
// update mod translation helpers
- foreach (IModMetadata mod in this.ModRegistry.GetMods())
+ foreach (IModMetadata mod in this.ModRegistry.GetAll())
(mod.Mod.Helper.Translation as TranslationHelper)?.SetLocale(locale, languageCode);
}
@@ -500,12 +500,11 @@ namespace StardewModdingAPI
{
// create client
WebApiClient client = new WebApiClient(this.Settings.WebApiBaseUrl, Constants.ApiVersion);
+ this.Monitor.Log("Checking for updates...", LogLevel.Trace);
// check SMAPI version
try
{
- this.Monitor.Log("Checking for SMAPI update...", LogLevel.Trace);
-
ModInfoModel response = client.GetModInfo($"GitHub:{this.Settings.GitHubProjectName}").Single().Value;
if (response.Error != null)
{
@@ -515,7 +514,7 @@ namespace StardewModdingAPI
else if (new SemanticVersion(response.Version).IsNewerThan(Constants.ApiVersion))
this.Monitor.Log($"You can update SMAPI to {response.Version}: {response.Url}", LogLevel.Alert);
else
- this.VerboseLog(" OK.");
+ this.Monitor.Log(" SMAPI okay.", LogLevel.Trace);
}
catch (Exception ex)
{
@@ -527,95 +526,102 @@ namespace StardewModdingAPI
}
// check mod versions
- try
+ if (mods.Any())
{
- // log issues
- if (this.Settings.VerboseLogging)
+ try
{
- this.VerboseLog("Validating mod update keys...");
- foreach (IModMetadata mod in mods)
+ // prepare update keys
+ Dictionary<string, IModMetadata[]> modsByKey =
+ (
+ from mod in mods
+ where mod.Manifest?.UpdateKeys != null
+ from key in mod.Manifest.UpdateKeys
+ select new { key, mod }
+ )
+ .GroupBy(p => p.key, StringComparer.InvariantCultureIgnoreCase)
+ .ToDictionary(
+ group => group.Key,
+ group => group.Select(p => p.mod).ToArray(),
+ StringComparer.InvariantCultureIgnoreCase
+ );
+
+ // report update keys
{
- if (mod.Manifest == null)
- this.VerboseLog($" {mod.DisplayName}: no manifest.");
- else if (mod.Manifest.UpdateKeys == null || !mod.Manifest.UpdateKeys.Any())
- this.VerboseLog($" {mod.DisplayName}: no update keys.");
+ IModMetadata[] modsWithoutKeys = (
+ from mod in mods
+ where
+ mod.Manifest != null
+ && (mod.Manifest.UpdateKeys == null || !mod.Manifest.UpdateKeys.Any())
+ && (mod.Manifest?.UniqueID != "SMAPI.ConsoleCommands" && mod.Manifest?.UniqueID != "SMAPI.TrainerMod")
+ orderby mod.DisplayName
+ select mod
+ ).ToArray();
+
+ string message = $"Checking {modsByKey.Count} mod update keys.";
+ if (modsWithoutKeys.Any())
+ message += $" {modsWithoutKeys.Length} mods have no update keys: {string.Join(", ", modsWithoutKeys.Select(p => p.DisplayName))}.";
+ this.Monitor.Log($" {message}", LogLevel.Trace);
}
- }
- // prepare update keys
- Dictionary<string, IModMetadata[]> modsByKey =
- (
- from mod in mods
- where mod.Manifest?.UpdateKeys != null
- from key in mod.Manifest.UpdateKeys
- select new { key, mod }
- )
- .GroupBy(p => p.key, StringComparer.InvariantCultureIgnoreCase)
- .ToDictionary(
- group => group.Key,
- group => group.Select(p => p.mod).ToArray(),
- StringComparer.InvariantCultureIgnoreCase
- );
+ // fetch results
+ var results =
+ (
+ from entry in client.GetModInfo(modsByKey.Keys.ToArray())
+ from mod in modsByKey[entry.Key]
+ orderby mod.DisplayName
+ select new { entry.Key, Mod = mod, Info = entry.Value }
+ )
+ .ToArray();
+
+ // extract latest versions
+ IDictionary<IModMetadata, ModInfoModel> updatesByMod = new Dictionary<IModMetadata, ModInfoModel>();
+ foreach (var result in results)
+ {
+ IModMetadata mod = result.Mod;
+ ModInfoModel info = result.Info;
- // fetch results
- this.Monitor.Log($"Checking for updates to {modsByKey.Keys.Count} keys...", LogLevel.Trace);
- var results =
- (
- from entry in client.GetModInfo(modsByKey.Keys.ToArray())
- from mod in modsByKey[entry.Key]
- orderby mod.DisplayName
- select new { entry.Key, Mod = mod, Info = entry.Value }
- )
- .ToArray();
-
- // extract latest versions
- IDictionary<IModMetadata, ModInfoModel> updatesByMod = new Dictionary<IModMetadata, ModInfoModel>();
- foreach (var result in results)
- {
- IModMetadata mod = result.Mod;
- ModInfoModel info = result.Info;
+ // handle error
+ if (info.Error != null)
+ {
+ this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {info.Error}", LogLevel.Trace);
+ continue;
+ }
- // handle error
- if (info.Error != null)
- {
- this.Monitor.Log($" {mod.DisplayName} ({result.Key}): update error: {info.Error}", LogLevel.Trace);
- continue;
+ // track update
+ ISemanticVersion localVersion = mod.DataRecord != null
+ ? new SemanticVersion(mod.DataRecord.GetLocalVersionForUpdateChecks(mod.Manifest.Version.ToString()))
+ : mod.Manifest.Version;
+ ISemanticVersion latestVersion = new SemanticVersion(mod.DataRecord != null
+ ? mod.DataRecord.GetRemoteVersionForUpdateChecks(new SemanticVersion(info.Version).ToString())
+ : info.Version
+ );
+ bool isUpdate = latestVersion.IsNewerThan(localVersion);
+ this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {info.Version}{(!latestVersion.Equals(new SemanticVersion(info.Version)) ? $" [{latestVersion}]" : "")}" : "okay")}.");
+ if (isUpdate)
+ {
+ if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || latestVersion.IsNewerThan(other.Version))
+ updatesByMod[mod] = info;
+ }
}
- // track update
- ISemanticVersion localVersion = mod.DataRecord != null
- ? new SemanticVersion(mod.DataRecord.GetLocalVersionForUpdateChecks(mod.Manifest.Version.ToString()))
- : mod.Manifest.Version;
- ISemanticVersion latestVersion = new SemanticVersion(mod.DataRecord != null
- ? mod.DataRecord.GetRemoteVersionForUpdateChecks(new SemanticVersion(info.Version).ToString())
- : info.Version
- );
- bool isUpdate = latestVersion.IsNewerThan(localVersion);
- this.VerboseLog($" {mod.DisplayName} ({result.Key}): {(isUpdate ? $"{mod.Manifest.Version}{(!localVersion.Equals(mod.Manifest.Version) ? $" [{localVersion}]" : "")} => {info.Version}{(!latestVersion.Equals(new SemanticVersion(info.Version)) ? $" [{latestVersion}]" : "")}" : "OK")}.");
- if (isUpdate)
+ // output
+ if (updatesByMod.Any())
{
- if (!updatesByMod.TryGetValue(mod, out ModInfoModel other) || latestVersion.IsNewerThan(other.Version))
- updatesByMod[mod] = info;
+ this.Monitor.Newline();
+ this.Monitor.Log($"You can update {updatesByMod.Count} mod{(updatesByMod.Count != 1 ? "s" : "")}:", LogLevel.Alert);
+ foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName))
+ this.Monitor.Log($" {entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}", LogLevel.Alert);
}
}
-
- // output
- if (updatesByMod.Any())
+ catch (Exception ex)
{
- this.Monitor.Newline();
- this.Monitor.Log($"You can update {updatesByMod.Count} mod{(updatesByMod.Count != 1 ? "s" : "")}:", LogLevel.Alert);
- foreach (var entry in updatesByMod.OrderBy(p => p.Key.DisplayName))
- this.Monitor.Log($" {entry.Key.DisplayName} {entry.Value.Version}: {entry.Value.Url}", LogLevel.Alert);
+ this.Monitor.Log("Couldn't check for new mod versions. This won't affect your game, but you won't be notified of mod updates if this keeps happening.", LogLevel.Warn);
+ this.Monitor.Log(ex is WebException && ex.InnerException == null
+ ? ex.Message
+ : ex.ToString()
+ );
}
}
- catch (Exception ex)
- {
- this.Monitor.Log("Couldn't check for new mod versions. This won't affect your game, but you won't be notified of mod updates if this keeps happening.", LogLevel.Warn);
- this.Monitor.Log(ex is WebException && ex.InnerException == null
- ? ex.Message
- : ex.ToString()
- );
- }
}).Start();
}
@@ -649,6 +655,7 @@ namespace StardewModdingAPI
AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.TargetPlatform, this.Monitor, this.Settings.DeveloperMode);
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name);
+ InterfaceProxyBuilder proxyBuilder = new InterfaceProxyBuilder();
foreach (IModMetadata metadata in mods)
{
// get basic info
@@ -690,53 +697,30 @@ namespace StardewModdingAPI
continue;
}
- // validate assembly
- try
- {
- int modEntries = modAssembly.DefinedTypes.Count(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract);
- if (modEntries == 0)
- {
- TrackSkip(metadata, $"its DLL has no '{nameof(Mod)}' subclass.");
- continue;
- }
- if (modEntries > 1)
- {
- TrackSkip(metadata, $"its DLL contains multiple '{nameof(Mod)}' subclasses.");
- continue;
- }
- }
- catch (Exception ex)
- {
- TrackSkip(metadata, $"its DLL couldn't be loaded:\n{ex.GetLogSummary()}");
- continue;
- }
-
// initialise mod
try
{
- // get implementation
- TypeInfo modEntryType = modAssembly.DefinedTypes.First(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract);
- Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString());
- if (mod == null)
- {
- TrackSkip(metadata, "its entry class couldn't be instantiated.");
- continue;
- }
-
- // inject data
+ // init mod helpers
+ IMonitor monitor = this.GetSecondaryMonitor(metadata.DisplayName);
+ IModHelper modHelper;
{
- IMonitor monitor = this.GetSecondaryMonitor(metadata.DisplayName);
ICommandHelper commandHelper = new CommandHelper(manifest.UniqueID, metadata.DisplayName, this.CommandManager);
IContentHelper contentHelper = new ContentHelper(contentManager, metadata.DirectoryPath, manifest.UniqueID, metadata.DisplayName, monitor);
- IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection);
- IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry);
+ IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection, this.DeprecationManager);
+ IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyBuilder, monitor);
ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentManager.GetLocale(), contentManager.GetCurrentLanguage());
-
- mod.ModManifest = manifest;
- mod.Helper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper);
- mod.Monitor = monitor;
+ modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper);
}
+ // get mod instance
+ if (!this.TryLoadModEntry(modAssembly, error => TrackSkip(metadata, error), out Mod mod))
+ continue;
+
+ // init mod
+ mod.ModManifest = manifest;
+ mod.Helper = modHelper;
+ mod.Monitor = monitor;
+
// track mod
metadata.SetMod(mod);
this.ModRegistry.Add(metadata);
@@ -747,7 +731,7 @@ namespace StardewModdingAPI
}
}
}
- IModMetadata[] loadedMods = this.ModRegistry.GetMods().ToArray();
+ IModMetadata[] loadedMods = this.ModRegistry.GetAll().ToArray();
// log skipped mods
this.Monitor.Newline();
@@ -811,6 +795,19 @@ namespace StardewModdingAPI
{
this.Monitor.Log($"{metadata.DisplayName} failed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
+
+ // get mod API
+ try
+ {
+ object api = metadata.Mod.GetApi();
+ if (api != null)
+ this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName}).", LogLevel.Trace);
+ metadata.SetApi(api);
+ }
+ catch (Exception ex)
+ {
+ this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error);
+ }
}
// invalidate cache entries when needed
@@ -846,13 +843,48 @@ namespace StardewModdingAPI
this.Monitor.Log("Invalidating cached assets for new editors & loaders...", LogLevel.Trace);
this.ContentManager.InvalidateCacheFor(editors, loaders);
}
+
+ // unlock mod integrations
+ this.ModRegistry.AreAllModsInitialised = true;
+ }
+
+ /// <summary>Load a mod's entry class.</summary>
+ /// <param name="modAssembly">The mod assembly.</param>
+ /// <param name="onError">A callback invoked when loading fails.</param>
+ /// <param name="mod">The loaded instance.</param>
+ private bool TryLoadModEntry(Assembly modAssembly, Action<string> onError, out Mod mod)
+ {
+ mod = null;
+
+ // find type
+ TypeInfo[] modEntries = modAssembly.DefinedTypes.Where(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract).Take(2).ToArray();
+ if (modEntries.Length == 0)
+ {
+ onError($"its DLL has no '{nameof(Mod)}' subclass.");
+ return false;
+ }
+ if (modEntries.Length > 1)
+ {
+ onError($"its DLL contains multiple '{nameof(Mod)}' subclasses.");
+ return false;
+ }
+
+ // get implementation
+ mod = (Mod)modAssembly.CreateInstance(modEntries[0].ToString());
+ if (mod == null)
+ {
+ onError("its entry class couldn't be instantiated.");
+ return false;
+ }
+
+ return true;
}
/// <summary>Reload translations for all mods.</summary>
private void ReloadTranslations()
{
JsonHelper jsonHelper = new JsonHelper();
- foreach (IModMetadata metadata in this.ModRegistry.GetMods())
+ foreach (IModMetadata metadata in this.ModRegistry.GetAll())
{
// read translation files
IDictionary<string, IDictionary<string, string>> translations = new Dictionary<string, IDictionary<string, string>>();
diff --git a/src/SMAPI/StardewModdingAPI.config.json b/src/SMAPI/StardewModdingAPI.config.json
index 6718806e..18a9f978 100644
--- a/src/SMAPI/StardewModdingAPI.config.json
+++ b/src/SMAPI/StardewModdingAPI.config.json
@@ -1891,6 +1891,16 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha
"UpdateKeys": [ "Nexus:1401" ]
},
{
+ // TrainerMod
+ "ID": "SMAPI.TrainerMod",
+ "Compatibility": {
+ "~": {
+ "Status": "Obsolete",
+ "ReasonPhrase": "replaced by ConsoleCommands, which is added by the SMAPI installer."
+ }
+ }
+ },
+ {
// Tree Transplant
"ID": "TreeTransplant",
"UpdateKeys": [ "Nexus:1342" ]
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index 380ed733..f76ac439 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -79,10 +79,6 @@
<Private>True</Private>
</Reference>
<Reference Include="System.Windows.Forms" Condition="$(OS) == 'Windows_NT'" />
- <Reference Include="System.Xml.Linq" />
- <Reference Include="System.Data.DataSetExtensions" />
- <Reference Include="Microsoft.CSharp" />
- <Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
@@ -110,7 +106,11 @@
<Compile Include="Framework\ContentManagerShim.cs" />
<Compile Include="Framework\Exceptions\SAssemblyLoadFailedException.cs" />
<Compile Include="Framework\ModLoading\AssemblyLoadStatus.cs" />
+ <Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" />
<Compile Include="Framework\Utilities\ContextHash.cs" />
+ <Compile Include="IReflectedField.cs" />
+ <Compile Include="IReflectedMethod.cs" />
+ <Compile Include="IReflectedProperty.cs" />
<Compile Include="Metadata\CoreAssets.cs" />
<Compile Include="ContentSource.cs" />
<Compile Include="Events\ContentEvents.cs" />
@@ -169,7 +169,7 @@
<Compile Include="Framework\Models\ModStatus.cs" />
<Compile Include="Framework\Models\SConfig.cs" />
<Compile Include="Framework\ModLoading\ModMetadata.cs" />
- <Compile Include="Framework\Reflection\PrivateProperty.cs" />
+ <Compile Include="Framework\Reflection\ReflectedProperty.cs" />
<Compile Include="Framework\RequestExitDelegate.cs" />
<Compile Include="Framework\SContentManager.cs" />
<Compile Include="Framework\Exceptions\SParseException.cs" />
@@ -198,8 +198,8 @@
<Compile Include="Framework\Models\ModDataRecord.cs" />
<Compile Include="Framework\ModLoading\AssemblyLoader.cs" />
<Compile Include="Framework\Reflection\CacheEntry.cs" />
- <Compile Include="Framework\Reflection\PrivateField.cs" />
- <Compile Include="Framework\Reflection\PrivateMethod.cs" />
+ <Compile Include="Framework\Reflection\ReflectedField.cs" />
+ <Compile Include="Framework\Reflection\ReflectedMethod.cs" />
<Compile Include="Framework\Reflection\Reflector.cs" />
<Compile Include="IManifest.cs" />
<Compile Include="IMod.cs" />