Compare commits

...

71 Commits
v6 ... v73

Author SHA1 Message Date
Roflmuffin
8d1891a3a8 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-26 13:19:54 +10:00
Roflmuffin
6bc43444f7 feat: add trigger touch start and end hooks 2023-11-26 13:19:40 +10:00
miguno
f0c7869f4a Check if userid is valid before accessing its fields, and explain why (#133) 2023-11-26 10:03:20 +10:00
Robert
3e38ed3c77 feat: Added ability to GiveNamedItem using the new CsItem Enum (#105) 2023-11-25 10:40:49 +10:00
Roflmuffin
7e9e7c6665 fix: wrong chat colors 2023-11-24 21:21:03 +10:00
Roflmuffin
9a018f295b feat: add player pawn post think signature 2023-11-24 21:04:22 +10:00
Michael Wilson
8b725d435f Dynamic Hooks (#78) 2023-11-24 19:59:47 +10:00
Nexd
ea3596417a Add methods to respawn players (#114) 2023-11-24 15:19:27 +10:00
Abner Santos
123f41914e docs: Additional info admin module documentation (#116) 2023-11-23 10:44:02 +10:00
Hackmastr
8f69076405 Update TestPlugin.cs, (#115) 2023-11-23 09:03:30 +10:00
Michael Wilson
44a85d1201 chore: upgrade hl2sdk, add protoc generation (#112) 2023-11-22 21:54:03 +10:00
Roflmuffin
20f50289ee docs: update docs to use ILogger 2023-11-21 16:50:20 +10:00
Michael Wilson
bb5fb5de72 Managed Core Logging & Plugin Logging (#102) 2023-11-21 16:42:56 +10:00
Nexd
6147739cfa hotfix: new signatures (#107) 2023-11-21 12:49:42 +10:00
zonical
3ab5893f22 Remove required from flags in AdminData (#106) 2023-11-21 10:34:11 +10:00
Nexd
50ce09a7b3 feat: new virtual functions with wrapper methods (#87) 2023-11-20 08:52:07 +10:00
Roflmuffin
9c7944e6f1 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp 2023-11-19 18:49:48 +10:00
Roflmuffin
bc71aa7739 feat: add FireEventToClient native 2023-11-19 18:49:36 +10:00
Snowy
16a1efc0cb docs: use nuget instead (#89) 2023-11-19 18:40:43 +10:00
Roflmuffin
8ae85cedf4 docs: add Admin Framework docs category 2023-11-19 14:23:30 +10:00
zonical
8e2234ff25 Admin Manager improvements (#74) 2023-11-19 14:18:40 +10:00
Roflmuffin
04e7ed682a fix: add command listener pre handlers 2023-11-19 09:51:20 +10:00
Roflmuffin
15e1260146 feat: update schema from 17.11.23 update 2023-11-19 09:44:15 +10:00
Roflmuffin
517607d962 fix: chat command config prefixes 2023-11-18 15:40:02 +10:00
Boink
0f72631eb0 Update CommitSuicide offset for 17-11-2023 update (#98) 2023-11-18 09:43:06 +10:00
Nexd
75fcf21fb7 feat: managed coreconfig implementation (#79) 2023-11-17 17:50:59 +10:00
Nexd
0ddf6bcdfa fix: new signature for CBaseModelEntity_SetModel (#84) 2023-11-15 15:18:03 +10:00
Roflmuffin
98661cd069 fix: public and silent triggers (finally) 2023-11-14 22:29:59 +10:00
Roflmuffin
86a5699b40 feat: re-add global command listener 2023-11-14 21:15:06 +10:00
Roflmuffin
414710d05c hotfix: wrap vfunc creation in try catch to prevent all vfuncs from erroring 2023-11-13 21:40:33 +10:00
Michael Wilson
b09c2b62c8 Improved Command Handling (#76) 2023-11-13 20:59:46 +10:00
Roflmuffin
31760518ed ci: fighting with the machines 2023-11-13 20:27:52 +10:00
Roflmuffin
6a160bcc3d ci: remove build number from PR checks 2023-11-13 20:25:49 +10:00
Roflmuffin
9c8e9db56e ci: set fallback build number for PRs 2023-11-13 20:23:46 +10:00
Roflmuffin
e2e0eab87d ci: run main pipeline skipping publish on PR 2023-11-13 20:19:37 +10:00
Nexd
43292bb1d2 feat: CBaseModelEntity_SetModel (#72) 2023-11-13 09:10:49 +10:00
Nexd
12c54cd4fc hotfix: deserializer couldn't call setter (#70) 2023-11-12 22:39:15 +10:00
Michael Wilson
e155a70873 Cross platform builds (#69) 2023-11-12 15:43:51 +10:00
Nexd
69d9b5d2c8 feat: Provide configuration standard for plugins (#67) 2023-11-12 14:25:06 +10:00
laper32
933fdf9d81 [Windows] feat: Windows support (#52) 2023-11-12 13:39:13 +10:00
Nexd
18e9e37a98 CoreConfig implementation on the managed side (#62)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2023-11-12 12:19:57 +10:00
Roflmuffin
fe236806e1 hotfix: con command hot reload 2023-11-11 10:30:05 +10:00
Nexd
2c4e9bca42 Small adjustments (#56) 2023-11-11 09:50:54 +10:00
Roflmuffin
8f3e0c226b docs: add information about flags and standard flags 2023-11-11 01:42:57 +10:00
Roflmuffin
5f6ccf2839 feat: change color marshalling to ABGR (tested against render color) 2023-11-11 00:05:13 +10:00
Roflmuffin
6c2f56793b hotfix: con command hot reload failing 2023-11-10 23:51:18 +10:00
Roflmuffin
cc7dd5ca96 ci: I have the utmost confidence 2023-11-10 20:16:04 +10:00
Roflmuffin
ebc361b2f8 ci: publish to api.nuget.org 2023-11-10 20:05:52 +10:00
Roflmuffin
c72eff2546 ci: fix nuget source 2023-11-10 20:00:09 +10:00
Roflmuffin
4b432e9efc ci: add package write permission 2023-11-10 19:52:13 +10:00
Roflmuffin
22bbf835c7 Merge remote-tracking branch 'origin/main' into main 2023-11-10 19:51:17 +10:00
Roflmuffin
092a6077c3 ci: try publishing nuget package 2023-11-10 19:49:44 +10:00
pedrotski
4430060efd Update README.md (#37)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2023-11-10 19:07:45 +10:00
Roflmuffin
77ea6fd80d fix: prevent server crash on duplicate command registration, fixes #51 2023-11-10 19:06:40 +10:00
Roflmuffin
f18df3df2b docs: update console command expected usage docs 2023-11-10 19:02:46 +10:00
Roflmuffin
4ce1ec2cf5 feat: add disabled plugins folder, and source folder for source code 2023-11-10 18:56:37 +10:00
Roflmuffin
9005f3c29c fix: my bad merging skills 2023-11-10 18:52:20 +10:00
Roflmuffin
b7ace4256a feat: change permission helper attribute to RequiresPermissions 2023-11-10 18:45:04 +10:00
Roflmuffin
b725f7f79a Basic admin system framework (plus some cleanup) (#44)
Co-authored-by: zonical <zonicalguy@gmail.com>
2023-11-10 18:40:20 +10:00
Roflmuffin
cb6d86a54d feat: implement IEquatable<T> for SteamID 2023-11-10 17:29:45 +10:00
Roflmuffin
d4a2ae68e1 Merge branch 'main' of github.com:roflmuffin/CounterStrikeSharp into main 2023-11-09 23:25:40 +10:00
Roflmuffin
82c92f555b chore: simplify auto-copy configs folder 2023-11-09 23:22:35 +10:00
Daniel Saewitz
19a0923559 feat: Add Current API Version to css console command (#47) 2023-11-09 13:24:54 +10:00
Roflmuffin
cef9758c12 fix: ignore -1 in get players, fixes, #46 2023-11-09 11:02:15 +10:00
Roflmuffin
0dc35818dd hotfix: native string memory leak 2023-11-09 09:18:33 +10:00
Roflmuffin
a0f9d30753 feat: add PrintToCenterHtml to player class 2023-11-09 01:12:17 +10:00
Michael Wilson
d6fe9e10e1 Generate all missing schema properties (#40) 2023-11-08 23:51:32 +10:00
Nexd
e1246af66a Added enum for ItemDefinitionIndex (#30)
Co-authored-by: Michael Wilson <roflmuffin@users.noreply.github.com>
2023-11-08 15:52:20 +10:00
Roflmuffin
6b4205a0d2 feat: add custom schema marshalers, provide Color schema get/set 2023-11-08 15:32:33 +10:00
Roflmuffin
f1efc6103d chore: retarget release zip from build/output folder 2023-11-08 14:52:56 +10:00
Roflmuffin
f6935cc9d2 fix: cast long event params properly, fixes #35 2023-11-08 14:32:26 +10:00
155 changed files with 154342 additions and 51162 deletions

View File

@@ -5,87 +5,193 @@ on:
paths-ignore:
- 'docs/**'
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
BUILD_TYPE: Release
jobs:
build:
permissions:
contents: write
runs-on: ubuntu-latest
container:
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
build_windows:
runs-on: windows-latest
steps:
- name: Prepare env
shell: bash
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Setup protobuf
shell: bash
run: sudo apt-get update && sudo apt install -y protobuf-compiler
- name: Visual Studio environment
shell: cmd
run: |
:: See https://github.com/microsoft/vswhere/wiki/Find-VC
for /f "usebackq delims=*" %%i in (`vswhere -latest -property installationPath`) do (
call "%%i"\Common7\Tools\vsdevcmd.bat -arch=x64 -host_arch=x64
)
:: Loop over all environment variables and make them global.
for /f "delims== tokens=1,2" %%a in ('set') do (
echo>>"%GITHUB_ENV%" %%a=%%b
)
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Generate build number
id: buildnumber
uses: onyxmueller/build-tag-number@v1
with:
token: ${{secrets.github_token}}
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.0.x'
- run: dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
# Build your program with the given configuration
run: cmake --build build --config ${{env.BUILD_TYPE}}
run: |
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ..
cmake --build . --config ${{env.BUILD_TYPE}} -- /m:16
- name: Clean build directory
run: |
mkdir -p build/addons/counterstrikesharp/bin/win64
mv build/${{env.BUILD_TYPE}}/*.dll build/addons/counterstrikesharp/bin/win64
mkdir build/output/
mv build/addons build/output
- uses: actions/upload-artifact@v3
with:
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
build_linux:
runs-on: ubuntu-latest
# Could not figure out how to run in a container only on some matrix paths, so I've split it out into its own build.
container:
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
steps:
- name: Prepare env
shell: bash
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Build
run: |
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ..
cmake --build . --config ${{env.BUILD_TYPE}} -- -j16
- name: Clean build directory
run: |
mkdir build/output/
mv build/addons build/output
- name: Add API to Artifacts
run: |
mkdir -p build/output/addons/counterstrikesharp/api
mkdir -p build/output/addons/counterstrikesharp/plugins
cp -r managed/CounterStrikeSharp.API/bin/Release/net7.0/publish/* build/output/addons/counterstrikesharp/api
- uses: actions/upload-artifact@v3
with:
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
build_managed:
permissions:
contents: write
runs-on: ubuntu-latest
outputs:
buildnumber: ${{ steps.buildnumber.outputs.build_number }}
steps:
- name: Prepare env
shell: bash
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Fallback build number
if: github.event_name == 'pull_request'
shell: bash
run: echo "BUILD_NUMBER=0" >> $GITHUB_ENV
# We don't need expensive submodules for the managed side.
- uses: actions/checkout@v3
- name: Generate build number
if: github.event_name == 'push'
id: buildnumber
uses: onyxmueller/build-tag-number@v1
with:
token: ${{secrets.github_token}}
- name: Build runtime v${{ env.BUILD_NUMBER }}
uses: actions/setup-dotnet@v3
with:
dotnet-version: '7.0.x'
- run: |
dotnet publish -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
dotnet pack -c Release /p:Version=1.0.${{ env.BUILD_NUMBER }} managed/CounterStrikeSharp.API
- uses: actions/upload-artifact@v3
with:
name: counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
path: managed/CounterStrikeSharp.API/bin/Release
- name: Zip CounterStrikeSharp Build
run: zip -qq -r counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip build/output/
publish:
if: github.event_name == 'push'
permissions:
contents: write
needs: [ "build_linux", "build_windows", "build_managed" ]
runs-on: ubuntu-latest
steps:
- name: Prepare env
shell: bash
run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV
- uses: actions/download-artifact@v3
with:
name: counterstrikesharp-build-windows-${{ env.GITHUB_SHA_SHORT }}
path: build/windows
- uses: actions/download-artifact@v3
with:
name: counterstrikesharp-build-linux-${{ env.GITHUB_SHA_SHORT }}
path: build/linux
- uses: actions/download-artifact@v3
with:
name: counterstrikesharp-build-api-${{ env.GITHUB_SHA_SHORT }}
path: build/api
# TODO: This stuff should really be in a matrix
- name: Add API to Artifacts
run: |
mkdir -p build/linux/addons/counterstrikesharp/api
mkdir -p build/windows/addons/counterstrikesharp/api
cp -r build/api/net7.0/publish/* build/linux/addons/counterstrikesharp/api
cp -r build/api/net7.0/publish/* build/windows/addons/counterstrikesharp/api
- name: Zip Builds
run: |
(cd build/linux && zip -qq -r ../../counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip *)
(cd build/windows && zip -qq -r ../../counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip *)
- name: Add dotnet runtime
run: |
mkdir -p build/output/addons/counterstrikesharp/dotnet
mkdir -p build/linux/addons/counterstrikesharp/dotnet
curl -s -L https://download.visualstudio.microsoft.com/download/pr/dc2c0a53-85a8-4fda-a283-fa28adb5fbe2/8ccade5bc400a5bb40cd9240f003b45c/aspnetcore-runtime-7.0.11-linux-x64.tar.gz \
| tar xvz -C build/output/addons/counterstrikesharp/dotnet
mv build/output/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/7.0.11/* build/output/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/
| tar xvz -C build/linux/addons/counterstrikesharp/dotnet
mv build/linux/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/7.0.11/* build/linux/addons/counterstrikesharp/dotnet/shared/Microsoft.NETCore.App/
mkdir -p build/windows/addons/counterstrikesharp/dotnet
curl -s -L https://download.visualstudio.microsoft.com/download/pr/a99861c8-2e00-4587-aaef-60366ca77307/a44ceec2c5d34165ae881600f52edc43/aspnetcore-runtime-7.0.11-win-x64.zip -o dotnet.zip
unzip -qq dotnet.zip -d build/windows/addons/counterstrikesharp/dotnet
- name: Zip CounterStrikeSharp Runtime Build
run: zip -qq -r counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip build/output/
- uses: actions/upload-artifact@v3
with:
name: counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}
path: build/output/
- name: Zip Builds
run: |
(cd build/linux && zip -qq -r ../../counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip *)
(cd build/windows && zip -qq -r ../../counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip *)
- name: Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ env.BUILD_NUMBER }}
tag_name: v${{ needs.build_managed.outputs.buildnumber }}
files: |
counterstrikesharp-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip
counterstrikesharp-with-runtime-build-${{ env.BUILD_NUMBER }}-${{ env.GITHUB_SHA_SHORT }}.zip
counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip
counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip
counterstrikesharp-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip
counterstrikesharp-with-runtime-build-${{ needs.build_managed.outputs.buildnumber }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip
- name: Publish NuGet package
run: |
dotnet nuget push build/api/CounterStrikeSharp.API.1.0.${{ needs.build_managed.outputs.buildnumber }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push build/api/CounterStrikeSharp.API.1.0.${{ needs.build_managed.outputs.buildnumber }}.snupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

View File

@@ -1,53 +0,0 @@
name: Build
on:
pull_request:
branches: [ "main" ]
env:
BUILD_TYPE: Release
jobs:
build:
runs-on: ubuntu-latest
container:
image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest
permissions:
pull-requests: read
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
csharp:
- managed/**/*
- src/scripting/natives/**/*
cpp:
- src/**/*
- uses: actions/setup-dotnet@v3
if: steps.changes.outputs.csharp == 'true'
with:
dotnet-version: '7.0.x'
- if: steps.changes.outputs.csharp == 'true'
run: dotnet build -c Release managed/CounterStrikeSharp.API
- name: Setup protobuf
shell: bash
if: steps.changes.outputs.cpp == 'true'
run: sudo apt-get update && sudo apt install -y protobuf-compiler
- name: Configure CMake
if: steps.changes.outputs.cpp == 'true'
run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
# Build your program with the given configuration
if: steps.changes.outputs.cpp == 'true'
run: cmake --build build --config ${{env.BUILD_TYPE}}

5
.gitignore vendored
View File

@@ -5,8 +5,13 @@ cmake-build-debug/
.vscode/
generated/
# configure_file auto generated.
configs/addons/metamod/counterstrikesharp.vdf
libraries/mono/
CMakeSettings.json
build/
build_test/

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

@@ -1,98 +1,119 @@
cmake_minimum_required(VERSION 3.18)
Project(counterstrikesharp C CXX)
# You must set ASM, otherwise CMAKE_ASM_COMPILE_OBJECT will not work on MSVC
project(counterstrikesharp C CXX ASM)
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
src/mm_plugin.cpp
src/mm_plugin.h
libraries/hl2sdk-cs2/tier1/convar.cpp
libraries/hl2sdk-cs2/tier1/generichash.cpp
libraries/hl2sdk-cs2/entity2/entitysystem.cpp
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
libraries/dotnet/hostfxr.h
libraries/dotnet/coreclr_delegates.h
"libraries/metamod-source/core/sourcehook/sourcehook.cpp"
"libraries/metamod-source/core/sourcehook/sourcehook_impl_chookidman.cpp"
"libraries/metamod-source/core/sourcehook/sourcehook_impl_chookmaninfo.cpp"
"libraries/metamod-source/core/sourcehook/sourcehook_impl_cvfnptr.cpp"
"libraries/metamod-source/core/sourcehook/sourcehook_impl_cproto.cpp"
src/scripting/dotnet_host.h
src/scripting/dotnet_host.cpp
src/core/utils.h
src/core/globals.h
src/core/globals.cpp
src/core/log.h
src/core/log.cpp
src/scripting/script_engine.h
src/scripting/script_engine.cpp
src/core/global_listener.h
src/scripting/callback_manager.h
src/scripting/callback_manager.cpp
src/core/managers/event_manager.h
src/core/managers/event_manager.cpp
src/core/timer_system.h
src/core/timer_system.cpp
src/scripting/autonative.h
src/scripting/natives/natives_engine.cpp
src/core/engine_trace.h
src/core/engine_trace.cpp
src/scripting/natives/natives_callbacks.cpp
src/core/managers/player_manager.h
src/core/managers/player_manager.cpp
src/scripting/natives/natives_vector.cpp
src/scripting/natives/natives_timers.cpp
src/utils/virtual.h
src/scripting/natives/natives_events.cpp
src/core/memory.cpp
src/core/memory.h
src/core/managers/con_command_manager.cpp
src/core/managers/con_command_manager.h
src/scripting/natives/natives_commands.cpp
src/core/memory_module.h
src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h
src/core/cs2_sdk/interfaces/cschemasystem.h
src/core/cs2_sdk/interfaces/cs2_interfaces.h
src/core/cs2_sdk/interfaces/cs2_interfaces.cpp
src/core/cs2_sdk/schema.h
src/core/cs2_sdk/schema.cpp
src/core/function.cpp
src/core/function.h
src/scripting/natives/natives_memory.cpp
src/scripting/natives/natives_schema.cpp
src/scripting/natives/natives_entities.cpp
src/core/managers/entity_manager.cpp
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp
src/core/managers/chat_manager.h
src/core/managers/client_command_manager.cpp
src/core/managers/client_command_manager.h
src/core/managers/server_manager.cpp
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
src/mm_plugin.cpp
src/mm_plugin.h
libraries/hl2sdk-cs2/tier1/convar.cpp
libraries/hl2sdk-cs2/tier1/generichash.cpp
libraries/hl2sdk-cs2/entity2/entitysystem.cpp
libraries/dotnet/hostfxr.h
libraries/dotnet/coreclr_delegates.h
libraries/metamod-source/core/sourcehook/sourcehook.cpp
libraries/metamod-source/core/sourcehook/sourcehook_impl_chookidman.cpp
libraries/metamod-source/core/sourcehook/sourcehook_impl_chookmaninfo.cpp
libraries/metamod-source/core/sourcehook/sourcehook_impl_cvfnptr.cpp
libraries/metamod-source/core/sourcehook/sourcehook_impl_cproto.cpp
src/scripting/dotnet_host.h
src/scripting/dotnet_host.cpp
src/core/utils.h
src/core/globals.h
src/core/globals.cpp
src/core/coreconfig.h
src/core/coreconfig.cpp
src/core/gameconfig.h
src/core/gameconfig.cpp
src/core/log.h
src/core/log.cpp
src/scripting/script_engine.h
src/scripting/script_engine.cpp
src/core/global_listener.h
src/scripting/callback_manager.h
src/scripting/callback_manager.cpp
src/core/managers/event_manager.h
src/core/managers/event_manager.cpp
src/core/timer_system.h
src/core/timer_system.cpp
src/scripting/autonative.h
src/scripting/natives/natives_engine.cpp
src/core/engine_trace.h
src/core/engine_trace.cpp
src/scripting/natives/natives_callbacks.cpp
src/core/managers/player_manager.h
src/core/managers/player_manager.cpp
src/scripting/natives/natives_vector.cpp
src/scripting/natives/natives_timers.cpp
src/utils/virtual.h
src/scripting/natives/natives_events.cpp
src/core/memory.cpp
src/core/memory.h
src/core/managers/con_command_manager.cpp
src/core/managers/con_command_manager.h
src/scripting/natives/natives_commands.cpp
src/core/memory_module.h
src/core/memory_module.cpp
src/core/cs2_sdk/interfaces/cgameresourceserviceserver.h
src/core/cs2_sdk/interfaces/cschemasystem.h
src/core/cs2_sdk/interfaces/cs2_interfaces.h
src/core/cs2_sdk/interfaces/cs2_interfaces.cpp
src/core/cs2_sdk/schema.h
src/core/cs2_sdk/schema.cpp
src/core/function.cpp
src/core/function.h
src/scripting/natives/natives_memory.cpp
src/scripting/natives/natives_schema.cpp
src/scripting/natives/natives_entities.cpp
src/core/managers/entity_manager.cpp
src/core/managers/entity_manager.h
src/core/managers/chat_manager.cpp
src/core/managers/chat_manager.h
src/core/managers/server_manager.cpp
src/core/managers/server_manager.h
src/scripting/natives/natives_server.cpp
libraries/nlohmann/json.hpp
src/scripting/natives/natives_dynamichooks.cpp
)
if (LINUX)
# memoverride.cpp is not usable on CMake Windows, cuz CMake default link libraries (seems) always link ucrt.lib
set(SOURCE_FILES
${SOURCE_FILES}
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
)
endif()
set(PROTO_DIRS -I${CMAKE_CURRENT_SOURCE_DIR}/libraries/GameTracking-CS2/Protobufs)
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})
@@ -104,16 +125,27 @@ target_include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
)
include("makefiles/linux.base.cmake")
if (LINUX)
include("makefiles/linux.base.cmake")
set_target_properties(${PROJECT_NAME} PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
)
elseif(WIN32)
include("makefiles/windows.base.cmake")
set_target_properties(${PROJECT_NAME} PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/win64"
)
endif()
# Libraries
target_link_libraries(${PROJECT_NAME} ${COUNTER_STRIKE_SHARP_LINK_LIBRARIES})
set_target_properties(${PROJECT_NAME} PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
)
add_custom_target(build-time-make-directory ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/addons/metamod")
configure_file(configs/counterstrikesharp.vdf "${CMAKE_BINARY_DIR}/addons/metamod/counterstrikesharp.vdf" COPYONLY)
configure_file(configs/gamedata.json "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/gamedata/gamedata.json" COPYONLY)
add_custom_command(
TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/configs ${CMAKE_BINARY_DIR}
)

View File

@@ -2,32 +2,29 @@
CounterStrikeSharp is a server side modding framework for Counter-Strike: Global Offensive. This project attempts to implement a .NET Core scripting layer on top of a Metamod Source Plugin, allowing developers to create plugins that interact with the game server in a modern language (C#) to facilitate the creation of maintainable and testable code.
[Come and join our Discord](https://discord.gg/X7r3PmuYKq)
## History
This project is an ongoing migration of a previous project (titled [VSP.NET](https://github.com/roflmuffin/vspdotnet)) whereby a scripting layer was added to a Valve Server Plugin for CSGO.
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.
## Philosophy
As a result, there are a few key philosophies and trade-offs that drive the project.
- Only 64 bit is supported.
- .NET only supports x64 on Linux; CSGO previously only supported 32 bit servers, but CS2 supports 64 bit on Linux.
- Supporting both platforms is a lot of work for 1 person, so there are no real plans to support Windows.
## Install
Development builds are currently available through GitHub actions, you can download the latest build from [there](https://github.com/roflmuffin/CounterStrikeSharp/actions/workflows/cmake-single-platform.yml).
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)
@@ -38,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
@@ -67,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;
}
@@ -82,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");
}
}
```
@@ -94,7 +92,7 @@ I've also used the scripting context & native system that is implemented in Five
## How to Build
Building requires CMake on Linux.
Building requires CMake.
Clone the repository

View File

@@ -0,0 +1,23 @@
{
"#css/admin": {
"flags": [
"@css/reservation",
"@css/generic",
"@css/kick",
"@css/ban",
"@css/unban",
"@css/vip",
"@css/slay",
"@css/changemap",
"@css/cvar",
"@css/config",
"@css/chat",
"@css/vote",
"@css/password",
"@css/rcon",
"@css/cheats",
"@css/root"
],
"immunity": 100
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
Place your plugin configurations here.
TestPlugin/TestPlugin.json
AnotherPlugin/AnotherPlugin.json

View File

@@ -0,0 +1,168 @@
{
"UTIL_ClientPrintAll": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x08\\x48\\x89\\x6C\\x24\\x10\\x48\\x89\\x74\\x24\\x18\\x57\\x48\\x81\\xEC\\x70\\x01\\x2A\\x2A\\x8B\\xE9",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xD7\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x41\\x89\\xFD"
}
},
"ClientPrint": {
"signatures": {
"library": "server",
"windows": "\\x48\\x85\\xC9\\x0F\\x84\\x2A\\x2A\\x2A\\x2A\\x48\\x8B\\xC4\\x48\\x89\\x58\\x18",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xCF\\x41\\x56\\x49\\x89\\xD6\\x41\\x55\\x41\\x89\\xF5\\x41\\x54\\x4C\\x8D\\xA5\\xA0\\xFE\\xFF\\xFF"
}
},
"CCSPlayerController_SwitchTeam": {
"signatures": {
"library": "server",
"windows": "\\x40\\x56\\x57\\x48\\x81\\xEC\\x2A\\x2A\\x2A\\x2A\\x48\\x8B\\xF9\\x8B\\xF2\\x8B\\xCA",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x55\\x49\\x89\\xFD\\x89\\xF7"
}
},
"CCSPlayerController_ChangeTeam": {
"offsets": {
"windows": 90,
"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",
"windows": "\\x48\\x89\\x5C\\x24\\x18\\x48\\x89\\x74\\x24\\x20\\x55\\x57\\x41\\x54\\x41\\x56\\x41\\x57\\x48\\x8D\\x6C\\x24\\xD9",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x49\\x89\\xCE\\x41\\x55\\x49\\x89\\xF5\\x41\\x54\\x49\\x89\\xD4"
}
},
"UTIL_Remove": {
"signatures": {
"library": "server",
"windows": "\\x48\\x85\\xC9\\x74\\x2A\\x48\\x8B\\xD1\\x48\\x8B\\x0D\\x2A\\x2A\\x2A\\x2A",
"linux": "\\x48\\x89\\xFE\\x48\\x85\\xFF\\x74\\x2A\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x48"
}
},
"Host_Say": {
"signatures": {
"library": "server",
"windows": "\\x44\\x89\\x4C\\x24\\x20\\x44\\x88\\x44\\x24\\x18",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xFF\\x41\\x56\\x41\\x55\\x41\\x54\\x4D\\x89\\xC4"
}
},
"CBaseModelEntity_SetModel": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x48\\x89\\x7C\\x24\\x20\\x55\\x48\\x8B\\xEC\\x48\\x83\\xEC\\x50",
"linux": "\\x55\\x48\\x89\\xF2\\x48\\x89\\xE5\\x41\\x54\\x49\\x89\\xFC\\x48\\x8D\\x7D\\xE0\\x48\\x83\\xEC\\x18\\x48\\x8D\\x05\\xA5"
}
},
"CCSPlayer_ItemServices_DropActivePlayerWeapon": {
"offsets": {
"windows": 20,
"linux": 19
}
},
"CCSPlayer_ItemServices_RemoveWeapons": {
"offsets": {
"windows": 21,
"linux": 20
}
},
"CGameSceneNode_GetSkeletonInstance": {
"offsets": {
"windows": 8,
"linux": 8
}
},
"CCSGameRules_TerminateRound": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\xC4\\x4C\\x89\\x48\\x20\\x55\\x56",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x41\\x55\\x49\\x89\\xFD\\x41\\x54\\x53\\x48\\x81\\xEC\\xE8"
}
},
"UTIL_CreateEntityByName": {
"signatures": {
"library": "server",
"windows": "\\x48\\x83\\xEC\\x48\\xC6\\x44\\x24\\x30\\x00\\x4C\\x8B\\xC1",
"linux": "\\x48\\x8D\\x05\\xC9\\xC2\\xBC"
}
},
"CBaseEntity_DispatchSpawn": {
"signatures": {
"library": "server",
"windows": "\\x48\\x89\\x5C\\x24\\x10\\x57\\x48\\x83\\xEC\\x30\\x48\\x8B\\xDA\\x48\\x8B\\xF9\\x48\\x85\\xC9",
"linux": "\\x48\\x85\\xFF\\x74\\x4B\\x55\\x48\\x89\\xE5\\x41\\x56"
}
},
"LegacyGameEventListener": {
"signatures": {
"library": "server",
"windows": "\\x48\\x8B\\x15\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xD2\\x74\\x2A\\x85\\xC9\\x74",
"linux": "\\x48\\x8B\\x05\\x2A\\x2A\\x2A\\x2A\\x48\\x85\\xC0\\x74\\x2A\\x83\\xFF\\x3F\\x76\\x2A\\x31\\xC0"
}
},
"CBasePlayerPawn_CommitSuicide": {
"offsets": {
"windows": 356,
"linux": 356
}
},
"CBaseEntity_Teleport": {
"offsets": {
"windows": 148,
"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,
"linux": 80
}
},
"GameEventManager": {
"offsets": {
"windows": 91,
"linux": 91
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
ActionType_t
AimMatrixBlendMode
AmmoFlags_t
AmmoPosition_t
AnimLoopMode_t
AnimNodeNetworkMode
AnimParamButton_t
AnimParamNetworkSetting
AnimParamType_t
AnimPoseControl
AnimScriptType
AnimVRFinger_t
AnimVRHandMotionRange_t
AnimVRHand_t
AnimValueSource
AnimVectorSource
AnimVrBoneTransformSource_t
AnimVrFingerSplay_t
AnimationProcessingType_t
AnimationSnapshotType_t
AnimationType_t
BBoxVolumeType_t
BaseExplosionTypes_t
BeamClipStyle_t
BeamType_t
BeginDeathLifeStateTransition_t
BinaryNodeChildOption
BinaryNodeTiming
Blend2DMode
BlendKeyType
BloomBlendMode_t
BlurFilterType_t
BoneMaskBlendSpace
BoneTransformSpace_t
BrushSolidities_e
CAnimationGraphVisualizerPrimitiveType
CLogicBranchList__LogicBranchListenerLastState_t
CRR_Response__ResponseEnum_t
CSPlayerBlockingUseAction_t
CSPlayerState
CSWeaponCategory
CSWeaponMode
CSWeaponSilencerType
CSWeaponState_t
CSWeaponType
CanPlaySequence_t
ChatIgnoreType_t
ChickenActivity
ChoiceBlendMethod
ChoiceChangeMethod
ChoiceMethod
Class_T
ClosestPointTestType_t
CommandEntitySpecType_t
CommandExecMode_t
CompMatPropertyMutatorConditionType_t
CompMatPropertyMutatorType_t
CompositeMaterialInputContainerSourceType_t
CompositeMaterialInputLooseVariableType_t
CompositeMaterialInputTextureType_t
CompositeMaterialMatchFilterType_t
CompositeMaterialVarSystemVar_t
DamageTypes_t
DampingSpeedFunction
DebugOverlayBits_t
Detail2Combo_t
DetailCombo_t
DisableShadows_t
Disposition_t
DoorState_t
EDemoBoneSelectionMode
EGrenadeThrowState
EInButtonState
EKillTypes_t
ELayoutNodeType
EOverrideBlockLOS_t
EStyleNodeType
EntFinderMethod_t
EntityDisolveType_t
EntityDormancyType_t
EntityIOTargetType_t
EntitySubclassScope_t
Explosions
FacingMode
FieldNetworkOption
FixAngleSet_t
FlexOpCode_t
FootFallTagFoot_t
FootLockSubVisualization
FootPinningTimingSource
FootstepLandedFootSoundType_t
ForcedCrouchState_t
FuncDoorSpawnPos_t
FuseVariableAccess_t
FuseVariableType_t
GameAnimEventIndex_t
GrenadeType_t
HierarchyType_t
HitGroup_t
HitboxLerpType_t
HorizJustification_e
Hull_t
IChoreoServices__ChoreoState_t
IChoreoServices__ScriptState_t
IKChannelMode
IKSolverType
IKTargetCoordinateSystem
IKTargetSource
IkEndEffectorType
IkTargetType
InheritableBoolType_t
InputBitMask_t
InputLayoutVariation_t
ItemFlagTypes_t
JiggleBoneSimSpace
JointAxis_t
JointMotion_t
JumpCorrectionMethod
LatchDirtyPermission_t
LayoutPositionType_e
LessonPanelLayoutFileTypes_t
LifeState_t
MaterialProxyType_t
Materials
MatterialAttributeTagType_t
MedalRank_t
MeshDrawPrimitiveFlags_t
MissingParentInheritBehavior_t
ModelBoneFlexComponent_t
ModelConfigAttachmentType_t
ModelSkeletonData_t__BoneFlags_t
ModifyDamageReturn_t
MoodType_t
MorphBundleType_t
MorphFlexControllerRemapType_t
MoveCollide_t
MoveLinearAuthoredPos_t
MoveMountingAmount_t
MoveType_t
NavAttributeEnum
NavDirType
ObjectTypeFlags_t
ObserverInterpState_t
ObserverMode_t
OnFrame
PFNoiseModifier_t
PFNoiseTurbulence_t
PFNoiseType_t
PFuncVisualizationType_t
ParticleAlphaReferenceType_t
ParticleAttachment_t
ParticleCollisionMode_t
ParticleColorBlendMode_t
ParticleColorBlendType_t
ParticleControlPointAxis_t
ParticleDepthFeatheringMode_t
ParticleDetailLevel_t
ParticleDirectionNoiseType_t
ParticleEndcapMode_t
ParticleFalloffFunction_t
ParticleFloatBiasType_t
ParticleFloatInputMode_t
ParticleFloatMapType_t
ParticleFloatRandomMode_t
ParticleFloatType_t
ParticleFogType_t
ParticleHitboxBiasType_t
ParticleHitboxDataSelection_t
ParticleImpulseType_t
ParticleLightBehaviorChoiceList_t
ParticleLightFogLightingMode_t
ParticleLightTypeChoiceList_t
ParticleLightUnitChoiceList_t
ParticleLightingQuality_t
ParticleLightnintBranchBehavior_t
ParticleModelType_t
ParticleOmni2LightTypeChoiceList_t
ParticleOrientationChoiceList_t
ParticleOrientationSetMode_t
ParticleOutputBlendMode_t
ParticleParentSetMode_t
ParticlePinDistance_t
ParticlePostProcessPriorityGroup_t
ParticleRotationLockType_t
ParticleSelection_t
ParticleSequenceCropOverride_t
ParticleSetMethod_t
ParticleSortingChoiceList_t
ParticleTextureLayerBlendType_t
ParticleTopology_t
ParticleTraceMissBehavior_t
ParticleTraceSet_t
ParticleTransformType_t
ParticleVRHandChoiceList_t
ParticleVecType_t
PerformanceMode_t
PermModelInfo_t__FlagEnum
PetGroundType_t
PlayerAnimEvent_t
PlayerConnectedState
PointTemplateClientOnlyEntityBehavior_t
PointTemplateOwnerSpawnGroupType_t
PointWorldTextJustifyHorizontal_t
PointWorldTextJustifyVertical_t
PointWorldTextReorientMode_t
PoseType_t
PropDoorRotatingOpenDirection_e
PropDoorRotatingSpawnPos_t
PulseInstructionCode_t
PulseMethodCallMode_t
PulseValueType_t
QuestProgress__Reason
RagdollPoseControl
RenderBufferFlags_t
RenderFx_t
RenderMode_t
RenderMultisampleType_t
RenderPrimitiveType_t
RenderSlotType_t
ResetCycleOption
RumbleEffect_t
ScalarExpressionType_t
SceneOnPlayerDeath_t
ScriptedConflictResponse_t
ScriptedMoveTo_t
ScriptedMoveType_t
ScriptedOnDeath_t
SelectorTagBehavior_t
SeqCmd_t
SeqPoseSetting_t
ShadowType_t
ShakeCommand_t
ShardSolid_t
ShatterDamageCause
ShatterGlassStressType
ShatterPanelMode
SimpleConstraintSoundProfile__SimpleConstraintsSoundProfileKeypoints_t
SolidType_t
SolveIKChainAnimNodeDebugSetting
SosActionSortType_t
SosActionStopType_t
SosEditItemType_t
SosGroupType_t
SoundEventStartType_t
SoundFlags_t
SpawnDebugOverrideState_t
SpawnDebugRestrictionOverrideState_t
SpawnPointCoopEnemy__BotDefaultBehavior_t
SpriteCardPerParticleScale_t
SpriteCardShaderType_t
SpriteCardTextureChannel_t
SpriteCardTextureType_t
StanceOverrideMode
StanceType_t
StandardLightingAttenuationStyle_t
StateActionBehavior
StepPhase
SubclassVDataChangeType_t
SurroundingBoundsType_t
TOGGLE_STATE
TRAIN_CODE
TakeDamageFlags_t
TextureRepetitionMode_t
ThreeState_t
TimelineCompression_t
Touch_t
TrackOrientationType_t
TrainOrientationType_t
TrainVelocityType_t
VMixChannelOperation_t
VMixFilterSlope_t
VMixFilterType_t
VMixLFOShape_t
VMixPannerType_t
VMixProcessorType_t
VMixSubgraphSwitchInterpolationType_t
VPhysXAggregateData_t__VPhysXFlagEnum_t
VPhysXBodyPart_t__VPhysXFlagEnum_t
VPhysXConstraintParams_t__EnumFlags0_t
VPhysXJoint_t__Flags_t
ValueRemapperHapticsType_t
ValueRemapperInputType_t
ValueRemapperMomentumType_t
ValueRemapperOutputType_t
ValueRemapperRatchetType_t
VectorExpressionType_t
VectorFloatExpressionType_t
VelocityMetricMode
VertJustification_e
ViewFadeMode_t
WaterLevel_t
WeaponAttackType_t
WeaponSound_t
WorldTextPanelHorizontalAlign_t
WorldTextPanelOrientation_t
WorldTextPanelVerticalAlign_t
attributeprovidertypes_t
doorCheck_e
fieldtype_t
filter_t
gear_slot_t
loadout_slot_t
navproperties_t
soundlevel_t
vote_create_failed_t

View File

@@ -0,0 +1,4 @@
Place plugins in this folder. Each plugin should be in its own subfolder, e.g.
TestPlugin/TestPlugin.dll
AnotherPlugin/AnotherPlugin.dll

View File

@@ -0,0 +1 @@
Place your source code for plugins here.

View File

@@ -1,5 +0,0 @@
"Metamod Plugin"
{
"alias" "counterstrikesharp"
"file" "addons/counterstrikesharp/bin/linuxsteamrt64/counterstrikesharp"
}

View File

@@ -1,47 +0,0 @@
{
"UTIL_ClientPrintAll": {
"signatures": {
"library": "server",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xD7\\x41\\x56\\x49\\x89\\xF6\\x41\\x55\\x41\\x89\\xFD"
}
},
"ClientPrint": {
"signatures": {
"library": "server",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x49\\x89\\xCF\\x41\\x56\\x49\\x89\\xD6\\x41\\x55\\x41\\x89\\xF5\\x41\\x54\\x4C\\x8D\\xA5\\xA0\\xFE\\xFF\\xFF"
}
},
"CCSPlayerController_SwitchTeam": {
"signatures": {
"library": "server",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x55\\x49\\x89\\xFD\\x89\\xF7"
}
},
"CCSPlayerController_ChangeTeam": {
"offsets": {
"linux": 89
}
},
"GiveNamedItem": {
"signatures": {
"library": "server",
"linux": "\\x55\\x48\\x89\\xE5\\x41\\x57\\x41\\x56\\x49\\x89\\xCE\\x41\\x55\\x49\\x89\\xF5\\x41\\x54\\x49\\x89\\xD4"
}
},
"UTIL_Remove": {
"signatures": {
"library": "server",
"linux": "\\x48\\x89\\xFE\\x48\\x85\\xFF\\x74\\x2A\\x48\\x8D\\x05\\x2A\\x2A\\x2A\\x2A\\x48"
}
},
"CBasePlayerPawn_CommitSuicide": {
"offsets": {
"linux": 355
}
},
"CBaseEntity_Teleport": {
"offsets": {
"linux": 147
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
---
title: Defining Admins
description: A guide on how to define admins for CounterStrikeSharp.
---
## Admin Framework
CounterStrikeSharp has a basic framework which allows plugin developers to assign permissions to commands. When CSS is initialized, a list of admins are loaded from `configs/admins.json`.
## Adding Admins
Adding an Admin is as simple as creating a new entry in the `configs/admins.json` file. The important things you need to declare are the SteamID identifier and the permissions they have. CounterStrikeSharp will do all the heavy-lifting to decipher your SteamID. If you're familiar with SourceMod, permission definitions are slightly different as they're defined by an array of strings instead of a string of characters.
```json
{
"ZoNiCaL": {
"identity": "76561198808392634",
"flags": ["@css/changemap", "@css/generic"]
},
"another ZoNiCaL": {
"identity": "STEAM_0:1:1",
"flags": ["@css/generic"]
}
}
```
You can also manually assign permissions to players in code with `AdminManager.AddPlayerPermissions` and `AdminManager.RemovePlayerPermissions`. These changes are not saved to `configs/admins.json`.
:::note
All user permissions MUST start with an at-symbol @ character, otherwise CounterStrikeSharp will not recognize the permission.
:::
### Standard Permissions
Because the flag system is just a list of strings associated with a user, there is no real list of letter based flags like there was previously in something like SourceMod. This means as a plugin author you can declare your own flags, scoped with an `@` symbol, like `@roflmuffin/guns`, which might be the permission to allow spawning of guns in a given command.
However there is a somewhat standardized list of flags that it is advised you use if you are adding functionality that aligns with their purpose, and these are based on the original SourceMod flags:
```shell
@css/reservation # Reserved slot access.
@css/generic # Generic admin.
@css/kick # Kick other players.
@css/ban # Ban other players.
@css/unban # Remove bans.
@css/vip # General vip status.
@css/slay # Slay/harm other players.
@css/changemap # Change the map or major gameplay features.
@css/cvar # Change most cvars.
@css/config # Execute config files.
@css/chat # Special chat privileges.
@css/vote # Start or create votes.
@css/password # Set a password on the server.
@css/rcon # Use RCON commands.
@css/cheats # Change sv_cheats or use cheating commands.
@css/root # Magically enables all flags and ignores immunity values.
```
:::note
CounterStrikeSharp does not implement traditional admin command such as `!slay`, `!kick`, and `!ban`. It is up to individual plugins to implement these commands.
:::

View File

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

View File

@@ -0,0 +1,42 @@
---
title: Defining Admin Groups
description: A guide on how to define admin groups for CounterStrikeSharp.
---
## Adding Groups
Groups can be created to group a series of permissions together under one tag. They are defined in `configs/admin_groups.json`. The important things you need to declare is the name of the group and the permissions they have.
```json
"#css/simple-admin": {
"flags": [
"@css/generic",
"@css/reservation",
"@css/ban",
"@css/slay",
]
}
```
You can add admins to groups using the `groups` array in `configs/admins.json`
```json
{
"erikj": {
"identity": "76561198808392634",
"flags": ["@mycustomplugin/admin"],
"groups": ["#css/simple-admin"]
},
"Another erikj": {
"identity": "STEAM_0:1:1",
"flags": ["@mycustomplugin/admin"],
"groups": ["#css/simple-admin"]
}
}
```
:::note
All group names MUST start with a hashtag # character, otherwise CounterStrikeSharp won't recognize the group.
:::
Admins can be assigned to multiple groups and they will inherit their flags. You can manually assign groups to players in code with `AdminManager.AddPlayerToGroup` and `AdminManager.RemovePlayerFromGroup`.

View File

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

View File

@@ -64,3 +64,39 @@ Command String: custom_command "Test Quoted" 5 13
First Argument: custom_command
Second Argument: Test Quoted
```
## Helper Attribute
CounterStrikeSharp provides the `CommandHelper` attribute for Command methods (function callback) to simplify the process of checking for the correct amount of arguments and to restrict commands to being executed by the server console or by players (or both!).
```csharp
[ConsoleCommand("freeze", "Freezes a client.")]
[CommandHelper(minArgs: 1, usage: "[target]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
...
}
```
If a client tries to execute the command without the `[target]` argument, it will print a message to them in chat:
```shell
[CSS] Expected usage: "!freeze [target]".
```
If a command is executed by the wrong user, it will print a message to them:
```shell
[CSS] This command can only be executed by clients.
```
Valid `CommandUsage` values:
```csharp
public enum CommandUsage
{
CLIENT_AND_SERVER = 0,
CLIENT_ONLY,
SERVER_ONLY
}
```

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

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

View File

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

View File

@@ -0,0 +1,26 @@
---
title: Core Configuration
description: Summary for core configuration values
---
## PublicChatTrigger
List of characters to use for public chat triggers.
## SilentChatTrigger
List of characters to use for silent chat triggers.
## FollowCS2ServerGuidelines
Per [CS2 Server Guidelines](https://blog.counter-strike.net/index.php/server_guidelines/), certain plugin
functionality will trigger all of the game server owner's Game Server Login Tokens
(GSLTs) to get banned when executed on a Counter-Strike 2 game server.
Enabling this option will block plugins from using functionality that is known to cause this.
This option only has any effect on CS2. Note that this does NOT guarantee that you cannot
receive a ban.
:::note
Disable this option at your own risk.
:::

View File

@@ -19,7 +19,9 @@
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj" />
<ProjectReference Include="..\..\managed\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />

1
libraries/DynoHook Submodule

Submodule libraries/DynoHook added at d7f8ebb059

1
libraries/asmjit Submodule

Submodule libraries/asmjit added at 0dd16b0a98

24596
libraries/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,20 @@
include("makefiles/shared.cmake")
#SET(_ITERATOR_DEBUG_LEVEL 2)
#set(_ITERATOR_DEBUG_LEVEL 2)
add_definitions(-D_LINUX -DPOSIX -DLINUX -DGNUC -DCOMPILER_GCC -DPLATFORM_64BITS)
#Add_Definitions(-DCOMPILER_MSVC -DCOMPILER_MSVC32 -D_WIN32 -D_WINDOWS -D_ALLOW_KEYWORD_MACROS -D__STDC_LIMIT_MACROS)
# Set(CMAKE_CXX_FLAGS_RELEASE "/D_NDEBUG /MD /wd4005 /MP")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstrnicmp=strncasecmp -D_snprintf=snprintf")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dstrnicmp=strncasecmp -D_snprintf=snprintf")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp")
# Warnings
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-uninitialized -Wno-switch -Wno-unused")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor -Wno-overloaded-virtual")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-conversion-null -Wno-write-strings")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-uninitialized -Wno-switch -Wno-unused")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor -Wno-overloaded-virtual")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-conversion-null -Wno-write-strings")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
# Others
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fno-threadsafe-statics -v -fvisibility=default")
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
@@ -31,4 +27,5 @@ SET(
dyncall_s
distorm
funchook-static
dynohook
)

View File

@@ -0,0 +1,10 @@
if (WIN32)
set(COUNTERSTRIKESHARP_VDF_PLATFORM "win64")
else()
set(COUNTERSTRIKESHARP_VDF_PLATFORM "linuxsteamrt64")
endif()
configure_file(
${CMAKE_CURRENT_LIST_DIR}/counterstrikesharp.vdf.in
${PROJECT_SOURCE_DIR}/configs/addons/metamod/counterstrikesharp.vdf
)

View File

@@ -0,0 +1,5 @@
"Metamod Plugin"
{
"alias" "counterstrikesharp"
"file" "addons/counterstrikesharp/bin/${COUNTERSTRIKESHARP_VDF_PLATFORM}/counterstrikesharp"
}

View File

@@ -1,40 +1,58 @@
Set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
"Only do Release and Debug"
FORCE
if (UNIX AND NOT APPLE)
set(LINUX TRUE)
endif()
if (WIN32 AND NOT MSVC)
message(FATAL "MSVC restricted.")
endif()
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
"Only do Release and Debug"
FORCE
)
# 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)
Set(METAMOD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/metamod-source)
set(SOURCESDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/hl2sdk-cs2)
set(METAMOD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libraries/metamod-source)
Set(SOURCESDK ${SOURCESDK_DIR}/${BRANCH})
Set(SOURCESDK_LIB ${SOURCESDK}/lib)
set(SOURCESDK ${SOURCESDK_DIR}/${BRANCH})
set(SOURCESDK_LIB ${SOURCESDK}/lib)
add_definitions(-DMETA_IS_SOURCE2)
include_directories(
${SOURCESDK}
${SOURCESDK}/common
${SOURCESDK}/game/shared
${SOURCESDK}/game/server
${SOURCESDK}/public
${SOURCESDK}/public/engine
${SOURCESDK}/public/mathlib
${SOURCESDK}/public/tier0
${SOURCESDK}/public/tier1
${SOURCESDK}/public/entity2
${SOURCESDK}/public/game/server
${SOURCESDK}/public/entity2
${METAMOD_DIR}/core
${METAMOD_DIR}/core/sourcehook
libraries/dyncall/dynload
libraries/dyncall/dyncall
libraries/spdlog/include
libraries/tl
libraries/funchook/include
libraries
${SOURCESDK}
${SOURCESDK}/thirdparty/protobuf-3.21.8/src
${SOURCESDK}/common
${SOURCESDK}/game/shared
${SOURCESDK}/game/server
${SOURCESDK}/public
${SOURCESDK}/public/engine
${SOURCESDK}/public/mathlib
${SOURCESDK}/public/tier0
${SOURCESDK}/public/tier1
${SOURCESDK}/public/entity2
${SOURCESDK}/public/game/server
${SOURCESDK}/public/entity2
${METAMOD_DIR}/core
${METAMOD_DIR}/core/sourcehook
libraries/dyncall/dynload
libraries/dyncall/dyncall
libraries/spdlog/include
libraries/tl
libraries/funchook/include
libraries/DynoHook/src
libraries
)
Project(counterstrikesharp C CXX)
include(${CMAKE_CURRENT_LIST_DIR}/metamod/configure_metamod.cmake)
if (LINUX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()

View File

@@ -0,0 +1,21 @@
add_definitions(
-DCOMPILER_MSVC -DCOMPILER_MSVC64 -D_WIN32 -D_WINDOWS -D_ALLOW_KEYWORD_MACROS -D__STDC_LIMIT_MACROS
-D_CRT_SECURE_NO_WARNINGS=1 -D_CRT_SECURE_NO_DEPRECATE=1 -D_CRT_NONSTDC_NO_DEPRECATE=1
)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819 /wd4828 /wd5033 /permissive- /utf-8 /wd4005 /MP")
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF")
set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
${SOURCESDK_LIB}/public/win64/tier0.lib
${SOURCESDK_LIB}/public/win64/tier1.lib
${SOURCESDK_LIB}/public/win64/interfaces.lib
${SOURCESDK_LIB}/public/win64/mathlib.lib
spdlog
dynload_s
dyncall_s
distorm
funchook-static
dynohook
)

View File

@@ -30,7 +30,7 @@ namespace CounterStrikeSharp.API.Core
}
}
public static IntPtr AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
public static void AddCommand(string name, string description, bool serveronly, int flags, InputArgument callback){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(name);
@@ -41,7 +41,6 @@ namespace CounterStrikeSharp.API.Core
ScriptContext.GlobalScriptContext.SetIdentifier(0x807C6B9C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
return (IntPtr)ScriptContext.GlobalScriptContext.GetResult(typeof(IntPtr));
}
}
@@ -158,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();
@@ -553,6 +602,17 @@ namespace CounterStrikeSharp.API.Core
}
}
public static void FireEventToClient(IntPtr gameevent, int clientindex){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(gameevent);
ScriptContext.GlobalScriptContext.Push(clientindex);
ScriptContext.GlobalScriptContext.SetIdentifier(0x40B7C06C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static string GetEventName(IntPtr gameevent){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
@@ -793,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

@@ -24,10 +24,15 @@ 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
{
@@ -49,6 +54,7 @@ namespace CounterStrikeSharp.API.Core
public string ModulePath { get; internal set; }
public string ModuleDirectory => Path.GetDirectoryName(ModulePath);
public ILogger Logger { get; set; }
public virtual void Load(bool hotReload)
{
@@ -154,19 +160,78 @@ namespace CounterStrikeSharp.API.Core
{
var wrappedHandler = new Action<int, IntPtr>((i, ptr) =>
{
if (i == -1)
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
handler?.Invoke(null, new CommandInfo(ptr, null));
return;
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1));
var command = new CommandInfo(ptr, entity);
handler?.Invoke(entity.IsValid ? entity : null, command);
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
{
switch (helperAttribute.WhoCanExcecute)
{
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
case CommandUsage.CLIENT_ONLY:
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; } break;
case CommandUsage.SERVER_ONLY:
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; } break;
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
}
// Technically the command itself counts as the first argument,
// but we'll just ignore that for this check.
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
{
command.ReplyToCommand($"[CSS] Expected usage: \"!{command.ArgByIndex(0)} {helperAttribute.Usage}\".");
return;
}
}
handler?.Invoke(caller, command);
});
var methodInfo = handler?.GetMethodInfo();
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
var subscriber = new CallbackSubscriber(handler, wrappedHandler, () => { RemoveCommand(name, handler); });
NativeAPI.AddCommand(name, description, false, (int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, subscriber.GetInputArgument());
NativeAPI.AddCommand(name, description, (helperAttribute?.WhoCanExcecute == CommandUsage.SERVER_ONLY),
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, subscriber.GetInputArgument());
CommandHandlers[handler] = subscriber;
}
@@ -174,14 +239,10 @@ namespace CounterStrikeSharp.API.Core
{
var wrappedHandler = new Func<int, IntPtr, HookResult>((i, ptr) =>
{
if (i == -1)
{
return HookResult.Continue;
}
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1));
var command = new CommandInfo(ptr, entity);
return handler.Invoke(entity.IsValid ? entity : null, command);
var command = new CommandInfo(ptr, caller);
return handler.Invoke(caller, command);
});
var subscriber = new CallbackSubscriber(handler, wrappedHandler, () => { RemoveCommandListener(name, handler, mode); });
@@ -255,7 +316,8 @@ namespace CounterStrikeSharp.API.Core
.Select(p => p.GetCustomAttribute<CastFromAttribute>()?.Type)
.ToArray();
Console.WriteLine($"Registering listener for {listenerName} with {parameterTypes.Length}");
GlobalContext.Instance.Logger.LogDebug("Registering listener for {ListenerName} with {ParameterCount} parameters",
listenerName, parameterTypes.Length);
var wrappedHandler = new Action<ScriptContext>(context =>
{
@@ -300,6 +362,31 @@ namespace CounterStrikeSharp.API.Core
this.RegisterConsoleCommandAttributeHandlers(instance);
}
public void InitializeConfig(object instance, Type pluginType)
{
Type[] interfaces = pluginType.GetInterfaces();
Func<Type, bool> predicate = (i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPluginConfig<>));
// if the plugin has set a configuration type (implements IPluginConfig<>)
if (interfaces.Any(predicate))
{
// IPluginConfig<>
Type @interface = interfaces.Where(predicate).FirstOrDefault()!;
// custom config type passed as generic
Type genericType = @interface!.GetGenericArguments().First();
var config = typeof(ConfigManager)
.GetMethod("Load")!
.MakeGenericMethod(genericType)
.Invoke(null, new object[] { Path.GetFileName(ModuleDirectory) }) as IBasePluginConfig;
// we KNOW that we can do this "safely"
pluginType.GetRuntimeMethod("OnConfigParsed", new Type[] { genericType })
.Invoke(instance, new object[] { config });
}
}
/// <summary>
/// Registers all game event handlers that are decorated with the `[GameEventHandler]` attribute.
/// </summary>

View File

@@ -0,0 +1,32 @@
/*
* 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.Text.Json.Serialization;
namespace CounterStrikeSharp.API.Core
{
public interface IBasePluginConfig
{
[JsonPropertyName("ConfigVersion")]
int Version { get; set; }
}
public class BasePluginConfig : IBasePluginConfig
{
[JsonPropertyName("ConfigVersion")]
public virtual int Version { get; set; } = 1;
}
}

View File

@@ -1,7 +1,37 @@
using System;
using System.Runtime.InteropServices;
namespace CounterStrikeSharp.API.Core;
public static class Constants
{
public const string ROOT_BINARY_PATH = "/bin/linuxsteamrt64/";
public const string GAME_BINARY_PATH = "/csgo/bin/linuxsteamrt64/";
public static string ModulePrefix { get; }
public static string ModuleSuffix { get; }
public static string RootBinaryPath { get; }
public static string GameBinaryPath { get; }
static Constants()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ModulePrefix = "";
ModuleSuffix = ".dll";
GameBinaryPath = "/csgo/bin/win64/";
RootBinaryPath = "/bin/win64";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
ModulePrefix = "lib";
ModuleSuffix = ".so";
GameBinaryPath = "/csgo/bin/linuxsteamrt64/";
RootBinaryPath = "/bin/linuxsteamrt64/";
}
else
{
throw new NotSupportedException($"""Current platform is "{RuntimeInformation.OSDescription}", but does not supported.""");
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* 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.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.Logging;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
/// <summary>
/// Serializable instance of the CoreConfig
/// </summary>
internal sealed partial class CoreConfigData
{
[JsonPropertyName("PublicChatTrigger")] public IEnumerable<string> PublicChatTrigger { get; set; } = new HashSet<string>() { "!" };
[JsonPropertyName("SilentChatTrigger")] public IEnumerable<string> SilentChatTrigger { get; set; } = new HashSet<string>() { "/" };
[JsonPropertyName("FollowCS2ServerGuidelines")] public bool FollowCS2ServerGuidelines { get; set; } = true;
}
/// <summary>
/// Configuration related to the Core API.
/// </summary>
public static partial class CoreConfig
{
/// <summary>
/// List of characters to use for public chat triggers.
/// </summary>
public static IEnumerable<string> PublicChatTrigger => _coreConfig.PublicChatTrigger;
/// <summary>
/// List of characters to use for silent chat triggers.
/// </summary>
public static IEnumerable<string> SilentChatTrigger => _coreConfig.SilentChatTrigger;
/// <summary>
/// <para>
/// Per <see href="http://blog.counter-strike.net/index.php/server_guidelines/"/>, certain plugin
/// functionality will trigger all of the game server owner's Game Server Login Tokens
/// (GSLTs) to get banned when executed on a Counter-Strike 2 game server.
/// </para>
///
/// <para>
/// Enabling this option will block plugins from using functionality that is known to cause this.
///
/// Note that this does NOT guarantee that you cannot
///
/// receive a ban.
/// </para>
///
/// <para>
/// Disable this option at your own risk.
/// </para>
/// </summary>
public static bool FollowCS2ServerGuidelines => _coreConfig.FollowCS2ServerGuidelines;
}
public static partial class CoreConfig
{
private static CoreConfigData _coreConfig = new CoreConfigData();
// TODO: ServiceCollection
private static ILogger _logger = CoreLogging.Factory.CreateLogger("CoreConfig");
static CoreConfig()
{
CommandUtils.AddStandaloneCommand("css_core_reload", "Reloads the core configuration file.", ReloadCoreConfigCommand);
}
[RequiresPermissions("@css/config")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadCoreConfigCommand(CCSPlayerController? player, CommandInfo command)
{
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
Load(Path.Combine(rootDir.FullName, "configs", "core.json"));
}
public static void Load(string coreConfigPath)
{
if (!File.Exists(coreConfigPath))
{
_logger.LogWarning("Core configuration could not be found at path \"{CoreConfigPath}\", fallback values will be used.", coreConfigPath);
return;
}
try
{
var data = JsonSerializer.Deserialize<CoreConfigData>(File.ReadAllText(coreConfigPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip });
if (data != null)
{
_coreConfig = data;
}
_logger.LogInformation("Successfully loaded core configuration.");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load core configuration, fallback values will be used");
}
}
}
}

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);
GlobalContext.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();
GlobalContext.Instance.Logger.LogDebug("Removing function/callback reference: {Reference}", reference);
}
}
}

View File

@@ -1,17 +1,17 @@
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 Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core;
class LoadedGameData
{
[JsonPropertyName("signatures")] public Signatures? Signatures { get; set; }
[JsonPropertyName("offsets")] public Offsets? Offsets { get; set; }
}
@@ -37,36 +37,70 @@ public static class GameData
public static void Load(string gameDataPath)
{
_methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(gameDataPath));
try
{
_methods = JsonSerializer.Deserialize<Dictionary<string, LoadedGameData>>(File.ReadAllText(gameDataPath))!;
Console.WriteLine($"Loaded game data with {_methods.Count} methods.");
GlobalContext.Instance.Logger.LogInformation("Loaded game data with {Count} methods.", _methods.Count);
}
catch (Exception ex)
{
GlobalContext.Instance.Logger.LogError(ex, "Failed to load game data");
}
}
public static string GetSignature(string key)
{
if (!_methods.ContainsKey(key)) throw new Exception($"Method {key} not found in gamedata.json");
if (_methods[key].Signatures == null) throw new Exception($"No signatures found for {key} in gamedata.json");
var methodMetadata = _methods[key];
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
GlobalContext.Instance.Logger.LogDebug("Getting signature: {Key}", key);
if (!_methods.ContainsKey(key))
{
return methodMetadata.Signatures!.Linux;
throw new ArgumentException($"Method {key} not found in gamedata.json");
}
return methodMetadata.Signatures!.Windows;
var methodMetadata = _methods[key];
if (methodMetadata.Signatures == null)
{
throw new InvalidOperationException($"No signatures found for {key} in gamedata.json");
}
string signature;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
signature = methodMetadata.Signatures?.Linux ?? throw new InvalidOperationException($"No Linux signature for {key} in gamedata.json");
}
else
{
signature = methodMetadata.Signatures?.Windows ?? throw new InvalidOperationException($"No Windows signature for {key} in gamedata.json");
}
return signature;
}
public static int GetOffset(string key)
{
if (!_methods.ContainsKey(key)) throw new Exception($"Method {key} not found in gamedata.json");
if (_methods[key].Offsets == null) throw new Exception($"No offsets found for {key} in gamedata.json");
var methodMetadata = _methods[key];
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
if (!_methods.ContainsKey(key))
{
return methodMetadata.Offsets!.Linux;
throw new Exception($"Method {key} not found in gamedata.json");
}
return methodMetadata.Offsets!.Windows;
var methodMetadata = _methods[key];
if (methodMetadata.Offsets == null)
{
throw new Exception($"No offsets found for {key} in gamedata.json");
}
int offset;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
offset = methodMetadata.Offsets?.Linux ?? throw new InvalidOperationException($"No Linux offset for {key} in gamedata.json");
}
else
{
offset = methodMetadata.Offsets?.Windows ?? throw new InvalidOperationException($"No Windows offset for {key} in gamedata.json");
}
return offset;
}
}

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,16 +20,25 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using CounterStrikeSharp.API.Core.Logging;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace CounterStrikeSharp.API.Core
{
public sealed class GlobalContext
{
private static GlobalContext _instance = null;
public ILogger Logger { get; }
public static GlobalContext Instance => _instance;
public static string RootDirectory => _instance.rootDir.FullName;
private DirectoryInfo rootDir;
private readonly List<PluginContext> _loadedPlugins = new();
@@ -38,6 +47,9 @@ namespace CounterStrikeSharp.API.Core
{
rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
_instance = this;
Logger = CoreLogging.Factory.CreateLogger("Core");
Logger.LogInformation("CounterStrikeSharp is starting up...");
}
~GlobalContext()
@@ -57,32 +69,52 @@ namespace CounterStrikeSharp.API.Core
}
public void InitGlobalContext()
{
Console.WriteLine("Loading GameData");
GameData.Load(Path.Combine(rootDir.FullName, "gamedata", "gamedata.json"));
var coreConfigPath = Path.Combine(rootDir.FullName, "configs", "core.json");
Logger.LogInformation("Loading CoreConfig from {Path}", coreConfigPath);
CoreConfig.Load(coreConfigPath);
for (int i = 1; i <= 9; i++)
var gameDataPath = Path.Combine(rootDir.FullName, "gamedata", "gamedata.json");
Logger.LogInformation("Loading GameData from {Path}", gameDataPath);
GameData.Load(gameDataPath);
var adminGroupsPath = Path.Combine(rootDir.FullName, "configs", "admin_groups.json");
Logger.LogInformation("Loading Admin Groups from {Path}", adminGroupsPath);
AdminManager.LoadAdminGroups(adminGroupsPath);
var adminPath = Path.Combine(rootDir.FullName, "configs", "admins.json");
Logger.LogInformation("Loading Admins from {Path}", adminPath);
AdminManager.LoadAdminData(adminPath);
var overridePath = Path.Combine(rootDir.FullName, "configs", "admin_overrides.json");
Logger.LogInformation("Loading Admin Command Overrides from {Path}", overridePath);
AdminManager.LoadCommandOverrides(overridePath);
AdminManager.MergeGroupPermsIntoAdmins();
for (var i = 1; i <= 9; i++)
{
AddCommand("css_" + i, "Command Key Handler", (player, info) =>
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);
}, false);
});
}
Console.WriteLine("Loading C# plugins...");
int pluginCount = LoadAllPlugins();
Console.WriteLine($"All managed modules were loaded. {pluginCount} plugins loaded.");
Logger.LogInformation("Loading C# plugins...");
var pluginCount = LoadAllPlugins();
Logger.LogInformation("All managed modules were loaded. {PluginCount} plugins loaded.", pluginCount);
RegisterPluginCommands();
}
public void LoadPlugin(string path)
private void LoadPlugin(string path)
{
var existingPlugin = FindPluginByModulePath(path);
if (existingPlugin != null)
{
throw new Exception("Plugin is already loaded.");
throw new FileLoadException("Plugin is already loaded.");
}
var plugin = new PluginContext(path, _loadedPlugins.Select(x => x.PluginId).DefaultIfEmpty(0).Max() + 1);
@@ -90,35 +122,36 @@ namespace CounterStrikeSharp.API.Core
_loadedPlugins.Add(plugin);
}
public int LoadAllPlugins()
private int LoadAllPlugins()
{
DirectoryInfo modules_directory_info;
DirectoryInfo modulesDirectoryInfo;
try
{
modules_directory_info = new DirectoryInfo(Path.Combine(rootDir.FullName, "plugins"));
modulesDirectoryInfo = new DirectoryInfo(Path.Combine(rootDir.FullName, "plugins"));
}
catch (Exception e)
{
Console.WriteLine(
"Unable to access .NET modules directory: " + e.GetType().ToString() + " " + e.Message);
Logger.LogError(e, "Error finding plugin path");
return 0;
}
DirectoryInfo[] proper_modules_directories;
DirectoryInfo[] properModulesDirectories;
try
{
proper_modules_directories = modules_directory_info.GetDirectories();
properModulesDirectories = modulesDirectoryInfo.GetDirectories();
}
catch
{
proper_modules_directories = new DirectoryInfo[0];
properModulesDirectories = Array.Empty<DirectoryInfo>();
}
var filePaths = proper_modules_directories
var filePaths = properModulesDirectories
.Where(d => d.GetFiles().Any((f) => f.Name == d.Name + ".dll"))
.Select(d => d.GetFiles().First((f) => f.Name == d.Name + ".dll").FullName)
.ToArray();
foreach (var path in filePaths)
{
try
@@ -127,7 +160,7 @@ namespace CounterStrikeSharp.API.Core
}
catch (Exception e)
{
Console.WriteLine($"Failed to load plugin {path} with error {e}");
Logger.LogError(e, "Failed to load plugin from {Path}", path);
}
}
@@ -165,7 +198,7 @@ namespace CounterStrikeSharp.API.Core
private PluginContext? FindPluginByIdOrName(string query)
{
PluginContext? plugin = null;
if (Int32.TryParse(query, out var pluginNumber))
{
@@ -178,21 +211,34 @@ namespace CounterStrikeSharp.API.Core
return plugin;
}
[RequiresPermissions("@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private void OnCSSCommand(CCSPlayerController? caller, CommandInfo info)
{
Utilities.ReplyToCommand(caller, " CounterStrikeSharp was created and is maintained by Michael \"roflmuffin\" Wilson.\n" +
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.", true);
" 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":
{
Utilities.ReplyToCommand(caller, $" List of all plugins currently loaded by CounterStrikeSharp: {_loadedPlugins.Count} plugins loaded.", true);
info.ReplyToCommand($" List of all plugins currently loaded by CounterStrikeSharp: {_loadedPlugins.Count} plugins loaded.", true);
foreach (var plugin in _loadedPlugins)
{
@@ -205,7 +251,7 @@ namespace CounterStrikeSharp.API.Core
sb.Append(" ");
sb.Append(plugin.Description);
}
Utilities.ReplyToCommand(caller, sb.ToString(), true);
info.ReplyToCommand(sb.ToString(), true);
}
@@ -216,7 +262,7 @@ namespace CounterStrikeSharp.API.Core
{
if (info.ArgCount < 2)
{
Utilities.ReplyToCommand(caller, "Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n", true);
info.ReplyToCommand("Valid usage: css_plugins start/load [relative plugin path || absolute plugin path] (e.g \"TestPlugin\", \"plugins/TestPlugin/TestPlugin.dll\")\n", true);
break;
}
@@ -231,16 +277,16 @@ namespace CounterStrikeSharp.API.Core
{
path = Path.Combine(rootDir.FullName, path);
}
try
{
LoadPlugin(path);
}
catch (Exception e)
{
Console.WriteLine($"Failed to load plugin {path} with error {e}");
Logger.LogError(e, "Failed to load plugin from {Path}", path);
}
break;
}
@@ -249,7 +295,7 @@ namespace CounterStrikeSharp.API.Core
{
if (info.ArgCount < 2)
{
Utilities.ReplyToCommand(caller, "Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n", true);
info.ReplyToCommand("Valid usage: css_plugins stop/unload [plugin name || #plugin id] (e.g \"TestPlugin\", \"1\")\n", true);
break;
}
@@ -257,7 +303,7 @@ namespace CounterStrikeSharp.API.Core
PluginContext? plugin = FindPluginByIdOrName(pluginIdentifier);
if (plugin == null)
{
Utilities.ReplyToCommand(caller, $"Could not unload plugin \"{pluginIdentifier}\")", true);
info.ReplyToCommand($"Could not unload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload();
@@ -270,7 +316,7 @@ namespace CounterStrikeSharp.API.Core
{
if (info.ArgCount < 2)
{
Utilities.ReplyToCommand(caller, "Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n", true);
info.ReplyToCommand("Valid usage: css_plugins restart/reload [plugin name || #plugin id] (e.g \"TestPlugin\", \"#1\")\n", true);
break;
}
@@ -279,7 +325,7 @@ namespace CounterStrikeSharp.API.Core
if (plugin == null)
{
Utilities.ReplyToCommand(caller, $"Could not reload plugin \"{pluginIdentifier}\")", true);
info.ReplyToCommand($"Could not reload plugin \"{pluginIdentifier}\")", true);
break;
}
plugin.Unload(true);
@@ -288,7 +334,7 @@ namespace CounterStrikeSharp.API.Core
}
default:
Utilities.ReplyToCommand(caller, "Valid usage: css_plugins [option]\n" +
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" +
@@ -299,35 +345,11 @@ namespace CounterStrikeSharp.API.Core
}
public void RegisterPluginCommands()
private void RegisterPluginCommands()
{
AddCommand("css", "Counter-Strike Sharp options.", OnCSSCommand, false);
AddCommand("css_plugins", "Counter-Strike Sharp plugin options.", OnCSSPluginCommand, true);
}
/**
* Temporary way for base CSS to add commands without a plugin context
*/
private void AddCommand(string name, string description, CommandInfo.CommandCallback handler, bool serverOnly)
{
var wrappedHandler = new Action<int, IntPtr>((i, ptr) =>
{
if (i == -1)
{
handler?.Invoke(null, new CommandInfo(ptr, null));
return;
}
if (serverOnly) return;
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1));
var command = new CommandInfo(ptr, entity);
handler?.Invoke(entity.IsValid ? entity : null, command);
});
var subscriber = new BasePlugin.CallbackSubscriber(handler, wrappedHandler, () => { });
NativeAPI.AddCommand(name, description, serverOnly, (int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND,
subscriber.GetInputArgument());
CommandUtils.AddStandaloneCommand("css", "Counter-Strike Sharp options.", OnCSSCommand);
CommandUtils.AddStandaloneCommand("css_plugins", "Counter-Strike Sharp plugin options.", OnCSSPluginCommand);
}
}
}

View File

@@ -64,7 +64,7 @@ namespace CounterStrikeSharp.API.Core
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
Console.WriteLine(e);
return 0;
}
}

View File

@@ -0,0 +1,28 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
namespace CounterStrikeSharp.API.Core
{
/// <summary>
/// An interface that describes a plugin configuration.
/// </summary>
public interface IPluginConfig<T> where T: IBasePluginConfig, new()
{
T Config { get; set; }
public void OnConfigParsed(T config);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
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.PluginType.Name);
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

@@ -6,22 +6,24 @@ namespace CounterStrikeSharp.API.Core;
public partial class CBaseEntity
{
public Vector AbsVelocity => Schema.GetDeclaredClass<Vector>(this.Handle, "CBaseEntity", "m_vecAbsVelocity");
public void Teleport(Vector position, QAngle angles, Vector velocity)
{
VirtualFunction.CreateVoid<IntPtr, IntPtr, IntPtr, IntPtr>(Handle, GameData.GetOffset("CBaseEntity_Teleport"))(
Handle, position.Handle, angles.Handle, velocity.Handle);
}
public void DispatchSpawn()
{
VirtualFunctions.CBaseEntity_DispatchSpawn(Handle, IntPtr.Zero);
}
/// <summary>
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsOrigin;
/// </summary>
public Vector? AbsOrigin => CBodyComponent?.SceneNode?.AbsOrigin;
/// <summary>
/// Shorthand for accessing an entity's CBodyComponent?.SceneNode?.AbsRotation;
/// </summary>
public QAngle? AbsRotation => CBodyComponent?.SceneNode?.AbsRotation;
}

View File

@@ -18,9 +18,10 @@ using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CGlowProperty
public partial class CBaseModelEntity
{
// m_bGlowing
public ref bool IsGlowing => ref Schema.GetRef<bool>(this.Handle, "CGlowProperty", "m_bGlowing");
}
public void SetModel(string model)
{
VirtualFunctions.SetModel(Handle, model);
}
}

View File

@@ -5,6 +5,11 @@ namespace CounterStrikeSharp.API.Core;
public partial class CBasePlayerPawn
{
/// <summary>
/// Force player suicide
/// </summary>
/// <param name="explode"></param>
/// <param name="force"></param>
public void CommitSuicide(bool explode, bool force)
{
VirtualFunction.CreateVoid<IntPtr, bool, bool>(Handle, GameData.GetOffset("CBasePlayerPawn_CommitSuicide"))(Handle, explode, force);

View File

@@ -0,0 +1,31 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSGameRules
{
/// <summary>
/// Terminate the round with the given delay and reason.
/// </summary>
public void TerminateRound(float delay, RoundEndReason roundEndReason)
{
VirtualFunctions.TerminateRound(Handle, roundEndReason, delay);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
@@ -24,9 +25,20 @@ public partial class CCSPlayerController
return VirtualFunctions.GiveNamedItem(PlayerPawn.Value.ItemServices.Handle, item, 0, 0, 0, 0);
}
public IntPtr GiveNamedItem(CsItem item)
{
string? itemString = EnumUtils.GetEnumMemberAttributeValue(item);
if (string.IsNullOrWhiteSpace(itemString))
{
return IntPtr.Zero;
}
return this.GiveNamedItem(itemString);
}
public void PrintToConsole(string message)
{
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, message);
NativeAPI.PrintToConsole((int)EntityIndex.Value.Value, $"{message}\n\0");
}
public void PrintToChat(string message)
@@ -38,6 +50,71 @@ public partial class CCSPlayerController
{
VirtualFunctions.ClientPrint(this.Handle, HudDestination.Center, message, 0, 0, 0, 0);
}
public void PrintToCenterHtml(string message)
{
var @event = new EventShowSurvivalRespawnStatus(true)
{
LocToken = message,
Duration = 5,
Userid = this
};
@event.FireEventToClient(this);
}
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActiveWeapon()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
if (PlayerPawn.Value.WeaponServices == null) return;
if (!PlayerPawn.Value.WeaponServices.ActiveWeapon.IsValid) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
CCSPlayer_WeaponServices weponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
itemServices.DropActivePlayerWeapon(weponServices.ActiveWeapon.Value);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
itemServices.RemoveWeapons();
}
/// <summary>
/// Force player suicide
/// </summary>
/// <param name="explode"></param>
/// <param name="force"></param>
public void CommitSuicide(bool explode, bool force)
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
PlayerPawn.Value.CommitSuicide(explode, force);
}
/// <summary>
/// Respawn player
/// </summary>
public void Respawn()
{
if (!PlayerPawn.IsValid) return;
if (!PlayerPawn.Value.IsValid) return;
VirtualFunctions.CCSPlayerPawn_Respawn(PlayerPawn.Value.Handle);
VirtualFunction.CreateVoid<IntPtr>(Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Handle);
}
public bool IsBot => ((PlayerFlags)Flags).HasFlag(PlayerFlags.FL_FAKECLIENT);
@@ -49,7 +126,7 @@ public partial class CCSPlayerController
{
VirtualFunctions.SwitchTeam(this.Handle, (byte)team);
}
/// <summary>
/// Switches the team of the player, has the same effect as the "jointeam" console command.
/// <remarks>
@@ -62,4 +139,9 @@ public partial class CCSPlayerController
VirtualFunction.CreateVoid<IntPtr, CsTeam>(Handle, GameData.GetOffset("CCSPlayerController_ChangeTeam"))(Handle,
team);
}
/// <summary>
/// Gets the active pawns button state. Will work even if the player is dead or observing.
/// </summary>
public PlayerButtons Buttons => (PlayerButtons)Pawn.Value.MovementServices!.Buttons.ButtonStates[0];
}

View File

@@ -0,0 +1,19 @@
using System;
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSPlayerPawn
{
/// <summary>
/// Respawn player
/// </summary>
public void Respawn()
{
if (!Controller.IsValid) return;
if (!Controller.Value.IsValid) return;
VirtualFunctions.CCSPlayerPawn_Respawn(Handle);
VirtualFunction.CreateVoid<IntPtr>(Controller.Value.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(Controller.Value.Handle);
}
}

View File

@@ -0,0 +1,38 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CCSPlayer_ItemServices
{
/// <summary>
/// Drops the active player weapon on the ground.
/// </summary>
public void DropActivePlayerWeapon(CBasePlayerWeapon activeWeapon)
{
VirtualFunction.CreateVoid<nint, nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_DropActivePlayerWeapon"))(Handle, activeWeapon.Handle);
}
/// <summary>
/// Removes every weapon from the player.
/// </summary>
public void RemoveWeapons()
{
VirtualFunction.CreateVoid<nint>(Handle, GameData.GetOffset("CCSPlayer_ItemServices_RemoveWeapons"))(Handle);
}
}

View File

@@ -51,9 +51,4 @@ public partial class CEntityIdentity
{
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
public unsafe CHandle<CEntityInstance> EntityHandle => new(Handle + 0x10);
public unsafe string DesignerName => Utilities.ReadStringUtf8(Handle + 0x20);
public unsafe PointerTo<CEntityIdentity> Prev => new(Handle + 0x58);
public unsafe PointerTo<CEntityIdentity> Next => new(Handle + 0x60);
public unsafe PointerTo<CEntityIdentity> PrevByClass => new(Handle + 0x68);
public unsafe PointerTo<CEntityIdentity> NextByClass => new(Handle + 0x70);
}

View File

@@ -0,0 +1,30 @@
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CounterStrikeSharp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
*/
using CounterStrikeSharp.API.Modules.Memory;
namespace CounterStrikeSharp.API.Core;
public partial class CGameSceneNode
{
/// <summary>
/// Gets the <see cref="CSkeletonInstance"/> instance from the node.
/// </summary>
public CSkeletonInstance GetSkeletonInstance()
{
return new CSkeletonInstance(VirtualFunction.Create<nint, nint>(Handle, GameData.GetOffset("CGameSceneNode_GetSkeletonInstance"))(Handle));
}
}

View File

@@ -0,0 +1,8 @@
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Core;
public partial class CPlayerPawnComponent
{
public PointerTo<CBasePlayerPawn> Pawn => new PointerTo<CBasePlayerPawn>(this.Handle + 0x30);
}

View File

@@ -1,22 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
namespace CounterStrikeSharp.API.Core;
public class CInButtonState : NativeObject
{
public CInButtonState(IntPtr pointer) : base(pointer)
{
}
public unsafe ref PlayerButtons Value =>
ref Unsafe.AsRef<PlayerButtons>((void*)(Handle + Schema.GetSchemaOffset("CInButtonState", "m_pButtonStates")));
}
public partial class CPlayer_MovementServices
{
public CInButtonState ButtonState =>
Schema.GetDeclaredClass<CInButtonState>(this.Handle, "CPlayer_MovementServices", "m_nButtons");
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/*
/*
* This file is part of CounterStrikeSharp.
* CounterStrikeSharp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,8 +20,9 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Core.Logging;
using McMaster.NETCore.Plugins;
using Microsoft.Extensions.Logging;
namespace CounterStrikeSharp.API.Core
{
@@ -42,6 +43,9 @@ namespace CounterStrikeSharp.API.Core
private readonly string _path;
private readonly FileSystemWatcher _fileWatcher;
// TOOD: ServiceCollection
private ILogger _logger = CoreLogging.Factory.CreateLogger<PluginContext>();
public PluginContext(string path, int id)
{
_path = path;
@@ -62,7 +66,7 @@ namespace CounterStrikeSharp.API.Core
{
if (e.FullPath == path)
{
Console.WriteLine($"Plugin {Name} has been deleted, unloading...");
_logger.LogInformation("Plugin {Name} has been deleted, unloading...", Name);
Unload(true);
}
};
@@ -74,7 +78,7 @@ namespace CounterStrikeSharp.API.Core
private Task OnReloadedAsync(object sender, PluginReloadedEventArgs eventargs)
{
Console.WriteLine($"Reloading plugin {Name}");
_logger.LogInformation("Reloading plugin {Name}", Name);
_assemblyLoader = eventargs.Loader;
Unload(hotReload: true);
Load(hotReload: true);
@@ -99,13 +103,15 @@ namespace CounterStrikeSharp.API.Core
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}");
_logger.LogInformation("Loading plugin {Name}", pluginType.Name);
_plugin = (BasePlugin)Activator.CreateInstance(pluginType)!;
_plugin.ModulePath = _path;
_plugin.RegisterAllAttributes(_plugin);
_plugin.Logger = PluginLogging.CreatePluginLogger(this);
_plugin.InitializeConfig(_plugin, pluginType);
_plugin.Load(hotReload);
Console.WriteLine($"Finished loading plugin: {Name}");
_logger.LogInformation("Finished loading plugin {Name}", Name);
}
}
@@ -113,7 +119,7 @@ namespace CounterStrikeSharp.API.Core
{
var cachedName = Name;
Console.WriteLine($"Unloading plugin {Name}");
_logger.LogInformation("Unloading plugin {Name}", Name);
_plugin.Unload(hotReload);
@@ -125,7 +131,7 @@ namespace CounterStrikeSharp.API.Core
_fileWatcher.Dispose();
}
Console.WriteLine($"Finished unloading plugin {cachedName}");
_logger.LogInformation("Finished unloading plugin {Name}", Name);
}
}
}

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>();
@@ -103,6 +101,7 @@ namespace CounterStrikeSharp.API.Core
public void Invoke()
{
InvokeNativeInternal();
GlobalCleanUp();
}
[SecurityCritical]

View File

@@ -1,8 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EnablePackageValidation>true</EnablePackageValidation>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<Nullable>enable</Nullable>
<Authors>Roflmuffin</Authors>
<Description>Official server side runtime assembly for CounterStrikeSharp</Description>
<PackageProjectUrl>http://docs.cssharp.dev/</PackageProjectUrl>
<RepositoryUrl>https://github.com/roflmuffin/CounterStrikeSharp</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<None Remove="Modules\Commands\CommandInfo" />
@@ -11,6 +23,10 @@
<ItemGroup>
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<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,35 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace CounterStrikeSharp.API;
public static class Marshaling
{
public static ColorMarshaler ColorMarshaler = new();
}
public interface ICustomMarshal<T>
{
T NativeToManaged(IntPtr pointer);
void ManagedToNative(IntPtr pointer, T managedObj);
}
public class ColorMarshaler : ICustomMarshal<Color>
{
public Color NativeToManaged(IntPtr pointer)
{
var color = Marshal.ReadInt32(pointer);
var alpha = (byte)((color >> 24) & 0xFF);
var blue = (byte)((color >> 16) & 0xFF);
var green = (byte)((color >> 8) & 0xFF);
var red = (byte)(color & 0xFF);
return Color.FromArgb(alpha, red, green, blue);
}
public void ManagedToNative(IntPtr pointer, Color managedObj)
{
Marshal.WriteInt32(pointer, (managedObj.A << 24) | (managedObj.B << 16) | (managedObj.G << 8) | managedObj.R);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,90 @@
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
{
public static partial class AdminManager
{
static AdminManager()
{
CommandUtils.AddStandaloneCommand("css_admins_reload", "Reloads the admin file.", ReloadAdminsCommand);
CommandUtils.AddStandaloneCommand("css_admins_list", "List admins and their flags.", ListAdminsCommand);
CommandUtils.AddStandaloneCommand("css_groups_reload", "Reloads the admin groups file.", ReloadAdminGroupsCommand);
CommandUtils.AddStandaloneCommand("css_groups_list", "List admin groups and their flags.", ListAdminGroupsCommand);
CommandUtils.AddStandaloneCommand("css_overrides_reload", "Reloads the admin command overrides file.", ReloadAdminOverridesCommand);
CommandUtils.AddStandaloneCommand("css_overrides_list", "List admin command overrides and their flags.", ListAdminOverridesCommand);
}
public static void MergeGroupPermsIntoAdmins()
{
foreach (var (steamID, adminDef) in Admins)
{
AddPlayerToGroup(steamID, adminDef.Groups.ToArray());
}
}
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
Admins.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminData(Path.Combine(rootDir.FullName, "configs", "admins.json"));
}
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminsCommand(CCSPlayerController? player, CommandInfo command)
{
foreach (var (steamId, data) in Admins)
{
command.ReplyToCommand($"{steamId.SteamId64}, {steamId.SteamId2} - {string.Join(", ", data.Flags)}");
}
}
[RequiresPermissions(permissions:"@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
Groups.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadAdminGroups(Path.Combine(rootDir.FullName, "configs", "admin_groups.json"));
}
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminGroupsCommand(CCSPlayerController? player, CommandInfo command)
{
foreach (var (groupName, groupDef) in Groups)
{
command.ReplyToCommand($"{groupName} - {string.Join(", ", groupDef.Flags)}");
}
}
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ReloadAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
CommandOverrides.Clear();
var rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
LoadCommandOverrides(Path.Combine(rootDir.FullName, "configs", "admin_overrides.json"));
}
[RequiresPermissions(permissions: "@css/generic")]
[CommandHelper(whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
private static void ListAdminOverridesCommand(CCSPlayerController? player, CommandInfo command)
{
foreach (var (commandName, commandDef) in CommandOverrides)
{
command.ReplyToCommand($"{commandName} (enabled: {commandDef.Enabled.ToString()}) - {string.Join(", ", commandDef.Flags)}");
}
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using CounterStrikeSharp.API.Modules.Entities;
namespace CounterStrikeSharp.API.Modules.Admin
{
public class BaseRequiresPermissions : Attribute
{
/// <summary>
/// The permissions for the command.
/// </summary>
public string[] Permissions { get; }
/// <summary>
/// The name of the command that is attached to this attribute.
/// </summary>
public string Command { get; set; }
/// <summary>
/// Whether this attribute should be used for permission checks.
/// </summary>
public bool Enabled { get; set; }
public BaseRequiresPermissions(params string[] permissions)
{
Permissions = permissions;
Command = "";
}
public virtual bool CanExecuteCommand(CCSPlayerController? caller)
{
// If we have a command in the "command_overrides" section in "configs/admins.json",
// we skip the checks below and just return the defined value.
var adminData = AdminManager.GetPlayerAdminData((SteamID)caller.SteamID);
if (adminData == null) return false;
if (adminData.CommandOverrides.ContainsKey(Command))
{
return adminData.CommandOverrides[Command];
}
return true;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
using CounterStrikeSharp.API.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CounterStrikeSharp.API.Modules.Commands
{
public enum CommandUsage
{
CLIENT_AND_SERVER = 0,
CLIENT_ONLY,
SERVER_ONLY
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CommandHelperAttribute : Attribute
{
public int MinArgs { get; }
public string Usage { get; }
public CommandUsage WhoCanExcecute { get; }
/// <summary>
///
/// </summary>
/// <param name="minArgs">The minimum amount of arguments required to execute this command.</param>
/// <param name="usage">If the command fails, this string is printed to the caller to show the CommandUtils intended usage.</param>
/// <param name="whoCanExecute">Restricts the command so it can only be executed by players, the server console, or both (see CommandUsage).</param>
public CommandHelperAttribute(int minArgs = 0, string usage = "", CommandUsage whoCanExecute = CommandUsage.CLIENT_AND_SERVER)
{
MinArgs = minArgs;
Usage = usage;
WhoCanExcecute = whoCanExecute;
}
}
}

View File

@@ -43,10 +43,11 @@ 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) {
public void ReplyToCommand(string message, bool console = false) {
if (_player != null)
{
_player.PrintToChat(message);
if (console) { _player.PrintToConsole(message); }
else _player.PrintToChat(message);
}
else
{

View File

@@ -0,0 +1,82 @@
/*
* 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.Reflection;
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
{
public static class ConfigManager
{
private static readonly DirectoryInfo? _rootDir;
private static readonly string _pluginConfigsFolderPath;
private static ILogger _logger = CoreLogging.Factory.CreateLogger("ConfigManager");
static ConfigManager()
{
_rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
_pluginConfigsFolderPath = Path.Combine(_rootDir.FullName, "configs", "plugins");
}
public static T Load<T>(string pluginName) where T : IBasePluginConfig, new()
{
string directoryPath = Path.Combine(_pluginConfigsFolderPath, pluginName);
string configPath = Path.Combine(directoryPath, $"{pluginName}.json");
T config = (T)Activator.CreateInstance(typeof(T))!;
if (!File.Exists(configPath))
{
try
{
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
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 }));
File.WriteAllText(configPath, builder.ToString());
return config;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to generate configuration file for {PluginName}", pluginName);
}
}
try
{
config = JsonSerializer.Deserialize<T>(File.ReadAllText(configPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip })!;
}
catch (Exception 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,
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -2,7 +2,7 @@ using System;
namespace CounterStrikeSharp.API.Modules.Entities
{
public class SteamID
public class SteamID : IEquatable<SteamID>
{
const long Base = 76561197960265728;
public ulong SteamId64 { get; set; }
@@ -12,7 +12,6 @@ namespace CounterStrikeSharp.API.Modules.Entities
public static explicit operator SteamID(ulong u) => new(u);
public static explicit operator SteamID(string s) => new(s);
ulong ParseId(string id)
{
var parts = id.Split(':');
@@ -46,5 +45,55 @@ namespace CounterStrikeSharp.API.Modules.Entities
}
public override string ToString() => $"[SteamID {SteamId64}, {SteamId2}, {SteamId3}]";
public bool Equals(SteamID? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return SteamId64 == other.SteamId64;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SteamID)obj);
}
public static bool TryParse(string s, out SteamID? steamId)
{
try
{
if (ulong.TryParse(s, out var steamid64))
{
steamId = new SteamID(steamid64);
return true;
}
steamId = new SteamID(s);
return true;
}
catch
{
steamId = null;
return false;
}
}
public override int GetHashCode()
{
return SteamId64.GetHashCode();
}
public static bool operator ==(SteamID? left, SteamID? right)
{
return Equals(left, right);
}
public static bool operator !=(SteamID? left, SteamID? right)
{
return !Equals(left, right);
}
}
}

View File

@@ -53,7 +53,7 @@ namespace CounterStrikeSharp.API.Modules.Events
_ when type == typeof(string) => GetString(name),
_ when type == typeof(bool) => GetBool(name),
_ when type == typeof(ulong) => GetUint64(name),
_ when type == typeof(long) => GetUint64(name),
_ when type == typeof(long) => (long)GetUint64(name),
_ when type == typeof(CCSPlayerController) => GetPlayer(name),
_ => throw new NotSupportedException(),
};
@@ -123,6 +123,7 @@ namespace CounterStrikeSharp.API.Modules.Events
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
public void FireEvent(bool dontBroadcast) => NativeAPI.FireEvent(Handle, dontBroadcast);
// public void FireEventToClient(int clientId, bool dontBroadcast) => NativeAPI.FireEventToClient(Handle, clientId);
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.EntityIndex.Value.Value);
}
}

View File

@@ -1,16 +1,17 @@
using System.IO;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
namespace CounterStrikeSharp.API.Modules.Memory;
public class Addresses
{
public static string EnginePath = Path.Join(Server.GameDirectory, Constants.ROOT_BINARY_PATH, "libengine2.so");
public static string Tier0Path = Path.Join(Server.GameDirectory, Constants.ROOT_BINARY_PATH, "libtier0.so");
public static string ServerPath = Path.Join(Server.GameDirectory, Constants.GAME_BINARY_PATH, "libserver.so");
public static string EnginePath => Path.Join(Server.GameDirectory, Constants.RootBinaryPath, $"{Constants.ModulePrefix}engine2{Constants.ModuleSuffix}");
public static string Tier0Path => Path.Join(Server.GameDirectory, Constants.RootBinaryPath, $"{Constants.ModulePrefix}tier0{Constants.ModuleSuffix}");
public static string SchemaSystemPath =
Path.Join(Server.GameDirectory, Constants.ROOT_BINARY_PATH, "libschemasystem.so");
public static string ServerPath => Path.Join(Server.GameDirectory, Constants.GameBinaryPath, $"{Constants.ModulePrefix}server{Constants.ModuleSuffix}");
public static string SchemaSystemPath => Path.Join(Server.GameDirectory, Constants.RootBinaryPath, $"{Constants.ModulePrefix}schemasystem{Constants.ModuleSuffix}");
public static string VScriptPath => Path.Join(Server.GameDirectory, Constants.RootBinaryPath, $"{Constants.ModulePrefix}vscript{Constants.ModuleSuffix}");
public static string VScriptPath = Path.Join(Server.GameDirectory, Constants.ROOT_BINARY_PATH, "libvscript.so");
}

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
{
@@ -52,7 +54,7 @@ namespace CounterStrikeSharp.API.Modules.Memory
{ typeof(string), DataType.DATA_TYPE_STRING },
{ typeof(long), DataType.DATA_TYPE_LONG },
{ typeof(ulong), DataType.DATA_TYPE_ULONG },
{ typeof(short), DataType.DATA_TYPE_VARIANT },
{ typeof(short), DataType.DATA_TYPE_SHORT },
{ typeof(sbyte), DataType.DATA_TYPE_UCHAR },
{ typeof(byte), DataType.DATA_TYPE_CHAR },
};
@@ -71,9 +73,26 @@ namespace CounterStrikeSharp.API.Modules.Memory
return types[Enum.GetUnderlyingType(type)];
}
Console.WriteLine("Error retrieving data type for type" + type.FullName);
GlobalContext.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,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.CompilerServices;
using CounterStrikeSharp.API.Core;
@@ -9,8 +10,53 @@ public class Schema
{
private static Dictionary<Tuple<string, string>, short> _schemaOffsets = new();
private static HashSet<string> _cs2BadList = new HashSet<string>()
{
"m_bIsValveDS",
"m_bIsQuestEligible",
// "m_iItemDefinitionIndex", // as of 2023.11.11 this is currently not blocked
"m_iEntityLevel",
"m_iItemIDHigh",
"m_iItemIDLow",
"m_iAccountID",
"m_iEntityQuality",
"m_bInitialized",
"m_szCustomName",
"m_iAttributeDefinitionIndex",
"m_iRawValue32",
"m_iRawInitialValue32",
"m_flValue", // MNetworkAlias "m_iRawValue32"
"m_flInitialValue", // MNetworkAlias "m_iRawInitialValue32"
"m_bSetBonus",
"m_nRefundableCurrency",
"m_OriginalOwnerXuidLow",
"m_OriginalOwnerXuidHigh",
"m_nFallbackPaintKit",
"m_nFallbackSeed",
"m_flFallbackWear",
"m_nFallbackStatTrak",
"m_iCompetitiveWins",
"m_iCompetitiveRanking",
"m_iCompetitiveRankType",
"m_iCompetitiveRankingPredicted_Win",
"m_iCompetitiveRankingPredicted_Loss",
"m_iCompetitiveRankingPredicted_Tie",
"m_nActiveCoinRank",
"m_nMusicID",
};
public static short GetSchemaOffset(string className, string propertyName)
{
if (CoreConfig.FollowCS2ServerGuidelines && _cs2BadList.Contains(propertyName))
{
throw new Exception($"Cannot set or get '{className}::{propertyName}' with \"FollowCS2ServerGuidelines\" option enabled.");
}
var key = new Tuple<string, string>(className, propertyName);
if (!_schemaOffsets.TryGetValue(key, out var offset))
{
@@ -28,6 +74,11 @@ public class Schema
public static void SetSchemaValue<T>(IntPtr handle, string className, string propertyName, T value)
{
if (CoreConfig.FollowCS2ServerGuidelines && _cs2BadList.Contains(propertyName))
{
throw new Exception($"Cannot set or get '{className}::{propertyName}' with \"FollowCS2ServerGuidelines\" option enabled.");
}
NativeAPI.SetSchemaValueByName<T>(handle, (int)typeof(T).ToDataType(), className, propertyName, value);
}
@@ -96,4 +147,30 @@ public class Schema
{
SetSchemaValue(pointer, className, memberName, value);
}
public static T GetCustomMarshalledType<T>(IntPtr pointer, string className, string memberName)
{
var type = typeof(T);
object result = type switch
{
_ when type == typeof(Color) => Marshaling.ColorMarshaler.NativeToManaged(pointer + GetSchemaOffset(className, memberName)),
_ => throw new NotSupportedException(),
};
return (T)result;
}
public static void SetCustomMarshalledType<T>(IntPtr pointer, string className, string memberName, T value)
{
var type = typeof(T);
switch (type)
{
case var _ when value is Color c:
Marshaling.ColorMarshaler.ManagedToNative(pointer + GetSchemaOffset(className, memberName), c);
break;
default:
throw new NotSupportedException();
}
}
}

View File

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

View File

@@ -1,27 +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 MemoryFunctionVoid<IntPtr, string> SetModelFunc =
new(GameData.GetSignature("CBaseModelEntity_SetModel"));
public static Action<IntPtr, string> SetModel = SetModelFunc.Invoke;
public static MemoryFunctionVoid<nint, RoundEndReason, float> TerminateRoundFunc =
new(GameData.GetSignature("CCSGameRules_TerminateRound"));
public static Action<IntPtr, RoundEndReason, float> TerminateRound = TerminateRoundFunc.Invoke;
public static MemoryFunctionWithReturn<string, int, IntPtr> UTIL_CreateEntityByNameFunc =
new(GameData.GetSignature("UTIL_CreateEntityByName"));
public static Func<string, int, IntPtr> UTIL_CreateEntityByName = UTIL_CreateEntityByNameFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, IntPtr> CBaseEntity_DispatchSpawnFunc =
new(GameData.GetSignature("CBaseEntity_DispatchSpawn"));
public static Action<IntPtr, IntPtr> CBaseEntity_DispatchSpawn = CBaseEntity_DispatchSpawnFunc.Invoke;
public static MemoryFunctionVoid<IntPtr> CCSPlayerPawn_RespawnFunc = new(GameData.GetSignature("CCSPlayerPawn_Respawn"));
public static Action<IntPtr> CCSPlayerPawn_Respawn = CCSPlayerPawn_RespawnFunc.Invoke;
public static MemoryFunctionVoid<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOldFunc = new (GameData.GetSignature("CBaseEntity_TakeDamageOld"));
public static Action<CEntityInstance, CTakeDamageInfo> CBaseEntity_TakeDamageOld = CBaseEntity_TakeDamageOldFunc.Invoke;
public static MemoryFunctionVoid<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThinkFunc = new (GameData.GetSignature("CCSPlayerPawnBase_PostThink"));
public static Action<CCSPlayerPawnBase> CCSPlayerPawnBase_PostThink = CCSPlayerPawnBase_PostThinkFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouchFunc = new (GameData.GetSignature("CBaseTrigger_StartTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_StartTouch = CBaseTrigger_StartTouchFunc.Invoke;
public static MemoryFunctionVoid<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouchFunc = new (GameData.GetSignature("CBaseTrigger_EndTouch"));
public static Action<CBaseTrigger, CBaseEntity> CBaseTrigger_EndTouch = CBaseTrigger_EndTouchFunc.Invoke;
}

View File

@@ -22,19 +22,20 @@ namespace CounterStrikeSharp.API.Modules.Utils
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 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 Purple = '\x03';
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 = '\x0D';
public static char BlueGrey = '\x0A';
public static char Magenta = '\x0E';
public static char LightRed = '\x0F';
}

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

@@ -0,0 +1,90 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using System;
using System.Reflection;
namespace CounterStrikeSharp.API.Modules.Utils;
public class CommandUtils
{
public static void AddStandaloneCommand(string name, string description, CommandInfo.CommandCallback handler)
{
var wrappedHandler = new Action<int, IntPtr>((i, ptr) =>
{
var caller = (i != -1) ? new CCSPlayerController(NativeAPI.GetEntityFromIndex(i + 1)) : null;
var command = new CommandInfo(ptr, caller);
var methodInfo = handler?.GetMethodInfo();
if (!AdminManager.CommandIsOverriden(name))
{
// Do not execute command if we do not have the correct permissions.
var permissions = methodInfo?.GetCustomAttributes<BaseRequiresPermissions>();
if (permissions != null)
{
foreach (var attr in permissions)
{
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
}
// If this command has it's permissions overriden, we will do an AND check for all permissions.
else
{
// I don't know if this is the most sane implementation of this, can be edited in code review.
var data = AdminManager.GetCommandOverrideData(name);
if (data != null)
{
var attrType = (data.CheckType == "all") ? typeof(RequiresPermissions) : typeof(RequiresPermissionsOr);
var attr = (BaseRequiresPermissions)Activator.CreateInstance(attrType, args: AdminManager.GetPermissionOverrides(name));
attr.Command = name;
if (!attr.CanExecuteCommand(caller))
{
command.ReplyToCommand("[CSS] You do not have the correct permissions to execute this command.");
return;
}
}
}
// Do not execute if we shouldn't be calling this command.
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
if (helperAttribute != null)
{
switch (helperAttribute.WhoCanExcecute)
{
case CommandUsage.CLIENT_AND_SERVER: break; // Allow command through.
case CommandUsage.CLIENT_ONLY:
if (caller == null || !caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by clients."); return; }
break;
case CommandUsage.SERVER_ONLY:
if (caller != null && caller.IsValid) { command.ReplyToCommand("[CSS] This command can only be executed by the server."); return; }
break;
default: throw new ArgumentException("Unrecognised CommandUsage value passed in CommandHelperAttribute.");
}
// Technically the command itself counts as the first argument,
// but we'll just ignore that for this check.
if (helperAttribute.MinArgs != 0 && command.ArgCount - 1 < helperAttribute.MinArgs)
{
command.ReplyToCommand($"[CSS] Expected usage: \"!{command.ArgByIndex(0)} {helperAttribute.Usage}\".");
return;
}
}
handler?.Invoke(caller, command);
});
var methodInfo = handler?.GetMethodInfo();
var helperAttribute = methodInfo?.GetCustomAttribute<CommandHelperAttribute>();
var subscriber = new BasePlugin.CallbackSubscriber(handler, wrappedHandler, () => { });
NativeAPI.AddCommand(name, description, (helperAttribute?.WhoCanExcecute == CommandUsage.SERVER_ONLY),
(int)ConCommandFlags.FCVAR_LINKED_CONCOMMAND, subscriber.GetInputArgument());
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Runtime.CompilerServices;
namespace CounterStrikeSharp.API.Modules.Utils;
public partial class matrix3x4_t : NativeObject
{
public matrix3x4_t(IntPtr pointer) : base(pointer)
{
}
public unsafe ref float this[int row, int column] => ref Unsafe.Add(ref *(float*)Handle, row * 4 + column);
}

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