void PacketBroker::receive(sf::Packet& packet, Protocol protocol) { using namespace sf; if (protocol == Protocol::TCP) { if (Globals::Networking->ReceiveSafe(packet) != Socket::Status::Done) return; } else { PacketHandler::RemoteAddress remoteAddress; if (Globals::Networking->ReceiveFast(packet, remoteAddress) != Socket::Status::Done) return; if (!Globals::Networking->isConnectedAddress(remoteAddress)) return; ushort sequence = 0; packet >> sequence; // TODO: Rejection threshold if (sequence == 0 || sequence <= lastSequence) { PrintDebug(">> Received out of order packet. Rejecting."); lastSequence = sequence % USHRT_MAX; return; } lastSequence = sequence % USHRT_MAX; } PlayerNumber pnum = -1; if (netStat) addBytesReceived(packet.getDataSize()); while (!packet.endOfPacket()) { MessageID newType; packet >> newType; // TODO: Re-implement packet loop failsafe using read offset. switch (newType) { case MessageID::None: PrintDebug("\a>> Reached end of packet."); break; case MessageID::Count: PrintDebug(">> Received message count?! Malformed packet warning!"); break; case MessageID::N_Disconnect: PrintDebug(">> Received disconnect request from client."); Globals::Networking->Disconnect(); break; case MessageID::N_Ready: { MessageID waitID; packet >> waitID; auto it = waitRequests.find(waitID); if (it != waitRequests.end()) { it->second = true; } else { waitRequests[waitID] = true; } break; } case MessageID::N_PlayerNumber: packet >> pnum; break; // TODO: verification that this is from the host case MessageID::N_SetPlayer: { PlayerNumber changed; packet >> changed; SetPlayerNumber(changed); break; } case MessageID::S_KeepAlive: receivedKeepalive = Millisecs(); packet.seekRead(sizeof(ushort), SEEK_CUR); break; default: { if (newType < MessageID::N_END) { packet.clear(); break; } ushort length; packet >> length; AddTypeReceived(newType, length, protocol == Protocol::TCP); if (pnum >= 0) { if (receiveSystem(newType, pnum, packet)) break; if (pnum != playerNum) { if (!writePlayer) inPlayer.Copy(Player[pnum]); if (receivePlayer(newType, pnum, packet)) { if (GameState >= GameState::Ingame) { writePlayer = false; PlayerObject::WritePlayer(Player[pnum], &inPlayer); } break; } } if (receiveMenu(newType, pnum, packet)) break; } if (runMessageHandler(newType, pnum, packet)) break; PrintDebug("\t\t[P%d] Skipping %d bytes for id %02d", pnum, length, newType); packet.seekRead(length, SEEK_CUR); break; } } } }
// called from ---NETPLAY--- thread unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) { MessageId mid; packet >> mid; INFO_LOG(NETPLAY, "Got client message: %x", mid); // don't need lock because this is the only thread that modifies the players // only need locks for writes to m_players in this thread switch (mid) { case NP_MSG_CHAT_MESSAGE: { std::string msg; packet >> msg; // send msg to other clients sf::Packet spac; spac << (MessageId)NP_MSG_CHAT_MESSAGE; spac << player.pid; spac << msg; SendToClients(spac, player.pid); } break; case NP_MSG_PAD_DATA: { // if this is pad data from the last game still being received, ignore it if (player.current_game != m_current_game) break; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_PAD_DATA); while (!packet.endOfPacket()) { PadMapping map; packet >> map; // If the data is not from the correct player, // then disconnect them. if (m_pad_map.at(map) != player.pid) { return 1; } GCPadStatus pad; packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; if (m_host_input_authority) { m_last_pad_status[map] = pad; if (!m_first_pad_status_received[map]) { m_first_pad_status_received[map] = true; SendFirstReceivedToHost(map, true); } } else { spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } } if (!m_host_input_authority) SendToClients(spac, player.pid); } break; case NP_MSG_PAD_HOST_POLL: { PadMapping pad_num; packet >> pad_num; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_PAD_DATA); if (pad_num < 0) { for (size_t i = 0; i < m_pad_map.size(); i++) { if (m_pad_map[i] == -1) continue; const GCPadStatus& pad = m_last_pad_status[i]; spac << static_cast<PadMapping>(i) << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } } else if (m_pad_map.at(pad_num) != -1) { const GCPadStatus& pad = m_last_pad_status[pad_num]; spac << pad_num << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } SendToClients(spac); } break; case NP_MSG_WIIMOTE_DATA: { // if this is Wiimote data from the last game still being received, ignore it if (player.current_game != m_current_game) break; PadMapping map = 0; u8 size; packet >> map >> size; std::vector<u8> data(size); for (size_t i = 0; i < data.size(); ++i) packet >> data[i]; // If the data is not from the correct player, // then disconnect them. if (m_wiimote_map.at(map) != player.pid) { return 1; } // relay to clients sf::Packet spac; spac << (MessageId)NP_MSG_WIIMOTE_DATA; spac << map; spac << size; for (const u8& byte : data) spac << byte; SendToClients(spac, player.pid); } break; case NP_MSG_PONG: { const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); u32 ping_key = 0; packet >> ping_key; if (m_ping_key == ping_key) { player.ping = ping; } sf::Packet spac; spac << (MessageId)NP_MSG_PLAYER_PING_DATA; spac << player.pid; spac << player.ping; SendToClients(spac); } break; case NP_MSG_START_GAME: { packet >> player.current_game; } break; case NP_MSG_STOP_GAME: { if (!m_is_running) break; m_is_running = false; // tell clients to stop game sf::Packet spac; spac << (MessageId)NP_MSG_STOP_GAME; std::lock_guard<std::recursive_mutex> lkp(m_crit.players); SendToClients(spac); } break; case NP_MSG_GAME_STATUS: { u32 status; packet >> status; m_players[player.pid].game_status = static_cast<PlayerGameStatus>(status); // send msg to other clients sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_GAME_STATUS); spac << player.pid; spac << status; SendToClients(spac); } break; case NP_MSG_IPL_STATUS: { bool status; packet >> status; m_players[player.pid].has_ipl_dump = status; } break; case NP_MSG_TIMEBASE: { u64 timebase = Common::PacketReadU64(packet); u32 frame; packet >> frame; if (m_desync_detected) break; std::vector<std::pair<PlayerId, u64>>& timebases = m_timebase_by_frame[frame]; timebases.emplace_back(player.pid, timebase); if (timebases.size() >= m_players.size()) { // we have all records for this frame if (!std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> pair) { return pair.second == timebases[0].second; })) { int pid_to_blame = -1; for (auto pair : timebases) { if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> other) { return other.first == pair.first || other.second != pair.second; })) { // we are the only outlier pid_to_blame = pair.first; break; } } sf::Packet spac; spac << (MessageId)NP_MSG_DESYNC_DETECTED; spac << pid_to_blame; spac << frame; SendToClients(spac); m_desync_detected = true; } m_timebase_by_frame.erase(frame); } } break; case NP_MSG_MD5_PROGRESS: { int progress; packet >> progress; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_PROGRESS); spac << player.pid; spac << progress; SendToClients(spac); } break; case NP_MSG_MD5_RESULT: { std::string result; packet >> result; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_RESULT); spac << player.pid; spac << result; SendToClients(spac); } break; case NP_MSG_MD5_ERROR: { std::string error; packet >> error; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_ERROR); spac << player.pid; spac << error; SendToClients(spac); } break; case NP_MSG_SYNC_SAVE_DATA: { MessageId sub_id; packet >> sub_id; switch (sub_id) { case SYNC_SAVE_DATA_SUCCESS: { if (m_start_pending) { m_save_data_synced_players++; if (m_save_data_synced_players >= m_players.size() - 1) { m_dialog->AppendChat(GetStringT("All players synchronized.")); StartGame(); } } } break; case SYNC_SAVE_DATA_FAILURE: { m_dialog->AppendChat( StringFromFormat(GetStringT("%s failed to synchronize.").c_str(), player.name.c_str())); m_dialog->OnSaveDataSyncFailure(); m_start_pending = false; } break; default: PanicAlertT( "Unknown SYNC_SAVE_DATA message with id:%d received from player:%d Kicking player!", sub_id, player.pid); return 1; } } break; default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); // unknown message, kick the client return 1; } return 0; }