Browse Source

Added better support for enabling and disabling mods

pull/12/head 3.12.19
Anairkoen Schno 5 years ago
parent
commit
ac81be1e15
13 changed files with 344 additions and 47 deletions
  1. +0
    -2
      BSIPA-ModList/Plugin.cs
  2. +2
    -2
      BSIPA-ModList/Properties/AssemblyInfo.cs
  3. +29
    -2
      BSIPA-ModList/UI/ModListFlowCoordinator.cs
  4. +10
    -3
      BSIPA-ModList/UI/ViewControllers/DownloadProgressViewController.cs
  5. +76
    -5
      BSIPA-ModList/UI/ViewControllers/ModCells.cs
  6. +78
    -17
      BSIPA-ModList/UI/ViewControllers/ModInfoViewController.cs
  7. +12
    -0
      BSIPA-ModList/UI/ViewControllers/ModListController.cs
  8. +1
    -1
      BSIPA-ModList/manifest.json
  9. +18
    -1
      IPA.Loader/Loader/PluginLoader.cs
  10. +115
    -10
      IPA.Loader/Loader/PluginManager.cs
  11. +3
    -4
      IPA.Loader/Updating/BeatMods/Updater.cs
  12. BIN
      Refs/Assembly-CSharp.dll
  13. BIN
      Refs/Unity.TextMeshPro.dll

+ 0
- 2
BSIPA-ModList/Plugin.cs View File

@ -24,8 +24,6 @@ namespace BSIPA_ModList
Logger.log = logger; Logger.log = logger;
IPA.Updating.BeatMods.Updater.ModListPresent = true; IPA.Updating.BeatMods.Updater.ModListPresent = true;
} }
public void OnActiveSceneChanged(Scene prevScene, Scene nextScene) public void OnActiveSceneChanged(Scene prevScene, Scene nextScene)


+ 2
- 2
BSIPA-ModList/Properties/AssemblyInfo.cs View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyVersion("1.2.2.0")]
[assembly: AssemblyFileVersion("1.2.2.0")]

+ 29
- 2
BSIPA-ModList/UI/ModListFlowCoordinator.cs View File

@ -3,9 +3,12 @@ using CustomUI.BeatSaber;
using CustomUI.Utilities; using CustomUI.Utilities;
using IPA.Loader; using IPA.Loader;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using VRUI; using VRUI;
namespace BSIPA_ModList.UI namespace BSIPA_ModList.UI
@ -100,6 +103,8 @@ namespace BSIPA_ModList.UI
PopViewControllerFromNavigationController(navigationController, callback, immediate); PopViewControllerFromNavigationController(navigationController, callback, immediate);
} }
internal HashSet<Action> exitActions = new HashSet<Action>();
private delegate void DismissFlowDel(FlowCoordinator self, FlowCoordinator newF, Action finished, bool immediate); private delegate void DismissFlowDel(FlowCoordinator self, FlowCoordinator newF, Action finished, bool immediate);
private static DismissFlowDel dismissFlow; private static DismissFlowDel dismissFlow;
@ -112,8 +117,30 @@ namespace BSIPA_ModList.UI
dismissFlow = (DismissFlowDel)Delegate.CreateDelegate(typeof(DismissFlowDel), m); dismissFlow = (DismissFlowDel)Delegate.CreateDelegate(typeof(DismissFlowDel), m);
} }
MainFlowCoordinator mainFlow = Resources.FindObjectsOfTypeAll<MainFlowCoordinator>().First();
dismissFlow(mainFlow, this, null, false);
if (exitActions.Count == 0)
{
MainFlowCoordinator mainFlow = Resources.FindObjectsOfTypeAll<MainFlowCoordinator>().First();
dismissFlow(mainFlow, this, null, false);
}
else
{
var actions = exitActions;
UnityAction<Scene, LoadSceneMode> releaseAction = null;
releaseAction = (a, b) => SceneManager_sceneLoaded(actions, releaseAction, a, b);
SceneManager.sceneLoaded += releaseAction;
Resources.FindObjectsOfTypeAll<MenuTransitionsHelperSO>().First().RestartGame(true);
}
}
private static void SceneManager_sceneLoaded(HashSet<Action> actions, UnityAction<Scene, LoadSceneMode> self, Scene arg0, LoadSceneMode arg1)
{
if (arg0.name == "Init")
{
SceneManager.sceneLoaded -= self;
foreach (var act in actions)
act();
}
} }
} }
} }

+ 10
- 3
BSIPA-ModList/UI/ViewControllers/DownloadProgressViewController.cs View File

