Compare commits

...

46 Commits
v71 ... v1.0.97

Author SHA1 Message Date
Roflmuffin
02bf2483d3 docs: add database (dapper) example plugin 2023-12-03 19:23:23 +10:00
Roflmuffin
cb181b6a49 docs: add database (dapper) example plugin 2023-12-03 19:23:02 +10:00
Nexd
cc21dca5a0 Exposing from ISource2Server and IVEngineServer2 (#159)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
Co-authored-by: Roflmuffin <shortguy014@gmail.com>
2023-12-03 15:17:04 +10:00
Roflmuffin
5721d060ea feat: add overload for PrintToCenterHtml that accepts duration
closes #127
2023-12-03 14:18:10 +10:00
Roflmuffin
220521d571 fix: free callback property on game event unhook 2023-12-03 14:02:42 +10:00
Roflmuffin
5698b511e9 fix: use authorized Steam ID for admin system
also fix reference equality for SteamID
2023-12-03 13:34:11 +10:00
Roflmuffin
48c9d195ff feat: add IpAddress to CCSPlayerController 2023-12-03 13:11:06 +10:00
Roflmuffin
603827d331 fix: remove reference equality for CEntityInstance 2023-12-03 13:02:33 +10:00
roflmuffin
e557d54c32 fix: fires client authorize on map change (fixes #162)
Also adds `AuthorizedSteamID` property which is guaranteed to be valid with Steam API
2023-12-02 16:35:52 +10:00
Nexd
48d3ade5cf VirtualFunction & MemoryFunction rework to support arbitrary binary path (#158) 2023-12-02 15:41:01 +10:00
DRANIX
77b05e912e fix github actions xml warnings (#164) 2023-12-02 12:10:32 +10:00
Michael Wilson
1354b4972d docs: add some example plugins (#154) 2023-12-01 13:27:55 +10:00
johnoclockdk
8b5eb7e38d Update README.md (#153) 2023-11-30 17:08:09 +10:00
Roflmuffin
2dd62c44d3 docs: add missing core config doc 2023-11-30 16:07:28 +10:00
Roflmuffin
f811338ce4 feat: add option to disable plugin hot reload, closes #151
Also adds default values to unmanaged core config
2023-11-30 13:49:24 +10:00
Daniel Saewitz
194c340ae7 Improve plugin setup docs (#152) 2023-11-30 09:58:38 +10:00
Roflmuffin
6b0912d3cd hotfix: revert entity enumeration 2023-11-29 23:09:38 +10:00
Roflmuffin
2d3aa09aa4 hotfix: allow handles to be written to again 2023-11-29 22:35:54 +10:00
Roflmuffin
911084e71e feat: Add Schema Size Native 2023-11-29 21:50:57 +10:00
Roflmuffin
5b99206568 Merge remote-tracking branch 'origin/main' into feature/add-schema-class-size 2023-11-29 21:46:17 +10:00
Roflmuffin
4bfdf28beb Merge branch 'feature/entity-handle-overhaul' into feature/add-schema-class-size 2023-11-29 21:45:54 +10:00
Michael Wilson
9bcd0f7e92 Entity Handle Overhaul (#142) 2023-11-29 21:45:31 +10:00
Roflmuffin
11c6486ec5 chore: update test plugin version 2023-11-29 21:42:59 +10:00
Roflmuffin
ee69560a66 fix: bad style 2023-11-29 21:41:51 +10:00
Roflmuffin
d37e5e194a fix: use IntPtr.Zero instead of 0 2023-11-29 21:38:45 +10:00
Roflmuffin
c4740d1cc9 feat: add schema class size native, cast native objects to input argument 2023-11-29 21:06:55 +10:00
Roflmuffin
7e92f178fd feat: add Slot to player controller 2023-11-29 20:24:23 +10:00
Roflmuffin
107ca08132 Merge branch 'main' into feature/entity-handle-overhaul 2023-11-29 20:21:38 +10:00
Roflmuffin
8cda8d9a50 feat: wrap ExecuteClientCommand and add sound example 2023-11-29 20:10:45 +10:00
Roflmuffin
3d59a05de8 feat: remove native call from native entity instantiation 2023-11-29 20:03:49 +10:00
Roflmuffin
77b7040d6c feat: add GetAllEntities method, update implementation 2023-11-29 19:52:14 +10:00
Roflmuffin
75de9732ef feat: move entity system into managed code for perf 2023-11-29 19:43:46 +10:00
Roflmuffin
575c859ddb fix: add brute force fallback for enum member attribute, fixes #150 2023-11-29 17:25:31 +10:00
Nexd
e12a7cb17a fix: wildcard bytes for signatures (resolves #123 and related issues) (#148) 2023-11-28 23:57:26 +10:00
Roflmuffin
7c7f52a219 feat: update test plugin 2023-11-27 22:31:05 +10:00
Roflmuffin
cd593fb238 feat: add EntityIndex back to api compat, mark as obsolete 2023-11-27 21:44:43 +10:00
Roflmuffin
c5cc65be48 fix: remove expensive calls in bullet impact event 2023-11-27 21:24:18 +10:00
Roflmuffin
59928bbcc5 feat: add NativeEntity class 2023-11-27 20:35:38 +10:00
Roflmuffin
319b116c5f fix: bugs in config manager & plugin load, fixes #138 2023-11-27 11:42:34 +10:00
Michael Wilson
e0dc053d22 Config Example Parsing (#136) 2023-11-26 20:30:21 +10:00
Charles_
f2e0dac32d Feature: ProcessTargetString() & GetPlayerFromSteamId() (#121)
Co-authored-by: Roflmuffin <shortguy014@gmail.com>
2023-11-26 15:03:01 +10:00
Michael Wilson
4e8c18abc7 Implement Core & Plugin Service Collection (#129) 2023-11-26 14:15:58 +10:00
Roflmuffin
8d1891a3a8 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-26 13:19:54 +10:00
Roflmuffin
6bc43444f7 feat: add trigger touch start and end hooks 2023-11-26 13:19:40 +10:00
miguno
f0c7869f4a Check if userid is valid before accessing its fields, and explain why (#133) 2023-11-26 10:03:20 +10:00
Robert
3e38ed3c77 feat: Added ability to GiveNamedItem using the new CsItem Enum (#105) 2023-11-25 10:40:49 +10:00
117 changed files with 4253 additions and 806 deletions

View File

@@ -63,6 +63,10 @@ public class HelloWorldPlugin : BasePlugin
public override string ModuleVersion => "0.0.1";
public override string ModuleAuthor => "roflmuffin";
public override string ModuleDescription => "Simple hello world plugin";
public override void Load(bool hotReload)
{
Logger.LogInformation("Plugin loaded successfully!");

View File

@@ -0,0 +1,6 @@
{
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ],
"FollowCS2ServerGuidelines": true,
"PluginHotReloadEnabled": true
}

View File

@@ -1,5 +0,0 @@
{
"PublicChatTrigger": [ "!" ],
"SilentChatTrigger": [ "/" ],
"FollowCS2ServerGuidelines": true
}

View File

@@ -71,12 +71,12 @@
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x48\\x89\\x7C\\x24\\x20\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50",
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x7D\\xE0\\x48\\x83\\xEC\\x18\\x48\\x8D\\x05\\xA5"
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x2A\\x2A\\x48\\x83\\x2A\\x2A\\x2A\\x8D\\x05\\x2A\\x2A\\x2A\\x00\\x48\\x8B\\x30\\x48\\x8B\\x06\\xFF\\x2A\\x2A\\x48\\x8B\\x45\\x2A\\x48\\x8D\\x2A\\x2A\\x4C\\x89\\x2A\\x48\\x89\\x45\\x2A\\x2A\\x68\\xFC"
}
},
"CCSPlayer_ItemServices_DropActivePlayerWeapon": {
"offsets": {
"windows": 20,
"windows": 18,
"linux": 19
}
},
@@ -103,7 +103,7 @@
"signatures": {
"library": "server",
"windows": "\\x48\\x83\\xEC\\x48\\xC6\\x44\\x24\\x30\\x00\\x4C\\x8B\\xC1",
"linux": "\\x48\\x8D\\x05\\xC9\\xC2\\xBC"
"linux": "\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x55\\x48\\x89\\xFA"
}
},
"CBaseEntity_DispatchSpawn": {
@@ -139,6 +139,20 @@
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x41\\x54\\x49\\x89\\xFC\\x53\\x48\\x83\\xEC\\x38\\x4C\\x8D\\x2D\\x2A\\x2A\\x2A\\x2A\\x49\\x8B\\x7D\\x00\\x48\\x85\\xFF\\x0F\\x84\\x2A\\x2A\\x2A\\x2A"
}
},
"CBaseTrigger_StartTouch": {
"signatures": {
"library": "server",
"windows": "\\x41\\x56\\x41\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x01",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\xBB"
}
},
"CBaseTrigger_EndTouch": {
"signatures": {
"library": "server",
"windows": "\\x40\\x53\\x57\\x41\\x55\\x48\\x83\\xEC\\x40",
"linux": "\\x55\\xBA\\xFF\\xFF\\xFF\\xFF\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49"
}
},
"GameEntitySystem": {
"offsets": {
"windows": 88,

View File

@@ -17,8 +17,13 @@ The first parameter type must be a subclass of the `GameEvent` class. The names
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
// Userid will give you a reference to a CCSPlayerController class
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
// Userid will give you a reference to a CCSPlayerController class.
// Before accessing any of its fields, you must first check if the Userid
// handle is actually valid, otherwise you may run into runtime exceptions.
// See the documentation section on Referencing Players for details.
if (@event.Userid.IsValid) {
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
}
return HookResult.Continue;
}

View File

@@ -0,0 +1,71 @@
---
title: Dependency Injection
description: How to make use of dependency injection in CounterStrikeSharp
sidebar:
order: 1
---
`CounterStrikeSharp` uses a standard <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0" target="_blank">`IServiceCollection`</a> to allow for dependency injection in plugins.
There are a handful of standard services that are predefined for you (`ILogger` for logging for instance), with more to come in the future. To add your own scoped & singleton services to the container, you can create a new class that implements the `IPluginServiceCollection<T>` interface for your plugin.
```csharp
public class TestPlugin : BasePlugin
{
// Plugin code...
}
public class TestPluginServiceCollection : IPluginServiceCollection<TestPlugin>
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ExampleInjectedClass>();
serviceCollection.AddLogging(builder => ...);
}
}
```
CounterStrikeSharp will search your assembly for any implementations of `IPlugin` and then any implementations of `IPluginServiceCollection<T>` where `T` is your plugin. It will then configure the service provider and then request a singleton instance of your plugin before proceeding to the load step.
In this way, any dependencies that are listed in your plugin class constructor will automatically get injected at instantation time (before load).
### Example
```csharp
public class TestInjectedClass
{
private readonly ILogger<TestInjectedClass> _logger;
public TestInjectedClass(ILogger<TestInjectedClass> logger)
{
_logger = logger;
}
public void Hello()
{
_logger.LogInformation("Hello World from Test Injected Class");
}
}
public class TestPluginServiceCollection : IPluginServiceCollection<SamplePlugin>
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<TestInjectedClass>();
}
}
public class SamplePlugin : BasePlugin
{
private readonly TestInjectedClass _testInjectedClass;
public SamplePlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;
}
public override void Load(bool hotReload)
{
_testInjectedClass.Hello();
}
}
```

View File

@@ -1,6 +1,8 @@
---
title: Getting Started
description: How to get started installing & using CounterStrikeSharp.
sidebar:
order: 0
---
# Installation

View File

@@ -1,6 +1,8 @@
---
title: Hello World Plugin
description: How to write your first plugin for CounterStrikeSharp
sidebar:
order: 0
---
## Creating a New Project
@@ -66,19 +68,20 @@ Now build your project using your ide or the `dotnet build` command. You should
### Installing your Plugin
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste all of the `.dll` files _except_ the `CounterStrikeSharp.API.dll` file. Once completed, the folder should look as follows:
Locate the `plugins` folder in your CS2 dedicated server (`/game/csgo/addons/counterstrikesharp/plugins`) and create a new folder with the exact same name as your output .dll file. In this example it would be `HelloWorldPlugin.dll`, so I will make a new folder called `HelloWorldPlugin`. Inside of this folder, copy and paste the: `HelloWorldPlugin.deps.json`, `HelloWorldPlugin.dll`, and `HelloWorldPlugin.pdb` files. Once completed, the folder should look as follows:
```shell
.
└── SamplePlugin
├── McMaster.NETCore.Plugins.dll
├── Microsoft.DotNet.PlatformAbstractions.dll
── Microsoft.Extensions.DependencyModel.dll
├── SamplePlugin.deps.json
├── SamplePlugin.dll
└── SamplePlugin.pdb
└── HelloWorldPlugin
├── HelloWorldPlugin.deps.json
├── HelloWorldPlugin.dll
── HelloWorldPlugin.pdb
```
:::caution
If you have installed external nuget packages for your plugin, you may need to include their respective `.dll`s. For example, if you utilize the `Stateless` C# library, include `stateless.dll` in your `HelloWorld` plugin directory.
:::
:::note
Note that some of these dependencies may change depending on the version of CounterStrikeSharp being used.
:::

View File

@@ -25,6 +25,10 @@ public class HelloWorldPlugin : BasePlugin
public override string ModuleVersion => "0.0.1";
public override string ModuleAuthor => "roflmuffin";
public override string ModuleDescription => "Simple hello world plugin";
public override void Load(bool hotReload)
{
Logger.LogInformation("Plugin loaded successfully!");

View File

@@ -23,4 +23,8 @@ receive a ban.
:::note
Disable this option at your own risk.
:::
:::
## PluginHotReloadEnabled
When enabled, plugins are automatically reloaded when their .dll file is updated.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using Microsoft.Extensions.Logging;
namespace HelloWorld;
[MinimumApiVersion(80)]
public class HelloWorldPlugin : BasePlugin
{
public override string ModuleName => "Example: Hello World";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that says hello world!";
public override void Load(bool hotReload)
{
Logger.LogInformation("Hello World! We are loading!");
}
public override void Unload(bool hotReload)
{
Logger.LogInformation("Hello World! We are unloading!");
}
}

View File

@@ -0,0 +1,2 @@
# Hello World
This is a minimal "Hello World" example that executes code on load & unload.

View File

@@ -73,7 +73,7 @@ namespace WarcraftPlugin
var victim = @event.Userid;
var headshot = @event.Headshot;
if (attacker.IsValid && victim.IsValid && (attacker.EntityIndex.Value.Value != victim.EntityIndex.Value.Value) && !attacker.IsBot)
if (attacker.IsValid && victim.IsValid && (attacker != victim) && !attacker.IsBot)
{
var weaponName = attacker.PlayerPawn.Value.WeaponServices.ActiveWeapon.Value.DesignerName;

View File

@@ -0,0 +1,4 @@
# With Commands
This is an example that shows how to register chat & console commands.
All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix. i.e. `css_ping` can be called with `!ping` or `/ping`.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,63 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
namespace WithCommands;
[MinimumApiVersion(80)]
public class WithCommandsPlugin : BasePlugin
{
public override string ModuleName => "Example: With Commands";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that registers some commands";
public override void Load(bool hotReload)
{
// All commands that are prefixed with "css_" will automatically be registered as a chat command without the prefix.
// i.e. `css_ping` can be called with `!ping` or `/ping`.
// Commands can be registered using the instance `AddCommand` method.
AddCommand("css_ping", "Responds to the caller with \"pong\"", (player, commandInfo) =>
{
// The player is null, then the command has been called by the server console.
if (player == null)
{
commandInfo.ReplyToCommand("pong server");
return;
}
commandInfo.ReplyToCommand("pong");
});
}
// Commands can also be registered using the `Command` attribute.
[ConsoleCommand("css_hello", "Responds to the caller with \"pong\"")]
// The `CommandHelper` attribute can be used to provide additional information about the command.
[CommandHelper(minArgs: 1, usage: "[name]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
[RequiresPermissions("@css/cvar")]
public void OnHelloCommand(CCSPlayerController? player, CommandInfo commandInfo)
{
// The first argument is the command name, in this case "css_hello".
commandInfo.GetArg(0); // css_hello
// The second argument is the first argument passed to the command, in this case "name".
// The `minArgs` helper parameter is used to ensure that the second argument is present.
var name = commandInfo.GetArg(1);
commandInfo.ReplyToCommand($"Hello {name}");
}
// Permissions can be added to commands using the `RequiresPermissions` attribute.
// See the admin documentation for more information on permissions.
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "[id]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSpecialCommand(CCSPlayerController? player, CommandInfo commandInfo)
{
var id = commandInfo.GetArg(1);
Server.ExecuteCommand($"kick {id}");
}
}

View File

@@ -0,0 +1,2 @@
# WithConfig
This example shows how you can implement the `IPluginConfig` interface to allow for semi-automatic config parsing & loading.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
namespace WithConfig;
public class SampleConfig : BasePluginConfig
{
[JsonPropertyName("ChatPrefix")] public string ChatPrefix { get; set; } = "My Cool Plugin";
[JsonPropertyName("ChatInterval")] public float ChatInterval { get; set; } = 60;
}
[MinimumApiVersion(80)]
public class WithConfigPlugin : BasePlugin, IPluginConfig<SampleConfig>
{
public override string ModuleName => "Example: With Config";
public override string ModuleVersion => "1.0.0";
public SampleConfig Config { get; set; }
public void OnConfigParsed(SampleConfig config)
{
// Do manual verification of the config and override any invalid values
if (config.ChatInterval > 60)
{
config.ChatInterval = 60;
}
if (config.ChatPrefix.Length > 25)
{
throw new Exception($"Invalid value has been set to config value 'ChatPrefix': {config.ChatPrefix}");
}
// Once we've validated the config, we can set it to the instance
Config = config;
}
}

View File

@@ -0,0 +1,2 @@
# With Database (Dapper)
Simple SQLite database example using Dapper library to track kills.

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.24" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.14" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,89 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using Dapper;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
namespace WithDatabaseDapper;
[MinimumApiVersion(80)]
public class WithDatabaseDapperPlugin : BasePlugin
{
public override string ModuleName => "Example: With Database (Dapper)";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A plugin that reads and writes from the database.";
private SqliteConnection _connection = null!;
public override void Load(bool hotReload)
{
Logger.LogInformation("Loading database from {Path}", Path.Join(ModuleDirectory, "database.db"));
_connection = new SqliteConnection($"Data Source={Path.Join(ModuleDirectory, "database.db")}");
_connection.Open();
// Create the table if it doesn't exist
// Run in a separate thread to avoid blocking the main thread
Task.Run(async () =>
{
await _connection.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS `players` (
`steamid` UNSIGNED BIG INT NOT NULL,
`kills` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`steamid`));");
});
}
[GameEventHandler]
public HookResult OnPlayerKilled(EventPlayerDeath @event, GameEventInfo info)
{
// Don't count suicides.
if (@event.Attacker == @event.Userid) return HookResult.Continue;
// Capture the steamid of the player as `@event` will not be available outside of this function.
var steamId = @event.Attacker.AuthorizedSteamID?.SteamId64;
if (steamId == null) return HookResult.Continue;
// Run in a separate thread to avoid blocking the main thread
Task.Run(async () =>
{
// insert or update the player's kills
await _connection.ExecuteAsync(@"
INSERT INTO `players` (`steamid`, `kills`) VALUES (@SteamId, 1)
ON CONFLICT(`steamid`) DO UPDATE SET `kills` = `kills` + 1;",
new
{
SteamId = steamId
});
});
return HookResult.Continue;
}
[ConsoleCommand("css_kills", "Get count of kills for a player")]
public void OnKillsCommand(CCSPlayerController? player, CommandInfo commandInfo)
{
if (player == null) return;
// Capture the SteamID of the player as `@event` will not be available outside of this function.
var steamId = player.AuthorizedSteamID.SteamId64;
// Run in a separate thread to avoid blocking the main thread
Task.Run(async () =>
{
var result = await _connection.QueryFirstOrDefaultAsync(@"SELECT `kills` FROM `players` WHERE `steamid` = @SteamId;",
new
{
SteamId = steamId
});
// Print the result to the player's chat. Note that this needs to be run on the game thread.
// So we use `Server.NextFrame` to run it on the next game tick.
Server.NextFrame(() => { player.PrintToChat($"Kills: {result?.kills ?? 0}"); });
});
}
}

View File

@@ -0,0 +1,2 @@
# With Dependency Injection
This example shows how can you implement the `IPluginServiceCollection` interface to register your services in a provided DI container.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WithDependencyInjection;
[MinimumApiVersion(80)]
public class WithDependencyInjectionPlugin : BasePlugin
{
public override string ModuleName => "Example: Dependency Injection";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "An example plugin that uses dependency injection.";
private readonly TestInjectedClass _testInjectedClass;
public WithDependencyInjectionPlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;
}
public override void Load(bool hotReload)
{
_testInjectedClass.SayHello();
}
}
public class WithDependencyInjectionPluginServiceCollection : IPluginServiceCollection<WithDependencyInjectionPlugin>
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<TestInjectedClass>();
}
}
public class TestInjectedClass
{
private readonly ILogger<TestInjectedClass> _logger;
public TestInjectedClass(ILogger<TestInjectedClass> logger)
{
_logger = logger;
}
public void SayHello()
{
_logger.LogInformation("Hello World from Test Injected Class");
}
}

View File

@@ -0,0 +1,2 @@
# With Game Event Handlers
This is example shows how you can subscribe to legacy Source 1 game events.

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,54 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using Microsoft.Extensions.Logging;
namespace WithGameEventHandlers;
[MinimumApiVersion(80)]
public class WithGameEventHandlersPlugin : BasePlugin
{
public override string ModuleName => "Example: With Game Event Handlers";
public override string ModuleVersion => "1.0.0";
public override string ModuleAuthor => "CounterStrikeSharp & Contributors";
public override string ModuleDescription => "A simple plugin that subscribes to game events";
public override void Load(bool hotReload)
{
// Subscriptions can be added via the instance method
RegisterEventHandler<EventPlayerDeath>((@event, info) =>
{
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event (in pre handlers)
// This will prevent the event from being broadcast to other clients.
// In this example we prevent kill-feed messages from being broadcast if it was not a headshot.
if (!@event.Headshot)
{
@event.Attacker.PrintToChat($"Skipping player_death broadcast");
info.DontBroadcast = true;
}
return HookResult.Continue;
}, HookMode.Pre);
}
// Subscriptions can be added via an attribute
[GameEventHandler]
public HookResult OnPlayerBlind(EventPlayerBlind @event, GameEventInfo info)
{
Logger.LogInformation("Player was just blinded for {Duration}", @event.BlindDuration);
return HookResult.Continue;
}
// The event name is inferred from the event type you pass to the first argument.
// e.g. EventRoundStart becomes "round_start"
// Note: You can use the `HookMode` enum to specify the hook mode
// If you do not specify a hook mode, it will default to `HookMode.Post`
[GameEventHandler(HookMode.Pre)]
public HookResult OnEventRoundStartPre(EventRoundStart @event, GameEventInfo info)
{
Logger.LogInformation("Round has started with Timelimit: {Timelimit}", @event.Timelimit);
return HookResult.Continue;
}
}

View File

@@ -30,5 +30,19 @@ namespace CounterStrikeSharp.API
{
Handle = pointer;
}
/// <summary>
/// Returns a new instance of the specified type using the pointer from the passed in object.
/// </summary>
/// <remarks>
/// Useful for creating a new instance of a class that inherits from NativeObject.
/// e.g. <code>var weaponServices = playerWeaponServices.As&lt;CCSPlayer_WeaponServices&gt;();</code>
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T As<T>() where T : NativeObject
{
return (T)Activator.CreateInstance(typeof(T), this.Handle);
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Plugin.Host;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
namespace CounterStrikeSharp.API;
public static class Bootstrap
{
[UnmanagedCallersOnly]
// Used by .NET Host in C++ to initiate loading
public static int Run()
{
try
{
// Path to /game/csgo/addons/counterstrikesharp
var contentRoot = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent.FullName;
using var host = Host.CreateDefaultBuilder()
.UseContentRoot(contentRoot)
.ConfigureServices(services =>
{
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddCoreLogging(contentRoot);
});
services.AddSingleton<IScriptHostConfiguration, ScriptHostConfiguration>();
services.AddScoped<Application>();
services.AddSingleton<IPluginManager, PluginManager>();
services.AddScoped<IPluginContextQueryHandler, PluginContextQueryHandler>();
services.Scan(i => i.FromCallingAssembly()
.AddClasses(c => c.AssignableTo<IStartupService>())
.AsSelfWithInterfaces()
.WithSingletonLifetime());
})
.Build();
using IServiceScope scope = host.Services.CreateScope();
// TODO: Improve static singleton access
GameData.GameDataProvider = scope.ServiceProvider.GetRequiredService<GameDataProvider>();
var application = scope.ServiceProvider.GetRequiredService<Application>();
application.Start();
return 1;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
Log.Fatal(e, "Failed to start application");
return 0;
}
}
}

View File

@@ -157,6 +157,30 @@ namespace CounterStrikeSharp.API.Core
}
}
public static string GetClientConvarValue(int clientindex, string convarname){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(clientindex);
ScriptContext.GlobalScriptContext.Push(convarname);
ScriptContext.GlobalScriptContext.SetIdentifier(0xAE4B1B79);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string));
}
}
public static void SetFakeClientConvarValue(int clientindex, string convarname, string convarvalue){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(clientindex);
ScriptContext.GlobalScriptContext.Push(convarname);
ScriptContext.GlobalScriptContext.Push(convarvalue);
ScriptContext.GlobalScriptContext.SetIdentifier(0x4C61E8BB);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static T DynamicHookGetReturn<T>(IntPtr hook, int datatype){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -534,6 +558,49 @@ namespace CounterStrikeSharp.API.Core
}
}
public static uint GetRefFromEntityPointer(IntPtr entitypointer){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(entitypointer);
ScriptContext.GlobalScriptContext.SetIdentifier(0xAF13DA94);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (uint)ScriptContext.GlobalScriptContext.GetResult(typeof(uint));
}
}
public static IntPtr GetEntityPointerFromRef(uint entityref){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(entityref);
ScriptContext.GlobalScriptContext.SetIdentifier(0xDBC17174);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
}
}
public static IntPtr GetConcreteEntityListPointer(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0x5756DB36);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
}
}
public static bool IsRefValidEntity(uint entityref){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(entityref);
ScriptContext.GlobalScriptContext.SetIdentifier(0x6E38A1FC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
}
}
public static void PrintToConsole(int index, string message){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -555,6 +622,28 @@ namespace CounterStrikeSharp.API.Core
}
}
public static ulong GetPlayerAuthorizedSteamid(int slot){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(slot);
ScriptContext.GlobalScriptContext.SetIdentifier(0xD1F30B3B);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (ulong)ScriptContext.GlobalScriptContext.GetResult(typeof(ulong));
}
}
public static string GetPlayerIpAddress(int slot){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(slot);
ScriptContext.GlobalScriptContext.SetIdentifier(0x46A45CB0);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (string)ScriptContext.GlobalScriptContext.GetResult(typeof(string));
}
}
public static void HookEvent(string name, InputArgument callback, bool ispost){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -967,6 +1056,17 @@ namespace CounterStrikeSharp.API.Core
}
}
public static int GetSchemaClassSize(string classname){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(classname);
ScriptContext.GlobalScriptContext.SetIdentifier(0x9CE4FC56);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (int)ScriptContext.GlobalScriptContext.GetResult(typeof(int));
}
}
public static IntPtr GetEconItemSystem(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -977,6 +1077,16 @@ namespace CounterStrikeSharp.API.Core
}
}
public static bool IsServerPaused(){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.SetIdentifier(0xB216AAAC);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (bool)ScriptContext.GlobalScriptContext.GetResult(typeof(bool));
}
}
public static IntPtr CreateTimer(float interval, InputArgument callback, int flags){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();

View File

@@ -0,0 +1,254 @@
/*
* 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/>. *
*/
using System.Linq;
using System.Text;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
public sealed class Application
{
private static Application _instance = null!;
public ILogger Logger { get; }
public static Application Instance => _instance!;
public static string RootDirectory => Instance._scriptHostConfiguration.RootPath;
private readonly IScriptHostConfiguration _scriptHostConfiguration;
private readonly GameDataProvider _gameDataProvider;
private readonly CoreConfig _coreConfig;
private readonly IPluginManager _pluginManager;
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
IPluginContextQueryHandler pluginContextQueryHandler)
{
Logger = loggerFactory.CreateLogger("Core");
_scriptHostConfiguration = scriptHostConfiguration;
_gameDataProvider = gameDataProvider;
_coreConfig = coreConfig;
_pluginManager = pluginManager;
_pluginContextQueryHandler = pluginContextQueryHandler;
_instance = this;
}
public void Start()
{
Logger.LogInformation("CounterStrikeSharp is starting up...");
_coreConfig.Load();
_gameDataProvider.Load();
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
Logger.LogInformation("Loading Admins from {Path}", adminPath);
AdminManager.LoadAdminData(adminPath);
var overridePath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_overrides.json");
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
AdminManager.LoadCommandOverrides(overridePath);
AdminManager.MergeGroupPermsIntoAdmins();
_pluginManager.Load();
for (var i = 1; i <= 9; i++)
{
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
});
}
RegisterPluginCommands();
}
[RequiresPermissions("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
var currentVersion = Api.GetVersion();
info.ReplyToCommand(
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 1,
usage: "[option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded.",
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
{
switch (info.GetArg(1))
{
case "list":
{
info.ReplyToCommand(
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.",
true);
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,
plugin.Plugin.ModuleVersion);
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(), true);
}
break;
}
case "start":
case "load":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n",
true);
break;
}
// If our arugment 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);
if (!path.EndsWith(".dll"))
{
path = Path.Combine(_scriptHostConfiguration.RootPath, $"plugins/{path}/{path}.dll");
}
else
{
path = Path.Combine(_scriptHostConfiguration.RootPath, path);
}
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
if (plugin == null)
{
try
{
_pluginManager.LoadPlugin(path);
} catch (Exception e)
{
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
}
}
else
{
plugin.Load(false);
}
break;
}
case "stop":
case "unload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n",
true);
break;
}
var pluginIdentifier = info.GetArg(2);
IPluginContext? plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(false);
break;
}
case "restart":
case "reload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n",
true);
break;
}
var pluginIdentifier = info.GetArg(2);
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(true);
plugin.Load(true);
break;
}
default:
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded."
, true);
break;
}
}
private void RegisterPluginCommands()
{
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
OnCSSPluginCommand);
}
}
}

