Compare commits

...

17 Commits

Author SHA1 Message Date
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
89 changed files with 3204 additions and 964 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
@@ -81,6 +83,7 @@ SET(SOURCE_FILES
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
libraries/nlohmann/json.hpp
src/scripting/natives/natives_dynamichooks.cpp
)
@@ -96,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

@@ -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",
@@ -112,6 +132,27 @@
"linux": 147
}
},
"CBaseEntity_TakeDamageOld": {
"signatures": {
"library": "server",
"windows": "\\x40\\x56\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x41\\x10",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x41\\x54\\x49\\x89\\xFC\\x53\\x48\\x83\\xEC\\x38\\x4C\\x8D\\x2D\\x2A\\x2A\\x2A\\x2A\\x49\\x8B\\x7D\\x00\\x48\\x85\\xFF\\x0F\\x84\\x2A\\x2A\\x2A\\x2A"
}
},
"CBaseTrigger_StartTouch": {
"signatures": {
"library": "server",
"windows": "\\x41\\x56\\x41\\x57\\x48\\x83\\xEC\\x58\\x48\\x8B\\x01",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\xBB"
}
},
"CBaseTrigger_EndTouch": {
"signatures": {
"library": "server",
"windows": "\\x40\\x53\\x57\\x41\\x55\\x48\\x83\\xEC\\x40",
"linux": "\\x55\\xBA\\xFF\\xFF\\xFF\\xFF\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49"
}
},
"GameEntitySystem": {
"offsets": {
"windows": 88,

View File

@@ -16,6 +16,10 @@ Adding an Admin is as simple as creating a new entry in the `configs/admins.json
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"]
},
"another ZoNiCaL": {
"identity": "STEAM_0:1:1",
"flags": ["@css/generic"]
}
}
```
@@ -53,4 +57,5 @@ However there is a somewhat standardized list of flags that it is advised you us
:::note
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
:::
:::

View File

@@ -18,8 +18,25 @@ Groups can be created to group a series of permissions together under one tag. T
}
```
You can add admins to groups using the `groups` array in `configs/admins.json`
```json
{
"erikj": {
"identity": "76561198808392634",
"flags": ["@mycustomplugin/admin"],
"groups": ["#css/simple-admin"]
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": ["@mycustomplugin/admin"],
"groups": ["#css/simple-admin"]
}
}
```
:::note
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp will recognize the group.
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp won't recognize the group.
:::
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.

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

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");
}
}
```

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

@@ -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();
@@ -803,6 +853,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)
{
@@ -314,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,11 +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
{
@@ -32,17 +35,20 @@ namespace CounterStrikeSharp.API.Core
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("PublicChatTrigger")]
public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("SilentChatTrigger")] public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("SilentChatTrigger")]
public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("FollowCS2ServerGuidelines")] public bool FollowCS2ServerGuidelines { get; set; } = true;
[JsonPropertyName("FollowCS2ServerGuidelines")]
public bool FollowCS2ServerGuidelines { get; set; } = true;
}
/// <summary>
/// Configuration related to the Core API.
/// </summary>
public static partial class CoreConfig
public partial class CoreConfig
{
/// <summary>
/// List of characters to use for public chat triggers.
@@ -76,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,338 +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 Admin Groups from \"configs/admin_groups.json\"");
AdminManager.LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
Console.WriteLine("Loading Admins from \"configs/admins.json\"");
AdminManager.LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
Console.WriteLine("Loading Admin Command Overrides from \"configs/admin_overrides.json\"");
AdminManager.LoadCommandOverrides(Path.Combine(rootDir.FullName, "configs", "admin_overrides.json"));
AdminManager.MergeGroupPermsIntoAdmins();
for (var i = 1; i <= 9; i++)
{
CommandUtils.AddStandaloneCommand("css_" + i, "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
});
}
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("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
var currentVersion = Api.GetVersion();
info.ReplyToCommand(" CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
" Counter-Strike Sharp uses code borrowed from SourceMod, Source.Python, FiveM, Saul Rennison and CS2Fixes.\n" +
" See ACKNOWLEDGEMENTS.md for more information.\n" +
" Current API Version: " + currentVersion, true);
return;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 1,
usage: "[option]\n" +
" list - List all plugins currently loaded.\n" +
" start / load - Loads a plugin not currently loaded.\n" +
" stop / unload - Unloads a plugin currently loaded.\n" +
" restart / reload - Reloads a plugin currently loaded.",
whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSPluginCommand(CCSPlayerController? caller, CommandInfo info)
{
switch (info.GetArg(1))
{
case "list":
{
info.ReplyToCommand($" List of all plugins currently loaded by CounterStrikeSharp: {_loadedPlugins.Count} plugins loaded.", true);
foreach (var plugin in _loadedPlugins)
{
var sb = new StringBuilder();
sb.AppendFormat(" [#{0}]: \"{1}\" ({2})", plugin.PluginId, plugin.Name, plugin.Version);
if (!string.IsNullOrEmpty(plugin.Author)) sb.AppendFormat(" by {0}", plugin.Author);
if (!string.IsNullOrEmpty(plugin.Description))
{
sb.Append("\n");
sb.Append(" ");
sb.Append(plugin.Description);
}
info.ReplyToCommand(sb.ToString(), true);
}
break;
}
case "start":
case "load":
{
if (info.ArgCount < 2)
{
info.ReplyToCommand("Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n", true);
break;
}
// If our arugment doesn't end in ".dll" - try and construct a path similar to PluginName/PluginName.dll.
// We'll assume we have a full path if we have ".dll".
var path = info.GetArg(2);
if (!path.EndsWith(".dll"))
{
path = Path.Combine(rootDir.FullName, $"plugins/{path}/{path}.dll");
}
else
{
path = Path.Combine(rootDir.FullName, path);
}
try
{
LoadPlugin(path);
}
catch (Exception e)
{
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

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

@@ -1,4 +1,5 @@
using System;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
@@ -24,6 +25,17 @@ public partial class CCSPlayerController
return VirtualFunctions.GiveNamedItem(PlayerPawn.Value.ItemServices.Handle, item, 0, 0, 0, 0);
}
public IntPtr GiveNamedItem(CsItem item)
{
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
if (string.IsNullOrWhiteSpace(itemString))
{
return IntPtr.Zero;
}
return this.GiveNamedItem(itemString);
}
public void PrintToConsole(string message)
{
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, $"{message}\n\0");
@@ -79,6 +91,31 @@ public partial class CCSPlayerController
itemServices.RemoveWeapons();
}
/// <summary>
/// Force player suicide
/// </summary>
/// <param name="explode"></param>
/// <param name="force"></param>
public void CommitSuicide(bool explode, bool force)
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
PlayerPawn.Value.CommitSuicide(explode, force);
}
/// <summary>
/// Respawn player
/// </summary>
public void Respawn()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
VirtualFunctions.CCSPlayerPawn_Respawn(PlayerPawn.Value.Handle);
VirtualFunction.CreateVoid<IntPtr>(Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Handle);
}
public bool IsBot => ((PlayerFlags)Flags).HasFlag(PlayerFlags.FL_FAKECLIENT);
/// <summary>

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

@@ -19806,6 +19806,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) {}

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

@@ -1,9 +1,11 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core;
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
{

View File

@@ -10,6 +10,8 @@ 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
{
@@ -24,6 +26,9 @@ namespace CounterStrikeSharp.API.Modules.Admin
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)
{
@@ -31,7 +36,7 @@ namespace CounterStrikeSharp.API.Modules.Admin
{
if (!File.Exists(adminDataPath))
{
Console.WriteLine("Admin data file not found. Skipping admin data load.");
_logger.LogWarning("Admin data file not found. Skipping admin data load.");
return;
}
@@ -56,11 +61,11 @@ namespace CounterStrikeSharp.API.Modules.Admin
}
}
Console.WriteLine($"Loaded admin data with {Admins.Count} admins.");
_logger.LogInformation("Loaded admin data with {Count} admins.", Admins.Count);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load admin data: {ex}");
_logger.LogError(ex, "Failed to load admin data");
}
}

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

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

