mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-06 08:03:12 -08:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7baf0a25e2 | ||
|
|
9fdbb9500b | ||
|
|
0ab3cf429a | ||
|
|
1f9630b92d | ||
|
|
02bf2483d3 | ||
|
|
cb181b6a49 | ||
|
|
cc21dca5a0 | ||
|
|
5721d060ea | ||
|
|
220521d571 | ||
|
|
5698b511e9 | ||
|
|
48c9d195ff | ||
|
|
603827d331 | ||
|
|
e557d54c32 | ||
|
|
48d3ade5cf | ||
|
|
77b05e912e | ||
|
|
1354b4972d | ||
|
|
8b5eb7e38d | ||
|
|
2dd62c44d3 | ||
|
|
f811338ce4 | ||
|
|
194c340ae7 | ||
|
|
6b0912d3cd | ||
|
|
2d3aa09aa4 | ||
|
|
911084e71e | ||
|
|
5b99206568 | ||
|
|
4bfdf28beb | ||
|
|
9bcd0f7e92 | ||
|
|
11c6486ec5 | ||
|
|
ee69560a66 | ||
|
|
d37e5e194a | ||
|
|
c4740d1cc9 | ||
|
|
7e92f178fd | ||
|
|
107ca08132 | ||
|
|
8cda8d9a50 | ||
|
|
3d59a05de8 | ||
|
|
77b7040d6c | ||
|
|
75de9732ef | ||
|
|
575c859ddb | ||
|
|
e12a7cb17a | ||
|
|
7c7f52a219 | ||
|
|
cd593fb238 | ||
|
|
c5cc65be48 | ||
|
|
59928bbcc5 | ||
|
|
319b116c5f | ||
|
|
e0dc053d22 | ||
|
|
f2e0dac32d | ||
|
|
4e8c18abc7 | ||
|
|
8d1891a3a8 | ||
|
|
6bc43444f7 | ||
|
|
f0c7869f4a | ||
|
|
3e38ed3c77 | ||
|
|
7e9e7c6665 | ||
|
|
9a018f295b | ||
|
|
8b725d435f | ||
|
|
ea3596417a | ||
|
|
123f41914e | ||
|
|
8f69076405 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -17,3 +17,9 @@
|
||||
[submodule "libraries/GameTracking-CS2"]
|
||||
path = libraries/GameTracking-CS2
|
||||
url = https://github.com/SteamDatabase/GameTracking-CS2
|
||||
[submodule "libraries/DynoHook"]
|
||||
path = libraries/DynoHook
|
||||
url = git@github.com:qubka/DynoHook.git
|
||||
[submodule "libraries/asmjit"]
|
||||
path = libraries/asmjit
|
||||
url = git@github.com:asmjit/asmjit.git
|
||||
|
||||
@@ -8,7 +8,9 @@ include("makefiles/shared.cmake")
|
||||
add_subdirectory(libraries/spdlog)
|
||||
add_subdirectory(libraries/dyncall)
|
||||
add_subdirectory(libraries/funchook)
|
||||
add_subdirectory(libraries/DynoHook)
|
||||
|
||||
set_property(TARGET dynohook PROPERTY DYNO_ARCH_X86 64)
|
||||
set_property(TARGET funchook-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
SET(SOURCE_FILES
|
||||
@@ -81,6 +83,7 @@ SET(SOURCE_FILES
|
||||
src/core/managers/server_manager.h
|
||||
src/scripting/natives/natives_server.cpp
|
||||
libraries/nlohmann/json.hpp
|
||||
src/scripting/natives/natives_dynamichooks.cpp
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"PublicChatTrigger": [ "!" ],
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true,
|
||||
"PluginHotReloadEnabled": true
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"PublicChatTrigger": [ "!" ],
|
||||
"SilentChatTrigger": [ "/" ],
|
||||
"FollowCS2ServerGuidelines": true
|
||||
}
|
||||
@@ -26,6 +26,26 @@
|
||||
"linux": 89
|
||||
}
|
||||
},
|
||||
"CCSPlayerController_Respawn": {
|
||||
"offsets": {
|
||||
"windows": 241,
|
||||
"linux": 243
|
||||
}
|
||||
},
|
||||
"CCSPlayerPawn_Respawn": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x53\\x48\\x83\\xEC\\x20\\x8B\\x91\\x38\\x0B\\x00\\x00\\x48\\x8B\\xD9",
|
||||
"linux": "\\x8B\\x8F\\x40\\x0E\\x00\\x00\\x83\\xF9\\xFF\\x0F\\x84\\xD9\\x01"
|
||||
}
|
||||
},
|
||||
"CCSPlayerPawnBase_PostThink": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x48\\x8B\\xC4\\x48\\x89\\x48\\x08\\x55\\x53\\x56\\x57\\x41\\x56\\x48\\x8D\\xA8\\xD8\\xFE\\xFF\\xFF",
|
||||
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xFF\\x41\\x56\\x41\\x55\\x41\\x54\\x53\\x48\\x81\\xEC\\x2A\\x2A\\x2A\\x2A\\xE8\\x2A\\x2A\\x2A\\x2A\\x4C"
|
||||
}
|
||||
},
|
||||
"GiveNamedItem": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
@@ -51,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
|
||||
}
|
||||
},
|
||||
@@ -83,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": {
|
||||
@@ -112,6 +132,27 @@
|
||||
"linux": 147
|
||||
}
|
||||
},
|
||||
"CBaseEntity_TakeDamageOld": {
|
||||
"signatures": {
|
||||
"library": "server",
|
||||
"windows": "\\x40\\x56\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x41\\x10",
|
||||
"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,
|
||||
|
||||
@@ -16,6 +16,10 @@ Adding an Admin is as simple as creating a new entry in the `configs/admins.json
|
||||
"ZoNiCaL": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@css/changemap", "@css/generic"]
|
||||
},
|
||||
"another ZoNiCaL": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": ["@css/generic"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -53,4 +57,5 @@ However there is a somewhat standardized list of flags that it is advised you us
|
||||
|
||||
:::note
|
||||
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
|
||||
:::
|
||||
:::
|
||||
|
||||
|
||||
@@ -18,8 +18,25 @@ Groups can be created to group a series of permissions together under one tag. T
|
||||
}
|
||||
```
|
||||
|
||||
You can add admins to groups using the `groups` array in `configs/admins.json`
|
||||
```json
|
||||
{
|
||||
"erikj": {
|
||||
"identity": "76561198808392634",
|
||||
"flags": ["@mycustomplugin/admin"],
|
||||
"groups": ["#css/simple-admin"]
|
||||
},
|
||||
"Another erikj": {
|
||||
"identity": "STEAM_0:1:1",
|
||||
"flags": ["@mycustomplugin/admin"],
|
||||
"groups": ["#css/simple-admin"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp will recognize the group.
|
||||
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp won't recognize the group.
|
||||
:::
|
||||
|
||||
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.
|
||||
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
71
docs/src/content/docs/guides/dependency-injection.md
Normal file
71
docs/src/content/docs/guides/dependency-injection.md
Normal 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();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: How to get started installing & using CounterStrikeSharp.
|
||||
sidebar:
|
||||
order: 0
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -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.
|
||||
:::
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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.
|
||||
|
||||
12
examples/HelloWorld/HelloWorld.csproj
Normal file
12
examples/HelloWorld/HelloWorld.csproj
Normal 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>
|
||||
24
examples/HelloWorld/HelloWorldPlugin.cs
Normal file
24
examples/HelloWorld/HelloWorldPlugin.cs
Normal 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!");
|
||||
}
|
||||
}
|
||||
2
examples/HelloWorld/README.md
Normal file
2
examples/HelloWorld/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Hello World
|
||||
This is a minimal "Hello World" example that executes code on load & unload.
|
||||
@@ -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;
|
||||
|
||||
|
||||
4
examples/WithCommands/README.md
Normal file
4
examples/WithCommands/README.md
Normal 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`.
|
||||
12
examples/WithCommands/WithCommands.csproj
Normal file
12
examples/WithCommands/WithCommands.csproj
Normal 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>
|
||||
63
examples/WithCommands/WithCommandsPlugin.cs
Normal file
63
examples/WithCommands/WithCommandsPlugin.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
2
examples/WithConfig/README.md
Normal file
2
examples/WithConfig/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# WithConfig
|
||||
This example shows how you can implement the `IPluginConfig` interface to allow for semi-automatic config parsing & loading.
|
||||
12
examples/WithConfig/WithConfig.csproj
Normal file
12
examples/WithConfig/WithConfig.csproj
Normal 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>
|
||||
38
examples/WithConfig/WithConfigPlugin.cs
Normal file
38
examples/WithConfig/WithConfigPlugin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
examples/WithDatabaseDapper/README.md
Normal file
2
examples/WithDatabaseDapper/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Database (Dapper)
|
||||
Simple SQLite database example using Dapper library to track kills.
|
||||
16
examples/WithDatabaseDapper/WithDatabaseDapper.csproj
Normal file
16
examples/WithDatabaseDapper/WithDatabaseDapper.csproj
Normal 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>
|
||||
89
examples/WithDatabaseDapper/WithDatabaseDapperPlugin.cs
Normal file
89
examples/WithDatabaseDapper/WithDatabaseDapperPlugin.cs
Normal 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}"); });
|
||||
});
|
||||
}
|
||||
}
|
||||
2
examples/WithDependencyInjection/README.md
Normal file
2
examples/WithDependencyInjection/README.md
Normal 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.
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
2
examples/WithGameEventHandlers/README.md
Normal file
2
examples/WithGameEventHandlers/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# With Game Event Handlers
|
||||
This is example shows how you can subscribe to legacy Source 1 game events.
|
||||
12
examples/WithGameEventHandlers/WithGameEventHandlers.csproj
Normal file
12
examples/WithGameEventHandlers/WithGameEventHandlers.csproj
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
1
libraries/DynoHook
Submodule
1
libraries/DynoHook
Submodule
Submodule libraries/DynoHook added at d7f8ebb059
1
libraries/asmjit
Submodule
1
libraries/asmjit
Submodule
Submodule libraries/asmjit added at 0dd16b0a98
@@ -16,14 +16,16 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics -v -fvisibility=default")
|
||||
|
||||
set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
${SOURCESDK_LIB}/linux64/libtier0.so
|
||||
${SOURCESDK_LIB}/linux64/tier1.a
|
||||
${SOURCESDK_LIB}/linux64/interfaces.a
|
||||
${SOURCESDK_LIB}/linux64/mathlib.a
|
||||
spdlog
|
||||
dynload_s
|
||||
dyncall_s
|
||||
distorm
|
||||
funchook-static
|
||||
SET(
|
||||
COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
${SOURCESDK_LIB}/linux64/libtier0.so
|
||||
${SOURCESDK_LIB}/linux64/tier1.a
|
||||
${SOURCESDK_LIB}/linux64/interfaces.a
|
||||
${SOURCESDK_LIB}/linux64/mathlib.a
|
||||
spdlog
|
||||
dynload_s
|
||||
dyncall_s
|
||||
distorm
|
||||
funchook-static
|
||||
dynohook
|
||||
)
|
||||
@@ -14,6 +14,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
|
||||
# TODO: Use C++20 instead.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
|
||||
Set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
|
||||
set(CMAKE_STATIC_LIBRARY_PREFIX "")
|
||||
|
||||
set(SOURCESDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2)
|
||||
@@ -45,6 +47,7 @@ include_directories(
|
||||
libraries/spdlog/include
|
||||
libraries/tl
|
||||
libraries/funchook/include
|
||||
libraries/DynoHook/src
|
||||
libraries
|
||||
)
|
||||
|
||||
|
||||
@@ -17,4 +17,5 @@ set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
|
||||
dyncall_s
|
||||
distorm
|
||||
funchook-static
|
||||
dynohook
|
||||
)
|
||||
@@ -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<CCSPlayer_WeaponServices>();</code>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T As<T>() where T : NativeObject
|
||||
{
|
||||
return (T)Activator.CreateInstance(typeof(T), this.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
managed/CounterStrikeSharp.API/Bootstrap.cs
Normal file
67
managed/CounterStrikeSharp.API/Bootstrap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +157,80 @@ 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();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x4F5B80D0);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (T)ScriptContext.GlobalScriptContext.GetResult(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DynamicHookSetReturn<T>(IntPtr hook, int datatype, T value){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xDB297E44);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static T DynamicHookGetParam<T>(IntPtr hook, int datatype, int paramindex){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(paramindex);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x5F5ABDD5);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
return (T)ScriptContext.GlobalScriptContext.GetResult(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
public static void DynamicHookSetParam<T>(IntPtr hook, int datatype, int paramindex, T value){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(hook);
|
||||
ScriptContext.GlobalScriptContext.Push(datatype);
|
||||
ScriptContext.GlobalScriptContext.Push(paramindex);
|
||||
ScriptContext.GlobalScriptContext.Push(value);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xA96CFBC1);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetMapName(){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -405,6 +479,16 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void QueueTaskForNextWorldUpdate(IntPtr callback){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(callback);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xAD51A0C9);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetValveInterface(int interfacetype, string interfacename){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -484,6 +568,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();
|
||||
@@ -505,6 +632,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();
|
||||
@@ -803,6 +952,30 @@ namespace CounterStrikeSharp.API.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static void HookFunction(IntPtr function, InputArgument hook, bool post){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(function);
|
||||
ScriptContext.GlobalScriptContext.Push((InputArgument)hook);
|
||||
ScriptContext.GlobalScriptContext.Push(post);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0xA6C8BA9B);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnhookFunction(IntPtr function, InputArgument hook, bool post){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
ScriptContext.GlobalScriptContext.Push(function);
|
||||
ScriptContext.GlobalScriptContext.Push((InputArgument)hook);
|
||||
ScriptContext.GlobalScriptContext.Push(post);
|
||||
ScriptContext.GlobalScriptContext.SetIdentifier(0x2051B00);
|
||||
ScriptContext.GlobalScriptContext.Invoke();
|
||||
ScriptContext.GlobalScriptContext.CheckErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public static T ExecuteVirtualFunction<T>(IntPtr function, object[] arguments){
|
||||
lock (ScriptContext.GlobalScriptContext.Lock) {
|
||||
ScriptContext.GlobalScriptContext.Reset();
|
||||
@@ -893,6 +1066,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();
|
||||
@@ -903,6 +1087,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();
|
||||
|
||||
254
managed/CounterStrikeSharp.API/Core/Application.cs
Normal file
254
managed/CounterStrikeSharp.API/Core/Application.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
6
managed/CounterStrikeSharp.API/Core/IStartupService.cs
Normal file
6
managed/CounterStrikeSharp.API/Core/IStartupService.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public interface IStartupService
|
||||
{
|
||||
public void Load();
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,11 @@ namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CBasePlayerPawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Force player suicide
|
||||
/// </summary>
|
||||
/// <param name="explode"></param>
|
||||
/// <param name="force"></param>
|
||||
public void CommitSuicide(bool explode, bool force)
|
||||
{
|
||||
VirtualFunction.CreateVoid<IntPtr, bool, bool>(Handle, GameData.GetOffset("CBasePlayerPawn_CommitSuicide"))(Handle, explode, force);
|
||||
|
||||
@@ -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);
|
||||
@@ -79,6 +95,31 @@ public partial class CCSPlayerController
|
||||
itemServices.RemoveWeapons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force player suicide
|
||||
/// </summary>
|
||||
/// <param name="explode"></param>
|
||||
/// <param name="force"></param>
|
||||
public void CommitSuicide(bool explode, bool force)
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
PlayerPawn.Value.CommitSuicide(explode, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawn player
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
if (!PlayerPawn.IsValid) return;
|
||||
if (!PlayerPawn.Value.IsValid) return;
|
||||
|
||||
VirtualFunctions.CCSPlayerPawn_Respawn(PlayerPawn.Value.Handle);
|
||||
VirtualFunction.CreateVoid<IntPtr>(Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Handle);
|
||||
}
|
||||
|
||||
public bool IsBot => ((PlayerFlags)Flags).HasFlag(PlayerFlags.FL_FAKECLIENT);
|
||||
|
||||
/// <summary>
|
||||
@@ -103,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
managed/CounterStrikeSharp.API/Core/Model/CCSPlayerPawn.cs
Normal file
19
managed/CounterStrikeSharp.API/Core/Model/CCSPlayerPawn.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Modules.Memory;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CCSPlayerPawn
|
||||
{
|
||||
/// <summary>
|
||||
/// Respawn player
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
if (!Controller.IsValid) return;
|
||||
if (!Controller.Value.IsValid) return;
|
||||
|
||||
VirtualFunctions.CCSPlayerPawn_Respawn(Handle);
|
||||
VirtualFunction.CreateVoid<IntPtr>(Controller.Value.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Controller.Value.Handle);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CounterStrikeSharp.API.Core;
|
||||
|
||||
public partial class CPlayerPawnComponent
|
||||
{
|
||||
public PointerTo<CBasePlayerPawn> Pawn => new PointerTo<CBasePlayerPawn>(this.Handle + 0x30);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -19806,6 +19804,63 @@ public partial class CTablet : CCSWeaponBase
|
||||
|
||||
}
|
||||
|
||||
public partial class CTakeDamageInfo : NativeObject
|
||||
{
|
||||
public CTakeDamageInfo (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
// m_vecDamageForce
|
||||
public Vector DamageForce => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamageForce");
|
||||
|
||||
// m_vecDamagePosition
|
||||
public Vector DamagePosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamagePosition");
|
||||
|
||||
// m_vecReportedPosition
|
||||
public Vector ReportedPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecReportedPosition");
|
||||
|
||||
// m_vecDamageDirection
|
||||
public Vector DamageDirection => Schema.GetDeclaredClass<Vector>(this.Handle, "CTakeDamageInfo", "m_vecDamageDirection");
|
||||
|
||||
// m_hInflictor
|
||||
public CHandle<CBaseEntity> Inflictor => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hInflictor");
|
||||
|
||||
// m_hAttacker
|
||||
public CHandle<CBaseEntity> Attacker => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hAttacker");
|
||||
|
||||
// m_hAbility
|
||||
public CHandle<CBaseEntity> Ability => Schema.GetDeclaredClass<CHandle<CBaseEntity>>(this.Handle, "CTakeDamageInfo", "m_hAbility");
|
||||
|
||||
// m_flDamage
|
||||
public ref float Damage => ref Schema.GetRef<float>(this.Handle, "CTakeDamageInfo", "m_flDamage");
|
||||
|
||||
// m_bitsDamageType
|
||||
public ref Int32 BitsDamageType => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_bitsDamageType");
|
||||
|
||||
// m_iDamageCustom
|
||||
public ref Int32 DamageCustom => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_iDamageCustom");
|
||||
|
||||
// m_iAmmoType
|
||||
public ref byte AmmoType => ref Schema.GetRef<byte>(this.Handle, "CTakeDamageInfo", "m_iAmmoType");
|
||||
|
||||
// m_flOriginalDamage
|
||||
public ref float OriginalDamage => ref Schema.GetRef<float>(this.Handle, "CTakeDamageInfo", "m_flOriginalDamage");
|
||||
|
||||
// m_bShouldBleed
|
||||
public ref bool ShouldBleed => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bShouldBleed");
|
||||
|
||||
// m_bShouldSpark
|
||||
public ref bool ShouldSpark => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bShouldSpark");
|
||||
|
||||
// m_nDamageFlags
|
||||
public ref TakeDamageFlags_t DamageFlags => ref Schema.GetRef<TakeDamageFlags_t>(this.Handle, "CTakeDamageInfo", "m_nDamageFlags");
|
||||
|
||||
// m_nNumObjectsPenetrated
|
||||
public ref Int32 NumObjectsPenetrated => ref Schema.GetRef<Int32>(this.Handle, "CTakeDamageInfo", "m_nNumObjectsPenetrated");
|
||||
|
||||
// m_bInTakeDamageFlow
|
||||
public ref bool InTakeDamageFlow => ref Schema.GetRef<bool>(this.Handle, "CTakeDamageInfo", "m_bInTakeDamageFlow");
|
||||
|
||||
}
|
||||
|
||||
public partial class CTankTargetChange : CPointEntity
|
||||
{
|
||||
public CTankTargetChange (IntPtr pointer) : base(pointer) {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
11
managed/CounterStrikeSharp.API/Core/Plugin/IPluginContext.cs
Normal file
11
managed/CounterStrikeSharp.API/Core/Plugin/IPluginContext.cs
Normal 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);
|
||||
}
|
||||
215
managed/CounterStrikeSharp.API/Core/Plugin/PluginContext.cs
Normal file
215
managed/CounterStrikeSharp.API/Core/Plugin/PluginContext.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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) =>
|
||||
{
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
Server.NextWorldUpdate(() =>
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace CounterStrikeSharp.API.Core.Plugin;
|
||||
|
||||
public enum PluginState
|
||||
{
|
||||
Unregistered,
|
||||
Loading,
|
||||
Loaded,
|
||||
Unloaded,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
5
managed/CounterStrikeSharp.API/GlobalUsings.cs
Normal file
5
managed/CounterStrikeSharp.API/GlobalUsings.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
// Global using directives
|
||||
|
||||
global using System;
|
||||
global using System.IO;
|
||||
global using CounterStrikeSharp.API.Core;
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the instance types for a Steam account.
|
||||
/// </summary>
|
||||
public enum SteamAccountInstance
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid instance.
|
||||
/// </summary>
|
||||
Invalid = -1,
|
||||
|
||||
/// <summary>
|
||||
/// All instances.
|
||||
/// </summary>
|
||||
All = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Desktop instance.
|
||||
/// </summary>
|
||||
Desktop = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Console instance.
|
||||
/// </summary>
|
||||
Console = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Web instance.
|
||||
/// </summary>
|
||||
Web = 4
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the types of Steam accounts.
|
||||
/// </summary>
|
||||
public enum SteamAccountType
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "I")]
|
||||
Invalid = 0,
|
||||
/// <summary>
|
||||
/// Individual account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "U")]
|
||||
Individual,
|
||||
/// <summary>
|
||||
/// MultiSeat account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "M")]
|
||||
MultiSeat,
|
||||
/// <summary>
|
||||
/// Game Server account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "G")]
|
||||
GameServer,
|
||||
/// <summary>
|
||||
/// Anonymous Game Server account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "A")]
|
||||
AnonGameServer,
|
||||
/// <summary>
|
||||
/// Pending account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "P")]
|
||||
Pending,
|
||||
/// <summary>
|
||||
/// Content Server account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "C")]
|
||||
ContentServer,
|
||||
/// <summary>
|
||||
/// Clan account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "g")]
|
||||
Clan,
|
||||
/// <summary>
|
||||
/// Chat account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "T")]
|
||||
Chat,
|
||||
/// <summary>
|
||||
/// Console user account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "c")]
|
||||
ConsoleUser,
|
||||
/// <summary>
|
||||
/// P2P Super Seeder account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = " ")]
|
||||
P2PSuperSeeder,
|
||||
/// <summary>
|
||||
/// Anonymous user account type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = "a")]
|
||||
AnonUser,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the universe of a Steam account.
|
||||
/// </summary>
|
||||
public enum SteamAccountUniverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Individual / unspecified universe.
|
||||
/// </summary>
|
||||
Unspecified = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Public universe.
|
||||
/// </summary>
|
||||
Public = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Beta universe.
|
||||
/// </summary>
|
||||
Beta = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Internal universe.
|
||||
/// </summary>
|
||||
Internal = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Development universe.
|
||||
/// </summary>
|
||||
Dev = 4,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||
using CounterStrikeSharp.API.Modules.Utils;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Entities
|
||||
{
|
||||
@@ -12,6 +13,7 @@ namespace CounterStrikeSharp.API.Modules.Entities
|
||||
|
||||
public static explicit operator SteamID(ulong u) => new(u);
|
||||
public static explicit operator SteamID(string s) => new(s);
|
||||
|
||||
ulong ParseId(string id)
|
||||
{
|
||||
var parts = id.Split(':');
|
||||
@@ -34,7 +36,7 @@ namespace CounterStrikeSharp.API.Modules.Entities
|
||||
|
||||
public string SteamId3
|
||||
{
|
||||
get => $"[U:1:{SteamId64 - Base}]";
|
||||
get => $"[{EnumUtils.GetEnumMemberAttributeValue(AccountType)}:{(int)AccountUniverse}:{SteamId64 - Base}]";
|
||||
set => SteamId64 = ParseId3(value);
|
||||
}
|
||||
|
||||
@@ -44,20 +46,54 @@ namespace CounterStrikeSharp.API.Modules.Entities
|
||||
set => SteamId64 = (ulong)value + Base;
|
||||
}
|
||||
|
||||
public int AccountId => (int)((SteamId64 >> 0) & 0xFFFFFFFF);
|
||||
|
||||
public SteamAccountInstance AccountInstance =>
|
||||
(SteamAccountInstance)((SteamId64 >> 32) & 0xFFFFF);
|
||||
|
||||
public SteamAccountType AccountType =>
|
||||
(SteamAccountType)((SteamId64 >> 52) & 0xF);
|
||||
|
||||
public SteamAccountUniverse AccountUniverse =>
|
||||
(SteamAccountUniverse)((SteamId64 >> 56) & 0xF);
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
if (AccountUniverse == SteamAccountUniverse.Unspecified
|
||||
|| AccountType == SteamAccountType.Invalid
|
||||
|| AccountInstance == SteamAccountInstance.Invalid)
|
||||
return false;
|
||||
if (AccountType == SteamAccountType.Individual
|
||||
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.Desktop))
|
||||
return false;
|
||||
if (AccountType == SteamAccountType.Clan
|
||||
&& (AccountId == 0 || AccountInstance != SteamAccountInstance.All))
|
||||
return false;
|
||||
if (AccountType == SteamAccountType.GameServer && AccountId == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString() => $"[SteamID {SteamId64}, {SteamId2}, {SteamId3}]";
|
||||
|
||||
public Uri ToCommunityUrl()
|
||||
{
|
||||
string url = string.Empty;
|
||||
if (AccountType == SteamAccountType.Individual)
|
||||
url = "https://steamcommunity.com/profiles/" + SteamId64;
|
||||
if (AccountType == SteamAccountType.Clan)
|
||||
url = "https://steamcommunity.com/gid/" + SteamId64;
|
||||
return new Uri(url);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -73,9 +73,26 @@ 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;
|
||||
}
|
||||
|
||||
public static DataType ToValidDataType(this Type type)
|
||||
{
|
||||
if (types.ContainsKey(type)) return types[type];
|
||||
|
||||
if (typeof(NativeObject).IsAssignableFrom(type))
|
||||
{
|
||||
return DataType.DATA_TYPE_POINTER;
|
||||
}
|
||||
|
||||
if (type.IsEnum && types.ContainsKey(Enum.GetUnderlyingType(type)))
|
||||
{
|
||||
return types[Enum.GetUnderlyingType(type)];
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Data type not supported:" + type.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
|
||||
public abstract class BaseMemoryFunction : NativeObject
|
||||
{
|
||||
private static Dictionary<string, IntPtr> _createdFunctions = new();
|
||||
|
||||
private static IntPtr CreateValveFunctionBySignature(string signature, DataType returnType,
|
||||
DataType[] argumentTypes)
|
||||
{
|
||||
if (!_createdFunctions.TryGetValue(signature, out var function))
|
||||
{
|
||||
try
|
||||
{
|
||||
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, Addresses.ServerPath, signature,
|
||||
argumentTypes.Length, (int)returnType, argumentTypes.Cast<object>().ToArray());
|
||||
_createdFunctions[signature] = function;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void Unhook(Func<DynamicHook, HookResult> handler, HookMode mode)
|
||||
{
|
||||
NativeAPI.UnhookFunction(Handle, handler, mode == HookMode.Post);
|
||||
}
|
||||
|
||||
protected T InvokeInternal<T>(params object[] args)
|
||||
{
|
||||
return NativeAPI.ExecuteVirtualFunction<T>(Handle, args);
|
||||
}
|
||||
|
||||
protected void InvokeInternalVoid(params object[] args)
|
||||
{
|
||||
NativeAPI.ExecuteVirtualFunction<object>(Handle, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
|
||||
public class DynamicHook : NativeObject
|
||||
{
|
||||
public DynamicHook(IntPtr pointer) : base(pointer)
|
||||
{
|
||||
}
|
||||
|
||||
public T GetParam<T>(int index)
|
||||
{
|
||||
return NativeAPI.DynamicHookGetParam<T>(Handle, (int)typeof(T).ToValidDataType(), index);
|
||||
}
|
||||
|
||||
public T GetReturn<T>(int index)
|
||||
{
|
||||
return NativeAPI.DynamicHookGetReturn<T>(Handle, (int)typeof(T).ToValidDataType());
|
||||
}
|
||||
|
||||
public void SetParam<T>(int index, T value)
|
||||
{
|
||||
NativeAPI.DynamicHookSetParam(Handle, (int)typeof(T).ToValidDataType(), index, value);
|
||||
}
|
||||
|
||||
public void SetReturn<T>(T value)
|
||||
{
|
||||
NativeAPI.DynamicHookSetReturn(Handle, (int)typeof(T).ToValidDataType(), value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
|
||||
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
|
||||
|
||||
public class MemoryFunctionVoid : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID, Array.Empty<DataType>())
|
||||
{
|
||||
}
|
||||
|
||||
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID, Array.Empty<DataType>())
|
||||
{
|
||||
}
|
||||
|
||||
public void Invoke()
|
||||
{
|
||||
InvokeInternalVoid();
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[] { typeof(T1).ToValidDataType() })
|
||||
{
|
||||
}
|
||||
|
||||
public MemoryFunctionVoid(string signature, string binarypath) : base(signature, binarypath, DataType.DATA_TYPE_VOID,
|
||||
new[] { typeof(T1).ToValidDataType() })
|
||||
{
|
||||
}
|
||||
|
||||
public void Invoke(T1 arg1)
|
||||
{
|
||||
InvokeInternalVoid(arg1);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType() })
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType() })
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[]
|
||||
{
|
||||
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
|
||||
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType()
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[]
|
||||
{
|
||||
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
|
||||
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
|
||||
typeof(T5).ToValidDataType()
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, DataType.DATA_TYPE_VOID,
|
||||
new[]
|
||||
{
|
||||
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(),
|
||||
typeof(T3).ToValidDataType(), typeof(T4).ToValidDataType(),
|
||||
typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType()
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8, T9> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
public class MemoryFunctionVoid<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : BaseMemoryFunction
|
||||
{
|
||||
public MemoryFunctionVoid(string signature) : base(signature, 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 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);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user