View File

@@ -18,25 +18,19 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Listeners;
using CounterStrikeSharp.API.Modules.Timers;
using McMaster.NETCore.Plugins;
using CounterStrikeSharp.API.Modules.Config;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
public abstract class BasePlugin : IPlugin, IDisposable
public abstract class BasePlugin : IPlugin
{
private bool _disposed;
@@ -51,7 +45,7 @@ namespace CounterStrikeSharp.API.Core
public virtual string ModuleDescription { get; }
public string ModulePath { get; internal set; }
public string ModulePath { get; set; }
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
public ILogger Logger { get; set; }
@@ -316,7 +310,7 @@ namespace CounterStrikeSharp.API.Core
.Select(p => p.GetCustomAttribute<CastFromAttribute>()?.Type)
.ToArray();
GlobalContext.Instance.Logger.LogDebug("Registering listener for {ListenerName} with {ParameterCount} parameters",
Application.Instance.Logger.LogDebug("Registering listener for {ListenerName} with {ParameterCount} parameters",
listenerName, parameterTypes.Length);
var wrappedHandler = new Action<ScriptContext>(context =>

View File

@@ -19,12 +19,13 @@ using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using System.Collections.Generic;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
@@ -34,17 +35,23 @@ namespace CounterStrikeSharp.API.Core
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("PublicChatTrigger")]
public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("SilentChatTrigger")] public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("SilentChatTrigger")]
public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("FollowCS2ServerGuidelines")] public bool FollowCS2ServerGuidelines { get; set; } = true;
[JsonPropertyName("FollowCS2ServerGuidelines")]
public bool FollowCS2ServerGuidelines { get; set; } = true;
[JsonPropertyName("PluginHotReloadEnabled")]
public bool PluginHotReloadEnabled { get; set; } = true;
}
/// <summary>
/// Configuration related to the Core API.
/// </summary>
public static partial class CoreConfig
public partial class CoreConfig
{
/// <summary>
/// List of characters to use for public chat triggers.
@@ -76,51 +83,63 @@ namespace CounterStrikeSharp.API.Core
/// </para>
/// </summary>
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
/// <summary>
/// When enabled, plugins are automatically reloaded when their .dll file is updated.
/// </summary>
public static bool PluginHotReloadEnabled => _coreConfig.PluginHotReloadEnabled;
}
public static partial class CoreConfig
public partial class CoreConfig : IStartupService
{
private static CoreConfigData _coreConfig = new CoreConfigData();
// TODO: ServiceCollection
private static ILogger _logger = CoreLogging.Factory.CreateLogger("CoreConfig");
static CoreConfig()
private readonly ILogger<CoreConfig> _logger;
private readonly string _coreConfigPath;
public CoreConfig(IScriptHostConfiguration scriptHostConfiguration, ILogger<CoreConfig> logger)
{
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.", ReloadCoreConfigCommand);
_logger = logger;
_coreConfigPath = Path.Join(scriptHostConfiguration.ConfigsPath, "core.json");
}
[RequiresPermissions("@css/config")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadCoreConfigCommand(CCSPlayerController? player, CommandInfo command)
private void ReloadCoreConfigCommand(CCSPlayerController? player, CommandInfo command)
{
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
Load(Path.Combine(rootDir.FullName, "configs", "core.json"));
Load();
}
public static void Load(string coreConfigPath)
public void Load()
{
if (!File.Exists(coreConfigPath))
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.",
ReloadCoreConfigCommand);
if (!File.Exists(_coreConfigPath))
{
_logger.LogWarning("Core configuration could not be found at path \"{CoreConfigPath}\", fallback values will be used.", coreConfigPath);
_logger.LogWarning(
"Core configuration could not be found at path \"{CoreConfigPath}\", fallback values will be used.",
_coreConfigPath);
return;
}
try
{
var data = JsonSerializer.Deserialize<CoreConfigData>(File.ReadAllText(coreConfigPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
var data = JsonSerializer.Deserialize<CoreConfigData>(File.ReadAllText(_coreConfigPath),
new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (data != null)
{
_coreConfig = data;
}
_logger.LogInformation("Successfully loaded core configuration.");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load core configuration, fallback values will be used");
}
_logger.LogInformation("Successfully loaded core configuration");
}
}
}
}

View File

@@ -77,7 +77,7 @@ namespace CounterStrikeSharp.API.Core
}
catch (Exception e)
{
GlobalContext.Instance.Logger.LogError(e, "Error invoking callback");
Application.Instance.Logger.LogError(e, "Error invoking callback");
}
});
s_callback = dg;
@@ -141,7 +141,7 @@ namespace CounterStrikeSharp.API.Core
{
ms_references.Remove(reference);
GlobalContext.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
Application.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
}
}
}