@@ -1,37 +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, RoundEndReason, float> TerminateRound = VirtualFunction.CreateVoid<nint, RoundEndReason, float>(GameData.GetSignature("CCSGameRules_TerminateRound"));
public static Action<IntPtr, string> SetModel = SetModelFunc.Invoke;
public static Func<string, int, IntPtr> UTIL_CreateEntityByName = VirtualFunction.Create<string, int, IntPtr>(GameData.GetSignature("UTIL_CreateEntityByName"));
public static MemoryFunctionVoid<nint, RoundEndReason, float> TerminateRoundFunc =
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
public static Action<IntPtr, IntPtr> CBaseEntity_DispatchSpawn = VirtualFunction.CreateVoid<IntPtr, IntPtr>(GameData.GetSignature("CBaseEntity_DispatchSpawn"));
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

@@ -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,36 @@
using System;
using System.Linq;
using System.Runtime.Serialization;
namespace CounterStrikeSharp.API.Modules.Utils
{
public static class EnumUtils
{
public static string? GetEnumMemberAttributeValue<T>(T enumValue)
{
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;
}
return null;
}
}
}

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,15 +14,14 @@
* 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;
namespace CounterStrikeSharp.API
{
@@ -64,6 +63,16 @@ 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());

View File

@@ -157,6 +157,9 @@ internal static partial class Program
WriteEnum(builder, enumName, schemaEnum);
}
// Manually whitelist some classes
visited.Add("CTakeDamageInfo");
var visitedClassNames = new HashSet<string>();
foreach (var (className, schemaClass) in allClasses)
{

View File

@@ -26,6 +26,11 @@ public record SchemaFieldType
this.Category = SchemaTypeCategory.Builtin;
this.Name = "int32";
}
else if (this.Name == "AmmoIndex_t")
{
this.Category = SchemaTypeCategory.Builtin;
this.Name = "uint8";
}
else if (this.Name == "CBitVec< 64 >")
{
this.Category = SchemaTypeCategory.FixedArray;
@@ -68,6 +73,7 @@ public record SchemaFieldType
"uint32" => "UInt32",
"uint64" => "UInt64",
"bool" => "bool",
"char" => "char",
_ => throw new ArgumentOutOfRangeException(nameof(name), name, $"Unknown built-in: {name}")
};
@@ -85,6 +91,7 @@ public record SchemaFieldType
SchemaAtomicCategory.T => $"{name.Split('<')[0]}<{inner!.CsTypeName}>",
SchemaAtomicCategory.Collection => $"NetworkedVector<{inner!.CsTypeName}>",
SchemaAtomicCategory.Unknown => "CBitVec",
SchemaAtomicCategory.TT => "Unknown",
_ => throw new ArgumentOutOfRangeException(nameof(atomic), atomic, $"Unsupported atomic: {atomic}")
};

View File

