Compare commits

...

14 Commits

Author SHA1 Message Date
dependabot[bot]
aec696abc0 chore(deps): bump libraries/hl2sdk-cs2 from 9ddef9a to f7ed6a0 (#451)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-09 00:43:54 +10:00
Roflmuffin
c01aeec14b [no ci] chore: fix dependabot prefix 2024-05-08 20:13:19 +10:00
dependabot[bot]
41e7bee85a chore(deps): update hl2sdk: bump libraries/metamod-source from e857fbe to 607301a (#450)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 20:10:01 +10:00
Roflmuffin
9834271956 [no ci] chore: add metamod source dependabot config 2024-05-08 19:59:53 +10:00
Michael Wilson
fb5967d102 Back to C++17 (#447) 2024-05-08 16:34:51 +10:00
dependabot[bot]
928bc3f74d chore(deps): update hl2sdk: bump libraries/hl2sdk-cs2 from 3fc8d0f to 9ddef9a (#446)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 15:27:48 +10:00
Roflmuffin
6a7d7dba4f [no ci] feat: add hl2sdk-cs2 dependabot 2024-05-08 14:30:51 +10:00
Roflmuffin
11e5e9972d fix: concommand crash when no description 2024-05-01 14:44:57 +10:00
Michael Wilson
9a221b4ebb chore: bump hl2sdk after update (#434) 2024-05-01 09:38:54 +10:00
Yarukon
e270bdfd44 AcceptInput changes (#433) 2024-05-01 01:12:25 +10:00
Michael Wilson
f591fe58d0 fix: add memoverride on windows, use correct MT flag (#431) 2024-04-30 17:40:25 +10:00
Roflmuffin
052cb4e14e chore: improve ccsplayercontroller helpers validity checks 2024-04-30 11:39:49 +10:00
Michael Wilson
bc3bec4aa8 feat: improve docs and add more null checking for handle accesses (#417) 2024-04-30 11:20:47 +10:00
Roflmuffin
20bab7f4a8 feat: add PrintToCenterAlert player helper 2024-04-29 20:14:58 +10:00
27 changed files with 2452 additions and 2281 deletions

12
.github/dependabot.yaml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
# Update the submodule in libraries/hl2sdk-cs2 every day
- package-ecosystem: "gitsubmodule"
directory: "/"
allow:
- dependency-name: "libraries/hl2sdk-cs2"
- dependency-name: "libraries/metamod-source"
schedule:
interval: "daily"
commit-message:
prefix: "chore(deps)"

View File

@@ -5,6 +5,8 @@ project(counterstrikesharp C CXX ASM)
include("makefiles/shared.cmake")
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_subdirectory(libraries/spdlog)
add_subdirectory(libraries/dyncall)
add_subdirectory(libraries/funchook)
@@ -13,7 +15,8 @@ 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
set(SOURCE_FILES
libraries/hl2sdk-cs2/public/tier0/memoverride.cpp
src/mm_plugin.cpp
src/mm_plugin.h
libraries/hl2sdk-cs2/tier1/convar.cpp
@@ -95,66 +98,55 @@ SET(SOURCE_FILES
src/core/game_system.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/Protobufs/csgo)
file(GLOB PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/Protobufs/csgo/*.proto")
## Generate protobuf source & headers
if (LINUX)
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
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)
set(SOURCE_FILES ${SOURCE_FILES} protobuf_output_stamp)
# Sources
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${NATIVES_SOURCES} ${CONVERSIONS_SOURCES} ${CONVERSIONS_HEADERS})
target_include_directories(
${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/core/cs2_sdk
)
if (LINUX)
if(LINUX)
include("makefiles/linux.base.cmake")
set_target_properties(${PROJECT_NAME} PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/linuxsteamrt64"
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"
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/addons/counterstrikesharp/bin/win64"
)
endif()
# Libraries
target_link_libraries(${PROJECT_NAME} ${COUNTER_STRIKE_SHARP_LINK_LIBRARIES})
add_custom_command(
TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/configs ${CMAKE_BINARY_DIR}
)
TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/configs ${CMAKE_BINARY_DIR}
)

View File

@@ -133,6 +133,13 @@
"linux": "55 48 89 E5 41 57 49 89 FF 41 56 48 8D 7D C0"
}
},
"CEntitySystem_AddEntityIOEvent": {
"signatures": {
"library": "server",
"windows": "48 89 5C 24 ? 48 89 74 24 ? 57 48 ? ? ? 49 ? ? 48 ? ? 48 ? ? 74",
"linux": "55 41 BA FF FF FF FF"
}
},
"LegacyGameEventListener": {
"signatures": {
"library": "server",

View File

@@ -1,6 +1,5 @@
#set(_ITERATOR_DEBUG_LEVEL 2)
add_definitions(-D_LINUX -DPOSIX -DLINUX -DGNUC -DCOMPILER_GCC -DPLATFORM_64BITS)
add_definitions(-D_LINUX -DPOSIX -DLINUX -DGNUC -DCOMPILER_GCC -DPLATFORM_64BITS -D_FILE_OFFSET_BITS=64)
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")
@@ -16,16 +15,18 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -fno-strict-aliasing")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-threadsafe-statics -v -fvisibility=default")
SET(
COUNTER_STRIKE_SHARP_LINK_LIBRARIES
${SOURCESDK_LIB}/linux64/libtier0.so
${SOURCESDK_LIB}/linux64/tier1.a
${SOURCESDK_LIB}/linux64/interfaces.a
${SOURCESDK_LIB}/linux64/mathlib.a
spdlog
dynload_s
dyncall_s
distorm
funchook-static
dynohook
)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libgcc -static-libstdc++")
set(
COUNTER_STRIKE_SHARP_LINK_LIBRARIES
${SOURCESDK_LIB}/linux64/libtier0.so
${SOURCESDK_LIB}/linux64/tier1.a
${SOURCESDK_LIB}/linux64/interfaces.a
${SOURCESDK_LIB}/linux64/mathlib.a
spdlog
dynload_s
dyncall_s
distorm
funchook-static
dynohook
)

View File

@@ -1,8 +1,8 @@
if (UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE)
set(LINUX TRUE)
endif()
if (WIN32 AND NOT MSVC)
if(WIN32 AND NOT MSVC)
message(FATAL "MSVC restricted.")
endif()
@@ -11,9 +11,9 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING
FORCE
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 17)
if (LINUX)
if(LINUX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
endif()
@@ -67,4 +67,4 @@ include_directories(
libraries
)
include(${CMAKE_CURRENT_LIST_DIR}/metamod/configure_metamod.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/metamod/configure_metamod.cmake)

View File

@@ -6,6 +6,7 @@ add_definitions(
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(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libcmt")
set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
${SOURCESDK_LIB}/public/win64/tier0.lib
@@ -18,4 +19,4 @@ set(COUNTER_STRIKE_SHARP_LINK_LIBRARIES
distorm
funchook-static
dynohook
)
)

View File

@@ -714,6 +714,37 @@ namespace CounterStrikeSharp.API.Core
}
}
public static void AcceptInput(IntPtr pthis, string inputname, IntPtr activator, IntPtr caller, string value, int outputid){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(pthis);
ScriptContext.GlobalScriptContext.Push(inputname);
ScriptContext.GlobalScriptContext.Push(activator);
ScriptContext.GlobalScriptContext.Push(caller);
ScriptContext.GlobalScriptContext.Push(value);
ScriptContext.GlobalScriptContext.Push(outputid);
ScriptContext.GlobalScriptContext.SetIdentifier(0x259E084C);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static void AddEntityIoEvent(IntPtr ptarget, string inputname, IntPtr activator, IntPtr caller, string value, float delay, int outputid){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();
ScriptContext.GlobalScriptContext.Push(ptarget);
ScriptContext.GlobalScriptContext.Push(inputname);
ScriptContext.GlobalScriptContext.Push(activator);
ScriptContext.GlobalScriptContext.Push(caller);
ScriptContext.GlobalScriptContext.Push(value);
ScriptContext.GlobalScriptContext.Push(delay);
ScriptContext.GlobalScriptContext.Push(outputid);
ScriptContext.GlobalScriptContext.SetIdentifier(0x4CFDE98A);
ScriptContext.GlobalScriptContext.Invoke();
ScriptContext.GlobalScriptContext.CheckErrors();
}
}
public static void HookEvent(string name, InputArgument callback, bool ispost){
lock (ScriptContext.GlobalScriptContext.Lock) {
ScriptContext.GlobalScriptContext.Reset();

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,7 @@ public partial class CCSPlayerController
{
Guard.IsValidEntity(this);
if (!PlayerPawn.IsValid) return null;
if (PlayerPawn.Value == null) return null;;
if (!PlayerPawn.Value.IsValid) return null;
if (PlayerPawn.Value.ItemServices == null) return null;
return PlayerPawn.Value.ItemServices.As<CCSPlayer_ItemServices>().GiveNamedItem<T>(item);
return PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>().GiveNamedItem<T>(item);
}
public IntPtr GiveNamedItem(CsItem item)
@@ -66,6 +61,14 @@ public partial class CCSPlayerController
VirtualFunctions.ClientPrint(Handle, HudDestination.Center, message, 0, 0, 0, 0);
}
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
public void PrintToCenterAlert(string message)
{
Guard.IsValidEntity(this);
VirtualFunctions.ClientPrint(Handle, HudDestination.Alert, message, 0, 0, 0, 0);
}
public void PrintToCenterHtml(string message) => PrintToCenterHtml(message, 5);
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
@@ -90,17 +93,13 @@ public partial class CCSPlayerController
public void DropActiveWeapon()
{
Guard.IsValidEntity(this);
if (!PlayerPawn.IsValid) return;
if (PlayerPawn.Value == null) 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 weaponServices = new CCSPlayer_WeaponServices(PlayerPawn.Value.WeaponServices.Handle);
var itemServices = PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>();
var activeWeapon = PlayerPawn.Value?.WeaponServices?.ActiveWeapon.Value;
itemServices.DropActivePlayerWeapon(weaponServices.ActiveWeapon.Value);
if (activeWeapon == null || itemServices == null) return;
itemServices.DropActivePlayerWeapon(activeWeapon);
}
/// <summary>
@@ -110,13 +109,8 @@ public partial class CCSPlayerController
public void RemoveWeapons()
{
Guard.IsValidEntity(this);
if (!PlayerPawn.IsValid) return;
if (PlayerPawn.Value == null) return;
if (!PlayerPawn.Value.IsValid) return;
if (PlayerPawn.Value.ItemServices == null) return;
CCSPlayer_ItemServices itemServices = new CCSPlayer_ItemServices(PlayerPawn.Value.ItemServices.Handle);
itemServices.RemoveWeapons();
PlayerPawn.Value?.ItemServices?.As<CCSPlayer_ItemServices>().RemoveWeapons();
}
/// <summary>
@@ -128,11 +122,8 @@ public partial class CCSPlayerController
public void CommitSuicide(bool explode, bool force)
{
Guard.IsValidEntity(this);
if (!PlayerPawn.IsValid) return;
if (PlayerPawn.Value == null) return;
if (!PlayerPawn.Value.IsValid) return;
PlayerPawn.Value.CommitSuicide(explode, force);
PlayerPawn.Value?.CommitSuicide(explode, force);
}
/// <summary>
@@ -142,9 +133,7 @@ public partial class CCSPlayerController
public void Respawn()
{
Guard.IsValidEntity(this);
if (!PlayerPawn.IsValid) return;
if (PlayerPawn.Value == null) return;
if (!PlayerPawn.Value.IsValid) return;
// The Call To Arms update appears to have invalidated the need for CCSPlayerPawn_Respawn.
SetPawn(PlayerPawn.Value);

View File

@@ -65,7 +65,7 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
}
/// <summary>
/// Calls a named input method on an entity.
/// Calls a named input method on an entity, this will bypass the map IO event queue system.
/// <example>
/// <code>
/// entity.AcceptInput("Break");
@@ -82,7 +82,30 @@ public partial class CEntityInstance : IEquatable<CEntityInstance>
{
Guard.IsValidEntity(this);
VirtualFunctions.AcceptInput(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, 0);
NativeAPI.AcceptInput(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, outputId);
}
/// <summary>
/// Calls a named input method on an entity, conforming to the map IO event queue system.
/// <example>
/// <code>
/// entity.AddEntityIOEvent("Break");
/// </code>
/// </example>
/// </summary>
/// <param name="inputName">Input action name</param>
/// <param name="activator">Entity which initiated the action, <see langword="null"/> for no entity</param>
/// <param name="caller">Entity that is sending the event, <see langword="null"/> for no entity</param>
/// <param name="value">String variant value to send with the event</param>
/// <param name="delay">Delay in seconds before calling the input</param>
/// <param name="outputId">Unknown, defaults to 0</param>
/// <exception cref="InvalidOperationException">Entity is not valid</exception>
public void AddEntityIOEvent(string inputName, CEntityInstance? activator = null, CEntityInstance? caller = null, string value = "", float delay = 0, int outputId = 0)
{
Guard.IsValidEntity(this);
NativeAPI.AddEntityIoEvent(Handle, inputName, activator?.Handle ?? IntPtr.Zero, caller?.Handle ?? IntPtr.Zero, value, delay, outputId);
}
}
@@ -90,4 +113,4 @@ public partial class CEntityIdentity
{
public unsafe CEntityInstance EntityInstance => new(Unsafe.Read<IntPtr>((void*)Handle));
public unsafe CHandle<CEntityInstance> EntityHandle => new(this.Handle + 0x10);
}
}

View File

@@ -34,16 +34,16 @@ namespace CounterStrikeSharp.API.Modules.Events
{
// Used to track freeable state for manually created events.
private bool _freeable = false;
public GameEvent(IntPtr pointer) : base(pointer)
{
}
public GameEvent(string name, bool force) : this(NativeAPI.CreateEvent(name, force))
{
_freeable = true;
}
public string EventName => NativeAPI.GetEventName(Handle);
public T Get<T>(string name)
@@ -58,7 +58,9 @@ namespace CounterStrikeSharp.API.Modules.Events
_ when type == typeof(bool) => GetBool(name),
_ when type == typeof(ulong) => GetUint64(name),
_ when type == typeof(long) => (long)GetUint64(name),
_ when type == typeof(CCSPlayerController) => GetPlayer(name),
// This is a special case for player controllers as this method previously did not allow for nullable returns.
// So we return an invalid player controller if the player is not found.
_ when type == typeof(CCSPlayerController) => GetPlayer(name) ?? new CCSPlayerController(0),
_ => throw new NotSupportedException(),
};
@@ -80,7 +82,7 @@ namespace CounterStrikeSharp.API.Modules.Events
SetInt(name, i);
break;
case var _ when value is CCSPlayerController player:
NativeAPI.SetEventPlayerController(Handle, name, player.Handle);
SetPlayer(name, player);
break;
case var _ when value is string s:
SetString(name, s);
@@ -104,9 +106,15 @@ namespace CounterStrikeSharp.API.Modules.Events
protected string GetString(string name) => NativeAPI.GetEventString(Handle, name);
protected int GetInt(string name) => NativeAPI.GetEventInt(Handle, name);
protected CCSPlayerController GetPlayer(string name)
protected CCSPlayerController? GetPlayer(string name)
{
return new CCSPlayerController(NativeAPI.GetEventPlayerController(Handle, name));
var ptr = NativeAPI.GetEventPlayerController(Handle, name);
if (ptr == IntPtr.Zero)
{
return null;
}
return new CCSPlayerController(ptr);
}
protected ulong GetUint64(string name) => NativeAPI.GetEventUint64(Handle, name);
@@ -125,6 +133,8 @@ namespace CounterStrikeSharp.API.Modules.Events
protected void SetEntityIndex(string name, int value) => NativeAPI.SetEventEntityIndex(Handle, name, value);
protected void SetPlayer(string name, CCSPlayerController? player) => NativeAPI.SetEventPlayerController(Handle, name, player?.Handle ?? IntPtr.Zero);
public void FireEvent(bool dontBroadcast)
{
NativeAPI.FireEvent(Handle, dontBroadcast);
@@ -132,7 +142,7 @@ namespace CounterStrikeSharp.API.Modules.Events
}
public void FireEventToClient(CCSPlayerController player) => NativeAPI.FireEventToClient(Handle, (int)player.Index);
/// <summary>
/// Used to manually free the event.
/// <remarks>If <see cref="FireEvent"/> is called, Free will be called automatically.</remarks>
@@ -143,10 +153,10 @@ namespace CounterStrikeSharp.API.Modules.Events
{
throw new InvalidOperationException("Event is not able to be freed.");
}
NativeAPI.FreeEvent(Handle);
_freeable = false;
}
}
}
}

View File

@@ -81,9 +81,6 @@ public static class VirtualFunctions
public static MemoryFunctionVoid<IntPtr, IntPtr> RemovePlayerItemFunc =
new(GameData.GetSignature("CBasePlayerPawn_RemovePlayerItem"));
public static Action<IntPtr, IntPtr> RemovePlayerItemVirtual = RemovePlayerItemFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, string, IntPtr, IntPtr, string, int> AcceptInputFunc = new(GameData.GetSignature("CEntityInstance_AcceptInput"));
public static Action<IntPtr, string, IntPtr, IntPtr, string, int> AcceptInput = AcceptInputFunc.Invoke;
public static MemoryFunctionVoid<IntPtr, IntPtr, int, short, short> StateChangedFunc =
new(GameData.GetSignature("StateChanged"));
@@ -92,4 +89,4 @@ public static class VirtualFunctions
public static MemoryFunctionVoid<IntPtr, int, long> NetworkStateChangedFunc = new(GameData.GetSignature("NetworkStateChanged"));
public static Action<IntPtr, int, long> NetworkStateChanged = NetworkStateChangedFunc.Invoke;
}
}

View File

@@ -1,8 +1,8 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Entities;
using Microsoft.Extensions.DependencyInjection;
namespace CounterStrikeSharp.API.Modules.Utils;
@@ -11,6 +11,12 @@ public readonly record struct CEntityIndex(uint Value)
public override string ToString() => $"Entity Index {Value}";
}
/// <summary>
/// CHandle is a class that represents a 32-bit ID (entindex + serial number) unique to every past and present entity in a game.
/// It is used to refer to entities where pointers and entity indexes are unsafe; mainly across the client/server divide.
/// <a href="https://developer.valvesoftware.com/wiki/CHandle">More info</a>
/// </summary>
/// <typeparam name="T">Type of entity this handle refers to</typeparam>
public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
{
private uint _raw;
@@ -49,15 +55,35 @@ public class CHandle<T> : IEquatable<CHandle<T>> where T : NativeEntity
_pointer = raw;
}
public T? Value => (T)Activator.CreateInstance(typeof(T), EntitySystem.GetEntityByHandle(this));
/// <inheritdoc cref="Get"/>
public T? Value => Get();
/// <summary>
/// Retrieves the instance of the entity this handle refers to.
/// </summary>
public T? Get()
{
if (!IsValid)
return null;
var entity = EntitySystem.GetEntityByHandle(this);
if (entity == null)
return null;
return (T)Activator.CreateInstance(typeof(T), entity)!;
}
public override string ToString() => IsValid ? $"Index = {Index}, Serial = {SerialNum}" : "<invalid>";
/// <summary>
/// Checks that the handle is valid and points to an entity.
/// </summary>
public bool IsValid => Index != (Utilities.MaxEdicts - 1);
public uint Index => (uint)(Raw & (Utilities.MaxEdicts - 1));
public uint SerialNum => Raw >> Utilities.MaxEdictBits;
public static implicit operator uint(CHandle<T> handle) => handle.Raw;
public bool Equals(CHandle<T>? other)
@@ -81,8 +107,8 @@ public class CEntityHandle : CHandle<CEntityInstance>
public CEntityHandle(uint raw) : base(raw)
{
}
public CEntityHandle(IntPtr raw) : base (raw)
public CEntityHandle(IntPtr raw) : base(raw)
{
}
}
@@ -103,4 +129,4 @@ public class PointerTo<T> : NativeObject where T : NativeObject
}
}
}
}
}

View File

@@ -42,9 +42,15 @@ namespace CounterStrikeSharp.API
.Where(x => flags.HasFlag(x)).AsEnumerable();
}
public static T GetEntityFromIndex<T>(int index) where T : CEntityInstance
public static T? GetEntityFromIndex<T>(int index) where T : CEntityInstance
{
return (T)Activator.CreateInstance(typeof(T), NativeAPI.GetEntityFromIndex(index))!;
var entityPtr = EntitySystem.GetEntityByIndex((uint)index);
if (entityPtr is null || entityPtr == IntPtr.Zero)
{
return null;
}
return (T)Activator.CreateInstance(typeof(T), entityPtr)!;
}
public static T? CreateEntityByName<T>(string name) where T : CBaseEntity
@@ -52,19 +58,25 @@ namespace CounterStrikeSharp.API
return (T?)Activator.CreateInstance(typeof(T), VirtualFunctions.UTIL_CreateEntityByName(name, -1));
}
public static CCSPlayerController GetPlayerFromIndex(int index)
public static CCSPlayerController? GetPlayerFromIndex(int index)
{
return Utilities.GetEntityFromIndex<CCSPlayerController>(index);
var player = GetEntityFromIndex<CCSPlayerController>(index);
if (player == null || player.DesignerName != "cs_player_controller")
{
return null;
}
return player;
}
public static CCSPlayerController GetPlayerFromSlot(int slot)
public static CCSPlayerController? GetPlayerFromSlot(int slot)
{
return Utilities.GetEntityFromIndex<CCSPlayerController>(slot + 1);
return GetPlayerFromIndex(slot + 1);
}
public static CCSPlayerController GetPlayerFromUserid(int userid)
public static CCSPlayerController? GetPlayerFromUserid(int userid)
{
return Utilities.GetEntityFromIndex<CCSPlayerController>((userid & 0xFF) + 1);
return GetPlayerFromIndex((userid & 0xFF) + 1);
}
public static CCSPlayerController? GetPlayerFromSteamId(ulong steamId)
@@ -87,16 +99,16 @@ namespace CounterStrikeSharp.API
CHandle<CBasePlayerWeapon>? item = null;
if (player.PlayerPawn.Value == null || player.PlayerPawn.Value.WeaponServices == null) return false;
foreach(var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
{
if (weapon is not { IsValid: true, Value.IsValid: true })
if (weapon is not { IsValid: true, Value.IsValid: true })
continue;
if (weapon.Value.DesignerName != designerName)
if (weapon.Value.DesignerName != designerName)
continue;
item = weapon;
}
if (item != null && item.Value != null)
{
player.PlayerPawn.Value.RemovePlayerItem(item.Value);
@@ -108,7 +120,7 @@ namespace CounterStrikeSharp.API
return true;
}
return false;
}
@@ -121,7 +133,7 @@ namespace CounterStrikeSharp.API
yield return new PointerTo<T>(pEntity.Handle).Value;
}
}
public static IEnumerable<CEntityInstance> GetAllEntities()
{
var pEntity = new CEntityIdentity(EntitySystem.FirstActiveEntity);
@@ -130,7 +142,7 @@ namespace CounterStrikeSharp.API
yield return new PointerTo<CEntityInstance>(pEntity.Handle).Value;
}
}
/// <summary>
/// Returns a list of <see cref="CCSPlayerController"/> that are valid and have a valid <see cref="CCSPlayerController.UserId"/> >= 0
/// </summary>
@@ -142,7 +154,7 @@ namespace CounterStrikeSharp.API
{
var controller = GetPlayerFromSlot(i);
if (!controller.IsValid || controller.UserId == -1)
if (controller == null || !controller.IsValid || controller.Connected != PlayerConnectedState.PlayerConnected)
continue;
players.Add(controller);
@@ -198,7 +210,7 @@ namespace CounterStrikeSharp.API
return (T)Activator.CreateInstance(typeof(T), pointerTo)!;
}
private static int FindSchemaChain(string className) => Schema.GetSchemaOffset(className, "__m_pChainEntity");
/// <summary>
@@ -216,10 +228,11 @@ namespace CounterStrikeSharp.API
if (!Schema.IsSchemaFieldNetworked(className, fieldName))
{
Application.Instance.Logger.LogWarning("Field {ClassName}:{FieldName} is not networked, but SetStateChanged was called on it.", className, fieldName);
Application.Instance.Logger.LogWarning(
"Field {ClassName}:{FieldName} is not networked, but SetStateChanged was called on it.", className, fieldName);
return;
}
int offset = Schema.GetSchemaOffset(className, fieldName);
int chainOffset = FindSchemaChain(className);
@@ -235,4 +248,4 @@ namespace CounterStrikeSharp.API
entity.IsSteadyState.Clear();
}
}
}
}

View File

@@ -24,18 +24,14 @@ using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Menu;
using CounterStrikeSharp.API.Modules.Utils;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using static CounterStrikeSharp.API.Core.Listeners;
namespace TestPlugin
{
@@ -56,7 +52,7 @@ namespace TestPlugin
public override string ModuleDescription => "A playground of features used for testing";
public SampleConfig Config { get; set; }
public SampleConfig Config { get; set; } = null!;
// This method is called right before `Load` is called
public void OnConfigParsed(SampleConfig config)
@@ -94,7 +90,6 @@ namespace TestPlugin
SetupGameEvents();
SetupListeners();
SetupCommands();
SetupMenus();
SetupEntityOutputHooks();
// ValveInterface provides pointers to loaded modules via Interface Name exposed from the engine (e.g. Source2Server001)
@@ -114,69 +109,10 @@ namespace TestPlugin
var virtualFunc = VirtualFunction.Create<IntPtr>(server.Pointer, 91);
var result = virtualFunc() - 8;
Logger.LogInformation("Result of virtual func call is {Pointer:X}", result);
_testInjectedClass.Hello();
VirtualFunctions.CBaseTrigger_StartTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger {Trigger} touched by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.CBaseTrigger_EndTouchFunc.Hook(h =>
{
var trigger = h.GetParam<CBaseTrigger>(0);
var entity = h.GetParam<CBaseEntity>(1);
Logger.LogInformation("Trigger left {Trigger} by {Entity}", trigger.DesignerName, entity.DesignerName);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.UTIL_RemoveFunc.Hook(hook =>
{
var entityInstance = hook.GetParam<CEntityInstance>(0);
Logger.LogInformation("Removed entity {EntityIndex}", entityInstance.Index);
return HookResult.Continue;
}, HookMode.Post);
VirtualFunctions.CBaseEntity_TakeDamageOldFunc.Hook((h =>
{
var victim = h.GetParam<CEntityInstance>(0);
var damageInfo = h.GetParam<CTakeDamageInfo>(1);
if (damageInfo.Inflictor.Value.DesignerName == "inferno")
{
var inferno = new CInferno(damageInfo.Inflictor.Value.Handle);
Logger.LogInformation("Owner of inferno is {Owner}", inferno.OwnerEntity);
if (victim == inferno.OwnerEntity.Value)
{
damageInfo.Damage = 0;
}
else
{
damageInfo.Damage = 150;
}
}
return HookResult.Continue;
}), HookMode.Pre);
// Precache resources
RegisterListener<Listeners.OnServerPrecacheResources>((manifest) =>
{
manifest.AddResource("path/to/model");
manifest.AddResource("path/to/material");
manifest.AddResource("path/to/particle");
});
}
public override void OnAllPluginsLoaded(bool hotReload)
{
Logger.LogInformation("All plugins loaded!");
@@ -186,8 +122,7 @@ namespace TestPlugin
{
RegisterListener<Listeners.OnMapStart>(name =>
{
var cheatsCvar = ConVar.Find("sv_cheats");
cheatsCvar.SetValue(true);
ConVar.Find("sv_cheats")?.SetValue(true);
var numericCvar = ConVar.Find("mp_warmuptime");
Logger.LogInformation("mp_warmuptime = {Value}", numericCvar?.GetPrimitiveValue<float>());
@@ -204,67 +139,73 @@ namespace TestPlugin
{
// Register Game Event Handlers
RegisterEventHandler<EventPlayerConnect>(GenericEventHandler, HookMode.Pre);
RegisterEventHandler<EventPlayerChat>(((@event, info) =>
{
var entity = new CCSPlayerController(NativeAPI.GetEntityFromIndex(@event.Userid));
if (!entity.IsValid)
{
Logger.LogInformation("invalid entity");
return HookResult.Continue;
}
RegisterEventHandler<EventPlayerBlind>(GenericEventHandler);
entity.PrintToChat($"You said {@event.Text}");
// Mirrors a chat message back to the player
RegisterEventHandler<EventPlayerChat>(((@event, _) =>
{
var player = Utilities.GetPlayerFromIndex(@event.Userid);
if (player == null) return HookResult.Continue;
player.PrintToChat($"You said {@event.Text}");
return HookResult.Continue;
}));
RegisterEventHandler<EventPlayerDeath>((@event, info) =>
{
// You can use `info.DontBroadcast` to set the dont broadcast flag on the event.
// You can use `info.DontBroadcast` to set the don't broadcast flag on the event.
if (new Random().NextSingle() > 0.5f)
{
@event.Attacker.PrintToChat($"Skipping player_death broadcast at {Server.CurrentTime}");
@event.Attacker?.PrintToChat($"Skipping player_death broadcast at {Server.CurrentTime}");
info.DontBroadcast = true;
}
return HookResult.Continue;
}, HookMode.Pre);
RegisterEventHandler<EventGrenadeBounce>((@event, info) =>
{
Logger.LogInformation("Player {Player} grenade bounce", @event.Userid.PlayerName);
Logger.LogInformation("Player \"{Player}\" grenade bounce", @event.Userid!.PlayerName);
return HookResult.Continue;
}, HookMode.Pre);
RegisterEventHandler<EventPlayerSpawn>((@event, info) =>
{
if (!@event.Userid.IsValid) return 0;
if (!@event.Userid.PlayerPawn.IsValid) return 0;
var player = @event.Userid;
var playerPawn = player?.PlayerPawn.Get();
if (player == null || playerPawn == null) return HookResult.Continue;
Logger.LogInformation("Player spawned with entity index: {EntityIndex} & User ID: {UserId}",
@event.Userid.Index, @event.Userid.UserId);
playerPawn.Index, player.UserId);
return HookResult.Continue;
});
RegisterEventHandler<EventPlayerBlind>(GenericEventHandler);
RegisterEventHandler<EventBulletImpact>((@event, info) =>
{
var player = @event.Userid;
var pawn = player.PlayerPawn.Value;
var activeWeapon = @event.Userid.PlayerPawn.Value.WeaponServices?.ActiveWeapon.Value;
var weapons = @event.Userid.PlayerPawn.Value.WeaponServices?.MyWeapons;
var pawn = player?.PlayerPawn.Get();
var activeWeapon = pawn?.WeaponServices?.ActiveWeapon.Get();
if (pawn == null) return HookResult.Continue;
Server.NextFrame(() =>
{
var weaponServices = player.PlayerPawn.Value.WeaponServices.As<CCSPlayer_WeaponServices>();
player.PrintToChat(weaponServices.ActiveWeapon.Value?.DesignerName);
player?.PrintToChat(activeWeapon?.DesignerName ?? "No Active Weapon");
});
// Set player to random colour
player.PlayerPawn.Value.Render = Color.FromArgb(Random.Shared.Next(0, 255),
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseModelEntity", "m_clrRender");
activeWeapon.ReserveAmmo[0] = 250;
activeWeapon.Clip1 = 250;
// Set player to random colour
pawn.Render = Color.FromArgb(Random.Shared.Next(0, 255),
Random.Shared.Next(0, 255), Random.Shared.Next(0, 255));
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
// Give player 5 health and set their reserve ammo to 250
if (activeWeapon != null)
{
activeWeapon.ReserveAmmo[0] = 250;
activeWeapon.Clip1 = 250;
}
pawn.Health += 5;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
@@ -279,20 +220,19 @@ namespace TestPlugin
foreach (var player in playerEntities)
{
//var player = new CCSPlayerController(entInst.Handle);
if (player.InGameMoneyServices != null) player.InGameMoneyServices.Account = 1337;
player.InGameMoneyServices!.Account = 1337;
}
// Grab everything starting with cs_, but we'll only mainpulate cs_gamerules.
// Grab everything starting with cs_, but we'll only manipulate cs_gamerules.
// Note: this iterates through all entities, so is an expensive operation.
var csEntities = Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_");
Logger.LogInformation("Amount of cs_* entities: {Count}", csEntities.Count());
var csEntities = Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_").ToArray();
Logger.LogInformation("Amount of cs_* entities: {Count}", csEntities.Length);
foreach (var entity in csEntities)
foreach (var entity in csEntities.Where(x => x.DesignerName == "cs_gamerules"))
{
if (entity.DesignerName != "cs_gamerules") continue;
var gamerulesEnt = new CCSGameRules(entity.Handle);
gamerulesEnt.CTTimeOutActive = true;
// It's safe to cast to `CCSGameRules` here as we know the entity is a cs_gamerules entity.
var gameRules = entity.As<CCSGameRules>();
gameRules.CTTimeOutActive = true;
}
return HookResult.Continue;
@@ -301,26 +241,32 @@ namespace TestPlugin
private void SetupListeners()
{
// Precache resources
RegisterListener<Listeners.OnServerPrecacheResources>((manifest) =>
{
manifest.AddResource("path/to/model");
manifest.AddResource("path/to/material");
manifest.AddResource("path/to/particle");
});
// Hook global listeners defined by CounterStrikeSharp
RegisterListener<Listeners.OnMapStart>(mapName =>
{
Logger.LogInformation("Map {Map} has started!", mapName);
});
RegisterListener<Listeners.OnMapEnd>(() => { Logger.LogInformation($"Map has ended."); });
RegisterListener<Listeners.OnClientConnect>((index, name, ip) =>
RegisterListener<Listeners.OnClientConnect>((playerSlot, name, ip) =>
{
Logger.LogInformation("Client {Name} from {Ip} has connected!", name, ip);
});
RegisterListener<Listeners.OnClientAuthorized>((index, id) =>
RegisterListener<Listeners.OnClientAuthorized>((playerSlot, steamId) =>
{
Logger.LogInformation("Client {Index} with address {Id}", index, id);
Logger.LogInformation("Client {Index} with address {Id}", playerSlot, steamId);
});
RegisterListener<Listeners.OnEntitySpawned>(entity =>
{
var designerName = entity.DesignerName;
switch (designerName)
switch (entity.DesignerName)
{
case "smokegrenade_projectile":
var projectile = entity.As<CSmokeGrenadeProjectile>();
@@ -328,8 +274,8 @@ namespace TestPlugin
Server.NextFrame(() =>
{
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.X = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.Y = Random.Shared.NextSingle() * 255.0f;
projectile.SmokeColor.Z = Random.Shared.NextSingle() * 255.0f;
Logger.LogInformation("Smoke grenade spawned with color {SmokeColor}",
projectile.SmokeColor);
});
@@ -343,58 +289,6 @@ namespace TestPlugin
});
}
private void SetupMenus()
{
// [Legacy] Chat Menu Example
var largeMenu = new ChatMenu("Test Menu");
for (int i = 1; i < 26; i++)
{
var i1 = i;
largeMenu.AddMenuOption(new Random().NextSingle().ToString(),
(player, option) => player.PrintToChat($"You just selected {option.Text}"), i1 % 5 == 0);
}
var giveItemMenu = new ChatMenu("Small Menu");
var handleGive = (CCSPlayerController player, ChatMenuOption option) =>
{
player.GiveNamedItem(option.Text);
};
giveItemMenu.AddMenuOption("weapon_ak47", handleGive);
giveItemMenu.AddMenuOption("weapon_p250", handleGive);
AddCommand("css_target", "Target Test", (player, info) =>
{
if (player == null) return;
var targetResult = info.GetArgTargetResult(1);
if (!targetResult.Any())
{
player.PrintToChat("No players found.");
return;
}
foreach (var result in targetResult.Players)
{
player.PrintToChat($"Target found: {result?.PlayerName}");
}
});
AddCommand("css_menu", "Opens example menu", (player, info) => { ChatMenus.OpenMenu(player, largeMenu); });
AddCommand("css_gunmenu", "Gun Menu", (player, info) => { ChatMenus.OpenMenu(player, giveItemMenu); });
for (int i = 1; i <= 9; i++)
{
AddCommand("css_" + i, "Command Key Handler", (player, info) =>
{
if (player == null) return;
var key = Convert.ToInt32(info.GetArg(0).Split("_")[1]);
ChatMenus.OnKeyPress(player, key);
});
}
}
private void SetupCommands()
{
// Adds a new server console command
@@ -407,24 +301,17 @@ namespace TestPlugin
((SteamID)player.SteamID).SteamId2, info.ArgString);
});
AddCommand("css_changeteam", "change team", (player, info) =>
AddCommand("css_changeteam", "change team", (player, _) =>
{
if (player?.IsValid != true) return;
if (player == null) return;
if ((CsTeam)player.TeamNum == CsTeam.Terrorist)
{
player.ChangeTeam(CsTeam.CounterTerrorist);
}
else
{
player.ChangeTeam(CsTeam.Terrorist);
}
player.ChangeTeam((CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist);
});
// Listens for any client use of the command `jointeam`.
AddCommandListener("jointeam", (player, info) =>
{
Logger.LogInformation("{PlayerName} just did a jointeam (pre) [{ArgString}]", player.PlayerName,
Logger.LogInformation("{PlayerName} just did a jointeam (pre) [{ArgString}]", player?.PlayerName,
info.ArgString);
return HookResult.Continue;
@@ -433,21 +320,21 @@ namespace TestPlugin
private void SetupEntityOutputHooks()
{
HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, name, activator, caller, value, delay) =>
HookEntityOutput("weapon_knife", "OnPlayerPickup", (output, _, activator, caller, _, delay) =>
{
Logger.LogInformation("weapon_knife called OnPlayerPickup ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
});
HookEntityOutput("*", "*", (output, name, activator, caller, value, delay) =>
HookEntityOutput("*", "*", (output, _, activator, caller, _, delay) =>
{
Logger.LogInformation("All EntityOutput ({name}, {activator}, {caller}, {delay})", output.Description.Name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
});
HookEntityOutput("*", "OnStartTouch", (output, name, activator, caller, value, delay) =>
HookEntityOutput("*", "OnStartTouch", (_, name, activator, caller, _, delay) =>
{
Logger.LogInformation("OnStartTouch: ({name}, {activator}, {caller}, {delay})", name, activator.DesignerName, caller.DesignerName, delay);
return HookResult.Continue;
@@ -470,49 +357,59 @@ namespace TestPlugin
return HookResult.Continue;
}
[ConsoleCommand("css_testinput", "Test AcceptInput and AddEntityIOEvent")]
public void OnTestInput(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
var pawn = player.PlayerPawn.Get();
if (pawn == null) return;
pawn!.AcceptInput("SetHealth", null, null, "50");
pawn!.AddEntityIOEvent("SetHealth", null, null, "75", 5);
}
[ConsoleCommand("css_killmeplease", "Kills the player")]
[ConsoleCommand("css_killme", "Kills the player")]
public void OnKillme(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
var pawn = player.PlayerPawn.Get();
if (pawn == null) return;
player.PlayerPawn.Value.CommitSuicide(true, true);
pawn.CommitSuicide(true, true);
}
[CommandHelper(minArgs: 1, usage: "[weaponName]")]
[ConsoleCommand("css_strip", "Removes weapon by name")]
public void OnStripActiveWeapon(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
if (player == null || player.PlayerPawn.Get() == null) return;
player.RemoveItemByDesignerName(command.GetArg(1));
}
[ConsoleCommand("css_stripweapons", "Removes player weapons")]
public void OnStripWeapons(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
if (player == null || player.PlayerPawn.Get() == null) return;
player.RemoveWeapons();
}
[ConsoleCommand("css_teleportup", "Teleports the player up")]
public void OnTeleport(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
var pawn = player.PlayerPawn.Get();
if (pawn == null) return;
player.PlayerPawn.Value.Teleport(player.PlayerPawn.Value.AbsOrigin.With(z: player.PlayerPawn.Value.AbsOrigin.Z + 100), player.PlayerPawn.Value.AbsRotation, new Vector(IntPtr.Zero));
pawn.Teleport(pawn.AbsOrigin!.With(z: pawn.AbsOrigin.Z + 100), pawn.AbsRotation, new Vector(IntPtr.Zero));
}
[ConsoleCommand("css_respawn", "Respawns the player")]
public void OnRespawn(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
if (player == null || player.PlayerPawn.Get() == null) return;
player.Respawn();
}
@@ -527,7 +424,7 @@ namespace TestPlugin
entity.AcceptInput("Break");
}
}
[ConsoleCommand("css_fov", "Sets the player's FOV")]
[CommandHelper(minArgs: 1, usage: "[fov]")]
public void OnFovCommand(CCSPlayerController? player, CommandInfo command)
@@ -557,7 +454,7 @@ namespace TestPlugin
}
else
{
player.PrintToChat($"Level \"{mapName}\" is invalid.");
command.ReplyToCommand($"Level \"{mapName}\" is invalid.");
}
}
@@ -565,15 +462,19 @@ namespace TestPlugin
public void OnCommandGuns(CCSPlayerController? player, CommandInfo command)
{
if (player == null) return;
if (!player.PlayerPawn.IsValid) return;
var pawn = player.PlayerPawn.Get();
if (pawn == null) return;
foreach (var weapon in player.PlayerPawn.Value.WeaponServices.MyWeapons)
foreach (var weapon in pawn.WeaponServices!.MyWeapons)
{
var vData = weapon.Value.As<CCSWeaponBase>().VData;
command.ReplyToCommand(string.Format("{0}, {1}, {2}, {3}, {4}, {5}", vData.Name, vData.GearSlot, vData.Price, vData.WeaponCategory, vData.WeaponType, vData.KillAward));
var vData = weapon.Get()?.As<CCSWeaponBase>().VData;
if (vData == null) continue;
command.ReplyToCommand(
$"{vData.Name}, {vData.GearSlot}, {vData.Price}, {vData.WeaponCategory}, {vData.WeaponType}, {vData.KillAward}");
}
}
[ConsoleCommand("css_entities", "List entities")]
public void OnCommandEntities(CCSPlayerController? player, CommandInfo command)
{
@@ -581,13 +482,13 @@ namespace TestPlugin
{
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
}
foreach (var entity in Utilities.FindAllEntitiesByDesignerName<CBaseEntity>("cs_"))
{
command.ReplyToCommand($"{entity.Index}:{entity.DesignerName}");
}
}
[ConsoleCommand("css_colors", "List Chat Colors")]
public void OnCommandColors(CCSPlayerController? player, CommandInfo command)
{
@@ -599,14 +500,14 @@ namespace TestPlugin
command.ReplyToCommand($" {(char)i}Color 0x{i:x}");
}
}
[ConsoleCommand("css_localetest", "Test Translations")]
public void OnCommandLocaleTest(CCSPlayerController? player, CommandInfo command)
{
Logger.LogInformation("Current Culture is {Culture}", CultureInfo.CurrentCulture);
command.ReplyToCommand(Localizer["testPlugin.maxPlayersAnnouncement", Server.MaxPlayers]);
}
[ConsoleCommand("css_sound", "Play a sound to client")]
public void OnCommandSound(CCSPlayerController? player, CommandInfo command)
{

View File

@@ -4,14 +4,14 @@
<TargetFramework>net8.0</TargetFramework>
<Platforms>AnyCPU;x86</Platforms>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CounterStrikeSharp.API\CounterStrikeSharp.API.csproj">
<Private>false</Private>
</ProjectReference>
</ProjectReference>
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,5 @@
#include "core/gameconfig.h"
#include <fstream>
#include "log.h"
@@ -13,7 +14,8 @@ CGameConfig::~CGameConfig() = default;
bool CGameConfig::Init(char* conf_error, int conf_error_size)
{
std::ifstream ifs(m_sPath);
if (!ifs) {
if (!ifs)
{
V_snprintf(conf_error, conf_error_size, "Gamedata file not found.");
return false;
}
@@ -26,44 +28,53 @@ bool CGameConfig::Init(char* conf_error, int conf_error_size)
constexpr auto platform = "linux";
#endif
try {
for (auto& [k, v] : m_json.items()) {
if (v.contains("signatures")) {
if (auto library = v["signatures"]["library"]; library.is_string()) {
try
{
for (auto& [k, v] : m_json.items())
{
if (v.contains("signatures"))
{
if (auto library = v["signatures"]["library"]; library.is_string())
{
m_umLibraries[k] = library.get<std::string>();
}
if (auto signature = v["signatures"][platform]; signature.is_string()) {
if (auto signature = v["signatures"][platform]; signature.is_string())
{
m_umSignatures[k] = signature.get<std::string>();
}
}
if (v.contains("offsets")) {
if (auto offset = v["offsets"][platform]; offset.is_number_integer()) {
if (v.contains("offsets"))
{
if (auto offset = v["offsets"][platform]; offset.is_number_integer())
{
m_umOffsets[k] = offset.get<std::int64_t>();
}
}
if (v.contains("patches")) {
if (auto patch = v["patches"][platform]; patch.is_string()) {
if (v.contains("patches"))
{
if (auto patch = v["patches"][platform]; patch.is_string())
{
m_umPatches[k] = patch.get<std::string>();
}
}
}
} catch (const std::exception& ex) {
}
catch (const std::exception& ex)
{
V_snprintf(conf_error, conf_error_size, "Failed to parse gamedata file: %s", ex.what());
return false;
}
return true;
}
const std::string CGameConfig::GetPath()
{
return m_sPath;
}
const std::string CGameConfig::GetPath() { return m_sPath; }
const char* CGameConfig::GetLibrary(const std::string& name)
{
// My recommendation is switch to C++20.
auto it = m_umLibraries.find(name);
if (it == m_umLibraries.end()) {
if (it == m_umLibraries.end())
{
return nullptr;
}
return it->second.c_str();
@@ -72,7 +83,8 @@ const char* CGameConfig::GetLibrary(const std::string& name)
const char* CGameConfig::GetSignature(const std::string& name)
{
auto it = m_umSignatures.find(name);
if (it == m_umSignatures.end()) {
if (it == m_umSignatures.end())
{
return nullptr;
}
return it->second.c_str();
@@ -82,7 +94,8 @@ const char* CGameConfig::GetSymbol(const char* name)
{
const char* symbol = this->GetSignature(name);
if (!symbol || strlen(symbol) <= 1) {
if (!symbol || strlen(symbol) <= 1)
{
CSSHARP_CORE_ERROR("Missing symbol: {}\n", name);
return nullptr;
}
@@ -92,7 +105,8 @@ const char* CGameConfig::GetSymbol(const char* name)
const char* CGameConfig::GetPatch(const std::string& name)
{
auto it = m_umPatches.find(name);
if (it == m_umPatches.end()) {
if (it == m_umPatches.end())
{
return nullptr;
}
return it->second.c_str();
@@ -101,14 +115,14 @@ const char* CGameConfig::GetPatch(const std::string& name)
int CGameConfig::GetOffset(const std::string& name)
{
auto it = m_umOffsets.find(name);
if (it == m_umOffsets.end()) {
if (it == m_umOffsets.end())
{
return -1;
}
return it->second;
}
void* CGameConfig::GetAddress(const std::string& name, void* engine, void* server, char* error,
int maxlen)
void* CGameConfig::GetAddress(const std::string& name, void* engine, void* server, char* error, int maxlen)
{
CSSHARP_CORE_ERROR("Not implemented.");
return nullptr;
@@ -117,11 +131,9 @@ void* CGameConfig::GetAddress(const std::string& name, void* engine, void* serve
modules::CModule** CGameConfig::GetModule(const char* name)
{
const char* library = this->GetLibrary(name);
if (!library)
return nullptr;
if (!library) return nullptr;
if (strcmp(library, "engine") == 0)
return &modules::engine;
if (strcmp(library, "engine") == 0) return &modules::engine;
else if (strcmp(library, "server") == 0)
return &modules::server;
else if (strcmp(library, "vscript") == 0)
@@ -135,7 +147,8 @@ modules::CModule** CGameConfig::GetModule(const char* name)
bool CGameConfig::IsSymbol(const char* name)
{
const char* sigOrSymbol = this->GetSignature(name);
if (!sigOrSymbol || strlen(sigOrSymbol) <= 0) {
if (!sigOrSymbol || strlen(sigOrSymbol) <= 0)
{
CSSHARP_CORE_ERROR("Missing signature or symbol: {}\n", name);
return false;
}
@@ -145,22 +158,28 @@ bool CGameConfig::IsSymbol(const char* name)
void* CGameConfig::ResolveSignature(const char* name)
{
modules::CModule** module = this->GetModule(name);
if (!module || !(*module)) {
if (!module || !(*module))
{
CSSHARP_CORE_ERROR("Invalid Module {}\n", name);
return nullptr;
}
void* address = nullptr;
if (this->IsSymbol(name)) {
if (this->IsSymbol(name))
{
const char* symbol = this->GetSymbol(name);
if (!symbol) {
if (!symbol)
{
CSSHARP_CORE_ERROR("Invalid symbol for {}\n", name);
return nullptr;
}
address = (*module)->FindSymbol(symbol);
} else {
}
else
{
const char* signature = this->GetSignature(name);
if (!signature) {
if (!signature)
{
CSSHARP_CORE_ERROR("Failed to find signature for {}\n", name);
return nullptr;
}
@@ -168,7 +187,8 @@ void* CGameConfig::ResolveSignature(const char* name)
address = (*module)->FindSignature(signature);
}
if (!address) {
if (!address)
{
CSSHARP_CORE_ERROR("Failed to find address for {}\n", name);
return nullptr;
}
@@ -180,7 +200,8 @@ std::string CGameConfig::GetDirectoryName(const std::string& directoryPathInput)
std::string directoryPath = std::string(directoryPathInput);
size_t found = std::string(directoryPath).find_last_of("/\\");
if (found != std::string::npos) {
if (found != std::string::npos)
{
return std::string(directoryPath, found + 1);
}
return "";
@@ -188,17 +209,15 @@ std::string CGameConfig::GetDirectoryName(const std::string& directoryPathInput)
std::vector<int16_t> CGameConfig::HexToByte(std::string_view src)
{
if (src.empty()) {
if (src.empty())
{
return {};
}
auto hex_char_to_byte = [](char c) -> int16_t {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
// a valid hex char can never go up to 0xFF
return -1;
@@ -211,36 +230,38 @@ std::vector<int16_t> CGameConfig::HexToByte(std::string_view src)
const std::string_view pattern = is_code_style ? R"(\x)" : " ";
const std::string_view wildcard = is_code_style ? "2A" : "?";
auto split = std::views::split(src, pattern);
std::string::size_type pos = 0;
for (auto&& str : split) {
if (str.empty()) [[unlikely]] {
continue;
while (pos < src.size())
{
std::string::size_type found = src.find(pattern, pos);
if (found == std::string::npos)
{
found = src.size();
}
std::string_view str = src.substr(pos, found - pos);
pos = found + pattern.size();
// std::string_view(std::subrange) constructor only exists in C++23 or above
// use this when compiler is GCC >= 12.1 or Clang >= 16
// const std::string_view byte(str.begin(), str.end());
// a workaround for GCC < 12.1, it doesn't work with Clang < 16
// https://stackoverflow.com/a/48403210
std::string_view byte (&*str.begin(), std::ranges::distance(str));
if (str.empty()) continue;
if (byte.starts_with(wildcard)) {
std::string byte(str.data(), str.size());
if (byte.substr(0, wildcard.size()) == wildcard)
{
result.emplace_back(-1);
continue;
}
if (byte.size() < 2) [[unlikely]] {
if (byte.size() < 2)
{
return {};
}
const auto high = hex_char_to_byte(byte[0]);
const auto low = hex_char_to_byte(byte[1]);
// if then is malformed, return nothing
// maybe print error message here?
if (high == 0xFF || low == 0xFF) [[unlikely]] {
if (high == 0xFF || low == 0xFF)
{
return {};
}

View File

@@ -31,21 +31,20 @@
#include "core/managers/con_command_manager.h"
#include <nlohmann/json.hpp>
#include <public/eiface.h>
#include <schemasystem.h>
#include <schematypes.h>
#include <sourcehook/sourcehook.h>
#include <algorithm>
#include "scripting/callback_manager.h"
#include "core/log.h"
#include "core/utils.h"
#include "core/memory.h"
#include "core/utils.h"
#include "interfaces/cs2_interfaces.h"
#include <schematypes.h>
#include <nlohmann/json.hpp>
#include <schemasystem.h>
#include "metamod_oslink.h"
#include "scripting/callback_manager.h"
using json = nlohmann::json;
namespace counterstrikesharp {
@@ -55,30 +54,37 @@ json WriteTypeJson(json obj, CSchemaType* current_type)
obj["name"] = current_type->m_sTypeName.Get();
obj["category"] = current_type->m_eTypeCategory;
if (current_type->m_eTypeCategory == SCHEMA_TYPE_ATOMIC) {
if (current_type->m_eTypeCategory == SCHEMA_TYPE_ATOMIC)
{
obj["atomic"] = current_type->m_eAtomicCategory;
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T) {
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T)
{
auto atomicTType = static_cast<CSchemaType_Atomic_T*>(current_type);
if (atomicTType->m_pAtomicInfo != nullptr) {
if (atomicTType->m_pAtomicInfo != nullptr)
{
obj["outer"] = atomicTType->m_pAtomicInfo->m_pszName1;
}
}
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T ||
current_type->m_eAtomicCategory == SCHEMA_ATOMIC_COLLECTION_OF_T) {
if (current_type->m_eAtomicCategory == SCHEMA_ATOMIC_T || current_type->m_eAtomicCategory == SCHEMA_ATOMIC_COLLECTION_OF_T)
{
auto atomicType = static_cast<CSchemaType_Atomic_T*>(current_type);
if (atomicType->GetInnerType().Get() != nullptr) {
if (atomicType->GetInnerType().Get() != nullptr)
{
obj["inner"] = WriteTypeJson(json::object(), atomicType->GetInnerType().Get());
}
}
} else if (current_type->m_eTypeCategory == SCHEMA_TYPE_FIXED_ARRAY) {
}
else if (current_type->m_eTypeCategory == SCHEMA_TYPE_FIXED_ARRAY)
{
auto fixedArrayType = static_cast<CSchemaType_FixedArray*>(current_type);
obj["inner"] = WriteTypeJson(json::object(), fixedArrayType->m_pElementType);
} else if (current_type->m_eTypeCategory == SCHEMA_TYPE_PTR) {
}
else if (current_type->m_eTypeCategory == SCHEMA_TYPE_PTR)
{
auto ptrType = static_cast<CSchemaType_Ptr*>(current_type);
obj["inner"] = WriteTypeJson(json::object(), ptrType->m_pObjectType);
}
@@ -97,68 +103,73 @@ CON_COMMAND(dump_schema, "dump schema symbols")
std::ofstream output(utils::GamedataDirectory() + "/schema.json");
std::string line;
while (std::getline(inputClasses, line)) {
if (!line.empty() && line.back() == '\r') {
while (std::getline(inputClasses, line))
{
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
classNames.push_back(line);
}
while (std::getline(inputEnums, line)) {
if (!line.empty() && line.back() == '\r') {
while (std::getline(inputEnums, line))
{
if (!line.empty() && line.back() == '\r')
{
line.pop_back();
}
enumNames.push_back(line);
}
CSchemaSystemTypeScope* pType =
globals::schemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
CSchemaSystemTypeScope* pType = globals::schemaSystem->FindTypeScopeForModule(MODULE_PREFIX "server" MODULE_EXT);
json j;
j["classes"] = json::object();
j["enums"] = json::object();
for (const auto& line : classNames) {
for (const auto& line : classNames)
{
auto* pClassInfo = pType->FindDeclaredClass(line.c_str()).Get();
if (!pClassInfo)
continue;
if (!pClassInfo) continue;
short fieldsSize = pClassInfo->m_nFieldCount;
SchemaClassFieldData_t* pFields = pClassInfo->m_pFields;
j["classes"][pClassInfo->m_pszName] = json::object();
if (pClassInfo->m_pBaseClasses) {
j["classes"][pClassInfo->m_pszName]["parent"] =
pClassInfo->m_pBaseClasses->m_pClass->m_pszName;
if (pClassInfo->m_pBaseClasses)
{
j["classes"][pClassInfo->m_pszName]["parent"] = pClassInfo->m_pBaseClasses->m_pClass->m_pszName;
}
j["classes"][pClassInfo->m_pszName]["fields"] = json::array();
for (int i = 0; i < fieldsSize; ++i) {
for (int i = 0; i < fieldsSize; ++i)
{
SchemaClassFieldData_t& field = pFields[i];
j["classes"][pClassInfo->m_pszName]["fields"].push_back({
{"name", field.m_pszName},
{"type", WriteTypeJson(json::object(), field.m_pType)},
{ "name", field.m_pszName },
{ "type", WriteTypeJson(json::object(), field.m_pType) },
});
}
}
for (const auto& line : enumNames) {
for (const auto& line : enumNames)
{
auto* pEnumInfo = pType->FindDeclaredEnum(line.c_str()).Get();
if (!pEnumInfo)
continue;
if (!pEnumInfo) continue;
j["enums"][pEnumInfo->m_pszName] = json::object();
j["enums"][pEnumInfo->m_pszName]["align"] = pEnumInfo->m_nSize;
j["enums"][pEnumInfo->m_pszName]["items"] = json::array();
for (int i = 0; i < pEnumInfo->m_nEnumeratorCount; ++i) {
for (int i = 0; i < pEnumInfo->m_nEnumeratorCount; ++i)
{
auto& field = pEnumInfo->m_pEnumerators[i];
j["enums"][pEnumInfo->m_pszName]["items"].push_back({
{"name", field.m_pszName},
{"value", field.m_nValue},
{ "name", field.m_pszName },
{ "value", field.m_nValue },
});
}
}
@@ -167,8 +178,7 @@ CON_COMMAND(dump_schema, "dump schema symbols")
output << std::setw(2) << j << std::endl;
}
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandHandle,
const CCommandContext&, const CCommand&);
SH_DECL_HOOK3_void(ICvar, DispatchConCommand, SH_NOATTRIB, 0, ConCommandHandle, const CCommandContext&, const CCommand&);
ConCommandInfo::ConCommandInfo()
{
@@ -180,9 +190,7 @@ ConCommandInfo::~ConCommandInfo()
globals::callbackManager.ReleaseCallback(callback_pre);
globals::callbackManager.ReleaseCallback(callback_post);
}
ConCommandInfo::ConCommandInfo(bool bNoCallbacks) {
}
ConCommandInfo::ConCommandInfo(bool bNoCallbacks) {}
ConCommandManager::ConCommandManager() {}
@@ -190,22 +198,17 @@ ConCommandManager::~ConCommandManager() {}
void ConCommandManager::OnAllInitialized()
{
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand, false);
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand_Post, true);
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand, false);
SH_ADD_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand_Post, true);
m_global_cmd.callback_pre = globals::callbackManager.CreateCallback("OnClientCommandGlobalPre");
m_global_cmd.callback_post =
globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
m_global_cmd.callback_post = globals::callbackManager.CreateCallback("OnClientCommandGlobalPost");
}
void ConCommandManager::OnShutdown()
{
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand, false);
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this,
&ConCommandManager::Hook_DispatchConCommand_Post, true);
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand, false);
SH_REMOVE_HOOK_MEMFUNC(ICvar, DispatchConCommand, globals::cvars, this, &ConCommandManager::Hook_DispatchConCommand_Post, true);
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_pre);
globals::callbackManager.ReleaseCallback(m_global_cmd.callback_post);
@@ -219,10 +222,14 @@ void CommandCallback(const CCommandContext& context, const CCommand& command)
void ConCommandManager::AddCommandListener(const char* name, CallbackT callback, HookMode mode)
{
if (name == nullptr) {
if (mode == HookMode::Pre) {
if (name == nullptr)
{
if (mode == HookMode::Pre)
{
m_global_cmd.callback_pre->AddListener(callback);
} else {
}
else
{
m_global_cmd.callback_post->AddListener(callback);
}
return;
@@ -231,29 +238,38 @@ void ConCommandManager::AddCommandListener(const char* name, CallbackT callback,
auto strName = std::string(name);
ConCommandInfo* pInfo = m_cmd_lookup[strName];
if (!pInfo) {
if (!pInfo)
{
pInfo = new ConCommandInfo();
m_cmd_lookup[strName] = pInfo;
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
if (hExistingCommand.IsValid()) {
if (hExistingCommand.IsValid())
{
pInfo->command = globals::cvars->GetCommand(hExistingCommand);
}
}
if (mode == HookMode::Pre) {
if (mode == HookMode::Pre)
{
pInfo->callback_pre->AddListener(callback);
} else {
}
else
{
pInfo->callback_post->AddListener(callback);
}
}
void ConCommandManager::RemoveCommandListener(const char* name, CallbackT callback, HookMode mode)
{
if (name == nullptr) {
if (mode == HookMode::Pre) {
if (name == nullptr)
{
if (mode == HookMode::Pre)
{
m_global_cmd.callback_pre->RemoveListener(callback);
} else {
}
else
{
m_global_cmd.callback_post->RemoveListener(callback);
}
return;
@@ -262,31 +278,33 @@ void ConCommandManager::RemoveCommandListener(const char* name, CallbackT callba
auto strName = std::string(name);
ConCommandInfo* pInfo = m_cmd_lookup[strName];
if (!pInfo) {
if (!pInfo)
{
return;
}
if (mode == HookMode::Pre) {
if (mode == HookMode::Pre)
{
pInfo->callback_pre->RemoveListener(callback);
} else {
}
else
{
pInfo->callback_post->RemoveListener(callback);
}
}
bool ConCommandManager::AddValveCommand(const char* name, const char* description, bool server_only,
int flags)
bool ConCommandManager::AddValveCommand(const char* name, const char* description, bool server_only, int flags)
{
ConCommandHandle hExistingCommand = globals::cvars->FindCommand(name);
if (hExistingCommand.IsValid())
return false;
if (hExistingCommand.IsValid()) return false;
ConCommandRefAbstract conCommandRefAbstract;
auto conCommand =
new ConCommand(&conCommandRefAbstract, strdup(name), CommandCallback, strdup(description), flags);
auto conCommand = new ConCommand(&conCommandRefAbstract, strdup(name), CommandCallback, description ? strdup(description) : "", flags);
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
if (!pInfo) {
if (!pInfo)
{
pInfo = new ConCommandInfo();
m_cmd_lookup[std::string(name)] = pInfo;
}
@@ -302,14 +320,16 @@ bool ConCommandManager::RemoveValveCommand(const char* name)
{
auto hFoundCommand = globals::cvars->FindCommand(name);
if (!hFoundCommand.IsValid()) {
if (!hFoundCommand.IsValid())
{
return false;
}
globals::cvars->UnregisterConCommand(hFoundCommand);
auto pInfo = m_cmd_lookup[std::string(name)];
if (!pInfo) {
if (!pInfo)
{
return true;
}
@@ -318,11 +338,10 @@ bool ConCommandManager::RemoveValveCommand(const char* name)
return true;
}
HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CCommandContext& ctx,
const CCommand& args, HookMode mode, CommandCallingContext callingContext)
HookResult ConCommandManager::ExecuteCommandCallbacks(
const char* name, const CCommandContext& ctx, const CCommand& args, HookMode mode, CommandCallingContext callingContext)
{
CSSHARP_CORE_TRACE("[ConCommandManager::ExecuteCommandCallbacks][{}]: {}",
mode == Pre ? "Pre" : "Post", name);
CSSHARP_CORE_TRACE("[ConCommandManager::ExecuteCommandCallbacks][{}]: {}", mode == Pre ? "Pre" : "Post", name);
ConCommandInfo* pInfo = m_cmd_lookup[std::string(name)];
HookResult result = HookResult::Continue;
@@ -331,20 +350,23 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
m_cmd_contexts[&args] = callingContext;
if (globalCallback->GetFunctionCount() > 0) {
if (globalCallback->GetFunctionCount() > 0)
{
globalCallback->ScriptContext().Reset();
globalCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
globalCallback->ScriptContext().Push(&args);
for (auto fnMethodToCall : globalCallback->GetFunctions()) {
if (!fnMethodToCall)
continue;
for (auto fnMethodToCall : globalCallback->GetFunctions())
{
if (!fnMethodToCall) continue;
fnMethodToCall(&globalCallback->ScriptContextStruct());
auto hookResult = globalCallback->ScriptContext().GetResult<HookResult>();
if (hookResult >= HookResult::Stop) {
if (mode == HookMode::Pre) {
if (hookResult >= HookResult::Stop)
{
if (mode == HookMode::Pre)
{
return HookResult::Stop;
}
@@ -352,13 +374,15 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
break;
}
if (hookResult >= HookResult::Handled) {
if (hookResult >= HookResult::Handled)
{
result = hookResult;
}
}
}
if (!pInfo) {
if (!pInfo)
{
m_cmd_contexts.erase(&args);
return result;
}
@@ -369,17 +393,20 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
pCallback->ScriptContext().Push(ctx.GetPlayerSlot().Get());
pCallback->ScriptContext().Push(&args);
for (auto fnMethodToCall : pCallback->GetFunctions()) {
if (!fnMethodToCall)
continue;
for (auto fnMethodToCall : pCallback->GetFunctions())
{
if (!fnMethodToCall) continue;
fnMethodToCall(&pCallback->ScriptContextStruct());
auto thisResult = pCallback->ScriptContext().GetResult<HookResult>();
if (thisResult >= HookResult::Handled) {
if (thisResult >= HookResult::Handled)
{
m_cmd_contexts.erase(&args);
return thisResult;
} else if (thisResult > result) {
}
else if (thisResult > result)
{
result = thisResult;
}
}
@@ -389,36 +416,34 @@ HookResult ConCommandManager::ExecuteCommandCallbacks(const char* name, const CC
return result;
}
void ConCommandManager::Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx,
const CCommand& args)
void ConCommandManager::Hook_DispatchConCommand(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args)
{
const char* name = args.Arg(0);
CSSHARP_CORE_TRACE("[ConCommandManager::Hook_DispatchConCommand]: {}", name);
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Pre, CommandCallingContext::Console);
if (result >= HookResult::Handled) {
if (result >= HookResult::Handled)
{
RETURN_META(MRES_SUPERCEDE);
}
}
void ConCommandManager::Hook_DispatchConCommand_Post(ConCommandHandle cmd,
const CCommandContext& ctx,
const CCommand& args)
void ConCommandManager::Hook_DispatchConCommand_Post(ConCommandHandle cmd, const CCommandContext& ctx, const CCommand& args)
{
const char* name = args.Arg(0);
auto result = ExecuteCommandCallbacks(name, ctx, args, HookMode::Post, CommandCallingContext::Console);
if (result >= HookResult::Handled) {
if (result >= HookResult::Handled)
{
RETURN_META(MRES_SUPERCEDE);
}
}
bool ConCommandManager::IsValidValveCommand(const char* name) {
bool ConCommandManager::IsValidValveCommand(const char* name)
{
ConCommandHandle pCmd = globals::cvars->FindCommand(name);
return pCmd.IsValid();
}
CommandCallingContext ConCommandManager::GetCommandCallingContext(CCommand* args) {
return m_cmd_contexts[args];
}
CommandCallingContext ConCommandManager::GetCommandCallingContext(CCommand* args) { return m_cmd_contexts[args]; }
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -46,6 +46,22 @@ void EntityManager::OnAllInitialized()
return;
}
CEntityInstance_AcceptInput = decltype(CEntityInstance_AcceptInput)(
modules::server->FindSignature(globals::gameConfig->GetSignature("CEntityInstance_AcceptInput")));
if (!CEntityInstance_AcceptInput)
{
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntityInstance_AcceptInput\'");
}
CEntitySystem_AddEntityIOEvent = decltype(CEntitySystem_AddEntityIOEvent)(
modules::server->FindSignature(globals::gameConfig->GetSignature("CEntitySystem_AddEntityIOEvent")));
if (!CEntitySystem_AddEntityIOEvent)
{
CSSHARP_CORE_CRITICAL("Failed to find signature for \'CEntitySystem_AddEntityIOEvent\'");
}
auto m_hook = funchook_create();
funchook_prepare(m_hook, (void**)&m_pFireOutputInternal, (void*)&DetourFireOutputInternal);
funchook_install(m_hook, 0);
@@ -219,4 +235,4 @@ void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstance* pAc
}
}
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -111,4 +111,15 @@ static void DetourFireOutputInternal(CEntityIOOutput* const pThis, CEntityInstan
static FireOutputInternal m_pFireOutputInternal = nullptr;
} // namespace counterstrikesharp
// Do it in here because i didn't found a good place to do this
inline void (*CEntityInstance_AcceptInput)(CEntityInstance* pThis, const char* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, int nOutputID);
inline void (*CEntitySystem_AddEntityIOEvent)(CEntitySystem* pEntitySystem,
CEntityInstance* pTarget,
const char* pInputName,
CEntityInstance* pActivator,
CEntityInstance* pCaller,
variant_t* value,
float delay,
int nOutputID);
} // namespace counterstrikesharp

View File

@@ -1,97 +1,86 @@
#include "core/memory_module.h"
#include "core/globals.h"
#include "platform.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string_view>
#include <algorithm>
#include "core/globals.h"
#include "platform.h"
#if _WIN32
#include <Psapi.h>
#include <winternl.h>
#else
#include <link.h>
#include <elf.h>
#include <link.h>
#endif
#include "dbg.h"
#include "log.h"
#include "core/gameconfig.h"
#include "core/memory.h"
#include "dbg.h"
#include "log.h"
#include "metamod_oslink.h"
namespace counterstrikesharp::modules {
void Initialize()
{
if (!moduleList.empty())
return;
if (!moduleList.empty()) return;
#ifdef _WIN32
// walk through peb to get modules
const auto pteb = reinterpret_cast<PTEB>(
__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
const auto pteb = reinterpret_cast<PTEB>(__readgsqword(reinterpret_cast<DWORD_PTR>(&static_cast<NT_TIB*>(nullptr)->Self)));
const auto peb = pteb->ProcessEnvironmentBlock;
for (auto entry = peb->Ldr->InMemoryOrderModuleList.Flink;
entry != &peb->Ldr->InMemoryOrderModuleList; entry = entry->Flink) {
const auto module_entry =
CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
for (auto entry = peb->Ldr->InMemoryOrderModuleList.Flink; entry != &peb->Ldr->InMemoryOrderModuleList; entry = entry->Flink)
{
const auto module_entry = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
std::wstring_view w_name = module_entry->FullDllName.Buffer;
// a hack way to do so
std::string name(w_name.begin(), w_name.end());
std::ranges::replace(name, '\\', '/');
std::replace(name.begin(), name.end(), '\\', '/');
// check for extension first
if (!name.ends_with(MODULE_EXT))
continue;
if (name.rfind(MODULE_EXT) != name.length() - strlen(MODULE_EXT)) continue;
// no addons
if (name.find(R"(csgo/addons/)") != std::string::npos)
continue;
if (name.find(R"(csgo/addons/)") != std::string::npos) continue;
// we need only modules from ROOTBIN and GAMEBIN
bool isFromRootBin = name.find(ROOTBIN) != std::string::npos;
bool isFromGameBin = name.find(GAMEBIN) != std::string::npos;
if (!isFromGameBin && !isFromRootBin)
continue;
if (!isFromGameBin && !isFromRootBin) continue;
auto mod = std::make_unique<CModule>(
name, reinterpret_cast<std::uintptr_t>(module_entry->DllBase));
auto mod = std::make_unique<CModule>(name, reinterpret_cast<std::uintptr_t>(module_entry->DllBase));
// it will delete itself after going out of scope
if (!mod->IsInitialized())
continue;
if (!mod->IsInitialized()) continue;
moduleList.emplace_back(std::move(mod));
}
#else
dl_iterate_phdr(
[](struct dl_phdr_info* info, size_t, void*) {
std::string name = info->dlpi_name;
std::string name = info->dlpi_name;
if (!name.ends_with(MODULE_EXT))
return 0;
if (name.rfind(MODULE_EXT) != name.length() - strlen(MODULE_EXT)) return 0;
if (name.find("csgo/addons") != std::string::npos)
return 0;
if (name.find("csgo/addons") != std::string::npos) return 0;
bool isFromRootBin = name.find(ROOTBIN) != std::string::npos;
bool isFromGameBin = name.find(GAMEBIN) != std::string::npos;
if (!isFromGameBin && !isFromRootBin)
return 0;
bool isFromRootBin = name.find(ROOTBIN) != std::string::npos;
bool isFromGameBin = name.find(GAMEBIN) != std::string::npos;
if (!isFromGameBin && !isFromRootBin) return 0;
auto mod = std::make_unique<CModule>(name, info);
if (!mod->IsInitialized())
return 0;
auto mod = std::make_unique<CModule>(name, info);
if (!mod->IsInitialized()) return 0;
moduleList.emplace_back(std::move(mod));
return 0;
},
moduleList.emplace_back(std::move(mod));
return 0;
},
nullptr);
#endif
}
@@ -100,14 +89,16 @@ CModule* GetModuleByName(std::string name)
{
#ifdef _WIN32
// or add this in GetGameDirectory()?
std::ranges::replace(name, '\\', '/');
std::replace(name.begin(), name.end(), '\\', '/');
#endif
const auto it = std::ranges::find_if(moduleList, [name](const std::unique_ptr<CModule>& i) {
return name.ends_with(i->m_pszModule);
const auto it = std::find_if(moduleList.begin(), moduleList.end(), [&name](const std::unique_ptr<CModule>& i) {
return !i->m_pszModule.empty() && name.size() >= i->m_pszModule.size() &&
name.substr(name.size() - i->m_pszModule.size()) == i->m_pszModule;
});
if (it == moduleList.end()) {
if (it == moduleList.end())
{
CSSHARP_CORE_ERROR("Cannot find module {}", name);
return nullptr;
@@ -125,12 +116,14 @@ constexpr std::array modules_to_read_from_disk = {
CModule::CModule(std::string_view path, std::uint64_t base)
{
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(base);
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
{
return;
}
const auto nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dos_header->e_lfanew);
if (nt_header->Signature != IMAGE_NT_SIGNATURE) {
if (nt_header->Signature != IMAGE_NT_SIGNATURE)
{
return;
}
@@ -140,13 +133,16 @@ CModule::CModule(std::string_view path, std::uint64_t base)
m_baseAddress = base;
m_size = nt_header->OptionalHeader.SizeOfImage;
const bool should_read_from_disk = std::ranges::any_of(modules_to_read_from_disk,
[&](const auto& i) { return m_pszModule == i; });
const bool should_read_from_disk = std::any_of(modules_to_read_from_disk.begin(), modules_to_read_from_disk.end(), [&](const auto& i) {
return m_pszModule == i;
});
std::vector<std::uint8_t> disk_data{};
if (should_read_from_disk) {
if (should_read_from_disk)
{
std::ifstream stream(m_pszPath, std::ios::in | std::ios::binary);
if (!stream.good()) {
if (!stream.good())
{
CSSHARP_CORE_ERROR("Cannot open file {}", m_pszPath);
return;
}
@@ -156,11 +152,13 @@ CModule::CModule(std::string_view path, std::uint64_t base)
auto section = IMAGE_FIRST_SECTION(nt_header);
for (auto i = 0; i < nt_header->FileHeader.NumberOfSections; i++, section++) {
for (auto i = 0; i < nt_header->FileHeader.NumberOfSections; i++, section++)
{
const auto is_executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
const auto is_readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0;
if (is_executable && is_readable) {
if (is_executable && is_readable)
{
const auto start = this->m_baseAddress + section->VirtualAddress;
const auto size = (std::min)(section->SizeOfRawData, section->Misc.VirtualSize);
const auto data = reinterpret_cast<std::uint8_t*>(start);
@@ -170,8 +168,10 @@ CModule::CModule(std::string_view path, std::uint64_t base)
segment.address = start;
segment.bytes.reserve(size);
if (should_read_from_disk) {
if (auto bytes = GetOriginalBytes(disk_data, start - m_baseAddress, size)) {
if (should_read_from_disk)
{
if (auto bytes = GetOriginalBytes(disk_data, start - m_baseAddress, size))
{
CSSHARP_CORE_INFO("Copying bytes from disk for {}", m_pszPath);
segment.bytes = bytes.value();
continue;
@@ -186,8 +186,7 @@ CModule::CModule(std::string_view path, std::uint64_t base)
DumpSymbols();
if (m_fnCreateInterface == nullptr)
return;
if (m_fnCreateInterface == nullptr) return;
m_bInitialized = true;
}
@@ -198,13 +197,16 @@ CModule::CModule(std::string_view path, dl_phdr_info* info)
m_pszPath = path.data();
m_baseAddress = info->dlpi_addr;
const bool should_read_from_disk = std::ranges::any_of(modules_to_read_from_disk,
[&](const auto& i) { return m_pszModule == i; });
const bool should_read_from_disk = std::any_of(modules_to_read_from_disk.begin(), modules_to_read_from_disk.end(), [&](const auto& i) {
return m_pszModule == i;
});
std::vector<std::uint8_t> disk_data{};
if (should_read_from_disk) {
if (should_read_from_disk)
{
std::ifstream stream(m_pszPath, std::ios::in | std::ios::binary);
if (!stream.good()) {
if (!stream.good())
{
CSSHARP_CORE_ERROR("Cannot open file {}", m_pszPath);
return;
}
@@ -212,25 +214,25 @@ CModule::CModule(std::string_view path, dl_phdr_info* info)
disk_data.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator<char>());
}
for (auto i = 0; i < info->dlpi_phnum; i++) {
for (auto i = 0; i < info->dlpi_phnum; i++)
{
auto address = m_baseAddress + info->dlpi_phdr[i].p_paddr;
auto type = info->dlpi_phdr[i].p_type;
auto is_dynamic_section = type == PT_DYNAMIC;
if (is_dynamic_section) {
if (is_dynamic_section)
{
DumpSymbols(reinterpret_cast<ElfW(Dyn)*>(address));
continue;
}
if (type != PT_LOAD)
continue;
if (type != PT_LOAD) continue;
auto flags = info->dlpi_phdr[i].p_flags;
auto is_executable = (flags & PF_X) != 0;
auto is_readable = (flags & PF_R) != 0;
if (!is_executable || !is_readable)
continue;
if (!is_executable || !is_readable) continue;
auto size = info->dlpi_phdr[i].p_filesz;
auto* data = reinterpret_cast<std::uint8_t*>(address);
@@ -240,8 +242,10 @@ CModule::CModule(std::string_view path, dl_phdr_info* info)
segment.address = address;
segment.bytes.reserve(size);
if (should_read_from_disk) {
if (auto bytes = GetOriginalBytes(disk_data, address - m_baseAddress, size)) {
if (should_read_from_disk)
{
if (auto bytes = GetOriginalBytes(disk_data, address - m_baseAddress, size))
{
CSSHARP_CORE_INFO("Copying bytes from disk for {}", m_pszPath);
segment.bytes = bytes.value();
continue;
@@ -253,8 +257,7 @@ CModule::CModule(std::string_view path, dl_phdr_info* info)
segment.bytes.assign(&data[0], &data[size]);
}
if (m_fnCreateInterface == nullptr)
return;
if (m_fnCreateInterface == nullptr) return;
m_bInitialized = true;
}
@@ -265,24 +268,18 @@ void CModule::DumpSymbols()
{
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(m_baseAddress);
const auto nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(
reinterpret_cast<std::uint8_t*>(m_baseAddress) + dos_header->e_lfanew);
const auto nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<std::uint8_t*>(m_baseAddress) + dos_header->e_lfanew);
const auto [export_address_rva, export_size] =
nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (export_size == 0 || export_address_rva == 0)
return;
const auto [export_address_rva, export_size] = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (export_size == 0 || export_address_rva == 0) return;
auto export_directory =
reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(m_baseAddress + export_address_rva);
const auto names =
reinterpret_cast<uint32_t*>(m_baseAddress + export_directory->AddressOfNames);
const auto addresses =
reinterpret_cast<uint32_t*>(m_baseAddress + export_directory->AddressOfFunctions);
const auto ordinals =
reinterpret_cast<std::uint16_t*>(m_baseAddress + export_directory->AddressOfNameOrdinals);
auto export_directory = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(m_baseAddress + export_address_rva);
const auto names = reinterpret_cast<uint32_t*>(m_baseAddress + export_directory->AddressOfNames);
const auto addresses = reinterpret_cast<uint32_t*>(m_baseAddress + export_directory->AddressOfFunctions);
const auto ordinals = reinterpret_cast<std::uint16_t*>(m_baseAddress + export_directory->AddressOfNameOrdinals);
for (auto i = 0ull; i < export_directory->NumberOfNames; i++) {
for (auto i = 0ull; i < export_directory->NumberOfNames; i++)
{
const auto export_name = reinterpret_cast<const char*>(m_baseAddress + names[i]);
const auto address = m_baseAddress + addresses[ordinals[i]];
@@ -290,7 +287,8 @@ void CModule::DumpSymbols()
address < reinterpret_cast<uintptr_t>(export_directory) + export_size)
continue;
if (std::string_view(export_name) == "CreateInterface") {
if (std::string_view(export_name) == "CreateInterface")
{
m_fnCreateInterface = reinterpret_cast<fnCreateInterface>(address);
}
@@ -313,33 +311,36 @@ void CModule::DumpSymbols(ElfW(Dyn) * dyn)
};
auto header = (Header*)gnuHashAddress;
const auto bucketsAddress =
gnuHashAddress + sizeof(Header) + (sizeof(std::uintptr_t) * header->bloom_size);
const auto bucketsAddress = gnuHashAddress + sizeof(Header) + (sizeof(std::uintptr_t) * header->bloom_size);
// Locate the chain that handles the largest index bucket.
uint32_t lastSymbol = 0;
auto bucketAddress = (uint32_t*)bucketsAddress;
for (uint32_t i = 0; i < header->nbuckets; ++i) {
for (uint32_t i = 0; i < header->nbuckets; ++i)
{
uint32_t bucket = *bucketAddress;
if (lastSymbol < bucket) {
if (lastSymbol < bucket)
{
lastSymbol = bucket;
}
bucketAddress++;
}
if (lastSymbol < header->symoffset) {
if (lastSymbol < header->symoffset)
{
return header->symoffset;
}
// Walk the bucket's chain to add the chain length to the total.
const auto chainBaseAddress = bucketsAddress + (sizeof(uint32_t) * header->nbuckets);
for (;;) {
auto chainEntry =
(uint32_t*)(chainBaseAddress + (lastSymbol - header->symoffset) * sizeof(uint32_t));
for (;;)
{
auto chainEntry = (uint32_t*)(chainBaseAddress + (lastSymbol - header->symoffset) * sizeof(uint32_t));
lastSymbol++;
// If the low bit is set, this entry is the end of the chain.
if (*chainEntry & 1) {
if (*chainEntry & 1)
{
break;
}
}
@@ -353,34 +354,46 @@ void CModule::DumpSymbols(ElfW(Dyn) * dyn)
char* string_table{};
std::size_t symbol_count{};
while (dyn->d_tag != DT_NULL) {
if (dyn->d_tag == DT_HASH) {
while (dyn->d_tag != DT_NULL)
{
if (dyn->d_tag == DT_HASH)
{
hash_ptr = reinterpret_cast<ElfW(Word)*>(dyn->d_un.d_ptr);
symbol_count = hash_ptr[1];
} else if (dyn->d_tag == DT_STRTAB) {
}
else if (dyn->d_tag == DT_STRTAB)
{
string_table = reinterpret_cast<char*>(dyn->d_un.d_ptr);
} else if (!symbol_count && dyn->d_tag == DT_GNU_HASH) {
}
else if (!symbol_count && dyn->d_tag == DT_GNU_HASH)
{
symbol_count = GetNumberOfSymbolsFromGnuHash(dyn->d_un.d_ptr);
} else if (dyn->d_tag == DT_SYMTAB) {
}
else if (dyn->d_tag == DT_SYMTAB)
{
symbols = reinterpret_cast<ElfW(Sym)*>(dyn->d_un.d_ptr);
for (auto i = 0; i < symbol_count; i++) {
if (!symbols[i].st_name) {
for (auto i = 0; i < symbol_count; i++)
{
if (!symbols[i].st_name)
{
continue;
}
if (symbols[i].st_other != 0) {
if (symbols[i].st_other != 0)
{
continue;
}
auto address = symbols[i].st_value + m_baseAddress;
std::string_view name = &string_table[symbols[i].st_name];
if (name == "CreateInterface") {
if (name == "CreateInterface")
{
m_fnCreateInterface = reinterpret_cast<fnCreateInterface>(address);
}
_symbols.insert({name.data(), address});
_symbols.insert({ name.data(), address });
}
}
@@ -390,11 +403,9 @@ void CModule::DumpSymbols(ElfW(Dyn) * dyn)
#endif
std::optional<std::vector<std::uint8_t>>
CModule::GetOriginalBytes(const std::vector<std::uint8_t>& disk_data, std::uintptr_t rva,
std::size_t size)
CModule::GetOriginalBytes(const std::vector<std::uint8_t>& disk_data, std::uintptr_t rva, std::size_t size)
{
auto get_file_ptr_from_rva = [](std::uint8_t* data,
std::uintptr_t address) -> std::optional<std::uintptr_t> {
auto get_file_ptr_from_rva = [](std::uint8_t* data, std::uintptr_t address) -> std::optional<std::uintptr_t> {
#ifdef _WIN32
// thank you praydog
// https://github.com/cursey/kananlib/blob/b0323a0b005fc9e3944e0ea36dcc98eda4b84eea/src/Module.cpp#L176
@@ -402,14 +413,16 @@ CModule::GetOriginalBytes(const std::vector<std::uint8_t>& disk_data, std::uintp
const auto dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(data);
const auto nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(&data[dos_header->e_lfanew]);
auto section = IMAGE_FIRST_SECTION(nt_header);
for (auto i = 0; i < nt_header->FileHeader.NumberOfSections; i++, section++) {
for (auto i = 0; i < nt_header->FileHeader.NumberOfSections; i++, section++)
{
auto section_size = section->Misc.VirtualSize;
if (section_size == 0) {
if (section_size == 0)
{
section_size = section->SizeOfRawData;
}
if (address >= section->VirtualAddress &&
address < static_cast<uintptr_t>(section->VirtualAddress) + section_size) {
if (address >= section->VirtualAddress && address < static_cast<uintptr_t>(section->VirtualAddress) + section_size)
{
const auto delta = section->VirtualAddress - section->PointerToRawData;
return reinterpret_cast<std::uintptr_t>(data + (address - delta));
@@ -423,23 +436,24 @@ CModule::GetOriginalBytes(const std::vector<std::uint8_t>& disk_data, std::uintp
};
const auto disk_ptr = get_file_ptr_from_rva(const_cast<std::uint8_t*>(disk_data.data()), rva);
if (!disk_ptr)
return std::nullopt;
if (!disk_ptr) return std::nullopt;
const auto disk_bytes = reinterpret_cast<std::uint8_t*>(*disk_ptr);
std::vector<std::uint8_t> result{&disk_bytes[0], &disk_bytes[size]};
std::vector<std::uint8_t> result{ &disk_bytes[0], &disk_bytes[size] };
return result;
}
void* CModule::FindSignature(const char* signature)
{
if (signature == nullptr || strlen(signature) == 0) {
if (signature == nullptr || strlen(signature) == 0)
{
return nullptr;
}
auto pData = CGameConfig::HexToByte(signature);
if (pData.empty()) [[unlikely]] {
if (pData.empty()) [[unlikely]]
{
CSSHARP_CORE_ERROR("Cannot convert signture \"{}\" to bytes", signature);
return nullptr;
}
@@ -449,25 +463,27 @@ void* CModule::FindSignature(const char* signature)
void* CModule::FindSignature(const std::vector<int16_t>& sigBytes)
{
for (auto&& segment : m_vecSegments) {
for (auto&& segment : m_vecSegments)
{
const auto size = segment.bytes.size();
auto* data = segment.bytes.data();
auto first_byte = sigBytes[0];
std::uint8_t* end = data + size - sigBytes.size();
for (std::uint8_t* current = data; current <= end; ++current) {
if (first_byte != -1)
current = std::find(current, end, first_byte);
for (std::uint8_t* current = data; current <= end; ++current)
{
if (first_byte != -1) current = std::find(current, end, first_byte);
if (current == end) {
if (current == end)
{
break;
}
if (std::equal(sigBytes.begin() + 1, sigBytes.end(), current + 1,
[](auto opt, auto byte) {
return opt == -1 || opt == byte;
})) {
if (std::equal(sigBytes.begin() + 1, sigBytes.end(), current + 1, [](auto opt, auto byte) {
return opt == -1 || opt == byte;
}))
{
return reinterpret_cast<void*>(current - data + segment.address);
}
}
@@ -478,7 +494,8 @@ void* CModule::FindSignature(const std::vector<int16_t>& sigBytes)
void* CModule::FindInterface(std::string_view name)
{
if (_interfaces.empty()) {
if (_interfaces.empty())
{
auto RelToAbs = [](std::uintptr_t address, int offset) {
const auto displacement = *reinterpret_cast<int32_t*>(address + offset);
return address + offset + displacement + sizeof(int32_t);
@@ -506,15 +523,16 @@ void* CModule::FindInterface(std::string_view name)
void* ret_interface{};
const auto interface_reg = *reinterpret_cast<CInterfaceRegister**>(RelToAbs(address, 3));
for (auto list = interface_reg; list != nullptr; list = list->pNext) {
for (auto list = interface_reg; list != nullptr; list = list->pNext)
{
auto interface_addrss = list->fnCreate();
if (const std::string_view interface_name = list->szName; interface_name == name)
ret_interface = interface_addrss;
if (const std::string_view interface_name = list->szName; interface_name == name) ret_interface = interface_addrss;
_interfaces.insert({list->szName, reinterpret_cast<uintptr_t>(interface_addrss)});
_interfaces.insert({ list->szName, reinterpret_cast<uintptr_t>(interface_addrss) });
}
if (ret_interface == nullptr) {
if (ret_interface == nullptr)
{
// Replace Error() from hl2sdk-cs2, it essentially calls Plat_ExitProcess
CSSHARP_CORE_ERROR("Could not find interface {} in {}", name, m_pszModule);
Plat_ExitProcess(1);
@@ -522,10 +540,11 @@ void* CModule::FindInterface(std::string_view name)
return ret_interface;
}
const auto it = _interfaces.find(name.data());
if (it == _interfaces.end()) {
if (it == _interfaces.end())
{
CSSHARP_CORE_ERROR("Could not find interface {} in {}", name, m_pszModule);
Plat_ExitProcess(1);
}
@@ -535,7 +554,8 @@ void* CModule::FindInterface(std::string_view name)
void* CModule::FindSymbol(const std::string& name)
{
if (const auto it = _symbols.find(name); it != _symbols.end()) {
if (const auto it = _symbols.find(name); it != _symbols.end())
{
return reinterpret_cast<void*>(it->second);
}

View File

@@ -193,6 +193,45 @@ void UnhookEntityOutput(ScriptContext& script_context)
globals::entityManager.UnhookEntityOutput(szClassname, szOutput, callback, mode);
}
void AcceptInput(ScriptContext& script_context)
{
if (!CEntityInstance_AcceptInput)
{
script_context.ThrowNativeError("Failed to find signature for \'CEntityInstance_AcceptInput\'");
return;
}
CEntityInstance* pThis = script_context.GetArgument<CEntityInstance*>(0);
const char* pInputName = script_context.GetArgument<const char*>(1);
CEntityInstance* pActivator = script_context.GetArgument<CEntityInstance*>(2);
CEntityInstance* pCaller = script_context.GetArgument<CEntityInstance*>(3);
const char* value = script_context.GetArgument<const char*>(4);
int outputID = script_context.GetArgument<int>(5);
variant_t _value = variant_t(value);
CEntityInstance_AcceptInput(pThis, pInputName, pActivator, pCaller, &_value, outputID);
}
void AddEntityIOEvent(ScriptContext& script_context)
{
if (!CEntitySystem_AddEntityIOEvent)
{
script_context.ThrowNativeError("Failed to find signature for \'CEntitySystem_AddEntityIOEvent\'");
return;
}
CEntityInstance* pTarget = script_context.GetArgument<CEntityInstance*>(0);
const char* pInputName = script_context.GetArgument<const char*>(1);
CEntityInstance* pActivator = script_context.GetArgument<CEntityInstance*>(2);
CEntityInstance* pCaller = script_context.GetArgument<CEntityInstance*>(3);
const char* value = script_context.GetArgument<const char*>(4);
float delay = script_context.GetArgument<float>(5);
int outputID = script_context.GetArgument<int>(6);
variant_t _value = variant_t(value);
CEntitySystem_AddEntityIOEvent(GameEntitySystem(), pTarget, pInputName, pActivator, pCaller, &_value, delay, outputID);
}
REGISTER_NATIVES(entities, {
ScriptEngine::RegisterNativeHandler("GET_ENTITY_FROM_INDEX", GetEntityFromIndex);
ScriptEngine::RegisterNativeHandler("GET_USERID_FROM_INDEX", GetUserIdFromIndex);
@@ -209,5 +248,7 @@ REGISTER_NATIVES(entities, {
ScriptEngine::RegisterNativeHandler("GET_PLAYER_IP_ADDRESS", GetPlayerIpAddress);
ScriptEngine::RegisterNativeHandler("HOOK_ENTITY_OUTPUT", HookEntityOutput);
ScriptEngine::RegisterNativeHandler("UNHOOK_ENTITY_OUTPUT", UnhookEntityOutput);
ScriptEngine::RegisterNativeHandler("ACCEPT_INPUT", AcceptInput);
ScriptEngine::RegisterNativeHandler("ADD_ENTITY_IO_EVENT", AddEntityIOEvent);
})
} // namespace counterstrikesharp
} // namespace counterstrikesharp

View File

@@ -11,4 +11,6 @@ GET_FIRST_ACTIVE_ENTITY: -> pointer
GET_PLAYER_AUTHORIZED_STEAMID: slot:int -> uint64
GET_PLAYER_IP_ADDRESS: slot:int -> string
HOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
UNHOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
UNHOOK_ENTITY_OUTPUT: classname:string, outputName:string, callback:func, mode:HookMode -> void
ACCEPT_INPUT: pThis:pointer, inputName:string, activator:pointer, caller:pointer, value:string, outputID:int -> void
ADD_ENTITY_IO_EVENT: pTarget:pointer, inputName:string, activator:pointer, caller:pointer, value:string, delay:float, outputID:int -> void

View File

@@ -88,7 +88,7 @@ public class Mapping
case "player_controller":
case "player_pawn":
case "player_controller_and_pawn":
return "CCSPlayerController";
return "CCSPlayerController?";
case "ehandle":
return "IntPtr";
case "uint64":
@@ -97,4 +97,4 @@ public class Mapping
return type;
}
}
}
}

View File

@@ -28,6 +28,32 @@ public partial class Generators
public string NamePascalCase => Name.ToPascalCase();
public string MappedType => Mapping.GetCSharpTypeFromGameEventType(Type);
public string? Comment { get; set; }
public string Getter
{
get
{
if (MappedType == "CCSPlayerController?")
{
return $"GetPlayer(\"{Name}\")";
}
return $"Get<{MappedType}>(\"{Name}\")";
}
}
public string Setter
{
get
{
if (MappedType == "CCSPlayerController?")
{
return $"SetPlayer(\"{Name}\", value)";
}
return $"Set<{MappedType}>(\"{Name}\", value)";
}
}
}
private static HttpClient _httpClient = new HttpClient();
@@ -88,6 +114,8 @@ public partial class Generators
return allGameEvents.Values.ToList();
}
public static async Task GenerateGameEvents()
{
var allGameEvents = await GetGameEvents();
@@ -102,12 +130,12 @@ public partial class Generators
: key.NamePascalCase;
return $@"
{(!string.IsNullOrEmpty(key.Comment) ? "// " + key.Comment : "")}
public {key.MappedType} {propertyName}
public {key.MappedType} {propertyName}
{{
get => Get<{key.MappedType}>(""{key.Name}"");
set => Set<{key.MappedType}>(""{key.Name}"", value);
get => {key.Getter};
set => {key.Setter};
}}";
});
return $@"
@@ -120,9 +148,10 @@ public partial class Generators
{string.Join("\n", propertyDefinition)}
}}";
}));
var result = $@"
#nullable enable
using System;
using CounterStrikeSharp.API.Modules.Events;
using CounterStrikeSharp.API.Modules.Entities;
@@ -132,11 +161,12 @@ namespace CounterStrikeSharp.API.Core
{{
{gameEventsString}
}}
#nullable restore
";
Console.WriteLine($"Generated C# bindings for {allGameEvents.Count} game events successfully.");
File.WriteAllText(Path.Join(Helpers.GetRootDirectory(), "managed/CounterStrikeSharp.API/Core/GameEvents.g.cs"),
result);
}
}
}