View File

@@ -5,11 +5,12 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core.Hosting;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core;
class LoadedGameData
public class LoadedGameData
{
[JsonPropertyName("signatures")] public Signatures? Signatures { get; set; }
[JsonPropertyName("offsets")] public Offsets? Offsets { get; set; }
@@ -31,33 +32,45 @@ public class Offsets
[JsonPropertyName("linux")] public int Linux { get; set; }
}
public static class GameData
public sealed class GameDataProvider : IStartupService
{
private static Dictionary<string, LoadedGameData> _methods;
private readonly string _gameDataFilePath;
public Dictionary<string,LoadedGameData> Methods;
private readonly ILogger<GameDataProvider> _logger;
public static void Load(string gameDataPath)
public GameDataProvider(IScriptHostConfiguration scriptHostConfiguration, ILogger<GameDataProvider> logger)
{
_logger = logger;
_gameDataFilePath = Path.Join(scriptHostConfiguration.GameDataPath, "gamedata.json");
}
public void Load()
{
try
{
_methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(gameDataPath))!;
GlobalContext.Instance.Logger.LogInformation("Loaded game data with {Count} methods.", _methods.Count);
Methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(_gameDataFilePath))!;
}
catch (Exception ex)
{
GlobalContext.Instance.Logger.LogError(ex, "Failed to load game data");
_logger.LogError(ex, "Failed to load game data");
}
_logger.LogInformation("Successfully loaded {Count} game data entries from {Path}", Methods.Count, _gameDataFilePath);
}
}
public static class GameData
{
internal static GameDataProvider GameDataProvider { get; set; } = null!;
public static string GetSignature(string key)
{
GlobalContext.Instance.Logger.LogDebug("Getting signature: {Key}", key);
if (!_methods.ContainsKey(key))
Application.Instance.Logger.LogDebug("Getting signature: {Key}", key);
if (!GameDataProvider.Methods.ContainsKey(key))
{
throw new ArgumentException($"Method {key} not found in gamedata.json");
}
var methodMetadata = _methods[key];
var methodMetadata = GameDataProvider.Methods[key];
if (methodMetadata.Signatures == null)
{
throw new InvalidOperationException($"No signatures found for {key} in gamedata.json");
@@ -79,12 +92,12 @@ public static class GameData
public static int GetOffset(string key)
{
if (!_methods.ContainsKey(key))
if (!GameDataProvider.Methods.ContainsKey(key))
{
throw new Exception($"Method {key} not found in gamedata.json");
}
var methodMetadata = _methods[key];
var methodMetadata = GameDataProvider.Methods[key];
if (methodMetadata.Offsets == null)
{

View File

@@ -1,355 +0,0 @@
/*
* 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/>. *
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace CounterStrikeSharp.API.Core
{
public sealed class GlobalContext
{
private static GlobalContext _instance = null;
public ILogger Logger { get; }
public static GlobalContext Instance => _instance;
public static string RootDirectory => _instance.rootDir.FullName;
private DirectoryInfo rootDir;
private readonly List<PluginContext> _loadedPlugins = new();
public GlobalContext()
{
rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
_instance = this;
Logger = CoreLogging.Factory.CreateLogger("Core");
Logger.LogInformation("CounterStrikeSharp is starting up...");
}
~GlobalContext()
{
foreach (var plugin in _loadedPlugins)
{
plugin.Unload();
}
}
public void OnNativeUnload()
{
foreach (var plugin in _loadedPlugins)
{
plugin.Unload();
}
}
public void InitGlobalContext()
{
var coreConfigPath = Path.Combine(rootDir.FullName, "configs", "core.json");
Logger.LogInformation("Loading CoreConfig from {Path}", coreConfigPath);
CoreConfig.Load(coreConfigPath);
var gameDataPath = Path.Combine(rootDir.FullName, "gamedata", "gamedata.json");
Logger.LogInformation("Loading GameData from {Path}", gameDataPath);
GameData.Load(gameDataPath);
var adminGroupsPath = Path.Combine(rootDir.FullName, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var adminPath = Path.Combine(rootDir.FullName, "configs", "admins.json");
Logger.LogInformation("Loading Admins from {Path}", adminPath);
AdminManager.LoadAdminData(adminPath);
var overridePath = Path.Combine(rootDir.FullName, "configs", "admin_overrides.json");
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
AdminManager.LoadCommandOverrides(overridePath);
AdminManager.MergeGroupPermsIntoAdmins();
for (var i = 1; i <= 9; i++)
{
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
});
}
Logger.LogInformation("Loading C# plugins...");
var pluginCount = LoadAllPlugins();
Logger.LogInformation("All managed modules were loaded. {PluginCount} plugins loaded.", pluginCount);
RegisterPluginCommands();
}
private void LoadPlugin(string path)
{
var existingPlugin = FindPluginByModulePath(path);
if (existingPlugin != null)
{
throw new FileLoadException("Plugin is already loaded.");
}
var plugin = new PluginContext(path, _loadedPlugins.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
plugin.Load();
_loadedPlugins.Add(plugin);
}
private int LoadAllPlugins()
{
DirectoryInfo modulesDirectoryInfo;
try
{
modulesDirectoryInfo = new DirectoryInfo(Path.Combine(rootDir.FullName, "plugins"));
}
catch (Exception e)
{
Logger.LogError(e, "Error finding plugin path");
return 0;
}
DirectoryInfo[] properModulesDirectories;
try
{
properModulesDirectories = modulesDirectoryInfo.GetDirectories();
}
catch
{
properModulesDirectories = Array.Empty<DirectoryInfo>();
}
var filePaths = properModulesDirectories
.Where(d => d.GetFiles().Any((f) => f.Name == d.Name + ".dll"))
.Select(d => d.GetFiles().First((f) => f.Name == d.Name + ".dll").FullName)
.ToArray();
foreach (var path in filePaths)
{
try
{
LoadPlugin(path);
}
catch (Exception e)
{
Logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
return _loadedPlugins.Count;
}
public void UnloadAllPlugins()
{
foreach (var plugin in _loadedPlugins)
{
plugin.Unload();
_loadedPlugins.Remove(plugin);
}
}
private PluginContext? FindPluginByType(Type moduleClass)
{
return _loadedPlugins.FirstOrDefault(x => x.PluginType == moduleClass);
}
private PluginContext? FindPluginById(int id)
{
return _loadedPlugins.FirstOrDefault(x => x.PluginId == id);
}
private PluginContext? FindPluginByModuleName(string name)
{
return _loadedPlugins.FirstOrDefault(x => x.Name == name);
}
private PluginContext? FindPluginByModulePath(string path)
{
return _loadedPlugins.FirstOrDefault(x => x.PluginPath == path);
}
private PluginContext? FindPluginByIdOrName(string query)
{
PluginContext? plugin = null;
if (Int32.TryParse(query, out var pluginNumber))
{
plugin = FindPluginById(pluginNumber);
if (plugin != null) return plugin;
}
plugin = FindPluginByModuleName(query);
return plugin;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
var currentVersion = Api.GetVersion();
info.ReplyToCommand(" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 1,
usage: "[option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded.",
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
{
switch (info.GetArg(1))
{
case "list":
{
info.ReplyToCommand($" List of all plugins currently loaded by CounterStrikeSharp: {_loadedPlugins.Count} plugins loaded.", true);
foreach (var plugin in _loadedPlugins)
{
var sb = new StringBuilder();
sb.AppendFormat(" [#{0}]: \"{1}\" ({2})", plugin.PluginId, plugin.Name, plugin.Version);
if (!string.IsNullOrEmpty(plugin.Author)) sb.AppendFormat(" by {0}", plugin.Author);
if (!string.IsNullOrEmpty(plugin.Description))
{
sb.Append("\n");
sb.Append(" ");
sb.Append(plugin.Description);
}
info.ReplyToCommand(sb.ToString(), true);
}
break;
}
case "start":
case "load":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n", true);
break;
}
// If our arugment 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);
if (!path.EndsWith(".dll"))
{
path = Path.Combine(rootDir.FullName, $"plugins/{path}/{path}.dll");
}
else
{
path = Path.Combine(rootDir.FullName, path);
}
try
{
LoadPlugin(path);
}
catch (Exception e)
{
Logger.LogError(e, "Failed to load plugin from {Path}", path);
}
break;
}
case "stop":
case "unload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n", true);
break;
}
var pluginIdentifier = info.GetArg(2);
PluginContext? plugin = FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload();
_loadedPlugins.Remove(plugin);
break;
}
case "restart":
case "reload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n", true);
break;
}
var pluginIdentifier = info.GetArg(2);
var plugin = FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(true);
plugin.Load(true);
break;
}
default:
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded."
, true);
break;
}
}
private void RegisterPluginCommands()
{
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.", OnCSSPluginCommand);
}
}
}

View File

@@ -22,53 +22,12 @@ using System.Security;
namespace CounterStrikeSharp.API.Core
{
public class MethodAttribute<T> where T : Attribute
{
public MethodAttribute(T attribute, MethodInfo method)
{
Attribute = attribute;
Method = method;
}
public T Attribute;
public MethodInfo Method;
}
public static class Helpers
{
private static MethodAttribute<T>[] FindMethodAttributes<T>(BasePlugin plugin) where T: Attribute
{
return plugin
.GetType()
.GetMethods()
.Where(m => m.GetCustomAttributes(typeof(T), false).Length > 0)
.Select(x => new MethodAttribute<T>(x.GetCustomAttribute<T>(), x))
.ToArray();
}
private const string dllPath = "counterstrikesharp";
[SecurityCritical]
[DllImport(dllPath, EntryPoint = "InvokeNative")]
public static extern void InvokeNative(IntPtr ptr);
[UnmanagedCallersOnly]
// Used by .NET Host in C++ to initiate loading
public static int LoadAllPlugins()
{
try
{
var globalContext = new GlobalContext();
globalContext.InitGlobalContext();
return 1;
}
catch (Exception e)
{
Console.WriteLine(e);
return 0;
}
}
public delegate void Callback();
}
}

View File

@@ -0,0 +1,32 @@
namespace CounterStrikeSharp.API.Core.Hosting;
/// <summary>
/// Provides information about the CounterStrikeSharp host configuration.
/// </summary>
public interface IScriptHostConfiguration
{
/// <summary>
/// Gets the absolute path to the directory that contains CounterStrikeSharp files.
/// e.g. /game/csgo/addons/counterstrikesharp
/// </summary>
string RootPath { get; }
/// <summary>
/// Gets the absolute path to the directory that contains CounterStrikeSharp plugins.
/// e.g. /game/csgo/addons/counterstrikesharp/plugins
/// </summary>
string PluginPath { get; }
/// <summary>
/// Gets the absolute path to the directory that contains CounterStrikeSharp configs.
/// e.g. /game/csgo/addons/counterstrikesharp/configs
/// </summary>
string ConfigsPath { get; }
/// <summary>
/// Gets the absolute path to the directory that contains CounterStrikeSharp game data.
/// e.g. /game/csgo/addons/counterstrikesharp/gamedata
/// </summary>
string GameDataPath { get; }
}

View File

@@ -0,0 +1,20 @@
using System.IO;
using Microsoft.Extensions.Hosting;
namespace CounterStrikeSharp.API.Core.Hosting;
internal sealed class ScriptHostConfiguration : IScriptHostConfiguration
{
public string RootPath { get; }
public string PluginPath { get; }
public string ConfigsPath { get; }
public string GameDataPath { get; }
public ScriptHostConfiguration(IHostEnvironment hostEnvironment)
{
RootPath = Path.Join(new[] { hostEnvironment.ContentRootPath });
PluginPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "plugins" });
ConfigsPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "configs" });
GameDataPath = Path.Join(new[] { hostEnvironment.ContentRootPath, "gamedata" });
}
}

View File

@@ -14,28 +14,30 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
/// <summary>
/// Interface which every CounterStrikeSharp plugin must implement. Module will be created with parameterless constructor and then Load method will be called.
/// </summary>
public interface IPlugin
public interface IPlugin : IDisposable
{
/// <summary>
/// Name of the plugin.
/// </summary>
string ModuleName
{
get;
}
string ModuleName { get; }
/// <summary>
/// Module version.
/// </summary>
string ModuleVersion
{
get;
}
string ModuleVersion { get; }
string ModuleAuthor { get; }
string ModuleDescription { get; }
/// <summary>
/// This method is called by CounterStrikeSharp on plugin load and should be treated as plugin constructor.
@@ -48,5 +50,13 @@ namespace CounterStrikeSharp.API.Core
/// Event handlers, listeners etc. will automatically be deregistered.
/// </summary>
void Unload(bool hotReload);
string ModulePath { get; internal set; }
ILogger Logger { get; set; }
void RegisterAllAttributes(object instance);
void InitializeConfig(object instance, Type pluginType);
}
}

View File

@@ -23,6 +23,10 @@ namespace CounterStrikeSharp.API.Core
{
T Config { get; set; }
/// <summary>
/// Called when the `ConfigManager` has parsed the configuration file for this plugin
/// </summary>
/// <param name="config">Parsed config instance</param>
public void OnConfigParsed(T config);
}
}

View File

@@ -0,0 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
namespace CounterStrikeSharp.API.Core;
/// <summary>
/// Represents a service collection configuration for a plugin.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IPluginServiceCollection<T> where T : IPlugin
{
/// <summary>
/// Used to configure services exposed for dependency injection.
/// </summary>
public void ConfigureServices(IServiceCollection serviceCollection);
}

View File

@@ -0,0 +1,6 @@
namespace CounterStrikeSharp.API.Core;
public interface IStartupService
{
public void Load();
}

View File

@@ -125,6 +125,18 @@ namespace CounterStrikeSharp.API.Core
{
return new InputArgument(value);
}
[SecurityCritical]
public static implicit operator InputArgument(NativeObject value)
{
return new InputArgument(value.Handle);
}
[SecurityCritical]
public static implicit operator InputArgument(NativeEntity value)
{
return new InputArgument(value.Handle);
}
[SecurityCritical]
public static unsafe implicit operator InputArgument(void* value)

View File

@@ -126,5 +126,26 @@ namespace CounterStrikeSharp.API.Core
/// <param name="hostname">New hostname of the server</param>
[ListenerName("OnHostNameChanged")]
public delegate void OnHostNameChanged(string hostname);
/// <summary>
/// Called before the server enters fatal shutdown.
/// </summary>
[ListenerName("OnPreFatalShutdown")]
public delegate void OnServerPreFatalShutdown();
/// <summary>
/// Called when the server is in a loading stage.
/// </summary>
/// <param name="frameTime"></param>
[ListenerName("OnUpdateWhenNotInGame")]
public delegate void OnUpdateWhenNotInGame(float frameTime);
/// <summary>
/// Called before the world updates.
/// This seems to be called even when the server is hibernating.
/// </summary>
/// <param name="simulating"><see langword="true"/> if simulating, <see langword="false"/> otherwise</param>
[ListenerName("OnServerPreWorldUpdate")]
public delegate void OnServerPreWorldUpdate(bool simulating);
}
}

View File

@@ -2,28 +2,40 @@ using System;
using System.IO;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Core;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace CounterStrikeSharp.API.Core.Logging;
public static class CoreLogging
{
public static ILoggerFactory Factory { get; }
static CoreLogging()
{
var logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.With<SourceContextEnricher>()
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] {GlobalContext.RootDirectory, "logs", $"log-cssharp.txt"}), rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] {GlobalContext.RootDirectory, "logs", $"log-all.txt"}), rollingInterval: RollingInterval.Day, shared: true, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
public static ILoggerFactory Factory { get; private set; }
private static Logger? SerilogLogger { get; set; }
Factory =
LoggerFactory.Create(builder =>
{
builder.AddSerilog(logger);
});
public static void AddCoreLogging(this ILoggingBuilder builder, string contentRoot)
{
if (SerilogLogger == null)
{
SerilogLogger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.With<SourceContextEnricher>()
.WriteTo.Console(
outputTemplate:
"{Timestamp:HH:mm:ss} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-cssharp.txt" }),
rollingInterval: RollingInterval.Day,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-all.txt" }),
rollingInterval: RollingInterval.Day, shared: true,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
Factory =
LoggerFactory.Create(builder => { builder.AddSerilog(SerilogLogger); });
}
builder.AddSerilog(SerilogLogger);
}
}

View File

@@ -1,32 +0,0 @@
using System.IO;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace CounterStrikeSharp.API.Core.Logging;
public class PluginLogging
{
/// <summary>
/// Creates a logger scoped to a specific plugin
/// <remarks>Eventually this should probably come from a service collection</remarks>
/// </summary>
public static ILogger CreatePluginLogger(PluginContext pluginContext)
{
var logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.With(new PluginNameEnricher(pluginContext))
.WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u4}] (plugin:{PluginName}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] {GlobalContext.RootDirectory, "logs", $"log-{pluginContext.PluginType.Name}.txt"}), rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] {GlobalContext.RootDirectory, "logs", $"log-all.txt"}), rollingInterval: RollingInterval.Day, shared: true, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
.CreateLogger();
using ILoggerFactory loggerFactory =
LoggerFactory.Create(builder =>
{
builder.AddSerilog(logger);
});
return loggerFactory.CreateLogger(pluginContext.PluginType);
}
}

View File

@@ -1,3 +1,4 @@
using CounterStrikeSharp.API.Core.Plugin;
using Serilog.Core;
using Serilog.Events;
@@ -16,7 +17,7 @@ public class PluginNameEnricher : ILogEventEnricher
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var property = propertyFactory.CreateProperty(PropertyName, Context.PluginType.Name);
var property = propertyFactory.CreateProperty(PropertyName, Context.Plugin.ModuleName);
logEvent.AddPropertyIfAbsent(property);
}
}

View File

@@ -1,4 +1,8 @@
using System;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
@@ -10,8 +14,7 @@ public partial class CCSPlayerController
{
get
{
if (EntityIndex == null) return null;
return NativeAPI.GetUseridFromIndex((int)this.EntityIndex.Value.Value);
return NativeAPI.GetUseridFromIndex((int)this.Index);
}
}
@@ -24,9 +27,20 @@ public partial class CCSPlayerController
return VirtualFunctions.GiveNamedItem(PlayerPawn.Value.ItemServices.Handle, item, 0, 0, 0, 0);
}
public IntPtr GiveNamedItem(CsItem item)
{
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
if (string.IsNullOrWhiteSpace(itemString))
{
return IntPtr.Zero;
}
return this.GiveNamedItem(itemString);
}
public void PrintToConsole(string message)
{
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, $"{message}\n\0");
NativeAPI.PrintToConsole((int)Index, $"{message}\n\0");
}
public void PrintToChat(string message)
@@ -38,13 +52,15 @@ public partial class CCSPlayerController
{
VirtualFunctions.ClientPrint(this.Handle, HudDestination.Center, message, 0, 0, 0, 0);
}
public void PrintToCenterHtml(string message) => PrintToCenterHtml(message, 5);
public void PrintToCenterHtml(string message)
public void PrintToCenterHtml(string message, int duration)
{
var @event = new EventShowSurvivalRespawnStatus(true)
{
LocToken = message,
Duration = 5,
Duration = duration,
Userid = this
};
@event.FireEventToClient(this);
@@ -128,8 +144,94 @@ public partial class CCSPlayerController
team);
}
/// <summary>
/// Get a ConVar value for given player
/// </summary>
/// <param name="conVar">Name of the convar to retrieve</param>
/// <returns>ConVar string value</returns>
public string GetConVarValue(string conVar)
{
return NativeAPI.GetClientConvarValue(this.Slot, conVar);
}
public string GetConVarValue(ConVar? conVar)
{
if (conVar == null)
{
throw new Exception("Invalid convar passed to 'GetConVarValue'");
}
return GetConVarValue(conVar.Name);
}
/// <summary>
/// Sets a ConVar value on a fake client (bot).
/// </summary>
/// <param name="conVar">Console variable name</param>
/// <param name="value">String value to set</param>
/// <exception cref="InvalidOperationException">Player is not a bot</exception>
public void SetFakeClientConVar(string conVar, string value)
{
if (!IsBot)
{
throw new InvalidOperationException("'SetFakeClientConVar' can only be called for fake clients (bots)");
}
NativeAPI.SetFakeClientConvarValue(this.Slot, conVar, value);
}
/// <summary>
/// <inheritdoc cref="SetFakeClientConVar(string,string)"/>
/// </summary>
/// <exception cref="ArgumentException"><paramref name="conVar"/> is <see langword="null"/></exception>
/// <inheritdoc cref="SetFakeClientConVar(string,string)" select="exception"/>
public void SetFakeClientConVar(ConVar conVar, string value)
{
if (conVar == null)
{
throw new ArgumentException("Invalid convar passed to 'SetFakeClientConVar'");
}
SetFakeClientConVar(conVar.Name, value);
}
/// <summary>
/// Gets the active pawns button state. Will work even if the player is dead or observing.
/// </summary>
public PlayerButtons Buttons => (PlayerButtons)Pawn.Value.MovementServices!.Buttons.ButtonStates[0];
public void ExecuteClientCommand(string command) => NativeAPI.IssueClientCommand(Slot, command);
public int Slot => (int)Index - 1;
/// <summary>
/// Returns the authorized SteamID of this user which has been validated with the SteamAPI.
/// </summary>
public SteamID? AuthorizedSteamID
{
get
{
if (!this.IsValid) return null;
var authorizedSteamId = NativeAPI.GetPlayerAuthorizedSteamid(this.Slot);
if ((long)authorizedSteamId == -1) return null;
return (SteamID)authorizedSteamId;
}
}
/// <summary>
/// Returns the IP address (and possibly port) of this player.
/// <remarks>Returns 127.0.0.1 if the player is a bot.</remarks>
/// </summary>
public string? IpAddress
{
get
{
if (!this.IsValid) return null;
var ipAddress = NativeAPI.GetPlayerIpAddress(this.Slot);
if (string.IsNullOrWhiteSpace(ipAddress)) return null;
return ipAddress;
}
}
}

View File

@@ -12,23 +12,36 @@ namespace CounterStrikeSharp.API.Core;
public partial class CEntityInstance : IEquatable<CEntityInstance>
{
public bool IsValid => Handle != IntPtr.Zero;
public CEntityInstance(IntPtr pointer) : base(pointer)
{
}
public CEntityIndex? EntityIndex => IsValid ? Entity?.EntityHandle.Index : null;
public CEntityInstance(uint rawHandle) : base(rawHandle)
{
}
/// <summary>
/// Checks that the entity handle is valid and the handle points to a valid entity
/// </summary>
public bool IsValid => EntityHandle.IsValid && Handle != IntPtr.Zero;
[Obsolete("Use Index instead", true)]
public CEntityIndex? EntityIndex => new CEntityIndex(EntityHandle.Index);
public uint Index => EntityHandle.Index;
public string DesignerName => IsValid ? Entity?.DesignerName : null;
public void Remove() => VirtualFunctions.UTIL_Remove(this.Handle);
public bool Equals(CEntityInstance? other)
{
return this.Handle == other?.Handle;
return this.EntityHandle.Equals(other?.EntityHandle);
}
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || obj is CEntityInstance other && Equals(other);
return obj is CEntityInstance other && Equals(other);
}
public override int GetHashCode()
@@ -50,5 +63,5 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
public partial class CEntityIdentity
{
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
public unsafe CHandle<CEntityInstance> EntityHandle => new(Handle + 0x10);
public unsafe CHandle<CEntityInstance> EntityHandle => new(this.Handle + 0x10);
}

View File

@@ -9420,10 +9420,8 @@ public partial class CEntityIdentity : NativeObject
}
public partial class CEntityInstance : NativeObject
public partial class CEntityInstance : NativeEntity
{
public CEntityInstance (IntPtr pointer) : base(pointer) {}
// m_iszPrivateVScripts
public string PrivateVScripts
{

View File

@@ -0,0 +1,14 @@
namespace CounterStrikeSharp.API.Core.Plugin.Host;
public interface IPluginContextQueryHandler
{
IPluginContext? FindPluginByType(Type moduleClass);
IPluginContext? FindPluginById(int id);
IPluginContext? FindPluginByModuleName(string name);
IPluginContext? FindPluginByModulePath(string path);
IPluginContext? FindPluginByIdOrName(string query);
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace CounterStrikeSharp.API.Core.Plugin.Host;
public interface IPluginManager
{
public void Load();
public void LoadPlugin(string path);
public IEnumerable<PluginContext> GetLoadedPlugins();
}

View File

@@ -0,0 +1,38 @@
using System.Linq;
namespace CounterStrikeSharp.API.Core.Plugin.Host;
public class PluginContextQueryHandler : IPluginContextQueryHandler
{
private readonly IPluginManager _pluginManager;
public PluginContextQueryHandler(IPluginManager pluginManager)
{
_pluginManager = pluginManager;
}
public IPluginContext? FindPluginByType(Type moduleClass)
{
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.GetType() == moduleClass);
}
public IPluginContext? FindPluginById(int id)
{
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.PluginId == id);
}
public IPluginContext? FindPluginByModuleName(string name)
{
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.ModuleName == name);
}
public IPluginContext? FindPluginByModulePath(string path)
{
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.Plugin.ModulePath == path);
}
public IPluginContext? FindPluginByIdOrName(string query)
{
return _pluginManager.GetLoadedPlugins().FirstOrDefault(x => x.PluginId.ToString() == query || x.Plugin.ModuleName == query);
}
}

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Core.Hosting;
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 IServiceProvider _serviceProvider;
private readonly ILogger<PluginManager> _logger;
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ILogger<PluginManager> logger, IServiceProvider serviceProvider)
{
_scriptHostConfiguration = scriptHostConfiguration;
_logger = logger;
_serviceProvider = serviceProvider;
}
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();
foreach (var path in pluginAssemblyPaths)
{
try
{
LoadPlugin(path);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
}
public IEnumerable<PluginContext> GetLoadedPlugins()
{
return _loadedPluginContexts;
}
public void LoadPlugin(string path)
{
var plugin = new PluginContext(_serviceProvider, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
_loadedPluginContexts.Add(plugin);
plugin.Load();
}
}

View File

@@ -0,0 +1,11 @@
namespace CounterStrikeSharp.API.Core.Plugin;
public interface IPluginContext
{
PluginState State { get; }
IPlugin Plugin { get; }
int PluginId { get; }
void Load(bool hotReload);
void Unload(bool hotReload);
}

View File

@@ -0,0 +1,209 @@
/*
* 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/>. *
*/
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Logging;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace CounterStrikeSharp.API.Core.Plugin
{
public class PluginContext : IPluginContext
{
public PluginState State { get; set; } = PluginState.Unregistered;
public IPlugin Plugin { get; private set; }
private PluginLoader Loader { get; set; }
private IServiceProvider ServiceProvider { get; set; }
public int PluginId { get; }
private readonly IScriptHostConfiguration _hostConfiguration;
private readonly string _path;
private readonly FileSystemWatcher _fileWatcher;
private readonly IServiceProvider _applicationServiceProvider;
// TOOD: ServiceCollection
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
public PluginContext(IServiceProvider applicationServiceProvider, IScriptHostConfiguration hostConfiguration, string path, int id)
{
_applicationServiceProvider = applicationServiceProvider;
_hostConfiguration = hostConfiguration;
_path = path;
PluginId = id;
Loader = PluginLoader.CreateFromAssemblyFile(path,
new[]
{
typeof(IPlugin), typeof(ILogger), typeof(IServiceCollection), typeof(IPluginServiceCollection<>)
}, config =>
{
config.EnableHotReload = true;
config.IsUnloadable = true;
});
if (CoreConfig.PluginHotReloadEnabled)
{
_fileWatcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(path)
};
_fileWatcher.Deleted += async (s, e) =>
{
if (e.FullPath == path)
{
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Plugin.ModuleName);
Unload(true);
}
};
_fileWatcher.Filter = "*.dll";
_fileWatcher.EnableRaisingEvents = true;
Loader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
}
}
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
{
_logger.LogInformation("Reloading plugin {Name}", Plugin.ModuleName);
Loader = eventargs.Loader;
Unload(hotReload: true);
Load(hotReload: true);
return Task.CompletedTask;
}
public void Load(bool hotReload = false)
{
if (State == PluginState.Loaded) return;
using (Loader.EnterContextualReflection())
{
var defaultAssembly = Loader.LoadDefaultAssembly();
Type pluginType = defaultAssembly.GetTypes()
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
if (pluginType == null) throw new Exception("Unable to find plugin in assembly");
var serviceCollection = new ServiceCollection();
serviceCollection.Scan(scan =>
scan.FromAssemblies(defaultAssembly)
.AddClasses(c => c.AssignableTo<IPlugin>())
.AsSelf()
.WithSingletonLifetime()
);
serviceCollection.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog(new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.With(new PluginNameEnricher(this))
.WriteTo.Console(
outputTemplate:
"{Timestamp:HH:mm:ss} [{Level:u4}] (plugin:{PluginName}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(
Path.Join(new[]
{
_hostConfiguration.RootPath, "logs",
$"log-{pluginType.Assembly.GetName().Name}.txt"
}), rollingInterval: RollingInterval.Day,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] { _hostConfiguration.RootPath, "logs", $"log-all.txt" }),
rollingInterval: RollingInterval.Day, shared: true,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] plugin:{PluginName} {Message:lj}{NewLine}{Exception}")
.CreateLogger());
});
Type interfaceType = typeof(IPluginServiceCollection<>).MakeGenericType(pluginType);
Type[] serviceCollectionConfiguratorTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => interfaceType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
.ToArray();
if (serviceCollectionConfiguratorTypes.Any())
{
foreach (var t in serviceCollectionConfiguratorTypes)
{
var pluginServiceCollection = Activator.CreateInstance(t);
MethodInfo method = t.GetMethod("ConfigureServices");
method?.Invoke(pluginServiceCollection, new object[] { serviceCollection });
}
}
serviceCollection.AddSingleton(this);
ServiceProvider = serviceCollection.BuildServiceProvider();
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
var currentVersion = Api.GetVersion();
// Ignore version 0 for local development
if (currentVersion > 0 && minimumApiVersion != null && minimumApiVersion > currentVersion)
throw new Exception(
$"Plugin \"{Path.GetFileName(_path)}\" requires a newer version of CounterStrikeSharp. The plugin expects version [{minimumApiVersion}] but the current version is [{currentVersion}].");
_logger.LogInformation("Loading plugin {Name}", pluginType.Assembly.GetName().Name);
Plugin = ServiceProvider.GetRequiredService(pluginType) as IPlugin;
if (Plugin == null) throw new Exception("Unable to create plugin instance");
State = PluginState.Loading;
Plugin.ModulePath = _path;
Plugin.RegisterAllAttributes(Plugin);
Plugin.Logger = ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(pluginType);
Plugin.InitializeConfig(Plugin, pluginType);
Plugin.Load(hotReload);
_logger.LogInformation("Finished loading plugin {Name}", Plugin.ModuleName);
State = PluginState.Loaded;
}
}
public void Unload(bool hotReload = false)
{
if (State == PluginState.Unloaded) return;
State = PluginState.Unloaded;
var cachedName = Plugin.ModuleName;
_logger.LogInformation("Unloading plugin {Name}", Plugin.ModuleName);
Plugin.Unload(hotReload);
Plugin.Dispose();
_logger.LogInformation("Finished unloading plugin {Name}", cachedName);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace CounterStrikeSharp.API.Core.Plugin;
public enum PluginState
{
Unregistered,
Loading,
Loaded,
Unloaded,
}

View File

@@ -1,137 +0,0 @@
/*
* 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/>. *
*/
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Logging;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
public class PluginContext
{
private BasePlugin _plugin;
private PluginLoader _assemblyLoader;
public string Name => _plugin?.ModuleName;
public string Version => _plugin?.ModuleVersion;
public string Description => _plugin.ModuleDescription;
public string Author => _plugin.ModuleAuthor;
public Type PluginType => _plugin?.GetType();
public string PluginPath => _plugin?.ModulePath;
public int PluginId { get; }
private readonly string _path;
private readonly FileSystemWatcher _fileWatcher;
// TOOD: ServiceCollection
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
public PluginContext(string path, int id)
{
_path = path;
PluginId = id;
_assemblyLoader = PluginLoader.CreateFromAssemblyFile(path, new[] { typeof(IPlugin) }, config =>
{
config.EnableHotReload = true;
config.IsUnloadable = true;
});
_fileWatcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(path)
};
_fileWatcher.Deleted += async (s, e) =>
{
if (e.FullPath == path)
{
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Name);
Unload(true);
}
};
_fileWatcher.Filter = "*.dll";
_fileWatcher.EnableRaisingEvents = true;
_assemblyLoader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
}
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
{
_logger.LogInformation("Reloading plugin {Name}", Name);
_assemblyLoader = eventargs.Loader;
Unload(hotReload: true);
Load(hotReload: true);
return Task.CompletedTask;
}
public void Load(bool hotReload = false)
{
using (_assemblyLoader.EnterContextualReflection())
{
Type pluginType = _assemblyLoader.LoadDefaultAssembly().GetTypes()
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t));
if (pluginType == null) throw new Exception("Unable to find plugin in DLL");
var minimumApiVersion = pluginType.GetCustomAttribute<MinimumApiVersion>()?.Version;
var currentVersion = Api.GetVersion();
// Ignore version 0 for local development
if (currentVersion > 0 && minimumApiVersion != null && minimumApiVersion > currentVersion)
throw new Exception(
$"Plugin \"{Path.GetFileName(_path)}\" requires a newer version of CounterStrikeSharp. The plugin expects version [{minimumApiVersion}] but the current version is [{currentVersion}].");
_logger.LogInformation("Loading plugin {Name}", pluginType.Name);
_plugin = (BasePlugin)Activator.CreateInstance(pluginType)!;
_plugin.ModulePath = _path;
_plugin.RegisterAllAttributes(_plugin);
_plugin.Logger = PluginLogging.CreatePluginLogger(this);
_plugin.InitializeConfig(_plugin, pluginType);
_plugin.Load(hotReload);
_logger.LogInformation("Finished loading plugin {Name}", Name);
}
}
public void Unload(bool hotReload = false)
{
var cachedName = Name;
_logger.LogInformation("Unloading plugin {Name}", Name);
_plugin.Unload(hotReload);
_plugin.Dispose();
if (!hotReload)
{
_assemblyLoader.Dispose();
_fileWatcher.Dispose();
}
_logger.LogInformation("Finished unloading plugin {Name}", Name);
}
}
}

