diff --git a/BSIPA-ModList/BSIPA-ModList.csproj b/BSIPA-ModList/BSIPA-ModList.csproj
new file mode 100644
index 00000000..9eaa304b
--- /dev/null
+++ b/BSIPA-ModList/BSIPA-ModList.csproj
@@ -0,0 +1,110 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {23AB2621-A05C-4377-8418-85E6012C0BBE}
+ Library
+ Properties
+ BSIPA_ModList
+ BSIPA-ModList
+ v4.6.1
+ 512
+ true
+
+
+ true
+ portable
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ portable
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\Refs\Assembly-CSharp.dll
+ False
+
+
+ ..\Refs\BeatSaberCustomUI.dll
+ False
+
+
+
+
+
+
+
+
+
+
+ ..\Refs\UnityEngine.CoreModule.dll
+ False
+
+
+
+
+
+
+
+
+
+
+ {5ad344f0-01a0-4ca8-92e5-9d095737744d}
+ IPA.Loader
+ False
+
+
+
+
+
+
+
+ 1.2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BSIPA-ModList/Icons/Thumbs.db b/BSIPA-ModList/Icons/Thumbs.db
new file mode 100644
index 00000000..6203089f
Binary files /dev/null and b/BSIPA-ModList/Icons/Thumbs.db differ
diff --git a/BSIPA-ModList/Icons/mod_bsipa.png b/BSIPA-ModList/Icons/mod_bsipa.png
new file mode 100644
index 00000000..33432786
Binary files /dev/null and b/BSIPA-ModList/Icons/mod_bsipa.png differ
diff --git a/BSIPA-ModList/Icons/mod_ipa.png b/BSIPA-ModList/Icons/mod_ipa.png
new file mode 100644
index 00000000..5a47691d
Binary files /dev/null and b/BSIPA-ModList/Icons/mod_ipa.png differ
diff --git a/BSIPA-ModList/Icons/self.png b/BSIPA-ModList/Icons/self.png
new file mode 100644
index 00000000..8624e118
Binary files /dev/null and b/BSIPA-ModList/Icons/self.png differ
diff --git a/BSIPA-ModList/Icons/self.xcf b/BSIPA-ModList/Icons/self.xcf
new file mode 100644
index 00000000..24d1204e
Binary files /dev/null and b/BSIPA-ModList/Icons/self.xcf differ
diff --git a/BSIPA-ModList/Plugin.cs b/BSIPA-ModList/Plugin.cs
new file mode 100644
index 00000000..3eec9210
--- /dev/null
+++ b/BSIPA-ModList/Plugin.cs
@@ -0,0 +1,65 @@
+using IPA;
+using UnityEngine.SceneManagement;
+using IPALogger = IPA.Logging.Logger;
+using CustomUI.BeatSaber;
+using BSIPA_ModList.UI;
+using CustomUI.MenuButton;
+using UnityEngine.Events;
+
+namespace BSIPA_ModList
+{
+ internal static class Logger
+ {
+ internal static IPALogger log { get; set; }
+ }
+
+ public class Plugin : IBeatSaberPlugin
+ {
+ public void Init(IPALogger logger)
+ {
+ Logger.log = logger;
+ Logger.log.Debug("Init");
+ }
+
+ public void OnActiveSceneChanged(Scene prevScene, Scene nextScene)
+ {
+ }
+
+ public void OnApplicationQuit()
+ {
+ }
+
+ private ModListMenu menu;
+ private MenuButton button;
+
+ public void OnApplicationStart()
+ {
+ Logger.log.Debug("Creating Menu");
+ }
+
+ public void OnFixedUpdate()
+ {
+ }
+
+ public void OnSceneLoaded(Scene scene, LoadSceneMode sceneMode)
+ {
+ if (scene.name == "MenuCore" && button == null)
+ {
+ menu = BeatSaberUI.CreateCustomMenu("Installed Mods");
+ button = MenuButtonUI.AddButton("All Mods", "Shows all installed mods, along with controls for updating them.", () =>
+ {
+ Logger.log.Debug("Presenting menu");
+ menu.Present();
+ });
+ }
+ }
+
+ public void OnSceneUnloaded(Scene scene)
+ {
+ }
+
+ public void OnUpdate()
+ {
+ }
+ }
+}
diff --git a/BSIPA-ModList/Properties/AssemblyInfo.cs b/BSIPA-ModList/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..6cd59713
--- /dev/null
+++ b/BSIPA-ModList/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("BSIPA-ModList")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BSIPA-ModList")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("23ab2621-a05c-4377-8418-85e6012c0bbe")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BSIPA-ModList/UI/ModListController.cs b/BSIPA-ModList/UI/ModListController.cs
new file mode 100644
index 00000000..55218d97
--- /dev/null
+++ b/BSIPA-ModList/UI/ModListController.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using CustomUI.BeatSaber;
+using CustomUI.Utilities;
+using HMUI;
+using IPA.Loader;
+using IPA.Old;
+using UnityEngine;
+
+namespace BSIPA_ModList.UI
+{
+ internal class ModListController : CustomListViewController
+ {
+ private interface IClickableCell
+ {
+ void OnSelect(ModListController cntrl);
+ }
+
+ private class BSIPAModCell : CustomCellInfo, IClickableCell
+ {
+ private static Sprite _defaultIcon;
+ public static Sprite DefaultIcon
+ {
+ get
+ {
+ if (_defaultIcon == null)
+ _defaultIcon = UIUtilities.LoadSpriteFromResources("BSIPA_ModList.Icons.mod_bsipa.png");
+ return _defaultIcon;
+ }
+ }
+
+ internal PluginLoader.PluginInfo Plugin;
+
+ public BSIPAModCell(PluginLoader.PluginInfo plugin)
+ : base($"{plugin.Metadata.Name} v{plugin.Metadata.Version}", plugin.Metadata.Manifest.Author, null)
+ {
+ Plugin = plugin;
+
+ if (plugin.Metadata.Manifest.IconPath != null)
+ icon = UIUtilities.LoadSpriteRaw(UIUtilities.GetResource(plugin.Metadata.Assembly, plugin.Metadata.Manifest.IconPath));
+ else
+ icon = DefaultIcon;
+
+ Logger.log.Debug($"BSIPAModCell {plugin.Metadata.Name} {plugin.Metadata.Version}");
+ }
+
+ public void OnSelect(ModListController cntrl)
+ {
+ Logger.log.Debug($"Selected BSIPAModCell {Plugin.Metadata.Name} {Plugin.Metadata.Version}");
+ }
+ }
+
+ private class BSIPAIgnoredModCell : CustomCellInfo, IClickableCell
+ {
+ internal PluginLoader.PluginMetadata Plugin;
+
+ public BSIPAIgnoredModCell(PluginLoader.PluginMetadata plugin)
+ : base($"{plugin.Name} v{plugin.Version}", $"{plugin.Manifest.Author} - Not loaded", BSIPAModCell.DefaultIcon)
+ {
+ Plugin = plugin;
+
+ Logger.log.Debug($"BSIPAIgnoredModCell {plugin.Name} {plugin.Version}");
+ }
+
+ public void OnSelect(ModListController cntrl)
+ {
+ Logger.log.Debug($"Selected BSIPAIgnoredModCell {Plugin.Name} {Plugin.Version}");
+ }
+ }
+
+#pragma warning disable CS0618
+ private class IPAModCell : CustomCellInfo, IClickableCell
+ {
+ private static Sprite _defaultIcon;
+ public static Sprite DefaultIcon
+ {
+ get
+ {
+ if (_defaultIcon == null)
+ _defaultIcon = UIUtilities.LoadSpriteFromResources("BSIPA_ModList.Icons.mod_ipa.png");
+ return _defaultIcon;
+ }
+ }
+
+ internal IPlugin Plugin;
+
+ public IPAModCell(IPlugin plugin)
+ : base($"{plugin.Name} {plugin.Version}", "Legacy", DefaultIcon)
+ {
+ Plugin = plugin;
+
+ Logger.log.Debug($"IPAModCell {plugin.Name} {plugin.Version}");
+ }
+
+ public void OnSelect(ModListController cntrl)
+ {
+ Logger.log.Debug($"Selected IPAModCell {Plugin.Name} {Plugin.Version}");
+ }
+ }
+#pragma warning restore
+
+#pragma warning disable CS0618
+ public void Init(IEnumerable bsipaPlugins, IEnumerable ignoredPlugins, IEnumerable ipaPlugins)
+ {
+ Logger.log.Debug("List Controller Init");
+
+ DidActivateEvent = DidActivate;
+ DidSelectRowEvent = DidSelectRow;
+
+ includePageButtons = true;
+
+ reuseIdentifier = "BSIPAModListTableCell";
+
+ foreach (var plugin in bsipaPlugins)
+ Data.Add(new BSIPAModCell(plugin));
+ foreach (var plugin in ignoredPlugins)
+ Data.Add(new BSIPAIgnoredModCell(plugin));
+ foreach (var plugin in ipaPlugins)
+ Data.Add(new IPAModCell(plugin));
+ }
+
+#pragma warning restore
+
+ private void DidSelectRow(TableView view, int index)
+ {
+ Debug.Assert(ReferenceEquals(view.dataSource, this));
+ (Data[index] as IClickableCell)?.OnSelect(this);
+ }
+
+ private new void DidActivate(bool first, ActivationType type)
+ {
+
+ }
+ }
+}
diff --git a/BSIPA-ModList/UI/ModListMenu.cs b/BSIPA-ModList/UI/ModListMenu.cs
new file mode 100644
index 00000000..9d1af08f
--- /dev/null
+++ b/BSIPA-ModList/UI/ModListMenu.cs
@@ -0,0 +1,26 @@
+using CustomUI.BeatSaber;
+using IPA.Loader;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BSIPA_ModList.UI
+{
+ internal class ModListMenu : CustomMenu
+ {
+ private ModListController controller;
+
+#pragma warning disable CS0618
+ public ModListMenu()
+ {
+ Logger.log.Debug("Menu constructor");
+
+ controller = BeatSaberUI.CreateViewController();
+ controller.Init(PluginManager.AllPlugins, PluginLoader.ignoredPlugins, PluginManager.Plugins);
+ SetMainViewController(controller, true);
+ }
+#pragma warning restore
+ }
+}
diff --git a/BSIPA-ModList/manifest.json b/BSIPA-ModList/manifest.json
new file mode 100644
index 00000000..b896a8df
--- /dev/null
+++ b/BSIPA-ModList/manifest.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://raw.githubusercontent.com/nike4613/ModSaber-MetadataFileSchema/master/Schema.json",
+ "author": "DaNike",
+ "description": "An in-game interface client for BSIPA.",
+ "gameVersion": "0.13.2",
+ "id": "BSIPA-ModList",
+ "name": "BSIPA Mod List",
+ "version": "1.0.0",
+ "icon": "BSIPA_ModList.Icons.self.png",
+ "dependsOn": {
+ "BSIPA": "^3.12.13",
+ "CustomUI": "^1.5.4"
+ },
+ "features": []
+}
\ No newline at end of file