From ed4faf7c114495db7426023c2b47914523fcdfd1 Mon Sep 17 00:00:00 2001
From: Arkshine <hv.contact@gmail.com>
Date: Fri, 17 Jul 2015 00:26:30 +0200
Subject: [PATCH] Fix player not being internally disconnected in some
 situation + add client_disconnected forward

---
 amxmodx/CGameConfigs.cpp                   | 12 ++++
 amxmodx/CGameConfigs.h                     |  2 +
 amxmodx/CMisc.cpp                          |  2 +
 amxmodx/CMisc.h                            |  1 +
 amxmodx/meta_api.cpp                       | 76 ++++++++++++++++++++++
 gamedata/common.games/functions.engine.txt | 27 ++++++++
 gamedata/common.games/master.games.txt     |  7 ++
 plugins/admincmd.sma                       |  2 +-
 plugins/adminhelp.sma                      |  2 +-
 plugins/adminslots.sma                     |  2 +-
 plugins/cstrike/miscstats.sma              |  2 +-
 plugins/cstrike/stats_logging.sma          |  2 +-
 plugins/dod/stats.sma                      |  2 +-
 plugins/dod/stats_logging.sma              |  2 +-
 plugins/include/amxmodx.inc                | 18 ++++-
 plugins/multilingual.sma                   |  2 +-
 plugins/ns/nscommands.sma                  |  2 +-
 plugins/tfc/stats_logging.sma              |  2 +-
 plugins/ts/stats_logging.sma               |  2 +-
 public/memtools/CDetour/detours.h          |  4 ++
 support/PackageScript                      |  1 +
 21 files changed, 158 insertions(+), 14 deletions(-)
 create mode 100644 gamedata/common.games/functions.engine.txt

diff --git a/amxmodx/CGameConfigs.cpp b/amxmodx/CGameConfigs.cpp
index f6e21e9a..0c007b78 100644
--- a/amxmodx/CGameConfigs.cpp
+++ b/amxmodx/CGameConfigs.cpp
@@ -13,6 +13,7 @@
 
 CGameConfigManager ConfigManager;
 static CGameMasterReader MasterReader;
+IGameConfig *CommonConfig;
 
 //
 // GAME CONFIG
@@ -886,6 +887,17 @@ CGameConfigManager::~CGameConfigManager()
 {
 }
 
+void CGameConfigManager::OnAmxxStartup()
+{
+	char error[256] = "";
+
+	if (!LoadGameConfigFile("common.games", &CommonConfig, error, sizeof(error)))
+	{
+		AMXXLOG_Log("Could not read common.games gamedata: %s", error);
+		return;
+	}
+}
+
 bool CGameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **config, char *error, size_t maxlength)
 {
 	CGameConfig *configFromCache;
diff --git a/amxmodx/CGameConfigs.h b/amxmodx/CGameConfigs.h
index e908bdb7..d7bcfc6e 100644
--- a/amxmodx/CGameConfigs.h
+++ b/amxmodx/CGameConfigs.h
@@ -152,6 +152,7 @@ class CGameConfigManager : public IGameConfigManager
 
 	public:
 
+		void OnAmxxStartup();
 		void RemoveCachedConfig(CGameConfig *config);
 
 	private:
@@ -164,5 +165,6 @@ class CGameConfigManager : public IGameConfigManager
 };
 
 extern CGameConfigManager ConfigManager;
+extern IGameConfig *CommonConfig;
 
 #endif // _INCLUDE_GAMECONFIG_H_
diff --git a/amxmodx/CMisc.cpp b/amxmodx/CMisc.cpp
index d2ff30a3..514d5013 100755
--- a/amxmodx/CMisc.cpp
+++ b/amxmodx/CMisc.cpp
@@ -20,6 +20,7 @@ void CPlayer::Init(edict_t* e, int i)
 	initialized = false;
 	ingame = false;
 	authorized = false;
+	disconnecting = false;
 	teamIdsInitialized = false;
 
 	current = 0;
@@ -42,6 +43,7 @@ void CPlayer::Disconnect()
 	ingame = false;
 	initialized = false;
 	authorized = false;
+	disconnecting = false;
 	teamIdsInitialized = false;
 
 	if (Menu *pMenu = get_menu_by_id(newmenu))