View File

@@ -205,6 +205,16 @@ namespace CounterStrikeSharp.API.Core
return;
}
else if (arg is NativeObject nativeObject)
{
Push(context, (InputArgument)nativeObject);
return;
}
else if (arg is NativeEntity nativeValue)
{
Push(context, (InputArgument)nativeValue);
return;
}
if (Marshal.SizeOf(arg.GetType()) <= 8)
{

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
@@ -23,7 +23,10 @@
<ItemGroup>
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Scrutor" Version="4.2.2" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

View File

@@ -0,0 +1,5 @@
// Global using directives
global using System;
global using System.IO;
global using CounterStrikeSharp.API.Core;

View File

@@ -94,7 +94,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of groups.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
@@ -119,7 +119,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
AddPlayerToGroup((SteamID)player.SteamID, groups);
AddPlayerToGroup((SteamID)player.AuthorizedSteamID, groups);
}
/// <summary>
@@ -161,7 +161,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
RemovePlayerFromGroup((SteamID)player.SteamID, true, groups);
RemovePlayerFromGroup((SteamID)player.AuthorizedSteamID, true, groups);
}
/// <summary>

View File

@@ -102,7 +102,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
var playerData = GetPlayerAdminData(player.AuthorizedSteamID);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
@@ -136,7 +136,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
@@ -165,7 +165,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return true;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return false; }
var playerData = GetPlayerAdminData((SteamID)player.SteamID);
var playerData = GetPlayerAdminData((SteamID)player.AuthorizedSteamID);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
@@ -193,7 +193,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
// The server console should have access to all commands, regardless of permissions.
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
SetPlayerCommandOverride((SteamID)player.SteamID, command, state);
SetPlayerCommandOverride((SteamID)player.AuthorizedSteamID, command, state);
}
/// <summary>
@@ -235,7 +235,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.SteamID, flags);
AddPlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
}
/// <summary>
@@ -278,7 +278,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.SteamID, flags);
RemovePlayerPermissions((SteamID)player.AuthorizedSteamID, flags);
}
/// <summary>
@@ -306,7 +306,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
ClearPlayerPermissions((SteamID)player.SteamID);
ClearPlayerPermissions((SteamID)player.AuthorizedSteamID);
}
/// <summary>
@@ -335,7 +335,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
SetPlayerImmunity((SteamID)player.SteamID, value);
SetPlayerImmunity((SteamID)player.AuthorizedSteamID, value);
}
/// <summary>
@@ -366,10 +366,10 @@ namespace CounterStrikeSharp.API.Modules.Admin
if (target == null) return false;
if (!target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return false;
var callerData = GetPlayerAdminData((SteamID)caller.SteamID);
var callerData = GetPlayerAdminData((SteamID)caller.AuthorizedSteamID);
if (callerData == null) return false;
var targetData = GetPlayerAdminData((SteamID)target.SteamID);
var targetData = GetPlayerAdminData((SteamID)target.AuthorizedSteamID);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;

View File

@@ -31,7 +31,8 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
// If we have a command in the "command_overrides" section in "configs/admins.json",
// we skip the checks below and just return the defined value.
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (caller?.AuthorizedSteamID == null) return false;
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
if (adminData == null) return false;
if (adminData.CommandOverrides.ContainsKey(Command))
{

View File

@@ -23,7 +23,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
var adminData = AdminManager.GetPlayerAdminData(caller.AuthorizedSteamID);
if (adminData == null) return false;
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.Flags).Count()) > 0;
}