@@ -19,7 +19,6 @@ using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.Json.Serialization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
@@ -27,20 +26,20 @@ using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
namespace TestPlugin
{
public class SampleConfig : BasePluginConfig
{
[JsonPropertyName("IsPluginEnabled")]
public bool IsPluginEnabled { get; set; } = true;
[JsonPropertyName("IsPluginEnabled")] public bool IsPluginEnabled { get; set; } = true;
[JsonPropertyName("LogPrefix")]
public string LogPrefix { get; set; } = "CSSharp";
[JsonPropertyName("LogPrefix")] public string LogPrefix { get; set; } = "CSSharp";
}
[MinimumApiVersion(33)]
@@ -62,19 +61,32 @@ namespace TestPlugin
Config = config;
}
private TestInjectedClass _testInjectedClass;
public SamplePlugin(TestInjectedClass testInjectedClass)
{
_testInjectedClass = testInjectedClass;
}
public override void Load(bool hotReload)
{
// Basic usage of the configuration system
if (!Config.IsPluginEnabled)
{
Console.WriteLine($"{Config.LogPrefix} {ModuleName} is disabled");
Logger.LogWarning($"{Config.LogPrefix} {ModuleName} is disabled");
return;
}
Console.WriteLine(
Logger.LogInformation(
$"Test Plugin has been loaded, and the hot reload flag was {hotReload}, path is {ModulePath}");
Console.WriteLine($"Max Players: {Server.MaxPlayers}");
Logger.LogWarning($"Max Players: {Server.MaxPlayers}");
VirtualFunctions.SwitchTeamFunc.Hook(hook =>
{
Logger.LogInformation("Switch team func called");
return HookResult.Continue;
}, HookMode.Pre);
SetupConvars();
SetupGameEvents();
@@ -84,7 +96,7 @@ namespace TestPlugin
// ValveInterface provides pointers to loaded modules via Interface Name exposed from the engine (e.g. Source2Server001)
var server = ValveInterface.Server;
Log($"Server pointer found @ {server.Pointer:X}");
Logger.LogInformation("Server pointer found @ {Pointer:X}", server.Pointer);
// You can use `ModuleDirectory` to get the directory of the plugin (for storing config files, saving database files etc.)
File.WriteAllText(Path.Join(ModuleDirectory, "example.txt"),
@@ -98,7 +110,60 @@ namespace TestPlugin
// This value is asserted against the native code that points to the same function.
var virtualFunc = VirtualFunction.Create<IntPtr>(server.Pointer, 91);
var result = virtualFunc() - 8;
Log($"Result of virtual func call is {result:X}");
Logger.LogInformation("Result of virtual func call is {Pointer:X}", result);
_testInjectedClass.Hello();
VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger {Trigger} touched by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger left {Trigger} by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.UTIL_RemoveFunc.Hook(hook =>
{
var entityInstance = hook.GetParam<CEntityInstance>(0);
Logger.LogInformation("Removed entity {EntityIndex}", entityInstance.EntityIndex.Value.Value);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook((h =>
{
var victim = h.GetParam<CEntityInstance>(0);
var damageInfo = h.GetParam<CTakeDamageInfo>(1);
if (damageInfo.Inflictor.Value.DesignerName == "inferno")
{
var inferno = new CInferno(damageInfo.Inflictor.Value.Handle);
Logger.LogInformation("Owner of inferno is {Owner}", inferno.OwnerEntity);
if (victim == inferno.OwnerEntity.Value)
{
damageInfo.Damage = 0;
}
else
{
damageInfo.Damage = 150;
}
}
return HookResult.Continue;
}), HookMode.Pre);
}
private void SetupConvars()
@@ -109,13 +174,13 @@ namespace TestPlugin
cheatsCvar.SetValue(true);
var numericCvar = ConVar.Find("mp_warmuptime");
Console.WriteLine($"mp_warmuptime = {numericCvar?.GetPrimitiveValue<float>()}");
Logger.LogInformation("mp_warmuptime = {Value}", numericCvar?.GetPrimitiveValue<float>());
var stringCvar = ConVar.Find("sv_skyname");
Console.WriteLine($"sv_skyname = {stringCvar?.StringValue}");
Logger.LogInformation("sv_skyname = {Value}", stringCvar?.StringValue);
var fogCvar = ConVar.Find("fog_color");
Console.WriteLine($"fog_color = {fogCvar?.GetNativeValue<Vector>()}");
Logger.LogInformation("fog_color = {Value}", fogCvar?.GetNativeValue<Vector>());
});
}
@@ -128,7 +193,7 @@ namespace TestPlugin
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(@event.Userid));
if (!entity.IsValid)
{
Log("invalid entity");
Logger.LogInformation("invalid entity");
return HookResult.Continue;
}
@@ -151,7 +216,8 @@ namespace TestPlugin
if (!@event.Userid.IsValid) return 0;
if (!@event.Userid.PlayerPawn.IsValid) return 0;
Log($"Player spawned with entity index: {@event.Userid.EntityIndex} & User ID: {@event.Userid.UserId}");
Logger.LogInformation("Player spawned with entity index: {EntityIndex} & User ID: {UserId}",
@event.Userid.EntityIndex, @event.Userid.UserId);
return HookResult.Continue;
});
@@ -166,7 +232,7 @@ namespace TestPlugin
// Set player to random colour
player.PlayerPawn.Value.Render = Color.FromArgb(Random.Shared.Next(0, 255),
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
Server.NextFrame(() =>
{
player.PrintToCenter(string.Join("\n", weapons.Select(x => x.Value.DesignerName)));
@@ -175,17 +241,14 @@ namespace TestPlugin
activeWeapon.ReserveAmmo[0] = 250;
activeWeapon.Clip1 = 250;
Log(
$"Pawn Position: {pawn.CBodyComponent?.SceneNode?.AbsOrigin} @{pawn.CBodyComponent?.SceneNode.Rotation}");
Logger.LogInformation("Pawn Position: {AbsOrigin}-{Rotation}", pawn.AbsOrigin, pawn.AbsRotation);
char randomColourChar = (char)new Random().Next(0, 16);
Server.PrintToChatAll($"Random String with Random Colour: {randomColourChar}{new Random().Next()}");
pawn.Health += 5;
Log(
$"Found steamID {new SteamID(player.SteamID)} for player {player.PlayerName}:{pawn.Health}|{pawn.InBuyZone}");
Log($"{@event.Userid}, {@event.X},{@event.Y},{@event.Z}");
Logger.LogInformation("Bullet Impact: {X},{Y},{Z}", @event.X, @event.Y, @event.Z);
return HookResult.Continue;
});
@@ -193,7 +256,7 @@ namespace TestPlugin
{
// Grab all cs_player_controller entities and set their cash value to $1337.
var playerEntities = Utilities.GetPlayers();
Log($"cs_player_controller count: {playerEntities.Count()}");
Logger.LogInformation($"cs_player_controller count: {playerEntities.Count()}");
foreach (var player in playerEntities)
{
@@ -204,7 +267,7 @@ namespace TestPlugin
// Grab everything starting with cs_, but we'll only mainpulate cs_gamerules.
// Note: this iterates through all entities, so is an expensive operation.
var csEntities = Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_");
Log($"Amount of cs_* entities: {csEntities.Count()}");
Logger.LogInformation("Amount of cs_* entities: {Count}", csEntities.Count());
foreach (var entity in csEntities)
{
@@ -220,15 +283,18 @@ namespace TestPlugin
private void SetupListeners()
{
// Hook global listeners defined by CounterStrikeSharp
RegisterListener<Listeners.OnMapStart>(mapName => { Log($"Map {mapName} has started!"); });
RegisterListener<Listeners.OnMapEnd>(() => { Log($"Map has ended."); });
RegisterListener<Listeners.OnMapStart>(mapName =>
{
Logger.LogInformation("Map {Map} has started!", mapName);
});
RegisterListener<Listeners.OnMapEnd>(() => { Logger.LogInformation($"Map has ended."); });
RegisterListener<Listeners.OnClientConnect>((index, name, ip) =>
{
Log($"Client {name} from {ip} has connected!");
Logger.LogInformation("Client {Name} from {Ip} has connected!", name, ip);
});
RegisterListener<Listeners.OnClientAuthorized>((index, id) =>
{
Log($"Client {index} with address {id}");
Logger.LogInformation("Client {Index} with address {Id}", index, id);
});
RegisterListener<Listeners.OnEntitySpawned>(entity =>
@@ -245,7 +311,8 @@ namespace TestPlugin
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
Log($"Smoke grenade spawned with color {projectile.SmokeColor}");
Logger.LogInformation("Smoke grenade spawned with color {SmokeColor}",
projectile.SmokeColor);
});
return;
case "flashbang_projectile":
@@ -277,6 +344,24 @@ namespace TestPlugin
giveItemMenu.AddMenuOption("weapon_ak47", handleGive);
giveItemMenu.AddMenuOption("weapon_p250", handleGive);
AddCommand("css_target", "Target Test", (player, info) =>
{
if (player == null) return;
var targetResult = info.GetArgTargetResult(1);
if (!targetResult.Any())
{
player.PrintToChat("No players found.");
return;
}
foreach (var result in targetResult.Players)
{
player.PrintToChat($"Target found: {result?.PlayerName}");
}
});
AddCommand("css_menu", "Opens example menu", (player, info) => { ChatMenus.OpenMenu(player, largeMenu); });
AddCommand("css_gunmenu", "Gun Menu", (player, info) => { ChatMenus.OpenMenu(player, giveItemMenu); });
@@ -298,8 +383,9 @@ namespace TestPlugin
(player, info) =>
{
if (player == null) return;
Log(
$"CounterStrikeSharp - a test command was called by {new SteamID(player.SteamID).SteamId2} with {info.ArgString}");
Logger.LogInformation(
"CounterStrikeSharp - a test command was called by {SteamID2} with {Arguments}",
((SteamID)player.SteamID).SteamId2, info.ArgString);
});
AddCommand("css_changeteam", "change team", (player, info) =>
@@ -319,7 +405,8 @@ namespace TestPlugin
// Listens for any client use of the command `jointeam`.
AddCommandListener("jointeam", (player, info) =>
{
Log($"{player.PlayerName} just did a jointeam (pre) [{info.ArgString}]");
Logger.LogInformation("{PlayerName} just did a jointeam (pre) [{ArgString}]", player.PlayerName,
info.ArgString);
return HookResult.Continue;
});
@@ -328,7 +415,7 @@ namespace TestPlugin
[GameEventHandler]
public HookResult OnPlayerConnect(EventPlayerConnect @event, GameEventInfo info)
{
Log($"Player {@event.Name} has connected! (post)");
Logger.LogInformation("Player {Name} has connected! (post)", @event.Name);
return HookResult.Continue;
}
@@ -336,7 +423,7 @@ namespace TestPlugin
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerConnectPre(EventPlayerConnect @event, GameEventInfo info)
{
Log($"Player {@event.Name} has connected! (pre)");
Logger.LogInformation("Player {Name} has connected! (pre)", @event.Name);
return HookResult.Continue;
}
@@ -379,15 +466,26 @@ namespace TestPlugin
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
{
// We don't currently have a `ReplyToCommand` equivalent so just print to chat for now.
player.PrintToChat(weapon.Value.DesignerName);
command.ReplyToCommand(weapon.Value.DesignerName);
}
}
[ConsoleCommand("css_colors", "List Chat Colors")]
public void OnCommandColors(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
for (int i = 0; i < 16; i++)
{
command.ReplyToCommand($" {(char)i}Color 0x{i:x}");
}
}
[ConsoleCommand("css_pause", "Pause Game")]
public void OnCommandPause(CCSPlayerController? player, CommandInfo command)
{
Log("Pause");
Logger.LogInformation("Pause");
}
[ConsoleCommand("css_give", "Give named item")]
@@ -398,19 +496,36 @@ namespace TestPlugin
player.GiveNamedItem(command.ArgByIndex(1));
}
[ConsoleCommand("css_giveenum", "giveenum")]
public void OnCommandGiveEnum(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.IsValid) return;
player.GiveNamedItem(CsItem.M4A1);
player.GiveNamedItem(CsItem.HEGrenade);
player.GiveNamedItem(CsItem.Kevlar);
player.GiveNamedItem(CsItem.Tec9);
}
[ConsoleCommand("css_give", "give")]
public void OnCommandGiveItems(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.IsValid) return;
player.GiveNamedItem("weapon_m4a1");
player.GiveNamedItem("weapon_hegrenade");
player.GiveNamedItem("item_kevlar");
player.GiveNamedItem("weapon_tec9");
}
private HookResult GenericEventHandler<T>(T @event, GameEventInfo info) where T : GameEvent
{
Log($"Event found {@event.Handle:X}, event name: {@event.EventName} dont broadcast: {info.DontBroadcast}");
Logger.LogInformation("Event found {Pointer:X}, event name: {EventName}, dont broadcast: {DontBroadcast}",
@event.Handle, @event.EventName, info.DontBroadcast);
return HookResult.Continue;
}
private void Log(string message)
{
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.DarkMagenta;
Console.WriteLine(message);
Console.ResetColor();
}
}
}
}

View File

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

View File

