Skip to content

Commit

Permalink
Add buttonwatch (#263)
Browse files Browse the repository at this point in the history
* Add buttonwatch
- Prints a message every time an entity fires OnPressed to players with buttonwatch enabled
- Adds c_bw for players with ADMFLAG_GENERIC to toggle buttonwatch on and off (disabled by default)
- Uses output hooking stuff from cs#: https://github.com/roflmuffin/CounterStrikeSharp/blob/f8451c2818a26380b49229655956e3058c3855ff/configs/addons/counterstrikesharp/gamedata/gamedata.json#L223-L228

* Clarify comment and decrease timer delay

* Disable buttonwatch by default, as it breaks CS#
- Add config cs2fixes.jsonc for configs that require a restart to changes
- Fix c_bw using console instead of chat for command echo

* Add console printing modes and anti-spam

* Remove timer that was needed with old ClientPrint

* Delay when user pref is grabbed

* Remove this as not being used anymore by bw

* Make IO detour more modular

* Fix issues

* Restructure to move out of entities.cpp/h

* adjustments

* Fix stupid error from refactor

* Null check pActivator

---------

Co-authored-by: Vauff <[email protected]>
  • Loading branch information
Frozen-H2O and Vauff authored Dec 9, 2024
1 parent 85af0af commit 153ae27
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 4 deletions.
1 change: 1 addition & 0 deletions AMBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/customio.cpp',
'src/entitylistener.cpp',
'src/leader.cpp',
'src/buttonwatch.cpp',
'src/idlemanager.cpp',
'sdk/entity2/entitysystem.cpp',
'sdk/entity2/entityidentity.cpp',
Expand Down
3 changes: 3 additions & 0 deletions CS2Fixes.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\buttonwatch.cpp" />
<ClCompile Include="protobuf\generated\cs_usercmd.pb.cc" />
<ClCompile Include="protobuf\generated\networkbasetypes.pb.cc" />
<ClCompile Include="protobuf\generated\network_connection.pb.cc" />
Expand Down Expand Up @@ -216,6 +217,7 @@
<ClCompile Include="src\utils\plat_win.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\buttonwatch.h" />
<ClInclude Include="src\adminsystem.h" />
<ClInclude Include="src\commands.h" />
<ClInclude Include="src\common.h" />
Expand Down Expand Up @@ -247,6 +249,7 @@
<ClInclude Include="src\cs2_sdk\entity\lights.h" />
<ClInclude Include="src\cs2_sdk\entity\services.h" />
<ClInclude Include="src\cs2_sdk\schema.h" />
<ClInclude Include="src\cs2_sdk\entityio.h" />
<ClInclude Include="src\cdetour.h" />
<ClInclude Include="src\ctimer.h" />
<ClInclude Include="src\customio.h" />
Expand Down
9 changes: 9 additions & 0 deletions CS2Fixes.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@
<ClCompile Include="sdk\public\tier0\memoverride.cpp">
<Filter>Source Files\sdk</Filter>
</ClCompile>
<ClCompile Include="src\buttonwatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\mempatch.h">
Expand All @@ -196,6 +199,9 @@
<ClInclude Include="src\cs2_sdk\schema.h">
<Filter>Header Files\cs2_sdk</Filter>
</ClInclude>
<ClInclude Include="src\cs2_sdk\entityio.h">
<Filter>Header Files\cs2_sdk</Filter>
</ClInclude>
<ClInclude Include="src\cs2_sdk\entity\cbaseplayercontroller.h">
<Filter>Header Files\cs2_sdk\entity</Filter>
</ClInclude>
Expand Down Expand Up @@ -295,6 +301,9 @@
<ClInclude Include="src\leader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\buttonwatch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\cs2_sdk\entity\lights.h">
<Filter>Header Files\cs2_sdk\entity</Filter>
</ClInclude>
Expand Down
5 changes: 4 additions & 1 deletion cfg/cs2fixes/cs2fixes.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,7 @@ cs2f_leader_max_beacons 3 // Max amount of beacons set by leaders (doesn
// Idle Kick Settings
cs2f_idle_kick_time 0.0 // Amount of minutes before kicking idle players. 0 to disable afk kicking.
cs2f_idle_kick_min_players 0 // Minimum amount of connected clients to kick idle players.
cs2f_idle_kick_admins 1 // Whether to kick idle players with ADMFLAG_GENERIC
cs2f_idle_kick_admins 1 // Whether to kick idle players with ADMFLAG_GENERIC
// Button watch
cs2f_enable_button_watch 0 // INCOMPATIBLE WITH CS#. Whether to enable button watch or not.
10 changes: 10 additions & 0 deletions gamedata/cs2fixes.games.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@
"windows" "\x48\x89\x54\x24\x2A\x48\x89\x4C\x24\x2A\x55\x53\x56\x57\x41\x55\x41\x56\x41\x57\x48\x8D\x6C\x24"
"linux" "\x55\x48\x89\xE5\x41\x57\x41\x56\x4C\x8D\xBD\xD0\xFE\xFF\xFF\x49\x89\xD6\x41\x55\x49\x89\xF5\x41\x54\x49\x89\xCC"
}
// func_pushable inside CTriggerBrush::Use calls CEntityIOOutput::FireOutputInternal
// Windows - https://imgur.com/a/A3zcxQm
// Linux doesn't inline it so you need to find any of the xrefs and the proper function is right after it.
// You can tell it apart by the arguments: (a1 + 2616, v4, a1, &v6, 0.0);
"CEntityIOOutput_FireOutputInternal"
{
"library" "server"
"windows" "\x4C\x89\x4C\x24\x20\x53\x55\x57\x41\x54\x41\x56\x48\x81\xEC"
"linux" "\x55\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x41\x54\x49\x89\xD4\x53\x48\x89\xF3\x48\x83\xEC\x58"
}
// "multi_manager" is passed to this
"CGameEntitySystem_FindEntityByClassName"
{
Expand Down
147 changes: 147 additions & 0 deletions src/buttonwatch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2024 Source2ZE
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "buttonwatch.h"

#include "ctimer.h"
#include "entity.h"
#include "entity/cbaseplayercontroller.h"
#include "entity/ccsplayercontroller.h"
#include "entity/ccsplayerpawn.h"
#include "entity/cgameplayerequip.h"
#include "entity/cgamerules.h"
#include "entity/clogiccase.h"
#include "entity/cpointviewcontrol.h"
#include "detours.h"
#include "cs2fixes.h"
#include "commands.h"

CON_COMMAND_F(cs2f_enable_button_watch, "INCOMPATIBLE WITH CS#. Whether to enable button watch or not.", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY | FCVAR_PROTECTED)
{
if (args.ArgC() < 2)
{
Msg("%s %i\n", args[0], IsButtonWatchEnabled());
return;
}

if (!V_StringToBool(args[1], false) || !SetupFireOutputInternalDetour())
mapIOFunctions.erase("buttonwatch");
else if (!IsButtonWatchEnabled())
mapIOFunctions["buttonwatch"] = ButtonWatch;
}

CON_COMMAND_CHAT_FLAGS(bw, "- Toggle button watch display", ADMFLAG_GENERIC)
{
if (!IsButtonWatchEnabled())
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Button watch is disabled on this server.");
return;
}

if (!player)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You cannot use this command from the server console.");
return;
}

