Merge pull request #360 from alliedmodders/cckv

Add support for listening to, blocking, changing, and faking ClientCommandKeyValues.
This commit is contained in:
Nicholas Hastings 2015-07-13 21:35:25 -04:00
commit d30cab04c3
7 changed files with 251 additions and 11 deletions

View File

@ -2,7 +2,7 @@
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
* Copyright (C) 2004-2015 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
@ -46,6 +46,7 @@
#include "ConsoleDetours.h"
#include "logic_bridge.h"
#include <sourcemod_version.h>
#include "smn_keyvalues.h"
PlayerManager g_Players;
bool g_OnMapStarted = false;
@ -76,6 +77,9 @@ SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *)
#endif
SH_DECL_HOOK1_void(IServerGameClients, ClientSettingsChanged, SH_NOATTRIB, 0, edict_t *);
#endif // SE_DOTA
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
SH_DECL_HOOK2_void(IServerGameClients, ClientCommandKeyValues, SH_NOATTRIB, 0, edict_t *, KeyValues *);
#endif
#if SOURCE_ENGINE == SE_DOTA
SH_DECL_HOOK0_void(IServerGameDLL, ServerActivate, SH_NOATTRIB, 0);
@ -136,6 +140,7 @@ PlayerManager::PlayerManager()
m_SourceTVUserId = -1;
m_ReplayUserId = -1;
m_bInCCKVHook = false;
m_bAuthstringValidation = true; // use steam auth by default
m_UserIdLookUp = new int[USHRT_MAX+1];
@ -171,6 +176,10 @@ void PlayerManager::OnSourceModAllInitialized()
SH_ADD_HOOK(IServerGameClients, ClientDisconnect, serverClients, SH_MEMBER(this, &PlayerManager::OnClientDisconnect), false);
SH_ADD_HOOK(IServerGameClients, ClientDisconnect, serverClients, SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
SH_ADD_HOOK(IServerGameClients, ClientCommand, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
SH_ADD_HOOK(IServerGameClients, ClientCommandKeyValues, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommandKeyValues), false);
SH_ADD_HOOK(IServerGameClients, ClientCommandKeyValues, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommandKeyValues_Post), true);
#endif
SH_ADD_HOOK(IServerGameClients, ClientSettingsChanged, serverClients, SH_MEMBER(this, &PlayerManager::OnClientSettingsChanged), true);
SH_ADD_HOOK(IServerGameDLL, ServerActivate, gamedll, SH_MEMBER(this, &PlayerManager::OnServerActivate), true);
#if SOURCE_ENGINE >= SE_LEFT4DEAD && SOURCE_ENGINE != SE_DOTA
@ -190,6 +199,8 @@ void PlayerManager::OnSourceModAllInitialized()
m_cldisconnect = forwardsys->CreateForward("OnClientDisconnect", ET_Ignore, 1, p2);
m_cldisconnect_post = forwardsys->CreateForward("OnClientDisconnect_Post", ET_Ignore, 1, p2);
m_clcommand = forwardsys->CreateForward("OnClientCommand", ET_Hook, 2, NULL, Param_Cell, Param_Cell);
m_clcommandkv = forwardsys->CreateForward("OnClientCommandKeyValues", ET_Hook, 2, NULL, Param_Cell, Param_Cell);
m_clcommandkv_post = forwardsys->CreateForward("OnClientCommandKeyValues_Post", ET_Ignore, 2, NULL, Param_Cell, Param_Cell);
m_clinfochanged = forwardsys->CreateForward("OnClientSettingsChanged", ET_Ignore, 1, p2);
m_clauth = forwardsys->CreateForward("OnClientAuthorized", ET_Ignore, 2, NULL, Param_Cell, Param_String);
m_onActivate = forwardsys->CreateForward("OnServerLoad", ET_Ignore, 0, NULL);
@ -218,6 +229,10 @@ void PlayerManager::OnSourceModShutdown()
SH_REMOVE_HOOK(IServerGameClients, ClientDisconnect, serverClients, SH_MEMBER(this, &PlayerManager::OnClientDisconnect), false);
SH_REMOVE_HOOK(IServerGameClients, ClientDisconnect, serverClients, SH_MEMBER(this, &PlayerManager::OnClientDisconnect_Post), true);
SH_REMOVE_HOOK(IServerGameClients, ClientCommand, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommand), false);
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
SH_REMOVE_HOOK(IServerGameClients, ClientCommandKeyValues, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommandKeyValues), false);
SH_REMOVE_HOOK(IServerGameClients, ClientCommandKeyValues, serverClients, SH_MEMBER(this, &PlayerManager::OnClientCommandKeyValues_Post), true);
#endif
SH_REMOVE_HOOK(IServerGameClients, ClientSettingsChanged, serverClients, SH_MEMBER(this, &PlayerManager::OnClientSettingsChanged), true);
SH_REMOVE_HOOK(IServerGameDLL, ServerActivate, gamedll, SH_MEMBER(this, &PlayerManager::OnServerActivate), true);
#if SOURCE_ENGINE >= SE_LEFT4DEAD && SOURCE_ENGINE != SE_DOTA
@ -233,6 +248,8 @@ void PlayerManager::OnSourceModShutdown()
forwardsys->ReleaseForward(m_cldisconnect);
forwardsys->ReleaseForward(m_cldisconnect_post);
forwardsys->ReleaseForward(m_clcommand);
forwardsys->ReleaseForward(m_clcommandkv);
forwardsys->ReleaseForward(m_clcommandkv_post);
forwardsys->ReleaseForward(m_clinfochanged);
forwardsys->ReleaseForward(m_clauth);
forwardsys->ReleaseForward(m_onActivate);
@ -1219,6 +1236,86 @@ void PlayerManager::OnClientCommand(edict_t *pEntity)
}
}
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
static bool s_LastCCKVAllowed = true;
void PlayerManager::OnClientCommandKeyValues(edict_t *pEntity, KeyValues *pCommand)
{
int client = IndexOfEdict(pEntity);
cell_t res = Pl_Continue;
CPlayer *pPlayer = &m_Players[client];
if (!pPlayer->IsInGame())
{
RETURN_META(MRES_IGNORED);
}
KeyValueStack *pStk = new KeyValueStack;
pStk->pBase = pCommand;
pStk->pCurRoot.push(pStk->pBase);
pStk->m_bDeleteOnDestroy = false;
Handle_t hndl = handlesys->CreateHandle(g_KeyValueType, pStk, g_pCoreIdent, g_pCoreIdent, NULL);
m_bInCCKVHook = true;
m_clcommandkv->PushCell(client);
m_clcommandkv->PushCell(hndl);
m_clcommandkv->Execute(&res);
m_bInCCKVHook = false;
HandleSecurity sec(g_pCoreIdent, g_pCoreIdent);
// Deletes pStk
handlesys->FreeHandle(hndl, &sec);
if (res >= Pl_Handled)
{
s_LastCCKVAllowed = false;
RETURN_META(MRES_SUPERCEDE);
}
s_LastCCKVAllowed = true;
RETURN_META(MRES_IGNORED);
}
void PlayerManager::OnClientCommandKeyValues_Post(edict_t *pEntity, KeyValues *pCommand)
{
if (!s_LastCCKVAllowed)
{
return;
}
int client = IndexOfEdict(pEntity);
CPlayer *pPlayer = &m_Players[client];
if (!pPlayer->IsInGame())
{
return;
}
KeyValueStack *pStk = new KeyValueStack;
pStk->pBase = pCommand;
pStk->pCurRoot.push(pStk->pBase);
pStk->m_bDeleteOnDestroy = false;
Handle_t hndl = handlesys->CreateHandle(g_KeyValueType, pStk, g_pCoreIdent, g_pCoreIdent, NULL);
m_bInCCKVHook = true;
m_clcommandkv_post->PushCell(client);
m_clcommandkv_post->PushCell(hndl);
m_clcommandkv_post->Execute();
m_bInCCKVHook = false;
HandleSecurity sec(g_pCoreIdent, g_pCoreIdent);
// Deletes pStk
handlesys->FreeHandle(hndl, &sec);
}
#endif
#if SOURCE_ENGINE == SE_DOTA
void PlayerManager::OnClientSettingsChanged(CEntityIndex index)
{

View File

@ -2,7 +2,7 @@
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
* Copyright (C) 2004-2015 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
@ -181,6 +181,10 @@ public:
void OnClientDisconnect_Post(edict_t *pEntity);
#if SOURCE_ENGINE >= SE_ORANGEBOX
void OnClientCommand(edict_t *pEntity, const CCommand &args);
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
void OnClientCommandKeyValues(edict_t *pEntity, KeyValues *pCommand);
void OnClientCommandKeyValues_Post(edict_t *pEntity, KeyValues *pCommand);
#endif
#else
void OnClientCommand(edict_t *pEntity);
#endif
@ -224,6 +228,10 @@ public:
unsigned int GetReplyTo();
unsigned int SetReplyTo(unsigned int reply);
void MaxPlayersChanged(int newvalue = -1);
inline bool InClientCommandKeyValuesHook()
{
return m_bInCCKVHook;
}
#if SOURCE_ENGINE == SE_CSGO
bool HandleConVarQuery(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue);
#endif
@ -242,6 +250,8 @@ private:
IForward *m_cldisconnect_post;
IForward *m_clputinserver;
IForward *m_clcommand;
IForward *m_clcommandkv;
IForward *m_clcommandkv_post;
IForward *m_clinfochanged;
IForward *m_clauth;
IForward *m_onActivate;
@ -262,6 +272,7 @@ private:
bool m_bIsReplayActive;
int m_SourceTVUserId;
int m_ReplayUserId;
bool m_bInCCKVHook;
};
#if SOURCE_ENGINE == SE_DOTA

