#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IPA.Logging;
using IPA.Utilities;
using IPA.AntiMalware;
#if NET4
using Expression = System.Linq.Expressions.Expression;
using ExpressionEx = System.Linq.Expressions.Expression;
#endif
#if NET3
using Net3_Proxy;
#endif
namespace IPA.Loader
{
///
/// The type that handles value injecting into a plugin's initialization methods.
///
///
/// The default injectors and what they provide are shown in this table.
///
///
/// Parameter Type
/// Injected Value
///
///
///
/// A specialized for the plugin being injected
///
///
///
/// The of the plugin being injected
///
///
///
///
/// A object for the plugin being injected.
///
/// These parameters may have and to control
/// how it is constructed.
///
///
///
///
/// For all of the default injectors, only one of each will be generated, and any later parameters will recieve the same value as the first one.
///
public static class PluginInitInjector
{
///
/// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
///
/// the previous return value of the function, or if never called for plugin.
/// the of the parameter being injected.
/// the for the plugin being loaded.
/// the value to inject into that parameter.
public delegate object? InjectParameter(object? previous, ParameterInfo param, PluginMetadata meta);
///
/// A provider for parameter injectors to request injected values themselves.
///
///
/// Some injectors may look at attributes on the parameter to gain additional information about what it should provide.
/// If an injector wants to allow end users to affect the things it requests, it may pass the parameter it is currently
/// injecting for to this delegate along with a type override to select some other type.
///
/// the parameter that this is providing for.
/// an optional override for the parameter type.
/// the value that would otherwise be injected.
public delegate object? InjectedValueProvider(ParameterInfo forParam, Type? typeOverride = null);
///
/// A typed injector for a plugin's Init method. When registered, called for all associated types. If it returns null, the default for the type will be used.
///
/// the previous return value of the function, or if never called for plugin.
/// the of the parameter being injected.
/// the for the plugin being loaded.
/// an to allow the injector to request injected values.
/// the value to inject into that parameter.
public delegate object? InjectParameterNested(object? previous, ParameterInfo param, PluginMetadata meta, InjectedValueProvider provider);
///
/// Invokes the provider with and and casts the result to .
///
/// the type of object to be injected
/// the provider to invoke.
/// the parameter to provide for
/// the value requested, or .
public static T? Inject(this InjectedValueProvider provider, ParameterInfo param)
=> (T?)provider?.Invoke(param, typeof(T));
///
/// Adds an injector to be used when calling future plugins' Init methods.
///
/// the type of the parameter.
/// the function to call for injection.
public static void AddInjector(Type type, InjectParameter injector)
=> AddInjector(type, (pre, par, met, pro) => injector(pre, par, met));
///
/// Adds an injector to be used when calling future plugins' Init methods.
///
/// the type of the parameter.
/// the function to call for injection.
public static void AddInjector(Type type, InjectParameterNested injector)
{
injectors.Add(new TypedInjector(type, injector));
}
private struct TypedInjector : IEquatable
{
public Type Type;
public InjectParameterNested Injector;
public TypedInjector(Type t, InjectParameterNested i)
{ Type = t; Injector = i; }
public object? Inject(object? prev, ParameterInfo info, PluginMetadata meta, InjectedValueProvider provider)
=> Injector(prev, info, meta, provider);
public bool Equals(TypedInjector other)
=> Type == other.Type && Injector == other.Injector;
public override bool Equals(object obj)
=> obj is TypedInjector i && Equals(i);
public override int GetHashCode()
=> Type.GetHashCode() ^ Injector.GetHashCode();
public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
}
private static readonly List injectors = new()
{
new TypedInjector(typeof(Logger), (prev, param, meta, _) => prev ?? new StandardLogger(meta.Name)),
new TypedInjector(typeof(PluginMetadata), (prev, param, meta, _) => prev ?? meta),
new TypedInjector(typeof(Config.Config), (prev, param, meta, _) => prev ?? Config.Config.GetConfigFor(meta.Name, param)),
new TypedInjector(typeof(IAntiMalware), (prev, param, meta, _) => prev ?? AntiMalwareEngine.Engine)
};
private static int? MatchPriority(Type target, Type source)
{
if (target == source) return int.MaxValue;
if (!target.IsAssignableFrom(source)) return null;
if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
int value = 0;
while (true)
{
if (source == null) return value;
if (target.IsInterface && source.GetInterfaces().Contains(target))
return value;
else if (target == source)
return value;
else
{
value--; // lower priority
source = source.BaseType;
}
}
}
private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static);
internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Expression persistVar, Func, Expression> exprGen)
{
var arr = ExpressionEx.Variable(typeof(object[]), "initArr");
return ExpressionEx.Block(new[] { arr },
ExpressionEx.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)),
exprGen(initParams
.Select(p => p.ParameterType)
.Select((t, i) => (Expression)Expression.Convert(
Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
}
private static object? InjectForParameter(
Dictionary previousValues,
PluginMetadata meta,
ParameterInfo param,
Type paramType,
InjectedValueProvider provider)
{
var value = paramType.GetDefault();
var toUse = injectors
.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
.NonNull(t => t.priority) // filter null priorities
.Select(t => (t.inject, priority: t.priority!.Value)) // remove nullable
.OrderByDescending(t => t.priority) // sort by value
.Select(t => t.inject); // remove priority value
// this tries injectors in order of closest match by type provided
foreach (var pair in toUse)
{
object? prev = null;
if (previousValues.ContainsKey(pair))
prev = previousValues[pair];
var val = pair.Inject(prev, param, meta, provider);
if (previousValues.ContainsKey(pair))
previousValues[pair] = val;
else
previousValues.Add(pair, val);
if (val == null) continue;
value = val;
break;
}
return value;
}
private class InjectedValueProviderWrapperImplementation
{
public Dictionary PreviousValues { get; }
public PluginMetadata Meta { get; }
public InjectedValueProvider Provider { get; }
public InjectedValueProviderWrapperImplementation(PluginMetadata meta)
{
Meta = meta;
PreviousValues = new();
Provider = Inject;
}
private object? Inject(ParameterInfo param, Type? typeOverride = null)
=> InjectForParameter(PreviousValues, Meta, param, typeOverride ?? param.ParameterType, Provider);
}
internal static object?[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object? persist)
{
var initArgs = new List