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
+279 -162
View File
@@ -34,6 +34,11 @@
#include "CDetour/detours.h"
#include "steam/steam_gameserver.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 <bitbuf.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_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_pSvVisibleMaxPlayers;
ConVar *g_pSvTags;
#if SOURCE_ENGINE < SE_CSGO
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;
IGameEventManager2 *g_pGameEvents = NULL;
@@ -156,53 +165,48 @@ struct CQueryCache
{
struct CPlayer
{
bool active;
bool fake;
int userid;
IClient *pClient;
char name[MAX_PLAYER_NAME_LENGTH];
unsigned nameLen;
int32_t score;
double time;
bool active = false;
bool fake = false;
int userid = 0;
IClient *pClient = NULL;
char name[MAX_PLAYER_NAME_LENGTH] = "\0";
unsigned nameLen = 0;
int32_t score = 0;
double time = 0.0;
} 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 aHostName[255] = "\0"; // Name | string | Name of the server.
uint8_t aHostNameLen = 0;
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.
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 aGameDirLen = 0;
char aGameDescription[255] = "\0"; // Game | string | Full name of the game.
uint8_t aGameDescriptionLen = 0;
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 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 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 nPassword = 0; // Visibility | byte | Indicates whether the server requires a password: 0 for public, 1 for private
uint8_t bIsSecure = 0; // VAC | byte | Specifies whether the server uses VAC: 0 for unsecured, 1 for secured
char aVersion[40] = "\0"; // Version | string | Version of the game installed on the server.
uint8_t aVersionLen = 0;
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.
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.
uint16_t iUDPPort = 0; // EDF & S2A_EXTRA_DATA_HAS_GAME_PORT -> Port | short | The server's game port number.
uint64_t iSteamID = 0; // EDF & S2A_EXTRA_DATA_HAS_STEAMID -> SteamID | long long | Server's SteamID.
uint16_t iHLTVUDPPort = 0; // EDF & S2A_EXTRA_DATA_HAS_SPECTATOR_DATA -> Port | short | Spectator port number 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 = 0;
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 = 0;
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;
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;
class CBaseClient;
@@ -213,7 +217,8 @@ void UpdateQueryCache()
{
// A2S_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)
info.aMapNameLen = strlcpy(info.aMapName, iserver->GetMapName(), sizeof(info.aMapName));
@@ -229,16 +234,32 @@ void UpdateQueryCache()
info.nMaxClients = g_pSvVisibleMaxPlayers->GetInt();
else
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.bIsSecure = true;
if(!(info.nNewFlags & 0x10) && engine->GetGameServerSteamID())
if(!(info.nNewFlags & S2A_EXTRA_DATA_HAS_STEAMID) && engine->GetGameServerSteamID())
{
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
hltv = hltvdirector->GetHLTVServer(0);
@@ -252,167 +273,259 @@ void UpdateQueryCache()
{
info.iHLTVUDPPort = ihltvserver->GetUDPPort();
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));
if(info.aKeywordsLen)
info.nNewFlags |= 0x20;
info.nNewFlags |= S2A_EXTRA_DATA_HAS_GAMETAG_DATA;
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;
bool RequireValidChallenge( const netadr_t &adr )
{
if ( g_pSvEnableOldQueries->GetBool() == true )
{
return false; // don't enforce challenge numbers
}
if(info.nNewFlags & 0x10) {
*(uint64_t *)&info_cache[pos] = info.iSteamID;
pos += 8;
return true;
}
bool ValidInfoChallenge( const netadr_t & adr, const char *nugget )
{
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 & 0x40) {
*(uint16_t *)&info_cache[pos] = info.iHLTVUDPPort;
pos += 2;
return true;
}
memcpy(&info_cache[pos], info.aHLTVName, info.aHLTVNameLen + 1);
pos += info.aHLTVNameLen + 1;
void SendA2S_PlayerChallenge(netpacket_t * packet, int32_t realChallengeNr)
{
struct sockaddr addr;
packet->from.ToSockadr ( &addr );
CUtlBuffer buf;
buf.EnsureCapacity( MAX_ROUTABLE_PAYLOAD );
buf.PutUnsignedInt( LittleDWord( CONNECTIONLESS_HEADER ) );
buf.PutUnsignedChar( S2C_CHALLENGE );
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++;
}
if(info.nNewFlags & 0x20) {
memcpy(&info_cache[pos], info.aKeywords, info.aKeywordsLen + 1);
pos += info.aKeywordsLen + 1;
}
// Number of players
buf.PutUnsignedChar( nPlayerCount );
if(info.nNewFlags & 0x01) {
*(uint64_t *)&info_cache[pos] = info.iGameID;
pos += 8;
}
g_QueryCache.info_cache_len = pos;
// A2S_PLAYER
uint8_t *players_cache = g_QueryCache.players_cache;
pos = 6; // header: FF FF FF FF D 0[numplayers]
unsigned char nPlayerUserID = 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;
players_cache[pos++] = players_cache[5]; // Index | byte | Index of player chunk starting from 0.
players_cache[5]++; // Players | byte | Number of players whose information was gathered.
memcpy(&players_cache[pos], player.name, player.nameLen + 1); // Name | string | Name of the player.
pos += player.nameLen + 1;
*(int32_t *)&players_cache[pos] = player.score; // Score | long | Player's score (usually "frags" or "kills".)
pos += 4;
*(float *)&players_cache[pos] = *net_time - player.time; // Duration | float | Time (in seconds) player has been connected to the server.
pos += 4;
// User ID
buf.PutUnsignedChar( nPlayerUserID );
// Player Name
buf.PutString( player.name );
// Player Score
buf.PutInt( player.score );
// Player Duration
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)
{
if(packet->size >= 25 && packet->data[4] == 'T')
bf_read msg = packet->message; // handy shortcut
char c = msg.ReadChar();
switch ( c )
{
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
case 0:
{
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
{
RETURN_META_VALUE(MRES_SUPERCEDE, false);
}
RETURN_META_VALUE(MRES_SUPERCEDE, false);
break;
}
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))
case A2S_INFO:
{
RETURN_META_VALUE(MRES_SUPERCEDE, false);
}
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;
// 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);
int32_t challengeNr = -1;
if(packet->size == 9)
challengeNr = *(int32_t *)&packet->data[5];
SendA2S_Info(packet);
/* TODO
* This is a complete nonsense challenge as it doesn't offer any protection at all.
* The point of this challenge is to stop spoofed source UDP 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);
break;
}
case A2S_PLAYER:
{
if(!CIPRateLimit__CheckIP(s_queryRateChecker, packet->from))
{
RETURN_META_VALUE(MRES_SUPERCEDE, false);
}
sendto(g_ServerUDPSocket, g_QueryCache.players_cache, g_QueryCache.players_cache_len, 0, (sockaddr *)&to, sizeof(to));
int32_t challengeNr = -1;
if(packet->size == 9)
challengeNr = *(int32_t *)&packet->data[5];
RETURN_META_VALUE(MRES_SUPERCEDE, true);
/* TODO
* This is a complete nonsense challenge as it doesn't offer any protection at all.
* The point of this challenge is to stop spoofed source UDP 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)
{
SendA2S_PlayerChallenge(packet, realChallengeNr);
RETURN_META_VALUE(MRES_SUPERCEDE, true);
}
SendA2S_Player(packet);
RETURN_META_VALUE(MRES_SUPERCEDE, true);
break;
}
}
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_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;
}
@@ -589,10 +706,10 @@ void A2SQCache::SDK_OnAllLoaded()
#endif
info.iUDPPort = iserver->GetUDPPort();
info.nNewFlags |= 0x80;
info.nNewFlags |= S2A_EXTRA_DATA_HAS_GAME_PORT;
info.iGameID = info.iSteamAppID;
info.nNewFlags |= 0x01;
info.nNewFlags |= S2A_EXTRA_DATA_GAMEID;
UpdateQueryCache();