diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index dfe91db48b..fdd3183587 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -80,6 +80,8 @@ + + @@ -116,6 +118,7 @@ + @@ -142,4 +145,4 @@ - + \ No newline at end of file diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 2f121ca882..da11bd379b 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -71,6 +71,9 @@ + + + @@ -117,8 +120,10 @@ + + - + \ No newline at end of file diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp new file mode 100644 index 0000000000..9ac5eca238 --- /dev/null +++ b/Source/Core/Common/TraversalClient.cpp @@ -0,0 +1,370 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#include "Common/TraversalClient.h" +#include "enet/enet.h" +#include "Timer.h" + +static void GetRandomishBytes(u8* buf, size_t size) +{ + // We don't need high quality random numbers (which might not be available), + // just non-repeating numbers! + srand(enet_time_get()); + for (size_t i = 0; i < size; i++) + buf[i] = rand() & 0xff; +} + +TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server) + : m_NetHost(netHost) + , m_Server(server) + , m_Client(nullptr) + , m_FailureReason(0) + , m_ConnectRequestId(0) + , m_PendingConnect(false) + , m_PingTime(0) +{ + netHost->intercept = TraversalClient::InterceptCallback; + + Reset(); + + ReconnectToServer(); +} + +TraversalClient::~TraversalClient() +{ +} + +void TraversalClient::ReconnectToServer() +{ + m_Server = "vps.qoid.us"; // XXX + if (enet_address_set_host(&m_ServerAddress, m_Server.c_str())) + { + OnFailure(BadHost); + return; + } + m_ServerAddress.port = 6262; + + m_State = Connecting; + + TraversalPacket hello = {}; + hello.type = TraversalPacketHelloFromClient; + hello.helloFromClient.protoVersion = TraversalProtoVersion; + SendTraversalPacket(hello); + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +static ENetAddress MakeENetAddress(TraversalInetAddress* address) +{ + ENetAddress eaddr; + if (address->isIPV6) + { + eaddr.port = 0; // no support yet :( + } + else + { + eaddr.host = address->address[0]; + eaddr.port = ntohs(address->port); + } + return eaddr; +} + +void TraversalClient::ConnectToClient(const std::string& host) +{ + if (host.size() > sizeof(TraversalHostId)) + { + PanicAlert("host too long"); + return; + } + TraversalPacket packet = {}; + packet.type = TraversalPacketConnectPlease; + memcpy(packet.connectPlease.hostId.data(), host.c_str(), host.size()); + m_ConnectRequestId = SendTraversalPacket(packet); + m_PendingConnect = true; +} + +bool TraversalClient::TestPacket(u8* data, size_t size, ENetAddress* from) +{ + if (from->host == m_ServerAddress.host && + from->port == m_ServerAddress.port) + { + if (size < sizeof(TraversalPacket)) + { + ERROR_LOG(NETPLAY, "Received too-short traversal packet."); + } + else + { + HandleServerPacket((TraversalPacket*) data); + return true; + } + } + return false; +} + +//--Temporary until more of the old netplay branch is moved over +void TraversalClient::Update() +{ + ENetEvent netEvent; + if (enet_host_service(m_NetHost, &netEvent, 4) > 0) + { + switch (netEvent.type) + { + case ENET_EVENT_TYPE_RECEIVE: + TestPacket(netEvent.packet->data, netEvent.packet->dataLength, &netEvent.peer->address); + + enet_packet_destroy(netEvent.packet); + break; + } + } + HandleResends(); +} + +void TraversalClient::HandleServerPacket(TraversalPacket* packet) +{ + u8 ok = 1; + switch (packet->type) + { + case TraversalPacketAck: + if (!packet->ack.ok) + { + OnFailure(ServerForgotAboutUs); + break; + } + for (auto it = m_OutgoingTraversalPackets.begin(); it != m_OutgoingTraversalPackets.end(); ++it) + { + if (it->packet.requestId == packet->requestId) + { + m_OutgoingTraversalPackets.erase(it); + break; + } + } + break; + case TraversalPacketHelloFromServer: + if (m_State != Connecting) + break; + if (!packet->helloFromServer.ok) + { + OnFailure(VersionTooOld); + break; + } + m_HostId = packet->helloFromServer.yourHostId; + m_State = Connected; + if (m_Client) + m_Client->OnTraversalStateChanged(); + break; + case TraversalPacketPleaseSendPacket: + { + // security is overrated. + ENetAddress addr = MakeENetAddress(&packet->pleaseSendPacket.address); + if (addr.port != 0) + { + char message[] = "Hello from Dolphin Netplay..."; + ENetBuffer buf; + buf.data = message; + buf.dataLength = sizeof(message) - 1; + enet_socket_send(m_NetHost->socket, &addr, &buf, 1); + } + else + { + // invalid IPV6 + ok = 0; + } + break; + } + case TraversalPacketConnectReady: + case TraversalPacketConnectFailed: + { + if (!m_PendingConnect || packet->connectReady.requestId != m_ConnectRequestId) + break; + + m_PendingConnect = false; + + if (!m_Client) + break; + + if (packet->type == TraversalPacketConnectReady) + m_Client->OnConnectReady(MakeENetAddress(&packet->connectReady.address)); + else + m_Client->OnConnectFailed(packet->connectFailed.reason); + break; + } + default: + WARN_LOG(NETPLAY, "Received unknown packet with type %d", packet->type); + break; + } + if (packet->type != TraversalPacketAck) + { + TraversalPacket ack = {}; + ack.type = TraversalPacketAck; + ack.requestId = packet->requestId; + ack.ack.ok = ok; + + ENetBuffer buf; + buf.data = &ack; + buf.dataLength = sizeof(ack); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); + } +} + +void TraversalClient::OnFailure(int reason) +{ + m_State = Failure; + m_FailureReason = reason; + + switch (reason) + { + case TraversalClient::BadHost: + { + auto server = "dolphin-emu.org"; + PanicAlertT("Couldn't look up central server %s", server); + break; + } + case TraversalClient::VersionTooOld: + PanicAlertT("Dolphin too old for traversal server"); + break; + case TraversalClient::ServerForgotAboutUs: + PanicAlertT("Disconnected from traversal server"); + break; + case TraversalClient::SocketSendError: + PanicAlertT("Socket error sending to traversal server"); + break; + case TraversalClient::ResendTimeout: + PanicAlertT("Timeout connecting to traversal server"); + break; + default: + PanicAlertT("Unknown error %x", reason); + break; + } + + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +void TraversalClient::ResendPacket(OutgoingTraversalPacketInfo* info) +{ + info->sendTime = enet_time_get(); + info->tries++; + ENetBuffer buf; + buf.data = &info->packet; + buf.dataLength = sizeof(info->packet); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); +} + +void TraversalClient::HandleResends() +{ + enet_uint32 now = enet_time_get(); + for (auto& tpi : m_OutgoingTraversalPackets) + { + if (now - tpi.sendTime >= (u32) (300 * tpi.tries)) + { + if (tpi.tries >= 5) + { + OnFailure(ResendTimeout); + m_OutgoingTraversalPackets.clear(); + break; + } + else + { + ResendPacket(&tpi); + } + } + } + HandlePing(); +} + +void TraversalClient::HandlePing() +{ + enet_uint32 now = enet_time_get(); + if (m_State == Connected && now - m_PingTime >= 500) + { + TraversalPacket ping = {0}; + ping.type = TraversalPacketPing; + ping.ping.hostId = m_HostId; + SendTraversalPacket(ping); + m_PingTime = now; + } +} + +TraversalRequestId TraversalClient::SendTraversalPacket(const TraversalPacket& packet) +{ + OutgoingTraversalPacketInfo info; + info.packet = packet; + GetRandomishBytes((u8*) &info.packet.requestId, sizeof(info.packet.requestId)); + info.tries = 0; + m_OutgoingTraversalPackets.push_back(info); + ResendPacket(&m_OutgoingTraversalPackets.back()); + return info.packet.requestId; +} + +void TraversalClient::Reset() +{ + m_PendingConnect = false; + m_Client = nullptr; +} + +int ENET_CALLBACK TraversalClient::InterceptCallback(ENetHost* host, ENetEvent* event) +{ + auto traversalClient = g_TraversalClient.get(); + if (traversalClient->TestPacket(host->receivedData, host->receivedDataLength, &host->receivedAddress)) + { + event->type = (ENetEventType)42; + return 1; + } + return 0; +} + +std::unique_ptr g_TraversalClient; +std::unique_ptr g_MainNetHost; + +// The settings at the previous TraversalClient reset - notably, we +// need to know not just what port it's on, but whether it was +// explicitly requested. +static std::string g_OldServer; +static u16 g_OldPort; + +bool EnsureTraversalClient(const std::string& server, u16 port) +{ + if (!g_MainNetHost || !g_TraversalClient || server != g_OldServer || port != g_OldPort) + { + g_OldServer = server; + g_OldPort = port; + + ENetAddress addr = { ENET_HOST_ANY, port }; + ENetHost* host = enet_host_create( + &addr, // address + 50, // peerCount + 1, // channelLimit + 0, // incomingBandwidth + 0); // outgoingBandwidth + if (!host) + { + g_MainNetHost.reset(); + return false; + } + g_MainNetHost.reset(host); + + g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server)); + + } + return true; +} + +void ReleaseTraversalClient() +{ + if (!g_TraversalClient) + return; + + if (g_OldPort != 0) + { + // If we were listening at a specific port, kill the + // TraversalClient to avoid hanging on to the port. + g_TraversalClient.reset(); + g_MainNetHost.reset(); + } + else + { + // Reset any pending connection attempts. + g_TraversalClient->Reset(); + } +} diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h new file mode 100644 index 0000000000..838ea6448a --- /dev/null +++ b/Source/Core/Common/TraversalClient.h @@ -0,0 +1,88 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include +#include +#include +#include "Common/Common.h" +#include "Common/Thread.h" +#include "Common/TraversalProto.h" + +#include "enet/enet.h" + +class TraversalClientClient +{ +public: + virtual ~TraversalClientClient(){}; + virtual void OnTraversalStateChanged()=0; + virtual void OnConnectReady(ENetAddress addr)=0; + virtual void OnConnectFailed(u8 reason)=0; +}; + +class TraversalClient +{ +public: + enum State + { + Connecting, + Connected, + Failure + }; + + enum FailureReason + { + BadHost = 0x300, + VersionTooOld, + ServerForgotAboutUs, + SocketSendError, + ResendTimeout, + ConnectFailedError = 0x400, + }; + + TraversalClient(ENetHost* netHost, const std::string& server); + ~TraversalClient(); + void Reset(); + void ConnectToClient(const std::string& host); + void ReconnectToServer(); + void Update(); + + // called from NetHost + bool TestPacket(u8* data, size_t size, ENetAddress* from); + void HandleResends(); + + ENetHost* m_NetHost; + TraversalClientClient* m_Client; + TraversalHostId m_HostId; + State m_State; + int m_FailureReason; + +private: + struct OutgoingTraversalPacketInfo + { + TraversalPacket packet; + int tries; + enet_uint32 sendTime; + }; + + void HandleServerPacket(TraversalPacket* packet); + void ResendPacket(OutgoingTraversalPacketInfo* info); + TraversalRequestId SendTraversalPacket(const TraversalPacket& packet); + void OnFailure(int reason); + void HandlePing(); + static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event); + + TraversalRequestId m_ConnectRequestId; + bool m_PendingConnect; + std::list m_OutgoingTraversalPackets; + ENetAddress m_ServerAddress; + std::string m_Server; + enet_uint32 m_PingTime; +}; + +extern std::unique_ptr g_TraversalClient; +// the NetHost connected to the TraversalClient. +extern std::unique_ptr g_MainNetHost; + +// Create g_TraversalClient and g_MainNetHost if necessary. +bool EnsureTraversalClient(const std::string& server, u16 port); +void ReleaseTraversalClient(); diff --git a/Source/Core/Common/TraversalProto.h b/Source/Core/Common/TraversalProto.h new file mode 100644 index 0000000000..32891beac6 --- /dev/null +++ b/Source/Core/Common/TraversalProto.h @@ -0,0 +1,96 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include +#include "Common/CommonTypes.h" + + +typedef std::array TraversalHostId; +typedef u64 TraversalRequestId; + +enum TraversalPacketType +{ + // [*->*] + TraversalPacketAck = 0, + // [c->s] + TraversalPacketPing = 1, + // [c->s] + TraversalPacketHelloFromClient = 2, + // [s->c] + TraversalPacketHelloFromServer = 3, + // [c->s] When connecting, first the client asks the central server... + TraversalPacketConnectPlease = 4, + // [s->c] ...who asks the game host to send a UDP packet to the + // client... (an ack implies success) + TraversalPacketPleaseSendPacket = 5, + // [s->c] ...which the central server relays back to the client. + TraversalPacketConnectReady = 6, + // [s->c] Alternately, the server might not have heard of this host. + TraversalPacketConnectFailed = 7 +}; + +enum +{ + TraversalProtoVersion = 0 +}; + +enum TraversalConnectFailedReason +{ + TraversalConnectFailedClientDidntRespond = 0, + TraversalConnectFailedClientFailure, + TraversalConnectFailedNoSuchClient +}; + +#pragma pack(push, 1) +struct TraversalInetAddress +{ + u8 isIPV6; + u32 address[4]; + u16 port; +}; +struct TraversalPacket +{ + u8 type; + TraversalRequestId requestId; + union + { + struct + { + u8 ok; + } ack; + struct + { + TraversalHostId hostId; + } ping; + struct + { + u8 protoVersion; + } helloFromClient; + struct + { + u8 ok; + TraversalHostId yourHostId; + TraversalInetAddress yourAddress; // currently unused + } helloFromServer; + struct + { + TraversalHostId hostId; + } connectPlease; + struct + { + TraversalInetAddress address; + } pleaseSendPacket; + struct + { + TraversalRequestId requestId; + TraversalInetAddress address; + } connectReady; + struct + { + TraversalRequestId requestId; + u8 reason; + } connectFailed; + }; +}; +#pragma pack(pop) + diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 8bb26feeb8..77c9a06e71 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -38,10 +38,25 @@ NetPlayClient::~NetPlayClient() Disconnect(); } + if (g_MainNetHost.get() == m_client) + { + g_MainNetHost.release(); + } + if (m_client) + { + enet_host_destroy(m_client); + m_client = nullptr; + } + + if (m_traversal_client) + { + ReleaseTraversalClient(); + } + } // called from ---GUI--- thread -NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name) +NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal) : m_dialog(dialog) , m_client(nullptr) , m_server(nullptr) @@ -53,6 +68,8 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay , m_is_recording(false) , m_pid(0) , m_connecting(false) + , m_traversal_client(nullptr) + , m_state(Failure) { m_target_buffer_size = 20; ClearBuffers(); @@ -61,35 +78,81 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay m_player_name = name; - //Direct Connection - m_client = enet_host_create(nullptr, 1, 3, 0, 0); - - if (m_client == nullptr) + if (!traversal) { - PanicAlertT("Couldn't Create Client"); - } + //Direct Connection + m_client = enet_host_create(nullptr, 1, 3, 0, 0); - ENetAddress addr; - enet_address_set_host(&addr, address.c_str()); - addr.port = port; + if (m_client == nullptr) + { + PanicAlertT("Couldn't Create Client"); + } - m_server = enet_host_connect(m_client, &addr, 3, 0); + ENetAddress addr; + enet_address_set_host(&addr, address.c_str()); + addr.port = port; - if (m_server == nullptr) - { - PanicAlertT("Couldn't create peer."); - } + m_server = enet_host_connect(m_client, &addr, 3, 0); + + if (m_server == nullptr) + { + PanicAlertT("Couldn't create peer."); + } + + ENetEvent netEvent; + int net = enet_host_service(m_client, &netEvent, 5000); + if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) + { + if (Connect()) + m_thread = std::thread(&NetPlayClient::ThreadFunc, this); + } + else + { + PanicAlertT("Failed to Connect!"); + } - ENetEvent netEvent; - int net = enet_host_service(m_client, &netEvent, 5000); - if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) - { - if (Connect()) - m_thread = std::thread(&NetPlayClient::ThreadFunc, this); } else { - PanicAlertT("Failed to Connect!"); + //Traversal Server + if (!EnsureTraversalClient("dolphin-emu.org", 0)) + return; + m_client = g_MainNetHost.get(); + + m_traversal_client = g_TraversalClient.get(); + + // If we were disconnected in the background, reconnect. + if (m_traversal_client->m_State == TraversalClient::Failure) + m_traversal_client->ReconnectToServer(); + m_traversal_client->m_Client = this; + m_host_spec = address; + m_state = WaitingForTraversalClientConnection; + OnTraversalStateChanged(); + m_connecting = true; + + while (m_connecting) + { + ENetEvent netEvent; + if (m_traversal_client) + m_traversal_client->HandleResends(); + + while (enet_host_service(m_client, &netEvent, 4) > 0) + { + sf::Packet rpac; + switch (netEvent.type) + { + case ENET_EVENT_TYPE_CONNECT: + m_server = netEvent.peer; + if (Connect()) + { + m_state = Connected; + m_thread = std::thread(&NetPlayClient::ThreadFunc, this); + } + return; + } + } + } + PanicAlertT("Failed To Connect!"); } } @@ -380,6 +443,7 @@ void NetPlayClient::Send(sf::Packet& packet) void NetPlayClient::Disconnect() { ENetEvent netEvent; + m_state = Failure; enet_peer_disconnect(m_server, 0); while (enet_host_service(m_client, &netEvent, 3000) > 0) { @@ -407,6 +471,8 @@ void NetPlayClient::ThreadFunc() int net; { std::lock_guard lks(m_crit.send); + if (m_traversal_client) + m_traversal_client->HandleResends(); net = enet_host_service(m_client, &netEvent, 4); } if (net > 0) @@ -630,6 +696,55 @@ void NetPlayClient::ClearBuffers() } } +// called from ---NETPLAY--- thread +void NetPlayClient::OnTraversalStateChanged() +{ + if (m_state == WaitingForTraversalClientConnection && + m_traversal_client->m_State == TraversalClient::Connected) + { + m_state = WaitingForTraversalClientConnectReady; + m_traversal_client->ConnectToClient(m_host_spec); + } + else if (m_state != Failure && + m_traversal_client->m_State == TraversalClient::Failure) + { + Disconnect(); + } +} + +// called from ---NETPLAY--- thread +void NetPlayClient::OnConnectReady(ENetAddress addr) +{ + if (m_state == WaitingForTraversalClientConnectReady) + { + m_state = Connecting; + enet_host_connect(m_client, &addr, 0, 0); + } +} + +// called from ---NETPLAY--- thread +void NetPlayClient::OnConnectFailed(u8 reason) +{ + m_connecting = false; + m_state = Failure; + int swtch = TraversalClient::ConnectFailedError + reason; + switch (swtch) + { + case TraversalClient::ConnectFailedError + TraversalConnectFailedClientDidntRespond: + PanicAlertT("Traversal server timed out connecting to the host"); + break; + case TraversalClient::ConnectFailedError + TraversalConnectFailedClientFailure: + PanicAlertT("Server rejected traversal attempt"); + break; + case TraversalClient::ConnectFailedError + TraversalConnectFailedNoSuchClient: + PanicAlertT("Invalid host"); + break; + default: + PanicAlertT("Unknown error %x", swtch); + break; + } +} + // called from ---CPU--- thread bool NetPlayClient::GetNetPads(const u8 pad_nb, GCPadStatus* pad_status) { diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 6d923f0697..fab8e6548f 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -7,12 +7,11 @@ #include #include #include - -#include "enet/enet.h" #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" #include "Common/Thread.h" #include "Common/Timer.h" +#include "Common/TraversalClient.h" #include "Core/NetPlayProto.h" #include "InputCommon/GCPadStatus.h" #include @@ -43,12 +42,12 @@ public: u32 ping; }; -class NetPlayClient +class NetPlayClient : public TraversalClientClient { public: void ThreadFunc(); - NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name); + NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal); ~NetPlayClient(); void GetPlayerList(std::string& list, std::vector& pid_list); @@ -66,11 +65,25 @@ public: bool WiimoteUpdate(int _number, u8* data, const u8 size); bool GetNetPads(const u8 pad_nb, GCPadStatus* pad_status); + void OnTraversalStateChanged() override; + void OnConnectReady(ENetAddress addr) override; + void OnConnectFailed(u8 reason) override; + u8 LocalPadToInGamePad(u8 localPad); u8 InGamePadToLocalPad(u8 localPad); u8 LocalWiimoteToInGameWiimote(u8 local_pad); + enum State + { + WaitingForTraversalClientConnection, + WaitingForTraversalClientConnectReady, + Connecting, + WaitingForHelloResponse, + Connected, + Failure + } m_state; + protected: void ClearBuffers(); @@ -121,6 +134,7 @@ private: std::string m_host_spec; std::string m_player_name; bool m_connecting; + TraversalClient* m_traversal_client; }; void NetPlay_Enable(NetPlayClient* const np); diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 255635810b..b7409fa54b 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -34,32 +34,31 @@ typedef std::vector NetWiimote; static const int NETPLAY_INITIAL_GCTIME = 1272737767; - // messages enum { - NP_MSG_PLAYER_JOIN = 0x10, - NP_MSG_PLAYER_LEAVE = 0x11, + NP_MSG_PLAYER_JOIN = 0x10, + NP_MSG_PLAYER_LEAVE = 0x11, - NP_MSG_CHAT_MESSAGE = 0x30, + NP_MSG_CHAT_MESSAGE = 0x30, - NP_MSG_PAD_DATA = 0x60, - NP_MSG_PAD_MAPPING = 0x61, - NP_MSG_PAD_BUFFER = 0x62, + NP_MSG_PAD_DATA = 0x60, + NP_MSG_PAD_MAPPING = 0x61, + NP_MSG_PAD_BUFFER = 0x62, - NP_MSG_WIIMOTE_DATA = 0x70, - NP_MSG_WIIMOTE_MAPPING = 0x71, + NP_MSG_WIIMOTE_DATA = 0x70, + NP_MSG_WIIMOTE_MAPPING = 0x71, - NP_MSG_START_GAME = 0xA0, - NP_MSG_CHANGE_GAME = 0xA1, - NP_MSG_STOP_GAME = 0xA2, - NP_MSG_DISABLE_GAME = 0xA3, + NP_MSG_START_GAME = 0xA0, + NP_MSG_CHANGE_GAME = 0xA1, + NP_MSG_STOP_GAME = 0xA2, + NP_MSG_DISABLE_GAME = 0xA3, - NP_MSG_READY = 0xD0, - NP_MSG_NOT_READY = 0xD1, + NP_MSG_READY = 0xD0, + NP_MSG_NOT_READY = 0xD1, - NP_MSG_PING = 0xE0, - NP_MSG_PONG = 0xE1, + NP_MSG_PING = 0xE0, + NP_MSG_PONG = 0xE1, NP_MSG_PLAYER_PING_DATA = 0xE2, }; @@ -70,8 +69,8 @@ typedef u32 FrameNum; enum { - CON_ERR_SERVER_FULL = 1, - CON_ERR_GAME_RUNNING = 2, + CON_ERR_SERVER_FULL = 1, + CON_ERR_GAME_RUNNING = 2, CON_ERR_VERSION_MISMATCH = 3 }; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index f8861c32e4..9282d71dbc 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -19,6 +19,17 @@ NetPlayServer::~NetPlayServer() m_do_loop = false; m_thread.join(); enet_host_destroy(m_server); + + if (g_MainNetHost.get() == m_server) + { + g_MainNetHost.release(); + } + + if (m_traversal_client) + { + g_TraversalClient->m_Client = nullptr; + ReleaseTraversalClient(); + } } #ifdef USE_UPNP @@ -30,7 +41,7 @@ NetPlayServer::~NetPlayServer() } // called from ---GUI--- thread -NetPlayServer::NetPlayServer(const u16 port) +NetPlayServer::NetPlayServer(const u16 port, bool traversal) : is_connected(false) , m_is_running(false) , m_do_loop(false) @@ -40,6 +51,8 @@ NetPlayServer::NetPlayServer(const u16 port) , m_target_buffer_size(0) , m_selected_game("") , m_server(nullptr) + , m_traversal_client(nullptr) + , m_dialog(nullptr) { //--use server time if (enet_initialize() != 0) @@ -49,11 +62,26 @@ NetPlayServer::NetPlayServer(const u16 port) memset(m_pad_map, -1, sizeof(m_pad_map)); memset(m_wiimote_map, -1, sizeof(m_wiimote_map)); - - ENetAddress serverAddr; - serverAddr.host = ENET_HOST_ANY; - serverAddr.port = port; - m_server = enet_host_create(&serverAddr, 10, 3, 0, 0); + if (traversal) + { + if (!EnsureTraversalClient("dolphin-emu.org", 0)) + return; + + g_TraversalClient->m_Client = this; + m_traversal_client = g_TraversalClient.get(); + + m_server = g_MainNetHost.get(); + + if (g_TraversalClient->m_State == TraversalClient::Failure) + g_TraversalClient->ReconnectToServer(); + } + else + { + ENetAddress serverAddr; + serverAddr.host = ENET_HOST_ANY; + serverAddr.port = port; + m_server = enet_host_create(&serverAddr, 10, 3, 0, 0); + } if (m_server != nullptr) { @@ -89,6 +117,8 @@ void NetPlayServer::ThreadFunc() int net; { std::lock_guard lks(m_crit.send); + if (m_traversal_client) + m_traversal_client->HandleResends(); net = enet_host_service(m_server, &netEvent, 4); } if (net > 0) @@ -533,6 +563,13 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) return 0; } + +void NetPlayServer::OnTraversalStateChanged() +{ + if (m_dialog) + m_dialog->Update(); +} + // called from ---GUI--- thread / and ---NETPLAY--- thread void NetPlayServer::SendChatMessage(const std::string& msg) { @@ -638,6 +675,96 @@ u16 NetPlayServer::GetPort() return m_server->address.port; } +void NetPlayServer::SetNetPlayUI(NetPlayUI* dialog) +{ + m_dialog = dialog; +} + +// called from ---GUI--- thread +std::unordered_set NetPlayServer::GetInterfaceSet() +{ + std::unordered_set result; + auto lst = GetInterfaceListInternal(); + for (auto list_entry : lst) + result.emplace(list_entry.first); + return result; +} + +// called from ---GUI--- thread +std::string NetPlayServer::GetInterfaceHost(const std::string inter) +{ + char buf[16]; + sprintf(buf, ":%d", GetPort()); + auto lst = GetInterfaceListInternal(); + for (const auto& list_entry : lst) + { + if (list_entry.first == inter) + { + return list_entry.second + buf; + } + } + return "?"; +} + +// called from ---GUI--- thread +std::vector> NetPlayServer::GetInterfaceListInternal() +{ + std::vector> result; +#if defined(_WIN32) + +#elif defined(__APPLE__) + // we do this to get the friendly names rather than the BSD ones. ew. + if (m_dynamic_store && m_prefs) + { + CFArrayRef ary = SCNetworkServiceCopyAll((SCPreferencesRef)m_prefs); + for (CFIndex i = 0; i < CFArrayGetCount(ary); i++) + { + SCNetworkServiceRef ifr = (SCNetworkServiceRef)CFArrayGetValueAtIndex(ary, i); + std::string name = CFStrToStr(SCNetworkServiceGetName(ifr)); + CFStringRef key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, SCNetworkServiceGetServiceID(ifr), kSCEntNetIPv4); + CFDictionaryRef props = (CFDictionaryRef)SCDynamicStoreCopyValue((SCDynamicStoreRef)m_dynamic_store, key); + CFRelease(key); + if (!props) + continue; + CFArrayRef ipary = (CFArrayRef)CFDictionaryGetValue(props, kSCPropNetIPv4Addresses); + if (ipary) + { + for (CFIndex j = 0; j < CFArrayGetCount(ipary); j++) + result.emplace_back(std::make_pair(name, CFStrToStr((CFStringRef)CFArrayGetValueAtIndex(ipary, j)))); + CFRelease(ipary); + } + } + CFRelease(ary); + } +#elif defined(ANDROID) + // Android has no getifaddrs for some stupid reason. If this + // functionality ends up actually being used on Android, fix this. +#else + ifaddrs* ifp; + char buf[512]; + if (getifaddrs(&ifp) != -1) + { + for (struct ifaddrs* curifp = ifp; curifp; curifp = curifp->ifa_next) + { + struct sockaddr* sa = curifp->ifa_addr; + if (sa->sa_family != AF_INET) + continue; + struct sockaddr_in* sai = (struct sockaddr_in*) sa; + if (ntohl(((struct sockaddr_in*) sa)->sin_addr.s_addr) == 0x7f000001) + continue; + const char* ip = inet_ntop(sa->sa_family, &sai->sin_addr, buf, sizeof(buf)); + if (ip == nullptr) + continue; + result.emplace_back(std::make_pair(curifp->ifa_name, ip)); + } + freeifaddrs(ifp); + } +#endif + if (result.empty()) + result.push_back(std::make_pair("!local!", "127.0.0.1")); + return result; +} + #ifdef USE_UPNP #include #include diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index d82195f5f7..6fa7909822 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -9,18 +9,20 @@ #include #include -#include "enet/enet.h" #include "Common/Thread.h" +#include "Common/TraversalClient.h" #include "Common/Timer.h" #include "Core/NetPlayProto.h" #include -class NetPlayServer +class NetPlayUI; + +class NetPlayServer : public TraversalClientClient { public: void ThreadFunc(); - NetPlayServer(const u16 port); + NetPlayServer(const u16 port, bool traversal); ~NetPlayServer(); bool ChangeGame(const std::string& game); @@ -42,6 +44,10 @@ public: u16 GetPort(); + void SetNetPlayUI(NetPlayUI* dialog); + std::unordered_set GetInterfaceSet(); + std::string GetInterfaceHost(const std::string inter); + bool is_connected; #ifdef USE_UPNP @@ -71,8 +77,12 @@ private: unsigned int OnConnect(ENetPeer* socket); unsigned int OnDisconnect(Client& player); unsigned int OnData(sf::Packet& packet, Client& player); + virtual void OnTraversalStateChanged(); + virtual void OnConnectReady(ENetAddress addr) {} + virtual void OnConnectFailed(u8 reason) {} void UpdatePadMapping(); void UpdateWiimoteMapping(); + std::vector> GetInterfaceListInternal(); NetSettings m_settings; @@ -99,6 +109,8 @@ private: std::thread m_thread; ENetHost* m_server; + TraversalClient* m_traversal_client; + NetPlayUI* m_dialog; #ifdef USE_UPNP static void mapPortThread(const u16 port); diff --git a/Source/Core/DolphinWX/NetWindow.cpp b/Source/Core/DolphinWX/NetWindow.cpp index eb15e533a0..8d99a9403b 100644 --- a/Source/Core/DolphinWX/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetWindow.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" @@ -53,6 +54,26 @@ static NetPlayServer* netplay_server = nullptr; static NetPlayClient* netplay_client = nullptr; NetPlayDiag *NetPlayDiag::npd = nullptr; + +static wxString FailureReasonStringForHostLabel(int reason) +{ + switch (reason) + { + case TraversalClient::BadHost: + return _("(Error: Bad host)"); + case TraversalClient::VersionTooOld: + return _("(Error: Dolphin too old)"); + case TraversalClient::ServerForgotAboutUs: + return _("(Error: Disconnected)"); + case TraversalClient::SocketSendError: + return _("(Error: Socket)"); + case TraversalClient::ResendTimeout: + return _("(Error: Timeout)"); + default: + return _("(Error: Unknown)"); + } +} + static std::string BuildGameName(const GameListItem& game) { // Lang needs to be consistent @@ -93,7 +114,6 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* nick_szr->Add(nick_lbl, 0, wxCENTER); nick_szr->Add(m_nickname_text, 0, wxALL, 5); - // tabs wxNotebook* const notebook = new wxNotebook(panel, wxID_ANY); wxPanel* const connect_tab = new wxPanel(notebook, wxID_ANY); @@ -101,85 +121,91 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* wxPanel* const host_tab = new wxPanel(notebook, wxID_ANY); notebook->AddPage(host_tab, _("Host")); + m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(75, -1)); + m_direct_traversal->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlaySetupDiag::OnChoice, this); + m_direct_traversal->Append(_("Traversal")); + m_direct_traversal->Append(_("Direct")); + m_direct_traversal->Select(0); // connect tab { - wxStaticText* const ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Address :")); + m_ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Host Code :")); - std::string address; - netplay_section.Get("Address", &address, "localhost"); - m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); + std::string address; + netplay_section.Get("HostCode", &address, "00000000"); + m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); - wxStaticText* const port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); + m_client_port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); - // string? w/e - std::string port; - netplay_section.Get("ConnectPort", &port, "2626"); - m_connect_port_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(port)); + // string? w/e + std::string port; + netplay_section.Get("ConnectPort", &port, "2626"); + m_connect_port_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(port)); - wxButton* const connect_btn = new wxButton(connect_tab, wxID_ANY, _("Connect")); - connect_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnJoin, this); + wxButton* const connect_btn = new wxButton(connect_tab, wxID_ANY, _("Connect")); + connect_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnJoin, this); - wxStaticText* const alert_lbl = new wxStaticText(connect_tab, wxID_ANY, - _("ALERT:\n\n" - "Netplay will only work with the following settings:\n" - " - DSP Emulator Engine Must be the same on all computers!\n" - " - DSP on Dedicated Thread [OFF]\n" - " - Manually set the extensions for each Wiimote\n" - "\n" - "All players should use the same Dolphin version and settings.\n" - "All memory cards must be identical between players or disabled.\n" - "Wiimote support is probably terrible. Don't use it.\n" - "\n" - "The host must have the chosen TCP port open/forwarded!\n")); + wxStaticText* const alert_lbl = new wxStaticText(connect_tab, wxID_ANY, + _("ALERT:\n\n" + "Netplay will only work with the following settings:\n" + " - DSP Emulator Engine Must be the same on all computers!\n" + " - DSP on Dedicated Thread [OFF]\n" + " - Manually set the extensions for each Wiimote\n" + "\n" + "All players should use the same Dolphin version and settings.\n" + "All memory cards must be identical between players or disabled.\n" + "Wiimote support is probably terrible. Don't use it.\n" + "\n" + "If connecting directly host must have the chosen UDP port open/forwarded!\n")); - wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - top_szr->Add(ip_lbl, 0, wxCENTER | wxRIGHT, 5); - top_szr->Add(m_connect_ip_text, 3); - top_szr->Add(port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); - top_szr->Add(m_connect_port_text, 1); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); - con_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); - con_szr->AddStretchSpacer(1); - con_szr->Add(alert_lbl, 0, wxLEFT | wxRIGHT | wxEXPAND, 5); - con_szr->AddStretchSpacer(1); - con_szr->Add(connect_btn, 0, wxALL | wxALIGN_RIGHT, 5); + top_szr->Add(m_ip_lbl, 0, wxCENTER | wxRIGHT, 5); + top_szr->Add(m_connect_ip_text, 3); + top_szr->Add(m_client_port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); + top_szr->Add(m_connect_port_text, 1); - connect_tab->SetSizerAndFit(con_szr); + wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); + con_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); + con_szr->AddStretchSpacer(1); + con_szr->Add(alert_lbl, 0, wxLEFT | wxRIGHT | wxEXPAND, 5); + con_szr->AddStretchSpacer(1); + con_szr->Add(connect_btn, 0, wxALL | wxALIGN_RIGHT, 5); + + connect_tab->SetSizerAndFit(con_szr); } // host tab { - wxStaticText* const port_lbl = new wxStaticText(host_tab, wxID_ANY, _("Port :")); + m_host_port_lbl = new wxStaticText(host_tab, wxID_ANY, _("Port :")); - // string? w/e - std::string port; - netplay_section.Get("HostPort", &port, "2626"); - m_host_port_text = new wxTextCtrl(host_tab, wxID_ANY, StrToWxStr(port)); + // string? w/e + std::string port; + netplay_section.Get("HostPort", &port, "2626"); + m_host_port_text = new wxTextCtrl(host_tab, wxID_ANY, StrToWxStr(port)); - wxButton* const host_btn = new wxButton(host_tab, wxID_ANY, _("Host")); - host_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnHost, this); + wxButton* const host_btn = new wxButton(host_tab, wxID_ANY, _("Host")); + host_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnHost, this); - m_game_lbox = new wxListBox(host_tab, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SORT); - m_game_lbox->Bind(wxEVT_LISTBOX_DCLICK, &NetPlaySetupDiag::OnHost, this); + m_game_lbox = new wxListBox(host_tab, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SORT); + m_game_lbox->Bind(wxEVT_LISTBOX_DCLICK, &NetPlaySetupDiag::OnHost, this); - FillWithGameNames(m_game_lbox, *game_list); + FillWithGameNames(m_game_lbox, *game_list); - wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - top_szr->Add(port_lbl, 0, wxCENTER | wxRIGHT, 5); - top_szr->Add(m_host_port_text, 0); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); + top_szr->Add(m_host_port_lbl, 0, wxCENTER | wxRIGHT, 5); + top_szr->Add(m_host_port_text, 0); #ifdef USE_UPNP - m_upnp_chk = new wxCheckBox(host_tab, wxID_ANY, _("Forward port (UPnP)")); - top_szr->Add(m_upnp_chk, 0, wxALL | wxALIGN_RIGHT, 5); + m_upnp_chk = new wxCheckBox(host_tab, wxID_ANY, _("Forward port (UPnP)")); + top_szr->Add(m_upnp_chk, 0, wxALL | wxALIGN_RIGHT, 5); #endif - wxBoxSizer* const host_szr = new wxBoxSizer(wxVERTICAL); - host_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); - host_szr->Add(m_game_lbox, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); - host_szr->Add(host_btn, 0, wxALL | wxALIGN_RIGHT, 5); + wxBoxSizer* const host_szr = new wxBoxSizer(wxVERTICAL); + host_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); + host_szr->Add(m_game_lbox, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); + host_szr->Add(host_btn, 0, wxALL | wxALIGN_RIGHT, 5); - host_tab->SetSizerAndFit(host_szr); + host_tab->SetSizerAndFit(host_szr); } // bottom row @@ -202,6 +228,21 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* Center(); Show(); + + // Needs to be done last or it set up the spacing on the page correctly + //client tab + { + m_client_port_lbl->Show(false); + m_connect_port_text->Show(false); + } + + //server tab + { + m_host_port_lbl->Show(false); + m_host_port_text->Show(false); + m_upnp_chk->Show(false); + } + } NetPlaySetupDiag::~NetPlaySetupDiag() @@ -212,7 +253,14 @@ NetPlaySetupDiag::~NetPlaySetupDiag() IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); netplay_section.Set("Nickname", WxStrToStr(m_nickname_text->GetValue())); - netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); + if (m_direct_traversal->GetCurrentSelection() == 1) + { + netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); + } + else + { + netplay_section.Set("HostCode", WxStrToStr(m_connect_ip_text->GetValue())); + } netplay_section.Set("ConnectPort", WxStrToStr(m_connect_port_text->GetValue())); netplay_section.Set("HostPort", WxStrToStr(m_host_port_text->GetValue())); @@ -230,7 +278,13 @@ void NetPlaySetupDiag::MakeNetPlayDiag(int port, const std::string &game, bool i else ip = WxStrToStr(m_connect_ip_text->GetValue()); - netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue())); + bool trav; + if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 0) + trav = true; + else + trav = false; + + netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue()), trav); if (netplay_client->is_connected) { npd->Show(); @@ -259,9 +313,15 @@ void NetPlaySetupDiag::OnHost(wxCommandEvent&) std::string game(WxStrToStr(m_game_lbox->GetStringSelection())); + bool trav; + if (m_direct_traversal->GetCurrentSelection() == 0) + trav = true; + else + trav = false; + unsigned long port = 0; m_host_port_text->GetValue().ToULong(&port); - netplay_server = new NetPlayServer(u16(port)); + netplay_server = new NetPlayServer(u16(port), trav); netplay_server->ChangeGame(game); netplay_server->AdjustPadBufferSize(INITIAL_PAD_BUFFER_SIZE); if (netplay_server->is_connected) @@ -270,7 +330,8 @@ void NetPlaySetupDiag::OnHost(wxCommandEvent&) if (m_upnp_chk->GetValue()) netplay_server->TryPortmapping(port); #endif - MakeNetPlayDiag(port, game, true); + MakeNetPlayDiag(netplay_server->GetPort(), game, true); + netplay_server->SetNetPlayUI(NetPlayDiag::GetInstance()); } else { @@ -292,17 +353,75 @@ void NetPlaySetupDiag::OnJoin(wxCommandEvent&) MakeNetPlayDiag(port, "", false); } +void NetPlaySetupDiag::OnChoice(wxCommandEvent& event) +{ + int sel = m_direct_traversal->GetSelection(); + IniFile inifile; + inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); + + if (sel == 0) + { + //Traversal + //client tab + { + m_ip_lbl->SetLabelText("Host Code: "); + + std::string address; + netplay_section.Get("HostCode", &address, "00000000"); + m_connect_ip_text->SetLabelText(address); + + m_client_port_lbl->Show(false); + m_connect_port_text->Show(false); + } + + //server tab + { + m_host_port_lbl->Show(false); + m_host_port_text->Show(false); + m_upnp_chk->Show(false); + } + } + else + { + //Direct + //client tab + { + m_ip_lbl->SetLabelText("IP Address :"); + + std::string address; + netplay_section.Get("Address", &address, "127.0.0.1"); + m_connect_ip_text->SetLabelText(address); + + m_client_port_lbl->Show(true); + m_connect_port_text->Show(true); + } + + //server tab + { + m_host_port_lbl->Show(true); + m_host_port_text->Show(true); + m_upnp_chk->Show(true); + } + } +} + void NetPlaySetupDiag::OnQuit(wxCommandEvent&) { Destroy(); } NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game_list, - const std::string& game, const bool is_hosting) + const std::string& game, const bool is_hosting) : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay")) , m_selected_game(game) , m_start_btn(nullptr) , m_game_list(game_list) + , m_host_type_choice(nullptr) + , m_host_label(nullptr) + , m_host_copy_btn(nullptr) + , m_host_copy_btn_is_retry(false) + , m_is_hosting(is_hosting) { Bind(wxEVT_THREAD, &NetPlayDiag::OnThread, this); @@ -310,10 +429,10 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game // top crap m_game_btn = new wxButton(panel, wxID_ANY, - StrToWxStr(m_selected_game).Prepend(_(" Game : ")), - wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + StrToWxStr(m_selected_game).Prepend(_(" Game : ")), + wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - if (is_hosting) + if (m_is_hosting) m_game_btn->Bind(wxEVT_BUTTON, &NetPlayDiag::OnChangeGame, this); else m_game_btn->Disable(); @@ -343,9 +462,34 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game m_player_lbox = new wxListBox(panel, wxID_ANY, wxDefaultPosition, wxSize(256, -1)); wxStaticBoxSizer* const player_szr = new wxStaticBoxSizer(wxVERTICAL, panel, _("Players")); - player_szr->Add(m_player_lbox, 1, wxEXPAND); + // player list - if (is_hosting) + if (m_is_hosting && g_TraversalClient) + { + wxBoxSizer* const host_szr = new wxBoxSizer(wxHORIZONTAL); + m_host_type_choice = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(60, -1)); + m_host_type_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlayDiag::OnChoice, this); + m_host_type_choice->Append(_("ID:")); + host_szr->Add(m_host_type_choice); + + m_host_label = new wxStaticText(panel, wxID_ANY, "555.555.555.555:55555", wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE | wxALIGN_LEFT); + // Update() should fix this immediately. + m_host_label->SetLabel(_("")); + host_szr->Add(m_host_label, 1, wxLEFT | wxCENTER, 5); + + m_host_copy_btn = new wxButton(panel, wxID_ANY, _("Copy")); + m_host_copy_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &NetPlayDiag::OnCopyIP, this); + m_host_copy_btn->Disable(); + host_szr->Add(m_host_copy_btn, 0, wxLEFT | wxCENTER, 5); + player_szr->Add(host_szr, 0, wxEXPAND | wxBOTTOM, 5); + m_host_type_choice->Select(0); + + UpdateHostLabel(); + } + + player_szr->Add(m_player_lbox, 1, wxEXPAND); + + if (m_is_hosting) { m_player_lbox->Bind(wxEVT_LISTBOX, &NetPlayDiag::OnPlayerSelect, this); m_kick_btn = new wxButton(panel, wxID_ANY, _("Kick Player")); @@ -398,7 +542,7 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game panel->SetSizerAndFit(main_szr); main_szr->SetSizeHints(this); - SetSize(512, 512-128); + SetSize(512, 512 - 128); Center(); } @@ -531,6 +675,11 @@ void NetPlayDiag::OnQuit(wxCommandEvent&) // update gui void NetPlayDiag::OnThread(wxThreadEvent& event) { + if (m_is_hosting && m_host_label && g_TraversalClient) + { + UpdateHostLabel(); + } + // player list m_playerids.clear(); std::string tmps; @@ -563,8 +712,8 @@ void NetPlayDiag::OnThread(wxThreadEvent& event) // flash window in taskbar when someone joins if window isn't active static u8 numPlayers = 1; bool focus = (wxWindow::FindFocus() == this || (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() == this) || - (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() != nullptr - && wxWindow::FindFocus()->GetParent()->GetParent() == this)); + (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() != nullptr + && wxWindow::FindFocus()->GetParent()->GetParent() == this)); if (netplay_server != nullptr && numPlayers < m_playerids.size() && !focus) { RequestUserAttention(); @@ -575,25 +724,25 @@ void NetPlayDiag::OnThread(wxThreadEvent& event) { case NP_GUI_EVT_CHANGE_GAME: // update selected game :/ - { + { m_selected_game.assign(WxStrToStr(event.GetString())); wxString button_label = event.GetString(); m_game_btn->SetLabel(button_label.Prepend(_(" Game : "))); - } - break; - case NP_GUI_EVT_START_GAME : + } + break; + case NP_GUI_EVT_START_GAME: // client start game :/ - { + { netplay_client->StartGame(FindGame()); - } - break; - case NP_GUI_EVT_STOP_GAME : + } + break; + case NP_GUI_EVT_STOP_GAME: // client stop game - { + { netplay_client->StopGame(); - } - break; + } + break; } // chat messages @@ -660,6 +809,98 @@ bool NetPlayDiag::IsRecording() return m_record_chkbox->GetValue(); } + +void NetPlayDiag::OnCopyIP(wxCommandEvent&) +{ + if (m_host_copy_btn_is_retry) + { + g_TraversalClient->ReconnectToServer(); + Update(); + } + else + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(m_host_label->GetLabel())); + wxTheClipboard->Close(); + } + } +} + +void NetPlayDiag::OnChoice(wxCommandEvent& event) +{ + UpdateHostLabel(); +} + + +void NetPlayDiag::UpdateHostLabel() +{ + wxString label = _(" (internal IP)"); + auto DeLabel = [=](wxString str) { + if (str == _("Localhost")) + return std::string("!local!"); + return WxStrToStr(str.Left(str.Len() - label.Len())); + }; + auto EnLabel = [=](std::string str) -> wxString { + if (str == "!local!") + return _("Localhost"); + return StrToWxStr(str) + label; + }; + int sel = m_host_type_choice->GetSelection(); + if (sel == 0) + { + // the traversal ID + switch (g_TraversalClient->m_State) + { + case TraversalClient::Connecting: + m_host_label->SetForegroundColour(*wxLIGHT_GREY); + m_host_label->SetLabel("..."); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Disable(); + break; + case TraversalClient::Connected: + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(wxString(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size())); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = false; + break; + case TraversalClient::Failure: + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(FailureReasonStringForHostLabel(g_TraversalClient->m_FailureReason)); + m_host_copy_btn->SetLabel(_("Retry")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = true; + break; + } + } + else if (sel != wxNOT_FOUND) // wxNOT_FOUND shouldn't generally happen + { + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(netplay_server->GetInterfaceHost(DeLabel(m_host_type_choice->GetString(sel)))); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = false; + } + + auto set = netplay_server->GetInterfaceSet(); + for (const std::string& iface : set) + { + wxString wxIface = EnLabel(iface); + if (m_host_type_choice->FindString(wxIface) == wxNOT_FOUND) + m_host_type_choice->Append(wxIface); + } + for (unsigned i = 1, count = m_host_type_choice->GetCount(); i != count; i++) + { + if (set.find(DeLabel(m_host_type_choice->GetString(i))) == set.end()) + { + m_host_type_choice->Delete(i); + i--; + count--; + } + } +} + ChangeGameDiag::ChangeGameDiag(wxWindow* const parent, const CGameListCtrl* const game_list, wxString& game_name) : wxDialog(parent, wxID_ANY, _("Change Game")) , m_game_name(game_name) @@ -728,16 +969,16 @@ PadMapDiag::PadMapDiag(wxWindow* const parent, PadMapping map[], PadMapping wiim v_szr->Add(new wxStaticText(this, wxID_ANY, (wxString(_("Wiimote ")) + (wxChar)('0' + i))), 1, wxALIGN_CENTER_HORIZONTAL); - m_map_cbox[i+4] = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, player_names); - m_map_cbox[i+4]->Bind(wxEVT_CHOICE, &PadMapDiag::OnAdjust, this); + m_map_cbox[i + 4] = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, player_names); + m_map_cbox[i + 4]->Bind(wxEVT_CHOICE, &PadMapDiag::OnAdjust, this); if (m_wiimapping[i] == -1) - m_map_cbox[i+4]->Select(0); + m_map_cbox[i + 4]->Select(0); else for (unsigned int j = 0; j < m_player_list.size(); j++) if (m_wiimapping[i] == m_player_list[j]->pid) - m_map_cbox[i+4]->Select(j + 1); + m_map_cbox[i + 4]->Select(j + 1); - v_szr->Add(m_map_cbox[i+4], 1); + v_szr->Add(m_map_cbox[i + 4], 1); h_szr->Add(v_szr, 1, wxTOP | wxEXPAND, 20); h_szr->AddSpacer(10); @@ -763,7 +1004,7 @@ void PadMapDiag::OnAdjust(wxCommandEvent& event) else m_mapping[i] = -1; - player_idx = m_map_cbox[i+4]->GetSelection(); + player_idx = m_map_cbox[i + 4]->GetSelection(); if (player_idx > 0) m_wiimapping[i] = m_player_list[player_idx - 1]->pid; else diff --git a/Source/Core/DolphinWX/NetWindow.h b/Source/Core/DolphinWX/NetWindow.h index 90d0dbd8c4..31cbf17b71 100644 --- a/Source/Core/DolphinWX/NetWindow.h +++ b/Source/Core/DolphinWX/NetWindow.h @@ -42,10 +42,16 @@ private: void MakeNetPlayDiag(int port, const std::string &game, bool is_hosting); - wxTextCtrl* m_nickname_text; - wxTextCtrl* m_host_port_text; - wxTextCtrl* m_connect_port_text; - wxTextCtrl* m_connect_ip_text; + void OnChoice(wxCommandEvent& event); + + wxStaticText* m_ip_lbl; + wxStaticText* m_client_port_lbl; + wxTextCtrl* m_nickname_text; + wxStaticText* m_host_port_lbl; + wxTextCtrl* m_host_port_text; + wxTextCtrl* m_connect_port_text; + wxTextCtrl* m_connect_ip_text; + wxChoice* m_direct_traversal; wxListBox* m_game_lbox; #ifdef USE_UPNP @@ -93,16 +99,25 @@ private: void GetNetSettings(NetSettings &settings); std::string FindGame(); - wxListBox* m_player_lbox; - wxTextCtrl* m_chat_text; - wxTextCtrl* m_chat_msg_text; - wxCheckBox* m_memcard_write; - wxCheckBox* m_record_chkbox; + void OnCopyIP(wxCommandEvent&); + void OnChoice(wxCommandEvent& event); + void UpdateHostLabel(); - std::string m_selected_game; - wxButton* m_game_btn; - wxButton* m_start_btn; - wxButton* m_kick_btn; + wxListBox* m_player_lbox; + wxTextCtrl* m_chat_text; + wxTextCtrl* m_chat_msg_text; + wxCheckBox* m_memcard_write; + wxCheckBox* m_record_chkbox; + + std::string m_selected_game; + wxButton* m_game_btn; + wxButton* m_start_btn; + wxButton* m_kick_btn; + wxStaticText* m_host_label; + wxChoice* m_host_type_choice; + wxButton* m_host_copy_btn; + bool m_host_copy_btn_is_retry; + bool m_is_hosting; std::vector m_playerids;