View File

@@ -0,0 +1,14 @@
using CounterStrikeSharp.API.Modules.Commands.Targeting;
namespace CounterStrikeSharp.API.Modules.Commands;
public static class CommandExtensions
{
/// <summary>
/// Treats the argument at the specified index as a target string (@all, @me etc.) and returns the result.
/// </summary>
public static TargetResult GetArgTargetResult(this CommandInfo commandInfo, int index)
{
return new Target(commandInfo.GetArg(index)).GetTarget(commandInfo.CallingPlayer);
}
}

View File

@@ -25,13 +25,14 @@ namespace CounterStrikeSharp.API.Modules.Commands
public delegate HookResult CommandListenerCallback(CCSPlayerController? player, CommandInfo commandInfo);
private CCSPlayerController _player;
public IntPtr Handle { get; private set; }
public CCSPlayerController? CallingPlayer { get; }
public IntPtr Handle { get; }
internal CommandInfo(IntPtr pointer, CCSPlayerController player)
{
Handle = pointer;
_player = player;
CallingPlayer = player;
}
public int ArgCount => NativeAPI.CommandGetArgCount(Handle);
@@ -42,12 +43,12 @@ namespace CounterStrikeSharp.API.Modules.Commands
public string ArgByIndex(int index) => NativeAPI.CommandGetArgByIndex(Handle, index);
public string GetArg(int index) => NativeAPI.CommandGetArgByIndex(Handle, index);
public void ReplyToCommand(string message, bool console = false) {
if (_player != null)
if (CallingPlayer != null)
{
if (console) { _player.PrintToConsole(message); }
else _player.PrintToChat(message);
if (console) { CallingPlayer.PrintToConsole(message); }
else CallingPlayer.PrintToChat(message);
}
else
{

View File

@@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Commands.Targeting;
public class Target
{
private TargetType Type { get; }
private string Raw { get; }
private string Slug { get; }
private static readonly Dictionary<string, TargetType> TargetTypeMap = new(StringComparer.OrdinalIgnoreCase)
{
{ "@all", TargetType.GroupAll },
{ "@bots", TargetType.GroupBots },
{ "@human", TargetType.GroupHumans },
{ "@alive", TargetType.GroupAlive },
{ "@dead", TargetType.GroupDead },
{ "@!me", TargetType.GroupNotMe },
{ "@me", TargetType.PlayerMe },
{ "@ct", TargetType.TeamCt },
{ "@t", TargetType.TeamT },
{ "@spec", TargetType.TeamSpec }
};
private static bool ConstTargetType(string target, out TargetType targetType)
{
targetType = TargetType.Invalid;
if (!target.StartsWith("@"))
{
return false;
}
return TargetTypeMap.TryGetValue(target, out targetType);
}
private bool IdTargetType(string target,
out TargetType targetType,
[MaybeNullWhen(false)] out string slug)
{
targetType = TargetType.Invalid;
slug = null!;
if (!target.StartsWith("#"))
{
return false;
}
slug = target.TrimStart('#');
if (slug.StartsWith("STEAM")) targetType = TargetType.IdSteamEscaped;
else if (!ulong.TryParse(slug, out _)) targetType = TargetType.ExplicitName;
else if (slug.Length == 17) targetType = TargetType.IdSteam64;
else targetType = TargetType.IdUserid;
return true;
}
public Target(string target)
{
Raw = target.Trim();
if (ConstTargetType(Raw, out var targetType))
{
Type = targetType;
Slug = Raw;
}
else if (IdTargetType(Raw, out targetType, out var slug))
{
Type = targetType;
Slug = slug;
}
else
{
Type = TargetType.ImplicitName;
Slug = Raw;
}
}
private bool TargetPredicate(CCSPlayerController player, CCSPlayerController? caller)
{
switch (Type)
{
case TargetType.TeamCt:
return player.TeamNum == (byte)CsTeam.CounterTerrorist;
case TargetType.TeamT:
return player.TeamNum == (byte)CsTeam.Terrorist;
case TargetType.TeamSpec:
return player.TeamNum == (byte)CsTeam.Spectator;
case TargetType.GroupAll:
return true;
case TargetType.GroupBots:
return player.IsBot;
case TargetType.GroupHumans:
return !player.IsBot;
case TargetType.GroupAlive:
return player.PlayerPawn is { IsValid: true, Value.LifeState: (byte)LifeState_t.LIFE_ALIVE };
case TargetType.GroupDead:
return player.PlayerPawn is { IsValid: true, Value.LifeState: (byte)LifeState_t.LIFE_DEAD or (byte)LifeState_t.LIFE_DYING };
case TargetType.GroupNotMe:
return player.SteamID != caller?.SteamID;
case TargetType.PlayerMe:
return player.SteamID == caller?.SteamID;
case TargetType.IdUserid:
return player.UserId.ToString() == Slug;
case TargetType.IdSteamEscaped:
return ((SteamID)player.SteamID).SteamId2 == Slug;
case TargetType.IdSteam64:
return ((SteamID)player.SteamID).SteamId64.ToString() == Slug;
case TargetType.ExplicitName:
case TargetType.ImplicitName:
return player.PlayerName.Contains(Slug, StringComparison.OrdinalIgnoreCase);
default:
return false;
}
}
public TargetResult GetTarget(CCSPlayerController? caller)
{
var players = Utilities.GetPlayers().Where(player => TargetPredicate(player, caller)).ToList();
return new TargetResult() { Players = players };
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace CounterStrikeSharp.API.Modules.Commands.Targeting;
public class TargetResult : IEnumerable<CCSPlayerController>
{
public List<CCSPlayerController> Players { get; set; } = new();
public IEnumerator<CCSPlayerController> GetEnumerator()
{
return Players.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@@ -0,0 +1,25 @@
namespace CounterStrikeSharp.API.Modules.Commands.Targeting;
public enum TargetType
{
TeamCt, // @ct
TeamT, // @t
TeamSpec, // @spec
GroupAll, // @all
GroupBots, // @bots
GroupHumans, // @human
GroupAlive, // @alive
GroupDead, // @dead
GroupNotMe, // @!me
PlayerMe, // @me
IdUserid, // #4
IdSteamEscaped, // "#STEAM_0:1:8614"
IdSteam64, // #76561198116940237
ExplicitName, // #name
ImplicitName, // name
Invalid
}

View File

@@ -43,10 +43,11 @@ namespace CounterStrikeSharp.API.Modules.Config
{
string directoryPath = Path.Combine(_pluginConfigsFolderPath, pluginName);
string configPath = Path.Combine(directoryPath, $"{pluginName}.json");
string exampleConfigPath = Path.Combine(directoryPath, $"{pluginName}.example.json");
T config = (T)Activator.CreateInstance(typeof(T))!;
if (!File.Exists(configPath))
if (!File.Exists(configPath) && !File.Exists(exampleConfigPath))
{
try
{
@@ -56,8 +57,10 @@ namespace CounterStrikeSharp.API.Modules.Config
}
StringBuilder builder = new StringBuilder();
builder.Append($"// This configuration was automatically generated by CounterStrikeSharp for plugin '{pluginName}', at {DateTimeOffset.Now:yyyy/MM/dd hh:mm:ss}\n");
builder.Append(JsonSerializer.Serialize<T>(config, new JsonSerializerOptions { WriteIndented = true }));
builder.Append(
$"// This configuration was automatically generated by CounterStrikeSharp for plugin '{pluginName}', at {DateTimeOffset.Now:yyyy/MM/dd hh:mm:ss}\n");
builder.Append(JsonSerializer.Serialize<T>(config,
new JsonSerializerOptions { WriteIndented = true }));
File.WriteAllText(configPath, builder.ToString());
return config;
}
@@ -65,6 +68,17 @@ namespace CounterStrikeSharp.API.Modules.Config
{
_logger.LogError(ex, "Failed to generate configuration file for {PluginName}", pluginName);
}
} else if (File.Exists(exampleConfigPath) && !File.Exists(configPath))
{
try
{
_logger.LogInformation("Copying example configuration file for {PluginName}", pluginName);
File.Copy(exampleConfigPath, configPath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to copy example configuration file for {PluginName}", pluginName);
}
}
try

View File

@@ -0,0 +1,241 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace CounterStrikeSharp.API.Modules.Entities.Constants
{
public enum CsItem
{
//-----------------------------------------
//EQUIPMENT
//-----------------------------------------
[EnumMember(Value = "item_kevlar")]
Kevlar = 000,
[EnumMember(Value = "item_assaultsuit")]
AssaultSuit = 001,
KevlarHelmet = AssaultSuit,
[EnumMember(Value = "weapon_taser")]
Taser = 002,
Zeus = Taser,
[EnumMember(Value = "weapon_snowball")]
Snowball = 003,
[EnumMember(Value = "weapon_shield")]
Shield = 004,
[EnumMember(Value = "weapon_c4")]
C4 = 005,
Bomb = C4,
[EnumMember(Value = "weapon_healthshot")]
Healthshot = 006,
[EnumMember(Value = "weapon_breachcharge")]
BreachCharge = 007,
[EnumMember(Value = "weapon_tablet")]
Tablet = 008,
[EnumMember(Value = "weapon_bumpmine")]
Bumpmine = 009,
//-----------------------------------------
//GRENADES
//-----------------------------------------
[EnumMember(Value = "weapon_smokegrenade")]
Smoke = 100,
SmokeGrenade = Smoke,
[EnumMember(Value = "weapon_flashbang")]
Flashbang = 101,
FlashbangGrenade = Flashbang,
[EnumMember(Value = "weapon_hegrenade")]
HighExplosive = 102,
HE = HighExplosive,
HighExplosiveGrenade = HighExplosive,
HEGrenade = HighExplosive,
[EnumMember(Value = "weapon_molotov")]
Molotov = 103,
[EnumMember(Value = "weapon_incgrenade")]
Incendiary = 104,
IncGrenade = Incendiary,
IncendiaryGrenade = Incendiary,
[EnumMember(Value = "weapon_decoy")]
Decoy = 105,
DecoyGrenade = Decoy,
//XRay-Grenade
[EnumMember(Value = "weapon_tagrenade")]
TacticalAwareness = 106,
TAGrenade = TacticalAwareness,
XRayGrenade = TacticalAwareness,
//Dangerzone: Better HighExplosive
[EnumMember(Value = "weapon_frag")]
Frag = 107,
FragGrenade = Frag,
//Dangerzone: Better Molotov
[EnumMember(Value = "weapon_firebomb")]
Firebomb = 108,
//Dangerzone: Decoy but Footsteps instead of gun sounds
[EnumMember(Value = "weapon_diversion")]
Diversion = 109,
//-----------------------------------------
//PISTOLS
//-----------------------------------------
[EnumMember(Value = "weapon_deagle")]
Deagle = 200,
DesertEagle = Deagle,
[EnumMember(Value = "weapon_glock")]
Glock = 201,
Glock18 = Glock,
[EnumMember(Value = "weapon_usp_silencer")]
USPS = 202,
USP = USPS,
[EnumMember(Value = "weapon_hkp2000")]
HKP2000 = 203,
P2000 = HKP2000,
P2K = HKP2000,
[EnumMember(Value = "weapon_elite")]
Elite = 204,
DualBerettas = Elite,
Dualies = Elite,
[EnumMember(Value = "weapon_tec9")]
Tec9 = 205,
[EnumMember(Value = "weapon_p250")]
P250 = 206,
[EnumMember(Value = "weapon_cz75a")]
CZ = 207,
CZ75 = CZ,
[EnumMember(Value = "weapon_fiveseven")]
FiveSeven = 208,
[EnumMember(Value = "weapon_revolver")]
Revolver = 209,
R8 = Revolver,
//-----------------------------------------
//MID-TIER
//-----------------------------------------
[EnumMember(Value = "weapon_mac10")]
Mac10 = 300,
[EnumMember(Value = "weapon_mp9")]
MP9 = 301,
[EnumMember(Value = "weapon_mp7")]
MP7 = 302,
[EnumMember(Value = "weapon_p90")]
P90 = 303,
[EnumMember(Value = "weapon_mp5sd")]
MP5SD = 304,
MP5 = MP5SD,
[EnumMember(Value = "weapon_bizon")]
Bizon = 305,
PPBizon = Bizon,
[EnumMember(Value = "weapon_ump45")]
UMP45 = 306,
UMP = UMP45,
[EnumMember(Value = "weapon_xm1014")]
XM1014 = 307,
[EnumMember(Value = "weapon_nova")]
Nova = 308,
[EnumMember(Value = "weapon_mag7")]
MAG7 = 309,
[EnumMember(Value = "weapon_sawedoff")]
SawedOff = 310,
[EnumMember(Value = "weapon_m249")]
M249 = 311,
[EnumMember(Value = "weapon_negev")]
Negev = 312,
//-----------------------------------------
//RIFLES
//-----------------------------------------
[EnumMember(Value = "weapon_ak47")]
AK47 = 400,
[EnumMember(Value = "weapon_m4a1_silencer")]
M4A1S = 401,
SilencedM4 = M4A1S,
[EnumMember(Value = "weapon_m4a1")]
M4A1 = 402,
M4A4 = M4A1,
[EnumMember(Value = "weapon_galilar")]
GalilAR = 403,
Galil = GalilAR,
[EnumMember(Value = "weapon_famas")]
Famas = 404,
[EnumMember(Value = "weapon_sg556")]
SG556 = 405,
SG553 = SG556,
Krieg = SG556,
[EnumMember(Value = "weapon_awp")]
AWP = 406,
[EnumMember(Value = "weapon_aug")]
AUG = 407,
[EnumMember(Value = "weapon_ssg08")]
SSG08 = 408,
Scout = SSG08,
[EnumMember(Value = "weapon_scar20")]
SCAR20 = 409,
AutoSniperCT = SCAR20,
[EnumMember(Value = "weapon_g3sg1")]
G3SG1 = 410,
AutoSniperT = G3SG1,
//-----------------------------------------
//KNIFE
//-----------------------------------------
[EnumMember(Value = "weapon_knife_t")]
DefaultKnifeT = 500,
KnifeT = DefaultKnifeT,
[EnumMember(Value = "weapon_knife")]
DefaultKnifeCT = 501,
KnifeCT = DefaultKnifeCT,
Knife = DefaultKnifeCT,
}
}

View File

@@ -0,0 +1,78 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Entities;
public static class EntitySystem
{
private static Lazy<IntPtr> ConcreteEntityListPointer = new(NativeAPI.GetConcreteEntityListPointer);
private const int MaxEntities = 32768;
private const int MaxEntitiesPerChunk = 512;
private const int MaxChunks = MaxEntities / MaxEntitiesPerChunk;
private const int SizeOfEntityIdentity = 0x78;
private const int HandleOffset = 0x10;
private const uint InvalidEHandleIndex = 0xFFFFFFFF;
static unsafe Span<IntPtr> IdentityChunks => new((void*)ConcreteEntityListPointer.Value, MaxChunks);
public static IntPtr FirstActiveEntity => Marshal.ReadIntPtr(ConcreteEntityListPointer.Value, MaxEntitiesPerChunk);
public static IntPtr? GetEntityByHandle(uint raw)
{
return GetEntityByHandle(new CHandle<CEntityInstance>(raw));
}
public static IntPtr? GetEntityByHandle<T>(CHandle<T> handle) where T : NativeEntity
{
if (!handle.IsValid)
return null;
IntPtr pChunkToUse = IdentityChunks[(int)(handle.Index / MaxEntitiesPerChunk)];
if (pChunkToUse == IntPtr.Zero)
return null;
IntPtr pIdentityPtr = IntPtr.Add(pChunkToUse, SizeOfEntityIdentity * (int)(handle.Index % MaxEntitiesPerChunk));
if (pIdentityPtr == IntPtr.Zero)
return null;
var foundHandle = new CEntityHandle(pIdentityPtr + HandleOffset);
if (foundHandle.Raw != handle.Raw)
return null;
return Marshal.ReadIntPtr(pIdentityPtr);
}
public static IntPtr? GetEntityByIndex(uint index)
{
if ((int)index <= -1 || index >= MaxEntities - 1) return null;
IntPtr pChunkToUse = IdentityChunks[(int)(index / MaxEntitiesPerChunk)];
if (pChunkToUse == IntPtr.Zero)
return null;
IntPtr pIdentityPtr = IntPtr.Add(pChunkToUse, SizeOfEntityIdentity * (int)(index % MaxEntitiesPerChunk));
if (pIdentityPtr == IntPtr.Zero)
return null;
var foundHandle = new CEntityHandle(pIdentityPtr + HandleOffset);
if (foundHandle.Index != index)
return null;
return Marshal.ReadIntPtr(pIdentityPtr);
}
public static uint GetRawHandleFromEntityPointer(IntPtr pointer)
{
if (pointer == IntPtr.Zero)
return InvalidEHandleIndex;
return Schema.GetPointer<CEntityIdentity?>(pointer, "CEntityInstance", "m_pEntity")?.EntityHandle.Raw ??
InvalidEHandleIndex;
}
}

View File

@@ -48,16 +48,12 @@ namespace CounterStrikeSharp.API.Modules.Entities
public bool Equals(SteamID? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return SteamId64 == other.SteamId64;
return other != null && SteamId64 == other.SteamId64;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
if (obj?.GetType() != this.GetType()) return false;
return Equals((SteamID)obj);
}

View File

@@ -77,7 +77,7 @@ namespace CounterStrikeSharp.API.Modules.Events
break;
case var _ when value is CCSPlayerController player:
// When I was testing this, the code seems to expect a slot, even though it is called index
SetEntityIndex(name, (int)player.EntityIndex.Value.Value - 1);
SetEntityIndex(name, (int)player.Index - 1);
break;
case var _ when value is string s:
SetString(name, s);
@@ -124,6 +124,6 @@ namespace CounterStrikeSharp.API.Modules.Events
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.EntityIndex.Value.Value);
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.Index);
}
}

View File

@@ -73,7 +73,7 @@ namespace CounterStrikeSharp.API.Modules.Memory
return types[Enum.GetUnderlyingType(type)];
}
GlobalContext.Instance.Logger.LogWarning("Error retrieving data type for type {Type}", type.FullName);
Core.Application.Instance.Logger.LogWarning("Error retrieving data type for type {Type}", type.FullName);
return null;
}

View File

@@ -28,11 +28,35 @@ public abstract class BaseMemoryFunction : NativeObject
return function;
}
private static IntPtr CreateValveFunctionBySignature(string signature, string binarypath, DataType returnType,
DataType[] argumentTypes)
{
if (!_createdFunctions.TryGetValue(signature, out var function))
{
try
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, binarypath, signature,
argumentTypes.Length, (int)returnType, argumentTypes.Cast<object>().ToArray());
_createdFunctions[signature] = function;
}
catch (Exception)
{
}
}
return function;
}
public BaseMemoryFunction(string signature, DataType returnType, DataType[] parameters) : base(
CreateValveFunctionBySignature(signature, returnType, parameters))
{
}
public BaseMemoryFunction(string signature, string binarypath, DataType returnType, DataType[] parameters) : base(
CreateValveFunctionBySignature(signature, binarypath, returnType, parameters))
{
}
public void Hook(Func<DynamicHook, HookResult> handler, HookMode mode)
{
NativeAPI.HookFunction(Handle, handler, mode == HookMode.Post);

View File

@@ -8,6 +8,10 @@ public class MemoryFunctionVoid : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID, Array.Empty<DataType>())
{
}
public void Invoke()
{
InvokeInternalVoid();
@@ -21,6 +25,11 @@ public class MemoryFunctionVoid<T1> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[] { typeof(T1).ToValidDataType() })
{
}
public void Invoke(T1 arg1)
{
InvokeInternalVoid(arg1);
@@ -34,6 +43,11 @@ public class MemoryFunctionVoid<T1, T2> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType() })
{
}
public void Invoke(T1 arg1, T2 arg2)
{
InvokeInternalVoid(arg1, arg2);
@@ -47,6 +61,11 @@ public class MemoryFunctionVoid<T1, T2, T3> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType() })
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3)
{
InvokeInternalVoid(arg1, arg2, arg3);
@@ -64,6 +83,15 @@ public class MemoryFunctionVoid<T1, T2, T3, T4> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4);
@@ -82,6 +110,16 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5);
@@ -100,6 +138,16 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6);
@@ -119,6 +167,17 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7> : BaseMemoryFunction
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
@@ -138,6 +197,17 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8> : BaseMemoryFunc
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
@@ -158,6 +228,18 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8, T9> : BaseMemory
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(),
typeof(T9).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
@@ -178,6 +260,18 @@ public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : BaseM
{
}
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(),
typeof(T9).ToValidDataType(), typeof(T10).ToValidDataType()
})
{
}
public void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);

