Merge remote-tracking branch 'origin/main' into release/v2

# Conflicts:
#	managed/CounterStrikeSharp.API/Core/Schema/Classes/CCSPlayer_MovementServices.g.cs
This commit is contained in:
roflmuffin
2025-11-06 11:12:52 +10:00
13 changed files with 9261 additions and 9615 deletions

View File

@@ -1,3 +1,13 @@
## What's Changed in v1.0.346
* chore: update server.json ([52550e3](https://github.com/roflmuffin/CounterStrikeSharp/commit/52550e31bc142ba672ac50b5b9e76b1681a6f510))
* fix: changes for 2025-11-5 Update by [@himenekocn](https://github.com/himenekocn) in [#1107](https://github.com/roflmuffin/CounterStrikeSharp/pull/1107) ([e59e9cf](https://github.com/roflmuffin/CounterStrikeSharp/commit/e59e9cf1485f6bdf26e12bce4f59f116ac68b03a))
* feat: add localization for no permission error messages & spanish language support. by [@wiruwiru](https://github.com/wiruwiru) in [#1099](https://github.com/roflmuffin/CounterStrikeSharp/pull/1099) ([1568d07](https://github.com/roflmuffin/CounterStrikeSharp/commit/1568d077e8def7688069a91ed374b70841546b26))
* feat: allow plugins to be loaded from subdirectories by [@Ravid-A](https://github.com/Ravid-A) in [#1031](https://github.com/roflmuffin/CounterStrikeSharp/pull/1031) ([55542db](https://github.com/roflmuffin/CounterStrikeSharp/commit/55542dba7c5318dfbb37b0862256f4dcde7cf0f4))
## What's Changed in v1.0.345
* fix: update linux signature for GetCSWeaponDataFromKey ([b4e83df](https://github.com/roflmuffin/CounterStrikeSharp/commit/b4e83dfb4a1a1723c08ac79ea68da4ab8a0255fd))
* feat(schema): update schema generator to use @GAMMACASE schema dumper format ([4ff2732](https://github.com/roflmuffin/CounterStrikeSharp/commit/4ff2732d8a55297c18cea6181b9022f56cd8fae3))
## What's Changed in v1.0.344
* chore(schema): update schema to latest ([f505405](https://github.com/roflmuffin/CounterStrikeSharp/commit/f50540583d079a6cf546ca590147905ba5eb2c83))
* fix(schema): allow for negative enum values in source schema file ([97957f6](https://github.com/roflmuffin/CounterStrikeSharp/commit/97957f62208fa782f89e9629ddde1944aaadd149))
@@ -757,14 +767,14 @@
* feat: add discord notify through GH actions ([59bff4f](https://github.com/roflmuffin/CounterStrikeSharp/commit/59bff4f500dfaccacb0b53584bf678c005a87598))
## What's Changed in v1.0.150
* Log exception if plugin load fails using the `load` command by [@wiesendaniel](https://github.com/wiesendaniel) in [#279](https://github.com/roflmuffin/CounterStrikeSharp/pull/279) ([a2581d8](https://github.com/roflmuffin/CounterStrikeSharp/commit/a2581d8e9116e3ab505029720719a82e4dd2fac5))
* Log exception if plugin load fails using the `load` command by [@D4n13X](https://github.com/D4n13X) in [#279](https://github.com/roflmuffin/CounterStrikeSharp/pull/279) ([a2581d8](https://github.com/roflmuffin/CounterStrikeSharp/commit/a2581d8e9116e3ab505029720719a82e4dd2fac5))
* Change TerroristsPlanned to TerroristsPlanted in RoundEndReason by [@Ravid-A](https://github.com/Ravid-A) ([e7d190a](https://github.com/roflmuffin/CounterStrikeSharp/commit/e7d190a6f74f9ccbf30e16f8a6a92b36e037e9a4))
* Menu system updates by [@B3none](https://github.com/B3none) ([5513d57](https://github.com/roflmuffin/CounterStrikeSharp/commit/5513d5710a20195922e725c9401ae1cb1291b3e8))
* fix(Offsets/Win): CCSPlayer_ItemServices.RemoveWeapons() by [@M1kep](https://github.com/M1kep) ([e5c2236](https://github.com/roflmuffin/CounterStrikeSharp/commit/e5c223699ccb300558788f86850a8e478e893156))
* Admin manager improvements by [@zonical](https://github.com/zonical) ([fa37c22](https://github.com/roflmuffin/CounterStrikeSharp/commit/fa37c222d9d8598a67cb2433c36b46a941813b14))
## New Contributors
* [@wiesendaniel](https://github.com/wiesendaniel) made their first contribution in [#279](https://github.com/roflmuffin/CounterStrikeSharp/pull/279)
* [@D4n13X](https://github.com/D4n13X) made their first contribution in [#279](https://github.com/roflmuffin/CounterStrikeSharp/pull/279)
* [@Ravid-A](https://github.com/Ravid-A) made their first contribution
* [@M1kep](https://github.com/M1kep) made their first contribution

View File

@@ -92,7 +92,7 @@
"signatures": {
"library": "server",
"windows": "48 89 5C 24 ? 57 48 83 EC ? 33 FF 4C 8B CA 8B D9",
"linux": "55 31 D2 48 89 E5 41 57 41 56 41 55 41 54 41 89 FC"
"linux": "55 31 D2 48 89 E5 41 56 41 55 41 54"
}
},
"CCSPlayer_ItemServices_GiveNamedItem": {

View File

@@ -19,5 +19,9 @@
"Target must be dead": "This command can only be used on dead players.",
"Unable to target": "You cannot target this player.",
"Cannot target bot": "Unable to perform this command on a bot.",
"More than one client matched": "More than one client matched the given pattern."
"More than one client matched": "More than one client matched the given pattern.",
"Missing permissions": "You are missing the correct permissions",
"Missing one permission": "You do not have one of the correct permissions",
"Command permission denied": "[CSS] {0} ({1}) to execute this command."
}

View File

@@ -0,0 +1,27 @@
{
"menu.button.previous": "Anterior",
"menu.button.next": "Siguiente",
"menu.button.close": "Cerrar",
"all": "todos los jugadores",
"bots": "bots",
"humans": "humanos",
"alive": "jugadores vivos",
"dead": "jugadores muertos",
"notme": "todos los jugadores excepto yo",
"ct": "jugadores CT",
"t": "jugadores T",
"spec": "espectadores",
"No matching client": "No se encontró ningún cliente que coincida.",
"No matching clients": "No se encontraron clientes que coincidan.",
"Target must be alive": "Este comando solo puede usarse en jugadores vivos.",
"Target must be dead": "Este comando solo puede usarse en jugadores muertos.",
"Unable to target": "No puedes seleccionar a este jugador.",
"Cannot target bot": "No se puede ejecutar este comando en un bot.",
"More than one client matched": "Más de un cliente coincidió con el patrón dado.",
"Missing permissions": "No tienes los permisos correctos",
"Missing one permission": "No tienes uno de los permisos correctos",
"Command permission denied": "[CSS] {0} ({1}) para ejecutar este comando."
}

View File

@@ -19,5 +19,9 @@
"Target must be dead": "{white}Bu komut yalnızca ölü oyunculara uygulanabilir.",
"Unable to target": "{white}Bu oyuncu hedeflenemez.",
"Cannot target bot": "{white}Bu komut bir bota uygulanamaz.",
"More than one client matched": "{white}Verilen kalıba birden fazla istemci eşleşti."
"More than one client matched": "{white}Verilen kalıba birden fazla istemci eşleşti.",
"Missing permissions": "{white}Doğru izinlere sahip değilsiniz",
"Missing one permission": "{white}Doğru izinlerden birine sahip değilsiniz",
"Command permission denied": "[CSS] {0} ({1}) bu komutu çalıştırmak için."
}

View File

@@ -187,7 +187,13 @@ namespace CounterStrikeSharp.API.Core
// 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);
path = Path.Combine(
_scriptHostConfiguration.RootPath,
!path.EndsWith(".dll")
? $"plugins/{path}/{Path.GetFileName(path)}.dll"
: path
);
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);

View File

@@ -65,7 +65,7 @@ public class CommandManager : ICommandManager
var info = new CommandInfo(commandInfo, caller);
var name = info.GetArg(0).ToLower();
using var temporaryCulture = new WithTemporaryCulture(caller.GetLanguage());
if (_commandDefinitions.TryGetValue(name, out var handler))
@@ -81,7 +81,7 @@ public class CommandManager : ICommandManager
// We do not need to do permission checks on commands executed from the server console.
// The server will always be allowed to execute commands (unless marked as client only like above)
if (caller != null)
if (caller != null)
{
// Do not execute command if we do not have the correct permissions.
var adminData = AdminManager.GetPlayerAdminData(caller!.AuthorizedSteamID);
@@ -120,11 +120,12 @@ public class CommandManager : ICommandManager
if (!attr.CanExecuteCommand(caller))
{
var responseStr = (attr.GetType() == typeof(RequiresPermissions)) ?
"You are missing the correct permissions" : "You do not have one of the correct permissions";
Application.Localizer["Missing permissions"] :
Application.Localizer["Missing one permission"];
var flags = attr.Permissions.Except(adminData?.GetAllFlags() ?? new HashSet<string>());
flags = flags.Except(adminData?.Groups ?? new HashSet<string>());
info.ReplyToCommand($"[CSS] {responseStr} ({string.Join(", ", flags)}) to execute this command.");
info.ReplyToCommand(Application.Localizer["Command permission denied", responseStr, string.Join(", ", flags)]);
return;
}

View File

@@ -1,218 +1,251 @@
using System.Collections.Concurrent;
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();
if (CoreConfig.PluginResolveNugetPackages)
{
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
if (TryLoadDependency(path, assembly.GetName().Name, assemblyName, out var dependency))
{
_sharedAssemblies.TryAdd(dependency.GetName().Name, dependency);
}
}
}
_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))
{
if (CoreConfig.PluginResolveNugetPackages && TryLoadExternalLibrary(name, out assembly))
{
return 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");
}
}
}
private bool TryLoadExternalLibrary(AssemblyName assemblyName, out Assembly? assembly)
{
assembly = null;
if (!TryResolveReflectionAssemblyPath(out var pluginName, out var pluginPath))
{
return false;
}
if (!TryLoadDependency(pluginPath, pluginName, assemblyName, out assembly))
{
return false;
}
return true;
}
private bool TryLoadDependency(string pluginAssemblyPath,
string pluginAssemblyName,
AssemblyName dependencyAssemblyName,
out Assembly? assembly)
{
assembly = null;
var dependencyName = dependencyAssemblyName.Name!;
if (string.IsNullOrEmpty(pluginAssemblyPath) || _sharedAssemblies.ContainsKey(dependencyName))
{
return false;
}
var resolver = new PluginContextNuGetDependencyResolver(
rootAssemblyName: pluginAssemblyName,
rootAssemblyPath: Path.GetDirectoryName(pluginAssemblyPath)!,
assemblyName: dependencyAssemblyName);
var dependencyPath = resolver.ResolvePath();
if (string.IsNullOrWhiteSpace(dependencyPath))
{
return false;
}
var loader = PluginLoader.CreateFromAssemblyFile(dependencyPath, configure: c =>
{
c.PreferSharedTypes = true;
});
assembly = loader.LoadDefaultAssembly();
_sharedAssemblies[dependencyAssemblyName.Name!] = assembly;
return true;
}
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 static bool TryResolveReflectionAssemblyPath(out string? assemblyName, out string? assemblyPath)
{
assemblyPath = null;
assemblyName = null;
if (AssemblyLoadContext.CurrentContextualReflectionContext is var reflectionContext && reflectionContext is null)
{
return false;
}
var mainAssemblyPathField = reflectionContext
.GetType()
.GetField("_mainAssemblyPath", BindingFlags.NonPublic | BindingFlags.Instance);
if (mainAssemblyPathField is null)
{
return false;
}
assemblyPath = (string)mainAssemblyPathField.GetValue(reflectionContext)!;
return !string.IsNullOrEmpty(assemblyPath);
}
}
using System.Collections.Concurrent;
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();
if (CoreConfig.PluginResolveNugetPackages)
{
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
if (TryLoadDependency(path, assembly.GetName().Name, assemblyName, out var dependency))
{
_sharedAssemblies.TryAdd(dependency.GetName().Name, dependency);
}
}
}
_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))
{
if (CoreConfig.PluginResolveNugetPackages && TryLoadExternalLibrary(name, out assembly))
{
return 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");
}
}
}
private bool TryLoadExternalLibrary(AssemblyName assemblyName, out Assembly? assembly)
{
assembly = null;
if (!TryResolveReflectionAssemblyPath(out var pluginName, out var pluginPath))
{
return false;
}
if (!TryLoadDependency(pluginPath, pluginName, assemblyName, out assembly))
{
return false;
}
return true;
}
private bool TryLoadDependency(string pluginAssemblyPath,
string pluginAssemblyName,
AssemblyName dependencyAssemblyName,
out Assembly? assembly)
{
assembly = null;
var dependencyName = dependencyAssemblyName.Name!;
if (string.IsNullOrEmpty(pluginAssemblyPath) || _sharedAssemblies.ContainsKey(dependencyName))
{
return false;
}
var resolver = new PluginContextNuGetDependencyResolver(
rootAssemblyName: pluginAssemblyName,
rootAssemblyPath: Path.GetDirectoryName(pluginAssemblyPath)!,
assemblyName: dependencyAssemblyName);
var dependencyPath = resolver.ResolvePath();
if (string.IsNullOrWhiteSpace(dependencyPath))
{
return false;
}
var loader = PluginLoader.CreateFromAssemblyFile(dependencyPath, configure: c =>
{
c.PreferSharedTypes = true;
});
assembly = loader.LoadDefaultAssembly();
_sharedAssemblies[dependencyAssemblyName.Name!] = assembly;
return true;
}
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 static bool TryResolveReflectionAssemblyPath(out string? assemblyName, out string? assemblyPath)
{
assemblyPath = null;
assemblyName = null;
if (AssemblyLoadContext.CurrentContextualReflectionContext is var reflectionContext && reflectionContext is null)
{
return false;
}
var mainAssemblyPathField = reflectionContext
.GetType()
.GetField("_mainAssemblyPath", BindingFlags.NonPublic | BindingFlags.Instance);
if (mainAssemblyPathField is null)
{
return false;
}
assemblyPath = (string)mainAssemblyPathField.GetValue(reflectionContext)!;
return !string.IsNullOrEmpty(assemblyPath);
}
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();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,9 @@ class CRecipientFilter : public IRecipientFilter
NetChannelBufType_t GetNetworkBufType(void) const override { return m_nBufType; }
bool IsInitMessage(void) const override { return m_bInitMessage; }
const CPlayerBitVec& GetRecipients(void) const override { return m_Recipients; }
CPlayerSlot GetPredictedPlayerSlot(void) const override { return m_slotPlayerExcludedDueToPrediction; }
virtual CPlayerSlot GetExcludedPlayerDueToPrediction() const { return m_slotPlayerExcludedDueToPrediction; }
void AddRecipient(CPlayerSlot slot)
{
@@ -48,9 +51,11 @@ class CRecipientFilter : public IRecipientFilter
}
protected:
NetChannelBufType_t m_nBufType;
bool m_bInitMessage;
CPlayerBitVec m_Recipients;
CPlayerSlot m_slotPlayerExcludedDueToPrediction = -1;
NetChannelBufType_t m_nBufType = BUF_DEFAULT;
bool m_bInitMessage;
bool m_bDoNotSuppressPrediction; // unused
};
class CSingleRecipientFilter : public CRecipientFilter