@@ -26,11 +26,22 @@ CCoreConfig::~CCoreConfig() = default;
bool CCoreConfig::Init(char* conf_error, int conf_error_size)
{
std::ifstream ifs(m_sPath);
std::ifstream ifs(std::string(m_sPath + ".json"));
if (!ifs) {
V_snprintf(conf_error, conf_error_size, "CoreConfig file not found.");
return false;
std::ifstream exampleIfs(std::string(m_sPath + ".example.json"));
if (!exampleIfs) {
V_snprintf(conf_error, conf_error_size, "CoreConfig file not found.");
return false;
}
CSSHARP_CORE_INFO("CoreConfig file not found, creating one from example.");
std::ofstream ofs(std::string(m_sPath + ".json"));
ofs << exampleIfs.rdbuf();
ofs.close();
return Init(conf_error, conf_error_size);
}
m_json = json::parse(ifs);

View File

@@ -32,41 +32,47 @@
#include "core/log.h"
#include "dyncall/dyncall/dyncall.h"
#include "pch.h"
#include "dynohook/core.h"
#include "dynohook/manager.h"
#include "dynohook/conventions/x64/x64SystemVcall.h"
namespace counterstrikesharp {
DCCallVM* g_pCallVM = dcNewCallVM(4096);
std::map<dyno::Hook*, ValveFunction*> g_HookMap;
// ============================================================================
// >> GetDynCallConvention
// ============================================================================
int GetDynCallConvention(Convention_t eConv) {
int GetDynCallConvention(Convention_t eConv)
{
switch (eConv) {
case CONV_CUSTOM:
return -1;
case CONV_CDECL:
return DC_CALL_C_DEFAULT;
case CONV_THISCALL:
case CONV_CUSTOM:
return -1;
case CONV_CDECL:
return DC_CALL_C_DEFAULT;
case CONV_THISCALL:
#ifdef _WIN32
return DC_CALL_C_X86_WIN32_THIS_MS;
return DC_CALL_C_X86_WIN32_THIS_MS;
#else
return DC_CALL_C_X86_WIN32_THIS_GNU;
return DC_CALL_C_X86_WIN32_THIS_GNU;
#endif
#ifdef _WIN32
case CONV_STDCALL:
return DC_CALL_C_X86_WIN32_STD;
case CONV_FASTCALL:
return DC_CALL_C_X86_WIN32_FAST_MS;
case CONV_STDCALL:
return DC_CALL_C_X86_WIN32_STD;
case CONV_FASTCALL:
return DC_CALL_C_X86_WIN32_FAST_MS;
#endif
}
return -1;
}
ValveFunction::ValveFunction(void* ulAddr,
Convention_t callingConvention,
std::vector<DataType_t> args,
DataType_t returnType)
: m_ulAddr(ulAddr) {
ValveFunction::ValveFunction(void* ulAddr, Convention_t callingConvention,
std::vector<DataType_t> args, DataType_t returnType)
: m_ulAddr(ulAddr)
{
m_Args = args;
m_eReturnType = returnType;
@@ -76,11 +82,8 @@ ValveFunction::ValveFunction(void* ulAddr,
m_iCallingConvention = GetDynCallConvention(m_eCallingConvention);
}
ValveFunction::ValveFunction(void* ulAddr,
Convention_t callingConvention,
DataType_t* args,
int argCount,
DataType_t returnType)
ValveFunction::ValveFunction(void* ulAddr, Convention_t callingConvention, DataType_t* args,
int argCount, DataType_t returnType)
: m_ulAddr(ulAddr)
{
@@ -93,26 +96,14 @@ ValveFunction::ValveFunction(void* ulAddr,
ValveFunction::~ValveFunction() {}
bool ValveFunction::IsCallable() {
bool ValveFunction::IsCallable()
{
return (m_eCallingConvention != CONV_CUSTOM) && (m_iCallingConvention != -1);
}
// bool ValveFunction::IsHookable() { return m_pCallingConvention != NULL; }
//
// bool ValveFunction::IsHooked() { return GetHookManager()->FindHook((void*)m_ulAddr) != NULL; }
//
// CHook* ValveFunction::GetHook() { return GetHookManager()->FindHook((void*)m_ulAddr); }
// ValveFunction* ValveFunction::GetTrampoline() {
// CHook* pHook = GetHookManager()->FindHook((void*)m_ulAddr);
// if (!pHook) return nullptr;
//
// return new ValveFunction((unsigned long)pHook->m_pTrampoline, m_eCallingConvention, m_Args,
// m_eReturnType);
// }
template <class ReturnType, class Function>
ReturnType CallHelper(Function func, DCCallVM* vm, void* addr) {
ReturnType CallHelper(Function func, DCCallVM* vm, void* addr)
{
ReturnType result;
result = (ReturnType)func(vm, (void*)addr);
return result;
@@ -120,8 +111,10 @@ ReturnType CallHelper(Function func, DCCallVM* vm, void* addr) {
void CallHelperVoid(DCCallVM* vm, void* addr) { dcCallVoid(vm, (void*)addr); }
void ValveFunction::Call(ScriptContext& script_context, int offset) {
if (!IsCallable()) return;
void ValveFunction::Call(ScriptContext& script_context, int offset)
{
if (!IsCallable())
return;
dcReset(g_pCallVM);
dcMode(g_pCallVM, m_iCallingConvention);
@@ -129,148 +122,195 @@ void ValveFunction::Call(ScriptContext& script_context, int offset) {
for (size_t i = 0; i < m_Args.size(); i++) {
int contextIndex = i + offset;
switch (m_Args[i]) {
case DATA_TYPE_BOOL:
dcArgBool(g_pCallVM, script_context.GetArgument<bool>(contextIndex));
break;
case DATA_TYPE_CHAR:
dcArgChar(g_pCallVM, script_context.GetArgument<char>(contextIndex));
break;
case DATA_TYPE_UCHAR:
dcArgChar(g_pCallVM, script_context.GetArgument<unsigned char>(contextIndex));
break;
case DATA_TYPE_SHORT:
dcArgShort(g_pCallVM, script_context.GetArgument<short>(contextIndex));
break;
case DATA_TYPE_USHORT:
dcArgShort(g_pCallVM, script_context.GetArgument<unsigned short>(contextIndex));
break;
case DATA_TYPE_INT:
dcArgInt(g_pCallVM, script_context.GetArgument<int>(contextIndex));
break;
case DATA_TYPE_UINT:
dcArgInt(g_pCallVM, script_context.GetArgument<unsigned int>(contextIndex));
break;
case DATA_TYPE_LONG:
dcArgLong(g_pCallVM, script_context.GetArgument<long>(contextIndex));
break;
case DATA_TYPE_ULONG:
dcArgLong(g_pCallVM, script_context.GetArgument<unsigned long>(contextIndex));
break;
case DATA_TYPE_LONG_LONG:
dcArgLongLong(g_pCallVM, script_context.GetArgument<long long>(contextIndex));
break;
case DATA_TYPE_ULONG_LONG:
dcArgLongLong(g_pCallVM,
script_context.GetArgument<unsigned long long>(contextIndex));
break;
case DATA_TYPE_FLOAT:
dcArgFloat(g_pCallVM, script_context.GetArgument<float>(contextIndex));
break;
case DATA_TYPE_DOUBLE:
dcArgDouble(g_pCallVM, script_context.GetArgument<double>(contextIndex));
break;
case DATA_TYPE_POINTER:
dcArgPointer(g_pCallVM, script_context.GetArgument<void*>(contextIndex));
break;
case DATA_TYPE_STRING:
dcArgPointer(g_pCallVM,
(void*)script_context.GetArgument<const char*>(contextIndex));
break;
default:
assert(!"Unknown function parameter type!");
break;
case DATA_TYPE_BOOL:
dcArgBool(g_pCallVM, script_context.GetArgument<bool>(contextIndex));
break;
case DATA_TYPE_CHAR:
dcArgChar(g_pCallVM, script_context.GetArgument<char>(contextIndex));
break;
case DATA_TYPE_UCHAR:
dcArgChar(g_pCallVM, script_context.GetArgument<unsigned char>(contextIndex));
break;
case DATA_TYPE_SHORT:
dcArgShort(g_pCallVM, script_context.GetArgument<short>(contextIndex));
break;
case DATA_TYPE_USHORT:
dcArgShort(g_pCallVM, script_context.GetArgument<unsigned short>(contextIndex));
break;
case DATA_TYPE_INT:
dcArgInt(g_pCallVM, script_context.GetArgument<int>(contextIndex));
break;
case DATA_TYPE_UINT:
dcArgInt(g_pCallVM, script_context.GetArgument<unsigned int>(contextIndex));
break;
case DATA_TYPE_LONG:
dcArgLong(g_pCallVM, script_context.GetArgument<long>(contextIndex));
break;
case DATA_TYPE_ULONG:
dcArgLong(g_pCallVM, script_context.GetArgument<unsigned long>(contextIndex));
break;
case DATA_TYPE_LONG_LONG:
dcArgLongLong(g_pCallVM, script_context.GetArgument<long long>(contextIndex));
break;
case DATA_TYPE_ULONG_LONG:
dcArgLongLong(g_pCallVM, script_context.GetArgument<unsigned long long>(contextIndex));
break;
case DATA_TYPE_FLOAT:
dcArgFloat(g_pCallVM, script_context.GetArgument<float>(contextIndex));
break;
case DATA_TYPE_DOUBLE:
dcArgDouble(g_pCallVM, script_context.GetArgument<double>(contextIndex));
break;
case DATA_TYPE_POINTER:
dcArgPointer(g_pCallVM, script_context.GetArgument<void*>(contextIndex));
break;
case DATA_TYPE_STRING:
dcArgPointer(g_pCallVM, (void*)script_context.GetArgument<const char*>(contextIndex));
break;
default:
assert(!"Unknown function parameter type!");
break;
}
}
switch (m_eReturnType) {
case DATA_TYPE_VOID:
CallHelperVoid(g_pCallVM, m_ulAddr);
break;
case DATA_TYPE_BOOL:
script_context.SetResult(CallHelper<bool>(dcCallBool, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_CHAR:
script_context.SetResult(CallHelper<char>(dcCallChar, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_UCHAR:
script_context.SetResult(CallHelper<unsigned char>(dcCallChar, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_SHORT:
script_context.SetResult(CallHelper<short>(dcCallShort, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_USHORT:
script_context.SetResult(CallHelper<unsigned short>(dcCallShort, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_INT:
script_context.SetResult(CallHelper<int>(dcCallInt, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_UINT:
script_context.SetResult(CallHelper<unsigned int>(dcCallInt, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_LONG:
script_context.SetResult(CallHelper<long>(dcCallLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_ULONG:
script_context.SetResult(CallHelper<unsigned long>(dcCallLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_LONG_LONG:
script_context.SetResult(CallHelper<long long>(dcCallLongLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_ULONG_LONG:
script_context.SetResult(
CallHelper<unsigned long long>(dcCallLongLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_FLOAT:
script_context.SetResult(CallHelper<float>(dcCallFloat, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_DOUBLE:
script_context.SetResult(CallHelper<double>(dcCallDouble, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_POINTER:
script_context.SetResult(CallHelper<void*>(dcCallPointer, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_STRING:
script_context.SetResult(CallHelper<const char*>(dcCallPointer, g_pCallVM, m_ulAddr));
break;
default:
assert(!"Unknown function return type!");
break;
case DATA_TYPE_VOID:
CallHelperVoid(g_pCallVM, m_ulAddr);
break;
case DATA_TYPE_BOOL:
script_context.SetResult(CallHelper<bool>(dcCallBool, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_CHAR:
script_context.SetResult(CallHelper<char>(dcCallChar, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_UCHAR:
script_context.SetResult(CallHelper<unsigned char>(dcCallChar, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_SHORT:
script_context.SetResult(CallHelper<short>(dcCallShort, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_USHORT:
script_context.SetResult(CallHelper<unsigned short>(dcCallShort, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_INT:
script_context.SetResult(CallHelper<int>(dcCallInt, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_UINT:
script_context.SetResult(CallHelper<unsigned int>(dcCallInt, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_LONG:
script_context.SetResult(CallHelper<long>(dcCallLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_ULONG:
script_context.SetResult(CallHelper<unsigned long>(dcCallLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_LONG_LONG:
script_context.SetResult(CallHelper<long long>(dcCallLongLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_ULONG_LONG:
script_context.SetResult(
CallHelper<unsigned long long>(dcCallLongLong, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_FLOAT:
script_context.SetResult(CallHelper<float>(dcCallFloat, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_DOUBLE:
script_context.SetResult(CallHelper<double>(dcCallDouble, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_POINTER:
script_context.SetResult(CallHelper<void*>(dcCallPointer, g_pCallVM, m_ulAddr));
break;
case DATA_TYPE_STRING:
script_context.SetResult(CallHelper<const char*>(dcCallPointer, g_pCallVM, m_ulAddr));
break;
default:
assert(!"Unknown function return type!");
break;
}
}
//
// CHook* HookFunctionHelper(void* addr, ICallingConvention* pConv) {
// CHook* result;
// result = GetHookManager()->HookFunction(addr, pConv);
// return result;
//}
//
// void ValveFunction::DeleteHook() {
// CHook* pHook = GetHookManager()->FindHook((void*)m_ulAddr);
// if (!pHook) return;
//
// // Set the calling convention to NULL, because DynamicHooks will delete it
// // otherwise.
// pHook->m_pCallingConvention = NULL;
// GetHookManager()->UnhookFunction((void*)m_ulAddr);
//}
//
// CHook* ValveFunction::AddHook(HookType_t eType, void* callable) {
// if (!IsHookable()) return nullptr;
//
// CHook* pHook = GetHookManager()->FindHook((void*)m_ulAddr);
//
// if (!pHook) {
// pHook = HookFunctionHelper((void*)m_ulAddr, m_pCallingConvention);
//
// // DynamicHooks will handle our convention from there, regardless if we
// // allocated it or not.
// m_bAllocatedCallingConvention = false;
// }
//
// // Add the hook handler. If it's already added, it won't be added twice
// pHook->AddCallback(eType, (HookHandlerFn*)(void*)callable);
//
// return pHook;
//}
} // namespace counterstrikesharp
dyno::ReturnAction HookHandler(dyno::HookType hookType, dyno::Hook& hook)
{
auto vf = g_HookMap[&hook];
auto callback = hookType == dyno::HookType::Pre ? vf->m_precallback : vf->m_postcallback;
if (callback == nullptr) {
return dyno::ReturnAction::Ignored;
}
callback->Reset();
callback->ScriptContext().Push(&hook);
for (auto fnMethodToCall : callback->GetFunctions()) {
if (!fnMethodToCall)
continue;
fnMethodToCall(&callback->ScriptContextStruct());
auto result = callback->ScriptContext().GetResult<HookResult>();
CSSHARP_CORE_TRACE("Received hook callback result of {}, hook mode {}", result,
(int)hookType);
if (result >= HookResult::Handled) {
return dyno::ReturnAction::Supercede;
}
}
return dyno::ReturnAction::Ignored;
}
std::vector<dyno::DataObject> ConvertArgsToDynoHook(const std::vector<DataType_t>& dataTypes)
{
std::vector<dyno::DataObject> converted;
converted.reserve(dataTypes.size());
for (DataType_t dt : dataTypes) {
converted.push_back(dyno::DataObject(static_cast<dyno::DataType>(dt)));
}
return converted;
}
void ValveFunction::AddHook(CallbackT callable, bool post)
{
dyno::HookManager& manager = dyno::HookManager::Get();
dyno::Hook* hook = manager.hook((void*)m_ulAddr, [this] {
return new dyno::x64SystemVcall(ConvertArgsToDynoHook(m_Args),
static_cast<dyno::DataType>(this->m_eReturnType));
});
g_HookMap[hook] = this;
hook->addCallback(dyno::HookType::Post, (dyno::HookHandler*)&HookHandler);
hook->addCallback(dyno::HookType::Pre, (dyno::HookHandler*)&HookHandler);
if (post) {
if (m_postcallback == nullptr) {
m_postcallback = globals::callbackManager.CreateCallback("");
}
m_postcallback->AddListener(callable);
} else {
if (m_precallback == nullptr) {
m_precallback = globals::callbackManager.CreateCallback("");
}
m_precallback->AddListener(callable);
}
}
void ValveFunction::RemoveHook(CallbackT callable, bool post) {
dyno::HookManager& manager = dyno::HookManager::Get();
dyno::Hook* hook = manager.hook((void*)m_ulAddr, [this] {
return new dyno::x64SystemVcall(ConvertArgsToDynoHook(m_Args),
static_cast<dyno::DataType>(this->m_eReturnType));
});
g_HookMap[hook] = this;
if (post) {
if (m_postcallback != nullptr) {
m_postcallback->RemoveListener(callable);
}
} else {
if (m_precallback != nullptr) {
m_precallback->RemoveListener(callable);
}
}
}
} // namespace counterstrikesharp

View File

@@ -32,6 +32,11 @@
#include "scripting/callback_manager.h"
#include "scripting/script_engine.h"
#include <map>
namespace dyno {
class Hook;
}
namespace counterstrikesharp {
@@ -81,19 +86,13 @@ public:
~ValveFunction();
bool IsCallable();
// bool IsHookable();
// bool IsHooked();
// CHook* GetHook();
// ValveFunction* GetTrampoline();
void SetOffset(int offset) { m_offset = offset; }
void SetSignature(const char* signature) { m_signature = signature; }
void Call(ScriptContext& args, int offset = 0);
// CHook* AddHook(HookType_t eType, void* callable);
// void DeleteHook();
void AddHook(CallbackT callable, bool post);
void RemoveHook(CallbackT callable, bool post);
void* m_ulAddr;
std::vector<DataType_t> m_Args;
@@ -107,6 +106,8 @@ public:
int m_offset;
const char* m_signature;
ScriptCallback* m_precallback = nullptr;
ScriptCallback* m_postcallback = nullptr;
};
} // namespace counterstrikesharp

View File

@@ -44,7 +44,7 @@
SH_DECL_HOOK4_void(IServerGameClients, ClientActive, SH_NOATTRIB, 0, CPlayerSlot, bool, const char*,
uint64);
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot, int,
SH_DECL_HOOK5_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, CPlayerSlot, ENetworkDisconnectionReason,
const char*, uint64, const char*);
SH_DECL_HOOK4_void(IServerGameClients, ClientPutInServer, SH_NOATTRIB, 0, CPlayerSlot, char const*,
int, uint64);
@@ -119,8 +119,8 @@ bool PlayerManager::OnClientConnect(CPlayerSlot slot, const char* pszName, uint6
CPlayer* pPlayer = &m_players[client];
if (pPlayer->IsConnected()) {
OnClientDisconnect(slot, 0, pszName, xuid, pszNetworkID);
OnClientDisconnect_Post(slot, 0, pszName, xuid, pszNetworkID);
OnClientDisconnect(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
OnClientDisconnect_Post(slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, pszName, xuid, pszNetworkID);
}
pPlayer->Initialize(pszName, pszNetworkID, slot);
@@ -222,7 +222,7 @@ void PlayerManager::OnClientPutInServer(CPlayerSlot slot, char const* pszName, i
}
void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
/* ENetworkDisconnectionReason */ int reason,
ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid, const char* pszNetworkID)
{
CSSHARP_CORE_TRACE("[PlayerManager][OnClientDisconnect] - {}, {}, {}", slot.Get(), pszName,
@@ -234,6 +234,7 @@ void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
if (pPlayer->IsConnected()) {
m_on_client_disconnect_callback->ScriptContext().Reset();
m_on_client_disconnect_callback->ScriptContext().Push(pPlayer->m_slot.Get());
m_on_client_disconnect_callback->ScriptContext().Push(reason);
m_on_client_disconnect_callback->Execute();
}
@@ -245,7 +246,7 @@ void PlayerManager::OnClientDisconnect(CPlayerSlot slot,
}
void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
/* ENetworkDisconnectionReason */ int reason,
ENetworkDisconnectionReason reason,
const char* pszName, uint64 xuid,
const char* pszNetworkID) const
{
@@ -263,6 +264,7 @@ void PlayerManager::OnClientDisconnect_Post(CPlayerSlot slot,
m_on_client_disconnect_post_callback->ScriptContext().Reset();
m_on_client_disconnect_post_callback->ScriptContext().Push(pPlayer->m_slot.Get());
m_on_client_disconnect_post_callback->ScriptContext().Push(reason);
m_on_client_disconnect_post_callback->Execute();
}
@@ -272,9 +274,9 @@ void PlayerManager::OnLevelEnd()
for (int i = 0; i <= m_max_clients; i++) {
if (m_players[i].IsConnected()) {
OnClientDisconnect(m_players[i].m_slot, 0, m_players[i].GetName(), 0,
OnClientDisconnect(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
m_players[i].GetIpAddress());
OnClientDisconnect_Post(m_players[i].m_slot, 0, m_players[i].GetName(), 0,
OnClientDisconnect_Post(m_players[i].m_slot, ENetworkDisconnectionReason::NETWORK_DISCONNECT_INVALID, m_players[i].GetName(), 0,
m_players[i].GetIpAddress());
}
}

View File

@@ -130,12 +130,12 @@ public:
CBufferString *pRejectReason);
void OnClientPutInServer(CPlayerSlot slot, char const *pszName, int type, uint64 xuid);
void OnClientDisconnect(CPlayerSlot slot,
/* ENetworkDisconnectionReason */ int reason,
ENetworkDisconnectionReason reason,
const char *pszName,
uint64 xuid,
const char *pszNetworkID);
void OnClientDisconnect_Post(CPlayerSlot slot,
/* ENetworkDisconnectionReason */ int reason,
ENetworkDisconnectionReason reason,
const char *pszName,
uint64 xuid,
const char *pszNetworkID) const;

View File

@@ -84,7 +84,7 @@ bool CounterStrikeSharpMMPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, s
GET_V_IFACE_ANY(GetEngineFactory, globals::gameEventSystem, IGameEventSystem,
GAMEEVENTSYSTEM_INTERFACE_VERSION);
auto coreconfig_path = std::string(utils::ConfigsDirectory() + "/core.json");
auto coreconfig_path = std::string(utils::ConfigsDirectory() + "/core");
globals::coreConfig = new CCoreConfig(coreconfig_path);
char coreconfig_error[255] = "";

View File

@@ -199,16 +199,16 @@ bool CDotNetManager::Initialize()
const std::string dotnetlib_path =
std::string((base_dir + "/api/CounterStrikeSharp.API.dll").c_str());
#endif
const auto dotnet_type = STR("CounterStrikeSharp.API.Core.Helpers, CounterStrikeSharp.API");
const auto dotnet_type = STR("CounterStrikeSharp.API.Bootstrap, CounterStrikeSharp.API");
// Namespace, assembly name
typedef int(CORECLR_DELEGATE_CALLTYPE * custom_entry_point_fn)();
custom_entry_point_fn entry_point = nullptr;
const int rc = load_assembly_and_get_function_pointer(
dotnetlib_path.c_str(), dotnet_type, STR("LoadAllPlugins"), UNMANAGEDCALLERSONLY_METHOD,
dotnetlib_path.c_str(), dotnet_type, STR("Run"), UNMANAGEDCALLERSONLY_METHOD,
nullptr, reinterpret_cast<void**>(&entry_point));
if (entry_point == nullptr) {
CSSHARP_CORE_ERROR("Trying to get entry point \"LoadAllPlugins\" but failed.");
CSSHARP_CORE_ERROR("Trying to get entry point \"Bootstrap::Run\" but failed.");
return false;
}
@@ -216,7 +216,7 @@ bool CDotNetManager::Initialize()
"Failure: load_assembly_and_get_function_pointer()");
if (const int invoke_result_code = entry_point(); invoke_result_code == 0) {
CSSHARP_CORE_ERROR("LoadAllPlugins return failure.");
CSSHARP_CORE_ERROR("Bootstrap::Run return failure.");
return false;
}

View File

@@ -0,0 +1,283 @@
/*
* 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/>. *
*/
// clang-format off
#include "mm_plugin.h"
#include "core/timer_system.h"
#include "scripting/autonative.h"
#include "scripting/script_engine.h"
#include "core/function.h"
#include "pch.h"
#include "dynohook/core.h"
#include "dynohook/manager.h"
// clang-format on
namespace counterstrikesharp {
void DHookGetReturn(ScriptContext& script_context)
{
auto hook = script_context.GetArgument<dyno::Hook*>(0);
auto dataType = script_context.GetArgument<DataType_t>(1);
if (hook == nullptr) {
script_context.ThrowNativeError("Invalid hook");
}
switch (dataType) {
case DATA_TYPE_BOOL:
script_context.SetResult(hook->getReturnValue<bool>());
break;
case DATA_TYPE_CHAR:
script_context.SetResult(hook->getReturnValue<char>());
break;
case DATA_TYPE_UCHAR:
script_context.SetResult(hook->getReturnValue<unsigned char>());
break;
case DATA_TYPE_SHORT:
script_context.SetResult(hook->getReturnValue<short>());
break;
case DATA_TYPE_USHORT:
script_context.SetResult(hook->getReturnValue<unsigned short>());
break;
case DATA_TYPE_INT:
script_context.SetResult(hook->getReturnValue<int>());
break;
case DATA_TYPE_UINT:
script_context.SetResult(hook->getReturnValue<unsigned int>());
break;
case DATA_TYPE_LONG:
script_context.SetResult(hook->getReturnValue<long>());
break;
case DATA_TYPE_ULONG:
script_context.SetResult(hook->getReturnValue<unsigned long>());
break;
case DATA_TYPE_LONG_LONG:
script_context.SetResult(hook->getReturnValue<long long>());
break;
case DATA_TYPE_ULONG_LONG:
script_context.SetResult(hook->getReturnValue<unsigned long long>());
break;
case DATA_TYPE_FLOAT:
script_context.SetResult(hook->getReturnValue<float>());
break;
case DATA_TYPE_DOUBLE:
script_context.SetResult(hook->getReturnValue<double>());
break;
case DATA_TYPE_POINTER:
script_context.SetResult(hook->getReturnValue<void*>());
break;
case DATA_TYPE_STRING:
script_context.SetResult(hook->getReturnValue<const char*>());
break;
default:
assert(!"Unknown function parameter type!");
break;
}
}
void DHookSetReturn(ScriptContext& script_context)
{
auto hook = script_context.GetArgument<dyno::Hook*>(0);
auto dataType = script_context.GetArgument<DataType_t>(1);
if (hook == nullptr) {
script_context.ThrowNativeError("Invalid hook");
}
auto valueIndex = 2;
switch (dataType) {
case DATA_TYPE_BOOL:
hook->setReturnValue(script_context.GetArgument<bool>(valueIndex));
break;
case DATA_TYPE_CHAR:
hook->setReturnValue(script_context.GetArgument<char>(valueIndex));
break;
case DATA_TYPE_UCHAR:
hook->setReturnValue(script_context.GetArgument<unsigned char>(valueIndex));
break;
case DATA_TYPE_SHORT:
hook->setReturnValue(script_context.GetArgument<short>(valueIndex));
break;
case DATA_TYPE_USHORT:
hook->setReturnValue(script_context.GetArgument<unsigned short>(valueIndex));
break;
case DATA_TYPE_INT:
hook->setReturnValue(script_context.GetArgument<int>(valueIndex));
break;
case DATA_TYPE_UINT:
hook->setReturnValue(script_context.GetArgument<unsigned int>(valueIndex));
break;
case DATA_TYPE_LONG:
hook->setReturnValue(script_context.GetArgument<long>(valueIndex));
break;
case DATA_TYPE_ULONG:
hook->setReturnValue(script_context.GetArgument<unsigned long>(valueIndex));
break;
case DATA_TYPE_LONG_LONG:
hook->setReturnValue(script_context.GetArgument<long long>(valueIndex));
break;
case DATA_TYPE_ULONG_LONG:
hook->setReturnValue(script_context.GetArgument<unsigned long long>(valueIndex));
break;
case DATA_TYPE_FLOAT:
hook->setReturnValue(script_context.GetArgument<float>(valueIndex));
break;
case DATA_TYPE_DOUBLE:
hook->setReturnValue(script_context.GetArgument<double>(valueIndex));
break;
case DATA_TYPE_POINTER:
hook->setReturnValue(script_context.GetArgument<void*>(valueIndex));
break;
case DATA_TYPE_STRING:
hook->setReturnValue(script_context.GetArgument<const char*>(valueIndex));
break;
default:
assert(!"Unknown function parameter type!");
break;
}
}
void DHookGetParam(ScriptContext& script_context)
{
auto hook = script_context.GetArgument<dyno::Hook*>(0);
auto dataType = script_context.GetArgument<DataType_t>(1);
auto paramIndex = script_context.GetArgument<int>(2);
if (hook == nullptr) {
script_context.ThrowNativeError("Invalid hook");
}
switch (dataType) {
case DATA_TYPE_BOOL:
script_context.SetResult(hook->getArgument<bool>(paramIndex));
break;
case DATA_TYPE_CHAR:
script_context.SetResult(hook->getArgument<char>(paramIndex));
break;
case DATA_TYPE_UCHAR:
script_context.SetResult(hook->getArgument<unsigned char>(paramIndex));
break;
case DATA_TYPE_SHORT:
script_context.SetResult(hook->getArgument<short>(paramIndex));
break;
case DATA_TYPE_USHORT:
script_context.SetResult(hook->getArgument<unsigned short>(paramIndex));
break;
case DATA_TYPE_INT:
script_context.SetResult(hook->getArgument<int>(paramIndex));
break;
case DATA_TYPE_UINT:
script_context.SetResult(hook->getArgument<unsigned int>(paramIndex));
break;
case DATA_TYPE_LONG:
script_context.SetResult(hook->getArgument<long>(paramIndex));
break;
case DATA_TYPE_ULONG:
script_context.SetResult(hook->getArgument<unsigned long>(paramIndex));
break;
case DATA_TYPE_LONG_LONG:
script_context.SetResult(hook->getArgument<long long>(paramIndex));
break;
case DATA_TYPE_ULONG_LONG:
script_context.SetResult(hook->getArgument<unsigned long long>(paramIndex));
break;
case DATA_TYPE_FLOAT:
script_context.SetResult(hook->getArgument<float>(paramIndex));
break;
case DATA_TYPE_DOUBLE:
script_context.SetResult(hook->getArgument<double>(paramIndex));
break;
case DATA_TYPE_POINTER:
script_context.SetResult(hook->getArgument<void*>(paramIndex));
break;
case DATA_TYPE_STRING:
script_context.SetResult(hook->getArgument<const char*>(paramIndex));
break;
default:
assert(!"Unknown function parameter type!");
break;
}
}
void DHookSetParam(ScriptContext& script_context)
{
auto hook = script_context.GetArgument<dyno::Hook*>(0);
auto dataType = script_context.GetArgument<DataType_t>(1);
auto paramIndex = script_context.GetArgument<int>(2);
if (hook == nullptr) {
script_context.ThrowNativeError("Invalid hook");
}
auto valueIndex = 3;
switch (dataType) {
case DATA_TYPE_BOOL:
hook->setArgument(paramIndex, script_context.GetArgument<bool>(valueIndex));
break;
case DATA_TYPE_CHAR:
hook->setArgument(paramIndex, script_context.GetArgument<char>(valueIndex));
break;
case DATA_TYPE_UCHAR:
hook->setArgument(paramIndex, script_context.GetArgument<unsigned char>(valueIndex));
break;
case DATA_TYPE_SHORT:
hook->setArgument(paramIndex, script_context.GetArgument<short>(valueIndex));
break;
case DATA_TYPE_USHORT:
hook->setArgument(paramIndex, script_context.GetArgument<unsigned short>(valueIndex));
break;
case DATA_TYPE_INT:
hook->setArgument(paramIndex, script_context.GetArgument<int>(valueIndex));
break;
case DATA_TYPE_UINT:
hook->setArgument(paramIndex, script_context.GetArgument<unsigned int>(valueIndex));
break;
case DATA_TYPE_LONG:
hook->setArgument(paramIndex, script_context.GetArgument<long>(valueIndex));
break;
case DATA_TYPE_ULONG:
hook->setArgument(paramIndex, script_context.GetArgument<unsigned long>(valueIndex));
break;
case DATA_TYPE_LONG_LONG:
hook->setArgument(paramIndex, script_context.GetArgument<long long>(valueIndex));
break;
case DATA_TYPE_ULONG_LONG:
hook->setArgument(paramIndex, script_context.GetArgument<unsigned long long>(valueIndex));
break;
case DATA_TYPE_FLOAT:
hook->setArgument(paramIndex, script_context.GetArgument<float>(valueIndex));
break;
case DATA_TYPE_DOUBLE:
hook->setArgument(paramIndex, script_context.GetArgument<double>(valueIndex));
break;
case DATA_TYPE_POINTER:
hook->setArgument(paramIndex, script_context.GetArgument<void*>(valueIndex));
break;
case DATA_TYPE_STRING:
hook->setArgument(paramIndex, script_context.GetArgument<const char*>(valueIndex));
break;
default:
assert(!"Unknown function parameter type!");
break;
}
}
REGISTER_NATIVES(dynamichooks, {
ScriptEngine::RegisterNativeHandler("DYNAMIC_HOOK_GET_RETURN", DHookGetReturn);
ScriptEngine::RegisterNativeHandler("DYNAMIC_HOOK_SET_RETURN", DHookSetReturn);
ScriptEngine::RegisterNativeHandler("DYNAMIC_HOOK_GET_PARAM", DHookGetParam);
ScriptEngine::RegisterNativeHandler("DYNAMIC_HOOK_SET_PARAM", DHookSetParam);
})
} // namespace counterstrikesharp

View File

@@ -0,0 +1,4 @@
DYNAMIC_HOOK_GET_RETURN: hook:pointer, datatype:int -> any
DYNAMIC_HOOK_SET_RETURN: hook:pointer, datatype:int, value:any -> void
DYNAMIC_HOOK_GET_PARAM: hook:pointer, datatype:int, paramIndex:int -> any
DYNAMIC_HOOK_SET_PARAM: hook:pointer, datatype:int, paramIndex:int, value:any -> void

View File

@@ -24,11 +24,12 @@
#include "core/log.h"
namespace counterstrikesharp {
std::vector<ValveFunction *> m_managed_ptrs;
std::vector<ValveFunction*> m_managed_ptrs;
byte *ConvertToByteArray(const char *str, size_t *outLength) {
size_t len = strlen(str) / 4; // Every byte is represented as \xHH
byte *result = (byte *)malloc(len);
byte* ConvertToByteArray(const char* str, size_t* outLength)
{
size_t len = strlen(str) / 4; // Every byte is represented as \xHH
byte* result = (byte*)malloc(len);
for (size_t i = 0, j = 0; i < len; ++i, j += 4) {
sscanf(str + j, "\\x%2hhx", &result[i]);
@@ -38,24 +39,26 @@ byte *ConvertToByteArray(const char *str, size_t *outLength) {
return result;
}
void *FindSignatureNative(ScriptContext &scriptContext) {
auto moduleName = scriptContext.GetArgument<const char *>(0);
auto bytesStr = scriptContext.GetArgument<const char *>(1);
void* FindSignatureNative(ScriptContext& scriptContext)
{
auto moduleName = scriptContext.GetArgument<const char*>(0);
auto bytesStr = scriptContext.GetArgument<const char*>(1);
return FindSignature(moduleName, bytesStr);
}
ValveFunction *CreateVirtualFunctionBySignature(ScriptContext &script_context) {
ValveFunction* CreateVirtualFunctionBySignature(ScriptContext& script_context)
{
auto ptr = script_context.GetArgument<unsigned long>(0);
auto binary_name = script_context.GetArgument<const char *>(1);
auto signature_hex_string = script_context.GetArgument<const char *>(2);
auto binary_name = script_context.GetArgument<const char*>(1);
auto signature_hex_string = script_context.GetArgument<const char*>(2);
auto num_arguments = script_context.GetArgument<int>(3);
auto return_type = script_context.GetArgument<DataType_t>(4);
auto* function_addr = FindSignature(binary_name, signature_hex_string);
if (function_addr == nullptr) {
script_context.ThrowNativeError("Could not find signature");
script_context.ThrowNativeError("Could not find signature %s", signature_hex_string);
return nullptr;
}
@@ -67,19 +70,21 @@ ValveFunction *CreateVirtualFunctionBySignature(ScriptContext &script_context) {
auto function = new ValveFunction(function_addr, CONV_CDECL, args, return_type);
function->SetSignature(signature_hex_string);
CSSHARP_CORE_TRACE("Created virtual function, pointer found at {}, signature {}", function_addr, signature_hex_string);
CSSHARP_CORE_TRACE("Created virtual function, pointer found at {}, signature {}", function_addr,
signature_hex_string);
m_managed_ptrs.push_back(function);
return function;
}
ValveFunction *CreateVirtualFunction(ScriptContext &script_context) {
auto ptr = script_context.GetArgument<void *>(0);
ValveFunction* CreateVirtualFunction(ScriptContext& script_context)
{
auto ptr = script_context.GetArgument<void*>(0);
auto vtable_offset = script_context.GetArgument<int>(1);
auto num_arguments = script_context.GetArgument<int>(2);
auto return_type = script_context.GetArgument<DataType_t>(3);
void **vtable = *(void ***)ptr;
void** vtable = *(void***)ptr;
if (!vtable) {
script_context.ThrowNativeError("Failed to get the virtual function table.");
return nullptr;
@@ -99,8 +104,37 @@ ValveFunction *CreateVirtualFunction(ScriptContext &script_context) {
return function;
}
void ExecuteVirtualFunction(ScriptContext &script_context) {
auto function = script_context.GetArgument<ValveFunction *>(0);
void HookFunction(ScriptContext& script_context)
{
auto function = script_context.GetArgument<ValveFunction*>(0);
auto callback = script_context.GetArgument<CallbackT>(1);
auto post = script_context.GetArgument<bool>(2);
if (!function) {
script_context.ThrowNativeError("Invalid function pointer");
return;
}
function->AddHook(callback, post);
}
void UnhookFunction(ScriptContext& script_context)
{
auto function = script_context.GetArgument<ValveFunction*>(0);
auto callback = script_context.GetArgument<CallbackT>(1);
auto post = script_context.GetArgument<bool>(2);
if (!function) {
script_context.ThrowNativeError("Invalid function pointer");
return;
}
function->RemoveHook(callback, post);
}
void ExecuteVirtualFunction(ScriptContext& script_context)
{
auto function = script_context.GetArgument<ValveFunction*>(0);
if (!function) {
script_context.ThrowNativeError("Invalid function pointer");
@@ -110,41 +144,15 @@ void ExecuteVirtualFunction(ScriptContext &script_context) {
function->Call(script_context, 1);
}
// void HookFunction(ScriptContext& script_context) {
// auto function = script_context.GetArgument<ValveFunction*>(0);
// auto entity_index = script_context.GetArgument<int>(1);
// auto post = script_context.GetArgument<bool>(2);
// auto callback = script_context.GetArgument<CallbackT>(3);
//
// if (!function) {
// script_context.ThrowNativeError("Invalid function pointer");
// return;
// }
//
// globals::hook_manager.AddHook(function, entity_index, callback, post);
// }
//
// void UnhookFunction(ScriptContext& script_context) {
// auto function = script_context.GetArgument<ValveFunction*>(0);
// auto entity_index = script_context.GetArgument<int>(1);
// auto post = script_context.GetArgument<bool>(2);
// auto callback = script_context.GetArgument<CallbackT>(3);
//
// if (!function) {
// script_context.ThrowNativeError("Invalid function pointer");
// return;
// }
//
// globals::hook_manager.Unhook(function, entity_index, callback, post);
// }
int GetNetworkVectorSize(ScriptContext &script_context) {
int GetNetworkVectorSize(ScriptContext& script_context)
{
auto vec = script_context.GetArgument<CUtlVector<void*>*>(0);
return vec->Count();
}
void* GetNetworkVectorElementAt(ScriptContext &script_context) {
void* GetNetworkVectorElementAt(ScriptContext& script_context)
{
auto vec = script_context.GetArgument<CUtlVector<CEntityHandle>*>(0);
auto index = script_context.GetArgument<int>(1);
@@ -156,10 +164,10 @@ REGISTER_NATIVES(memory, {
ScriptEngine::RegisterNativeHandler("CREATE_VIRTUAL_FUNCTION_BY_SIGNATURE",
CreateVirtualFunctionBySignature);
ScriptEngine::RegisterNativeHandler("EXECUTE_VIRTUAL_FUNCTION", ExecuteVirtualFunction);
// ScriptEngine::RegisterNativeHandler("HOOK_FUNCTION", HookFunction);
// ScriptEngine::RegisterNativeHandler("UNHOOK_FUNCTION", UnhookFunction);
ScriptEngine::RegisterNativeHandler("HOOK_FUNCTION", HookFunction);
ScriptEngine::RegisterNativeHandler("UNHOOK_FUNCTION", UnhookFunction);
ScriptEngine::RegisterNativeHandler("FIND_SIGNATURE", FindSignatureNative);
ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_SIZE", GetNetworkVectorSize);
ScriptEngine::RegisterNativeHandler("GET_NETWORK_VECTOR_ELEMENT_AT", GetNetworkVectorElementAt);
})
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -1,5 +1,7 @@
CREATE_VIRTUAL_FUNCTION: pointer:pointer,vtableOffset:int,numArguments:int,returnType:int,arguments:object[] -> pointer
CREATE_VIRTUAL_FUNCTION_BY_SIGNATURE: pointer:pointer,binaryName:string,signature:string,numArguments:int,returnType:int,arguments:object[] -> pointer
HOOK_FUNCTION: function:pointer, hook:callback, post:bool -> void
UNHOOK_FUNCTION: function:pointer, hook:callback, post:bool -> void
EXECUTE_VIRTUAL_FUNCTION: function:pointer,arguments:object[] -> any
FIND_SIGNATURE: modulePath:string, signature:string -> pointer
GET_NETWORK_VECTOR_SIZE: vec:pointer -> int