diff --git a/AMBuildScript b/AMBuildScript index e26c518..acdda43 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -149,7 +149,7 @@ class ExtensionConfig(object): '-pipe', '-fno-strict-aliasing', '-Wall', - '-Werror', +# '-Werror', '-Wno-unused', '-Wno-switch', '-Wno-array-bounds', diff --git a/extension.cpp b/extension.cpp index 499f104..9e5ac63 100644 --- a/extension.cpp +++ b/extension.cpp @@ -8,7 +8,7 @@ * 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 @@ -33,7 +33,43 @@ #include "CDetour/detours.h" #include "steam/steam_gameserver.h" #include "sm_namehashset.h" +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +size_t +strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} /** * @file extension.cpp @@ -42,20 +78,110 @@ Connect g_Connect; /**< Global singleton for extension's main interface */ ConnectEvents g_ConnectEvents; +ConnectTimer g_ConnectTimer; SMEXT_LINK(&g_Connect); ConVar g_ConnectVersion("connect_version", SMEXT_CONF_VERSION, FCVAR_REPLICATED|FCVAR_NOTIFY, SMEXT_CONF_DESCRIPTION " Version"); ConVar g_SvNoSteam("sv_nosteam", "0", FCVAR_NOTIFY, "Disable steam validation and force steam authentication."); ConVar g_SvForceSteam("sv_forcesteam", "0", FCVAR_NOTIFY, "Force steam authentication."); +ConVar *g_pSvVisibleMaxPlayers; +ConVar *g_pSvTags; IGameConfig *g_pGameConf = NULL; IForward *g_pConnectForward = NULL; IGameEventManager2 *g_pGameEvents = NULL; +ITimer *g_pConnectTimer = NULL; +ISDKTools *g_pSDKTools = NULL; +IServer *iserver = NULL; +CGlobalVars *gpGlobals = NULL; +IHLTVDirector *hltvdirector = NULL; +IHLTVServer *hltv = NULL; + +uint8_t g_UserIDtoClientMap[USHRT_MAX + 1]; +char g_ClientSteamIDMap[SM_MAXPLAYERS + 1][32]; + +typedef struct netpacket_s +{ + netadr_t from; // sender IP + int source; // received source + double received; // received time + unsigned char *data; // pointer to raw packet data + bf_read message; // easy bitbuf data access + int size; // size in bytes + int wiresize; // size in bytes before decompression + bool stream; // was send as stream + struct netpacket_s *pNext; // for internal use, should be NULL in public +} netpacket_t; + +typedef struct +{ + int nPort; // UDP/TCP use same port number + bool bListening; // true if TCP port is listening + int hUDP; // handle to UDP socket from socket() + int hTCP; // handle to TCP socket from socket() +} netsocket_t; + +CUtlVector *net_sockets; +int g_ServerUDPSocket = 0; + +SH_DECL_MANUALHOOK1(ProcessConnectionlessPacket, 0, 0, 0, bool, netpacket_t *); // virtual bool IServer::ProcessConnectionlessPacket( netpacket_t *packet ) = 0; + +void *s_queryRateChecker = NULL; +bool (*CIPRateLimit__CheckIP)(void *pThis, netadr_t adr); +bool (*CBaseServer__ValidChallenge)(void *pThis, netadr_t adr, int challengeNr); + +struct CQueryCache +{ + struct CPlayer + { + bool active; + bool fake; + int userid; + IClient *pClient; + char name[MAX_PLAYER_NAME_LENGTH]; + unsigned nameLen; + int32_t score; + float time; + } players[SM_MAXPLAYERS + 1]; + + struct CInfo + { + uint8_t nProtocol = 17; // Protocol | byte | Protocol version used by the server. + char aHostName[255]; // Name | string | Name of the server. + uint8_t aHostNameLen; + char aMapName[255]; // Map | string | Map the server has currently loaded. + uint8_t aMapNameLen; + char aGameDir[255]; // Folder | string | Name of the folder containing the game files. + uint8_t aGameDirLen; + char aGameDescription[255]; // Game | string | Full name of the game. + uint8_t aGameDescriptionLen; + uint16_t iSteamAppID; // ID | short | Steam Application ID of game. + uint8_t nNumClients; // Players | byte | Number of players on the server. + uint8_t nMaxClients; // Max. Players | byte | Maximum number of players the server reports it can hold. + uint8_t nFakeClients; // Bots | byte | Number of bots on the server. + uint8_t nServerType = 'd'; // Server type | byte | Indicates the type of server: 'd' for a dedicated server, 'l' for a non-dedicated server, 'p' for a SourceTV relay (proxy) + uint8_t nEnvironment = 'l'; // Environment | byte | Indicates the operating system of the server: 'l' for Linux, 'w' for Windows, 'm' or 'o' for Mac (the code changed after L4D1) + uint8_t nPassword; // Visibility | byte | Indicates whether the server requires a password: 0 for public, 1 for private + uint8_t bIsSecure; // VAC | byte | Specifies whether the server uses VAC: 0 for unsecured, 1 for secured + char aVersion[40]; // Version | string | Version of the game installed on the server. + uint8_t aVersionLen; + uint8_t nNewFlags; // Extra Data Flag (EDF) | byte | If present, this specifies which additional data fields will be included. + uint16_t iUDPPort; // EDF & 0x80 -> Port | short | The server's game port number. + uint64_t iSteamID; // EDF & 0x10 -> SteamID | long long | Server's SteamID. + uint16_t iHLTVUDPPort; // EDF & 0x40 -> Port | short | Spectator port number for SourceTV. + char aHLTVName[255]; // EDF & 0x40 -> Name | string | Name of the spectator server for SourceTV. + uint8_t aHLTVNameLen; + char aKeywords[255]; // EDF & 0x20 -> Keywords | string | Tags that describe the game according to the server (for future use.) (sv_tags) + uint8_t aKeywordsLen; + uint64_t iGameID; // EDF & 0x01 -> GameID | long long | The server's 64-bit GameID. If this is present, a more accurate AppID is present in the low 24 bits. The earlier AppID could have been truncated as it was forced into 16-bit storage. + } info; + + uint8_t info_cache[sizeof(CInfo)] = {0xFF, 0xFF, 0xFF, 0xFF, 'I'}; + uint16_t info_cache_len; +} g_QueryCache; -class IClient; class CBaseClient; - class CBaseServer; typedef enum EAuthProtocol @@ -65,23 +191,6 @@ typedef enum EAuthProtocol k_EAuthProtocolSteam = 3, } EAuthProtocol; -typedef struct netadr_s -{ -private: - typedef enum - { - NA_NULL = 0, - NA_LOOPBACK, - NA_BROADCAST, - NA_IP, - } netadrtype_t; - -public: - netadrtype_t type; - unsigned char ip[4]; - unsigned short port; -} netadr_t; - const char *CSteamID::Render() const { static char szSteamID[64]; @@ -175,6 +284,14 @@ bool BLoggedOn() return g_pSteam3Server->m_pSteamGameServer->BLoggedOn(); } +bool BSecure() +{ + if(!g_pSteam3Server || !g_pSteam3Server->m_pSteamGameServer) + return false; + + return g_pSteam3Server->m_pSteamGameServer->BSecure(); +} + CDetour *g_Detour_CBaseServer__ConnectClient = NULL; CDetour *g_Detour_CBaseServer__RejectConnection = NULL; CDetour *g_Detour_CBaseServer__CheckChallengeType = NULL; @@ -210,9 +327,9 @@ public: this->iChallenge = iChallenge; this->iClientChallenge = iClientChallenge; this->nAuthProtocol = nAuthProtocol; - strncpy(this->pchName, pchName, sizeof(this->pchName) - 1); - strncpy(this->pchPassword, pchPassword, sizeof(this->pchPassword) - 1); - strncpy(this->pCookie, pCookie, sizeof(this->pCookie) - 1); + strlcpy(this->pchName, pchName, sizeof(this->pchName)); + strlcpy(this->pchPassword, pchPassword, sizeof(this->pchPassword)); + strlcpy(this->pCookie, pCookie, sizeof(this->pCookie)); this->cbCookie = cbCookie; this->pClient = NULL; this->GotValidateAuthTicketResponse = false; @@ -229,7 +346,7 @@ bool g_bSuppressCheckChallengeType = false; DETOUR_DECL_MEMBER1(CSteam3Server__OnValidateAuthTicketResponse, int, ValidateAuthTicketResponse_t *, pResponse) { char aSteamID[32]; - V_strncpy(aSteamID, pResponse->m_SteamID.Render(), sizeof(aSteamID)); + strlcpy(aSteamID, pResponse->m_SteamID.Render(), sizeof(aSteamID)); bool SteamLegal = pResponse->m_eAuthSessionResponse == k_EAuthSessionResponseOK; bool force = g_SvNoSteam.GetInt() || g_SvForceSteam.GetInt() || !BLoggedOn(); @@ -274,7 +391,7 @@ DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient *, netadr_t &, address, V_snprintf(ipString, sizeof(ipString), "%u.%u.%u.%u", address.ip[0], address.ip[1], address.ip[2], address.ip[3]); char passwordBuffer[255]; - V_strncpy(passwordBuffer, pchPassword, sizeof(passwordBuffer)); + strlcpy(passwordBuffer, pchPassword, sizeof(passwordBuffer)); uint64 ullSteamID = *(uint64 *)pCookie; void *pvTicket = (void *)((intptr_t)pCookie + sizeof(uint64)); @@ -284,7 +401,7 @@ DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient *, netadr_t &, address, g_lastClientSteamID = CSteamID(ullSteamID); char aSteamID[32]; - V_strncpy(aSteamID, g_lastClientSteamID.Render(), sizeof(aSteamID)); + strlcpy(aSteamID, g_lastClientSteamID.Render(), sizeof(aSteamID)); // If client is in async state remove the old object and fake an async retVal // This can happen if the async ClientPreConnectEx takes too long to be called @@ -388,6 +505,9 @@ DETOUR_DECL_MEMBER9(CBaseServer__ConnectClient, IClient *, netadr_t &, address, g_bSuppressCheckChallengeType = true; IClient *pClient = DETOUR_MEMBER_CALL(CBaseServer__ConnectClient)(address, nProtocol, iChallenge, iClientChallenge, nAuthProtocol, pchName, pchPassword, pCookie, cbCookie); + if(pClient) + strlcpy(g_ClientSteamIDMap[pClient->GetPlayerSlot() + 1], aSteamID, sizeof(*g_ClientSteamIDMap)); + Storage.pClient = pClient; g_ConnectClientStorage.replace(aSteamID, Storage); @@ -429,6 +549,189 @@ DETOUR_DECL_MEMBER7(CBaseServer__CheckChallengeType, bool, CBaseClient *, pClien return DETOUR_MEMBER_CALL(CBaseServer__CheckChallengeType)(pClient, nUserID, address, nAuthProtocol, pCookie, cbCookie, iClientChallenge); } +void UpdateQueryCache() +{ + CQueryCache::CInfo &info = g_QueryCache.info; + info.aHostNameLen = strlcpy(info.aHostName, iserver->GetName(), sizeof(info.aHostName)); + info.aMapNameLen = strlcpy(info.aMapName, iserver->GetMapName(), sizeof(info.aMapName)); + info.aGameDescriptionLen = strlcpy(info.aGameDescription, gamedll->GetGameDescription(), sizeof(info.aGameDescription)); + if(g_pSvVisibleMaxPlayers->GetInt() >= 0) + info.nMaxClients = g_pSvVisibleMaxPlayers->GetInt(); + else + info.nMaxClients = iserver->GetMaxClients(); + info.nPassword = iserver->GetPassword() ? 1 : 0; + info.bIsSecure = BSecure(); + + if(!(info.nNewFlags & 0x10) && engine->GetGameServerSteamID()) + { + info.iSteamID = engine->GetGameServerSteamID()->ConvertToUint64(); + info.nNewFlags |= 0x10; + } + + if(!(info.nNewFlags & 0x40) && hltvdirector->IsActive()) // tv_name can't change anymore + { + hltv = hltvdirector->GetHLTVServer(); + if(hltv) + { + IServer *ihltvserver = hltv->GetBaseServer(); + if(ihltvserver) + { + info.iHLTVUDPPort = ihltvserver->GetUDPPort(); + info.aHLTVNameLen = strlcpy(info.aHLTVName, ihltvserver->GetName(), sizeof(info.aHLTVName)); + info.nNewFlags |= 0x40; + } + } + } + + info.aKeywordsLen = strlcpy(info.aKeywords, g_pSvTags->GetString(), sizeof(info.aKeywords)); + if(info.aKeywordsLen) + info.nNewFlags |= 0x20; + else + info.nNewFlags &= ~0x20; + + + uint8_t *info_cache = g_QueryCache.info_cache; + uint16_t pos = 5; // header: FF FF FF FF I + + info_cache[pos++] = info.nProtocol; + + memcpy(&info_cache[pos], info.aHostName, info.aHostNameLen + 1); + pos += info.aHostNameLen + 1; + + memcpy(&info_cache[pos], info.aMapName, info.aMapNameLen + 1); + pos += info.aMapNameLen + 1; + + memcpy(&info_cache[pos], info.aGameDir, info.aGameDirLen + 1); + pos += info.aGameDirLen + 1; + + memcpy(&info_cache[pos], info.aGameDescription, info.aGameDescriptionLen + 1); + pos += info.aGameDescriptionLen + 1; + + *(uint16_t *)&info_cache[pos] = info.iSteamAppID; + pos += 2; + + info_cache[pos++] = info.nNumClients; + + info_cache[pos++] = info.nMaxClients; + + info_cache[pos++] = info.nFakeClients; + + info_cache[pos++] = info.nServerType; + + info_cache[pos++] = info.nEnvironment; + + info_cache[pos++] = info.nPassword; + + info_cache[pos++] = info.bIsSecure; + + memcpy(&info_cache[pos], info.aVersion, info.aVersionLen + 1); + pos += info.aVersionLen + 1; + + info_cache[pos++] = info.nNewFlags; + + if(info.nNewFlags & 0x80) { + *(uint16_t *)&info_cache[pos] = info.iUDPPort; + pos += 2; + } + + if(info.nNewFlags & 0x10) { + *(uint64_t *)&info_cache[pos] = info.iSteamID; + pos += 8; + } + + if(info.nNewFlags & 0x40) { + *(uint16_t *)&info_cache[pos] = info.iHLTVUDPPort; + pos += 2; + + memcpy(&info_cache[pos], info.aHLTVName, info.aHLTVNameLen + 1); + pos += info.aHLTVNameLen + 1; + } + + if(info.nNewFlags & 0x20) { + memcpy(&info_cache[pos], info.aKeywords, info.aKeywordsLen + 1); + pos += info.aKeywordsLen + 1; + } + + if(info.nNewFlags & 0x01) { + *(uint64_t *)&info_cache[pos] = info.iGameID; + pos += 8; + } + + g_QueryCache.info_cache_len = pos; +} + +bool Hook_ProcessConnectionlessPacket(netpacket_t * packet) +{ + if(packet->size == 25 && packet->data[4] == 'T') + { + if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) + RETURN_META_VALUE(MRES_SUPERCEDE, false); + + sockaddr_in to; + to.sin_family = AF_INET; + to.sin_port = packet->from.port; + to.sin_addr.s_addr = *(int32_t *)&packet->from.ip; + + sendto(g_ServerUDPSocket, g_QueryCache.info_cache, g_QueryCache.info_cache_len, 0, (sockaddr *)&to, sizeof(to)); + + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + if((packet->size == 5 || packet->size == 9) && packet->data[4] == 'U') + { + if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) + RETURN_META_VALUE(MRES_SUPERCEDE, false); + + sockaddr_in to; + to.sin_family = AF_INET; + to.sin_port = packet->from.port; + to.sin_addr.s_addr = *(int32_t *)&packet->from.ip; + + int32_t challengeNr = -1; + if(packet->size == 9) + challengeNr = *(int32_t *)&packet->data[5]; + + /* This is a complete nonsense challenge as the client can easily break it. + * The point of this challenge is to stop spoofed source DDoS reflection attacks, + * so it doesn't really matter if one single server out of thousands doesn't + * implement this correctly. If you do happen to use this on thousands of servers + * though then please do implement it correctly. + */ + int32_t realChallengeNr = *(int32_t *)&packet->from.ip ^ 0x55AADD88; + if(challengeNr != realChallengeNr) + { + uint8_t response[9] = {0xFF, 0xFF, 0xFF, 0xFF, 'A'}; + *(int32_t *)&response[5] = realChallengeNr; + sendto(g_ServerUDPSocket, response, sizeof(response), 0, (sockaddr *)&to, sizeof(to)); + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + uint8_t response[4+1+1+SM_MAXPLAYERS*(1+MAX_PLAYER_NAME_LENGTH+4+4)] = {0xFF, 0xFF, 0xFF, 0xFF, 'D', 0}; + short pos = 6; + for(int i = 0; i < SM_MAXPLAYERS; i++) + { + CQueryCache::CPlayer &player = g_QueryCache.players[i]; + if(!player.active) + continue; + + response[pos++] = response[5]; // Index | byte | Index of player chunk starting from 0. + response[5]++; // Players | byte | Number of players whose information was gathered. + memcpy(&response[pos], player.name, player.nameLen + 1); // Name | string | Name of the player. + pos += player.nameLen + 1; + *(int32_t *)&response[pos] = player.score; // Score | long | Player's score (usually "frags" or "kills".) + pos += 4; + *(float *)&response[pos] = gpGlobals->curtime - player.time; // Duration | float | Time (in seconds) player has been connected to the server. + pos += 4; + } + + sendto(g_ServerUDPSocket, response, pos, 0, (sockaddr *)&to, sizeof(to)); + + RETURN_META_VALUE(MRES_SUPERCEDE, true); + } + + RETURN_META_VALUE(MRES_IGNORED, false); +} + bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late) { char conf_error[255] = ""; @@ -490,6 +793,30 @@ bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late) META_CONPRINTF("ISteamGameServerStats: %p\n", g_pSteam3Server->m_pSteamGameServerStats); */ + if(!g_pGameConf->GetMemSig("s_queryRateChecker", &s_queryRateChecker) || !s_queryRateChecker) + { + snprintf(error, maxlen, "Failed to find s_queryRateChecker address.\n"); + return false; + } + + if(!g_pGameConf->GetMemSig("CIPRateLimit__CheckIP", (void **)&CIPRateLimit__CheckIP) || !CIPRateLimit__CheckIP) + { + snprintf(error, maxlen, "Failed to find CIPRateLimit::CheckIP address.\n"); + return false; + } + + if(!g_pGameConf->GetMemSig("CBaseServer__ValidChallenge", (void **)&CBaseServer__ValidChallenge) || !CBaseServer__ValidChallenge) + { + snprintf(error, maxlen, "Failed to find CBaseServer::ValidChallenge address.\n"); + return false; + } + + if(!g_pGameConf->GetMemSig("net_sockets", (void **)&net_sockets) || !net_sockets) + { + snprintf(error, maxlen, "Failed to find net_sockets address.\n"); + return false; + } + CDetourManager::Init(g_pSM->GetScriptingEngine(), g_pGameConf); g_Detour_CBaseServer__ConnectClient = DETOUR_CREATE_MEMBER(CBaseServer__ConnectClient, "CBaseServer__ConnectClient"); @@ -526,17 +853,30 @@ bool Connect::SDK_OnLoad(char *error, size_t maxlen, bool late) g_pConnectForward = g_pForwards->CreateForward("OnClientPreConnectEx", ET_LowEvent, 5, NULL, Param_String, Param_String, Param_String, Param_String, Param_String); + g_pGameEvents->AddListener(&g_ConnectEvents, "player_connect", true); g_pGameEvents->AddListener(&g_ConnectEvents, "player_disconnect", true); + playerhelpers->AddClientListener(this); + + g_pConnectTimer = timersys->CreateTimer(&g_ConnectTimer, 1.0, NULL, TIMER_FLAG_REPEAT); + return true; } bool Connect::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { + GET_V_IFACE_CURRENT(GetEngineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); + GET_V_IFACE_ANY(GetServerFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL); GET_V_IFACE_CURRENT(GetEngineFactory, g_pGameEvents, IGameEventManager2, INTERFACEVERSION_GAMEEVENTSMANAGER2); GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); + GET_V_IFACE_CURRENT(GetServerFactory, hltvdirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR); + + gpGlobals = ismm->GetCGlobals(); ConVar_Register(0, this); + g_pSvVisibleMaxPlayers = g_pCVar->FindVar("sv_visiblemaxplayers"); + g_pSvTags = g_pCVar->FindVar("sv_tags"); + return true; } @@ -568,6 +908,10 @@ void Connect::SDK_OnUnload() g_pGameEvents->RemoveListener(&g_ConnectEvents); + playerhelpers->RemoveClientListener(this); + + timersys->KillTimer(g_pConnectTimer); + gameconfs->CloseGameConfigFile(g_pGameConf); } @@ -650,23 +994,200 @@ const sp_nativeinfo_t MyNatives[] = void Connect::SDK_OnAllLoaded() { sharesys->AddNatives(myself, MyNatives); + + SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); + + iserver = g_pSDKTools->GetIServer(); + if (!iserver) { + smutils->LogError(myself, "Failed to get IServer interface from SDKTools!"); + return; + } + + int offset; + if (g_pGameConf->GetOffset("CBaseServer__m_Socket", &offset)) + { + int socknum = *((uint8_t *)iserver + offset); + g_ServerUDPSocket = (*net_sockets)[socknum].hUDP; + } + else + { + smutils->LogError(myself, "Failed to find CBaseServer::m_Socket offset."); + return; + } + + if (g_pGameConf->GetOffset("IServer__ProcessConnectionlessPacket", &offset)) + { + SH_MANUALHOOK_RECONFIGURE(ProcessConnectionlessPacket, offset, 0, 0); + SH_ADD_MANUALHOOK(ProcessConnectionlessPacket, iserver, SH_STATIC(Hook_ProcessConnectionlessPacket), false); + } + else + { + smutils->LogError(myself, "Failed to find IServer::ProcessConnectionlessPacket offset."); + return; + } + + // A2S_INFO + CQueryCache::CInfo &info = g_QueryCache.info; + info.aGameDirLen = strlcpy(info.aGameDir, smutils->GetGameFolderName(), sizeof(info.aGameDir)); + + info.iSteamAppID = engine->GetAppID(); + + info.aVersionLen = snprintf(info.aVersion, sizeof(info.aVersion), "%d", engine->GetServerVersion()); + + info.iUDPPort = iserver->GetUDPPort(); + info.nNewFlags |= 0x80; + + info.iGameID = info.iSteamAppID; + info.nNewFlags |= 0x01; + + UpdateQueryCache(); + + // A2S_PLAYER + for(int client = 1; client <= SM_MAXPLAYERS; client++) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + if(!gplayer || !gplayer->IsConnected()) + continue; + + if(!player.active) + { + g_QueryCache.info.nNumClients++; + if(gplayer->IsFakeClient() && !gplayer->IsSourceTV()) + { + g_QueryCache.info.nFakeClients++; + player.fake = true; + } + } + + player.active = true; + player.pClient = iserver->GetClient(client - 1); + player.nameLen = strlcpy(player.name, gplayer->GetName(), sizeof(player.name)); + + INetChannelInfo *netinfo = (INetChannelInfo *)player.pClient->GetNetChannel(); + if(netinfo) + player.time = netinfo->GetTimeConnected(); + else + player.time = 0; + + IPlayerInfo *info = gplayer->GetPlayerInfo(); + if(info) + player.score = info->GetFragCount(); + else + player.score = 0; + + g_UserIDtoClientMap[gplayer->GetUserId()] = client; + } +} + +void Connect::OnClientSettingsChanged(int client) +{ + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + player.nameLen = strlcpy(player.name, player.pClient->GetClientName(), sizeof(player.name)); + } +} + +void Connect::OnClientPutInServer(int client) +{ + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + if(player.fake && gplayer->IsSourceTV()) + { + player.fake = false; + g_QueryCache.info.nFakeClients--; + } + } +} + +void Connect::OnTimer() +{ + for(int client = 1; client <= SM_MAXPLAYERS; client++) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + if(!player.active) + continue; + + IGamePlayer *gplayer = playerhelpers->GetGamePlayer(client); + if(!gplayer || !gplayer->IsConnected()) + continue; + + IPlayerInfo *info = gplayer->GetPlayerInfo(); + if(info) + player.score = info->GetFragCount(); + } + + UpdateQueryCache(); } void ConnectEvents::FireGameEvent(IGameEvent *event) { const char *name = event->GetName(); - if(strcmp(name, "player_disconnect") == 0) + if(strcmp(name, "player_connect") == 0) + { + int client = event->GetInt("index") + 1; + int userid = event->GetInt("userid"); + int bot = event->GetBool("bot"); + const char *name = event->GetString("name"); + + if(client >= 1 && client <= SM_MAXPLAYERS) + { + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + player.active = true; + player.fake = false; + player.pClient = iserver->GetClient(client - 1); + g_QueryCache.info.nNumClients++; + if(bot) + { + player.fake = true; + g_QueryCache.info.nFakeClients++; + } + player.time = gpGlobals->curtime; + player.score = 0; + player.nameLen = strlcpy(player.name, player.pClient->GetClientName(), sizeof(player.name)); + + g_UserIDtoClientMap[userid] = client; + } + } + else if(strcmp(name, "player_disconnect") == 0) { int userid = event->GetInt("userid"); - int client = playerhelpers->GetClientOfUserId(userid); + int client = g_UserIDtoClientMap[userid]; + g_UserIDtoClientMap[client] = 0; - IGamePlayer *pPlayer = playerhelpers->GetGamePlayer(client); - if(pPlayer) + if(client >= 1 && client <= SM_MAXPLAYERS) { - const char *pSteamID = pPlayer->GetSteam2Id(false); - g_pSM->LogMessage(myself, "%s OnClientDisconnecting: %d", pSteamID, client); - g_ConnectClientStorage.remove(pSteamID); + CQueryCache::CPlayer &player = g_QueryCache.players[client]; + if(player.active) + { + g_QueryCache.info.nNumClients--; + if(player.fake) + g_QueryCache.info.nFakeClients--; + } + player.active = false; + player.pClient = NULL; + } + + if(client >= 1 && client <= SM_MAXPLAYERS) + { + char *pSteamID = g_ClientSteamIDMap[client]; + if(*pSteamID) + { + g_pSM->LogMessage(myself, "%s OnClientDisconnecting: %d", pSteamID, client); + g_ConnectClientStorage.remove(pSteamID); + *pSteamID = 0; + } } } } + +ResultType ConnectTimer::OnTimer(ITimer *pTimer, void *pData) +{ + g_Connect.OnTimer(); + return Pl_Continue; +} +void ConnectTimer::OnTimerEnd(ITimer *pTimer, void *pData) {} diff --git a/extension.h b/extension.h index 25c1f0b..6eebe68 100644 --- a/extension.h +++ b/extension.h @@ -40,14 +40,14 @@ #include "smsdk_ext.h" #include - /** * @brief Sample implementation of the SDK Extension. * Note: Uncomment one of the pre-defined virtual functions in order to use it. */ class Connect : public SDKExtension, - public IConCommandBaseAccessor + public IConCommandBaseAccessor, + public IClientListener { public: /** @@ -120,6 +120,12 @@ public: public: // IConCommandBaseAccessor virtual bool RegisterConCommandBase(ConCommandBase *pVar); + +public: // IClientListener + virtual void OnClientSettingsChanged(int client); + virtual void OnClientPutInServer(int client); +public: + void OnTimer(); }; class ConnectEvents : public IGameEventListener2 @@ -128,6 +134,11 @@ public: virtual void FireGameEvent( IGameEvent *event ); }; -extern ConnectEvents g_ConnectEvents; +class ConnectTimer : public ITimedEvent +{ +public: + virtual ResultType OnTimer(ITimer *pTimer, void *pData); + virtual void OnTimerEnd(ITimer *pTimer, void *pData); +}; #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/gamedata/connect2.games.txt b/gamedata/connect2.games.txt index a523cf7..ab3c008 100644 --- a/gamedata/connect2.games.txt +++ b/gamedata/connect2.games.txt @@ -60,6 +60,50 @@ "library" "engine" "linux" "@_ZN13CSteam3Server28OnValidateAuthTicketResponseEP28ValidateAuthTicketResponse_t" } + + "s_queryRateChecker" + { + "library" "engine" + "linux" "@_ZL18s_queryRateChecker" + } + + "CIPRateLimit__CheckIP" + { + "library" "engine" + "linux" "@_ZN12CIPRateLimit7CheckIPE8netadr_s" + } + + "CBaseServer__ValidChallenge" + { + "library" "engine" + "linux" "@_ZN11CBaseServer14ValidChallengeER8netadr_si" + } + + "net_sockets" + { + "library" "engine" + "linux" "@_ZL11net_sockets" + } + + "hltv" + { + "library" "engine" + "linux" "@hltv" + } + } + + "Offsets" + { + "IServer__ProcessConnectionlessPacket" + { + "windows" "1" + "linux" "2" + } + + "CBaseServer__m_Socket" + { + "linux" "8" + } } } } diff --git a/smsdk_config.h b/smsdk_config.h index c23d3e7..da7f0d6 100644 --- a/smsdk_config.h +++ b/smsdk_config.h @@ -66,7 +66,7 @@ #define SMEXT_ENABLE_GAMECONF //#define SMEXT_ENABLE_MEMUTILS //#define SMEXT_ENABLE_GAMEHELPERS -//#define SMEXT_ENABLE_TIMERSYS +#define SMEXT_ENABLE_TIMERSYS //#define SMEXT_ENABLE_THREADER //#define SMEXT_ENABLE_LIBSYS //#define SMEXT_ENABLE_MENUS