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;
/*********
** Public methods
*********/
/// Construct an instance.
/// 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)
: base(modID)
{
this.ModName = modName;
this.Reflector = reflector;
}
/****
** Fields
****/
/// 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.
public IPrivateField GetPrivateField(object obj, string name, bool required = true)
{
return this.AssertAccessAllowed(
this.Reflector.GetPrivateField(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.
public IPrivateField GetPrivateField(Type type, string name, bool required = true)
{
return this.AssertAccessAllowed(
this.Reflector.GetPrivateField(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.
public IPrivateProperty GetPrivateProperty(object obj, string name, bool required = true)
{
return this.AssertAccessAllowed(
this.Reflector.GetPrivateProperty(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.
public IPrivateProperty GetPrivateProperty(Type type, string name, bool required = true)
{
return this.AssertAccessAllowed(
this.Reflector.GetPrivateProperty(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.
/// 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.
///
public TValue GetPrivateValue(object obj, string name, bool required = true)
{
IPrivateField field = this.GetPrivateField(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.
///
public TValue GetPrivateValue(Type type, string name, bool required = true)
{
IPrivateField field = this.GetPrivateField(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.
public IPrivateMethod GetPrivateMethod(object obj, string name, bool required = true)
{
return this.AssertAccessAllowed(
this.Reflector.GetPrivateMethod(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.
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)
);
}
/*********
** 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 IPrivateField AssertAccessAllowed(IPrivateField 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 IPrivateProperty AssertAccessAllowed(IPrivateProperty 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 IPrivateMethod AssertAccessAllowed(IPrivateMethod 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}.)");
}
}
}