View File

@@ -9,6 +9,11 @@ public class MemoryFunctionWithReturn<TResult> : BaseMemoryFunction
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
Array.Empty<DataType>())
{
}
public TResult Invoke()
{
return InvokeInternal<TResult>();
@@ -22,6 +27,11 @@ public class MemoryFunctionWithReturn<T1, TResult> : BaseMemoryFunction
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1)
{
return InvokeInternal<TResult>(arg1);
@@ -35,6 +45,11 @@ public class MemoryFunctionWithReturn<T1, T2, TResult> : BaseMemoryFunction
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1, T2 arg2)
{
return InvokeInternal<TResult>(arg1, arg2);
@@ -48,6 +63,11 @@ public class MemoryFunctionWithReturn<T1, T2, T3, TResult> : BaseMemoryFunction
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3)
{
return InvokeInternal<TResult>(arg1, arg2, arg3);
@@ -65,6 +85,15 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, TResult> : BaseMemoryFunct
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4);
@@ -82,6 +111,15 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, TResult> : BaseMemoryF
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5);
@@ -99,6 +137,15 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, TResult> : BaseMem
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6);
@@ -117,6 +164,16 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, TResult> : Bas
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
@@ -135,6 +192,16 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, TResult> :
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
@@ -153,6 +220,16 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResul
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(), typeof(T9).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
@@ -172,6 +249,17 @@ public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T
{
}
public MemoryFunctionWithReturn(string signature, string binarypath) : base(signature, binarypath, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(), typeof(T9).ToValidDataType(),
typeof(T10).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Memory;
@@ -49,6 +50,8 @@ public class Schema
"m_nActiveCoinRank",
"m_nMusicID",
};
public static int GetClassSize(string className) => NativeAPI.GetSchemaClassSize(className);
public static short GetSchemaOffset(string className, string propertyName)
{
@@ -92,9 +95,9 @@ public class Schema
return ref Unsafe.AsRef<T>((void*)(pointer + GetSchemaOffset(className, memberName)));
}
public static unsafe T GetPointer<T>(IntPtr pointer)
public static T GetPointer<T>(IntPtr pointer)
{
var pointerTo = Unsafe.Read<IntPtr>((void*)pointer);
var pointerTo = Marshal.ReadIntPtr(pointer);
if (pointerTo == IntPtr.Zero)
{
return default;
@@ -103,9 +106,9 @@ public class Schema
return (T)Activator.CreateInstance(typeof(T), pointerTo);
}
public static unsafe T GetPointer<T>(IntPtr pointer, string className, string memberName)
public static T GetPointer<T>(IntPtr pointer, string className, string memberName)
{
var pointerTo = Unsafe.Read<IntPtr>((void*)(pointer + GetSchemaOffset(className, memberName)));
var pointerTo = Marshal.ReadIntPtr(pointer + GetSchemaOffset(className, memberName));
if (pointerTo == IntPtr.Zero)
{
return default;

View File

@@ -45,6 +45,26 @@ public partial class VirtualFunction
return function;
}
private static IntPtr CreateVirtualFunctionBySignature(string signature, string binarypath, IEnumerable<DataType> argumentTypes,
DataType returnType,
object[] arguments)
{
if (!_createdFunctions.TryGetValue(signature, out var function))
{
try
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, binarypath, signature,
argumentTypes.Count(), (int)returnType, arguments);
_createdFunctions[signature] = function;
}
catch (Exception)
{
}
}
return function;
}
#region Funcs
public static Func<TResult> Create<TResult>(string signature)
{
@@ -306,7 +326,270 @@ public partial class VirtualFunction
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
}
#endregion
#region FuncsBinary
public static Func<TResult> Create<TResult>(string signature, string binarypath)
{
var arguments = Enumerable.Empty<DataType>().ToArray();
if (typeof(TResult).ToDataType() == null)
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments,
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return () => NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer, new object[] { });
}
public static Func<TArg1, TResult> Create<TArg1, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1) => NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer, new object[] { arg1 });
}
public static Func<TArg1, TArg2, TResult> Create<TArg1, TArg2, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer, new object[] { arg1, arg2 });
}
public static Func<TArg1, TArg2, TArg3, TResult> Create<TArg1, TArg2, TArg3, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer, new object[] { arg1, arg2, arg3 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TResult> Create<TArg1, TArg2, TArg3, TArg4, TResult>(
string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer, new object[] { arg1, arg2, arg3, arg4 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TResult> Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(
string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult> Create<TArg1, TArg2, TArg3, TArg4, TArg5,
TArg6, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6) => NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult> Create<TArg1, TArg2, TArg3, TArg4,
TArg5, TArg6, TArg7, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult> Create<TArg1, TArg2, TArg3,
TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult> Create<TArg1, TArg2,
TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType(),
typeof(TArg9).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
}
public static Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult> Create<TArg1,
TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType(),
typeof(TArg9).ToDataType(),
typeof(TArg10).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
(DataType)typeof(TResult).ToDataType()!, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) =>
NativeAPI.ExecuteVirtualFunction<TResult>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
}
#endregion
#region Void Actions
@@ -587,4 +870,281 @@ public partial class VirtualFunction
}
#endregion
#region Void Actions Binary
public static Action CreateVoid(string signature, string binarypath)
{
var arguments = Enumerable.Empty<DataType>().ToArray();
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments,
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return () => { NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer, new object[] { }); };
}
public static Action<TArg1> CreateVoid<TArg1>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1) => { NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer, new object[] { arg1 }); };
}
public static Action<TArg1, TArg2> CreateVoid<TArg1, TArg2>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer, new object[] { arg1, arg2 });
};
}
public static Action<TArg1, TArg2, TArg3> CreateVoid<TArg1, TArg2, TArg3>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer, new object[] { arg1, arg2, arg3 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4> CreateVoid<TArg1, TArg2, TArg3, TArg4>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5> CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5>(
string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6> CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>(
string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7> CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5,
TArg6, TArg7>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8> CreateVoid<TArg1, TArg2, TArg3, TArg4,
TArg5, TArg6, TArg7, TArg8>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9> CreateVoid<TArg1, TArg2, TArg3,
TArg4, TArg5, TArg6, TArg7, TArg8, TArg9>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType(),
typeof(TArg9).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
};
}
public static Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10> CreateVoid<TArg1, TArg2,
TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10>(string signature, string binarypath)
{
var arguments = new[]
{
typeof(TArg1).ToDataType(),
typeof(TArg2).ToDataType(),
typeof(TArg3).ToDataType(),
typeof(TArg4).ToDataType(),
typeof(TArg5).ToDataType(),
typeof(TArg6).ToDataType(),
typeof(TArg7).ToDataType(),
typeof(TArg8).ToDataType(),
typeof(TArg9).ToDataType(),
typeof(TArg10).ToDataType()
};
if (arguments.Any(x => x == null))
{
throw new Exception($"Invalid argument type(s) supplied to Virtual Function");
}
var virtualFunctionPointer = CreateVirtualFunctionBySignature(signature, binarypath, arguments.Cast<DataType>(),
DataType.DATA_TYPE_VOID, arguments.Cast<object>().ToArray());
return (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) =>
{
NativeAPI.ExecuteVirtualFunction<object>(virtualFunctionPointer,
new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
};
}
#endregion
}

View File

@@ -0,0 +1,287 @@
/*
* 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.Memory;
public partial class VirtualFunctionVoid
{
private Action Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid(signature, binarypath);
}
public void Invoke()
{
this.Function();
}
}
public partial class VirtualFunctionVoid<TArg1>
{
private Action<TArg1> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1>(objectPtr, offset);
}
public void Invoke(TArg1 arg1)
{
this.Function(arg1);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2>
{
private Action<TArg1, TArg2> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2)
{
this.Function(arg1, arg2);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3>
{
private Action<TArg1, TArg2, TArg3> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3)
{
this.Function(arg1, arg2, arg3);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4>
{
private Action<TArg1, TArg2, TArg3, TArg4> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
{
this.Function(arg1, arg2, arg3, arg4);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
{
this.Function(arg1, arg2, arg3, arg4, arg5);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6)
{
this.Function(arg1, arg2, arg3, arg4, arg5, arg6);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7)
{
this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8)
{
this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8, TArg9 arg9)
{
this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}
}
public partial class VirtualFunctionVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10>
{
private Action<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10> Function;
public VirtualFunctionVoid(string signature)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10>(signature);
}
public VirtualFunctionVoid(string signature, string binarypath)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10>(signature, binarypath);
}
public VirtualFunctionVoid(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.CreateVoid<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10>(objectPtr, offset);
}
public void Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8, TArg9 arg9, TArg10 arg10)
{
this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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.Memory;
public partial class VirtualFunctionWithReturn<TResult>
{
private Func<TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TResult>(objectPtr, offset);
}
public TResult Invoke()
{
return this.Function();
}
}
public partial class VirtualFunctionWithReturn<TArg1, TResult>
{
private Func<TArg1, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1)
{
return this.Function(arg1);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TResult>
{
private Func<TArg1, TArg2, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2)
{
return this.Function(arg1, arg2);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TResult>
{
private Func<TArg1, TArg2, TArg3, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3)
{
return this.Function(arg1, arg2, arg3);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4)
{
return this.Function(arg1, arg2, arg3, arg4);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5)
{
return this.Function(arg1, arg2, arg3, arg4, arg5);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6)
{
return this.Function(arg1, arg2, arg3, arg4, arg5, arg6);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7)
{
return this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8)
{
return this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8, TArg9 arg9)
{
return this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}
}
public partial class VirtualFunctionWithReturn<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>
{
private Func<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult> Function;
public VirtualFunctionWithReturn(string signature)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(signature);
}
public VirtualFunctionWithReturn(string signature, string binarypath)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(signature, binarypath);
}
public VirtualFunctionWithReturn(IntPtr objectPtr, int offset)
{
this.Function = VirtualFunction.Create<TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7, TArg8, TArg9, TArg10, TResult>(objectPtr, offset);
}
public TResult Invoke(TArg1 arg1, TArg2 arg2, TArg3 arg3, TArg4 arg4, TArg5 arg5, TArg6 arg6, TArg7 arg7, TArg8 arg8, TArg9 arg9, TArg10 arg10)
{
return this.Function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}

View File

@@ -70,4 +70,10 @@ public static class VirtualFunctions
public static MemoryFunctionVoid<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThinkFunc = new (GameData.GetSignature("CCSPlayerPawnBase_PostThink"));
public static Action<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThink = CCSPlayerPawnBase_PostThinkFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouchFunc = new (GameData.GetSignature("CBaseTrigger_StartTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouch = CBaseTrigger_StartTouchFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouchFunc = new (GameData.GetSignature("CBaseTrigger_EndTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouch = CBaseTrigger_EndTouchFunc.Invoke;
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Utils;
@@ -9,22 +11,80 @@ public readonly record struct CEntityIndex(uint Value)
public override string ToString() => $"Entity Index {Value}";
}
public class CHandle<T> : NativeObject
public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
{
public CHandle(IntPtr pointer) : base(pointer)
private uint _raw;
private IntPtr? _pointer;
public uint Raw
{
get
{
if (_pointer != null)
{
return (uint)Marshal.ReadInt32(_pointer.Value);
}
return _raw;
}
set
{
if (_pointer != null)
{
Marshal.WriteInt32(_pointer.Value, (int)value);
return;
}
_raw = value;
}
}
public T Value => (T)Activator.CreateInstance(typeof(T), NativeAPI.GetEntityPointerFromHandle(Handle));
public CHandle(uint raw)
{
Raw = raw;
}
public unsafe ref ulong Raw => ref Unsafe.AsRef<ulong>((void*)Handle);
public CHandle(IntPtr raw)
{
_pointer = raw;
}
public override string ToString() => IsValid ? $"Index = {Index.Value}, Serial = {SerialNum}" : "<invalid>";
public T? Value => (T)Activator.CreateInstance(typeof(T), EntitySystem.GetEntityByHandle(this));
public bool IsValid => Index.Value != (Utilities.MaxEdicts - 1);
public override string ToString() => IsValid ? $"Index = {Index}, Serial = {SerialNum}" : "<invalid>";
public CEntityIndex Index => new((uint)(Raw & (Utilities.MaxEdicts - 1)));
public uint SerialNum => (uint)(Raw >> Utilities.MaxEdictBits);
public bool IsValid => Index != (Utilities.MaxEdicts - 1);
public uint Index => (uint)(Raw & (Utilities.MaxEdicts - 1));
public uint SerialNum => Raw >> Utilities.MaxEdictBits;
public static implicit operator uint(CHandle<T> handle) => handle.Raw;
public bool Equals(CHandle<T>? other)
{
return other != null && Raw == other.Raw;
}
public override bool Equals(object? obj)
{
return Equals(obj as CHandle<T>);
}
public override int GetHashCode()
{
return (int)Raw;
}
}
public class CEntityHandle : CHandle<CEntityInstance>
{
public CEntityHandle(uint raw) : base(raw)
{
}
public CEntityHandle(IntPtr raw) : base (raw)
{
}
}
public class PointerTo<T> : NativeObject where T : NativeObject

View File

@@ -14,29 +14,29 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
namespace CounterStrikeSharp.API.Modules.Utils
namespace CounterStrikeSharp.API.Modules.Utils;
public class ChatColors
{
public class ChatColors
{
public static char Default = '\x01';
public static char White = '\x01';
public static char Darkred = '\x02';
public static char Green = '\x04';
public static char LightYellow = '\x09';
public static char LightBlue = '\x0B';
public static char Olive = '\x05';
public static char Lime = '\x06';
public static char Red = '\x07';
public static char LightPurple = '\x03';
public static char Purple = '\x0E';
public static char Grey = '\x08';
public static char Yellow = '\x09';
public static char Gold = '\x10';
public static char Silver = '\x0A';
public static char Blue = '\x0B';
public static char DarkBlue = '\x0C';
public static char BlueGrey = '\x0A';
public static char Magenta = '\x0E';
public static char LightRed = '\x0F';
}
public static char Default = '\x01';
public static char White = '\x01';
public static char Darkred = '\x02';
public static char Green = '\x04';
public static char LightYellow = '\x09';
public static char LightBlue = '\x0B';
public static char Olive = '\x05';
public static char Lime = '\x06';
public static char Red = '\x07';
public static char LightPurple = '\x03';
public static char Purple = '\x0E';
public static char Grey = '\x08';
public static char Yellow = '\x09';
public static char Gold = '\x10';
public static char Silver = '\x0A';
public static char Blue = '\x0B';
public static char DarkBlue = '\x0C';
public static char BlueGrey = '\x0A';
public static char Magenta = '\x0E';
public static char LightRed = '\x0F';
public static char Orange = '\x10';
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace CounterStrikeSharp.API.Modules.Utils
{
public static class EnumUtils
{
/// <summary>
/// Brute force search using Enum.GetNames as enum members pointing to other enum members do not have the correct attributes.
/// </summary>
public static T? GetEnumMemberAttribute<T>(this Enum enumValue) where T : Attribute
{
var type = enumValue.GetType();
foreach (var name in Enum.GetNames(type))
{
var field = type.GetField(name);
if (field == null) continue;
var fieldValue = field.GetValue(null)!;
if (fieldValue.Equals(enumValue))
{
var attribute = field.GetCustomAttribute<T>();
if (attribute != null)
{
return attribute;
}
}
}
return null;
}
public static string? GetEnumMemberAttributeValue<T>(T? enumValue) where T : Enum
{
var enumType = typeof(T);
if (!enumType.IsEnum || enumValue == null)
{
return null;
}
var enumString = enumValue.ToString();
if (string.IsNullOrWhiteSpace(enumString))
{
return null;
}
var memberInfo = enumType.GetMember(enumString);
var enumMemberAttribute = memberInfo.FirstOrDefault()?.GetCustomAttributes(false)
.OfType<EnumMemberAttribute>().FirstOrDefault();
if (enumMemberAttribute != null)
{
return enumMemberAttribute.Value;
}
// Brute force search by name if we still can't find it.
return enumValue.GetEnumMemberAttribute<EnumMemberAttribute>()?.Value;
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API;
public abstract class NativeEntity : NativeObject
{
public new IntPtr Handle => EntitySystem.GetEntityByHandle(EntityHandle) ?? IntPtr.Zero;
public CEntityHandle EntityHandle { get; }
public NativeEntity(IntPtr pointer) : base(pointer)
{
EntityHandle = new(EntitySystem.GetRawHandleFromEntityPointer(pointer));
}
public NativeEntity(uint rawHandle) : base(EntitySystem.GetEntityByHandle(rawHandle) ?? IntPtr.Zero)
{
EntityHandle = new(rawHandle);
}
}

View File

@@ -14,8 +14,6 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
namespace CounterStrikeSharp.API
{
[Flags]
@@ -37,14 +35,14 @@ namespace CounterStrikeSharp.API
Reload = (1 << 13),
Alt1 = (1 << 14),
Alt2 = (1 << 15),
Speed = (1 << 16), /**< Player is holding the speed key */
Walk = (1 << 17), /**< Player holding walk key */
Zoom = (1 << 18), /**< Zoom key for HUD zoom */
Weapon1 = (1 << 19), /**< weapon defines these bits */
Weapon2 = (1 << 20), /**< weapon defines these bits */
Speed = (1 << 16), /** Player is holding the speed key */
Walk = (1 << 17), /** Player holding walk key */
Zoom = (1 << 18), /** Zoom key for HUD zoom */
Weapon1 = (1 << 19), /** weapon defines these bits */
Weapon2 = (1 << 20), /** weapon defines these bits */
Bullrush = (1 << 21),
Grenade1 = (1 << 22), /**< grenade 1 */
Grenade2 = (1 << 23), /**< grenade 2 */
Grenade1 = (1 << 22), /** grenade 1 */
Grenade2 = (1 << 23), /** grenade 2 */
Attack3 = (1 << 24)
}
}
}

