From 1398e591abc23a7af927cc7de1e8df512b6fc598 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 9 Dec 2017 12:46:10 -0500 Subject: fix reflection API error with properties which don't have both get and set --- src/SMAPI/Framework/Reflection/PrivateProperty.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/Reflection') diff --git a/src/SMAPI/Framework/Reflection/PrivateProperty.cs b/src/SMAPI/Framework/Reflection/PrivateProperty.cs index be346d71..c81f99c7 100644 --- a/src/SMAPI/Framework/Reflection/PrivateProperty.cs +++ b/src/SMAPI/Framework/Reflection/PrivateProperty.cs @@ -14,10 +14,10 @@ namespace StardewModdingAPI.Framework.Reflection private readonly string DisplayName; /// The underlying property getter. - private readonly Func GetterDelegate; + private readonly Func GetMethod; /// The underlying property setter. - private readonly Action SetterDelegate; + private readonly Action SetMethod; /********* @@ -55,16 +55,21 @@ namespace StardewModdingAPI.Framework.Reflection this.DisplayName = $"{parentType.FullName}::{property.Name}"; this.PropertyInfo = property; - this.GetterDelegate = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); - this.SetterDelegate = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); + if (this.PropertyInfo.GetMethod != null) + this.GetMethod = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); + if (this.PropertyInfo.SetMethod != null) + this.SetMethod = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); } /// Get the property value. public TValue GetValue() { + if (this.GetMethod == null) + throw new InvalidOperationException($"The private {this.DisplayName} property has no get method."); + try { - return this.GetterDelegate(); + return this.GetMethod(); } catch (InvalidCastException) { @@ -80,9 +85,12 @@ namespace StardewModdingAPI.Framework.Reflection //// The value to set. public void SetValue(TValue value) { + if (this.SetMethod == null) + throw new InvalidOperationException($"The private {this.DisplayName} property has no set method."); + try { - this.SetterDelegate(value); + this.SetMethod(value); } catch (InvalidCastException) { -- cgit From 8776d1afa6dce054f3bc7cb421c86f3e2fe06ab3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 10 Dec 2017 18:05:18 -0500 Subject: adjust reflection API to correctly reflect what it does (#410) --- src/SMAPI/Framework/Content/ContentCache.cs | 4 +- src/SMAPI/Framework/DeprecationManager.cs | 2 +- src/SMAPI/Framework/InternalExtensions.cs | 2 +- src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 167 +++++++++++++-------- src/SMAPI/Framework/Reflection/PrivateField.cs | 93 ------------ src/SMAPI/Framework/Reflection/PrivateMethod.cs | 99 ------------ src/SMAPI/Framework/Reflection/PrivateProperty.cs | 105 ------------- src/SMAPI/Framework/Reflection/ReflectedField.cs | 93 ++++++++++++ src/SMAPI/Framework/Reflection/ReflectedMethod.cs | 99 ++++++++++++ .../Framework/Reflection/ReflectedProperty.cs | 105 +++++++++++++ src/SMAPI/Framework/Reflection/Reflector.cs | 106 ++++++------- src/SMAPI/Framework/SContentManager.cs | 6 +- src/SMAPI/Framework/SGame.cs | 24 +-- src/SMAPI/IPrivateField.cs | 6 +- src/SMAPI/IPrivateMethod.cs | 6 +- src/SMAPI/IPrivateProperty.cs | 6 +- src/SMAPI/IReflectedField.cs | 26 ++++ src/SMAPI/IReflectedMethod.cs | 27 ++++ src/SMAPI/IReflectedProperty.cs | 26 ++++ src/SMAPI/IReflectionHelper.cs | 55 ++++++- src/SMAPI/Program.cs | 2 +- src/SMAPI/StardewModdingAPI.csproj | 9 +- 22 files changed, 625 insertions(+), 443 deletions(-) delete mode 100644 src/SMAPI/Framework/Reflection/PrivateField.cs delete mode 100644 src/SMAPI/Framework/Reflection/PrivateMethod.cs delete mode 100644 src/SMAPI/Framework/Reflection/PrivateProperty.cs create mode 100644 src/SMAPI/Framework/Reflection/ReflectedField.cs create mode 100644 src/SMAPI/Framework/Reflection/ReflectedMethod.cs create mode 100644 src/SMAPI/Framework/Reflection/ReflectedProperty.cs create mode 100644 src/SMAPI/IReflectedField.cs create mode 100644 src/SMAPI/IReflectedMethod.cs create mode 100644 src/SMAPI/IReflectedProperty.cs (limited to 'src/SMAPI/Framework/Reflection') 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>(contentManager, "loadedAssets").GetValue(); + this.Cache = reflection.GetField>(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(path); } else diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index b07c6c7d..20bb0d2d 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -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}"; diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index 3709e05d..f81e05a9 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -125,7 +125,7 @@ namespace StardewModdingAPI.Framework #endif // get result - return reflection.GetPrivateField(Game1.spriteBatch, fieldName).GetValue(); + return reflection.GetField(Game1.spriteBatch, fieldName).GetValue(); } } } 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 /// The mod name for error messages. private readonly string ModName; + /// Manages deprecation warnings. + private readonly DeprecationManager DeprecationManager; + /********* ** Public methods @@ -25,15 +28,88 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The mod name for error messages. /// The underlying reflection helper. - public ReflectionHelper(string modID, string modName, Reflector reflector) + /// Manages deprecation warnings. + public ReflectionHelper(string modID, string modName, Reflector reflector, DeprecationManager deprecationManager) : base(modID) { this.ModName = modName; this.Reflector = reflector; + this.DeprecationManager = deprecationManager; + } + + /// Get an instance field. + /// The field type. + /// The object which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedField GetField(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField(obj, name, required) + ); + } + + /// Get a static field. + /// The field type. + /// The type which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedField GetField(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetField(type, name, required) + ); + } + + /// Get an instance property. + /// The property type. + /// The object which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty(obj, name, required) + ); + } + + /// Get a static property. + /// The property type. + /// The type which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetProperty(type, name, required) + ); + } + + /// Get an instance method. + /// The object which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedMethod GetMethod(object obj, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(obj, name, required) + ); + } + + /// Get a static method. + /// The type which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + public IReflectedMethod GetMethod(Type type, string name, bool required = true) + { + return this.AssertAccessAllowed( + this.Reflector.GetMethod(type, name, required) + ); } + /**** - ** Fields + ** Obsolete ****/ /// Get a private instance field. /// The field type. @@ -41,11 +117,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The field name. /// Whether to throw an exception if the private field is not found. /// Returns the field wrapper, or null if the field doesn't exist and is false. + [Obsolete] public IPrivateField GetPrivateField(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField)this.GetField(obj, name, required); } /// Get a private static field. @@ -53,26 +129,23 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete] public IPrivateField GetPrivateField(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateField(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateField)this.GetField(type, name, required); } - /**** - ** Properties - ****/ /// Get a private instance property. /// The property type. /// The object which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete] public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty(obj, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty)this.GetProperty(obj, name, required); } /// Get a private static property. @@ -80,17 +153,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The type which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete] public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateProperty(type, name, required) - ); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + return (IPrivateProperty)this.GetProperty(type, name, required); } - /**** - ** Field values - ** (shorthand since this is the most common case) - ****/ /// Get the value of a private instance field. /// The field type. /// The object which has the field. @@ -101,9 +170,11 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. /// + [Obsolete] public TValue GetPrivateValue(object obj, string name, bool required = true) { - IPrivateField field = this.GetPrivateField(obj, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField field = (IPrivateField)this.GetField(obj, name, required); return field != null ? field.GetValue() : default(TValue); @@ -119,64 +190,36 @@ namespace StardewModdingAPI.Framework.ModHelpers /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. /// + [Obsolete] public TValue GetPrivateValue(Type type, string name, bool required = true) { - IPrivateField field = this.GetPrivateField(type, name, required); + this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice); + IPrivateField field = (IPrivateField)this.GetField(type, name, required); return field != null ? field.GetValue() : default(TValue); } - /**** - ** Methods - ****/ /// Get a private instance method. /// The object which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [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); } /// Get a private static method. /// The type which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete] public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(type, name, required) - ); - } - - /**** - ** Methods by signature - ****/ - /// Get a private instance method. - /// The object which has the method. - /// The field name. - /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(object obj, string name, Type[] argumentTypes, bool required = true) - { - return this.AssertAccessAllowed( - this.Reflector.GetPrivateMethod(obj, name, argumentTypes, required) - ); - } - - /// Get a private static method. - /// The type which has the method. - /// The field name. - /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - 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 /// The field value type. /// The field being accessed. /// Returns the same field instance for convenience. - private IPrivateField AssertAccessAllowed(IPrivateField field) + private IReflectedField AssertAccessAllowed(IReflectedField field) { this.AssertAccessAllowed(field?.FieldInfo); return field; @@ -197,7 +240,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The property value type. /// The property being accessed. /// Returns the same property instance for convenience. - private IPrivateProperty AssertAccessAllowed(IPrivateProperty property) + private IReflectedProperty AssertAccessAllowed(IReflectedProperty property) { this.AssertAccessAllowed(property?.PropertyInfo); return property; @@ -206,7 +249,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Assert that mods can use the reflection helper to access the given member. /// The method being accessed. /// Returns the same method instance for convenience. - private IPrivateMethod AssertAccessAllowed(IPrivateMethod method) + private IReflectedMethod AssertAccessAllowed(IReflectedMethod method) { this.AssertAccessAllowed(method?.MethodInfo); return method; diff --git a/src/SMAPI/Framework/Reflection/PrivateField.cs b/src/SMAPI/Framework/Reflection/PrivateField.cs deleted file mode 100644 index 0bf45969..00000000 --- a/src/SMAPI/Framework/Reflection/PrivateField.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Reflection; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// A private field obtained through reflection. - /// The field value type. - internal class PrivateField : IPrivateField - { - /********* - ** Properties - *********/ - /// The type that has the field. - private readonly Type ParentType; - - /// The object that has the instance field (if applicable). - private readonly object Parent; - - /// The display name shown in error messages. - private string DisplayName => $"{this.ParentType.FullName}::{this.FieldInfo.Name}"; - - - /********* - ** Accessors - *********/ - /// The reflection metadata. - public FieldInfo FieldInfo { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type that has the field. - /// The object that has the instance field (if applicable). - /// The reflection metadata. - /// Whether the field is static. - /// The or is null. - /// The is null for a non-static field, or not null for a static field. - public PrivateField(Type parentType, object obj, FieldInfo field, bool isStatic) - { - // validate - if (parentType == null) - throw new ArgumentNullException(nameof(parentType)); - if (field == null) - throw new ArgumentNullException(nameof(field)); - if (isStatic && obj != null) - throw new ArgumentException("A static field cannot have an object instance."); - if (!isStatic && obj == null) - throw new ArgumentException("A non-static field must have an object instance."); - - // save - this.ParentType = parentType; - this.Parent = obj; - this.FieldInfo = field; - } - - /// Get the field value. - public TValue GetValue() - { - try - { - return (TValue)this.FieldInfo.GetValue(this.Parent); - } - catch (InvalidCastException) - { - throw new InvalidCastException($"Can't convert the private {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); - } - } - - /// Set the field value. - //// The value to set. - public void SetValue(TValue value) - { - try - { - this.FieldInfo.SetValue(this.Parent, value); - } - 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}."); - } - catch (Exception ex) - { - throw new Exception($"Couldn't set the value of the private {this.DisplayName} field", ex); - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/PrivateMethod.cs b/src/SMAPI/Framework/Reflection/PrivateMethod.cs deleted file mode 100644 index ba2374f4..00000000 --- a/src/SMAPI/Framework/Reflection/PrivateMethod.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.Reflection; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// A private method obtained through reflection. - internal class PrivateMethod : IPrivateMethod - { - /********* - ** Properties - *********/ - /// The type that has the method. - private readonly Type ParentType; - - /// The object that has the instance method (if applicable). - private readonly object Parent; - - /// The display name shown in error messages. - private string DisplayName => $"{this.ParentType.FullName}::{this.MethodInfo.Name}"; - - - /********* - ** Accessors - *********/ - /// The reflection metadata. - public MethodInfo MethodInfo { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type that has the method. - /// The object that has the instance method(if applicable). - /// The reflection metadata. - /// Whether the field is static. - /// The or is null. - /// The is null for a non-static method, or not null for a static method. - public PrivateMethod(Type parentType, object obj, MethodInfo method, bool isStatic) - { - // validate - if (parentType == null) - throw new ArgumentNullException(nameof(parentType)); - if (method == null) - throw new ArgumentNullException(nameof(method)); - if (isStatic && obj != null) - throw new ArgumentException("A static method cannot have an object instance."); - if (!isStatic && obj == null) - throw new ArgumentException("A non-static method must have an object instance."); - - // save - this.ParentType = parentType; - this.Parent = obj; - this.MethodInfo = method; - } - - /// Invoke the method. - /// The return type. - /// The method arguments to pass in. - public TValue Invoke(params object[] arguments) - { - // invoke method - object result; - try - { - result = this.MethodInfo.Invoke(this.Parent, arguments); - } - catch (Exception ex) - { - throw new Exception($"Couldn't invoke the private {this.DisplayName} field", ex); - } - - // cast return value - try - { - return (TValue)result; - } - 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}."); - } - } - - /// Invoke the method. - /// The method arguments to pass in. - public void Invoke(params object[] arguments) - { - // invoke method - try - { - this.MethodInfo.Invoke(this.Parent, arguments); - } - catch (Exception ex) - { - throw new Exception($"Couldn't invoke the private {this.DisplayName} field", ex); - } - } - } -} \ No newline at end of file diff --git a/src/SMAPI/Framework/Reflection/PrivateProperty.cs b/src/SMAPI/Framework/Reflection/PrivateProperty.cs deleted file mode 100644 index c81f99c7..00000000 --- a/src/SMAPI/Framework/Reflection/PrivateProperty.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Reflection; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// A private property obtained through reflection. - /// The property value type. - internal class PrivateProperty : IPrivateProperty - { - /********* - ** Properties - *********/ - /// The display name shown in error messages. - private readonly string DisplayName; - - /// The underlying property getter. - private readonly Func GetMethod; - - /// The underlying property setter. - private readonly Action SetMethod; - - - /********* - ** Accessors - *********/ - /// The reflection metadata. - public PropertyInfo PropertyInfo { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type that has the field. - /// The object that has the instance field (if applicable). - /// The reflection metadata. - /// Whether the field is static. - /// The or is null. - /// The is null for a non-static field, or not null for a static field. - public PrivateProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) - { - // validate input - if (parentType == null) - throw new ArgumentNullException(nameof(parentType)); - if (property == null) - throw new ArgumentNullException(nameof(property)); - - // validate static - if (isStatic && obj != null) - throw new ArgumentException("A static property cannot have an object instance."); - if (!isStatic && obj == null) - throw new ArgumentException("A non-static property must have an object instance."); - - - this.DisplayName = $"{parentType.FullName}::{property.Name}"; - this.PropertyInfo = property; - - if (this.PropertyInfo.GetMethod != null) - this.GetMethod = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); - if (this.PropertyInfo.SetMethod != null) - this.SetMethod = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); - } - - /// Get the property value. - public TValue GetValue() - { - if (this.GetMethod == null) - throw new InvalidOperationException($"The private {this.DisplayName} property has no get method."); - - try - { - 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}."); - } - catch (Exception ex) - { - throw new Exception($"Couldn't get the value of the private {this.DisplayName} property", ex); - } - } - - /// Set the property value. - //// The value to set. - public void SetValue(TValue value) - { - if (this.SetMethod == null) - throw new InvalidOperationException($"The private {this.DisplayName} property has no set method."); - - try - { - 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}."); - } - catch (Exception ex) - { - throw new Exception($"Couldn't set the value of the private {this.DisplayName} property", ex); - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/ReflectedField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs new file mode 100644 index 00000000..ad1557bb --- /dev/null +++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs @@ -0,0 +1,93 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A field obtained through reflection. + /// The field value type. + internal class ReflectedField : IPrivateField, IReflectedField + { + /********* + ** Properties + *********/ + /// The type that has the field. + private readonly Type ParentType; + + /// The object that has the instance field (if applicable). + private readonly object Parent; + + /// The display name shown in error messages. + private string DisplayName => $"{this.ParentType.FullName}::{this.FieldInfo.Name}"; + + + /********* + ** Accessors + *********/ + /// The reflection metadata. + public FieldInfo FieldInfo { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type that has the field. + /// The object that has the instance field (if applicable). + /// The reflection metadata. + /// Whether the field is static. + /// The or is null. + /// The is null for a non-static field, or not null for a static field. + public ReflectedField(Type parentType, object obj, FieldInfo field, bool isStatic) + { + // validate + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (field == null) + throw new ArgumentNullException(nameof(field)); + if (isStatic && obj != null) + throw new ArgumentException("A static field cannot have an object instance."); + if (!isStatic && obj == null) + throw new ArgumentException("A non-static field must have an object instance."); + + // save + this.ParentType = parentType; + this.Parent = obj; + this.FieldInfo = field; + } + + /// Get the field value. + public TValue GetValue() + { + try + { + return (TValue)this.FieldInfo.GetValue(this.Parent); + } + catch (InvalidCastException) + { + 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 {this.DisplayName} field", ex); + } + } + + /// Set the field value. + //// The value to set. + public void SetValue(TValue value) + { + try + { + this.FieldInfo.SetValue(this.Parent, value); + } + catch (InvalidCastException) + { + 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 {this.DisplayName} field", ex); + } + } + } +} diff --git a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs new file mode 100644 index 00000000..376de869 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs @@ -0,0 +1,99 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A method obtained through reflection. + internal class ReflectedMethod : IPrivateMethod, IReflectedMethod + { + /********* + ** Properties + *********/ + /// The type that has the method. + private readonly Type ParentType; + + /// The object that has the instance method (if applicable). + private readonly object Parent; + + /// The display name shown in error messages. + private string DisplayName => $"{this.ParentType.FullName}::{this.MethodInfo.Name}"; + + + /********* + ** Accessors + *********/ + /// The reflection metadata. + public MethodInfo MethodInfo { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type that has the method. + /// The object that has the instance method(if applicable). + /// The reflection metadata. + /// Whether the method is static. + /// The or is null. + /// The is null for a non-static method, or not null for a static method. + public ReflectedMethod(Type parentType, object obj, MethodInfo method, bool isStatic) + { + // validate + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (method == null) + throw new ArgumentNullException(nameof(method)); + if (isStatic && obj != null) + throw new ArgumentException("A static method cannot have an object instance."); + if (!isStatic && obj == null) + throw new ArgumentException("A non-static method must have an object instance."); + + // save + this.ParentType = parentType; + this.Parent = obj; + this.MethodInfo = method; + } + + /// Invoke the method. + /// The return type. + /// The method arguments to pass in. + public TValue Invoke(params object[] arguments) + { + // invoke method + object result; + try + { + result = this.MethodInfo.Invoke(this.Parent, arguments); + } + catch (Exception ex) + { + throw new Exception($"Couldn't invoke the {this.DisplayName} method", ex); + } + + // cast return value + try + { + return (TValue)result; + } + catch (InvalidCastException) + { + throw new InvalidCastException($"Can't convert the return value of the {this.DisplayName} method from {this.MethodInfo.ReturnType.FullName} to {typeof(TValue).FullName}."); + } + } + + /// Invoke the method. + /// The method arguments to pass in. + public void Invoke(params object[] arguments) + { + // invoke method + try + { + this.MethodInfo.Invoke(this.Parent, arguments); + } + catch (Exception ex) + { + throw new Exception($"Couldn't invoke the {this.DisplayName} method", ex); + } + } + } +} diff --git a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs new file mode 100644 index 00000000..d6c964c1 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs @@ -0,0 +1,105 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A property obtained through reflection. + /// The property value type. + internal class ReflectedProperty : IPrivateProperty, IReflectedProperty + { + /********* + ** Properties + *********/ + /// The display name shown in error messages. + private readonly string DisplayName; + + /// The underlying property getter. + private readonly Func GetMethod; + + /// The underlying property setter. + private readonly Action SetMethod; + + + /********* + ** Accessors + *********/ + /// The reflection metadata. + public PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type that has the property. + /// The object that has the instance property (if applicable). + /// The reflection metadata. + /// Whether the property is static. + /// The or is null. + /// The is null for a non-static property, or not null for a static property. + public ReflectedProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) + { + // validate input + if (parentType == null) + throw new ArgumentNullException(nameof(parentType)); + if (property == null) + throw new ArgumentNullException(nameof(property)); + + // validate static + if (isStatic && obj != null) + throw new ArgumentException("A static property cannot have an object instance."); + if (!isStatic && obj == null) + throw new ArgumentException("A non-static property must have an object instance."); + + + this.DisplayName = $"{parentType.FullName}::{property.Name}"; + this.PropertyInfo = property; + + if (this.PropertyInfo.GetMethod != null) + this.GetMethod = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); + if (this.PropertyInfo.SetMethod != null) + this.SetMethod = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); + } + + /// Get the property value. + public TValue GetValue() + { + if (this.GetMethod == null) + throw new InvalidOperationException($"The {this.DisplayName} property has no get method."); + + try + { + return this.GetMethod(); + } + catch (InvalidCastException) + { + 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 {this.DisplayName} property", ex); + } + } + + /// Set the property value. + //// The value to set. + public void SetValue(TValue value) + { + if (this.SetMethod == null) + throw new InvalidOperationException($"The {this.DisplayName} property has no set method."); + + try + { + this.SetMethod(value); + } + catch (InvalidCastException) + { + 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 {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 { - /// Provides helper methods for accessing private game code. + /// Provides helper methods for accessing inaccessible code. /// 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). internal class Reflector { @@ -25,139 +25,139 @@ namespace StardewModdingAPI.Framework.Reflection /**** ** Fields ****/ - /// Get a private instance field. + /// Get a instance field. /// The field type. /// The object which has the field. /// The field name. - /// Whether to throw an exception if the private field is not found. + /// Whether to throw an exception if the field is not found. /// Returns the field wrapper, or null if the field doesn't exist and is false. - public IPrivateField GetPrivateField(object obj, string name, bool required = true) + public IReflectedField GetField(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 field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedField field = this.GetFieldFromHierarchy(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; } - /// Get a private static field. + /// Get a static field. /// The field type. /// The type which has the field. /// The field name. - /// Whether to throw an exception if the private field is not found. - public IPrivateField GetPrivateField(Type type, string name, bool required = true) + /// Whether to throw an exception if the field is not found. + public IReflectedField GetField(Type type, string name, bool required = true) { // get field from hierarchy - IPrivateField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); + IReflectedField field = this.GetFieldFromHierarchy(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 ****/ - /// Get a private instance property. + /// Get a instance property. /// The property type. /// The object which has the property. /// The property name. - /// Whether to throw an exception if the private property is not found. - public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true) + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(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 property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedProperty property = this.GetPropertyFromHierarchy(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; } - /// Get a private static property. + /// Get a static property. /// The property type. /// The type which has the property. /// The property name. - /// Whether to throw an exception if the private property is not found. - public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true) + /// Whether to throw an exception if the property is not found. + public IReflectedProperty GetProperty(Type type, string name, bool required = true) { // get field from hierarchy - IPrivateProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedProperty property = this.GetPropertyFromHierarchy(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 ****/ - /// Get a private instance method. + /// Get a instance method. /// The object which has the method. /// The field name. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true) + /// Whether to throw an exception if the field is not found. + 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; } - /// Get a private static method. + /// Get a static method. /// The type which has the method. /// The field name. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true) + /// Whether to throw an exception if the field is not found. + 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 ****/ - /// Get a private instance method. + /// Get a instance method. /// The object which has the method. /// The field name. /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(object obj, string name, Type[] argumentTypes, bool required = true) + /// Whether to throw an exception if the field is not found. + 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; } - /// Get a private static method. + /// Get a static method. /// The type which has the method. /// The field name. /// The argument types of the method signature to find. - /// Whether to throw an exception if the private field is not found. - public IPrivateMethod GetPrivateMethod(Type type, string name, Type[] argumentTypes, bool required = true) + /// Whether to throw an exception if the field is not found. + 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 /// The object which has the field. /// The field name. /// The reflection binding which flags which indicates what type of field to find. - private IPrivateField GetFieldFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedField GetFieldFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); FieldInfo field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => @@ -183,7 +183,7 @@ namespace StardewModdingAPI.Framework.Reflection }); return field != null - ? new PrivateField(type, obj, field, isStatic) + ? new ReflectedField(type, obj, field, isStatic) : null; } @@ -193,7 +193,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the property. /// The property name. /// The reflection binding which flags which indicates what type of property to find. - private IPrivateProperty GetPropertyFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedProperty GetPropertyFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); PropertyInfo property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => @@ -205,7 +205,7 @@ namespace StardewModdingAPI.Framework.Reflection }); return property != null - ? new PrivateProperty(type, obj, property, isStatic) + ? new ReflectedProperty(type, obj, property, isStatic) : null; } @@ -214,7 +214,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the method. /// The method name. /// The reflection binding which flags which indicates what type of method to find. - 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 /// The method name. /// The reflection binding which flags which indicates what type of method to find. /// The argument types of the method signature to find. - 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..1803098d 100644 --- a/src/SMAPI/Framework/SContentManager.cs +++ b/src/SMAPI/Framework/SContentManager.cs @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework private readonly ContentCache Cache; /// The private method which generates the locale portion of an asset name. - private readonly IPrivateMethod GetKeyLocale; + private readonly IReflectedMethod GetKeyLocale; /// The language codes used in asset keys. private readonly IDictionary 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 @@ -413,7 +413,7 @@ namespace StardewModdingAPI.Framework private IDictionary GetKeyLocales(Reflector reflection) { // get the private code field directly to avoid changed-code logic - IPrivateField codeField = reflection.GetPrivateField(typeof(LocalizedContentManager), "_currentLangCode"); + IReflectedField codeField = reflection.GetField(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 3062b0f6..e9777e0b 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 /// Used to access private fields and methods. - private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); - private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); + private static List _fpsList => SGame.Reflection.GetField>(typeof(Game1), nameof(_fpsList)).GetValue(); + private static Stopwatch _fpsStopwatch => SGame.Reflection.GetField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); private static float _fps { - set => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_fps)).SetValue(value); + set => SGame.Reflection.GetField(typeof(Game1), nameof(_fps)).SetValue(value); } - private static Task _newDayTask => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(_newDayTask)).GetValue(); - private Color bgColor => SGame.Reflection.GetPrivateField(this, nameof(bgColor)).GetValue(); - public RenderTarget2D screenWrapper => SGame.Reflection.GetPrivateProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop - public BlendState lightingBlend => SGame.Reflection.GetPrivateField(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(typeof(Game1), nameof(_newDayTask)).GetValue(); + private Color bgColor => SGame.Reflection.GetField(this, nameof(bgColor)).GetValue(); + public RenderTarget2D screenWrapper => SGame.Reflection.GetProperty(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop + public BlendState lightingBlend => SGame.Reflection.GetField(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(typeof(Game1), "_temporaryContent").SetValue(new ContentManagerShim(this.SContentManager, "Game1._temporaryContent")); // regenerate value with new content manager + reflection.GetField(typeof(Game1), "_temporaryContent").SetValue(new ContentManagerShim(this.SContentManager, "Game1._temporaryContent")); // regenerate value with new content manager } /**** 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 { /// A private field obtained through reflection. /// The field value type. + [Obsolete("Use " + nameof(IReflectedField) + " instead")] public interface IPrivateField { /********* @@ -23,4 +25,4 @@ namespace StardewModdingAPI //// The value to set. 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 { /// A private method obtained through reflection. + [Obsolete("Use " + nameof(IReflectedMethod) + " instead")] public interface IPrivateMethod { /********* @@ -24,4 +26,4 @@ namespace StardewModdingAPI /// The method arguments to pass in. 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 { /// A private property obtained through reflection. /// The property value type. + [Obsolete("Use " + nameof(IPrivateProperty) + " instead")] public interface IPrivateProperty { /********* @@ -23,4 +25,4 @@ namespace StardewModdingAPI //// The value to set. 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 +{ + /// A field obtained through reflection. + /// The field value type. + public interface IReflectedField + { + /********* + ** Accessors + *********/ + /// The reflection metadata. + FieldInfo FieldInfo { get; } + + + /********* + ** Public methods + *********/ + /// Get the field value. + TValue GetValue(); + + /// Set the field value. + //// The value to set. + 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 +{ + /// A method obtained through reflection. + public interface IReflectedMethod + { + /********* + ** Accessors + *********/ + /// The reflection metadata. + MethodInfo MethodInfo { get; } + + + /********* + ** Public methods + *********/ + /// Invoke the method. + /// The return type. + /// The method arguments to pass in. + TValue Invoke(params object[] arguments); + + /// Invoke the method. + /// The method arguments to pass in. + 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 +{ + /// A property obtained through reflection. + /// The property value type. + public interface IReflectedProperty + { + /********* + ** Accessors + *********/ + /// The reflection metadata. + PropertyInfo PropertyInfo { get; } + + + /********* + ** Public methods + *********/ + /// Get the property value. + TValue GetValue(); + + /// Set the property value. + //// The value to set. + 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 { - /// Provides an API for accessing private game code. + /// Provides an API for accessing inaccessible code. public interface IReflectionHelper : IModLinked { /********* ** Public methods *********/ + /// Get an instance field. + /// The field type. + /// The object which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + IReflectedField GetField(object obj, string name, bool required = true); + + /// Get a static field. + /// The field type. + /// The type which has the field. + /// The field name. + /// Whether to throw an exception if the field is not found. + IReflectedField GetField(Type type, string name, bool required = true); + + /// Get an instance property. + /// The property type. + /// The object which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + IReflectedProperty GetProperty(object obj, string name, bool required = true); + + /// Get a static property. + /// The property type. + /// The type which has the property. + /// The property name. + /// Whether to throw an exception if the property is not found. + IReflectedProperty GetProperty(Type type, string name, bool required = true); + + /// Get an instance method. + /// The object which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + IReflectedMethod GetMethod(object obj, string name, bool required = true); + + /// Get a static method. + /// The type which has the method. + /// The field name. + /// Whether to throw an exception if the field is not found. + IReflectedMethod GetMethod(Type type, string name, bool required = true); + + /***** + ** Obsolete + *****/ /// Get a private instance field. /// The field type. /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " instead")] IPrivateField GetPrivateField(object obj, string name, bool required = true); /// Get a private static field. @@ -20,6 +64,7 @@ namespace StardewModdingAPI /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " instead")] IPrivateField GetPrivateField(Type type, string name, bool required = true); /// Get a private instance property. @@ -27,6 +72,7 @@ namespace StardewModdingAPI /// The object which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")] IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true); /// Get a private static property. @@ -34,6 +80,7 @@ namespace StardewModdingAPI /// The type which has the property. /// The property name. /// Whether to throw an exception if the private property is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")] IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true); /// Get the value of a private instance field. @@ -42,6 +89,7 @@ namespace StardewModdingAPI /// The field name. /// Whether to throw an exception if the private field is not found. /// This is a shortcut for followed by . + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " or " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")] TValue GetPrivateValue(object obj, string name, bool required = true); /// Get the value of a private static field. @@ -50,18 +98,21 @@ namespace StardewModdingAPI /// The field name. /// Whether to throw an exception if the private field is not found. /// This is a shortcut for followed by . + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetField) + " or " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetProperty) + " instead")] TValue GetPrivateValue(Type type, string name, bool required = true); /// Get a private instance method. /// The object which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetMethod) + " instead")] IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true); /// Get a private static method. /// The type which has the method. /// The field name. /// Whether to throw an exception if the private field is not found. + [Obsolete("Use " + nameof(IReflectionHelper) + "." + nameof(IReflectionHelper.GetMethod) + " instead")] IPrivateMethod GetPrivateMethod(Type type, string name, bool required = true); } } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 3ba35e43..7bfb0abd 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -728,7 +728,7 @@ namespace StardewModdingAPI 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); + IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, metadata.DisplayName, this.Reflection, this.DeprecationManager); IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry); ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentManager.GetLocale(), contentManager.GetCurrentLanguage()); diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 380ed733..0db94843 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -111,6 +111,9 @@ + + + @@ -169,7 +172,7 @@ - + @@ -198,8 +201,8 @@ - - + + -- cgit From 21fd2d1e39a6a94758f6298c2da52cd46cffdfcd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 15 Dec 2017 21:37:08 -0500 Subject: emit proxy classes directly to simplify crossplatform compatibility (#409) --- build/common.targets | 1 - build/prepare-install-package.targets | 2 - .../Framework/ModHelpers/ModRegistryHelper.cs | 11 +- .../Framework/Reflection/InterfaceProxyBuilder.cs | 138 +++++++++++++++++++++ src/SMAPI/Program.cs | 3 +- src/SMAPI/StardewModdingAPI.csproj | 4 +- src/SMAPI/packages.config | 1 - 7 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs (limited to 'src/SMAPI/Framework/Reflection') diff --git a/build/common.targets b/build/common.targets index 15c935e3..aa11344e 100644 --- a/build/common.targets +++ b/build/common.targets @@ -86,7 +86,6 @@ - diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index f8262271..dde2ff0a 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -22,7 +22,6 @@ - @@ -38,7 +37,6 @@ - diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 9574a632..ea0dbb38 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using ImpromptuInterface; +using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers { @@ -19,6 +19,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod IDs for APIs accessed by this instanced. private readonly HashSet AccessedModApis = new HashSet(); + /// Generates proxy classes to access mod APIs through an arbitrary interface. + private readonly InterfaceProxyBuilder ProxyBuilder; + /********* ** Public methods @@ -26,11 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, IMonitor monitor) + public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyBuilder proxyBuilder, IMonitor monitor) : base(modID) { this.Registry = registry; + this.ProxyBuilder = proxyBuilder; this.Monitor = monitor; } @@ -94,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get API of type if (api is TInterface castApi) return castApi; - return api.ActLike(); + return this.ProxyBuilder.CreateProxy(api, this.ModID, uniqueID); } } } 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 +{ + /// Generates proxy classes to access mod APIs through an arbitrary interface. + internal class InterfaceProxyBuilder + { + /********* + ** Properties + *********/ + /// The CLR module in which to create proxy classes. + private readonly ModuleBuilder ModuleBuilder; + + /// The generated proxy types. + private readonly IDictionary GeneratedTypes = new Dictionary(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + 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"); + } + + /// Create an API proxy. + /// The interface through which to access the API. + /// The API instance to access. + /// The unique ID of the mod consuming the API. + /// The unique ID of the mod providing the API. + public TInterface CreateProxy(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 + *********/ + /// Define a class which proxies access to a target type through an interface. + /// The name of the proxy type to generate. + /// The interface type through which to access the target. + /// The target type to access. + 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(); + } + + /// Define a method which proxies access to a method on the target. + /// The proxy type being generated. + /// The target method. + /// The proxy field containing the API instance. + 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/Program.cs b/src/SMAPI/Program.cs index 786549fe..7eda9c66 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -655,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 @@ -706,7 +707,7 @@ namespace StardewModdingAPI 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, this.DeprecationManager); - IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, monitor); + IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyBuilder, monitor); ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentManager.GetLocale(), contentManager.GetCurrentLanguage()); modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper); } diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 026ac106..0e8ccaa3 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -53,9 +53,6 @@ icon.ico - - ..\packages\ImpromptuInterface.6.2.2\lib\net40\ImpromptuInterface.dll - ..\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll True @@ -116,6 +113,7 @@ + diff --git a/src/SMAPI/packages.config b/src/SMAPI/packages.config index 60be6881..98d742c7 100644 --- a/src/SMAPI/packages.config +++ b/src/SMAPI/packages.config @@ -1,6 +1,5 @@  - \ No newline at end of file -- cgit