using System; using System.Reflection; using StardewModdingAPI.Reflection; namespace StardewModdingAPI.Framework.Reflection { /// Provides helper methods for accessing private game code. internal class ReflectionHelper : IReflectionHelper { /********* ** Public methods *********/ /**** ** 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) { // validate if (obj == null) throw new ArgumentNullException(nameof(obj), "Can't get a private instance field from a null object."); // get field from hierarchy IPrivateField field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); if (required && field == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance field."); return field; } /// 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) { // get field from hierarchy IPrivateField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); if (required && field == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static field."); return field; } /**** ** 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. /// This is a shortcut for followed by . public TValue GetPrivateValue(object obj, string name, bool required = true) { return this.GetPrivateField(obj, name, required).GetValue(); } /// 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. /// This is a shortcut for followed by . public TValue GetPrivateValue(Type type, string name, bool required = true) { return this.GetPrivateField(type, name, required).GetValue(); } /**** ** 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) { // validate if (obj == null) throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object."); // get method from hierarchy IPrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method."); return method; } /// 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) { // get method from hierarchy IPrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a private '{name}' static method."); return method; } /**** ** 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) { // validate parent if (obj == null) throw new ArgumentNullException(nameof(obj), "Can't get a private instance method from a null object."); // get method from hierarchy PrivateMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic, argumentTypes); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a private '{name}' instance method with that signature."); return method; } /// 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) { // get field from hierarchy PrivateMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | 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."); return method; } /********* ** Private methods *********/ /// Get a field from the type hierarchy. /// The expected field type. /// The type which has the field. /// 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) { FieldInfo field = null; for (; type != null && field == null; type = type.BaseType) field = type.GetField(name, bindingFlags); return field != null ? new PrivateField(type, obj, field, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) : null; } /// Get a method from the type hierarchy. /// The type which has the method. /// 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) { MethodInfo method = null; for (; type != null && method == null; type = type.BaseType) method = type.GetMethod(name, bindingFlags); return method != null ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) : null; } /// Get a method from the type hierarchy. /// The type which has the method. /// The object which has the method. /// 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) { MethodInfo method = null; for (; type != null && method == null; type = type.BaseType) method = type.GetMethod(name, bindingFlags, null, argumentTypes, null); return method != null ? new PrivateMethod(type, obj, method, isStatic: bindingFlags.HasFlag(BindingFlags.Static)) : null; } } }