diff --git a/Source/Core/DolphinWX/Src/NetPlayClient.cpp b/Source/Core/DolphinWX/Src/NetPlayClient.cpp new file mode 100644 index 0000000000..13a42f8bb0 --- /dev/null +++ b/Source/Core/DolphinWX/Src/NetPlayClient.cpp @@ -0,0 +1,347 @@ +#include "NetPlay.h" +#include "NetWindow.h" + +// called from ---GUI--- thread +NetPlayClient::~NetPlayClient() +{ + if (is_connected) + { + m_do_loop = false; + m_thread->WaitForDeath(); + delete m_thread; + } +} + +// called from ---GUI--- thread +NetPlayClient::NetPlayClient(const std::string& address, const u16 port, const std::string& name, NetPlayDiag* const npd) +{ + m_dialog = npd; + is_connected = false; + + // why is false successful? documentation says true is + if (0 == m_socket.Connect(port, address)) + { + // send connect message + sf::Packet spac; + spac << NETPLAY_VERSION; + spac << NETPLAY_DOLPHIN_VER; + spac << name; + m_socket.Send(spac); + + sf::Packet rpac; + // TODO: make this not hang + m_socket.Receive(rpac); + MessageId error; + rpac >> error; + + // got error message + if (error) + { + switch (error) + { + case CON_ERR_SERVER_FULL : + PanicAlert("The server is full!"); + break; + case CON_ERR_VERSION_MISMATCH : + PanicAlert("The server and client's NetPlay versions are incompatible!"); + break; + case CON_ERR_GAME_RUNNING : + PanicAlert("The server responded: the game is currently running!"); + break; + default : + PanicAlert("The server sent an unknown error message!"); + break; + } + m_socket.Close(); + } + else + { + rpac >> m_pid; + + Player player; + player.name = name; + player.pid = m_pid; + player.revision = NETPLAY_DOLPHIN_VER; + + // add self to player list + m_players[m_pid] = player; + m_local_player = &m_players[m_pid]; + + UpdateGUI(); + + //PanicAlert("Connection successful: assigned player id: %d", m_pid); + is_connected = true; + + m_selector.Add(m_socket); + m_thread = new Common::Thread(NetPlayThreadFunc, this); + } + } + else + PanicAlert("Failed to Connect!"); + +} + +// called from ---NETPLAY--- thread +unsigned int NetPlayClient::OnData(sf::Packet& packet) +{ + MessageId mid; + packet >> mid; + + switch (mid) + { + case NP_MSG_PLAYER_JOIN : + { + Player player; + packet >> player.pid; + packet >> player.name; + packet >> player.revision; + + m_crit.players.Enter(); // lock players + m_players[player.pid] = player; + m_crit.players.Leave(); + + UpdateGUI(); + } + break; + + case NP_MSG_PLAYER_LEAVE : + { + PlayerId pid; + packet >> pid; + + m_crit.players.Enter(); // lock players + m_players.erase(m_players.find(pid)); + m_crit.players.Leave(); + + UpdateGUI(); + } + break; + + case NP_MSG_CHAT_MESSAGE : + { + PlayerId pid; + packet >> pid; + std::string msg; + packet >> msg; + + // don't need lock to read in this thread + const Player& player = m_players[pid]; + + // add to gui + std::ostringstream ss; + ss << player.name << '[' << (char)(pid+'0') << "]: " << msg; + + AppendChatGUI(ss.str()); + } + break; + + case NP_MSG_PAD_MAPPING : + { + PlayerId pid; + packet >> pid; + + m_crit.players.Enter(); // lock players + Player& player = m_players[pid]; + + for (unsigned int i=0; i<4; ++i) + packet >> player.pad_map[i]; + m_crit.players.Leave(); + + UpdateGUI(); + } + break; + + case NP_MSG_PAD_DATA : + { + PadMapping map = 0; + NetPad np; + packet >> map >> np.nHi >> np.nLo; + + // trusting server for good map value (>=0 && <4) + // add to pad buffer + m_crit.buffer.Enter(); // lock buffer + m_pad_buffer[(unsigned)map].push(np); + m_crit.buffer.Leave(); + } + break; + + case NP_MSG_PAD_BUFFER : + { + u32 size = 0; + packet >> size; + + m_crit.buffer.Enter(); // lock buffer + m_target_buffer_size = size; + m_crit.buffer.Leave(); + } + break; + + case NP_MSG_CHANGE_GAME : + { + // lock here? + m_crit.game.Enter(); // lock game state + packet >> m_selected_game; + m_crit.game.Leave(); + + // update gui + wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_CHANGE_GAME); + // TODO: using a wxString in AddPendingEvent from another thread is unsafe i guess? + evt.SetString(wxString(m_selected_game.c_str(), *wxConvCurrent)); + m_dialog->GetEventHandler()->AddPendingEvent(evt); + } + break; + + case NP_MSG_START_GAME : + { + packet >> m_on_game; + + wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_START_GAME); + m_dialog->GetEventHandler()->AddPendingEvent(evt); + } + break; + + case NP_MSG_STOP_GAME : + { + wxCommandEvent evt(wxEVT_THREAD, NP_GUI_EVT_STOP_GAME); + m_dialog->GetEventHandler()->AddPendingEvent(evt); + } + break; + + case NP_MSG_DISABLE_GAME : + { + PanicAlert("Other client disconnected while game is running!! NetPlay is disabled. You must manually stop the game."); + CritLocker game_lock(m_crit.game); // lock game state + m_is_running = false; + NetPlay_Disable(); + } + break; + + case NP_MSG_PING : + { + u32 ping_key = 0; + packet >> ping_key; + + sf::Packet spac; + spac << (MessageId)NP_MSG_PONG; + spac << ping_key; + + CritLocker send_lock(m_crit.send); + m_socket.Send(spac); + } + break; + + default : + PanicAlert("Unknown message received with id : %d", mid); + break; + + } + + return 0; +} + +// called from ---NETPLAY--- thread +void NetPlayClient::Entry() +{ + while (m_do_loop) + { + if (m_selector.Wait(0.01f)) + { + sf::Packet rpac; + switch (m_socket.Receive(rpac)) + { + case sf::Socket::Done : + OnData(rpac); + break; + + //case sf::Socket::Disconnected : + default : + m_is_running = false; + NetPlay_Disable(); + AppendChatGUI("< LOST CONNECION TO SERVER >"); + PanicAlert("Lost connection to server!"); + m_do_loop = false; + break; + } + } + } + + m_socket.Close(); + + return; +} + +// called from ---GUI--- thread +void NetPlayClient::GetPlayerList(std::string &list) +{ + CritLocker player_lock(m_crit.players); // lock players + + std::ostringstream ss; + + std::map::const_iterator + i = m_players.begin(), + e = m_players.end(); + for ( ; i!=e; ++i) + ss << i->second.ToString() << '\n'; + + list = ss.str(); +} + + +// called from ---GUI--- thread +void NetPlayClient::SendChatMessage(const std::string& msg) +{ + sf::Packet spac; + spac << (MessageId)NP_MSG_CHAT_MESSAGE; + spac << msg; + + m_crit.send.Enter(); // lock send + m_socket.Send(spac); + m_crit.send.Leave(); +} + +// called from ---CPU--- thread +void NetPlayClient::SendPadState(const PadMapping local_nb, const NetPad& np) +{ + // send to server + sf::Packet spac; + spac << (MessageId)NP_MSG_PAD_DATA; + spac << local_nb; // local pad num + spac << np.nHi << np.nLo; + + m_crit.send.Enter(); // lock send + m_socket.Send(spac); + m_crit.send.Leave(); +} + +// called from ---GUI--- thread +bool NetPlayClient::StartGame(const std::string &path) +{ + m_crit.buffer.Enter(); // lock buffer + + if (false == NetPlay::StartGame(path)) + return false; + + // TODO: i dont like this here + ClearBuffers(); + m_crit.buffer.Leave(); + + // tell server i started the game + sf::Packet spac; + spac << (MessageId)NP_MSG_START_GAME; + spac << m_on_game; + + m_crit.send.Enter(); // lock send + m_socket.Send(spac); + m_crit.send.Leave(); + + return true; +} + +// called from ---GUI--- thread +bool NetPlayClient::ChangeGame(const std::string &game) +{ + // warning removal + game.size(); + + return true; +} diff --git a/Source/Core/DolphinWX/Src/NetPlayServer.cpp b/Source/Core/DolphinWX/Src/NetPlayServer.cpp new file mode 100644 index 0000000000..c827c06e7c --- /dev/null +++ b/Source/Core/DolphinWX/Src/NetPlayServer.cpp @@ -0,0 +1,577 @@ +#include "NetPlay.h" +#include "NetWindow.h" + +// called from ---GUI--- thread +NetPlayServer::~NetPlayServer() +{ + if (is_connected) + { + m_do_loop = false; + m_thread->WaitForDeath(); + delete m_thread; + } +} + +// called from ---GUI--- thread +NetPlayServer::NetPlayServer(const u16 port, const std::string& name, NetPlayDiag* const npd, const std::string& game) +{ + m_dialog = npd; + m_selected_game = game; + + m_update_pings = true; + + if (m_socket.Listen(port)) + { + Client player; + player.pid = 0; + player.revision = NETPLAY_DOLPHIN_VER; + player.socket = m_socket; + player.name = name; + + // map local pad 1 to game pad 1 + player.pad_map[0] = 0; + + // add self to player list + m_players[m_socket] = player; + m_local_player = &m_players[m_socket]; + //PanicAlert("Listening"); + + UpdateGUI(); + + is_connected = true; + + m_selector.Add(m_socket); + m_thread = new Common::Thread(NetPlayThreadFunc, this); + } + else + is_connected = false; +} + +// called from ---NETPLAY--- thread +void NetPlayServer::Entry() +{ + while (m_do_loop) + { + // update pings every so many seconds + if ((m_ping_timer.GetTimeElapsed() > (10 * 1000)) || m_update_pings) + { + //PanicAlert("sending pings"); + + m_ping_key = Common::Timer::GetTimeMs(); + + sf::Packet spac; + spac << (MessageId)NP_MSG_PING; + spac << m_ping_key; + + CritLocker player_lock(m_crit.players); + CritLocker send_lock(m_crit.send); + m_ping_timer.Start(); + SendToClients(spac); + + m_update_pings = false; + } + + // check which sockets need attention + const unsigned int num = m_selector.Wait(0.01f); + for (unsigned int i=0; i::reverse_iterator + i = m_players.rbegin(), + e = m_players.rend(); + for ( ; i!=e; ++i) + i->second.socket.Close(); + } + + return; +} + +// called from ---NETPLAY--- thread +unsigned int NetPlayServer::OnConnect(sf::SocketTCP& socket) +{ + sf::Packet rpac; + // TODO: make this not hang / check if good packet + socket.Receive(rpac); + + std::string npver; + rpac >> npver; + // dolphin netplay version + if (npver != NETPLAY_VERSION) + return CON_ERR_VERSION_MISMATCH; + + // game is currently running + if (m_is_running) + return CON_ERR_GAME_RUNNING; + + // too many players + if (m_players.size() >= 255) + return CON_ERR_SERVER_FULL; + + // cause pings to be updated + m_update_pings = true; + + Client player; + player.socket = socket; + rpac >> player.revision; + rpac >> player.name; + + // give new client first available id + player.pid = 0; + std::map::const_iterator + i, + e = m_players.end(); + for (PlayerId p = 1; 0 == player.pid; ++p) + { + for (i = m_players.begin(); ; ++i) + { + if (e == i) + { + player.pid = p; + break; + } + if (p == i->second.pid) + break; + } + } + + // TODO: this is crappy + // try to automatically assign new user a pad + { + bool is_mapped[4] = {false,false,false,false}; + + for ( unsigned int m = 0; m<4; ++m) + { + for (i = m_players.begin(); i!=e; ++i) + { + if (i->second.pad_map[m] >= 0) + is_mapped[(unsigned)i->second.pad_map[m]] = true; + } + } + + for ( unsigned int m = 0; m<4; ++m) + if (false == is_mapped[m]) + { + player.pad_map[0] = m; + break; + } + + } + + // ENTER + m_crit.send.Enter(); + + // send join message to already connected clients + sf::Packet spac; + spac << (MessageId)NP_MSG_PLAYER_JOIN; + spac << player.pid << player.name << player.revision; + SendToClients(spac); + + // send new client success message with their id + spac.Clear(); + spac << (MessageId)0; + spac << player.pid; + socket.Send(spac); + + // send user their pad mapping + spac.Clear(); + spac << (MessageId)NP_MSG_PAD_MAPPING; + spac << player.pid; + for (unsigned int pm = 0; pm<4; ++pm) + spac << player.pad_map[pm]; + socket.Send(spac); + + // send new client the selected game + spac.Clear(); + spac << (MessageId)NP_MSG_CHANGE_GAME; + spac << m_selected_game; + socket.Send(spac); + + // sync values with new client + for (i = m_players.begin(); i!=e; ++i) + { + spac.Clear(); + spac << (MessageId)NP_MSG_PLAYER_JOIN; + spac << i->second.pid << i->second.name << i->second.revision; + socket.Send(spac); + + spac.Clear(); + spac << (MessageId)NP_MSG_PAD_MAPPING; + spac << i->second.pid; + for (unsigned int pm = 0; pm<4; ++pm) + spac << i->second.pad_map[pm]; + socket.Send(spac); + } + + // LEAVE + m_crit.send.Leave(); + + // add client to the player list + m_crit.players.Enter(); // lock players + m_players[socket] = player; + m_crit.players.Leave(); + + // add client to selector/ used for receiving + m_selector.Add(socket); + + UpdateGUI(); + + return 0; +} + +// called from ---NETPLAY--- thread +unsigned int NetPlayServer::OnDisconnect(sf::SocketTCP& socket) +{ + if (m_is_running) + { + PanicAlert("Client disconnect while game is running!! NetPlay is disabled. You manually stop the game."); + CritLocker game_lock(m_crit.game); // lock game state + m_is_running = false; + NetPlay_Disable(); + + sf::Packet spac; + spac << (MessageId)NP_MSG_DISABLE_GAME; + // this thread doesnt need players lock + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); + } + + sf::Packet spac; + spac << (MessageId)NP_MSG_PLAYER_LEAVE; + spac << m_players[socket].pid; + + m_selector.Remove(socket); + + m_crit.players.Enter(); // lock players + m_players.erase(m_players.find(socket)); + m_crit.players.Leave(); + + // alert other players of disconnect + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); + + UpdateGUI(); + + return 0; +} + +// called from ---GUI--- thread and ---NETPLAY--- thread +u64 NetPlayServer::CalculateMinimumBufferTime() +{ + CritLocker player_lock(m_crit.players); + + std::map::const_iterator + i = m_players.begin(), + e = m_players.end(); + std::priority_queue pings; + for ( ;i!=e; ++i) + pings.push(i->second.ping); + + unsigned int required_ms = pings.top(); + // if there is more than 1 client, buffersize must be >= (2 highest ping times combined) + if (pings.size() > 1) + { + pings.pop(); + required_ms += pings.top(); + } + + return required_ms; +} + +// called from ---GUI--- thread and ---NETPLAY--- thread +void NetPlayServer::AdjustPadBufferSize(unsigned int size) +{ + CritLocker game_lock(m_crit.game); // lock game state + + m_crit.buffer.Enter(); // lock buffer + m_target_buffer_size = size; + m_crit.buffer.Leave(); + + // tell clients to change buffer size + sf::Packet spac; + spac << (MessageId)NP_MSG_PAD_BUFFER; + spac << (u32)m_target_buffer_size; + + m_crit.players.Enter(); // lock players + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); + m_crit.players.Leave(); +} + +// called from ---NETPLAY--- thread +unsigned int NetPlayServer::OnData(sf::Packet& packet, sf::SocketTCP& socket) +{ + MessageId mid; + packet >> 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 + Client& player = m_players[socket]; + + 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; + + m_crit.send.Enter(); // lock send + SendToClients(spac, player.pid); + m_crit.send.Leave(); + + // add to gui + std::ostringstream ss; + ss << player.name << '[' << (char)(player.pid+'0') << "]: " << msg; + + AppendChatGUI(ss.str()); + } + break; + + case NP_MSG_PAD_DATA : + { + m_crit.buffer.Enter(); // lock buffer + + // if this is pad data from the last game still being received, ignore it + if (player.on_game != m_on_game) + break; + + PadMapping map = 0; + NetPad np; + packet >> map >> np.nHi >> np.nLo; + + // check if client's pad indeed maps in game + if (map >= 0 && map < 4) + map = player.pad_map[(unsigned)map]; + else + map = -1; + + // if not, they are hacking, so disconnect them + // this could happen right after a pad map change, but that isn't implimented yet + if (map < 0) + return 1; + + // add to pad buffer + m_pad_buffer[(unsigned)map].push(np); + m_crit.buffer.Leave(); + + // relay to clients + sf::Packet spac; + spac << (MessageId)NP_MSG_PAD_DATA; + spac << map; // in game mapping + spac << np.nHi << np.nLo; + + m_crit.send.Enter(); // lock send + SendToClients(spac, player.pid); + m_crit.send.Leave(); + } + break; + + case NP_MSG_PONG : + { + const u32 ping = m_ping_timer.GetTimeElapsed(); + u32 ping_key = 0; + packet >> ping_key; + + if (m_ping_key == ping_key) + { + //PanicAlert("good pong"); + player.ping = ping; + } + UpdateGUI(); + } + break; + + case NP_MSG_START_GAME : + { + packet >> player.on_game; + } + break; + + default : + PanicAlert("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); + // unknown message, kick the client + return 1; + break; + } + + return 0; +} + +// called from ---GUI--- thread +void NetPlayServer::GetPlayerList(std::string &list) +{ + CritLocker player_lock(m_crit.players); + + std::ostringstream ss; + + std::map::const_iterator + i = m_players.begin(), + e = m_players.end(); + for ( ; i!=e; ++i) + ss << i->second.ToString() << " " << i->second.ping << "ms\n"; + + list = ss.str(); +} + +// called from ---GUI--- thread / and ---NETPLAY--- thread +void NetPlayServer::SendChatMessage(const std::string& msg) +{ + sf::Packet spac; + spac << (MessageId)NP_MSG_CHAT_MESSAGE; + spac << (PlayerId)0; // server id always 0 + spac << msg; + + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); +} + +// called from ---GUI--- thread +bool NetPlayServer::ChangeGame(const std::string &game) +{ + CritLocker game_lock(m_crit.game); // lock game state + + m_selected_game = game; + + // send changed game to clients + sf::Packet spac; + spac << (MessageId)NP_MSG_CHANGE_GAME; + spac << game; + + m_crit.players.Enter(); // lock players + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); + m_crit.players.Leave(); + + return true; +} + +// called from ---CPU--- thread +void NetPlayServer::SendPadState(const PadMapping local_nb, const NetPad& np) +{ + // send to server + sf::Packet spac; + spac << (MessageId)NP_MSG_PAD_DATA; + spac << m_local_player->pad_map[local_nb]; // in-game pad num + spac << np.nHi << np.nLo; + + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); +} + +// called from ---GUI--- thread +bool NetPlayServer::StartGame(const std::string &path) +{ + m_crit.buffer.Enter(); // lock buffer + + if (false == NetPlay::StartGame(path)) + return false; + + // TODO: i dont like this here + m_on_game = Common::Timer::GetTimeMs(); + ClearBuffers(); + m_crit.buffer.Leave(); + + // no change, just update with clients + AdjustPadBufferSize(m_target_buffer_size); + + // tell clients to start game + sf::Packet spac; + spac << (MessageId)NP_MSG_START_GAME; + spac << m_on_game; + + m_crit.players.Enter(); // lock players + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.send.Leave(); + m_crit.players.Leave(); + + return true; +} + + +// called from ---GUI--- thread +bool NetPlayServer::StopGame() +{ + if (false == NetPlay::StopGame()) + return false; + + // tell clients to stop game + sf::Packet spac; + spac << (MessageId)NP_MSG_STOP_GAME; + + m_crit.players.Enter(); // lock players + m_crit.send.Enter(); // lock send + SendToClients(spac); + m_crit.players.Leave(); + m_crit.send.Leave(); + + return true; +} + +// called from multiple threads +void NetPlayServer::SendToClients(sf::Packet& packet, const PlayerId skip_pid) +{ + std::map::iterator + i = m_players.begin(), + e = m_players.end(); + for ( ; i!=e; ++i) + if (i->second.pid && (i->second.pid != skip_pid)) + i->second.socket.Send(packet); +}