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.

272 lines
10 KiB

  1. using Mono.Cecil;
  2. using Newtonsoft.Json.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Reflection;
  6. using System.Text;
  7. #if NET3
  8. using Net3_Proxy;
  9. #endif
  10. namespace IPA.Loader.Features
  11. {
  12. /// <summary>
  13. /// The root interface for a mod Feature.
  14. /// </summary>
  15. /// <remarks>
  16. /// Avoid storing any data in any subclasses. If you do, it may result in a failure to load the feature.
  17. /// </remarks>
  18. public abstract class Feature
  19. {
  20. /// <summary>
  21. /// Initializes the feature with the data provided in the definition.
  22. /// </summary>
  23. /// <remarks>
  24. /// <para>This gets called AFTER your <c>Init</c> method, but BEFORE the target's <c>Init</c> method. If it is applied to the defining plugin, <c>BeforeInit</c> is not called.</para>
  25. /// <para>Returning <see langword="false" /> does <i>not</i> prevent the plugin from being loaded. It simply prevents the feature from being used.</para>
  26. /// </remarks>
  27. /// <param name="meta">the metadata of the plugin that is being prepared</param>
  28. /// <param name="featureData">the data provided with the feature</param>
  29. /// <returns><see langword="true"/> if the feature is valid for the plugin, <see langword="false"/> otherwise</returns>
  30. protected abstract bool Initialize(PluginMetadata meta, JObject featureData);
  31. /// <summary>
  32. /// The message to be logged when the feature is not valid for a plugin.
  33. /// This should also be set whenever either <see cref="BeforeInit"/> returns false.
  34. /// </summary>
  35. /// <value>the message to show when the feature is marked invalid</value>
  36. public virtual string InvalidMessage { get; protected set; }
  37. /// <summary>
  38. /// Called before a plugin's `Init` method is called. This will not be called if there is no `Init` method. This should never throw an exception. An exception will abort the loading of the plugin with an error.
  39. /// </summary>
  40. /// <param name="plugin">the plugin to be initialized</param>
  41. /// <returns>whether or not to call the Init method</returns>
  42. public virtual void BeforeInit(PluginMetadata plugin) { }
  43. /// <summary>
  44. /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
  45. /// </summary>
  46. /// <param name="plugin">the plugin that was just initialized</param>
  47. /// <param name="pluginInstance">the instance of the plugin being initialized</param>
  48. public virtual void AfterInit(PluginMetadata plugin, object pluginInstance) => AfterInit(plugin);
  49. /// <summary>
  50. /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception.
  51. /// </summary>
  52. /// <param name="plugin">the plugin that was just initialized</param>
  53. public virtual void AfterInit(PluginMetadata plugin) { }
  54. /// <summary>
  55. /// Called after a plugin with this feature appplied is disabled.
  56. /// </summary>
  57. /// <param name="plugin">the plugin that was disabled</param>
  58. public virtual void AfterDisable(PluginMetadata plugin) { }
  59. // TODO: rework features to take arguments as JSON objects
  60. static Feature()
  61. {
  62. Reset();
  63. }
  64. internal static void Reset()
  65. {
  66. featureTypes = new Dictionary<string, Type>
  67. {
  68. { "IPA.DefineFeature", typeof(DefineFeature) }
  69. };
  70. featureDelcarers = new Dictionary<string, PluginMetadata>
  71. {
  72. { "IPA.DefineFeature", null }
  73. };
  74. }
  75. private static Dictionary<string, Type> featureTypes;
  76. private static Dictionary<string, PluginMetadata> featureDelcarers;
  77. internal static bool HasFeature(string name) => featureTypes.ContainsKey(name);
  78. internal static bool PreregisterFeature(PluginMetadata defining, string name)
  79. {
  80. if (featureDelcarers.ContainsKey(name)) return false;
  81. featureDelcarers.Add(name, defining);
  82. return true;
  83. }
  84. internal static bool RegisterFeature(PluginMetadata definingPlugin, string name, Type type)
  85. {
  86. if (!typeof(Feature).IsAssignableFrom(type))
  87. throw new ArgumentException($"Feature type not subclass of {nameof(Feature)}", nameof(type));
  88. if (featureTypes.ContainsKey(name)) return false;
  89. if (featureDelcarers.TryGetValue(name, out var declarer))
  90. {
  91. if (definingPlugin != declarer)
  92. return false;
  93. }
  94. else
  95. {
  96. featureDelcarers.Add(name, definingPlugin);
  97. }
  98. featureTypes.Add(name, type);
  99. return true;
  100. }
  101. private class EmptyFeature : Feature
  102. {
  103. protected override bool Initialize(PluginMetadata meta, JObject featureData)
  104. {
  105. throw new NotImplementedException();
  106. }
  107. }
  108. internal string FeatureName;
  109. internal class Instance
  110. {
  111. public readonly PluginMetadata AppliedTo;
  112. public readonly string Name;
  113. public readonly JObject Data;
  114. public Instance(PluginMetadata appliedTo, string name, JObject data)
  115. {
  116. AppliedTo = appliedTo;
  117. Name = name;
  118. Data = data;
  119. type = null;
  120. }
  121. private Type type;
  122. public bool TryGetDefiningPlugin(out PluginMetadata plugin)
  123. {
  124. return featureDelcarers.TryGetValue(Name, out plugin);
  125. }
  126. // returns whether or not Initialize returned true, feature is always set when the thing exists
  127. public bool TryCreate(out Feature feature)
  128. {
  129. if (type == null)
  130. {
  131. if (!featureTypes.TryGetValue(Name, out type))
  132. {
  133. feature = new EmptyFeature() { InvalidMessage = "No such feature type found", FeatureName = Name };
  134. return false;
  135. }
  136. }
  137. bool result;
  138. try
  139. {
  140. feature = (Feature)Activator.CreateInstance(type);
  141. feature.FeatureName = Name;
  142. result = feature.Initialize(AppliedTo, Data);
  143. }
  144. catch (Exception e)
  145. {
  146. result = false;
  147. feature = new EmptyFeature() { InvalidMessage = e.ToString(), FeatureName = Name };
  148. }
  149. return result;
  150. }
  151. }
  152. /*
  153. // returns false with both outs null for no such feature
  154. internal static bool TryParseFeature(string featureString, PluginMetadata plugin,
  155. out Feature feature, out Exception failException, out bool featureValid, out Instance parsed,
  156. Instance? preParsed = null)
  157. {
  158. failException = null;
  159. feature = null;
  160. featureValid = false;
  161. if (preParsed == null)
  162. {
  163. var builder = new StringBuilder();
  164. string name = null;
  165. var parameters = new List<string>();
  166. bool escape = false;
  167. int parens = 0;
  168. bool removeWhitespace = true;
  169. foreach (var chr in featureString)
  170. {
  171. if (escape)
  172. {
  173. builder.Append(chr);
  174. escape = false;
  175. }
  176. else
  177. {
  178. switch (chr)
  179. {
  180. case '\\':
  181. escape = true;
  182. break;
  183. case '(':
  184. parens++;
  185. if (parens != 1) goto default;
  186. removeWhitespace = true;
  187. name = builder.ToString();
  188. builder.Clear();
  189. break;
  190. case ')':
  191. parens--;
  192. if (parens != 0) goto default;
  193. goto case ',';
  194. case ',':
  195. if (parens > 1) goto default;
  196. parameters.Add(builder.ToString());
  197. builder.Clear();
  198. removeWhitespace = true;
  199. break;
  200. default:
  201. if (removeWhitespace && !char.IsWhiteSpace(chr))
  202. removeWhitespace = false;
  203. if (!removeWhitespace)
  204. builder.Append(chr);
  205. break;
  206. }
  207. }
  208. }
  209. if (name == null)
  210. name = builder.ToString();
  211. parsed = new Instance(name, parameters.ToArray());
  212. if (parens != 0)
  213. {
  214. failException = new Exception("Malformed feature definition");
  215. return false;
  216. }
  217. }
  218. else
  219. parsed = preParsed.Value;
  220. if (!featureTypes.TryGetValue(parsed.Name, out var featureType))
  221. return false;
  222. try
  223. {
  224. if (!(Activator.CreateInstance(featureType) is Feature aFeature))
  225. {
  226. failException = new InvalidCastException("Feature type not a subtype of Feature");
  227. return false;
  228. }
  229. featureValid = aFeature.Initialize(plugin, TODO);
  230. feature = aFeature;
  231. return true;
  232. }
  233. catch (Exception e)
  234. {
  235. failException = e;
  236. return false;
  237. }
  238. }*/
  239. }
  240. }