//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Server Query States: // 1: Master server query status -> wait for master response // 2: Master server packet status -> wait for master packets to arrive // 3: Server ping status -> wait for servers to respond to pings // 4: Server query status -> wait for servers to respond to queries // 5: Done // Master Server Packets: // Header // message Message id // flags Query flags // sequenceNumber Packet sequence id // Server Query Filter Packet // packetIndex Request specific page # (rest empty) // gameType Game type string // missionType Mission type string // minPlayers At least this many players // maxPlayers No more than this many // regions Region mask, 0 = all // version Server version, 0 = any // filterFlags Server flags (dedicated, etc), 0 = any // maxBots No more than maxBots // minCPUSpeed At least this fast // playerCount Buddy list search // playerList[playerCount] // Master Server Info Packet // gameType Game type string // missionType Mission type string // maxPlayers Max allowed // regions Region mask // version Server version # // infoFlags Server flags (dedicated, etc) // numBots Current bot count // CPUSpeed Server CPU speed // playerCount Current player count // playerList[playerCount] // Game Info Query Packet // gameType Game type string // missionType Mission type string // missionName You get one guess... // satusFlags Dedicated, etc. // playerCount Current player count // maxPlayers Max allowed // numBots Current bot count // CPUSpeed Server CPU speed // statusString Server info message // statusString Server status message // Accessed Environment Vars // Server::MissionType // Server::MissionName // Server::GameType // Server::ServerType // Server::PlayerCount // Server::BotCount // Server::GuidList[playerCount] // Server::Dedicated // Server::Status // Pref::Server::Name // Pref::Server::Password // Pref::Server::Info // Pref::Server::MaxPlayers // Pref::Server::RegionMask // Pref::Net::RegionMask // Pref::Client::Master[n] // Pref::Client::ServerFavoriteCount // Pref::Client::ServerFavorite[ServerFavoriteCount] //----------------------------------------------------------------------------- #include "game/net/serverQuery.h" #include "platform/platform.h" #include "platform/event.h" #include "core/dnet.h" #include "core/tVector.h" #include "core/resManager.h" #include "core/bitStream.h" #include "console/console.h" #include "console/simBase.h" #include "game/banList.h" #include "game/version.h" #include "game/auth.h" #include "game/gameConnection.h" // This is basically the server query protocol version now: static const char* versionString = "VER1"; Vector gServerList(__FILE__, __LINE__); static Vector gMasterServerList(__FILE__, __LINE__); static Vector gFinishedList(__FILE__, __LINE__); // timed out servers and finished servers go here NetAddress gMasterServerQueryAddress; bool gServerBrowserDirty = false; static const U32 gHeartbeatInterval = 120000; static const S32 gMasterServerRetryCount = 3; static const S32 gMasterServerTimeout = 2000; static const S32 gPacketRetryCount = 4; static const S32 gPacketTimeout = 1000; static const S32 gMaxConcurrentPings = 10; static const S32 gMaxConcurrentQueries = 2; static const S32 gPingRetryCount = 4; static const S32 gPingTimeout = 800; static const S32 gQueryRetryCount = 4; static const S32 gQueryTimeout = 1000; // State variables: static bool sgServerQueryActive = false; static S32 gPingSession = 0; static S32 gKey = 0; static bool gGotFirstListPacket = false; // Variables used for the interface: static U32 gServerPingCount = 0; static U32 gServerQueryCount = 0; static U32 gHeartbeatSeq = 0; ConsoleFunctionGroupBegin( ServerQuery, "Functions which allow you to query the LAN or a master server for online games."); //----------------------------------------------------------------------------- struct Ping { NetAddress address; S32 session; S32 key; U32 time; U32 tryCount; bool broadcast; }; static Ping gMasterServerPing; static Vector gPingList(__FILE__, __LINE__); static Vector gQueryList(__FILE__, __LINE__); //----------------------------------------------------------------------------- struct PacketStatus { U8 index; S32 key; U32 time; U32 tryCount; PacketStatus( U8 _index, S32 _key, U32 _time ) { index = _index; key = _key; time = _time; tryCount = gPacketRetryCount; } }; static Vector gPacketStatusList(__FILE__, __LINE__); static U8 sendPacketData[MaxPacketDataSize]; //----------------------------------------------------------------------------- struct ServerFilter { enum Type { Normal = 0, Buddy = 1, Offline = 2, Favorites = 3, }; Type type; char* gameType; char* missionType; enum // Query Flags { OnlineQuery = 0, // Authenticated with master OfflineQuery = BIT(0), // On our own NoStringCompress = BIT(1), }; enum // Filter flags: { Dedicated = BIT(0), NotPassworded = BIT(1), Linux = BIT(2), CurrentVersion = BIT(7), }; U8 queryFlags; U8 minPlayers; U8 maxPlayers; U8 maxBots; U32 regionMask; U32 maxPing; U8 filterFlags; U16 minCPU; U8 buddyCount; U32* buddyList; ServerFilter() { queryFlags = 0; gameType = NULL; missionType = NULL; minPlayers = 0; maxPlayers = 255; maxBots = 16; regionMask = 0xFFFFFFFF; maxPing = 0; filterFlags = 0; minCPU = 0; buddyCount = 0; buddyList = NULL; } ~ServerFilter() { if ( gameType ) dFree( gameType ); if ( missionType ) dFree( missionType ); if ( buddyList ) dFree( buddyList ); } }; static ServerFilter sActiveFilter; //----------------------------------------------------------------------------- // Forward function declarations: //----------------------------------------------------------------------------- static void pushPingRequest( const NetAddress *addr ); static void pushPingBroadcast( const NetAddress *addr ); static void pushServerFavorites(); static bool pickMasterServer(); static S32 findPingEntry( Vector &v, const NetAddress* addr ); static bool addressFinished( const NetAddress* addr ); static ServerInfo* findServerInfo( const NetAddress* addr ); static ServerInfo* findOrCreateServerInfo( const NetAddress* addr ); static void removeServerInfo( const NetAddress* addr ); static void sendPacket( U8 pType, const NetAddress* addr, U32 key, U32 session, U8 flags ); static void writeCString( BitStream* stream, const char* string ); static void readCString( BitStream* stream, char* buffer ); static void writeLongCString( BitStream* stream, const char* string ); static void readLongCString( BitStream* stream, char* buffer ); static void processMasterServerQuery( U32 session ); static void processPingsAndQueries( U32 session, bool schedule = true); static void processServerListPackets( U32 session ); static void processHeartbeat(U32); static void updatePingProgress(); static void updateQueryProgress(); Vector* getMasterServerList(); bool pickMasterServer(); void clearServerList(); //----------------------------------------------------------------------------- // Events //----------------------------------------------------------------------------- //---------------------------------------------------------------- class ProcessMasterQueryEvent : public SimEvent { U32 session; public: ProcessMasterQueryEvent( U32 _session ) { session = _session; } void process( SimObject *object ) { processMasterServerQuery( session ); } }; //---------------------------------------------------------------- class ProcessPingEvent : public SimEvent { U32 session; public: ProcessPingEvent( U32 _session ) { session = _session; } void process( SimObject *object ) { processPingsAndQueries( session ); } }; //---------------------------------------------------------------- class ProcessPacketEvent : public SimEvent { U32 session; public: ProcessPacketEvent( U32 _session ) { session = _session; } void process( SimObject *object ) { processServerListPackets( session ); } }; //---------------------------------------------------------------- class HeartbeatEvent : public SimEvent { U32 mSeq; public: HeartbeatEvent(U32 seq) { mSeq = seq; } void process( SimObject *object ) { processHeartbeat(mSeq); } }; //----------------------------------------------------------------------------- // Public query methods //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType, U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU, U8 filterFlags) { sgServerQueryActive = true; clearServerList(); pushServerFavorites(); sActiveFilter.type = ServerFilter::Offline; // Clear the filter: if ( !sActiveFilter.gameType || dStricmp( sActiveFilter.gameType, "Any" ) != 0 ) { sActiveFilter.gameType = (char*) dRealloc( sActiveFilter.gameType, 4 ); dStrcpy( sActiveFilter.gameType, "Any" ); } if ( !sActiveFilter.missionType || dStricmp( sActiveFilter.missionType, "Any" ) != 0 ) { sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, 4 ); dStrcpy( sActiveFilter.missionType, "Any" ); } sActiveFilter.queryFlags = 0; sActiveFilter.minPlayers = minPlayers; sActiveFilter.maxPlayers = maxPlayers; sActiveFilter.maxBots = maxBots; sActiveFilter.regionMask = regionMask; sActiveFilter.maxPing = maxPing; sActiveFilter.minCPU = minCPU; sActiveFilter.filterFlags = filterFlags; NetAddress addr; char addrText[256]; dSprintf( addrText, sizeof( addrText ), "IP:BROADCAST:%d", port ); Net::stringToAddress( addrText, &addr ); pushPingBroadcast( &addr ); Con::executef( 4, "onServerQueryStatus", "start", "Querying LAN servers", "0"); processPingsAndQueries( gPingSession ); } //----------------------------------------------------------------------------- ConsoleFunction( queryLanServers, void, 12, 12, "queryLanServers(...);" ) { argc; U32 lanPort = dAtoi(argv[1]); U8 flags = dAtoi(argv[2]); // It's not a good idea to hold onto args, recursive calls to // console exec will trash them. char* gameType = dStrdup(argv[3]); char* missionType = dStrdup(argv[4]); U8 minPlayers = dAtoi(argv[5]); U8 maxPlayers = dAtoi(argv[6]); U8 maxBots = dAtoi(argv[7]); U32 regionMask = dAtoi(argv[8]); U32 maxPing = dAtoi(argv[9]); U16 minCPU = dAtoi(argv[10]); U8 filterFlags = dAtoi(argv[11]); queryLanServers(lanPort, flags, gameType, missionType, minPlayers, maxPlayers, maxBots, regionMask, maxPing, minCPU, filterFlags); dFree(gameType); dFree(missionType); } //----------------------------------------------------------------------------- void queryMasterGameTypes() { Vector *masterList = getMasterServerList(); if ( masterList->size() != 0 ) { U32 master = Sim::getCurrentTime() % masterList->size(); // Send a request to the master server for the game types: Con::printf( "Requesting game types from the master server..." ); sendPacket( NetInterface::MasterServerGameTypesRequest, &(*masterList)[master].address, gKey, gPingSession, 0 ); } } //----------------------------------------------------------------------------- void queryMasterServer(U8 flags, const char* gameType, const char* missionType, U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU, U8 filterFlags, U8 buddyCount, U32* buddyList ) { // Reset the list packet flag: gGotFirstListPacket = false; sgServerQueryActive = true; clearServerList(); Con::executef( 4, "onServerQueryStatus", "start", "Querying master server", "0"); if ( buddyCount == 0 ) { sActiveFilter.type = ServerFilter::Normal; // Update the active filter: if ( !sActiveFilter.gameType || dStrcmp( sActiveFilter.gameType, gameType ) != 0 ) { sActiveFilter.gameType = (char*) dRealloc( sActiveFilter.gameType, dStrlen( gameType ) + 1 ); dStrcpy( sActiveFilter.gameType, gameType ); } if ( !sActiveFilter.missionType || dStrcmp( sActiveFilter.missionType, missionType ) != 0 ) { sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, dStrlen( missionType ) + 1 ); dStrcpy( sActiveFilter.missionType, missionType ); } sActiveFilter.queryFlags = flags; sActiveFilter.minPlayers = minPlayers; sActiveFilter.maxPlayers = maxPlayers; sActiveFilter.maxBots = maxBots; sActiveFilter.regionMask = regionMask; sActiveFilter.maxPing = maxPing; sActiveFilter.minCPU = minCPU; sActiveFilter.filterFlags = filterFlags; sActiveFilter.buddyCount = buddyCount; dFree( sActiveFilter.buddyList ); sActiveFilter.buddyList = NULL; } else { sActiveFilter.type = ServerFilter::Buddy; sActiveFilter.buddyCount = buddyCount; sActiveFilter.buddyList = (U32*) dRealloc( sActiveFilter.buddyList, buddyCount * 4 ); dMemcpy( sActiveFilter.buddyList, buddyList, buddyCount * 4 ); clearServerList(); } // Pick a random master server from the list: gMasterServerList.clear(); Vector *masterList = getMasterServerList(); for ( U32 i = 0; i < masterList->size(); i++ ) gMasterServerList.push_back( (*masterList)[i] ); // Clear the master server ping: gMasterServerPing.time = 0; gMasterServerPing.tryCount = gMasterServerRetryCount; if ( !pickMasterServer() ) Con::errorf( "No master servers found!" ); else processMasterServerQuery( gPingSession ); } ConsoleFunction( queryMasterServer, void, 11, 11, "queryMasterServer(...);" ) { argc; U8 flags = dAtoi(argv[1]); // It's not a good idea to hold onto args, recursive calls to // console exec will trash them. char* gameType = dStrdup(argv[2]); char* missionType = dStrdup(argv[3]); U8 minPlayers = dAtoi(argv[4]); U8 maxPlayers = dAtoi(argv[5]); U8 maxBots = dAtoi(argv[6]); U32 regionMask = dAtoi(argv[7]); U32 maxPing = dAtoi(argv[8]); U16 minCPU = dAtoi(argv[9]); U8 filterFlags = dAtoi(argv[10]); U8 buddyCount = 0; U32 buddyList = 0; queryMasterServer(flags,gameType,missionType,minPlayers,maxPlayers, maxBots,regionMask,maxPing,minCPU,filterFlags,0,&buddyList); dFree(gameType); dFree(missionType); } //----------------------------------------------------------------------------- ConsoleFunction( querySingleServer, void, 3, 3, "querySingleServer(address, flags);" ) { argc; NetAddress addr; char* addrText; addrText = dStrdup(argv[1]); U8 flags = dAtoi(argv[2]); Net::stringToAddress( addrText, &addr ); querySingleServer(&addr,flags); } //----------------------------------------------------------------------------- void queryFavoriteServers( U8 /*flags*/ ) { sgServerQueryActive = true; clearServerList(); sActiveFilter.type = ServerFilter::Favorites; pushServerFavorites(); Con::executef( 4, "onServerQueryStatus", "start", "Query favorites...", "0" ); processPingsAndQueries( gPingSession ); } //----------------------------------------------------------------------------- void querySingleServer( const NetAddress* addr, U8 /*flags*/ ) { sgServerQueryActive = true; ServerInfo* si = findServerInfo( addr ); if ( si ) si->status = ServerInfo::Status_New | ServerInfo::Status_Updating; // Remove the server from the finished list (if it's there): for ( U32 i = 0; i < gFinishedList.size(); i++ ) { if ( Net::compareAddresses( addr, &gFinishedList[i] ) ) { gFinishedList.erase( i ); break; } } Con::executef( 4, "onServerQueryStatus", "start", "Refreshing server...", "0" ); gServerPingCount = gServerQueryCount = 0; pushPingRequest( addr ); processPingsAndQueries( gPingSession ); } //----------------------------------------------------------------------------- void cancelServerQuery() { // Cancel the current query, if there is anything left // on the ping list, it's dropped. if ( sgServerQueryActive ) { Con::printf( "Server query canceled." ); ServerInfo* si; // Clear the master server packet list: gPacketStatusList.clear(); // Clear the ping list: while ( gPingList.size() ) { si = findServerInfo( &gPingList[0].address ); if ( si && !si->status.test( ServerInfo::Status_Responded ) ) si->status = ServerInfo::Status_TimedOut; gPingList.erase( U32( 0 ) ); } // Clear the query list: while ( gQueryList.size() ) { si = findServerInfo( &gQueryList[0].address ); if ( si && !si->status.test( ServerInfo::Status_Responded ) ) si->status = ServerInfo::Status_TimedOut; gQueryList.erase( U32( 0 ) ); } sgServerQueryActive = false; gServerBrowserDirty = true; } } ConsoleFunction( cancelServerQuery, void, 1, 1, "cancelServerQuery()" ) { argc; argv; cancelServerQuery(); } //----------------------------------------------------------------------------- void stopServerQuery() { // Cancel the current query, anything left on the ping // list is moved to the finished list as "done". if ( sgServerQueryActive ) { gPacketStatusList.clear(); if ( gPingList.size() ) { while ( gPingList.size() ) { gFinishedList.push_back( gPingList[0].address ); gPingList.erase( U32( 0 ) ); } } else cancelServerQuery(); } } ConsoleFunction( stopServerQuery, void, 1, 1, "stopServerQuery()" ) { argc; argv; stopServerQuery(); } //----------------------------------------------------------------------------- ConsoleFunction(startHeartbeat, void, 1, 1, "startHeartbeat()") { argc; argv; if (validateAuthenticatedServer()) { gHeartbeatSeq++; processHeartbeat(gHeartbeatSeq); // thump-thump... } } ConsoleFunction(stopHeartbeat, void, 1, 1, "stopHeartbeat();") { argc; argv; gHeartbeatSeq++; } //----------------------------------------------------------------------------- ConsoleFunction( getServerCount, int, 1, 1, "getServerCount();" ) { argv,argc; return gServerList.size(); } ConsoleFunction( setServerInfo, bool, 2, 2, "setServerInfo(index);" ) { argc; U32 index = dAtoi(argv[1]); if (index >= 0 && index < gServerList.size()) { ServerInfo& info = gServerList[index]; char addrString[256]; Net::addressToString( &info.address, addrString ); Con::setIntVariable("ServerInfo::Status",info.status); Con::setVariable("ServerInfo::Address",addrString); Con::setVariable("ServerInfo::Name",info.name); Con::setVariable("ServerInfo::GameType",info.gameType); Con::setVariable("ServerInfo::MissionName",info.missionName); Con::setVariable("ServerInfo::MissionType",info.missionType); Con::setVariable("ServerInfo::State",info.statusString); Con::setVariable("ServerInfo::Info",info.infoString); Con::setIntVariable("ServerInfo::PlayerCount",info.numPlayers); Con::setIntVariable("ServerInfo::MaxPlayers",info.maxPlayers); Con::setIntVariable("ServerInfo::BotCount",info.numBots); Con::setIntVariable("ServerInfo::Version",info.version); Con::setIntVariable("ServerInfo::Ping",info.ping); Con::setIntVariable("ServerInfo::CPUSpeed",info.cpuSpeed); Con::setBoolVariable("ServerInfo::Favorite",info.isFavorite); Con::setBoolVariable("ServerInfo::Dedicated",info.isDedicated()); Con::setBoolVariable("ServerInfo::Password",info.isPassworded()); return true; } return false; } //----------------------------------------------------------------------------- // Internal //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- ServerInfo::~ServerInfo() { if ( name ) dFree( name ); if ( gameType ) dFree( gameType ); if ( missionType ) dFree( missionType ); if ( statusString ) dFree( statusString ); if ( infoString ) dFree( infoString ); } //----------------------------------------------------------------------------- Vector* getMasterServerList() { // This code used to get the master server list from the // WON.net directory. static Vector masterList; masterList.clear(); for (U32 i = 0; i < 10; i++) { char buffer[50]; dSprintf(buffer,sizeof(buffer),"Pref::Master%d",i); const char* master = Con::getVariable(buffer); if (master && *master) { NetAddress address; // Format for master server variable: // regionMask:netAddress U32 region = 1; // needs to default to something > 0 dSscanf(master,"%d:",®ion); const char* madd = dStrchr(master,':') + 1; if (region && Net::stringToAddress(madd,&address)) { masterList.increment(); MasterInfo& info = masterList.last(); info.address = address; info.region = region; } else Con::errorf("Bad master server address: %s",master); } } if (!masterList.size()) Con::errorf("No master servers found"); return &masterList; } //----------------------------------------------------------------------------- bool pickMasterServer() { // Reset the master server ping: gMasterServerPing.time = 0; gMasterServerPing.key = 0; gMasterServerPing.tryCount = gMasterServerRetryCount; gMasterServerPing.session = gPingSession; char addrString[256]; const char* regionString = NULL; U32 serverCount = gMasterServerList.size(); if ( !serverCount ) { // There are no more servers left to try...:( return( false ); } U32 region = Con::getIntVariable( "$pref::Net::RegionMask" ); U32 index = Sim::getCurrentTime() % serverCount; // First try to find a master server in the same region: for ( U32 i = 0; i < serverCount; i++ ) { if ( gMasterServerList[index].region == region ) { Net::addressToString( &gMasterServerList[index].address, addrString ); Con::printf( "Found master server %s in same region.", addrString ); gMasterServerPing.address = gMasterServerList[index].address; return( true ); } index = index < serverCount - 1 ? index + 1 : 0; } // Settle for the one we first picked: Net::addressToString( &gMasterServerList[index].address, addrString ); Con::printf( "No master servers found in this region, trying %s.", addrString ); gMasterServerPing.address = gMasterServerList[index].address; return( true ); } //----------------------------------------------------------------------------- void clearServerList() { gPacketStatusList.clear(); gServerList.clear(); gFinishedList.clear(); gPingList.clear(); gQueryList.clear(); gServerPingCount = gServerQueryCount = 0; gPingSession++; } //----------------------------------------------------------------------------- void sendHeartbeat( U8 flags ) { // send heartbeats to all of the master servers: Vector *masterList = getMasterServerList(); for(U32 i = 0; i < masterList->size(); i++) { char buffer[256]; Net::addressToString(&(*masterList)[i].address, buffer); // Send a request to the master server for the game types: Con::printf( "Sending heartbeat to master server [%s]", buffer ); sendPacket( NetInterface::GameHeartbeat, &(*masterList)[i].address, 0, gPingSession, flags ); } } //----------------------------------------------------------------------------- static void pushPingRequest( const NetAddress* addr ) { if( addressFinished( addr ) ) return; Ping p; p.address = *addr; p.session = gPingSession; p.key = 0; p.time = 0; p.tryCount = gPingRetryCount; p.broadcast = false; gPingList.push_back( p ); gServerPingCount++; } //----------------------------------------------------------------------------- static void pushPingBroadcast( const NetAddress* addr ) { if( addressFinished( addr ) ) return; Ping p; p.address = *addr; p.session = gPingSession; p.key = 0; p.time = 0; p.tryCount = 1; // only try this once p.broadcast = true; gPingList.push_back( p ); // Don't increment gServerPingCount, broadcasts are not // counted as requests. } //----------------------------------------------------------------------------- static S32 countPingRequests() { // Need a function here because the ping list also includes // broadcast pings we don't want counted. U32 pSize = gPingList.size(), count = pSize; for (U32 i = 0; i < pSize; i++) if (gPingList[i].broadcast) count--; return count; } //----------------------------------------------------------------------------- static void pushServerFavorites() { S32 count = Con::getIntVariable( "$pref::Client::ServerFavoriteCount" ); if ( count < 0 ) { Con::setIntVariable( "$pref::Client::ServerFavoriteCount", 0 ); return; } NetAddress addr; const char* server = NULL; char buf[256], serverName[25], addrString[256]; U32 sz, len; for ( S32 i = 0; i < count; i++ ) { dSprintf( buf, sizeof( buf ), "Pref::Client::ServerFavorite%d", i ); server = Con::getVariable( buf ); if ( server ) { sz = dStrcspn( server, "\t" ); if ( sz > 0 ) { len = sz > 24 ? 24 : sz; dStrncpy( serverName, server, len ); serverName[len] = 0; dStrncpy( addrString, server + ( sz + 1 ), 255 ); //Con::errorf( "Pushing server favorite \"%s\" - %s...", serverName, addrString ); Net::stringToAddress( addrString, &addr ); ServerInfo* si = findOrCreateServerInfo( &addr ); AssertFatal(si, "pushServerFavorites - failed to create Server Info!" ); si->name = (char*) dRealloc( (void*) si->name, dStrlen( serverName ) + 1 ); dStrcpy( si->name, serverName ); si->isFavorite = true; pushPingRequest( &addr ); } } } } //----------------------------------------------------------------------------- static S32 findPingEntry( Vector &v, const NetAddress* addr ) { for ( U32 i = 0; i < v.size(); i++ ) if ( Net::compareAddresses( addr, &v[i].address ) ) return S32( i ); return -1; } //----------------------------------------------------------------------------- static bool addressFinished( const NetAddress* addr ) { for ( U32 i = 0; i < gFinishedList.size(); i++ ) if ( Net::compareAddresses( addr, &gFinishedList[i] ) ) return true; return false; } //----------------------------------------------------------------------------- static ServerInfo* findServerInfo( const NetAddress* addr ) { for ( U32 i = 0; i < gServerList.size(); i++ ) if ( Net::compareAddresses( addr, &gServerList[i].address ) ) return &gServerList[i]; return NULL; } //----------------------------------------------------------------------------- static ServerInfo* findOrCreateServerInfo( const NetAddress* addr ) { ServerInfo* ret = findServerInfo( addr ); if ( ret ) return ret; ServerInfo si; si.address = *addr; gServerList.push_back( si ); return &gServerList.last(); } //----------------------------------------------------------------------------- static void removeServerInfo( const NetAddress* addr ) { for ( U32 i = 0; i < gServerList.size(); i++ ) { if ( Net::compareAddresses( addr, &gServerList[i].address ) ) { gServerList.erase( i ); gServerBrowserDirty = true; } } } //----------------------------------------------------------------------------- #if defined(TORQUE_DEBUG) // This function is solely for testing the functionality of the server browser // with more servers in the list. void addFakeServers( S32 howMany ) { static S32 sNumFakeServers = 1; ServerInfo newServer; for ( S32 i = 0; i < howMany; i++ ) { newServer.numPlayers = Platform::getRandom() * 64; newServer.maxPlayers = 64; char buf[256]; dSprintf( buf, 255, "Fake server #%d", sNumFakeServers ); newServer.name = (char*) dMalloc( dStrlen( buf ) + 1 ); dStrcpy( newServer.name, buf ); newServer.gameType = (char*) dMalloc( 5 ); dStrcpy( newServer.gameType, "Fake" ); newServer.missionType = (char*) dMalloc( 4 ); dStrcpy( newServer.missionType, "FakeMissionType" ); newServer.missionName = (char*) dMalloc( 14 ); dStrcpy( newServer.missionName, "FakeMapName" ); Net::stringToAddress( "IP:198.74.33.35:28000", &newServer.address ); newServer.ping = ( Platform::getRandom() * 200 ); newServer.cpuSpeed = 470; newServer.status = ServerInfo::Status_Responded; gServerList.push_back( newServer ); sNumFakeServers++; } gServerBrowserDirty = true; } #endif // DEBUG //----------------------------------------------------------------------------- static void sendPacket( U8 pType, const NetAddress* addr, U32 key, U32 session, U8 flags ) { BitStream *out = BitStream::getPacketStream(); out->write( pType ); out->write( flags ); out->write( U32( ( session << 16 ) | ( key & 0xFFFF ) ) ); BitStream::sendPacketStream(addr); } //----------------------------------------------------------------------------- static void writeCString( BitStream* stream, const char* string ) { U8 strLen = ( string != NULL ) ? dStrlen( string ) : 0; stream->write( strLen ); for ( U32 i = 0; i < strLen; i++ ) stream->write( U8( string[i] ) ); } //----------------------------------------------------------------------------- static void readCString( BitStream* stream, char* buffer ) { U32 i; U8 strLen; stream->read( &strLen ); for ( i = 0; i < strLen; i++ ) { U8* ptr = (U8*) buffer; stream->read( &ptr[i] ); } buffer[i] = 0; } //----------------------------------------------------------------------------- static void writeLongCString( BitStream* stream, const char* string ) { U16 strLen = ( string != NULL ) ? dStrlen( string ) : 0; stream->write( strLen ); for ( U32 i = 0; i < strLen; i++ ) stream->write( U8( string[i] ) ); } //----------------------------------------------------------------------------- static void readLongCString( BitStream* stream, char* buffer ) { U32 i; U16 strLen; stream->read( &strLen ); for ( i = 0; i < strLen; i++ ) { U8* ptr = (U8*) buffer; stream->read( &ptr[i] ); } buffer[i] = 0; } //----------------------------------------------------------------------------- // Event processing //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- static void processMasterServerQuery( U32 session ) { if ( session != gPingSession || !sgServerQueryActive ) return; if ( !gGotFirstListPacket ) { bool keepGoing = true; U32 time = Platform::getVirtualMilliseconds(); char addressString[256]; if ( gMasterServerPing.time + gMasterServerTimeout < time ) { Net::addressToString( &gMasterServerPing.address, addressString ); if ( !gMasterServerPing.tryCount ) { // The query timed out. Con::printf( "Server list request to %s timed out.", addressString ); // Remove this server from the list: for ( U32 i = 0; i < gMasterServerList.size(); i++ ) { if ( Net::compareAddresses( &gMasterServerList[i].address, &gMasterServerPing.address ) ) { gMasterServerList.erase( i ); break; } } // Pick a new master server to try: keepGoing = pickMasterServer(); if ( keepGoing ) { Con::executef( 4, "onServerQueryStatus", "update", "Switching master servers...", "0" ); Net::addressToString( &gMasterServerPing.address, addressString ); } } if ( keepGoing ) { gMasterServerPing.tryCount--; gMasterServerPing.time = time; gMasterServerPing.key = gKey++; // Send a request to the master server for the server list: BitStream *out = BitStream::getPacketStream(); out->write( U8( NetInterface::MasterServerListRequest ) ); out->write( U8( sActiveFilter.queryFlags) ); out->write( ( gMasterServerPing.session << 16 ) | ( gMasterServerPing.key & 0xFFFF ) ); out->write( U8( 255 ) ); writeCString( out, sActiveFilter.gameType ); writeCString( out, sActiveFilter.missionType ); out->write( sActiveFilter.minPlayers ); out->write( sActiveFilter.maxPlayers ); out->write( sActiveFilter.regionMask ); U32 version = ( sActiveFilter.filterFlags & ServerFilter::CurrentVersion ) ? getVersionNumber() : 0; out->write( version ); out->write( sActiveFilter.filterFlags ); out->write( sActiveFilter.maxBots ); out->write( sActiveFilter.minCPU ); out->write( sActiveFilter.buddyCount ); for ( U32 i = 0; i < sActiveFilter.buddyCount; i++ ) out->write( sActiveFilter.buddyList[i] ); BitStream::sendPacketStream( &gMasterServerPing.address ); Con::printf( "Requesting the server list from master server %s (%d tries left)...", addressString, gMasterServerPing.tryCount ); if ( gMasterServerPing.tryCount < gMasterServerRetryCount - 1 ) Con::executef( 4, "onServerQueryStatus", "update", "Retrying the master server...", "0" ); } } if ( keepGoing ) { // schedule another check: Sim::postEvent( Sim::getRootGroup(), new ProcessMasterQueryEvent( session ), Sim::getTargetTime() + 1 ); } else { Con::errorf( "There are no more master servers to try!" ); Con::executef( 4, "onServerQueryStatus", "done", "No master servers found.", "0" ); } } } //----------------------------------------------------------------------------- static void processPingsAndQueries( U32 session, bool schedule ) { if( session != gPingSession ) return; U32 i = 0; U32 activePings = 0; U32 time = Platform::getVirtualMilliseconds(); char addressString[256]; U8 flags = ServerFilter::OnlineQuery; bool waitingForMaster = ( sActiveFilter.type == ServerFilter::Normal ) && !gGotFirstListPacket && sgServerQueryActive; for ( i = 0; i < gPingList.size() && i < gMaxConcurrentPings; ) { Ping &p = gPingList[i]; if ( p.time + gPingTimeout < time ) { if ( !p.tryCount ) { // it's timed out. if (!p.broadcast) { Net::addressToString( &p.address, addressString ); Con::printf( "Ping to server %s timed out.", addressString ); } // If server info is in list (favorite), set its status: ServerInfo* si = findServerInfo( &p.address ); if ( si ) { si->status = ServerInfo::Status_TimedOut; gServerBrowserDirty = true; } gFinishedList.push_back( p.address ); gPingList.erase( i ); if ( !waitingForMaster ) updatePingProgress(); } else { p.tryCount--; p.time = time; p.key = gKey++; Net::addressToString( &p.address, addressString ); if (p.broadcast) Con::printf( "LAN server ping: %s...", addressString ); else Con::printf( "Pinging Server %s (%d)...", addressString, p.tryCount ); sendPacket( NetInterface::GamePingRequest, &p.address, p.key, p.session, flags ); i++; } } else i++; } if ( !gPingList.size() && !waitingForMaster ) { // Start the query phase: for ( U32 i = 0; i < gQueryList.size() && i < gMaxConcurrentQueries; ) { Ping &p = gQueryList[i]; if ( p.time + gPingTimeout < time ) { ServerInfo* si = findServerInfo( &p.address ); if ( !si ) { // Server info not found, so remove the query: gQueryList.erase( i ); gServerBrowserDirty = true; continue; } Net::addressToString( &p.address, addressString ); if ( !p.tryCount ) { Con::printf( "Query to server %s timed out.", addressString ); si->status = ServerInfo::Status_TimedOut; gQueryList.erase( i ); gServerBrowserDirty = true; } else { p.tryCount--; p.time = time; p.key = gKey++; Con::printf( "Querying Server %s (%d)...", addressString, p.tryCount ); sendPacket( NetInterface::GameInfoRequest, &p.address, p.key, p.session, flags ); if ( !si->isQuerying() ) { si->status |= ServerInfo::Status_Querying; gServerBrowserDirty = true; } i++; } } else i++; } } if ( gPingList.size() || gQueryList.size() || waitingForMaster ) { // The LAN query function doesn't always want to schedule // the next ping. if (schedule) Sim::postEvent( Sim::getRootGroup(), new ProcessPingEvent( session ), Sim::getTargetTime() + 1 ); } else { // All done! char msg[64]; U32 foundCount = gServerList.size(); if ( foundCount == 0 ) dStrcpy( msg, "No servers found." ); else if ( foundCount == 1 ) dStrcpy( msg, "One server found." ); else dSprintf( msg, sizeof( msg ), "%d servers found.", foundCount ); Con::executef( 4, "onServerQueryStatus", "done", msg, "1"); } } //----------------------------------------------------------------------------- static void processServerListPackets( U32 session ) { if ( session != gPingSession || !sgServerQueryActive ) return; U32 currentTime = Platform::getVirtualMilliseconds(); // Loop through the packet status list and resend packet requests where necessary: for ( U32 i = 0; i < gPacketStatusList.size(); i++ ) { PacketStatus &p = gPacketStatusList[i]; if ( p.time + gPacketTimeout < currentTime ) { if ( !p.tryCount ) { // Packet timed out :( Con::printf( "Server list packet #%d timed out.", p.index + 1 ); gPacketStatusList.erase( i ); } else { // Try again... Con::printf( "Rerequesting server list packet #%d...", p.index + 1 ); p.tryCount--; p.time = currentTime; p.key = gKey++; BitStream *out = BitStream::getPacketStream(); out->write( U8( NetInterface::MasterServerListRequest ) ); out->write( U8( sActiveFilter.queryFlags ) ); // flags out->write( ( session << 16) | ( p.key & 0xFFFF ) ); out->write( p.index ); // packet index out->write( U8( 0 ) ); // game type out->write( U8( 0 ) ); // mission type out->write( U8( 0 ) ); // minPlayers out->write( U8( 0 ) ); // maxPlayers out->write( U32( 0 ) ); // region mask out->write( U32( 0 ) ); // version out->write( U8( 0 ) ); // filter flags out->write( U8( 0 ) ); // max bots out->write( U16( 0 ) ); // min CPU out->write( U8( 0 ) ); // buddy count BitStream::sendPacketStream(&gMasterServerQueryAddress); } } } if ( gPacketStatusList.size() ) Sim::postEvent( Sim::getRootGroup(), new ProcessPacketEvent( session ), Sim::getCurrentTime() + 30 ); else processPingsAndQueries( gPingSession ); } //----------------------------------------------------------------------------- static void processHeartbeat(U32 seq) { if(seq != gHeartbeatSeq) return; sendHeartbeat( 0 ); Sim::postEvent( Sim::getRootGroup(), new HeartbeatEvent(seq), Sim::getCurrentTime() + gHeartbeatInterval ); } //----------------------------------------------------------------------------- static void updatePingProgress() { if ( !gPingList.size() ) { updateQueryProgress(); return; } char msg[64]; U32 pingsLeft = countPingRequests(); dSprintf( msg, sizeof(msg), (!pingsLeft && gPingList.size())? "Waiting for lan servers...": "Pinging servers: %d left...", pingsLeft ); // Ping progress is 0 -> 0.5 F32 progress = 0.0f; if ( gServerPingCount ) progress = F32( gServerPingCount - pingsLeft ) / F32( gServerPingCount * 2 ); //Con::errorf( ConsoleLogEntry::General, "Ping progress - %d of %d left - progress = %.2f", pingsLeft, gServerPingCount, progress ); Con::executef( 4, "onServerQueryStatus", "ping", msg, Con::getFloatArg( progress ) ); } //----------------------------------------------------------------------------- static void updateQueryProgress() { if ( gPingList.size() ) return; char msg[64]; U32 queriesLeft = gQueryList.size(); dSprintf( msg, sizeof( msg ), "Querying servers: %d left...", queriesLeft ); // Query progress is 0.5 -> 1 F32 progress = 0.5f; if ( gServerQueryCount ) progress += ( F32( gServerQueryCount - queriesLeft ) / F32( gServerQueryCount * 2 ) ); //Con::errorf( ConsoleLogEntry::General, "Query progress - %d of %d left - progress = %.2f", queriesLeft, gServerQueryCount, progress ); Con::executef( 4, "onServerQueryStatus", "query", msg, Con::getFloatArg( progress ) ); } //----------------------------------------------------------------------------- // Server packet handlers: //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- static void handleMasterServerGameTypesResponse( BitStream* stream, U32 /*key*/, U8 /*flags*/ ) { Con::printf( "Received game type list from the master server." ); U32 i; U8 temp; char stringBuf[256]; stream->read( &temp ); Con::executef(1, "onClearGameTypes"); for ( i = 0; i < U32( temp ); i++ ) { readCString( stream, stringBuf ); Con::executef(2, "onAddGameType", stringBuf); } stream->read( &temp ); Con::executef(1, "onClearMissionTypes"); for ( i = 0; i < U32( temp ); i++ ) { readCString( stream, stringBuf ); Con::executef(2, "onAddMissionType", stringBuf); } } //----------------------------------------------------------------------------- static void handleMasterServerListResponse( BitStream* stream, U32 key, U8 /*flags*/ ) { U8 packetIndex, packetTotal; U32 i; U16 serverCount, port; U8 netNum[4]; char addressBuffer[256]; NetAddress addr; stream->read( &packetIndex ); // Validate the packet key: U32 packetKey = gMasterServerPing.key; if ( gGotFirstListPacket ) { for ( i = 0; i < gPacketStatusList.size(); i++ ) { if ( gPacketStatusList[i].index == packetIndex ) { packetKey = gPacketStatusList[i].key; break; } } } U32 testKey = ( gPingSession << 16 ) | ( packetKey & 0xFFFF ); if ( testKey != key ) return; stream->read( &packetTotal ); stream->read( &serverCount ); Con::printf( "Received server list packet %d of %d from the master server (%d servers).", ( packetIndex + 1 ), packetTotal, serverCount ); // Enter all of the servers in this packet into the ping list: for ( i = 0; i < serverCount; i++ ) { stream->read( &netNum[0] ); stream->read( &netNum[1] ); stream->read( &netNum[2] ); stream->read( &netNum[3] ); stream->read( &port ); dSprintf( addressBuffer, sizeof( addressBuffer ), "IP:%d.%d.%d.%d:%d", netNum[0], netNum[1], netNum[2], netNum[3], port ); Net::stringToAddress( addressBuffer, &addr ); pushPingRequest( &addr ); } // If this is the first list packet we have received, fill the packet status list // and start processing: if ( !gGotFirstListPacket ) { gGotFirstListPacket = true; gMasterServerQueryAddress = gMasterServerPing.address; U32 currentTime = Platform::getVirtualMilliseconds(); for ( i = 0; i < packetTotal; i++ ) { if ( i != packetIndex ) { PacketStatus* p = new PacketStatus( i, gMasterServerPing.key, currentTime ); gPacketStatusList.push_back( *p ); } } processServerListPackets( gPingSession ); } else { // Remove the packet we just received from the status list: for ( i = 0; i < gPacketStatusList.size(); i++ ) { if ( gPacketStatusList[i].index == packetIndex ) { gPacketStatusList.erase( i ); break; } } } } //----------------------------------------------------------------------------- static void handleGameMasterInfoRequest( const NetAddress* address, U32 key, U8 flags ) { if ( GNet->doesAllowConnections() ) { U8 temp8; U32 temp32; char netString[256]; Net::addressToString(address, netString); Vector *masterList = getMasterServerList(); const NetAddress *masterAddr; bool fromMaster = false; for(U32 i = 0; i < masterList->size(); i++) { masterAddr = &(*masterList)[i].address; if (*(U32*)(masterAddr->netNum) == *(U32*)(address->netNum)) { fromMaster = true; break; } } Con::printf( "Received info request from %s [%s].", fromMaster?"a master server":"a machine", netString); BitStream *out = BitStream::getPacketStream(); out->write( U8( NetInterface::GameMasterInfoResponse ) ); out->write( U8( flags ) ); out->write( key ); writeCString( out, Con::getVariable( "Server::GameType" ) ); writeCString( out, Con::getVariable( "Server::MissionType" ) ); temp8 = U8( Con::getIntVariable( "Pref::Server::MaxPlayers" ) ); out->write( temp8 ); temp32 = Con::getIntVariable( "Pref::Server::RegionMask" ); out->write( temp32 ); temp32 = getVersionNumber(); out->write( temp32 ); temp8 = 0; #if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD) temp8 |= ServerInfo::Status_Linux; #endif if ( Con::getBoolVariable( "Server::Dedicated" ) ) temp8 |= ServerInfo::Status_Dedicated; if ( dStrlen( Con::getVariable( "Pref::Server::Password" ) ) > 0 ) temp8 |= ServerInfo::Status_Passworded; out->write( temp8 ); temp8 = U8( Con::getIntVariable( "Server::BotCount" ) ); out->write( temp8 ); out->write( Platform::SystemInfo.processor.mhz ); U8 playerCount = U8( Con::getIntVariable( "Server::PlayerCount" ) ); out->write( playerCount ); const char* guidList = Con::getVariable( "Server::GuidList" ); char* buf = new char[dStrlen( guidList ) + 1]; dStrcpy( buf, guidList ); char* temp = dStrtok( buf, "\t" ); temp8 = 0; for ( ; temp && temp8 < playerCount; temp8++ ) { out->write( U32( dAtoi( temp ) ) ); temp = dStrtok( NULL, "\t" ); temp8++; } for ( ; temp8 < playerCount; temp8++ ) out->write( U32( 0 ) ); delete [] buf; BitStream::sendPacketStream(address); } } //----------------------------------------------------------------------------- static void handleGamePingRequest( const NetAddress* address, U32 key, U8 flags ) { // Do not respond if a mission is not running: if ( GNet->doesAllowConnections() ) { // Do not respond if this is a single-player game: if ( dStricmp( Con::getVariable( "Server::ServerType" ), "SinglePlayer" ) == 0 ) return; // Do not respond to offline queries if this is an online server: if ( flags & ServerFilter::OfflineQuery ) return; // some banning code here (?) BitStream *out = BitStream::getPacketStream(); out->write( U8( NetInterface::GamePingResponse ) ); out->write( flags ); out->write( key ); if ( flags & ServerFilter::NoStringCompress ) writeCString( out, versionString ); else out->writeString( versionString ); out->write( GameConnection::CurrentProtocolVersion ); out->write( GameConnection::MinRequiredProtocolVersion ); out->write( getVersionNumber() ); // Enforce a 24-character limit on the server name: char serverName[25]; dStrncpy( serverName, Con::getVariable( "Pref::Server::Name" ), 24 ); serverName[24] = 0; if ( flags & ServerFilter::NoStringCompress ) writeCString( out, serverName ); else out->writeString( serverName ); BitStream::sendPacketStream(address); } } //----------------------------------------------------------------------------- static void handleGamePingResponse( const NetAddress* address, BitStream* stream, U32 key, U8 /*flags*/ ) { // Broadcast has timed out or query has been cancelled: if( !gPingList.size() ) return; S32 index = findPingEntry( gPingList, address ); if( index == -1 ) { // an anonymous ping response - if it's not already timed // out or finished, ping it. Probably from a broadcast if( !addressFinished( address ) ) pushPingRequest( address ); return; } Ping &p = gPingList[index]; U32 infoKey = ( p.session << 16 ) | ( p.key & 0xFFFF ); if( infoKey != key ) return; // Find if the server info already exists (favorite or refreshing): ServerInfo* si = findServerInfo( address ); bool applyFilter = false; if ( sActiveFilter.type == ServerFilter::Normal ) applyFilter = si ? !si->isUpdating() : true; char addrString[256]; Net::addressToString( address, addrString ); bool waitingForMaster = ( sActiveFilter.type == ServerFilter::Normal ) && !gGotFirstListPacket; // Verify the version: char buf[256]; stream->readString( buf ); if ( dStrcmp( buf, versionString ) != 0 ) { // Version is different, so remove it from consideration: Con::printf( "Server %s is a different version.", addrString ); gFinishedList.push_back( *address ); gPingList.erase( index ); if ( si ) { si->status = ServerInfo::Status_TimedOut; gServerBrowserDirty = true; } if ( !waitingForMaster ) updatePingProgress(); return; } // See if the server meets our minimum protocol: U32 temp32; stream->read( &temp32 ); if ( temp32 < GameConnection::MinRequiredProtocolVersion ) { Con::printf( "Protocol for server %s does not meet minimum protocol.", addrString ); gFinishedList.push_back( *address ); gPingList.erase( index ); if ( si ) { si->status = ServerInfo::Status_TimedOut; gServerBrowserDirty = true; } if ( !waitingForMaster ) updatePingProgress(); return; } // See if we meet the server's minimum protocol: stream->read( &temp32 ); if ( GameConnection::CurrentProtocolVersion < temp32 ) { Con::printf( "You do not meet the minimum protocol for server %s.", addrString ); gFinishedList.push_back( *address ); gPingList.erase( index ); if ( si ) { si->status = ServerInfo::Status_TimedOut; gServerBrowserDirty = true; } if ( !waitingForMaster ) updatePingProgress(); return; } // Calculate the ping: U32 time = Platform::getVirtualMilliseconds(); U32 ping = ( time > p.time ) ? time - p.time : 0; // Check for max ping filter: if ( applyFilter && sActiveFilter.maxPing > 0 && ping > sActiveFilter.maxPing ) { // Ping is too high, so remove this server from consideration: Con::printf( "Server %s filtered out by maximum ping.", addrString ); gFinishedList.push_back( *address ); gPingList.erase( index ); if ( si ) removeServerInfo( address ); if ( !waitingForMaster ) updatePingProgress(); return; } // Get the server build version: stream->read( &temp32 ); if ( applyFilter && ( sActiveFilter.filterFlags & ServerFilter::CurrentVersion ) && ( temp32 != getVersionNumber() ) ) { Con::printf( "Server %s filtered out by version number.", addrString ); gFinishedList.push_back( *address ); gPingList.erase( index ); if ( si ) removeServerInfo( address ); if ( !waitingForMaster ) updatePingProgress(); return; } // OK, we can finally create the server info structure: if ( !si ) si = findOrCreateServerInfo( address ); si->ping = ping; si->version = temp32; // Get the server name: stream->readString( buf ); if ( !si->name ) { si->name = (char*) dMalloc( dStrlen( buf ) + 1 ); dStrcpy( si->name, buf ); } // Set the server up to be queried: gFinishedList.push_back( *address ); p.key = 0; p.time = 0; p.tryCount = gQueryRetryCount; gQueryList.push_back( p ); gServerQueryCount++; gPingList.erase( index ); if ( !waitingForMaster ) updatePingProgress(); // Update the server browser gui! gServerBrowserDirty = true; } //----------------------------------------------------------------------------- static void handleGameInfoRequest( const NetAddress* address, U32 key, U8 flags ) { // Do not respond unless there is a server running: if ( GNet->doesAllowConnections() ) { // Do not respond to offline queries if this is an online server: if ( flags & ServerFilter::OfflineQuery ) return; bool compressStrings = !( flags & ServerFilter::NoStringCompress ); BitStream *out = BitStream::getPacketStream(); out->write( U8( NetInterface::GameInfoResponse ) ); out->write( flags ); out->write( key ); if ( compressStrings ) { out->writeString( Con::getVariable( "Server::GameType" ) ); out->writeString( Con::getVariable( "Server::MissionType" ) ); out->writeString( Con::getVariable( "Server::MissionName" ) ); } else { writeCString( out, Con::getVariable( "Server::GameType" ) ); writeCString( out, Con::getVariable( "Server::MissionType" ) ); writeCString( out, Con::getVariable( "Server::MissionName" ) ); } U8 status = 0; #if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD) status |= ServerInfo::Status_Linux; #endif if ( Con::getBoolVariable( "Server::Dedicated" ) ) status |= ServerInfo::Status_Dedicated; if ( dStrlen( Con::getVariable( "Pref::Server::Password" ) ) ) status |= ServerInfo::Status_Passworded; out->write( status ); out->write( U8( Con::getIntVariable( "Server::PlayerCount" ) ) ); out->write( U8( Con::getIntVariable( "Pref::Server::MaxPlayers" ) ) ); out->write( U8( Con::getIntVariable( "Server::BotCount" ) ) ); out->write( U16( Platform::SystemInfo.processor.mhz ) ); if ( compressStrings ) out->writeString( Con::getVariable( "Pref::Server::Info" ) ); else writeCString( out, Con::getVariable( "Pref::Server::Info" ) ); writeLongCString( out, Con::evaluate( "onServerInfoQuery();" ) ); BitStream::sendPacketStream(address); } } //----------------------------------------------------------------------------- static void handleGameInfoResponse( const NetAddress* address, BitStream* stream, U32 /*key*/, U8 /*flags*/ ) { if ( !gQueryList.size() ) return; S32 index = findPingEntry( gQueryList, address ); if ( index == -1 ) return; // Remove the server from the query list since it has been so kind as to respond: gQueryList.erase( index ); updateQueryProgress(); ServerInfo *si = findServerInfo( address ); if ( !si ) return; bool isUpdate = si->isUpdating(); bool applyFilter = !isUpdate && ( sActiveFilter.type == ServerFilter::Normal ); char addrString[256]; Net::addressToString( address, addrString ); // Get the rules set: char stringBuf[2048]; // Who knows how big this should be? stream->readString( stringBuf ); if ( !si->gameType || dStricmp( si->gameType, stringBuf ) != 0 ) { si->gameType = (char*) dRealloc( (void*) si->gameType, dStrlen( stringBuf ) + 1 ); dStrcpy( si->gameType, stringBuf ); // Test against the active filter: if ( applyFilter && dStricmp( sActiveFilter.gameType, "any" ) != 0 && dStricmp( si->gameType, sActiveFilter.gameType ) != 0 ) { Con::printf( "Server %s filtered out by rules set. (%s:%s)", addrString, sActiveFilter.gameType, si->gameType ); removeServerInfo( address ); return; } } // Get the mission type: stream->readString( stringBuf ); if ( !si->missionType || dStrcmp( si->missionType, stringBuf ) != 0 ) { si->missionType = (char*) dRealloc( (void*) si->missionType, dStrlen( stringBuf ) + 1 ); dStrcpy( si->missionType, stringBuf ); // Test against the active filter: if ( applyFilter && dStricmp( sActiveFilter.missionType, "any" ) != 0 && dStricmp( si->missionType, sActiveFilter.missionType ) != 0 ) { Con::printf( "Server %s filtered out by mission type. (%s:%s)", addrString, sActiveFilter.missionType, si->missionType ); removeServerInfo( address ); return; } } // Get the mission name: stream->readString( stringBuf ); // Clip the file extension off: char* temp = dStrstr( static_cast( stringBuf ), const_cast( ".mis" ) ); if ( temp ) *temp = '\0'; if ( !si->missionName || dStrcmp( si->missionName, stringBuf ) != 0 ) { si->missionName = (char*) dRealloc( (void*) si->missionName, dStrlen( stringBuf ) + 1 ); dStrcpy( si->missionName, stringBuf ); } // Get the server status: U8 temp_U8; stream->read( &temp_U8 ); si->status = temp_U8; // Filter by the flags: if ( applyFilter ) { if ( sActiveFilter.filterFlags & ServerFilter::Dedicated && !si->isDedicated() ) { Con::printf( "Server %s filtered out by dedicated flag.", addrString ); removeServerInfo( address ); return; } if ( sActiveFilter.filterFlags & ServerFilter::NotPassworded && si->isPassworded() ) { Con::printf( "Server %s filtered out by no-password flag.", addrString ); removeServerInfo( address ); return; } } si->status.set( ServerInfo::Status_Responded ); // Get the player count: stream->read( &si->numPlayers ); // Test player count against active filter: if ( applyFilter && ( si->numPlayers < sActiveFilter.minPlayers || si->numPlayers > sActiveFilter.maxPlayers ) ) { Con::printf( "Server %s filtered out by player count.", addrString ); removeServerInfo( address ); return; } // Get the max players and bot count: stream->read( &si->maxPlayers ); stream->read( &si->numBots ); // Test bot count against active filter: if ( applyFilter && ( si->numBots > sActiveFilter.maxBots ) ) { Con::printf( "Server %s filtered out by maximum bot count.", addrString ); removeServerInfo( address ); return; } // Get the CPU speed; U16 temp_U16; stream->read( &temp_U16 ); si->cpuSpeed = temp_U16; // Test CPU speed against active filter: if ( applyFilter && ( si->cpuSpeed < sActiveFilter.minCPU ) ) { Con::printf( "Server %s filtered out by minimum CPU speed.", addrString ); removeServerInfo( address ); return; } // Get the server info: stream->readString( stringBuf ); if ( !si->statusString || ( isUpdate && dStrcmp( si->statusString, stringBuf ) != 0 ) ) { si->infoString = (char*) dRealloc( (void*) si->infoString, dStrlen( stringBuf ) + 1 ); dStrcpy( si->infoString, stringBuf ); } // Get the content string: readLongCString( stream, stringBuf ); if ( !si->statusString || ( isUpdate && dStrcmp( si->statusString, stringBuf ) != 0 ) ) { si->statusString = (char*) dRealloc( (void*) si->statusString, dStrlen( stringBuf ) + 1 ); dStrcpy( si->statusString, stringBuf ); } // Update the server browser gui! gServerBrowserDirty = true; } //----------------------------------------------------------------------------- // Packet Dispatch void DemoNetInterface::handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ) { U8 flags; U32 key; stream->read( &flags ); stream->read( &key ); switch( packetType ) { case GamePingRequest: handleGamePingRequest( address, key, flags ); break; case GamePingResponse: handleGamePingResponse( address, stream, key, flags ); break; case GameInfoRequest: handleGameInfoRequest( address, key, flags ); break; case GameInfoResponse: handleGameInfoResponse( address, stream, key, flags ); break; case MasterServerGameTypesResponse: handleMasterServerGameTypesResponse( stream, key, flags ); break; case MasterServerListResponse: handleMasterServerListResponse( stream, key, flags ); break; case GameMasterInfoRequest: handleGameMasterInfoRequest( address, key, flags ); break; } } ConsoleFunctionGroupEnd( ServerQuery );