using Mono.Cecil; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Reflection; using System.Text; #if NET3 using Net3_Proxy; #endif namespace IPA.Loader.Features { /// /// The root interface for a mod Feature. /// /// /// Avoid storing any data in any subclasses. If you do, it may result in a failure to load the feature. /// public abstract class Feature { /// /// Initializes the feature with the data provided in the definition. /// /// /// This gets called AFTER your Init method, but BEFORE the target's Init method. If it is applied to the defining plugin, BeforeInit is not called. /// Returning does not prevent the plugin from being loaded. It simply prevents the feature from being used. /// /// the metadata of the plugin that is being prepared /// the data provided with the feature /// if the feature is valid for the plugin, otherwise protected abstract bool Initialize(PluginMetadata meta, JObject featureData); /// /// The message to be logged when the feature is not valid for a plugin. /// This should also be set whenever either returns false. /// /// the message to show when the feature is marked invalid public virtual string InvalidMessage { get; protected set; } /// /// 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. /// /// the plugin to be initialized /// whether or not to call the Init method public virtual void BeforeInit(PluginMetadata plugin) { } /// /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception. /// /// the plugin that was just initialized /// the instance of the plugin being initialized public virtual void AfterInit(PluginMetadata plugin, object pluginInstance) => AfterInit(plugin); /// /// Called after a plugin has been fully initialized, whether or not there is an `Init` method. This should never throw an exception. /// /// the plugin that was just initialized public virtual void AfterInit(PluginMetadata plugin) { } /// /// Called after a plugin with this feature appplied is disabled. /// /// the plugin that was disabled public virtual void AfterDisable(PluginMetadata plugin) { } // TODO: rework features to take arguments as JSON objects static Feature() { Reset(); } internal static void Reset() { featureTypes = new Dictionary { { "IPA.DefineFeature", typeof(DefineFeature) } }; featureDelcarers = new Dictionary { { "IPA.DefineFeature", null } }; } private static Dictionary featureTypes; private static Dictionary featureDelcarers; internal static bool HasFeature(string name) => featureTypes.ContainsKey(name); internal static bool PreregisterFeature(PluginMetadata defining, string name) { if (featureDelcarers.ContainsKey(name)) return false; featureDelcarers.Add(name, defining); return true; } internal static bool RegisterFeature(PluginMetadata definingPlugin, string name, Type type) { if (!typeof(Feature).IsAssignableFrom(type)) throw new ArgumentException($"Feature type not subclass of {nameof(Feature)}", nameof(type)); if (featureTypes.ContainsKey(name)) return false; if (featureDelcarers.TryGetValue(name, out var declarer)) { if (definingPlugin != declarer) return false; } else { featureDelcarers.Add(name, definingPlugin); } featureTypes.Add(name, type); return true; } private class EmptyFeature : Feature { protected override bool Initialize(PluginMetadata meta, JObject featureData) { throw new NotImplementedException(); } } internal string FeatureName; internal class Instance { public readonly PluginMetadata AppliedTo; public readonly string Name; public readonly JObject Data; public Instance(PluginMetadata appliedTo, string name, JObject data) { AppliedTo = appliedTo; Name = name; Data = data; type = null; } private Type type; public bool TryGetDefiningPlugin(out PluginMetadata plugin) { return featureDelcarers.TryGetValue(Name, out plugin); } // returns whether or not Initialize returned true, feature is always set when the thing exists public bool TryCreate(out Feature feature) { if (type == null) { if (!featureTypes.TryGetValue(Name, out type)) { feature = new EmptyFeature() { InvalidMessage = "No such feature type found", FeatureName = Name }; return false; } } bool result; try { feature = (Feature)Activator.CreateInstance(type); feature.FeatureName = Name; result = feature.Initialize(AppliedTo, Data); } catch (Exception e) { result = false; feature = new EmptyFeature() { InvalidMessage = e.ToString(), FeatureName = Name }; } return result; } } /* // returns false with both outs null for no such feature internal static bool TryParseFeature(string featureString, PluginMetadata plugin, out Feature feature, out Exception failException, out bool featureValid, out Instance parsed, Instance? preParsed = null) { failException = null; feature = null; featureValid = false; if (preParsed == null) { var builder = new StringBuilder(); string name = null; var parameters = new List(); bool escape = false; int parens = 0; bool removeWhitespace = true; foreach (var chr in featureString) { if (escape) { builder.Append(chr); escape = false; } else { switch (chr) { case '\\': escape = true; break; case '(': parens++; if (parens != 1) goto default; removeWhitespace = true; name = builder.ToString(); builder.Clear(); break; case ')': parens--; if (parens != 0) goto default; goto case ','; case ',': if (parens > 1) goto default; parameters.Add(builder.ToString()); builder.Clear(); removeWhitespace = true; break; default: if (removeWhitespace && !char.IsWhiteSpace(chr)) removeWhitespace = false; if (!removeWhitespace) builder.Append(chr); break; } } } if (name == null) name = builder.ToString(); parsed = new Instance(name, parameters.ToArray()); if (parens != 0) { failException = new Exception("Malformed feature definition"); return false; } } else parsed = preParsed.Value; if (!featureTypes.TryGetValue(parsed.Name, out var featureType)) return false; try { if (!(Activator.CreateInstance(featureType) is Feature aFeature)) { failException = new InvalidCastException("Feature type not a subtype of Feature"); return false; } featureValid = aFeature.Initialize(plugin, TODO); feature = aFeature; return true; } catch (Exception e) { failException = e; return false; } }*/ } }