diff --git a/amxmodx/CMisc.h b/amxmodx/CMisc.h
index 5b7368cb..b64e0764 100755
--- a/amxmodx/CMisc.h
+++ b/amxmodx/CMisc.h
@@ -37,6 +37,7 @@ public:
 	bool initialized;
 	bool ingame;
 	bool authorized;
+	bool disconnecting;
 	bool vgui;
 	bool teamIdsInitialized;
 
diff --git a/amxmodx/meta_api.cpp b/amxmodx/meta_api.cpp
index f90fc534..cb3279d8 100755
--- a/amxmodx/meta_api.cpp
+++ b/amxmodx/meta_api.cpp
@@ -28,6 +28,9 @@
 #include "CLibrarySys.h"
 #include "CFileSystem.h"
 #include "gameconfigs.h"
+#include "CGameConfigs.h"
+#include <engine_strucs.h>
+#include <CDetour/detours.h>
 
 plugin_info_t Plugin_info = 
 {
@@ -112,6 +115,8 @@ int mPlayerIndex;
 int mState;
 int g_srvindex;
 
+CDetour *DropClientDetour;
+
 cvar_t init_amxmodx_version = {"amxmodx_version", "", FCVAR_SERVER | FCVAR_SPONLY};
 cvar_t init_amxmodx_modules = {"amxmodx_modules", "", FCVAR_SPONLY};
 cvar_t init_amxmodx_debug = {"amx_debug", "1", FCVAR_SPONLY};
@@ -128,6 +133,7 @@ cvar_t* mp_timelimit = NULL;
 int FF_ClientCommand = -1;
 int FF_ClientConnect = -1;
 int FF_ClientDisconnect = -1;
+int FF_ClientDisconnected = -1;
 int FF_ClientInfoChanged = -1;
 int FF_ClientPutInServer = -1;
 int FF_PluginInit = -1;
@@ -487,6 +493,7 @@ int	C_Spawn(edict_t *pent)
 	FF_ClientCommand = registerForward("client_command", ET_STOP, FP_CELL, FP_DONE);
 	FF_ClientConnect = registerForward("client_connect", ET_IGNORE, FP_CELL, FP_DONE);
 	FF_ClientDisconnect = registerForward("client_disconnect", ET_IGNORE, FP_CELL, FP_DONE);
+	FF_ClientDisconnected = registerForward("client_disconnected", ET_IGNORE, FP_CELL, FP_CELL, FP_ARRAY, FP_CELL, FP_DONE);
 	FF_ClientInfoChanged = registerForward("client_infochanged", ET_IGNORE, FP_CELL, FP_DONE);
 	FF_ClientPutInServer = registerForward("client_putinserver", ET_IGNORE, FP_CELL, FP_DONE);
 	FF_PluginCfg = registerForward("plugin_cfg", ET_IGNORE, FP_DONE);
@@ -648,9 +655,18 @@ void C_ServerDeactivate()
 	for (int i = 1; i <= gpGlobals->maxClients; ++i)
 	{
 		CPlayer	*pPlayer = GET_PLAYER_POINTER_I(i);
+
 		if (pPlayer->initialized)
+		{
+			// deprecated
 			executeForwards(FF_ClientDisconnect, static_cast<cell>(pPlayer->index));
 
+			if (DropClientDetour && !pPlayer->disconnecting)
+			{
+				executeForwards(FF_ClientDisconnected, static_cast<cell>(pPlayer->index), FALSE, prepareCharArray(const_cast<char*>(""), 0), 0);
+			}
+		}
+
 		if (pPlayer->ingame)
 		{
 			pPlayer->Disconnect();
@@ -811,18 +827,59 @@ BOOL C_ClientConnect_Post(edict_t *pEntity, const char *pszName, const char *psz
 void C_ClientDisconnect(edict_t *pEntity)
 {
 	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
+
 	if (pPlayer->initialized)
+	{
+		// deprecated
 		executeForwards(FF_ClientDisconnect, static_cast<cell>(pPlayer->index));
+		
+		if (DropClientDetour && !pPlayer->disconnecting)
+		{
+			executeForwards(FF_ClientDisconnected, static_cast<cell>(pPlayer->index), FALSE, prepareCharArray(const_cast<char*>(""), 0), 0);
+		}
+	}
 
 	if (pPlayer->ingame)
 	{
 		--g_players_num;
 	}
+
 	pPlayer->Disconnect();
 
 	RETURN_META(MRES_IGNORED);
 }
 
+// void SV_DropClient(client_t *cl, qboolean crash, const char *fmt, ...);
+DETOUR_DECL_STATIC3_VAR(SV_DropClient, void, client_t*, cl, qboolean, crash, const char*, format)
+{
+	char buffer[1024];
+
+	va_list ap;
+	va_start(ap, format);
+	ke::SafeVsnprintf(buffer, sizeof(buffer) - 1, format, ap);
+	va_end(ap);
+
+	CPlayer *pPlayer;
+
+	if (cl->edict)
+	{
+		pPlayer = GET_PLAYER_POINTER(cl->edict);
+
+		if (pPlayer->initialized)
+		{
+			pPlayer->disconnecting = true;
+			executeForwards(FF_ClientDisconnected, pPlayer->index, TRUE, prepareCharArray(buffer, sizeof(buffer), true), sizeof(buffer) - 1);
+		}
+	}
+
+	DETOUR_STATIC_CALL(SV_DropClient)(cl, crash, "%s", buffer);
+
+	if (cl->edict)
+	{
+		pPlayer->Disconnect();
+	}
+}
+
 void C_ClientPutInServer_Post(edict_t *pEntity)
 {
 	CPlayer *pPlayer = GET_PLAYER_POINTER(pEntity);
@@ -1489,6 +1546,20 @@ C_DLLEXPORT	int	Meta_Attach(PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, m
 
 	g_CvarManager.CreateCvarHook();
 
+	ConfigManager.OnAmxxStartup();
+
+	void *address = nullptr;
+
+	if (CommonConfig && CommonConfig->GetMemSig("SV_DropClient", &address))
+	{
+		DropClientDetour = DETOUR_CREATE_STATIC_FIXED(SV_DropClient, address);
+		DropClientDetour->EnableDetour();
+	}
+	else
+	{
+		AMXXLOG_Log("client_disconnected forward has been disabled - check your gamedata files.");
+	}
+
 	GET_IFACE<IFileSystem>("filesystem_stdio", g_FileSystem, FILESYSTEM_INTERFACE_VERSION);
 
 	return (TRUE);
@@ -1534,6 +1605,11 @@ C_DLLEXPORT	int	Meta_Detach(PLUG_LOADTIME now, PL_UNLOAD_REASON	reason)
 	ClearLibraries(LibSource_Plugin);
 	ClearLibraries(LibSource_Module);
 
+	if (DropClientDetour)
+	{
+		DropClientDetour->Destroy();
+	}
+
 	return (TRUE);
 }
 
diff --git a/gamedata/common.games/functions.engine.txt b/gamedata/common.games/functions.engine.txt
new file mode 100644
index 00000000..f219d70e
--- /dev/null
+++ b/gamedata/common.games/functions.engine.txt
@@ -0,0 +1,27 @@
+/**
+ * Do not edit this file.  Any changes will be overwritten by the gamedata
+ * updater or by upgrading your AMX Mod X install.
+ *
+ * To override data in this file, create a subdirectory named "custom" and
+ * place your own gamedata file(s) inside of it.  Such files will be parsed
+ * after AMXX's own.
+ *
+ * For more information, see http://wiki.alliedmods.net/Gamedata_Updating_(AMX_Mod_X)
+ */
+
+"Games"
+{
+	"#default"
+	{
+		"Signatures"
+		{
+			"SV_DropClient" // void SV_DropClient(client_t *cl, qboolean crash, const char *fmt, ...);
+			{
+				"library"   "engine"
+				"windows"   "\x55\x8B\x2A\x81\x2A\x2A\x2A\x2A\x2A\x8B\x2A\x2A\x53\x56\x8D"
+				"linux"     "@SV_DropClient"
+				"mac"       "@SV_DropClient"
+			}
+		}
+	}
+}
diff --git a/gamedata/common.games/master.games.txt b/gamedata/common.games/master.games.txt
index 2da3a5aa..47e1476e 100644
--- a/gamedata/common.games/master.games.txt
+++ b/gamedata/common.games/master.games.txt
@@ -21,6 +21,13 @@
 		"engine"  "engine_ls"
 	}
 
+	"functions.engine.txt"
+	{
+		"engine"  "engine_ds"
+		"engine"  "engine_ls"
+	}
+
+
 
 	//
 	// Counter-Strike
diff --git a/plugins/admincmd.sma b/plugins/admincmd.sma
index 4c70038f..68f29fe0 100755
--- a/plugins/admincmd.sma
+++ b/plugins/admincmd.sma
@@ -125,7 +125,7 @@ stock GetInfo(i, name[], namesize, auth[], authsize, ip[], ipsize, &access)
 	access = g_Access[target];
 	
 }
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	if (!is_user_bot(id))
 	{
diff --git a/plugins/adminhelp.sma b/plugins/adminhelp.sma
index 6d1caf91..56b835c1 100755
--- a/plugins/adminhelp.sma
+++ b/plugins/adminhelp.sma
@@ -34,7 +34,7 @@ public client_putinserver(id)
 	}
 }
 
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	remove_task(id)
 }
diff --git a/plugins/adminslots.sma b/plugins/adminslots.sma
index 90eef653..2f5c9c0e 100755
--- a/plugins/adminslots.sma
+++ b/plugins/adminslots.sma
@@ -56,7 +56,7 @@ public client_authorized(id)
  	server_cmd("kick #%d ^"%L^"", get_user_userid(id), id, "DROPPED_RES")
 }
 
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	if (get_pcvar_num(g_HidePtr))
 	{
diff --git a/plugins/cstrike/miscstats.sma b/plugins/cstrike/miscstats.sma
index 3b6de0d2..89ecb000 100755
--- a/plugins/cstrike/miscstats.sma
+++ b/plugins/cstrike/miscstats.sma
@@ -472,7 +472,7 @@ public client_putinserver(id)
 	g_connected[id] = true
 }
 
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	g_connected[id] = false
 }
