uint32_t CGameProtocol :: RECEIVE_W3GS_OUTGOING_KEEPALIVE( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED W3GS_OUTGOING_KEEPALIVE" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 1 byte -> ??? // 4 bytes -> CheckSum??? (used in replays) if( ValidateLength( data ) && data.size( ) == 9 ) return UTIL_ByteArrayToUInt32( data, false, 5 ); return 0; }
CIncomingMapSize *CGameProtocol :: RECEIVE_W3GS_MAPSIZE( BYTEARRAY data, BYTEARRAY mapSize ) { // DEBUG_Print( "RECEIVED W3GS_MAPSIZE" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> ??? // 1 byte -> SizeFlag (1 = have map, 3 = continue download) // 4 bytes -> MapSize if( ValidateLength( data ) && data.size( ) >= 13 ) return new CIncomingMapSize( data[8], UTIL_ByteArrayToUInt32( data, false, 9 ) ); return NULL; }
uint32_t CGameProtocol :: RECEIVE_W3GS_MAPPARTOK( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED W3GS_MAPPARTOK" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 1 byte -> SenderPID // 1 byte -> ReceiverPID // 4 bytes -> ??? // 4 bytes -> MapSize if( ValidateLength( data ) && data.size( ) >= 14 ) return UTIL_ByteArrayToUInt32( data, false, 10 ); return 0; }
uint32_t CGameProtocol :: RECEIVE_W3GS_PONG_TO_HOST( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED W3GS_PONG_TO_HOST" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> Pong // the pong value is just a copy of whatever was sent in SEND_W3GS_PING_FROM_HOST which was GetTicks( ) at the time of sending // so as long as we trust that the client isn't trying to fake us out and mess with the pong value we can find the round trip time by simple subtraction // (the subtraction is done elsewhere because the very first pong value seems to be 1 and we want to discard that one) if( ValidateLength( data ) && data.size( ) >= 8 ) return UTIL_ByteArrayToUInt32( data, false, 4 ); return 1; }
bool CBNETProtocol :: RECEIVE_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED SID_AUTH_ACCOUNTLOGONPROOF" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> Status if( ValidateLength( data ) && data.size( ) >= 8 ) { uint32_t Status = UTIL_ByteArrayToUInt32( BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ), false ); if( Status == 0 || Status == 0xE ) return true; } return false; }
bool CBNETProtocol :: RECEIVE_SID_LOGONRESPONSE( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED SID_LOGONRESPONSE" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> Status if( ValidateLength( data ) && data.size( ) >= 8 ) { BYTEARRAY Status = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); if( UTIL_ByteArrayToUInt32( Status, false ) == 1 ) return true; } return false; }
CIncomingGameHost *CBNETProtocol :: RECEIVE_SID_GETADVLISTEX( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED SID_GETADVLISTEX" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> GamesFound // if( GamesFound > 0 ) // 10 bytes -> ??? // 2 bytes -> Port // 4 bytes -> IP // null term string -> GameName // 2 bytes -> ??? // 8 bytes -> HostCounter if( ValidateLength( data ) && data.size( ) >= 8 ) { BYTEARRAY GamesFound = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); if( UTIL_ByteArrayToUInt32( GamesFound, false ) > 0 && data.size( ) >= 25 ) { BYTEARRAY Port = BYTEARRAY( data.begin( ) + 18, data.begin( ) + 20 ); BYTEARRAY IP = BYTEARRAY( data.begin( ) + 20, data.begin( ) + 24 ); BYTEARRAY GameName = UTIL_ExtractCString( data, 24 ); if( data.size( ) >= GameName.size( ) + 35 ) { BYTEARRAY HostCounter; HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 27, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 29, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 31, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 33, true ) ); return new CIncomingGameHost( IP, UTIL_ByteArrayToUInt16( Port, false ), string( GameName.begin( ), GameName.end( ) ), HostCounter ); } } } return NULL; }
bool CBNETProtocol :: RECEIVE_SID_AUTH_CHECK( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED SID_AUTH_CHECK" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> KeyState // null terminated string -> KeyStateDescription if( ValidateLength( data ) && data.size( ) >= 9 ) { m_KeyState = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); m_KeyStateDescription = UTIL_ExtractCString( data, 8 ); if( UTIL_ByteArrayToUInt32( m_KeyState, false ) == KR_GOOD ) return true; } return false; }
void CGamePlayer :: ProcessPackets( ) { if( !m_Socket ) return; CIncomingAction *Action = NULL; CIncomingChatPlayer *ChatPlayer = NULL; CIncomingMapSize *MapSize = NULL; bool HasMap = false; uint32_t CheckSum = 0; uint32_t Pong = 0; // process all the received packets in the m_Packets queue while( !m_Packets.empty( ) ) { CCommandPacket *Packet = m_Packets.front( ); m_Packets.pop( ); if( Packet->GetPacketType( ) == W3GS_HEADER_CONSTANT ) { switch( Packet->GetID( ) ) { case CGameProtocol :: W3GS_LEAVEGAME: m_Game->EventPlayerLeft( this, m_Protocol->RECEIVE_W3GS_LEAVEGAME( Packet->GetData( ) ) ); break; case CGameProtocol :: W3GS_GAMELOADED_SELF: if( m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF( Packet->GetData( ) ) ) { if( !m_FinishedLoading && m_Game->GetGameLoading( ) ) { m_FinishedLoading = true; m_FinishedLoadingTicks = GetTicks( ); m_Game->EventPlayerLoaded( this ); } else { // we received two W3GS_GAMELOADED_SELF packets from this player! } } break; case CGameProtocol :: W3GS_OUTGOING_ACTION: Action = m_Protocol->RECEIVE_W3GS_OUTGOING_ACTION( Packet->GetData( ), m_PID ); if( Action ) { // don't delete Action here because the game is going to store it in a queue and delete it later m_Game->EventPlayerAction( this, Action ); } break; case CGameProtocol :: W3GS_OUTGOING_KEEPALIVE: CheckSum = m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE( Packet->GetData( ) ); m_CheckSums.push( CheckSum ); ++m_SyncCounter; m_Game->EventPlayerKeepAlive( this, CheckSum ); break; case CGameProtocol :: W3GS_CHAT_TO_HOST: ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST( Packet->GetData( ) ); if( ChatPlayer ) { // determine if we should auto-mute this player if( ChatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGE || ChatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGEEXTRA ) { if( m_Level <= 1 &&! GetMuted( ) ) { m_MuteMessages.push_back( GetTicks( ) ); if( m_MuteMessages.size( ) > 7 ) m_MuteMessages.erase( m_MuteMessages.begin( ) ); uint32_t RecentCount = 0; for( unsigned int i = 0; i < m_MuteMessages.size( ); ++i ) { if( GetTicks( ) - m_MuteMessages[i] < 5000 ) { RecentCount++; } } if( m_Game->m_OHBot->m_AutoMuteSpammer && RecentCount >= 7 ) { m_Count++; if( m_Count == 1 ) { SetMuted( true ); m_MutedAuto = true; m_Game->SendChat( m_PID, "["+m_Game->m_OHBot->m_BotManagerName+"] "+m_Game->m_OHBot->m_Language->SpamWarning( ) ); m_MuteMessages.clear( ); m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "spamming" ) ); } if( m_Count == 2 ) { m_Game->SendAllChat( m_Game->m_OHBot->m_Language->UserIgnoerNotify( m_Name ) ); m_Game->SendChat( m_PID, "["+m_Game->m_OHBot->m_BotManagerName+"] "+m_Game->m_OHBot->m_Language->SpamWarning2( ) ); SetMuted( true ); m_Game->m_Pairedpenps.push_back( Pairedpenp( string(), m_Game->m_OHBot->m_DB->Threadedpenp( m_Name, "Spam" , m_Game->m_OHBot->m_BotManagerName, 1, "add" ) ) ); m_MutedAuto = true; m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "spamming" ) ); } if( m_Count == 3 ) { m_Game->BanPlayerByPenality( m_Name, GetExternalIPString( ), m_Game->m_OHBot->m_BotManagerName, m_PenalityLevel, "Spam" ); SetMuted( true ); m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "spamming" ) ); } } //we adding this condition not in the next condition to avoid a jump into ohbot.cpp to check if the message was a flame message or not if(m_Game->m_OHBot->m_FlameCheck && ( m_Name != "dolan" || m_Name != "Dolan" ) ) { //now check for flamers if( m_Game->m_OHBot->FlameCheck( ChatPlayer->GetMessage( ) ) ) { m_FlameMessages.push_back( GetTicks( ) ); if( m_FlameMessages.size( ) > 10 ) m_FlameMessages.erase( m_FlameMessages.begin( ) ); RecentCount = 0; for( unsigned int i = 0; i < m_FlameMessages.size( ); ++i ) { if( GetTicks( ) - m_FlameMessages[i] < 60000 ) RecentCount++; } if( RecentCount == 1 ) { m_Game->SendChat( m_PID, "["+m_Game->m_OHBot->m_BotManagerName+"] "+m_Game->m_OHBot->m_Language->FlameWarn ()); } if( RecentCount == 2 ) { m_Game->SendChat( m_PID, "["+m_Game->m_OHBot->m_BotManagerName+"] "+m_Game->m_OHBot->m_Language->FlameWarn2 () ); SetMuted( true ); m_MutedAuto = true; m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "flaming" ) ); } if( RecentCount == 3 ) { m_Game->SendChat( m_PID, m_Game->m_OHBot->m_Language->FlameWarn3 () ); SetMuted( true ); m_Game->m_Pairedpenps.push_back( Pairedpenp( string(), m_Game->m_OHBot->m_DB->Threadedpenp( m_Name, "Flame/Insult" , m_Game->m_OHBot->m_BotManagerName, 1, "add" ) ) ); m_MutedAuto = true; m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "flaming" ) ); } if( RecentCount == 4 ) { m_Game->BanPlayerByPenality( m_Name, GetExternalIPString( ), m_Game->m_OHBot->m_BotManagerName, m_PenalityLevel, "Flame/Insult" ); SetMuted( true ); m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "flaming" ) ); } if( RecentCount == 5 ) { //some people simple dont understand the ban policy. m_Game->BanPlayerByPenality( m_Name, GetExternalIPString( ), m_Game->m_OHBot->m_BotManagerName, m_PenalityLevel, "Flame/Insult" ); SetMuted( true ); m_Game->SendAllChat( "["+m_Game->m_OHBot->m_BotManagerName+"] " + m_Game->m_OHBot->m_Language->UserWasMutedForReason( m_Name, "flaming" ) ); } } } } } m_Game->EventPlayerChatToHost( this, ChatPlayer ); } delete ChatPlayer; ChatPlayer = NULL; break; case CGameProtocol :: W3GS_DROPREQ: // todotodo: no idea what's in this packet if( !m_DropVote ) { m_DropVote = true; m_Game->EventPlayerDropRequest( this ); } break; case CGameProtocol :: W3GS_MAPSIZE: MapSize = m_Protocol->RECEIVE_W3GS_MAPSIZE( Packet->GetData( ), m_Game->m_OHBot->m_Map->GetMapSize( ) ); if( MapSize ) m_Game->EventPlayerMapSize( this, MapSize ); delete MapSize; MapSize = NULL; break; case CGameProtocol :: W3GS_PONG_TO_HOST: Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST( Packet->GetData( ) ); // we discard pong values of 1 // the client sends one of these when connecting plus we return 1 on error to kill two birds with one stone if( Pong != 1 ) { // we also discard pong values when we're downloading because they're almost certainly inaccurate // this statement also gives the player a 5 second grace period after downloading the map to allow queued (i.e. delayed) ping packets to be ignored if( !m_DownloadStarted || ( m_DownloadFinished && GetTime( ) - m_FinishedDownloadingTime >= 5 ) ) { // we also discard pong values when anyone else is downloading if we're configured to if( m_Game->m_OHBot->m_PingDuringDownloads || !m_Game->IsDownloading( ) ) { m_Pings.push_back( GetTicks( ) - Pong ); if( m_Pings.size( ) > 20 ) m_Pings.erase( m_Pings.begin( ) ); } } } m_Game->EventPlayerPongToHost( this, Pong ); break; } } else if( Packet->GetPacketType( ) == GPS_HEADER_CONSTANT ) { BYTEARRAY Data = Packet->GetData( ); if( Packet->GetID( ) == CGPSProtocol :: GPS_INIT ) { if( m_Game->m_OHBot->IsMode( BOT_MODE_GPROXY ) ) { m_GProxy = true; m_Socket->PutBytes( m_Game->m_OHBot->m_GPSProtocol->SEND_GPSS_INIT( m_Game->m_OHBot->m_ReconnectPort, m_PID, m_GProxyReconnectKey, m_Game->GetGProxyEmptyActions( ) ) ); //Log->Info( "[GAME: " + m_Game->GetGameName( ) + "] player [" + m_Name + "] is using GProxy++" ); } else { // todotodo: send notice that we're not permitting reconnects // note: GProxy++ should never send a GPS_INIT message if bot_reconnect = 0 because we don't advertise the game with invalid map dimensions // but it would be nice to cover this case anyway } } else if( Packet->GetID( ) == CGPSProtocol :: GPS_RECONNECT ) { // this is handled in ohbot.cpp } else if( Packet->GetID( ) == CGPSProtocol :: GPS_ACK && Data.size( ) == 8 ) { uint32_t LastPacket = UTIL_ByteArrayToUInt32( Data, false, 4 ); uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); if( LastPacket > PacketsAlreadyUnqueued ) { uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) PacketsToUnqueue = m_GProxyBuffer.size( ); while( PacketsToUnqueue > 0 ) { m_GProxyBuffer.pop( ); --PacketsToUnqueue; } } } } delete Packet; } }
void CGamePlayer :: ProcessPackets( ) { if( !m_Socket ) return; CIncomingAction *Action = NULL; CIncomingChatPlayer *ChatPlayer = NULL; CIncomingMapSize *MapSize = NULL; bool HasMap = false; uint32_t CheckSum = 0; uint32_t Pong = 0; // process all the received packets in the m_Packets queue while( !m_Packets.empty( ) ) { CCommandPacket *Packet = m_Packets.front( ); m_Packets.pop( ); if( Packet->GetPacketType( ) == W3GS_HEADER_CONSTANT ) { switch( Packet->GetID( ) ) { case CGameProtocol :: W3GS_LEAVEGAME: m_Game->EventPlayerLeft( this, m_Protocol->RECEIVE_W3GS_LEAVEGAME( Packet->GetData( ) ) ); break; case CGameProtocol :: W3GS_GAMELOADED_SELF: if( m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF( Packet->GetData( ) ) ) { if( !m_FinishedLoading && m_Game->GetGameLoading( ) ) { m_FinishedLoading = true; m_FinishedLoadingTicks = GetTicks( ); m_Game->EventPlayerLoaded( this ); } else { // we received two W3GS_GAMELOADED_SELF packets from this player! } } break; case CGameProtocol :: W3GS_OUTGOING_ACTION: Action = m_Protocol->RECEIVE_W3GS_OUTGOING_ACTION( Packet->GetData( ), m_PID ); if( Action ) { // don't delete Action here because the game is going to store it in a queue and delete it later m_Game->EventPlayerAction( this, Action ); } break; case CGameProtocol :: W3GS_OUTGOING_KEEPALIVE: CheckSum = m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE( Packet->GetData( ) ); m_CheckSums.push( CheckSum ); ++m_SyncCounter; m_Game->EventPlayerKeepAlive( this, CheckSum ); break; case CGameProtocol :: W3GS_CHAT_TO_HOST: ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST( Packet->GetData( ) ); if( ChatPlayer ) m_Game->EventPlayerChatToHost( this, ChatPlayer ); delete ChatPlayer; ChatPlayer = NULL; break; case CGameProtocol :: W3GS_DROPREQ: // todotodo: no idea what's in this packet if( !m_DropVote ) { m_DropVote = true; m_Game->EventPlayerDropRequest( this ); } break; case CGameProtocol :: W3GS_MAPSIZE: MapSize = m_Protocol->RECEIVE_W3GS_MAPSIZE( Packet->GetData( ), m_Game->m_GHost->m_Map->GetMapSize( ) ); if( MapSize ) m_Game->EventPlayerMapSize( this, MapSize ); delete MapSize; MapSize = NULL; break; case CGameProtocol :: W3GS_PONG_TO_HOST: Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST( Packet->GetData( ) ); // we discard pong values of 1 // the client sends one of these when connecting plus we return 1 on error to kill two birds with one stone if( Pong != 1 ) { // we also discard pong values when we're downloading because they're almost certainly inaccurate // this statement also gives the player a 5 second grace period after downloading the map to allow queued (i.e. delayed) ping packets to be ignored if( !m_DownloadStarted || ( m_DownloadFinished && GetTime( ) - m_FinishedDownloadingTime >= 5 ) ) { // we also discard pong values when anyone else is downloading if we're configured to if( m_Game->m_GHost->m_PingDuringDownloads || !m_Game->IsDownloading( ) ) { m_Pings.push_back( GetTicks( ) - Pong ); if( m_Pings.size( ) > 20 ) m_Pings.erase( m_Pings.begin( ) ); } } } m_Game->EventPlayerPongToHost( this, Pong ); break; } } else if( Packet->GetPacketType( ) == GPS_HEADER_CONSTANT ) { BYTEARRAY Data = Packet->GetData( ); if( Packet->GetID( ) == CGPSProtocol :: GPS_INIT ) { if( m_Game->m_GHost->m_Reconnect ) { m_GProxy = true; m_Socket->PutBytes( m_Game->m_GHost->m_GPSProtocol->SEND_GPSS_INIT( m_Game->m_GHost->m_ReconnectPort, m_PID, m_GProxyReconnectKey, m_Game->GetGProxyEmptyActions( ) ) ); CONSOLE_Print( "[GAME: " + m_Game->GetGameName( ) + "] player [" + m_Name + "] is using GProxy++" ); } else { // todotodo: send notice that we're not permitting reconnects // note: GProxy++ should never send a GPS_INIT message if bot_reconnect = 0 because we don't advertise the game with invalid map dimensions // but it would be nice to cover this case anyway } } else if( Packet->GetID( ) == CGPSProtocol :: GPS_RECONNECT ) { // this is handled in ghost.cpp } else if( Packet->GetID( ) == CGPSProtocol :: GPS_ACK && Data.size( ) == 8 ) { uint32_t LastPacket = UTIL_ByteArrayToUInt32( Data, false, 4 ); uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); if( LastPacket > PacketsAlreadyUnqueued ) { uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) PacketsToUnqueue = m_GProxyBuffer.size( ); while( PacketsToUnqueue > 0 ) { m_GProxyBuffer.pop( ); --PacketsToUnqueue; } } } } delete Packet; } }
CIncomingGameHost *CBNETProtocol :: RECEIVE_SID_GETADVLISTEX( BYTEARRAY data ) { // DEBUG_Print( "RECEIVED SID_GETADVLISTEX" ); // DEBUG_Print( data ); // 2 bytes -> Header // 2 bytes -> Length // 4 bytes -> GamesFound // if( GamesFound > 0 ) // 10 bytes -> ??? // 2 bytes -> Port // 4 bytes -> IP // null term string -> GameName // 2 bytes -> ??? // 8 bytes -> HostCounter if( ValidateLength( data ) && data.size( ) >= 8 ) { BYTEARRAY GamesFound = BYTEARRAY( data.begin( ) + 4, data.begin( ) + 8 ); /* if (UTIL_ByteArrayToUInt32( GamesFound, false )>1) { string sIP =""; string GN =""; uint32_t PR; uint32_t GF = UTIL_ByteArrayToUInt32( GamesFound, false ); uint32_t Idx = 18; uint32_t Idx2 = 0; for (uint32_t i=0;i<GF;i++) { BYTEARRAY Port = BYTEARRAY( data.begin( ) + Idx, data.begin( ) + 2+Idx ); BYTEARRAY IP = BYTEARRAY( data.begin( ) + 2+Idx, data.begin( ) + 6+Idx ); BYTEARRAY GameName = UTIL_ExtractCString( data, 6+Idx ); Idx2 = GameName.size( ); Idx = Idx + Idx2+ 11; GN = string( GameName.begin( ), GameName.end( ) ); PR = UTIL_ByteArrayToUInt16( Port, false ); CONSOLE_Print("[GHOST] Game "+UTIL_ToString(i)+ " "+ GN + " "+sIP+" "+UTIL_ToString(PR)); } } else */ if( UTIL_ByteArrayToUInt32( GamesFound, false ) > 0 && data.size( ) >= 25 ) { BYTEARRAY Port = BYTEARRAY( data.begin( ) + 18, data.begin( ) + 20 ); BYTEARRAY IP = BYTEARRAY( data.begin( ) + 20, data.begin( ) + 24 ); BYTEARRAY GameName = UTIL_ExtractCString( data, 24 ); if( data.size( ) >= GameName.size( ) + 35 ) { BYTEARRAY HostCounter; HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 27, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 29, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 31, true ) ); HostCounter.push_back( UTIL_ExtractHex( data, GameName.size( ) + 33, true ) ); return new CIncomingGameHost( IP, UTIL_ByteArrayToUInt16( Port, false ), string( GameName.begin( ), GameName.end( ) ), HostCounter ); } } } return NULL; }
void CGamePlayer :: ProcessPackets( ) { if( !m_Socket ) return; CIncomingAction *Action = NULL; CIncomingChatPlayer *ChatPlayer = NULL; CIncomingMapSize *MapSize = NULL; uint32_t CheckSum = 0; uint32_t Pong = 0; // process all the received packets in the m_Packets queue while( !m_Packets.empty( ) ) { CCommandPacket *Packet = m_Packets.front( ); m_Packets.pop( ); if( Packet->GetPacketType( ) == W3GS_HEADER_CONSTANT ) { switch( Packet->GetID( ) ) { case CGameProtocol :: W3GS_LEAVEGAME: m_Game->EventPlayerLeft( this, m_Protocol->RECEIVE_W3GS_LEAVEGAME( Packet->GetData( ) ) ); break; case CGameProtocol :: W3GS_GAMELOADED_SELF: if( m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF( Packet->GetData( ) ) ) { if( !m_FinishedLoading && m_Game->GetGameLoading( ) ) { m_FinishedLoading = true; m_FinishedLoadingTicks = GetTicks( ); m_Game->EventPlayerLoaded( this ); } else { // we received two W3GS_GAMELOADED_SELF packets from this player! } } break; case CGameProtocol :: W3GS_OUTGOING_ACTION: Action = m_Protocol->RECEIVE_W3GS_OUTGOING_ACTION( Packet->GetData( ), m_PID ); if( Action ) m_Game->EventPlayerAction( this, Action ); // don't delete Action here because the game is going to store it in a queue and delete it later break; case CGameProtocol :: W3GS_OUTGOING_KEEPALIVE: CheckSum = m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE( Packet->GetData( ) ); m_CheckSums.push( CheckSum ); m_SyncCounter++; m_Game->EventPlayerKeepAlive( this, CheckSum ); break; case CGameProtocol :: W3GS_CHAT_TO_HOST: ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST( Packet->GetData( ) ); if( ChatPlayer ) { // determine if we should auto-mute this player if( ChatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGE || ChatPlayer->GetType( ) == CIncomingChatPlayer :: CTH_MESSAGEEXTRA ) { m_MuteMessages.push_back( GetTicks( ) ); if( m_MuteMessages.size( ) > 7 ) m_MuteMessages.erase( m_MuteMessages.begin( ) ); uint32_t RecentCount = 0; for( unsigned int i = 0; i < m_MuteMessages.size( ); ++i ) { if( GetTicks( ) - m_MuteMessages[i] < 7000 ) RecentCount++; } if( !GetMuted( ) && RecentCount >= 7 ) { SetMuted( true ); m_MutedAuto = true; m_Game->SendAllChat( "[" + m_Name + "] has been automatically muted for spamming. (You will be unmuted momentarily, but please do not spam again!)" ); m_MuteMessages.clear( ); } //now check for flamers if( m_Game->m_GHost->FlameCheck( ChatPlayer->GetMessage( ) ) ) { m_FlameMessages.push_back( GetTicks( ) ); if( m_FlameMessages.size( ) > 10 ) m_FlameMessages.erase( m_FlameMessages.begin( ) ); RecentCount = 0; for( unsigned int i = 0; i < m_FlameMessages.size( ); ++i ) { if( GetTicks( ) - m_FlameMessages[i] < 80000 ) RecentCount++; } if( RecentCount >= 4 ) { m_Game->SendAllChat( "Use "+string(1, m_Game->m_GHost->m_CommandTrigger)+"ignore <playername> to ignore players (for example, if they are flaming); partial names work. Don't flame back!" ); m_FlameMessages.clear( ); } else if( RecentCount >= 3 ) { m_Game->SendAllChat( "[Calm] has refilled [" + GetName() + "]'s cookie jar. [" + GetName() + "] now has three cookies (try "+string(1, m_Game->m_GHost->m_CommandTrigger)+"eat)!"); SetCookies(3); } } } m_Game->EventPlayerChatToHost( this, ChatPlayer ); } delete ChatPlayer; ChatPlayer = NULL; break; case CGameProtocol :: W3GS_DROPREQ: // todotodo: no idea what's in this packet if( !m_DropVote ) { m_DropVote = true; m_Game->EventPlayerDropRequest( this ); } break; case CGameProtocol :: W3GS_MAPSIZE: m_ConnectionState = 2; m_ConnectionTime = GetTicks( ); MapSize = m_Protocol->RECEIVE_W3GS_MAPSIZE( Packet->GetData( ), m_Game->m_GHost->m_Map->GetMapSize( ) ); if( MapSize ) m_Game->EventPlayerMapSize( this, MapSize ); delete MapSize; MapSize = NULL; break; case CGameProtocol :: W3GS_PONG_TO_HOST: Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST( Packet->GetData( ) ); // we discard pong values of 1 // the client sends one of these when connecting plus we return 1 on error to kill two birds with one stone if( Pong != 1 ) { // we also discard pong values when we're downloading because they're almost certainly inaccurate // this statement also gives the player a 5 second grace period after downloading the map to allow queued (i.e. delayed) ping packets to be ignored if( !m_DownloadStarted || ( m_DownloadFinished && GetTime( ) - m_FinishedDownloadingTime >= 5 ) ) { // we also discard pong values when anyone else is downloading if we're configured to if( m_Game->m_GHost->m_PingDuringDownloads || !m_Game->IsDownloading( ) ) { m_Pings.push_back( GetTicks( ) - Pong ); if( m_Pings.size( ) > 20 ) m_Pings.erase( m_Pings.begin( ) ); } } } m_Game->EventPlayerPongToHost( this, Pong ); break; } } else if( Packet->GetPacketType( ) == GPS_HEADER_CONSTANT ) { BYTEARRAY Data = Packet->GetData( ); if( Packet->GetID( ) == CGPSProtocol :: GPS_INIT ) { if( m_Game->m_GHost->m_Reconnect ) { m_GProxy = true; m_Socket->PutBytes( m_Game->m_GHost->m_GPSProtocol->SEND_GPSS_INIT( m_Game->m_GHost->m_ReconnectPort, m_PID, m_GProxyReconnectKey, m_Game->GetGProxyEmptyActions( ) ) ); CONSOLE_Print( "[GAME: " + m_Game->GetGameName( ) + "] player [" + m_Name + "] is using GProxy++" ); } else { // todotodo: send notice that we're not permitting reconnects // note: GProxy++ should never send a GPS_INIT message if bot_reconnect = 0 because we don't advertise the game with invalid map dimensions // but it would be nice to cover this case anyway } } else if( Packet->GetID( ) == CGPSProtocol :: GPS_RECONNECT ) { // this is handled in ghost.cpp } else if( Packet->GetID( ) == CGPSProtocol :: GPS_ACK && Data.size( ) == 8 ) { uint32_t LastPacket = UTIL_ByteArrayToUInt32( Data, false, 4 ); uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); if( LastPacket > PacketsAlreadyUnqueued ) { uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) PacketsToUnqueue = m_GProxyBuffer.size( ); while( PacketsToUnqueue > 0 ) { m_GProxyBuffer.pop( ); PacketsToUnqueue--; } } } } delete Packet; } }
bool CGamePlayer :: Update( void *fd ) { uint32_t Time = GetTime( ); // wait 4 seconds after joining before sending the /whois or /w // if we send the /whois too early battle.net may not have caught up with where the player is and return erroneous results if( m_WhoisShouldBeSent && !m_Spoofed && !m_WhoisSent && !m_JoinedRealm.empty( ) && Time - m_JoinTime >= 4 ) { // todotodo: we could get kicked from battle.net for sending a command with invalid characters, do some basic checking for( vector<CBNET *> :: iterator i = m_Game->m_Aura->m_BNETs.begin( ); i != m_Game->m_Aura->m_BNETs.end( ); ++i ) { if( (*i)->GetServer( ) == m_JoinedRealm ) { if( m_Game->GetGameState( ) == GAME_PUBLIC || (*i)->GetPasswordHashType( ) == "pvpgn" ) (*i)->QueueChatCommand( "/whois " + m_Name ); else if( m_Game->GetGameState( ) == GAME_PRIVATE ) (*i)->QueueChatCommand( m_Game->m_Aura->m_Language->SpoofCheckByReplying( ), m_Name, true, false ); } } m_WhoisSent = true; } // check for socket timeouts // if we don't receive anything from a player for 35 seconds we can assume they've dropped // this works because in the lobby we send pings every 5 seconds and expect a response to each one // and in the game the Warcraft 3 client sends keepalives frequently (at least once per second it looks like) if( m_Socket && Time - m_Socket->GetLastRecv( ) >= 35 ) m_Game->EventPlayerDisconnectTimedOut( this ); // GProxy++ acks if( m_GProxy && Time - m_LastGProxyAckTime >= 10 ) { if( m_Socket ) m_Socket->PutBytes( m_Game->m_Aura->m_GPSProtocol->SEND_GPSS_ACK( m_TotalPacketsReceived ) ); m_LastGProxyAckTime = Time; } // base class update if( m_Socket->GetConnected( ) ) m_Socket->DoRecv( (fd_set *)fd ); // extract as many packets as possible from the socket's receive buffer and process them string *RecvBuffer = m_Socket->GetBytes( ); BYTEARRAY Bytes = UTIL_CreateByteArray( (unsigned char *)RecvBuffer->c_str( ), RecvBuffer->size( ) ); // a packet is at least 4 bytes so loop as long as the buffer contains 4 bytes CIncomingAction *Action = NULL; CIncomingChatPlayer *ChatPlayer = NULL; CIncomingMapSize *MapSize = NULL; uint32_t CheckSum = 0, Pong = 0; while( Bytes.size( ) >= 4 ) { if( Bytes[0] == W3GS_HEADER_CONSTANT ) { // bytes 2 and 3 contain the length of the packet uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); if( Length >= 4 ) { if( Bytes.size( ) >= Length ) { // m_Packets.push( new CCommandPacket( Bytes[0], Bytes[1], BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); switch( Bytes[1] ) { case CGameProtocol :: W3GS_LEAVEGAME: m_Game->EventPlayerLeft( this, m_Protocol->RECEIVE_W3GS_LEAVEGAME( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ); break; case CGameProtocol :: W3GS_GAMELOADED_SELF: if( m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ) ) { if( !m_FinishedLoading ) { m_FinishedLoading = true; m_FinishedLoadingTicks = GetTicks( ); m_Game->EventPlayerLoaded( this ); } } break; case CGameProtocol :: W3GS_OUTGOING_ACTION: Action = m_Protocol->RECEIVE_W3GS_OUTGOING_ACTION( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ), m_PID ); if( Action ) m_Game->EventPlayerAction( this, Action ); // don't delete Action here because the game is going to store it in a queue and delete it later break; case CGameProtocol :: W3GS_OUTGOING_KEEPALIVE: CheckSum = m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ); m_CheckSums.push( CheckSum ); ++m_SyncCounter; m_Game->EventPlayerKeepAlive( ); break; case CGameProtocol :: W3GS_CHAT_TO_HOST: ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ); if( ChatPlayer ) m_Game->EventPlayerChatToHost( this, ChatPlayer ); delete ChatPlayer; ChatPlayer = NULL; break; case CGameProtocol :: W3GS_DROPREQ: if( !m_DropVote ) { m_DropVote = true; m_Game->EventPlayerDropRequest( this ); } break; case CGameProtocol :: W3GS_MAPSIZE: MapSize = m_Protocol->RECEIVE_W3GS_MAPSIZE( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ), m_Game->m_Aura->m_Map->GetMapSize( ) ); if( MapSize ) m_Game->EventPlayerMapSize( this, MapSize ); delete MapSize; MapSize = NULL; break; case CGameProtocol :: W3GS_PONG_TO_HOST: Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST( BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ) ); // we discard pong values of 1 // the client sends one of these when connecting plus we return 1 on error to kill two birds with one stone if( Pong != 1 ) { // we also discard pong values when we're downloading because they're almost certainly inaccurate // this statement also gives the player a 5 second grace period after downloading the map to allow queued (i.e. delayed) ping packets to be ignored if( !m_DownloadStarted || ( m_DownloadFinished && GetTime( ) - m_FinishedDownloadingTime >= 5 ) ) { // we also discard pong values when anyone else is downloading if we're configured to if( !m_Game->IsDownloading( ) ) { m_Pings.push_back( GetTicks( ) - Pong ); if( m_Pings.size( ) > 20 ) m_Pings.erase( m_Pings.begin( ) ); } } } m_Game->EventPlayerPongToHost( this, Pong ); break; } *RecvBuffer = RecvBuffer->substr( Length ); Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); } else break; } } else if( Bytes[0] == GPS_HEADER_CONSTANT ) { uint16_t Length = UTIL_ByteArrayToUInt16( Bytes, false, 2 ); if( Length >= 4 ) { if( Bytes.size( ) >= Length ) { BYTEARRAY Data = BYTEARRAY( Bytes.begin( ), Bytes.begin( ) + Length ); if( Bytes[1] == CGPSProtocol :: GPS_ACK && Data.size( ) == 8 ) { uint32_t LastPacket = UTIL_ByteArrayToUInt32( Data, false, 4 ); uint32_t PacketsAlreadyUnqueued = m_TotalPacketsSent - m_GProxyBuffer.size( ); if( LastPacket > PacketsAlreadyUnqueued ) { uint32_t PacketsToUnqueue = LastPacket - PacketsAlreadyUnqueued; if( PacketsToUnqueue > m_GProxyBuffer.size( ) ) PacketsToUnqueue = m_GProxyBuffer.size( ); while( PacketsToUnqueue > 0 ) { m_GProxyBuffer.pop( ); --PacketsToUnqueue; } } } else if( Bytes[1] == CGPSProtocol :: GPS_INIT ) { if( m_Game->m_Aura->m_Reconnect ) { m_GProxy = true; m_Socket->PutBytes( m_Game->m_Aura->m_GPSProtocol->SEND_GPSS_INIT( m_Game->m_Aura->m_ReconnectPort, m_PID, m_GProxyReconnectKey, m_Game->GetGProxyEmptyActions( ) ) ); Print( "[GAME: " + m_Game->GetGameName( ) + "] player [" + m_Name + "] is using GProxy++" ); } } *RecvBuffer = RecvBuffer->substr( Length ); Bytes = BYTEARRAY( Bytes.begin( ) + Length, Bytes.end( ) ); } else break; } } } bool Deleting; if( m_GProxy && m_Game->GetGameLoaded( ) ) Deleting = m_DeleteMe; else Deleting = m_DeleteMe || m_Socket->HasError( ) || !m_Socket->GetConnected( ); // try to find out why we're requesting deletion // in cases other than the ones covered here m_LeftReason should have been set when m_DeleteMe was set if( m_Socket ) { if( m_Socket->HasError( ) ) { m_Game->EventPlayerDisconnectSocketError( this ); m_Socket->Reset( ); } else if( !m_Socket->GetConnected( ) ) { m_Game->EventPlayerDisconnectConnectionClosed( this ); m_Socket->Reset( ); } } return Deleting; }