From 5992b9f76e65a2ca40558c923d376e59fbc4861d Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 4 May 2019 21:27:12 +0000 Subject: [PATCH 01/26] Add UDP socket class --- src/openrct2/network/UdpSocket.cpp | 322 +++++++++++++++++++++++++++++ src/openrct2/network/UdpSocket.h | 57 +++++ 2 files changed, 379 insertions(+) create mode 100644 src/openrct2/network/UdpSocket.cpp create mode 100644 src/openrct2/network/UdpSocket.h diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp new file mode 100644 index 0000000000..63780afcda --- /dev/null +++ b/src/openrct2/network/UdpSocket.cpp @@ -0,0 +1,322 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#ifndef DISABLE_NETWORK + +# include +# include +# include +# include +# include +# include + +// clang-format off +// MSVC: include here otherwise PI gets defined twice +#include + +#ifdef _WIN32 + // winsock2 must be included before windows.h + #include + #include + + #define LAST_SOCKET_ERROR() WSAGetLastError() + #undef EWOULDBLOCK + #define EWOULDBLOCK WSAEWOULDBLOCK + #ifndef SHUT_RD + #define SHUT_RD SD_RECEIVE + #endif + #ifndef SHUT_RDWR + #define SHUT_RDWR SD_BOTH + #endif + #define FLAG_NO_PIPE 0 +#else + #include + #include + #include + #include + #include + #include + #include + #include "../common.h" + using SOCKET = int32_t; + #define SOCKET_ERROR -1 + #define INVALID_SOCKET -1 + #define LAST_SOCKET_ERROR() errno + #define closesocket close + #define ioctlsocket ioctl + #if defined(__linux__) + #define FLAG_NO_PIPE MSG_NOSIGNAL + #else + #define FLAG_NO_PIPE 0 + #endif // defined(__linux__) +#endif // _WIN32 +// clang-format on + +# include "UdpSocket.h" + +constexpr auto CONNECT_TIMEOUT = std::chrono::milliseconds(3000); + +# ifdef _WIN32 +static bool _wsaInitialised = false; +# endif + +class UdpSocket; + +class SocketException : public std::runtime_error +{ +public: + explicit SocketException(const std::string& message) + : std::runtime_error(message) + { + } +}; + +class UdpSocket final : public IUdpSocket +{ +private: + SOCKET_STATUS _status = SOCKET_STATUS_CLOSED; + uint16_t _listeningPort = 0; + SOCKET _socket = INVALID_SOCKET; + + std::string _hostName; + std::string _error; + +public: + UdpSocket() = default; + + ~UdpSocket() override + { + CloseSocket(); + } + + SOCKET_STATUS GetStatus() override + { + return _status; + } + + const char* GetError() override + { + return _error.empty() ? nullptr : _error.c_str(); + } + + void Listen(uint16_t port) override + { + Listen("", port); + } + + void Listen(const std::string& address, uint16_t port) override + { + if (_status != SOCKET_STATUS_CLOSED) + { + throw std::runtime_error("Socket not closed."); + } + + sockaddr_storage ss{}; + int32_t ss_len; + if (!ResolveAddress(address, port, &ss, &ss_len)) + { + throw SocketException("Unable to resolve address."); + } + + // Create the listening socket + _socket = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (_socket == INVALID_SOCKET) + { + throw SocketException("Unable to create socket."); + } + + // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections + int32_t value = 0; + if (setsockopt(_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&value, sizeof(value)) != 0) + { + log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); + } + + value = 1; + if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&value, sizeof(value)) != 0) + { + log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); + } + + try + { + // Bind to address:port and listen + if (bind(_socket, (sockaddr*)&ss, ss_len) != 0) + { + throw SocketException("Unable to bind to socket."); + } + if (listen(_socket, SOMAXCONN) != 0) + { + throw SocketException("Unable to listen on socket."); + } + + if (!SetNonBlocking(_socket, true)) + { + throw SocketException("Failed to set non-blocking mode."); + } + } + catch (const std::exception&) + { + CloseSocket(); + throw; + } + + _listeningPort = port; + _status = SOCKET_STATUS_LISTENING; + } + + size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) override + { + if (_status != SOCKET_STATUS_CONNECTED) + { + throw std::runtime_error("Socket not connected."); + } + + size_t totalSent = 0; + do + { + const char* bufferStart = (const char*)buffer + totalSent; + size_t remainingSize = size - totalSent; + int32_t sentBytes = send(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE); + if (sentBytes == SOCKET_ERROR) + { + return totalSent; + } + totalSent += sentBytes; + } while (totalSent < size); + return totalSent; + } + + NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) override + { + if (_status != SOCKET_STATUS_CONNECTED) + { + throw std::runtime_error("Socket not connected."); + } + + int32_t readBytes = recv(_socket, (char*)buffer, (int32_t)size, 0); + if (readBytes == 0) + { + *sizeReceived = 0; + return NETWORK_READPACKET_DISCONNECTED; + } + else if (readBytes == SOCKET_ERROR) + { + *sizeReceived = 0; +# ifndef _WIN32 + // Removing the check for EAGAIN and instead relying on the values being the same allows turning on of + // -Wlogical-op warning. + // This is not true on Windows, see: + // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms737828(v=vs.85).aspx + // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741580(v=vs.85).aspx + // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx + static_assert( + EWOULDBLOCK == EAGAIN, + "Portability note: your system has different values for EWOULDBLOCK " + "and EAGAIN, please extend the condition below"); +# endif // _WIN32 + if (LAST_SOCKET_ERROR() != EWOULDBLOCK) + { + return NETWORK_READPACKET_DISCONNECTED; + } + else + { + return NETWORK_READPACKET_NO_DATA; + } + } + else + { + *sizeReceived = readBytes; + return NETWORK_READPACKET_SUCCESS; + } + } + + void Close() override + { + CloseSocket(); + } + + const char* GetHostName() const override + { + return _hostName.empty() ? nullptr : _hostName.c_str(); + } + +private: + explicit UdpSocket(SOCKET socket, const std::string& hostName) + { + _socket = socket; + _hostName = hostName; + _status = SOCKET_STATUS_CONNECTED; + } + + void CloseSocket() + { + if (_socket != INVALID_SOCKET) + { + closesocket(_socket); + _socket = INVALID_SOCKET; + } + _status = SOCKET_STATUS_CLOSED; + } + + bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, int32_t* ss_len) + { + std::string serviceName = std::to_string(port); + + addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + if (address.empty()) + { + hints.ai_flags = AI_PASSIVE; + } + + addrinfo* result = nullptr; + int errorcode = getaddrinfo(address.empty() ? nullptr : address.c_str(), serviceName.c_str(), &hints, &result); + if (errorcode != 0) + { + log_error("Resolving address failed: Code %d.", errorcode); + log_error("Resolution error message: %s.", gai_strerror(errorcode)); + return false; + } + if (result == nullptr) + { + return false; + } + else + { + std::memcpy(ss, result->ai_addr, result->ai_addrlen); + *ss_len = (int32_t)result->ai_addrlen; + freeaddrinfo(result); + return true; + } + } + + static bool SetNonBlocking(SOCKET socket, bool on) + { +# ifdef _WIN32 + u_long nonBlocking = on; + return ioctlsocket(socket, FIONBIO, &nonBlocking) == 0; +# else + int32_t flags = fcntl(socket, F_GETFL, 0); + return fcntl(socket, F_SETFL, on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) == 0; +# endif + } + + static bool SetSOBroadcast(SOCKET socket, bool enabled) + { + return setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&enabled, sizeof(enabled)) == 0; + } +}; + +std::unique_ptr CreateUdpSocket() +{ + return std::make_unique(); +} + +#endif diff --git a/src/openrct2/network/UdpSocket.h b/src/openrct2/network/UdpSocket.h new file mode 100644 index 0000000000..07beec7a75 --- /dev/null +++ b/src/openrct2/network/UdpSocket.h @@ -0,0 +1,57 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../common.h" + +#include +#include + +enum SOCKET_STATUS +{ + SOCKET_STATUS_CLOSED, + SOCKET_STATUS_RESOLVING, + SOCKET_STATUS_CONNECTING, + SOCKET_STATUS_CONNECTED, + SOCKET_STATUS_LISTENING, +}; + +enum NETWORK_READPACKET +{ + NETWORK_READPACKET_SUCCESS, + NETWORK_READPACKET_NO_DATA, + NETWORK_READPACKET_MORE_DATA, + NETWORK_READPACKET_DISCONNECTED +}; + +/** + * Represents a UDP socket / listener. + */ +interface IUdpSocket +{ +public: + virtual ~IUdpSocket() + { + } + + virtual SOCKET_STATUS GetStatus() abstract; + virtual const char* GetError() abstract; + virtual const char* GetHostName() const abstract; + + virtual void Listen(uint16_t port) abstract; + virtual void Listen(const std::string& address, uint16_t port) abstract; + + virtual size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) abstract; + virtual NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract; + + virtual void Close() abstract; +}; + +std::unique_ptr CreateUdpSocket(); From 694cb7eb3fd78cac81205c0a0882260c1b3c830d Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 01:02:20 +0000 Subject: [PATCH 02/26] Implement UDP socket and broadcasting --- src/openrct2-ui/windows/ServerList.cpp | 67 +++++-- src/openrct2/network/Network.cpp | 6 +- .../network/NetworkServerAdvertiser.cpp | 72 ++++++-- src/openrct2/network/UdpSocket.cpp | 172 +++++++++++++----- src/openrct2/network/UdpSocket.h | 16 +- 5 files changed, 248 insertions(+), 85 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 16f665d7e8..fde87fa51a 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -636,29 +637,61 @@ static void join_server(std::string address) } #ifndef DISABLE_HTTP + +static void fetch_lan_servers() +{ + std::string msg = "Are you an OpenRCT2 server?"; + auto udpSocket = CreateUdpSocket(); + auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size()); + if (len == msg.size()) + { + char buffer[256]{}; + size_t recievedLen{}; + std::unique_ptr endpoint; + for (int i = 0; i < 5 * 10; i++) + { + auto p = udpSocket->ReceiveData(buffer, sizeof(buffer), &recievedLen, &endpoint); + if (p == NETWORK_READPACKET_SUCCESS) + { + auto sender = endpoint->GetHostname(); + std::printf(">> Recieved packet from %s\n", sender.c_str()); + std::printf(">> %s\n", buffer); + } + usleep(100 * 1000); + } + } +} + static void fetch_servers() { - std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL; - if (!gConfigNetwork.master_server_url.empty()) + if (1 == 1) { - masterServerUrl = gConfigNetwork.master_server_url; + fetch_lan_servers(); } - + else { - std::lock_guard guard(_mutex); - _serverEntries.erase( - std::remove_if( - _serverEntries.begin(), _serverEntries.end(), [](const server_entry& server) { return !server.favourite; }), - _serverEntries.end()); - sort_servers(); - } + std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL; + if (gConfigNetwork.master_server_url.empty() == false) + { + masterServerUrl = gConfigNetwork.master_server_url; + } - Http::Request request; - request.url = masterServerUrl; - request.method = Http::Method::GET; - request.header["Accept"] = "application/json"; - status_text = STR_SERVER_LIST_CONNECTING; - Http::DoAsync(request, fetch_servers_callback); + { + std::lock_guard guard(_mutex); + _serverEntries.erase( + std::remove_if( + _serverEntries.begin(), _serverEntries.end(), [](const server_entry& server) { return !server.favourite; }), + _serverEntries.end()); + sort_servers(); + } + + Http::Request request; + request.url = masterServerUrl; + request.method = Http::Method::GET; + request.header["Accept"] = "application/json"; + status_text = STR_SERVER_LIST_CONNECTING; + Http::DoAsync(request, fetch_servers_callback); + } } static uint32_t get_total_player_count() diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 55376637ec..8a24933a11 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -680,11 +680,7 @@ bool Network::BeginServer(uint16_t port, const std::string& address) status = NETWORK_STATUS_CONNECTED; listening_port = port; _serverState.gamestateSnapshotsEnabled = gConfigNetwork.desync_debugging; - - if (gConfigNetwork.advertise) - { - _advertiser = CreateServerAdvertiser(listening_port); - } + _advertiser = CreateServerAdvertiser(listening_port); if (gConfigNetwork.pause_server_if_no_clients) { diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index 1b8623bfb4..f347d193ba 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -18,13 +18,16 @@ # include "../localisation/Date.h" # include "../management/Finance.h" # include "../peep/Peep.h" +# include "../platform/Platform2.h" # include "../platform/platform.h" # include "../util/Util.h" # include "../world/Map.h" # include "../world/Park.h" # include "Http.h" +# include "UdpSocket.h" # include "network.h" +# include # include # include # include @@ -44,6 +47,8 @@ enum MASTER_SERVER_STATUS constexpr int32_t MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes constexpr int32_t MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute +constexpr int32_t LAN_BROADCAST_PORT = 11754; + class NetworkServerAdvertiser final : public INetworkServerAdvertiser { private: @@ -62,11 +67,15 @@ private: // See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953 bool _forceIPv4 = false; + std::unique_ptr _lanListener; + uint32_t _lastListenTime{}; + public: explicit NetworkServerAdvertiser(uint16_t port) { _port = port; _key = GenerateAdvertiseKey(); + _lanListener = CreateUdpSocket(); } ADVERTISE_STATUS GetStatus() const override @@ -76,23 +85,58 @@ public: void Update() override { - switch (_status) + auto ticks = Platform::GetTicks(); + if (ticks > _lastListenTime + 500) { - case ADVERTISE_STATUS::UNREGISTERED: - if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME) + if (_lanListener->GetStatus() != SOCKET_STATUS_LISTENING) + { + _lanListener->Listen(LAN_BROADCAST_PORT); + } + else + { + char buffer[256]; + size_t recievedBytes; + std::unique_ptr endpoint; + auto p = _lanListener->ReceiveData(buffer, sizeof(buffer), &recievedBytes, &endpoint); + if (p == NETWORK_READPACKET_SUCCESS) { - SendRegistration(_forceIPv4); + std::string sender = endpoint->GetHostname(); + std::printf("\r>> Received %zu bytes from %s\n", recievedBytes, sender.c_str()); + + auto body = GetHeartbeatJson(); + auto bodyDump = json_dumps(body, JSON_COMPACT); + size_t sendLen = strlen(bodyDump) + 1; + std::printf("\r>> Sending %zu bytes back to %s\n", sendLen, sender.c_str()); + _lanListener->SendData(*endpoint, bodyDump, sendLen); + free(bodyDump); + json_decref(body); } - break; - case ADVERTISE_STATUS::REGISTERED: - if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME) - { - SendHeartbeat(); - } - break; - // exhaust enum values to satisfy clang - case ADVERTISE_STATUS::DISABLED: - break; + } + _lastListenTime = ticks; + } + + if (gConfigNetwork.advertise) + { + /* + switch (_status) + { + case ADVERTISE_STATUS::UNREGISTERED: + if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME) + { + SendRegistration(_forceIPv4); + } + break; + case ADVERTISE_STATUS::REGISTERED: + if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME) + { + SendHeartbeat(); + } + break; + // exhaust enum values to satisfy clang + case ADVERTISE_STATUS::DISABLED: + break; + } + */ } } diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp index 63780afcda..80c13e2f26 100644 --- a/src/openrct2/network/UdpSocket.cpp +++ b/src/openrct2/network/UdpSocket.cpp @@ -77,12 +77,64 @@ public: } }; +class NetworkEndpoint final : public INetworkEndpoint +{ +private: + sockaddr _address{}; + socklen_t _addressLen{}; + +public: + NetworkEndpoint() + { + } + + NetworkEndpoint(const sockaddr* address, socklen_t addressLen) + { + std::memcpy(&_address, address, addressLen); + _addressLen = addressLen; + } + + const sockaddr& GetAddress() const + { + return _address; + } + + socklen_t GetAddressLen() const + { + return _addressLen; + } + + int32_t GetPort() const + { + if (_address.sa_family == AF_INET) + { + return ((sockaddr_in*)&_address)->sin_port; + } + else + { + return ((sockaddr_in6*)&_address)->sin6_port; + } + } + + std::string GetHostname() override + { + char hostname[256]; + int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST); + if (res == 0) + { + return hostname; + } + return {}; + } +}; + class UdpSocket final : public IUdpSocket { private: SOCKET_STATUS _status = SOCKET_STATUS_CLOSED; uint16_t _listeningPort = 0; SOCKET _socket = INVALID_SOCKET; + NetworkEndpoint _endpoint; std::string _hostName; std::string _error; @@ -118,32 +170,36 @@ public: } sockaddr_storage ss{}; - int32_t ss_len; + socklen_t ss_len; if (!ResolveAddress(address, port, &ss, &ss_len)) { throw SocketException("Unable to resolve address."); } // Create the listening socket - _socket = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + _socket = socket(ss.ss_family, SOCK_DGRAM, IPPROTO_UDP); if (_socket == INVALID_SOCKET) { throw SocketException("Unable to create socket."); } // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections - int32_t value = 0; - if (setsockopt(_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&value, sizeof(value)) != 0) + if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) { log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); } - value = 1; - if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&value, sizeof(value)) != 0) + if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) { log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); } + // Enable send and receiving of broadcast messages + if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) + { + log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); + } + try { // Bind to address:port and listen @@ -151,10 +207,6 @@ public: { throw SocketException("Unable to bind to socket."); } - if (listen(_socket, SOMAXCONN) != 0) - { - throw SocketException("Unable to listen on socket."); - } if (!SetNonBlocking(_socket, true)) { @@ -173,9 +225,49 @@ public: size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) override { - if (_status != SOCKET_STATUS_CONNECTED) + sockaddr_storage ss{}; + socklen_t ss_len; + if (!ResolveAddress(address, port, &ss, &ss_len)) { - throw std::runtime_error("Socket not connected."); + throw SocketException("Unable to resolve address."); + } + NetworkEndpoint endpoint((const sockaddr*)&ss, ss_len); + return SendData(endpoint, buffer, size); + } + + size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) override + { + if (_socket == INVALID_SOCKET) + { + _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (_socket == INVALID_SOCKET) + { + throw SocketException("Unable to create socket."); + } + + // Enable send and receiving of broadcast messages + if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) + { + log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); + } + + if (!SetNonBlocking(_socket, true)) + { + throw SocketException("Failed to set non-blocking mode."); + } + } + + const auto& dest = dynamic_cast(&destination); + if (dest == nullptr) + { + throw std::invalid_argument("destination is not compatible."); + } + auto ss = &dest->GetAddress(); + auto ss_len = dest->GetAddressLen(); + + if (_status != SOCKET_STATUS_LISTENING) + { + _endpoint = *dest; } size_t totalSent = 0; @@ -183,7 +275,7 @@ public: { const char* bufferStart = (const char*)buffer + totalSent; size_t remainingSize = size - totalSent; - int32_t sentBytes = send(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE); + int32_t sentBytes = sendto(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE, (const sockaddr*)ss, ss_len); if (sentBytes == SOCKET_ERROR) { return totalSent; @@ -193,46 +285,29 @@ public: return totalSent; } - NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) override + NETWORK_READPACKET ReceiveData( + void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr* sender) override { - if (_status != SOCKET_STATUS_CONNECTED) + sockaddr_in senderAddr{}; + socklen_t senderAddrLen{}; + if (_status != SOCKET_STATUS_LISTENING) { - throw std::runtime_error("Socket not connected."); + senderAddrLen = _endpoint.GetAddressLen(); + std::memcpy(&senderAddr, &_endpoint.GetAddress(), senderAddrLen); } - - int32_t readBytes = recv(_socket, (char*)buffer, (int32_t)size, 0); - if (readBytes == 0) + auto readBytes = recvfrom(_socket, (char*)buffer, (int32_t)size, 0, (sockaddr*)&senderAddr, &senderAddrLen); + if (readBytes <= 0) { *sizeReceived = 0; - return NETWORK_READPACKET_DISCONNECTED; - } - else if (readBytes == SOCKET_ERROR) - { - *sizeReceived = 0; -# ifndef _WIN32 - // Removing the check for EAGAIN and instead relying on the values being the same allows turning on of - // -Wlogical-op warning. - // This is not true on Windows, see: - // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms737828(v=vs.85).aspx - // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741580(v=vs.85).aspx - // * https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx - static_assert( - EWOULDBLOCK == EAGAIN, - "Portability note: your system has different values for EWOULDBLOCK " - "and EAGAIN, please extend the condition below"); -# endif // _WIN32 - if (LAST_SOCKET_ERROR() != EWOULDBLOCK) - { - return NETWORK_READPACKET_DISCONNECTED; - } - else - { - return NETWORK_READPACKET_NO_DATA; - } + return NETWORK_READPACKET_NO_DATA; } else { *sizeReceived = readBytes; + if (sender != nullptr) + { + *sender = std::make_unique((sockaddr*)&senderAddr, senderAddrLen); + } return NETWORK_READPACKET_SUCCESS; } } @@ -265,7 +340,7 @@ private: _status = SOCKET_STATUS_CLOSED; } - bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, int32_t* ss_len) + bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len) { std::string serviceName = std::to_string(port); @@ -291,7 +366,7 @@ private: else { std::memcpy(ss, result->ai_addr, result->ai_addrlen); - *ss_len = (int32_t)result->ai_addrlen; + *ss_len = result->ai_addrlen; freeaddrinfo(result); return true; } @@ -308,9 +383,10 @@ private: # endif } - static bool SetSOBroadcast(SOCKET socket, bool enabled) + static bool SetOption(SOCKET socket, int32_t a, int32_t b, bool value) { - return setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (const char*)&enabled, sizeof(enabled)) == 0; + int32_t ivalue = value ? 1 : 0; + return setsockopt(socket, a, b, (const char*)&ivalue, sizeof(ivalue)) == 0; } }; diff --git a/src/openrct2/network/UdpSocket.h b/src/openrct2/network/UdpSocket.h index 07beec7a75..a2523e3719 100644 --- a/src/openrct2/network/UdpSocket.h +++ b/src/openrct2/network/UdpSocket.h @@ -31,6 +31,18 @@ enum NETWORK_READPACKET NETWORK_READPACKET_DISCONNECTED }; +/** + * Represents an address and port. + */ +interface INetworkEndpoint +{ + virtual ~INetworkEndpoint() + { + } + + virtual std::string GetHostname() abstract; +}; + /** * Represents a UDP socket / listener. */ @@ -49,7 +61,9 @@ public: virtual void Listen(const std::string& address, uint16_t port) abstract; virtual size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) abstract; - virtual NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract; + virtual size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) abstract; + virtual NETWORK_READPACKET ReceiveData( + void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr* sender) abstract; virtual void Close() abstract; }; From 123a8eacad2cbe78d11280745c2a0719a9270b54 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 02:18:50 +0000 Subject: [PATCH 03/26] Get the server list showing LAN servers --- src/openrct2-ui/windows/ServerList.cpp | 74 ++++++++++++------- src/openrct2/core/Json.cpp | 4 +- src/openrct2/core/Json.hpp | 3 +- src/openrct2/network/Network.cpp | 23 +++++- .../network/NetworkServerAdvertiser.cpp | 7 +- src/openrct2/network/UdpSocket.cpp | 4 +- src/openrct2/network/network.h | 2 + 7 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index fde87fa51a..efd389b2f1 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifndef DISABLE_HTTP @@ -147,8 +148,10 @@ static void sort_servers(); static void join_server(std::string address); #ifndef DISABLE_HTTP static void fetch_servers(); +static uint32_t get_total_player_count(); static void fetch_servers_callback(Http::Response& response); static void RefreshServersFromJson(const json_t* jsonServers); +static void AddServerFromJson(const json_t* server); #endif static bool is_version_valid(const std::string& version); @@ -638,28 +641,41 @@ static void join_server(std::string address) #ifndef DISABLE_HTTP -static void fetch_lan_servers() +static void fetch_lan_servers_worker() { std::string msg = "Are you an OpenRCT2 server?"; auto udpSocket = CreateUdpSocket(); auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size()); if (len == msg.size()) { - char buffer[256]{}; + char buffer[1024]{}; size_t recievedLen{}; std::unique_ptr endpoint; for (int i = 0; i < 5 * 10; i++) { - auto p = udpSocket->ReceiveData(buffer, sizeof(buffer), &recievedLen, &endpoint); + auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint); if (p == NETWORK_READPACKET_SUCCESS) { auto sender = endpoint->GetHostname(); std::printf(">> Recieved packet from %s\n", sender.c_str()); - std::printf(">> %s\n", buffer); + auto jinfo = Json::FromString(std::string_view(buffer)); + AddServerFromJson(jinfo); + json_decref(jinfo); } usleep(100 * 1000); } } + + sort_servers(); + _numPlayersOnline = get_total_player_count(); + status_text = STR_X_PLAYERS_ONLINE; + window_invalidate_by_class(WC_SERVER_LIST); +} + +static void fetch_lan_servers() +{ + std::thread worker(fetch_lan_servers_worker); + worker.detach(); } static void fetch_servers() @@ -757,28 +773,38 @@ static void RefreshServersFromJson(const json_t* jsonServers) for (int32_t i = 0; i < count; i++) { auto server = json_array_get(jsonServers, i); - if (!json_is_object(server)) + if (json_is_object(server)) { - continue; + AddServerFromJson(server); } + } - auto port = json_object_get(server, "port"); - auto name = json_object_get(server, "name"); - auto description = json_object_get(server, "description"); - auto requiresPassword = json_object_get(server, "requiresPassword"); - auto version = json_object_get(server, "version"); - auto players = json_object_get(server, "players"); - auto maxPlayers = json_object_get(server, "maxPlayers"); - auto ip = json_object_get(server, "ip"); - auto ip4 = json_object_get(ip, "v4"); - auto addressIp = json_array_get(ip4, 0); + sort_servers(); + _numPlayersOnline = get_total_player_count(); - if (name == nullptr || version == nullptr) - { - log_verbose("Cowardly refusing to add server without name or version specified."); - continue; - } + status_text = STR_X_PLAYERS_ONLINE; + window_invalidate_by_class(WC_SERVER_LIST); +} +static void AddServerFromJson(const json_t* server) +{ + auto port = json_object_get(server, "port"); + auto name = json_object_get(server, "name"); + auto description = json_object_get(server, "description"); + auto requiresPassword = json_object_get(server, "requiresPassword"); + auto version = json_object_get(server, "version"); + auto players = json_object_get(server, "players"); + auto maxPlayers = json_object_get(server, "maxPlayers"); + auto ip = json_object_get(server, "ip"); + auto ip4 = json_object_get(ip, "v4"); + auto addressIp = json_array_get(ip4, 0); + + if (name == nullptr || version == nullptr) + { + log_verbose("Cowardly refusing to add server without name or version specified."); + } + else + { auto address = String::StdFormat("%s:%d", json_string_value(addressIp), (int32_t)json_integer_value(port)); { std::lock_guard guard(_mutex); @@ -791,12 +817,6 @@ static void RefreshServersFromJson(const json_t* jsonServers) newserver.maxplayers = (uint8_t)json_integer_value(maxPlayers); } } - - sort_servers(); - _numPlayersOnline = get_total_player_count(); - - status_text = STR_X_PLAYERS_ONLINE; - window_invalidate_by_class(WC_SERVER_LIST); } #endif diff --git a/src/openrct2/core/Json.cpp b/src/openrct2/core/Json.cpp index 94b129133a..87fd5a6799 100644 --- a/src/openrct2/core/Json.cpp +++ b/src/openrct2/core/Json.cpp @@ -53,11 +53,11 @@ namespace Json fs.Write(jsonOutput, jsonOutputSize); } - json_t* FromString(const std::string& raw) + json_t* FromString(const std::string_view& raw) { json_t* root; json_error_t error; - root = json_loads(raw.c_str(), 0, &error); + root = json_loadb(raw.data(), raw.size(), 0, &error); if (root == nullptr) { throw JsonException(&error); diff --git a/src/openrct2/core/Json.hpp b/src/openrct2/core/Json.hpp index c3d97fc408..693097ec46 100644 --- a/src/openrct2/core/Json.hpp +++ b/src/openrct2/core/Json.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace Json { @@ -23,7 +24,7 @@ namespace Json json_t* ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE); void WriteToFile(const utf8* path, const json_t* json, size_t flags = 0); - json_t* FromString(const std::string& raw); + json_t* FromString(const std::string_view& raw); } // namespace Json class JsonException final : public std::runtime_error diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 8a24933a11..39e24cfaac 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -201,6 +201,7 @@ public: void Server_Send_OBJECTS(NetworkConnection& connection, const std::vector& objects) const; NetworkStats_t GetStats() const; + json_t* GetServerInfoAsJson() const; std::vector> player_list; std::vector> group_list; @@ -1810,11 +1811,8 @@ void Network::Server_Send_SETDISCONNECTMSG(NetworkConnection& connection, const connection.QueuePacket(std::move(packet)); } -void Network::Server_Send_GAMEINFO(NetworkConnection& connection) +json_t* Network::GetServerInfoAsJson() const { - std::unique_ptr packet(NetworkPacket::Allocate()); - *packet << (uint32_t)NETWORK_COMMAND_GAMEINFO; -# ifndef DISABLE_HTTP json_t* obj = json_object(); json_object_set_new(obj, "name", json_string(gConfigNetwork.server_name.c_str())); json_object_set_new(obj, "requiresPassword", json_boolean(_password.size() > 0)); @@ -1824,6 +1822,15 @@ void Network::Server_Send_GAMEINFO(NetworkConnection& connection) json_object_set_new(obj, "description", json_string(gConfigNetwork.server_description.c_str())); json_object_set_new(obj, "greeting", json_string(gConfigNetwork.server_greeting.c_str())); json_object_set_new(obj, "dedicated", json_boolean(gOpenRCT2Headless)); + return obj; +} + +void Network::Server_Send_GAMEINFO(NetworkConnection& connection) +{ + std::unique_ptr packet(NetworkPacket::Allocate()); + *packet << (uint32_t)NETWORK_COMMAND_GAMEINFO; +# ifndef DISABLE_HTTP + json_t* obj = GetServerInfoAsJson(); // Provider details json_t* jsonProvider = json_object(); @@ -4175,6 +4182,10 @@ bool network_gamestate_snapshots_enabled() return network_get_server_state().gamestateSnapshotsEnabled; } +json_t* network_get_server_info_as_json() +{ + return gNetwork.GetServerInfoAsJson(); +} #else int32_t network_get_mode() { @@ -4432,4 +4443,8 @@ NetworkServerState_t network_get_server_state() { return NetworkServerState_t{}; } +json_t* network_get_server_info_as_json() +{ + return nullptr; +} #endif /* DISABLE_NETWORK */ diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index f347d193ba..c60be89ffa 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -103,7 +103,7 @@ public: std::string sender = endpoint->GetHostname(); std::printf("\r>> Received %zu bytes from %s\n", recievedBytes, sender.c_str()); - auto body = GetHeartbeatJson(); + auto body = GetBroadcastJson(); auto bodyDump = json_dumps(body, JSON_COMPACT); size_t sendLen = strlen(bodyDump) + 1; std::printf("\r>> Sending %zu bytes back to %s\n", sendLen, sender.c_str()); @@ -279,6 +279,11 @@ private: return root; } + json_t* GetBroadcastJson() + { + return network_get_server_info_as_json(); + } + static std::string GenerateAdvertiseKey() { // Generate a string of 16 random hex characters (64-integer key as a hex formatted string) diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp index 80c13e2f26..a08bdd97b3 100644 --- a/src/openrct2/network/UdpSocket.cpp +++ b/src/openrct2/network/UdpSocket.cpp @@ -289,7 +289,7 @@ public: void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr* sender) override { sockaddr_in senderAddr{}; - socklen_t senderAddrLen{}; + socklen_t senderAddrLen = sizeof(sockaddr_in); if (_status != SOCKET_STATUS_LISTENING) { senderAddrLen = _endpoint.GetAddressLen(); @@ -345,7 +345,7 @@ private: std::string serviceName = std::to_string(port); addrinfo hints = {}; - hints.ai_family = AF_UNSPEC; + hints.ai_family = AF_INET; if (address.empty()) { hints.ai_flags = AI_PASSIVE; diff --git a/src/openrct2/network/network.h b/src/openrct2/network/network.h index f19a83f8b3..080e567639 100644 --- a/src/openrct2/network/network.h +++ b/src/openrct2/network/network.h @@ -19,6 +19,7 @@ #include #include +struct json_t; struct GameAction; struct Peep; struct LocationXYZ16; @@ -109,3 +110,4 @@ std::string network_get_version(); NetworkStats_t network_get_stats(); NetworkServerState_t network_get_server_state(); +json_t* network_get_server_info_as_json(); From 7a20874366f83c251f7b5e340feb13ecdc285446 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 02:34:14 +0100 Subject: [PATCH 04/26] Fix Windows build --- src/openrct2-ui/windows/ServerList.cpp | 5 +++-- src/openrct2/network/UdpSocket.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index efd389b2f1..6b64fdb636 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -662,7 +663,7 @@ static void fetch_lan_servers_worker() AddServerFromJson(jinfo); json_decref(jinfo); } - usleep(100 * 1000); + platform_sleep(100); } } @@ -680,7 +681,7 @@ static void fetch_lan_servers() static void fetch_servers() { - if (1 == 1) + if (toupper('A') == 'A') { fetch_lan_servers(); } diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp index a08bdd97b3..2f02c08219 100644 --- a/src/openrct2/network/UdpSocket.cpp +++ b/src/openrct2/network/UdpSocket.cpp @@ -366,7 +366,7 @@ private: else { std::memcpy(ss, result->ai_addr, result->ai_addrlen); - *ss_len = result->ai_addrlen; + *ss_len = (socklen_t)result->ai_addrlen; freeaddrinfo(result); return true; } From 443711380578467bbc90a0b67798d701f201cfe5 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 02:34:45 +0000 Subject: [PATCH 05/26] Fix server list LAN address and port --- src/openrct2-ui/windows/ServerList.cpp | 7 +++++++ src/openrct2/network/NetworkServerAdvertiser.cpp | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 6b64fdb636..e350c909b0 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -660,6 +660,13 @@ static void fetch_lan_servers_worker() auto sender = endpoint->GetHostname(); std::printf(">> Recieved packet from %s\n", sender.c_str()); auto jinfo = Json::FromString(std::string_view(buffer)); + + auto ip4 = json_array(); + json_array_append_new(ip4, json_string(sender.c_str())); + auto ip = json_object(); + json_object_set_new(ip, "v4", ip4); + json_object_set_new(jinfo, "ip", ip); + AddServerFromJson(jinfo); json_decref(jinfo); } diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index c60be89ffa..bdfaa4beda 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -281,7 +281,9 @@ private: json_t* GetBroadcastJson() { - return network_get_server_info_as_json(); + auto root = network_get_server_info_as_json(); + json_object_set(root, "port", json_integer(_port)); + return root; } static std::string GenerateAdvertiseKey() From 3a400a2471b7e15f9a009f859ef285f75d297409 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 13:15:46 +0000 Subject: [PATCH 06/26] Refactor server list --- src/openrct2-ui/windows/ServerList.cpp | 433 ++++++------------------- src/openrct2/network/ServerList.cpp | 262 ++++++++++++++- src/openrct2/network/ServerList.h | 55 +++- 3 files changed, 405 insertions(+), 345 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index e350c909b0..c39dab3b54 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -8,8 +8,7 @@ *****************************************************************************/ #include -#include -#include +#include #include #include #include @@ -27,8 +26,6 @@ #include #include #include -#include -#include #ifndef DISABLE_HTTP using namespace OpenRCT2::Network; @@ -40,20 +37,9 @@ using namespace OpenRCT2::Network; #define WHEIGHT_MAX 800 #define ITEM_HEIGHT (3 + 9 + 3) -class MasterServerException : public std::exception -{ -public: - rct_string_id StatusText; - - MasterServerException(rct_string_id statusText) - : StatusText(statusText) - { - } -}; - static char _playerName[32 + 1]; -static std::vector _serverEntries; -static std::mutex _mutex; +static ServerList _serverList; +static std::future> _fetchFuture; static uint32_t _numPlayersOnline = 0; static rct_string_id status_text = STR_SERVER_LIST_CONNECTING; @@ -141,20 +127,9 @@ static int32_t _hoverButtonIndex = -1; static std::string _version; static void server_list_get_item_button(int32_t buttonIndex, int32_t x, int32_t y, int32_t width, int32_t* outX, int32_t* outY); -static void server_list_load_server_entries(); -static void server_list_save_server_entries(); -static void dispose_server_entry_list(); -static server_entry& add_server_entry(const std::string& address); -static void sort_servers(); static void join_server(std::string address); -#ifndef DISABLE_HTTP -static void fetch_servers(); -static uint32_t get_total_player_count(); -static void fetch_servers_callback(Http::Response& response); -static void RefreshServersFromJson(const json_t* jsonServers); -static void AddServerFromJson(const json_t* server); -#endif -static bool is_version_valid(const std::string& version); +static void server_list_fetch_servers_begin(); +static void server_list_fetch_servers_check(rct_window* w); rct_window* window_server_list_open() { @@ -188,20 +163,18 @@ rct_window* window_server_list_open() safe_strcpy(_playerName, gConfigNetwork.player_name.c_str(), sizeof(_playerName)); - server_list_load_server_entries(); - window->no_list_items = (uint16_t)_serverEntries.size(); + _serverList.ReadAndAddFavourites(); + window->no_list_items = (uint16_t)_serverList.GetCount(); -#ifndef DISABLE_HTTP - fetch_servers(); -#endif + server_list_fetch_servers_begin(); return window; } static void window_server_list_close(rct_window* w) { - std::lock_guard guard(_mutex); - dispose_server_entry_list(); + _serverList = {}; + _fetchFuture = {}; } static void window_server_list_mouseup(rct_window* w, rct_widgetindex widgetIndex) @@ -217,10 +190,10 @@ static void window_server_list_mouseup(rct_window* w, rct_widgetindex widgetInde case WIDX_LIST: { int32_t serverIndex = w->selected_list_item; - if (serverIndex >= 0 && serverIndex < (int32_t)_serverEntries.size()) + if (serverIndex >= 0 && serverIndex < (int32_t)_serverList.GetCount()) { - const auto& server = _serverEntries[serverIndex]; - if (is_version_valid(server.version)) + const auto& server = _serverList.GetServer(serverIndex); + if (server.IsVersionValid()) { join_server(server.address); } @@ -233,9 +206,7 @@ static void window_server_list_mouseup(rct_window* w, rct_widgetindex widgetInde break; } case WIDX_FETCH_SERVERS: -#ifndef DISABLE_HTTP - fetch_servers(); -#endif + server_list_fetch_servers_begin(); break; case WIDX_ADD_SERVER: window_text_input_open(w, widgetIndex, STR_ADD_SERVER, STR_ENTER_HOSTNAME_OR_IP_ADDRESS, STR_NONE, 0, 128); @@ -254,27 +225,26 @@ static void window_server_list_resize(rct_window* w) static void window_server_list_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex) { auto serverIndex = w->selected_list_item; - if (serverIndex >= 0 && serverIndex < (int32_t)_serverEntries.size()) + if (serverIndex >= 0 && serverIndex < (int32_t)_serverList.GetCount()) { - auto& server = _serverEntries[serverIndex]; + auto& server = _serverList.GetServer(serverIndex); switch (dropdownIndex) { case DDIDX_JOIN: - if (is_version_valid(server.version)) + if (server.IsVersionValid()) { join_server(server.address); } else { - set_format_arg(0, void*, _serverEntries[serverIndex].version.c_str()); + set_format_arg(0, void*, server.version.c_str()); context_show_error(STR_UNABLE_TO_CONNECT_TO_SERVER, STR_MULTIPLAYER_INCORRECT_SOFTWARE_VERSION); } break; case DDIDX_FAVOURITE: { - std::lock_guard guard(_mutex); server.favourite = !server.favourite; - server_list_save_server_entries(); + _serverList.WriteFavourites(); } break; } @@ -288,6 +258,7 @@ static void window_server_list_update(rct_window* w) window_update_textbox_caret(); widget_invalidate(w, WIDX_PLAYER_NAME_INPUT); } + server_list_fetch_servers_check(w); } static void window_server_list_scroll_getsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) @@ -299,25 +270,25 @@ static void window_server_list_scroll_getsize(rct_window* w, int32_t scrollIndex static void window_server_list_scroll_mousedown(rct_window* w, int32_t scrollIndex, int32_t x, int32_t y) { int32_t serverIndex = w->selected_list_item; - if (serverIndex < 0) - return; - if (serverIndex >= (int32_t)_serverEntries.size()) - return; - - rct_widget* listWidget = &w->widgets[WIDX_LIST]; - int32_t ddx = w->x + listWidget->left + x + 2 - w->scrolls[0].h_left; - int32_t ddy = w->y + listWidget->top + y + 2 - w->scrolls[0].v_top; - - gDropdownItemsFormat[0] = STR_JOIN_GAME; - if (_serverEntries[serverIndex].favourite) + if (serverIndex >= 0 && serverIndex < (int32_t)_serverList.GetCount()) { - gDropdownItemsFormat[1] = STR_REMOVE_FROM_FAVOURITES; + const auto& server = _serverList.GetServer(serverIndex); + + auto listWidget = &w->widgets[WIDX_LIST]; + int32_t ddx = w->x + listWidget->left + x + 2 - w->scrolls[0].h_left; + int32_t ddy = w->y + listWidget->top + y + 2 - w->scrolls[0].v_top; + + gDropdownItemsFormat[0] = STR_JOIN_GAME; + if (server.favourite) + { + gDropdownItemsFormat[1] = STR_REMOVE_FROM_FAVOURITES; + } + else + { + gDropdownItemsFormat[1] = STR_ADD_TO_FAVOURITES; + } + window_dropdown_show_text(ddx, ddy, 0, COLOUR_GREY, 0, 2); } - else - { - gDropdownItemsFormat[1] = STR_ADD_TO_FAVOURITES; - } - window_dropdown_show_text(ddx, ddy, 0, COLOUR_GREY, 0, 2); } static void window_server_list_scroll_mouseover(rct_window* w, int32_t scrollIndex, int32_t x, int32_t y) @@ -392,14 +363,15 @@ static void window_server_list_textinput(rct_window* w, rct_widgetindex widgetIn case WIDX_ADD_SERVER: { - std::lock_guard guard(_mutex); - auto& entry = add_server_entry(text); + ServerListEntry entry; + entry.address = text; + entry.name = text; entry.favourite = true; - sort_servers(); - server_list_save_server_entries(); - } + _serverList.Add(entry); + _serverList.WriteFavourites(); window_invalidate(w); break; + } } } @@ -429,7 +401,7 @@ static void window_server_list_invalidate(rct_window* w) window_server_list_widgets[WIDX_START_SERVER].top = buttonTop; window_server_list_widgets[WIDX_START_SERVER].bottom = buttonBottom; - w->no_list_items = (uint16_t)_serverEntries.size(); + w->no_list_items = (uint16_t)_serverList.GetCount(); } static void window_server_list_paint(rct_window* w, rct_drawpixelinfo* dpi) @@ -449,8 +421,6 @@ static void window_server_list_paint(rct_window* w, rct_drawpixelinfo* dpi) static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) { - std::lock_guard guard(_mutex); - uint8_t paletteIndex = ColourMapA[w->colours[1]].mid_light; gfx_clear(dpi, paletteIndex); @@ -464,31 +434,31 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi continue; // if (y + ITEM_HEIGHT < dpi->y) continue; - server_entry* serverDetails = &_serverEntries[i]; + const auto& serverDetails = _serverList.GetServer(i); bool highlighted = i == w->selected_list_item; // Draw hover highlight if (highlighted) { gfx_filter_rect(dpi, 0, y, width, y + ITEM_HEIGHT, PALETTE_DARKEN_1); - _version = serverDetails->version; + _version = serverDetails.version; w->widgets[WIDX_LIST].tooltip = STR_NETWORK_VERSION_TIP; } int32_t colour = w->colours[1]; - if (serverDetails->favourite) + if (serverDetails.favourite) { colour = COLOUR_YELLOW; } // Draw server information - if (highlighted && !serverDetails->description.empty()) + if (highlighted && !serverDetails.description.empty()) { - gfx_draw_string(dpi, serverDetails->description.c_str(), colour, 3, y + 3); + gfx_draw_string(dpi, serverDetails.description.c_str(), colour, 3, y + 3); } else { - gfx_draw_string(dpi, serverDetails->name.c_str(), colour, 3, y + 3); + gfx_draw_string(dpi, serverDetails.name.c_str(), colour, 3, y + 3); } int32_t right = width - 3 - 14; @@ -496,7 +466,7 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi // Draw compatibility icon right -= 10; int32_t compatibilitySpriteId; - if (serverDetails->version.empty()) + if (serverDetails.version.empty()) { // Server not online... compatibilitySpriteId = SPR_G2_RCT1_CLOSE_BUTTON_0; @@ -504,7 +474,7 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi else { // Server online... check version - bool correctVersion = serverDetails->version == network_get_version(); + bool correctVersion = serverDetails.version == network_get_version(); compatibilitySpriteId = correctVersion ? SPR_G2_RCT1_OPEN_BUTTON_2 : SPR_G2_RCT1_CLOSE_BUTTON_2; } gfx_draw_sprite(dpi, compatibilitySpriteId, right, y + 1, 0); @@ -512,7 +482,7 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi // Draw lock icon right -= 8; - if (serverDetails->requiresPassword) + if (serverDetails.requiresPassword) { gfx_draw_sprite(dpi, SPR_G2_LOCKED, right, y + 4, 0); } @@ -521,9 +491,9 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi // Draw number of players char players[32]; players[0] = 0; - if (serverDetails->maxplayers > 0) + if (serverDetails.maxplayers > 0) { - snprintf(players, 32, "%d/%d", serverDetails->players, serverDetails->maxplayers); + snprintf(players, 32, "%d/%d", serverDetails.players, serverDetails.maxplayers); } int32_t numPlayersStringWidth = gfx_get_string_width(players); gfx_draw_string(dpi, players, w->colours[1], right - numPlayersStringWidth, y + 3); @@ -538,81 +508,6 @@ static void server_list_get_item_button(int32_t buttonIndex, int32_t x, int32_t *outY = y + 2; } -static void server_list_load_server_entries() -{ - auto entries = server_list_read(); - { - std::lock_guard guard(_mutex); - dispose_server_entry_list(); - _serverEntries = entries; - sort_servers(); - } -} - -static void server_list_save_server_entries() -{ - // Save just favourite servers - std::vector favouriteServers; - std::copy_if( - _serverEntries.begin(), _serverEntries.end(), std::back_inserter(favouriteServers), - [](const server_entry& entry) { return entry.favourite; }); - server_list_write(favouriteServers); -} - -static void dispose_server_entry_list() -{ - _serverEntries.clear(); - _serverEntries.shrink_to_fit(); -} - -static server_entry& add_server_entry(const std::string& address) -{ - auto entry = std::find_if(std::begin(_serverEntries), std::end(_serverEntries), [address](const server_entry& e) { - return e.address == address; - }); - if (entry != _serverEntries.end()) - { - return *entry; - } - - server_entry newserver; - newserver.address = address; - newserver.name = address; - _serverEntries.push_back(newserver); - return _serverEntries.back(); -} - -static bool server_compare(const server_entry& a, const server_entry& b) -{ - // Order by favourite - if (a.favourite != b.favourite) - { - return a.favourite; - } - - // Then by version - bool serverACompatible = a.version == network_get_version(); - bool serverBCompatible = b.version == network_get_version(); - if (serverACompatible != serverBCompatible) - { - return serverACompatible; - } - - // Then by password protection - if (a.requiresPassword != b.requiresPassword) - { - return !a.requiresPassword; - } - - // Then by name - return String::Compare(a.name, b.name, true) < 0; -} - -static void sort_servers() -{ - std::sort(_serverEntries.begin(), _serverEntries.end(), server_compare); -} - static void join_server(std::string address) { int32_t port = gConfigNetwork.default_port; @@ -640,196 +535,68 @@ static void join_server(std::string address) } } -#ifndef DISABLE_HTTP - -static void fetch_lan_servers_worker() +static void server_list_fetch_servers_begin() { - std::string msg = "Are you an OpenRCT2 server?"; - auto udpSocket = CreateUdpSocket(); - auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size()); - if (len == msg.size()) + if (_fetchFuture.valid()) { - char buffer[1024]{}; - size_t recievedLen{}; - std::unique_ptr endpoint; - for (int i = 0; i < 5 * 10; i++) - { - auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint); - if (p == NETWORK_READPACKET_SUCCESS) - { - auto sender = endpoint->GetHostname(); - std::printf(">> Recieved packet from %s\n", sender.c_str()); - auto jinfo = Json::FromString(std::string_view(buffer)); - - auto ip4 = json_array(); - json_array_append_new(ip4, json_string(sender.c_str())); - auto ip = json_object(); - json_object_set_new(ip, "v4", ip4); - json_object_set_new(jinfo, "ip", ip); - - AddServerFromJson(jinfo); - json_decref(jinfo); - } - platform_sleep(100); - } + // A fetch is already in progress + return; } - sort_servers(); - _numPlayersOnline = get_total_player_count(); - status_text = STR_X_PLAYERS_ONLINE; - window_invalidate_by_class(WC_SERVER_LIST); -} + _fetchFuture = std::async([] { + // Spin off background fetches + auto lanF = _serverList.FetchLocalServerListAsync(); + auto wanF = _serverList.FetchOnlineServerListAsync(); -static void fetch_lan_servers() -{ - std::thread worker(fetch_lan_servers_worker); - worker.detach(); -} - -static void fetch_servers() -{ - if (toupper('A') == 'A') - { - fetch_lan_servers(); - } - else - { - std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL; - if (gConfigNetwork.master_server_url.empty() == false) + // Merge or deal with errors + std::vector allEntries; + try + { + auto entries = lanF.get(); + allEntries.insert(allEntries.end(), entries.begin(), entries.end()); + } + catch (const std::exception& e) { - masterServerUrl = gConfigNetwork.master_server_url; } + try + { + auto entries = wanF.get(); + allEntries.insert(allEntries.end(), entries.begin(), entries.end()); + } + catch (const std::exception& e) { - std::lock_guard guard(_mutex); - _serverEntries.erase( - std::remove_if( - _serverEntries.begin(), _serverEntries.end(), [](const server_entry& server) { return !server.favourite; }), - _serverEntries.end()); - sort_servers(); } - Http::Request request; - request.url = masterServerUrl; - request.method = Http::Method::GET; - request.header["Accept"] = "application/json"; - status_text = STR_SERVER_LIST_CONNECTING; - Http::DoAsync(request, fetch_servers_callback); - } -} - -static uint32_t get_total_player_count() -{ - return std::accumulate(_serverEntries.begin(), _serverEntries.end(), 0, [](uint32_t acc, const server_entry& entry) { - return acc + entry.players; + return allEntries; }); } -static void fetch_servers_callback(Http::Response& response) +static void server_list_fetch_servers_check(rct_window* w) { - json_t* root = nullptr; - try + if (_fetchFuture.valid()) { - if (response.status != Http::Status::OK) + auto status = _fetchFuture.wait_for(std::chrono::seconds::zero()); + if (status == std::future_status::ready) { - throw MasterServerException(STR_SERVER_LIST_NO_CONNECTION); - } - - root = Json::FromString(response.body); - auto jsonStatus = json_object_get(root, "status"); - if (!json_is_number(jsonStatus)) - { - throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER); - } - - auto status = (int32_t)json_integer_value(jsonStatus); - if (status != 200) - { - throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED); - } - - auto jsonServers = json_object_get(root, "servers"); - if (!json_is_array(jsonServers)) - { - throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY); - } - - RefreshServersFromJson(jsonServers); - } - catch (const MasterServerException& e) - { - status_text = e.StatusText; - window_invalidate_by_class(WC_SERVER_LIST); - } - catch (const std::exception& e) - { - status_text = STR_SERVER_LIST_NO_CONNECTION; - window_invalidate_by_class(WC_SERVER_LIST); - log_warning("Unable to connect to master server: %s", e.what()); - } - - if (root != nullptr) - { - json_decref(root); - root = nullptr; - } -} - -static void RefreshServersFromJson(const json_t* jsonServers) -{ - auto count = (int32_t)json_array_size(jsonServers); - for (int32_t i = 0; i < count; i++) - { - auto server = json_array_get(jsonServers, i); - if (json_is_object(server)) - { - AddServerFromJson(server); - } - } - - sort_servers(); - _numPlayersOnline = get_total_player_count(); - - status_text = STR_X_PLAYERS_ONLINE; - window_invalidate_by_class(WC_SERVER_LIST); -} - -static void AddServerFromJson(const json_t* server) -{ - auto port = json_object_get(server, "port"); - auto name = json_object_get(server, "name"); - auto description = json_object_get(server, "description"); - auto requiresPassword = json_object_get(server, "requiresPassword"); - auto version = json_object_get(server, "version"); - auto players = json_object_get(server, "players"); - auto maxPlayers = json_object_get(server, "maxPlayers"); - auto ip = json_object_get(server, "ip"); - auto ip4 = json_object_get(ip, "v4"); - auto addressIp = json_array_get(ip4, 0); - - if (name == nullptr || version == nullptr) - { - log_verbose("Cowardly refusing to add server without name or version specified."); - } - else - { - auto address = String::StdFormat("%s:%d", json_string_value(addressIp), (int32_t)json_integer_value(port)); - { - std::lock_guard guard(_mutex); - auto& newserver = add_server_entry(address); - newserver.name = json_string_value(name); - newserver.requiresPassword = json_is_true(requiresPassword); - newserver.description = (description == nullptr ? "" : json_string_value(description)); - newserver.version = json_string_value(version); - newserver.players = (uint8_t)json_integer_value(players); - newserver.maxplayers = (uint8_t)json_integer_value(maxPlayers); + try + { + auto entries = _fetchFuture.get(); + _serverList.AddRange(entries); + _numPlayersOnline = _serverList.GetTotalPlayerCount(); + status_text = STR_X_PLAYERS_ONLINE; + } + catch (const MasterServerException& e) + { + status_text = e.StatusText; + } + catch (const std::exception& e) + { + status_text = STR_SERVER_LIST_NO_CONNECTION; + log_warning("Unable to connect to master server: %s", e.what()); + } + _fetchFuture = {}; + window_invalidate(w); } } } - -#endif - -static bool is_version_valid(const std::string& version) -{ - return version.empty() || version == network_get_version(); -} diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index d565ab0d68..4a90db8e2b 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -11,18 +11,122 @@ #include "../Context.h" #include "../PlatformEnvironment.h" +#include "../config/Config.h" #include "../core/FileStream.hpp" +#include "../core/Json.hpp" #include "../core/Memory.hpp" #include "../core/Path.hpp" #include "../core/String.hpp" +#include "../network/Http.h" #include "../platform/platform.h" +#include "UdpSocket.h" +#include "network.h" + +#include +#include using namespace OpenRCT2; +using namespace OpenRCT2::Network; -std::vector server_list_read() +int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const +{ + const auto& a = *this; + const auto& b = other; + + // Order by favourite + if (a.favourite != b.favourite) + { + return a.favourite ? -1 : 1; + } + + // Then by version + bool serverACompatible = a.version == network_get_version(); + bool serverBCompatible = b.version == network_get_version(); + if (serverACompatible != serverBCompatible) + { + return serverACompatible ? 1 : -1; + } + + // Then by password protection + if (a.requiresPassword != b.requiresPassword) + { + return a.requiresPassword ? -1 : 1; + } + + // Then by name + return String::Compare(a.name, b.name, true); +} + +bool ServerListEntry::IsVersionValid() const +{ + return version.empty() || version == network_get_version(); +} + +std::optional ServerListEntry::FromJson(const json_t* server) +{ + auto port = json_object_get(server, "port"); + auto name = json_object_get(server, "name"); + auto description = json_object_get(server, "description"); + auto requiresPassword = json_object_get(server, "requiresPassword"); + auto version = json_object_get(server, "version"); + auto players = json_object_get(server, "players"); + auto maxPlayers = json_object_get(server, "maxPlayers"); + auto ip = json_object_get(server, "ip"); + auto ip4 = json_object_get(ip, "v4"); + auto addressIp = json_array_get(ip4, 0); + + if (name == nullptr || version == nullptr) + { + log_verbose("Cowardly refusing to add server without name or version specified."); + return {}; + } + else + { + ServerListEntry entry; + entry.address = String::StdFormat("%s:%d", json_string_value(addressIp), (int32_t)json_integer_value(port)); + entry.name = (name == nullptr ? "" : json_string_value(name)); + entry.description = (description == nullptr ? "" : json_string_value(description)); + entry.version = json_string_value(version); + entry.requiresPassword = json_is_true(requiresPassword); + entry.players = (uint8_t)json_integer_value(players); + entry.maxplayers = (uint8_t)json_integer_value(maxPlayers); + return entry; + } +} + +void ServerList::Sort() +{ + std::sort(_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& a, const ServerListEntry& b) { + return a.CompareTo(b) > 0; + }); +} + +ServerListEntry& ServerList::GetServer(size_t index) +{ + return _serverEntries[index]; +} + +size_t ServerList::GetCount() const +{ + return _serverEntries.size(); +} + +void ServerList::Add(const ServerListEntry& entry) +{ + _serverEntries.push_back(entry); + Sort(); +} + +void ServerList::AddRange(const std::vector& entries) +{ + _serverEntries.insert(_serverEntries.end(), entries.begin(), entries.end()); + Sort(); +} + +std::vector ServerList::ReadFavourites() { log_verbose("server_list_read(...)"); - std::vector entries; + std::vector entries; try { auto env = GetContext()->GetPlatformEnvironment(); @@ -33,7 +137,7 @@ std::vector server_list_read() auto numEntries = fs.ReadValue(); for (size_t i = 0; i < numEntries; i++) { - server_entry serverInfo; + ServerListEntry serverInfo; serverInfo.address = fs.ReadStdString(); serverInfo.name = fs.ReadStdString(); serverInfo.requiresPassword = false; @@ -49,12 +153,32 @@ std::vector server_list_read() catch (const std::exception& e) { log_error("Unable to read server list: %s", e.what()); - entries = std::vector(); + entries = std::vector(); } return entries; } -bool server_list_write(const std::vector& entries) +void ServerList::ReadAndAddFavourites() +{ + _serverEntries.erase( + std::remove_if( + _serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& entry) { return entry.favourite; }), + _serverEntries.end()); + auto entries = ReadFavourites(); + AddRange(entries); +} + +void ServerList::WriteFavourites() +{ + // Save just favourite servers + std::vector favouriteServers; + std::copy_if( + _serverEntries.begin(), _serverEntries.end(), std::back_inserter(favouriteServers), + [](const ServerListEntry& entry) { return entry.favourite; }); + WriteFavourites(favouriteServers); +} + +bool ServerList::WriteFavourites(const std::vector& entries) { log_verbose("server_list_write(%d, 0x%p)", entries.size(), entries.data()); @@ -80,3 +204,131 @@ bool server_list_write(const std::vector& entries) return false; } } + +std::future> ServerList::FetchLocalServerListAsync() +{ + return std::async([] { + constexpr auto RECV_DELAY_MS = 10; + constexpr auto RECV_WAIT_MS = 2000; + + std::vector entries; + std::string msg = "Are you an OpenRCT2 server?"; + auto udpSocket = CreateUdpSocket(); + auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size()); + if (len != msg.size()) + { + throw std::runtime_error("Unable to broadcast server query."); + } + + char buffer[1024]{}; + size_t recievedLen{}; + std::unique_ptr endpoint; + for (int i = 0; i < (RECV_WAIT_MS / RECV_DELAY_MS); i++) + { + auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint); + if (p == NETWORK_READPACKET_SUCCESS) + { + auto sender = endpoint->GetHostname(); + std::printf(">> Recieved packet from %s\n", sender.c_str()); + auto jinfo = Json::FromString(std::string_view(buffer)); + + auto ip4 = json_array(); + json_array_append_new(ip4, json_string(sender.c_str())); + auto ip = json_object(); + json_object_set_new(ip, "v4", ip4); + json_object_set_new(jinfo, "ip", ip); + + auto entry = ServerListEntry::FromJson(jinfo); + if (entry.has_value()) + { + entries.push_back(entry.value()); + } + + json_decref(jinfo); + } + platform_sleep(RECV_DELAY_MS); + } + + return entries; + }); +} + +std::future> ServerList::FetchOnlineServerListAsync() +{ +#ifdef DISABLE_HTTP + return std::async(std::launch::deferred, [] { return std::vector(); }); +#else + auto p = std::make_shared>>(); + auto f = p->get_future(); + + std::string masterServerUrl = OPENRCT2_MASTER_SERVER_URL; + if (!gConfigNetwork.master_server_url.empty()) + { + masterServerUrl = gConfigNetwork.master_server_url; + } + + Http::Request request; + request.url = masterServerUrl; + request.method = Http::Method::GET; + request.header["Accept"] = "application/json"; + Http::DoAsync(request, [p](Http::Response& response) -> void { + json_t* root{}; + try + { + if (response.status != Http::Status::OK) + { + throw MasterServerException(STR_SERVER_LIST_NO_CONNECTION); + } + + root = Json::FromString(response.body); + auto jsonStatus = json_object_get(root, "status"); + if (!json_is_number(jsonStatus)) + { + throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER); + } + + auto status = (int32_t)json_integer_value(jsonStatus); + if (status != 200) + { + throw MasterServerException(STR_SERVER_LIST_MASTER_SERVER_FAILED); + } + + auto jServers = json_object_get(root, "servers"); + if (!json_is_array(jServers)) + { + throw MasterServerException(STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY); + } + + std::vector entries; + auto count = json_array_size(jServers); + for (size_t i = 0; i < count; i++) + { + auto jServer = json_array_get(jServers, i); + if (json_is_object(jServer)) + { + auto entry = ServerListEntry::FromJson(jServer); + if (entry.has_value()) + { + entries.push_back(entry.value()); + } + } + } + + p->set_value(entries); + } + catch (...) + { + p->set_exception(std::current_exception()); + } + json_decref(root); + }); + return f; +#endif +} + +uint32_t ServerList::GetTotalPlayerCount() const +{ + return std::accumulate(_serverEntries.begin(), _serverEntries.end(), 0, [](uint32_t acc, const ServerListEntry& entry) { + return acc + entry.players; + }); +} diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index 22afcfbe69..6aaa510157 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -10,21 +10,62 @@ #pragma once #include "../common.h" +#include "../core/Optional.hpp" +#include +#include #include #include -struct server_entry +struct json_t; + +struct ServerListEntry { std::string address; std::string name; std::string description; std::string version; - bool requiresPassword = false; - bool favourite = false; - uint8_t players = 0; - uint8_t maxplayers = 0; + bool requiresPassword{}; + bool favourite{}; + uint8_t players{}; + uint8_t maxplayers{}; + + int32_t CompareTo(const ServerListEntry& other) const; + bool IsVersionValid() const; + + static std::optional FromJson(const json_t* root); }; -std::vector server_list_read(); -bool server_list_write(const std::vector& entries); +class ServerList +{ +private: + std::vector _serverEntries; + + void Sort(); + std::vector ReadFavourites(); + bool WriteFavourites(const std::vector& entries); + +public: + ServerListEntry& GetServer(size_t index); + size_t GetCount() const; + void Add(const ServerListEntry& entry); + void AddRange(const std::vector& entries); + + void ReadAndAddFavourites(); + void WriteFavourites(); + + std::future> FetchLocalServerListAsync(); + std::future> FetchOnlineServerListAsync(); + uint32_t GetTotalPlayerCount() const; +}; + +class MasterServerException : public std::exception +{ +public: + rct_string_id StatusText; + + MasterServerException(rct_string_id statusText) + : StatusText(statusText) + { + } +}; From 4f0a73349681a02e3e6227ed2209aee2104874f2 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 14:12:50 +0000 Subject: [PATCH 07/26] Sort by LAN servers --- src/openrct2/network/ServerList.cpp | 7 +++++++ src/openrct2/network/ServerList.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 4a90db8e2b..c4501b97a6 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -39,6 +39,12 @@ int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const return a.favourite ? -1 : 1; } + // Order by local + if (a.local != b.local) + { + return a.local ? 1 : -1; + } + // Then by version bool serverACompatible = a.version == network_get_version(); bool serverBCompatible = b.version == network_get_version(); @@ -241,6 +247,7 @@ std::future> ServerList::FetchLocalServerListAsync( auto entry = ServerListEntry::FromJson(jinfo); if (entry.has_value()) { + entry.value().local = true; entries.push_back(entry.value()); } diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index 6aaa510157..7a1f6b4ced 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -29,6 +29,7 @@ struct ServerListEntry bool favourite{}; uint8_t players{}; uint8_t maxplayers{}; + bool local{}; int32_t CompareTo(const ServerListEntry& other) const; bool IsVersionValid() const; From 04c04d197e2b52cc700d7cf8b2d88098e62baa9c Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 14:28:10 +0000 Subject: [PATCH 08/26] Refactor broadcasting code and logging --- .../network/NetworkServerAdvertiser.cpp | 59 ++++++++++--------- src/openrct2/network/ServerList.cpp | 9 ++- src/openrct2/network/network.h | 1 + 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index bdfaa4beda..e593d0db1e 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -47,8 +47,6 @@ enum MASTER_SERVER_STATUS constexpr int32_t MASTER_SERVER_REGISTER_TIME = 120 * 1000; // 2 minutes constexpr int32_t MASTER_SERVER_HEARTBEAT_TIME = 60 * 1000; // 1 minute -constexpr int32_t LAN_BROADCAST_PORT = 11754; - class NetworkServerAdvertiser final : public INetworkServerAdvertiser { private: @@ -84,13 +82,23 @@ public: } void Update() override + { + UpdateLAN(); + if (gConfigNetwork.advertise) + { + UpdateWAN(); + } + } + +private: + void UpdateLAN() { auto ticks = Platform::GetTicks(); if (ticks > _lastListenTime + 500) { if (_lanListener->GetStatus() != SOCKET_STATUS_LISTENING) { - _lanListener->Listen(LAN_BROADCAST_PORT); + _lanListener->Listen(NETWORK_LAN_BROADCAST_PORT); } else { @@ -101,12 +109,12 @@ public: if (p == NETWORK_READPACKET_SUCCESS) { std::string sender = endpoint->GetHostname(); - std::printf("\r>> Received %zu bytes from %s\n", recievedBytes, sender.c_str()); + log_verbose("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str()); auto body = GetBroadcastJson(); auto bodyDump = json_dumps(body, JSON_COMPACT); size_t sendLen = strlen(bodyDump) + 1; - std::printf("\r>> Sending %zu bytes back to %s\n", sendLen, sender.c_str()); + log_verbose("Sending %zu bytes back to %s", sendLen, sender.c_str()); _lanListener->SendData(*endpoint, bodyDump, sendLen); free(bodyDump); json_decref(body); @@ -114,33 +122,30 @@ public: } _lastListenTime = ticks; } + } - if (gConfigNetwork.advertise) + void UpdateWAN() + { + switch (_status) { - /* - switch (_status) - { - case ADVERTISE_STATUS::UNREGISTERED: - if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME) - { - SendRegistration(_forceIPv4); - } - break; - case ADVERTISE_STATUS::REGISTERED: - if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME) - { - SendHeartbeat(); - } - break; - // exhaust enum values to satisfy clang - case ADVERTISE_STATUS::DISABLED: - break; - } - */ + case ADVERTISE_STATUS::UNREGISTERED: + if (_lastAdvertiseTime == 0 || platform_get_ticks() > _lastAdvertiseTime + MASTER_SERVER_REGISTER_TIME) + { + SendRegistration(_forceIPv4); + } + break; + case ADVERTISE_STATUS::REGISTERED: + if (platform_get_ticks() > _lastHeartbeatTime + MASTER_SERVER_HEARTBEAT_TIME) + { + SendHeartbeat(); + } + break; + // exhaust enum values to satisfy clang + case ADVERTISE_STATUS::DISABLED: + break; } } -private: void SendRegistration(bool forceIPv4) { _lastAdvertiseTime = platform_get_ticks(); diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index c4501b97a6..a7fe3ca586 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -217,15 +217,18 @@ std::future> ServerList::FetchLocalServerListAsync( constexpr auto RECV_DELAY_MS = 10; constexpr auto RECV_WAIT_MS = 2000; - std::vector entries; + auto broadcastAddress = "192.168.1.255"; + std::string msg = "Are you an OpenRCT2 server?"; auto udpSocket = CreateUdpSocket(); - auto len = udpSocket->SendData("192.168.1.255", 11754, msg.data(), msg.size()); + log_verbose("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress); + auto len = udpSocket->SendData(broadcastAddress, NETWORK_LAN_BROADCAST_PORT, msg.data(), msg.size()); if (len != msg.size()) { throw std::runtime_error("Unable to broadcast server query."); } + std::vector entries; char buffer[1024]{}; size_t recievedLen{}; std::unique_ptr endpoint; @@ -235,7 +238,7 @@ std::future> ServerList::FetchLocalServerListAsync( if (p == NETWORK_READPACKET_SUCCESS) { auto sender = endpoint->GetHostname(); - std::printf(">> Recieved packet from %s\n", sender.c_str()); + log_verbose("Received %zu bytes back from %s", recievedLen, sender.c_str()); auto jinfo = Json::FromString(std::string_view(buffer)); auto ip4 = json_array(); diff --git a/src/openrct2/network/network.h b/src/openrct2/network/network.h index 080e567639..0859cd4c42 100644 --- a/src/openrct2/network/network.h +++ b/src/openrct2/network/network.h @@ -10,6 +10,7 @@ #pragma once #define NETWORK_DEFAULT_PORT 11753 +#define NETWORK_LAN_BROADCAST_PORT 11754 #define MAX_SERVER_DESCRIPTION_LENGTH 256 #include "../common.h" From 51117432f0a148e8cc4a7085c360caee013cc610 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 15:48:35 +0000 Subject: [PATCH 09/26] Improve status messages and prevent duplicates --- src/openrct2-ui/windows/ServerList.cpp | 32 ++++++++++++++++++-------- src/openrct2/network/ServerList.cpp | 30 ++++++++++++++++++++---- src/openrct2/network/ServerList.h | 1 + 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index c39dab3b54..f10826ab62 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifndef DISABLE_HTTP using namespace OpenRCT2::Network; @@ -39,9 +40,9 @@ using namespace OpenRCT2::Network; static char _playerName[32 + 1]; static ServerList _serverList; -static std::future> _fetchFuture; +static std::future, rct_string_id>> _fetchFuture; static uint32_t _numPlayersOnline = 0; -static rct_string_id status_text = STR_SERVER_LIST_CONNECTING; +static rct_string_id _statusText = STR_SERVER_LIST_CONNECTING; // clang-format off enum { @@ -416,7 +417,7 @@ static void window_server_list_paint(rct_window* w, rct_drawpixelinfo* dpi) gfx_draw_string_left( dpi, STR_NETWORK_VERSION, (void*)&versionCStr, COLOUR_WHITE, w->x + 324, w->y + w->widgets[WIDX_START_SERVER].top + 1); - gfx_draw_string_left(dpi, status_text, (void*)&_numPlayersOnline, COLOUR_WHITE, w->x + 8, w->y + w->height - 15); + gfx_draw_string_left(dpi, _statusText, (void*)&_numPlayersOnline, COLOUR_WHITE, w->x + 8, w->y + w->height - 15); } static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) @@ -543,6 +544,10 @@ static void server_list_fetch_servers_begin() return; } + _serverList.Clear(); + _serverList.ReadAndAddFavourites(); + _statusText = STR_SERVER_LIST_CONNECTING; + _fetchFuture = std::async([] { // Spin off background fetches auto lanF = _serverList.FetchLocalServerListAsync(); @@ -559,16 +564,21 @@ static void server_list_fetch_servers_begin() { } + auto status = STR_NONE; try { auto entries = wanF.get(); allEntries.insert(allEntries.end(), entries.begin(), entries.end()); } + catch (const MasterServerException& e) + { + status = e.StatusText; + } catch (const std::exception& e) { + status = STR_SERVER_LIST_NO_CONNECTION; } - - return allEntries; + return std::make_tuple(allEntries, status); }); } @@ -581,18 +591,22 @@ static void server_list_fetch_servers_check(rct_window* w) { try { - auto entries = _fetchFuture.get(); + auto [entries, statusText] = std::move(_fetchFuture.get()); _serverList.AddRange(entries); _numPlayersOnline = _serverList.GetTotalPlayerCount(); - status_text = STR_X_PLAYERS_ONLINE; + _statusText = STR_X_PLAYERS_ONLINE; + if (statusText != STR_NONE) + { + _statusText = statusText; + } } catch (const MasterServerException& e) { - status_text = e.StatusText; + _statusText = e.StatusText; } catch (const std::exception& e) { - status_text = STR_SERVER_LIST_NO_CONNECTION; + _statusText = STR_SERVER_LIST_NO_CONNECTION; log_warning("Unable to connect to master server: %s", e.what()); } _fetchFuture = {}; diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index a7fe3ca586..d8a0e999cd 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -42,7 +42,7 @@ int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const // Order by local if (a.local != b.local) { - return a.local ? 1 : -1; + return a.local ? -1 : 1; } // Then by version @@ -50,13 +50,19 @@ int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const bool serverBCompatible = b.version == network_get_version(); if (serverACompatible != serverBCompatible) { - return serverACompatible ? 1 : -1; + return serverACompatible ? -1 : 1; } // Then by password protection if (a.requiresPassword != b.requiresPassword) { - return a.requiresPassword ? -1 : 1; + return a.requiresPassword ? 1 : -1; + } + + // Then by number of players + if (a.players != b.players) + { + return a.players > b.players ? -1 : 1; } // Then by name @@ -102,8 +108,19 @@ std::optional ServerListEntry::FromJson(const json_t* server) void ServerList::Sort() { + _serverEntries.erase( + std::unique( + _serverEntries.begin(), _serverEntries.end(), + [](const ServerListEntry& a, const ServerListEntry& b) { + if (a.favourite == b.favourite) + { + return String::Equals(a.address, b.address, true); + } + return false; + }), + _serverEntries.end()); std::sort(_serverEntries.begin(), _serverEntries.end(), [](const ServerListEntry& a, const ServerListEntry& b) { - return a.CompareTo(b) > 0; + return a.CompareTo(b) < 0; }); } @@ -129,6 +146,11 @@ void ServerList::AddRange(const std::vector& entries) Sort(); } +void ServerList::Clear() +{ + _serverEntries.clear(); +} + std::vector ServerList::ReadFavourites() { log_verbose("server_list_read(...)"); diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index 7a1f6b4ced..e6a147cd1b 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -51,6 +51,7 @@ public: size_t GetCount() const; void Add(const ServerListEntry& entry); void AddRange(const std::vector& entries); + void Clear(); void ReadAndAddFavourites(); void WriteFavourites(); From 59ddd7e1ea921745f3c897439ad4bbcec1a7f8f8 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 16:23:58 +0000 Subject: [PATCH 10/26] Get and broadcast to all broadcast address --- src/openrct2/network/ServerList.cpp | 41 +++++++++-- src/openrct2/network/ServerList.h | 2 + src/openrct2/network/UdpSocket.cpp | 109 ++++++++++++++++++++++++++-- src/openrct2/network/UdpSocket.h | 4 +- 4 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index d8a0e999cd..edcaeb01e4 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -233,16 +233,16 @@ bool ServerList::WriteFavourites(const std::vector& entries) } } -std::future> ServerList::FetchLocalServerListAsync() +std::future> ServerList::FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) { - return std::async([] { + auto broadcastAddress = broadcastEndpoint.GetHostname(); + return std::async([broadcastAddress] { constexpr auto RECV_DELAY_MS = 10; constexpr auto RECV_WAIT_MS = 2000; - auto broadcastAddress = "192.168.1.255"; - std::string msg = "Are you an OpenRCT2 server?"; auto udpSocket = CreateUdpSocket(); + log_verbose("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress); auto len = udpSocket->SendData(broadcastAddress, NETWORK_LAN_BROADCAST_PORT, msg.data(), msg.size()); if (len != msg.size()) @@ -280,11 +280,42 @@ std::future> ServerList::FetchLocalServerListAsync( } platform_sleep(RECV_DELAY_MS); } - return entries; }); } +std::future> ServerList::FetchLocalServerListAsync() +{ + return std::async([&] { + // Get all possible LAN broadcast addresses + auto broadcastEndpoints = GetBroadcastAddresses(); + + // Spin off a fetch for each broadcast address + std::vector>> futures; + for (const auto& broadcastEndpoint : broadcastEndpoints) + { + auto f = FetchLocalServerListAsync(*broadcastEndpoint); + futures.push_back(std::move(f)); + } + + // Wait and merge all results + std::vector mergedEntries; + for (auto& f : futures) + { + try + { + auto entries = std::move(f.get()); + mergedEntries.insert(mergedEntries.begin(), entries.begin(), entries.end()); + } + catch (...) + { + // Ignore any exceptions from a particular broadcast fetch + } + } + return mergedEntries; + }); +} + std::future> ServerList::FetchOnlineServerListAsync() { #ifdef DISABLE_HTTP diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index e6a147cd1b..c9b44a5746 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -18,6 +18,7 @@ #include struct json_t; +struct INetworkEndpoint; struct ServerListEntry { @@ -45,6 +46,7 @@ private: void Sort(); std::vector ReadFavourites(); bool WriteFavourites(const std::vector& entries); + std::future> FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint); public: ServerListEntry& GetServer(size_t index); diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp index 2f02c08219..3da7991c10 100644 --- a/src/openrct2/network/UdpSocket.cpp +++ b/src/openrct2/network/UdpSocket.cpp @@ -36,13 +36,15 @@ #endif #define FLAG_NO_PIPE 0 #else - #include #include - #include - #include - #include - #include + #include #include + #include + #include + #include + #include + #include + #include #include "../common.h" using SOCKET = int32_t; #define SOCKET_ERROR -1 @@ -116,7 +118,7 @@ public: } } - std::string GetHostname() override + std::string GetHostname() const override { char hostname[256]; int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST); @@ -395,4 +397,99 @@ std::unique_ptr CreateUdpSocket() return std::make_unique(); } +# ifdef _WIN32 +std::vector GetNetworkInterfaces() +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + printf("socket returned -1\n"); + return {}; + } + + DWORD len = 0; + size_t capacity = 2; + std::vector interfaces; + for (;;) + { + interfaces.resize(capacity); + if (WSAIoctl( + sock, SIO_GET_INTERFACE_LIST, nullptr, 0, interfaces.data(), capacity * sizeof(INTERFACE_INFO), &len, nullptr, + nullptr) + == 0) + { + break; + } + if (WSAGetLastError() != WSAEFAULT) + { + closesocket(sock); + return {}; + } + capacity *= 2; + } + interfaces.resize(len / sizeof(INTERFACE_INFO)); + interfaces.shrink_to_fit(); + return interfaces; +} +# endif + +std::vector> GetBroadcastAddresses() +{ + std::vector> baddresses; +# ifdef _WIN32 + auto interfaces = GetNetworkInterfaces(); + for (const auto& ifo : interfaces) + { + if (ifo.iiFlags & IFF_LOOPBACK) + continue; + if (!(ifo.iiFlags & IFF_BROADCAST)) + continue; + + // iiBroadcast is unusable, because it always seems to be set to 255.255.255.255. + sockaddr_storage address{}; + memcpy(&address, &ifo.iiAddress.Address, sizeof(sockaddr)); + ((sockaddr_in*)&address)->sin_addr.s_addr = ifo.iiAddress.AddressIn.sin_addr.s_addr + | ~ifo.iiNetmask.AddressIn.sin_addr.s_addr; + baddresses.push_back(std::make_unique(address, sizeof(sockaddr))); + } +# else + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + return baddresses; + } + + char buf[4 * 1024]{}; + ifconf ifconfx{}; + ifconfx.ifc_len = sizeof(buf); + ifconfx.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifconfx) == -1) + { + close(sock); + return baddresses; + } + + const char* buf_end = buf + ifconfx.ifc_len; + for (const char* p = buf; p < buf_end;) + { + auto req = (const ifreq*)p; + if (req->ifr_addr.sa_family == AF_INET) + { + ifreq r; + strcpy(r.ifr_name, req->ifr_name); + if (ioctl(sock, SIOCGIFFLAGS, &r) != -1 && (r.ifr_flags & IFF_BROADCAST) && ioctl(sock, SIOCGIFBRDADDR, &r) != -1) + { + baddresses.push_back(std::make_unique(&r.ifr_broadaddr, sizeof(sockaddr))); + } + } + p += sizeof(ifreq); +# if defined(AF_LINK) && !defined(SUNOS) + p += req->ifr_addr.sa_len - sizeof(struct sockaddr); +# endif + } + close(sock); +# endif + return baddresses; +} + #endif diff --git a/src/openrct2/network/UdpSocket.h b/src/openrct2/network/UdpSocket.h index a2523e3719..35df2ee370 100644 --- a/src/openrct2/network/UdpSocket.h +++ b/src/openrct2/network/UdpSocket.h @@ -13,6 +13,7 @@ #include #include +#include enum SOCKET_STATUS { @@ -40,7 +41,7 @@ interface INetworkEndpoint { } - virtual std::string GetHostname() abstract; + virtual std::string GetHostname() const abstract; }; /** @@ -69,3 +70,4 @@ public: }; std::unique_ptr CreateUdpSocket(); +std::vector> GetBroadcastAddresses(); From 6a4791e39e42540533a99706834d45685ddac3c5 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 16:31:50 +0000 Subject: [PATCH 11/26] Only reply to broadcasts with correct message --- src/openrct2/network/NetworkServerAdvertiser.cpp | 8 ++++---- src/openrct2/network/ServerList.cpp | 2 +- src/openrct2/network/network.h | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index e593d0db1e..fbc61f844c 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -102,11 +102,11 @@ private: } else { - char buffer[256]; - size_t recievedBytes; + char buffer[256]{}; + size_t recievedBytes{}; std::unique_ptr endpoint; - auto p = _lanListener->ReceiveData(buffer, sizeof(buffer), &recievedBytes, &endpoint); - if (p == NETWORK_READPACKET_SUCCESS) + auto p = _lanListener->ReceiveData(buffer, sizeof(buffer) - 1, &recievedBytes, &endpoint); + if (p == NETWORK_READPACKET_SUCCESS && String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG)) { std::string sender = endpoint->GetHostname(); log_verbose("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str()); diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index edcaeb01e4..49b8b1243f 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -240,7 +240,7 @@ std::future> ServerList::FetchLocalServerListAsync( constexpr auto RECV_DELAY_MS = 10; constexpr auto RECV_WAIT_MS = 2000; - std::string msg = "Are you an OpenRCT2 server?"; + std::string_view msg = NETWORK_LAN_BROADCAST_MSG; auto udpSocket = CreateUdpSocket(); log_verbose("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress); diff --git a/src/openrct2/network/network.h b/src/openrct2/network/network.h index 0859cd4c42..b57dd058cc 100644 --- a/src/openrct2/network/network.h +++ b/src/openrct2/network/network.h @@ -11,6 +11,7 @@ #define NETWORK_DEFAULT_PORT 11753 #define NETWORK_LAN_BROADCAST_PORT 11754 +#define NETWORK_LAN_BROADCAST_MSG "openrct2.server.query" #define MAX_SERVER_DESCRIPTION_LENGTH 256 #include "../common.h" From 131a6dad3441e38158d69075eefb933fa19b3032 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 16:36:10 +0000 Subject: [PATCH 12/26] Update changelog --- distribution/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 73674304cb..69033cec49 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.2.2+ (in development) ------------------------------------------------------------------------ +- Feature: [#2339] Find local servers automatically when fetching servers. - Feature: [#7296] Allow assigning a keyboard shortcut for the scenery picker. - Feature: [#8029] Add the Hungarian Forint (HUF) to the list of available currencies. - Feature: [#8481] Multi-threaded rendering. From 20f52a8cbe685fbe3d7c00bfa435bcef9c4f59c9 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 5 May 2019 16:54:16 +0000 Subject: [PATCH 13/26] Refactor TcpSocket and UdpSocket --- src/openrct2-ui/windows/ServerList.cpp | 1 - src/openrct2/network/Network.cpp | 2 +- src/openrct2/network/NetworkConnection.cpp | 2 +- src/openrct2/network/NetworkConnection.h | 2 +- .../network/NetworkServerAdvertiser.cpp | 2 +- src/openrct2/network/ServerList.cpp | 2 +- .../network/{TcpSocket.cpp => Socket.cpp} | 472 +++++++++++++++-- .../network/{UdpSocket.h => Socket.h} | 37 ++ src/openrct2/network/TcpSocket.h | 71 --- src/openrct2/network/UdpSocket.cpp | 495 ------------------ 10 files changed, 468 insertions(+), 618 deletions(-) rename src/openrct2/network/{TcpSocket.cpp => Socket.cpp} (57%) rename src/openrct2/network/{UdpSocket.h => Socket.h} (64%) delete mode 100644 src/openrct2/network/TcpSocket.h delete mode 100644 src/openrct2/network/UdpSocket.cpp diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index f10826ab62..7976f4bac3 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 39e24cfaac..1e64fe8f11 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -75,7 +75,7 @@ static constexpr uint32_t CHUNK_SIZE = 1024 * 63; # include "NetworkPlayer.h" # include "NetworkServerAdvertiser.h" # include "NetworkUser.h" -# include "TcpSocket.h" +# include "Socket.h" # include # include diff --git a/src/openrct2/network/NetworkConnection.cpp b/src/openrct2/network/NetworkConnection.cpp index ff090665f9..2f8c14725d 100644 --- a/src/openrct2/network/NetworkConnection.cpp +++ b/src/openrct2/network/NetworkConnection.cpp @@ -14,7 +14,7 @@ # include "../core/String.hpp" # include "../localisation/Localisation.h" # include "../platform/platform.h" -# include "TcpSocket.h" +# include "Socket.h" # include "network.h" constexpr size_t NETWORK_DISCONNECT_REASON_BUFFER_SIZE = 256; diff --git a/src/openrct2/network/NetworkConnection.h b/src/openrct2/network/NetworkConnection.h index def4a85304..2872ac6b6f 100644 --- a/src/openrct2/network/NetworkConnection.h +++ b/src/openrct2/network/NetworkConnection.h @@ -14,7 +14,7 @@ # include "NetworkKey.h" # include "NetworkPacket.h" # include "NetworkTypes.h" -# include "TcpSocket.h" +# include "Socket.h" # include # include diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index fbc61f844c..6e5504d8c8 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -24,7 +24,7 @@ # include "../world/Map.h" # include "../world/Park.h" # include "Http.h" -# include "UdpSocket.h" +# include "Socket.h" # include "network.h" # include diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 49b8b1243f..cf2dd67161 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -19,7 +19,7 @@ #include "../core/String.hpp" #include "../network/Http.h" #include "../platform/platform.h" -#include "UdpSocket.h" +#include "Socket.h" #include "network.h" #include diff --git a/src/openrct2/network/TcpSocket.cpp b/src/openrct2/network/Socket.cpp similarity index 57% rename from src/openrct2/network/TcpSocket.cpp rename to src/openrct2/network/Socket.cpp index 51bdc33ff8..fe93b295ea 100644 --- a/src/openrct2/network/TcpSocket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -36,13 +36,15 @@ #endif #define FLAG_NO_PIPE 0 #else - #include #include - #include - #include - #include - #include + #include #include + #include + #include + #include + #include + #include + #include #include "../common.h" using SOCKET = int32_t; #define SOCKET_ERROR -1 @@ -58,7 +60,7 @@ #endif // _WIN32 // clang-format on -# include "TcpSocket.h" +# include "Socket.h" constexpr auto CONNECT_TIMEOUT = std::chrono::milliseconds(3000); @@ -66,8 +68,6 @@ constexpr auto CONNECT_TIMEOUT = std::chrono::milliseconds(3000); static bool _wsaInitialised = false; # endif -class TcpSocket; - class SocketException : public std::runtime_error { public: @@ -77,7 +77,123 @@ public: } }; -class TcpSocket final : public ITcpSocket +class NetworkEndpoint final : public INetworkEndpoint +{ +private: + sockaddr _address{}; + socklen_t _addressLen{}; + +public: + NetworkEndpoint() + { + } + + NetworkEndpoint(const sockaddr* address, socklen_t addressLen) + { + std::memcpy(&_address, address, addressLen); + _addressLen = addressLen; + } + + const sockaddr& GetAddress() const + { + return _address; + } + + socklen_t GetAddressLen() const + { + return _addressLen; + } + + int32_t GetPort() const + { + if (_address.sa_family == AF_INET) + { + return ((sockaddr_in*)&_address)->sin_port; + } + else + { + return ((sockaddr_in6*)&_address)->sin6_port; + } + } + + std::string GetHostname() const override + { + char hostname[256]; + int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST); + if (res == 0) + { + return hostname; + } + return {}; + } +}; + +class Socket +{ +protected: + static bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len) + { + return ResolveAddress(AF_UNSPEC, address, port, ss, ss_len); + } + + static bool ResolveAddressIPv4(const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len) + { + return ResolveAddress(AF_INET, address, port, ss, ss_len); + } + + static bool SetNonBlocking(SOCKET socket, bool on) + { +# ifdef _WIN32 + u_long nonBlocking = on; + return ioctlsocket(socket, FIONBIO, &nonBlocking) == 0; +# else + int32_t flags = fcntl(socket, F_GETFL, 0); + return fcntl(socket, F_SETFL, on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) == 0; +# endif + } + + static bool SetOption(SOCKET socket, int32_t a, int32_t b, bool value) + { + int32_t ivalue = value ? 1 : 0; + return setsockopt(socket, a, b, (const char*)&ivalue, sizeof(ivalue)) == 0; + } + +private: + static bool ResolveAddress( + int32_t family, const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len) + { + std::string serviceName = std::to_string(port); + + addrinfo hints = {}; + hints.ai_family = family; + if (address.empty()) + { + hints.ai_flags = AI_PASSIVE; + } + + addrinfo* result = nullptr; + int errorcode = getaddrinfo(address.empty() ? nullptr : address.c_str(), serviceName.c_str(), &hints, &result); + if (errorcode != 0) + { + log_error("Resolving address failed: Code %d.", errorcode); + log_error("Resolution error message: %s.", gai_strerror(errorcode)); + return false; + } + if (result == nullptr) + { + return false; + } + else + { + std::memcpy(ss, result->ai_addr, result->ai_addrlen); + *ss_len = (socklen_t)result->ai_addrlen; + freeaddrinfo(result); + return true; + } + } +}; + +class TcpSocket final : public ITcpSocket, protected Socket { private: SOCKET_STATUS _status = SOCKET_STATUS_CLOSED; @@ -123,7 +239,7 @@ public: } sockaddr_storage ss{}; - int32_t ss_len; + socklen_t ss_len; if (!ResolveAddress(address, port, &ss, &ss_len)) { throw SocketException("Unable to resolve address."); @@ -209,7 +325,7 @@ public: int32_t rc = getnameinfo( (struct sockaddr*)&client_addr, client_len, hostName, sizeof(hostName), nullptr, 0, NI_NUMERICHOST | NI_NUMERICSERV); - SetTCPNoDelay(socket, true); + SetOption(socket, IPPROTO_TCP, TCP_NODELAY, true); if (rc == 0) { tcpSocket = std::unique_ptr(new TcpSocket(socket, hostName)); @@ -236,7 +352,7 @@ public: _status = SOCKET_STATUS_RESOLVING; sockaddr_storage ss{}; - int32_t ss_len; + socklen_t ss_len; if (!ResolveAddress(address, port, &ss, &ss_len)) { throw SocketException("Unable to resolve address."); @@ -249,7 +365,7 @@ public: throw SocketException("Unable to create socket."); } - SetTCPNoDelay(_socket, true); + SetOption(_socket, IPPROTO_TCP, TCP_NODELAY, true); if (!SetNonBlocking(_socket, true)) { throw SocketException("Failed to set non-blocking mode."); @@ -446,61 +562,221 @@ private: } _status = SOCKET_STATUS_CLOSED; } +}; - bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, int32_t* ss_len) +class UdpSocket final : public IUdpSocket, protected Socket +{ +private: + SOCKET_STATUS _status = SOCKET_STATUS_CLOSED; + uint16_t _listeningPort = 0; + SOCKET _socket = INVALID_SOCKET; + NetworkEndpoint _endpoint; + + std::string _hostName; + std::string _error; + +public: + UdpSocket() = default; + + ~UdpSocket() override { - std::string serviceName = std::to_string(port); + CloseSocket(); + } - addrinfo hints = {}; - hints.ai_family = AF_UNSPEC; - if (address.empty()) + SOCKET_STATUS GetStatus() override + { + return _status; + } + + const char* GetError() override + { + return _error.empty() ? nullptr : _error.c_str(); + } + + void Listen(uint16_t port) override + { + Listen("", port); + } + + void Listen(const std::string& address, uint16_t port) override + { + if (_status != SOCKET_STATUS_CLOSED) { - hints.ai_flags = AI_PASSIVE; + throw std::runtime_error("Socket not closed."); } - addrinfo* result = nullptr; - int errorcode = getaddrinfo(address.empty() ? nullptr : address.c_str(), serviceName.c_str(), &hints, &result); - if (errorcode != 0) + sockaddr_storage ss{}; + socklen_t ss_len; + if (!ResolveAddressIPv4(address, port, &ss, &ss_len)) { - log_error("Resolving address failed: Code %d.", errorcode); - log_error("Resolution error message: %s.", gai_strerror(errorcode)); - return false; + throw SocketException("Unable to resolve address."); } - if (result == nullptr) + + // Create the listening socket + _socket = socket(ss.ss_family, SOCK_DGRAM, IPPROTO_UDP); + if (_socket == INVALID_SOCKET) { - return false; + throw SocketException("Unable to create socket."); + } + + // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections + if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) + { + log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); + } + + if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) + { + log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); + } + + // Enable send and receiving of broadcast messages + if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) + { + log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); + } + + try + { + // Bind to address:port and listen + if (bind(_socket, (sockaddr*)&ss, ss_len) != 0) + { + throw SocketException("Unable to bind to socket."); + } + + if (!SetNonBlocking(_socket, true)) + { + throw SocketException("Failed to set non-blocking mode."); + } + } + catch (const std::exception&) + { + CloseSocket(); + throw; + } + + _listeningPort = port; + _status = SOCKET_STATUS_LISTENING; + } + + size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) override + { + sockaddr_storage ss{}; + socklen_t ss_len; + if (!ResolveAddressIPv4(address, port, &ss, &ss_len)) + { + throw SocketException("Unable to resolve address."); + } + NetworkEndpoint endpoint((const sockaddr*)&ss, ss_len); + return SendData(endpoint, buffer, size); + } + + size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) override + { + if (_socket == INVALID_SOCKET) + { + _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (_socket == INVALID_SOCKET) + { + throw SocketException("Unable to create socket."); + } + + // Enable send and receiving of broadcast messages + if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) + { + log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); + } + + if (!SetNonBlocking(_socket, true)) + { + throw SocketException("Failed to set non-blocking mode."); + } + } + + const auto& dest = dynamic_cast(&destination); + if (dest == nullptr) + { + throw std::invalid_argument("destination is not compatible."); + } + auto ss = &dest->GetAddress(); + auto ss_len = dest->GetAddressLen(); + + if (_status != SOCKET_STATUS_LISTENING) + { + _endpoint = *dest; + } + + size_t totalSent = 0; + do + { + const char* bufferStart = (const char*)buffer + totalSent; + size_t remainingSize = size - totalSent; + int32_t sentBytes = sendto(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE, (const sockaddr*)ss, ss_len); + if (sentBytes == SOCKET_ERROR) + { + return totalSent; + } + totalSent += sentBytes; + } while (totalSent < size); + return totalSent; + } + + NETWORK_READPACKET ReceiveData( + void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr* sender) override + { + sockaddr_in senderAddr{}; + socklen_t senderAddrLen = sizeof(sockaddr_in); + if (_status != SOCKET_STATUS_LISTENING) + { + senderAddrLen = _endpoint.GetAddressLen(); + std::memcpy(&senderAddr, &_endpoint.GetAddress(), senderAddrLen); + } + auto readBytes = recvfrom(_socket, (char*)buffer, (int32_t)size, 0, (sockaddr*)&senderAddr, &senderAddrLen); + if (readBytes <= 0) + { + *sizeReceived = 0; + return NETWORK_READPACKET_NO_DATA; } else { - std::memcpy(ss, result->ai_addr, result->ai_addrlen); - *ss_len = (int32_t)result->ai_addrlen; - freeaddrinfo(result); - return true; + *sizeReceived = readBytes; + if (sender != nullptr) + { + *sender = std::make_unique((sockaddr*)&senderAddr, senderAddrLen); + } + return NETWORK_READPACKET_SUCCESS; } } - static bool SetNonBlocking(SOCKET socket, bool on) + void Close() override { -# ifdef _WIN32 - u_long nonBlocking = on; - return ioctlsocket(socket, FIONBIO, &nonBlocking) == 0; -# else - int32_t flags = fcntl(socket, F_GETFL, 0); - return fcntl(socket, F_SETFL, on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) == 0; -# endif + CloseSocket(); } - static bool SetTCPNoDelay(SOCKET socket, bool enabled) + const char* GetHostName() const override { - return setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&enabled, sizeof(enabled)) == 0; + return _hostName.empty() ? nullptr : _hostName.c_str(); + } + +private: + explicit UdpSocket(SOCKET socket, const std::string& hostName) + { + _socket = socket; + _hostName = hostName; + _status = SOCKET_STATUS_CONNECTED; + } + + void CloseSocket() + { + if (_socket != INVALID_SOCKET) + { + closesocket(_socket); + _socket = INVALID_SOCKET; + } + _status = SOCKET_STATUS_CLOSED; } }; -std::unique_ptr CreateTcpSocket() -{ - return std::make_unique(); -} - bool InitialiseWSA() { # ifdef _WIN32 @@ -532,6 +808,110 @@ void DisposeWSA() # endif } +std::unique_ptr CreateTcpSocket() +{ + return std::make_unique(); +} + +std::unique_ptr CreateUdpSocket() +{ + return std::make_unique(); +} + +# ifdef _WIN32 +std::vector GetNetworkInterfaces() +{ + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + return {}; + } + + DWORD len = 0; + size_t capacity = 2; + std::vector interfaces; + for (;;) + { + interfaces.resize(capacity); + if (WSAIoctl( + sock, SIO_GET_INTERFACE_LIST, nullptr, 0, interfaces.data(), capacity * sizeof(INTERFACE_INFO), &len, nullptr, + nullptr) + == 0) + { + break; + } + if (WSAGetLastError() != WSAEFAULT) + { + closesocket(sock); + return {}; + } + capacity *= 2; + } + interfaces.resize(len / sizeof(INTERFACE_INFO)); + interfaces.shrink_to_fit(); + return interfaces; +} +# endif + +std::vector> GetBroadcastAddresses() +{ + std::vector> baddresses; +# ifdef _WIN32 + auto interfaces = GetNetworkInterfaces(); + for (const auto& ifo : interfaces) + { + if (ifo.iiFlags & IFF_LOOPBACK) + continue; + if (!(ifo.iiFlags & IFF_BROADCAST)) + continue; + + // iiBroadcast is unusable, because it always seems to be set to 255.255.255.255. + sockaddr_storage address{}; + memcpy(&address, &ifo.iiAddress.Address, sizeof(sockaddr)); + ((sockaddr_in*)&address)->sin_addr.s_addr = ifo.iiAddress.AddressIn.sin_addr.s_addr + | ~ifo.iiNetmask.AddressIn.sin_addr.s_addr; + baddresses.push_back(std::make_unique(address, sizeof(sockaddr))); + } +# else + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + { + return baddresses; + } + + char buf[4 * 1024]{}; + ifconf ifconfx{}; + ifconfx.ifc_len = sizeof(buf); + ifconfx.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifconfx) == -1) + { + close(sock); + return baddresses; + } + + const char* buf_end = buf + ifconfx.ifc_len; + for (const char* p = buf; p < buf_end;) + { + auto req = (const ifreq*)p; + if (req->ifr_addr.sa_family == AF_INET) + { + ifreq r; + strcpy(r.ifr_name, req->ifr_name); + if (ioctl(sock, SIOCGIFFLAGS, &r) != -1 && (r.ifr_flags & IFF_BROADCAST) && ioctl(sock, SIOCGIFBRDADDR, &r) != -1) + { + baddresses.push_back(std::make_unique(&r.ifr_broadaddr, sizeof(sockaddr))); + } + } + p += sizeof(ifreq); +# if defined(AF_LINK) && !defined(SUNOS) + p += req->ifr_addr.sa_len - sizeof(struct sockaddr); +# endif + } + close(sock); +# endif + return baddresses; +} + namespace Convert { uint16_t HostToNetwork(uint16_t value) diff --git a/src/openrct2/network/UdpSocket.h b/src/openrct2/network/Socket.h similarity index 64% rename from src/openrct2/network/UdpSocket.h rename to src/openrct2/network/Socket.h index 35df2ee370..178388aa89 100644 --- a/src/openrct2/network/UdpSocket.h +++ b/src/openrct2/network/Socket.h @@ -44,6 +44,34 @@ interface INetworkEndpoint virtual std::string GetHostname() const abstract; }; +/** + * Represents a TCP socket / connection or listener. + */ +interface ITcpSocket +{ +public: + virtual ~ITcpSocket() + { + } + + virtual SOCKET_STATUS GetStatus() abstract; + virtual const char* GetError() abstract; + virtual const char* GetHostName() const abstract; + + virtual void Listen(uint16_t port) abstract; + virtual void Listen(const std::string& address, uint16_t port) abstract; + virtual std::unique_ptr Accept() abstract; + + virtual void Connect(const std::string& address, uint16_t port) abstract; + virtual void ConnectAsync(const std::string& address, uint16_t port) abstract; + + virtual size_t SendData(const void* buffer, size_t size) abstract; + virtual NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract; + + virtual void Disconnect() abstract; + virtual void Close() abstract; +}; + /** * Represents a UDP socket / listener. */ @@ -69,5 +97,14 @@ public: virtual void Close() abstract; }; +bool InitialiseWSA(); +void DisposeWSA(); +std::unique_ptr CreateTcpSocket(); std::unique_ptr CreateUdpSocket(); std::vector> GetBroadcastAddresses(); + +namespace Convert +{ + uint16_t HostToNetwork(uint16_t value); + uint16_t NetworkToHost(uint16_t value); +} // namespace Convert diff --git a/src/openrct2/network/TcpSocket.h b/src/openrct2/network/TcpSocket.h deleted file mode 100644 index 1e6186f20a..0000000000 --- a/src/openrct2/network/TcpSocket.h +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#pragma once - -#include "../common.h" - -#include -#include - -enum SOCKET_STATUS -{ - SOCKET_STATUS_CLOSED, - SOCKET_STATUS_RESOLVING, - SOCKET_STATUS_CONNECTING, - SOCKET_STATUS_CONNECTED, - SOCKET_STATUS_LISTENING, -}; - -enum NETWORK_READPACKET -{ - NETWORK_READPACKET_SUCCESS, - NETWORK_READPACKET_NO_DATA, - NETWORK_READPACKET_MORE_DATA, - NETWORK_READPACKET_DISCONNECTED -}; - -/** - * Represents a TCP socket / connection or listener. - */ -interface ITcpSocket -{ -public: - virtual ~ITcpSocket() - { - } - - virtual SOCKET_STATUS GetStatus() abstract; - virtual const char* GetError() abstract; - virtual const char* GetHostName() const abstract; - - virtual void Listen(uint16_t port) abstract; - virtual void Listen(const std::string& address, uint16_t port) abstract; - virtual std::unique_ptr Accept() abstract; - - virtual void Connect(const std::string& address, uint16_t port) abstract; - virtual void ConnectAsync(const std::string& address, uint16_t port) abstract; - - virtual size_t SendData(const void* buffer, size_t size) abstract; - virtual NETWORK_READPACKET ReceiveData(void* buffer, size_t size, size_t* sizeReceived) abstract; - - virtual void Disconnect() abstract; - virtual void Close() abstract; -}; - -std::unique_ptr CreateTcpSocket(); - -bool InitialiseWSA(); -void DisposeWSA(); - -namespace Convert -{ - uint16_t HostToNetwork(uint16_t value); - uint16_t NetworkToHost(uint16_t value); -} // namespace Convert diff --git a/src/openrct2/network/UdpSocket.cpp b/src/openrct2/network/UdpSocket.cpp deleted file mode 100644 index 3da7991c10..0000000000 --- a/src/openrct2/network/UdpSocket.cpp +++ /dev/null @@ -1,495 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2019 OpenRCT2 developers - * - * For a complete list of all authors, please refer to contributors.md - * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is licensed under the GNU General Public License version 3. - *****************************************************************************/ - -#ifndef DISABLE_NETWORK - -# include -# include -# include -# include -# include -# include - -// clang-format off -// MSVC: include here otherwise PI gets defined twice -#include - -#ifdef _WIN32 - // winsock2 must be included before windows.h - #include - #include - - #define LAST_SOCKET_ERROR() WSAGetLastError() - #undef EWOULDBLOCK - #define EWOULDBLOCK WSAEWOULDBLOCK - #ifndef SHUT_RD - #define SHUT_RD SD_RECEIVE - #endif - #ifndef SHUT_RDWR - #define SHUT_RDWR SD_BOTH - #endif - #define FLAG_NO_PIPE 0 -#else - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include "../common.h" - using SOCKET = int32_t; - #define SOCKET_ERROR -1 - #define INVALID_SOCKET -1 - #define LAST_SOCKET_ERROR() errno - #define closesocket close - #define ioctlsocket ioctl - #if defined(__linux__) - #define FLAG_NO_PIPE MSG_NOSIGNAL - #else - #define FLAG_NO_PIPE 0 - #endif // defined(__linux__) -#endif // _WIN32 -// clang-format on - -# include "UdpSocket.h" - -constexpr auto CONNECT_TIMEOUT = std::chrono::milliseconds(3000); - -# ifdef _WIN32 -static bool _wsaInitialised = false; -# endif - -class UdpSocket; - -class SocketException : public std::runtime_error -{ -public: - explicit SocketException(const std::string& message) - : std::runtime_error(message) - { - } -}; - -class NetworkEndpoint final : public INetworkEndpoint -{ -private: - sockaddr _address{}; - socklen_t _addressLen{}; - -public: - NetworkEndpoint() - { - } - - NetworkEndpoint(const sockaddr* address, socklen_t addressLen) - { - std::memcpy(&_address, address, addressLen); - _addressLen = addressLen; - } - - const sockaddr& GetAddress() const - { - return _address; - } - - socklen_t GetAddressLen() const - { - return _addressLen; - } - - int32_t GetPort() const - { - if (_address.sa_family == AF_INET) - { - return ((sockaddr_in*)&_address)->sin_port; - } - else - { - return ((sockaddr_in6*)&_address)->sin6_port; - } - } - - std::string GetHostname() const override - { - char hostname[256]; - int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST); - if (res == 0) - { - return hostname; - } - return {}; - } -}; - -class UdpSocket final : public IUdpSocket -{ -private: - SOCKET_STATUS _status = SOCKET_STATUS_CLOSED; - uint16_t _listeningPort = 0; - SOCKET _socket = INVALID_SOCKET; - NetworkEndpoint _endpoint; - - std::string _hostName; - std::string _error; - -public: - UdpSocket() = default; - - ~UdpSocket() override - { - CloseSocket(); - } - - SOCKET_STATUS GetStatus() override - { - return _status; - } - - const char* GetError() override - { - return _error.empty() ? nullptr : _error.c_str(); - } - - void Listen(uint16_t port) override - { - Listen("", port); - } - - void Listen(const std::string& address, uint16_t port) override - { - if (_status != SOCKET_STATUS_CLOSED) - { - throw std::runtime_error("Socket not closed."); - } - - sockaddr_storage ss{}; - socklen_t ss_len; - if (!ResolveAddress(address, port, &ss, &ss_len)) - { - throw SocketException("Unable to resolve address."); - } - - // Create the listening socket - _socket = socket(ss.ss_family, SOCK_DGRAM, IPPROTO_UDP); - if (_socket == INVALID_SOCKET) - { - throw SocketException("Unable to create socket."); - } - - // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections - if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) - { - log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); - } - - if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) - { - log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); - } - - // Enable send and receiving of broadcast messages - if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) - { - log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); - } - - try - { - // Bind to address:port and listen - if (bind(_socket, (sockaddr*)&ss, ss_len) != 0) - { - throw SocketException("Unable to bind to socket."); - } - - if (!SetNonBlocking(_socket, true)) - { - throw SocketException("Failed to set non-blocking mode."); - } - } - catch (const std::exception&) - { - CloseSocket(); - throw; - } - - _listeningPort = port; - _status = SOCKET_STATUS_LISTENING; - } - - size_t SendData(const std::string& address, uint16_t port, const void* buffer, size_t size) override - { - sockaddr_storage ss{}; - socklen_t ss_len; - if (!ResolveAddress(address, port, &ss, &ss_len)) - { - throw SocketException("Unable to resolve address."); - } - NetworkEndpoint endpoint((const sockaddr*)&ss, ss_len); - return SendData(endpoint, buffer, size); - } - - size_t SendData(const INetworkEndpoint& destination, const void* buffer, size_t size) override - { - if (_socket == INVALID_SOCKET) - { - _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (_socket == INVALID_SOCKET) - { - throw SocketException("Unable to create socket."); - } - - // Enable send and receiving of broadcast messages - if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) - { - log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); - } - - if (!SetNonBlocking(_socket, true)) - { - throw SocketException("Failed to set non-blocking mode."); - } - } - - const auto& dest = dynamic_cast(&destination); - if (dest == nullptr) - { - throw std::invalid_argument("destination is not compatible."); - } - auto ss = &dest->GetAddress(); - auto ss_len = dest->GetAddressLen(); - - if (_status != SOCKET_STATUS_LISTENING) - { - _endpoint = *dest; - } - - size_t totalSent = 0; - do - { - const char* bufferStart = (const char*)buffer + totalSent; - size_t remainingSize = size - totalSent; - int32_t sentBytes = sendto(_socket, bufferStart, (int32_t)remainingSize, FLAG_NO_PIPE, (const sockaddr*)ss, ss_len); - if (sentBytes == SOCKET_ERROR) - { - return totalSent; - } - totalSent += sentBytes; - } while (totalSent < size); - return totalSent; - } - - NETWORK_READPACKET ReceiveData( - void* buffer, size_t size, size_t* sizeReceived, std::unique_ptr* sender) override - { - sockaddr_in senderAddr{}; - socklen_t senderAddrLen = sizeof(sockaddr_in); - if (_status != SOCKET_STATUS_LISTENING) - { - senderAddrLen = _endpoint.GetAddressLen(); - std::memcpy(&senderAddr, &_endpoint.GetAddress(), senderAddrLen); - } - auto readBytes = recvfrom(_socket, (char*)buffer, (int32_t)size, 0, (sockaddr*)&senderAddr, &senderAddrLen); - if (readBytes <= 0) - { - *sizeReceived = 0; - return NETWORK_READPACKET_NO_DATA; - } - else - { - *sizeReceived = readBytes; - if (sender != nullptr) - { - *sender = std::make_unique((sockaddr*)&senderAddr, senderAddrLen); - } - return NETWORK_READPACKET_SUCCESS; - } - } - - void Close() override - { - CloseSocket(); - } - - const char* GetHostName() const override - { - return _hostName.empty() ? nullptr : _hostName.c_str(); - } - -private: - explicit UdpSocket(SOCKET socket, const std::string& hostName) - { - _socket = socket; - _hostName = hostName; - _status = SOCKET_STATUS_CONNECTED; - } - - void CloseSocket() - { - if (_socket != INVALID_SOCKET) - { - closesocket(_socket); - _socket = INVALID_SOCKET; - } - _status = SOCKET_STATUS_CLOSED; - } - - bool ResolveAddress(const std::string& address, uint16_t port, sockaddr_storage* ss, socklen_t* ss_len) - { - std::string serviceName = std::to_string(port); - - addrinfo hints = {}; - hints.ai_family = AF_INET; - if (address.empty()) - { - hints.ai_flags = AI_PASSIVE; - } - - addrinfo* result = nullptr; - int errorcode = getaddrinfo(address.empty() ? nullptr : address.c_str(), serviceName.c_str(), &hints, &result); - if (errorcode != 0) - { - log_error("Resolving address failed: Code %d.", errorcode); - log_error("Resolution error message: %s.", gai_strerror(errorcode)); - return false; - } - if (result == nullptr) - { - return false; - } - else - { - std::memcpy(ss, result->ai_addr, result->ai_addrlen); - *ss_len = (socklen_t)result->ai_addrlen; - freeaddrinfo(result); - return true; - } - } - - static bool SetNonBlocking(SOCKET socket, bool on) - { -# ifdef _WIN32 - u_long nonBlocking = on; - return ioctlsocket(socket, FIONBIO, &nonBlocking) == 0; -# else - int32_t flags = fcntl(socket, F_GETFL, 0); - return fcntl(socket, F_SETFL, on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) == 0; -# endif - } - - static bool SetOption(SOCKET socket, int32_t a, int32_t b, bool value) - { - int32_t ivalue = value ? 1 : 0; - return setsockopt(socket, a, b, (const char*)&ivalue, sizeof(ivalue)) == 0; - } -}; - -std::unique_ptr CreateUdpSocket() -{ - return std::make_unique(); -} - -# ifdef _WIN32 -std::vector GetNetworkInterfaces() -{ - int sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock == -1) - { - printf("socket returned -1\n"); - return {}; - } - - DWORD len = 0; - size_t capacity = 2; - std::vector interfaces; - for (;;) - { - interfaces.resize(capacity); - if (WSAIoctl( - sock, SIO_GET_INTERFACE_LIST, nullptr, 0, interfaces.data(), capacity * sizeof(INTERFACE_INFO), &len, nullptr, - nullptr) - == 0) - { - break; - } - if (WSAGetLastError() != WSAEFAULT) - { - closesocket(sock); - return {}; - } - capacity *= 2; - } - interfaces.resize(len / sizeof(INTERFACE_INFO)); - interfaces.shrink_to_fit(); - return interfaces; -} -# endif - -std::vector> GetBroadcastAddresses() -{ - std::vector> baddresses; -# ifdef _WIN32 - auto interfaces = GetNetworkInterfaces(); - for (const auto& ifo : interfaces) - { - if (ifo.iiFlags & IFF_LOOPBACK) - continue; - if (!(ifo.iiFlags & IFF_BROADCAST)) - continue; - - // iiBroadcast is unusable, because it always seems to be set to 255.255.255.255. - sockaddr_storage address{}; - memcpy(&address, &ifo.iiAddress.Address, sizeof(sockaddr)); - ((sockaddr_in*)&address)->sin_addr.s_addr = ifo.iiAddress.AddressIn.sin_addr.s_addr - | ~ifo.iiNetmask.AddressIn.sin_addr.s_addr; - baddresses.push_back(std::make_unique(address, sizeof(sockaddr))); - } -# else - int sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock == -1) - { - return baddresses; - } - - char buf[4 * 1024]{}; - ifconf ifconfx{}; - ifconfx.ifc_len = sizeof(buf); - ifconfx.ifc_buf = buf; - if (ioctl(sock, SIOCGIFCONF, &ifconfx) == -1) - { - close(sock); - return baddresses; - } - - const char* buf_end = buf + ifconfx.ifc_len; - for (const char* p = buf; p < buf_end;) - { - auto req = (const ifreq*)p; - if (req->ifr_addr.sa_family == AF_INET) - { - ifreq r; - strcpy(r.ifr_name, req->ifr_name); - if (ioctl(sock, SIOCGIFFLAGS, &r) != -1 && (r.ifr_flags & IFF_BROADCAST) && ioctl(sock, SIOCGIFBRDADDR, &r) != -1) - { - baddresses.push_back(std::make_unique(&r.ifr_broadaddr, sizeof(sockaddr))); - } - } - p += sizeof(ifreq); -# if defined(AF_LINK) && !defined(SUNOS) - p += req->ifr_addr.sa_len - sizeof(struct sockaddr); -# endif - } - close(sock); -# endif - return baddresses; -} - -#endif From 4b0f2bbceb7fb542a2894d48e2979fa8fc5562b9 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 6 May 2019 11:19:35 +0000 Subject: [PATCH 14/26] Fix disable network / disable http builds --- src/openrct2-ui/WindowManager.cpp | 2 + src/openrct2-ui/windows/ServerList.cpp | 54 ++++++------- src/openrct2-ui/windows/ServerStart.cpp | 26 ++++--- src/openrct2-ui/windows/Window.h | 2 + .../network/NetworkServerAdvertiser.cpp | 78 +++++++++---------- src/openrct2/network/ServerList.cpp | 45 ++++++----- 6 files changed, 106 insertions(+), 101 deletions(-) diff --git a/src/openrct2-ui/WindowManager.cpp b/src/openrct2-ui/WindowManager.cpp index 8c8e497466..d897f0f641 100644 --- a/src/openrct2-ui/WindowManager.cpp +++ b/src/openrct2-ui/WindowManager.cpp @@ -100,10 +100,12 @@ public: return window_save_prompt_open(); case WC_SCENERY: return window_scenery_open(); +#ifndef DISABLE_NETWORK case WC_SERVER_LIST: return window_server_list_open(); case WC_SERVER_START: return window_server_start_open(); +#endif case WC_KEYBOARD_SHORTCUT_LIST: return window_shortcut_keys_open(); case WC_STAFF_LIST: diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 7976f4bac3..fa285efcb9 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -7,35 +7,33 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifndef DISABLE_NETWORK -#ifndef DISABLE_HTTP -using namespace OpenRCT2::Network; -#endif +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include -#define WWIDTH_MIN 500 -#define WHEIGHT_MIN 300 -#define WWIDTH_MAX 1200 -#define WHEIGHT_MAX 800 -#define ITEM_HEIGHT (3 + 9 + 3) +# define WWIDTH_MIN 500 +# define WHEIGHT_MIN 300 +# define WWIDTH_MAX 1200 +# define WHEIGHT_MAX 800 +# define ITEM_HEIGHT (3 + 9 + 3) static char _playerName[32 + 1]; static ServerList _serverList; @@ -613,3 +611,5 @@ static void server_list_fetch_servers_check(rct_window* w) } } } + +#endif diff --git a/src/openrct2-ui/windows/ServerStart.cpp b/src/openrct2-ui/windows/ServerStart.cpp index 5fc1d49cd0..631ea62f60 100644 --- a/src/openrct2-ui/windows/ServerStart.cpp +++ b/src/openrct2-ui/windows/ServerStart.cpp @@ -7,18 +7,20 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#include "../interface/Theme.h" +#ifndef DISABLE_NETWORK -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +# include "../interface/Theme.h" + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include static char _port[7]; static char _name[65]; @@ -345,3 +347,5 @@ static void window_server_start_paint(rct_window* w, rct_drawpixelinfo* dpi) gfx_draw_string_left(dpi, STR_PASSWORD, nullptr, w->colours[1], w->x + 6, w->y + w->widgets[WIDX_PASSWORD_INPUT].top); gfx_draw_string_left(dpi, STR_MAX_PLAYERS, nullptr, w->colours[1], w->x + 6, w->y + w->widgets[WIDX_MAXPLAYERS].top); } + +#endif diff --git a/src/openrct2-ui/windows/Window.h b/src/openrct2-ui/windows/Window.h index 8c8d770e81..76c45cb3f4 100644 --- a/src/openrct2-ui/windows/Window.h +++ b/src/openrct2-ui/windows/Window.h @@ -43,8 +43,10 @@ rct_window* window_news_open(); rct_window* window_news_options_open(); rct_window* window_options_open(); rct_window* window_save_prompt_open(); +#ifndef DISABLE_NETWORK rct_window* window_server_list_open(); rct_window* window_server_start_open(); +#endif rct_window* window_shortcut_change_open(int32_t selected_key); rct_window* window_shortcut_keys_open(); rct_window* window_staff_list_open(); diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index 6e5504d8c8..bf9c921c51 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -32,10 +32,6 @@ # include # include -# ifndef DISABLE_HTTP - -using namespace OpenRCT2::Network; - enum MASTER_SERVER_STATUS { MASTER_SERVER_STATUS_OK = 200, @@ -52,7 +48,12 @@ class NetworkServerAdvertiser final : public INetworkServerAdvertiser private: uint16_t _port; + std::unique_ptr _lanListener; + uint32_t _lastListenTime{}; + ADVERTISE_STATUS _status = ADVERTISE_STATUS::UNREGISTERED; + +# ifndef DISABLE_HTTP uint32_t _lastAdvertiseTime = 0; uint32_t _lastHeartbeatTime = 0; @@ -64,16 +65,16 @@ private: // See https://github.com/OpenRCT2/OpenRCT2/issues/6277 and 4953 bool _forceIPv4 = false; - - std::unique_ptr _lanListener; - uint32_t _lastListenTime{}; +# endif public: explicit NetworkServerAdvertiser(uint16_t port) { _port = port; - _key = GenerateAdvertiseKey(); _lanListener = CreateUdpSocket(); +# ifndef DISABLE_HTTP + _key = GenerateAdvertiseKey(); +# endif } ADVERTISE_STATUS GetStatus() const override @@ -84,10 +85,12 @@ public: void Update() override { UpdateLAN(); +# ifndef DISABLE_HTTP if (gConfigNetwork.advertise) { UpdateWAN(); } +# endif } private: @@ -106,24 +109,34 @@ private: size_t recievedBytes{}; std::unique_ptr endpoint; auto p = _lanListener->ReceiveData(buffer, sizeof(buffer) - 1, &recievedBytes, &endpoint); - if (p == NETWORK_READPACKET_SUCCESS && String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG)) + if (p == NETWORK_READPACKET_SUCCESS) { std::string sender = endpoint->GetHostname(); log_verbose("Received %zu bytes from %s on LAN broadcast port", recievedBytes, sender.c_str()); - - auto body = GetBroadcastJson(); - auto bodyDump = json_dumps(body, JSON_COMPACT); - size_t sendLen = strlen(bodyDump) + 1; - log_verbose("Sending %zu bytes back to %s", sendLen, sender.c_str()); - _lanListener->SendData(*endpoint, bodyDump, sendLen); - free(bodyDump); - json_decref(body); + if (String::Equals(buffer, NETWORK_LAN_BROADCAST_MSG)) + { + auto body = GetBroadcastJson(); + auto bodyDump = json_dumps(body, JSON_COMPACT); + size_t sendLen = strlen(bodyDump) + 1; + log_verbose("Sending %zu bytes back to %s", sendLen, sender.c_str()); + _lanListener->SendData(*endpoint, bodyDump, sendLen); + free(bodyDump); + json_decref(body); + } } } _lastListenTime = ticks; } } + json_t* GetBroadcastJson() + { + auto root = network_get_server_info_as_json(); + json_object_set(root, "port", json_integer(_port)); + return root; + } + +# ifndef DISABLE_HTTP void UpdateWAN() { switch (_status) @@ -148,6 +161,8 @@ private: void SendRegistration(bool forceIPv4) { + using namespace OpenRCT2::Network; + _lastAdvertiseTime = platform_get_ticks(); // Send the registration request @@ -181,6 +196,8 @@ private: void SendHeartbeat() { + using namespace OpenRCT2::Network; + Http::Request request; request.url = GetMasterServerUrl(); request.method = Http::Method::PUT; @@ -284,13 +301,6 @@ private: return root; } - json_t* GetBroadcastJson() - { - auto root = network_get_server_info_as_json(); - json_object_set(root, "port", json_integer(_port)); - return root; - } - static std::string GenerateAdvertiseKey() { // Generate a string of 16 random hex characters (64-integer key as a hex formatted string) @@ -316,6 +326,7 @@ private: } return result; } +# endif }; std::unique_ptr CreateServerAdvertiser(uint16_t port) @@ -323,23 +334,4 @@ std::unique_ptr CreateServerAdvertiser(uint16_t port) return std::make_unique(port); } -# else // DISABLE_HTTP - -class DummyNetworkServerAdvertiser final : public INetworkServerAdvertiser -{ -public: - virtual ADVERTISE_STATUS GetStatus() const override - { - return ADVERTISE_STATUS::DISABLED; - }; - virtual void Update() override{}; -}; - -std::unique_ptr CreateServerAdvertiser(uint16_t port) -{ - return std::make_unique(); -} - -# endif // DISABLE_HTTP - #endif // DISABLE_NETWORK diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index cf2dd67161..46f181ae3c 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -7,26 +7,27 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#include "ServerList.h" +#ifndef DISABLE_NETWORK -#include "../Context.h" -#include "../PlatformEnvironment.h" -#include "../config/Config.h" -#include "../core/FileStream.hpp" -#include "../core/Json.hpp" -#include "../core/Memory.hpp" -#include "../core/Path.hpp" -#include "../core/String.hpp" -#include "../network/Http.h" -#include "../platform/platform.h" -#include "Socket.h" -#include "network.h" +# include "ServerList.h" -#include -#include +# include "../Context.h" +# include "../PlatformEnvironment.h" +# include "../config/Config.h" +# include "../core/FileStream.hpp" +# include "../core/Json.hpp" +# include "../core/Memory.hpp" +# include "../core/Path.hpp" +# include "../core/String.hpp" +# include "../network/Http.h" +# include "../platform/platform.h" +# include "Socket.h" +# include "network.h" + +# include +# include using namespace OpenRCT2; -using namespace OpenRCT2::Network; int32_t ServerListEntry::CompareTo(const ServerListEntry& other) const { @@ -243,7 +244,7 @@ std::future> ServerList::FetchLocalServerListAsync( std::string_view msg = NETWORK_LAN_BROADCAST_MSG; auto udpSocket = CreateUdpSocket(); - log_verbose("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress); + log_verbose("Broadcasting %zu bytes to the LAN (%s)", msg.size(), broadcastAddress.c_str()); auto len = udpSocket->SendData(broadcastAddress, NETWORK_LAN_BROADCAST_PORT, msg.data(), msg.size()); if (len != msg.size()) { @@ -318,9 +319,11 @@ std::future> ServerList::FetchLocalServerListAsync( std::future> ServerList::FetchOnlineServerListAsync() { -#ifdef DISABLE_HTTP +# ifdef DISABLE_HTTP return std::async(std::launch::deferred, [] { return std::vector(); }); -#else +# else + using namespace OpenRCT2::Network; + auto p = std::make_shared>>(); auto f = p->get_future(); @@ -386,7 +389,7 @@ std::future> ServerList::FetchOnlineServerListAsync json_decref(root); }); return f; -#endif +# endif } uint32_t ServerList::GetTotalPlayerCount() const @@ -395,3 +398,5 @@ uint32_t ServerList::GetTotalPlayerCount() const return acc + entry.players; }); } + +#endif From 47f48721e8768a9ce969deeb58064123f6ca40b4 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 6 May 2019 11:36:09 +0000 Subject: [PATCH 15/26] Fix clang build --- src/openrct2-ui/windows/ServerList.cpp | 2 +- src/openrct2/network/ServerList.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index fa285efcb9..dfef1b6bb9 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -588,7 +588,7 @@ static void server_list_fetch_servers_check(rct_window* w) { try { - auto [entries, statusText] = std::move(_fetchFuture.get()); + auto [entries, statusText] = _fetchFuture.get(); _serverList.AddRange(entries); _numPlayersOnline = _serverList.GetTotalPlayerCount(); _statusText = STR_X_PLAYERS_ONLINE; diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 46f181ae3c..262e7162c9 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -305,7 +305,7 @@ std::future> ServerList::FetchLocalServerListAsync( { try { - auto entries = std::move(f.get()); + auto entries = f.get(); mergedEntries.insert(mergedEntries.begin(), entries.begin(), entries.end()); } catch (...) From 52afcb795fbe94215c6bafb794f9f9aab14cd4b5 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 6 May 2019 12:51:01 +0100 Subject: [PATCH 16/26] Fix windows build --- src/openrct2-ui/windows/ServerList.cpp | 4 ++-- src/openrct2/network/Socket.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index dfef1b6bb9..80ce64a33f 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -557,7 +557,7 @@ static void server_list_fetch_servers_begin() auto entries = lanF.get(); allEntries.insert(allEntries.end(), entries.begin(), entries.end()); } - catch (const std::exception& e) + catch (...) { } @@ -571,7 +571,7 @@ static void server_list_fetch_servers_begin() { status = e.StatusText; } - catch (const std::exception& e) + catch (...) { status = STR_SERVER_LIST_NO_CONNECTION; } diff --git a/src/openrct2/network/Socket.cpp b/src/openrct2/network/Socket.cpp index fe93b295ea..3fc5b80c10 100644 --- a/src/openrct2/network/Socket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -819,7 +819,7 @@ std::unique_ptr CreateUdpSocket() } # ifdef _WIN32 -std::vector GetNetworkInterfaces() +static std::vector GetNetworkInterfaces() { int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == -1) @@ -834,8 +834,8 @@ std::vector GetNetworkInterfaces() { interfaces.resize(capacity); if (WSAIoctl( - sock, SIO_GET_INTERFACE_LIST, nullptr, 0, interfaces.data(), capacity * sizeof(INTERFACE_INFO), &len, nullptr, - nullptr) + sock, SIO_GET_INTERFACE_LIST, nullptr, 0, interfaces.data(), (DWORD)(capacity * sizeof(INTERFACE_INFO)), &len, + nullptr, nullptr) == 0) { break; @@ -870,7 +870,7 @@ std::vector> GetBroadcastAddresses() memcpy(&address, &ifo.iiAddress.Address, sizeof(sockaddr)); ((sockaddr_in*)&address)->sin_addr.s_addr = ifo.iiAddress.AddressIn.sin_addr.s_addr | ~ifo.iiNetmask.AddressIn.sin_addr.s_addr; - baddresses.push_back(std::make_unique(address, sizeof(sockaddr))); + baddresses.push_back(std::make_unique((const sockaddr*)&address, (socklen_t)sizeof(sockaddr))); } # else int sock = socket(AF_INET, SOCK_DGRAM, 0); From 26e0264b5dd3aafda5f4ba4739aa44400e91338a Mon Sep 17 00:00:00 2001 From: Gymnasiast Date: Mon, 6 May 2019 19:08:14 +0200 Subject: [PATCH 17/26] Fix Xcode project --- OpenRCT2.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index a7b3e30d16..19504974b2 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -512,7 +512,7 @@ F76C86551EC4E88300FA49E2 /* NetworkServerAdvertiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C84061EC4E7CC00FA49E2 /* NetworkServerAdvertiser.cpp */; }; F76C86581EC4E88300FA49E2 /* NetworkUser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C84091EC4E7CC00FA49E2 /* NetworkUser.cpp */; }; F76C865A1EC4E88300FA49E2 /* ServerList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C840B1EC4E7CC00FA49E2 /* ServerList.cpp */; }; - F76C865C1EC4E88300FA49E2 /* TcpSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C840D1EC4E7CC00FA49E2 /* TcpSocket.cpp */; }; + F76C865C1EC4E88300FA49E2 /* Socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C840D1EC4E7CC00FA49E2 /* Socket.cpp */; }; F76C86601EC4E88300FA49E2 /* BannerObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C84121EC4E7CC00FA49E2 /* BannerObject.cpp */; }; F76C86621EC4E88300FA49E2 /* EntranceObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C84141EC4E7CC00FA49E2 /* EntranceObject.cpp */; }; F76C86641EC4E88300FA49E2 /* FootpathItemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C84161EC4E7CC00FA49E2 /* FootpathItemObject.cpp */; }; @@ -1660,8 +1660,8 @@ F76C840A1EC4E7CC00FA49E2 /* NetworkUser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkUser.h; sourceTree = ""; }; F76C840B1EC4E7CC00FA49E2 /* ServerList.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ServerList.cpp; sourceTree = ""; }; F76C840C1EC4E7CC00FA49E2 /* ServerList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ServerList.h; sourceTree = ""; }; - F76C840D1EC4E7CC00FA49E2 /* TcpSocket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TcpSocket.cpp; sourceTree = ""; }; - F76C840E1EC4E7CC00FA49E2 /* TcpSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TcpSocket.h; sourceTree = ""; }; + F76C840D1EC4E7CC00FA49E2 /* Socket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Socket.cpp; sourceTree = ""; }; + F76C840E1EC4E7CC00FA49E2 /* Socket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Socket.h; sourceTree = ""; }; F76C840F1EC4E7CC00FA49E2 /* Twitch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Twitch.cpp; sourceTree = ""; }; F76C84101EC4E7CC00FA49E2 /* twitch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = twitch.h; sourceTree = ""; }; F76C84121EC4E7CC00FA49E2 /* BannerObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BannerObject.cpp; sourceTree = ""; }; @@ -2820,8 +2820,8 @@ F76C840A1EC4E7CC00FA49E2 /* NetworkUser.h */, F76C840B1EC4E7CC00FA49E2 /* ServerList.cpp */, F76C840C1EC4E7CC00FA49E2 /* ServerList.h */, - F76C840D1EC4E7CC00FA49E2 /* TcpSocket.cpp */, - F76C840E1EC4E7CC00FA49E2 /* TcpSocket.h */, + F76C840D1EC4E7CC00FA49E2 /* Socket.cpp */, + F76C840E1EC4E7CC00FA49E2 /* Socket.h */, F76C840F1EC4E7CC00FA49E2 /* Twitch.cpp */, F76C84101EC4E7CC00FA49E2 /* twitch.h */, ); @@ -4123,7 +4123,7 @@ 93F76EFF20BFF77B00D4512C /* Paint.Wall.cpp in Sources */, F76C86581EC4E88300FA49E2 /* NetworkUser.cpp in Sources */, F76C865A1EC4E88300FA49E2 /* ServerList.cpp in Sources */, - F76C865C1EC4E88300FA49E2 /* TcpSocket.cpp in Sources */, + F76C865C1EC4E88300FA49E2 /* Socket.cpp in Sources */, C688784B202899B90084B384 /* Intro.cpp in Sources */, C68878FD20289B9B0084B384 /* MiniRollerCoaster.cpp in Sources */, 2A1F4FE0221FF4B0003CA045 /* Twitch.cpp in Sources */, From 9b1321067b0b6230e1a2b81e549c40de50987455 Mon Sep 17 00:00:00 2001 From: Gymnasiast Date: Mon, 6 May 2019 19:21:09 +0200 Subject: [PATCH 18/26] Use opt:: namespace --- src/openrct2/network/ServerList.cpp | 2 +- src/openrct2/network/ServerList.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 262e7162c9..41bbe696b3 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -75,7 +75,7 @@ bool ServerListEntry::IsVersionValid() const return version.empty() || version == network_get_version(); } -std::optional ServerListEntry::FromJson(const json_t* server) +opt::optional ServerListEntry::FromJson(const json_t* server) { auto port = json_object_get(server, "port"); auto name = json_object_get(server, "name"); diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index c9b44a5746..84e4ca2a33 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -35,7 +35,7 @@ struct ServerListEntry int32_t CompareTo(const ServerListEntry& other) const; bool IsVersionValid() const; - static std::optional FromJson(const json_t* root); + static opt::optional FromJson(const json_t* root); }; class ServerList From 6e6fe3c3c1b111b6098798eb4d407db369ab20af Mon Sep 17 00:00:00 2001 From: Gymnasiast Date: Mon, 6 May 2019 19:55:42 +0200 Subject: [PATCH 19/26] Replace optional.value() with *optional (fix Xcode compilation) Xcode cannot handle the optional.value() notation, but *optional should mean the same. Also see https://en.cppreference.com/w/cpp/utility/optional/operator* --- src/openrct2/network/ServerList.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 41bbe696b3..324d431b3e 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -273,8 +273,8 @@ std::future> ServerList::FetchLocalServerListAsync( auto entry = ServerListEntry::FromJson(jinfo); if (entry.has_value()) { - entry.value().local = true; - entries.push_back(entry.value()); + (*entry).local = true; + entries.push_back(*entry); } json_decref(jinfo); @@ -375,7 +375,7 @@ std::future> ServerList::FetchOnlineServerListAsync auto entry = ServerListEntry::FromJson(jServer); if (entry.has_value()) { - entries.push_back(entry.value()); + entries.push_back(*entry); } } } From 3334d40da499208c274c63f77842239852d06924 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 7 May 2019 09:00:23 +0100 Subject: [PATCH 20/26] Explicitly use std::launch::async --- src/openrct2-ui/windows/ServerList.cpp | 2 +- src/openrct2/network/ServerList.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 80ce64a33f..2ee2e1cf1e 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -545,7 +545,7 @@ static void server_list_fetch_servers_begin() _serverList.ReadAndAddFavourites(); _statusText = STR_SERVER_LIST_CONNECTING; - _fetchFuture = std::async([] { + _fetchFuture = std::async(std::launch::async, [] { // Spin off background fetches auto lanF = _serverList.FetchLocalServerListAsync(); auto wanF = _serverList.FetchOnlineServerListAsync(); diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 324d431b3e..544d795892 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -237,7 +237,7 @@ bool ServerList::WriteFavourites(const std::vector& entries) std::future> ServerList::FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) { auto broadcastAddress = broadcastEndpoint.GetHostname(); - return std::async([broadcastAddress] { + return std::async(std::launch::async, [broadcastAddress] { constexpr auto RECV_DELAY_MS = 10; constexpr auto RECV_WAIT_MS = 2000; @@ -287,7 +287,7 @@ std::future> ServerList::FetchLocalServerListAsync( std::future> ServerList::FetchLocalServerListAsync() { - return std::async([&] { + return std::async(std::launch::async, [&] { // Get all possible LAN broadcast addresses auto broadcastEndpoints = GetBroadcastAddresses(); From f0d1e9c3208c0e37c3b9ed915412af3b54fd04f7 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 7 May 2019 17:41:34 +0100 Subject: [PATCH 21/26] Apply some of the code review comments --- src/openrct2/network/ServerList.cpp | 52 +++++++++++++++++------------ src/openrct2/network/Socket.cpp | 4 ++- src/openrct2/network/Socket.h | 8 ++--- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index 544d795892..f5106a52ac 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -252,32 +252,40 @@ std::future> ServerList::FetchLocalServerListAsync( } std::vector entries; - char buffer[1024]{}; - size_t recievedLen{}; - std::unique_ptr endpoint; for (int i = 0; i < (RECV_WAIT_MS / RECV_DELAY_MS); i++) { - auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint); - if (p == NETWORK_READPACKET_SUCCESS) + try { - auto sender = endpoint->GetHostname(); - log_verbose("Received %zu bytes back from %s", recievedLen, sender.c_str()); - auto jinfo = Json::FromString(std::string_view(buffer)); - - auto ip4 = json_array(); - json_array_append_new(ip4, json_string(sender.c_str())); - auto ip = json_object(); - json_object_set_new(ip, "v4", ip4); - json_object_set_new(jinfo, "ip", ip); - - auto entry = ServerListEntry::FromJson(jinfo); - if (entry.has_value()) + // Start with initialised buffer in case we receive a non-terminated string + char buffer[1024]{}; + size_t recievedLen{}; + std::unique_ptr endpoint; + auto p = udpSocket->ReceiveData(buffer, sizeof(buffer) - 1, &recievedLen, &endpoint); + if (p == NETWORK_READPACKET_SUCCESS) { - (*entry).local = true; - entries.push_back(*entry); - } + auto sender = endpoint->GetHostname(); + log_verbose("Received %zu bytes back from %s", recievedLen, sender.c_str()); + auto jinfo = Json::FromString(std::string_view(buffer)); - json_decref(jinfo); + auto ip4 = json_array(); + json_array_append_new(ip4, json_string(sender.c_str())); + auto ip = json_object(); + json_object_set_new(ip, "v4", ip4); + json_object_set_new(jinfo, "ip", ip); + + auto entry = ServerListEntry::FromJson(jinfo); + if (entry.has_value()) + { + (*entry).local = true; + entries.push_back(*entry); + } + + json_decref(jinfo); + } + } + catch (const std::exception& e) + { + log_warning("Error receiving data: %s", e.what()); } platform_sleep(RECV_DELAY_MS); } @@ -320,7 +328,7 @@ std::future> ServerList::FetchLocalServerListAsync( std::future> ServerList::FetchOnlineServerListAsync() { # ifdef DISABLE_HTTP - return std::async(std::launch::deferred, [] { return std::vector(); }); + return {}; # else using namespace OpenRCT2::Network; diff --git a/src/openrct2/network/Socket.cpp b/src/openrct2/network/Socket.cpp index 3fc5b80c10..6a26622b85 100644 --- a/src/openrct2/network/Socket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -827,8 +827,10 @@ static std::vector GetNetworkInterfaces() return {}; } + // Get all the network interfaces, requires a trial and error approch + // until we find the capacity required to store all of them. DWORD len = 0; - size_t capacity = 2; + size_t capacity = 16; std::vector interfaces; for (;;) { diff --git a/src/openrct2/network/Socket.h b/src/openrct2/network/Socket.h index 178388aa89..87d563cf23 100644 --- a/src/openrct2/network/Socket.h +++ b/src/openrct2/network/Socket.h @@ -50,9 +50,7 @@ interface INetworkEndpoint interface ITcpSocket { public: - virtual ~ITcpSocket() - { - } + virtual ~ITcpSocket() = default; virtual SOCKET_STATUS GetStatus() abstract; virtual const char* GetError() abstract; @@ -78,9 +76,7 @@ public: interface IUdpSocket { public: - virtual ~IUdpSocket() - { - } + virtual ~IUdpSocket() = default; virtual SOCKET_STATUS GetStatus() abstract; virtual const char* GetError() abstract; From f8e3abcf4eaa116ece7649f69fc656975c7cdd1f Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 7 May 2019 17:47:29 +0100 Subject: [PATCH 22/26] Remove by-reference string_view --- src/openrct2/core/Json.cpp | 2 +- src/openrct2/core/Json.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openrct2/core/Json.cpp b/src/openrct2/core/Json.cpp index 87fd5a6799..07c868630b 100644 --- a/src/openrct2/core/Json.cpp +++ b/src/openrct2/core/Json.cpp @@ -53,7 +53,7 @@ namespace Json fs.Write(jsonOutput, jsonOutputSize); } - json_t* FromString(const std::string_view& raw) + json_t* FromString(std::string_view raw) { json_t* root; json_error_t error; diff --git a/src/openrct2/core/Json.hpp b/src/openrct2/core/Json.hpp index 693097ec46..53687b9890 100644 --- a/src/openrct2/core/Json.hpp +++ b/src/openrct2/core/Json.hpp @@ -24,7 +24,7 @@ namespace Json json_t* ReadFromFile(const utf8* path, size_t maxSize = MAX_JSON_SIZE); void WriteToFile(const utf8* path, const json_t* json, size_t flags = 0); - json_t* FromString(const std::string_view& raw); + json_t* FromString(std::string_view raw); } // namespace Json class JsonException final : public std::runtime_error From 73b8310e8e955d022b56aa7627de188e9fd2ee37 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 7 May 2019 17:55:48 +0100 Subject: [PATCH 23/26] Refactor creation of UDP socket --- src/openrct2/network/Socket.cpp | 79 +++++++++++++++------------------ 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/src/openrct2/network/Socket.cpp b/src/openrct2/network/Socket.cpp index 6a26622b85..63e30d47fe 100644 --- a/src/openrct2/network/Socket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -613,29 +613,7 @@ public: } // Create the listening socket - _socket = socket(ss.ss_family, SOCK_DGRAM, IPPROTO_UDP); - if (_socket == INVALID_SOCKET) - { - throw SocketException("Unable to create socket."); - } - - // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections - if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) - { - log_error("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); - } - - if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) - { - log_error("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); - } - - // Enable send and receiving of broadcast messages - if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) - { - log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); - } - + _socket = CreateSocket(); try { // Bind to address:port and listen @@ -643,11 +621,6 @@ public: { throw SocketException("Unable to bind to socket."); } - - if (!SetNonBlocking(_socket, true)) - { - throw SocketException("Failed to set non-blocking mode."); - } } catch (const std::exception&) { @@ -675,22 +648,7 @@ public: { if (_socket == INVALID_SOCKET) { - _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (_socket == INVALID_SOCKET) - { - throw SocketException("Unable to create socket."); - } - - // Enable send and receiving of broadcast messages - if (!SetOption(_socket, SOL_SOCKET, SO_BROADCAST, true)) - { - log_error("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); - } - - if (!SetNonBlocking(_socket, true)) - { - throw SocketException("Failed to set non-blocking mode."); - } + _socket = CreateSocket(); } const auto& dest = dynamic_cast(&destination); @@ -766,6 +724,39 @@ private: _status = SOCKET_STATUS_CONNECTED; } + SOCKET CreateSocket() + { + auto sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) + { + throw SocketException("Unable to create socket."); + } + + // Enable send and receiving of broadcast messages + if (!SetOption(sock, SOL_SOCKET, SO_BROADCAST, true)) + { + log_warning("SO_BROADCAST failed. %d", LAST_SOCKET_ERROR()); + } + + // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections + if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) + { + log_warning("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); + } + + if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) + { + log_warning("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); + } + + if (!SetNonBlocking(sock, true)) + { + throw SocketException("Failed to set non-blocking mode."); + } + + return sock; + } + void CloseSocket() { if (_socket != INVALID_SOCKET) From 5e94f6385d061a7d9d950f5cc54e6f3ad519a258 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 11 May 2019 13:59:34 +0100 Subject: [PATCH 24/26] Initialise hostname --- src/openrct2/network/Socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/network/Socket.cpp b/src/openrct2/network/Socket.cpp index 63e30d47fe..e0ef79537b 100644 --- a/src/openrct2/network/Socket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -118,7 +118,7 @@ public: std::string GetHostname() const override { - char hostname[256]; + char hostname[256]{}; int res = getnameinfo(&_address, _addressLen, hostname, sizeof(hostname), nullptr, 0, NI_NUMERICHOST); if (res == 0) { From dd20ebad49f46d58904e5ea0ed92a9c4d2a13a7b Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 12 May 2019 00:57:56 +0100 Subject: [PATCH 25/26] Make more methods const --- src/openrct2/network/ServerList.cpp | 12 ++++++------ src/openrct2/network/ServerList.h | 12 ++++++------ src/openrct2/network/Socket.cpp | 12 ++++++------ src/openrct2/network/Socket.h | 8 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/openrct2/network/ServerList.cpp b/src/openrct2/network/ServerList.cpp index f5106a52ac..d2efbb0fac 100644 --- a/src/openrct2/network/ServerList.cpp +++ b/src/openrct2/network/ServerList.cpp @@ -152,7 +152,7 @@ void ServerList::Clear() _serverEntries.clear(); } -std::vector ServerList::ReadFavourites() +std::vector ServerList::ReadFavourites() const { log_verbose("server_list_read(...)"); std::vector entries; @@ -197,7 +197,7 @@ void ServerList::ReadAndAddFavourites() AddRange(entries); } -void ServerList::WriteFavourites() +void ServerList::WriteFavourites() const { // Save just favourite servers std::vector favouriteServers; @@ -207,7 +207,7 @@ void ServerList::WriteFavourites() WriteFavourites(favouriteServers); } -bool ServerList::WriteFavourites(const std::vector& entries) +bool ServerList::WriteFavourites(const std::vector& entries) const { log_verbose("server_list_write(%d, 0x%p)", entries.size(), entries.data()); @@ -234,7 +234,7 @@ bool ServerList::WriteFavourites(const std::vector& entries) } } -std::future> ServerList::FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) +std::future> ServerList::FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) const { auto broadcastAddress = broadcastEndpoint.GetHostname(); return std::async(std::launch::async, [broadcastAddress] { @@ -293,7 +293,7 @@ std::future> ServerList::FetchLocalServerListAsync( }); } -std::future> ServerList::FetchLocalServerListAsync() +std::future> ServerList::FetchLocalServerListAsync() const { return std::async(std::launch::async, [&] { // Get all possible LAN broadcast addresses @@ -325,7 +325,7 @@ std::future> ServerList::FetchLocalServerListAsync( }); } -std::future> ServerList::FetchOnlineServerListAsync() +std::future> ServerList::FetchOnlineServerListAsync() const { # ifdef DISABLE_HTTP return {}; diff --git a/src/openrct2/network/ServerList.h b/src/openrct2/network/ServerList.h index 84e4ca2a33..b0cb5e1bc2 100644 --- a/src/openrct2/network/ServerList.h +++ b/src/openrct2/network/ServerList.h @@ -44,9 +44,9 @@ private: std::vector _serverEntries; void Sort(); - std::vector ReadFavourites(); - bool WriteFavourites(const std::vector& entries); - std::future> FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint); + std::vector ReadFavourites() const; + bool WriteFavourites(const std::vector& entries) const; + std::future> FetchLocalServerListAsync(const INetworkEndpoint& broadcastEndpoint) const; public: ServerListEntry& GetServer(size_t index); @@ -56,10 +56,10 @@ public: void Clear(); void ReadAndAddFavourites(); - void WriteFavourites(); + void WriteFavourites() const; - std::future> FetchLocalServerListAsync(); - std::future> FetchOnlineServerListAsync(); + std::future> FetchLocalServerListAsync() const; + std::future> FetchOnlineServerListAsync() const; uint32_t GetTotalPlayerCount() const; }; diff --git a/src/openrct2/network/Socket.cpp b/src/openrct2/network/Socket.cpp index e0ef79537b..12fed91b78 100644 --- a/src/openrct2/network/Socket.cpp +++ b/src/openrct2/network/Socket.cpp @@ -216,12 +216,12 @@ public: CloseSocket(); } - SOCKET_STATUS GetStatus() override + SOCKET_STATUS GetStatus() const override { return _status; } - const char* GetError() override + const char* GetError() const override { return _error.empty() ? nullptr : _error.c_str(); } @@ -583,12 +583,12 @@ public: CloseSocket(); } - SOCKET_STATUS GetStatus() override + SOCKET_STATUS GetStatus() const override { return _status; } - const char* GetError() override + const char* GetError() const override { return _error.empty() ? nullptr : _error.c_str(); } @@ -739,12 +739,12 @@ private: } // Turn off IPV6_V6ONLY so we can accept both v4 and v6 connections - if (!SetOption(_socket, IPPROTO_IPV6, IPV6_V6ONLY, false)) + if (!SetOption(sock, IPPROTO_IPV6, IPV6_V6ONLY, false)) { log_warning("IPV6_V6ONLY failed. %d", LAST_SOCKET_ERROR()); } - if (!SetOption(_socket, SOL_SOCKET, SO_REUSEADDR, true)) + if (!SetOption(sock, SOL_SOCKET, SO_REUSEADDR, true)) { log_warning("SO_REUSEADDR failed. %d", LAST_SOCKET_ERROR()); } diff --git a/src/openrct2/network/Socket.h b/src/openrct2/network/Socket.h index 87d563cf23..601168b31b 100644 --- a/src/openrct2/network/Socket.h +++ b/src/openrct2/network/Socket.h @@ -52,8 +52,8 @@ interface ITcpSocket public: virtual ~ITcpSocket() = default; - virtual SOCKET_STATUS GetStatus() abstract; - virtual const char* GetError() abstract; + virtual SOCKET_STATUS GetStatus() const abstract; + virtual const char* GetError() const abstract; virtual const char* GetHostName() const abstract; virtual void Listen(uint16_t port) abstract; @@ -78,8 +78,8 @@ interface IUdpSocket public: virtual ~IUdpSocket() = default; - virtual SOCKET_STATUS GetStatus() abstract; - virtual const char* GetError() abstract; + virtual SOCKET_STATUS GetStatus() const abstract; + virtual const char* GetError() const abstract; virtual const char* GetHostName() const abstract; virtual void Listen(uint16_t port) abstract; From 1ac8d9058f908a2558d11a6624adc79278a34ddf Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 12 May 2019 01:05:11 +0100 Subject: [PATCH 26/26] Colour local servers as green --- src/openrct2-ui/windows/ServerList.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 2ee2e1cf1e..7bc4c7a175 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -448,6 +448,10 @@ static void window_server_list_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi { colour = COLOUR_YELLOW; } + else if (serverDetails.local) + { + colour = COLOUR_MOSS_GREEN; + } // Draw server information if (highlighted && !serverDetails.description.empty())