using Minecraft.Server.FourKit.Event.Server; using Minecraft.Server.FourKit.Plugin; using System.Reflection; namespace Minecraft.Server.FourKit; internal sealed class PluginLoader { private static readonly BindingFlags DeclaredPublic = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly; private readonly List _plugins = new(); public IReadOnlyList Plugins => _plugins.AsReadOnly(); public void LoadPlugins(string pluginsDirectory) { if (!Directory.Exists(pluginsDirectory)) { ServerLog.Info("fourkit", $"Creating plugins directory: {pluginsDirectory}"); Directory.CreateDirectory(pluginsDirectory); return; } var rootDlls = Directory.GetFiles(pluginsDirectory, "*.dll"); if (rootDlls.Length > 0) { ServerLog.Info("fourkit", $"Found {rootDlls.Length} DLL(s) in plugins root."); foreach (var dll in rootDlls) { try { LoadPluginAssembly(dll); } catch (Exception ex) { ServerLog.Error("fourkit", $"Failed to load {Path.GetFileName(dll)}: {ex.Message}"); } } } foreach (var subDir in Directory.GetDirectories(pluginsDirectory)) { string folderName = Path.GetFileName(subDir); var allDlls = Directory.GetFiles(subDir, "*.dll"); if (allDlls.Length == 0) continue; string? mainDll = allDlls.FirstOrDefault(f => string.Equals(Path.GetFileNameWithoutExtension(f), folderName, StringComparison.OrdinalIgnoreCase)); if (mainDll != null) { try { LoadPluginAssembly(mainDll); } catch (Exception ex) { ServerLog.Error("fourkit", $"Failed to load {Path.GetFileName(mainDll)}: {ex.Message}"); FourKit.FireEvent(new PluginLoadFailedEvent(mainDll, ex.Message)); } } else { foreach (var dll in allDlls) { try { LoadPluginAssembly(dll); } catch (Exception ex) { ServerLog.Error("fourkit", $"Failed to load {Path.GetFileName(dll)}: {ex.Message}"); FourKit.FireEvent(new PluginLoadFailedEvent(dll, ex.Message)); } } } } } private void LoadPluginAssembly(string dllPath) { var context = new PluginLoadContext(dllPath); var assembly = context.LoadFromAssemblyPath(Path.GetFullPath(dllPath)); int found = 0; foreach (var type in assembly.GetTypes()) { if (type.IsAbstract || type.IsInterface) continue; if (!typeof(ServerPlugin).IsAssignableFrom(type)) continue; var plugin = (ServerPlugin?)Activator.CreateInstance(type); if (plugin == null) continue; _plugins.Add(plugin); found++; string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name); string pVersion = GetPluginString(plugin, "version", "getVersion", "GetVersion", "1.0"); string pAuthor = GetPluginString(plugin, "author", "getAuthor", "GetAuthor", "Unknown"); if (!HasDeclaredMember(type, "name")) ServerLog.Warn("fourkit", $"Plugin {type.Name} does not declare a 'name' property."); if (!HasDeclaredMember(type, "version")) ServerLog.Warn("fourkit", $"Plugin {type.Name} does not declare a 'version' property."); if (!HasDeclaredMember(type, "author")) ServerLog.Warn("fourkit", $"Plugin {type.Name} does not declare an 'author' property."); ServerLog.Info("fourkit", $"Loaded plugin: {pName} v{pVersion} by {pAuthor}"); } if (found == 0) { ServerLog.Warn("fourkit", $"No ServerPlugin classes found in {Path.GetFileName(dllPath)}"); } } public void EnableAll() { foreach (var plugin in _plugins) { EnablePlugin(plugin); } } public void DisableAll() { foreach (var plugin in _plugins) { DisablePlugin(plugin); } } public void EnablePlugin(ServerPlugin plugin) { try { InvokePluginMethod(plugin, "onEnable", "OnEnable"); string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name); ServerLog.Info("fourkit", $"Enabled: {pName}"); FourKit.FireEvent(new PluginEnableEvent(plugin)); } catch (Exception ex) { string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name); ServerLog.Error("fourkit", $"Error enabling {pName}: {ex.Message}"); } } public void DisablePlugin(ServerPlugin plugin) { try { InvokePluginMethod(plugin, "onDisable", "OnDisable"); string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name); ServerLog.Info("fourkit", $"Disabled: {pName}"); FourKit.FireEvent(new PluginDisableEvent(plugin)); } catch (Exception ex) { string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name); ServerLog.Error("fourkit", $"Error disabling {pName}: {ex.Message}"); } } private static void InvokePluginMethod(ServerPlugin plugin, string camelName, string pascalName) { Type type = plugin.GetType(); MethodInfo? method = type.GetMethod(camelName, DeclaredPublic, Type.EmptyTypes) ?? type.GetMethod(pascalName, DeclaredPublic, Type.EmptyTypes); if (method != null) { method.Invoke(plugin, null); } } private static bool HasDeclaredMember(Type type, string name) { return type.GetProperty(name, DeclaredPublic) != null || type.GetMethod("get" + char.ToUpper(name[0]) + name[1..], DeclaredPublic, Type.EmptyTypes) != null; } private static string GetPluginString( ServerPlugin plugin, string propertyName, string getterCamel, string getterPascal, string fallback) { Type type = plugin.GetType(); PropertyInfo? prop = type.GetProperty(propertyName, DeclaredPublic); if (prop != null && prop.PropertyType == typeof(string)) { return (string?)prop.GetValue(plugin) ?? fallback; } MethodInfo? getter = type.GetMethod(getterCamel, DeclaredPublic, Type.EmptyTypes); if (getter != null && getter.ReturnType == typeof(string)) { return (string?)getter.Invoke(plugin, null) ?? fallback; } getter = type.GetMethod(getterPascal, DeclaredPublic, Type.EmptyTypes); if (getter != null && getter.ReturnType == typeof(string)) { return (string?)getter.Invoke(plugin, null) ?? fallback; } return fallback; } }