/* ================= SV_SendServerCommand Sends a reliable command string to be interpreted by the client game module: "cp", "print", "chat", etc A nullptr client will broadcast to all clients ================= */ void QDECL PRINTF_LIKE(2) SV_SendServerCommand( client_t *cl, const char *fmt, ... ) { va_list argptr; byte message[ MAX_MSGLEN ]; client_t *client; int j; va_start( argptr, fmt ); Q_vsnprintf( ( char * ) message, sizeof( message ), fmt, argptr ); va_end( argptr ); // do not forward server command messages that would be too big to clients // ( q3infoboom / q3msgboom stuff ) if ( strlen( ( char * ) message ) > 1022 ) { return; } if ( cl != nullptr ) { SV_AddServerCommand( cl, ( char * ) message ); return; } if ( Com_IsDedicatedServer() ) { if ( !strncmp( ( char * ) message, "print_tr_p ", 11 ) ) { SV_PrintTranslatedText( ( const char * ) message, true, true ); } else if ( !strncmp( ( char * ) message, "print_tr ", 9 ) ) { SV_PrintTranslatedText( ( const char * ) message, true, false ); } // hack to echo broadcast prints to console else if ( !strncmp( ( char * ) message, "print ", 6 ) ) { Com_Printf( "Broadcast: %s", Cmd_UnquoteString( ( char * ) message + 6 ) ); } } // send the data to all relevent clients for ( j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++ ) { if ( client->state < CS_PRIMED ) { continue; } // Ridah, don't need to send messages to AI if ( SV_IsBot(client) ) { continue; } // done. SV_AddServerCommand( client, ( char * ) message ); } }
/* ===================== SV_DropClient Called when the player is totally leaving the server, either willingly or unwillingly. This is NOT called if the entire server is quiting or crashing -- SV_FinalCommand() will handle that ===================== */ void SV_DropClient( client_t *drop, const char *reason ) { if ( drop->state == clientState_t::CS_ZOMBIE ) { return; // already dropped } Log::Debug( "Going to CS_ZOMBIE for %s", drop->name ); drop->state = clientState_t::CS_ZOMBIE; // become free in a few seconds // call the prog function for removing a client // this will remove the body, among other things gvm.GameClientDisconnect( drop - svs.clients ); if ( SV_IsBot(drop) ) { SV_BotFreeClient( drop - svs.clients ); } else { // tell everyone why they got dropped // Gordon: we want this displayed elsewhere now SV_SendServerCommand( nullptr, "print %s\"^* \"%s\"\n\"", Cmd_QuoteString( drop->name ), Cmd_QuoteString( reason ) ); // add the disconnect command SV_SendServerCommand( drop, "disconnect %s\n", Cmd_QuoteString( reason ) ); } // nuke user info SV_SetUserinfo( drop - svs.clients, "" ); SV_FreeClient( drop ); // if this was the last client on the server, send a heartbeat // to the master so it is known the server is empty // send a heartbeat now so the master will get up to date info // if there is already a slot for this IP address, reuse it int i; for ( i = 0; i < sv_maxclients->integer; i++ ) { if ( svs.clients[ i ].state >= clientState_t::CS_CONNECTED ) { break; } } if ( i == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from, const Cmd::Args& args ) { if ( args.Argc() < 2 ) { return; } Log::Debug( "SVC_DirectConnect ()" ); InfoMap userinfo = InfoStringToMap(args.Argv(1)); // DHM - Nerve :: Update Server allows any protocol to connect // NOTE TTimo: but we might need to store the protocol around for potential non http/ftp clients int version = atoi( userinfo["protocol"].c_str() ); if ( version != PROTOCOL_VERSION ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).", PROTOCOL_VERSION, version ); Log::Debug( " rejected connect from version %i", version ); return; } int qport = atoi( userinfo["qport"].c_str() ); auto clients_begin = svs.clients; auto clients_end = clients_begin + sv_maxclients->integer; client_t* reconnecting = std::find_if(clients_begin, clients_end, [&from, qport](const client_t& client) { return NET_CompareBaseAdr( from, client.netchan.remoteAddress ) && ( client.netchan.qport == qport || from.port == client.netchan.remoteAddress.port ); } ); if ( reconnecting != clients_end && svs.time - reconnecting->lastConnectTime < sv_reconnectlimit->integer * 1000 ) { Log::Debug( "%s: reconnect rejected: too soon", NET_AdrToString( from ) ); return; } if ( NET_IsLocalAddress( from ) ) { userinfo["ip"] = "loopback"; } else { // see if the challenge is valid (local clients don't need to challenge) Challenge::Duration ping_duration; if ( !ChallengeManager::MatchString( from, userinfo["challenge"], &ping_duration ) ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]No or bad challenge for address." ); return; } userinfo["ip"] = NET_AdrToString( from ); } client_t *new_client = nullptr; // if there is already a slot for this IP address, reuse it if ( reconnecting != clients_end ) { Log::Notice( "%s:reconnect\n", NET_AdrToString( from ) ); new_client = reconnecting; } else { // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password int startIndex = 0; if ( userinfo["password"] != sv_privatePassword->string ) { // skip past the reserved slots startIndex = sv_privateClients->integer; } new_client = std::find_if(clients_begin, clients_end, [](const client_t& client) { return client.state == clientState_t::CS_FREE; }); if ( new_client == clients_end ) { if ( NET_IsLocalAddress( from ) ) { int count = std::count_if(clients_begin+startIndex, clients_end, [](const client_t& client) { return SV_IsBot(&client); } ); // if they're all bots if ( count >= sv_maxclients->integer - startIndex ) { SV_DropClient( &svs.clients[ sv_maxclients->integer - 1 ], "only bots on server" ); new_client = &svs.clients[ sv_maxclients->integer - 1 ]; } else { Com_Error( errorParm_t::ERR_FATAL, "server is full on local connect" ); } } else { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n%s", sv_fullmsg->string ); Log::Debug( "Rejected a connection." ); return; } } } // build a new connection // accept the new client // this is the only place a client_t is ever initialized memset( new_client, 0, sizeof( client_t ) ); int clientNum = new_client - svs.clients; #ifdef HAVE_GEOIP const char * country = NET_GeoIP_Country( &from ); if ( country ) { Log::Notice( "Client %i connecting from %s\n", clientNum, country ); userinfo["geoip"] = country; } else { Log::Notice( "Client %i connecting from somewhere unknown\n", clientNum ); } #else Log::Notice( "Client %i connecting\n", clientNum ); #endif new_client->gentity = SV_GentityNum( clientNum ); new_client->gentity->r.svFlags = 0; // save the address Netchan_Setup( netsrc_t::NS_SERVER, &new_client->netchan, from, qport ); // init the netchan queue // Save the pubkey Q_strncpyz( new_client->pubkey, userinfo["pubkey"].c_str(), sizeof( new_client->pubkey ) ); userinfo.erase("pubkey"); // save the userinfo Q_strncpyz( new_client->userinfo, InfoMapToString(userinfo).c_str(), sizeof( new_client->userinfo ) ); // get the game a chance to reject this connection or modify the userinfo char reason[ MAX_STRING_CHARS ]; if ( gvm.GameClientConnect( reason, sizeof( reason ), clientNum, true, false ) ) { Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]%s", reason ); Log::Debug( "Game rejected a connection: %s.", reason ); return; } SV_UserinfoChanged( new_client ); // send the connect packet to the client Net::OutOfBandPrint( netsrc_t::NS_SERVER, from, "connectResponse" ); Log::Debug( "Going from CS_FREE to CS_CONNECTED for %s", new_client->name ); new_client->state = clientState_t::CS_CONNECTED; new_client->nextSnapshotTime = svs.time; new_client->lastPacketTime = svs.time; new_client->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit new_client->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. int count = std::count_if(clients_begin, clients_end, [](const client_t& client) { return client.state >= clientState_t::CS_CONNECTED; }); if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }
/* ================ SV_SpawnServer Change the server to a new map, taking all connected clients along with it. This is NOT called for map_restart ================ */ void SV_SpawnServer( const char *server ) { int i; bool isBot; // shut down the existing game if it is running SV_ShutdownGameProgs(); PrintBanner( "Server Initialization" ) Com_Printf( "Server: %s\n", server ); // clear the whole hunk because we're (re)loading the server Hunk_Clear(); // if not running a dedicated server CL_MapLoading will connect the client to the server // also print some status stuff CL_MapLoading(); // clear collision map data CM_ClearMap(); // wipe the entire per-level structure SV_ClearServer(); // allocate empty config strings for ( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { sv.configstrings[ i ] = CopyString( "" ); sv.configstringsmodified[ i ] = false; } // init client structures and svs.numSnapshotEntities if ( !Cvar_VariableValue( "sv_running" ) ) { SV_Startup(); } else { // check for maxclients change if ( sv_maxclients->modified ) { SV_ChangeMaxClients(); } } // allocate the snapshot entities on the hunk svs.snapshotEntities = ( entityState_t * ) Hunk_Alloc( sizeof( entityState_t ) * svs.numSnapshotEntities, h_high ); svs.nextSnapshotEntities = 0; // toggle the server bit so clients can detect that a // server has changed svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; // set sv_nextmap to the same map, but it may be overridden // by the game startup or another console command Cvar_Set( "sv_nextmap", "map_restart 0" ); // make sure we are not paused Cvar_Set( "cl_paused", "0" ); // get a new checksum feed and restart the file system srand( Sys_Milliseconds() ); sv.checksumFeed = ( ( rand() << 16 ) ^ rand() ) ^ Sys_Milliseconds(); FS::PakPath::ClearPaks(); FS_LoadBasePak(); if (!FS_LoadPak(va("map-%s", server))) Com_Error(ERR_DROP, "Could not load map pak\n"); CM_LoadMap(server); // set serverinfo visible name Cvar_Set( "mapname", server ); // serverid should be different each time sv.serverId = com_frameTime; sv.restartedServerId = sv.serverId; Cvar_Set( "sv_serverid", va( "%i", sv.serverId ) ); // media configstring setting should be done during // the loading stage, so connected clients don't have // to load during actual gameplay sv.state = SS_LOADING; // load and spawn all other entities SV_InitGameProgs(); // run a few frames to allow everything to settle for ( i = 0; i < GAME_INIT_FRAMES; i++ ) { gvm.GameRunFrame( sv.time ); svs.time += FRAMETIME; sv.time += FRAMETIME; } // create a baseline for more efficient communications SV_CreateBaseline(); for ( i = 0; i < sv_maxclients->integer; i++ ) { // send the new gamestate to all connected clients if ( svs.clients[ i ].state >= CS_CONNECTED ) { bool denied; char reason[ MAX_STRING_CHARS ]; isBot = SV_IsBot(&svs.clients[i]); // connect the client again denied = gvm.GameClientConnect( reason, sizeof( reason ), i, false, isBot ); // firstTime = false if ( denied ) { // this generally shouldn't happen, because the client // was connected before the level change SV_DropClient( &svs.clients[ i ], reason ); } else { if ( !isBot ) { // when we get the next packet from a connected client, // the new gamestate will be sent svs.clients[ i ].state = CS_CONNECTED; } else { client_t *client; sharedEntity_t *ent; client = &svs.clients[ i ]; client->state = CS_ACTIVE; ent = SV_GentityNum( i ); ent->s.number = i; client->gentity = ent; client->deltaMessage = -1; client->nextSnapshotTime = svs.time; // generate a snapshot immediately gvm.GameClientBegin( i ); } } } } // run another frame to allow things to look at all the players gvm.GameRunFrame( sv.time ); svs.time += FRAMETIME; sv.time += FRAMETIME; // the server sends these to the clients so they can figure // out which pk3s should be auto-downloaded Cvar_Set( "sv_paks", FS_LoadedPaks() ); // save systeminfo and serverinfo strings cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString( CVAR_SYSTEMINFO, true ) ); SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO, false ) ); cvar_modifiedFlags &= ~CVAR_SERVERINFO; // any media configstring setting now should issue a warning // and any configstring changes should be reliably transmitted // to all clients sv.state = SS_GAME; // send a heartbeat now so the master will get up to date info SV_Heartbeat_f(); Hunk_SetMark(); SV_UpdateConfigStrings(); SV_AddOperatorCommands(); Com_Printf( "-----------------------------------\n" ); }
/* ================ SVC_Info Responds with a short info message that should be enough to determine if a user is interested in a server to do a full status ================ */ void SVC_Info( netadr_t from, const Cmd::Args& args ) { int i, count, botCount; char infostring[ MAX_INFO_STRING ]; if ( args.Argc() < 2 ) { return; } const char *challenge = args.Argv(1).c_str(); /* * Check whether Cmd_Argv(1) has a sane length. This was not done in the original Quake3 version which led * to the Infostring bug discovered by Luigi Auriemma. See http://aluigi.altervista.org/ for the advisory. */ // A maximum challenge length of 128 should be more than plenty. if ( strlen( challenge ) > MAX_CHALLENGE_LEN ) { return; } //bani - bugtraq 12534 if ( !SV_VerifyChallenge( challenge ) ) { return; } SV_ResolveMasterServers(); // don't count privateclients botCount = count = 0; for ( i = sv_privateClients->integer; i < sv_maxclients->integer; i++ ) { if ( svs.clients[ i ].state >= CS_CONNECTED ) { if ( SV_IsBot(&svs.clients[ i ]) ) { ++botCount; } else { ++count; } } } infostring[ 0 ] = 0; // echo back the parameter to status. so servers can use it as a challenge // to prevent timed spoofed reply packets that add ghost servers Info_SetValueForKey( infostring, "challenge", challenge, false ); // If the master server listens on IPv4 and IPv6, we want to send the // most recent challenge received from it over the OTHER protocol for ( i = 0; i < MAX_MASTER_SERVERS; i++ ) { // First, see if the challenge was sent by this master server if ( !NET_CompareBaseAdr( from, masterServerAddr[ i ].ipv4 ) && !NET_CompareBaseAdr( from, masterServerAddr[ i ].ipv6 ) ) { continue; } // It was - if the saved challenge is for the other protocol, send it and record the current one if ( challenges[ i ].type == NA_IP || challenges[ i ].type == NA_IP6 ) { if ( challenges[ i ].type != from.type ) { Info_SetValueForKey( infostring, "challenge2", challenges[ i ].text, false ); challenges[ i ].type = from.type; strcpy( challenges[ i ].text, challenge ); break; } } // Otherwise record the current one regardless and check the next server challenges[ i ].type = from.type; strcpy( challenges[ i ].text, challenge ); } Info_SetValueForKey( infostring, "protocol", va( "%i", PROTOCOL_VERSION ), false ); Info_SetValueForKey( infostring, "hostname", sv_hostname->string, false ); Info_SetValueForKey( infostring, "serverload", va( "%i", svs.serverLoad ), false ); Info_SetValueForKey( infostring, "mapname", sv_mapname->string, false ); Info_SetValueForKey( infostring, "clients", va( "%i", count ), false ); Info_SetValueForKey( infostring, "bots", va( "%i", botCount ), false ); Info_SetValueForKey( infostring, "sv_maxclients", va( "%i", sv_maxclients->integer - sv_privateClients->integer ), false ); Info_SetValueForKey( infostring, "pure", va( "%i", sv_pure->integer ), false ); if ( sv_statsURL->string[0] ) { Info_SetValueForKey( infostring, "stats", sv_statsURL->string, false ); } #ifdef USE_VOIP if ( sv_voip->integer ) { Info_SetValueForKey( infostring, "voip", va( "%i", sv_voip->integer ), false ); } #endif if ( sv_minPing->integer ) { Info_SetValueForKey( infostring, "minPing", va( "%i", sv_minPing->integer ), false ); } if ( sv_maxPing->integer ) { Info_SetValueForKey( infostring, "maxPing", va( "%i", sv_maxPing->integer ), false ); } Info_SetValueForKey( infostring, "gamename", GAMENAME_STRING, false ); // Arnout: to be able to filter out Quake servers NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); }
/* ================== SV_DirectConnect A "connect" OOB command has been received ================== */ void SV_DirectConnect( netadr_t from, const Cmd::Args& args ) { char userinfo[ MAX_INFO_STRING ]; int i; client_t *cl, *newcl; client_t temp; sharedEntity_t *ent; int clientNum; int version; int qport; int challenge; const char *password; int startIndex; bool denied; char reason[ MAX_STRING_CHARS ]; int count; const char *ip; #ifdef HAVE_GEOIP const char *country = nullptr; #endif if ( args.Argc() < 2 ) { return; } Log::Debug( "SVC_DirectConnect ()" ); Q_strncpyz( userinfo, args.Argv(1).c_str(), sizeof( userinfo ) ); // DHM - Nerve :: Update Server allows any protocol to connect // NOTE TTimo: but we might need to store the protocol around for potential non http/ftp clients version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); if ( version != PROTOCOL_VERSION ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nServer uses protocol version %i (yours is %i).", PROTOCOL_VERSION, version ); Log::Debug( " rejected connect from version %i", version ); return; } challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); // quick reject for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { // DHM - Nerve :: This check was allowing clients to reconnect after zombietime(2 secs) //if ( cl->state == CS_FREE ) { //continue; //} if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { if ( ( svs.time - cl->lastConnectTime ) < ( sv_reconnectlimit->integer * 1000 ) ) { Log::Debug( "%s: reconnect rejected: too soon", NET_AdrToString( from ) ); return; } break; } } if ( NET_IsLocalAddress( from ) ) { ip = "localhost"; } else { ip = NET_AdrToString( from ); } if ( ( strlen( ip ) + strlen( userinfo ) + 4 ) >= MAX_INFO_STRING ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\nUserinfo string length exceeded. " "Try removing setu cvars from your config." ); return; } Info_SetValueForKey( userinfo, "ip", ip, false ); // see if the challenge is valid (local clients don't need to challenge) if ( !NET_IsLocalAddress( from ) ) { int ping; for ( i = 0; i < MAX_CHALLENGES; i++ ) { if ( NET_CompareAdr( from, svs.challenges[ i ].adr ) ) { if ( challenge == svs.challenges[ i ].challenge ) { break; // good } } } if ( i == MAX_CHALLENGES ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]No or bad challenge for address." ); return; } // force the IP address key/value pair, so the game can filter based on it Info_SetValueForKey( userinfo, "ip", NET_AdrToString( from ), false ); if ( svs.challenges[ i ].firstPing == 0 ) { ping = svs.time - svs.challenges[ i ].pingTime; svs.challenges[ i ].firstPing = ping; } else { ping = svs.challenges[ i ].firstPing; } #ifdef HAVE_GEOIP country = NET_GeoIP_Country( &from ); if ( country ) { Log::Notice( "Client %i connecting from %s with %i challenge ping\n", i, country, ping ); } else { Log::Notice( "Client %i connecting from somewhere unknown with %i challenge ping\n", i, ping ); } #else Log::Notice( "Client %i connecting with %i challenge ping\n", i, ping ); #endif svs.challenges[ i ].connected = true; // never reject a LAN client based on ping if ( !Sys_IsLANAddress( from ) ) { if ( sv_minPing->value && ping < sv_minPing->value ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]Server is for high pings only" ); Log::Debug( "Client %i rejected on a too low ping", i ); return; } if ( sv_maxPing->value && ping > sv_maxPing->value ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]Server is for low pings only" ); Log::Debug( "Client %i rejected on a too high ping: %i", i, ping ); return; } } } else { // force the "ip" info key to "localhost" Info_SetValueForKey( userinfo, "ip", "localhost", false ); } newcl = &temp; memset( newcl, 0, sizeof( client_t ) ); // if there is already a slot for this IP address, reuse it for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if ( cl->state == clientState_t::CS_FREE ) { continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Log::Notice( "%s:reconnect\n", NET_AdrToString( from ) ); newcl = cl; // this doesn't work because it nukes the players userinfo // // disconnect the client from the game first so any flags the // // player might have are dropped // VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); // goto gotnewcl; } } // find a client slot // if "sv_privateClients" is set > 0, then that number // of client slots will be reserved for connections that // have "password" set to the value of "sv_privatePassword" // Info requests will report the maxclients as if the private // slots didn't exist, to prevent people from trying to connect // to a full server. // This is to allow us to reserve a couple slots here on our // servers so we can play without having to kick people. // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { startIndex = 0; } else { // skip past the reserved slots startIndex = sv_privateClients->integer; } newcl = nullptr; for ( i = startIndex; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[ i ]; if ( cl->state == clientState_t::CS_FREE ) { newcl = cl; break; } } if ( !newcl ) { if ( NET_IsLocalAddress( from ) ) { count = 0; for ( i = startIndex; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[ i ]; if ( SV_IsBot(cl) ) { count++; } } // if they're all bots if ( count >= sv_maxclients->integer - startIndex ) { SV_DropClient( &svs.clients[ sv_maxclients->integer - 1 ], "only bots on server" ); newcl = &svs.clients[ sv_maxclients->integer - 1 ]; } else { Com_Error( errorParm_t::ERR_FATAL, "server is full on local connect" ); } } else { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n%s", sv_fullmsg->string ); Log::Debug( "Rejected a connection." ); return; } } // we got a newcl, so reset the reliableSequence and reliableAcknowledge cl->reliableAcknowledge = 0; cl->reliableSequence = 0; gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized *newcl = std::move(temp); clientNum = newcl - svs.clients; ent = SV_GentityNum( clientNum ); newcl->gentity = ent; ent->r.svFlags = 0; #ifdef HAVE_GEOIP if ( country ) { Info_SetValueForKey( userinfo, "geoip", country, false ); } #endif // save the challenge newcl->challenge = challenge; // save the address Netchan_Setup( netsrc_t::NS_SERVER, &newcl->netchan, from, qport ); // init the netchan queue // Save the pubkey Q_strncpyz( newcl->pubkey, Info_ValueForKey( userinfo, "pubkey" ), sizeof( newcl->pubkey ) ); Info_RemoveKey( userinfo, "pubkey", false ); // save the userinfo Q_strncpyz( newcl->userinfo, userinfo, sizeof( newcl->userinfo ) ); // get the game a chance to reject this connection or modify the userinfo denied = gvm.GameClientConnect( reason, sizeof( reason ), clientNum, true, false ); // firstTime = true if ( denied ) { NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "print\n[err_dialog]%s", reason ); Log::Debug( "Game rejected a connection: %s.", reason ); return; } SV_UserinfoChanged( newcl ); // DHM - Nerve :: Clear out firstPing now that client is connected svs.challenges[ i ].firstPing = 0; // send the connect packet to the client NET_OutOfBandPrint( netsrc_t::NS_SERVER, from, "connectResponse" ); Log::Debug( "Going from CS_FREE to CS_CONNECTED for %s", newcl->name ); newcl->state = clientState_t::CS_CONNECTED; newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit newcl->gamestateMessageNum = -1; // if this was the first client on the server, or the last client // the server can hold, send a heartbeat to the master. count = 0; for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if ( svs.clients[ i ].state >= clientState_t::CS_CONNECTED ) { count++; } } if ( count == 1 || count == sv_maxclients->integer ) { SV_Heartbeat_f(); } }