diff --git a/plugins/cstrike/stats_logging.sma b/plugins/cstrike/stats_logging.sma
index de4f7047..02c5bbeb 100755
--- a/plugins/cstrike/stats_logging.sma
+++ b/plugins/cstrike/stats_logging.sma
@@ -23,7 +23,7 @@ public plugin_init()
 	register_plugin("CS Stats Logging", AMXX_VERSION_STR, "AMXX Dev Team")
 }
 
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	if (!g_inGame[id])
 		return
diff --git a/plugins/dod/stats.sma b/plugins/dod/stats.sma
index e9313b1a..79aee492 100755
--- a/plugins/dod/stats.sma
+++ b/plugins/dod/stats.sma
@@ -819,7 +819,7 @@ new NumOfLeaders
 new LeaderID 
 new PScore[MAX_PLAYERS + 1] 
 
-public client_disconnect(id) { 
+public client_disconnected(id) { 
   if ( !LeadSounds || isDSMActive() ) return PLUGIN_CONTINUE
   if ( PScore[id] == LeaderScore && LeaderScore > 0 ){ 
     NumOfLeaders -- 
diff --git a/plugins/dod/stats_logging.sma b/plugins/dod/stats_logging.sma
index 9635a66e..8e6bc071 100755
--- a/plugins/dod/stats_logging.sma
+++ b/plugins/dod/stats_logging.sma
@@ -21,7 +21,7 @@ new g_pingCount[MAX_PLAYERS + 1]
 public plugin_init()
   register_plugin("Stats Logging",AMXX_VERSION_STR,"AMXX Dev Team")
 
-public client_disconnect(id) {
+public client_disconnected(id) {
   if ( is_user_bot( id ) || !is_user_connected(id) || !isDSMActive() ) return PLUGIN_CONTINUE
   remove_task( id )
   new szTeam[16],szName[MAX_NAME_LENGTH],szAuthid[32], iStats[9], iHits[8], szWeapon[16]
diff --git a/plugins/include/amxmodx.inc b/plugins/include/amxmodx.inc
index 3d2023bf..3770eb85 100755
--- a/plugins/include/amxmodx.inc
+++ b/plugins/include/amxmodx.inc
@@ -156,16 +156,28 @@ forward client_connect(id);
 forward client_authorized(id);
 
 /**
- * Called when a client is disconnecting from the server.
+ * @deprecated This function does not catch all cases.
+ */
+#pragma deprecated Use client_disconnected() instead.
+forward client_disconnect(id);
+
+/**
+ * Called when a client is disconnected from the server.
  *
+ * @note This will be called in some additional cases that client_disconnect doesn't cover, 
+ *       most notably when a client aborts the connection process. It is guaranteed to pair 
+ *       with the client_connect() forward.
  * @note By this point it is already too late to do anything that directly
  *       affects the client.
  *
- * @param id    Client index
+ * @param id         Client index
+ * @param drop       If true, client has been explicitly dropped by game
+ * @param message    If drop is true, a disconnected message or buffer to copy a new message to
+ * @param maxlen     Maximum size of buffer
  *
  * @noreturn
  */
-forward client_disconnect(id);
+forward client_disconnected(id, bool:drop, message[], maxlen);
 
 /**
  * Called when a client attempts to execute a command.
diff --git a/plugins/multilingual.sma b/plugins/multilingual.sma
index 253fda3c..759ca132 100755
--- a/plugins/multilingual.sma
+++ b/plugins/multilingual.sma
@@ -48,7 +48,7 @@ public client_putinserver(id)
 	}
 }
 
-public client_disconnect(id)
+public client_disconnected(id)
 {
 	remove_task(id)
 }
diff --git a/plugins/ns/nscommands.sma b/plugins/ns/nscommands.sma
index faef04be..f65b2bca 100755
--- a/plugins/ns/nscommands.sma
+++ b/plugins/ns/nscommands.sma
@@ -100,7 +100,7 @@ public msgScoreInfo() {
   g_Class[id]=read_data(g_ScoreInfo_Class);
   g_Team[id]=read_data(g_ScoreInfo_Team);
 }
-public client_disconnect(id) {
+public client_disconnected(id) {
   g_Class[id]=0;
   g_Team[id]=-1;
 }
diff --git a/plugins/tfc/stats_logging.sma b/plugins/tfc/stats_logging.sma
index 9a6750a8..e844803d 100755
--- a/plugins/tfc/stats_logging.sma
+++ b/plugins/tfc/stats_logging.sma
@@ -20,7 +20,7 @@ new g_pingCount[MAX_PLAYERS + 1]
 public plugin_init()
   register_plugin("TFC Stats Logging",AMXX_VERSION_STR,"AMXX Dev Team")
 
-public client_disconnect(id) {
+public client_disconnected(id) {
   if ( is_user_bot( id ) ) return PLUGIN_CONTINUE
   remove_task( id )
   new szTeam[16],szName[MAX_NAME_LENGTH],szAuthid[32], iStats[8], iHits[8], szWeapon[24]
diff --git a/plugins/ts/stats_logging.sma b/plugins/ts/stats_logging.sma
index 91631995..bb8b651e 100755
--- a/plugins/ts/stats_logging.sma
+++ b/plugins/ts/stats_logging.sma
@@ -22,7 +22,7 @@ new g_pingCount[MAX_PLAYERS + 1]
 public plugin_init()
   register_plugin("Stats Logging",AMXX_VERSION_STR,"AMXX Dev Team")
 
-public client_disconnect(id) {
+public client_disconnected(id) {
   if ( is_user_bot( id ) ) return PLUGIN_CONTINUE
   remove_task( id )
   new szTeam[16],szName[MAX_NAME_LENGTH],szAuthid[32], iStats[8], iHits[8], szWeapon[16]
diff --git a/public/memtools/CDetour/detours.h b/public/memtools/CDetour/detours.h
index b0335191..1eb59617 100644
--- a/public/memtools/CDetour/detours.h
+++ b/public/memtools/CDetour/detours.h
@@ -70,6 +70,10 @@ ret name(p1type p1name, p2type p2name, p3type p3name)
 ret (*name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \
 ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name)
 
+#define DETOUR_DECL_STATIC3_VAR(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \
+ret (*name##_Actual)(p1type, p2type, p3type, ...) = NULL; \
+ret name(p1type p1name, p2type p2name, p3type p3name, ...)
+
 #define DETOUR_DECL_MEMBER0(name, ret) \
 class name##Class \
 { \
diff --git a/support/PackageScript b/support/PackageScript
index 88c53844..3dc7cd78 100644
--- a/support/PackageScript
+++ b/support/PackageScript
@@ -400,6 +400,7 @@ CopyFiles('gamedata/modules.games', 'base/addons/amxmodx/data/gamedata/modules.g
 CopyFiles('gamedata/common.games', 'base/addons/amxmodx/data/gamedata/common.games',
   [ 
     'master.games.txt',
+    'functions.engine.txt',
     'globalvars.engine.txt',
   ]
 )