ZEPlayer* zpPlayer = player->GetZEPlayer();
if (!zpPlayer)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Something went wrong, please wait a moment before trying this command again.");
return;
}

zpPlayer->CycleButtonWatch();

switch (zpPlayer->GetButtonWatchMode())
{
case 0:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have\x02 disabled\1 button watch.");
break;
case 1:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have\x04 enabled\1 button watch in chat.");
break;
case 2:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have\x04 enabled\1 button watch in console.");
break;
case 3:
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have\x04 enabled\1 button watch in chat and console.");
break;
}
}

bool IsButtonWatchEnabled()
{
return std::any_of(mapIOFunctions.begin(), mapIOFunctions.end(), [](const auto& p) {
return p.first == "buttonwatch";
});
}

std::map <int, bool> mapRecentEnts;
void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay)
{
if (!IsButtonWatchEnabled() || V_stricmp(pThis->m_pDesc->m_pName, "OnPressed") ||
!pActivator || !((CBaseEntity*)pActivator)->IsPawn() ||
!pCaller || mapRecentEnts.contains(pCaller->GetEntityIndex().Get()))
return;

CCSPlayerController* ccsPlayer = CCSPlayerController::FromPawn(static_cast<CCSPlayerPawn*>(pActivator));
std::string strPlayerName = ccsPlayer->GetPlayerName();

ZEPlayer* zpPlayer = ccsPlayer->GetZEPlayer();
std::string strPlayerID = "";
if (zpPlayer && !zpPlayer->IsFakeClient())
{
strPlayerID = std::to_string(zpPlayer->IsAuthenticated() ? zpPlayer->GetSteamId64() : zpPlayer->GetUnauthenticatedSteamId64());
strPlayerID = "(" + strPlayerID + ")";
}

std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " +
std::string(((CBaseEntity*)pCaller)->GetName());

for (int i = 0; i < gpGlobals->maxClients; i++)
{
CCSPlayerController* ccsPlayer = CCSPlayerController::FromSlot(i);
if (!ccsPlayer)
continue;

ZEPlayer* zpPlayer = ccsPlayer->GetZEPlayer();
if (!zpPlayer)
continue;

if (zpPlayer->GetButtonWatchMode() % 2 == 1)
ClientPrint(ccsPlayer, HUD_PRINTTALK, " \x02[BW]\x0C %s\1 pressed button \x0C%s\1", strPlayerName.c_str(), strButton.c_str());
if (zpPlayer->GetButtonWatchMode() >= 2)
{
ClientPrint(ccsPlayer, HUD_PRINTCONSOLE, "------------------------------------ [ButtonWatch] ------------------------------------");
ClientPrint(ccsPlayer, HUD_PRINTCONSOLE, "Player: %s %s", strPlayerName.c_str(), strPlayerID.c_str());
ClientPrint(ccsPlayer, HUD_PRINTCONSOLE, "Button: %s", strButton.c_str());
ClientPrint(ccsPlayer, HUD_PRINTCONSOLE, "---------------------------------------------------------------------------------------");
}
}

// Limit each button to only printing out at most once every 5 seconds
int iIndex = pCaller->GetEntityIndex().Get();
mapRecentEnts[iIndex] = true;
new CTimer(5.0f, true, true, [iIndex]()
{
mapRecentEnts.erase(iIndex);
return -1.0f;
});
}
24 changes: 24 additions & 0 deletions src/buttonwatch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2024 Source2ZE
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once
#include "detours.h"

