/* ======================== idLobbyBackendDirect::GetConnectInfo ======================== */ lobbyConnectInfo_t idLobbyBackendDirect::GetConnectInfo() { lobbyConnectInfo_t connectInfo; // If we aren't the host, this lobby should have been joined through JoinFromConnectInfo if ( IsHost() ) { // If we are the host, give them our ip address const char * ip = Sys_GetLocalIP( 0 ); Sys_StringToNetAdr( ip, &address, false ); address.port = net_port.GetInteger(); } connectInfo.netAddr = address; return connectInfo; }
CUT_PBASE_T_USBDI_0474::~CUT_PBASE_T_USBDI_0474() { OstTraceFunctionEntry1( CUT_PBASE_T_USBDI_0474_CUT_PBASE_T_USBDI_0474_ENTRY_DUP01, this ); Cancel(); iUsbInterface0.Close(); delete iClientAction; delete iActorFDF; if(!IsHost() && iTestDevice) { iTestDevice->Close(); } delete iTestDevice; OstTraceFunctionExit1( CUT_PBASE_T_USBDI_0474_CUT_PBASE_T_USBDI_0474_EXIT_DUP01, this ); }
//Should keep the script kiddies away. //TODO: Replace with a better check BOOL CALLBACK AntiTrainer(HWND hwnd, LPARAM lParam) { char title[80]; GetWindowText(hwnd, title, sizeof(title)); if (strcmp(title, "Halo Online Trainer") == 0) { auto session = Blam::Network::GetActiveSession(); if (session && session->IsEstablished() && !session->IsHost()) { MessageBox(NULL, "Detected Halo Online Trainer!\nOnly hosts are allowed to use a trainer!", "AntiCheat", MB_OK); //Tell the user what they did wrong. Modules::CommandMap::Instance().ExecuteCommand("exit"); //exit game } } return TRUE; }
/* ======================== idLobby::BecomeHost ======================== */ void idLobby::BecomeHost() { if( !verify( migrationInfo.state == MIGRATE_PICKING_HOST ) ) { idLib::Printf( "BecomeHost: Must be called from PickNewHost.\n" ); EndMigration(); return; } if( IsHost() ) { idLib::Printf( "BecomeHost: Already host of session.\n" ); EndMigration(); return; } if( !sessionCB->BecomingHost( *this ) ) { EndMigration(); return; } idLib::Printf( "BecomeHost: Sending %i invites on %s.\n", migrationInfo.invites.Num(), GetLobbyName() ); migrationInfo.state = MIGRATE_BECOMING_HOST; migrationInfo.migrationStartTime = Sys_Milliseconds(); if( lobbyBackend == NULL ) { // If we don't have a lobbyBackend, then just create one Shutdown(); StartCreating(); return; } // Shutdown the current lobby, but keep the lobbyBackend (we'll migrate it) Shutdown( true ); // Migrate the lobbyBackend to host lobbyBackend->BecomeHost( migrationInfo.invites.Num() ); // Wait for it to complete SetState( STATE_CREATE_LOBBY_BACKEND ); }
CUT_PBASE_T_USBDI_0485::~CUT_PBASE_T_USBDI_0485() { OstTraceFunctionEntry1( CUT_PBASE_T_USBDI_0485_CUT_PBASE_T_USBDI_0485_ENTRY_DUP01, this ); Cancel(); // Close the interface iUsbInterface0.Close(); delete iControlEp0; delete iActorFDF; if(!IsHost() && iTestDevice) { iTestDevice->Close(); } delete iTestDevice; OstTraceFunctionExit1( CUT_PBASE_T_USBDI_0485_CUT_PBASE_T_USBDI_0485_EXIT_DUP01, this ); }
/* ======================== idLobby::KickLobbyUser ======================== */ void idLobby::KickLobbyUser( lobbyUserID_t lobbyUserID ) { if ( !IsHost() ) { return; } const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID ); lobbyUser_t * user = GetLobbyUser( lobbyUserIndex ); if ( user != NULL && !IsSessionUserLocal( user ) ) { // Send an explicit kick msg, so they know why they were removed if ( user->peerIndex >= 0 && user->peerIndex < peers.Num() ) { byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; idBitMsg msg( buffer, sizeof( buffer ) ); msg.WriteByte( lobbyUserIndex ); QueueReliableMessage( user->peerIndex, RELIABLE_KICK_PLAYER, msg.GetReadData(), msg.GetSize() ); } } }
bool FOnlineSessionNull::NeedsToAdvertise( FNamedOnlineSession& Session ) { // In Null, we have to imitate missing online service functionality, so we advertise: // a) LAN match with open public connections (same as usually) // b) Not started public LAN session (same as usually) // d) Joinable presence-enabled session that would be advertised with in an online service // (all of that only if we're server) return Session.SessionSettings.bShouldAdvertise && IsHost(Session) && ( ( Session.SessionSettings.bIsLANMatch && (Session.SessionState != EOnlineSessionState::InProgress || (Session.SessionSettings.bAllowJoinInProgress && Session.NumOpenPublicConnections > 0)) ) || ( Session.SessionSettings.bAllowJoinViaPresence || Session.SessionSettings.bAllowJoinViaPresenceFriendsOnly ) ); }
void DiscordRPC::UpdatePresence(int networkMode) { if (Modules::ModuleGame::Instance().VarDiscordEnable->ValueInt == 0) { Discord_ClearPresence(); return; } auto session = Blam::Network::GetActiveSession(); if (networkMode == 3 && session->IsHost()) { auto thread = CreateThread(NULL, 0, DiscordRetrieveExternalIP_Thread, NULL, 0, NULL); } else { joinString = ""; } UpdatePresence(); }
/* ======================== idLobby::RequestSessionUserDisconnect Sends a request to the host to remove a session user from the session. If we are the host, we will do it immediately. ======================== */ void idLobby::RequestSessionUserDisconnect( int sessionUserIndex ) { if ( !IsRunningAsHostOrPeer() ) { // If we are not in an actual running session, just remove it. // This is so we accurately reflect the local user list through the session users in the menus, etc. // FIXME: // This is a total hack, and we should really look at better separation of local users/session users // and not rely on session users while in the menus. FreeUser( GetLobbyUser( sessionUserIndex ) ); return; } lobbyUser_t * lobbyUser = GetLobbyUser( sessionUserIndex ); if ( !verify( lobbyUser != NULL ) ) { return; } if ( lobbyUser->disconnecting == true ) { return; // Already disconnecting } byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; idBitMsg msg( buffer, sizeof( buffer ) ); msg.WriteByte( 1 ); // 1 user lobbyUser->lobbyUserID.WriteToMsg( msg ); if ( IsHost() ) { idBitMsg readMsg; readMsg.InitRead( msg.GetReadData(), msg.GetSize() ); // As the host, just disconnect immediately (we'll still send the notification to all peers though) ProcessUserDisconnectMsg( readMsg ); } else { // Send the message QueueReliableMessage( host, RELIABLE_USER_DISCONNECT_REQUEST, msg.GetReadData(), msg.GetSize() ); // Mark user as disconnecting to make sure we don't keep sending the request lobbyUser->disconnecting = true; } }
CUT_PBASE_T_USBDI_0481::~CUT_PBASE_T_USBDI_0481() { OstTraceFunctionEntry1( CUT_PBASE_T_USBDI_0481_CUT_PBASE_T_USBDI_0481_ENTRY_DUP01, this ); Cancel(); // Close interfaces iUsbInterface0.Close(); iUsbInterface1.Close(); // Free resources delete iControlEp0; delete iActorFDF; if(!IsHost() && iTestDevice) { iTestDevice->Close(); } delete iTestDevice; OstTraceFunctionExit1( CUT_PBASE_T_USBDI_0481_CUT_PBASE_T_USBDI_0481_EXIT_DUP01, this ); }
/* ======================== idLobby::RequestLocalUserJoin Sends a request to the host to join a local user to a session. If we are the host, we will do it immediately. ======================== */ void idLobby::RequestLocalUserJoin( idLocalUser * localUser ) { assert( IsRunningAsHostOrPeer() ); // Construct a msg that contains the user connect request lobbyUser_t lobbyUser = CreateLobbyUserFromLocalUser( localUser ); byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; idBitMsg msg( buffer, sizeof( buffer ) ); msg.WriteByte( 1 ); // 1 user lobbyUser.WriteToMsg( msg ); // Write user if ( IsHost() ) { AddUsersFromMsg( msg, -1 ); localUser->SetJoiningLobby( lobbyType, false ); } else { // Send request to host to add user QueueReliableMessage( host, RELIABLE_USER_CONNECT_REQUEST, msg.GetReadData(), msg.GetSize() ); } }
//----------------------------------------------------------------------------- // Purpose: Migrate the session to a new host //----------------------------------------------------------------------------- bool CSession::MigrateHost() { if ( IsHost() ) { // Migrate call will fill this in for us Q_memcpy( &m_NewSessionInfo, &m_SessionInfo, sizeof( m_NewSessionInfo ) ); } m_hMigrateHandle = g_pXboxSystem->CreateAsyncHandle(); int ret = g_pXboxSystem->SessionMigrate( m_hSession, m_nOwnerId, &m_NewSessionInfo, true, &m_hMigrateHandle ); if ( ret != ERROR_IO_PENDING ) { return false; } SwitchToState( SESSION_STATE_MIGRATING ); return true; }
/* ======================== idLobby::RemoveUsersWithDisconnectedPeers Go through each user, and remove the ones that have a peer marked as disconnected NOTE - This should only be called from the host. The host will call RemoveSessionUsersByIDList, which will forward the action to the connected peers. ======================== */ void idLobby::RemoveUsersWithDisconnectedPeers() { if ( !verify( IsHost() ) ) { // We're not allowed to do this unless we are the host of this session type // If we are the host, RemoveSessionUsersByIDList will forward the call to peers. return; } idList< lobbyUserID_t > removeList; for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { lobbyUser_t * user = GetLobbyUser( u ); if ( !verify( user != NULL ) ) { continue; } if ( IsSessionUserIndexLocal( u ) || user->IsDisconnected() ) { continue; } if ( user->peerIndex == -1 ) { // Wanting to know if this actually happens. // If this is a user on the hosts machine, IsSessionUserIndexLocal should catch it above. assert( false ); // The user is on the host. // The host's peer is disconnected via other mechanisms that I don't have a firm // grasp on yet. continue; } if ( user->peerIndex >= peers.Num() ) { // TTimo - I am hitting this in ~12 client games for some reason? // only throwing an assertion in debug, with no crashing, so adding a warning verbose idLib::Warning( "idLobby::RemoveUsersWithDisconnectedPeers: user %d %s is out of range in the peers list (%d elements)", u, user->gamertag, peers.Num() ); continue; } peer_t & peer = peers[ user->peerIndex ]; if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) { removeList.Append( user->lobbyUserID ); } } RemoveSessionUsersByIDList( removeList ); }
Vector<ValueType> Vector<ValueType>::operator+( const Vector<ValueType>& otherVector) { DEBUGLOG( this, "Vector::operator+", "Vec =" << &otherVector, 1); assert(GetSize() == otherVector.GetSize()); assert(( IsHost() && otherVector.IsHost() )|| (IsDevice() && otherVector.IsDevice()) ); Vector<ValueType> result(GetSize()); if (pImpl == pImplHost) result.pImpl->Add(*(otherVector.pImpl), *pImpl); else if (pImpl == pImplDevice) { result.MoveToDevice(); result.pImpl->Add(*(otherVector.pImpl), *pImpl); } DEBUGEND(); return result; }
void CNetManager::SendLocalCharacterUpdates() { if(Connected()) { std::vector<CCharacterObject*>::iterator playerIter; for(playerIter = localPlayerCharactersVectorRef->begin(); playerIter != localPlayerCharactersVectorRef->end(); playerIter++) { if((*playerIter)->IsMovementUpdate()) { SendCharacterMovement(*(*playerIter)); } } if(IsHost()) { std::vector<CEnemyObject*>::iterator enemyIter; for(enemyIter = localEnemiesVectorRef->begin(); enemyIter != localEnemiesVectorRef->end(); enemyIter++) { SendCharacterMovement(*(*enemyIter)); } } } }
QueryError OnSessionInfo(const rapidjson::Value &p_Args, std::string *p_Result) { rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); writer.StartObject(); auto session = Blam::Network::GetActiveSession(); if (!session || !session->IsEstablished()) { writer.Key("established"); writer.Bool(false); writer.Key("hasTeams"); writer.Bool(false); writer.Key("isHost"); writer.Bool(false); } else { writer.Key("established"); writer.Bool(true); writer.Key("hasTeams"); writer.Bool(session->HasTeams()); writer.Key("isHost"); writer.Bool(session->IsHost()); } writer.Key("mapName"); writer.String((char*)Pointer(0x22AB018)(0x1A4)); writer.EndObject(); *p_Result = buffer.GetString(); return QueryError_Ok; }
/* ======================== idLobby::HandleHeadsetStateChange ======================== */ void idLobby::HandleHeadsetStateChange( int fromPeer, idBitMsg & msg ) { int userCount = msg.ReadLong(); for ( int i = 0; i < userCount; ++i ) { lobbyUserID_t lobbyUserID; lobbyUserID.ReadFromMsg( msg ); bool state = msg.ReadBool(); int talkerIndex = sessionCB->GetVoiceChat()->FindTalkerByUserId( lobbyUserID, lobbyType ); sessionCB->GetVoiceChat()->SetHeadsetState( talkerIndex, state ); idLib::Printf( "User %d headset status: %d\n", talkerIndex, state ); // If we are the host, let the other clients know about the headset state of this peer if ( IsHost() ) { // We should not be receiving a message with a user count > 1 if we are the host assert( userCount == 1 ); byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; idBitMsg outMsg( buffer, sizeof( buffer ) ); outMsg.WriteLong( 1 ); lobbyUserID.WriteToMsg( outMsg ); outMsg.WriteBool( state ); for ( int j = 0; j < peers.Num(); ++j ) { // Don't send this to the player that we just received the message from if ( !peers[ j ].IsConnected() || j == fromPeer ) { continue; } QueueReliableMessage( j, RELIABLE_HEADSET_STATE, outMsg.GetReadData(), outMsg.GetSize() ); } } } }
/* ======================== idLobby::UpdateLocalSessionUsers ======================== */ void idLobby::UpdateLocalSessionUsers() { for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( i ); lobbyUser_t * lobbyUser = GetLobbyUser( i ); if ( localUser == NULL || lobbyUser == NULL ) { continue; } if ( !lobbyUser->UpdateClientMutableData( localUser ) ) { continue; } byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; idBitMsg msg( buffer, sizeof( buffer ) ); CreateUserUpdateMessage( i, msg ); if ( IsHost() ) { UpdateSessionUserOnPeers( msg ); } else if ( IsPeer() ) { QueueReliableMessage( host, RELIABLE_SESSION_USER_MODIFIED, msg.GetReadData(), msg.GetSize() ); } } }
/* ======================== idLobby::UpdateHostMigration ======================== */ void idLobby::UpdateHostMigration() { int time = Sys_Milliseconds(); // If we are picking a new host, then update that if( migrationInfo.state == MIGRATE_PICKING_HOST ) { const int MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS = 20; // FIXME: set back to 5 // Give other hosts 5 seconds if( time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS", MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS ) * 1000 ) { // Just become the host if we haven't heard from a host in awhile BecomeHost(); } else { return; } } // See if we are a new migrated host that needs to invite the original members back if( migrationInfo.state != MIGRATE_BECOMING_HOST ) { return; } if( lobbyBackend == NULL || lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { return; } if( state != STATE_IDLE ) { return; } if( !IsHost() ) { return; } const int MIGRATION_TIMEOUT_IN_SECONDS = 30; // FIXME: setting to 30 for dev purposes. 10 seems more reasonable. Need to make unloading game / loading lobby async const int MIGRATION_INVITE_TIME_IN_SECONDS = 2; if( migrationInfo.invites.Num() == 0 || time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_TIMEOUT_IN_SECONDS", MIGRATION_TIMEOUT_IN_SECONDS ) * 1000 ) { // Either everyone acked, or we timed out, just keep who we have, and stop sending invites EndMigration(); return; } // Send invites to anyone who hasn't responded for( int i = 0; i < migrationInfo.invites.Num(); i++ ) { if( time - migrationInfo.invites[i].lastInviteTime < session->GetTitleStorageInt( "MIGRATION_INVITE_TIME_IN_SECONDS", MIGRATION_INVITE_TIME_IN_SECONDS ) * 1000 ) { continue; // Not enough time passed } // Mark the time migrationInfo.invites[i].lastInviteTime = time; byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; idBitMsg outmsg( buffer, sizeof( buffer ) ); // Have lobbyBackend fill out msg with connection info lobbyConnectInfo_t connectInfo = lobbyBackend->GetConnectInfo(); connectInfo.WriteToMsg( outmsg ); // Let them know whether or not this was from in game outmsg.WriteBool( migrationInfo.persistUntilGameEndsData.wasMigratedGame ); NET_VERBOSE_PRINT( "NET: Sending migration invite to %s\n", migrationInfo.invites[i].address.ToString() ); // Send the migration invite SendConnectionLess( migrationInfo.invites[i].address, OOB_MIGRATE_INVITE, outmsg.GetReadData(), outmsg.GetSize() ); } }
/* ======================== idLobby::PickNewHostInternal ======================== */ void idLobby::PickNewHostInternal( bool forceMe, bool inviteOldHost ) { if( migrationInfo.state == MIGRATE_PICKING_HOST ) { return; // Already picking new host } idLib::Printf( "PickNewHost: Started picking new host %s.\n", GetLobbyName() ); if( IsHost() ) { idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() ); return; } // Find the user with the lowest ping int bestUserIndex = -1; int bestPingMs = 0; lobbyUserID_t bestUserId; for( int i = 0; i < GetNumLobbyUsers(); i++ ) { lobbyUser_t* user = GetLobbyUser( i ); if( !verify( user != NULL ) ) { continue; } if( user->IsDisconnected() ) { continue; } if( user->peerIndex == -1 ) { continue; // Don't try and pick old host } if( bestUserIndex == -1 || IsBetterHost( user->pingMs, user->lobbyUserID, bestPingMs, bestUserId ) ) { bestUserIndex = i; bestPingMs = user->pingMs; bestUserId = user->lobbyUserID; } if( user->peerIndex == net_migration_forcePeerAsHost.GetInteger() ) { bestUserIndex = i; bestPingMs = user->pingMs; bestUserId = user->lobbyUserID; break; } } // Remember when we first started picking a new host migrationInfo.state = MIGRATE_PICKING_HOST; migrationInfo.migrationStartTime = Sys_Milliseconds(); migrationInfo.persistUntilGameEndsData.wasMigratedGame = sessionCB->GetState() == idSession::INGAME; if( bestUserIndex == -1 ) // This can happen if we call PickNewHost on an lobby that was Shutdown { NET_VERBOSE_PRINT( "MIGRATION: PickNewHost was called on an lobby that was Shutdown\n" ); BecomeHost(); return; } NET_VERBOSE_PRINT( "MIGRATION: Chose user index %d (%s) for new host\n", bestUserIndex, GetLobbyUser( bestUserIndex )->gamertag ); bool bestWasLocal = IsSessionUserIndexLocal( bestUserIndex ); // Check before shutting down the lobby migrateMsgFlags = parms.matchFlags; // Save off match parms // Build invite list BuildMigrationInviteList( inviteOldHost ); // If the best user is on this machine, then we become the host now, otherwise, wait for a new host to contact us if( forceMe || bestWasLocal ) { BecomeHost(); } }
bool CommandMap::ExecuteCommandWithStatus(std::string command, bool isUserInput, std::string *output) { *output = ""; int numArgs = 0; auto args = CommandLineToArgvA((PCHAR)command.c_str(), &numArgs); if (numArgs <= 0) { *output = "Invalid input"; return false; } auto cmd = FindCommand(args[0]); if (!cmd || (isUserInput && cmd->Flags & eCommandFlagsInternal)) { *output = "Command/Variable not found"; return false; } if ((cmd->Flags & eCommandFlagsRunOnMainMenu) && !ElDorito::Instance().GameHasMenuShown) { queuedCommands.push_back(command); *output = "Command queued until mainmenu shows"; return true; } // Host-only commands if (cmd->Flags & eCommandFlagsCheat || cmd->Flags & eCommandFlagsHostOnly) { auto session = Blam::Network::GetActiveSession(); if (session && session->IsEstablished() && !session->IsHost()) { *output = "Only a player hosting a game can use this command"; return false; } } std::vector<std::string> argsVect; if (numArgs > 1) for (int i = 1; i < numArgs; i++) argsVect.push_back(args[i]); if (cmd->Type == eCommandTypeCommand && cmd->Flags == eCommandFlagsArgsNoParse) { argsVect.clear(); if (numArgs >= 2) argsVect.push_back(command.substr(std::strlen(args[0]) + 1)); //push unparsed arguments after the command return cmd->UpdateEvent(argsVect, *output); } if (cmd->Type == eCommandTypeCommand) return cmd->UpdateEvent(argsVect, *output); // if it's a command call it and return if (numArgs <= 1) { *output = cmd->ValueString; return true; } std::string previousValue; auto updateRet = SetVariable(cmd, argsVect[0], previousValue); switch (updateRet) { case eVariableSetReturnValueError: *output = "Command/variable not found"; return false; case eVariableSetReturnValueInvalidArgument: *output = "Invalid value"; return false; case eVariableSetReturnValueOutOfRange: if (cmd->Type == eCommandTypeVariableInt) *output = "Value " + argsVect[0] + " out of range [" + std::to_string(cmd->ValueIntMin) + ".." + std::to_string(cmd->ValueIntMax) + "]"; else if (cmd->Type == eCommandTypeVariableInt64) *output = "Value " + argsVect[0] + " out of range [" + std::to_string(cmd->ValueInt64Min) + ".." + std::to_string(cmd->ValueInt64Max) + "]"; else if (cmd->Type == eCommandTypeVariableFloat) *output = "Value " + argsVect[0] + " out of range [" + std::to_string(cmd->ValueFloatMin) + ".." + std::to_string(cmd->ValueFloatMax) + "]"; else *output = "Value " + argsVect[0] + " out of range [this shouldn't be happening!]"; return false; } if (!cmd->UpdateEvent) { *output = previousValue + " -> " + cmd->ValueString; // no update event, so we'll just return with what we set the value to return true; } auto ret = cmd->UpdateEvent(argsVect, *output); if (!ret) // error, revert the variable this->SetVariable(cmd, previousValue, std::string()); if (output->length() <= 0) *output = previousValue + " -> " + cmd->ValueString; return ret; }
/* ======================== idLobby::CheckPeerThrottle ======================== */ void idLobby::CheckPeerThrottle( int p ) { assert( lobbyType == GetActingGameStateLobbyType() ); if( !verify( p >= 0 && p < peers.Num() ) ) { return; } peer_t& peer = peers[p]; if( !peer.IsConnected() ) { return; } if( !IsHost() ) { return; } if( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) { return; } if( peer.receivedBps < 0.0f ) { return; } int time = Sys_Milliseconds(); if( !AllPeersHaveBaseState() ) { return; } if( verify( peer.snapProc != NULL ) ) { const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); if( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { return; } } // This is bps throttling which compares the sent bytes per second to the reported received bps float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() ); if( peer_throttle_bps_host_threshold > 0.0f ) { int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime ); if( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) { bool throttled = false; float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ]; // Min outgoing rate from server (don't throttle if we are sending < 1k) if( sentBps > peer_throttle_bps_host_threshold ) { float pct = peer.receivedBps / idMath::ClampFloat( 0.01f, static_cast<float>( BANDWIDTH_REPORTING_MAX ), sentBps ); // note the receivedBps is implicitly clamped on client end to 10k/sec /* static int lastSeq = 0; if ( peer.receivedBpsIndex != lastSeq ) { NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct ); } lastSeq = peer.receivedBpsIndex; */ // Increase throttle time if peer is < % of what we are sending him if( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) { peer.receivedThrottle += ( float )deltaT; throttled = true; NET_VERBOSE_PRINT( "NET: throttled... %.2f ....pct %.2f receivedBps %.2f outgoingBps %.2f, peer %i, seq %i\n", peer.receivedThrottle, pct, peer.receivedBps, sentBps, p, peer.snapProc->GetFullSnapBaseSequence() ); } } if( !throttled ) { float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() ); peer.receivedThrottle = Max<float>( 0.0f, peer.receivedThrottle - ( ( ( float )deltaT ) * decayRate ) ); //NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps ); } float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() ); if( peer.receivedThrottle > duration ) { peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() ); int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); if( peer.throttledSnapRate == 0 ) { peer.throttledSnapRate = common->GetSnapRate() * 2; } else if( peer.throttledSnapRate < maxRate ) { peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); } peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again } } peer.receivedThrottleTime = time; } }
/* ======================== idLobby::SendCompletedPendingSnap ======================== */ void idLobby::SendCompletedPendingSnap( int p ) { assert( lobbyType == GetActingGameStateLobbyType() ); int time = Sys_Milliseconds(); peer_t& peer = peers[p]; if( !peer.IsConnected() ) { return; } if( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) { return; } // If we have a pending snap ready to send, we better have a pending snap assert( peer.snapProc->HasPendingSnap() ); // Get the snap data blob now, even if we don't send it. // This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work // If we don't do this, this peer will cause other peers to be starved of snapshots, when they may very well be ready to send a snap byte buffer[ MAX_SNAP_SIZE ]; int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128; int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength ); if( !CanSendMoreData( p ) ) { return; } // Can't send anymore snapshots until all fragments are sent if( peer.packetProc->HasMoreFragments() ) { return; } // If the peer doesn't have the latest resource list, send it to him before sending any new snapshots if( SendResources( p ) ) { return; } int timeFromJobSub = time - peer.lastSnapJobTime; int timeFromLastSend = time - peer.lastSnapTime; if( timeFromLastSend > 0 ) { peer.snapHz = 1000.0f / ( float )timeFromLastSend; } else { peer.snapHz = 0.0f; } if( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) { idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p ); } if( peer.throttleSnapsForXSeconds != 0 ) { if( time < peer.throttleSnapsForXSeconds ) { return; } // If we were trying to recover ping, see if we succeeded if( peer.recoverPing != 0 ) { if( peer.lastPingRtt >= peer.recoverPing ) { peer.failedPingRecoveries++; } else { const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() ); if( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) { // If throttling recovered the ping int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() ); peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() ); } } } peer.throttleSnapsForXSeconds = 0; } peer.lastSnapTime = time; if( size != 0 ) { if( size > 0 ) { NET_VERBOSESNAPSHOT_PRINT_LEVEL( 3, va( "NET: (peer %d) Sending snapshot %d delta'd against %d. Since JobSub: %d Since LastSend: %d. Size: %d\n", p, peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence(), timeFromJobSub, timeFromLastSend, size ) ); ProcessOutgoingMsg( p, buffer, size, false, 0 ); } else if( size < 0 ) // Size < 0 indicates the delta buffer filled up { // There used to be code here that would disconnect peers if they were in game and filled up the buffer // This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation // determined that it was not needed since a timeout mechanism has been added since ProcessOutgoingMsg( p, buffer, -size, false, 0 ); if( peer.snapProc != NULL ) { NET_VERBOSESNAPSHOT_PRINT( "NET: (peerNum: %d - name: %s) Resending last snapshot delta %d because his delta list filled up. Since JobSub: %d Since LastSend: %d Delta Size: %d\n", p, GetPeerName( p ), peer.snapProc->GetSnapSequence(), timeFromJobSub, timeFromLastSend, size ); } } } // We calculate what our outgoing rate was for each sequence, so we can have a relative comparison // for when the client reports what his downstream was in the same timeframe if( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) { //NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() ); peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes(); } }
/* ======================== idLobby::AddUsersFromMsg Called on peer and host. Simply parses a msg, and adds any new users from it to our own user list. If we are the host, we will forward this to all peers except the peer that we just received it from. ======================== */ void idLobby::AddUsersFromMsg( idBitMsg & msg, int fromPeer ) { int userStart = GetNumLobbyUsers(); int numNewUsers = msg.ReadByte(); assert( lobbyBackend != NULL ); // Add the new users to our own list for ( int u = 0; u < numNewUsers; u++ ) { lobbyUser_t newUser; // Read in the new user newUser.ReadFromMsg( msg ); // Initialize their peerIndex and userID if we are the host // (we'll send these back to them in the initial connect) if ( IsHost() ) { if ( fromPeer != -1 ) { // -1 means this is the host adding his own users, and this stuff is already computed // local users will already have this information filled out. newUser.address = peers[ fromPeer ].address; newUser.peerIndex = fromPeer; if ( lobbyType == TYPE_PARTY ) { newUser.partyToken = GetPartyTokenAsHost(); } } } else { assert( fromPeer == host ); // The host sends us all user addresses, except his local users, so we compute that here if ( newUser.peerIndex == -1 ) { newUser.address = peers[ fromPeer ].address; } } idLib::Printf( "NET: %s joined (%s) [partyToken = %08x].\n", newUser.gamertag, GetLobbyName(), newUser.partyToken ); lobbyUser_t * appendedUser = NULL; // First, try to replace a disconnected user for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { lobbyUser_t * user = GetLobbyUser( i ); if ( user->IsDisconnected() ) { userStart = i; *user = newUser; appendedUser = user; break; } } // Add them to our list if ( appendedUser == NULL ) { appendedUser = AllocUser( newUser ); } // Run platform-specific handler after adding assert( appendedUser->peerIndex == newUser.peerIndex ); // paranoia assert( appendedUser->lobbyUserID == newUser.lobbyUserID ); // paranoia RegisterUser( appendedUser ); } // Forward list of the new users to all other peers if ( IsHost() ) { SendNewUsersToPeers( fromPeer, userStart, numNewUsers ); // Set the lobbies skill level lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() ); } idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() ); for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { lobbyUser_t * user = GetLobbyUser( userIndex ); idLib::Printf( "party %08x user %s\n", user->partyToken, user->gamertag ); } idLib::Printf( "---------------- %s --------------------\n", GetLobbyName() ); }