View File

@@ -14,24 +14,25 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API
{
public static class Utilities
{
// https://github.com/dotabuff/manta/blob/master/entity.go#L186-L190
public const int MaxEdictBits = 14;
public const int MaxEdictBits = 15;
public const int MaxEdicts = 1 << MaxEdictBits;
public const int NumEHandleSerialNumberBits = 17;
public const uint InvalidEHandleIndex = 0xFFFFFFFF;
public static IEnumerable<T> FlagsToList<T>(this T flags) where T : Enum
{
@@ -64,9 +65,19 @@ namespace CounterStrikeSharp.API
return Utilities.GetEntityFromIndex<CCSPlayerController>((userid & 0xFF) + 1);
}
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
{
return Utilities.GetPlayers().FirstOrDefault(player => player.AuthorizedSteamID == (SteamID)steamId);
}
public static TargetResult ProcessTargetString(string pattern, CCSPlayerController player)
{
return new Target(pattern).GetTarget(player);
}
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
{
var pEntity = new CEntityIdentity(NativeAPI.GetFirstActiveEntity());
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
if (!pEntity.DesignerName.Contains(designerName)) continue;
@@ -74,6 +85,15 @@ namespace CounterStrikeSharp.API
}
}
public static IEnumerable<CEntityInstance> GetAllEntities()
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
yield return new PointerTo<CEntityInstance>(pEntity.Handle).Value;
}
}
/// <summary>
/// Returns a list of <see cref="CCSPlayerController"/> that are valid and have a valid <see cref="CCSPlayerController.UserId"/> >= 0
/// </summary>

