using System;
using System.Reflection;
using StardewModdingAPI.Framework.Reflection;
namespace StardewModdingAPI.Framework.ModHelpers
{
/// Provides helper methods for accessing private game 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 ReflectionHelper : BaseHelper, IReflectionHelper
{
/*********
** Properties
*********/
/// The underlying reflection helper.
private readonly Reflector Reflector;
/// The mod name for error messages.
private readonly string ModName;
/// Manages deprecation warnings.
private readonly DeprecationManager DeprecationManager;
/*********
** Public methods
*********/
/// Construct an instance.
/// The unique ID of the relevant mod.
/// The mod name for error messages.
/// The underlying reflection helper.
/// 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)
);
}
/****
** 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.
/// 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)
{
this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice);
return (IPrivateField)this.GetField(obj, name, required);
}
/// Get a private 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.
[Obsolete]
public IPrivateField GetPrivateField(Type type, string name, bool required = true)
{
this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice);
return (IPrivateField)this.GetField(type, name, required);
}
/// 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)
{
this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice);
return (IPrivateProperty)this.GetProperty(obj, name, required);
}
/// Get a private 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.
[Obsolete]
public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true)
{
this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice);
return (IPrivateProperty)this.GetProperty(type, name, required);
}
/// Get the value of 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.
/// Returns the field value, or the default value for if the field wasn't found and is false.
///
/// 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)
{
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);
}
/// Get the value of a private 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.
/// Returns the field value, or the default value for if the field wasn't found and is false.
///
/// 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)
{
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);
}
/// 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)
{
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)
{
this.DeprecationManager.Warn($"{nameof(IReflectionHelper)}.GetPrivate*", "2.3", DeprecationLevel.Notice);
return (IPrivateMethod)this.GetMethod(type, name, required);
}
/*********
** Private methods
*********/
/// Assert that mods can use the reflection helper to access the given member.
/// The field value type.
/// The field being accessed.
/// Returns the same field instance for convenience.
private IReflectedField AssertAccessAllowed(IReflectedField field)
{
this.AssertAccessAllowed(field?.FieldInfo);
return field;
}
/// Assert that mods can use the reflection helper to access the given member.
/// The property value type.
/// The property being accessed.
/// Returns the same property instance for convenience.
private IReflectedProperty AssertAccessAllowed(IReflectedProperty property)
{
this.AssertAccessAllowed(property?.PropertyInfo);
return property;
}
/// 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 IReflectedMethod AssertAccessAllowed(IReflectedMethod method)
{
this.AssertAccessAllowed(method?.MethodInfo);
return method;
}
/// Assert that mods can use the reflection helper to access the given member.
/// The member being accessed.
private void AssertAccessAllowed(MemberInfo member)
{
if (member == null)
return;
// get type which defines the member
Type declaringType = member.DeclaringType;
if (declaringType == null)
throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen
// validate access
string rootNamespace = typeof(Program).Namespace;
if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true)
throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)");
}
}
}