From ce6e92ef5a5b3c5ce4483efbee6d0d3a18e0dd7f Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sat, 22 Aug 2020 02:01:44 -0500 Subject: [PATCH] Implemented new Features system --- .../Loader/Features/ConfigProviderFeature.cs | 29 ++-- IPA.Loader/Loader/Features/DefineFeature.cs | 70 ++++++--- IPA.Loader/Loader/Features/Feature.cs | 139 ++++++++++++------ .../Loader/Features/InitInjectorFeature.cs | 105 ------------- IPA.Loader/Loader/Features/NoUpdateFeature.cs | 12 -- IPA.Loader/Loader/Features/PrintFeature.cs | 32 ---- IPA.Loader/Loader/PluginLoader.cs | 105 ++++++------- IPA.Loader/Loader/PluginManager.cs | 28 +++- IPA.Loader/Loader/PluginManifest.cs | 3 +- IPA.Loader/Loader/PluginMetadata.cs | 4 + IPA.Loader/Loader/manifest.json | 14 +- IPA.Loader/Updating/BeatMods/Updater.cs | 15 -- 12 files changed, 242 insertions(+), 314 deletions(-) delete mode 100644 IPA.Loader/Loader/Features/InitInjectorFeature.cs delete mode 100644 IPA.Loader/Loader/Features/NoUpdateFeature.cs delete mode 100644 IPA.Loader/Loader/Features/PrintFeature.cs diff --git a/IPA.Loader/Loader/Features/ConfigProviderFeature.cs b/IPA.Loader/Loader/Features/ConfigProviderFeature.cs index 95c4ff38..bceceaf7 100644 --- a/IPA.Loader/Loader/Features/ConfigProviderFeature.cs +++ b/IPA.Loader/Loader/Features/ConfigProviderFeature.cs @@ -1,28 +1,39 @@ -using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; using System.IO; namespace IPA.Loader.Features { internal class ConfigProviderFeature : Feature { - public override bool Initialize(PluginMetadata meta, string[] parameters) - {// parameters should be (fully qualified name of provider type) - if (parameters.Length != 1) + private class DataModel + { + [JsonProperty("type", Required = Required.Always)] + public string TypeName; + } + + protected override bool Initialize(PluginMetadata meta, JObject featureData) + { + DataModel data; + try { - InvalidMessage = "Incorrect number of parameters"; + data = featureData.ToObject(); + } + catch (Exception e) + { + InvalidMessage = $"Invalid data: {e}"; return false; } - RequireLoaded(meta); - Type getType; try { - getType = meta.Assembly.GetType(parameters[0]); + getType = meta.Assembly.GetType(data.TypeName); } catch (ArgumentException) { - InvalidMessage = $"Invalid type name {parameters[0]}"; + InvalidMessage = $"Invalid type name {data.TypeName}"; return false; } catch (Exception e) when (e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException) diff --git a/IPA.Loader/Loader/Features/DefineFeature.cs b/IPA.Loader/Loader/Features/DefineFeature.cs index 6835cc32..1b08bfa5 100644 --- a/IPA.Loader/Loader/Features/DefineFeature.cs +++ b/IPA.Loader/Loader/Features/DefineFeature.cs @@ -1,4 +1,7 @@ -using System; +using IPA.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; using System.IO; namespace IPA.Loader.Features @@ -7,27 +10,49 @@ namespace IPA.Loader.Features { public static bool NewFeature = true; - protected internal override bool StoreOnPlugin => false; + private class DataModel + { + [JsonProperty("type", Required = Required.Always)] + public string TypeName; + [JsonProperty("name", Required = Required.DisallowNull)] + public string ActualName = null; - public override bool Initialize(PluginMetadata meta, string[] parameters) - { // parameters should be (name, fully qualified type) - if (parameters.Length != 2) + public string Name => ActualName ?? TypeName; + } + + private DataModel data; + + protected override bool Initialize(PluginMetadata meta, JObject featureData) + { + Logger.features.Debug("Executing DefineFeature Init"); + + try { - InvalidMessage = "Incorrect number of parameters"; + data = featureData.ToObject(); + } + catch (Exception e) + { + InvalidMessage = $"Invalid data: {e}"; return false; } - - RequireLoaded(meta); + + InvalidMessage = $"Feature {data.Name} already exists"; + return PreregisterFeature(meta, data.Name); + } + + public override void AfterInit(PluginMetadata meta, object pluginInstance) + { + Logger.features.Debug("Executing DefineFeature AfterInit"); Type type; try { - type = meta.Assembly.GetType(parameters[1]); + type = meta.Assembly.GetType(data.TypeName); } catch (ArgumentException) { - InvalidMessage = $"Invalid type name {parameters[1]}"; - return false; + Logger.features.Error($"Invalid type name {data.TypeName}"); + return; } catch (Exception e) when (e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException) { @@ -46,28 +71,31 @@ namespace IPA.Loader.Features break; } - InvalidMessage = $"Could not find {filename} while loading type"; - return false; + Logger.features.Error($"Could not find {filename} while loading type"); + return; } if (type == null) { - InvalidMessage = $"Invalid type name {parameters[1]}"; - return false; + Logger.features.Error($"Invalid type name {data.TypeName}"); + return; } try { - if (RegisterFeature(parameters[0], type)) return NewFeature = true; - - InvalidMessage = $"Feature with name {parameters[0]} already exists"; - return false; + if (RegisterFeature(meta, data.Name, type)) + { + NewFeature = true; + return; + } + Logger.features.Error($"Feature with name {data.Name} already exists"); + return; } catch (ArgumentException) { - InvalidMessage = $"{type.FullName} not a subclass of {nameof(Feature)}"; - return false; + Logger.features.Error($"{type.FullName} not a subclass of {nameof(Feature)}"); + return; } } } diff --git a/IPA.Loader/Loader/Features/Feature.cs b/IPA.Loader/Loader/Features/Feature.cs index 20d07728..d51b94ea 100644 --- a/IPA.Loader/Loader/Features/Feature.cs +++ b/IPA.Loader/Loader/Features/Feature.cs @@ -1,5 +1,8 @@ -using System; +using Mono.Cecil; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; +using System.Reflection; using System.Text; #if NET3 using Net3_Proxy; @@ -16,46 +19,24 @@ namespace IPA.Loader.Features public abstract class Feature { /// - /// Initializes the feature with the parameters provided in the definition. - /// - /// Note: When no parenthesis are provided, is an empty array. + /// Initializes the feature with the data provided in the definition. /// /// - /// This gets called BEFORE *your* `Init` method. - /// - /// Returning does *not* prevent the plugin from being loaded. It simply prevents the feature from being used. + /// 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 parameters passed to the feature definition, or null + /// the data provided with the feature /// if the feature is valid for the plugin, otherwise - public abstract bool Initialize(PluginMetadata meta, string[] parameters); - - /// - /// Evaluates the Feature for use in conditional meta-Features. This should be re-calculated on every call, unless it can be proven to not change. - /// - /// This will be called on every feature that returns from - /// - /// the truthiness of the Feature. - public virtual bool Evaluate() => true; + 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 or returns false. + /// 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 is loaded. This should never throw an exception. An exception will abort the loading of the plugin with an error. - /// - /// - /// The assembly will still be loaded, but the plugin will not be constructed if this returns . - /// Any features it defines, for example, will still be loaded. - /// - /// the plugin about to be loaded - /// whether or not the plugin should be loaded - public virtual bool BeforeLoad(PluginMetadata plugin) => true; - /// /// 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. /// @@ -77,16 +58,10 @@ namespace IPA.Loader.Features public virtual void AfterInit(PluginMetadata plugin) { } /// - /// Ensures a plugin's assembly is loaded. Do not use unless you need to. - /// - /// the plugin to ensure is loaded. - protected void RequireLoaded(PluginMetadata plugin) => PluginLoader.Load(plugin); - - /// - /// Defines whether or not this feature will be accessible from the plugin metadata once loaded. + /// Called after a plugin with this feature appplied is disabled. /// - /// if this will be stored on the plugin metadata, otherwise - protected internal virtual bool StoreOnPlugin => true; + /// the plugin that was disabled + public virtual void AfterDisable(PluginMetadata plugin) { } // TODO: rework features to take arguments as JSON objects @@ -99,39 +74,105 @@ namespace IPA.Loader.Features { featureTypes = new Dictionary { - { "define-feature", typeof(DefineFeature) } + { "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 RegisterFeature(string name, Type type) + 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; + } + featureTypes.Add(name, type); return true; } - internal struct FeatureParse + 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 string[] Parameters; + public readonly JObject Data; - public FeatureParse(string name, string[] parameters) + public Instance(PluginMetadata appliedTo, string name, JObject data) { + AppliedTo = appliedTo; Name = name; - Parameters = parameters; + 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) + { + feature = null; + if (type == null) + { + if (!featureTypes.TryGetValue(Name, out type)) + 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() }; + } + 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 FeatureParse parsed, - FeatureParse? preParsed = null) + out Feature feature, out Exception failException, out bool featureValid, out Instance parsed, + Instance? preParsed = null) { failException = null; feature = null; @@ -190,7 +231,7 @@ namespace IPA.Loader.Features if (name == null) name = builder.ToString(); - parsed = new FeatureParse(name, parameters.ToArray()); + parsed = new Instance(name, parameters.ToArray()); if (parens != 0) { @@ -212,7 +253,7 @@ namespace IPA.Loader.Features return false; } - featureValid = aFeature.Initialize(plugin, parsed.Parameters); + featureValid = aFeature.Initialize(plugin, TODO); feature = aFeature; return true; } @@ -221,6 +262,6 @@ namespace IPA.Loader.Features failException = e; return false; } - } + }*/ } } \ No newline at end of file diff --git a/IPA.Loader/Loader/Features/InitInjectorFeature.cs b/IPA.Loader/Loader/Features/InitInjectorFeature.cs deleted file mode 100644 index af6487f9..00000000 --- a/IPA.Loader/Loader/Features/InitInjectorFeature.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -#if NET3 -using Array = Net3_Proxy.Array; -#endif - -namespace IPA.Loader.Features -{ - internal class InitInjectorFeature : Feature - { - protected internal override bool StoreOnPlugin => false; - - public override bool Initialize(PluginMetadata meta, string[] parameters) - { // parameters should be (assembly qualified lookup type, [fully qualified type]:[method name]) - // method should be static - if (parameters.Length != 2) - { - InvalidMessage = "Incorrect number of parameters"; - return false; - } - - RequireLoaded(meta); - - var methodParts = parameters[1].Split(':'); - - var type = Type.GetType(parameters[0], false); - if (type == null) - { - InvalidMessage = $"Could not find type {parameters[0]}"; - return false; - } - - Type getType; - try - { - getType = meta.Assembly.GetType(methodParts[0]); - } - catch (ArgumentException) - { - InvalidMessage = $"Invalid type name {methodParts[0]}"; - return false; - } - catch (Exception e) when (e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException) - { - string filename; - - switch (e) - { - case FileNotFoundException fn: - filename = fn.FileName; - goto hasFilename; - case FileLoadException fl: - filename = fl.FileName; - goto hasFilename; - case BadImageFormatException bi: - filename = bi.FileName; - hasFilename: - InvalidMessage = $"Could not find {filename} while loading type"; - break; - default: - InvalidMessage = $"Error while loading type: {e}"; - break; - } - - return false; - } - - MethodInfo method; - try - { - method = getType.GetMethod(methodParts[1], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, - null, new[] - { - typeof(object), - typeof(ParameterInfo), - typeof(PluginMetadata) - }, Array.Empty()); - } - catch (Exception e) - { - InvalidMessage = $"Error while loading type: {e}"; - return false; - } - - if (method == null) - { - InvalidMessage = $"Could not find method {methodParts[1]} in type {methodParts[0]}"; - return false; - } - - try - { - var del = (PluginInitInjector.InjectParameter)Delegate.CreateDelegate(typeof(PluginInitInjector.InjectParameter), null, method); - PluginInitInjector.AddInjector(type, del); - return true; - } - catch (Exception e) - { - InvalidMessage = $"Error generated while creating delegate: {e}"; - return false; - } - } - } -} diff --git a/IPA.Loader/Loader/Features/NoUpdateFeature.cs b/IPA.Loader/Loader/Features/NoUpdateFeature.cs deleted file mode 100644 index afe38972..00000000 --- a/IPA.Loader/Loader/Features/NoUpdateFeature.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace IPA.Loader.Features -{ - internal class NoUpdateFeature : Feature - { - public override bool Initialize(PluginMetadata meta, string[] parameters) - { - return meta.Id != null; - } - - public override string InvalidMessage { get; protected set; } = "No ID specified; cannot update anyway"; - } -} diff --git a/IPA.Loader/Loader/Features/PrintFeature.cs b/IPA.Loader/Loader/Features/PrintFeature.cs deleted file mode 100644 index 2acc48d8..00000000 --- a/IPA.Loader/Loader/Features/PrintFeature.cs +++ /dev/null @@ -1,32 +0,0 @@ - -using IPA.Logging; - -namespace IPA.Loader.Features -{ - internal class PrintFeature : Feature - { - public override bool Initialize(PluginMetadata meta, string[] parameters) - { - Logger.features.Info($"{meta.Name}: {string.Join(" ", parameters)}"); - return true; - } - } - - internal class DebugFeature : Feature - { - public override bool Initialize(PluginMetadata meta, string[] parameters) - { - Logger.features.Debug($"{meta.Name}: {string.Join(" ", parameters)}"); - return true; - } - } - - internal class WarnFeature : Feature - { - public override bool Initialize(PluginMetadata meta, string[] parameters) - { - Logger.features.Warn($"{meta.Name}: {string.Join(" ", parameters)}"); - return true; - } - } -} diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index aefc86fa..b3b8facb 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -39,6 +39,7 @@ namespace IPA.Loader LoadMetadata(); Resolve(); + InitFeatures(); ComputeLoadOrder(); FilterDisabled(); FilterWithoutFiles(); @@ -687,57 +688,47 @@ namespace IPA.Loader internal static void InitFeatures() { - var parsedFeatures = PluginsMetadata.Select(m => - (metadata: m, - features: m.Manifest.Features.Select(feature => - (feature, parsed: Ref.Create(null)) - ).ToList() - ) - ).ToList(); - - while (DefineFeature.NewFeature) + foreach (var meta in PluginsMetadata) { - DefineFeature.NewFeature = false; - - foreach (var (metadata, features) in parsedFeatures) - for (var i = 0; i < features.Count; i++) - { - var feature = features[i]; - - var success = Feature.TryParseFeature(feature.feature, metadata, out var featureObj, - out var exception, out var valid, out var parsed, feature.parsed.Value); - - if (!success && !valid && featureObj == null && exception == null) // no feature of type found - feature.parsed.Value = parsed; - else if (success) + foreach (var feature in meta.Manifest.Features.Select(f => new Feature.Instance(meta, f.Key, f.Value))) + { + if (feature.TryGetDefiningPlugin(out var plugin) && plugin == null) + { // this is a DefineFeature, so we want to initialize it early + if (!feature.TryCreate(out var inst)) { - if (valid && featureObj.StoreOnPlugin) - metadata.InternalFeatures.Add(featureObj); - else if (!valid) - Logger.features.Warn( - $"Feature not valid on {metadata.Name}: {featureObj.InvalidMessage}"); - features.RemoveAt(i--); + Logger.features.Error($"Error evaluating {feature.Name}: {inst.InvalidMessage}"); } else { - Logger.features.Error($"Error parsing feature definition on {metadata.Name}"); - Logger.features.Error(exception); - features.RemoveAt(i--); + meta.InternalFeatures.Add(inst); } } - - foreach (var plugin in PluginsMetadata) - foreach (var feature in plugin.Features) - feature.Evaluate(); + else + { // this is literally any other feature, so we want to delay its initialization + meta.UnloadedFeatures.Add(feature); + } + } } - foreach (var plugin in parsedFeatures) + // at this point we have pre-initialized all features, so we can go ahead and use them to add stuff to the dep resolver + foreach (var meta in PluginsMetadata) { - if (plugin.features.Count <= 0) continue; + foreach (var feature in meta.UnloadedFeatures) + { + if (feature.TryGetDefiningPlugin(out var plugin)) + { + if (plugin != meta) + { // if the feature is not applied to the defining feature + meta.LoadsAfter.Add(plugin); + } - Logger.features.Warn($"On plugin {plugin.metadata.Name}:"); - foreach (var feature in plugin.features) - Logger.features.Warn($" Feature not found with name {feature.feature}"); + plugin.CreateFeaturesWhenLoaded.Add(feature); + } + else + { + Logger.features.Warn($"No such feature {feature.Name}"); + } + } } } @@ -807,20 +798,6 @@ namespace IPA.Loader Load(meta); - foreach (var feature in meta.Features) - { - if (!feature.BeforeLoad(meta)) - { - Logger.loader.Warn( - $"Feature {feature?.GetType()} denied plugin {meta.Name} from loading! {feature?.InvalidMessage}"); - ignoredPlugins.Add(meta, new IgnoreReason(Reason.Feature) - { - ReasonText = $"Denied in {nameof(Feature.BeforeLoad)} of feature {feature?.GetType()}:\n\t{feature?.InvalidMessage}" - }); - return null; - } - } - PluginExecutor exec; try { @@ -838,10 +815,10 @@ namespace IPA.Loader if (!feature.BeforeInit(meta)) { Logger.loader.Warn( - $"Feature {feature?.GetType()} denied plugin {meta.Name} from initializing! {feature?.InvalidMessage}"); + $"Feature {feature?.FeatureName} denied plugin {meta.Name} from initializing! {feature?.InvalidMessage}"); ignoredPlugins.Add(meta, new IgnoreReason(Reason.Feature) { - ReasonText = $"Denied in {nameof(Feature.BeforeInit)} of feature {feature?.GetType()}:\n\t{feature?.InvalidMessage}" + ReasonText = $"Denied in {nameof(Feature.BeforeInit)} of feature {feature?.FeatureName}:\n\t{feature?.InvalidMessage}" }); return null; } @@ -863,6 +840,21 @@ namespace IPA.Loader return null; } + // TODO: make this new features system behave better wrt DynamicInit plugins + foreach (var feature in meta.CreateFeaturesWhenLoaded) + { + if (!feature.TryCreate(out var inst)) + { + Logger.features.Warn($"Could not create instance of feature {feature.Name}: {inst.InvalidMessage}"); + } + else + { + feature.AppliedTo.InternalFeatures.Add(inst); + feature.AppliedTo.UnloadedFeatures.Remove(feature); + } + } + meta.CreateFeaturesWhenLoaded.Clear(); // if a plugin is loaded twice, for the moment, we don't want to create the feature twice + foreach (var feature in meta.Features) try { @@ -880,7 +872,6 @@ namespace IPA.Loader internal static List LoadPlugins() { - InitFeatures(); DisabledPlugins.ForEach(Load); // make sure they get loaded into memory so their metadata and stuff can be read more easily var list = new List(); diff --git a/IPA.Loader/Loader/PluginManager.cs b/IPA.Loader/Loader/PluginManager.cs index c8af2db6..28f117f0 100644 --- a/IPA.Loader/Loader/PluginManager.cs +++ b/IPA.Loader/Loader/PluginManager.cs @@ -14,6 +14,7 @@ using UnityEngine; using Logger = IPA.Logging.Logger; using System.Threading.Tasks; using IPA.Utilities.Async; +using IPA.Loader.Features; #if NET4 using TaskEx = System.Threading.Tasks.Task; using TaskEx6 = System.Threading.Tasks.Task; @@ -189,16 +190,33 @@ namespace IPA.Loader { if (alreadyDisabled.TryGetValue(exec.Executor, out var task)) return task; - else + else { if (exec.Executor.Metadata.RuntimeOptions != RuntimeOptions.DynamicInit) return TaskEx6.FromException(new CannotRuntimeDisableException(exec.Executor.Metadata)); var res = TaskEx.WhenAll(exec.Dependents.Select(d => Disable(d, alreadyDisabled))) - .ContinueWith(t => t.IsFaulted - ? TaskEx.WhenAll(t, TaskEx6.FromException( - new CannotRuntimeDisableException(exec.Executor.Metadata, "Dependents cannot be disabled for plugin"))) - : exec.Executor.Disable(), UnityMainThreadTaskScheduler.Default).Unwrap(); + .ContinueWith(t => + { + if (t.IsFaulted) { + return TaskEx.WhenAll(t, TaskEx6.FromException( + new CannotRuntimeDisableException(exec.Executor.Metadata, "Dependents cannot be disabled for plugin"))); + } + return exec.Executor.Disable() + .ContinueWith(t => + { + foreach (var feature in exec.Executor.Metadata.Features) + { + try { + feature.AfterDisable(exec.Executor.Metadata); + } + catch (Exception e) + { + Logger.loader.Critical($"Feature errored in {nameof(Feature.AfterDisable)}: {e}"); + } + } + }, UnityMainThreadTaskScheduler.Default); + }, UnityMainThreadTaskScheduler.Default).Unwrap(); // We do not want to call the disable method if a dependent couldn't be disabled // By scheduling on a UnityMainThreadScheduler, we ensure that Disable() is always called on the Unity main thread alreadyDisabled.Add(exec.Executor, res); diff --git a/IPA.Loader/Loader/PluginManifest.cs b/IPA.Loader/Loader/PluginManifest.cs index f36f6e9d..405bf488 100644 --- a/IPA.Loader/Loader/PluginManifest.cs +++ b/IPA.Loader/Loader/PluginManifest.cs @@ -1,6 +1,7 @@ using IPA.JsonConverters; using IPA.Utilities; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using SemVer; using System; using System.Collections.Generic; @@ -40,7 +41,7 @@ namespace IPA.Loader public Dictionary Conflicts = new Dictionary(); [JsonProperty("features", Required = Required.DisallowNull)] - public string[] Features = Array.Empty(); + public Dictionary Features = new Dictionary(); [JsonProperty("loadBefore", Required = Required.DisallowNull)] public string[] LoadBefore = Array.Empty(); diff --git a/IPA.Loader/Loader/PluginMetadata.cs b/IPA.Loader/Loader/PluginMetadata.cs index 22e83805..5ff7b92b 100644 --- a/IPA.Loader/Loader/PluginMetadata.cs +++ b/IPA.Loader/Loader/PluginMetadata.cs @@ -76,6 +76,10 @@ namespace IPA.Loader internal readonly List InternalFeatures = new List(); + internal readonly HashSet UnloadedFeatures = new HashSet(); + + internal readonly List CreateFeaturesWhenLoaded = new List(); + /// /// A list of files (that aren't ) that are associated with this plugin. /// diff --git a/IPA.Loader/Loader/manifest.json b/IPA.Loader/Loader/manifest.json index a38fc719..60b78874 100644 --- a/IPA.Loader/Loader/manifest.json +++ b/IPA.Loader/Loader/manifest.json @@ -10,14 +10,12 @@ "name": "Beat Saber IPA", "version": "4.0.6", "icon": "IPA.icon_white.png", - "features": [ - "define-feature(print, IPA.Loader.Features.PrintFeature)", - "define-feature(debug, IPA.Loader.Features.DebugFeature)", - "define-feature(warn, IPA.Loader.Features.WarnFeature)", - "define-feature(no-update, IPA.Loader.Features.NoUpdateFeature)", - "define-feature(init-injector, IPA.Loader.Features.InitInjectorFeature)", - "define-feature(config-provider, IPA.Loader.Features.ConfigProviderFeature)" - ], + "features": { + "IPA.DefineFeature": { + "type": "IPA.Loader.Features.ConfigProviderFeature", + "name": "IPA.ConfigProvider" + } + }, "links": { "project-home": "https://beat-saber-modding-group.github.io/BeatSaber-IPA-Reloaded/index.html", "project-source": "https://github.com/beat-saber-modding-group/BeatSaber-IPA-Reloaded", diff --git a/IPA.Loader/Updating/BeatMods/Updater.cs b/IPA.Loader/Updating/BeatMods/Updater.cs index 200c8d0c..33b4596c 100644 --- a/IPA.Loader/Updating/BeatMods/Updater.cs +++ b/IPA.Loader/Updating/BeatMods/Updater.cs @@ -204,11 +204,6 @@ namespace IPA.Updating.BeatMods LocalPluginMeta = msinfo }; - if (msinfo.Features.FirstOrDefault(f => f is NoUpdateFeature) != null) - { // disable updating, by only matching self, so that dependencies can still be resolved - dep.Requirement = new Range(msinfo.Version.ToString()); - } - depList.Value.Add(dep); } } @@ -225,11 +220,6 @@ namespace IPA.Updating.BeatMods LocalPluginMeta = meta }; - if (meta.Features.FirstOrDefault(f => f is NoUpdateFeature) != null) - { // disable updating, by only matching self - dep.Requirement = new Range(meta.Version.ToString()); - } - depList.Value.Add(dep); } } @@ -246,11 +236,6 @@ namespace IPA.Updating.BeatMods LocalPluginMeta = meta }; - if (meta.Features.FirstOrDefault(f => f is NoUpdateFeature) != null) - { // disable updating, by only matching self - dep.Requirement = new Range(meta.Version.ToString()); - } - depList.Value.Add(dep); } }