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; }
BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY clientPasswordProof ) { BYTEARRAY packet; if( clientPasswordProof.size( ) == 20 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_AUTH_ACCOUNTLOGONPROOF ); // SID_AUTH_ACCOUNTLOGONPROOF packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, clientPasswordProof ); // Client Password Proof AssignLength( packet ); } else cout << "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON\n"; return packet; }
BYTEARRAY CBNETProtocol :: SEND_SID_PING( BYTEARRAY pingValue ) { BYTEARRAY packet; if( pingValue.size() == 4 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_PING ); // SID_PING packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, pingValue ); // Ping Value AssignLength( packet ); } else cout << "[BNETPROTO] invalid parameters passed to SEND_SID_PING\n"; return packet; }
BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGON( BYTEARRAY clientPublicKey, string accountName ) { BYTEARRAY packet; if( clientPublicKey.size( ) == 32 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_AUTH_ACCOUNTLOGON ); // SID_AUTH_ACCOUNTLOGON packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, clientPublicKey ); // Client Key UTIL_AppendByteArrayFast( packet, accountName ); // Account Name AssignLength( packet ); } else cout << "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON\n"; return packet; }
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; }
bool CGameProtocol :: RECEIVE_W3GS_GAMEINFO ( BYTEARRAY data, unsigned char war3Version) { uint32_t ProductID = 1462982736; // "W3XP" uint32_t Version = war3Version; if( ValidateLength( data ) && data.size( ) >= 16 ) { if( UTIL_ByteArrayToUInt32( data, false, 4 ) == ProductID ) { if( UTIL_ByteArrayToUInt32( data, false, 8 ) == Version ) { if( UTIL_ByteArrayToUInt32( data, false, 12 ) == 0 ) return true; } } } return false; }
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; }
string CPotentialPlayer :: GetExternalIPString( ) { BYTEARRAY IP; string EIP; if( m_Socket ) { bool local=m_LAN; if (!m_LANSet) { IP= m_Socket->GetIP( ); if (IP.size()>=2) { if (IP[0]==10 && IP[1]==0) local=true; if (IP[0]==10 && IP[1]==1) local=true; if (IP[0]==192 && IP[1]==168) local=true; if (IP[0]==169 && IP[1]==254) local=true; if (IP[0]==8 && IP[1]==0) local=true; if (IP[0]==5) local=true; } if (UTIL_IsLocalIP(IP, m_Game->m_GHost->m_LocalAddresses)) local = true; m_LANSet = true; m_LAN = local; } EIP=m_Socket->GetIPString( ); if (local && !m_Game->m_Config->m_ExternalIP.empty()) { EIP = m_Game->m_Config->m_ExternalIP; } return EIP; } return string( ); }
BYTEARRAY CBNETProtocol :: SEND_SID_PING( BYTEARRAY pingValue ) { BYTEARRAY packet; if( pingValue.size( ) == 4 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_PING ); // SID_PING packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, pingValue ); // Ping Value AssignLength( packet ); } else { CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_PING" ); forward(new CFwdData(FWD_GENERAL, "Invalid parameters passed to SEND_SID_PING", 6, 0)); } // DEBUG_Print( "SENT SID_PING" ); // DEBUG_Print( packet ); return packet; }
void CReplay :: AddTimeSlot2( queue<CIncomingAction *> actions ) { BYTEARRAY Block; Block.push_back( REPLAY_TIMESLOT2 ); UTIL_AppendByteArray( Block, (uint16_t)0, false ); UTIL_AppendByteArray( Block, (uint16_t)0, false ); while( !actions.empty( ) ) { CIncomingAction *Action = actions.front( ); actions.pop( ); Block.push_back( Action->GetPID( ) ); UTIL_AppendByteArray( Block, (uint16_t)Action->GetAction( )->size( ), false ); UTIL_AppendByteArrayFast( Block, *Action->GetAction( ) ); } // assign length BYTEARRAY LengthBytes = UTIL_CreateByteArray( (uint16_t)( Block.size( ) - 3 ), false ); Block[1] = LengthBytes[0]; Block[2] = LengthBytes[1]; m_CompiledBlocks += string( Block.begin( ), Block.end( ) ); }
BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGONPROOF( BYTEARRAY clientPasswordProof ) { BYTEARRAY packet; if( clientPasswordProof.size( ) == 20 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_AUTH_ACCOUNTLOGONPROOF ); // SID_AUTH_ACCOUNTLOGONPROOF packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, clientPasswordProof ); // Client Password Proof AssignLength( packet ); } else { CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON" ); forward(new CFwdData(FWD_GENERAL, "Invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON", 6, 0)); } // DEBUG_Print( "SENT SID_AUTH_ACCOUNTLOGONPROOF" ); // DEBUG_Print( packet ); return packet; }
BYTEARRAY CBNETProtocol :: SEND_SID_AUTH_ACCOUNTLOGON( BYTEARRAY clientPublicKey, string accountName ) { BYTEARRAY packet; if( clientPublicKey.size( ) == 32 ) { packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_AUTH_ACCOUNTLOGON ); // SID_AUTH_ACCOUNTLOGON packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later UTIL_AppendByteArrayFast( packet, clientPublicKey ); // Client Key UTIL_AppendByteArrayFast( packet, accountName ); // Account Name AssignLength( packet ); } else { CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON" ); forward(new CFwdData(FWD_GENERAL, "Invalid parameters passed to SEND_SID_AUTH_ACCOUNTLOGON", 6, 0)); } // DEBUG_Print( "SENT SID_AUTH_ACCOUNTLOGON" ); // DEBUG_Print( packet ); return packet; }
BYTEARRAY CGameProtocol :: SEND_W3GS_CHAT_FROM_HOST( unsigned char fromPID, BYTEARRAY toPIDs, unsigned char flag, BYTEARRAY flagExtra, string message ) { BYTEARRAY packet; if( !toPIDs.empty( ) && !message.empty( ) && message.size( ) < 255 ) { packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant packet.push_back( W3GS_CHAT_FROM_HOST ); // W3GS_CHAT_FROM_HOST packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later packet.push_back( toPIDs.size( ) ); // number of receivers UTIL_AppendByteArrayFast( packet, toPIDs ); // receivers packet.push_back( fromPID ); // sender packet.push_back( flag ); // flag UTIL_AppendByteArrayFast( packet, flagExtra ); // extra flag UTIL_AppendByteArrayFast( packet, message ); // message AssignLength( packet ); } else cout << "[GAMEPROTO] invalid parameters passed to SEND_W3GS_CHAT_FROM_HOST\n"; return packet; }
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; } }
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; } }
bool CAura::Update() { uint32_t NumFDs = 0; // take every socket we own and throw it in one giant select statement so we can block on all sockets int32_t nfds = 0; fd_set fd, send_fd; FD_ZERO(&fd); FD_ZERO(&send_fd); // 1. the current game's server and player sockets if (m_CurrentGame) NumFDs += m_CurrentGame->SetFD(&fd, &send_fd, &nfds); // 2. all running games' player sockets for (auto & game : m_Games) NumFDs += game->SetFD(&fd, &send_fd, &nfds); // 3. all battle.net sockets for (auto & bnet : m_BNETs) NumFDs += bnet->SetFD(&fd, &send_fd, &nfds); // 4. irc socket if (m_IRC) NumFDs += m_IRC->SetFD(&fd, &send_fd, &nfds); // 5. reconnect socket if (m_ReconnectSocket->HasError()) { Print("[AURA] GProxy++ reconnect listener error (" + m_ReconnectSocket->GetErrorString() + ")"); return true; } else { m_ReconnectSocket->SetFD(&fd, &send_fd, &nfds); ++NumFDs; } // 6. reconnect sockets for (auto & socket : m_ReconnectSockets) { socket->SetFD(&fd, &send_fd, &nfds); ++NumFDs; } // before we call select we need to determine how long to block for // 50 ms is the hard maximum int64_t usecBlock = 50000; for (auto & game : m_Games) { if (game->GetNextTimedActionTicks() * 1000 < usecBlock) usecBlock = game->GetNextTimedActionTicks() * 1000; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = static_cast<long int>(usecBlock); struct timeval send_tv; send_tv.tv_sec = 0; send_tv.tv_usec = 0; #ifdef WIN32 select(1, &fd, nullptr, nullptr, &tv); select(1, nullptr, &send_fd, nullptr, &send_tv); #else select(nfds + 1, &fd, nullptr, nullptr, &tv); select(nfds + 1, nullptr, &send_fd, nullptr, &send_tv); #endif if (NumFDs == 0) { // we don't have any sockets (i.e. we aren't connected to battle.net and irc maybe due to a lost connection and there aren't any games running) // select will return immediately and we'll chew up the CPU if we let it loop so just sleep for 200ms to kill some time MILLISLEEP(200); } bool Exit = false; // update running games for (auto i = begin(m_Games); i != end(m_Games);) { if ((*i)->Update(&fd, &send_fd)) { Print2("[AURA] deleting game [" + (*i)->GetGameName() + "]"); EventGameDeleted(*i); delete *i; i = m_Games.erase(i); } else { (*i)->UpdatePost(&send_fd); ++i; } } // update current game if (m_CurrentGame) { if (m_CurrentGame->Update(&fd, &send_fd)) { Print2("[AURA] deleting current game [" + m_CurrentGame->GetGameName() + "]"); delete m_CurrentGame; m_CurrentGame = nullptr; for (auto & bnet : m_BNETs) { bnet->QueueGameUncreate(); bnet->QueueEnterChat(); } } else if (m_CurrentGame) m_CurrentGame->UpdatePost(&send_fd); } // update battle.net connections for (auto & bnet : m_BNETs) { if (bnet->Update(&fd, &send_fd)) Exit = true; } // update irc if (m_IRC && m_IRC->Update(&fd, &send_fd)) Exit = true; // update GProxy++ reliable reconnect sockets CTCPSocket *NewSocket = m_ReconnectSocket->Accept(&fd); if (NewSocket) m_ReconnectSockets.push_back(NewSocket); for (auto i = begin(m_ReconnectSockets); i != end(m_ReconnectSockets);) { if ((*i)->HasError() || !(*i)->GetConnected() || GetTime() - (*i)->GetLastRecv() >= 10) { delete *i; i = m_ReconnectSockets.erase(i); continue; } (*i)->DoRecv(&fd); string *RecvBuffer = (*i)->GetBytes(); const BYTEARRAY Bytes = CreateByteArray((uint8_t *) RecvBuffer->c_str(), RecvBuffer->size()); // a packet is at least 4 bytes if (Bytes.size() >= 4) { if (Bytes[0] == GPS_HEADER_CONSTANT) { // bytes 2 and 3 contain the length of the packet const uint16_t Length = (uint16_t)(Bytes[3] << 8 | Bytes[2]); if (Bytes.size() >= Length) { if (Bytes[1] == CGPSProtocol::GPS_RECONNECT && Length == 13) { const uint32_t ReconnectKey = ByteArrayToUInt32(Bytes, false, 5); const uint32_t LastPacket = ByteArrayToUInt32(Bytes, false, 9); // look for a matching player in a running game CGamePlayer *Match = nullptr; for (auto & game : m_Games) { if (game->GetGameLoaded()) { CGamePlayer *Player = game->GetPlayerFromPID(Bytes[4]); if (Player && Player->GetGProxy() && Player->GetGProxyReconnectKey() == ReconnectKey) { Match = Player; break; } } } if (Match) { // reconnect successful! *RecvBuffer = RecvBuffer->substr(Length); Match->EventGProxyReconnect(*i, LastPacket); i = m_ReconnectSockets.erase(i); continue; } else { (*i)->PutBytes(m_GPSProtocol->SEND_GPSS_REJECT(REJECTGPS_NOTFOUND)); (*i)->DoSend(&send_fd); delete *i; i = m_ReconnectSockets.erase(i); continue; } } else { (*i)->PutBytes(m_GPSProtocol->SEND_GPSS_REJECT(REJECTGPS_INVALID)); (*i)->DoSend(&send_fd); delete *i; i = m_ReconnectSockets.erase(i); continue; } } else { (*i)->PutBytes(m_GPSProtocol->SEND_GPSS_REJECT(REJECTGPS_INVALID)); (*i)->DoSend(&send_fd); delete *i; i = m_ReconnectSockets.erase(i); continue; } } else { (*i)->PutBytes(m_GPSProtocol->SEND_GPSS_REJECT(REJECTGPS_INVALID)); (*i)->DoSend(&send_fd); delete *i; i = m_ReconnectSockets.erase(i); continue; } } (*i)->DoSend(&send_fd); ++i; } return m_Exiting || Exit; }
void CReplay :: BuildReplay( string gameName, string statString ) { CONSOLE_Print( "[REPLAY] building replay" ); uint32_t LanguageID = 0x0012F8B0; BYTEARRAY Replay; Replay.push_back( 16 ); // Unknown (4.0) Replay.push_back( 1 ); // Unknown (4.0) Replay.push_back( 0 ); // Unknown (4.0) Replay.push_back( 0 ); // Unknown (4.0) Replay.push_back( 0 ); // Host RecordID (4.1) Replay.push_back( m_HostPID ); // Host PlayerID (4.1) UTIL_AppendByteArray( Replay, m_HostName ); // Host PlayerName (4.1) Replay.push_back( 1 ); // Host AdditionalSize (4.1) Replay.push_back( 0 ); // Host AdditionalData (4.1) UTIL_AppendByteArray( Replay, gameName ); // GameName (4.2) Replay.push_back( 0 ); // Null (4.0) UTIL_AppendByteArray( Replay, statString ); // StatString (4.3) UTIL_AppendByteArray( Replay, (uint32_t)m_Slots.size( ), false ); // PlayerCount (4.6) Replay.push_back( m_MapGameType ); // GameType (4.7) Replay.push_back( 32 ); // GameType (4.7) Replay.push_back( 73 ); // GameType (4.7) Replay.push_back( 0 ); // GameType (4.7) UTIL_AppendByteArray( Replay, LanguageID, false ); // LanguageID (4.8) // PlayerList (4.9) for( vector<ReplayPlayer> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i).first != m_HostPID ) { Replay.push_back( 22 ); // Player RecordID (4.1) Replay.push_back( (*i).first ); // Player PlayerID (4.1) UTIL_AppendByteArray( Replay, (*i).second ); // Player PlayerName (4.1) Replay.push_back( 1 ); // Player AdditionalSize (4.1) Replay.push_back( 0 ); // Player AdditionalData (4.1) UTIL_AppendByteArray( Replay, (uint32_t)0, false ); // Unknown } } // GameStartRecord (4.10) Replay.push_back( 25 ); // RecordID (4.10) UTIL_AppendByteArray( Replay, (uint16_t)( 7 + m_Slots.size( ) * 9 ), false ); // Size (4.10) Replay.push_back( m_Slots.size( ) ); // NumSlots (4.10) for( unsigned char i = 0; i < m_Slots.size( ); i++ ) UTIL_AppendByteArray( Replay, m_Slots[i].GetByteArray( ) ); UTIL_AppendByteArray( Replay, m_RandomSeed, false ); // RandomSeed (4.10) Replay.push_back( m_SelectMode ); // SelectMode (4.10) Replay.push_back( m_StartSpotCount ); // StartSpotCount (4.10) // ReplayData (5.0) Replay.push_back( REPLAY_FIRSTSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); Replay.push_back( REPLAY_SECONDSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); // leavers during loading need to be stored between the second and third start blocks while( !m_LoadingBlocks.empty( ) ) { UTIL_AppendByteArray( Replay, m_LoadingBlocks.front( ) ); m_LoadingBlocks.pop( ); } Replay.push_back( REPLAY_THIRDSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); // initialize replay length to zero // we'll accumulate the replay length as we iterate through the timeslots // this is necessary because we might be discarding some timeslots due to not enough checksums and the replay length needs to be accurate m_ReplayLength = 0; uint32_t TimeSlotsDiscarded = 0; while( !m_Blocks.empty( ) ) { BYTEARRAY Block = m_Blocks.front( ); m_Blocks.pop( ); if( Block.size( ) >= 5 && Block[0] == REPLAY_TIMESLOT ) { if( m_CheckSums.empty( ) ) { TimeSlotsDiscarded++; continue; } // append timeslot UTIL_AppendByteArray( Replay, Block ); // append checksum BYTEARRAY CheckSum; CheckSum.push_back( REPLAY_CHECKSUM ); CheckSum.push_back( 4 ); UTIL_AppendByteArray( CheckSum, m_CheckSums.front( ), false ); m_CheckSums.pop( ); UTIL_AppendByteArray( Replay, CheckSum ); // accumulate replay length m_ReplayLength += UTIL_ByteArrayToUInt16( Block, false, 3 ); } else UTIL_AppendByteArray( Replay, Block ); } if( TimeSlotsDiscarded > 0 ) CONSOLE_Print( "[REPLAY] ran out of checksums, discarded " + UTIL_ToString( TimeSlotsDiscarded ) + " timeslots" ); // done m_Decompressed = string( Replay.begin( ), Replay.end( ) ); }
void CMap :: Load( CConfig *CFG, string nCFGFile ) { m_Valid = true; m_CFGFile = nCFGFile; m_GHost->UDPChatSend("|cfg "+nCFGFile); // load the map data m_MapLocalPath = CFG->GetString( "map_localpath", string( ) ); m_MapData.clear( ); if( !m_MapLocalPath.empty( ) ) m_MapData = UTIL_FileRead( m_GHost->m_MapPath + m_MapLocalPath ); // load the map MPQ string MapMPQFileName = m_GHost->m_MapPath + m_MapLocalPath; HANDLE MapMPQ; bool MapMPQReady = false; if( SFileOpenArchive( MapMPQFileName.c_str( ), 0, MPQ_OPEN_FORCE_MPQ_V1, &MapMPQ ) ) { CONSOLE_Print( "[MAP] loading MPQ file [" + MapMPQFileName + "]" ); MapMPQReady = true; } else CONSOLE_Print( "[MAP] warning - unable to load MPQ file [" + MapMPQFileName + "]" ); // try to calculate map_size, map_info, map_crc, map_sha1 BYTEARRAY MapSize; BYTEARRAY MapInfo; BYTEARRAY MapCRC; BYTEARRAY MapSHA1; if( !m_MapData.empty( ) ) { m_GHost->m_SHA->Reset( ); // calculate map_size MapSize = UTIL_CreateByteArray( (uint32_t)m_MapData.size( ), false ); CONSOLE_Print( "[MAP] calculated map_size = " + UTIL_ToString( MapSize[0] ) + " " + UTIL_ToString( MapSize[1] ) + " " + UTIL_ToString( MapSize[2] ) + " " + UTIL_ToString( MapSize[3] ) ); // calculate map_info (this is actually the CRC) MapInfo = UTIL_CreateByteArray( (uint32_t)m_GHost->m_CRC->FullCRC( (unsigned char *)m_MapData.c_str( ), m_MapData.size( ) ), false ); CONSOLE_Print( "[MAP] calculated map_info = " + UTIL_ToString( MapInfo[0] ) + " " + UTIL_ToString( MapInfo[1] ) + " " + UTIL_ToString( MapInfo[2] ) + " " + UTIL_ToString( MapInfo[3] ) ); // calculate map_crc (this is not the CRC) and map_sha1 // a big thank you to Strilanc for figuring the map_crc algorithm out string CommonJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "common.j" ); if( CommonJ.empty( ) ) CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "common.j]" ); else { string BlizzardJ = UTIL_FileRead( m_GHost->m_MapCFGPath + "blizzard.j" ); if( BlizzardJ.empty( ) ) CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - unable to read file [" + m_GHost->m_MapCFGPath + "blizzard.j]" ); else { uint32_t Val = 0; // update: it's possible for maps to include their own copies of common.j and/or blizzard.j // this code now overrides the default copies if required bool OverrodeCommonJ = false; bool OverrodeBlizzardJ = false; if( MapMPQReady ) { HANDLE SubFile; // override common.j if( SFileOpenFileEx( MapMPQ, "Scripts\\common.j", 0, &SubFile ) ) { uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); if( FileLength > 0 && FileLength != 0xFFFFFFFF ) { char *SubFileData = new char[FileLength]; DWORD BytesRead = 0; if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) { CONSOLE_Print( "[MAP] overriding default common.j with map copy while calculating map_crc/sha1" ); OverrodeCommonJ = true; Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ); m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); } delete [] SubFileData; } SFileCloseFile( SubFile ); } } if( !OverrodeCommonJ ) { Val = Val ^ XORRotateLeft( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) ); m_GHost->m_SHA->Update( (unsigned char *)CommonJ.c_str( ), CommonJ.size( ) ); } if( MapMPQReady ) { HANDLE SubFile; // override blizzard.j if( SFileOpenFileEx( MapMPQ, "Scripts\\blizzard.j", 0, &SubFile ) ) { uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); if( FileLength > 0 && FileLength != 0xFFFFFFFF ) { char *SubFileData = new char[FileLength]; DWORD BytesRead = 0; if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) { CONSOLE_Print( "[MAP] overriding default blizzard.j with map copy while calculating map_crc/sha1" ); OverrodeBlizzardJ = true; Val = Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ); m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); } delete [] SubFileData; } SFileCloseFile( SubFile ); } } if( !OverrodeBlizzardJ ) { Val = Val ^ XORRotateLeft( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) ); m_GHost->m_SHA->Update( (unsigned char *)BlizzardJ.c_str( ), BlizzardJ.size( ) ); } Val = ROTL( Val, 3 ); Val = ROTL( Val ^ 0x03F1379E, 3 ); m_GHost->m_SHA->Update( (unsigned char *)"\x9E\x37\xF1\x03", 4 ); if( MapMPQReady ) { vector<string> FileList; FileList.push_back( "war3map.j" ); FileList.push_back( "scripts\\war3map.j" ); FileList.push_back( "war3map.w3e" ); FileList.push_back( "war3map.wpm" ); FileList.push_back( "war3map.doo" ); FileList.push_back( "war3map.w3u" ); FileList.push_back( "war3map.w3b" ); FileList.push_back( "war3map.w3d" ); FileList.push_back( "war3map.w3a" ); FileList.push_back( "war3map.w3q" ); bool FoundScript = false; for( vector<string> :: iterator i = FileList.begin( ); i != FileList.end( ); i++ ) { // don't use scripts\war3map.j if we've already used war3map.j (yes, some maps have both but only war3map.j is used) if( FoundScript && *i == "scripts\\war3map.j" ) continue; HANDLE SubFile; if( SFileOpenFileEx( MapMPQ, (*i).c_str( ), 0, &SubFile ) ) { uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); if( FileLength > 0 && FileLength != 0xFFFFFFFF ) { char *SubFileData = new char[FileLength]; DWORD BytesRead = 0; if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) { if( *i == "war3map.j" || *i == "scripts\\war3map.j" ) FoundScript = true; Val = ROTL( Val ^ XORRotateLeft( (unsigned char *)SubFileData, BytesRead ), 3 ); m_GHost->m_SHA->Update( (unsigned char *)SubFileData, BytesRead ); // DEBUG_Print( "*** found: " + *i ); } delete [] SubFileData; } SFileCloseFile( SubFile ); } else { // DEBUG_Print( "*** not found: " + *i ); } } if( !FoundScript ) CONSOLE_Print( "[MAP] couldn't find war3map.j or scripts\\war3map.j in MPQ file, calculated map_crc/sha1 is probably wrong" ); MapCRC = UTIL_CreateByteArray( Val, false ); CONSOLE_Print( "[MAP] calculated map_crc = " + UTIL_ToString( MapCRC[0] ) + " " + UTIL_ToString( MapCRC[1] ) + " " + UTIL_ToString( MapCRC[2] ) + " " + UTIL_ToString( MapCRC[3] ) ); m_GHost->m_SHA->Final( ); unsigned char SHA1[20]; memset( SHA1, 0, sizeof( unsigned char ) * 20 ); m_GHost->m_SHA->GetHash( SHA1 ); MapSHA1 = UTIL_CreateByteArray( SHA1, 20 ); CONSOLE_Print( "[MAP] calculated map_sha1 = " + UTIL_ByteArrayToDecString( MapSHA1 ) ); } else CONSOLE_Print( "[MAP] unable to calculate map_crc/sha1 - map MPQ file not loaded" ); } } } else CONSOLE_Print( "[MAP] no map data available, using config file for map_size, map_info, map_crc, map_sha1" ); // try to calculate map_width, map_height, map_slot<x>, map_numplayers, map_numteams BYTEARRAY MapWidth; BYTEARRAY MapHeight; uint32_t MapNumPlayers = 0; uint32_t MapNumTeams = 0; uint32_t MapFilterType = MAPFILTER_TYPE_SCENARIO; vector<CGameSlot> Slots; if( !m_MapData.empty( ) ) { if( MapMPQReady ) { HANDLE SubFile; if( SFileOpenFileEx( MapMPQ, "war3map.w3i", 0, &SubFile ) ) { uint32_t FileLength = SFileGetFileSize( SubFile, NULL ); if( FileLength > 0 && FileLength != 0xFFFFFFFF ) { char *SubFileData = new char[FileLength]; DWORD BytesRead = 0; if( SFileReadFile( SubFile, SubFileData, FileLength, &BytesRead ) ) { istringstream ISS( string( SubFileData, BytesRead ) ); // war3map.w3i format found at http://www.wc3campaigns.net/tools/specs/index.html by Zepir/PitzerMike string GarbageString; uint32_t FileFormat; uint32_t RawMapWidth; uint32_t RawMapHeight; uint32_t RawMapFlags; uint32_t RawMapNumPlayers; uint32_t RawMapNumTeams; ISS.read( (char *)&FileFormat, 4 ); // file format (18 = ROC, 25 = TFT) if( FileFormat == 18 || FileFormat == 25 ) { ISS.seekg( 4, ios :: cur ); // number of saves ISS.seekg( 4, ios :: cur ); // editor version getline( ISS, GarbageString, '\0' ); // map name getline( ISS, GarbageString, '\0' ); // map author getline( ISS, GarbageString, '\0' ); // map description getline( ISS, GarbageString, '\0' ); // players recommended ISS.seekg( 32, ios :: cur ); // camera bounds ISS.seekg( 16, ios :: cur ); // camera bounds complements ISS.read( (char *)&RawMapWidth, 4 ); // map width ISS.read( (char *)&RawMapHeight, 4 ); // map height ISS.read( (char *)&RawMapFlags, 4 ); // flags ISS.seekg( 1, ios :: cur ); // map main ground type if( FileFormat == 18 ) ISS.seekg( 4, ios :: cur ); // campaign background number else if( FileFormat == 25 ) { ISS.seekg( 4, ios :: cur ); // loading screen background number getline( ISS, GarbageString, '\0' ); // path of custom loading screen model } getline( ISS, GarbageString, '\0' ); // map loading screen text getline( ISS, GarbageString, '\0' ); // map loading screen title getline( ISS, GarbageString, '\0' ); // map loading screen subtitle if( FileFormat == 18 ) ISS.seekg( 4, ios :: cur ); // map loading screen number else if( FileFormat == 25 ) { ISS.seekg( 4, ios :: cur ); // used game data set getline( ISS, GarbageString, '\0' ); // prologue screen path } getline( ISS, GarbageString, '\0' ); // prologue screen text getline( ISS, GarbageString, '\0' ); // prologue screen title getline( ISS, GarbageString, '\0' ); // prologue screen subtitle if( FileFormat == 25 ) { ISS.seekg( 4, ios :: cur ); // uses terrain fog ISS.seekg( 4, ios :: cur ); // fog start z height ISS.seekg( 4, ios :: cur ); // fog end z height ISS.seekg( 4, ios :: cur ); // fog density ISS.seekg( 1, ios :: cur ); // fog red value ISS.seekg( 1, ios :: cur ); // fog green value ISS.seekg( 1, ios :: cur ); // fog blue value ISS.seekg( 1, ios :: cur ); // fog alpha value ISS.seekg( 4, ios :: cur ); // global weather id getline( ISS, GarbageString, '\0' ); // custom sound environment ISS.seekg( 1, ios :: cur ); // tileset id of the used custom light environment ISS.seekg( 1, ios :: cur ); // custom water tinting red value ISS.seekg( 1, ios :: cur ); // custom water tinting green value ISS.seekg( 1, ios :: cur ); // custom water tinting blue value ISS.seekg( 1, ios :: cur ); // custom water tinting alpha value } ISS.read( (char *)&RawMapNumPlayers, 4 ); // number of players uint32_t ClosedSlots = 0; for( uint32_t i = 0; i < RawMapNumPlayers; i++ ) { CGameSlot Slot( 0, 255, SLOTSTATUS_OPEN, 0, 0, 1, SLOTRACE_RANDOM ); uint32_t Colour; uint32_t Status; uint32_t Race; ISS.read( (char *)&Colour, 4 ); // colour Slot.SetColour( Colour ); ISS.read( (char *)&Status, 4 ); // status if( Status == 1 ) Slot.SetSlotStatus( SLOTSTATUS_OPEN ); else if( Status == 2 ) { Slot.SetSlotStatus( SLOTSTATUS_OCCUPIED ); Slot.SetComputer( 1 ); Slot.SetComputerType( SLOTCOMP_NORMAL ); } else { Slot.SetSlotStatus( SLOTSTATUS_CLOSED ); ClosedSlots++; } ISS.read( (char *)&Race, 4 ); // race if( Race == 1 ) Slot.SetRace( SLOTRACE_HUMAN ); else if( Race == 2 ) Slot.SetRace( SLOTRACE_ORC ); else if( Race == 3 ) Slot.SetRace( SLOTRACE_UNDEAD ); else if( Race == 4 ) Slot.SetRace( SLOTRACE_NIGHTELF ); else Slot.SetRace( SLOTRACE_RANDOM ); ISS.seekg( 4, ios :: cur ); // fixed start position getline( ISS, GarbageString, '\0' ); // player name ISS.seekg( 4, ios :: cur ); // start position x ISS.seekg( 4, ios :: cur ); // start position y ISS.seekg( 4, ios :: cur ); // ally low priorities ISS.seekg( 4, ios :: cur ); // ally high priorities if( Slot.GetSlotStatus( ) != SLOTSTATUS_CLOSED ) Slots.push_back( Slot ); } ISS.read( (char *)&RawMapNumTeams, 4 ); // number of teams for( uint32_t i = 0; i < RawMapNumTeams; i++ ) { uint32_t Flags; uint32_t PlayerMask; ISS.read( (char *)&Flags, 4 ); // flags ISS.read( (char *)&PlayerMask, 4 ); // player mask for( unsigned char j = 0; j < 12; j++ ) { if( PlayerMask & 1 ) { for( vector<CGameSlot> :: iterator k = Slots.begin( ); k != Slots.end( ); k++ ) { if( (*k).GetColour( ) == j ) (*k).SetTeam( i ); } } PlayerMask >>= 1; } getline( ISS, GarbageString, '\0' ); // team name } MapWidth = UTIL_CreateByteArray( (uint16_t)RawMapWidth, false ); CONSOLE_Print( "[MAP] calculated map_width = " + UTIL_ToString( MapWidth[0] ) + " " + UTIL_ToString( MapWidth[1] ) ); MapHeight = UTIL_CreateByteArray( (uint16_t)RawMapHeight, false ); CONSOLE_Print( "[MAP] calculated map_height = " + UTIL_ToString( MapHeight[0] ) + " " + UTIL_ToString( MapHeight[1] ) ); MapNumPlayers = RawMapNumPlayers - ClosedSlots; CONSOLE_Print( "[MAP] calculated map_numplayers = " + UTIL_ToString( MapNumPlayers ) ); MapNumTeams = RawMapNumTeams; CONSOLE_Print( "[MAP] calculated map_numteams = " + UTIL_ToString( MapNumTeams ) ); CONSOLE_Print( "[MAP] found " + UTIL_ToString( Slots.size( ) ) + " slots" ); BYTEARRAY b; string s; int k=0; if (false && m_LogAll) for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) { k++; b =(*i).GetByteArray( ); s = "map_slot"+UTIL_ToString(k)+" = "; for( unsigned int j = 0; j < b.size( ); j++ ) { s= s+ UTIL_ToString((int)b[j]); if (j<b.size()-1) s=s+" "; } CONSOLE_Print( "[MAP] calculated " + s ); } /* for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) DEBUG_Print( (*i).GetByteArray( ) ); */ // if it's a melee map... if( RawMapFlags & 4 ) { CONSOLE_Print( "[MAP] found melee map, initializing slots" ); // give each slot a different team and set the race to random unsigned char Team = 0; for( vector<CGameSlot> :: iterator i = Slots.begin( ); i != Slots.end( ); i++ ) { (*i).SetTeam( Team++ ); (*i).SetRace( SLOTRACE_RANDOM ); } MapFilterType = MAPFILTER_TYPE_MELEE; } } } else
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; }
string CPotentialPlayer :: GetExternalIPString( ) { BYTEARRAY IP; string EIP; if( m_Socket ) { if( m_IncomingGarenaUser != NULL ) { BYTEARRAY GarenaIP = GetGarenaIP( ); return UTIL_ToString(GarenaIP[0]) + "." + UTIL_ToString(GarenaIP[1]) + "." + UTIL_ToString(GarenaIP[2]) + "." + UTIL_ToString(GarenaIP[3]); } else { bool local=m_LAN; if (!m_LANSet) { IP= m_Socket->GetIP( ); if (IP.size()>=2) { if (IP[0]==10) local=true; else if (IP[0]==192 && IP[1]==168) local=true; else if (IP[0]==169 && IP[1]==254) local=true; else if (IP[0]==172 && IP[1]==16) local=true; else if (IP[0]==172 && IP[1]==17) local=true; else if (IP[0]==172 && IP[1]==18) local=true; else if (IP[0]==172 && IP[1]==19) local=true; else if (IP[0]==172 && IP[1]==20) local=true; else if (IP[0]==172 && IP[1]==21) local=true; else if (IP[0]==172 && IP[1]==22) local=true; else if (IP[0]==172 && IP[1]==23) local=true; else if (IP[0]==172 && IP[1]==24) local=true; else if (IP[0]==172 && IP[1]==25) local=true; else if (IP[0]==172 && IP[1]==26) local=true; else if (IP[0]==172 && IP[1]==27) local=true; else if (IP[0]==172 && IP[1]==28) local=true; else if (IP[0]==172 && IP[1]==29) local=true; else if (IP[0]==172 && IP[1]==30) local=true; else if (IP[0]==172 && IP[1]==31) local=true; } if (UTIL_IsLocalIP(IP, m_Game->m_GHost->m_LocalAddresses)) local = true; m_LANSet = true; m_LAN = local; } EIP=m_Socket->GetIPString( ); if (local && m_Game->m_GHost->m_ExternalIP!="") { EIP=m_Game->m_GHost->m_ExternalIP; } if( !EIP.empty( ) && EIP != "0.0.0.0" ) return EIP; else return m_CachedIP; } } return m_CachedIP; }
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; } }
bool CGamePlayer::Update(void *fd) { const 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) { for (auto & bnet : m_Game->m_Aura->m_BNETs) { if (bnet->GetServer() == m_JoinedRealm) { if (m_Game->GetGameState() == GAME_PUBLIC || bnet->GetPvPGN()) bnet->QueueChatCommand("/whois " + m_Name); else if (m_Game->GetGameState() == GAME_PRIVATE) bnet->QueueChatCommand("Spoof check by replying to this message with \"sc\" [ /r sc ]", m_Name, true, string()); } } m_WhoisSent = true; } // check for socket timeouts // if we don't receive anything from a player for 30 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 (Time - m_Socket->GetLastRecv() >= 30) m_Game->EventPlayerDisconnectTimedOut(this); // GProxy++ acks if (m_GProxy && Time - m_LastGProxyAckTime >= 10) { m_Socket->PutBytes(m_Game->m_Aura->m_GPSProtocol->SEND_GPSS_ACK(m_TotalPacketsReceived)); m_LastGProxyAckTime = Time; } 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 = CreateByteArray((uint8_t *) RecvBuffer->c_str(), RecvBuffer->size()); uint32_t LengthProcessed = 0; // a packet is at least 4 bytes so loop as long as the buffer contains 4 bytes CIncomingAction *Action; CIncomingChatPlayer *ChatPlayer; CIncomingMapSize *MapSize; uint32_t Pong; while (Bytes.size() >= 4) { // bytes 2 and 3 contain the length of the packet const uint16_t Length = ByteArrayToUInt16(Bytes, false, 2); const BYTEARRAY Data = BYTEARRAY(begin(Bytes), begin(Bytes) + Length); if (Bytes[0] == W3GS_HEADER_CONSTANT) { ++m_TotalPacketsReceived; if (Bytes.size() >= Length) { // byte 1 contains the packet ID switch (Bytes[1]) { case CGameProtocol::W3GS_LEAVEGAME: m_Game->EventPlayerLeft(this, m_Protocol->RECEIVE_W3GS_LEAVEGAME(Data)); break; case CGameProtocol::W3GS_GAMELOADED_SELF: if (m_Protocol->RECEIVE_W3GS_GAMELOADED_SELF(Data)) { 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(Data, 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: m_CheckSums.push(m_Protocol->RECEIVE_W3GS_OUTGOING_KEEPALIVE(Data)); ++m_SyncCounter; m_Game->EventPlayerKeepAlive(this); break; case CGameProtocol::W3GS_CHAT_TO_HOST: ChatPlayer = m_Protocol->RECEIVE_W3GS_CHAT_TO_HOST(Data); if (ChatPlayer) m_Game->EventPlayerChatToHost(this, ChatPlayer); delete ChatPlayer; 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(Data); if (MapSize) m_Game->EventPlayerMapSize(this, MapSize); delete MapSize; break; case CGameProtocol::W3GS_PONG_TO_HOST: Pong = m_Protocol->RECEIVE_W3GS_PONG_TO_HOST(Data); // 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() > 10) m_Pings.erase(begin(m_Pings)); } } } m_Game->EventPlayerPongToHost(this); break; } LengthProcessed += Length; Bytes = BYTEARRAY(begin(Bytes) + Length, end(Bytes)); } else break; } else if (Bytes[0] == GPS_HEADER_CONSTANT) { if (Length >= 4) { if (Bytes.size() >= Length) { if (Bytes[1] == CGPSProtocol::GPS_ACK && Data.size() == 8) { const uint32_t LastPacket = ByteArrayToUInt32(Data, false, 4); const 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) { 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++"); } } LengthProcessed += Length; Bytes = BYTEARRAY(begin(Bytes) + Length, end(Bytes)); } else break; } } *RecvBuffer = RecvBuffer->substr(LengthProcessed); // 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(); } } if (m_GProxy && m_Game->GetGameLoaded()) return m_DeleteMe; return m_DeleteMe || m_Socket->HasError() || !m_Socket->GetConnected(); }
BYTEARRAY CBNETProtocol :: SEND_SID_STARTADVEX3( unsigned char state, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, BYTEARRAY mapSHA1, uint32_t hostCounter ) { // todotodo: sort out how GameType works, the documentation is horrendous /* Game type tag: (read W3GS_GAMEINFO for this field) 0x00000001 - Custom 0x00000009 - Blizzard/Ladder Map author: (mask 0x00006000) can be combined *0x00002000 - Blizzard 0x00004000 - Custom Battle type: (mask 0x00018000) cant be combined 0x00000000 - Battle *0x00010000 - Scenario Map size: (mask 0x000E0000) can be combined with 2 nearest values 0x00020000 - Small 0x00040000 - Medium *0x00080000 - Huge Observers: (mask 0x00700000) cant be combined 0x00100000 - Allowed observers 0x00200000 - Observers on defeat *0x00400000 - No observers Flags: 0x00000800 - Private game flag (not used in game list) */ unsigned char Unknown[] = { 255, 3, 0, 0 }; unsigned char CustomGame[] = { 0, 0, 0, 0 }; string HostCounterString = UTIL_ToHexString( hostCounter ); if( HostCounterString.size( ) < 8 ) HostCounterString.insert( 0, 8 - HostCounterString.size( ), '0' ); HostCounterString = string( HostCounterString.rbegin( ), HostCounterString.rend( ) ); BYTEARRAY packet; // make the stat string BYTEARRAY StatString; UTIL_AppendByteArrayFast( StatString, mapFlags ); StatString.push_back( 0 ); UTIL_AppendByteArrayFast( StatString, mapWidth ); UTIL_AppendByteArrayFast( StatString, mapHeight ); UTIL_AppendByteArrayFast( StatString, mapCRC ); UTIL_AppendByteArrayFast( StatString, mapPath ); UTIL_AppendByteArrayFast( StatString, hostName ); StatString.push_back( 0 ); UTIL_AppendByteArrayFast( StatString, mapSHA1 ); StatString = UTIL_EncodeStatString( StatString ); if( mapGameType.size( ) == 4 && mapFlags.size( ) == 4 && mapWidth.size( ) == 2 && mapHeight.size( ) == 2 && !gameName.empty( ) && !hostName.empty( ) && !mapPath.empty( ) && mapCRC.size( ) == 4 && mapSHA1.size( ) == 20 && StatString.size( ) < 128 && HostCounterString.size( ) == 8 ) { // make the rest of the packet packet.push_back( BNET_HEADER_CONSTANT ); // BNET header constant packet.push_back( SID_STARTADVEX3 ); // SID_STARTADVEX3 packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later packet.push_back( state ); // State (16 = public, 17 = private, 18 = close) packet.push_back( 0 ); // State continued... packet.push_back( 0 ); // State continued... packet.push_back( 0 ); // State continued... UTIL_AppendByteArray( packet, upTime, false ); // time since creation UTIL_AppendByteArrayFast( packet, mapGameType ); // Game Type, Parameter UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? UTIL_AppendByteArray( packet, CustomGame, 4 ); // Custom Game UTIL_AppendByteArrayFast( packet, gameName ); // Game Name packet.push_back( 0 ); // Game Password is NULL packet.push_back( 98 ); // Slots Free (ascii 98 = char 'b' = 11 slots free) - note: do not reduce this as this is the # of PID's Warcraft III will allocate UTIL_AppendByteArrayFast( packet, HostCounterString, false ); // Host Counter UTIL_AppendByteArrayFast( packet, StatString ); // Stat String packet.push_back( 0 ); // Stat String null terminator (the stat string is encoded to remove all even numbers i.e. zeros) AssignLength( packet ); } else CONSOLE_Print( "[BNETPROTO] invalid parameters passed to SEND_SID_STARTADVEX3" ); // DEBUG_Print( "SENT SID_STARTADVEX3" ); // DEBUG_Print( packet ); return packet; }
void CReplay :: BuildReplay( string gameName, string statString, uint32_t war3Version, uint16_t buildNumber ) { m_War3Version = war3Version; m_BuildNumber = buildNumber; m_Flags = 32768; CONSOLE_Print( "[REPLAY] building replay" ); uint32_t LanguageID = 0x0012F8B0; BYTEARRAY Replay; Replay.push_back( 16 ); // Unknown (4.0) Replay.push_back( 1 ); // Unknown (4.0) Replay.push_back( 0 ); // Unknown (4.0) Replay.push_back( 0 ); // Unknown (4.0) Replay.push_back( 0 ); // Host RecordID (4.1) Replay.push_back( m_HostPID ); // Host PlayerID (4.1) UTIL_AppendByteArrayFast( Replay, m_HostName ); // Host PlayerName (4.1) Replay.push_back( 1 ); // Host AdditionalSize (4.1) Replay.push_back( 0 ); // Host AdditionalData (4.1) UTIL_AppendByteArrayFast( Replay, gameName ); // GameName (4.2) Replay.push_back( 0 ); // Null (4.0) UTIL_AppendByteArrayFast( Replay, statString ); // StatString (4.3) UTIL_AppendByteArray( Replay, (uint32_t)m_Slots.size( ), false ); // PlayerCount (4.6) Replay.push_back( m_MapGameType ); // GameType (4.7) Replay.push_back( 32 ); // GameType (4.7) Replay.push_back( 73 ); // GameType (4.7) Replay.push_back( 0 ); // GameType (4.7) UTIL_AppendByteArray( Replay, LanguageID, false ); // LanguageID (4.8) // PlayerList (4.9) for( vector<PIDPlayer> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ ) { if( (*i).first != m_HostPID ) { Replay.push_back( 22 ); // Player RecordID (4.1) Replay.push_back( (*i).first ); // Player PlayerID (4.1) UTIL_AppendByteArrayFast( Replay, (*i).second ); // Player PlayerName (4.1) Replay.push_back( 1 ); // Player AdditionalSize (4.1) Replay.push_back( 0 ); // Player AdditionalData (4.1) UTIL_AppendByteArray( Replay, (uint32_t)0, false ); // Unknown } } // GameStartRecord (4.10) Replay.push_back( 25 ); // RecordID (4.10) UTIL_AppendByteArray( Replay, (uint16_t)( 7 + m_Slots.size( ) * 9 ), false ); // Size (4.10) Replay.push_back( m_Slots.size( ) ); // NumSlots (4.10) for( unsigned char i = 0; i < m_Slots.size( ); i++ ) UTIL_AppendByteArray( Replay, m_Slots[i].GetByteArray( ) ); UTIL_AppendByteArray( Replay, m_RandomSeed, false ); // RandomSeed (4.10) Replay.push_back( m_SelectMode ); // SelectMode (4.10) Replay.push_back( m_StartSpotCount ); // StartSpotCount (4.10) // ReplayData (5.0) Replay.push_back( REPLAY_FIRSTSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); Replay.push_back( REPLAY_SECONDSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); // leavers during loading need to be stored between the second and third start blocks while( !m_LoadingBlocks.empty( ) ) { UTIL_AppendByteArray( Replay, m_LoadingBlocks.front( ) ); m_LoadingBlocks.pop( ); } Replay.push_back( REPLAY_THIRDSTARTBLOCK ); UTIL_AppendByteArray( Replay, (uint32_t)1, false ); // initialize replay length to zero // we'll accumulate the replay length as we iterate through the timeslots // this is necessary because we might be discarding some timeslots due to not enough checksums and the replay length needs to be accurate m_ReplayLength = 0; uint32_t TimeSlotsDiscarded = 0; bool EndOfTimeSlots = false; while( !m_Blocks.empty( ) ) { BYTEARRAY Block = m_Blocks.front( ); m_Blocks.pop( ); if( Block.size( ) >= 5 && Block[0] == REPLAY_TIMESLOT ) { uint16_t TimeIncrement = UTIL_ByteArrayToUInt16( Block, false, 3 ); if( TimeIncrement != 0 && m_CheckSums.empty( ) ) EndOfTimeSlots = true; if( EndOfTimeSlots ) { TimeSlotsDiscarded++; continue; } // append timeslot UTIL_AppendByteArrayFast( Replay, Block ); // append checksum // todotodo: after experimenting, Strilanc discovered that checksums are NOT required in replays // we could optimize saving of replays by building a complete stream without waiting for checksums as the game progresses // alternatively, we could build that stream as the checksums were added if we wanted to keep them // rather than building it in one go right now, which might take a few hundred ms and cause a spike in other games if( TimeIncrement != 0 ) { BYTEARRAY CheckSum; CheckSum.reserve( 6 ); CheckSum.push_back( REPLAY_CHECKSUM ); CheckSum.push_back( 4 ); UTIL_AppendByteArray( CheckSum, m_CheckSums.front( ), false ); m_CheckSums.pop( ); UTIL_AppendByteArrayFast( Replay, CheckSum ); } // accumulate replay length m_ReplayLength += TimeIncrement; } else UTIL_AppendByteArrayFast( Replay, Block ); } if( TimeSlotsDiscarded > 0 ) CONSOLE_Print( "[REPLAY] ran out of checksums, discarded " + UTIL_ToString( TimeSlotsDiscarded ) + " timeslots" ); // done m_Decompressed = string( Replay.begin( ), Replay.end( ) ); }
BYTEARRAY CGameProtocol :: SEND_W3GS_INCOMING_ACTION2( queue<CIncomingAction *> actions ) { BYTEARRAY packet; packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant packet.push_back( W3GS_INCOMING_ACTION2 ); // W3GS_INCOMING_ACTION2 packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // ??? (send interval?) packet.push_back( 0 ); // ??? (send interval?) // create subpacket if( !actions.empty( ) ) { BYTEARRAY subpacket; while( !actions.empty( ) ) { CIncomingAction *Action = actions.front( ); actions.pop( ); subpacket.push_back( Action->GetPID( ) ); UTIL_AppendByteArray( subpacket, (uint16_t)Action->GetAction( )->size( ), false ); UTIL_AppendByteArrayFast( subpacket, *Action->GetAction( ) ); } // calculate crc (we only care about the first 2 bytes though) BYTEARRAY crc32 = UTIL_CreateByteArray( m_GHost->m_CRC->FullCRC( (unsigned char *)string( subpacket.begin( ), subpacket.end( ) ).c_str( ), subpacket.size( ) ), false ); crc32.resize( 2 ); // finish subpacket UTIL_AppendByteArrayFast( packet, crc32 ); // crc UTIL_AppendByteArrayFast( packet, subpacket ); // subpacket } AssignLength( packet ); // DEBUG_Print( "SENT W3GS_INCOMING_ACTION2" ); // DEBUG_Print( packet ); return packet; }
BYTEARRAY CPotentialPlayer :: GetExternalIP( ) { unsigned char Zeros[] = { 0, 0, 0, 0 }; BYTEARRAY IP; if( m_Socket ) { if( m_IncomingGarenaUser != NULL ) return GetGarenaIP( ); else { bool local=false; IP= m_Socket->GetIP( ); if (IP.size()>=2) { if (IP[0]==10) local=true; else if (IP[0]==192 && IP[1]==168) local=true; else if (IP[0]==169 && IP[1]==254) local=true; else if (IP[0]==172 && IP[1]==16) local=true; else if (IP[0]==172 && IP[1]==17) local=true; else if (IP[0]==172 && IP[1]==18) local=true; else if (IP[0]==172 && IP[1]==19) local=true; else if (IP[0]==172 && IP[1]==20) local=true; else if (IP[0]==172 && IP[1]==21) local=true; else if (IP[0]==172 && IP[1]==22) local=true; else if (IP[0]==172 && IP[1]==23) local=true; else if (IP[0]==172 && IP[1]==24) local=true; else if (IP[0]==172 && IP[1]==25) local=true; else if (IP[0]==172 && IP[1]==26) local=true; else if (IP[0]==172 && IP[1]==27) local=true; else if (IP[0]==172 && IP[1]==28) local=true; else if (IP[0]==172 && IP[1]==29) local=true; else if (IP[0]==172 && IP[1]==30) local=true; else if (IP[0]==172 && IP[1]==31) local=true; } if (local && m_Game->m_GHost->m_ExternalIP!="") { IP=UTIL_CreateByteArray(m_Game->m_GHost->m_ExternalIPL,true); } return IP; } } return UTIL_CreateByteArray( Zeros, 4 ); }
BYTEARRAY CGameProtocol :: SEND_W3GS_GAMEINFO( bool TFT, unsigned char war3Version, BYTEARRAY mapGameType, BYTEARRAY mapFlags, BYTEARRAY mapWidth, BYTEARRAY mapHeight, string gameName, string hostName, uint32_t upTime, string mapPath, BYTEARRAY mapCRC, uint32_t slotsTotal, uint32_t slotsOpen, uint16_t port, uint32_t hostCounter, uint32_t entryKey ) { unsigned char ProductID_ROC[] = { 51, 82, 65, 87 }; // "WAR3" unsigned char ProductID_TFT[] = { 80, 88, 51, 87 }; // "W3XP" unsigned char Version[] = { war3Version, 0, 0, 0 }; unsigned char Unknown[] = { 1, 0, 0, 0 }; BYTEARRAY packet; if( mapGameType.size( ) == 4 && mapFlags.size( ) == 4 && mapWidth.size( ) == 2 && mapHeight.size( ) == 2 && !gameName.empty( ) && !hostName.empty( ) && !mapPath.empty( ) && mapCRC.size( ) == 4 ) { // make the stat string BYTEARRAY StatString; UTIL_AppendByteArrayFast( StatString, mapFlags ); StatString.push_back( 0 ); UTIL_AppendByteArrayFast( StatString, mapWidth ); UTIL_AppendByteArrayFast( StatString, mapHeight ); UTIL_AppendByteArrayFast( StatString, mapCRC ); UTIL_AppendByteArrayFast( StatString, mapPath ); UTIL_AppendByteArrayFast( StatString, hostName ); StatString.push_back( 0 ); StatString = UTIL_EncodeStatString( StatString ); // make the rest of the packet packet.push_back( W3GS_HEADER_CONSTANT ); // W3GS header constant packet.push_back( W3GS_GAMEINFO ); // W3GS_GAMEINFO packet.push_back( 0 ); // packet length will be assigned later packet.push_back( 0 ); // packet length will be assigned later if( TFT ) UTIL_AppendByteArray( packet, ProductID_TFT, 4 ); // Product ID (TFT) else UTIL_AppendByteArray( packet, ProductID_ROC, 4 ); // Product ID (ROC) UTIL_AppendByteArray( packet, Version, 4 ); // Version UTIL_AppendByteArray( packet, hostCounter, false ); // Host Counter UTIL_AppendByteArray( packet, entryKey, false ); // Entry Key UTIL_AppendByteArrayFast( packet, gameName ); // Game Name packet.push_back( 0 ); // ??? (maybe game password) UTIL_AppendByteArrayFast( packet, StatString ); // Stat String packet.push_back( 0 ); // Stat String null terminator (the stat string is encoded to remove all even numbers i.e. zeros) UTIL_AppendByteArray( packet, slotsTotal, false ); // Slots Total UTIL_AppendByteArrayFast( packet, mapGameType ); // Game Type UTIL_AppendByteArray( packet, Unknown, 4 ); // ??? UTIL_AppendByteArray( packet, slotsOpen, false ); // Slots Open UTIL_AppendByteArray( packet, upTime, false ); // time since creation UTIL_AppendByteArray( packet, port, false ); // port AssignLength( packet ); } else { if(mapGameType.size( ) != 4) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (mapGameType)" ); else if(mapFlags.size( ) != 4) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (mapFlags)" ); else if(mapWidth.size( ) != 2) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (mapWidth)" ); else if(mapHeight.size( ) != 2) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (mapHeight)" ); else if(gameName.empty( )) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (gameName)" ); else if(hostName.empty( )) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (hostName)" ); else if(mapPath.empty( )) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (mapPath)" ); else if(mapCRC.size( ) != 4) CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (CRC)" ); else CONSOLE_Print( "[GAMEPROTO] invalid parameters passed to SEND_W3GS_GAMEINFO (unknown)" ); } // DEBUG_Print( "SENT W3GS_GAMEINFO" ); // DEBUG_Print( packet ); return packet; }
void DEBUG_Print( BYTEARRAY b , uint32_t level ) { if(level>debug_level) return ; switch(level) { case DEBUG_LEVEL_PACKET_RECV: cout << "<<<IN\n" ; break; case DEBUG_LEVEL_PACKET_SEND: cout << " OUT>>>\n" ; break; default : cout << level << "\n" ; break; } unsigned int i = 0; while( i < b.size( ) ) { switch(level) { case DEBUG_LEVEL_PACKET_RECV: cout << "" ; break; case DEBUG_LEVEL_PACKET_SEND: cout << " " ; break; default : cout << level << ">" ; break; } unsigned int j; for( j = 0; j < 8; j++ ) { if(j+i < b.size( )) cout << setfill('0') << setw(2) << hex << (int)b[i+j] << " "; else cout << " "; } cout << " "; for( j = 8; j < 16; j++ ) { if(j+i < b.size( )) cout << setfill('0') << setw(2) << hex << (int)b[i+j] << " "; else cout << " "; } cout << " "; for( j = 0; j < 16 && i < b.size( ); j++,i++ ) { if(j % 8 == 0) cout << " "; if((int)b[i] >= 32 && (int)b[i] < 127) cout << b[i]; else cout << "."; } cout << endl; } }