@ -18,6 +18,7 @@ namespace BSIPA_ModList.UI.ViewControllers
internal class DownloadProgressViewController : VRUIViewController, TableView.IDataSource internal class DownloadProgressViewController : VRUIViewController, TableView.IDataSource
{ {
private TextMeshProUGUI _titleText; private TextMeshProUGUI _titleText;
private TextMeshProUGUI _manualDownloadText;
private Button _checkForUpdates; private Button _checkForUpdates;
private Button _downloadUpdates; private Button _downloadUpdates;
@ -44,6 +45,11 @@ namespace BSIPA_ModList.UI.ViewControllers
_titleText.alignment = TextAlignmentOptions.Top; _titleText.alignment = TextAlignmentOptions.Top;
_titleText.fontSize = 6f; _titleText.fontSize = 6f;
_manualDownloadText = BeatSaberUI.CreateText(rectTransform, "Manual Restart Required", new Vector2(37f, -3f));
_manualDownloadText.alignment = TextAlignmentOptions.Top;
_manualDownloadText.fontSize = 4f;
_manualDownloadText.gameObject.SetActive(false);
_pageUpButton = Instantiate(Resources.FindObjectsOfTypeAll<Button>().Last(x => (x.name == "PageUpButton")), rectTransform, false); _pageUpButton = Instantiate(Resources.FindObjectsOfTypeAll<Button>().Last(x => (x.name == "PageUpButton")), rectTransform, false);
(_pageUpButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 1f); (_pageUpButton.transform as RectTransform).anchorMin = new Vector2(0.5f, 1f);
(_pageUpButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 1f); (_pageUpButton.transform as RectTransform).anchorMax = new Vector2(0.5f, 1f);
@ -131,9 +137,10 @@ namespace BSIPA_ModList.UI.ViewControllers
private void DownloaderStateChanged() private void DownloaderStateChanged()
{ {
_checkForUpdates.interactable = DownloadController.Instance.CanCheck || DownloadController.Instance.CanReset;
_downloadUpdates.interactable = DownloadController.Instance.CanDownload;
_restartGame.interactable = DownloadController.Instance.HadUpdates;
_checkForUpdates.interactable = (DownloadController.Instance.CanCheck || DownloadController.Instance.CanReset) && !Updater.NeedsManualRestart;
_downloadUpdates.interactable = DownloadController.Instance.CanDownload && !Updater.NeedsManualRestart;
_restartGame.interactable = DownloadController.Instance.HadUpdates && !Updater.NeedsManualRestart;
_manualDownloadText.gameObject.SetActive(Updater.NeedsManualRestart);
} }
protected override void DidDeactivate(DeactivationType type) protected override void DidDeactivate(DeactivationType type)


+ 76
- 5
BSIPA-ModList/UI/ViewControllers/ModCells.cs View File

@ -15,6 +15,7 @@ namespace BSIPA_ModList.UI.ViewControllers
internal interface IClickableCell internal interface IClickableCell
{ {
void OnSelect(ModListController cntrl); void OnSelect(ModListController cntrl);
void Update();
} }
internal class BSIPAModCell : CustomCellInfo, IClickableCell internal class BSIPAModCell : CustomCellInfo, IClickableCell
@ -22,16 +23,47 @@ namespace BSIPA_ModList.UI.ViewControllers
internal PluginLoader.PluginMetadata Plugin; internal PluginLoader.PluginMetadata Plugin;
private ModListController list; private ModListController list;
private PluginManager.PluginEnableDelegate enableDel;
private PluginManager.PluginDisableDelegate disableDel;
public BSIPAModCell(ModListController list, PluginLoader.PluginMetadata plugin) public BSIPAModCell(ModListController list, PluginLoader.PluginMetadata plugin)
: base($"{plugin.Name} <size=60%>v{plugin.Version}", plugin.Manifest.Author, null)
: base("", "", null)
{ {
Plugin = plugin; Plugin = plugin;
this.list = list; this.list = list;
if (string.IsNullOrWhiteSpace(subtext))
subtext = "<color=#BFBFBF><i>Unspecified Author</i>";
var thisWeakRef = new WeakReference<BSIPAModCell>(this);
PluginManager.PluginDisableDelegate reflessDDel = null;
reflessDDel = disableDel = (p, r) => PluginManager_PluginDisabled(p, r, thisWeakRef, reflessDDel); // some indirection to make it a weak link for GC
PluginManager.PluginDisabled += reflessDDel;
PluginManager.PluginEnableDelegate reflessEDel = null;
reflessEDel = enableDel = (p, r) => PluginManager_PluginEnabled(p, r, thisWeakRef, reflessEDel); // some indirection to make it a weak link for GC
PluginManager.PluginEnabled += reflessEDel;
icon = plugin.GetIcon();
Update(propogate: false);
}
private static void PluginManager_PluginEnabled(PluginLoader.PluginInfo plugin, bool needsRestart, WeakReference<BSIPAModCell> _self, PluginManager.PluginEnableDelegate ownDel)
{
if (!_self.TryGetTarget(out var self))
{
PluginManager.PluginEnabled -= ownDel;
return;
}
if (plugin.Metadata != self.Plugin) return;
self.Update(true, needsRestart);
}
private static void PluginManager_PluginDisabled(PluginLoader.PluginMetadata plugin, bool needsRestart, WeakReference<BSIPAModCell> _self, PluginManager.PluginDisableDelegate ownDel)
{
if (!_self.TryGetTarget(out var self))
PluginManager.PluginDisabled -= ownDel;
if (plugin != self.Plugin) return;
self.Update(false, needsRestart);
} }
private ModInfoViewController infoView; private ModInfoViewController infoView;
@ -48,11 +80,38 @@ namespace BSIPA_ModList.UI.ViewControllers
infoView = BeatSaberUI.CreateViewController<ModInfoViewController>(); infoView = BeatSaberUI.CreateViewController<ModInfoViewController>();
infoView.Init(icon, Plugin.Name, "v" + Plugin.Version.ToString(), subtext, infoView.Init(icon, Plugin.Name, "v" + Plugin.Version.ToString(), subtext,
desc, Plugin, Plugin.Manifest.Links);
desc, Plugin, Plugin.Manifest.Links, true, list.flow);
} }
list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); list.flow.SetSelected(infoView, immediate: list.flow.HasSelected);
} }
void IClickableCell.Update() => Update(null, false, false);
public void Update(bool? _enabled = null, bool needsRestart = false, bool propogate = true)
{
text = $"{Plugin.Name} <size=60%>v{Plugin.Version}";
subtext = Plugin.Manifest.Author;
if (string.IsNullOrWhiteSpace(subtext))
subtext = "<color=#BFBFBF><i>Unspecified Author</i>";
var enabled = _enabled ?? !PluginManager.IsDisabled(Plugin);
if (!enabled)
subtext += " <color=#C2C2C2>- <i>Disabled</i>";
if (needsRestart)
subtext += " <i>(Restart to apply)</i>";
icon = Plugin.GetIcon();
var desc = Plugin.Manifest.Description;
if (string.IsNullOrWhiteSpace(desc))
desc = "*No description*";
infoView?.Reload(Plugin.Name, "v" + Plugin.Version.ToString(), subtext, desc);
if (propogate)
list.Reload();
}
} }
internal class BSIPAIgnoredModCell : CustomCellInfo, IClickableCell internal class BSIPAIgnoredModCell : CustomCellInfo, IClickableCell
@ -96,6 +155,10 @@ namespace BSIPA_ModList.UI.ViewControllers
list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); list.flow.SetSelected(infoView, immediate: list.flow.HasSelected);
} }
public void Update()
{
}
} }
internal class LibraryModCell : CustomCellInfo, IClickableCell internal class LibraryModCell : CustomCellInfo, IClickableCell
{ {
@ -133,6 +196,10 @@ namespace BSIPA_ModList.UI.ViewControllers
list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); list.flow.SetSelected(infoView, immediate: list.flow.HasSelected);
} }
public void Update()
{
}
} }
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -181,6 +248,10 @@ namespace BSIPA_ModList.UI.ViewControllers
list.flow.SetSelected(infoView, immediate: list.flow.HasSelected); list.flow.SetSelected(infoView, immediate: list.flow.HasSelected);
} }
public void Update()
{
}
} }
#pragma warning restore #pragma warning restore


