diff --git a/Doorstop b/Doorstop
index 055301d4..92da7bf4 160000
--- a/Doorstop
+++ b/Doorstop
@@ -1 +1 @@
-Subproject commit 055301d4402532f60957f0e7745359aa78962a46
+Subproject commit 92da7bf4fbea717040bf4b1bb2d4e07af12bf861
diff --git a/IPA.Injector/Backups/BackupUnit.cs b/IPA.Injector/Backups/BackupUnit.cs
index 99b2158e..4a0a78d8 100644
--- a/IPA.Injector/Backups/BackupUnit.cs
+++ b/IPA.Injector/Backups/BackupUnit.cs
@@ -1,5 +1,4 @@
-using IPA.Utilities;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
@@ -68,7 +67,7 @@ namespace IPA.Injector.Backups
///
public void Add(FileInfo file)
{
- var relativePath = LoneFunctions.GetRelativePath(file.FullName, Environment.CurrentDirectory);
+ var relativePath = Utilities.LoneFunctions.GetRelativePath(file.FullName, Environment.CurrentDirectory);
var backupPath = new FileInfo(Path.Combine(_backupPath.FullName, relativePath));
// Copy over
diff --git a/IPA.Injector/Injector.cs b/IPA.Injector/Injector.cs
index 0e296c07..81a2435a 100644
--- a/IPA.Injector/Injector.cs
+++ b/IPA.Injector/Injector.cs
@@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
+using IPA.Config;
using UnityEngine;
using static IPA.Logging.Logger;
using MethodAttributes = Mono.Cecil.MethodAttributes;
@@ -34,6 +35,8 @@ namespace IPA.Injector
SetupLibraryLoading();
+ SelfConfig.Set();
+
loader.Debug("Prepping bootstrapper");
InstallBootstrapPatch();
@@ -184,7 +187,7 @@ namespace IPA.Injector
pluginAsyncLoadTask.Wait();
log.Debug("Plugins loaded");
log.Debug(string.Join(", ", PluginLoader.PluginsMetadata));
- //PluginComponent.Create();
+ PluginComponent.Create();
}
}
}
diff --git a/IPA.Loader/Config/Config.cs b/IPA.Loader/Config/Config.cs
new file mode 100644
index 00000000..f7307c41
--- /dev/null
+++ b/IPA.Loader/Config/Config.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using IPA.Config.ConfigProviders;
+using IPA.Utilities;
+
+namespace IPA.Config
+{
+ ///
+ /// A class to handle updating ConfigProviders automatically
+ ///
+ public static class Config
+ {
+ static Config()
+ {
+ JsonConfigProvider.RegisterConfig();
+ }
+
+ ///
+ ///
+ /// Defines the type of the
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public class TypeAttribute : Attribute
+ {
+ ///
+ /// The extension associated with this type, without the '.'
+ ///
+ // ReSharper disable once UnusedAutoPropertyAccessor.Global
+ public string Extension { get; private set; }
+
+ ///
+ ///
+ /// Constructs the attribute with a specified extension.
+ ///
+ /// the extension associated with this type, without the '.'
+ public TypeAttribute(string ext)
+ {
+ Extension = ext;
+ }
+ }
+
+ ///
+ ///
+ /// Specifies that a particular parameter is preferred to be a specific type of . If it is not available, also specifies backups. If none are available, the default is used.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public class PreferAttribute : Attribute
+ {
+ ///
+ /// The order of preference for the config type.
+ ///
+ // ReSharper disable once UnusedAutoPropertyAccessor.Global
+ public string[] PreferenceOrder { get; private set; }
+
+ ///
+ ///
+ /// Constructs the attribute with a specific preference list. Each entry is the extension without a '.'
+ ///
+ /// The preferences in order of preference.
+ public PreferAttribute(params string[] preference)
+ {
+ PreferenceOrder = preference;
+ }
+ }
+
+ private static readonly Dictionary registeredProviders = new Dictionary();
+
+ ///
+ /// Registers a to use for configs.
+ ///
+ /// the type to register
+ public static void Register() where T : IConfigProvider => Register(typeof(T));
+
+ ///
+ /// Registers a to use for configs.
+ ///
+ /// the type to register
+ public static void Register(Type type)
+ {
+ if (!(type.GetCustomAttribute(typeof(TypeAttribute)) is TypeAttribute ext))
+ throw new InvalidOperationException("Type does not have TypeAttribute");
+
+ if (!typeof(IConfigProvider).IsAssignableFrom(type))
+ throw new InvalidOperationException("Type not IConfigProvider");
+
+ if (registeredProviders.ContainsKey(ext.Extension))
+ throw new InvalidOperationException($"Extension provider for {ext.Extension} already exists");
+
+ registeredProviders.Add(ext.Extension, type);
+ }
+
+ private static SortedList[, IConfigProvider> configProviders = new SortedList][, IConfigProvider>();
+
+ ///
+ /// Gets an using the specified list pf preferred config types.
+ ///
+ /// the name of the file to associate it with
+ /// the preferred config types to try to get
+ /// an of the requested type, or of type JSON.
+ public static IConfigProvider GetProviderFor(string filename, params string[] extensions)
+ {
+ var chosenExt = extensions.FirstOrDefault(s => registeredProviders.ContainsKey(s)) ?? "json";
+ var type = registeredProviders[chosenExt];
+ var provider = Activator.CreateInstance(type) as IConfigProvider;
+ if (provider != null)
+ {
+ provider.Filename = filename;
+ configProviders.Add(provider.LastModified, provider);
+ }
+
+ return provider;
+ }
+
+ ///
+ /// Gets an using the specified list pf preferred config types.
+ ///
+ /// the name of the file to associate it with
+ /// the parameter info to try and get info for
+ /// an of the requested type, or of type JSON.
+ public static IConfigProvider GetProviderFor(string filename, ParameterInfo info)
+ {
+ var prefs = new string[0];
+ if (info.GetCustomAttribute(typeof(PreferAttribute)) is PreferAttribute prefer)
+ prefs = prefer.PreferenceOrder;
+
+ return GetProviderFor(filename, prefs);
+ }
+
+ private static SortedDictionary linkedProviders =
+ new SortedDictionary();
+
+ ///
+ /// Creates a linked for the config provider. This will be automatically updated whenever the file on-disk changes.
+ ///
+ /// the type of the parsed value
+ /// the to create a link to
+ /// an action to perform on value change
+ /// a to an ever-changing value, mirroring whatever the file contains.
+ public static Ref MakeLink(this IConfigProvider config, Action> onChange = null)
+ {
+ Ref @ref = config.Parse();
+ void ChangeDelegate()
+ {
+ @ref.Value = config.Parse();
+ onChange?.Invoke(config, @ref);
+ }
+
+ if (linkedProviders.ContainsKey(config))
+ linkedProviders[config] = (Action) Delegate.Combine(linkedProviders[config], (Action) ChangeDelegate);
+ else
+ linkedProviders.Add(config, ChangeDelegate);
+
+ ChangeDelegate();
+
+ return @ref;
+ }
+
+ ///
+ /// Removes all linked such that they are no longer updated.
+ ///
+ /// the to unlink
+ public static void RemoveLinks(this IConfigProvider config)
+ {
+ if (linkedProviders.ContainsKey(config))
+ linkedProviders.Remove(config);
+ }
+
+ internal static void Update()
+ {
+ foreach (var provider in configProviders)
+ {
+
+ if (provider.Value.LastModified > provider.Key.Value)
+ {
+ try
+ {
+ provider.Value.Load(); // auto reload if it changes
+ provider.Key.Value = provider.Value.LastModified;
+ }
+ catch (Exception e)
+ {
+ Logging.Logger.config.Error("Error when trying to load config");
+ Logging.Logger.config.Error(e);
+ }
+ }
+ if (provider.Value.HasChanged)
+ {
+ try
+ {
+ provider.Value.Save();
+ provider.Key.Value = DateTime.Now;
+ }
+ catch (Exception e)
+ {
+ Logging.Logger.config.Error("Error when trying to save config");
+ Logging.Logger.config.Error(e);
+ }
+ }
+
+ if (provider.Value.InMemoryChanged)
+ {
+ provider.Value.InMemoryChanged = false;
+ try
+ {
+ if (linkedProviders.ContainsKey(provider.Value))
+ linkedProviders[provider.Value]();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger.config.Error("Error running link change events");
+ Logging.Logger.config.Error(e);
+ }
+ }
+ }
+ }
+
+ internal static void Save()
+ {
+ foreach (var provider in configProviders)
+ if (provider.Value.HasChanged)
+ try
+ {
+ provider.Value.Save();
+ }
+ catch (Exception e)
+ {
+ Logging.Logger.config.Error("Error when trying to save config");
+ Logging.Logger.config.Error(e);
+ }
+ }
+
+ }
+}
diff --git a/IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs b/IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs
index 4c2eba10..c0b018b0 100644
--- a/IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs
+++ b/IPA.Loader/Config/ConfigProviders/JsonConfigProvider.cs
@@ -8,14 +8,21 @@ using Newtonsoft.Json.Linq;
namespace IPA.Config.ConfigProviders
{
+ [Config.Type("json")]
internal class JsonConfigProvider : IConfigProvider
{
+ public static void RegisterConfig()
+ {
+ Config.Register();
+ }
+
private JObject jsonObj;
// TODO: create a wrapper that allows empty object creation
public dynamic Dynamic => jsonObj;
-
+
public bool HasChanged { get; private set; }
+ public bool InMemoryChanged { get; set; }
public DateTime LastModified => File.GetLastWriteTime(Filename + ".json");
@@ -38,7 +45,7 @@ namespace IPA.Config.ConfigProviders
var fileInfo = new FileInfo(Filename + ".json");
if (fileInfo.Exists)
{
- var json = fileInfo.OpenText().ReadToEnd();
+ string json = File.ReadAllText(fileInfo.FullName);
try
{
jsonObj = JObject.Parse(json);
@@ -57,6 +64,7 @@ namespace IPA.Config.ConfigProviders
}
SetupListeners();
+ InMemoryChanged = true;
}
private void SetupListeners()
@@ -69,30 +77,32 @@ namespace IPA.Config.ConfigProviders
private void JsonObj_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
HasChanged = true;
+ InMemoryChanged = true;
}
private void JsonObj_ListChanged(object sender, ListChangedEventArgs e)
{
HasChanged = true;
+ InMemoryChanged = true;
}
private void JsonObj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
HasChanged = true;
+ InMemoryChanged = true;
}
public T Parse()
{
+ if (jsonObj == null)
+ return default(T);
return jsonObj.ToObject();
}
public void Save()
{
Logger.config.Debug($"Saving file {Filename}.json");
-
- var fileInfo = new FileInfo(Filename + ".json");
-
- File.WriteAllText(fileInfo.FullName, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
+ File.WriteAllText(Filename + ".json", JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
HasChanged = false;
}
@@ -102,6 +112,7 @@ namespace IPA.Config.ConfigProviders
jsonObj = JObject.FromObject(obj);
SetupListeners();
HasChanged = true;
+ InMemoryChanged = true;
}
}
}
diff --git a/IPA.Loader/Config/IConfigProvider.cs b/IPA.Loader/Config/IConfigProvider.cs
index d26816d3..88f23efe 100644
--- a/IPA.Loader/Config/IConfigProvider.cs
+++ b/IPA.Loader/Config/IConfigProvider.cs
@@ -32,7 +32,11 @@ namespace IPA.Config
///
bool HasChanged { get; }
///
- /// Will be set with the filename (no extension) to save to. When saving, the implimentation should add the appropriate extension. Should error if set multiple times.
+ /// Returns if the data in memory has been changed - notably including loads.
+ ///
+ bool InMemoryChanged { get; set; }
+ ///
+ /// Will be set with the filename (no extension) to save to. When saving, the implementation should add the appropriate extension. Should error if set multiple times.
///
string Filename { set; }
///
diff --git a/IPA.Loader/Config/SelfConfig.cs b/IPA.Loader/Config/SelfConfig.cs
new file mode 100644
index 00000000..d83097df
--- /dev/null
+++ b/IPA.Loader/Config/SelfConfig.cs
@@ -0,0 +1,48 @@
+using System.IO;
+using IPA.Logging;
+using IPA.Utilities;
+
+namespace IPA.Config
+{
+ internal class SelfConfig
+ {
+ private static IConfigProvider _loaderConfig;
+
+ public static IConfigProvider LoaderConfig
+ {
+ get => _loaderConfig;
+ set
+ {
+ _loaderConfig?.RemoveLinks();
+ value.Load();
+ SelfConfigRef = value.MakeLink((c, v) =>
+ {
+ var val = v.Value;
+ if (val.Regenerate)
+ c.Store(val = new SelfConfig { Regenerate = false });
+
+ StandardLogger.Configure(val);
+ });
+ _loaderConfig = value;
+ }
+ }
+
+ public static Ref SelfConfigRef;
+ public static void Set()
+ {
+ LoaderConfig = Config.GetProviderFor(Path.Combine("UserData", IPA_Name), "toml", "json");
+ }
+
+ internal const string IPA_Name = "Beat Saber IPA - Builtin manifest support";
+ internal const string IPA_Version = "3.12.0";
+
+ public bool Regenerate = true;
+
+ public class DebugObject
+ {
+ public bool ShowCallSource = false;
+ public bool ShowDebug = false;
+ }
+ public DebugObject Debug = new DebugObject();
+ }
+}
diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj
index dbc49238..995a137e 100644
--- a/IPA.Loader/IPA.Loader.csproj
+++ b/IPA.Loader/IPA.Loader.csproj
@@ -56,8 +56,10 @@
+
+
diff --git a/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs b/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs
index 5080628f..2efa8158 100644
--- a/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs
+++ b/IPA.Loader/Loader/Composite/CompositeBSPlugin.cs
@@ -54,11 +54,11 @@ namespace IPA.Loader.Composite
Invoke(plugin => plugin.OnFixedUpdate());
}
- public string Name => throw new NotImplementedException();
+ public string Name => throw new InvalidOperationException();
- public string Version => throw new NotImplementedException();
+ public string Version => throw new InvalidOperationException();
- public ModsaberModInfo ModInfo => throw new NotImplementedException();
+ public ModsaberModInfo ModInfo => throw new InvalidOperationException();
public void OnLateUpdate() {
Invoke(plugin => {
diff --git a/IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs b/IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs
index b254aa52..9825f6bc 100644
--- a/IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs
+++ b/IPA.Loader/Loader/Composite/CompositeIPAPlugin.cs
@@ -43,9 +43,9 @@ namespace IPA.Loader.Composite
Invoke(plugin => plugin.OnFixedUpdate());
}
- public string Name => throw new NotImplementedException();
+ public string Name => throw new InvalidOperationException();
- public string Version => throw new NotImplementedException();
+ public string Version => throw new InvalidOperationException();
public void OnLateUpdate() {
Invoke(plugin => {
diff --git a/IPA.Loader/Loader/PluginComponent.cs b/IPA.Loader/Loader/PluginComponent.cs
index d0e1b36a..2ff13378 100644
--- a/IPA.Loader/Loader/PluginComponent.cs
+++ b/IPA.Loader/Loader/PluginComponent.cs
@@ -1,5 +1,4 @@
using IPA.Loader.Composite;
-using System;
using System.Diagnostics.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -24,9 +23,9 @@ namespace IPA.Loader
DontDestroyOnLoad(gameObject);
bsPlugins = new CompositeBSPlugin(PluginManager.BSPlugins);
-#pragma warning disable CS0618 // Type or member is obsolete
+#pragma warning disable 618
ipaPlugins = new CompositeIPAPlugin(PluginManager.Plugins);
-#pragma warning restore CS0618 // Type or member is obsolete
+#pragma warning restore 618
gameObject.AddComponent();
@@ -37,23 +36,15 @@ namespace IPA.Loader
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
- foreach (var provider in PluginManager.configProviders)
- if (provider.Key.HasChanged)
- try
- {
- provider.Key.Save();
- }
- catch (Exception e)
- {
- Logging.Logger.log.Error("Error when trying to save config");
- Logging.Logger.log.Error(e);
- }
+ Config.Config.Save();
}
void Update()
{
bsPlugins.OnUpdate();
ipaPlugins.OnUpdate();
+
+ Config.Config.Update();
}
void LateUpdate()
@@ -61,32 +52,7 @@ namespace IPA.Loader
bsPlugins.OnLateUpdate();
ipaPlugins.OnLateUpdate();
- foreach (var provider in PluginManager.configProviders)
- {
- if (provider.Key.HasChanged)
- try
- {
- provider.Key.Save();
- }
- catch (Exception e)
- {
- Logging.Logger.log.Error("Error when trying to save config");
- Logging.Logger.log.Error(e);
- }
- else if (provider.Key.LastModified > provider.Value.Value)
- {
- try
- {
- provider.Key.Load(); // auto reload if it changes
- provider.Value.Value = provider.Key.LastModified;
- }
- catch (Exception e)
- {
- Logging.Logger.log.Error("Error when trying to load config");
- Logging.Logger.log.Error(e);
- }
- }
- }
+ //Config.Config.Update();
}
void FixedUpdate()
@@ -112,17 +78,7 @@ namespace IPA.Loader
bsPlugins.OnApplicationQuit();
ipaPlugins.OnApplicationQuit();
- foreach (var provider in PluginManager.configProviders)
- if (provider.Key.HasChanged)
- try
- {
- provider.Key.Save();
- }
- catch (Exception e)
- {
- Logging.Logger.log.Error("Error when trying to save config");
- Logging.Logger.log.Error(e);
- }
+ Config.Config.Save();
quitting = true;
}
diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs
index d59d011e..c79f2496 100644
--- a/IPA.Loader/Loader/PluginLoader.cs
+++ b/IPA.Loader/Loader/PluginLoader.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
+using IPA.Config;
using IPA.Logging;
using IPA.Utilities;
using Newtonsoft.Json;
@@ -16,17 +17,10 @@ namespace IPA.Loader
///
public class PluginLoader
{
- ///
- /// The directory to load plugins from.
- ///
- public static string PluginsDirectory => Path.Combine(BeatSaber.InstallPath, "Plugins");
-
internal static Task LoadTask() => Task.Run(() =>
{
LoadMetadata();
- Logger.log.Debug(string.Join(", ", PluginsMetadata));
Resolve();
- Logger.log.Debug(string.Join(", ", PluginsMetadata));
ComputeLoadOrder();
});
@@ -81,7 +75,7 @@ namespace IPA.Loader
}
///
- public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.AssemblyQualifiedName}) from '{File.Name}'";
+ public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.AssemblyQualifiedName}) from '{LoneFunctions.GetRelativePath(File.FullName, BeatSaber.InstallPath)}'";
}
///
@@ -101,7 +95,7 @@ namespace IPA.Loader
internal static void LoadMetadata()
{
- string[] plugins = Directory.GetFiles(PluginsDirectory, "*.dll");
+ string[] plugins = Directory.GetFiles(BeatSaber.PluginsPath, "*.dll");
try
{
@@ -273,7 +267,6 @@ namespace IPA.Loader
return 0;
});
- Logger.log.Debug(string.Join(", ", PluginsMetadata));
var metadata = new List();
var pluginsToLoad = new Dictionary();
@@ -303,5 +296,75 @@ namespace IPA.Loader
{
}
+
+ internal static PluginInfo LoadPlugin(PluginMetadata meta)
+ {
+ var info = new PluginInfo();
+
+ try
+ {
+ Logger.loader.Debug(meta.Assembly.GetName().ToString());
+ meta.Assembly = Assembly.Load(meta.Assembly.GetName());
+
+ var type = meta.PluginType;
+ var instance = (IBeatSaberPlugin)Activator.CreateInstance(type);
+
+ info.Metadata = meta;
+ info.Filename = meta.File.FullName;
+ info.Plugin = instance;
+
+ {
+ var init = type.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public);
+ if (init != null)
+ {
+ var initArgs = new List
All = DebugOnly | InfoUp,
+
+ ///
+ /// Used for when the level is undefined.
+ ///
+ Undefined = Byte.MaxValue
}
///
diff --git a/IPA.Loader/Logging/StandardLogger.cs b/IPA.Loader/Logging/StandardLogger.cs
index aa64f4d2..0b297782 100644
--- a/IPA.Loader/Logging/StandardLogger.cs
+++ b/IPA.Loader/Logging/StandardLogger.cs
@@ -44,25 +44,25 @@ namespace IPA.Logging
};
private readonly string logName;
- private static readonly bool showSourceClass;
+ private static bool showSourceClass;
+
///
/// All levels defined by this filter will be sent to loggers. All others will be ignored.
///
- public static LogLevel PrintFilter { get; set; }
+ public static LogLevel PrintFilter { get; set; } = LogLevel.All;
private readonly List printers = new List(defaultPrinters);
private readonly Dictionary children = new Dictionary();
- static StandardLogger()
+ internal static void Configure(SelfConfig cfg)
{
- showSourceClass = ModPrefs.GetBool("IPA", "DebugShowCallSource", false, true);
- PrintFilter = ModPrefs.GetBool("IPA", "PrintDebug", false, true) ? LogLevel.All : LogLevel.InfoUp;
+ showSourceClass = cfg.Debug.ShowCallSource;
+ PrintFilter = cfg.Debug.ShowDebug ? LogLevel.All : LogLevel.InfoUp;
}
private StandardLogger(string mainName, string subName, params LogPrinter[] inherited)
{
logName = $"{mainName}/{subName}";
-
printers = new List(inherited)
{
new PluginSubLogPrinter(mainName, subName)
@@ -78,7 +78,6 @@ namespace IPA.Logging
internal StandardLogger(string name)
{
logName = name;
-
printers.Add(new PluginLogFilePrinter(name));
if (logThread == null || !logThread.IsAlive)
@@ -219,13 +218,9 @@ namespace IPA.Logging
public static Logger GetChildLogger(this Logger logger, string name)
{
if (logger is StandardLogger standardLogger)
- {
return standardLogger.GetChild(name);
- }
- else
- {
- throw new InvalidOperationException();
- }
+
+ throw new InvalidOperationException();
}
}
}
diff --git a/IPA.Loader/Updating/SelfPlugin.cs b/IPA.Loader/Updating/SelfPlugin.cs
index b86d6b61..852af2f7 100644
--- a/IPA.Loader/Updating/SelfPlugin.cs
+++ b/IPA.Loader/Updating/SelfPlugin.cs
@@ -1,21 +1,21 @@
-using UnityEngine.SceneManagement;
+using System;
+using IPA.Config;
+using UnityEngine.SceneManagement;
namespace IPA.Updating
{
+ [Obsolete("Only used for old updating system, replaced with a PluginMeta for teh embedded manifest")]
internal class SelfPlugin : IBeatSaberPlugin
{
- internal const string IPA_Name = "Beat Saber IPA - Builtin manifest support";
- internal const string IPA_Version = "3.12.0";
-
public static SelfPlugin Instance { get; set; } = new SelfPlugin();
- public string Name => IPA_Name;
+ public string Name => SelfConfig.IPA_Name;
- public string Version => IPA_Version;
+ public string Version => SelfConfig.IPA_Version;
public ModsaberModInfo ModInfo => new ModsaberModInfo
{
- CurrentVersion = IPA_Version,
+ CurrentVersion = SelfConfig.IPA_Version,
InternalName = "beatsaber-ipa-reloaded"
};
diff --git a/IPA.Loader/Utilities/BeatSaber.cs b/IPA.Loader/Utilities/BeatSaber.cs
index 3585b5be..a26d9b85 100644
--- a/IPA.Loader/Utilities/BeatSaber.cs
+++ b/IPA.Loader/Utilities/BeatSaber.cs
@@ -48,6 +48,10 @@ namespace IPA.Utilities
/// The path to the `Libs\Native` folder. Use only if necessary.
///
public static string NativeLibraryPath => Path.Combine(LibraryPath, "Native");
+ ///
+ /// The directory to load plugins from.
+ ///
+ public static string PluginsPath => Path.Combine(InstallPath, "Plugins");
private static bool FindSteamVRAsset()
{
]