fix: redirect AppContext.BaseDirectory to server root for FourKit plugins

AppContext.BaseDirectory pointed to the runtime/ subfolder where the
self-contained .NET payload lives, causing plugins that use it for file
paths to write to the wrong directory. Now set to the server exe
directory at startup via AppContext.SetData.

Also adds serverDirectory and dataDirectory properties to ServerPlugin
so plugin authors have convenient access to the server root and a
per-plugin data folder (plugins/<PluginName>/) without needing to
resolve paths manually.
This commit is contained in:
itsRevela 2026-04-10 02:03:44 -05:00
parent d14e46701c
commit 8f41d34325
3 changed files with 37 additions and 3 deletions

View file

@ -25,9 +25,15 @@ public static partial class FourKitHost
// host exe's directory instead so end users see a top-level plugins/.
string hostExePath = Environment.ProcessPath ?? AppContext.BaseDirectory;
string serverRoot = Path.GetDirectoryName(hostExePath) ?? AppContext.BaseDirectory;
// Redirect AppContext.BaseDirectory to the server root so that
// plugins using AppContext.BaseDirectory get the exe directory
// instead of the runtime/ subfolder.
AppContext.SetData("APP_CONTEXT_BASE_DIRECTORY", serverRoot + Path.DirectorySeparatorChar);
string pluginsDir = Path.Combine(serverRoot, "plugins");
s_loader = new PluginLoader();
s_loader.LoadPlugins(pluginsDir);
s_loader.LoadPlugins(pluginsDir, serverRoot);
s_loader.EnableAll();
ServerLog.Info("fourkit", "Plugin system ready.");

View file

@ -28,6 +28,22 @@ public abstract class ServerPlugin
/// </summary>
public virtual string author { get; } = "Unknown";
/// <summary>
/// The server's root directory (where the server executable lives).
/// Use this instead of <c>AppContext.BaseDirectory</c> which points
/// to the .NET runtime subfolder. Set automatically before
/// <see cref="onEnable"/> is called.
/// </summary>
public string serverDirectory { get; internal set; } = string.Empty;
/// <summary>
/// A per-plugin data directory (<c>plugins/&lt;PluginName&gt;/</c>).
/// Created automatically if it does not exist. Use this for config
/// files, logs, databases, or any plugin-specific storage.
/// Set automatically before <see cref="onEnable"/> is called.
/// </summary>
public string dataDirectory { get; internal set; } = string.Empty;
/// <summary>
/// Called when this plugin is enabled
/// </summary>

View file

@ -10,11 +10,16 @@ internal sealed class PluginLoader
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
private readonly List<ServerPlugin> _plugins = new();
private string _serverRoot = string.Empty;
private string _pluginsDirectory = string.Empty;
public IReadOnlyList<ServerPlugin> Plugins => _plugins.AsReadOnly();
public void LoadPlugins(string pluginsDirectory)
public void LoadPlugins(string pluginsDirectory, string serverRoot)
{
_serverRoot = serverRoot;
_pluginsDirectory = pluginsDirectory;
if (!Directory.Exists(pluginsDirectory))
{
ServerLog.Info("fourkit", $"Creating plugins directory: {pluginsDirectory}");
@ -136,8 +141,15 @@ internal sealed class PluginLoader
{
try
{
InvokePluginMethod(plugin, "onEnable", "OnEnable");
string pName = GetPluginString(plugin, "name", "getName", "GetName", plugin.GetType().Name);
plugin.serverDirectory = _serverRoot;
string dataDir = Path.Combine(_pluginsDirectory, pName);
if (!Directory.Exists(dataDir))
Directory.CreateDirectory(dataDir);
plugin.dataDirectory = dataDir;
InvokePluginMethod(plugin, "onEnable", "OnEnable");
ServerLog.Info("fourkit", $"Enabled: {pName}");
FourKit.FireEvent(new PluginEnableEvent(plugin));