diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 20d41b68..a46e1f99 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -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 +#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,76 @@ 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; + + 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; +} +#endif + #if SOURCE_ENGINE == SE_DOTA void PlayerManager::OnClientSettingsChanged(CEntityIndex index) { diff --git a/core/PlayerManager.h b/core/PlayerManager.h index d580e848..4c16c79d 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -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 diff --git a/core/smn_console.cpp b/core/smn_console.cpp index f4cece35..f5467e57 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -46,6 +46,7 @@ #include "ConCommandBaseIterator.h" #include "logic_bridge.h" #include +#include "smn_keyvalues.h" #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA #include @@ -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(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}, diff --git a/core/smn_keyvalues.cpp b/core/smn_keyvalues.cpp index a3b997c5..f420c5b1 100644 --- a/core/smn_keyvalues.cpp +++ b/core/smn_keyvalues.cpp @@ -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 pCurRoot; -}; - class KeyValueNatives : public SMGlobalClass, public IHandleTypeDispatch @@ -62,7 +58,11 @@ public: void OnHandleDestroy(HandleType_t type, void *object) { KeyValueStack *pStk = reinterpret_cast(object); - pStk->pBase->deleteThis(); + if (pStk->m_bDeleteOnDestroy) + { + pStk->pBase->deleteThis(); + } + delete pStk; } int CalcKVSizeR(KeyValues *pv) diff --git a/core/smn_keyvalues.h b/core/smn_keyvalues.h new file mode 100644 index 00000000..de032506 --- /dev/null +++ b/core/smn_keyvalues.h @@ -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 . +* +* 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 . +* +* Version: $Id$ +*/ + +#ifndef _INCLUDE_SOURCEMOD_KVWRAPPER_H_ +#define _INCLUDE_SOURCEMOD_KVWRAPPER_H_ + +#include +#include + +using namespace SourceMod; + +class KeyValues; + +struct KeyValueStack +{ + KeyValues *pBase; + SourceHook::CStack pCurRoot; + bool m_bDeleteOnDestroy = true; +}; + +extern HandleType_t g_KeyValueType;; + +#endif // _INCLUDE_SOURCEMOD_KVWRAPPER_H_ \ No newline at end of file diff --git a/plugins/include/clients.inc b/plugins/include/clients.inc index 226ce3a9..41411593 100644 --- a/plugins/include/clients.inc +++ b/plugins/include/clients.inc @@ -138,10 +138,32 @@ 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 cannot be closed.) + * @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 cannot be closed.) + * @noreturn + */ +forward void OnClientCommandKeyValues_Post(int client, KeyValues kv); + /** * Called whenever the client's settings are changed. * diff --git a/plugins/include/console.inc b/plugins/include/console.inc index 7f27d50c..aea4ee03 100644 --- a/plugins/include/console.inc +++ b/plugins/include/console.inc @@ -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. *