You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
8.1 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using IPA.Config;
  7. using IPA.Logging;
  8. using IPA.Utilities;
  9. using System.Linq.Expressions;
  10. using IPA.AntiMalware;
  11. #if NET4
  12. using Expression = System.Linq.Expressions.Expression;
  13. using ExpressionEx = System.Linq.Expressions.Expression;
  14. #endif
  15. #if NET3
  16. using Net3_Proxy;
  17. #endif
  18. namespace IPA.Loader
  19. {
  20. /// <summary>
  21. /// The type that handles value injecting into a plugin's initialization methods.
  22. /// </summary>
  23. /// <remarks>
  24. /// The default injectors and what they provide are shown in this table.
  25. /// <list type="table">
  26. /// <listheader>
  27. /// <term>Parameter Type</term>
  28. /// <description>Injected Value</description>
  29. /// </listheader>
  30. /// <item>
  31. /// <term><see cref="Logger"/></term>
  32. /// <description>A <see cref="StandardLogger"/> specialized for the plugin being injected</description>
  33. /// </item>
  34. /// <item>
  35. /// <term><see cref="PluginMetadata"/></term>
  36. /// <description>The <see cref="PluginMetadata"/> of the plugin being injected</description>
  37. /// </item>
  38. /// <item>
  39. /// <term><see cref="Config.Config"/></term>
  40. /// <description>
  41. /// <para>A <see cref="Config.Config"/> object for the plugin being injected.</para>
  42. /// <para>
  43. /// These parameters may have <see cref="Config.Config.NameAttribute"/> and <see cref="Config.Config.PreferAttribute"/> to control
  44. /// how it is constructed.
  45. /// </para>
  46. /// </description>
  47. /// </item>
  48. /// </list>
  49. /// 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.
  50. /// </remarks>
  51. public static class PluginInitInjector
  52. {
  53. /// <summary>
  54. /// 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.
  55. /// </summary>
  56. /// <param name="previous">the previous return value of the function, or <see langword="null"/> if never called for plugin.</param>
  57. /// <param name="param">the <see cref="ParameterInfo"/> of the parameter being injected.</param>
  58. /// <param name="meta">the <see cref="PluginMetadata"/> for the plugin being loaded.</param>
  59. /// <returns>the value to inject into that parameter.</returns>
  60. public delegate object? InjectParameter(object? previous, ParameterInfo param, PluginMetadata meta);
  61. /// <summary>
  62. /// Adds an injector to be used when calling future plugins' Init methods.
  63. /// </summary>
  64. /// <param name="type">the type of the parameter.</param>
  65. /// <param name="injector">the function to call for injection.</param>
  66. public static void AddInjector(Type type, InjectParameter injector)
  67. {
  68. injectors.Add(new TypedInjector(type, injector));
  69. }
  70. private struct TypedInjector : IEquatable<TypedInjector>
  71. {
  72. public Type Type;
  73. public InjectParameter Injector;
  74. public TypedInjector(Type t, InjectParameter i)
  75. { Type = t; Injector = i; }
  76. public object? Inject(object? prev, ParameterInfo info, PluginMetadata meta)
  77. => Injector(prev, info, meta);
  78. public bool Equals(TypedInjector other)
  79. => Type == other.Type && Injector == other.Injector;
  80. public override bool Equals(object obj)
  81. => obj is TypedInjector i && Equals(i);
  82. public override int GetHashCode()
  83. => Type.GetHashCode() ^ Injector.GetHashCode();
  84. public static bool operator ==(TypedInjector a, TypedInjector b) => a.Equals(b);
  85. public static bool operator !=(TypedInjector a, TypedInjector b) => !a.Equals(b);
  86. }
  87. private static readonly List<TypedInjector> injectors = new()
  88. {
  89. new TypedInjector(typeof(Logger), (prev, param, meta) => prev ?? new StandardLogger(meta.Name)),
  90. new TypedInjector(typeof(PluginMetadata), (prev, param, meta) => prev ?? meta),
  91. new TypedInjector(typeof(Config.Config), (prev, param, meta) => prev ?? Config.Config.GetConfigFor(meta.Name, param)),
  92. new TypedInjector(typeof(IAntiMalware), (prev, param, meta) => prev ?? AntiMalwareEngine.Engine)
  93. };
  94. private static int? MatchPriority(Type target, Type source)
  95. {
  96. if (target == source) return int.MaxValue;
  97. if (!target.IsAssignableFrom(source)) return null;
  98. if (!target.IsInterface && !source.IsSubclassOf(target)) return int.MinValue;
  99. int value = 0;
  100. while (true)
  101. {
  102. if (source == null) return value;
  103. if (target.IsInterface && source.GetInterfaces().Contains(target))
  104. return value;
  105. else if (target == source)
  106. return value;
  107. else
  108. {
  109. value--; // lower priority
  110. source = source.BaseType;
  111. }
  112. }
  113. }
  114. private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static);
  115. internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Expression persistVar, Func<IEnumerable<Expression>, Expression> exprGen)
  116. {
  117. var arr = ExpressionEx.Variable(typeof(object[]), "initArr");
  118. return ExpressionEx.Block(new[] { arr },
  119. ExpressionEx.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)),
  120. exprGen(initParams
  121. .Select(p => p.ParameterType)
  122. .Select((t, i) => (Expression)Expression.Convert(
  123. Expression.ArrayIndex(arr, Expression.Constant(i)), t))));
  124. }
  125. internal static object?[] Inject(ParameterInfo[] initParams, PluginMetadata meta, ref object? persist)
  126. {
  127. var initArgs = new List<object?>();
  128. var previousValues = persist as Dictionary<TypedInjector, object?>;
  129. if (previousValues == null)
  130. {
  131. previousValues = new(injectors.Count);
  132. persist = previousValues;
  133. }
  134. foreach (var param in initParams)
  135. {
  136. var paramType = param.ParameterType;
  137. var value = paramType.GetDefault();
  138. var toUse = injectors.Select(i => (inject: i, priority: MatchPriority(paramType, i.Type))) // check match priority, combine it
  139. .NonNull(t => t.priority) // filter null priorities
  140. .Select(t => (t.inject, priority: t.priority!.Value)) // remove nullable
  141. .OrderByDescending(t => t.priority) // sort by value
  142. .Select(t => t.inject); // remove priority value
  143. // this tries injectors in order of closest match by type provided
  144. foreach (var pair in toUse)
  145. {
  146. object? prev = null;
  147. if (previousValues.ContainsKey(pair))
  148. prev = previousValues[pair];
  149. var val = pair.Inject(prev, param, meta);
  150. if (previousValues.ContainsKey(pair))
  151. previousValues[pair] = val;
  152. else
  153. previousValues.Add(pair, val);
  154. if (val == null) continue;
  155. value = val;
  156. break;
  157. }
  158. initArgs.Add(value);
  159. }
  160. //init.Invoke(instance, initArgs.ToArray());
  161. return initArgs.ToArray();
  162. }
  163. }
  164. }