Compare commits

...

38 Commits

Author SHA1 Message Date
Michael Wilson
9bcd0f7e92 Entity Handle Overhaul (#142) 2023-11-29 21:45:31 +10:00
Roflmuffin
8cda8d9a50 feat: wrap ExecuteClientCommand and add sound example 2023-11-29 20:10:45 +10:00
Roflmuffin
575c859ddb fix: add brute force fallback for enum member attribute, fixes #150 2023-11-29 17:25:31 +10:00
Nexd
e12a7cb17a fix: wildcard bytes for signatures (resolves #123 and related issues) (#148) 2023-11-28 23:57:26 +10:00
Roflmuffin
319b116c5f fix: bugs in config manager & plugin load, fixes #138 2023-11-27 11:42:34 +10:00
Michael Wilson
e0dc053d22 Config Example Parsing (#136) 2023-11-26 20:30:21 +10:00
Charles_
f2e0dac32d Feature: ProcessTargetString() & GetPlayerFromSteamId() (#121)
Co-authored-by: Roflmuffin <shortguy014@gmail.com>
2023-11-26 15:03:01 +10:00
Michael Wilson
4e8c18abc7 Implement Core & Plugin Service Collection (#129) 2023-11-26 14:15:58 +10:00
Roflmuffin
8d1891a3a8 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-26 13:19:54 +10:00
Roflmuffin
6bc43444f7 feat: add trigger touch start and end hooks 2023-11-26 13:19:40 +10:00
miguno
f0c7869f4a Check if userid is valid before accessing its fields, and explain why (#133) 2023-11-26 10:03:20 +10:00
Robert
3e38ed3c77 feat: Added ability to GiveNamedItem using the new CsItem Enum (#105) 2023-11-25 10:40:49 +10:00
Roflmuffin
7e9e7c6665 fix: wrong chat colors 2023-11-24 21:21:03 +10:00
Roflmuffin
9a018f295b feat: add player pawn post think signature 2023-11-24 21:04:22 +10:00
Michael Wilson
8b725d435f Dynamic Hooks (#78) 2023-11-24 19:59:47 +10:00
Nexd
ea3596417a Add methods to respawn players (#114) 2023-11-24 15:19:27 +10:00
Abner Santos
123f41914e docs: Additional info admin module documentation (#116) 2023-11-23 10:44:02 +10:00
Hackmastr
8f69076405 Update TestPlugin.cs, (#115) 2023-11-23 09:03:30 +10:00
Michael Wilson
44a85d1201 chore: upgrade hl2sdk, add protoc generation (#112) 2023-11-22 21:54:03 +10:00
Roflmuffin
20f50289ee docs: update docs to use ILogger 2023-11-21 16:50:20 +10:00
Michael Wilson
bb5fb5de72 Managed Core Logging & Plugin Logging (#102) 2023-11-21 16:42:56 +10:00
Nexd
6147739cfa hotfix: new signatures (#107) 2023-11-21 12:49:42 +10:00
zonical
3ab5893f22 Remove required from flags in AdminData (#106) 2023-11-21 10:34:11 +10:00
Nexd
50ce09a7b3 feat: new virtual functions with wrapper methods (#87) 2023-11-20 08:52:07 +10:00
Roflmuffin
9c7944e6f1 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-19 18:49:48 +10:00
Roflmuffin
bc71aa7739 feat: add FireEventToClient native 2023-11-19 18:49:36 +10:00
Snowy
16a1efc0cb docs: use nuget instead (#89) 2023-11-19 18:40:43 +10:00
Roflmuffin
8ae85cedf4 docs: add Admin Framework docs category 2023-11-19 14:23:30 +10:00
zonical
8e2234ff25 Admin Manager improvements (#74) 2023-11-19 14:18:40 +10:00
Roflmuffin
04e7ed682a fix: add command listener pre handlers 2023-11-19 09:51:20 +10:00
Roflmuffin
15e1260146 feat: update schema from 17.11.23 update 2023-11-19 09:44:15 +10:00
Roflmuffin
517607d962 fix: chat command config prefixes 2023-11-18 15:40:02 +10:00
Boink
0f72631eb0 Update CommitSuicide offset for 17-11-2023 update (#98) 2023-11-18 09:43:06 +10:00
Nexd
75fcf21fb7 feat: managed coreconfig implementation (#79) 2023-11-17 17:50:59 +10:00
Nexd
0ddf6bcdfa fix: new signature for CBaseModelEntity_SetModel (#84) 2023-11-15 15:18:03 +10:00
Roflmuffin
98661cd069 fix: public and silent triggers (finally) 2023-11-14 22:29:59 +10:00
Roflmuffin
86a5699b40 feat: re-add global command listener 2023-11-14 21:15:06 +10:00
Roflmuffin
414710d05c hotfix: wrap vfunc creation in try catch to prevent all vfuncs from erroring 2023-11-13 21:40:33 +10:00
130 changed files with 5618 additions and 1329 deletions

6
.gitmodules vendored
View File

@@ -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

View File

@@ -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
@@ -29,6 +31,8 @@ SET(SOURCE_FILES
src/core/utils.h
src/core/globals.h
src/core/globals.cpp
src/core/coreconfig.h
src/core/coreconfig.cpp
src/core/gameconfig.h
src/core/gameconfig.cpp
src/core/log.h
@@ -79,12 +83,13 @@ 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
)
if (LINUX)
# memoverride.cpp is not usable on CMake Windows, cuz CMake default link libraries (seems) always link ucrt.lib
set(SOURCE_FILES
set(SOURCE_FILES
${SOURCE_FILES}
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
)
@@ -94,16 +99,21 @@ set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobuf
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs/*.proto")
## Generate protobuf source & headers
#add_custom_command(
# OUTPUT protobuf_output_stamp
# COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf/compile.sh
# COMMENT "Generating protobuf files using compile.sh script"
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/protobuf/compile.sh
# VERBATIM
#)
#
#SET(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
if (LINUX)
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/linux/protoc)
elseif(WIN32)
set(PROTOC_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2/devtools/bin/protoc.exe)
endif()
add_custom_command(
OUTPUT protobuf_output_stamp
COMMAND ${PROTOC_EXECUTABLE} --proto_path=thirdparty/protobuf-3.21.8/src --proto_path=common --cpp_out=common common/network_connection.proto
COMMENT "Generating protobuf file"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2
VERBATIM
)
SET(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
# Sources
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${NATIVES_SOURCES} ${CONVERSIONS_SOURCES} ${CONVERSIONS_HEADERS})

View File

@@ -11,18 +11,20 @@ This project is an ongoing migration of a previous project (titled [VSP.NET](htt
Due to the architectural changes of CS2, the plugin is being rebuilt on the ground up, to support Linux 64-bit, something which was previously impossible.
## Install
Download the latest build from [here](https://github.com/roflmuffin/CounterStrikeSharp/releases). (Download the with runtime version if this is your first time installing).
Detailed installation instructions can be found in the [docs](https://docs.cssharp.dev/guides/getting-started/).
## What works?
_(Note, these were features in the previous VSP.NET project, but have not been implemented yet in this project)_
These features are the core of the platform and work pretty well/have a low risk of causing issues.
- [x] Console Commands, Server Commands (e.g. css_mycommand)
- [x] Chat Commands with `!` and `/` prefixes (e.g. !mycommand)
- [ ] **(In Progress)** Console Variables
- [x] Console Commands, Server Commands (e.g. css_mycommand)
- [x] Chat Commands with `!` and `/` prefixes (e.g. !mycommand)
- [ ] **(In Progress)** Console Variables
- [x] Game Event Handlers & Custom Events (e.g. player_death)
- [x] Basic event value get/set (string, bool, int32, float)
- [x] Complex event values get/set (ehandle, pawn, player controller)
@@ -33,9 +35,10 @@ These features are the core of the platform and work pretty well/have a low risk
- [x] OnMapStart
- [x] OnTick
- [x] Server Information (current map, game time, tick rate, model precaching)
- [x] Schema System Access (access player values like current weapon, money, location etc.)
- [x] Schema System Access (access player values like current weapon, money, location etc.)
## Links
- [Join the Discord](https://discord.gg/X7r3PmuYKq): Ask questions, provide suggestions
- [Read the docs](https://docs.cssharp.dev/): Getting started guide, hello world plugin example
- [Issue tracker](https://github.com/roflmuffin/CounterStrikeSharp/issues): Raise any issues here
@@ -62,14 +65,14 @@ public class HelloWorldPlugin : BasePlugin
public override void Load(bool hotReload)
{
Console.WriteLine("Hello World!");
Logger.LogInformation("Plugin loaded successfully!");
}
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
// Userid will give you a reference to a CCSPlayerController class
Log($"Player {@event.Userid.PlayerName} has connected!");
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
return HookResult.Continue;
}
@@ -77,7 +80,7 @@ public class HelloWorldPlugin : BasePlugin
[ConsoleCommand("issue_warning", "Issue warning to player")]
public void OnCommand(CCSPlayerController? player, CommandInfo command)
{
Log("You shouldn't be doing that!");
Logger.LogWarning("Player shouldn't be doing that");
}
}
```

View File

@@ -1,6 +1,5 @@
{
"Erikj": {
"identity": "76561197960265731",
"#css/admin": {
"flags": [
"@css/reservation",
"@css/generic",
@@ -18,10 +17,7 @@
"@css/rcon",
"@css/cheats",
"@css/root"
]
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": ["@mycustomplugin/admin"]
],
"immunity": 100
}
}

View File

@@ -0,0 +1,9 @@
{
"example_command": {
"flags": [
"@css/custom-permission"
],
"check_type": "all",
"enabled": true
}
}

View File

@@ -0,0 +1,21 @@
{
"Erikj": {
"identity": "76561197960265731",
"immunity": 100,
"flags": [
"@css/custom-flag-1",
"@css/custom-flag-2"
],
"groups": [
"#css/admin"
],
"command_overrides": {
"css_plugins": true,
"css": false
}
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": ["@mycustomplugin/admin"]
}
}

View File

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

View File

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

View File

@@ -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",
@@ -50,14 +70,60 @@
"CBaseModelEntity_SetModel": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x2A\\x48\\x89\\x7C\\x24\\x2A\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50\\x48\\x8B\\xF9",
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x7D\\xE0\\x48\\x83\\xEC\\x18\\x48\\x8D\\x05\\xE5\\xD1\\xBF\\x00"
"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\\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": 18,
"linux": 19
}
},
"CCSPlayer_ItemServices_RemoveWeapons": {
"offsets": {
"windows": 21,
"linux": 20
}
},
"CGameSceneNode_GetSkeletonInstance": {
"offsets": {
"windows": 8,
"linux": 8
}
},
"CCSGameRules_TerminateRound": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\xC4\\x4C\\x89\\x48\\x20\\x55\\x56",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\x48\\x81\\xEC\\xE8"
}
},
"UTIL_CreateEntityByName": {
"signatures": {
"library": "server",
"windows": "\\x48\\x83\\xEC\\x48\\xC6\\x44\\x24\\x30\\x00\\x4C\\x8B\\xC1",
"linux": "\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x55\\x48\\x89\\xFA"
}
},
"CBaseEntity_DispatchSpawn": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA\\x48\\x8B\\xF9\\x48\\x85\\xC9",
"linux": "\\x48\\x85\\xFF\\x74\\x4B\\x55\\x48\\x89\\xE5\\x41\\x56"
}
},
"LegacyGameEventListener": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\x15\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xD2\\x74\\x2A\\x85\\xC9\\x74",
"linux": "\\x48\\x8B\\x05\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xC0\\x74\\x2A\\x83\\xFF\\x3F\\x76\\x2A\\x31\\xC0"
}
},
"CBasePlayerPawn_CommitSuicide": {
"offsets": {
"windows": 355,
"linux": 355
"windows": 356,
"linux": 356
}
},
"CBaseEntity_Teleport": {
@@ -66,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,

View File

@@ -36,6 +36,10 @@ export default defineConfig({
label: 'Features',
autogenerate: { directory: 'features' },
},
{
label: 'Admin Framework',
autogenerate: { directory: 'admin-framework' },
},
{
label: 'Reference',
autogenerate: { directory: 'reference' },

View File

@@ -0,0 +1,47 @@
---
title: Admin Command Attributes
description: A guide on using the Admin Command Attributes in plugins.
---
## Assigning permissions to a Command
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with either a `RequiresPermissions` or `RequiresPermissionsOr` attribute. The difference between the two attributes is that `RequiresPermissionsOr` needs only one permission to be present on the caller to be passed, while `RequiresPermissions` needs the caller to have all permissions listed. CounterStrikeSharp handles all of the permission checks behind the scenes for you.
```csharp
[RequiresPermissions("@css/slay", "@custom/permission")]
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
```csharp
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
public void OnMyOtherCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
You can even stack the attributes on top of each other, and all of them will be checked.
```csharp
// Requires (@css/cvar AND @custom/permission-1) AND either (@custom/permission-1 OR @custom/permission-2).
[RequiresPermissions("@css/cvar", "@custom/permission-1")]
[RequiresPermissionsOr("@css/ban", "@custom/permission-2")]
public void OnMyComplexCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
You can also check for groups using the same attributes.
```csharp
[RequiresPermissions("#css/simple-admin")]
public void OnMyGroupCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```

View File

@@ -1,6 +1,6 @@
---
title: Admin Framework
description: A guide on using the Admin Framework in plugins.
title: Defining Admins
description: A guide on how to define admins for CounterStrikeSharp.
---
## Admin Framework
@@ -9,38 +9,32 @@ CounterStrikeSharp has a basic framework which allows plugin developers to assig
## Adding Admins
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familiar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"]
},
"another ZoNiCaL": {
"identity": "STEAM_0:1:1",
"flags": ["@css/generic"]
}
}
```
You can also manually assign permissions to players in code with `AddPlayerPermissions` and `RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
You can also manually assign permissions to players in code with `AdminManager.AddPlayerPermissions` and `AdminManager.RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
## Assigning permissions to a Command
Assigning permissions to a Command is as easy as tagging the Command method (function callback) with a `RequiresPermissions` attribute.
```csharp
[RequiresPermissions("@css/slay", "@custom/permission")]
public void OnMyCommand(CCSPlayerController? caller, CommandInfo info)
{
...
}
```
CounterStrikeSharp handles all of the permission checks behind the scenes for you.
:::note
All user permissions MUST start with an at-symbol @ character, otherwise CounterStrikeSharp will not recognize the permission.
:::
### Standard Permissions
Because the flag system is just a list of strings associated with a user, there is no real list of letter based flags like there was previously in something like SourceMod. This means as a plugin author you can declare your own flags, scoped with an `@` symbol, like `@roflmuffin/guns`, which might be the permission to allow spawning of guns in a given command.
However there is a somewhat standardised list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
However there is a somewhat standardized list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
```shell
@css/reservation # Reserved slot access.
@@ -60,3 +54,8 @@ However there is a somewhat standardised list of flags that it is advised you us
@css/cheats # Change sv_cheats or use cheating commands.
@css/root # Magically enables all flags and ignores immunity values.
```
:::note
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
:::

View File

@@ -0,0 +1,53 @@
---
title: Defining Command Overrides
description: A guide on how to define command overrides for CounterStrikeSharp.
---
## Defining Admin and Group specific overrides
Command permissions can be overriden so specific admins or groups can execute the command, regardless of any permissions they may or may not have. You can define command overrides by adding the `command_overrides` key to each admin in `configs/admins.json` or group in `configs/admin_groups.json`. Command overrides can either be set to `true`, meaning the admin/group can execute the command, or `false`, meaning the admin/group cannot execute the command at all.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"],
"immunity": 100,
"command_overrides": {
"example_command": true
}
}
}
```
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
],
"command_overrides": {
"example_command_2": false
}
}
```
You can set a command override for a player in code using `AdminManager.SetPlayerCommandOverride`.
## Replacing Command permissions
Command permissions can be entirely replaced. These are defined in `configs/admin_overrides.json`. The important things you need to declare are what commands are being changed and what their new flags are. Command overrides can be set to be enabled or disabled, and you can toggle them in code with `AdminManager.SetCommandOverrideState`. You can also specify whether the command override requires the caller to have all of the new permissions (similar to a `RequiresPermissions` attribute check) or only one or more permissions (similar to a `RequirePermissionsOr` attribute check). You cannot stack permission checks.
```json
"css": {
"flags": [
"@css/custom-permission"
],
"check_type": "all",
"enabled": true
}
```
You can check if a command has been overriden in code using `AdminManager.CommandIsOverriden`, and you can manipulate the command override permissions using `AdminManager.AddPermissionOverride` and `AdminManager.RemovePermissionOverride`.

View File

@@ -0,0 +1,42 @@
---
title: Defining Admin Groups
description: A guide on how to define admin groups for CounterStrikeSharp.
---
## Adding Groups
Groups can be created to group a series of permissions together under one tag. They are defined in `configs/admin_groups.json`. The important things you need to declare is the name of the group and the permissions they have.
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
]
}
```
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 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`.

View File

@@ -0,0 +1,36 @@
---
title: Defining Admin Immunity
description: A guide on how to define immunity for admins for CounterStrikeSharp.
---
## Player Immunity
Admins can be assigned an immunity value, similar to SourceMod. If an admin or player with a lower immunity value targets another admin or player with a larger immunity value, then the command will fail. You can define an immunity value by adding the `immunity` key to each admin in `configs/admins.json`.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"],
"immunity": 100
}
}
```
You can even assign an immunity value to groups. If an admin has a lower immunity value, the group's value will be used instead.
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
],
"immunity": 100
}
```
:::note
CounterStrikeSharp does not automatically handle immunity checking. It is up to individual plugins to handle immunity checks as they can implement different ways of targeting players. This can be done in code with `AdminManager.CanPlayerTarget`. You can also set a player's immunity in code with `AdminManager.SetPlayerImmunity`.
:::

View File

@@ -17,8 +17,13 @@ The first parameter type must be a subclass of the `GameEvent` class. The names
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
// Userid will give you a reference to a CCSPlayerController class
Log($"Player {@event.Userid.PlayerName} has connected!");
// 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;
}
@@ -33,7 +38,7 @@ public override void Load(bool hotReload)
{
RegisterEventHandler<EventRoundStart>((@event, info) =>
{
Console.WriteLine($"Round has started with time limit of {@event.Timelimit}");
Logger.LogInformation("Round has started with time limit of {Timelimit}", @event.Timelimit);
return HookResult.Continue;
});

View File

@@ -22,7 +22,7 @@ public override void Load(bool hotReload)
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.Y = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.Z = Random.Shared.NextSingle() * 255.0f;
Log($"Smoke grenade spawned with color {projectile.SmokeColor}");
Logger.LogInformation("Smoke grenade spawned with color {SmokeColor}", projectile.SmokeColor);
});
});
}

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
---
title: Hello World Plugin
description: How to write your first plugin for CounterStrikeSharp
sidebar:
order: 0
---
## Creating a New Project
@@ -34,6 +36,13 @@ Use your IDE (Visual Studio/Rider) to add a reference to the `CounterStrikeSharp
</Project>
```
:::tip
Instead of manually adding a reference to `CounterStrikeSharp.Api.dll`, you can install the NuGet package `CounterStrikeSharp.Api` using the following:
```shell
dotnet add package CounterStrikeSharp.API
```
:::
### Creating a Plugin File
Rename the default class file that came with your new project (by default it should be `Class1.cs`) to something more accurate, like `HelloWorldPlugin.cs`. Inside this file, we will insert the stub hello world plugin. Be sure to change the name and namespace so it matches your project name.

View File

@@ -27,14 +27,14 @@ public class HelloWorldPlugin : BasePlugin
public override void Load(bool hotReload)
{
Console.WriteLine("Hello World!");
Logger.LogInformation("Plugin loaded successfully!");
}
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
// Userid will give you a reference to a CCSPlayerController class
Log($"Player {@event.Userid.PlayerName} has connected!");
Logger.LogInformation("Player {Name} has connected!", @event.Userid.PlayerName);
return HookResult.Continue;
}
@@ -42,7 +42,7 @@ public class HelloWorldPlugin : BasePlugin
[ConsoleCommand("issue_warning", "Issue warning to player")]
public void OnCommand(CCSPlayerController? player, CommandInfo command)
{
Log("You shouldn't be doing that!");
Logger.LogWarning("Player shouldn't be doing that");
}
}
```

View File

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

1
libraries/DynoHook Submodule

Submodule libraries/DynoHook added at d7f8ebb059

1
libraries/asmjit Submodule

Submodule libraries/asmjit added at 0dd16b0a98

View File

@@ -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
)

View File

@@ -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)
@@ -26,6 +28,7 @@ add_definitions(-DMETA_IS_SOURCE2)
include_directories(
${SOURCESDK}
${SOURCESDK}/thirdparty/protobuf-3.21.8/src
${SOURCESDK}/common
${SOURCESDK}/game/shared
${SOURCESDK}/game/server
@@ -44,6 +47,7 @@ include_directories(
libraries/spdlog/include
libraries/tl
libraries/funchook/include
libraries/DynoHook/src
libraries
)

View File

@@ -17,4 +17,5 @@ set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
dyncall_s
distorm
funchook-static
dynohook
)

View File

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

View File

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

View File

@@ -157,6 +157,56 @@ namespace CounterStrikeSharp.API.Core
}
}
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();
@@ -484,6 +534,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();
@@ -552,6 +645,17 @@ namespace CounterStrikeSharp.API.Core
}
}
public static void FireEventToClient(IntPtr gameevent, int clientindex){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(gameevent);
ScriptContext.GlobalScriptContext.Push(clientindex);
ScriptContext.GlobalScriptContext.SetIdentifier(0x40B7C06C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static string GetEventName(IntPtr gameevent){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -792,6 +896,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();

View File

@@ -0,0 +1,254 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System.Linq;
using System.Text;
using CounterStrikeSharp.API.Core.Hosting;
using CounterStrikeSharp.API.Core.Plugin;
using CounterStrikeSharp.API.Core.Plugin.Host;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
public sealed class Application
{
private static Application _instance = null!;
public ILogger Logger { get; }
public static Application Instance => _instance!;
public static string RootDirectory => Instance._scriptHostConfiguration.RootPath;
private readonly IScriptHostConfiguration _scriptHostConfiguration;
private readonly GameDataProvider _gameDataProvider;
private readonly CoreConfig _coreConfig;
private readonly IPluginManager _pluginManager;
private readonly IPluginContextQueryHandler _pluginContextQueryHandler;
public Application(ILoggerFactory loggerFactory, IScriptHostConfiguration scriptHostConfiguration,
GameDataProvider gameDataProvider, CoreConfig coreConfig, IPluginManager pluginManager,
IPluginContextQueryHandler pluginContextQueryHandler)
{
Logger = loggerFactory.CreateLogger("Core");
_scriptHostConfiguration = scriptHostConfiguration;
_gameDataProvider = gameDataProvider;
_coreConfig = coreConfig;
_pluginManager = pluginManager;
_pluginContextQueryHandler = pluginContextQueryHandler;
_instance = this;
}
public void Start()
{
Logger.LogInformation("CounterStrikeSharp is starting up...");
_coreConfig.Load();
_gameDataProvider.Load();
var adminGroupsPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var adminPath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admins.json");
Logger.LogInformation("Loading Admins from {Path}", adminPath);
AdminManager.LoadAdminData(adminPath);
var overridePath = Path.Combine(_scriptHostConfiguration.RootPath, "configs", "admin_overrides.json");
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
AdminManager.LoadCommandOverrides(overridePath);
AdminManager.MergeGroupPermsIntoAdmins();
_pluginManager.Load();
for (var i = 1; i <= 9; i++)
{
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
});
}
RegisterPluginCommands();
}
[RequiresPermissions("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
var currentVersion = Api.GetVersion();
info.ReplyToCommand(
" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 1,
usage: "[option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded.",
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
{
switch (info.GetArg(1))
{
case "list":
{
info.ReplyToCommand(
$" List of all plugins currently loaded by CounterStrikeSharp: {_pluginManager.GetLoadedPlugins().Count()} plugins loaded.",
true);
foreach (var plugin in _pluginManager.GetLoadedPlugins())
{
var sb = new StringBuilder();
sb.AppendFormat(" [#{0}:{1}]: \"{2}\" ({3})", plugin.PluginId,
plugin.State.ToString().ToUpper(), plugin.Plugin.ModuleName,
plugin.Plugin.ModuleVersion);
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleAuthor))
sb.AppendFormat(" by {0}", plugin.Plugin.ModuleAuthor);
if (!string.IsNullOrEmpty(plugin.Plugin.ModuleDescription))
{
sb.Append("\n");
sb.Append(" ");
sb.Append(plugin.Plugin.ModuleDescription);
}
info.ReplyToCommand(sb.ToString(), true);
}
break;
}
case "start":
case "load":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n",
true);
break;
}
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
// We'll assume we have a full path if we have ".dll".
var path = info.GetArg(2);
if (!path.EndsWith(".dll"))
{
path = Path.Combine(_scriptHostConfiguration.RootPath, $"plugins/{path}/{path}.dll");
}
else
{
path = Path.Combine(_scriptHostConfiguration.RootPath, path);
}
var plugin = _pluginContextQueryHandler.FindPluginByModulePath(path);
if (plugin == null)
{
try
{
_pluginManager.LoadPlugin(path);
} catch (Exception e)
{
info.ReplyToCommand($"Could not load plugin \"{path}\")", true);
}
}
else
{
plugin.Load(false);
}
break;
}
case "stop":
case "unload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n",
true);
break;
}
var pluginIdentifier = info.GetArg(2);
IPluginContext? plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(false);
break;
}
case "restart":
case "reload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand(
"Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n",
true);
break;
}
var pluginIdentifier = info.GetArg(2);
var plugin = _pluginContextQueryHandler.FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(true);
plugin.Load(true);
break;
}
default:
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded."
, true);
break;
}
}
private void RegisterPluginCommands()
{
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.",
OnCSSPluginCommand);
}
}
}

View File

@@ -18,24 +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;
@@ -50,9 +45,10 @@ 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; }
public virtual void Load(bool hotReload)
{
@@ -162,6 +158,42 @@ namespace CounterStrikeSharp.API.Core
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
@@ -185,14 +217,6 @@ namespace CounterStrikeSharp.API.Core
}
}
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttribute<RequiresPermissions>()?.RequiredPermissions;
if (permissions != null && !AdminManager.PlayerHasPermissions(caller, permissions))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
handler?.Invoke(caller, command);
});
@@ -286,7 +310,8 @@ namespace CounterStrikeSharp.API.Core
.Select(p => p.GetCustomAttribute<CastFromAttribute>()?.Type)
.ToArray();
Console.WriteLine($"Registering listener for {listenerName} with {parameterTypes.Length}");
Application.Instance.Logger.LogDebug("Registering listener for {ListenerName} with {ParameterCount} parameters",
listenerName, parameterTypes.Length);
var wrappedHandler = new Action<ScriptContext>(context =>
{

View File

@@ -19,10 +19,14 @@ 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
{
@@ -31,27 +35,30 @@ namespace CounterStrikeSharp.API.Core
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public string PublicChatTrigger { get; set; } = "!";
[JsonPropertyName("PublicChatTrigger")]
public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("SilentChatTrigger")] public string SilentChatTrigger { get; set; } = "/";
[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;
}
/// <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.
/// </summary>
public static string PublicChatTrigger => _coreConfig.PublicChatTrigger;
public static IEnumerable<string> PublicChatTrigger => _coreConfig.PublicChatTrigger;
/// <summary>
/// List of characters to use for silent chat triggers.
/// </summary>
public static string SilentChatTrigger => _coreConfig.SilentChatTrigger;
public static IEnumerable<string> SilentChatTrigger => _coreConfig.SilentChatTrigger;
/// <summary>
/// <para>
@@ -75,46 +82,56 @@ namespace CounterStrikeSharp.API.Core
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
}
public static partial class CoreConfig
public partial class CoreConfig : IStartupService
{
private static CoreConfigData _coreConfig = new CoreConfigData();
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))
{
Console.WriteLine($"Core configuration could not be found at path '{coreConfigPath}', fallback values will be used.");
_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;
}
Console.WriteLine($"Loaded core configuration");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load core configuration: {ex}, fallback values will be used.");
_logger.LogWarning(ex, "Failed to load core configuration, fallback values will be used");
}
_logger.LogInformation("Successfully loaded core configuration");
}
}
}
}

View File

@@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
@@ -76,7 +77,7 @@ namespace CounterStrikeSharp.API.Core
}
catch (Exception e)
{
Console.WriteLine(e);
Application.Instance.Logger.LogError(e, "Error invoking callback");
}
});
s_callback = dg;
@@ -140,10 +141,7 @@ namespace CounterStrikeSharp.API.Core
{
ms_references.Remove(reference);
Console.BackgroundColor = ConsoleColor.Blue;
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine($"Removing function/callback reference: {reference}");
Console.ResetColor();
Application.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
}
}
}

View File

@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.IO;
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; }
@@ -29,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))!;
Console.WriteLine($"Loaded game data with {_methods.Count} methods.");
Methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(_gameDataFilePath))!;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load game data: {ex}");
_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)
{
Console.WriteLine($"Getting signature: {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");
@@ -77,12 +92,12 @@ public static class GameData
public static int GetOffset(string key)
{
if (!_methods.ContainsKey(key))
if (!GameDataProvider.Methods.ContainsKey(key))
{
throw new Exception($"Method {key} not found in gamedata.json");
}
var methodMetadata = _methods[key];
var methodMetadata = GameDataProvider.Methods[key];
if (methodMetadata.Offsets == null)
{

View File

@@ -1,332 +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.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Core
{
public sealed class GlobalContext
{
private static GlobalContext _instance = null;
public static GlobalContext Instance => _instance;
private DirectoryInfo rootDir;
private readonly List<PluginContext> _loadedPlugins = new();
public GlobalContext()
{
rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
_instance = this;
}
~GlobalContext()
{
foreach (var plugin in _loadedPlugins)
{
plugin.Unload();
}
}
public void OnNativeUnload()
{
foreach (var plugin in _loadedPlugins)
{
plugin.Unload();
}
}
public void InitGlobalContext()
{
Console.WriteLine("Loading CoreConfig from \"configs/core.json\"");
CoreConfig.Load(Path.Combine(rootDir.FullName, "configs", "core.json"));
Console.WriteLine("Loading GameData from \"gamedata/gamedata.json\"");
GameData.Load(Path.Combine(rootDir.FullName, "gamedata", "gamedata.json"));
Console.WriteLine("Loading Admins from \"configs/admins.json\"");
AdminManager.Load(Path.Combine(rootDir.FullName, "configs", "admins.json"));
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);
});
}
Console.WriteLine("Loading C# plugins...");
var pluginCount = LoadAllPlugins();
Console.WriteLine($"All managed modules were loaded. {pluginCount} plugins loaded.");
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)
{
Console.WriteLine(e);
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)
{
Console.WriteLine($"Plugin path: {path}");
try
{
LoadPlugin(path);
}
catch (Exception e)
{
Console.WriteLine($"Failed to load plugin {path} with error {e}");
}
}
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("can_execute_css_commands")]
[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("can_execute_css_commands")]
[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)
{
Console.WriteLine($"Failed to load plugin {path} with error {e}");
}
break;
}
case "stop":
case "unload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n", true);
break;
}
var pluginIdentifier = info.GetArg(2);
PluginContext? plugin = FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload();
_loadedPlugins.Remove(plugin);
break;
}
case "restart":
case "reload":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n", true);
break;
}
var pluginIdentifier = info.GetArg(2);
var plugin = FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(true);
plugin.Load(true);
break;
}
default:
info.ReplyToCommand("Valid usage: css_plugins [option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded."
, true);
break;
}
}
private void RegisterPluginCommands()
{
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.", OnCSSPluginCommand);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
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; private set; }
private static Logger? SerilogLogger { get; set; }
public static void AddCoreLogging(this ILoggingBuilder builder, string contentRoot)
{
if (SerilogLogger == null)
{
SerilogLogger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.With<SourceContextEnricher>()
.WriteTo.Console(
outputTemplate:
"{Timestamp:HH:mm:ss} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-cssharp.txt" }),
rollingInterval: RollingInterval.Day,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.WriteTo.File(Path.Join(new[] { contentRoot, "logs", $"log-all.txt" }),
rollingInterval: RollingInterval.Day, shared: true,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u4}] (cssharp:{SourceContext}) {Message:lj}{NewLine}{Exception}")
.CreateLogger();
Factory =
LoggerFactory.Create(builder => { builder.AddSerilog(SerilogLogger); });
}
builder.AddSerilog(SerilogLogger);
}
}

View File

@@ -0,0 +1,23 @@
using CounterStrikeSharp.API.Core.Plugin;
using Serilog.Core;
using Serilog.Events;
namespace CounterStrikeSharp.API.Core.Logging;
public class PluginNameEnricher : ILogEventEnricher
{
public const string PropertyName = "PluginName";
public PluginNameEnricher(PluginContext pluginContext)
{
Context = pluginContext;
}
public PluginContext Context { get; }
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var property = propertyFactory.CreateProperty(PropertyName, Context.Plugin.ModuleName);
logEvent.AddPropertyIfAbsent(property);
}
}

View File

@@ -0,0 +1,26 @@
using System.Linq;
using Serilog.Core;
using Serilog.Events;
namespace CounterStrikeSharp.API.Core.Logging;
public class SourceContextEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
if (logEvent.Properties.TryGetValue("SourceContext", out var property))
{
var scalarValue = property as ScalarValue;
var value = scalarValue?.Value as string;
if (value?.StartsWith("CounterStrikeSharp") ?? false)
{
var lastElement = value.Split(".").LastOrDefault();
if (!string.IsNullOrWhiteSpace(lastElement))
{
logEvent.AddOrUpdateProperty(new LogEventProperty("SourceContext", new ScalarValue(lastElement)));
}
}
}
}
}

View File

@@ -12,6 +12,11 @@ public partial class CBaseEntity
Handle, position.Handle, angles.Handle, velocity.Handle);
}
public void DispatchSpawn()
{
VirtualFunctions.CBaseEntity_DispatchSpawn(Handle, IntPtr.Zero);
}
/// <summary>
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
/// </summary>

View File

@@ -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);

View File

@@ -0,0 +1,31 @@
/*
* 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 CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSGameRules
{
/// <summary>
/// Terminate the round with the given delay and reason.
/// </summary>
public void TerminateRound(float delay, RoundEndReason roundEndReason)
{
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
@@ -10,8 +11,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 +24,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);
NativeAPI.PrintToConsole((int)Index, $"{message}\n\0");
}
public void PrintToChat(string message)
@@ -47,9 +58,62 @@ public partial class CCSPlayerController
Duration = 5,
Userid = this
};
@event.FireEvent(false);
@event.FireEventToClient(this);
}
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActiveWeapon()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
if (PlayerPawn.Value.WeaponServices == null) return;
if (!PlayerPawn.Value.WeaponServices.ActiveWeapon.IsValid) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
CCSPlayer_WeaponServices weponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
itemServices.DropActivePlayerWeapon(weponServices.ActiveWeapon.Value);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
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);
@@ -79,4 +143,8 @@ public partial class CCSPlayerController
/// 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;
}

View 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);
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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 CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSPlayer_ItemServices
{
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActivePlayerWeapon(CBasePlayerWeapon activeWeapon)
{
VirtualFunction.CreateVoid<nint, nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_DropActivePlayerWeapon"))(Handle, activeWeapon.Handle);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
VirtualFunction.CreateVoid<nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_RemoveWeapons"))(Handle);
}
}

View File

@@ -12,18 +12,31 @@ 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 == other?.EntityHandle;
}
public override bool Equals(object? obj)
@@ -50,5 +63,5 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
public partial class CEntityIdentity
{
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
public unsafe CHandle<CEntityInstance> EntityHandle => new(Handle + 0x10);
public unsafe CHandle<CEntityInstance> EntityHandle => new(this.Handle + 0x10);
}

View File

@@ -0,0 +1,30 @@
/*
* 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 CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CGameSceneNode
{
/// <summary>
/// Gets the <see cref="CSkeletonInstance"/> instance from the node.
/// </summary>
public CSkeletonInstance GetSkeletonInstance()
{
return new CSkeletonInstance(VirtualFunction.Create<nint, nint>(Handle, GameData.GetOffset("CGameSceneNode_GetSkeletonInstance"))(Handle));
}
}

View File

@@ -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);
}

View File

@@ -355,6 +355,13 @@ public enum BrushSolidities_e : uint
BRUSHSOLID_ALWAYS = 0x2,
}
public enum C4LightEffect_t : uint
{
eLightEffectNone = 0x0,
eLightEffectDropped = 0x1,
eLightEffectThirdPersonHeld = 0x2,
}
public enum CAnimationGraphVisualizerPrimitiveType : uint
{
ANIMATIONGRAPHVISUALIZERPRIMITIVETYPE_Text = 0x0,
@@ -2773,6 +2780,7 @@ public enum TakeDamageFlags_t : uint
DFLAG_RADIUS_DMG = 0x400,
DMG_LASTDFLAG = 0x400,
DFLAG_IGNORE_ARMOR = 0x800,
DFLAG_SUPPRESS_UTILREMOVE = 0x1000,
}
public enum TextureRepetitionMode_t : uint
@@ -3927,6 +3935,9 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
{
public CBaseCSGrenadeProjectile (IntPtr pointer) : base(pointer) {}
// m_vInitialPosition
public Vector InitialPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialPosition");
// m_vInitialVelocity
public Vector InitialVelocity => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseCSGrenadeProjectile", "m_vInitialVelocity");
@@ -3972,6 +3983,12 @@ public partial class CBaseCSGrenadeProjectile : CBaseGrenade
// m_nTicksAtZeroVelocity
public ref Int32 TicksAtZeroVelocity => ref Schema.GetRef<Int32>(this.Handle, "CBaseCSGrenadeProjectile", "m_nTicksAtZeroVelocity");
// m_bHasEverHitPlayer
public ref bool HasEverHitPlayer => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bHasEverHitPlayer");
// m_bClearFromPlayers
public ref bool ClearFromPlayers => ref Schema.GetRef<bool>(this.Handle, "CBaseCSGrenadeProjectile", "m_bClearFromPlayers");
}
public partial class CBaseDMStart : CPointEntity
@@ -7099,8 +7116,8 @@ public partial class CCSGameRules : CTeamplayRules
// m_nShorthandedBonusLastEvalRound
public ref Int32 ShorthandedBonusLastEvalRound => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nShorthandedBonusLastEvalRound");
// m_bMatchAbortedDueToPlayerBan
public ref bool MatchAbortedDueToPlayerBan => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bMatchAbortedDueToPlayerBan");
// m_nMatchAbortedEarlyReason
public ref Int32 MatchAbortedEarlyReason => ref Schema.GetRef<Int32>(this.Handle, "CCSGameRules", "m_nMatchAbortedEarlyReason");
// m_bHasTriggeredRoundStartMusic
public ref bool HasTriggeredRoundStartMusic => ref Schema.GetRef<bool>(this.Handle, "CCSGameRules", "m_bHasTriggeredRoundStartMusic");
@@ -7515,6 +7532,12 @@ public partial class CCSPlayer_MovementServices : CPlayer_MovementServices_Human
// m_flStamina
public ref float Stamina => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flStamina");
// m_flHeightAtJumpStart
public ref float HeightAtJumpStart => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flHeightAtJumpStart");
// m_flMaxJumpHeightThisJump
public ref float MaxJumpHeightThisJump => ref Schema.GetRef<float>(this.Handle, "CCSPlayer_MovementServices", "m_flMaxJumpHeightThisJump");
}
public partial class CCSPlayer_PingServices : CPlayerPawnComponent
@@ -7792,6 +7815,9 @@ public partial class CCSPlayerController : CBasePlayerController
// m_uiAbandonRecordedReason
public ref UInt32 UiAbandonRecordedReason => ref Schema.GetRef<UInt32>(this.Handle, "CCSPlayerController", "m_uiAbandonRecordedReason");
// m_bCannotBeKicked
public ref bool CannotBeKicked => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bCannotBeKicked");
// m_bEverFullyConnected
public ref bool EverFullyConnected => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bEverFullyConnected");
@@ -7909,9 +7935,15 @@ public partial class CCSPlayerController : CBasePlayerController
// m_bGaveTeamDamageWarningThisRound
public ref bool GaveTeamDamageWarningThisRound => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerController", "m_bGaveTeamDamageWarningThisRound");
// m_dblLastReceivedPacketPlatFloatTime
public ref double DblLastReceivedPacketPlatFloatTime => ref Schema.GetRef<double>(this.Handle, "CCSPlayerController", "m_dblLastReceivedPacketPlatFloatTime");
// m_LastTeamDamageWarningTime
public ref float LastTeamDamageWarningTime => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTeamDamageWarningTime");
// m_LastTimePlayerWasDisconnectedForPawnsRemove
public ref float LastTimePlayerWasDisconnectedForPawnsRemove => ref Schema.GetRef<float>(this.Handle, "CCSPlayerController", "m_LastTimePlayerWasDisconnectedForPawnsRemove");
}
public partial class CCSPlayerController_ActionTrackingServices : CPlayerControllerComponent
@@ -8046,6 +8078,9 @@ public partial class CCSPlayerPawn : CCSPlayerPawnBase
set { Schema.SetString(this.Handle, "CCSPlayerPawn", "m_szLastPlaceName", value); }
}
// m_bInHostageResetZone
public ref bool InHostageResetZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInHostageResetZone");
// m_bInBuyZone
public ref bool InBuyZone => ref Schema.GetRef<bool>(this.Handle, "CCSPlayerPawn", "m_bInBuyZone");
@@ -9385,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
{
@@ -12825,6 +12858,9 @@ public partial class CHostage : CHostageExpresserShim
// m_vecSpawnGroundPos
public Vector SpawnGroundPos => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecSpawnGroundPos");
// m_vecHostageResetPosition
public Vector HostageResetPosition => Schema.GetDeclaredClass<Vector>(this.Handle, "CHostage", "m_vecHostageResetPosition");
}
public partial class CHostageAlias_info_hostage_spawn : CHostage
@@ -14153,6 +14189,28 @@ public partial class CLogicDistanceCheck : CLogicalEntity
}
public partial class CLogicEventListener : CLogicalEntity
{
public CLogicEventListener (IntPtr pointer) : base(pointer) {}
// m_strEventName
public string StrEventName
{
get { return Schema.GetUtf8String(this.Handle, "CLogicEventListener", "m_strEventName"); }
set { Schema.SetString(this.Handle, "CLogicEventListener", "m_strEventName", value); }
}
// m_bIsEnabled
public ref bool IsEnabled => ref Schema.GetRef<bool>(this.Handle, "CLogicEventListener", "m_bIsEnabled");
// m_nTeam
public ref Int32 Team => ref Schema.GetRef<Int32>(this.Handle, "CLogicEventListener", "m_nTeam");
// m_OnEventFired
public CEntityIOOutput OnEventFired => Schema.GetDeclaredClass<CEntityIOOutput>(this.Handle, "CLogicEventListener", "m_OnEventFired");
}
public partial class CLogicGameEvent : CLogicalEntity
{
public CLogicGameEvent (IntPtr pointer) : base(pointer) {}
@@ -19746,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) {}
@@ -20138,6 +20253,12 @@ public partial class CTriggerGravity : CBaseTrigger
}
public partial class CTriggerHostageReset : CBaseTrigger
{
public CTriggerHostageReset (IntPtr pointer) : base(pointer) {}
}
public partial class CTriggerHurt : CBaseTrigger
{
public CTriggerHurt (IntPtr pointer) : base(pointer) {}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Core.Hosting;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core.Plugin.Host;
public class PluginManager : IPluginManager
{
private readonly HashSet<PluginContext> _loadedPluginContexts = new();
private readonly IScriptHostConfiguration _scriptHostConfiguration;
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<PluginManager> _logger;
public PluginManager(IScriptHostConfiguration scriptHostConfiguration, ILogger<PluginManager> logger, IServiceProvider serviceProvider)
{
_scriptHostConfiguration = scriptHostConfiguration;
_logger = logger;
_serviceProvider = serviceProvider;
}
public void Load()
{
var pluginDirectories = Directory.GetDirectories(_scriptHostConfiguration.PluginPath);
var pluginAssemblyPaths = pluginDirectories
.Select(dir => Path.Combine(dir, Path.GetFileName(dir) + ".dll"))
.Where(File.Exists)
.ToArray();
foreach (var path in pluginAssemblyPaths)
{
try
{
LoadPlugin(path);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
}
public IEnumerable<PluginContext> GetLoadedPlugins()
{
return _loadedPluginContexts;
}
public void LoadPlugin(string path)
{
var plugin = new PluginContext(_serviceProvider, _scriptHostConfiguration, path, _loadedPluginContexts.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
_loadedPluginContexts.Add(plugin);
plugin.Load();
}
}

View File

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

View File

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

View File

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

View File

@@ -1,133 +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.Modules.Config;
using CounterStrikeSharp.API.Modules.Events;
using McMaster.NETCore.Plugins;
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;
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)
{
Console.WriteLine($"Plugin {Name} has been deleted, unloading...");
Unload(true);
}
};
_fileWatcher.Filter = "*.dll";
_fileWatcher.EnableRaisingEvents = true;
_assemblyLoader.Reloaded += async (s, e) => await OnReloadedAsync(s, e);
}
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
{
Console.WriteLine($"Reloading plugin {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}].");
Console.WriteLine($"Loading plugin: {pluginType.Name}");
_plugin = (BasePlugin)Activator.CreateInstance(pluginType)!;
_plugin.ModulePath = _path;
_plugin.RegisterAllAttributes(_plugin);
_plugin.InitializeConfig(_plugin, pluginType);
_plugin.Load(hotReload);
Console.WriteLine($"Finished loading plugin: {Name}");
}
}
public void Unload(bool hotReload = false)
{
var cachedName = Name;
Console.WriteLine($"Unloading plugin {Name}");
_plugin.Unload(hotReload);
_plugin.Dispose();
if (!hotReload)
{
_assemblyLoader.Dispose();
_fileWatcher.Dispose();
}
Console.WriteLine($"Finished unloading plugin {cachedName}");
}
}
}

View File

@@ -67,13 +67,11 @@ namespace CounterStrikeSharp.API.Core
public unsafe ScriptContext()
{
//Console.WriteLine("Global context address: " + (IntPtr)m_extContext);
}
public unsafe ScriptContext(fxScriptContext* context)
{
m_extContext = *context;
//Console.WriteLine("Global context address: " + (IntPtr)m_extContext);
}
private readonly ConcurrentQueue<Action> ms_finalizers = new ConcurrentQueue<Action>();

View File

@@ -23,6 +23,13 @@
<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" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@@ -0,0 +1,176 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Linq;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class CommandData
{
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
[JsonPropertyName("enabled")] public bool Enabled { get; set; } = true;
[JsonPropertyName("check_type")] public required string CheckType { get; init; }
}
public static partial class AdminManager
{
private static Dictionary<string, CommandData> CommandOverrides = new();
public static void LoadCommandOverrides(string overridePath)
{
try
{
if (!File.Exists(overridePath))
{
Console.WriteLine("Admin command overrides file not found. Skipping admin command overrides load.");
return;
}
var overridesFromFile = JsonSerializer.Deserialize<Dictionary<string, CommandData>>
(File.ReadAllText(overridePath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (overridesFromFile == null) { throw new FileNotFoundException(); }
foreach (var (command, overrideDef) in overridesFromFile)
{
if (CommandOverrides.ContainsKey(command))
{
CommandOverrides[command].Flags.UnionWith(overrideDef.Flags);
}
else
{
CommandOverrides.Add(command, overrideDef);
}
}
Console.WriteLine($"Loaded {CommandOverrides.Count} admin command overrides.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin command overrides: {ex}");
}
}
/// <summary>
/// Checks to see if a command has overriden permissions.
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>True if the command has overriden permissions, false if not.</returns>
public static bool CommandIsOverriden(string commandName)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return false;
return overrideDef.Enabled && overrideDef?.Flags.Count() > 0;
}
/// <summary>
/// Grabs the data for a command override that was loaded from "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>CommandData class if found, null if not.</returns>
public static CommandData? GetCommandOverrideData(string commandName)
{
return CommandOverrides.GetValueOrDefault(commandName);
}
/// <summary>
/// Grabs the new, overriden flags for a command.
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <returns>If the command is valid, a valid array of flags.</returns>
public static string[] GetPermissionOverrides(string commandName)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
return overrideDef?.Flags.ToArray() ?? new string[] { };
}
/// <summary>
/// Adds a new permission to a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="permissions">Permissions to add to the command override.</param>
public static void AddPermissionOverride(string commandName, params string[] permissions)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null)
{
overrideDef = new CommandData()
{
Flags = new(permissions),
Enabled = true,
CheckType = "all"
};
CommandOverrides[commandName] = overrideDef;
return;
}
foreach (var flag in permissions)
{
overrideDef.Flags.Add(flag);
}
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Removes a permission from a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="permissions">Permissions to remove from the command override.</param>
public static void RemovePermissionOverride(string commandName, params string[] permissions)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags.ExceptWith(permissions);
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Clears all permissions from a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="disable">Whether to disable the command override after clearing.</param>
public static void ClearPermissionOverride(string commandName, bool disable = true)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags?.Clear();
overrideDef.Enabled = !disable;
CommandOverrides[commandName] = overrideDef;
}
/// <summary>
/// Deletes a command override.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
public static void DeleteCommandOverride(string commandName)
{
if (!CommandOverrides.ContainsKey(commandName)) return;
CommandOverrides.Remove(commandName);
}
/// <summary>
/// Sets a command override to be enabled or disabled.
/// This is not saved to "configs/admin_overrides.json".
/// </summary>
/// <param name="commandName">Name of the command.</param>
/// <param name="state">New state of the command override.</param>
public static void SetCommandOverideState(string commandName, bool state)
{
CommandOverrides.TryGetValue(commandName, out var overrideDef);
if (overrideDef == null) return;
overrideDef.Flags?.Clear();
overrideDef.Enabled = state;
CommandOverrides[commandName] = overrideDef;
}
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("groups")] public HashSet<string> Groups { get; init; } = new();
}
public partial class AdminGroupData
{
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
[JsonPropertyName("immunity")] public uint Immunity { get; set; } = 0;
[JsonPropertyName("command_overrides")] public Dictionary<string, bool> CommandOverrides { get; init; } = new();
}
public static partial class AdminManager
{
private static Dictionary<string, AdminGroupData> Groups = new();
public static void LoadAdminGroups(string adminGroupsPath)
{
try
{
if (!File.Exists(adminGroupsPath))
{
Console.WriteLine("Admin groups file not found. Skipping admin groups load.");
return;
}
var groupsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminGroupData>>(File.ReadAllText(adminGroupsPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (groupsFromFile == null) { throw new FileNotFoundException(); }
foreach (var (key, groupDef) in groupsFromFile)
{
if (Groups.ContainsKey(key))
{
Groups[key].Flags.UnionWith(groupDef.Flags);
if (groupDef.Immunity > Groups[key].Immunity)
{
Groups[key].Immunity = groupDef.Immunity;
}
}
else
{
Groups.Add(key, groupDef);
}
}
Console.WriteLine($"Loaded {Groups.Count} admin groups.");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin groups: {ex}");
}
// Loop over each of the admins. If one of our admins is in a group,
// add the flags from the group to their admin definition and change
// the admin's immunity if it's higher.
foreach (var adminData in Admins.Values)
{
var groups = adminData.Groups;
foreach (var group in groups)
{
// roflmuffin is probably smart enough to condense this function down ;)
if (Groups.TryGetValue(group, out var groupData))
{
adminData.Flags.UnionWith(groupData.Flags);
if (groupData.Immunity > adminData.Immunity)
{
adminData.Immunity = groupData.Immunity;
}
}
}
}
}
/// <summary>
/// Checks to see if the player is part of an admin group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to check for.</param>
/// <returns>True if a player is part of all of the groups provided, false if not.</returns>
public static bool PlayerInGroup(CCSPlayerController? player, params string[] groups)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
/// <summary>
/// Checks to see if the player is part of an admin group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="groups">Groups to check for.</param>
/// <returns>True if a player is part of all of the groups provided, false if not.</returns>
public static bool PlayerInGroup(SteamID steamId, params string[] groups)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Groups.IsSupersetOf(groups) ?? false;
}
/// <summary>
/// Adds a player to a group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(CCSPlayerController? player, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
AddPlayerToGroup((SteamID)player.SteamID, groups);
}
/// <summary>
/// Adds a player to a group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="groups">Groups to add the player to.</param>
public static void AddPlayerToGroup(SteamID steamId, params string[] groups)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(),
Groups = new(groups)
};
}
foreach (var group in groups)
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.UnionWith(groupDef.Flags);
groupDef.CommandOverrides.ToList().ForEach(x => data.CommandOverrides[x.Key] = x.Value);
}
}
Admins[steamId] = data;
}
/// <summary>
/// Removes a player from a group.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="removeInheritedFlags">If true, all of the flags that the player inherited from being in the group will be removed.</param>
/// <param name="groups"></param>
public static void RemovePlayerFromGroup(CCSPlayerController? player, bool removeInheritedFlags = true, params string[] groups)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) { return; }
RemovePlayerFromGroup((SteamID)player.SteamID, true, groups);
}
/// <summary>
/// Removes a player from a group.
/// </summary>
/// <param name="steamId">SteamID of the player.</param>
/// <param name="removeInheritedFlags">If true, all of the flags that the player inherited from being in the group will be removed.</param>
/// <param name="groups"></param>
public static void RemovePlayerFromGroup(SteamID steamId, bool removeInheritedFlags = true, params string[] groups)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Groups.ExceptWith(groups);
if (removeInheritedFlags)
{
foreach (var group in groups)
{
if (Groups.TryGetValue(group, out var groupDef))
{
data.Flags.ExceptWith(groupDef.Flags);
}
}
}
Admins[steamId] = data;
}
}
}

View File

@@ -1,44 +1,45 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json.Serialization;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using System.IO;
using System.Linq;
using System.Reflection;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("identity")] public required string Identity { get; init; }
[JsonPropertyName("flags")] public required HashSet<string> Flags { get; init; }
}
public static class AdminManager
public static partial class AdminManager
{
private static readonly Dictionary<SteamID, AdminData> Admins = new();
static AdminManager()
{
CommandUtils.AddStandaloneCommand("css_admins_reload", "Reloads the admin file.", ReloadAdminsCommand);
CommandUtils.AddStandaloneCommand("css_admins_list", "List admins and their flags.", ListAdminsCommand);
CommandUtils.AddStandaloneCommand("css_groups_reload", "Reloads the admin groups file.", ReloadAdminGroupsCommand);
CommandUtils.AddStandaloneCommand("css_groups_list", "List admin groups and their flags.", ListAdminGroupsCommand);
CommandUtils.AddStandaloneCommand("css_overrides_reload", "Reloads the admin command overrides file.", ReloadAdminOverridesCommand);
CommandUtils.AddStandaloneCommand("css_overrides_list", "List admin command overrides and their flags.", ListAdminOverridesCommand);
}
[RequiresPermissions("can_reload_admins")]
public static void MergeGroupPermsIntoAdmins()
{
foreach (var (steamID, adminDef) in Admins)
{
AddPlayerToGroup(steamID, adminDef.Groups.ToArray());
}
}
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
Admins.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
Load(Path.Combine(rootDir.FullName, "configs", "admins.json"));
LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
}
[RequiresPermissions("can_reload_admins")]
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
@@ -48,166 +49,42 @@ namespace CounterStrikeSharp.API.Modules.Admin
}
}
public static void Load(string adminDataPath)
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
try
{
if (!File.Exists(adminDataPath))
{
Console.WriteLine("Admin data file not found. Skipping admin data load.");
return;
}
var adminsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminData>>(File.ReadAllText(adminDataPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (adminsFromFile == null) { throw new FileNotFoundException(); }
foreach (var adminDef in adminsFromFile.Values)
{
if (SteamID.TryParse(adminDef.Identity, out var steamId))
{
if (Admins.ContainsKey(steamId!))
{
Admins[steamId!].Flags.UnionWith(adminDef.Flags);
}
else
{
Admins.Add(steamId!, adminDef);
}
}
}
Groups.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
}
Console.WriteLine($"Loaded admin data with {Admins.Count} admins.");
}
catch (Exception ex)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
foreach (var (groupName, groupDef) in Groups)
{
Console.WriteLine($"Failed to load admin data: {ex}");
command.ReplyToCommand($"{groupName} - {string.Join(", ", groupDef.Flags)}");
}
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID steamId)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
return Admins.GetValueOrDefault(steamId);
CommandOverrides.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadCommandOverrides(Path.Combine(rootDir.FullName, "configs", "admin_overrides.json"));
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(CCSPlayerController? player, params string[] flags)
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(SteamID steamId, params string[] flags)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
foreach (var (commandName, commandDef) in CommandOverrides)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(flags)
};
Admins[steamId] = data;
return;
command.ReplyToCommand($"{commandName} (enabled: {commandDef.Enabled.ToString()}) - {string.Join(", ", commandDef.Flags)}");
}
foreach (var flag in flags)
{
data.Flags.Add(flag);
}
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.ExceptWith(flags);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="player">Player controller to remove admin data from.</param>
public static void RemovePlayerAdminData(CCSPlayerController? player)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerAdminData((SteamID)player.SteamID);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID steamId)
{
Admins.Remove(steamId);
}
}
}

View File

@@ -0,0 +1,396 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Utils;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Commands;
using System.Reflection;
using System.Numerics;
using System.Linq;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Admin
{
public partial class AdminData
{
[JsonPropertyName("identity")] public required string Identity { get; init; }
[JsonPropertyName("flags")] public HashSet<string> Flags { get; init; } = new();
[JsonPropertyName("immunity")] public uint Immunity { get; set; } = 0;
[JsonPropertyName("command_overrides")] public Dictionary<string, bool> CommandOverrides { get; init; } = new();
}
public static partial class AdminManager
{
private static Dictionary<SteamID, AdminData> Admins = new();
// TODO: ServiceCollection
private static ILogger _logger = CoreLogging.Factory.CreateLogger("AdminManager");
public static void LoadAdminData(string adminDataPath)
{
try
{
if (!File.Exists(adminDataPath))
{
_logger.LogWarning("Admin data file not found. Skipping admin data load.");
return;
}
var adminsFromFile = JsonSerializer.Deserialize<Dictionary<string, AdminData>>(File.ReadAllText(adminDataPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (adminsFromFile == null) { throw new FileNotFoundException(); }
foreach (var adminDef in adminsFromFile.Values)
{
if (SteamID.TryParse(adminDef.Identity, out var steamId))
{
if (Admins.ContainsKey(steamId!))
{
Admins[steamId!].Flags.UnionWith(adminDef.Flags);
if (adminDef.Immunity > Admins[steamId!].Immunity)
{
Admins[steamId!].Immunity = adminDef.Immunity;
}
}
else
{
Admins.Add(steamId!, adminDef);
}
}
}
_logger.LogInformation("Loaded admin data with {Count} admins.", Admins.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load admin data");
}
}
/// <summary>
/// Grabs the admin data for a player that was loaded from "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID object of the player.</param>
/// <returns>AdminData class if data found, null if not.</returns>
public static AdminData? GetPlayerAdminData(SteamID steamId)
{
return Admins.GetValueOrDefault(steamId);
}
/// <summary>
/// Removes a players admin data. This is not saved to "configs/admins.json"
/// </summary>
/// <param name="steamId">Steam ID remove admin data from.</param>
public static void RemovePlayerAdminData(SteamID steamId)
{
Admins.Remove(steamId);
}
#region Command Permission Checks
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(CCSPlayerController? player, params string[] flags)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
/// <summary>
/// Checks to see if a player has access to a certain set of permission flags.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="flags">Flags to look for in the players permission flags.</param>
/// <returns>True if flags are present, false if not.</returns>
public static bool PlayerHasPermissions(SteamID steamId, params string[] flags)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.Flags.IsSupersetOf(flags) ?? false;
}
#endregion
// This is placed here instead of in AdminCommandOverrides.cs as this all relates to admins that are
// defined within the "configs/admins.json" file.
#region Admin Specific Command Overrides
/// <summary>
/// Checks to see if a player has a command override. This does NOT return the actual
/// state of the override.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override exists, false if not.</returns>
public static bool PlayerHasCommandOverride(CCSPlayerController? player, string command)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
/// <summary>
/// Checks to see if a player has a command override. This does NOT return the actual
/// state of the override.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override exists, false if not.</returns>
public static bool PlayerHasCommandOverride(SteamID steamId, string command)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.ContainsKey(command) ?? false;
}
/// <summary>
/// Gets the value of a command override state.
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override is active, false if not.</returns>
public static bool GetPlayerCommandOverrideState(CCSPlayerController? player, string command)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
/// <summary>
/// Gets the value of a command override state.
/// </summary>
/// <param name="steamId">Steam ID object.</param>
/// <param name="command">Name of the command to check for.</param>
/// <returns>True if override is active, false if not.</returns>
public static bool GetPlayerCommandOverrideState(SteamID steamId, string command)
{
var playerData = GetPlayerAdminData(steamId);
return playerData?.CommandOverrides.GetValueOrDefault(command) ?? false;
}
/// <summary>
/// Sets a player command override. This is not saved to "configs/admins.json".
/// </summary>
/// <param name="player">Player or server console.</param>
/// <param name="command">Name of the command to check for.</param>
/// <param name="state">New state of the command override.</param>
public static void SetPlayerCommandOverride(CCSPlayerController? player, string command, bool state)
{
// This is here for cases where the server console is attempting to call commands.
// 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);
}
/// <summary>
/// Sets a player command override. This is not saved to "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="command">Name of the command to check for.</param>
/// <param name="state">New state of the command override.</param>
public static void SetPlayerCommandOverride(SteamID steamId, string command, bool state)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(),
Groups = new(),
CommandOverrides = new() { { command, state } }
};
Admins[steamId] = data;
return;
}
data.CommandOverrides[command] = state;
Admins[steamId] = data;
}
#endregion
#region Manipulating Permissions
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
AddPlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily adds a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">SteamID to add a flag to.</param>
/// <param name="flags">Flags to add for the player.</param>
public static void AddPlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null)
{
data = new AdminData()
{
Identity = steamId.SteamId64.ToString(),
Flags = new(flags),
Groups = new()
};
Admins[steamId] = data;
return;
}
foreach (var flag in flags)
{
data.Flags.Add(flag);
}
Admins[steamId] = data;
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(CCSPlayerController? player, params string[] flags)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
RemovePlayerPermissions((SteamID)player.SteamID, flags);
}
/// <summary>
/// Temporarily removes a permission flag to the player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
/// <param name="flags">Flags to remove from the player.</param>
public static void RemovePlayerPermissions(SteamID steamId, params string[] flags)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.ExceptWith(flags);
Admins[steamId] = data;
}
/// <summary>
/// Temporarily removes all permission flags from a player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="player">Player controller to remove flags from.</param>
public static void ClearPlayerPermissions(CCSPlayerController? player)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
ClearPlayerPermissions((SteamID)player.SteamID);
}
/// <summary>
/// Temporarily removes all permission flags from a player. These flags are not saved to
/// "configs/admins.json".
/// </summary>
/// <param name="steamId">Steam ID to remove flags from.</param>
public static void ClearPlayerPermissions(SteamID steamId)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Flags.Clear();
Admins[steamId] = data;
}
#endregion
#region Player Immunity
/// <summary>
/// Sets the immunity value for a player.
/// </summary>
/// <param name="player">Player controller.</param>
/// <param name="value">New immunity value.</param>
public static void SetPlayerImmunity(CCSPlayerController? player, uint value)
{
if (player == null) return;
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected || player.IsBot) return;
SetPlayerImmunity((SteamID)player.SteamID, value);
}
/// <summary>
/// Sets the immunity value for a player.
/// </summary>
/// <param name="steamId">Steam ID of the player.</param>
/// <param name="value">New immunity value.</param>
public static void SetPlayerImmunity(SteamID steamId, uint value)
{
var data = GetPlayerAdminData(steamId);
if (data == null) return;
data.Immunity = value;
Admins[steamId] = data;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(CCSPlayerController? caller, CCSPlayerController? target)
{
// The server console should be able to target everyone.
if (caller == null) return true;
if (target == null) return false;
if (!target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected) return false;
var callerData = GetPlayerAdminData((SteamID)caller.SteamID);
if (callerData == null) return false;
var targetData = GetPlayerAdminData((SteamID)target.SteamID);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;
}
/// <summary>
/// Checks to see if a player can target another player based on their immunity value.
/// </summary>
/// <param name="caller">Caller of the command.</param>
/// <param name="target">Target of the command.</param>
/// <returns></returns>
public static bool CanPlayerTarget(SteamID caller, SteamID target)
{
var callerData = GetPlayerAdminData(caller);
if (callerData == null) return false;
var targetData = GetPlayerAdminData(caller);
if (targetData == null) return true;
return callerData.Immunity >= targetData.Immunity;
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
public class BaseRequiresPermissions : Attribute
{
/// <summary>
/// The permissions for the command.
/// </summary>
public string[] Permissions { get; }
/// <summary>
/// The name of the command that is attached to this attribute.
/// </summary>
public string Command { get; set; }
/// <summary>
/// Whether this attribute should be used for permission checks.
/// </summary>
public bool Enabled { get; set; }
public BaseRequiresPermissions(params string[] permissions)
{
Permissions = permissions;
Command = "";
}
public virtual bool CanExecuteCommand(CCSPlayerController? caller)
{
// 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 (adminData == null) return false;
if (adminData.CommandOverrides.ContainsKey(Command))
{
return adminData.CommandOverrides[Command];
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CounterStrikeSharp.API.Modules.Admin
{
public class PermissionCharacters
{
// Example: "#css/admin"
public const char GroupPermissionChar = '#';
// Example: "@css/manipulate_players"
public const char UserPermissionChar = '@';
}
}

View File

@@ -1,15 +1,33 @@
using System;
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
[AttributeUsage(AttributeTargets.Method)]
public class RequiresPermissions : Attribute
{
public string[] RequiredPermissions { get; }
public RequiresPermissions(params string[] permissions)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequiresPermissions : BaseRequiresPermissions
{
public RequiresPermissions(params string[] permissions) : base(permissions) { }
public override bool CanExecuteCommand(CCSPlayerController? caller)
{
RequiredPermissions = permissions;
if (caller == null) return true;
if (AdminManager.PlayerHasCommandOverride(caller, Command))
{
return AdminManager.GetPlayerCommandOverrideState(caller, Command);
}
if (!base.CanExecuteCommand(caller)) return false;
var groupPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.GroupPermissionChar));
var userPermissions = Permissions.Where(perm => perm.StartsWith(PermissionCharacters.UserPermissionChar));
if (!AdminManager.PlayerInGroup(caller, groupPermissions.ToArray())) return false;
if (!AdminManager.PlayerHasPermissions(caller, userPermissions.ToArray())) return false;
return true;
}
}
}

View File

@@ -0,0 +1,31 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class RequiresPermissionsOr : BaseRequiresPermissions
{
public RequiresPermissionsOr(params string[] permissions) : base(permissions) { }
public override bool CanExecuteCommand(CCSPlayerController? caller)
{
if (caller == null) return true;
if (AdminManager.PlayerHasCommandOverride(caller, Command))
{
return AdminManager.GetPlayerCommandOverrideState(caller, Command);
}
if (!base.CanExecuteCommand(caller)) return false;
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);
if (adminData == null) return false;
return (groupPermissions.Intersect(adminData.Groups).Count() + userPermissions.Intersect(adminData.Flags).Count()) > 0;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,6 +21,8 @@ using System.Text;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Logging;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Config
{
@@ -29,6 +31,7 @@ namespace CounterStrikeSharp.API.Modules.Config
private static readonly DirectoryInfo? _rootDir;
private static readonly string _pluginConfigsFolderPath;
private static ILogger _logger = CoreLogging.Factory.CreateLogger("ConfigManager");
static ConfigManager()
{
@@ -40,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
{
@@ -53,14 +57,27 @@ 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;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to generate configuration file for {pluginName}: {ex}");
_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);
}
}
@@ -70,7 +87,7 @@ namespace CounterStrikeSharp.API.Modules.Config
}
catch (Exception ex)
{
Console.WriteLine($"Failed to parse configuration '{pluginName}': {ex}");
_logger.LogError(ex, "Failed to parse configuration file for {PluginName}", pluginName);
}
return config;

View File

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

View File

@@ -0,0 +1,44 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
namespace CounterStrikeSharp.API.Modules.Entities.Constants
{
public enum RoundEndReason : uint
{
Unknown = 0x0u,
TargetBombed = 0x1u,
TerroristsEscaped = 0x4u,
CTsPreventEscape = 0x5u,
EscapingTerroristsNeutralized = 0x6u,
BombDefused = 0x7u,
CTsWin = 0x8u,
TerroristsWin = 0x9u,
RoundDraw = 0xAu,
AllHostageRescued = 0xBu,
TargetSaved = 0xCu,
HostagesNotRescued = 0xDu,
TerroristsNotEscaped = 0xEu,
GameCommencing = 0x10u,
TerroristsSurrender = 0x11u, // this also triggers match cancelled
CTsSurrender = 0x12u, // this also triggers match cancelled
TerroristsPlanned = 0x13u,
CTsReachedHostage = 0x14u,
SurvivalWin = 0x15u,
SurvivalDraw = 0x16u
}
}

View File

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

View File

@@ -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);
@@ -123,6 +123,7 @@ namespace CounterStrikeSharp.API.Modules.Events
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
// public void FireEventToClient(int clientId, bool dontBroadcast) => NativeAPI.FireEventToClient(Handle, clientId);
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.Index);
}
}

View File

@@ -16,6 +16,8 @@
using System;
using System.Collections.Generic;
using CounterStrikeSharp.API.Core;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Modules.Memory
{
@@ -71,9 +73,26 @@ namespace CounterStrikeSharp.API.Modules.Memory
return types[Enum.GetUnderlyingType(type)];
}
Console.WriteLine("Error retrieving data type for 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);
}
}
}

View File

@@ -0,0 +1,55 @@
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;
}
public BaseMemoryFunction(string signature, DataType returnType, DataType[] parameters) : base(
CreateValveFunctionBySignature(signature, 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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,185 @@
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 void Invoke()
{
InvokeInternalVoid();
}
}
public class MemoryFunctionVoid<T1> : BaseMemoryFunction
{
public MemoryFunctionVoid(string signature) : base(signature, 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 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 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 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 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 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 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 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 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 void Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
{
InvokeInternalVoid(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}

View File

@@ -0,0 +1,179 @@
using System;
namespace CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
public class MemoryFunctionWithReturn<TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
Array.Empty<DataType>())
{
}
public TResult Invoke()
{
return InvokeInternal<TResult>();
}
}
public class MemoryFunctionWithReturn<T1, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1)
{
return InvokeInternal<TResult>(arg1);
}
}
public class MemoryFunctionWithReturn<T1, T2, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1, T2 arg2)
{
return InvokeInternal<TResult>(arg1, arg2);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[] { typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType() })
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3)
{
return InvokeInternal<TResult>(arg1, arg2, arg3);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, T9, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(), typeof(T9).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}
}
public class MemoryFunctionWithReturn<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TResult> : BaseMemoryFunction
{
public MemoryFunctionWithReturn(string signature) : base(signature, typeof(TResult).ToValidDataType(),
new[]
{
typeof(T1).ToValidDataType(), typeof(T2).ToValidDataType(), typeof(T3).ToValidDataType(),
typeof(T4).ToValidDataType(), typeof(T5).ToValidDataType(), typeof(T6).ToValidDataType(),
typeof(T7).ToValidDataType(), typeof(T8).ToValidDataType(), typeof(T9).ToValidDataType(),
typeof(T10).ToValidDataType()
})
{
}
public TResult Invoke(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
{
return InvokeInternal<TResult>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Memory;
@@ -92,9 +93,9 @@ public class Schema
return ref Unsafe.AsRef<T>((void*)(pointer + GetSchemaOffset(className, memberName)));
}
public static unsafe T GetPointer<T>(IntPtr pointer)
public static T GetPointer<T>(IntPtr pointer)
{
var pointerTo = Unsafe.Read<IntPtr>((void*)pointer);
var pointerTo = Marshal.ReadIntPtr(pointer);
if (pointerTo == IntPtr.Zero)
{
return default;
@@ -103,9 +104,9 @@ public class Schema
return (T)Activator.CreateInstance(typeof(T), pointerTo);
}
public static unsafe T GetPointer<T>(IntPtr pointer, string className, string memberName)
public static T GetPointer<T>(IntPtr pointer, string className, string memberName)
{
var pointerTo = Unsafe.Read<IntPtr>((void*)(pointer + GetSchemaOffset(className, memberName)));
var pointerTo = Marshal.ReadIntPtr(pointer + GetSchemaOffset(className, memberName));
if (pointerTo == IntPtr.Zero)
{
return default;

View File

@@ -31,9 +31,15 @@ public partial class VirtualFunction
{
if (!_createdFunctions.TryGetValue(signature, out var function))
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, Addresses.ServerPath, signature,
argumentTypes.Count(), (int)returnType, arguments);
_createdFunctions[signature] = function;
try
{
function = NativeAPI.CreateVirtualFunctionBySignature(IntPtr.Zero, Addresses.ServerPath, signature,
argumentTypes.Count(), (int)returnType, arguments);
_createdFunctions[signature] = function;
}
catch (Exception)
{
}
}
return function;

View File

@@ -1,30 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Modules.Memory;
public static class VirtualFunctions
{
public static Action<IntPtr, HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr> ClientPrint =
VirtualFunction.CreateVoid<IntPtr, HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr>(
public static MemoryFunctionVoid<IntPtr, HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr> ClientPrintFunc =
new(
GameData.GetSignature("ClientPrint"));
public static Action<IntPtr, HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr> ClientPrint =
ClientPrintFunc.Invoke;
public static MemoryFunctionVoid<HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr> ClientPrintAllFunc =
new(GameData.GetSignature("UTIL_ClientPrintAll"));
public static Action<HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr> ClientPrintAll =
VirtualFunction.CreateVoid<HudDestination, string, IntPtr, IntPtr, IntPtr, IntPtr>(
GameData.GetSignature("UTIL_ClientPrintAll"));
ClientPrintAllFunc.Invoke;
// void (*FnGiveNamedItem)(void* itemService,const char* pchName, void* iSubType,void* pScriptItem, void* a5,void* a6) = nullptr;
public static Func<IntPtr, string, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr> GiveNamedItem =
VirtualFunction.Create<IntPtr, string, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr>(
GameData.GetSignature("GiveNamedItem"));
public static MemoryFunctionWithReturn<IntPtr, string, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr> GiveNamedItemFunc =
new(GameData.GetSignature("GiveNamedItem"));
public static Action<IntPtr, byte> SwitchTeam =
VirtualFunction.CreateVoid<IntPtr, byte>(GameData.GetSignature("CCSPlayerController_SwitchTeam"));
public static Func<IntPtr, string, IntPtr, IntPtr, IntPtr, IntPtr, IntPtr> GiveNamedItem = GiveNamedItemFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, byte> SwitchTeamFunc =
new(GameData.GetSignature("CCSPlayerController_SwitchTeam"));
public static Action<IntPtr, byte> SwitchTeam = SwitchTeamFunc.Invoke;
// void(*UTIL_Remove)(CEntityInstance*);
public static Action<IntPtr> UTIL_Remove = VirtualFunction.CreateVoid<IntPtr>(GameData.GetSignature("UTIL_Remove"));
public static MemoryFunctionVoid<IntPtr> UTIL_RemoveFunc =
new(GameData.GetSignature("UTIL_Remove"));
public static Action<IntPtr> UTIL_Remove = UTIL_RemoveFunc.Invoke;
// void(*CBaseModelEntity_SetModel)(CBaseModelEntity*, const char*);
public static Action<IntPtr, string> SetModel = VirtualFunction.CreateVoid<IntPtr, string>(GameData.GetSignature("CBaseModelEntity_SetModel"));
public static MemoryFunctionVoid<IntPtr, string> SetModelFunc =
new(GameData.GetSignature("CBaseModelEntity_SetModel"));
public static Action<IntPtr, string> SetModel = SetModelFunc.Invoke;
public static MemoryFunctionVoid<nint, RoundEndReason, float> TerminateRoundFunc =
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
public static Action<IntPtr, RoundEndReason, float> TerminateRound = TerminateRoundFunc.Invoke;
public static MemoryFunctionWithReturn<string, int, IntPtr> UTIL_CreateEntityByNameFunc =
new(GameData.GetSignature("UTIL_CreateEntityByName"));
public static Func<string, int, IntPtr> UTIL_CreateEntityByName = UTIL_CreateEntityByNameFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, IntPtr> CBaseEntity_DispatchSpawnFunc =
new(GameData.GetSignature("CBaseEntity_DispatchSpawn"));
public static Action<IntPtr, IntPtr> CBaseEntity_DispatchSpawn = CBaseEntity_DispatchSpawnFunc.Invoke;
public static MemoryFunctionVoid<IntPtr> CCSPlayerPawn_RespawnFunc = new(GameData.GetSignature("CCSPlayerPawn_Respawn"));
public static Action<IntPtr> CCSPlayerPawn_Respawn = CCSPlayerPawn_RespawnFunc.Invoke;
public static MemoryFunctionVoid<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOldFunc = new (GameData.GetSignature("CBaseEntity_TakeDamageOld"));
public static Action<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOld = CBaseEntity_TakeDamageOldFunc.Invoke;
public static MemoryFunctionVoid<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThinkFunc = new (GameData.GetSignature("CCSPlayerPawnBase_PostThink"));
public static Action<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThink = CCSPlayerPawnBase_PostThinkFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouchFunc = new (GameData.GetSignature("CBaseTrigger_StartTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouch = CBaseTrigger_StartTouchFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouchFunc = new (GameData.GetSignature("CBaseTrigger_EndTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouch = CBaseTrigger_EndTouchFunc.Invoke;
}

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,42 @@ public class CommandUtils
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
@@ -41,14 +77,6 @@ public class CommandUtils
}
}
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttribute<RequiresPermissions>()?.RequiredPermissions;
if (permissions != null && !AdminManager.PlayerHasPermissions(caller, permissions))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
handler?.Invoke(caller, command);
});

View File

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

View File

@@ -14,8 +14,6 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using System;
namespace CounterStrikeSharp.API
{
[Flags]

View File

@@ -14,23 +14,25 @@
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API
{
public static class Utilities
{
// https://github.com/dotabuff/manta/blob/master/entity.go#L186-L190
public const int MaxEdictBits = 14;
public const int MaxEdictBits = 15;
public const int MaxEdicts = 1 << MaxEdictBits;
public const int NumEHandleSerialNumberBits = 17;
public const uint InvalidEHandleIndex = 0xFFFFFFFF;
public static IEnumerable<T> FlagsToList<T>(this T flags) where T : Enum
{
@@ -43,6 +45,11 @@ namespace CounterStrikeSharp.API
return (T)Activator.CreateInstance(typeof(T), NativeAPI.GetEntityFromIndex(index))!;
}
public static T? CreateEntityByName<T>(string name) where T : CBaseEntity
{
return (T?)Activator.CreateInstance(typeof(T), VirtualFunctions.UTIL_CreateEntityByName(name, -1));
}
public static CCSPlayerController GetPlayerFromIndex(int index)
{
return Utilities.GetEntityFromIndex<CCSPlayerController>(index);
@@ -58,13 +65,37 @@ namespace CounterStrikeSharp.API
return Utilities.GetEntityFromIndex<CCSPlayerController>((userid & 0xFF) + 1);
}
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
{
return Utilities.GetPlayers().FirstOrDefault(player => player.SteamID == steamId);
}
public static TargetResult ProcessTargetString(string pattern, CCSPlayerController player)
{
return new Target(pattern).GetTarget(player);
}
public static IEnumerable<T> FindAllEntitiesByDesignerName<T>(string designerName) where T : CEntityInstance
{
var pEntity = new CEntityIdentity(NativeAPI.GetFirstActiveEntity());
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.EntityHandle.IsValid; pEntity = pEntity.Next)
{
var value = pEntity.EntityInstance.EntityHandle.Value;
if (value == null) continue;
yield return value.As<T>();
}
}
public static IEnumerable<CEntityInstance> GetAllEntities()
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
for (; pEntity != null && pEntity.Handle != IntPtr.Zero; pEntity = pEntity.Next)
{
if (!pEntity.DesignerName.Contains(designerName)) continue;
yield return new PointerTo<T>(pEntity.Handle).Value;
var value = pEntity.EntityInstance.EntityHandle.Value;
if (value == null) continue;
yield return value;
}
}

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