bool IsButtonWatchEnabled();
void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay);
62 changes: 62 additions & 0 deletions src/cs2_sdk/entityio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2024 Source2ZE
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

// Bandaid needed for #include "string_t.h" to compile...
#ifndef NULL
#define NULL 0
#endif

#include <string_t.h>
#include <entityhandle.h>
#include <entitysystem.h>
#include <entityinstance.h>

struct EntityIOConnectionDesc_t
{
string_t m_targetDesc;
string_t m_targetInput;
string_t m_valueOverride;
CEntityHandle m_hTarget;
EntityIOTargetType_t m_nTargetType;
int32 m_nTimesToFire;
float m_flDelay;
};

struct EntityIOConnection_t : EntityIOConnectionDesc_t
{
bool m_bMarkedForRemoval;
EntityIOConnection_t* m_pNext;
};

struct EntityIOOutputDesc_t
{
const char* m_pName;
uint32 m_nFlags;
uint32 m_nOutputOffset;
};

class CEntityIOOutput
{
public:
void* vtable;
EntityIOConnection_t* m_pConnections;
EntityIOOutputDesc_t* m_pDesc;
};
32 changes: 32 additions & 0 deletions src/detours.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "serversideclient.h"
#include "tier0/vprof.h"
#include "zombiereborn.h"
#include "buttonwatch.h"

#include "tier0/memdbgon.h"

Expand Down Expand Up @@ -632,6 +633,37 @@ bool FASTCALL Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceF
return TraceShape(a1, a2, a3, a4, filter, a6);
}


CDetour<decltype(Detour_CEntityIOOutput_FireOutputInternal)>* CEntityIOOutput_FireOutputInternal = nullptr;
std::map<std::string, std::function<void(const CEntityIOOutput*, CEntityInstance*, CEntityInstance*, const CVariant*, float)>> mapIOFunctions{};
void FASTCALL Detour_CEntityIOOutput_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay)
{
for (const auto& [name, cb] : mapIOFunctions)
cb(pThis, pActivator, pCaller, value, flDelay);

(*CEntityIOOutput_FireOutputInternal)(pThis, pActivator, pCaller, value, flDelay);
}

// Tries to setup Detour_CEntityIOOutput_FireOutputInternal if it is not already setup. This is not
// enabled unless a feature needs it, as the detour breaks CS# compatibility
// Returns true if detour is usable, otherwise false.
bool SetupFireOutputInternalDetour()
{
if (CEntityIOOutput_FireOutputInternal != nullptr)
return true;

CEntityIOOutput_FireOutputInternal = new CDetour(Detour_CEntityIOOutput_FireOutputInternal, "CEntityIOOutput_FireOutputInternal");
if (!CEntityIOOutput_FireOutputInternal->CreateDetour(g_GameConfig))
{
Panic("Failed to detour CEntityIOOutput_FireOutputInternal\n");
delete CEntityIOOutput_FireOutputInternal;
CEntityIOOutput_FireOutputInternal = nullptr;
return false;
}
CEntityIOOutput_FireOutputInternal->EnableDetour();
return true;
}

#ifdef PLATFORM_WINDOWS
Vector* FASTCALL Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn, Vector* pRet)
{
Expand Down
Loading

0 comments on commit 153ae27

Please sign in to comment.