using System;
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)
{
this.AssertAccessAllowed(obj);
return 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)
{
this.AssertAccessAllowed(type);
return 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)
{
this.AssertAccessAllowed(obj);
return 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)
{
this.AssertAccessAllowed(type);
return 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)
{
this.AssertAccessAllowed(obj);
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)
{
this.AssertAccessAllowed(type);
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)
{
this.AssertAccessAllowed(obj);
return 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)
{
this.AssertAccessAllowed(type);
return 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)
{
this.AssertAccessAllowed(obj);
return 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)
{
this.AssertAccessAllowed(type);
return this.Reflector.GetPrivateMethod(type, name, argumentTypes, required);
}
/*********
** Private methods
*********/
/// Assert that mods can use the reflection helper to access the given type.
/// The type being accessed.
private void AssertAccessAllowed(Type type)
{
// validate type namespace
if (type.Namespace != null)
{
string rootSmapiNamespace = typeof(Program).Namespace;
if (type.Namespace == rootSmapiNamespace || type.Namespace.StartsWith(rootSmapiNamespace + "."))
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.");
}
}
/// Assert that mods can use the reflection helper to access the given type.
/// The object being accessed.
private void AssertAccessAllowed(object obj)
{
if (obj != null)
this.AssertAccessAllowed(obj.GetType());
}
}
}