diff --git a/EXILED/Exiled.Loader/ConfigManager.cs b/EXILED/Exiled.Loader/ConfigManager.cs
index e925517be..f04835e61 100644
--- a/EXILED/Exiled.Loader/ConfigManager.cs
+++ b/EXILED/Exiled.Loader/ConfigManager.cs
@@ -11,6 +11,7 @@ namespace Exiled.Loader
using System.Collections.Generic;
using System.IO;
using System.Linq;
+ using System.Reflection;
using API.Enums;
using API.Extensions;
@@ -19,13 +20,19 @@ namespace Exiled.Loader
using Exiled.API.Features;
using Exiled.API.Features.Pools;
+ using LabApi.Loader.Features.Plugins.Configuration;
using YamlDotNet.Core;
+ using YamlDotNet.Serialization;
+
+ using LabPlugin = LabApi.Loader.Features.Plugins.Plugin;
///
/// Used to handle plugin configs.
///
public static class ConfigManager
{
+ private static readonly MethodInfo PropertiesSetter = typeof(LabPlugin).GetProperty("Properties", BindingFlags.Public | BindingFlags.Instance)?.GetSetMethod(true);
+
///
/// Loads all the plugin configs.
///
@@ -281,5 +288,186 @@ public static void ReloadRemoteAdmin()
player.ReferenceHub.serverRoles.RefreshPermissions();
}
}
+
+ ///
+ /// Reloads all LabAPI configs.
+ ///
+ public static void ReloadLabAPIConfigs()
+ {
+ try
+ {
+ // this is 10x more readable than Exileds current config management system LOL
+ foreach (LabPlugin plugin in Loader.LabAPIPlugins.Keys)
+ {
+ LoadLabAPIConfig(plugin);
+ SaveLabAPIConfig(plugin);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex);
+ }
+ }
+
+ ///
+ /// Attempts to load a config for a LabAPI plugin.
+ ///
+ /// The LabAPI plugin.
+ /// I love it when the modding framework people call the best has all plugin loading methods private and the config loading methods don't take custom directory paths.
+ public static void LoadLabAPIConfig(LabPlugin plugin)
+ {
+ Type pluginType = plugin.GetType();
+ Type type = pluginType;
+ while (type is not null)
+ {
+ type = type.BaseType;
+
+ if (type is { IsGenericType: true })
+ {
+ Type genericTypeDef = type.GetGenericTypeDefinition();
+
+ if (genericTypeDef == typeof(LabApi.Loader.Features.Plugins.Plugin<>))
+ break;
+ }
+ }
+
+ if (type is null)
+ return;
+
+ Type configType = type.GetGenericArguments().FirstOrDefault();
+ if (configType is null)
+ {
+ Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, could not get the generic of TConfig!");
+ return;
+ }
+
+ ConstructorInfo parameterless = configType.GetConstructors().FirstOrDefault(ctor => ctor.GetParameters().Length == 0);
+
+ if (parameterless is null)
+ {
+ Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, config type has no parameterless constructor!");
+ return;
+ }
+
+ MethodInfo configSetter = pluginType.GetProperty("Config")?.GetSetMethod();
+ if (configSetter is null)
+ {
+ Log.Error($"Failed to load config for LabAPI plugin {plugin.Name}, no setter for property \"Config\" was found!");
+ return;
+ }
+
+ string configPath = Paths.GetConfigPath(plugin.Name);
+
+ if (!File.Exists(configPath))
+ {
+ Log.Warn($"LabAPI Plugin {plugin.Name} doesn't have default configs, generating...");
+ configSetter.Invoke(plugin, new[] { parameterless.Invoke(null) });
+ return;
+ }
+
+ IDeserializer deserializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Deserializer;
+ MethodInfo deserialize = deserializer.GetType().GetMethods().Single(method => method.Name == "Deserialize" && method.IsGenericMethod && method.GetParameters().FirstOrDefault()?.ParameterType == typeof(string)).MakeGenericMethod(configType);
+ try
+ {
+ configSetter.Invoke(plugin, new[] { deserialize.Invoke(deserializer, new object[] { File.ReadAllText(configPath) }) });
+ }
+ catch (TargetInvocationException ex)
+ {
+ if (ex.InnerException is YamlException yamlException)
+ {
+ Log.Error($"{plugin.Name} configs could not be loaded, some of them are in a wrong format, default configs will be loaded instead!\n{yamlException}");
+ configSetter.Invoke(plugin, new[] { parameterless.Invoke(null) });
+ }
+ else
+ {
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Saves a config for a LabAPI plugin.
+ ///
+ /// The LabAPI plugin.
+ public static void SaveLabAPIConfig(LabPlugin plugin)
+ {
+ Type pluginType = plugin.GetType();
+ Type type = pluginType;
+ while (type is not null)
+ {
+ type = type.BaseType;
+
+ if (type is { IsGenericType: true })
+ {
+ Type genericTypeDef = type.GetGenericTypeDefinition();
+
+ if (genericTypeDef == typeof(LabApi.Loader.Features.Plugins.Plugin<>))
+ break;
+ }
+ }
+
+ if (type is null)
+ return;
+
+ MethodInfo configGetter = pluginType.GetProperty("Config")?.GetGetMethod();
+ if (configGetter is null)
+ {
+ Log.Error($"Failed to save config for LabAPI plugin {plugin.Name}, no getter for property \"Config\" was found!");
+ return;
+ }
+
+ string config = LabApi.Loader.Features.Yaml.YamlConfigParser.Serializer.Serialize(configGetter.Invoke(plugin, null));
+ string configPath = Paths.GetConfigPath(plugin.Name);
+
+ try
+ {
+ Directory.CreateDirectory(Path.Combine(Paths.IndividualConfigs, plugin.Name));
+ File.WriteAllText(configPath, config);
+ }
+ catch (Exception exception)
+ {
+ Log.Error($"An error has occurred while saving configs to {configPath} path: {exception}");
+ }
+ }
+
+ ///
+ /// Loads the properties of a LabAPI plugin.
+ ///
+ /// The LabAPI plugin.
+ /// Whether the properties were successfully retrieved.
+ public static bool LoadLabAPIProperties(LabPlugin plugin)
+ {
+ if (PropertiesSetter is null)
+ {
+ Log.Error("Cannot load LabAPI properties as the setter from reflection is null!");
+ return false;
+ }
+
+ ISerializer serializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Serializer;
+ IDeserializer deserializer = LabApi.Loader.Features.Yaml.YamlConfigParser.Deserializer;
+
+ string configPath = Path.Combine(Paths.IndividualConfigs, plugin.Name, $"{Server.Port}-properties.yml");
+
+ if (!File.Exists(configPath))
+ {
+ Log.Warn($"LabAPI Plugin {plugin.Name} doesn't have default properties, generating...");
+ PropertiesSetter.Invoke(plugin, new object[] { Properties.CreateDefault() });
+ File.WriteAllText(configPath, serializer.Serialize(plugin.Properties!));
+ return true;
+ }
+
+ try
+ {
+ PropertiesSetter.Invoke(plugin, new[] { deserializer.Deserialize(File.ReadAllText(configPath), typeof(Properties)) });
+ }
+ catch (YamlException yamlException)
+ {
+ Log.Error($"{plugin.Name} properties could not be loaded, default properties will be loaded instead!\n{yamlException}");
+ PropertiesSetter.Invoke(plugin, new object[] { Properties.CreateDefault() });
+ File.WriteAllText(configPath, serializer.Serialize(plugin.Properties!));
+ }
+
+ return true;
+ }
}
}
\ No newline at end of file
diff --git a/EXILED/Exiled.Loader/Loader.cs b/EXILED/Exiled.Loader/Loader.cs
index 18ef26fdc..5dce161c0 100644
--- a/EXILED/Exiled.Loader/Loader.cs
+++ b/EXILED/Exiled.Loader/Loader.cs
@@ -23,17 +23,25 @@ namespace Exiled.Loader
using CommandSystem.Commands.Shared;
using Exiled.API.Features;
+ using Exiled.API.Features.Pools;
using Features;
using Features.Configs;
using Features.Configs.CustomConverters;
+ using LabApi.Loader;
+ using LabApi.Loader.Features.Misc;
+ using LabApi.Loader.Features.Plugins.Configuration;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NodeDeserializers;
+ using LabPlugin = LabApi.Loader.Features.Plugins.Plugin;
+
///
/// Used to handle plugins.
///
public class Loader
{
+ private static readonly MethodInfo FilePathSetter = typeof(LabPlugin).GetProperty("FilePath", BindingFlags.Public | BindingFlags.Instance)?.GetSetMethod(true);
+
///
/// Initializes a new instance of the class.
///
@@ -67,6 +75,11 @@ public Loader()
///
public static SortedSet> Plugins { get; } = new(PluginPriorityComparer.Instance);
+ ///
+ /// Gets the plugins list.
+ ///
+ public static Dictionary LabAPIPlugins { get; } = new();
+
///
/// Gets a dictionary containing the file paths of assemblies.
///
@@ -226,6 +239,60 @@ public static IPlugin CreatePlugin(Assembly assembly)
return null;
}
+ ///
+ /// Create a plugin instance.
+ ///
+ /// The plugin assembly.
+ /// The path of the assembly.
+ /// Returns the created plugin instance or .
+ public static LabPlugin CreateLabAPIPlugin(Assembly assembly, string path)
+ {
+ try
+ {
+ AssemblyUtils.ResolveEmbeddedResources(assembly);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Failed to resolve embedded resources for assembly '" + path + "'");
+ string[] missingDependencies = AssemblyUtils.GetMissingDependencies(assembly).ToArray();
+ if (!missingDependencies.Any())
+ return null;
+ Log.Error("Missing dependencies:\n" + string.Join("\n", missingDependencies.Select(x => "-\t " + x)));
+ Log.Error(ex);
+ }
+
+ LabPlugin plugin = null;
+
+ if (FilePathSetter is null)
+ {
+ Log.Error("FilePath setter for LabAPI Plugin type is null!");
+ }
+
+ try
+ {
+ foreach (Type type in assembly.GetTypes())
+ {
+ if (!type.IsSubclassOf(typeof(LabPlugin)) || type.IsAbstract || Activator.CreateInstance(type) is not LabPlugin instance)
+ continue;
+
+ FilePathSetter?.Invoke(instance, new object[] { path });
+
+ plugin = instance;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error(" Couldn't load the LabAPI plugin inside '" + path + "'");
+ string[] missingDependencies = AssemblyUtils.GetMissingDependencies(assembly).ToArray();
+ if (!missingDependencies.Any())
+ return null;
+ Log.Error("Missing dependencies:\n" + string.Join("\n", missingDependencies.Select(x => "-\t " + x)));
+ Log.Error(ex);
+ }
+
+ return plugin;
+ }
+
///
/// Enables all plugins.
///
@@ -268,6 +335,38 @@ public static void EnablePlugins()
Log.Error($"Plugin \"{plugin.Name}\" threw an exception while enabling: {exception}");
}
}
+
+ foreach (LabPlugin plugin in LabAPIPlugins.Keys.OrderBy(plugin => plugin.Priority))
+ {
+ try
+ {
+ if (ConfigManager.LoadLabAPIProperties(plugin))
+ {
+ Properties properties = plugin.Properties;
+ if (properties is { IsEnabled: true })
+ {
+ // copy pasted from LabAPI plugin enabling
+ try
+ {
+ CustomNetworkManager.Modded = true;
+ plugin.RegisterCommands();
+ plugin.Enable();
+
+ Log.Info($"LabAPI plugin {plugin.Name} v{plugin.Version.Major}.{plugin.Version.Minor}.{plugin.Version.Build} by {plugin.Author} has been enabled!");
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Couldn't enable the LabAPI plugin {plugin}");
+ Log.Error(ex);
+ }
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ Log.Error($"Plugin \"{plugin.Name}\" threw an exception while enabling: {exception}");
+ }
+ }
}
///
@@ -294,12 +393,14 @@ public static void ReloadPlugins()
}
Plugins.Clear();
+ LabAPIPlugins.Clear();
Server.PluginAssemblies.Clear();
Locations.Clear();
LoadPlugins();
ConfigManager.Reload();
+ ConfigManager.ReloadLabAPIConfigs();
TranslationManager.Reload();
EnablePlugins();
@@ -322,6 +423,12 @@ public static void DisablePlugins()
Log.Error($"Plugin \"{plugin.Name}\" threw an exception while disabling: {exception}");
}
}
+
+ foreach (LabPlugin plugin in LabAPIPlugins.Keys)
+ {
+ plugin.UnregisterCommands();
+ plugin.Disable();
+ }
}
///
@@ -331,6 +438,14 @@ public static void DisablePlugins()
/// The desired plugin, null if not found.
public static IPlugin GetPlugin(string args) => Plugins.FirstOrDefault(x => x.Name == args || x.Prefix == args);
+ ///
+ /// Gets a LabAPI plugin Exiled loaded by its name.
+ ///
+ /// The name of the plugin.
+ /// The desired plugin, null if not found.
+ /// This method does not check LabAPI's loaded plugins, only LabAPI plugins EXILED loaded.
+ public static LabPlugin GetLabAPIPlugin(string args) => LabAPIPlugins.Keys.FirstOrDefault(x => x.Name == args);
+
///
/// Runs the plugin manager, by loading all dependencies, plugins, configs and then enables all plugins.
///
@@ -391,6 +506,7 @@ public IEnumerator Run(Assembly[] dependencies = null)
LoadPlugins();
ConfigManager.Reload();
+ ConfigManager.ReloadLabAPIConfigs();
TranslationManager.Reload();
EnablePlugins();
@@ -485,6 +601,7 @@ private static void LoadPluginsFromDirectory(string dir = null)
Locations[assembly] = assemblyPath;
}
+ List failed = ListPool.Pool.Get();
foreach (Assembly assembly in Locations.Keys)
{
if (Locations[assembly].Contains("dependencies"))
@@ -493,7 +610,10 @@ private static void LoadPluginsFromDirectory(string dir = null)
IPlugin plugin = CreatePlugin(assembly);
if (plugin == null)
+ {
+ failed.Add(assembly);
continue;
+ }
if (Plugins.Any(p => p.Name == plugin.Name))
continue;
@@ -505,6 +625,23 @@ private static void LoadPluginsFromDirectory(string dir = null)
Server.PluginAssemblies.Add(assembly, plugin);
Plugins.Add(plugin);
}
+
+ // inefficient enumeration (could be forced inside earlier foreach), but a lot easier to read.
+ foreach (Assembly attempt in failed)
+ {
+ LabPlugin plugin = CreateLabAPIPlugin(attempt, Locations[attempt]);
+
+ if (plugin == null)
+ continue;
+
+ if (Plugins.Any(p => p.Name == plugin.Name) || LabAPIPlugins.Keys.Any(p => p.Name == plugin.Name))
+ continue;
+
+ Log.Info("Successfully loaded LabAPI plugin " + plugin.Name);
+ LabAPIPlugins.Add(plugin, attempt);
+ }
+
+ ListPool.Pool.Return(failed);
}
///