//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= // hud_servers.cpp #include "hud.h" #include "cl_util.h" #include "hud_servers_priv.h" #include "hud_servers.h" #include "net_api.h" #include #ifdef _WIN32 #include #else #define __cdecl #include #endif static int context_id; // Default master server address in case we can't read any from valvecomm.lst file #define VALVE_MASTER_ADDRESS "half-life.east.won.net" #define PORT_MASTER 27010 #define PORT_SERVER 27015 // File where we really should look for master servers #define MASTER_PARSE_FILE "valvecomm.lst" #define MAX_QUERIES 20 #define NET_API gEngfuncs.pNetAPI static CHudServers *g_pServers = NULL; /* =================== ListResponse Callback from engine =================== */ void NET_CALLBACK ListResponse( struct net_response_s *response ) { if ( g_pServers ) { g_pServers->ListResponse( response ); } } /* =================== ServerResponse Callback from engine =================== */ void NET_CALLBACK ServerResponse( struct net_response_s *response ) { if ( g_pServers ) { g_pServers->ServerResponse( response ); } } /* =================== PingResponse Callback from engine =================== */ void NET_CALLBACK PingResponse( struct net_response_s *response ) { if ( g_pServers ) { g_pServers->PingResponse( response ); } } /* =================== RulesResponse Callback from engine =================== */ void NET_CALLBACK RulesResponse( struct net_response_s *response ) { if ( g_pServers ) { g_pServers->RulesResponse( response ); } } /* =================== PlayersResponse Callback from engine =================== */ void NET_CALLBACK PlayersResponse( struct net_response_s *response ) { if ( g_pServers ) { g_pServers->PlayersResponse( response ); } } /* =================== ListResponse =================== */ void CHudServers::ListResponse( struct net_response_s *response ) { request_t *list; request_t *p; int c = 0; if ( !( response->error == NET_SUCCESS ) ) return; if ( response->type != NETAPI_REQUEST_SERVERLIST ) return; if ( response->response ) { list = ( request_t * ) response->response; while ( list ) { c++; //if ( c < 40 ) { // Copy from parsed stuff p = new request_t; p->context = -1; p->remote_address = list->remote_address; p->next = m_pServerList; m_pServerList = p; } // Move on list = list->next; } } gEngfuncs.Con_Printf( "got list\n" ); m_nQuerying = 1; m_nActiveQueries = 0; } /* =================== ServerResponse =================== */ void CHudServers::ServerResponse( struct net_response_s *response ) { char *szresponse; request_t *p; server_t *browser; int len; char sz[ 32 ]; // Remove from active list p = FindRequest( response->context, m_pActiveList ); if ( p ) { RemoveServerFromList( &m_pActiveList, p ); m_nActiveQueries--; } if ( response->error != NET_SUCCESS ) return; switch ( response->type ) { case NETAPI_REQUEST_DETAILS: if ( response->response ) { szresponse = (char *)response->response; len = strlen( szresponse ) + 100 + 1; sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); browser = new server_t; browser->remote_address = response->remote_address; browser->info = new char[ len ]; browser->ping = (int)( 1000.0 * response->ping ); strcpy( browser->info, szresponse ); NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); NET_API->SetValueForKey( browser->info, "ping", sz, len ); AddServer( &m_pServers, browser ); } break; default: break; } } /* =================== PingResponse =================== */ void CHudServers::PingResponse( struct net_response_s *response ) { char sz[ 32 ]; if ( response->error != NET_SUCCESS ) return; switch ( response->type ) { case NETAPI_REQUEST_PING: sprintf( sz, "%.2f", 1000.0 * response->ping ); gEngfuncs.Con_Printf( "ping == %s\n", sz ); break; default: break; } } /* =================== RulesResponse =================== */ void CHudServers::RulesResponse( struct net_response_s *response ) { char *szresponse; if ( response->error != NET_SUCCESS ) return; switch ( response->type ) { case NETAPI_REQUEST_RULES: if ( response->response ) { szresponse = (char *)response->response; gEngfuncs.Con_Printf( "rules %s\n", szresponse ); } break; default: break; } } /* =================== PlayersResponse =================== */ void CHudServers::PlayersResponse( struct net_response_s *response ) { char *szresponse; if ( response->error != NET_SUCCESS ) return; switch ( response->type ) { case NETAPI_REQUEST_PLAYERS: if ( response->response ) { szresponse = (char *)response->response; gEngfuncs.Con_Printf( "players %s\n", szresponse ); } break; default: break; } } /* =================== CompareServers Return 1 if p1 is "less than" p2, 0 otherwise =================== */ int CHudServers::CompareServers( server_t *p1, server_t *p2 ) { const char *n1, *n2; if ( p1->ping < p2->ping ) return 1; if ( p1->ping == p2->ping ) { // Pings equal, sort by second key: hostname if ( p1->info && p2->info ) { n1 = NET_API->ValueForKey( p1->info, "hostname" ); n2 = NET_API->ValueForKey( p2->info, "hostname" ); if ( n1 && n2 ) { if ( stricmp( n1, n2 ) < 0 ) return 1; } } } return 0; } /* =================== AddServer =================== */ void CHudServers::AddServer( server_t **ppList, server_t *p ) { server_t *list; if ( !ppList || ! p ) return; m_nServerCount++; // What sort key? Ping? list = *ppList; // Head of list? if ( !list ) { p->next = NULL; *ppList = p; return; } // Put on head of list if ( CompareServers( p, list ) ) { p->next = *ppList; *ppList = p; } else { while ( list->next ) { // Insert before list next if ( CompareServers( p, list->next ) ) { p->next = list->next->next; list->next = p; return; } list = list->next; } // Just add at end p->next = NULL; list->next = p; } } /* =================== Think =================== */ void CHudServers::Think( double time ) { m_fElapsed += time; if ( !m_nRequesting ) return; if ( !m_nQuerying ) return; QueryThink(); if ( ServerListSize() > 0 ) return; m_dStarted = 0.0; m_nRequesting = 0; m_nDone = 0; m_nQuerying = 0; m_nActiveQueries = 0; } /* =================== QueryThink =================== */ void CHudServers::QueryThink( void ) { request_t *p; if ( !m_nRequesting || m_nDone ) return; if ( !m_nQuerying ) return; if ( m_nActiveQueries > MAX_QUERIES ) return; // Nothing left if ( !m_pServerList ) return; while ( 1 ) { p = m_pServerList; // No more in list? if ( !p ) break; // Move to next m_pServerList = m_pServerList->next; // Setup context_id p->context = context_id; // Start up query on this one NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); // Increment active list m_nActiveQueries++; // Add to active list p->next = m_pActiveList; m_pActiveList = p; // Too many active? if ( m_nActiveQueries > MAX_QUERIES ) break; } } /* ================== ServerListSize # of servers in active query and in pending to be queried lists ================== */ int CHudServers::ServerListSize( void ) { int c = 0; request_t *p; p = m_pServerList; while ( p ) { c++; p = p->next; } p = m_pActiveList; while ( p ) { c++; p = p->next; } return c; } /* =================== FindRequest Look up a request by context id =================== */ CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) { request_t *p; p = pList; while ( p ) { if ( context == p->context ) return p; p = p->next; } return NULL; } /* =================== RemoveServerFromList Remote, but don't delete, item from *ppList =================== */ void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) { request_t *p, *n; request_t *newlist = NULL; if ( !ppList ) return; p = *ppList; while ( p ) { n = p->next; if ( p != item ) { p->next = newlist; newlist = p; } p = n; } *ppList = newlist; } /* =================== ClearRequestList =================== */ void CHudServers::ClearRequestList( request_t **ppList ) { request_t *p, *n; if ( !ppList ) return; p = *ppList; while ( p ) { n = p->next; delete p; p = n; } *ppList = NULL; } /* =================== ClearServerList =================== */ void CHudServers::ClearServerList( server_t **ppList ) { server_t *p, *n; if ( !ppList ) return; p = *ppList; while ( p ) { n = p->next; delete[] p->info; delete p; p = n; } *ppList = NULL; } int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) { const char *sz1, *sz2; float fv1, fv2; sz1 = NET_API->ValueForKey( p1->info, fieldname ); sz2 = NET_API->ValueForKey( p2->info, fieldname ); fv1 = atof( sz1 ); fv2 = atof( sz2 ); if ( fv1 && fv2 ) { if ( fv1 > fv2 ) return iSortOrder; else if ( fv1 < fv2 ) return -iSortOrder; else return 0; } // String compare return stricmp( sz1, sz2 ); } int ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) { if (!p1 || !p2) // No meaningful comparison return 0; int iSortOrder = 1; int retval = 0; retval = CompareField( p1, p2, fieldname, iSortOrder ); return retval; } static char g_fieldname[ 256 ]; int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) { CHudServers::server_t *list1, *list2; list1 = *(CHudServers::server_t **)elem1; list2 = *(CHudServers::server_t **)elem2; return ServerListCompareFunc( list1, list2, g_fieldname ); } void CHudServers::SortServers( const char *fieldname ) { server_t *p; // Create a list if ( !m_pServers ) return; strcpy( g_fieldname, fieldname ); int i; int c = 0; p = m_pServers; while ( p ) { c++; p = p->next; } server_t **pSortArray; pSortArray = new server_t *[ c ]; memset( pSortArray, 0, c * sizeof( server_t * ) ); // Now copy the list into the pSortArray: p = m_pServers; i = 0; while ( p ) { pSortArray[ i++ ] = p; p = p->next; } // Now do that actual sorting. size_t nCount = c; size_t nSize = sizeof( server_t * ); qsort( pSortArray, (size_t)nCount, (size_t)nSize, FnServerCompare ); // Now rebuild the list. m_pServers = pSortArray[0]; for ( i = 0; i < c - 1; i++ ) { pSortArray[ i ]->next = pSortArray[ i + 1 ]; } pSortArray[ c - 1 ]->next = NULL; // Clean Up. delete[] pSortArray; } /* =================== GetServer Return particular server =================== */ CHudServers::server_t *CHudServers::GetServer( int server ) { int c = 0; server_t *p; p = m_pServers; while ( p ) { if ( c == server ) return p; c++; p = p->next; } return NULL; } /* =================== GetServerInfo Return info ( key/value ) string for particular server =================== */ char *CHudServers::GetServerInfo( int server ) { server_t *p = GetServer( server ); if ( p ) { return p->info; } return NULL; } /* =================== CancelRequest Kill all pending requests in engine =================== */ void CHudServers::CancelRequest( void ) { m_nRequesting = 0; m_nQuerying = 0; m_nDone = 1; NET_API->CancelAllRequests(); } /* ================== LoadMasterAddresses Loads the master server addresses from file and into the passed in array ================== */ int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) { int i; char szMaster[ 256 ]; char szMasterFile[256]; char *pbuffer = NULL; char *pstart = NULL ; netadr_t adr; char szAdr[64]; int nPort; int nCount = 0; bool bIgnore; int nDefaultPort; // Assume default master and master file strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string strcpy( szMasterFile, MASTER_PARSE_FILE ); // See if there is a command line override i = gEngfuncs.CheckParm( "-comm", &pstart ); if ( i && pstart ) { strcpy (szMasterFile, pstart ); } // Read them in from proper file pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc if ( !pbuffer ) { goto finish_master; } pstart = pbuffer; while ( nCount < maxservers ) { pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); if ( strlen(m_szToken) <= 0) break; bIgnore = true; if ( !stricmp( m_szToken, "Master" ) ) { nDefaultPort = PORT_MASTER; bIgnore = FALSE; } // Now parse all addresses between { } pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); if ( strlen(m_szToken) <= 0 ) break; if ( stricmp ( m_szToken, "{" ) ) break; // Parse addresses until we get to "}" while ( nCount < maxservers ) { char base[256]; // Now parse all addresses between { } pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); if (strlen(m_szToken) <= 0) break; if ( !stricmp ( m_szToken, "}" ) ) break; sprintf( base, "%s", m_szToken ); pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); if (strlen(m_szToken) <= 0) break; if ( stricmp( m_szToken, ":" ) ) break; pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); if (strlen(m_szToken) <= 0) break; nPort = atoi ( m_szToken ); if ( !nPort ) nPort = nDefaultPort; sprintf( szAdr, "%s:%i", base, nPort ); // Can we resolve it any better if ( !NET_API->StringToAdr( szAdr, &adr ) ) bIgnore = true; if ( !bIgnore ) { padr[ nCount++ ] = adr; } } } finish_master: if ( !nCount ) { sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string // Convert to netadr_t if ( NET_API->StringToAdr ( szMaster, &adr ) ) { padr[ nCount++ ] = adr; } } *count = nCount; if ( pbuffer ) { gEngfuncs.COM_FreeFile( pbuffer ); } return ( nCount > 0 ) ? 1 : 0; } /* =================== RequestList Request list of game servers from master =================== */ void CHudServers::RequestList( void ) { m_nRequesting = 1; m_nDone = 0; m_dStarted = m_fElapsed; int count = 0; netadr_t adr; if ( !LoadMasterAddresses( 1, &count, &adr ) ) { gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); return; } ClearRequestList( &m_pActiveList ); ClearRequestList( &m_pServerList ); ClearServerList( &m_pServers ); m_nServerCount = 0; // Make sure networking system has started. NET_API->InitNetworking(); // Kill off left overs if any NET_API->CancelAllRequests(); // Request Server List from master NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); } void CHudServers::RequestBroadcastList( int clearpending ) { m_nRequesting = 1; m_nDone = 0; m_dStarted = m_fElapsed; netadr_t adr; memset( &adr, 0, sizeof( adr ) ); if ( clearpending ) { ClearRequestList( &m_pActiveList ); ClearRequestList( &m_pServerList ); ClearServerList( &m_pServers ); m_nServerCount = 0; } // Make sure to byte swap server if necessary ( using "host" to "net" conversion adr.port = htons( PORT_SERVER ); // Make sure networking system has started. NET_API->InitNetworking(); if ( clearpending ) { // Kill off left overs if any NET_API->CancelAllRequests(); } adr.type = NA_BROADCAST; // Request Servers from LAN via IP NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); adr.type = NA_BROADCAST_IPX; // Request Servers from LAN via IPX ( if supported ) NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); } void CHudServers::ServerPing( int server ) { server_t *p; p = GetServer( server ); if ( !p ) return; // Make sure networking system has started. NET_API->InitNetworking(); // Request Server List from master NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); } void CHudServers::ServerRules( int server ) { server_t *p; p = GetServer( server ); if ( !p ) return; // Make sure networking system has started. NET_API->InitNetworking(); // Request Server List from master NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); } void CHudServers::ServerPlayers( int server ) { server_t *p; p = GetServer( server ); if ( !p ) return; // Make sure networking system has started. NET_API->InitNetworking(); // Request Server List from master NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); } int CHudServers::isQuerying() { return m_nRequesting ? 1 : 0; } /* =================== GetServerCount Return number of servers in browser list =================== */ int CHudServers::GetServerCount( void ) { return m_nServerCount; } /* =================== CHudServers =================== */ CHudServers::CHudServers( void ) { m_nRequesting = 0; m_dStarted = 0.0; m_nDone = 0; m_pServerList = NULL; m_pServers = NULL; m_pActiveList = NULL; m_nQuerying = 0; m_nActiveQueries = 0; m_fElapsed = 0.0; m_pPingRequest = NULL; m_pRulesRequest = NULL; m_pPlayersRequest = NULL; } /* =================== ~CHudServers =================== */ CHudServers::~CHudServers( void ) { ClearRequestList( &m_pActiveList ); ClearRequestList( &m_pServerList ); ClearServerList( &m_pServers ); m_nServerCount = 0; if ( m_pPingRequest ) { delete m_pPingRequest; m_pPingRequest = NULL; } if ( m_pRulesRequest ) { delete m_pRulesRequest; m_pRulesRequest = NULL; } if ( m_pPlayersRequest ) { delete m_pPlayersRequest; m_pPlayersRequest = NULL; } } /////////////////////////////// // // PUBLIC APIs // /////////////////////////////// /* =================== ServersGetCount =================== */ int ServersGetCount( void ) { if ( g_pServers ) { return g_pServers->GetServerCount(); } return 0; } int ServersIsQuerying( void ) { if ( g_pServers ) { return g_pServers->isQuerying(); } return 0; } /* =================== ServersGetInfo =================== */ const char *ServersGetInfo( int server ) { if ( g_pServers ) { return g_pServers->GetServerInfo( server ); } return NULL; } void SortServers( const char *fieldname ) { if ( g_pServers ) { g_pServers->SortServers( fieldname ); } } /* =================== ServersShutdown =================== */ void ServersShutdown( void ) { if ( g_pServers ) { delete g_pServers; g_pServers = NULL; } } /* =================== ServersInit =================== */ void ServersInit( void ) { // Kill any previous instance ServersShutdown(); g_pServers = new CHudServers(); } /* =================== ServersThink =================== */ void ServersThink( double time ) { if ( g_pServers ) { g_pServers->Think( time ); } } /* =================== ServersCancel =================== */ void ServersCancel( void ) { if ( g_pServers ) { g_pServers->CancelRequest(); } } // Requests /* =================== ServersList =================== */ void ServersList( void ) { if ( g_pServers ) { g_pServers->RequestList(); } } void BroadcastServersList( int clearpending ) { if ( g_pServers ) { g_pServers->RequestBroadcastList( clearpending ); } } void ServerPing( int server ) { if ( g_pServers ) { g_pServers->ServerPing( server ); } } void ServerRules( int server ) { if ( g_pServers ) { g_pServers->ServerRules( server ); } } void ServerPlayers( int server ) { if ( g_pServers ) { g_pServers->ServerPlayers( server ); } }