Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion managed/CounterStrikeSharp.API/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,22 @@ private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
// If our argument doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
// We'll assume we have a full path if we have ".dll".
var path = info.GetArg(2);
path = Path.Combine(_scriptHostConfiguration.RootPath, !path.EndsWith(".dll") ? $"plugins/{path}/{path}.dll" : path);

if (path.StartsWith("disabled/"))
{
info.ReplyToCommand(
$"[WARNING] Attempted to load plugin from disabled folder: '{path}'. " +
"Action aborted. Move the plugin out of 'disabled/' to enable it.\n"
);
break;
}

path = Path.Combine(
_scriptHostConfiguration.RootPath,
!path.EndsWith(".dll")
? $"plugins/{path}/{Path.GetFileName(path)}.dll"
: path
);

var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);

Expand Down
285 changes: 159 additions & 126 deletions managed/CounterStrikeSharp.API/Core/Plugin/Host/PluginManager.cs
Original file line number Diff line number Diff line change
@@ -1,127 +1,160 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Core.Hosting;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CounterStrikeSharp.API.Core.Plugin.Host;

public class PluginManager : IPluginManager
{
private readonly HashSet<PluginContext> _loadedPluginContexts = new();
private readonly IScriptHostConfiguration _scriptHostConfiguration;
private readonly ICommandManager _commandManager;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PluginManager> _logger;
private readonly Dictionary<string, Assembly> _sharedAssemblies = new();
private bool _loadedSharedLibs = false;

public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager,
ILogger<PluginManager> logger, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
{
_scriptHostConfiguration = scriptHostConfiguration;
_commandManager = commandManager;
_logger = logger;
_serviceProvider = serviceProvider;
}

private void LoadLibrary(string path)
{
var loader = PluginLoader.CreateFromAssemblyFile(path, new[] { typeof(IPlugin), typeof(PluginCapability<>), typeof(PlayerCapability<>) },
config => { config.PreferSharedTypes = true; });
var assembly = loader.LoadDefaultAssembly();

_sharedAssemblies[assembly.GetName().Name] = assembly;
}

private void LoadSharedLibraries()
{
var sharedDirectory = Directory.GetDirectories(_scriptHostConfiguration.SharedPath);
var sharedAssemblyPaths = sharedDirectory
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
.Where(File.Exists)
.ToArray();

foreach (var sharedAssemblyPath in sharedAssemblyPaths)
{
try
{
LoadLibrary(sharedAssemblyPath);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load shared assembly from {Path}", sharedAssemblyPath);
}
}
}

public void Load()
{
var pluginDirectories = Directory.GetDirectories(_scriptHostConfiguration.PluginPath);
var pluginAssemblyPaths = pluginDirectories
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
.Where(File.Exists)
.ToArray();

AssemblyLoadContext.Default.Resolving += (context, name) =>
{
if (!_loadedSharedLibs)
{
LoadSharedLibraries();
_loadedSharedLibs = true;
}

if (!_sharedAssemblies.TryGetValue(name.Name, out var assembly))
{
return null;
}

return assembly;
};

if (CoreConfig.PluginAutoLoadEnabled)
{
foreach (var path in pluginAssemblyPaths)
{
try
{
LoadPlugin(path);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
}

foreach (var plugin in _loadedPluginContexts)
{
try
{
plugin.Plugin?.OnAllPluginsLoaded(false);
}
catch (Exception e)
{
_logger.LogError(e, "OnAllPluginsLoaded failed");
}
}
}

public IEnumerable<PluginContext> GetLoadedPlugins()
{
return _loadedPluginContexts;
}

public void LoadPlugin(string path)
{
var plugin = new PluginContext(_serviceProvider, _commandManager, _scriptHostConfiguration, path,
_loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
_loadedPluginContexts.Add(plugin);
plugin.Load();
}
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Core.Hosting;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace CounterStrikeSharp.API.Core.Plugin.Host;

public class PluginManager : IPluginManager
{
private readonly HashSet<PluginContext> _loadedPluginContexts = new();
private readonly IScriptHostConfiguration _scriptHostConfiguration;
private readonly ICommandManager _commandManager;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PluginManager> _logger;
private readonly Dictionary<string, Assembly> _sharedAssemblies = new();
private bool _loadedSharedLibs = false;

public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ICommandManager commandManager,
ILogger<PluginManager> logger, IServiceProvider serviceProvider, IServiceScopeFactory serviceScopeFactory)
{
_scriptHostConfiguration = scriptHostConfiguration;
_commandManager = commandManager;
_logger = logger;
_serviceProvider = serviceProvider;
}

private void LoadLibrary(string path)
{
var loader = PluginLoader.CreateFromAssemblyFile(path, new[] { typeof(IPlugin), typeof(PluginCapability<>), typeof(PlayerCapability<>) },
config => { config.PreferSharedTypes = true; });
var assembly = loader.LoadDefaultAssembly();

_sharedAssemblies[assembly.GetName().Name] = assembly;
}

private void LoadSharedLibraries()
{
var sharedDirectory = Directory.GetDirectories(_scriptHostConfiguration.SharedPath);
var sharedAssemblyPaths = sharedDirectory
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
.Where(File.Exists)
.ToArray();

foreach (var sharedAssemblyPath in sharedAssemblyPaths)
{
try
{
LoadLibrary(sharedAssemblyPath);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load shared assembly from {Path}", sharedAssemblyPath);
}
}
}

public void Load()
{
var pluginAssemblyPaths = GetPluginsAssemblyPaths();

AssemblyLoadContext.Default.Resolving += (context, name) =>
{
if (!_loadedSharedLibs)
{
LoadSharedLibraries();
_loadedSharedLibs = true;
}

if (!_sharedAssemblies.TryGetValue(name.Name, out var assembly))
{
return null;
}

return assembly;
};

if (CoreConfig.PluginAutoLoadEnabled)
{
foreach (var path in pluginAssemblyPaths)
{
try
{
LoadPlugin(path);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
}

foreach (var plugin in _loadedPluginContexts)
{
try
{
plugin.Plugin?.OnAllPluginsLoaded(false);
}
catch (Exception e)
{
_logger.LogError(e, "OnAllPluginsLoaded failed");
}
}
}

public IEnumerable<PluginContext> GetLoadedPlugins()
{
return _loadedPluginContexts;
}

public void LoadPlugin(string path)
{
var plugin = new PluginContext(_serviceProvider, _commandManager, _scriptHostConfiguration, path,
_loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
_loadedPluginContexts.Add(plugin);
plugin.Load();
}

private string[] GetPluginsAssemblyPaths()
{
// Skip "disabled" at root level
var rootSubDirs = Directory.GetDirectories(_scriptHostConfiguration.PluginPath)
.Where(d => !Path.GetFileName(d).Equals("disabled", StringComparison.OrdinalIgnoreCase));

var pluginDirectories = new List<string>();

foreach (var subDir in rootSubDirs)
{
var stack = new Stack<string>();
stack.Push(subDir);

while (stack.Count > 0)
{
var currentDir = stack.Pop();
var dirName = Path.GetFileName(currentDir);
var expectedDll = Path.Combine(currentDir, dirName + ".dll");

if (File.Exists(expectedDll))
{
pluginDirectories.Add(currentDir);
// Stop scanning deeper in this directory
continue;
}

// Add subdirectories to stack for further scanning
foreach (var child in Directory.GetDirectories(currentDir))
stack.Push(child);
}
}

return pluginDirectories
.Select(d => Path.Combine(d, Path.GetFileName(d) + ".dll"))
.ToArray();
}
}
Loading