+ 78
- 17
BSIPA-ModList/UI/ViewControllers/ModInfoViewController.cs View File

@ -35,8 +35,17 @@ namespace BSIPA_ModList.UI
private Button linkSourceButton; private Button linkSourceButton;
private Button linkDonateButton; private Button linkDonateButton;
public void Init(Sprite icon, string name, string version, string author, string description, PluginLoader.PluginMetadata updateInfo, PluginManifest.LinksObject links = null)
private ModListFlowCoordinator flowController;
private bool showEnableDisable = false;
private TextMeshProUGUI restartMessage;
private Button enableDisableButton;
private new bool enabled = false;
public void Init(Sprite icon, string name, string version, string author, string description, PluginLoader.PluginMetadata updateInfo, PluginManifest.LinksObject links = null, bool showEnDis = false, ModListFlowCoordinator mlfc = null)
{ {
showEnableDisable = showEnDis;
Icon = icon; Icon = icon;
Name = name; Name = name;
Version = version; Version = version;
@ -44,6 +53,10 @@ namespace BSIPA_ModList.UI
Description = description; Description = description;
UpdateInfo = updateInfo; UpdateInfo = updateInfo;
enabled = !PluginManager.IsDisabled(updateInfo);
flowController = mlfc;
if (rowTransformOriginal == null) if (rowTransformOriginal == null)
rowTransformOriginal = MenuButtonUI.Instance.GetPrivateField<RectTransform>("menuButtonsOriginal"); rowTransformOriginal = MenuButtonUI.Instance.GetPrivateField<RectTransform>("menuButtonsOriginal");
@ -63,9 +76,54 @@ namespace BSIPA_ModList.UI
view.Init(this); view.Init(this);
go.SetActive(true); go.SetActive(true);
if (showEnDis)
{
restartMessage = BeatSaberUI.CreateText(rectTransform, "A restart is required to apply", new Vector2(11f, 33.5f));
restartMessage.fontSize = 4f;
restartMessage.gameObject.SetActive(false);
enableDisableButton = BeatSaberUI.CreateUIButton(rectTransform, "CreditsButton", new Vector2(33, 32), new Vector2(25, 10), ToggleEnable);
enableDisableButton.GetComponentInChildren<StartMiddleEndButtonBackgroundController>().SetMiddleSprite();
UpdateButtonText();
}
SetupLinks(links); SetupLinks(links);
} }
private Action setAction = () => { };
private void ToggleEnable()
{
var info = enabled ? PluginManager.InfoFromMetadata(UpdateInfo) : null;
bool needsRestart;
if (enabled)
needsRestart = PluginManager.DisablePlugin(info);
else
needsRestart = PluginManager.EnablePlugin(UpdateInfo);
UpdateButtonText(!enabled, needsRestart);
if (!needsRestart)
flowController.exitActions.Add(setAction);
}
private void UpdateButtonText(bool? _enabled = null, bool _needsRestart = false)
{
enabled = _enabled ?? enabled;
if (enabled)
enableDisableButton.SetButtonText("Disable");
else
enableDisableButton.SetButtonText("Enable");
restartMessage.gameObject.SetActive(_needsRestart);
}
public void Reload(string name, string version, string author, string description)
{
Name = name;
Version = version;
Author = author;
Description = description;
view.Refresh();
}
private void SetupLinks(PluginManifest.LinksObject links = null, Uri moreInfoLink = null) private void SetupLinks(PluginManifest.LinksObject links = null, Uri moreInfoLink = null)
{ {
bool addedLink = false; bool addedLink = false;
@ -88,30 +146,26 @@ namespace BSIPA_ModList.UI
if (links?.ProjectHome != null) if (links?.ProjectHome != null)
{ {
linkHomeButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Home", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
linkHomeButton = BeatSaberUI.CreateUIButton(rowTransform, "CreditsButton", buttonText: "Home", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
onClick: () => Process.Start(links.ProjectHome.ToString())); onClick: () => Process.Start(links.ProjectHome.ToString()));
linkHomeButton.GetComponentInChildren<HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
addedLink = true; addedLink = true;
} }
if (links?.ProjectSource != null) if (links?.ProjectSource != null)
{ {
linkSourceButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Source", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
linkSourceButton = BeatSaberUI.CreateUIButton(rowTransform, "CreditsButton", buttonText: "Source", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
onClick: () => Process.Start(links.ProjectSource.ToString())); onClick: () => Process.Start(links.ProjectSource.ToString()));
linkSourceButton.GetComponentInChildren<HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
addedLink = true; addedLink = true;
} }
if (links?.Donate != null) if (links?.Donate != null)
{ {
linkDonateButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Donate", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
linkDonateButton = BeatSaberUI.CreateUIButton(rowTransform, "CreditsButton", buttonText: "Donate", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
onClick: () => Process.Start(links.Donate.ToString())); onClick: () => Process.Start(links.Donate.ToString()));
linkDonateButton.GetComponentInChildren<HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
addedLink = true; addedLink = true;
} }
if (moreInfoLink != null) if (moreInfoLink != null)
{ {
linkDonateButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "More Info", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
linkDonateButton = BeatSaberUI.CreateUIButton(rowTransform, "CreditsButton", buttonText: "More Info", anchoredPosition: Vector2.zero, sizeDelta: new Vector2(20, 10),
onClick: () => Process.Start(moreInfoLink.ToString())); onClick: () => Process.Start(moreInfoLink.ToString()));
linkDonateButton.GetComponentInChildren<HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
addedLink = true; addedLink = true;
} }
@ -174,29 +228,28 @@ namespace BSIPA_ModList.UI
internal class ModInfoView : MonoBehaviour internal class ModInfoView : MonoBehaviour
{ {
private MarkdownView mdv;
private TextMeshProUGUI titleText; private TextMeshProUGUI titleText;
private TextMeshProUGUI authorText; private TextMeshProUGUI authorText;
private Image icon; private Image icon;
private ModInfoViewController controller;
private const string TitleFormat = "{0} <size=60%>{1}";
public void Init(ModInfoViewController controller) public void Init(ModInfoViewController controller)
{ {
this.controller = controller;
var rectTransform = transform as RectTransform; var rectTransform = transform as RectTransform;
rectTransform.sizeDelta = new Vector2(60f, 10f); rectTransform.sizeDelta = new Vector2(60f, 10f);
titleText = BeatSaberUI.CreateText(rectTransform, $"{controller.Name} <size=60%>{controller.Version}", new Vector2(11f, 27.5f));
titleText = BeatSaberUI.CreateText(rectTransform, string.Format(TitleFormat, controller.Name, controller.Version), new Vector2(11f, 27.5f));
titleText.fontSize = 6f; titleText.fontSize = 6f;
authorText = BeatSaberUI.CreateText(rectTransform, controller.Author, new Vector2(11f, 22f)); authorText = BeatSaberUI.CreateText(rectTransform, controller.Author, new Vector2(11f, 22f));
authorText.fontSize = 4.5f; authorText.fontSize = 4.5f;
/*
descText = BeatSaberUI.CreateText(rectTransform, controller.Description, new Vector2(-4.5f, 12f));
descText.fontSize = 3.5f;
descText.enableWordWrapping = true;
descText.overflowMode = TextOverflowModes.ScrollRect;*/
var mdvgo = new GameObject("MarkDown Desc"); var mdvgo = new GameObject("MarkDown Desc");
mdvgo.SetActive(false); mdvgo.SetActive(false);
var mdv = mdvgo.AddComponent<MarkdownView>();
mdv = mdvgo.AddComponent<MarkdownView>();
mdv.rectTransform.SetParent(rectTransform); mdv.rectTransform.SetParent(rectTransform);
mdv.rectTransform.anchorMin = new Vector2(.5f, .5f); mdv.rectTransform.anchorMin = new Vector2(.5f, .5f);
mdv.rectTransform.anchorMax = new Vector2(.5f, .5f); mdv.rectTransform.anchorMax = new Vector2(.5f, .5f);
@ -219,6 +272,14 @@ namespace BSIPA_ModList.UI
icon.gameObject.SetActive(true); icon.gameObject.SetActive(true);
} }
public void Refresh()
{
titleText.text = string.Format(TitleFormat, controller.Name, controller.Version);
authorText.text = controller.Author;
mdv.Markdown = controller.Description;
icon.sprite = controller.Icon;
}
#if DEBUG #if DEBUG
#if ADJUST_INFO_TEXT_UI_KEYS #if ADJUST_INFO_TEXT_UI_KEYS
private int currentItem = 0; private int currentItem = 0;


+ 12
- 0
BSIPA-ModList/UI/ViewControllers/ModListController.cs View File

@ -59,6 +59,18 @@ namespace BSIPA_ModList.UI
#pragma warning restore #pragma warning restore
public void Reload()
{
var cells = _customListTableView.GetPrivateField<List<TableCell>>("_visibleCells");
foreach (var c in cells)
{
c.gameObject.SetActive(false);
_customListTableView.AddCellToReusableCells(c);
}
cells.Clear();
_customListTableView.RefreshCells(true);
}
private void DidSelectRow(TableView view, int index) private void DidSelectRow(TableView view, int index)
{ {
Debug.Assert(ReferenceEquals(view.dataSource, this)); Debug.Assert(ReferenceEquals(view.dataSource, this));


+ 1
- 1
BSIPA-ModList/manifest.json View File

@ -6,7 +6,7 @@
"Mod List", "Mod List",
"========", "========",
"", "",
"## An in-game interface client for BSIPA.",
"## An in-game interface client for BSIPA."
], ],
"gameVersion": "1.0.0", "gameVersion": "1.0.0",
"id": "BSIPA Mod List", "id": "BSIPA Mod List",


+ 18
- 1
IPA.Loader/Loader/PluginLoader.cs View File

@ -436,9 +436,11 @@ namespace IPA.Loader
var metadata = new List<PluginMetadata>(); var metadata = new List<PluginMetadata>();
var pluginsToLoad = new Dictionary<string, Version>(); var pluginsToLoad = new Dictionary<string, Version>();
var disabledLookup = DisabledPlugins.Where(m => m.Id != null).ToDictionary(m => m.Id, m => m.Version);
foreach (var meta in deTreed) foreach (var meta in deTreed)
{ {
bool load = true; bool load = true;
bool disable = false;
foreach (var dep in meta.Manifest.Dependencies) foreach (var dep in meta.Manifest.Dependencies)
{ {
#if DEBUG #if DEBUG
@ -448,7 +450,16 @@ namespace IPA.Loader
continue; continue;
load = false; load = false;
Logger.loader.Warn($"{meta.Name} is missing dependency {dep.Key}@{dep.Value}");
if (disabledLookup.ContainsKey(dep.Key) && dep.Value.IsSatisfied(disabledLookup[dep.Key]))
{
disable = true;
Logger.loader.Warn($"Dependency {dep.Key} was found, but disabled. Disabling {meta.Name} too.");
}
else
Logger.loader.Warn($"{meta.Name} is missing dependency {dep.Key}@{dep.Value}");
break;
} }
if (load) if (load)
@ -457,6 +468,11 @@ namespace IPA.Loader
if (meta.Id != null) if (meta.Id != null)
pluginsToLoad.Add(meta.Id, meta.Version); pluginsToLoad.Add(meta.Id, meta.Version);
} }
else if (disable)
{
DisabledPlugins.Add(meta);
DisabledConfig.Ref.Value.DisabledModIds.Add(meta.Id ?? meta.Name);
}
else else
ignoredPlugins.Add(meta); ignoredPlugins.Add(meta);
} }
@ -636,6 +652,7 @@ namespace IPA.Loader
internal static List<PluginInfo> LoadPlugins() internal static List<PluginInfo> LoadPlugins()
{ {
InitFeatures(); InitFeatures();
DisabledPlugins.ForEach(Load); // make sure they get loaded into memory so their metadata and stuff can be read more easily
return PluginsMetadata.Select(InitPlugin).Where(p => p != null).ToList(); return PluginsMetadata.Select(InitPlugin).Where(p => p != null).ToList();
} }
} }

