diff --git a/AMBuildScript b/AMBuildScript index a0b6366..9b0fa5f 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -480,7 +480,8 @@ class ExtensionConfig(object): ['public', 'mathlib'], ['public', 'vstdlib'], ['public', 'tier0'], - ['public', 'tier1'] + ['public', 'tier1'], + ['public', 'appframework'] ] if sdk.name == 'episode1' or sdk.name == 'darkm': paths.append(['public', 'dlls']) diff --git a/extension.cpp b/extension.cpp index 74f3703..eafdf5f 100644 --- a/extension.cpp +++ b/extension.cpp @@ -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 +#include #include #include #include @@ -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(); diff --git a/inetworksystem.h b/inetworksystem.h new file mode 100644 index 0000000..6d02a0e --- /dev/null +++ b/inetworksystem.h @@ -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 \ No newline at end of file diff --git a/proto_oob.h b/proto_oob.h new file mode 100644 index 0000000..57015fc --- /dev/null +++ b/proto_oob.h @@ -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 diff --git a/protocol.h b/protocol.h new file mode 100644 index 0000000..a6274e4 --- /dev/null +++ b/protocol.h @@ -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 +