Add proper/clean method for a2s_info and a2s_player

This commit is contained in:
maxime1907
2021-11-19 17:40:08 +01:00
parent b87d807e65
commit f6e114f3b7
5 changed files with 732 additions and 163 deletions
+2 -1
View File
@@ -480,7 +480,8 @@ class ExtensionConfig(object):
['public', 'mathlib'], ['public', 'mathlib'],
['public', 'vstdlib'], ['public', 'vstdlib'],
['public', 'tier0'], ['public', 'tier0'],
['public', 'tier1'] ['public', 'tier1'],
['public', 'appframework']
] ]
if sdk.name == 'episode1' or sdk.name == 'darkm': if sdk.name == 'episode1' or sdk.name == 'darkm':
paths.append(['public', 'dlls']) paths.append(['public', 'dlls'])
+253 -136
View File
@@ -34,6 +34,11 @@
#include "CDetour/detours.h" #include "CDetour/detours.h"
#include "steam/steam_gameserver.h" #include "steam/steam_gameserver.h"
#include "sm_namehashset.h" #include "sm_namehashset.h"
#include "proto_oob.h"
#include "protocol.h"
#include "inetworksystem.h"
#include <strtools.h>
#include <utlbuffer.h>
#include <sourcehook.h> #include <sourcehook.h>
#include <bitbuf.h> #include <bitbuf.h>
#include <netadr.h> #include <netadr.h>
@@ -88,8 +93,12 @@ ConVar *g_SvGameDesc = CreateConVar("sv_gamedesc_override", "default", FCVAR_NOT
ConVar *g_SvMapName = CreateConVar("sv_mapname_override", "default", FCVAR_NOTIFY, "Overwrite the map name. Set to 'default' to keep default name."); ConVar *g_SvMapName = CreateConVar("sv_mapname_override", "default", FCVAR_NOTIFY, "Overwrite the map name. Set to 'default' to keep default name.");
ConVar *g_SvCountBotsInfo = CreateConVar("sv_count_bots_info", "1", FCVAR_NOTIFY, "Display bots as players in the a2s_info server query. Enable = '1', Disable = '0'"); ConVar *g_SvCountBotsInfo = CreateConVar("sv_count_bots_info", "1", FCVAR_NOTIFY, "Display bots as players in the a2s_info server query. Enable = '1', Disable = '0'");
ConVar *g_SvCountBotsPlayer = CreateConVar("sv_count_bots_player", "0", FCVAR_NOTIFY, "Display bots as players in the a2s_player server query. Enable = '1', Disable = '0'"); ConVar *g_SvCountBotsPlayer = CreateConVar("sv_count_bots_player", "0", FCVAR_NOTIFY, "Display bots as players in the a2s_player server query. Enable = '1', Disable = '0'");
ConVar *g_pSvVisibleMaxPlayers; #if SOURCE_ENGINE < SE_CSGO
ConVar *g_pSvTags; ConVar *g_SvHostNameStore = CreateConVar("host_name_store", "1", FCVAR_NOTIFY, "Whether hostname is recorded in game events and GOTV.");
#endif
ConVar *g_pSvVisibleMaxPlayers = NULL;
ConVar *g_pSvTags = NULL;
ConVar *g_pSvEnableOldQueries = NULL;
IGameConfig *g_pGameConf = NULL; IGameConfig *g_pGameConf = NULL;
IGameEventManager2 *g_pGameEvents = NULL; IGameEventManager2 *g_pGameEvents = NULL;
@@ -156,53 +165,48 @@ struct CQueryCache
{ {
struct CPlayer struct CPlayer
{ {
bool active; bool active = false;
bool fake; bool fake = false;
int userid; int userid = 0;
IClient *pClient; IClient *pClient = NULL;
char name[MAX_PLAYER_NAME_LENGTH]; char name[MAX_PLAYER_NAME_LENGTH] = "\0";
unsigned nameLen; unsigned nameLen = 0;
int32_t score; int32_t score = 0;
double time; double time = 0.0;
} players[SM_MAXPLAYERS + 1]; } players[SM_MAXPLAYERS + 1];
struct CInfo struct CInfo
{ {
uint8_t nProtocol = 17; // Protocol | byte | Protocol version used by the server. uint8_t nProtocol = 17; // Protocol | byte | Protocol version used by the server.
char aHostName[255]; // Name | string | Name of the server. char aHostName[255] = "\0"; // Name | string | Name of the server.
uint8_t aHostNameLen; uint8_t aHostNameLen = 0;
char aMapName[255]; // Map | string | Map the server has currently loaded. char aMapName[255]; // Map | string | Map the server has currently loaded.
uint8_t aMapNameLen; uint8_t aMapNameLen = 0;
char aGameDir[255]; // Folder | string | Name of the folder containing the game files. char aGameDir[255]; // Folder | string | Name of the folder containing the game files.
uint8_t aGameDirLen; uint8_t aGameDirLen = 0;
char aGameDescription[255]; // Game | string | Full name of the game. char aGameDescription[255] = "\0"; // Game | string | Full name of the game.
uint8_t aGameDescriptionLen; uint8_t aGameDescriptionLen = 0;
uint16_t iSteamAppID; // ID | short | Steam Application ID of game. uint16_t iSteamAppID = 0; // ID | short | Steam Application ID of game.
uint8_t nNumClients = 0; // Players | byte | Number of players on the server. uint8_t nNumClients = 0; // 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 nMaxClients = 0; // Max. Players | byte | Maximum number of players the server reports it can hold.
uint8_t nFakeClients = 0; // Bots | byte | Number of bots on the server. uint8_t nFakeClients = 0; // 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 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 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 nPassword = 0; // 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 uint8_t bIsSecure = 0; // 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. char aVersion[40] = "\0"; // Version | string | Version of the game installed on the server.
uint8_t aVersionLen; uint8_t aVersionLen = 0;
uint8_t nNewFlags = 0; // Extra Data Flag (EDF) | byte | If present, this specifies which additional data fields will be included. uint8_t nNewFlags = 0; // 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. uint16_t iUDPPort = 0; // EDF & S2A_EXTRA_DATA_HAS_GAME_PORT -> Port | short | The server's game port number.
uint64_t iSteamID; // EDF & 0x10 -> SteamID | long long | Server's SteamID. uint64_t iSteamID = 0; // EDF & S2A_EXTRA_DATA_HAS_STEAMID -> SteamID | long long | Server's SteamID.
uint16_t iHLTVUDPPort; // EDF & 0x40 -> Port | short | Spectator port number for SourceTV. uint16_t iHLTVUDPPort = 0; // EDF & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA -> Port | short | Spectator port number for SourceTV.
char aHLTVName[255]; // EDF & 0x40 -> Name | string | Name of the spectator server for SourceTV. char aHLTVName[255] = "\0"; // EDF & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA -> Name | string | Name of the spectator server for SourceTV.
uint8_t aHLTVNameLen; uint8_t aHLTVNameLen = 0;
char aKeywords[255]; // EDF & 0x20 -> Keywords | string | Tags that describe the game according to the server (for future use.) (sv_tags) char aKeywords[255] = "\0"; // EDF & S2A_EXTRA_DATA_HAS_GAMETAG_DATA -> Keywords | string | Tags that describe the game according to the server (for future use.) (sv_tags)
uint8_t aKeywordsLen; uint8_t aKeywordsLen = 0;
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. uint64_t iGameID = 0; // EDF & S2A_EXTRA_DATA_GAMEID -> 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; } info;
uint8_t info_cache[sizeof(CInfo)] = {0xFF, 0xFF, 0xFF, 0xFF, 'I'};
uint16_t info_cache_len;
uint8_t players_cache[4+1+1+SM_MAXPLAYERS*(1+MAX_PLAYER_NAME_LENGTH+4+4)] = {0xFF, 0xFF, 0xFF, 0xFF, 'D', 0};
uint16_t players_cache_len;
} g_QueryCache; } g_QueryCache;
class CBaseClient; class CBaseClient;
@@ -213,7 +217,8 @@ void UpdateQueryCache()
{ {
// A2S_INFO // A2S_INFO
CQueryCache::CInfo &info = g_QueryCache.info; CQueryCache::CInfo &info = g_QueryCache.info;
info.aHostNameLen = strlcpy(info.aHostName, iserver->GetName(), sizeof(info.aHostName));
info.aHostNameLen = strlcpy(info.aHostName, g_SvHostNameStore->GetBool() ? iserver->GetName() : gamedll->GetGameDescription(), sizeof(info.aHostName));
if(strcmp(g_SvMapName->GetString(), "default") == 0) if(strcmp(g_SvMapName->GetString(), "default") == 0)
info.aMapNameLen = strlcpy(info.aMapName, iserver->GetMapName(), sizeof(info.aMapName)); info.aMapNameLen = strlcpy(info.aMapName, iserver->GetMapName(), sizeof(info.aMapName));
@@ -229,16 +234,32 @@ void UpdateQueryCache()
info.nMaxClients = g_pSvVisibleMaxPlayers->GetInt(); info.nMaxClients = g_pSvVisibleMaxPlayers->GetInt();
else else
info.nMaxClients = iserver->GetMaxClients(); info.nMaxClients = iserver->GetMaxClients();
// NOTE: This key's meaning is changed in the new version. Since we send gameport and specport,
// it knows whether we're running SourceTV or not. Then it only needs to know if we're a dedicated or listen server.
if ( iserver->IsDedicated() )
info.nServerType = 'd'; // d = dedicated server
else
info.nServerType = 'l'; // l = listen server
#if defined(_WIN32)
info.nEnvironment = 'w';
#elif defined(OSX)
info.nEnvironment = 'm';
#else // LINUX?
info.nEnvironment = 'l';
#endif
info.nPassword = iserver->GetPassword() ? 1 : 0; info.nPassword = iserver->GetPassword() ? 1 : 0;
info.bIsSecure = true; info.bIsSecure = true;
if(!(info.nNewFlags & 0x10) && engine->GetGameServerSteamID()) if(!(info.nNewFlags & S2A_EXTRA_DATA_HAS_STEAMID) && engine->GetGameServerSteamID())
{ {
info.iSteamID = engine->GetGameServerSteamID()->ConvertToUint64(); info.iSteamID = engine->GetGameServerSteamID()->ConvertToUint64();
info.nNewFlags |= 0x10; info.nNewFlags |= S2A_EXTRA_DATA_HAS_STEAMID;
} }
if(!(info.nNewFlags & 0x40) && hltvdirector->IsActive()) // tv_name can't change anymore if(!(info.nNewFlags & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA) && hltvdirector->IsActive()) // tv_name can't change anymore
{ {
#if SOURCE_ENGINE >= SE_CSGO #if SOURCE_ENGINE >= SE_CSGO
hltv = hltvdirector->GetHLTVServer(0); hltv = hltvdirector->GetHLTVServer(0);
@@ -252,144 +273,235 @@ void UpdateQueryCache()
{ {
info.iHLTVUDPPort = ihltvserver->GetUDPPort(); info.iHLTVUDPPort = ihltvserver->GetUDPPort();
info.aHLTVNameLen = strlcpy(info.aHLTVName, ihltvserver->GetName(), sizeof(info.aHLTVName)); info.aHLTVNameLen = strlcpy(info.aHLTVName, ihltvserver->GetName(), sizeof(info.aHLTVName));
info.nNewFlags |= 0x40; info.nNewFlags |= S2A_EXTRA_DATA_HAS_SPECTATOR_DATA;
} }
} }
} }
info.aKeywordsLen = strlcpy(info.aKeywords, g_pSvTags->GetString(), sizeof(info.aKeywords)); info.aKeywordsLen = strlcpy(info.aKeywords, g_pSvTags->GetString(), sizeof(info.aKeywords));
if(info.aKeywordsLen) if(info.aKeywordsLen)
info.nNewFlags |= 0x20; info.nNewFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA;
else else
info.nNewFlags &= ~0x20; info.nNewFlags &= ~S2A_EXTRA_DATA_HAS_GAMETAG_DATA;
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;
if (g_SvCountBotsInfo->GetInt())
info_cache[pos++] = 0;
else
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) { bool RequireValidChallenge( const netadr_t &adr )
*(uint64_t *)&info_cache[pos] = info.iSteamID; {
pos += 8; if ( g_pSvEnableOldQueries->GetBool() == true )
{
return false; // don't enforce challenge numbers
} }
if(info.nNewFlags & 0x40) { return true;
*(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) { bool ValidInfoChallenge( const netadr_t & adr, const char *nugget )
memcpy(&info_cache[pos], info.aKeywords, info.aKeywordsLen + 1); {
pos += info.aKeywordsLen + 1; if ( !iserver->IsActive() ) // Must be running a server.
return false ;
if ( !iserver->IsMultiplayer() ) // ignore in single player
return false ;
if ( RequireValidChallenge( adr ) )
{
if ( Q_stricmp( nugget, A2S_KEY_STRING ) ) // if the string isn't equal then fail out
{
return false;
}
} }
if(info.nNewFlags & 0x01) { return true;
*(uint64_t *)&info_cache[pos] = info.iGameID;
pos += 8;
} }
g_QueryCache.info_cache_len = pos; void SendA2S_PlayerChallenge(netpacket_t * packet, int32_t realChallengeNr)
{
struct sockaddr addr;
packet->from.ToSockadr ( &addr );
CUtlBuffer buf;
buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
// A2S_PLAYER buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
uint8_t *players_cache = g_QueryCache.players_cache; buf.PutUnsignedChar( S2C_CHALLENGE );
pos = 6; // header: FF FF FF FF D 0[numplayers] buf.PutInt( realChallengeNr );
sendto(g_ServerUDPSocket, (const char*)buf.Base(), buf.TellPut(), 0, &addr, sizeof(addr));
}
void SendA2S_Player(netpacket_t * packet)
{
struct sockaddr addr;
packet->from.ToSockadr ( &addr );
CUtlBuffer buf;
buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
buf.PutUnsignedChar( S2A_PLAYER );
unsigned char nPlayerCount = 0;
for(int i = 1; i <= SM_MAXPLAYERS; i++)
{
const CQueryCache::CPlayer &player = g_QueryCache.players[i];
if(!player.active || (player.fake && !g_SvCountBotsPlayer->GetInt()))
continue;
nPlayerCount++;
}
// Number of players
buf.PutUnsignedChar( nPlayerCount );
unsigned char nPlayerUserID = 0;
for(int i = 1; i <= SM_MAXPLAYERS; i++) for(int i = 1; i <= SM_MAXPLAYERS; i++)
{ {
const CQueryCache::CPlayer &player = g_QueryCache.players[i]; const CQueryCache::CPlayer &player = g_QueryCache.players[i];
if(!player.active || (player.fake && !g_SvCountBotsPlayer->GetInt())) if(!player.active || (player.fake && !g_SvCountBotsPlayer->GetInt()))
continue; continue;
players_cache[pos++] = players_cache[5]; // Index | byte | Index of player chunk starting from 0. // User ID
players_cache[5]++; // Players | byte | Number of players whose information was gathered. buf.PutUnsignedChar( nPlayerUserID );
memcpy(&players_cache[pos], player.name, player.nameLen + 1); // Name | string | Name of the player. // Player Name
pos += player.nameLen + 1; buf.PutString( player.name );
*(int32_t *)&players_cache[pos] = player.score; // Score | long | Player's score (usually "frags" or "kills".) // Player Score
pos += 4; buf.PutInt( player.score );
*(float *)&players_cache[pos] = *net_time - player.time; // Duration | float | Time (in seconds) player has been connected to the server. // Player Duration
pos += 4; buf.PutFloat( *net_time - player.time );
nPlayerUserID++;
} }
g_QueryCache.players_cache_len = pos; sendto(g_ServerUDPSocket, (const char *)buf.Base(), buf.TellPut(), 0, &addr, sizeof(addr));
}
void SendA2S_Info(netpacket_t * packet)
{
struct sockaddr addr;
packet->from.ToSockadr ( &addr );
CUtlBuffer buf;
buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
buf.PutUnsignedChar( S2A_INFO_SRC );
buf.PutUnsignedChar( 17 ); // Hardcoded protocol version number
buf.PutString( g_SvHostNameStore->GetBool() ? iserver->GetName() : gamedll->GetGameDescription() );
buf.PutString( strcmp(g_SvMapName->GetString(), "default") == 0 ? iserver->GetMapName() : g_SvMapName->GetString());
buf.PutString( smutils->GetGameFolderName() );
buf.PutString( strcmp(g_SvGameDesc->GetString(), "default") == 0 ? gamedll->GetGameDescription() : g_SvGameDesc->GetString() );
// The next field is a 16-bit version of the AppID. If our AppID < 65536,
// then let's go ahead and put in in there, to maximize compatibility
// with old clients who might be only using this field but not the new one.
// However, if our AppID won't fit, there's no way we can be compatible,
// anyway, so just put in a zero, which is better than a bogus AppID.
buf.PutShort( LittleWord( g_QueryCache.info.iSteamAppID ) );
// player info
buf.PutUnsignedChar( g_QueryCache.info.nNumClients );
buf.PutUnsignedChar( g_pSvVisibleMaxPlayers->GetInt() >= 0 ? g_pSvVisibleMaxPlayers->GetInt() : iserver->GetMaxClients() );
buf.PutUnsignedChar( g_SvCountBotsInfo->GetInt() ? 0 : g_QueryCache.info.nFakeClients );
// NOTE: This key's meaning is changed in the new version. Since we send gameport and specport,
// it knows whether we're running SourceTV or not. Then it only needs to know if we're a dedicated or listen server.
buf.PutUnsignedChar( g_QueryCache.info.nServerType );
buf.PutUnsignedChar( g_QueryCache.info.nEnvironment );
// Password?
buf.PutUnsignedChar( iserver->GetPassword() ? 1 : 0 );
// buf.PutUnsignedChar( Steam3Server().BSecure() ? 1 : 0 );
// Secure?
buf.PutUnsignedChar( 1 );
buf.PutString( g_QueryCache.info.aVersion );
//
// NEW DATA.
//
buf.PutUnsignedChar( g_QueryCache.info.nNewFlags );
// Write the rest of the data.
if ( g_QueryCache.info.nNewFlags & S2A_EXTRA_DATA_HAS_GAME_PORT )
{
buf.PutShort( LittleWord( iserver->GetUDPPort() ) );
}
if ( g_QueryCache.info.nNewFlags & S2A_EXTRA_DATA_HAS_STEAMID )
{
buf.PutShort( LittleWord( g_QueryCache.info.iSteamID ) );
}
if ( g_QueryCache.info.nNewFlags & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA )
{
buf.PutShort( LittleWord( g_QueryCache.info.iHLTVUDPPort ) );
buf.PutString( g_SvHostNameStore->GetBool() ? iserver->GetName() : g_QueryCache.info.aHLTVName );
}
if ( g_QueryCache.info.nNewFlags & S2A_EXTRA_DATA_HAS_GAMETAG_DATA )
{
buf.PutString( g_QueryCache.info.aKeywords );
}
if ( g_QueryCache.info.nNewFlags & S2A_EXTRA_DATA_GAMEID )
{
// !FIXME! Is there a reason we aren't using the other half
// of this field? Shouldn't we put the game mod ID in there, too?
// We have the game dir.
// buf.PutInt64( LittleQWord( CGameID( appIdResponse ).ToUint64() ) );
buf.PutInt64( LittleQWord( g_QueryCache.info.iGameID ) );
}
sendto(g_ServerUDPSocket, (const char *)buf.Base(), buf.TellPut(), 0, &addr, sizeof(addr));
} }
bool Hook_ProcessConnectionlessPacket(netpacket_t * packet) bool Hook_ProcessConnectionlessPacket(netpacket_t * packet)
{ {
if(packet->size >= 25 && packet->data[4] == 'T') bf_read msg = packet->message; // handy shortcut
char c = msg.ReadChar();
switch ( c )
{
case 0:
{ {
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
{ {
RETURN_META_VALUE(MRES_SUPERCEDE, false); RETURN_META_VALUE(MRES_SUPERCEDE, false);
} }
sockaddr_in to; RETURN_META_VALUE(MRES_SUPERCEDE, false);
to.sin_family = AF_INET; break;
to.sin_port = packet->from.port; }
to.sin_addr.s_addr = *(int32_t *)&packet->from.ip; case A2S_INFO:
{
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
{
RETURN_META_VALUE(MRES_SUPERCEDE, false);
}
sendto(g_ServerUDPSocket, g_QueryCache.info_cache, g_QueryCache.info_cache_len, 0, (sockaddr *)&to, sizeof(to)); // Validate challenge
char nugget[ 64 ];
if ( !msg.ReadString( nugget, sizeof( nugget ) ) )
RETURN_META_VALUE(MRES_SUPERCEDE, true);
if ( !ValidInfoChallenge( packet->from, nugget ) )
RETURN_META_VALUE(MRES_SUPERCEDE, true);
SendA2S_Info(packet);
RETURN_META_VALUE(MRES_SUPERCEDE, true); RETURN_META_VALUE(MRES_SUPERCEDE, true);
break;
} }
case A2S_PLAYER:
if((packet->size == 5 || packet->size == 9) && packet->data[4] == 'U')
{ {
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from)) if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
{ {
RETURN_META_VALUE(MRES_SUPERCEDE, false); 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; int32_t challengeNr = -1;
if(packet->size == 9) if(packet->size == 9)
challengeNr = *(int32_t *)&packet->data[5]; challengeNr = *(int32_t *)&packet->data[5];
@@ -404,15 +516,16 @@ bool Hook_ProcessConnectionlessPacket(netpacket_t * packet)
int32_t realChallengeNr = *(int32_t *)&packet->from.ip ^ 0x55AADD88; int32_t realChallengeNr = *(int32_t *)&packet->from.ip ^ 0x55AADD88;
if(challengeNr != realChallengeNr) if(challengeNr != realChallengeNr)
{ {
uint8_t response[9] = {0xFF, 0xFF, 0xFF, 0xFF, 'A'}; SendA2S_PlayerChallenge(packet, realChallengeNr);
*(int32_t *)&response[5] = realChallengeNr;
sendto(g_ServerUDPSocket, response, sizeof(response), 0, (sockaddr *)&to, sizeof(to));
RETURN_META_VALUE(MRES_SUPERCEDE, true); RETURN_META_VALUE(MRES_SUPERCEDE, true);
} }
sendto(g_ServerUDPSocket, g_QueryCache.players_cache, g_QueryCache.players_cache_len, 0, (sockaddr *)&to, sizeof(to)); SendA2S_Player(packet);
RETURN_META_VALUE(MRES_SUPERCEDE, true); RETURN_META_VALUE(MRES_SUPERCEDE, true);
break;
}
} }
RETURN_META_VALUE(MRES_IGNORED, false); RETURN_META_VALUE(MRES_IGNORED, false);
@@ -515,6 +628,10 @@ bool A2SQCache::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, boo
g_pSvVisibleMaxPlayers = g_pCVar->FindVar("sv_visiblemaxplayers"); g_pSvVisibleMaxPlayers = g_pCVar->FindVar("sv_visiblemaxplayers");
g_pSvTags = g_pCVar->FindVar("sv_tags"); g_pSvTags = g_pCVar->FindVar("sv_tags");
g_pSvEnableOldQueries = g_pCVar->FindVar("sv_enableoldqueries");
#if SOURCE_ENGINE >= SE_CSGO
g_SvHostNameStore = g_pCVar->FindVar("host_name_store");
#endif
return true; return true;
} }
@@ -589,10 +706,10 @@ void A2SQCache::SDK_OnAllLoaded()
#endif #endif
info.iUDPPort = iserver->GetUDPPort(); info.iUDPPort = iserver->GetUDPPort();
info.nNewFlags |= 0x80; info.nNewFlags |= S2A_EXTRA_DATA_HAS_GAME_PORT;
info.iGameID = info.iSteamAppID; info.iGameID = info.iSteamAppID;
info.nNewFlags |= 0x01; info.nNewFlags |= S2A_EXTRA_DATA_GAMEID;
UpdateQueryCache(); UpdateQueryCache();
+193
View File
@@ -0,0 +1,193 @@
//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef INETWORKSYSTEM_H
#define INETWORKSYSTEM_H
#ifdef _WIN32
#pragma once
#endif
#include "tier0/platform.h"
#include "appframework/IAppSystem.h"
// This is the packet payload without any header bytes (which are attached for actual sending)
#define NET_MAX_PAYLOAD ( 262144 - 4) // largest message we can send in bytes
#define NET_MAX_PAYLOAD_BITS 18 // 2^NET_MAX_PAYLOAD_BITS > NET_MAX_PAYLOAD
// This is just the client_t->netchan.datagram buffer size (shouldn't ever need to be huge)
#define NET_MAX_DATAGRAM_PAYLOAD 4000 // = maximum unreliable playload size
// UDP has 28 byte headers
#define UDP_HEADER_SIZE (20+8) // IP = 20, UDP = 8
#define MAX_ROUTABLE_PAYLOAD 1200 // x360 requires <= 1260, but now that listen servers can support "steam" mediated sockets, steam enforces 1200 byte limit
#if (MAX_ROUTABLE_PAYLOAD & 3) != 0
#error Bit buffers must be a multiple of 4 bytes
#endif
#define MIN_ROUTABLE_PAYLOAD 16 // minimum playload size
#define NETMSG_TYPE_BITS 8 // must be 2^NETMSG_TYPE_BITS > SVC_LASTMSG
// This is the payload plus any header info (excluding UDP header)
#define HEADER_BYTES 9 // 2*4 bytes seqnr, 1 byte flags
// Pad this to next higher 16 byte boundary
// This is the largest packet that can come in/out over the wire, before processing the header
// bytes will be stripped by the networking channel layer
#define NET_MAX_MESSAGE PAD_NUMBER( ( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 )
#define NET_HEADER_FLAG_SPLITPACKET -2
#define NET_HEADER_FLAG_COMPRESSEDPACKET -3
//-----------------------------------------------------------------------------
// Forward declarations:
//-----------------------------------------------------------------------------
class INetworkMessageHandler;
class INetworkMessage;
class INetChannel;
class INetworkMessageFactory;
class bf_read;
class bf_write;
typedef struct netadr_s netadr_t;
class CNetPacket;
//-----------------------------------------------------------------------------
// Default ports
//-----------------------------------------------------------------------------
enum
{
NETWORKSYSTEM_DEFAULT_SERVER_PORT = 27001,
NETWORKSYSTEM_DEFAULT_CLIENT_PORT = 27002
};
//-----------------------------------------------------------------------------
// This interface encompasses a one-way communication path between two
//-----------------------------------------------------------------------------
typedef int ConnectionHandle_t;
enum ConnectionStatus_t
{
CONNECTION_STATE_DISCONNECTED = 0,
CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTION_FAILED,
CONNECTION_STATE_CONNECTED,
};
//-----------------------------------------------------------------------------
// This interface encompasses a one-way communication path between two machines
//-----------------------------------------------------------------------------
//
// abstract_class INetChannel
// {
// public:
// // virtual INetworkMessageHandler *GetMsgHandler( void ) const = 0;
// virtual const netadr_t &GetRemoteAddress( void ) const = 0;
//
// // send a net message
// // NOTE: There are special connect/disconnect messages?
// virtual bool AddNetMsg( INetworkMessage *msg, bool bForceReliable = false ) = 0;
// // virtual bool RegisterMessage( INetworkMessage *msg ) = 0;
//
// virtual ConnectionStatus_t GetConnectionState( ) = 0;
//
// /*
// virtual ConnectTo( const netadr_t& to ) = 0;
// virtual Disconnect() = 0;
//
// virtual const netadr_t& GetLocalAddress() = 0;
//
// virtual const netadr_t& GetRemoteAddress() = 0;
// */
// };
//-----------------------------------------------------------------------------
// Network event types + structures
//-----------------------------------------------------------------------------
enum NetworkEventType_t
{
NETWORK_EVENT_CONNECTED = 0,
NETWORK_EVENT_DISCONNECTED,
NETWORK_EVENT_MESSAGE_RECEIVED,
};
struct NetworkEvent_t
{
NetworkEventType_t m_nType;
};
struct NetworkConnectionEvent_t : public NetworkEvent_t
{
INetChannel *m_pChannel;
};
struct NetworkDisconnectionEvent_t : public NetworkEvent_t
{
INetChannel *m_pChannel;
};
struct NetworkMessageReceivedEvent_t : public NetworkEvent_t
{
INetChannel *m_pChannel;
INetworkMessage *m_pNetworkMessage;
};
//-----------------------------------------------------------------------------
// Main interface for low-level networking (packet sending). This is a low-level interface
//-----------------------------------------------------------------------------
abstract_class INetworkSystem : public IAppSystem
{
public:
// Installs network message factories to be used with all connections
virtual bool RegisterMessage( INetworkMessage *msg ) = 0;
// Start, shutdown a server
virtual bool StartServer( unsigned short nServerListenPort = NETWORKSYSTEM_DEFAULT_SERVER_PORT ) = 0;
virtual void ShutdownServer( ) = 0;
// Process server-side network messages
virtual void ServerReceiveMessages() = 0;
virtual void ServerSendMessages() = 0;
// Start, shutdown a client
virtual bool StartClient( unsigned short nClientListenPort = NETWORKSYSTEM_DEFAULT_CLIENT_PORT ) = 0;
virtual void ShutdownClient( ) = 0;
// Process client-side network messages
virtual void ClientSendMessages() = 0;
virtual void ClientReceiveMessages() = 0;
// Connect, disconnect a client to a server
virtual INetChannel* ConnectClientToServer( const char *pServer, int nServerListenPort = NETWORKSYSTEM_DEFAULT_SERVER_PORT ) = 0;
virtual void DisconnectClientFromServer( INetChannel* pChan ) = 0;
// Event queue
virtual NetworkEvent_t *FirstNetworkEvent( ) = 0;
virtual NetworkEvent_t *NextNetworkEvent( ) = 0;
// Returns the local host name
virtual const char* GetLocalHostName( void ) const = 0;
virtual const char* GetLocalAddress( void ) const = 0;
/*
// NOTE: Server methods
// NOTE: There's only 1 client INetChannel ever
// There can be 0-N server INetChannels.
virtual INetChannel* CreateConnection( bool bIsClientConnection ) = 0;
// Add methods for setting unreliable payloads
*/
};
#endif // INETWORKSYSTEM_H
+188
View File
@@ -0,0 +1,188 @@
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#if !defined( PROTO_OOB_H )
#define PROTO_OOB_H
#ifdef _WIN32
#pragma once
#endif
// This is used, unless overridden in the registry
#define VALVE_MASTER_ADDRESS "207.173.177.10:27011"
#define PORT_RCON 27015 // Default RCON port, TCP
#define PORT_MASTER 27011 // Default master port, UDP
#define PORT_CLIENT 27005 // Default client port, UDP/TCP
#define PORT_SERVER 27015 // Default server port, UDP/TCP
#define PORT_HLTV 27020 // Default hltv port
#define PORT_HLTV1 27021 // Default hltv[instance 1] port
#define PORT_X360_RESERVED_FIRST 27026 // X360 reserved port first
#define PORT_X360_RESERVED_LAST 27034 // X360 reserved port last
#ifdef ENABLE_RPT
#define PORT_RPT 27035 // default RPT (remote perf testing) port, TCP
#define PORT_RPT_LISTEN 27036 // RPT connection listener (remote perf testing) port, TCP
#endif // ENABLE_RPT
#define PORT_REPLAY 27040 // Default replay port
// out of band message id bytes
// M = master, S = server, C = client, A = any
// the second character will always be \n if the message isn't a single
// byte long (?? not true anymore?)
// Requesting for full server list from Server Master
#define A2M_GET_SERVERS 'c' // no params
// Master response with full server list
#define M2A_SERVERS 'd' // + 6 byte IP/Port list.
// Request for full server list from Server Master done in batches
#define A2M_GET_SERVERS_BATCH 'e' // + in532 uniqueID ( -1 for first batch )
// Master response with server list for channel
#define M2A_SERVER_BATCH 'f' // + int32 next uniqueID( -1 for last batch ) + 6 byte IP/Port list.
// Request for MOTD from Server Master (Message of the Day)
#define A2M_GET_MOTD 'g' // no params
// MOTD response Server Master
#define M2A_MOTD 'h' // + string
// Generic Ping Request
#define A2A_PING 'i' // respond with an A2A_ACK
// Generic Ack
#define A2A_ACK 'j' // general acknowledgement without info
#define C2S_CONNECT 'k' // client requests to connect
// Print to client console.
#define A2A_PRINT 'l' // print a message on client
// info request
#define S2A_INFO_DETAILED 'm' // New Query protocol, returns dedicated or not, + other performance info.
#define A2S_RESERVE 'n' // reserves this server for specific players for a short period of time. Fails if not empty.
#define S2A_RESERVE_RESPONSE 'p' // server response to reservation request
// Another user is requesting a challenge value from this machine
// NOTE: this is currently duplicated in SteamClient.dll but for a different purpose,
// so these can safely diverge anytime. SteamClient will be using a different protocol
// to update the master servers anyway.
#define A2S_GETCHALLENGE 'q' // Request challenge # from another machine
#define A2S_RCON 'r' // client rcon command
#define A2A_CUSTOM 't' // a custom command, follow by a string for 3rd party tools
// A user is requesting the list of master servers, auth servers, and titan dir servers from the Client Master server
#define A2M_GETMASTERSERVERS 'v' // + byte (type of request, TYPE_CLIENT_MASTER or TYPE_SERVER_MASTER)
// Master server list response
#define M2A_MASTERSERVERS 'w' // + byte type + 6 byte IP/Port List
#define A2M_GETACTIVEMODS 'x' // + string Request to master to provide mod statistics ( current usage ). "1" for first mod.
#define M2A_ACTIVEMODS 'y' // response: modname\r\nusers\r\nservers
#define M2M_MSG 'z' // Master peering message
// SERVER TO CLIENT/ANY
// Client connection is initiated by requesting a challenge value
// the server sends this value back
#define S2C_CHALLENGE 'A' // + challenge value
// Server notification to client to commence signon process using challenge value.
#define S2C_CONNECTION 'B' // no params
// Response to server info requests
// Request for detailed server/rule information.
#define S2A_INFO_GOLDSRC 'm' // Reserved for use by goldsrc servers
#define S2M_GETFILE 'J' // request module from master
#define M2S_SENDFILE 'K' // send module to server
#define S2C_REDIRECT 'L' // + IP x.x.x.x:port, redirect client to other server/proxy
#define C2M_CHECKMD5 'M' // player client asks secure master if Module MD5 is valid
#define M2C_ISVALIDMD5 'N' // secure servers answer to C2M_CHECKMD5
// MASTER TO SERVER
#define M2A_ACTIVEMODS3 'P' // response: keyvalues struct of mods
#define A2M_GETACTIVEMODS3 'Q' // get a list of mods and the stats about them
#define S2A_LOGSTRING 'R' // send a log string
#define S2A_LOGKEY 'S' // send a log event as key value
#define S2A_LOGSTRING2 'S' // send a log string including a secret value << this clashes with S2A_LOGKEY that nothing seems to use in CS:GO and is followed by secret value so should be compatible for server ops with their existing tools
#define A2S_SERVERQUERY_GETCHALLENGE 'W' // Request challenge # from another machine
#define A2S_KEY_STRING "Source Engine Query" // required postfix to a A2S_INFO query
#define A2M_GET_SERVERS_BATCH2 '1' // New style server query
#define A2M_GETACTIVEMODS2 '2' // New style mod info query
#define C2S_AUTHREQUEST1 '3' //
#define S2C_AUTHCHALLENGE1 '4' //
#define C2S_AUTHCHALLENGE2 '5' //
#define S2C_AUTHCOMPLETE '6'
#define C2S_AUTHCONNECT '7' // Unused, signals that the client has
// authenticated the server
#define C2S_VALIDATE_SESSION '8'
// #define UNUSED_A2S_LANSEARCH 'C' // LAN game details searches
// #define UNUSED_S2A_LANSEARCHREPLY 'F' // LAN game details reply
#define S2C_CONNREJECT '9' // Special protocol for rejected connections.
#define MAX_OOB_KEYVALUES 600 // max size in bytes for keyvalues included in an OOB msg
#define MAKE_4BYTES( a, b, c, d ) ( ( ((unsigned char)(d)) << 24 ) | ( ((unsigned char)(c)) << 16 ) | ( ((unsigned char)(b)) << 8 ) | ( ((unsigned char)(a)) << 0 ) )
#define A2A_KV_CMD '?' // generic KeyValues command [1 byte: version] [version dependent data...]
#define A2A_KV_VERSION 1 // version of generic KeyValues command
// [4 bytes: header] [4 bytes: replyid] [4 bytes: challenge] [4 bytes: extra] [4 bytes: numbytes] [numbytes: serialized KV]
// These can be owned by Steam after we get rid of this legacy code.
#define S2A_INFO_SRC 'I' // + Address, hostname, map, gamedir, gamedescription, active players, maxplayers, protocol
#define S2M_HEARTBEAT 'a' // + challeange + sequence + active + #channels + channels
#define S2M_HEARTBEAT2 '0' // New style heartbeat
#define S2M_SHUTDOWN 'b' // no params
#define M2A_CHALLENGE 's' // + challenge value
#define M2S_REQUESTRESTART 'O' // HLMaster rejected a server's connection because the server needs to be updated
#define A2S_RULES 'V' // request rules list from server
#define S2A_RULES 'E' // + number of rules + string key and string value pairs
#define A2S_INFO 'T' // server info request - this must match the Goldsrc engine
#define S2A_PLAYER 'D' // + Playernum, name, frags, /*deaths*/, time on server
#define A2S_PLAYER 'U' // request player list
#define A2S_PING2 'Y' // new-style minimalist ping request
#define S2A_PING2REPLY 'Z' // new-style minimalist ping reply
// temp hack until we kill the legacy interface
// The new S2A_INFO_SRC packet has a byte at the end that has these bits in it, telling
// which data follows.
#define S2A_EXTRA_DATA_HAS_GAME_PORT 0x80 // Next 2 bytes include the game port.
#define S2A_EXTRA_DATA_HAS_SPECTATOR_DATA 0x40 // Next 2 bytes include the spectator port, then the spectator server name.
#define S2A_EXTRA_DATA_HAS_GAMETAG_DATA 0x20 // Next bytes are the game tag string
#define S2A_EXTRA_DATA_HAS_STEAMID 0x10 // Next 8 bytes are the steamID
#define S2A_EXTRA_DATA_GAMEID 0x01 // Next 8 bytes are the gameID of the server
#define A2S_RESERVE_CHECK '!' // check if server reservation cookie is same as the one we are holding
#define S2A_RESERVE_CHECK_RESPONSE '%' // server response to reservation request
#define A2S_PING '$'
#define S2A_PING_RESPONSE '^'
#endif
+70
View File
@@ -0,0 +1,70 @@
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// protocol.h -- communications protocols
#ifndef PROTOCOL_H
#define PROTOCOL_H
#ifdef _WIN32
#pragma once
#endif
#define INSTANCE_BASELINE_TABLENAME "instancebaseline"
#define LIGHT_STYLES_TABLENAME "lightstyles"
#define USER_INFO_TABLENAME "userinfo"
#define SERVER_STARTUP_DATA_TABLENAME "server_query_info" // the name is a remnant...
#define DYNAMIC_MODEL_TABLENAME "dynamicmodel"
//#define CURRENT_PROTOCOL 1
#define DELTA_OFFSET_BITS 5
#define DELTA_OFFSET_MAX ( ( 1 << DELTA_OFFSET_BITS ) - 1 )
#define DELTASIZE_BITS 20 // must be: 2^DELTASIZE_BITS > (NET_MAX_PAYLOAD * 8)
// Largest # of commands to send in a packet
#define NUM_NEW_COMMAND_BITS 4
#define MAX_NEW_COMMANDS ((1 << NUM_NEW_COMMAND_BITS)-1)
// Max number of history commands to send ( 2 by default ) in case of dropped packets
#define NUM_BACKUP_COMMAND_BITS 3
#define MAX_BACKUP_COMMANDS ((1 << NUM_BACKUP_COMMAND_BITS)-1)
#define PROTOCOL_AUTHCERTIFICATE 0x01 // Connection from client is using a WON authenticated certificate
#define PROTOCOL_HASHEDCDKEY 0x02 // Connection from client is using hashed CD key because WON comm. channel was unreachable
#define PROTOCOL_STEAM 0x03 // Steam certificates
#define PROTOCOL_LASTVALID 0x03 // Last valid protocol
#define CONNECTIONLESS_HEADER 0xFFFFFFFF // all OOB packet start with this sequence
#define STEAM_KEYSIZE 2048 // max size needed to contain a steam authentication key (both server and client)
// each channel packet has 1 byte of FLAG bits
#define PACKET_FLAG_RELIABLE (1<<0) // packet contains subchannel stream data
#define PACKET_FLAG_COMPRESSED (1<<1) // packet is compressed
#define PACKET_FLAG_ENCRYPTED (1<<2) // packet is encrypted
#define PACKET_FLAG_SPLIT (1<<3) // packet is split
#define PACKET_FLAG_CHOKED (1<<4) // packet was choked by sender
// NOTE: Bits 5, 6, and 7 are used to specify the # of padding bits at the end of the packet!!!
#define ENCODE_PAD_BITS( x ) ( ( x << 5 ) & 0xff )
#define DECODE_PAD_BITS( x ) ( ( x >> 5 ) & 0xff )
//
// client to server
//
#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file.
#define RES_PRELOAD (1<<1) // Load on client rather than just reserving name
// Some day we may want to integrate Zoid's CL 1295766 - Rewrite of Source networking to be protobuf based.
#endif // PROTOCOL_H