+ 115
- 10
IPA.Loader/Loader/PluginManager.cs View File

@ -75,7 +75,7 @@ namespace IPA.Loader
DisabledPlugins.FirstOrDefault(p => p.Id == name); DisabledPlugins.FirstOrDefault(p => p.Id == name);
/// <summary> /// <summary>
/// Disables a plugin.
/// Disables a plugin, and all dependents.
/// </summary> /// </summary>
/// <param name="plugin">the plugin to disable</param> /// <param name="plugin">the plugin to disable</param>
/// <returns>whether or not it needs a restart to enable</returns> /// <returns>whether or not it needs a restart to enable</returns>
@ -83,10 +83,25 @@ namespace IPA.Loader
{ {
if (plugin == null) return false; if (plugin == null) return false;
if (plugin.Metadata.IsBare)
{
Logger.loader.Warn($"Trying to disable bare manifest");
return false;
}
if (IsDisabled(plugin.Metadata)) return false;
var needsRestart = false;
Logger.loader.Info($"Disabling {plugin.Metadata.Name}");
var dependents = BSMetas.Where(m => m.Metadata.Dependencies.Contains(plugin.Metadata)).ToList();
needsRestart = dependents.Aggregate(needsRestart, (b, p) => DisablePlugin(p) || b);
DisabledConfig.Ref.Value.DisabledModIds.Add(plugin.Metadata.Id ?? plugin.Metadata.Name); DisabledConfig.Ref.Value.DisabledModIds.Add(plugin.Metadata.Id ?? plugin.Metadata.Name);
DisabledConfig.Provider.Store(DisabledConfig.Ref.Value); DisabledConfig.Provider.Store(DisabledConfig.Ref.Value);
if (plugin.Plugin is IDisablablePlugin disable)
if (!needsRestart && plugin.Plugin is IDisablablePlugin disable)
{ {
try try
{ {
@ -98,17 +113,30 @@ namespace IPA.Loader
Logger.loader.Error(e); Logger.loader.Error(e);
} }
runtimeDisabled.Add(plugin);
_bsPlugins.Remove(plugin);
if (needsRestart)
Logger.loader.Warn($"Disablable plugin has non-disablable dependents; some things may not work properly");
}
else needsRestart = true;
return false;
runtimeDisabled.Add(plugin);
_bsPlugins.Remove(plugin);
PluginLoader.DisabledPlugins.Add(plugin.Metadata);
try
{
PluginDisabled?.Invoke(plugin.Metadata, needsRestart);
}
catch (Exception e)
{
Logger.loader.Error($"Error occurred invoking disable event for {plugin.Metadata.Name}");
Logger.loader.Error(e);
} }
return true;
return needsRestart;
} }
/// <summary> /// <summary>
/// Disables a plugin.
/// Disables a plugin, and all dependents.
/// </summary> /// </summary>
/// <param name="pluginId">the ID, or name if the ID is null, of the plugin to disable</param> /// <param name="pluginId">the ID, or name if the ID is null, of the plugin to disable</param>
/// <returns>whether a restart is needed to activate</returns> /// <returns>whether a restart is needed to activate</returns>
@ -123,15 +151,26 @@ namespace IPA.Loader
{ {
if (plugin == null) return false; if (plugin == null) return false;
if (plugin.IsBare)
{
Logger.loader.Warn($"Trying to enable bare manifest");
return false;
}
if (IsEnabled(plugin)) return false;
Logger.loader.Info($"Enabling {plugin.Name}");
DisabledConfig.Ref.Value.DisabledModIds.Remove(plugin.Id ?? plugin.Name); DisabledConfig.Ref.Value.DisabledModIds.Remove(plugin.Id ?? plugin.Name);
DisabledConfig.Provider.Store(DisabledConfig.Ref.Value); DisabledConfig.Provider.Store(DisabledConfig.Ref.Value);
var needsRestart = true; var needsRestart = true;
var depsNeedRestart = plugin.Dependencies.Aggregate(false, (b, p) => EnablePlugin(p) || b);
var runtimeInfo = runtimeDisabled.FirstOrDefault(p => p.Metadata == plugin); var runtimeInfo = runtimeDisabled.FirstOrDefault(p => p.Metadata == plugin);
if (runtimeInfo != null && runtimeInfo.Plugin is IDisablablePlugin disable) if (runtimeInfo != null && runtimeInfo.Plugin is IDisablablePlugin disable)
{ {
runtimeDisabled.Remove(runtimeInfo);
try try
{ {
disable.OnEnable(); disable.OnEnable();
@ -146,12 +185,29 @@ namespace IPA.Loader
else else
{ {
PluginLoader.DisabledPlugins.Remove(plugin); PluginLoader.DisabledPlugins.Remove(plugin);
runtimeInfo = InitPlugin(plugin);
if (runtimeInfo == null)
{
runtimeInfo = InitPlugin(plugin);
needsRestart = false;
}
} }
if (runtimeInfo != null)
runtimeDisabled.Remove(runtimeInfo);
_bsPlugins.Add(runtimeInfo); _bsPlugins.Add(runtimeInfo);
return needsRestart;
try
{
PluginEnabled?.Invoke(runtimeInfo, needsRestart || depsNeedRestart);
}
catch (Exception e)
{
Logger.loader.Error($"Error occurred invoking enable event for {plugin.Name}");
Logger.loader.Error(e);
}
return needsRestart || depsNeedRestart;
} }
/// <summary> /// <summary>
@ -162,17 +218,66 @@ namespace IPA.Loader
public static bool EnablePlugin(string pluginId) => public static bool EnablePlugin(string pluginId) =>
EnablePlugin(GetDisabledPluginFromId(pluginId) ?? GetDisabledPlugin(pluginId)); EnablePlugin(GetDisabledPluginFromId(pluginId) ?? GetDisabledPlugin(pluginId));
/// <summary>
/// Checks if a given plugin is disabled.
/// </summary>
/// <param name="meta">the plugin to check</param>
/// <returns><see langword="true"/> if the plugin is disabled, <see langword="false"/> otherwise.</returns>
public static bool IsDisabled(PluginMetadata meta) => DisabledPlugins.Contains(meta);
/// <summary>
/// Checks if a given plugin is enabled.
/// </summary>
/// <param name="meta">the plugin to check</param>
/// <returns><see langword="true"/> if the plugin is enabled, <see langword="false"/> otherwise.</returns>
public static bool IsEnabled(PluginMetadata meta) => !IsDisabled(meta);
private static readonly List<PluginInfo> runtimeDisabled = new List<PluginInfo>(); private static readonly List<PluginInfo> runtimeDisabled = new List<PluginInfo>();
/// <summary> /// <summary>
/// Gets a list of disabled BSIPA plugins. /// Gets a list of disabled BSIPA plugins.
/// </summary> /// </summary>
public static IEnumerable<PluginMetadata> DisabledPlugins => PluginLoader.DisabledPlugins.Concat(runtimeDisabled.Select(p => p.Metadata)); public static IEnumerable<PluginMetadata> DisabledPlugins => PluginLoader.DisabledPlugins.Concat(runtimeDisabled.Select(p => p.Metadata));
/// <summary>
/// An invoker for the <see cref="PluginEnabled"/> event.
/// </summary>
/// <param name="plugin">the plugin that was enabled</param>
/// <param name="needsRestart">whether it needs a restart to take effect</param>
public delegate void PluginEnableDelegate(PluginInfo plugin, bool needsRestart);
/// <summary>
/// An invoker for the <see cref="PluginDisabled"/> event.
/// </summary>
/// <param name="plugin">the plugin that was disabled</param>
/// <param name="needsRestart">whether it needs a restart to take effect</param>
public delegate void PluginDisableDelegate(PluginMetadata plugin, bool needsRestart);
/// <summary>
/// Called whenever a plugin is enabled.
/// </summary>
public static event PluginEnableDelegate PluginEnabled;
/// <summary>
/// Called whenever a plugin is disabled.
/// </summary>
public static event PluginDisableDelegate PluginDisabled;
/// <summary> /// <summary>
/// Gets a list of all BSIPA plugins. /// Gets a list of all BSIPA plugins.
/// </summary> /// </summary>
public static IEnumerable<PluginInfo> AllPlugins => BSMetas; public static IEnumerable<PluginInfo> AllPlugins => BSMetas;
/// <summary>
/// Converts a plugin's metadata to a <see cref="PluginInfo"/>.
/// </summary>
/// <param name="meta">the metadata</param>
/// <returns>the plugin info</returns>
public static PluginInfo InfoFromMetadata(PluginMetadata meta)
{
if (IsDisabled(meta))
return runtimeDisabled.FirstOrDefault(p => p.Metadata == meta);
else
return AllPlugins.FirstOrDefault(p => p.Metadata == meta);
}
/// <summary> /// <summary>
/// An <see cref="IEnumerable"/> of old IPA plugins. /// An <see cref="IEnumerable"/> of old IPA plugins.
/// </summary> /// </summary>


+ 3
- 4
IPA.Loader/Updating/BeatMods/Updater.cs View File

@ -697,10 +697,6 @@ namespace IPA.Updating.BeatMods
} }
catch (Exception) catch (Exception)
{ // something failed; restore { // something failed; restore
/*foreach (var file in newFiles)
file.Delete();
backup.Restore();
backup.Delete();*/
Directory.Delete(targetDir, true); // delete extraction site Directory.Delete(targetDir, true); // delete extraction site
throw; throw;
@ -708,6 +704,7 @@ namespace IPA.Updating.BeatMods
if ((item.LocalPluginMeta?.Metadata.IsSelf).Unwrap()) if ((item.LocalPluginMeta?.Metadata.IsSelf).Unwrap())
{ // currently updating self, so copy to working dir and update { // currently updating self, so copy to working dir and update
NeedsManualRestart = true; // flag so that ModList keeps the restart button hidden
Utils.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(BeatSaber.InstallPath)); Utils.CopyAll(new DirectoryInfo(targetDir), new DirectoryInfo(BeatSaber.InstallPath));
var deleteFile = Path.Combine(BeatSaber.InstallPath, SpecialDeletionsFile); var deleteFile = Path.Combine(BeatSaber.InstallPath, SpecialDeletionsFile);
if (File.Exists(deleteFile)) File.Delete(deleteFile); if (File.Exists(deleteFile)) File.Delete(deleteFile);
@ -726,6 +723,8 @@ namespace IPA.Updating.BeatMods
Logger.updater.Debug("Extractor exited"); Logger.updater.Debug("Extractor exited");
} }
internal static bool NeedsManualRestart = false;
internal const string SpecialDeletionsFile = "$$delete"; internal const string SpecialDeletionsFile = "$$delete";
} }


BIN
Refs/Assembly-CSharp.dll View File


BIN
Refs/Unity.TextMeshPro.dll View File


Loading…
Cancel
Save