View File

@@ -12,6 +12,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarcraftPlugin", "..\exampl
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithConfig", "..\examples\WithConfig\WithConfig.csproj", "{2846604A-5B9F-4D80-9476-657C09CFDD5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "..\examples\HelloWorld\HelloWorld.csproj", "{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithDependencyInjection", "..\examples\WithDependencyInjection\WithDependencyInjection.csproj", "{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithCommands", "..\examples\WithCommands\WithCommands.csproj", "{EA2F596E-2236-4999-B476-B1FDA287674A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithGameEventHandlers", "..\examples\WithGameEventHandlers\WithGameEventHandlers.csproj", "{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WithDatabaseDapper", "..\examples\WithDatabaseDapper\WithDatabaseDapper.csproj", "{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -38,9 +50,39 @@ Global
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DAE388A8-94A4-4C24-9450-E34677EEA2CF}.Release|Any CPU.Build.0 = Release|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2846604A-5B9F-4D80-9476-657C09CFDD5C}.Release|Any CPU.Build.0 = Release|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72}.Release|Any CPU.Build.0 = Release|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF}.Release|Any CPU.Build.0 = Release|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA2F596E-2236-4999-B476-B1FDA287674A}.Release|Any CPU.Build.0 = Release|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4}.Release|Any CPU.Build.0 = Release|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E64289-5D69-4AA1-BEF0-D0D96A55EE8F} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{DAE388A8-94A4-4C24-9450-E34677EEA2CF} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{2846604A-5B9F-4D80-9476-657C09CFDD5C} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{DDA4F93A-7D4A-4698-8C2A-5DAE7FBCDC72} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{E497E40C-A7B4-41A7-A1C6-2EC6698FF3BF} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{EA2F596E-2236-4999-B476-B1FDA287674A} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{3032F3FA-E20A-4581-9A08-2FB5FF1524F4} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
{A641D8D7-35F1-48AB-AABA-EDFB6B7FC49B} = {7DF99C35-881D-4FF2-B1C9-246BD3DECB9A}
EndGlobalSection
EndGlobal

View File

@@ -26,6 +26,7 @@ using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
@@ -41,7 +42,7 @@ namespace TestPlugin
[JsonPropertyName("LogPrefix")] public string LogPrefix { get; set; } = "CSSharp";
}
[MinimumApiVersion(33)]
[MinimumApiVersion(80)]
public class SamplePlugin : BasePlugin, IPluginConfig<SampleConfig>
{
public override string ModuleName => "Sample Plugin";
@@ -60,6 +61,13 @@ namespace TestPlugin
Config = config;
}
private TestInjectedClass _testInjectedClass;
public SamplePlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;
}
public override void Load(bool hotReload)
{
// Basic usage of the configuration system
@@ -104,10 +112,32 @@ namespace TestPlugin
var result = virtualFunc() - 8;
Logger.LogInformation("Result of virtual func call is {Pointer:X}", result);
_testInjectedClass.Hello();
VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger {Trigger} touched by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger left {Trigger} by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.UTIL_RemoveFunc.Hook(hook =>
{
var entityInstance = hook.GetParam<CEntityInstance>(0);
Logger.LogInformation("Removed entity {EntityIndex}", entityInstance.EntityIndex.Value.Value);
Logger.LogInformation("Removed entity {EntityIndex}", entityInstance.Index);
return HookResult.Continue;
}, HookMode.Post);
@@ -187,7 +217,7 @@ namespace TestPlugin
if (!@event.Userid.PlayerPawn.IsValid) return 0;
Logger.LogInformation("Player spawned with entity index: {EntityIndex} & User ID: {UserId}",
@event.Userid.EntityIndex, @event.Userid.UserId);
@event.Userid.Index, @event.Userid.UserId);
return HookResult.Continue;
});
@@ -199,27 +229,21 @@ namespace TestPlugin
var activeWeapon = @event.Userid.PlayerPawn.Value.WeaponServices?.ActiveWeapon.Value;
var weapons = @event.Userid.PlayerPawn.Value.WeaponServices?.MyWeapons;
Server.NextFrame(() =>
{
var weaponServices = player.PlayerPawn.Value.WeaponServices.As<CCSPlayer_WeaponServices>();
player.PrintToChat(weaponServices.ActiveWeapon.Value?.DesignerName);
});
// Set player to random colour
player.PlayerPawn.Value.Render = Color.FromArgb(Random.Shared.Next(0, 255),
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
Server.NextFrame(() =>
{
player.PrintToCenter(string.Join("\n", weapons.Select(x => x.Value.DesignerName)));
});
activeWeapon.ReserveAmmo[0] = 250;
activeWeapon.Clip1 = 250;
Logger.LogInformation("Pawn Position: {AbsOrigin}-{Rotation}", pawn.AbsOrigin, pawn.AbsRotation);
char randomColourChar = (char)new Random().Next(0, 16);
Server.PrintToChatAll($"Random String with Random Colour: {randomColourChar}{new Random().Next()}");
pawn.Health += 5;
Logger.LogInformation("Bullet Impact: {X},{Y},{Z}", @event.X, @event.Y, @event.Z);
return HookResult.Continue;
});
RegisterEventHandler<EventRoundStart>((@event, info) =>
@@ -274,7 +298,7 @@ namespace TestPlugin
switch (designerName)
{
case "smokegrenade_projectile":
var projectile = new CSmokeGrenadeProjectile(entity.Handle);
var projectile = entity.As<CSmokeGrenadeProjectile>();
Server.NextFrame(() =>
{
@@ -286,7 +310,7 @@ namespace TestPlugin
});
return;
case "flashbang_projectile":
var flashbang = new CBaseCSGrenadeProjectile(entity.Handle);
var flashbang = entity.As<CBaseCSGrenadeProjectile>();
Server.NextFrame(() => { flashbang.Remove(); });
return;
@@ -314,6 +338,24 @@ namespace TestPlugin
giveItemMenu.AddMenuOption("weapon_ak47", handleGive);
giveItemMenu.AddMenuOption("weapon_p250", handleGive);
AddCommand("css_target", "Target Test", (player, info) =>
{
if (player == null) return;
var targetResult = info.GetArgTargetResult(1);
if (!targetResult.Any())
{
player.PrintToChat("No players found.");
return;
}
foreach (var result in targetResult.Players)
{
player.PrintToChat($"Target found: {result?.PlayerName}");
}
});
AddCommand("css_menu", "Opens example menu", (player, info) => { ChatMenus.OpenMenu(player, largeMenu); });
AddCommand("css_gunmenu", "Gun Menu", (player, info) => { ChatMenus.OpenMenu(player, giveItemMenu); });
@@ -422,6 +464,20 @@ namespace TestPlugin
}
}
[ConsoleCommand("css_entities", "List entities")]
public void OnCommandEntities(CCSPlayerController? player, CommandInfo command)
{
foreach (var entity in Utilities.GetAllEntities())
{
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
}
foreach (var entity in Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_"))
{
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
}
}
[ConsoleCommand("css_colors", "List Chat Colors")]
public void OnCommandColors(CCSPlayerController? player, CommandInfo command)
{
@@ -433,6 +489,15 @@ namespace TestPlugin
command.ReplyToCommand($" {(char)i}Color 0x{i:x}");
}
}
[ConsoleCommand("css_sound", "Play a sound to client")]
public void OnCommandSound(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
player.ExecuteClientCommand($"play sounds/ui/counter_beep.vsnd");
}
[ConsoleCommand("css_pause", "Pause Game")]
public void OnCommandPause(CCSPlayerController? player, CommandInfo command)
@@ -448,6 +513,30 @@ namespace TestPlugin
player.GiveNamedItem(command.ArgByIndex(1));
}
[ConsoleCommand("css_giveenum", "giveenum")]
public void OnCommandGiveEnum(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.IsValid) return;
player.GiveNamedItem(CsItem.M4A1);
player.GiveNamedItem(CsItem.HEGrenade);
player.GiveNamedItem(CsItem.Kevlar);
player.GiveNamedItem(CsItem.Tec9);
}
[ConsoleCommand("css_give", "give")]
public void OnCommandGiveItems(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.IsValid) return;
player.GiveNamedItem("weapon_m4a1");
player.GiveNamedItem("weapon_hegrenade");
player.GiveNamedItem("item_kevlar");
player.GiveNamedItem("weapon_tec9");
}
private HookResult GenericEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent
{
Logger.LogInformation("Event found {Pointer:X}, event name: {EventName}, dont broadcast: {DontBroadcast}",

View File

@@ -0,0 +1,29 @@
using System;
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace TestPlugin;
public class TestInjectedClass
{
private readonly ILogger<TestInjectedClass> _logger;
public TestInjectedClass(ILogger<TestInjectedClass> logger)
{
_logger = logger;
}
public void Hello()
{
_logger.LogInformation("Hello World from Test Injected Class");
}
}
public class TestPluginServiceCollection : IPluginServiceCollection<SamplePlugin>
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<TestInjectedClass>();
}
}

View File

@@ -26,19 +26,31 @@ CCoreConfig::~CCoreConfig() = default;
bool CCoreConfig::Init(char* conf_error, int conf_error_size)
{
std::ifstream ifs(m_sPath);
std::ifstream ifs(std::string(m_sPath + ".json"));
if (!ifs) {
V_snprintf(conf_error, conf_error_size, "CoreConfig file not found.");
return false;
std::ifstream exampleIfs(std::string(m_sPath + ".example.json"));
if (!exampleIfs) {
V_snprintf(conf_error, conf_error_size, "CoreConfig file not found.");
return false;
}
CSSHARP_CORE_INFO("CoreConfig file not found, creating one from example.");
std::ofstream ofs(std::string(m_sPath + ".json"));
ofs << exampleIfs.rdbuf();
ofs.close();
return Init(conf_error, conf_error_size);
}
m_json = json::parse(ifs);
try {
PublicChatTrigger = m_json["PublicChatTrigger"];
SilentChatTrigger = m_json["SilentChatTrigger"];
FollowCS2ServerGuidelines = m_json["FollowCS2ServerGuidelines"];
PublicChatTrigger = m_json.value("PublicChatTrigger", PublicChatTrigger);
SilentChatTrigger = m_json.value("SilentChatTrigger", SilentChatTrigger);
FollowCS2ServerGuidelines = m_json.value("FollowCS2ServerGuidelines", FollowCS2ServerGuidelines);
PluginHotReloadEnabled = m_json.value("PluginHotReloadEnabled", PluginHotReloadEnabled);
} catch (const std::exception& ex) {
V_snprintf(conf_error, conf_error_size, "Failed to parse CoreConfig file: %s", ex.what());
return false;

Some files were not shown because too many files have changed in this diff Show More