View File

@ -46,6 +46,7 @@
#include "ConCommandBaseIterator.h"
#include "logic_bridge.h"
#include <sm_namehashset.h>
#include "smn_keyvalues.h"
#if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA
#include <netmessages.pb.h>
@ -1292,6 +1293,50 @@ static cell_t ConVar_ReplicateToClient(IPluginContext *pContext, const cell_t *p
return SendConVarValue(pContext, new_params);
}
static cell_t FakeClientCommandKeyValues(IPluginContext *pContext, const cell_t *params)
{
#if SOURCE_ENGINE >= SE_EYE && SOURCE_ENGINE != SE_DOTA
int client = params[1];
CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
if (!pPlayer)
{
return pContext->ThrowNativeError("Client index %d is invalid", client);
}
else if (!pPlayer->IsConnected())
{
return pContext->ThrowNativeError("Client %d is not connected", client);
}
Handle_t hndl = static_cast<Handle_t>(params[2]);
HandleError herr;
HandleSecurity sec;
KeyValueStack *pStk;
sec.pOwner = NULL;
sec.pIdentity = g_pCoreIdent;
if ((herr = handlesys->ReadHandle(hndl, g_KeyValueType, &sec, (void **) &pStk))
!= HandleError_None)
{
return pContext->ThrowNativeError("Invalid key value handle %x (error %d)", hndl, herr);
}
if (g_Players.InClientCommandKeyValuesHook())
{
SH_CALL(serverClients, &IServerGameClients::ClientCommandKeyValues)(pPlayer->GetEdict(), pStk->pBase);
}
else
{
serverClients->ClientCommandKeyValues(pPlayer->GetEdict(), pStk->pBase);
}
return 1;
#else
return pContext->ThrowNativeError("FakeClientCommandKeyValues is not supported on this game.");
#endif
}
REGISTER_NATIVES(consoleNatives)
{
{"CreateConVar", sm_CreateConVar},
@ -1332,6 +1377,7 @@ REGISTER_NATIVES(consoleNatives)
{"SendConVarValue", SendConVarValue},
{"AddCommandListener", AddCommandListener},
{"RemoveCommandListener", RemoveCommandListener},
{"FakeClientCommandKeyValues", FakeClientCommandKeyValues},
// Transitional syntax support.
{"ConVar.BoolValue.get", sm_GetConVarBool},

View File

@ -2,7 +2,7 @@
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
* Copyright (C) 2004-2015 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
@ -29,6 +29,8 @@
* Version: $Id$
*/
#include "smn_keyvalues.h"
#include "sourcemod.h"
#include "sourcemm_api.h"
#include "sm_stringutil.h"
@ -39,12 +41,6 @@
HandleType_t g_KeyValueType;
struct KeyValueStack
{
KeyValues *pBase;
CStack<KeyValues *> pCurRoot;
};
class KeyValueNatives :
public SMGlobalClass,
public IHandleTypeDispatch
@ -62,7 +58,11 @@ public:
void OnHandleDestroy(HandleType_t type, void *object)
{
KeyValueStack *pStk = reinterpret_cast<KeyValueStack *>(object);
pStk->pBase->deleteThis();
if (pStk->m_bDeleteOnDestroy)
{
pStk->pBase->deleteThis();
}
delete pStk;
}
int CalcKVSizeR(KeyValues *pv)

51
core/smn_keyvalues.h Normal file
View File

@ -0,0 +1,51 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2015 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* 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/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_KVWRAPPER_H_
#define _INCLUDE_SOURCEMOD_KVWRAPPER_H_
#include <IHandleSys.h>
#include <sh_stack.h>
using namespace SourceMod;
class KeyValues;
struct KeyValueStack
{
KeyValues *pBase;
SourceHook::CStack<KeyValues *> pCurRoot;
bool m_bDeleteOnDestroy = true;
};
extern HandleType_t g_KeyValueType;;
#endif // _INCLUDE_SOURCEMOD_KVWRAPPER_H_

View File

@ -138,10 +138,34 @@ forward void OnClientDisconnect_Post(client);
*
* @param client Client index.
* @param args Number of arguments.
* @noreturn
* @return Plugin_Handled blocks the command from being sent,
* and Plugin_Continue resumes normal functionality.
*/
forward Action:OnClientCommand(client, args);
/**
* Called when a client is sending a KeyValues command.
*
* @param client Client index.
* @param kv Editable KeyValues data to be sent as the command.
* (This handle should not be stored and will be closed
* after this forward completes.)
* @return Plugin_Handled blocks the command from being sent,
* and Plugin_Continue resumes normal functionality.
*/
forward Action OnClientCommandKeyValues(int client, KeyValues kv);
/**
* Called after a client has sent a KeyValues command.
*
* @param client Client index.
* @param kv KeyValues data sent as the command.
* (This handle should not be stored and will be closed
* after this forward completes.)
* @noreturn
*/
forward void OnClientCommandKeyValues_Post(int client, KeyValues kv);
/**
* Called whenever the client's settings are changed.
*

View File

@ -190,6 +190,17 @@ native FakeClientCommand(client, const String:fmt[], any:...);
*/
native FakeClientCommandEx(client, const String:fmt[], any:...);
/**
* Executes a KeyValues client command on the server without being networked.
*
* @param client Index of the client.
* @param kv KeyValues data to be sent.
* @noreturn
* @error Invalid client index, client not connected,
* or unsupported on current game.
*/
native void FakeClientCommandKeyValues(int client, KeyValues kv);
/**
* Sends a message to the server console.
*