diff --git a/BSIPA-ModList/UI/ModListFlowCoordinator.cs b/BSIPA-ModList/UI/ModListFlowCoordinator.cs index e05cac0b..5459173b 100644 --- a/BSIPA-ModList/UI/ModListFlowCoordinator.cs +++ b/BSIPA-ModList/UI/ModListFlowCoordinator.cs @@ -28,7 +28,7 @@ namespace BSIPA_ModList.UI navigationController.didFinishEvent += backButton_DidFinish; modList = BeatSaberUI.CreateViewController(); - modList.Init(this, PluginManager.AllPlugins, PluginLoader.ignoredPlugins, PluginManager.Plugins); + modList.Init(this, PluginManager.AllPlugins.Select(p => p.Metadata).Concat(PluginManager.DisabledPlugins), PluginLoader.ignoredPlugins, PluginManager.Plugins); settings = SettingsViewController.Create(); diff --git a/BSIPA-ModList/UI/ViewControllers/ModCells.cs b/BSIPA-ModList/UI/ViewControllers/ModCells.cs index 54741014..509b2b1f 100644 --- a/BSIPA-ModList/UI/ViewControllers/ModCells.cs +++ b/BSIPA-ModList/UI/ViewControllers/ModCells.cs @@ -19,11 +19,11 @@ namespace BSIPA_ModList.UI.ViewControllers internal class BSIPAModCell : CustomCellInfo, IClickableCell { - internal PluginLoader.PluginInfo Plugin; + internal PluginLoader.PluginMetadata Plugin; private ModListController list; - public BSIPAModCell(ModListController list, PluginLoader.PluginInfo plugin) - : base($"{plugin.Metadata.Name} v{plugin.Metadata.Version}", plugin.Metadata.Manifest.Author, null) + public BSIPAModCell(ModListController list, PluginLoader.PluginMetadata plugin) + : base($"{plugin.Name} v{plugin.Version}", plugin.Manifest.Author, null) { Plugin = plugin; this.list = list; @@ -31,24 +31,24 @@ namespace BSIPA_ModList.UI.ViewControllers if (string.IsNullOrWhiteSpace(subtext)) subtext = "Unspecified Author"; - icon = plugin.Metadata.GetIcon(); + icon = plugin.GetIcon(); } private ModInfoViewController infoView; public void OnSelect(ModListController cntrl) { - Logger.log.Debug($"Selected BSIPAModCell {Plugin.Metadata.Name} {Plugin.Metadata.Version}"); + Logger.log.Debug($"Selected BSIPAModCell {Plugin.Name} {Plugin.Version}"); if (infoView == null) { - var desc = Plugin.Metadata.Manifest.Description; + var desc = Plugin.Manifest.Description; if (string.IsNullOrWhiteSpace(desc)) desc = "No description"; infoView = BeatSaberUI.CreateViewController(); - infoView.Init(icon, Plugin.Metadata.Name, "v" + Plugin.Metadata.Version.ToString(), subtext, - desc, Plugin.Metadata, Plugin.Metadata.Manifest.Links); + infoView.Init(icon, Plugin.Name, "v" + Plugin.Version.ToString(), subtext, + desc, Plugin, Plugin.Manifest.Links); } list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); @@ -99,11 +99,11 @@ namespace BSIPA_ModList.UI.ViewControllers } internal class LibraryModCell : CustomCellInfo, IClickableCell { - internal PluginLoader.PluginInfo Plugin; + internal PluginLoader.PluginMetadata Plugin; private ModListController list; - public LibraryModCell(ModListController list, PluginLoader.PluginInfo plugin) - : base($"{plugin.Metadata.Name} v{plugin.Metadata.Version}", plugin.Metadata.Manifest.Author, null) + public LibraryModCell(ModListController list, PluginLoader.PluginMetadata plugin) + : base($"{plugin.Name} v{plugin.Version}", plugin.Manifest.Author, null) { Plugin = plugin; this.list = list; @@ -118,17 +118,17 @@ namespace BSIPA_ModList.UI.ViewControllers public void OnSelect(ModListController cntrl) { - Logger.log.Debug($"Selected LibraryModCell {Plugin.Metadata.Name} {Plugin.Metadata.Version}"); + Logger.log.Debug($"Selected LibraryModCell {Plugin.Name} {Plugin.Version}"); if (infoView == null) { - var desc = Plugin.Metadata.Manifest.Description; + var desc = Plugin.Manifest.Description; if (string.IsNullOrWhiteSpace(desc)) desc = "No description"; infoView = BeatSaberUI.CreateViewController(); - infoView.Init(icon, Plugin.Metadata.Name, "v" + Plugin.Metadata.Version.ToString(), subtext, - desc, Plugin.Metadata, Plugin.Metadata.Manifest.Links); + infoView.Init(icon, Plugin.Name, "v" + Plugin.Version.ToString(), subtext, + desc, Plugin, Plugin.Manifest.Links); } list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); diff --git a/BSIPA-ModList/UI/ViewControllers/ModListController.cs b/BSIPA-ModList/UI/ViewControllers/ModListController.cs index 8277f049..015c895a 100644 --- a/BSIPA-ModList/UI/ViewControllers/ModListController.cs +++ b/BSIPA-ModList/UI/ViewControllers/ModListController.cs @@ -28,8 +28,10 @@ namespace BSIPA_ModList.UI internal ModListFlowCoordinator flow; #pragma warning disable CS0618 - public void Init(ModListFlowCoordinator flow, IEnumerable bsipaPlugins, IEnumerable ignoredPlugins, IEnumerable ipaPlugins) + public void Init(ModListFlowCoordinator flow, IEnumerable bsipaPlugins, IEnumerable ignoredPlugins, IEnumerable ipaPlugins) { + Data.Clear(); + Logger.log.Debug("List Controller Init"); DidActivateEvent = DidActivate; @@ -43,11 +45,11 @@ namespace BSIPA_ModList.UI reuseIdentifier = "BSIPAModListTableCell"; - foreach (var plugin in bsipaPlugins.Where(p => !p.Metadata.IsBare)) + foreach (var plugin in bsipaPlugins.Where(p => !p.IsBare)) Data.Add(new BSIPAModCell(this, plugin)); foreach (var plugin in ignoredPlugins) Data.Add(new BSIPAIgnoredModCell(this, plugin)); - foreach (var plugin in bsipaPlugins.Where(p => p.Metadata.IsBare)) + foreach (var plugin in bsipaPlugins.Where(p => p.IsBare)) Data.Add(new LibraryModCell(this, plugin)); foreach (var plugin in ipaPlugins) Data.Add(new IPAModCell(this, plugin)); diff --git a/BSIPA-ModList/manifest.json b/BSIPA-ModList/manifest.json index 94685593..04c63aa6 100644 --- a/BSIPA-ModList/manifest.json +++ b/BSIPA-ModList/manifest.json @@ -14,7 +14,7 @@ "version": "1.2.0", "icon": "BSIPA_ModList.Icons.self.png", "dependsOn": { - "BSIPA": "^3.12.16", + "BSIPA": "^3.12.18", "CustomUI": "^1.5.4" }, "features": [], diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index 34ca9950..ca31f518 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -108,6 +108,7 @@ namespace IPA.Loader } internal static List PluginsMetadata = new List(); + internal static List DisabledPlugins = new List(); private static readonly Regex embeddedTextDescriptionPattern = new Regex(@"#!\[(.+)\]", RegexOptions.Compiled | RegexOptions.Singleline); @@ -363,7 +364,7 @@ namespace IPA.Loader foreach (var meta in PluginsMetadata) { if (disabled.Contains(meta.Id ?? meta.Name)) - ignoredPlugins.Add(meta); + DisabledPlugins.Add(meta); else enabled.Add(meta); } @@ -519,10 +520,23 @@ namespace IPA.Loader } } - internal static void ReleaseAll() + internal static void ReleaseAll(bool full = false) { - ignoredPlugins = new HashSet(); + if (full) + ignoredPlugins = new HashSet(); + else + { + foreach (var m in PluginsMetadata) + ignoredPlugins.Add(m); + foreach (var m in ignoredPlugins) + { // clean them up so we can still use the metadata for updates + m.InternalFeatures.Clear(); + m.PluginType = null; + m.Assembly = null; + } + } PluginsMetadata = new List(); + DisabledPlugins = new List(); Feature.Reset(); GC.Collect(); } @@ -590,6 +604,17 @@ namespace IPA.Loader { Logger.loader.Critical($"Feature errored in {nameof(Feature.AfterInit)}: {e}"); } + + if (instance is IDisablablePlugin disable) + try + { + disable.OnEnable(); + } + catch (Exception e) + { + Logger.loader.Error($"Error occurred trying to enable {meta.Name}"); + Logger.loader.Error(e); + } } catch (AmbiguousMatchException) { diff --git a/IPA.Loader/Loader/PluginManager.cs b/IPA.Loader/Loader/PluginManager.cs index 84fb14c1..b911abd6 100644 --- a/IPA.Loader/Loader/PluginManager.cs +++ b/IPA.Loader/Loader/PluginManager.cs @@ -59,36 +59,114 @@ namespace IPA.Loader } /// - /// Disables a plugin for the next time the game starts. + /// Gets a disabled plugin's metadata by its name. /// - /// the plugin to disable - public static void DisablePlugin(PluginInfo plugin) => - DisablePlugin(plugin.Metadata.Id ?? plugin.Metadata.Name); + /// the name of the disabled plugin to get + /// the metadata for the corresponding plugin + public static PluginMetadata GetDisabledPlugin(string name) => + DisabledPlugins.FirstOrDefault(p => p.Name == name); /// - /// Disables a plugin for the next time the game starts. + /// Gets a disabled plugin's metadata by its ID. /// - /// the ID, or name if the ID is null, of the plugin to disable - public static void DisablePlugin(string pluginId) + /// the ID of the disabled plugin to get + /// the metadata for the corresponding plugin + public static PluginMetadata GetDisabledPluginFromId(string name) => + DisabledPlugins.FirstOrDefault(p => p.Id == name); + + /// + /// Disables a plugin. + /// + /// the plugin to disable + /// whether or not it needs a restart to enable + public static bool DisablePlugin(PluginInfo plugin) { - DisabledConfig.Ref.Value.DisabledModIds.Add(pluginId); + if (plugin == null) return false; + + DisabledConfig.Ref.Value.DisabledModIds.Add(plugin.Metadata.Id ?? plugin.Metadata.Name); + DisabledConfig.Provider.Store(DisabledConfig.Ref.Value); + + if (plugin.Plugin is IDisablablePlugin disable) + { + try + { + disable.OnDisable(); + } + catch (Exception e) + { + Logger.loader.Error($"Error occurred trying to disable {plugin.Metadata.Name}"); + Logger.loader.Error(e); + } + + runtimeDisabled.Add(plugin); + _bsPlugins.Remove(plugin); + + return false; + } + + return true; } + /// + /// Disables a plugin. + /// + /// the ID, or name if the ID is null, of the plugin to disable + /// whether a restart is needed to activate + public static bool DisablePlugin(string pluginId) => DisablePlugin(GetPluginFromId(pluginId) ?? GetPlugin(pluginId)); + /// /// Enables a plugin that had been previously disabled. /// /// the plugin to enable - public static void EnablePlugin(PluginMetadata plugin) => - EnablePlugin(plugin.Id ?? plugin.Name); + /// whether a restart is needed to activate + public static bool EnablePlugin(PluginMetadata plugin) + { + if (plugin == null) return false; + + DisabledConfig.Ref.Value.DisabledModIds.Remove(plugin.Id ?? plugin.Name); + DisabledConfig.Provider.Store(DisabledConfig.Ref.Value); + + var needsRestart = true; + + var runtimeInfo = runtimeDisabled.FirstOrDefault(p => p.Metadata == plugin); + if (runtimeInfo != null && runtimeInfo.Plugin is IDisablablePlugin disable) + { + runtimeDisabled.Remove(runtimeInfo); + try + { + disable.OnEnable(); + } + catch (Exception e) + { + Logger.loader.Error($"Error occurred trying to enable {plugin.Name}"); + Logger.loader.Error(e); + } + needsRestart = false; + } + else + { + PluginLoader.DisabledPlugins.Remove(plugin); + runtimeInfo = InitPlugin(plugin); + } + + _bsPlugins.Add(runtimeInfo); + + return needsRestart; + } /// /// Enables a plugin that had been previously disabled. /// /// the ID, or name if the ID is null, of the plugin to enable - public static void EnablePlugin(string pluginId) - { - DisabledConfig.Ref.Value.DisabledModIds.Remove(pluginId); - } + /// whether a restart is needed to activate + public static bool EnablePlugin(string pluginId) => + EnablePlugin(GetDisabledPluginFromId(pluginId) ?? GetDisabledPlugin(pluginId)); + + private static readonly List runtimeDisabled = new List(); + /// + /// Gets a list of disabled BSIPA plugins. + /// + public static IEnumerable DisabledPlugins => PluginLoader.DisabledPlugins.Concat(runtimeDisabled.Select(p => p.Metadata)); /// /// Gets a list of all BSIPA plugins. diff --git a/IPA.Loader/PluginInterfaces/BeatSaber/IDisablablePlugin.cs b/IPA.Loader/PluginInterfaces/BeatSaber/IDisablablePlugin.cs index 40fa9734..14c918fe 100644 --- a/IPA.Loader/PluginInterfaces/BeatSaber/IDisablablePlugin.cs +++ b/IPA.Loader/PluginInterfaces/BeatSaber/IDisablablePlugin.cs @@ -6,7 +6,7 @@ public interface IDisablablePlugin { /// - /// Called when a plugin is enabled. This is where you shhould set up Harmony patches and the like. + /// Called when a plugin is enabled. This is where you should set up Harmony patches and the like. /// /// /// This will be called after Init, and will be called when the plugin loads normally too. diff --git a/IPA.Loader/Updating/BeatMods/Updater.cs b/IPA.Loader/Updating/BeatMods/Updater.cs index f347975a..cee8889e 100644 --- a/IPA.Loader/Updating/BeatMods/Updater.cs +++ b/IPA.Loader/Updating/BeatMods/Updater.cs @@ -205,8 +205,8 @@ namespace IPA.Updating.BeatMods } } - foreach (var meta in PluginLoader.ignoredPlugins.Where(m => m.Id != null)) - { + foreach (var meta in PluginLoader.ignoredPlugins) + { // update ignored if (meta.Id != null) { // updatable var dep = new DependencyObject @@ -230,6 +230,30 @@ namespace IPA.Updating.BeatMods } } + foreach (var meta in DisabledPlugins) + { // update ignored + if (meta.Id != null) + { // updatable + var dep = new DependencyObject + { + Name = meta.Id, + Version = meta.Version, + Requirement = new Range($">={meta.Version}"), + LocalPluginMeta = new PluginLoader.PluginInfo + { + Metadata = meta, + Plugin = null + } + }; + + 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); + } + } #pragma warning disable CS0618 // Type or member is obsolete foreach (var plug in Plugins)