mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-08 00:46:34 -08:00
Compare commits
17 Commits
v1.0.342
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
851a317db5 | ||
|
|
55542dba7c | ||
|
|
33538eca60 | ||
|
|
b4e83dfb4a | ||
|
|
4ff2732d8a | ||
|
|
67d3d044fd | ||
|
|
f50540583d | ||
|
|
97957f6220 | ||
|
|
0c2f1cd078 | ||
|
|
9eefe9c61a | ||
|
|
7be329466a | ||
|
|
a21f0b5277 | ||
|
|
b4ba7d8ca0 | ||
|
|
0eb73eb348 | ||
|
|
43c1c89596 | ||
|
|
53996666f8 | ||
|
|
a8510d183d |
@@ -18,4 +18,4 @@
|
||||
"ghcr.io/devcontainers/features/dotnet": "8.0",
|
||||
"ghcr.io/devcontainers/features/node": "lts"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,3 +1,26 @@
|
||||
## 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))
|
||||
* chore: update devcontainer location ([0c2f1cd](https://github.com/roflmuffin/CounterStrikeSharp/commit/0c2f1cd078c0c54a6cf1fb402082b3f38c6303d2))
|
||||
|
||||
## What's Changed in v1.0.343
|
||||
* feat: add `BuyWithCtrl` to `AcquireMethod` enum by [@NockyCZ](https://github.com/NockyCZ) in [#697](https://github.com/roflmuffin/CounterStrikeSharp/pull/697) ([7be3294](https://github.com/roflmuffin/CounterStrikeSharp/commit/7be329466ad7d40a92608e7d6c4e2c6cd1a05a3c))
|
||||
* fix: update ConVar flag retrieval that adapts to different Source 2 SDK versions by [@Matlord93](https://github.com/Matlord93) in [#1059](https://github.com/roflmuffin/CounterStrikeSharp/pull/1059) ([a21f0b5](https://github.com/roflmuffin/CounterStrikeSharp/commit/a21f0b5277541434fa71f595d7c0c420305e9a50))
|
||||
* feat(experimental): add NuGet Dependency Resolver for Plugins by [@dxqwww](https://github.com/dxqwww) in [#1012](https://github.com/roflmuffin/CounterStrikeSharp/pull/1012) ([b4ba7d8](https://github.com/roflmuffin/CounterStrikeSharp/commit/b4ba7d8ca02bdf487ee9424f2bdb119510ab1d2c))
|
||||
* feat: add FindVirtualTable method by [@SlynxCZ](https://github.com/SlynxCZ) in [#1075](https://github.com/roflmuffin/CounterStrikeSharp/pull/1075) ([0eb73eb](https://github.com/roflmuffin/CounterStrikeSharp/commit/0eb73eb3487f7c0200b14c58b34aaa39b2408e29))
|
||||
* feat: use shared libgcc and libc++ by [@markus-wa](https://github.com/markus-wa) in [#1007](https://github.com/roflmuffin/CounterStrikeSharp/pull/1007) ([43c1c89](https://github.com/roflmuffin/CounterStrikeSharp/commit/43c1c8959605ccafa54f8fc155ef3e37016ed7f6))
|
||||
* feat: implement `TerminateSelf(string reason)` to allow plugins to safely terminate themselves by [@ELDment](https://github.com/ELDment) in [#1047](https://github.com/roflmuffin/CounterStrikeSharp/pull/1047) ([5399666](https://github.com/roflmuffin/CounterStrikeSharp/commit/53996666f8fbc99a989af5e79dae710912439115))
|
||||
* feat: add core translations & processtargetstring by [@schwarper](https://github.com/schwarper) in [#1051](https://github.com/roflmuffin/CounterStrikeSharp/pull/1051) ([a8510d1](https://github.com/roflmuffin/CounterStrikeSharp/commit/a8510d183d1edc6dd9ed97536def64a4d219c135))
|
||||
|
||||
## New Contributors
|
||||
* [@NockyCZ](https://github.com/NockyCZ) made their first contribution in [#697](https://github.com/roflmuffin/CounterStrikeSharp/pull/697)
|
||||
* [@Matlord93](https://github.com/Matlord93) made their first contribution in [#1059](https://github.com/roflmuffin/CounterStrikeSharp/pull/1059)
|
||||
* [@dxqwww](https://github.com/dxqwww) made their first contribution in [#1012](https://github.com/roflmuffin/CounterStrikeSharp/pull/1012)
|
||||
|
||||
## What's Changed in v1.0.342
|
||||
* fix: update Sigs & CTakeDamageResult & EmitSound_t by [@himenekocn](https://github.com/himenekocn) in [#1071](https://github.com/roflmuffin/CounterStrikeSharp/pull/1071) ([34598dd](https://github.com/roflmuffin/CounterStrikeSharp/commit/34598dd56ea2e9e18229185dc225db00a336bb5d))
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true,
|
||||
"PluginAutoLoadEnabled": true,
|
||||
"PluginResolveNugetPackages": false,
|
||||
"ServerLanguage": "en",
|
||||
"UnlockConCommands": true,
|
||||
"UnlockConVars": true,
|
||||
|
||||
@@ -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": {
|
||||
|
||||
23
configs/addons/counterstrikesharp/lang/en.json
Normal file
23
configs/addons/counterstrikesharp/lang/en.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"menu.button.previous": "Prev",
|
||||
"menu.button.next": "Next",
|
||||
"menu.button.close": "Close",
|
||||
|
||||
"all": "all players",
|
||||
"bots": "bots",
|
||||
"humans": "humans",
|
||||
"alive": "alive players",
|
||||
"dead": "dead players",
|
||||
"notme": "all players except self",
|
||||
"ct": "ct players",
|
||||
"t": "t players",
|
||||
"spec": "spec players",
|
||||
|
||||
"No matching client": "No matching client was found.",
|
||||
"No matching clients": "No matching clients were found.",
|
||||
"Target must be alive": "This command can only be used on alive players.",
|
||||
"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."
|
||||
}
|
||||
23
configs/addons/counterstrikesharp/lang/tr.json
Normal file
23
configs/addons/counterstrikesharp/lang/tr.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"menu.button.previous": "Geri",
|
||||
"menu.button.next": "İleri",
|
||||
"menu.button.close": "Çıkış",
|
||||
|
||||
"all": "tüm oyuncular",
|
||||
"bots": "botlar",
|
||||
"humans": "insanlar",
|
||||
"alive": "hayatta olan oyuncular",
|
||||
"dead": "ölü oyuncular",
|
||||
"notme": "kendi hariç tüm oyuncular",
|
||||
"ct": "CT oyuncular",
|
||||
"t": "T oyuncular",
|
||||
"spec": "izleyici oyuncular",
|
||||
|
||||
"No matching client": "{white}Eşleşen bir istemci bulunamadı.",
|
||||
"No matching clients": "{white}Eşleşen istemciler bulunamadı.",
|
||||
"Target must be alive": "{white}Bu komut yalnızca hayatta olan oyunculara uygulanabilir.",
|
||||
"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."
|
||||
}
|
||||
@@ -5,6 +5,9 @@ description: How to add inter-plugin communication to CounterStrikeSharp plugins
|
||||
|
||||
# Shared Plugin API
|
||||
|
||||
> [!NOTE]
|
||||
> **New (experimental)**: You can now resolve plugin dependencies directly from your local **NuGet packages cache** instead of copying every DLL into the `shared/` folder. See **Dependency Resolution** below. This feature **disabled by default.**
|
||||
|
||||
How to expose and use shared plugin APIs between multiple plugins.
|
||||
|
||||
## Creating a Contract Library
|
||||
@@ -65,3 +68,36 @@ balance.Add(500);
|
||||
```
|
||||
|
||||
This value _MUST_ be checked for null, as if there are no plugins providing implementations for a given capability, this method will return null, and you must handle this flow in your plugin.
|
||||
|
||||
|
||||
## Dependency Resolution
|
||||
|
||||
CounterStrikeSharp supports two complementary ways to resolve **external** assemblies used by your plugins and shared contracts:
|
||||
|
||||
1. **Shared Folder Resolution (manual)**: copy dependency DLLs into `shared/<PackageName>/<Assembly>.dll`.
|
||||
2. **NuGet Dependency Resolver (auto)**: when enabled, resolves missing assemblies from the local **NuGet packages root**
|
||||
|
||||
### Enabling the NuGet Resolver
|
||||
|
||||
Add the following property to your core config (disabled by default):
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"PluginResolveNugetPackages": true
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The engine looks for assemblies in the NuGet cache defined by the `NUGET_PACKAGES` environment variable, or falls back to the default user cache (e.g., `~/.nuget/packages` on Linux/macOS, `%UserProfile%\.nuget\packages` on Windows).
|
||||
|
||||
### Dependencies Resolution Order
|
||||
|
||||
When the NuGet resolver is **enabled**, resolution proceeds in this general order:
|
||||
|
||||
1. **Plugins directory** (in-place assemblies)
|
||||
2. `shared/` **folder** (existing shared assemblies mechanism)
|
||||
3. **NuGet cache** (auto-resolver)
|
||||
|
||||
This lets you keep proven `shared/` workflows while reducing manual copying for common NuGet dependencies.
|
||||
@@ -14,7 +14,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics -v -fvisibility=default")
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs=libprotobuf.a")
|
||||
|
||||
set(
|
||||
|
||||
@@ -11,8 +11,10 @@ using CounterStrikeSharp.API.Core.Plugin;
|
||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
|
||||
@@ -44,7 +46,11 @@ public static class Bootstrap
|
||||
services.AddSingleton<IPluginManager, PluginManager>();
|
||||
services.AddSingleton<IPlayerLanguageManager, PlayerLanguageManager>();
|
||||
services.AddScoped<IPluginContextQueryHandler, PluginContextQueryHandler>();
|
||||
services.AddSingleton<ICommandManager, CommandManager>();
|
||||
services.AddSingleton<ICommandManager, CommandManager>();
|
||||
|
||||
services.TryAddSingleton<IStringLocalizerFactory, CoreJsonStringLocalizerFactory>();
|
||||
services.TryAddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||
services.TryAddTransient(typeof(IStringLocalizer), typeof(StringLocalizer));
|
||||
|
||||
services.Scan(i => i.FromCallingAssembly()
|
||||
.AddClasses(c => c.AssignableTo<IStartupService>())
|
||||
@@ -71,4 +77,4 @@ public static class Bootstrap
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using CounterStrikeSharp.API.Modules.Utils;
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
public class NativeAPI {
|
||||
|
||||
|
||||
public static bool AddListener(string name, InputArgument callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -1459,6 +1459,18 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr FindVirtualTable(string modulepath, string vtablename){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(modulepath);
|
||||
ScriptContext.GlobalScriptContext.Push(vtablename);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xB4A0F13C);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetNetworkVectorSize(IntPtr vec){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core.Commands;
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using CounterStrikeSharp.API.Core.Plugin;
|
||||
@@ -28,6 +29,7 @@ using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Menu;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core
|
||||
@@ -35,6 +37,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
public sealed class Application
|
||||
{
|
||||
private static Application _instance = null!;
|
||||
public static IStringLocalizer Localizer => Instance._localizer;
|
||||
|
||||
public ILogger Logger { get; }
|
||||
|
||||
public static Application Instance => _instance!;
|
||||
@@ -48,11 +52,12 @@ namespace CounterStrikeSharp.API.Core
|
||||
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
|
||||
private readonly IPlayerLanguageManager _playerLanguageManager;
|
||||
private readonly ICommandManager _commandManager;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
|
||||
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
|
||||
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
|
||||
IPluginContextQueryHandler pluginContextQueryHandler, IPlayerLanguageManager playerLanguageManager,
|
||||
ICommandManager commandManager)
|
||||
ICommandManager commandManager, IStringLocalizer localizer)
|
||||
{
|
||||
Logger = loggerFactory.CreateLogger("Core");
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
@@ -62,11 +67,28 @@ namespace CounterStrikeSharp.API.Core
|
||||
_pluginContextQueryHandler = pluginContextQueryHandler;
|
||||
_playerLanguageManager = playerLanguageManager;
|
||||
_commandManager = commandManager;
|
||||
_localizer = localizer;
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||
{
|
||||
if ((e.ExceptionObject as Exception) is PluginTerminationException pluginEx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||
{
|
||||
if (e.Exception.InnerExceptions.Any(ex => ex is PluginTerminationException))
|
||||
{
|
||||
e.SetObserved();
|
||||
}
|
||||
};
|
||||
|
||||
Logger.LogInformation("CounterStrikeSharp is starting up...");
|
||||
|
||||
_coreConfig.Load();
|
||||
@@ -122,123 +144,135 @@ namespace CounterStrikeSharp.API.Core
|
||||
switch (info.GetArg(1))
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.");
|
||||
|
||||
foreach (var plugin in _pluginManager.GetLoadedPlugins())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin?.ModuleName ?? "Unknown",
|
||||
plugin.Plugin?.ModuleVersion ?? "Unknown");
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Plugin.ModuleDescription);
|
||||
}
|
||||
|
||||
info.ReplyToCommand(sb.ToString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n");
|
||||
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.");
|
||||
|
||||
foreach (var plugin in _pluginManager.GetLoadedPlugins())
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
|
||||
plugin.State.ToString().ToUpper(), plugin.Plugin?.ModuleName ?? "Unknown",
|
||||
plugin.Plugin?.ModuleVersion ?? "Unknown");
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleAuthor))
|
||||
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
|
||||
if (!string.IsNullOrEmpty(plugin.Plugin?.ModuleDescription))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.Append(" ");
|
||||
sb.Append(plugin.Plugin.ModuleDescription);
|
||||
}
|
||||
|
||||
if (plugin.State == PluginState.Unloaded && !string.IsNullOrEmpty(plugin.TerminationReason))
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(" Termination Reason: {0}", plugin.TerminationReason);
|
||||
}
|
||||
|
||||
info.ReplyToCommand(sb.ToString());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
case "start":
|
||||
case "load":
|
||||
{
|
||||
try
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// 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.GetFileName(path)}.dll"
|
||||
: path
|
||||
);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_pluginManager.LoadPlugin(path);
|
||||
plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\"");
|
||||
Logger.LogError(e, "Could not load plugin \"{Path}\"", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
info.ReplyToCommand($"Could not load plugin \"{path}\"");
|
||||
Logger.LogError(e, "Could not load plugin \"{Path}\"", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin.Load(false);
|
||||
plugin.Plugin.OnAllPluginsLoaded(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "stop":
|
||||
case "unload":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n");
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
string path;
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath,
|
||||
!pluginIdentifier.EndsWith(".dll") ? $"plugins/{pluginIdentifier}/{pluginIdentifier}.dll" : pluginIdentifier);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier)
|
||||
?? _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
string path;
|
||||
path = Path.Combine(_scriptHostConfiguration.RootPath,
|
||||
!pluginIdentifier.EndsWith(".dll") ? $"plugins/{pluginIdentifier}/{pluginIdentifier}.dll" : pluginIdentifier);
|
||||
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier)
|
||||
?? _pluginContextQueryHandler.FindPluginByModulePath(path);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
case "restart":
|
||||
case "reload":
|
||||
{
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n");
|
||||
if (info.ArgCount < 3)
|
||||
{
|
||||
info.ReplyToCommand(
|
||||
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n");
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
plugin.Plugin.OnAllPluginsLoaded(true);
|
||||
break;
|
||||
}
|
||||
|
||||
var pluginIdentifier = info.GetArg(2);
|
||||
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\"");
|
||||
break;
|
||||
}
|
||||
|
||||
plugin.Unload(true);
|
||||
plugin.Load(true);
|
||||
plugin.Plugin.OnAllPluginsLoaded(true);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
|
||||
" list - List all plugins currently loaded.\n" +
|
||||
|
||||
@@ -53,20 +53,27 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public abstract string ModuleName { get; }
|
||||
public abstract string ModuleVersion { get; }
|
||||
|
||||
|
||||
public virtual string ModuleAuthor { get; }
|
||||
|
||||
|
||||
public virtual string ModuleDescription { get; }
|
||||
|
||||
public string ModulePath { get; set; }
|
||||
|
||||
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
|
||||
public ICommandManager CommandManager { get; set; }
|
||||
|
||||
public IStringLocalizer Localizer { get; set; }
|
||||
|
||||
|
||||
internal Plugin.ISelfPluginControl SelfControl { get; set; }
|
||||
|
||||
public void TerminateSelf(string reason)
|
||||
{
|
||||
SelfControl?.TerminateSelf(reason);
|
||||
}
|
||||
|
||||
public virtual void Load(bool hotReload)
|
||||
{
|
||||
}
|
||||
@@ -74,7 +81,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public virtual void Unload(bool hotReload)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnAllPluginsLoaded(bool hotReload)
|
||||
{
|
||||
}
|
||||
@@ -116,7 +123,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
|
||||
public readonly Dictionary<Delegate, CallbackSubscriber> Handlers =
|
||||
new Dictionary<Delegate, CallbackSubscriber>();
|
||||
|
||||
|
||||
public readonly Dictionary<Delegate, CallbackSubscriber> CommandListeners =
|
||||
new Dictionary<Delegate, CallbackSubscriber>();
|
||||
|
||||
@@ -132,7 +139,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public readonly List<CommandDefinition> CommandDefinitions = new List<CommandDefinition>();
|
||||
|
||||
public readonly List<Timer> Timers = new List<Timer>();
|
||||
|
||||
|
||||
public delegate HookResult GameEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent;
|
||||
|
||||
private void RegisterEventHandlerInternal<T>(string name, GameEventHandler<T> handler, bool post)
|
||||
@@ -156,7 +163,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()?.Name;
|
||||
RegisterEventHandlerInternal(name, handler, hookMode == HookMode.Post);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// De-registers a game event handler.
|
||||
/// </summary>
|
||||
@@ -164,7 +171,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
public void DeregisterEventHandler<T>(GameEventHandler<T> handler, HookMode hookMode = HookMode.Post) where T : GameEvent
|
||||
{
|
||||
var name = typeof(T).GetCustomAttribute<EventNameAttribute>()!.Name;
|
||||
|
||||
|
||||
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
|
||||
|
||||
NativeAPI.UnhookEvent(name, subscriber.GetInputArgument(), hookMode == HookMode.Post);
|
||||
@@ -195,7 +202,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
CommandDefinitions.Add(definition);
|
||||
CommandManager.RegisterCommand(definition);
|
||||
}
|
||||
|
||||
|
||||
private void AddCommand(CommandDefinition definition)
|
||||
{
|
||||
CommandDefinitions.Add(definition);
|
||||
@@ -319,9 +326,9 @@ namespace CounterStrikeSharp.API.Core
|
||||
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
|
||||
nameof(T));
|
||||
}
|
||||
|
||||
|
||||
if (!Listeners.TryGetValue(handler, out var subscriber)) return;
|
||||
|
||||
|
||||
NativeAPI.RemoveListener(listenerName, subscriber.GetInputArgument());
|
||||
FunctionReference.Remove(subscriber.GetReferenceIdentifier());
|
||||
Listeners.Remove(handler);
|
||||
@@ -408,7 +415,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
.Where(method =>
|
||||
method.GetParameters().FirstOrDefault()?.ParameterType.IsSubclassOf(typeof(GameEvent)) == true)
|
||||
.ToArray();
|
||||
|
||||
|
||||
var listenerHandlers = methods
|
||||
.Where(method => method.GetCustomAttribute(typeof(ListenerHandlerAttribute<>)) != null)
|
||||
.ToArray();
|
||||
@@ -440,7 +447,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
throw new ArgumentException("Listener of type T is invalid and does not have a name attribute",
|
||||
listenerType.Name);
|
||||
|
||||
var listenerDelegate = Delegate.CreateDelegate(listenerType, instance, listnerHandler);
|
||||
var listenerDelegate = Delegate.CreateDelegate(listenerType, instance, listnerHandler);
|
||||
|
||||
registerListener.MakeGenericMethod(listenerType).Invoke(this, [listenerDelegate]);
|
||||
}
|
||||
@@ -502,29 +509,29 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
var convars = type
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Where(prop => prop.FieldType.IsGenericType &&
|
||||
.Where(prop => prop.FieldType.IsGenericType &&
|
||||
prop.FieldType.GetGenericTypeDefinition() == typeof(FakeConVar<>));
|
||||
|
||||
|
||||
foreach (var prop in convars)
|
||||
{
|
||||
object propValue = prop.GetValue(instance); // FakeConvar<?> instance
|
||||
var propValueType = prop.FieldType.GenericTypeArguments[0];
|
||||
var name = prop.FieldType.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance)
|
||||
.GetValue(propValue);
|
||||
|
||||
|
||||
var description = prop.FieldType.GetProperty("Description", BindingFlags.Public | BindingFlags.Instance)
|
||||
.GetValue(propValue);
|
||||
|
||||
MethodInfo executeCommandMethod = prop.FieldType
|
||||
.GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
this.AddCommand((string)name, (string) description, (caller, command) =>
|
||||
|
||||
this.AddCommand((string)name, (string)description, (caller, command) =>
|
||||
{
|
||||
executeCommandMethod.Invoke(propValue, new object[] {caller, command});
|
||||
executeCommandMethod.Invoke(propValue, new object[] { caller, command });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to bind a fake ConVar to a plugin command. Only required for ConVars that are not public properties of the plugin class.
|
||||
/// </summary>
|
||||
@@ -549,7 +556,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
NativeAPI.HookEntityOutput(classname, outputName, subscriber.GetInputArgument(), mode);
|
||||
EntityOutputHooks[handler] = subscriber;
|
||||
}
|
||||
|
||||
|
||||
public void HookUserMessage(int messageId, UserMessage.UserMessageHandler handler, HookMode mode = HookMode.Pre)
|
||||
{
|
||||
var subscriber = new CallbackSubscriber(handler, handler,
|
||||
@@ -558,7 +565,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
NativeAPI.HookUsermessage(messageId, subscriber.GetInputArgument(), mode);
|
||||
Handlers[handler] = subscriber;
|
||||
}
|
||||
|
||||
|
||||
public void UnhookUserMessage(int messageId, UserMessage.UserMessageHandler handler, HookMode mode = HookMode.Pre)
|
||||
{
|
||||
if (!Handlers.TryGetValue(handler, out var subscriber)) return;
|
||||
@@ -641,7 +648,7 @@ namespace CounterStrikeSharp.API.Core
|
||||
{
|
||||
subscriber.Dispose();
|
||||
}
|
||||
|
||||
|
||||
foreach (var subscriber in CommandListeners.Values)
|
||||
{
|
||||
subscriber.Dispose();
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace CounterStrikeSharp.API.Core
|
||||
[JsonPropertyName("PluginAutoLoadEnabled")]
|
||||
public bool PluginAutoLoadEnabled { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("PluginResolveNugetPackages")]
|
||||
public bool PluginResolveNugetPackages { get; set; }
|
||||
|
||||
[JsonPropertyName("ServerLanguage")]
|
||||
public string ServerLanguage { get; set; } = "en";
|
||||
|
||||
@@ -115,6 +118,8 @@ namespace CounterStrikeSharp.API.Core
|
||||
/// </summary>
|
||||
public static bool PluginAutoLoadEnabled => _coreConfig.PluginAutoLoadEnabled;
|
||||
|
||||
public static bool PluginResolveNugetPackages => _coreConfig.PluginResolveNugetPackages;
|
||||
|
||||
public static string ServerLanguage => _coreConfig.ServerLanguage;
|
||||
|
||||
public static bool UnlockConCommands => _coreConfig.UnlockConCommands;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -107,6 +107,11 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if ((e.InnerException ?? e) is Plugin.PluginTerminationException pluginEx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Application.Instance.Logger.LogError(e, "Error invoking callback");
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -34,5 +34,11 @@ public interface IScriptHostConfiguration
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp game data.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/gamedata
|
||||
/// </summary>
|
||||
string GameDataPath { get; }
|
||||
}
|
||||
string GameDataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the directory that contains CounterStrikeSharp translation files.
|
||||
/// e.g. /game/csgo/addons/counterstrikesharp/lang
|
||||
/// </summary>
|
||||
string LanguagePath { get; }
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ internal sealed class ScriptHostConfiguration : IScriptHostConfiguration
|
||||
public string PluginPath { get; }
|
||||
public string SharedPath { get; }
|
||||
public string ConfigsPath { get; }
|
||||
public string GameDataPath { get; }
|
||||
public string GameDataPath { get; }
|
||||
public string LanguagePath { get; }
|
||||
|
||||
public ScriptHostConfiguration(IHostEnvironment hostEnvironment)
|
||||
{
|
||||
@@ -17,6 +18,7 @@ internal sealed class ScriptHostConfiguration : IScriptHostConfiguration
|
||||
SharedPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "shared" });
|
||||
PluginPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "plugins" });
|
||||
ConfigsPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "configs" });
|
||||
GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" });
|
||||
GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" });
|
||||
LanguagePath = Path.Join(new[] { hostEnvironment.ContentRootPath, "lang" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public interface IPluginContextDependencyResolver
|
||||
{
|
||||
public string? ResolvePath();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyModel;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin.Host;
|
||||
|
||||
public class PluginContextNuGetDependencyResolver : IPluginContextDependencyResolver
|
||||
{
|
||||
private const string NuGetPackagesEnvName = "NUGET_PACKAGES";
|
||||
|
||||
private readonly string _rootAssemblyName;
|
||||
private readonly string _rootAssemblyPath;
|
||||
private readonly AssemblyName _assemblyName;
|
||||
|
||||
public PluginContextNuGetDependencyResolver(string rootAssemblyName,
|
||||
string rootAssemblyPath,
|
||||
AssemblyName assemblyName)
|
||||
{
|
||||
_rootAssemblyName = rootAssemblyName;
|
||||
_rootAssemblyPath = rootAssemblyPath;
|
||||
_assemblyName = assemblyName;
|
||||
}
|
||||
|
||||
public string? ResolvePath()
|
||||
{
|
||||
var packagesRoot = GetNuGetPackagesRoot();
|
||||
if (string.IsNullOrWhiteSpace(packagesRoot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var packageName = _assemblyName.Name;
|
||||
if (string.IsNullOrWhiteSpace(packageName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dependenciesPath = Path.Combine(_rootAssemblyPath, $"{_rootAssemblyName}.deps.json");
|
||||
if (!File.Exists(dependenciesPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var dependenciesStream = File.OpenRead(dependenciesPath);
|
||||
|
||||
using var dependencyReader = new DependencyContextJsonReader();
|
||||
var context = dependencyReader.Read(dependenciesStream);
|
||||
|
||||
var dependencyPath = string.Empty;
|
||||
foreach (var dependency in context.RuntimeLibraries)
|
||||
{
|
||||
if (dependency.Name == packageName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dependency.Path) || !dependency.RuntimeAssemblyGroups.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var runtimeAssemblyGroup = dependency.RuntimeAssemblyGroups[0];
|
||||
if (!runtimeAssemblyGroup.AssetPaths.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
dependencyPath = Path.Combine(dependency.Path, runtimeAssemblyGroup.AssetPaths[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dependencyPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Path.Combine(packagesRoot, dependencyPath);
|
||||
}
|
||||
|
||||
private static string? GetNuGetPackagesRoot()
|
||||
{
|
||||
var nugetPath = Environment.GetEnvironmentVariable(NuGetPackagesEnvName);
|
||||
if (!string.IsNullOrWhiteSpace(nugetPath) && Directory.Exists(nugetPath))
|
||||
{
|
||||
return nugetPath;
|
||||
}
|
||||
|
||||
var userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
if (string.IsNullOrWhiteSpace(userProfilePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Path.Combine(userProfilePath, ".nuget", "packages");
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,251 @@
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,17 @@ using Microsoft.Extensions.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin
|
||||
{
|
||||
public class PluginContext : IPluginContext
|
||||
public interface ISelfPluginControl
|
||||
{
|
||||
void TerminateSelf(string reason);
|
||||
}
|
||||
|
||||
public class PluginContext : IPluginContext, ISelfPluginControl
|
||||
{
|
||||
public PluginState State { get; set; } = PluginState.Unregistered;
|
||||
public IPlugin Plugin { get; private set; }
|
||||
@@ -50,10 +57,12 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
private readonly string _path;
|
||||
private readonly FileSystemWatcher _fileWatcher;
|
||||
private readonly IServiceProvider _applicationServiceProvider;
|
||||
|
||||
|
||||
public string FilePath => _path;
|
||||
private IServiceScope _serviceScope;
|
||||
|
||||
public string TerminationReason { get; private set; }
|
||||
|
||||
// TOOD: ServiceCollection
|
||||
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
|
||||
|
||||
@@ -65,7 +74,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
_hostConfiguration = hostConfiguration;
|
||||
_path = path;
|
||||
PluginId = id;
|
||||
|
||||
|
||||
Loader = PluginLoader.CreateFromAssemblyFile(path,
|
||||
new[]
|
||||
{
|
||||
@@ -77,7 +86,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
config.IsUnloadable = true;
|
||||
config.PreferSharedTypes = true;
|
||||
});
|
||||
|
||||
|
||||
if (CoreConfig.PluginHotReloadEnabled)
|
||||
{
|
||||
_fileWatcher = new FileSystemWatcher
|
||||
@@ -113,14 +122,14 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Load(hotReload: true);
|
||||
Plugin.OnAllPluginsLoaded(hotReload: true);
|
||||
});
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Load(bool hotReload = false)
|
||||
{
|
||||
if (State == PluginState.Loaded) return;
|
||||
|
||||
|
||||
using (Loader.EnterContextualReflection())
|
||||
{
|
||||
var defaultAssembly = Loader.LoadDefaultAssembly();
|
||||
@@ -178,7 +187,7 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
serviceCollection.AddScoped<ICommandManager>(c => _commandManager);
|
||||
serviceCollection.DecorateSingleton<ICommandManager, PluginCommandManagerDecorator>();
|
||||
|
||||
@@ -215,7 +224,33 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
|
||||
|
||||
Plugin.InitializeConfig(Plugin, pluginType);
|
||||
Plugin.Load(hotReload);
|
||||
|
||||
if (Plugin is BasePlugin basePlugin)
|
||||
{
|
||||
basePlugin.SelfControl = this;
|
||||
}
|
||||
|
||||
this.TerminationReason = string.Empty;
|
||||
try
|
||||
{
|
||||
Plugin.Load(hotReload);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if ((ex.InnerException ?? ex) is PluginTerminationException pluginEx)
|
||||
{
|
||||
_logger.LogCritical("Terminating plugin {Name} with reason: {Reason}", Plugin.ModuleName, pluginEx.TerminationReason);
|
||||
this.TerminationReason = pluginEx.TerminationReason;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load plugin {Name}", Plugin.ModuleName);
|
||||
this.TerminationReason = ex.Message ?? "Unknown";
|
||||
}
|
||||
|
||||
Unload(hotReload);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished loading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
@@ -233,12 +268,60 @@ namespace CounterStrikeSharp.API.Core.Plugin
|
||||
|
||||
_logger.LogInformation("Unloading plugin {Name}", Plugin.ModuleName);
|
||||
|
||||
Plugin.Unload(hotReload);
|
||||
|
||||
Plugin.Dispose();
|
||||
_serviceScope.Dispose();
|
||||
try
|
||||
{
|
||||
Plugin.Unload(hotReload);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_logger.LogError("Failed to unload {Name} during error recovery, forcing cleanup", Plugin.ModuleName);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Plugin.Dispose();
|
||||
_serviceScope.Dispose();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Finished unloading plugin {Name}", cachedName);
|
||||
}
|
||||
|
||||
public void TerminateWithReason(string reason)
|
||||
{
|
||||
this.TerminationReason = reason;
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case PluginState.Unloaded:
|
||||
case PluginState.Loading:
|
||||
break;
|
||||
case PluginState.Loaded:
|
||||
_logger.LogInformation("Terminating plugin {Name} with reason: {Reason}", Plugin.ModuleName, reason);
|
||||
Unload(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Force execution flow interruption via globally-handled exception to prevent stack unwinding
|
||||
throw new PluginTerminationException(reason);
|
||||
}
|
||||
|
||||
void ISelfPluginControl.TerminateSelf(string reason)
|
||||
{
|
||||
if (State != PluginState.Unloaded)
|
||||
{
|
||||
if (Thread.CurrentThread.IsThreadPoolThread)
|
||||
{
|
||||
Server.NextFrame(() => TerminateWithReason(reason));
|
||||
}
|
||||
else
|
||||
{
|
||||
TerminateWithReason(reason);
|
||||
}
|
||||
|
||||
// **Failsafe mechanism** ensures execution termination
|
||||
// Prevents control flow leakage back to plugin execution context
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Plugin
|
||||
{
|
||||
public class PluginTerminationException : Exception
|
||||
{
|
||||
public string PluginName { get; }
|
||||
public string TerminationReason { get; }
|
||||
|
||||
public PluginTerminationException(string reason) : base($"Plugin terminated: {reason}")
|
||||
{
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string pluginName, string reason) : base($"Plugin '{pluginName}' terminated: {reason}")
|
||||
{
|
||||
PluginName = pluginName;
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string reason, Exception innerException) : base($"Plugin terminated: {reason}", innerException)
|
||||
{
|
||||
TerminationReason = reason;
|
||||
}
|
||||
|
||||
public PluginTerminationException(string pluginName, string reason, Exception innerException) : base($"Plugin '{pluginName}' terminated: {reason}", innerException)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
TerminationReason = reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@ public partial class CCSGameRules : CTeamplayRules
|
||||
[SchemaMember("CCSGameRules", "m_bMatchWaitingForResume")]
|
||||
public ref bool MatchWaitingForResume => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bMatchWaitingForResume");
|
||||
|
||||
// m_iFreezeTime
|
||||
[SchemaMember("CCSGameRules", "m_iFreezeTime")]
|
||||
public ref Int32 FreezeTime => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_iFreezeTime");
|
||||
|
||||
// m_iRoundTime
|
||||
[SchemaMember("CCSGameRules", "m_iRoundTime")]
|
||||
public ref Int32 RoundTime => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_iRoundTime");
|
||||
@@ -350,10 +354,6 @@ public partial class CCSGameRules : CTeamplayRules
|
||||
[SchemaMember("CCSGameRules", "m_endMatchOnThink")]
|
||||
public ref bool EndMatchOnThink => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_endMatchOnThink");
|
||||
|
||||
// m_iFreezeTime
|
||||
[SchemaMember("CCSGameRules", "m_iFreezeTime")]
|
||||
public ref Int32 FreezeTime => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_iFreezeTime");
|
||||
|
||||
// m_iNumTerrorist
|
||||
[SchemaMember("CCSGameRules", "m_iNumTerrorist")]
|
||||
public ref Int32 NumTerrorist => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_iNumTerrorist");
|
||||
|
||||
@@ -24,6 +24,6 @@ public partial class CCSPlayer_PingServices : CPlayerPawnComponent
|
||||
|
||||
// m_hPlayerPing
|
||||
[SchemaMember("CCSPlayer_PingServices", "m_hPlayerPing")]
|
||||
public CHandle<CBaseEntity> PlayerPing => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CCSPlayer_PingServices", "m_hPlayerPing");
|
||||
public CHandle<CPlayerPing> PlayerPing => Schema.GetDeclaredClass<CHandle<CPlayerPing>>(this.Handle, "CCSPlayer_PingServices", "m_hPlayerPing");
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using CounterStrikeSharp;
|
||||
using CounterStrikeSharp.API.Modules.Events;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
using CounterStrikeSharp.API.Core.Attributes;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CFuncRetakeBarrier : CDynamicProp
|
||||
{
|
||||
public CFuncRetakeBarrier (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,10 @@ public partial class CPropDoorRotating : CBasePropDoor
|
||||
[SchemaMember("CPropDoorRotating", "m_eCurrentOpenDirection")]
|
||||
public ref PropDoorRotatingOpenDirection_e CurrentOpenDirection => ref Schema.GetRef<PropDoorRotatingOpenDirection_e>(this.Handle, "CPropDoorRotating", "m_eCurrentOpenDirection");
|
||||
|
||||
// m_eDefaultCheckDirection
|
||||
[SchemaMember("CPropDoorRotating", "m_eDefaultCheckDirection")]
|
||||
public ref doorCheck_e DefaultCheckDirection => ref Schema.GetRef<doorCheck_e>(this.Handle, "CPropDoorRotating", "m_eDefaultCheckDirection");
|
||||
|
||||
// m_flAjarAngle
|
||||
[SchemaMember("CPropDoorRotating", "m_flAjarAngle")]
|
||||
public ref float AjarAngle => ref Schema.GetRef<float>(this.Handle, "CPropDoorRotating", "m_flAjarAngle");
|
||||
|
||||
@@ -38,4 +38,8 @@ public partial class CRetakeGameRules : NativeObject
|
||||
[SchemaMember("CRetakeGameRules", "m_iBombSite")]
|
||||
public ref Int32 BombSite => ref Schema.GetRef<Int32>(this.Handle, "CRetakeGameRules", "m_iBombSite");
|
||||
|
||||
// m_hBombPlanter
|
||||
[SchemaMember("CRetakeGameRules", "m_hBombPlanter")]
|
||||
public CHandle<CCSPlayerPawn> BombPlanter => Schema.GetDeclaredClass<CHandle<CCSPlayerPawn>>(this.Handle, "CRetakeGameRules", "m_hBombPlanter");
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ public partial class CTakeDamageResult : NativeObject
|
||||
[SchemaMember("CTakeDamageResult", "m_nHealthLost")]
|
||||
public ref Int32 HealthLost => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageResult", "m_nHealthLost");
|
||||
|
||||
// m_nHealthBefore
|
||||
[SchemaMember("CTakeDamageResult", "m_nHealthBefore")]
|
||||
public ref Int32 HealthBefore => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageResult", "m_nHealthBefore");
|
||||
|
||||
// m_nDamageDealt
|
||||
[SchemaMember("CTakeDamageResult", "m_nDamageDealt")]
|
||||
public ref Int32 DamageDealt => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageResult", "m_nDamageDealt");
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public enum PulseTestEnumColor_t : uint
|
||||
{
|
||||
BLACK = 0x0,
|
||||
WHITE = 0x1,
|
||||
RED = 0x2,
|
||||
GREEN = 0x3,
|
||||
BLUE = 0x4,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// <auto-generated />
|
||||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public enum PulseTestEnumShape_t : uint
|
||||
{
|
||||
CIRCLE = 0x64,
|
||||
SQUARE = 0xC8,
|
||||
TRIANGLE = 0x12C,
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public enum loadout_slot_t : uint
|
||||
{
|
||||
LOADOUT_SLOT_PROMOTED = 0xFFFFFFFF,
|
||||
LOADOUT_SLOT_PROMOTED = 0xFFFFFFFE,
|
||||
LOADOUT_SLOT_INVALID = 0xFFFFFFFF,
|
||||
LOADOUT_SLOT_MELEE = 0x0,
|
||||
LOADOUT_SLOT_C4 = 0x1,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using CounterStrikeSharp.API.Core.Hosting;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core.Translations;
|
||||
|
||||
public class CoreJsonStringLocalizerFactory : IStringLocalizerFactory
|
||||
{
|
||||
private IScriptHostConfiguration _scriptHostConfiguration;
|
||||
|
||||
public CoreJsonStringLocalizerFactory(IScriptHostConfiguration scriptHostConfiguration)
|
||||
{
|
||||
_scriptHostConfiguration = scriptHostConfiguration;
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(Type resourceSource)
|
||||
{
|
||||
return new JsonStringLocalizer(_scriptHostConfiguration.LanguagePath);
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(string baseName, string location)
|
||||
{
|
||||
return new JsonStringLocalizer(_scriptHostConfiguration.LanguagePath);
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class JsonStringLocalizer : IStringLocalizer
|
||||
result = _resourceManager.GetFallbackString(name);
|
||||
}
|
||||
|
||||
// Fallback to the default culture (en-US) if the resource is not found for the current culture.
|
||||
// Fallback to the default culture (whatever is in core.json) if the resource is not found for the current culture.
|
||||
if (result == null && !culture.Equals(CultureInfo.DefaultThreadCurrentUICulture))
|
||||
{
|
||||
result = _resourceManager.GetString(name, CultureInfo.DefaultThreadCurrentUICulture!);
|
||||
@@ -119,4 +119,4 @@ public class JsonStringLocalizer : IStringLocalizer
|
||||
|
||||
return resourceNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies filters for processing command targets.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ProcessTargetFilterFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// No filter applied.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Only allow alive players as targets.
|
||||
/// </summary>
|
||||
FilterAlive = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Only allow dead players as targets.
|
||||
/// </summary>
|
||||
FilterDead = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Filter out targets that the command issuer cannot target due to immunity rules.
|
||||
/// </summary>
|
||||
FilterNoImmunity = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Do not allow multiple target patterns like @all, @ct, etc.
|
||||
/// </summary>
|
||||
FilterNoMulti = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Do not allow bots to be targeted.
|
||||
/// </summary>
|
||||
FilterNoBots = 1 << 4
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of CounterStrikeSharp.
|
||||
* CounterStrikeSharp is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* CounterStrikeSharp is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
*/
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Commands.Targeting;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a target processing operation.
|
||||
/// </summary>
|
||||
public enum ProcessTargetResultFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// Target(s) were successfully found and filtered.
|
||||
/// </summary>
|
||||
TargetFound,
|
||||
|
||||
/// <summary>
|
||||
/// No target was found matching the initial pattern.
|
||||
/// </summary>
|
||||
TargetNone,
|
||||
|
||||
/// <summary>
|
||||
/// A single target was found, but they were not alive as required by the filter.
|
||||
/// Or a multi-target filter resulted in no alive players.
|
||||
/// </summary>
|
||||
TargetNotAlive,
|
||||
|
||||
/// <summary>
|
||||
/// A single target was found, but they were not dead as required by the filter.
|
||||
/// Or a multi-target filter resulted in no dead players.
|
||||
/// </summary>
|
||||
TargetNotDead,
|
||||
|
||||
/// <summary>
|
||||
/// The target is immune and cannot be targeted by the command issuer.
|
||||
/// </summary>
|
||||
TargetImmune,
|
||||
|
||||
/// <summary>
|
||||
/// A multi-target filter (like @all) resulted in no players after filtering.
|
||||
/// </summary>
|
||||
TargetEmptyFilter,
|
||||
|
||||
/// <summary>
|
||||
/// The target was found, but it was not a human player as required by the filter.
|
||||
/// </summary>
|
||||
TargetNotHuman,
|
||||
|
||||
/// <summary>
|
||||
/// The target string was ambiguous and matched more than one player when a single target was expected.
|
||||
/// </summary>
|
||||
TargetAmbiguous
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
@@ -120,4 +122,155 @@ public class Target
|
||||
|
||||
return new TargetResult() { Players = Utilities.GetPlayers().Where(player => TargetPredicate(player, caller, _gameRulesEntity?.GameRules)).ToList() };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a target string, finds matching players, and applies specified filters.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who executed the command.</param>
|
||||
/// <param name="targetString">The target string (e.g., player name, #userid, @all).</param>
|
||||
/// <param name="filter">Flags to filter the found targets.</param>
|
||||
/// <param name="tnIsMl">If true, the target name buffer will be an ML phrase. Otherwise, it will be normal string.</param>
|
||||
/// <param name="targetname">
|
||||
/// An output list that will contain the resolved target names. These may be localization keys
|
||||
/// (e.g., "all", "ct") if <paramref name="tnIsMl"/> is true, or actual player names otherwise.
|
||||
/// </param>
|
||||
/// <param name="players">An output list that will be populated with the player entities matching the target string.</param>
|
||||
public static ProcessTargetResultFlag ProcessTargetString(CCSPlayerController? player,
|
||||
string targetString, ProcessTargetFilterFlag filter, bool tnIsMl,
|
||||
out string targetname, out List<CCSPlayerController> players)
|
||||
{
|
||||
targetname = string.Empty;
|
||||
players = new Target(targetString).GetTarget(player).Players;
|
||||
|
||||
if (players.Count == 0)
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetNone;
|
||||
}
|
||||
|
||||
if (players.Count > 1 && filter.HasFlag(ProcessTargetFilterFlag.FilterNoMulti))
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetAmbiguous;
|
||||
}
|
||||
|
||||
if (filter.HasFlag(ProcessTargetFilterFlag.FilterNoImmunity))
|
||||
{
|
||||
players.RemoveAll(target => player != null && !AdminManager.CanPlayerTarget(new SteamID(player.SteamID), new SteamID(target.SteamID)));
|
||||
if (players.Count == 0)
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetImmune;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.HasFlag(ProcessTargetFilterFlag.FilterNoBots))
|
||||
{
|
||||
players.RemoveAll(p => p.IsBot);
|
||||
if (players.Count == 0)
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetNotHuman;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.HasFlag(ProcessTargetFilterFlag.FilterAlive))
|
||||
{
|
||||
players.RemoveAll(p => p.PlayerPawn.Value?.LifeState != (byte)LifeState_t.LIFE_ALIVE);
|
||||
if (players.Count == 0)
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetNotAlive;
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.HasFlag(ProcessTargetFilterFlag.FilterDead))
|
||||
{
|
||||
players.RemoveAll(p => p.PlayerPawn.Value?.LifeState == (byte)LifeState_t.LIFE_ALIVE);
|
||||
if (players.Count == 0)
|
||||
{
|
||||
return ProcessTargetResultFlag.TargetNotDead;
|
||||
}
|
||||
}
|
||||
|
||||
if (tnIsMl)
|
||||
{
|
||||
if (!TargetTypeMap.TryGetValue(targetString, out TargetType type))
|
||||
type = TargetType.PlayerMe;
|
||||
|
||||
targetname = type switch
|
||||
{
|
||||
TargetType.GroupAll => "all",
|
||||
TargetType.GroupBots => "bots",
|
||||
TargetType.GroupHumans => "humans",
|
||||
TargetType.GroupAlive => "alive",
|
||||
TargetType.GroupDead => "dead",
|
||||
TargetType.GroupNotMe => "notme",
|
||||
TargetType.TeamCt => "ct",
|
||||
TargetType.TeamT => "t",
|
||||
TargetType.TeamSpec => "spec",
|
||||
_ => players[0].PlayerName
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
targetname = string.Join(", ", players.Select(p => p.PlayerName));
|
||||
}
|
||||
|
||||
return ProcessTargetResultFlag.TargetFound;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wraps ProcessTargetString() and handles producing error messages for bad targets.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who executed the command.</param>
|
||||
/// <param name="targetString">The target string (e.g., player name, #userid, @all).</param>
|
||||
/// <param name="nobots">Optional. Set to true if bots should NOT be targetted</param>
|
||||
/// <param name="immunity">Optional. Set to false to ignore target immunity.</param>
|
||||
public static CCSPlayerController? FindTarget(CCSPlayerController player, string targetString, bool nobots = false, bool immunity = true)
|
||||
{
|
||||
var filter = ProcessTargetFilterFlag.FilterNoMulti;
|
||||
|
||||
if (nobots)
|
||||
filter |= ProcessTargetFilterFlag.FilterNoBots;
|
||||
|
||||
if (!immunity)
|
||||
filter |= ProcessTargetFilterFlag.FilterNoImmunity;
|
||||
|
||||
ProcessTargetResultFlag result;
|
||||
if ((result = ProcessTargetString(player, targetString, filter, false, out var targetname, out var players)) == ProcessTargetResultFlag.TargetFound)
|
||||
return players[0];
|
||||
|
||||
ReplyToTargetError(player, result);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Replies to a client with a given message describing a targetting failure reason.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who executed the command.</param>
|
||||
/// <param name="resultFlag">The <see cref="ProcessTargetResultFlag"/> value indicating why it is failed.</param>
|
||||
public static void ReplyToTargetError(CCSPlayerController player, ProcessTargetResultFlag resultFlag)
|
||||
{
|
||||
switch (resultFlag)
|
||||
{
|
||||
case ProcessTargetResultFlag.TargetNone:
|
||||
player.PrintToChat(Application.Localizer["No matching client"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetEmptyFilter:
|
||||
player.PrintToChat(Application.Localizer["No matching clients"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetNotAlive:
|
||||
player.PrintToChat(Application.Localizer["Target must be alive"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetNotDead:
|
||||
player.PrintToChat(Application.Localizer["Target must be dead"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetImmune:
|
||||
player.PrintToChat(Application.Localizer["Unable to target"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetNotHuman:
|
||||
player.PrintToChat(Application.Localizer["Cannot target bot"]);
|
||||
break;
|
||||
case ProcessTargetResultFlag.TargetAmbiguous:
|
||||
player.PrintToChat(Application.Localizer["More than one client matched"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,19 +101,19 @@ public class CenterHtmlMenuInstance : BaseMenuInstance
|
||||
|
||||
if (HasPrevButton)
|
||||
{
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.PrevPageColor}'>!7</font> <- Prev");
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.PrevPageColor}'>!7</font> <- {Application.Localizer["menu.button.previous"]}");
|
||||
builder.AppendLine("<br>");
|
||||
}
|
||||
|
||||
if (HasNextButton)
|
||||
{
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.NextPageColor}'>!8</font> -> Next");
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.NextPageColor}'>!8</font> -> {Application.Localizer["menu.button.next"]}");
|
||||
builder.AppendLine("<br>");
|
||||
}
|
||||
|
||||
if (centerHtmlMenu.ExitButton)
|
||||
{
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.CloseColor}'>!9</font> -> Close");
|
||||
builder.AppendFormat($"<font color='{centerHtmlMenu.CloseColor}'>!9</font> -> {Application.Localizer["menu.button.close"]}");
|
||||
builder.AppendLine("<br>");
|
||||
}
|
||||
|
||||
@@ -135,4 +135,4 @@ public class CenterHtmlMenuInstance : BaseMenuInstance
|
||||
var onTick = new Core.Listeners.OnTick(Display);
|
||||
_plugin.RemoveListener("OnTick", onTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,17 @@ public class ChatMenuInstance : BaseMenuInstance
|
||||
|
||||
if (HasPrevButton)
|
||||
{
|
||||
Player.PrintToChat($" {chatMenu.PrevPageColor}!7 {ChatColors.Default}-> Prev");
|
||||
Player.PrintToChat($" {chatMenu.PrevPageColor}!7 {ChatColors.Default}-> {Application.Localizer["menu.button.previous"]}");
|
||||
}
|
||||
|
||||
if (HasNextButton)
|
||||
{
|
||||
Player.PrintToChat($" {chatMenu.NextPageColor}!8 {ChatColors.Default}-> Next");
|
||||
Player.PrintToChat($" {chatMenu.NextPageColor}!8 {ChatColors.Default}-> {Application.Localizer["menu.button.next"]}");
|
||||
}
|
||||
|
||||
if (Menu.ExitButton)
|
||||
{
|
||||
Player.PrintToChat($" {chatMenu.CloseColor}!9 {ChatColors.Default}-> Close");
|
||||
Player.PrintToChat($" {chatMenu.CloseColor}!9 {ChatColors.Default}-> {Application.Localizer["menu.button.close"]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,4 +91,4 @@ public static class ChatMenus
|
||||
{
|
||||
MenuManager.OnKeyPress(player, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,17 +52,17 @@ public class ConsoleMenuInstance : BaseMenuInstance
|
||||
|
||||
if (HasPrevButton)
|
||||
{
|
||||
Player.PrintToConsole("css_7 -> Prev");
|
||||
Player.PrintToConsole($"css_7 -> {Application.Localizer["menu.button.previous"]}");
|
||||
}
|
||||
|
||||
if (HasNextButton)
|
||||
{
|
||||
Player.PrintToConsole("css_8 -> Next");
|
||||
Player.PrintToConsole($"css_8 -> {Application.Localizer["menu.button.next"]}");
|
||||
}
|
||||
|
||||
if (Menu.ExitButton)
|
||||
{
|
||||
Player.PrintToConsole("css_9 -> Close");
|
||||
Player.PrintToConsole($"css_9 -> {Application.Localizer["menu.button.close"]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ public enum AcquireMethod : int
|
||||
{
|
||||
PickUp = 0,
|
||||
Buy,
|
||||
BuyWithCtrl,
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Core.Translations;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
|
||||
74
managed/CounterStrikeSharp.SchemaGen/NewSchemaModule.cs
Normal file
74
managed/CounterStrikeSharp.SchemaGen/NewSchemaModule.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace CounterStrikeSharp.SchemaGen;
|
||||
|
||||
public record NewSchemaModule(
|
||||
GameInfo game_info,
|
||||
DumperInfo dumper_info,
|
||||
string[] dump_flags,
|
||||
SchemaDef[] defs);
|
||||
|
||||
public record GameInfo(
|
||||
string ClientVersion,
|
||||
string ServerVersion,
|
||||
string PatchVersion,
|
||||
string ProductName,
|
||||
string appID,
|
||||
string ServerAppID,
|
||||
string SourceRevision,
|
||||
string VersionDate,
|
||||
string VersionTime);
|
||||
|
||||
public record DumperInfo(
|
||||
string version,
|
||||
string dump_date,
|
||||
int dump_format_version);
|
||||
|
||||
public record SchemaDef(
|
||||
string type,
|
||||
string name,
|
||||
string? scope,
|
||||
string? project,
|
||||
int? size,
|
||||
int? alignment,
|
||||
SchemaTraits? traits);
|
||||
|
||||
public record SchemaTraits(
|
||||
int? parent_class_idx,
|
||||
string[]? flags,
|
||||
SchemaMetaTag[]? metatags,
|
||||
int? multi_depth,
|
||||
int? single_depth,
|
||||
SchemaBaseClass[]? baseclasses,
|
||||
SchemaMember[]? members,
|
||||
SchemaEnumField[]? fields);
|
||||
|
||||
public record SchemaMetaTag(
|
||||
string name,
|
||||
string? value);
|
||||
|
||||
public record SchemaBaseClass(
|
||||
int offset,
|
||||
int ref_idx);
|
||||
|
||||
public record SchemaMember(
|
||||
string name,
|
||||
int offset,
|
||||
MemberTraits? traits);
|
||||
|
||||
public record MemberTraits(
|
||||
SchemaMetaTag[]? metatags,
|
||||
SchemaSubtype? subtype);
|
||||
|
||||
public record SchemaSubtype(
|
||||
string type,
|
||||
string? name,
|
||||
int? size,
|
||||
int? alignment,
|
||||
SchemaSubtype[]? template,
|
||||
int? ref_idx,
|
||||
int? element_size,
|
||||
int? count,
|
||||
SchemaSubtype? subtype);
|
||||
|
||||
public record SchemaEnumField(
|
||||
string name,
|
||||
long value);
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using QuickGraph;
|
||||
@@ -45,7 +44,142 @@ internal static partial class Program
|
||||
"Unknown"
|
||||
};
|
||||
|
||||
public static string SanitiseTypeName(string typeName) => typeName.Replace(":", "");
|
||||
public static string SanitiseTypeName(string typeName) =>
|
||||
typeName.Replace(":", "")
|
||||
.Replace("< ", "<")
|
||||
.Replace(" >", ">");
|
||||
|
||||
private static (Dictionary<string, SchemaEnum>, Dictionary<string, SchemaClass>) ConvertNewSchemaToOld(NewSchemaModule newSchema)
|
||||
{
|
||||
var enums = new Dictionary<string, SchemaEnum>();
|
||||
var classes = new Dictionary<string, SchemaClass>();
|
||||
|
||||
var defLookup = newSchema.defs.Select((def, idx) => new { def, idx }).ToDictionary(x => x.idx, x => x.def);
|
||||
|
||||
for (int i = 0; i < newSchema.defs.Length; i++)
|
||||
{
|
||||
var def = newSchema.defs[i];
|
||||
if (def.type == "enum" && def.traits?.fields != null)
|
||||
{
|
||||
var enumItems = def.traits.fields.Select(f => new SchemaEnumItem(f.name, f.value)).ToList();
|
||||
enums[def.name] = new SchemaEnum(def.alignment ?? 4, enumItems);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < newSchema.defs.Length; i++)
|
||||
{
|
||||
var def = newSchema.defs[i];
|
||||
if (def.type == "class" && def.traits != null)
|
||||
{
|
||||
string? parentName = null;
|
||||
|
||||
if (def.traits.baseclasses != null && def.traits.baseclasses.Length > 0)
|
||||
{
|
||||
var parentIdx = def.traits.baseclasses[0].ref_idx;
|
||||
if (defLookup.TryGetValue(parentIdx, out var parentDef))
|
||||
{
|
||||
parentName = parentDef.name;
|
||||
}
|
||||
}
|
||||
|
||||
var fields = new List<SchemaField>();
|
||||
|
||||
if (def.traits.members != null)
|
||||
{
|
||||
foreach (var member in def.traits.members)
|
||||
{
|
||||
if (member.traits?.subtype != null)
|
||||
{
|
||||
var fieldType = ConvertSubtypeToFieldType(member.traits.subtype, defLookup);
|
||||
var metadata = member.traits.metatags?.ToDictionary(m => m.name, m => m.value ?? "") ??
|
||||
new Dictionary<string, string>();
|
||||
fields.Add(new SchemaField(member.name, fieldType, metadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
classes[def.name] = new SchemaClass(i, def.name, parentName, fields);
|
||||
}
|
||||
}
|
||||
|
||||
return (enums, classes);
|
||||
}
|
||||
|
||||
private static SchemaFieldType ConvertSubtypeToFieldType(SchemaSubtype subtype, Dictionary<int, SchemaDef> defLookup)
|
||||
{
|
||||
if (subtype.type == "ref" && subtype.ref_idx.HasValue)
|
||||
{
|
||||
if (defLookup.TryGetValue(subtype.ref_idx.Value, out var referencedDef))
|
||||
{
|
||||
return ConvertSubtypeToFieldType(new SchemaSubtype(
|
||||
referencedDef.type == "class"
|
||||
? "declared_class"
|
||||
: (referencedDef.type == "enum" ? "declared_enum" : referencedDef.type),
|
||||
referencedDef.name,
|
||||
referencedDef.size,
|
||||
referencedDef.alignment,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
), defLookup);
|
||||
}
|
||||
}
|
||||
|
||||
SchemaTypeCategory category = subtype.type switch
|
||||
{
|
||||
"builtin" => SchemaTypeCategory.Builtin,
|
||||
"atomic" => SchemaTypeCategory.Atomic,
|
||||
"ptr" => SchemaTypeCategory.Ptr,
|
||||
"fixed_array" => SchemaTypeCategory.FixedArray,
|
||||
"declared_class" => SchemaTypeCategory.DeclaredClass,
|
||||
"declared_enum" => SchemaTypeCategory.DeclaredEnum,
|
||||
"bitfield" => SchemaTypeCategory.Bitfield,
|
||||
_ => SchemaTypeCategory.None
|
||||
};
|
||||
|
||||
SchemaAtomicCategory? atomic = null;
|
||||
if (subtype.type == "atomic" && subtype.name != null)
|
||||
{
|
||||
if (subtype.name.Contains("CUtlVector") || subtype.name.Contains("CNetworkUtlVectorBase"))
|
||||
{
|
||||
atomic = SchemaAtomicCategory.Collection;
|
||||
}
|
||||
else if (subtype.name.Contains("CHandle") || subtype.name.Contains("CWeakHandle"))
|
||||
{
|
||||
atomic = SchemaAtomicCategory.T;
|
||||
}
|
||||
else
|
||||
{
|
||||
atomic = SchemaAtomicCategory.Basic;
|
||||
}
|
||||
}
|
||||
|
||||
SchemaFieldType? innerType = null;
|
||||
|
||||
if (subtype.template != null && subtype.template.Length > 0)
|
||||
{
|
||||
innerType = ConvertSubtypeToFieldType(subtype.template[0], defLookup);
|
||||
}
|
||||
else if (subtype.subtype != null)
|
||||
{
|
||||
innerType = ConvertSubtypeToFieldType(subtype.subtype, defLookup);
|
||||
}
|
||||
|
||||
string typeName = subtype.name ?? "unknown";
|
||||
if (category == SchemaTypeCategory.FixedArray && subtype.count.HasValue && innerType != null)
|
||||
{
|
||||
typeName = $"{innerType.Name}[{subtype.count.Value}]";
|
||||
}
|
||||
|
||||
return new SchemaFieldType(
|
||||
typeName,
|
||||
category,
|
||||
atomic,
|
||||
innerType
|
||||
);
|
||||
}
|
||||
|
||||
private static StringBuilder GetTemplate(bool includeUsings)
|
||||
{
|
||||
@@ -77,8 +211,8 @@ internal static partial class Program
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var outputPath =
|
||||
args.SingleOrDefault() ??
|
||||
throw new Exception("Expected a single CLI argument: <output path .cs>");
|
||||
args.FirstOrDefault() ??
|
||||
"../CounterStrikeSharp.API/Core/Schema";
|
||||
|
||||
// Concat together all enums and classes
|
||||
var allEnums = new SortedDictionary<string, SchemaEnum>();
|
||||
@@ -90,16 +224,18 @@ internal static partial class Program
|
||||
{
|
||||
var schemaPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Schema", schemaFile);
|
||||
|
||||
var schema = JsonSerializer.Deserialize<SchemaModule>(
|
||||
var newSchema = JsonSerializer.Deserialize<NewSchemaModule>(
|
||||
File.ReadAllText(schemaPath),
|
||||
SerializerOptions)!;
|
||||
|
||||
foreach (var (enumName, schemaEnum) in schema.Enums)
|
||||
var (enums, classes) = ConvertNewSchemaToOld(newSchema);
|
||||
|
||||
foreach (var (enumName, schemaEnum) in enums)
|
||||
{
|
||||
allEnums[enumName] = schemaEnum;
|
||||
}
|
||||
|
||||
foreach (var (className, schemaClass) in schema.Classes)
|
||||
foreach (var (className, schemaClass) in classes)
|
||||
{
|
||||
if (IgnoreClasses.Contains(className))
|
||||
continue;
|
||||
@@ -199,6 +335,7 @@ internal static partial class Program
|
||||
|
||||
// Manually whitelist some classes
|
||||
visited.Add("CTakeDamageInfo");
|
||||
visited.Add("CTakeDamageResult");
|
||||
visited.Add("CEntitySubclassVDataBase");
|
||||
visited.Add("CFiringModeFloat");
|
||||
visited.Add("CFiringModeInt");
|
||||
@@ -209,7 +346,6 @@ internal static partial class Program
|
||||
visited.Add("DecalGroupOption_t");
|
||||
visited.Add("DestructibleHitGroupToDestroy_t");
|
||||
|
||||
|
||||
var classBuilder = GetTemplate(true);
|
||||
|
||||
var visitedClassNames = new HashSet<string>();
|
||||
@@ -302,7 +438,8 @@ internal static partial class Program
|
||||
if (IgnoreClasses.Contains(field.Type.Inner!.Name)) continue;
|
||||
}
|
||||
|
||||
var requiresNewKeyword = parentFields.Any(x => x.clazz.CsPropertyNameForField(x.clazz.Name, x.field) == schemaClass.CsPropertyNameForField(schemaClassName, field));
|
||||
var requiresNewKeyword = parentFields.Any(x =>
|
||||
x.clazz.CsPropertyNameForField(x.clazz.Name, x.field) == schemaClass.CsPropertyNameForField(schemaClassName, field));
|
||||
|
||||
var handleParams = $"this.Handle, \"{schemaClassName}\", \"{field.Name}\"";
|
||||
|
||||
@@ -314,7 +451,7 @@ internal static partial class Program
|
||||
var getter = $"return Schema.GetString({handleParams});";
|
||||
var setter = $"Schema.SetString({handleParams}, value{(field.Type.ArraySize != null ? ", " + field.Type.ArraySize : "")});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
builder.AppendLine($"\t{{");
|
||||
builder.AppendLine(
|
||||
$"\t\tget {{ {getter} }}");
|
||||
@@ -329,7 +466,7 @@ internal static partial class Program
|
||||
var getter = $"return Schema.GetString({handleParams});";
|
||||
var setter = $"Schema.SetStringBytes({handleParams}, value, {field.Type.ArraySize});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
builder.AppendLine($"\t{{");
|
||||
builder.AppendLine(
|
||||
$"\t\tget {{ {getter} }}");
|
||||
@@ -344,7 +481,7 @@ internal static partial class Program
|
||||
var getter = $"return Schema.GetUtf8String({handleParams});";
|
||||
var setter = $"Schema.SetString({handleParams}, value);";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
builder.AppendLine($"\t{{");
|
||||
builder.AppendLine(
|
||||
$"\t\tget {{ {getter} }}");
|
||||
@@ -358,7 +495,7 @@ internal static partial class Program
|
||||
var getter =
|
||||
$"Schema.GetFixedArray<{SanitiseTypeName(field.Type.Inner!.CsTypeName)}>({handleParams}, {field.Type.ArraySize});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}Span<{SanitiseTypeName(field.Type.Inner!.CsTypeName)}> {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}Span<{SanitiseTypeName(field.Type.Inner!.CsTypeName)}> {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else if (field.Type.Category == SchemaTypeCategory.DeclaredClass &&
|
||||
@@ -366,7 +503,7 @@ internal static partial class Program
|
||||
{
|
||||
var getter = $"Schema.GetDeclaredClass<{SanitiseTypeName(field.Type.CsTypeName)}>({handleParams});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else if ((field.Type.Category == SchemaTypeCategory.Builtin ||
|
||||
@@ -375,7 +512,7 @@ internal static partial class Program
|
||||
{
|
||||
var getter = $"ref Schema.GetRef<{SanitiseTypeName(field.Type.CsTypeName)}>({handleParams});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}ref {SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}ref {SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else if (field.Type.Category == SchemaTypeCategory.Ptr)
|
||||
@@ -384,7 +521,7 @@ internal static partial class Program
|
||||
if (inner.Category != SchemaTypeCategory.DeclaredClass) continue;
|
||||
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => Schema.GetPointer<{SanitiseTypeName(inner.CsTypeName)}>({handleParams});");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => Schema.GetPointer<{SanitiseTypeName(inner.CsTypeName)}>({handleParams});");
|
||||
builder.AppendLine();
|
||||
}
|
||||
else if (field.Type is { Category: SchemaTypeCategory.Atomic, Name: "Color" })
|
||||
@@ -392,7 +529,7 @@ internal static partial class Program
|
||||
var getter = $"return Schema.GetCustomMarshalledType<{field.Type.CsTypeName}>({handleParams});";
|
||||
var setter = $"Schema.SetCustomMarshalledType<{field.Type.CsTypeName}>({handleParams}, value);";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)}");
|
||||
builder.AppendLine($"\t{{");
|
||||
builder.AppendLine(
|
||||
$"\t\tget {{ {getter} }}");
|
||||
@@ -405,7 +542,7 @@ internal static partial class Program
|
||||
{
|
||||
var getter = $"Schema.GetDeclaredClass<{SanitiseTypeName(field.Type.CsTypeName)}>({handleParams});";
|
||||
builder.AppendLine(
|
||||
$"\tpublic {(requiresNewKeyword ? "new ": "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
$"\tpublic {(requiresNewKeyword ? "new " : "")}{SanitiseTypeName(field.Type.CsTypeName)} {schemaClass.CsPropertyNameForField(schemaClassName, field)} => {getter}");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
@@ -435,20 +572,28 @@ internal static partial class Program
|
||||
builder.AppendLine($"public enum {SanitiseTypeName(enumName)} : {EnumType(schemaEnum.Align)}");
|
||||
builder.AppendLine("{");
|
||||
|
||||
var maxValue = schemaEnum.Align switch
|
||||
{
|
||||
1 => byte.MaxValue,
|
||||
2 => ushort.MaxValue,
|
||||
4 => uint.MaxValue,
|
||||
8 => ulong.MaxValue,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
// Write enum items
|
||||
foreach (var enumItem in schemaEnum.Items)
|
||||
{
|
||||
var value = enumItem.Value < maxValue ? enumItem.Value : maxValue;
|
||||
builder.AppendLine($"\t{enumItem.Name} = 0x{value:X},");
|
||||
string value;
|
||||
if (schemaEnum.Align == 8)
|
||||
{
|
||||
value = unchecked((ulong)enumItem.Value).ToString("X");
|
||||
}
|
||||
else if (schemaEnum.Align == 4)
|
||||
{
|
||||
value = unchecked((uint)enumItem.Value).ToString("X");
|
||||
}
|
||||
else if (schemaEnum.Align == 2)
|
||||
{
|
||||
value = unchecked((ushort)enumItem.Value).ToString("X");
|
||||
}
|
||||
else
|
||||
{
|
||||
value = unchecked((byte)enumItem.Value).ToString("X");
|
||||
}
|
||||
|
||||
builder.AppendLine($"\t{enumItem.Name} = 0x{value},");
|
||||
}
|
||||
|
||||
builder.AppendLine("}");
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,4 +2,4 @@
|
||||
|
||||
public record SchemaEnumItem(
|
||||
string Name,
|
||||
ulong Value);
|
||||
long Value);
|
||||
|
||||
@@ -32,3 +32,14 @@ void* FindSignature(const char* moduleName, const char* bytesStr)
|
||||
|
||||
return module->FindSignature(bytesStr);
|
||||
}
|
||||
|
||||
void* FindVirtualTable(const char* moduleName, const char* vtableName)
|
||||
{
|
||||
auto module = counterstrikesharp::modules::GetModuleByName(moduleName);
|
||||
if (module == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return module->FindVirtualTable(vtableName);
|
||||
}
|
||||
|
||||
@@ -13,3 +13,4 @@
|
||||
#endif
|
||||
|
||||
void* FindSignature(const char* moduleName, const char* bytesStr);
|
||||
void* FindVirtualTable(const char* moduleName, const char* vtableName);
|
||||
|
||||
@@ -11,17 +11,79 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define private public
|
||||
#include "core/log.h"
|
||||
#include "scripting/autonative.h"
|
||||
#include "scripting/script_engine.h"
|
||||
|
||||
// ---- Flag setter compatible with various SDKs ----
|
||||
template <typename T>
|
||||
concept HasAddClear = requires(T* t, uint64_t f) {
|
||||
t->AddFlags(f);
|
||||
t->ClearFlags(f);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasAddRemove = requires(T* t, uint64_t f) {
|
||||
t->AddFlags(f);
|
||||
t->RemoveFlags(f);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasSetFlagBit = requires(T* t, uint64_t f) {
|
||||
t->SetFlag(f, true);
|
||||
t->SetFlag(f, false);
|
||||
};
|
||||
|
||||
template <typename T> void SetAllFlagsCompat(T* data, uint64_t desired)
|
||||
{
|
||||
uint64_t cur = data->GetFlags();
|
||||
uint64_t add = desired & ~cur;
|
||||
uint64_t rem = cur & ~desired;
|
||||
|
||||
if constexpr (HasAddClear<T>)
|
||||
{
|
||||
if (add) data->AddFlags(add);
|
||||
if (rem) data->ClearFlags(rem);
|
||||
}
|
||||
else if constexpr (HasAddRemove<T>)
|
||||
{
|
||||
if (add) data->AddFlags(add);
|
||||
if (rem) data->RemoveFlags(rem);
|
||||
}
|
||||
else if constexpr (HasSetFlagBit<T>)
|
||||
{
|
||||
// Fallback: set/clear bitwise
|
||||
for (int i = 0; i < 64; i)
|
||||
{
|
||||
uint64_t bit = (1ULL << i);
|
||||
bool want = (desired & bit) != 0;
|
||||
data->SetFlag(bit, want);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(sizeof(T) == 0, "ConVarData hat keine passende Flags-API (Add/Clear/Remove/SetFlag).");
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------
|
||||
|
||||
// First STL/SPDLOG, then SDK with the hack – and clean up immediately afterwards
|
||||
#ifdef private
|
||||
#undef private
|
||||
#endif
|
||||
#ifdef protected
|
||||
#undef protected
|
||||
#endif
|
||||
#define private public
|
||||
#include <eiface.h>
|
||||
#include <convar.h>
|
||||
#undef private
|
||||
#ifdef protected
|
||||
#undef protected
|
||||
#endif
|
||||
|
||||
namespace counterstrikesharp {
|
||||
|
||||
@@ -37,7 +99,7 @@ static void SetConvarFlags(ScriptContext& script_context)
|
||||
}
|
||||
|
||||
auto flags = script_context.GetArgument<uint64_t>(1);
|
||||
ref.GetConVarData()->m_nFlags = flags;
|
||||
SetAllFlagsCompat(ref.GetConVarData(), flags);
|
||||
}
|
||||
|
||||
static void GetConvarFlags(ScriptContext& script_context)
|
||||
@@ -51,7 +113,7 @@ static void GetConvarFlags(ScriptContext& script_context)
|
||||
return;
|
||||
}
|
||||
|
||||
script_context.SetResult(ref.GetConVarData()->m_nFlags);
|
||||
script_context.SetResult(ref.GetConVarData()->GetFlags());
|
||||
}
|
||||
|
||||
static void GetConvarType(ScriptContext& script_context)
|
||||
@@ -407,8 +469,6 @@ static void CreateConVar(ScriptContext& script_context)
|
||||
auto hasMin = script_context.GetArgument<bool>(4);
|
||||
auto hasMax = script_context.GetArgument<bool>(5);
|
||||
|
||||
// default, min, max is 6,7,8
|
||||
|
||||
ConVarRefAbstract cvar(name);
|
||||
if (cvar.IsValidRef())
|
||||
{
|
||||
|
||||
@@ -34,6 +34,14 @@ void* FindSignatureNative(ScriptContext& scriptContext)
|
||||
return FindSignature(moduleName, bytesStr);
|
||||
}
|
||||
|
||||
void* FindVirtualTableNative(ScriptContext& scriptContext)
|
||||
{
|
||||
auto moduleName = scriptContext.GetArgument<const char*>(0);
|
||||
auto vtableName = scriptContext.GetArgument<const char*>(1);
|
||||
|
||||
return FindVirtualTable(moduleName, vtableName);
|
||||
}
|
||||
|
||||
ValveFunction* CreateVirtualFunctionBySignature(ScriptContext& script_context)
|
||||
{
|
||||
auto ptr = script_context.GetArgument<unsigned long>(0);
|
||||
@@ -167,6 +175,7 @@ REGISTER_NATIVES(memory, {
|
||||
ScriptEngine::RegisterNativeHandler("HOOK_FUNCTION", HookFunction);
|
||||
ScriptEngine::RegisterNativeHandler("UNHOOK_FUNCTION", UnhookFunction);
|
||||
ScriptEngine::RegisterNativeHandler("FIND_SIGNATURE", FindSignatureNative);
|
||||
ScriptEngine::RegisterNativeHandler("FIND_VIRTUAL_TABLE", FindVirtualTableNative);
|
||||
ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_SIZE", GetNetworkVectorSize);
|
||||
ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_ELEMENT_AT", GetNetworkVectorElementAt);
|
||||
ScriptEngine::RegisterNativeHandler("REMOVE_ALL_NETWORK_VECTOR_ELEMENTS", RemoveAllNetworkVectorElements);
|
||||
|
||||
@@ -4,6 +4,7 @@ HOOK_FUNCTION: function:pointer, hook:callback, post:bool -> void
|
||||
UNHOOK_FUNCTION: function:pointer, hook:callback, post:bool -> void
|
||||
EXECUTE_VIRTUAL_FUNCTION: function:pointer, bypass:bool, arguments:object[] -> any
|
||||
FIND_SIGNATURE: modulePath:string, signature:string -> pointer
|
||||
FIND_VIRTUAL_TABLE: modulePath:string, vtablename:string -> pointer
|
||||
GET_NETWORK_VECTOR_SIZE: vec:pointer -> int
|
||||
GET_NETWORK_VECTOR_ELEMENT_AT: vec:pointer, index:int -> pointer
|
||||
REMOVE_ALL_NETWORK_VECTOR_ELEMENTS: vec:pointer -> void
|
||||
Reference in New Issue
Block a user