Merge branch upstream/master while introducing compile errors and reverting parts of Android changes, video BPP options are gone
This commit is contained in:
@@ -14,14 +14,22 @@ add_files(
|
||||
network_content.h
|
||||
network_content_gui.cpp
|
||||
network_content_gui.h
|
||||
network_coordinator.cpp
|
||||
network_coordinator.h
|
||||
network_func.h
|
||||
network_gamelist.cpp
|
||||
network_gamelist.h
|
||||
network_gui.cpp
|
||||
network_gui.h
|
||||
network_internal.h
|
||||
network_query.cpp
|
||||
network_query.h
|
||||
network_server.cpp
|
||||
network_server.h
|
||||
network_stun.cpp
|
||||
network_stun.h
|
||||
network_turn.cpp
|
||||
network_turn.h
|
||||
network_type.h
|
||||
network_udp.cpp
|
||||
network_udp.h
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
add_files(
|
||||
address.cpp
|
||||
address.h
|
||||
config.cpp
|
||||
config.h
|
||||
core.cpp
|
||||
core.h
|
||||
@@ -22,11 +23,17 @@ add_files(
|
||||
tcp_content.cpp
|
||||
tcp_content.h
|
||||
tcp_content_type.h
|
||||
tcp_coordinator.cpp
|
||||
tcp_coordinator.h
|
||||
tcp_game.cpp
|
||||
tcp_game.h
|
||||
tcp_http.cpp
|
||||
tcp_http.h
|
||||
tcp_listen.h
|
||||
tcp_stun.cpp
|
||||
tcp_stun.h
|
||||
tcp_turn.cpp
|
||||
tcp_turn.h
|
||||
udp.cpp
|
||||
udp.h
|
||||
)
|
||||
|
||||
+112
-134
@@ -10,22 +10,23 @@
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "address.h"
|
||||
#include "../network_internal.h"
|
||||
#include "../../debug.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
static const int DEFAULT_CONNECT_TIMEOUT_SECONDS = 3; ///< Allow connect() three seconds to connect.
|
||||
|
||||
/**
|
||||
* Get the hostname; in case it wasn't given the
|
||||
* IPv4 dotted representation is given.
|
||||
* @return the hostname
|
||||
*/
|
||||
const char *NetworkAddress::GetHostname()
|
||||
const std::string &NetworkAddress::GetHostname()
|
||||
{
|
||||
if (StrEmpty(this->hostname) && this->address.ss_family != AF_UNSPEC) {
|
||||
if (this->hostname.empty() && this->address.ss_family != AF_UNSPEC) {
|
||||
assert(this->address_length != 0);
|
||||
getnameinfo((struct sockaddr *)&this->address, this->address_length, this->hostname, sizeof(this->hostname), nullptr, 0, NI_NUMERICHOST);
|
||||
char buffer[NETWORK_HOSTNAME_LENGTH];
|
||||
getnameinfo((struct sockaddr *)&this->address, this->address_length, buffer, sizeof(buffer), nullptr, 0, NI_NUMERICHOST);
|
||||
this->hostname = buffer;
|
||||
}
|
||||
return this->hostname;
|
||||
}
|
||||
@@ -71,26 +72,17 @@ void NetworkAddress::SetPort(uint16 port)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address as a string, e.g. 127.0.0.1:12345.
|
||||
* @param buffer the buffer to write to
|
||||
* @param last the last element in the buffer
|
||||
* @param with_family whether to add the family (e.g. IPvX).
|
||||
* Helper to get the formatting string of an address for a given family.
|
||||
* @param family The family to get the address format for.
|
||||
* @param with_family Whether to add the familty to the address (e.g. IPv4).
|
||||
* @return The format string for the address.
|
||||
*/
|
||||
void NetworkAddress::GetAddressAsString(char *buffer, const char *last, bool with_family)
|
||||
static const char *GetAddressFormatString(uint16 family, bool with_family)
|
||||
{
|
||||
if (this->GetAddress()->ss_family == AF_INET6) buffer = strecpy(buffer, "[", last);
|
||||
buffer = strecpy(buffer, this->GetHostname(), last);
|
||||
if (this->GetAddress()->ss_family == AF_INET6) buffer = strecpy(buffer, "]", last);
|
||||
buffer += seprintf(buffer, last, ":%d", this->GetPort());
|
||||
|
||||
if (with_family) {
|
||||
char family;
|
||||
switch (this->address.ss_family) {
|
||||
case AF_INET: family = '4'; break;
|
||||
case AF_INET6: family = '6'; break;
|
||||
default: family = '?'; break;
|
||||
}
|
||||
seprintf(buffer, last, " (IPv%c)", family);
|
||||
switch (family) {
|
||||
case AF_INET: return with_family ? "{}:{} (IPv4)" : "{}:{}";
|
||||
case AF_INET6: return with_family ? "[{}]:{} (IPv6)" : "[{}]:{}";
|
||||
default: return with_family ? "{}:{} (IPv?)" : "{}:{}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +93,7 @@ void NetworkAddress::GetAddressAsString(char *buffer, const char *last, bool wit
|
||||
*/
|
||||
std::string NetworkAddress::GetAddressAsString(bool with_family)
|
||||
{
|
||||
/* 7 extra are for with_family, which adds " (IPvX)". */
|
||||
char buf[NETWORK_HOSTNAME_PORT_LENGTH + 7];
|
||||
this->GetAddressAsString(buf, lastof(buf), with_family);
|
||||
return buf;
|
||||
return fmt::format(GetAddressFormatString(this->GetAddress()->ss_family, with_family), this->GetHostname(), this->GetPort());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +144,7 @@ bool NetworkAddress::IsFamily(int family)
|
||||
* @note netmask without /n assumes all bits need to match.
|
||||
* @return true if this IP is within the netmask.
|
||||
*/
|
||||
bool NetworkAddress::IsInNetmask(const char *netmask)
|
||||
bool NetworkAddress::IsInNetmask(const std::string &netmask)
|
||||
{
|
||||
/* Resolve it if we didn't do it already */
|
||||
if (!this->IsResolved()) this->GetAddress();
|
||||
@@ -165,16 +154,15 @@ bool NetworkAddress::IsInNetmask(const char *netmask)
|
||||
NetworkAddress mask_address;
|
||||
|
||||
/* Check for CIDR separator */
|
||||
const char *chr_cidr = strchr(netmask, '/');
|
||||
if (chr_cidr != nullptr) {
|
||||
int tmp_cidr = atoi(chr_cidr + 1);
|
||||
auto cidr_separator_location = netmask.find('/');
|
||||
if (cidr_separator_location != std::string::npos) {
|
||||
int tmp_cidr = atoi(netmask.substr(cidr_separator_location + 1).c_str());
|
||||
|
||||
/* Invalid CIDR, treat as single host */
|
||||
if (tmp_cidr > 0 || tmp_cidr < cidr) cidr = tmp_cidr;
|
||||
if (tmp_cidr > 0 && tmp_cidr < cidr) cidr = tmp_cidr;
|
||||
|
||||
/* Remove the / so that NetworkAddress works on the IP portion */
|
||||
std::string ip_str(netmask, chr_cidr - netmask);
|
||||
mask_address = NetworkAddress(ip_str.c_str(), 0, this->address.ss_family);
|
||||
mask_address = NetworkAddress(netmask.substr(0, cidr_separator_location), 0, this->address.ss_family);
|
||||
} else {
|
||||
mask_address = NetworkAddress(netmask, 0, this->address.ss_family);
|
||||
}
|
||||
@@ -234,32 +222,32 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList *
|
||||
/* Setting both hostname to nullptr and port to 0 is not allowed.
|
||||
* As port 0 means bind to any port, the other must mean that
|
||||
* we want to bind to 'all' IPs. */
|
||||
if (StrEmpty(this->hostname) && this->address_length == 0 && this->GetPort() == 0) {
|
||||
if (this->hostname.empty() && this->address_length == 0 && this->GetPort() == 0) {
|
||||
reset_hostname = true;
|
||||
int fam = this->address.ss_family;
|
||||
if (fam == AF_UNSPEC) fam = family;
|
||||
strecpy(this->hostname, fam == AF_INET ? "0.0.0.0" : "::", lastof(this->hostname));
|
||||
this->hostname = fam == AF_INET ? "0.0.0.0" : "::";
|
||||
}
|
||||
|
||||
static bool _resolve_timeout_error_message_shown = false;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int e = getaddrinfo(StrEmpty(this->hostname) ? nullptr : this->hostname, port_name, &hints, &ai);
|
||||
int e = getaddrinfo(this->hostname.empty() ? nullptr : this->hostname.c_str(), port_name, &hints, &ai);
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
std::chrono::seconds duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||
if (!_resolve_timeout_error_message_shown && duration >= std::chrono::seconds(5)) {
|
||||
DEBUG(net, 0, "getaddrinfo for hostname \"%s\", port %s, address family %s and socket type %s took %i seconds",
|
||||
this->hostname, port_name, AddressFamilyAsString(family), SocketTypeAsString(socktype), (int)duration.count());
|
||||
DEBUG(net, 0, " this is likely an issue in the DNS name resolver's configuration causing it to time out");
|
||||
Debug(net, 0, "getaddrinfo for hostname \"{}\", port {}, address family {} and socket type {} took {} seconds",
|
||||
this->hostname, port_name, AddressFamilyAsString(family), SocketTypeAsString(socktype), duration.count());
|
||||
Debug(net, 0, " this is likely an issue in the DNS name resolver's configuration causing it to time out");
|
||||
_resolve_timeout_error_message_shown = true;
|
||||
}
|
||||
|
||||
|
||||
if (reset_hostname) strecpy(this->hostname, "", lastof(this->hostname));
|
||||
if (reset_hostname) this->hostname.clear();
|
||||
|
||||
if (e != 0) {
|
||||
if (func != ResolveLoopProc) {
|
||||
DEBUG(net, 0, "getaddrinfo for hostname \"%s\", port %s, address family %s and socket type %s failed: %s",
|
||||
this->hostname, port_name, AddressFamilyAsString(family), SocketTypeAsString(socktype), FS2OTTD(gai_strerror(e)).c_str());
|
||||
Debug(net, 0, "getaddrinfo for hostname \"{}\", port {}, address family {} and socket type {} failed: {}",
|
||||
this->hostname, port_name, AddressFamilyAsString(family), SocketTypeAsString(socktype), FS2OTTD(gai_strerror(e)));
|
||||
}
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
@@ -304,82 +292,6 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList *
|
||||
return sock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to resolve a connected socket.
|
||||
* @param runp information about the socket to try not
|
||||
* @return the opened socket or INVALID_SOCKET
|
||||
*/
|
||||
static SOCKET ConnectLoopProc(addrinfo *runp)
|
||||
{
|
||||
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
|
||||
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
|
||||
std::string address = NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString();
|
||||
|
||||
SOCKET sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
DEBUG(net, 1, "[%s] could not create %s socket: %s", type, family, NetworkError::GetLast().AsString());
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (!SetNoDelay(sock)) DEBUG(net, 1, "[%s] setting TCP_NODELAY failed", type);
|
||||
|
||||
if (!SetNonBlocking(sock)) DEBUG(net, 0, "[%s] setting non-blocking mode failed", type);
|
||||
|
||||
int err = connect(sock, runp->ai_addr, (int)runp->ai_addrlen);
|
||||
if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
|
||||
DEBUG(net, 1, "[%s] could not connect to %s over %s: %s", type, address.c_str(), family, NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
fd_set write_fd;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&write_fd);
|
||||
FD_SET(sock, &write_fd);
|
||||
|
||||
/* Wait for connect() to either connect, timeout or fail. */
|
||||
tv.tv_usec = 0;
|
||||
tv.tv_sec = DEFAULT_CONNECT_TIMEOUT_SECONDS;
|
||||
int n = select(FD_SETSIZE, NULL, &write_fd, NULL, &tv);
|
||||
if (n < 0) {
|
||||
DEBUG(net, 1, "[%s] could not connect to %s: %s", type, address.c_str(), NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/* If no fd is selected, the timeout has been reached. */
|
||||
if (n == 0) {
|
||||
DEBUG(net, 1, "[%s] timed out while connecting to %s", type, address.c_str());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/* Retrieve last error, if any, on the socket. */
|
||||
NetworkError socket_error = GetSocketError(sock);
|
||||
if (socket_error.HasError()) {
|
||||
DEBUG(net, 1, "[%s] could not connect to %s: %s", type, address.c_str(), socket_error.AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/* Connection succeeded. */
|
||||
DEBUG(net, 1, "[%s] connected to %s", type, address.c_str());
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the given address.
|
||||
* @return the connected socket or INVALID_SOCKET.
|
||||
*/
|
||||
SOCKET NetworkAddress::Connect()
|
||||
{
|
||||
DEBUG(net, 1, "Connecting to %s", this->GetAddressAsString().c_str());
|
||||
|
||||
return this->Resolve(AF_UNSPEC, SOCK_STREAM, AI_ADDRCONFIG, nullptr, ConnectLoopProc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to resolve a listening.
|
||||
* @param runp information about the socket to try not
|
||||
@@ -387,49 +299,51 @@ SOCKET NetworkAddress::Connect()
|
||||
*/
|
||||
static SOCKET ListenLoopProc(addrinfo *runp)
|
||||
{
|
||||
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
|
||||
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
|
||||
std::string address = NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString();
|
||||
|
||||
SOCKET sock = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
DEBUG(net, 0, "[%s] could not create %s socket on port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
|
||||
const char *type = NetworkAddress::SocketTypeAsString(runp->ai_socktype);
|
||||
const char *family = NetworkAddress::AddressFamilyAsString(runp->ai_family);
|
||||
Debug(net, 0, "Could not create {} {} socket: {}", type, family, NetworkError::GetLast().AsString());
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (runp->ai_socktype == SOCK_STREAM && !SetNoDelay(sock)) {
|
||||
DEBUG(net, 3, "[%s] setting TCP_NODELAY failed for port %s", type, address.c_str());
|
||||
Debug(net, 1, "Setting no-delay mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
int on = 1;
|
||||
/* The (const char*) cast is needed for windows!! */
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) == -1) {
|
||||
DEBUG(net, 3, "[%s] could not set reusable %s sockets for port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
|
||||
if (!SetReusePort(sock)) {
|
||||
Debug(net, 0, "Setting reuse-address mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
#ifndef __OS2__
|
||||
int on = 1;
|
||||
if (runp->ai_family == AF_INET6 &&
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&on, sizeof(on)) == -1) {
|
||||
DEBUG(net, 3, "[%s] could not disable IPv4 over IPv6 on port %s: %s", type, address.c_str(), NetworkError::GetLast().AsString());
|
||||
Debug(net, 3, "Could not disable IPv4 over IPv6: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bind(sock, runp->ai_addr, (int)runp->ai_addrlen) != 0) {
|
||||
DEBUG(net, 1, "[%s] could not bind on %s port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
|
||||
Debug(net, 0, "Could not bind socket on {}: {}", address, NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (runp->ai_socktype != SOCK_DGRAM && listen(sock, 1) != 0) {
|
||||
DEBUG(net, 1, "[%s] could not listen at %s port %s: %s", type, family, address.c_str(), NetworkError::GetLast().AsString());
|
||||
Debug(net, 0, "Could not listen on socket: {}", NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/* Connection succeeded */
|
||||
if (!SetNonBlocking(sock)) DEBUG(net, 0, "[%s] setting non-blocking mode failed for %s port %s", type, family, address.c_str());
|
||||
|
||||
DEBUG(net, 1, "[%s] listening on %s port %s", type, family, address.c_str());
|
||||
if (!SetNonBlocking(sock)) {
|
||||
Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
Debug(net, 3, "Listening on {}", address);
|
||||
return sock;
|
||||
}
|
||||
|
||||
@@ -442,11 +356,11 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets)
|
||||
{
|
||||
assert(sockets != nullptr);
|
||||
|
||||
/* Setting both hostname to nullptr and port to 0 is not allowed.
|
||||
/* Setting both hostname to "" and port to 0 is not allowed.
|
||||
* As port 0 means bind to any port, the other must mean that
|
||||
* we want to bind to 'all' IPs. */
|
||||
if (this->address_length == 0 && this->address.ss_family == AF_UNSPEC &&
|
||||
StrEmpty(this->hostname) && this->GetPort() == 0) {
|
||||
this->hostname.empty() && this->GetPort() == 0) {
|
||||
this->Resolve(AF_INET, socktype, AI_ADDRCONFIG | AI_PASSIVE, sockets, ListenLoopProc);
|
||||
this->Resolve(AF_INET6, socktype, AI_ADDRCONFIG | AI_PASSIVE, sockets, ListenLoopProc);
|
||||
} else {
|
||||
@@ -484,3 +398,67 @@ void NetworkAddress::Listen(int socktype, SocketList *sockets)
|
||||
default: return "unsupported";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer address of a socket as NetworkAddress.
|
||||
* @param sock The socket to get the peer address of.
|
||||
* @return The NetworkAddress of the peer address.
|
||||
*/
|
||||
/* static */ NetworkAddress NetworkAddress::GetPeerAddress(SOCKET sock)
|
||||
{
|
||||
sockaddr_storage addr = {};
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getpeername(sock, (sockaddr *)&addr, &addr_len) != 0) {
|
||||
Debug(net, 0, "Failed to get address of the peer: {}", NetworkError::GetLast().AsString());
|
||||
return NetworkAddress();
|
||||
}
|
||||
return NetworkAddress(addr, addr_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local address of a socket as NetworkAddress.
|
||||
* @param sock The socket to get the local address of.
|
||||
* @return The NetworkAddress of the local address.
|
||||
*/
|
||||
/* static */ NetworkAddress NetworkAddress::GetSockAddress(SOCKET sock)
|
||||
{
|
||||
sockaddr_storage addr = {};
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(sock, (sockaddr *)&addr, &addr_len) != 0) {
|
||||
Debug(net, 0, "Failed to get address of the socket: {}", NetworkError::GetLast().AsString());
|
||||
return NetworkAddress();
|
||||
}
|
||||
return NetworkAddress(addr, addr_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer name of a socket in string format.
|
||||
* @param sock The socket to get the peer name of.
|
||||
* @return The string representation of the peer name.
|
||||
*/
|
||||
/* static */ const std::string NetworkAddress::GetPeerName(SOCKET sock)
|
||||
{
|
||||
return NetworkAddress::GetPeerAddress(sock).GetAddressAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing either "hostname", "hostname:port" or invite code
|
||||
* to a ServerAddress, where the string can be postfixed with "#company" to
|
||||
* indicate the requested company.
|
||||
*
|
||||
* @param connection_string The string to parse.
|
||||
* @param default_port The default port to set port to if not in connection_string.
|
||||
* @param company Pointer to the company variable to set iff indicated.
|
||||
* @return A valid ServerAddress of the parsed information.
|
||||
*/
|
||||
/* static */ ServerAddress ServerAddress::Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id)
|
||||
{
|
||||
if (StrStartsWith(connection_string, "+")) {
|
||||
std::string_view invite_code = ParseCompanyFromConnectionString(connection_string, company_id);
|
||||
return ServerAddress(SERVER_ADDRESS_INVITE_CODE, std::string(invite_code));
|
||||
}
|
||||
|
||||
uint16 port = default_port;
|
||||
std::string_view ip = ParseFullConnectionString(connection_string, port, company_id);
|
||||
return ServerAddress(SERVER_ADDRESS_DIRECT, std::string(ip) + ":" + std::to_string(port));
|
||||
}
|
||||
|
||||
+50
-17
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "os_abstraction.h"
|
||||
#include "config.h"
|
||||
#include "../../company_type.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../core/smallmap_type.hpp"
|
||||
|
||||
@@ -28,10 +29,10 @@ typedef SmallMap<NetworkAddress, SOCKET> SocketList; ///< Type for a mapping
|
||||
*/
|
||||
class NetworkAddress {
|
||||
private:
|
||||
char hostname[NETWORK_HOSTNAME_LENGTH]; ///< The hostname
|
||||
int address_length; ///< The length of the resolved address
|
||||
sockaddr_storage address; ///< The resolved address
|
||||
bool resolved; ///< Whether the address has been (tried to be) resolved
|
||||
std::string hostname; ///< The hostname
|
||||
int address_length; ///< The length of the resolved address
|
||||
sockaddr_storage address; ///< The resolved address
|
||||
bool resolved; ///< Whether the address has been (tried to be) resolved
|
||||
|
||||
/**
|
||||
* Helper function to resolve something to a socket.
|
||||
@@ -52,7 +53,6 @@ public:
|
||||
address(address),
|
||||
resolved(address_length != 0)
|
||||
{
|
||||
*this->hostname = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,6 @@ public:
|
||||
address_length(address_length),
|
||||
resolved(address_length != 0)
|
||||
{
|
||||
*this->hostname = '\0';
|
||||
memset(&this->address, 0, sizeof(this->address));
|
||||
memcpy(&this->address, address, address_length);
|
||||
}
|
||||
@@ -75,24 +74,22 @@ public:
|
||||
* @param port the port
|
||||
* @param family the address family
|
||||
*/
|
||||
NetworkAddress(const char *hostname = "", uint16 port = 0, int family = AF_UNSPEC) :
|
||||
NetworkAddress(std::string_view hostname = "", uint16 port = 0, int family = AF_UNSPEC) :
|
||||
address_length(0),
|
||||
resolved(false)
|
||||
{
|
||||
/* Also handle IPv6 bracket enclosed hostnames */
|
||||
if (StrEmpty(hostname)) hostname = "";
|
||||
if (*hostname == '[') hostname++;
|
||||
strecpy(this->hostname, StrEmpty(hostname) ? "" : hostname, lastof(this->hostname));
|
||||
char *tmp = strrchr(this->hostname, ']');
|
||||
if (tmp != nullptr) *tmp = '\0';
|
||||
if (!hostname.empty() && hostname.front() == '[' && hostname.back() == ']') {
|
||||
hostname.remove_prefix(1);
|
||||
hostname.remove_suffix(1);
|
||||
}
|
||||
this->hostname = hostname;
|
||||
|
||||
memset(&this->address, 0, sizeof(this->address));
|
||||
this->address.ss_family = family;
|
||||
this->SetPort(port);
|
||||
}
|
||||
|
||||
const char *GetHostname();
|
||||
void GetAddressAsString(char *buffer, const char *last, bool with_family = true);
|
||||
const std::string &GetHostname();
|
||||
std::string GetAddressAsString(bool with_family = true);
|
||||
const sockaddr_storage *GetAddress();
|
||||
|
||||
@@ -120,7 +117,7 @@ public:
|
||||
}
|
||||
|
||||
bool IsFamily(int family);
|
||||
bool IsInNetmask(const char *netmask);
|
||||
bool IsInNetmask(const std::string &netmask);
|
||||
|
||||
/**
|
||||
* Compare the address of this class with the address of another.
|
||||
@@ -174,11 +171,47 @@ public:
|
||||
return this->CompareTo(address) < 0;
|
||||
}
|
||||
|
||||
SOCKET Connect();
|
||||
void Listen(int socktype, SocketList *sockets);
|
||||
|
||||
static const char *SocketTypeAsString(int socktype);
|
||||
static const char *AddressFamilyAsString(int family);
|
||||
static NetworkAddress GetPeerAddress(SOCKET sock);
|
||||
static NetworkAddress GetSockAddress(SOCKET sock);
|
||||
static const std::string GetPeerName(SOCKET sock);
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of server addresses we know.
|
||||
*
|
||||
* Sorting will prefer entries at the top of this list above ones at the bottom.
|
||||
*/
|
||||
enum ServerAddressType {
|
||||
SERVER_ADDRESS_DIRECT, ///< Server-address is based on an hostname:port.
|
||||
SERVER_ADDRESS_INVITE_CODE, ///< Server-address is based on an invite code.
|
||||
};
|
||||
|
||||
/**
|
||||
* Address to a game server.
|
||||
*
|
||||
* This generalises addresses which are based on different identifiers.
|
||||
*/
|
||||
class ServerAddress {
|
||||
private:
|
||||
/**
|
||||
* Create a new ServerAddress object.
|
||||
*
|
||||
* Please use ServerAddress::Parse() instead of calling this directly.
|
||||
*
|
||||
* @param type The type of the ServerAdress.
|
||||
* @param connection_string The connection_string that belongs to this ServerAddress type.
|
||||
*/
|
||||
ServerAddress(ServerAddressType type, const std::string &connection_string) : type(type), connection_string(connection_string) {}
|
||||
|
||||
public:
|
||||
ServerAddressType type; ///< The type of this ServerAddress.
|
||||
std::string connection_string; ///< The connection string for this ServerAddress.
|
||||
|
||||
static ServerAddress Parse(const std::string &connection_string, uint16 default_port, CompanyID *company_id = nullptr);
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_ADDRESS_H */
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file config.cpp Configuration of the connection strings for network stuff using environment variables.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include "../../string_func.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/**
|
||||
* Get the environment variable using std::getenv and when it is an empty string (or nullptr), return a fallback value instead.
|
||||
* @param variable The environment variable to read from.
|
||||
* @param fallback The fallback in case the environment variable is not set.
|
||||
* @return The environment value, or when that does not exist the given fallback value.
|
||||
*/
|
||||
static const char *GetEnv(const char *variable, const char *fallback)
|
||||
{
|
||||
const char *value = std::getenv(variable);
|
||||
return StrEmpty(value) ? fallback : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection string for the game coordinator from the environment variable OTTD_COORDINATOR_CS,
|
||||
* or when it has not been set a hard coded default DNS hostname of the production server.
|
||||
* @return The game coordinator's connection string.
|
||||
*/
|
||||
const char *NetworkCoordinatorConnectionString()
|
||||
{
|
||||
return GetEnv("OTTD_COORDINATOR_CS", "coordinator.openttd.org");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection string for the STUN server from the environment variable OTTD_STUN_CS,
|
||||
* or when it has not been set a hard coded default DNS hostname of the production server.
|
||||
* @return The STUN server's connection string.
|
||||
*/
|
||||
const char *NetworkStunConnectionString()
|
||||
{
|
||||
return GetEnv("OTTD_STUN_CS", "stun.openttd.org");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection string for the content server from the environment variable OTTD_CONTENT_SERVER_CS,
|
||||
* or when it has not been set a hard coded default DNS hostname of the production server.
|
||||
* @return The content server's connection string.
|
||||
*/
|
||||
const char *NetworkContentServerConnectionString()
|
||||
{
|
||||
return GetEnv("OTTD_CONTENT_SERVER_CS", "content.openttd.org");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection string for the content mirror from the environment variable OTTD_CONTENT_MIRROR_CS,
|
||||
* or when it has not been set a hard coded default DNS hostname of the production server.
|
||||
* @return The content mirror's connection string.
|
||||
*/
|
||||
const char *NetworkContentMirrorConnectionString()
|
||||
{
|
||||
return GetEnv("OTTD_CONTENT_MIRROR_CS", "binaries.openttd.org");
|
||||
}
|
||||
+56
-46
@@ -12,25 +12,24 @@
|
||||
#ifndef NETWORK_CORE_CONFIG_H
|
||||
#define NETWORK_CORE_CONFIG_H
|
||||
|
||||
/** DNS hostname of the masterserver */
|
||||
static const char * const NETWORK_MASTER_SERVER_HOST = "master.openttd.org";
|
||||
/** DNS hostname of the content server */
|
||||
static const char * const NETWORK_CONTENT_SERVER_HOST = "content.openttd.org";
|
||||
/** DNS hostname of the HTTP-content mirror server */
|
||||
static const char * const NETWORK_CONTENT_MIRROR_HOST = "binaries.openttd.org";
|
||||
const char *NetworkCoordinatorConnectionString();
|
||||
const char *NetworkStunConnectionString();
|
||||
const char *NetworkContentServerConnectionString();
|
||||
const char *NetworkContentMirrorConnectionString();
|
||||
|
||||
/** URL of the HTTP mirror system */
|
||||
static const char * const NETWORK_CONTENT_MIRROR_URL = "/bananas";
|
||||
/** Message sent to the masterserver to 'identify' this client as OpenTTD */
|
||||
static const char * const NETWORK_MASTER_SERVER_WELCOME_MESSAGE = "OpenTTDRegister";
|
||||
|
||||
static const uint16 NETWORK_MASTER_SERVER_PORT = 3978; ///< The default port of the master server (UDP)
|
||||
static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP)
|
||||
static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP)
|
||||
static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP)
|
||||
static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network
|
||||
static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP)
|
||||
static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP)
|
||||
static const uint16 NETWORK_STUN_SERVER_PORT = 3975; ///< The default port of the STUN server (TCP)
|
||||
static const uint16 NETWORK_TURN_SERVER_PORT = 3974; ///< The default port of the TURN server (TCP)
|
||||
static const uint16 NETWORK_CONTENT_SERVER_PORT = 3978; ///< The default port of the content server (TCP)
|
||||
static const uint16 NETWORK_CONTENT_MIRROR_PORT = 80; ///< The default port of the content mirror (TCP)
|
||||
static const uint16 NETWORK_DEFAULT_PORT = 3979; ///< The default port of the game server (TCP & UDP)
|
||||
static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The default port for admin network
|
||||
static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP)
|
||||
|
||||
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
|
||||
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
|
||||
/*
|
||||
* Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future
|
||||
* to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8.
|
||||
@@ -45,43 +44,54 @@ static const uint16 UDP_MTU = 1460; ///< Number of
|
||||
* Send_uint16(GB(size, 16, 14) | 0b10 << 14)
|
||||
* Send_uint16(GB(size, 0, 16))
|
||||
*/
|
||||
static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet
|
||||
static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility
|
||||
static const uint16 TCP_MTU = 32767; ///< Number of bytes we can pack in a single TCP packet
|
||||
static const uint16 COMPAT_MTU = 1460; ///< Number of bytes we can pack in a single packet for backward compatibility
|
||||
|
||||
static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use?
|
||||
static const byte NETWORK_GAME_INFO_VERSION = 4; ///< What version of game-info do we use?
|
||||
static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this?
|
||||
static const byte NETWORK_MASTER_SERVER_VERSION = 2; ///< What version of master-server-protocol do we use?
|
||||
static const byte NETWORK_GAME_ADMIN_VERSION = 1; ///< What version of the admin network do we use?
|
||||
static const byte NETWORK_GAME_INFO_VERSION = 6; ///< What version of game-info do we use?
|
||||
static const byte NETWORK_COMPANY_INFO_VERSION = 6; ///< What version of company info is this?
|
||||
static const byte NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use?
|
||||
|
||||
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
|
||||
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
|
||||
static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0'
|
||||
static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536)
|
||||
static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0'
|
||||
static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0'
|
||||
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
|
||||
static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0'
|
||||
static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0'
|
||||
static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0'
|
||||
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU-3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes)
|
||||
static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0'
|
||||
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
|
||||
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
|
||||
static const uint NETWORK_HOSTNAME_LENGTH = 80; ///< The maximum length of the host name, in bytes including '\0'
|
||||
static const uint NETWORK_HOSTNAME_PORT_LENGTH = 80 + 6; ///< The maximum length of the host name + port, in bytes including '\0'. The extra six is ":" + port number (with a max of 65536)
|
||||
static const uint NETWORK_SERVER_ID_LENGTH = 33; ///< The maximum length of the network id of the servers, in bytes including '\0'
|
||||
static const uint NETWORK_REVISION_LENGTH = 33; ///< The maximum length of the revision, in bytes including '\0'
|
||||
static const uint NETWORK_PASSWORD_LENGTH = 33; ///< The maximum length of the password, in bytes including '\0' (must be >= NETWORK_SERVER_ID_LENGTH)
|
||||
static const uint NETWORK_CLIENTS_LENGTH = 200; ///< The maximum length for the list of clients that controls a company, in bytes including '\0'
|
||||
static const uint NETWORK_CLIENT_NAME_LENGTH = 25; ///< The maximum length of a client's name, in bytes including '\0'
|
||||
static const uint NETWORK_RCONCOMMAND_LENGTH = 500; ///< The maximum length of a rconsole command, in bytes including '\0'
|
||||
static const uint NETWORK_GAMESCRIPT_JSON_LENGTH = COMPAT_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than COMPAT_MTU including header (3 bytes)
|
||||
static const uint NETWORK_CHAT_LENGTH = 900; ///< The maximum length of a chat message, in bytes including '\0'
|
||||
static const uint NETWORK_CONTENT_FILENAME_LENGTH = 48; ///< The maximum length of a content's filename, in bytes including '\0'.
|
||||
static const uint NETWORK_CONTENT_NAME_LENGTH = 32; ///< The maximum length of a content's name, in bytes including '\0'.
|
||||
static const uint NETWORK_CONTENT_VERSION_LENGTH = 16; ///< The maximum length of a content's version, in bytes including '\0'.
|
||||
static const uint NETWORK_CONTENT_URL_LENGTH = 96; ///< The maximum length of a content's url, in bytes including '\0'.
|
||||
static const uint NETWORK_CONTENT_DESC_LENGTH = 512; ///< The maximum length of a content's description, in bytes including '\0'.
|
||||
static const uint NETWORK_CONTENT_TAG_LENGTH = 32; ///< The maximum length of a content's tag, in bytes including '\0'.
|
||||
static const uint NETWORK_ERROR_DETAIL_LENGTH = 100; ///< The maximum length of the error detail, in bytes including '\0'.
|
||||
static const uint NETWORK_INVITE_CODE_LENGTH = 64; ///< The maximum length of the invite code, in bytes including '\0'.
|
||||
static const uint NETWORK_INVITE_CODE_SECRET_LENGTH = 80; ///< The maximum length of the invite code secret, in bytes including '\0'.
|
||||
static const uint NETWORK_TOKEN_LENGTH = 64; ///< The maximum length of a token, in bytes including '\0'.
|
||||
|
||||
static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
|
||||
static const uint NETWORK_GRF_NAME_LENGTH = 80; ///< Maximum length of the name of a GRF
|
||||
|
||||
/**
|
||||
* Maximum number of GRFs that can be sent.
|
||||
* This limit is reached when PACKET_UDP_SERVER_RESPONSE reaches the maximum size of UDP_MTU bytes.
|
||||
*
|
||||
* This limit exists to avoid that the SERVER_INFO packet exceeding the
|
||||
* maximum MTU. At the time of writing this limit is 32767 (TCP_MTU).
|
||||
*
|
||||
* In the SERVER_INFO packet is the NetworkGameInfo struct, which is
|
||||
* 142 bytes + 100 per NewGRF (under the assumption strings are used to
|
||||
* their max). This brings us to roughly 326 possible NewGRFs. Round it
|
||||
* down so people don't freak out because they see a weird value, and you
|
||||
* get the limit: 255.
|
||||
*
|
||||
* PS: in case you ever want to raise this number, please be mindful that
|
||||
* "amount of NewGRFs" in NetworkGameInfo is currently an uint8.
|
||||
*/
|
||||
static const uint NETWORK_MAX_GRF_COUNT = 62;
|
||||
|
||||
/**
|
||||
* The number of landscapes in OpenTTD.
|
||||
* This number must be equal to NUM_LANDSCAPE, but as this number is used
|
||||
* within the network code and that the network code is shared with the
|
||||
* masterserver/updater, it has to be declared in here too. In network.cpp
|
||||
* there is a compile assertion to check that this NUM_LANDSCAPE is equal
|
||||
* to NETWORK_NUM_LANDSCAPES.
|
||||
*/
|
||||
static const uint NETWORK_NUM_LANDSCAPES = 4;
|
||||
static const uint NETWORK_MAX_GRF_COUNT = 255;
|
||||
|
||||
#endif /* NETWORK_CORE_CONFIG_H */
|
||||
|
||||
@@ -27,9 +27,9 @@ bool NetworkCoreInitialize()
|
||||
#ifdef _WIN32
|
||||
{
|
||||
WSADATA wsa;
|
||||
DEBUG(net, 3, "[core] loading windows socket library");
|
||||
Debug(net, 5, "Loading windows socket library");
|
||||
if (WSAStartup(MAKEWORD(2, 0), &wsa) != 0) {
|
||||
DEBUG(net, 0, "[core] WSAStartup failed, network unavailable");
|
||||
Debug(net, 0, "WSAStartup failed, network unavailable");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-21
@@ -20,16 +20,17 @@ void NetworkCoreShutdown();
|
||||
|
||||
/** Status of a network client; reasons why a client has quit */
|
||||
enum NetworkRecvStatus {
|
||||
NETWORK_RECV_STATUS_OKAY, ///< Everything is okay
|
||||
NETWORK_RECV_STATUS_DESYNC, ///< A desync did occur
|
||||
NETWORK_RECV_STATUS_NEWGRF_MISMATCH, ///< We did not have the required NewGRFs
|
||||
NETWORK_RECV_STATUS_SAVEGAME, ///< Something went wrong (down)loading the savegame
|
||||
NETWORK_RECV_STATUS_CONN_LOST, ///< The connection is 'just' lost
|
||||
NETWORK_RECV_STATUS_MALFORMED_PACKET, ///< We apparently send a malformed packet
|
||||
NETWORK_RECV_STATUS_SERVER_ERROR, ///< The server told us we made an error
|
||||
NETWORK_RECV_STATUS_SERVER_FULL, ///< The server is full
|
||||
NETWORK_RECV_STATUS_SERVER_BANNED, ///< The server has banned us
|
||||
NETWORK_RECV_STATUS_CLOSE_QUERY, ///< Done querying the server
|
||||
NETWORK_RECV_STATUS_OKAY, ///< Everything is okay.
|
||||
NETWORK_RECV_STATUS_DESYNC, ///< A desync did occur.
|
||||
NETWORK_RECV_STATUS_NEWGRF_MISMATCH, ///< We did not have the required NewGRFs.
|
||||
NETWORK_RECV_STATUS_SAVEGAME, ///< Something went wrong (down)loading the savegame.
|
||||
NETWORK_RECV_STATUS_CLIENT_QUIT, ///< The connection is lost gracefully. Other clients are already informed of this leaving client.
|
||||
NETWORK_RECV_STATUS_MALFORMED_PACKET, ///< We apparently send a malformed packet.
|
||||
NETWORK_RECV_STATUS_SERVER_ERROR, ///< The server told us we made an error.
|
||||
NETWORK_RECV_STATUS_SERVER_FULL, ///< The server is full.
|
||||
NETWORK_RECV_STATUS_SERVER_BANNED, ///< The server has banned us.
|
||||
NETWORK_RECV_STATUS_CLOSE_QUERY, ///< Done querying the server.
|
||||
NETWORK_RECV_STATUS_CONNECTION_LOST, ///< The connection is lost unexpectedly.
|
||||
};
|
||||
|
||||
/** Forward declaration due to circular dependencies */
|
||||
@@ -39,24 +40,24 @@ struct Packet;
|
||||
* SocketHandler for all network sockets in OpenTTD.
|
||||
*/
|
||||
class NetworkSocketHandler {
|
||||
private:
|
||||
bool has_quit; ///< Whether the current client has quit/send a bad packet
|
||||
|
||||
public:
|
||||
/** Create a new unbound socket */
|
||||
NetworkSocketHandler() { this->has_quit = false; }
|
||||
|
||||
/** Close the socket when destructing the socket handler */
|
||||
virtual ~NetworkSocketHandler() { this->Close(); }
|
||||
|
||||
/** Really close the socket */
|
||||
virtual void Close() {}
|
||||
virtual ~NetworkSocketHandler() {}
|
||||
|
||||
/**
|
||||
* Close the current connection; for TCP this will be mostly equivalent
|
||||
* to Close(), but for UDP it just means the packet has to be dropped.
|
||||
* @param error Whether we quit under an error condition or not.
|
||||
* @return new status of the connection.
|
||||
* Mark the connection as closed.
|
||||
*
|
||||
* This doesn't mean the actual connection is closed, but just that we
|
||||
* act like it is. This is useful for UDP, which doesn't normally close
|
||||
* a socket, but its handler might need to pretend it does.
|
||||
*/
|
||||
virtual NetworkRecvStatus CloseConnection(bool error = true) { this->has_quit = true; return NETWORK_RECV_STATUS_OKAY; }
|
||||
void MarkClosed() { this->has_quit = true; }
|
||||
|
||||
/**
|
||||
* Whether the current client connected to the socket has quit.
|
||||
@@ -70,8 +71,6 @@ public:
|
||||
* Reopen the socket so we can send/receive stuff again.
|
||||
*/
|
||||
void Reopen() { this->has_quit = false; }
|
||||
|
||||
void SendCompanyInformation(Packet *p, const struct Company *c, const struct NetworkCompanyStats *stats, uint max_len = NETWORK_COMPANY_NAME_LENGTH);
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_CORE_H */
|
||||
|
||||
+140
-83
@@ -16,6 +16,8 @@
|
||||
#include "../../date_func.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../map_func.h"
|
||||
#include "../../game/game.hpp"
|
||||
#include "../../game/game_info.hpp"
|
||||
#include "../../settings_type.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../rev.h"
|
||||
@@ -36,42 +38,33 @@ NetworkServerGameInfo _network_game_info; ///< Information about our game.
|
||||
|
||||
/**
|
||||
* Get the network version string used by this build.
|
||||
* The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes.
|
||||
* The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes including '\0' terminator.
|
||||
*/
|
||||
const char *GetNetworkRevisionString()
|
||||
std::string_view GetNetworkRevisionString()
|
||||
{
|
||||
/* This will be allocated on heap and never free'd, but only once so not a "real" leak. */
|
||||
static char *network_revision = nullptr;
|
||||
static std::string network_revision;
|
||||
|
||||
if (!network_revision) {
|
||||
/* Start by taking a chance on the full revision string. */
|
||||
network_revision = stredup(_openttd_revision);
|
||||
/* Ensure it's not longer than the packet buffer length. */
|
||||
if (strlen(network_revision) >= NETWORK_REVISION_LENGTH) network_revision[NETWORK_REVISION_LENGTH - 1] = '\0';
|
||||
|
||||
/* Tag names are not mangled further. */
|
||||
if (network_revision.empty()) {
|
||||
network_revision = _openttd_revision;
|
||||
if (_openttd_revision_tagged) {
|
||||
DEBUG(net, 1, "Network revision name is '%s'", network_revision);
|
||||
return network_revision;
|
||||
}
|
||||
/* Tagged; do not mangle further, though ensure it's not too long. */
|
||||
if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1);
|
||||
} else {
|
||||
/* Not tagged; add the githash suffix while ensuring the string does not become too long. */
|
||||
assert(_openttd_revision_modified < 3);
|
||||
std::string githash_suffix = fmt::format("-{}{}", "gum"[_openttd_revision_modified], _openttd_revision_hash);
|
||||
if (githash_suffix.size() > GITHASH_SUFFIX_LEN) githash_suffix.resize(GITHASH_SUFFIX_LEN);
|
||||
|
||||
/* Prepare a prefix of the git hash.
|
||||
* Size is length + 1 for terminator, +2 for -g prefix. */
|
||||
assert(_openttd_revision_modified < 3);
|
||||
char githash_suffix[GITHASH_SUFFIX_LEN + 1] = "-";
|
||||
githash_suffix[1] = "gum"[_openttd_revision_modified];
|
||||
for (uint i = 2; i < GITHASH_SUFFIX_LEN; i++) {
|
||||
githash_suffix[i] = _openttd_revision_hash[i-2];
|
||||
}
|
||||
/* Where did the hash start in the original string? Overwrite from that position, unless that would create a too long string. */
|
||||
size_t hash_end = network_revision.find_last_of('-');
|
||||
if (hash_end == std::string::npos) hash_end = network_revision.size();
|
||||
if (hash_end + githash_suffix.size() >= NETWORK_REVISION_LENGTH) hash_end = NETWORK_REVISION_LENGTH - githash_suffix.size() - 1;
|
||||
|
||||
/* Where did the hash start in the original string?
|
||||
* Overwrite from that position, unless that would go past end of packet buffer length. */
|
||||
ptrdiff_t hashofs = strrchr(_openttd_revision, '-') - _openttd_revision;
|
||||
if (hashofs + strlen(githash_suffix) + 1 > NETWORK_REVISION_LENGTH) hashofs = strlen(network_revision) - strlen(githash_suffix);
|
||||
/* Replace the git hash in revision string. */
|
||||
strecpy(network_revision + hashofs, githash_suffix, network_revision + NETWORK_REVISION_LENGTH);
|
||||
assert(strlen(network_revision) < NETWORK_REVISION_LENGTH); // strlen does not include terminator, constant does, hence strictly less than
|
||||
DEBUG(net, 1, "Network revision name is '%s'", network_revision);
|
||||
/* Replace the git hash in revision string. */
|
||||
network_revision.replace(hash_end, std::string::npos, githash_suffix);
|
||||
}
|
||||
assert(network_revision.size() < NETWORK_REVISION_LENGTH); // size does not include terminator, constant does, hence strictly less than
|
||||
Debug(net, 3, "Network revision name: {}", network_revision);
|
||||
}
|
||||
|
||||
return network_revision;
|
||||
@@ -79,12 +72,14 @@ const char *GetNetworkRevisionString()
|
||||
|
||||
/**
|
||||
* Extract the git hash from the revision string.
|
||||
* @param revstr The revision string (formatted as DATE-BRANCH-GITHASH).
|
||||
* @param revision_string The revision string (formatted as DATE-BRANCH-GITHASH).
|
||||
* @return The git has part of the revision.
|
||||
*/
|
||||
static const char *ExtractNetworkRevisionHash(const char *revstr)
|
||||
static std::string_view ExtractNetworkRevisionHash(std::string_view revision_string)
|
||||
{
|
||||
return strrchr(revstr, '-');
|
||||
size_t index = revision_string.find_last_of('-');
|
||||
if (index == std::string::npos) return {};
|
||||
return revision_string.substr(index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,18 +87,18 @@ static const char *ExtractNetworkRevisionHash(const char *revstr)
|
||||
* First tries to match the full string, if that fails, attempts to compare just git hashes.
|
||||
* @param other the version string to compare to
|
||||
*/
|
||||
bool IsNetworkCompatibleVersion(const char *other)
|
||||
bool IsNetworkCompatibleVersion(std::string_view other)
|
||||
{
|
||||
if (strncmp(GetNetworkRevisionString(), other, NETWORK_REVISION_LENGTH - 1) == 0) return true;
|
||||
if (GetNetworkRevisionString() == other) return true;
|
||||
|
||||
/* If this version is tagged, then the revision string must be a complete match,
|
||||
* since there is no git hash suffix in it.
|
||||
* This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1". */
|
||||
if (_openttd_revision_tagged) return false;
|
||||
|
||||
const char *hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString());
|
||||
const char *hash2 = ExtractNetworkRevisionHash(other);
|
||||
return hash1 != nullptr && hash2 != nullptr && strncmp(hash1, hash2, GITHASH_SUFFIX_LEN) == 0;
|
||||
std::string_view hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString());
|
||||
std::string_view hash2 = ExtractNetworkRevisionHash(other);
|
||||
return hash1 == hash2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,30 +117,40 @@ void CheckGameCompatibility(NetworkGameInfo &ngi)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill a NetworkGameInfo structure with the latest information of the server.
|
||||
* @param ngi the NetworkGameInfo struct to fill with data.
|
||||
* Fill a NetworkServerGameInfo structure with the static content, or things
|
||||
* that are so static they can be updated on request from a settings change.
|
||||
*/
|
||||
void FillNetworkGameInfo(NetworkGameInfo &ngi)
|
||||
void FillStaticNetworkServerGameInfo()
|
||||
{
|
||||
/* Update some game_info */
|
||||
ngi.clients_on = _network_game_info.clients_on;
|
||||
ngi.start_date = ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1);
|
||||
_network_game_info.use_password = !_settings_client.network.server_password.empty();
|
||||
_network_game_info.start_date = ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1);
|
||||
_network_game_info.clients_max = _settings_client.network.max_clients;
|
||||
_network_game_info.companies_max = _settings_client.network.max_companies;
|
||||
_network_game_info.map_width = MapSizeX();
|
||||
_network_game_info.map_height = MapSizeY();
|
||||
_network_game_info.landscape = _settings_game.game_creation.landscape;
|
||||
_network_game_info.dedicated = _network_dedicated;
|
||||
_network_game_info.grfconfig = _grfconfig;
|
||||
|
||||
ngi.use_password = !StrEmpty(_settings_client.network.server_password);
|
||||
ngi.clients_max = _settings_client.network.max_clients;
|
||||
ngi.companies_on = (byte)Company::GetNumItems();
|
||||
ngi.companies_max = _settings_client.network.max_companies;
|
||||
ngi.spectators_on = NetworkSpectatorCount();
|
||||
ngi.spectators_max = _settings_client.network.max_spectators;
|
||||
ngi.game_date = _date;
|
||||
ngi.map_width = MapSizeX();
|
||||
ngi.map_height = MapSizeY();
|
||||
ngi.map_set = _settings_game.game_creation.landscape;
|
||||
ngi.dedicated = _network_dedicated;
|
||||
ngi.grfconfig = _grfconfig;
|
||||
_network_game_info.server_name = _settings_client.network.server_name;
|
||||
_network_game_info.server_revision = GetNetworkRevisionString();
|
||||
}
|
||||
|
||||
strecpy(ngi.server_name, _settings_client.network.server_name, lastof(ngi.server_name));
|
||||
strecpy(ngi.server_revision, GetNetworkRevisionString(), lastof(ngi.server_revision));
|
||||
/**
|
||||
* Get the NetworkServerGameInfo structure with the latest information of the server.
|
||||
* @return The current NetworkServerGameInfo.
|
||||
*/
|
||||
const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo()
|
||||
{
|
||||
/* These variables are updated inside _network_game_info as if they are global variables:
|
||||
* - clients_on
|
||||
* - invite_code
|
||||
* These don't need to be updated manually here.
|
||||
*/
|
||||
_network_game_info.companies_on = (byte)Company::GetNumItems();
|
||||
_network_game_info.spectators_on = NetworkSpectatorCount();
|
||||
_network_game_info.game_date = _date;
|
||||
return &_network_game_info;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,17 +158,15 @@ void FillNetworkGameInfo(NetworkGameInfo &ngi)
|
||||
* a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This
|
||||
* function must set all appropriate fields. This GRF is later appended to
|
||||
* the grfconfig list of the NetworkGameInfo.
|
||||
* @param config the GRF to handle.
|
||||
* @param config The GRF to handle.
|
||||
* @param name The name of the NewGRF, empty when unknown.
|
||||
*/
|
||||
static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config)
|
||||
static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string name)
|
||||
{
|
||||
/* Find the matching GRF file */
|
||||
const GRFConfig *f = FindGRFConfig(config->ident.grfid, FGCM_EXACT, config->ident.md5sum);
|
||||
if (f == nullptr) {
|
||||
/* Don't know the GRF, so mark game incompatible and the (possibly)
|
||||
* already resolved name for this GRF (another server has sent the
|
||||
* name of the GRF already */
|
||||
config->name = FindUnknownGRFName(config->ident.grfid, config->ident.md5sum, true);
|
||||
AddGRFTextToList(config->name, name.empty() ? GetString(STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN) : name);
|
||||
config->status = GCS_NOT_FOUND;
|
||||
} else {
|
||||
config->filename = f->filename;
|
||||
@@ -179,7 +182,7 @@ static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config)
|
||||
* @param p the packet to write the data to.
|
||||
* @param info the NetworkGameInfo struct to serialize from.
|
||||
*/
|
||||
void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info)
|
||||
void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names)
|
||||
{
|
||||
p->Send_uint8 (NETWORK_GAME_INFO_VERSION);
|
||||
|
||||
@@ -191,6 +194,14 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info)
|
||||
/* Update the documentation in game_info.h on changes
|
||||
* to the NetworkGameInfo wire-protocol! */
|
||||
|
||||
/* NETWORK_GAME_INFO_VERSION = 6 */
|
||||
p->Send_uint8(send_newgrf_names ? NST_GRFID_MD5_NAME : NST_GRFID_MD5);
|
||||
|
||||
/* NETWORK_GAME_INFO_VERSION = 5 */
|
||||
GameInfo *game_info = Game::GetInfo();
|
||||
p->Send_uint32(game_info == nullptr ? -1 : (uint32)game_info->GetVersion());
|
||||
p->Send_string(game_info == nullptr ? "" : game_info->GetName());
|
||||
|
||||
/* NETWORK_GAME_INFO_VERSION = 4 */
|
||||
{
|
||||
/* Only send the GRF Identification (GRF_ID and MD5 checksum) of
|
||||
@@ -208,7 +219,10 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info)
|
||||
|
||||
/* Send actual GRF Identifications */
|
||||
for (c = info->grfconfig; c != nullptr; c = c->next) {
|
||||
if (!HasBit(c->flags, GCF_STATIC)) SerializeGRFIdentifier(p, &c->ident);
|
||||
if (HasBit(c->flags, GCF_STATIC)) continue;
|
||||
|
||||
SerializeGRFIdentifier(p, &c->ident);
|
||||
if (send_newgrf_names) p->Send_string(c->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,20 +233,18 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info)
|
||||
/* NETWORK_GAME_INFO_VERSION = 2 */
|
||||
p->Send_uint8 (info->companies_max);
|
||||
p->Send_uint8 (info->companies_on);
|
||||
p->Send_uint8 (info->spectators_max);
|
||||
p->Send_uint8 (info->clients_max); // Used to be max-spectators
|
||||
|
||||
/* NETWORK_GAME_INFO_VERSION = 1 */
|
||||
p->Send_string(info->server_name);
|
||||
p->Send_string(info->server_revision);
|
||||
p->Send_uint8 (0); // Used to be server-lang.
|
||||
p->Send_bool (info->use_password);
|
||||
p->Send_uint8 (info->clients_max);
|
||||
p->Send_uint8 (info->clients_on);
|
||||
p->Send_uint8 (info->spectators_on);
|
||||
p->Send_string(""); // Used to be map-name.
|
||||
p->Send_uint16(info->map_width);
|
||||
p->Send_uint16(info->map_height);
|
||||
p->Send_uint8 (info->map_set);
|
||||
p->Send_uint8 (info->landscape);
|
||||
p->Send_bool (info->dedicated);
|
||||
}
|
||||
|
||||
@@ -241,11 +253,12 @@ void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info)
|
||||
* @param p the packet to read the data from.
|
||||
* @param info the NetworkGameInfo to deserialize into.
|
||||
*/
|
||||
void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info)
|
||||
void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table)
|
||||
{
|
||||
static const Date MAX_DATE = ConvertYMDToDate(MAX_YEAR, 11, 31); // December is month 11
|
||||
|
||||
info->game_info_version = p->Recv_uint8();
|
||||
byte game_info_version = p->Recv_uint8();
|
||||
NewGRFSerializationType newgrf_serialisation = NST_GRFID_MD5;
|
||||
|
||||
/*
|
||||
* Please observe the order.
|
||||
@@ -255,7 +268,18 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info)
|
||||
/* Update the documentation in game_info.h on changes
|
||||
* to the NetworkGameInfo wire-protocol! */
|
||||
|
||||
switch (info->game_info_version) {
|
||||
switch (game_info_version) {
|
||||
case 6:
|
||||
newgrf_serialisation = (NewGRFSerializationType)p->Recv_uint8();
|
||||
if (newgrf_serialisation >= NST_END) return;
|
||||
FALLTHROUGH;
|
||||
|
||||
case 5: {
|
||||
info->gamescript_version = (int)p->Recv_uint32();
|
||||
info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH);
|
||||
FALLTHROUGH;
|
||||
}
|
||||
|
||||
case 4: {
|
||||
GRFConfig **dst = &info->grfconfig;
|
||||
uint i;
|
||||
@@ -265,9 +289,31 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info)
|
||||
if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
|
||||
|
||||
for (i = 0; i < num_grfs; i++) {
|
||||
NamedGRFIdentifier grf;
|
||||
switch (newgrf_serialisation) {
|
||||
case NST_GRFID_MD5:
|
||||
DeserializeGRFIdentifier(p, &grf.ident);
|
||||
break;
|
||||
|
||||
case NST_GRFID_MD5_NAME:
|
||||
DeserializeGRFIdentifierWithName(p, &grf);
|
||||
break;
|
||||
|
||||
case NST_LOOKUP_ID: {
|
||||
if (newgrf_lookup_table == nullptr) return;
|
||||
auto it = newgrf_lookup_table->find(p->Recv_uint32());
|
||||
if (it == newgrf_lookup_table->end()) return;
|
||||
grf = it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
GRFConfig *c = new GRFConfig();
|
||||
DeserializeGRFIdentifier(p, &c->ident);
|
||||
HandleIncomingNetworkGameInfoGRFConfig(c);
|
||||
c->ident = grf.ident;
|
||||
HandleIncomingNetworkGameInfoGRFConfig(c, grf.name);
|
||||
|
||||
/* Append GRFConfig to the list */
|
||||
*dst = c;
|
||||
@@ -284,28 +330,28 @@ void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info)
|
||||
case 2:
|
||||
info->companies_max = p->Recv_uint8 ();
|
||||
info->companies_on = p->Recv_uint8 ();
|
||||
info->spectators_max = p->Recv_uint8 ();
|
||||
p->Recv_uint8(); // Used to contain max-spectators.
|
||||
FALLTHROUGH;
|
||||
|
||||
case 1:
|
||||
p->Recv_string(info->server_name, sizeof(info->server_name));
|
||||
p->Recv_string(info->server_revision, sizeof(info->server_revision));
|
||||
p->Recv_uint8 (); // Used to contain server-lang.
|
||||
info->server_name = p->Recv_string(NETWORK_NAME_LENGTH);
|
||||
info->server_revision = p->Recv_string(NETWORK_REVISION_LENGTH);
|
||||
if (game_info_version < 6) p->Recv_uint8 (); // Used to contain server-lang.
|
||||
info->use_password = p->Recv_bool ();
|
||||
info->clients_max = p->Recv_uint8 ();
|
||||
info->clients_on = p->Recv_uint8 ();
|
||||
info->spectators_on = p->Recv_uint8 ();
|
||||
if (info->game_info_version < 3) { // 16 bits dates got scrapped and are read earlier
|
||||
if (game_info_version < 3) { // 16 bits dates got scrapped and are read earlier
|
||||
info->game_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR;
|
||||
info->start_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR;
|
||||
}
|
||||
while (p->Recv_uint8() != 0) {} // Used to contain the map-name.
|
||||
if (game_info_version < 6) while (p->Recv_uint8() != 0) {} // Used to contain the map-name.
|
||||
info->map_width = p->Recv_uint16();
|
||||
info->map_height = p->Recv_uint16();
|
||||
info->map_set = p->Recv_uint8 ();
|
||||
info->landscape = p->Recv_uint8 ();
|
||||
info->dedicated = p->Recv_bool ();
|
||||
|
||||
if (info->map_set >= NETWORK_NUM_LANDSCAPES) info->map_set = 0;
|
||||
if (info->landscape >= NUM_LANDSCAPE) info->landscape = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,3 +382,14 @@ void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf)
|
||||
grf->md5sum[j] = p->Recv_uint8();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the NamedGRFIdentifier (GRF ID, MD5 checksum and name) from the packet
|
||||
* @param p the packet to read the data from.
|
||||
* @param grf the NamedGRFIdentifier to deserialize.
|
||||
*/
|
||||
void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf)
|
||||
{
|
||||
DeserializeGRFIdentifier(p, &grf->ident);
|
||||
grf->name = p->Recv_string(NETWORK_GRF_NAME_LENGTH);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "../../newgrf_config.h"
|
||||
#include "../../date_type.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
/*
|
||||
* NetworkGameInfo has several revisions which we still need to support on the
|
||||
* wire. The table below shows the version and size for each field of the
|
||||
@@ -25,11 +27,32 @@
|
||||
* Version: Bytes: Description:
|
||||
* all 1 the version of this packet's structure
|
||||
*
|
||||
* 4+ 1 number of GRFs attached (n)
|
||||
* 4+ n * 20 unique identifier for GRF files. Consists of:
|
||||
* - one 4 byte variable with the GRF ID
|
||||
* - 16 bytes (sent sequentially) for the MD5 checksum
|
||||
* of the GRF
|
||||
* 6+ 1 type of storage for the NewGRFs below:
|
||||
* 0 = NewGRF ID and MD5 checksum.
|
||||
* Used as default for version 5 and below, and for
|
||||
* later game updates to the Game Coordinator.
|
||||
* 1 = NewGRF ID, MD5 checksum and name.
|
||||
* Used for direct requests and the first game
|
||||
* update to Game Coordinator.
|
||||
* 2 = Index in NewGRF lookup table.
|
||||
* Used for sending server listing from the Game
|
||||
* Coordinator to the clients.
|
||||
*
|
||||
* 5+ 4 version number of the Game Script (-1 is case none is selected).
|
||||
* 5+ var string with the name of the Game Script.
|
||||
*
|
||||
* 4+ 1 number of GRFs attached (n).
|
||||
* 4+ n * var identifiers for GRF files. Consists of:
|
||||
* Note: the 'vN' refers to packet version and 'type'
|
||||
* refers to the v6+ type of storage for the NewGRFs.
|
||||
* - 4 byte variable with the GRF ID.
|
||||
* For v4, v5, and v6+ in case of type 0 and/or type 1.
|
||||
* - 16 bytes with the MD5 checksum of the GRF.
|
||||
* For v4, v5, and v6+ in case of type 0 and/or type 1.
|
||||
* - string with name of NewGRF.
|
||||
* For v6+ in case of type 1.
|
||||
* - 4 byte lookup table index.
|
||||
* For v6+ in case of type 2.
|
||||
*
|
||||
* 3+ 4 current game date in days since 1-1-0 (DMY)
|
||||
* 3+ 4 game introduction date in days since 1-1-0 (DMY)
|
||||
@@ -40,7 +63,7 @@
|
||||
*
|
||||
* 1+ var string with the name of the server
|
||||
* 1+ var string with the revision of the server
|
||||
* 1+ 1 the language run on the server
|
||||
* 1 - 5 1 the language run on the server
|
||||
* (0 = any, 1 = English, 2 = German, 3 = French)
|
||||
* 1+ 1 whether the server uses a password (0 = no, 1 = yes)
|
||||
* 1+ 1 maximum number of clients allowed on the server
|
||||
@@ -48,7 +71,7 @@
|
||||
* 1+ 1 number of spectators on the server
|
||||
* 1 & 2 2 current game date in days since 1-1-1920 (DMY)
|
||||
* 1 & 2 2 game introduction date in days since 1-1-1920 (DMY)
|
||||
* 1+ var string with the name of the map
|
||||
* 1 - 5 var string with the name of the map
|
||||
* 1+ 2 width of the map in tiles
|
||||
* 1+ 2 height of the map in tiles
|
||||
* 1+ 1 type of map:
|
||||
@@ -56,50 +79,71 @@
|
||||
* 1+ 1 whether the server is dedicated (0 = no, 1 = yes)
|
||||
*/
|
||||
|
||||
/**
|
||||
* The game information that is not generated on-the-fly and has to
|
||||
* be sent to the clients.
|
||||
*/
|
||||
struct NetworkServerGameInfo {
|
||||
byte clients_on; ///< Current count of clients on server
|
||||
/** The different types/ways a NewGRF can be serialized in the GameInfo since version 6. */
|
||||
enum NewGRFSerializationType {
|
||||
NST_GRFID_MD5 = 0, ///< Unique GRF ID and MD5 checksum.
|
||||
NST_GRFID_MD5_NAME = 1, ///< Unique GRF ID, MD5 checksum and name.
|
||||
NST_LOOKUP_ID = 2, ///< Unique ID into a lookup table that is sent before.
|
||||
NST_END ///< The end of the list (period).
|
||||
};
|
||||
|
||||
/**
|
||||
* The game information that is sent from the server to the clients.
|
||||
* The game information that is sent from the server to the client.
|
||||
*/
|
||||
struct NetworkServerGameInfo {
|
||||
GRFConfig *grfconfig; ///< List of NewGRF files used
|
||||
Date start_date; ///< When the game started
|
||||
Date game_date; ///< Current date
|
||||
uint16 map_width; ///< Map width
|
||||
uint16 map_height; ///< Map height
|
||||
std::string server_name; ///< Server name
|
||||
std::string server_revision; ///< The version number the server is using (e.g.: 'r304' or 0.5.0)
|
||||
bool dedicated; ///< Is this a dedicated server?
|
||||
bool use_password; ///< Is this server passworded?
|
||||
byte clients_on; ///< Current count of clients on server
|
||||
byte clients_max; ///< Max clients allowed on server
|
||||
byte companies_on; ///< How many started companies do we have
|
||||
byte companies_max; ///< Max companies allowed on server
|
||||
byte spectators_on; ///< How many spectators do we have?
|
||||
byte landscape; ///< The used landscape
|
||||
int gamescript_version; ///< Version of the gamescript.
|
||||
std::string gamescript_name; ///< Name of the gamescript.
|
||||
};
|
||||
|
||||
/**
|
||||
* The game information that is sent from the server to the clients
|
||||
* with extra information only required at the client side.
|
||||
*/
|
||||
struct NetworkGameInfo : NetworkServerGameInfo {
|
||||
GRFConfig *grfconfig; ///< List of NewGRF files used
|
||||
Date start_date; ///< When the game started
|
||||
Date game_date; ///< Current date
|
||||
uint16 map_width; ///< Map width
|
||||
uint16 map_height; ///< Map height
|
||||
char server_name[NETWORK_NAME_LENGTH]; ///< Server name
|
||||
char server_revision[NETWORK_REVISION_LENGTH]; ///< The version number the server is using (e.g.: 'r304' or 0.5.0)
|
||||
bool dedicated; ///< Is this a dedicated server?
|
||||
bool version_compatible; ///< Can we connect to this server or not? (based on server_revision)
|
||||
bool compatible; ///< Can we connect to this server or not? (based on server_revision _and_ grf_match
|
||||
bool use_password; ///< Is this server passworded?
|
||||
byte game_info_version; ///< Version of the game info
|
||||
byte clients_max; ///< Max clients allowed on server
|
||||
byte companies_on; ///< How many started companies do we have
|
||||
byte companies_max; ///< Max companies allowed on server
|
||||
byte spectators_on; ///< How many spectators do we have?
|
||||
byte spectators_max; ///< Max spectators allowed on server
|
||||
byte map_set; ///< Graphical set
|
||||
};
|
||||
|
||||
/**
|
||||
* Container to hold the GRF identifier (GRF ID + MD5 checksum) and the name
|
||||
* associated with that NewGRF.
|
||||
*/
|
||||
struct NamedGRFIdentifier {
|
||||
GRFIdentifier ident; ///< The unique identifier of the NewGRF.
|
||||
std::string name; ///< The name of the NewGRF.
|
||||
};
|
||||
/** Lookup table for the GameInfo in case of #NST_LOOKUP_ID. */
|
||||
typedef std::unordered_map<uint32, NamedGRFIdentifier> GameInfoNewGRFLookupTable;
|
||||
|
||||
extern NetworkServerGameInfo _network_game_info;
|
||||
|
||||
const char *GetNetworkRevisionString();
|
||||
bool IsNetworkCompatibleVersion(const char *other);
|
||||
std::string_view GetNetworkRevisionString();
|
||||
bool IsNetworkCompatibleVersion(std::string_view other);
|
||||
void CheckGameCompatibility(NetworkGameInfo &ngi);
|
||||
|
||||
void FillNetworkGameInfo(NetworkGameInfo &ngi);
|
||||
void FillStaticNetworkServerGameInfo();
|
||||
const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo();
|
||||
|
||||
void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf);
|
||||
void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf);
|
||||
void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf);
|
||||
|
||||
void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info);
|
||||
void SerializeNetworkGameInfo(Packet *p, const NetworkGameInfo *info);
|
||||
void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table = nullptr);
|
||||
void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names = true);
|
||||
|
||||
#endif /* NETWORK_CORE_GAME_INFO_H */
|
||||
|
||||
@@ -20,72 +20,7 @@
|
||||
*/
|
||||
static void NetworkFindBroadcastIPsInternal(NetworkAddressList *broadcast);
|
||||
|
||||
#if defined(__HAIKU__) /* doesn't have neither getifaddrs or net/if.h */
|
||||
/* Based on Andrew Bachmann's netstat+.c. Big thanks to him! */
|
||||
extern "C" int _netstat(int fd, char **output, int verbose);
|
||||
|
||||
int seek_past_header(char **pos, const char *header)
|
||||
{
|
||||
char *new_pos = strstr(*pos, header);
|
||||
if (new_pos == 0) {
|
||||
return B_ERROR;
|
||||
}
|
||||
*pos += strlen(header) + new_pos - *pos + 1;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
static void NetworkFindBroadcastIPsInternal(NetworkAddressList *broadcast) // BEOS implementation
|
||||
{
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
if (sock < 0) {
|
||||
DEBUG(net, 0, "[core] error creating socket");
|
||||
return;
|
||||
}
|
||||
|
||||
char *output_pointer = nullptr;
|
||||
int output_length = _netstat(sock, &output_pointer, 1);
|
||||
if (output_length < 0) {
|
||||
DEBUG(net, 0, "[core] error running _netstat");
|
||||
return;
|
||||
}
|
||||
|
||||
char **output = &output_pointer;
|
||||
if (seek_past_header(output, "IP Interfaces:") == B_OK) {
|
||||
for (;;) {
|
||||
uint32 n;
|
||||
int fields, read;
|
||||
uint8 i1, i2, i3, i4, j1, j2, j3, j4;
|
||||
uint32 ip;
|
||||
uint32 netmask;
|
||||
|
||||
fields = sscanf(*output, "%u: %hhu.%hhu.%hhu.%hhu, netmask %hhu.%hhu.%hhu.%hhu%n",
|
||||
&n, &i1, &i2, &i3, &i4, &j1, &j2, &j3, &j4, &read);
|
||||
read += 1;
|
||||
if (fields != 9) {
|
||||
break;
|
||||
}
|
||||
|
||||
ip = (uint32)i1 << 24 | (uint32)i2 << 16 | (uint32)i3 << 8 | (uint32)i4;
|
||||
netmask = (uint32)j1 << 24 | (uint32)j2 << 16 | (uint32)j3 << 8 | (uint32)j4;
|
||||
|
||||
if (ip != INADDR_LOOPBACK && ip != INADDR_ANY) {
|
||||
sockaddr_storage address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
((sockaddr_in*)&address)->sin_addr.s_addr = htonl(ip | ~netmask);
|
||||
NetworkAddress addr(address, sizeof(sockaddr));
|
||||
if (std::none_of(broadcast->begin(), broadcast->end(), [&addr](NetworkAddress const& elem) -> bool { return elem == addr; })) broadcast->push_back(addr);
|
||||
}
|
||||
if (read < 0) {
|
||||
break;
|
||||
}
|
||||
*output += read;
|
||||
}
|
||||
closesocket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(HAVE_GETIFADDRS)
|
||||
#if defined(HAVE_GETIFADDRS)
|
||||
static void NetworkFindBroadcastIPsInternal(NetworkAddressList *broadcast) // GETIFADDRS implementation
|
||||
{
|
||||
struct ifaddrs *ifap, *ifa;
|
||||
@@ -196,10 +131,10 @@ void NetworkFindBroadcastIPs(NetworkAddressList *broadcast)
|
||||
NetworkFindBroadcastIPsInternal(broadcast);
|
||||
|
||||
/* Now display to the debug all the detected ips */
|
||||
DEBUG(net, 3, "Detected broadcast addresses:");
|
||||
Debug(net, 3, "Detected broadcast addresses:");
|
||||
int i = 0;
|
||||
for (NetworkAddress &addr : *broadcast) {
|
||||
addr.SetPort(NETWORK_DEFAULT_PORT);
|
||||
DEBUG(net, 3, "%d) %s", i++, addr.GetHostname());
|
||||
Debug(net, 3, " {}) {}", i++, addr.GetHostname());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ bool NetworkError::IsConnectInProgress() const
|
||||
* Get the string representation of the error message.
|
||||
* @return The string representation that will get overwritten by next calls.
|
||||
*/
|
||||
const char *NetworkError::AsString() const
|
||||
const std::string &NetworkError::AsString() const
|
||||
{
|
||||
if (this->message.empty()) {
|
||||
#if defined(_WIN32)
|
||||
@@ -97,7 +97,7 @@ const char *NetworkError::AsString() const
|
||||
this->message.assign(strerror(this->error));
|
||||
#endif
|
||||
}
|
||||
return this->message.c_str();
|
||||
return this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,6 +155,23 @@ bool SetNoDelay(SOCKET d)
|
||||
return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags)) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to set the socket to reuse ports.
|
||||
* @param d The socket to reuse ports on.
|
||||
* @return True if disabling the delaying succeeded, otherwise false.
|
||||
*/
|
||||
bool SetReusePort(SOCKET d)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
/* Windows has no SO_REUSEPORT, but for our usecases SO_REUSEADDR does the same job. */
|
||||
int reuse_port = 1;
|
||||
return setsockopt(d, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse_port, sizeof(reuse_port)) == 0;
|
||||
#else
|
||||
int reuse_port = 1;
|
||||
return setsockopt(d, SOL_SOCKET, SO_REUSEPORT, &reuse_port, sizeof(reuse_port)) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error from a socket, if any.
|
||||
* @param d The socket to get the error from.
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
bool WouldBlock() const;
|
||||
bool IsConnectionReset() const;
|
||||
bool IsConnectInProgress() const;
|
||||
const char *AsString() const;
|
||||
const std::string &AsString() const;
|
||||
|
||||
static NetworkError GetLast();
|
||||
};
|
||||
@@ -108,6 +108,13 @@ typedef unsigned long in_addr_t;
|
||||
# undef FD_SETSIZE
|
||||
# define FD_SETSIZE 64
|
||||
# endif
|
||||
|
||||
/* Haiku says it supports FD_SETSIZE fds, but it really only supports 512. */
|
||||
# if defined(__HAIKU__)
|
||||
# undef FD_SETSIZE
|
||||
# define FD_SETSIZE 512
|
||||
# endif
|
||||
|
||||
#endif /* UNIX */
|
||||
|
||||
/* OS/2 stuff */
|
||||
@@ -166,6 +173,7 @@ typedef unsigned long in_addr_t;
|
||||
|
||||
bool SetNonBlocking(SOCKET d);
|
||||
bool SetNoDelay(SOCKET d);
|
||||
bool SetReusePort(SOCKET d);
|
||||
NetworkError GetSocketError(SOCKET d);
|
||||
|
||||
/* Make sure these structures have the size we expect them to be */
|
||||
|
||||
+25
-27
@@ -98,7 +98,7 @@ void Packet::PrepareToSend()
|
||||
*/
|
||||
bool Packet::CanWriteToPacket(size_t bytes_to_write)
|
||||
{
|
||||
return this->Size() + bytes_to_write < this->limit;
|
||||
return this->Size() + bytes_to_write <= this->limit;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -178,12 +178,11 @@ void Packet::Send_uint64(uint64 data)
|
||||
* the string + '\0'. No size-byte or something.
|
||||
* @param data The string to send
|
||||
*/
|
||||
void Packet::Send_string(const char *data)
|
||||
void Packet::Send_string(const std::string_view data)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
/* Length of the string + 1 for the '\0' termination. */
|
||||
assert(this->CanWriteToPacket(strlen(data) + 1));
|
||||
while (this->buffer.emplace_back(*data++) != '\0') {}
|
||||
assert(this->CanWriteToPacket(data.size() + 1));
|
||||
this->buffer.insert(this->buffer.end(), data.begin(), data.end());
|
||||
this->buffer.emplace_back('\0');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +222,7 @@ bool Packet::CanReadFromPacket(size_t bytes_to_read, bool close_connection)
|
||||
|
||||
/* Check if variable is within packet-size */
|
||||
if (this->pos + bytes_to_read > this->Size()) {
|
||||
if (close_connection) this->cs->NetworkSocketHandler::CloseConnection();
|
||||
if (close_connection) this->cs->NetworkSocketHandler::MarkClosed();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -368,33 +367,32 @@ uint64 Packet::Recv_uint64()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a string till it finds a '\0' in the stream.
|
||||
* @param buffer The buffer to put the data into.
|
||||
* @param size The size of the buffer.
|
||||
* Reads characters (bytes) from the packet until it finds a '\0', or reaches a
|
||||
* maximum of \c length characters.
|
||||
* When the '\0' has not been reached in the first \c length read characters,
|
||||
* more characters are read from the packet until '\0' has been reached. However,
|
||||
* these characters will not end up in the returned string.
|
||||
* The length of the returned string will be at most \c length - 1 characters.
|
||||
* @param length The maximum length of the string including '\0'.
|
||||
* @param settings The string validation settings.
|
||||
* @return The validated string.
|
||||
*/
|
||||
void Packet::Recv_string(char *buffer, size_t size, StringValidationSettings settings)
|
||||
std::string Packet::Recv_string(size_t length, StringValidationSettings settings)
|
||||
{
|
||||
PacketSize pos;
|
||||
char *bufp = buffer;
|
||||
const char *last = buffer + size - 1;
|
||||
assert(length > 1);
|
||||
|
||||
/* Don't allow reading from a closed socket */
|
||||
if (cs->HasClientQuit()) return;
|
||||
/* Both loops with Recv_uint8 terminate when reading past the end of the
|
||||
* packet as Recv_uint8 then closes the connection and returns 0. */
|
||||
std::string str;
|
||||
char character;
|
||||
while (--length > 0 && (character = this->Recv_uint8()) != '\0') str.push_back(character);
|
||||
|
||||
pos = this->pos;
|
||||
while (--size > 0 && pos < this->Size() && (*buffer++ = this->buffer[pos++]) != '\0') {}
|
||||
|
||||
if (size == 0 || pos == this->Size()) {
|
||||
*buffer = '\0';
|
||||
/* If size was sooner to zero then the string in the stream
|
||||
* skip till the \0, so than packet can be read out correctly for the rest */
|
||||
while (pos < this->Size() && this->buffer[pos] != '\0') pos++;
|
||||
pos++;
|
||||
if (length == 0) {
|
||||
/* The string in the packet was longer. Read until the termination. */
|
||||
while (this->Recv_uint8() != '\0') {}
|
||||
}
|
||||
this->pos = pos;
|
||||
|
||||
str_validate(bufp, last, settings);
|
||||
return StrMakeValid(str, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
void Send_uint16(uint16 data);
|
||||
void Send_uint32(uint32 data);
|
||||
void Send_uint64(uint64 data);
|
||||
void Send_string(const char *data);
|
||||
void Send_string(const std::string_view data);
|
||||
size_t Send_bytes (const byte *begin, const byte *end);
|
||||
|
||||
/* Reading/receiving of packets */
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
uint16 Recv_uint16();
|
||||
uint32 Recv_uint32();
|
||||
uint64 Recv_uint64();
|
||||
void Recv_string(char *buffer, size_t size, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK);
|
||||
std::string Recv_string(size_t length, StringValidationSettings settings = SVS_REPLACE_WITH_QUESTION_MARK);
|
||||
|
||||
size_t RemainingBytesToTransfer() const;
|
||||
|
||||
|
||||
+34
-12
@@ -29,23 +29,45 @@ NetworkTCPSocketHandler::NetworkTCPSocketHandler(SOCKET s) :
|
||||
|
||||
NetworkTCPSocketHandler::~NetworkTCPSocketHandler()
|
||||
{
|
||||
this->CloseConnection();
|
||||
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
this->EmptyPacketQueue();
|
||||
this->CloseSocket();
|
||||
}
|
||||
|
||||
NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection(bool error)
|
||||
/**
|
||||
* Free all pending and partially received packets.
|
||||
*/
|
||||
void NetworkTCPSocketHandler::EmptyPacketQueue()
|
||||
{
|
||||
this->writable = false;
|
||||
NetworkSocketHandler::CloseConnection(error);
|
||||
|
||||
/* Free all pending and partially received packets */
|
||||
while (this->packet_queue != nullptr) {
|
||||
delete Packet::PopFromQueue(&this->packet_queue);
|
||||
}
|
||||
delete this->packet_recv;
|
||||
this->packet_recv = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the actual socket of the connection.
|
||||
* Please make sure CloseConnection is called before CloseSocket, as
|
||||
* otherwise not all resources might be released.
|
||||
*/
|
||||
void NetworkTCPSocketHandler::CloseSocket()
|
||||
{
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will put this socket handler in a close state. It will not
|
||||
* actually close the OS socket; use CloseSocket for this.
|
||||
* @param error Whether we quit under an error condition or not.
|
||||
* @return new status of the connection.
|
||||
*/
|
||||
NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
this->MarkClosed();
|
||||
this->writable = false;
|
||||
|
||||
this->EmptyPacketQueue();
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -90,7 +112,7 @@ SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
|
||||
if (!err.WouldBlock()) {
|
||||
/* Something went wrong.. close client! */
|
||||
if (!closing_down) {
|
||||
DEBUG(net, 0, "send failed with error %s", err.AsString());
|
||||
Debug(net, 0, "Send failed: {}", err.AsString());
|
||||
this->CloseConnection();
|
||||
}
|
||||
return SPS_CLOSED;
|
||||
@@ -139,7 +161,7 @@ Packet *NetworkTCPSocketHandler::ReceivePacket()
|
||||
NetworkError err = NetworkError::GetLast();
|
||||
if (!err.WouldBlock()) {
|
||||
/* Something went wrong... */
|
||||
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
|
||||
if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
|
||||
this->CloseConnection();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -167,7 +189,7 @@ Packet *NetworkTCPSocketHandler::ReceivePacket()
|
||||
NetworkError err = NetworkError::GetLast();
|
||||
if (!err.WouldBlock()) {
|
||||
/* Something went wrong... */
|
||||
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
|
||||
if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
|
||||
this->CloseConnection();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+67
-13
@@ -16,6 +16,9 @@
|
||||
#include "packet.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
|
||||
/** The states of sending the packets. */
|
||||
enum SendPacketsState {
|
||||
@@ -30,6 +33,8 @@ class NetworkTCPSocketHandler : public NetworkSocketHandler {
|
||||
private:
|
||||
Packet *packet_queue; ///< Packets that are awaiting delivery
|
||||
Packet *packet_recv; ///< Partially received packet
|
||||
|
||||
void EmptyPacketQueue();
|
||||
public:
|
||||
SOCKET sock; ///< The socket currently connected to
|
||||
bool writable; ///< Can we write to this socket?
|
||||
@@ -40,7 +45,9 @@ public:
|
||||
*/
|
||||
bool IsConnected() const { return this->sock != INVALID_SOCKET; }
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
virtual NetworkRecvStatus CloseConnection(bool error = true);
|
||||
void CloseSocket();
|
||||
|
||||
virtual void SendPacket(Packet *packet);
|
||||
SendPacketsState SendPackets(bool closing_down = false);
|
||||
|
||||
@@ -63,23 +70,53 @@ public:
|
||||
*/
|
||||
class TCPConnecter {
|
||||
private:
|
||||
std::atomic<bool> connected;///< Whether we succeeded in making the connection
|
||||
std::atomic<bool> aborted; ///< Whether we bailed out (i.e. connection making failed)
|
||||
bool killed; ///< Whether we got killed
|
||||
SOCKET sock; ///< The socket we're connecting with
|
||||
/**
|
||||
* The current status of the connecter.
|
||||
*
|
||||
* We track the status like this to ensure everything is executed from the
|
||||
* game-thread, and not at another random time where we might not have the
|
||||
* lock on the game-state.
|
||||
*/
|
||||
enum class Status {
|
||||
INIT, ///< TCPConnecter is created but resolving hasn't started.
|
||||
RESOLVING, ///< The hostname is being resolved (threaded).
|
||||
FAILURE, ///< Resolving failed.
|
||||
CONNECTING, ///< We are currently connecting.
|
||||
CONNECTED, ///< The connection is established.
|
||||
};
|
||||
|
||||
void Connect();
|
||||
std::thread resolve_thread; ///< Thread used during resolving.
|
||||
std::atomic<Status> status = Status::INIT; ///< The current status of the connecter.
|
||||
std::atomic<bool> killed = false; ///< Whether this connecter is marked as killed.
|
||||
|
||||
static void ThreadEntry(TCPConnecter *param);
|
||||
addrinfo *ai = nullptr; ///< getaddrinfo() allocated linked-list of resolved addresses.
|
||||
std::vector<addrinfo *> addresses; ///< Addresses we can connect to.
|
||||
std::map<SOCKET, NetworkAddress> sock_to_address; ///< Mapping of a socket to the real address it is connecting to. USed for DEBUG statements.
|
||||
size_t current_address = 0; ///< Current index in addresses we are trying.
|
||||
|
||||
protected:
|
||||
/** Address we're connecting to */
|
||||
NetworkAddress address;
|
||||
std::vector<SOCKET> sockets; ///< Pending connect() attempts.
|
||||
std::chrono::steady_clock::time_point last_attempt; ///< Time we last tried to connect.
|
||||
|
||||
std::string connection_string; ///< Current address we are connecting to (before resolving).
|
||||
NetworkAddress bind_address; ///< Address we're binding to, if any.
|
||||
int family = AF_UNSPEC; ///< Family we are using to connect with.
|
||||
|
||||
void Resolve();
|
||||
void OnResolved(addrinfo *ai);
|
||||
bool TryNextAddress();
|
||||
void Connect(addrinfo *address);
|
||||
virtual bool CheckActivity();
|
||||
|
||||
/* We do not want any other derived classes from this class being able to
|
||||
* access these private members, but it is okay for TCPServerConnecter. */
|
||||
friend class TCPServerConnecter;
|
||||
|
||||
static void ResolveThunk(TCPConnecter *connecter);
|
||||
|
||||
public:
|
||||
TCPConnecter(const NetworkAddress &address);
|
||||
/** Silence the warnings */
|
||||
virtual ~TCPConnecter() {}
|
||||
TCPConnecter() {};
|
||||
TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address = {}, int family = AF_UNSPEC);
|
||||
virtual ~TCPConnecter();
|
||||
|
||||
/**
|
||||
* Callback when the connection succeeded.
|
||||
@@ -92,8 +129,25 @@ public:
|
||||
*/
|
||||
virtual void OnFailure() {}
|
||||
|
||||
void Kill();
|
||||
|
||||
static void CheckCallbacks();
|
||||
static void KillAll();
|
||||
};
|
||||
|
||||
class TCPServerConnecter : public TCPConnecter {
|
||||
private:
|
||||
SOCKET socket = INVALID_SOCKET; ///< The socket when a connection is established.
|
||||
|
||||
bool CheckActivity() override;
|
||||
|
||||
public:
|
||||
ServerAddress server_address; ///< Address we are connecting to.
|
||||
|
||||
TCPServerConnecter(const std::string &connection_string, uint16 default_port);
|
||||
|
||||
void SetConnected(SOCKET sock);
|
||||
void SetFailure();
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_H */
|
||||
|
||||
@@ -30,18 +30,12 @@ static_assert((int)CRR_END == (int)ADMIN_CRR_END);
|
||||
NetworkAdminSocketHandler::NetworkAdminSocketHandler(SOCKET s) : status(ADMIN_STATUS_INACTIVE)
|
||||
{
|
||||
this->sock = s;
|
||||
this->admin_name[0] = '\0';
|
||||
this->admin_version[0] = '\0';
|
||||
}
|
||||
|
||||
NetworkAdminSocketHandler::~NetworkAdminSocketHandler()
|
||||
{
|
||||
}
|
||||
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
delete this;
|
||||
return NETWORK_RECV_STATUS_CONN_LOST;
|
||||
return NETWORK_RECV_STATUS_CLIENT_QUIT;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,6 +53,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet *p)
|
||||
case ADMIN_PACKET_ADMIN_UPDATE_FREQUENCY: return this->Receive_ADMIN_UPDATE_FREQUENCY(p);
|
||||
case ADMIN_PACKET_ADMIN_POLL: return this->Receive_ADMIN_POLL(p);
|
||||
case ADMIN_PACKET_ADMIN_CHAT: return this->Receive_ADMIN_CHAT(p);
|
||||
case ADMIN_PACKET_ADMIN_EXTERNAL_CHAT: return this->Receive_ADMIN_EXTERNAL_CHAT(p);
|
||||
case ADMIN_PACKET_ADMIN_RCON: return this->Receive_ADMIN_RCON(p);
|
||||
case ADMIN_PACKET_ADMIN_GAMESCRIPT: return this->Receive_ADMIN_GAMESCRIPT(p);
|
||||
case ADMIN_PACKET_ADMIN_PING: return this->Receive_ADMIN_PING(p);
|
||||
@@ -93,9 +88,9 @@ NetworkRecvStatus NetworkAdminSocketHandler::HandlePacket(Packet *p)
|
||||
|
||||
default:
|
||||
if (this->HasClientQuit()) {
|
||||
DEBUG(net, 0, "[tcp/admin] received invalid packet type %d from '%s' (%s)", type, this->admin_name, this->admin_version);
|
||||
Debug(net, 0, "[tcp/admin] Received invalid packet type {} from '{}' ({})", type, this->admin_name, this->admin_version);
|
||||
} else {
|
||||
DEBUG(net, 0, "[tcp/admin] received illegal packet from '%s' (%s)", this->admin_name, this->admin_version);
|
||||
Debug(net, 0, "[tcp/admin] Received illegal packet from '{}' ({})", this->admin_name, this->admin_version);
|
||||
}
|
||||
|
||||
this->CloseConnection();
|
||||
@@ -129,7 +124,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::ReceivePackets()
|
||||
*/
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::ReceiveInvalidPacket(PacketAdminType type)
|
||||
{
|
||||
DEBUG(net, 0, "[tcp/admin] received illegal packet type %d from admin %s (%s)", type, this->admin_name, this->admin_version);
|
||||
Debug(net, 0, "[tcp/admin] Received illegal packet type {} from admin {} ({})", type, this->admin_name, this->admin_version);
|
||||
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
}
|
||||
|
||||
@@ -138,6 +133,7 @@ NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_QUIT(Packet *p) { ret
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_UPDATE_FREQUENCY(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_UPDATE_FREQUENCY); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_POLL(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_POLL); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_CHAT); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_EXTERNAL_CHAT); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_RCON); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_GAMESCRIPT); }
|
||||
NetworkRecvStatus NetworkAdminSocketHandler::Receive_ADMIN_PING(Packet *p) { return this->ReceiveInvalidPacket(ADMIN_PACKET_ADMIN_PING); }
|
||||
|
||||
@@ -30,6 +30,7 @@ enum PacketAdminType {
|
||||
ADMIN_PACKET_ADMIN_RCON, ///< The admin sends a remote console command.
|
||||
ADMIN_PACKET_ADMIN_GAMESCRIPT, ///< The admin sends a JSON string for the GameScript.
|
||||
ADMIN_PACKET_ADMIN_PING, ///< The admin sends a ping to the server, expecting a ping-reply (PONG) packet.
|
||||
ADMIN_PACKET_ADMIN_EXTERNAL_CHAT, ///< The admin sends a chat message from external source.
|
||||
|
||||
ADMIN_PACKET_SERVER_FULL = 100, ///< The server tells the admin it cannot accept the admin.
|
||||
ADMIN_PACKET_SERVER_BANNED, ///< The server tells the admin it is banned.
|
||||
@@ -109,9 +110,9 @@ enum AdminCompanyRemoveReason {
|
||||
/** Main socket handler for admin related connections. */
|
||||
class NetworkAdminSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
char admin_name[NETWORK_CLIENT_NAME_LENGTH]; ///< Name of the admin.
|
||||
char admin_version[NETWORK_REVISION_LENGTH]; ///< Version string of the admin.
|
||||
AdminStatus status; ///< Status of this admin.
|
||||
std::string admin_name; ///< Name of the admin.
|
||||
std::string admin_version; ///< Version string of the admin.
|
||||
AdminStatus status; ///< Status of this admin.
|
||||
|
||||
NetworkRecvStatus ReceiveInvalidPacket(PacketAdminType type);
|
||||
|
||||
@@ -163,6 +164,17 @@ protected:
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_ADMIN_CHAT(Packet *p);
|
||||
|
||||
/**
|
||||
* Send chat from the external source:
|
||||
* string Name of the source this message came from.
|
||||
* uint16 TextColour to use for the message.
|
||||
* string Name of the user who sent the messsage.
|
||||
* string Message.
|
||||
* @param p The packet that was just received.
|
||||
* @return The state the network should have.
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_ADMIN_EXTERNAL_CHAT(Packet *p);
|
||||
|
||||
/**
|
||||
* Execute a command on the servers console:
|
||||
* string Command to be executed.
|
||||
@@ -482,7 +494,6 @@ public:
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
|
||||
NetworkAdminSocketHandler(SOCKET s);
|
||||
~NetworkAdminSocketHandler();
|
||||
|
||||
NetworkRecvStatus ReceivePackets();
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include "../../thread.h"
|
||||
|
||||
#include "tcp.h"
|
||||
#include "../network_coordinator.h"
|
||||
#include "../network_internal.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
@@ -20,40 +24,443 @@
|
||||
static std::vector<TCPConnecter *> _tcp_connecters;
|
||||
|
||||
/**
|
||||
* Create a new connecter for the given address
|
||||
* @param address the (un)resolved address to connect to
|
||||
* Create a new connecter for the given address.
|
||||
* @param connection_string The address to connect to.
|
||||
* @param default_port If not indicated in connection_string, what port to use.
|
||||
* @param bind_address The local bind address to use. Defaults to letting the OS find one.
|
||||
*/
|
||||
TCPConnecter::TCPConnecter(const NetworkAddress &address) :
|
||||
connected(false),
|
||||
aborted(false),
|
||||
killed(false),
|
||||
sock(INVALID_SOCKET),
|
||||
address(address)
|
||||
TCPConnecter::TCPConnecter(const std::string &connection_string, uint16 default_port, NetworkAddress bind_address, int family) :
|
||||
bind_address(bind_address),
|
||||
family(family)
|
||||
{
|
||||
this->connection_string = NormalizeConnectionString(connection_string, default_port);
|
||||
|
||||
_tcp_connecters.push_back(this);
|
||||
if (!StartNewThread(nullptr, "ottd:tcp", &TCPConnecter::ThreadEntry, this)) {
|
||||
this->Connect();
|
||||
}
|
||||
}
|
||||
|
||||
/** The actual connection function */
|
||||
void TCPConnecter::Connect()
|
||||
/**
|
||||
* Create a new connecter for the server.
|
||||
* @param connection_string The address to connect to.
|
||||
* @param default_port If not indicated in connection_string, what port to use.
|
||||
*/
|
||||
TCPServerConnecter::TCPServerConnecter(const std::string &connection_string, uint16 default_port) :
|
||||
server_address(ServerAddress::Parse(connection_string, default_port))
|
||||
{
|
||||
this->sock = this->address.Connect();
|
||||
if (this->sock == INVALID_SOCKET) {
|
||||
this->aborted = true;
|
||||
} else {
|
||||
this->connected = true;
|
||||
switch (this->server_address.type) {
|
||||
case SERVER_ADDRESS_DIRECT:
|
||||
this->connection_string = this->server_address.connection_string;
|
||||
break;
|
||||
|
||||
case SERVER_ADDRESS_INVITE_CODE:
|
||||
this->status = Status::CONNECTING;
|
||||
_network_coordinator_client.ConnectToServer(this->server_address.connection_string, this);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
_tcp_connecters.push_back(this);
|
||||
}
|
||||
|
||||
TCPConnecter::~TCPConnecter()
|
||||
{
|
||||
if (this->resolve_thread.joinable()) {
|
||||
this->resolve_thread.join();
|
||||
}
|
||||
|
||||
for (const auto &socket : this->sockets) {
|
||||
closesocket(socket);
|
||||
}
|
||||
this->sockets.clear();
|
||||
this->sock_to_address.clear();
|
||||
|
||||
if (this->ai != nullptr) freeaddrinfo(this->ai);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill this connecter.
|
||||
* It will abort as soon as it can and not call any of the callbacks.
|
||||
*/
|
||||
void TCPConnecter::Kill()
|
||||
{
|
||||
/* Delay the removing of the socket till the next CheckActivity(). */
|
||||
this->killed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a connection to the indicated address.
|
||||
* @param address The address to connection to.
|
||||
*/
|
||||
void TCPConnecter::Connect(addrinfo *address)
|
||||
{
|
||||
SOCKET sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
if (sock == INVALID_SOCKET) {
|
||||
Debug(net, 0, "Could not create {} {} socket: {}", NetworkAddress::SocketTypeAsString(address->ai_socktype), NetworkAddress::AddressFamilyAsString(address->ai_family), NetworkError::GetLast().AsString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetReusePort(sock)) {
|
||||
Debug(net, 0, "Setting reuse-port mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
if (this->bind_address.GetPort() > 0) {
|
||||
if (bind(sock, (const sockaddr *)this->bind_address.GetAddress(), this->bind_address.GetAddressLength()) != 0) {
|
||||
Debug(net, 1, "Could not bind socket on {}: {}", this->bind_address.GetAddressAsString(), NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetNoDelay(sock)) {
|
||||
Debug(net, 1, "Setting TCP_NODELAY failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
if (!SetNonBlocking(sock)) {
|
||||
Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
NetworkAddress network_address = NetworkAddress(address->ai_addr, (int)address->ai_addrlen);
|
||||
Debug(net, 5, "Attempting to connect to {}", network_address.GetAddressAsString());
|
||||
|
||||
int err = connect(sock, address->ai_addr, (int)address->ai_addrlen);
|
||||
if (err != 0 && !NetworkError::GetLast().IsConnectInProgress()) {
|
||||
closesocket(sock);
|
||||
|
||||
Debug(net, 1, "Could not connect to {}: {}", network_address.GetAddressAsString(), NetworkError::GetLast().AsString());
|
||||
return;
|
||||
}
|
||||
|
||||
this->sock_to_address[sock] = network_address;
|
||||
this->sockets.push_back(sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the connect() for the next address in the list.
|
||||
* @return True iff a new connect() is attempted.
|
||||
*/
|
||||
bool TCPConnecter::TryNextAddress()
|
||||
{
|
||||
if (this->current_address >= this->addresses.size()) return false;
|
||||
|
||||
this->last_attempt = std::chrono::steady_clock::now();
|
||||
this->Connect(this->addresses[this->current_address++]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when resolving is done.
|
||||
* @param ai A linked-list of address information.
|
||||
*/
|
||||
void TCPConnecter::OnResolved(addrinfo *ai)
|
||||
{
|
||||
std::deque<addrinfo *> addresses_ipv4, addresses_ipv6;
|
||||
|
||||
/* Apply "Happy Eyeballs" if it is likely IPv6 is functional. */
|
||||
|
||||
/* Detect if IPv6 is likely to succeed or not. */
|
||||
bool seen_ipv6 = false;
|
||||
bool resort = true;
|
||||
for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
|
||||
if (runp->ai_family == AF_INET6) {
|
||||
seen_ipv6 = true;
|
||||
} else if (!seen_ipv6) {
|
||||
/* We see an IPv4 before an IPv6; this most likely means there is
|
||||
* no IPv6 available on the system, so keep the order of this
|
||||
* list. */
|
||||
resort = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert the addrinfo into NetworkAddresses. */
|
||||
for (addrinfo *runp = ai; runp != nullptr; runp = runp->ai_next) {
|
||||
/* Skip entries if the family is set and it is not matching. */
|
||||
if (this->family != AF_UNSPEC && this->family != runp->ai_family) continue;
|
||||
|
||||
if (resort) {
|
||||
if (runp->ai_family == AF_INET6) {
|
||||
addresses_ipv6.emplace_back(runp);
|
||||
} else {
|
||||
addresses_ipv4.emplace_back(runp);
|
||||
}
|
||||
} else {
|
||||
this->addresses.emplace_back(runp);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we want to resort, make the list like IPv6 / IPv4 / IPv6 / IPv4 / ..
|
||||
* for how ever many (round-robin) DNS entries we have. */
|
||||
if (resort) {
|
||||
while (!addresses_ipv4.empty() || !addresses_ipv6.empty()) {
|
||||
if (!addresses_ipv6.empty()) {
|
||||
this->addresses.push_back(addresses_ipv6.front());
|
||||
addresses_ipv6.pop_front();
|
||||
}
|
||||
if (!addresses_ipv4.empty()) {
|
||||
this->addresses.push_back(addresses_ipv4.front());
|
||||
addresses_ipv4.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_debug_net_level >= 6) {
|
||||
if (this->addresses.size() == 0) {
|
||||
Debug(net, 6, "{} did not resolve", this->connection_string);
|
||||
} else {
|
||||
Debug(net, 6, "{} resolved in:", this->connection_string);
|
||||
for (const auto &address : this->addresses) {
|
||||
Debug(net, 6, "- {}", NetworkAddress(address->ai_addr, (int)address->ai_addrlen).GetAddressAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->current_address = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start resolving the hostname.
|
||||
*
|
||||
* This function must change "status" to either Status::FAILURE
|
||||
* or Status::CONNECTING before returning.
|
||||
*/
|
||||
void TCPConnecter::Resolve()
|
||||
{
|
||||
/* Port is already guaranteed part of the connection_string. */
|
||||
NetworkAddress address = ParseConnectionString(this->connection_string, 0);
|
||||
|
||||
addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_ADDRCONFIG;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
char port_name[6];
|
||||
seprintf(port_name, lastof(port_name), "%u", address.GetPort());
|
||||
|
||||
static bool getaddrinfo_timeout_error_shown = false;
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
addrinfo *ai;
|
||||
int error = getaddrinfo(address.GetHostname().c_str(), port_name, &hints, &ai);
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
||||
if (!getaddrinfo_timeout_error_shown && duration >= std::chrono::seconds(5)) {
|
||||
Debug(net, 0, "getaddrinfo() for address \"{}\" took {} seconds", this->connection_string, duration.count());
|
||||
Debug(net, 0, " This is likely an issue in the DNS name resolver's configuration causing it to time out");
|
||||
getaddrinfo_timeout_error_shown = true;
|
||||
}
|
||||
|
||||
if (error != 0) {
|
||||
Debug(net, 0, "Failed to resolve DNS for {}", this->connection_string);
|
||||
this->status = Status::FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
this->ai = ai;
|
||||
this->OnResolved(ai);
|
||||
|
||||
this->status = Status::CONNECTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thunk to start Resolve() on the right instance.
|
||||
*/
|
||||
/* static */ void TCPConnecter::ResolveThunk(TCPConnecter *connecter)
|
||||
{
|
||||
connecter->Resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there was activity for this connecter.
|
||||
* @return True iff the TCPConnecter is done and can be cleaned up.
|
||||
*/
|
||||
bool TCPConnecter::CheckActivity()
|
||||
{
|
||||
if (this->killed) return true;
|
||||
|
||||
switch (this->status) {
|
||||
case Status::INIT:
|
||||
/* Start the thread delayed, so the vtable is loaded. This allows classes
|
||||
* to overload functions used by Resolve() (in case threading is disabled). */
|
||||
if (StartNewThread(&this->resolve_thread, "ottd:resolve", &TCPConnecter::ResolveThunk, this)) {
|
||||
this->status = Status::RESOLVING;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No threads, do a blocking resolve. */
|
||||
this->Resolve();
|
||||
|
||||
/* Continue as we are either failed or can start the first
|
||||
* connection. The rest of this function handles exactly that. */
|
||||
break;
|
||||
|
||||
case Status::RESOLVING:
|
||||
/* Wait till Resolve() comes back with an answer (in case it runs threaded). */
|
||||
return false;
|
||||
|
||||
case Status::FAILURE:
|
||||
/* Ensure the OnFailure() is called from the game-thread instead of the
|
||||
* resolve-thread, as otherwise we can get into some threading issues. */
|
||||
this->OnFailure();
|
||||
return true;
|
||||
|
||||
case Status::CONNECTING:
|
||||
case Status::CONNECTED:
|
||||
break;
|
||||
}
|
||||
|
||||
/* If there are no attempts pending, connect to the next. */
|
||||
if (this->sockets.empty()) {
|
||||
if (!this->TryNextAddress()) {
|
||||
/* There were no more addresses to try, so we failed. */
|
||||
this->OnFailure();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fd_set write_fd;
|
||||
FD_ZERO(&write_fd);
|
||||
for (const auto &socket : this->sockets) {
|
||||
FD_SET(socket, &write_fd);
|
||||
}
|
||||
|
||||
timeval tv;
|
||||
tv.tv_usec = 0;
|
||||
tv.tv_sec = 0;
|
||||
int n = select(FD_SETSIZE, nullptr, &write_fd, nullptr, &tv);
|
||||
/* select() failed; hopefully next try it doesn't. */
|
||||
if (n < 0) {
|
||||
/* select() normally never fails; so hopefully it works next try! */
|
||||
Debug(net, 1, "select() failed: {}", NetworkError::GetLast().AsString());
|
||||
return false;
|
||||
}
|
||||
|
||||
/* No socket updates. */
|
||||
if (n == 0) {
|
||||
/* Wait 250ms between attempting another address. */
|
||||
if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(250)) return false;
|
||||
|
||||
/* Try the next address in the list. */
|
||||
if (this->TryNextAddress()) return false;
|
||||
|
||||
/* Wait up to 3 seconds since the last connection we started. */
|
||||
if (std::chrono::steady_clock::now() < this->last_attempt + std::chrono::milliseconds(3000)) return false;
|
||||
|
||||
/* More than 3 seconds no socket reported activity, and there are no
|
||||
* more address to try. Timeout the attempt. */
|
||||
Debug(net, 0, "Timeout while connecting to {}", this->connection_string);
|
||||
|
||||
for (const auto &socket : this->sockets) {
|
||||
closesocket(socket);
|
||||
}
|
||||
this->sockets.clear();
|
||||
this->sock_to_address.clear();
|
||||
|
||||
this->OnFailure();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check for errors on any of the sockets. */
|
||||
for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
|
||||
NetworkError socket_error = GetSocketError(*it);
|
||||
if (socket_error.HasError()) {
|
||||
Debug(net, 1, "Could not connect to {}: {}", this->sock_to_address[*it].GetAddressAsString(), socket_error.AsString());
|
||||
closesocket(*it);
|
||||
this->sock_to_address.erase(*it);
|
||||
it = this->sockets.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
/* In case all sockets had an error, queue a new one. */
|
||||
if (this->sockets.empty()) {
|
||||
if (!this->TryNextAddress()) {
|
||||
/* There were no more addresses to try, so we failed. */
|
||||
this->OnFailure();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* At least one socket is connected. The first one that does is the one
|
||||
* we will be using, and we close all other sockets. */
|
||||
SOCKET connected_socket = INVALID_SOCKET;
|
||||
for (auto it = this->sockets.begin(); it != this->sockets.end(); /* nothing */) {
|
||||
if (connected_socket == INVALID_SOCKET && FD_ISSET(*it, &write_fd)) {
|
||||
connected_socket = *it;
|
||||
} else {
|
||||
closesocket(*it);
|
||||
}
|
||||
this->sock_to_address.erase(*it);
|
||||
it = this->sockets.erase(it);
|
||||
}
|
||||
assert(connected_socket != INVALID_SOCKET);
|
||||
|
||||
Debug(net, 3, "Connected to {}", this->connection_string);
|
||||
if (_debug_net_level >= 5) {
|
||||
Debug(net, 5, "- using {}", NetworkAddress::GetPeerName(connected_socket));
|
||||
}
|
||||
|
||||
this->OnConnect(connected_socket);
|
||||
this->status = Status::CONNECTED;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there was activity for this connecter.
|
||||
* @return True iff the TCPConnecter is done and can be cleaned up.
|
||||
*/
|
||||
bool TCPServerConnecter::CheckActivity()
|
||||
{
|
||||
if (this->killed) return true;
|
||||
|
||||
switch (this->server_address.type) {
|
||||
case SERVER_ADDRESS_DIRECT:
|
||||
return TCPConnecter::CheckActivity();
|
||||
|
||||
case SERVER_ADDRESS_INVITE_CODE:
|
||||
/* Check if a result has come in. */
|
||||
switch (this->status) {
|
||||
case Status::FAILURE:
|
||||
this->OnFailure();
|
||||
return true;
|
||||
|
||||
case Status::CONNECTED:
|
||||
this->OnConnect(this->socket);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for the new threads.
|
||||
* @param param the TCPConnecter instance to call Connect on.
|
||||
* The connection was successfully established.
|
||||
* This socket is fully setup and ready to send/recv game protocol packets.
|
||||
* @param sock The socket of the established connection.
|
||||
*/
|
||||
/* static */ void TCPConnecter::ThreadEntry(TCPConnecter *param)
|
||||
void TCPServerConnecter::SetConnected(SOCKET sock)
|
||||
{
|
||||
param->Connect();
|
||||
this->socket = sock;
|
||||
this->status = Status::CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection couldn't be established.
|
||||
*/
|
||||
void TCPServerConnecter::SetFailure()
|
||||
{
|
||||
this->status = Status::FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,32 +473,22 @@ void TCPConnecter::Connect()
|
||||
{
|
||||
for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
|
||||
TCPConnecter *cur = *iter;
|
||||
const bool connected = cur->connected.load();
|
||||
const bool aborted = cur->aborted.load();
|
||||
if ((connected || aborted) && cur->killed) {
|
||||
|
||||
if (cur->CheckActivity()) {
|
||||
iter = _tcp_connecters.erase(iter);
|
||||
if (cur->sock != INVALID_SOCKET) closesocket(cur->sock);
|
||||
delete cur;
|
||||
continue;
|
||||
} else {
|
||||
iter++;
|
||||
}
|
||||
if (connected) {
|
||||
iter = _tcp_connecters.erase(iter);
|
||||
cur->OnConnect(cur->sock);
|
||||
delete cur;
|
||||
continue;
|
||||
}
|
||||
if (aborted) {
|
||||
iter = _tcp_connecters.erase(iter);
|
||||
cur->OnFailure();
|
||||
delete cur;
|
||||
continue;
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
/** Kill all connection attempts. */
|
||||
/* static */ void TCPConnecter::KillAll()
|
||||
{
|
||||
for (TCPConnecter *conn : _tcp_connecters) conn->killed = true;
|
||||
for (auto iter = _tcp_connecters.begin(); iter < _tcp_connecters.end(); /* nothing */) {
|
||||
TCPConnecter *cur = *iter;
|
||||
iter = _tcp_connecters.erase(iter);
|
||||
delete cur;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,50 +20,6 @@
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/** Clear everything in the struct */
|
||||
ContentInfo::ContentInfo()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
/** Free everything allocated */
|
||||
ContentInfo::~ContentInfo()
|
||||
{
|
||||
free(this->dependencies);
|
||||
free(this->tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy data from other #ContentInfo and take ownership of allocated stuff.
|
||||
* @param other Source to copy from. #dependencies and #tags will be NULLed.
|
||||
*/
|
||||
void ContentInfo::TransferFrom(ContentInfo *other)
|
||||
{
|
||||
if (other != this) {
|
||||
free(this->dependencies);
|
||||
free(this->tags);
|
||||
memcpy(this, other, sizeof(ContentInfo));
|
||||
other->dependencies = nullptr;
|
||||
other->tags = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the data as send over the network.
|
||||
* @return the size.
|
||||
*/
|
||||
size_t ContentInfo::Size() const
|
||||
{
|
||||
size_t len = 0;
|
||||
for (uint i = 0; i < this->tag_count; i++) len += strlen(this->tags[i]) + 1;
|
||||
|
||||
/* The size is never larger than the content info size plus the size of the
|
||||
* tags and dependencies */
|
||||
return sizeof(*this) +
|
||||
sizeof(this->dependency_count) +
|
||||
sizeof(*this->dependencies) * this->dependency_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the state either selected or autoselected?
|
||||
* @return true iff that's the case
|
||||
@@ -137,15 +93,6 @@ const char *ContentInfo::GetTextfile(TextfileType type) const
|
||||
return ::GetTextfile(type, GetContentInfoSubDir(this->type), tmp);
|
||||
}
|
||||
|
||||
void NetworkContentSocketHandler::Close()
|
||||
{
|
||||
CloseConnection();
|
||||
if (this->sock == INVALID_SOCKET) return;
|
||||
|
||||
closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given packet, i.e. pass it to the right
|
||||
* parser receive command.
|
||||
@@ -167,9 +114,9 @@ bool NetworkContentSocketHandler::HandlePacket(Packet *p)
|
||||
|
||||
default:
|
||||
if (this->HasClientQuit()) {
|
||||
DEBUG(net, 0, "[tcp/content] received invalid packet type %d", type);
|
||||
Debug(net, 0, "[tcp/content] Received invalid packet type {}", type);
|
||||
} else {
|
||||
DEBUG(net, 0, "[tcp/content] received illegal packet");
|
||||
Debug(net, 0, "[tcp/content] Received illegal packet");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -220,7 +167,7 @@ bool NetworkContentSocketHandler::ReceivePackets()
|
||||
*/
|
||||
bool NetworkContentSocketHandler::ReceiveInvalidPacket(PacketContentType type)
|
||||
{
|
||||
DEBUG(net, 0, "[tcp/content] received illegal packet type %d", type);
|
||||
Debug(net, 0, "[tcp/content] Received illegal packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,16 @@
|
||||
/** Base socket handler for all Content TCP sockets */
|
||||
class NetworkContentSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
void Close() override;
|
||||
|
||||
bool ReceiveInvalidPacket(PacketContentType type);
|
||||
|
||||
/**
|
||||
* Client requesting a list of content info:
|
||||
* byte type
|
||||
* uint32 openttd version
|
||||
* uint32 openttd version (or 0xFFFFFFFF if using a list)
|
||||
* Only if the above value is 0xFFFFFFFF:
|
||||
* uint8 count
|
||||
* string branch-name ("vanilla" for upstream OpenTTD)
|
||||
* string release version (like "12.0")
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
@@ -124,7 +126,11 @@ public:
|
||||
}
|
||||
|
||||
/** On destructing of this class, the socket needs to be closed */
|
||||
virtual ~NetworkContentSocketHandler() { this->Close(); }
|
||||
virtual ~NetworkContentSocketHandler()
|
||||
{
|
||||
/* Virtual functions get called statically in destructors, so make it explicit to remove any confusion. */
|
||||
this->CloseSocket();
|
||||
}
|
||||
|
||||
bool ReceivePackets();
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ enum ContentType {
|
||||
CONTENT_TYPE_GAME = 9, ///< The content consists of a game script
|
||||
CONTENT_TYPE_GAME_LIBRARY = 10, ///< The content consists of a GS library
|
||||
CONTENT_TYPE_END, ///< Helper to mark the end of the types
|
||||
INVALID_CONTENT_TYPE = 0xFF, ///< Invalid/uninitialized content
|
||||
};
|
||||
|
||||
/** Enum with all types of TCP content packets. The order MUST not be changed **/
|
||||
@@ -57,29 +58,21 @@ struct ContentInfo {
|
||||
INVALID, ///< The content's invalid
|
||||
};
|
||||
|
||||
ContentType type; ///< Type of content
|
||||
ContentID id; ///< Unique (server side) ID for the content
|
||||
uint32 filesize; ///< Size of the file
|
||||
char filename[48]; ///< Filename (for the .tar.gz; only valid on download)
|
||||
char name[32]; ///< Name of the content
|
||||
char version[16]; ///< Version of the content
|
||||
char url[96]; ///< URL related to the content
|
||||
char description[512]; ///< Description of the content
|
||||
uint32 unique_id; ///< Unique ID; either GRF ID or shortname
|
||||
byte md5sum[16]; ///< The MD5 checksum
|
||||
uint8 dependency_count; ///< Number of dependencies
|
||||
ContentID *dependencies; ///< Malloced array of dependencies (unique server side ids)
|
||||
uint8 tag_count; ///< Number of tags
|
||||
char (*tags)[32]; ///< Malloced array of tags (strings)
|
||||
State state; ///< Whether the content info is selected (for download)
|
||||
bool upgrade; ///< This item is an upgrade
|
||||
ContentType type = INVALID_CONTENT_TYPE; ///< Type of content
|
||||
ContentID id = INVALID_CONTENT_ID; ///< Unique (server side) ID for the content
|
||||
uint32 filesize = 0; ///< Size of the file
|
||||
std::string filename; ///< Filename (for the .tar.gz; only valid on download)
|
||||
std::string name; ///< Name of the content
|
||||
std::string version; ///< Version of the content
|
||||
std::string url; ///< URL related to the content
|
||||
std::string description; ///< Description of the content
|
||||
uint32 unique_id = 0; ///< Unique ID; either GRF ID or shortname
|
||||
byte md5sum[16] = {0}; ///< The MD5 checksum
|
||||
std::vector<ContentID> dependencies; ///< The dependencies (unique server side ids)
|
||||
StringList tags; ///< Tags associated with the content
|
||||
State state = State::UNSELECTED; ///< Whether the content info is selected (for download)
|
||||
bool upgrade = false; ///< This item is an upgrade
|
||||
|
||||
ContentInfo();
|
||||
~ContentInfo();
|
||||
|
||||
void TransferFrom(ContentInfo *other);
|
||||
|
||||
size_t Size() const;
|
||||
bool IsSelected() const;
|
||||
bool IsValid() const;
|
||||
const char *GetTextfile(TextfileType type) const;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_coordinator.cpp Basic functions to receive and send Game Coordinator packets.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../date_func.h"
|
||||
#include "../../debug.h"
|
||||
#include "tcp_coordinator.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/**
|
||||
* Handle the given packet, i.e. pass it to the right.
|
||||
* parser receive command.
|
||||
* @param p The packet to handle.
|
||||
* @return True iff we should immediately handle further packets.
|
||||
*/
|
||||
bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p)
|
||||
{
|
||||
PacketCoordinatorType type = (PacketCoordinatorType)p->Recv_uint8();
|
||||
|
||||
switch (type) {
|
||||
case PACKET_COORDINATOR_GC_ERROR: return this->Receive_GC_ERROR(p);
|
||||
case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p);
|
||||
case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p);
|
||||
case PACKET_COORDINATOR_SERVER_UPDATE: return this->Receive_SERVER_UPDATE(p);
|
||||
case PACKET_COORDINATOR_CLIENT_LISTING: return this->Receive_CLIENT_LISTING(p);
|
||||
case PACKET_COORDINATOR_GC_LISTING: return this->Receive_GC_LISTING(p);
|
||||
case PACKET_COORDINATOR_CLIENT_CONNECT: return this->Receive_CLIENT_CONNECT(p);
|
||||
case PACKET_COORDINATOR_GC_CONNECTING: return this->Receive_GC_CONNECTING(p);
|
||||
case PACKET_COORDINATOR_SERCLI_CONNECT_FAILED: return this->Receive_SERCLI_CONNECT_FAILED(p);
|
||||
case PACKET_COORDINATOR_GC_CONNECT_FAILED: return this->Receive_GC_CONNECT_FAILED(p);
|
||||
case PACKET_COORDINATOR_CLIENT_CONNECTED: return this->Receive_CLIENT_CONNECTED(p);
|
||||
case PACKET_COORDINATOR_GC_DIRECT_CONNECT: return this->Receive_GC_DIRECT_CONNECT(p);
|
||||
case PACKET_COORDINATOR_GC_STUN_REQUEST: return this->Receive_GC_STUN_REQUEST(p);
|
||||
case PACKET_COORDINATOR_SERCLI_STUN_RESULT: return this->Receive_SERCLI_STUN_RESULT(p);
|
||||
case PACKET_COORDINATOR_GC_STUN_CONNECT: return this->Receive_GC_STUN_CONNECT(p);
|
||||
case PACKET_COORDINATOR_GC_NEWGRF_LOOKUP: return this->Receive_GC_NEWGRF_LOOKUP(p);
|
||||
case PACKET_COORDINATOR_GC_TURN_CONNECT: return this->Receive_GC_TURN_CONNECT(p);
|
||||
|
||||
default:
|
||||
Debug(net, 0, "[tcp/coordinator] Received invalid packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a packet at TCP level.
|
||||
* @return Whether at least one packet was received.
|
||||
*/
|
||||
bool NetworkCoordinatorSocketHandler::ReceivePackets()
|
||||
{
|
||||
/*
|
||||
* We read only a few of the packets. This allows the GUI to update when
|
||||
* a large set of servers is being received. Otherwise the interface
|
||||
* "hangs" while the game is updating the server-list.
|
||||
*
|
||||
* What arbitrary number to choose is the ultimate question though.
|
||||
*/
|
||||
Packet *p;
|
||||
static const int MAX_PACKETS_TO_RECEIVE = 42;
|
||||
int i = MAX_PACKETS_TO_RECEIVE;
|
||||
while (--i != 0 && (p = this->ReceivePacket()) != nullptr) {
|
||||
bool cont = this->HandlePacket(p);
|
||||
delete p;
|
||||
if (!cont) return true;
|
||||
}
|
||||
|
||||
return i != MAX_PACKETS_TO_RECEIVE - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for logging receiving invalid packets.
|
||||
* @param type The received packet type.
|
||||
* @return Always false, as it's an error.
|
||||
*/
|
||||
bool NetworkCoordinatorSocketHandler::ReceiveInvalidPacket(PacketCoordinatorType type)
|
||||
{
|
||||
Debug(net, 0, "[tcp/coordinator] Received illegal packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_ERROR); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_LISTING); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_LISTING); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECT); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECTING); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_SERCLI_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_CONNECT_FAILED); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_CLIENT_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_CONNECTED); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_DIRECT_CONNECT); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_REQUEST); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_SERCLI_STUN_RESULT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERCLI_STUN_RESULT); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_STUN_CONNECT); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_NEWGRF_LOOKUP); }
|
||||
bool NetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_TURN_CONNECT); }
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_coordinator.h Basic functions to receive and send TCP packets to/from the Game Coordinator server.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_TCP_COORDINATOR_H
|
||||
#define NETWORK_CORE_TCP_COORDINATOR_H
|
||||
|
||||
#include "os_abstraction.h"
|
||||
#include "tcp.h"
|
||||
#include "packet.h"
|
||||
#include "game_info.h"
|
||||
|
||||
/**
|
||||
* Enum with all types of TCP Game Coordinator packets. The order MUST not be changed.
|
||||
*
|
||||
* GC -> packets from Game Coordinator to either Client or Server.
|
||||
* SERVER -> packets from Server to Game Coordinator.
|
||||
* CLIENT -> packets from Client to Game Coordinator.
|
||||
* SERCLI -> packets from either the Server or Client to Game Coordinator.
|
||||
**/
|
||||
enum PacketCoordinatorType {
|
||||
PACKET_COORDINATOR_GC_ERROR, ///< Game Coordinator indicates there was an error.
|
||||
PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration.
|
||||
PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration.
|
||||
PACKET_COORDINATOR_SERVER_UPDATE, ///< Server sends an set intervals an update of the server.
|
||||
PACKET_COORDINATOR_CLIENT_LISTING, ///< Client is requesting a listing of all public servers.
|
||||
PACKET_COORDINATOR_GC_LISTING, ///< Game Coordinator returns a listing of all public servers.
|
||||
PACKET_COORDINATOR_CLIENT_CONNECT, ///< Client wants to connect to a server based on an invite code.
|
||||
PACKET_COORDINATOR_GC_CONNECTING, ///< Game Coordinator informs the client of the token assigned to the connection attempt.
|
||||
PACKET_COORDINATOR_SERCLI_CONNECT_FAILED, ///< Client/server tells the Game Coordinator the current connection attempt failed.
|
||||
PACKET_COORDINATOR_GC_CONNECT_FAILED, ///< Game Coordinator informs client/server it has given up on the connection attempt.
|
||||
PACKET_COORDINATOR_CLIENT_CONNECTED, ///< Client informs the Game Coordinator the connection with the server is established.
|
||||
PACKET_COORDINATOR_GC_DIRECT_CONNECT, ///< Game Coordinator tells client to directly connect to the hostname:port of the server.
|
||||
PACKET_COORDINATOR_GC_STUN_REQUEST, ///< Game Coordinator tells client/server to initiate a STUN request.
|
||||
PACKET_COORDINATOR_SERCLI_STUN_RESULT, ///< Client/server informs the Game Coordinator of the result of the STUN request.
|
||||
PACKET_COORDINATOR_GC_STUN_CONNECT, ///< Game Coordinator tells client/server to connect() reusing the STUN local address.
|
||||
PACKET_COORDINATOR_GC_NEWGRF_LOOKUP, ///< Game Coordinator informs client about NewGRF lookup table updates needed for GC_LISTING.
|
||||
PACKET_COORDINATOR_GC_TURN_CONNECT, ///< Game Coordinator tells client/server to connect to a specific TURN server.
|
||||
PACKET_COORDINATOR_END, ///< Must ALWAYS be on the end of this list!! (period)
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of connection the Game Coordinator can detect we have.
|
||||
*/
|
||||
enum ConnectionType {
|
||||
CONNECTION_TYPE_UNKNOWN, ///< The Game Coordinator hasn't informed us yet what type of connection we have.
|
||||
CONNECTION_TYPE_ISOLATED, ///< The Game Coordinator failed to find a way to connect to your server. Nobody will be able to join.
|
||||
CONNECTION_TYPE_DIRECT, ///< The Game Coordinator can directly connect to your server.
|
||||
CONNECTION_TYPE_STUN, ///< The Game Coordinator can connect to your server via a STUN request.
|
||||
CONNECTION_TYPE_TURN, ///< The Game Coordinator needs you to connect to a relay.
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of error from the Game Coordinator.
|
||||
*/
|
||||
enum NetworkCoordinatorErrorType {
|
||||
NETWORK_COORDINATOR_ERROR_UNKNOWN, ///< There was an unknown error.
|
||||
NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED, ///< Your request for registration failed.
|
||||
NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE, ///< The invite code given is invalid.
|
||||
NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE, ///< The invite code is used by another (newer) server.
|
||||
};
|
||||
|
||||
/** Base socket handler for all Game Coordinator TCP sockets. */
|
||||
class NetworkCoordinatorSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
bool ReceiveInvalidPacket(PacketCoordinatorType type);
|
||||
|
||||
/**
|
||||
* Game Coordinator indicates there was an error. This can either be a
|
||||
* permanent error causing the connection to be dropped, or in response
|
||||
* to a request that is invalid.
|
||||
*
|
||||
* uint8 Type of error (see NetworkCoordinatorErrorType).
|
||||
* string Details of the error.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_ERROR(Packet *p);
|
||||
|
||||
/**
|
||||
* Server is starting a multiplayer game and wants to let the
|
||||
* Game Coordinator know.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* uint8 Type of game (see ServerGameType).
|
||||
* uint16 Local port of the server.
|
||||
* string Invite code the server wants to use (can be empty; coordinator will assign a new invite code).
|
||||
* string Secret that belongs to the invite code (empty if invite code is empty).
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERVER_REGISTER(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator acknowledges the registration.
|
||||
*
|
||||
* string Invite code that can be used to join this server.
|
||||
* string Secret that belongs to the invite code (only needed if reusing the invite code on next SERVER_REGISTER).
|
||||
* uint8 Type of connection was detected (see ConnectionType).
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_REGISTER_ACK(Packet *p);
|
||||
|
||||
/**
|
||||
* Send an update of the current state of the server to the Game Coordinator.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* Serialized NetworkGameInfo. See game_info.hpp for details.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERVER_UPDATE(Packet *p);
|
||||
|
||||
/**
|
||||
* Client requests a list of all public servers.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* uint8 Game-info version used by this client.
|
||||
* string Revision of the client.
|
||||
* uint32 (Game Coordinator protocol >= 4) Cursor as received from GC_NEWGRF_LOOKUP, or zero.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_CLIENT_LISTING(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator replies with a list of all public servers. Multiple
|
||||
* of these packets are received after a request till all servers are
|
||||
* sent over. Last packet will have server count of 0.
|
||||
*
|
||||
* uint16 Amount of public servers in this packet.
|
||||
* For each server:
|
||||
* string Connection string for this server.
|
||||
* Serialized NetworkGameInfo. See game_info.hpp for details.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_LISTING(Packet *p);
|
||||
|
||||
/**
|
||||
* Client wants to connect to a Server.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Invite code of the Server to join.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_CLIENT_CONNECT(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator informs the Client under what token it will start the
|
||||
* attempt to connect the Server and Client together.
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
* string Invite code of the Server to join.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_CONNECTING(Packet *p);
|
||||
|
||||
/**
|
||||
* Client or Server failed to connect to the remote side.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Token to track the current connect request.
|
||||
* uint8 Tracking number to track current connect request.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERCLI_CONNECT_FAILED(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator informs the Client that it failed to find a way to
|
||||
* connect the Client to the Server. Any open connections for this token
|
||||
* should be closed now.
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_CONNECT_FAILED(Packet *p);
|
||||
|
||||
/**
|
||||
* Client informs the Game Coordinator the connection with the Server is
|
||||
* established. The Client will disconnect from the Game Coordinator next.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Token to track the current connect request.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_CLIENT_CONNECTED(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator requests that the Client makes a direct connection to
|
||||
* the indicated peer, which is a Server.
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
* uint8 Tracking number to track current connect request.
|
||||
* string Hostname of the peer.
|
||||
* uint16 Port of the peer.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_DIRECT_CONNECT(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator requests the client/server to do a STUN request to the
|
||||
* STUN server. Important is to remember the local port these STUN requests
|
||||
* are sent from, as this will be needed for later conenctions too.
|
||||
* The client/server should do multiple STUN requests for every available
|
||||
* interface that connects to the Internet (e.g., once for IPv4 and once
|
||||
* for IPv6).
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_STUN_REQUEST(Packet *p);
|
||||
|
||||
/**
|
||||
* Client/server informs the Game Coordinator the result of a STUN request.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Token to track the current connect request.
|
||||
* uint8 Interface number, as given during STUN request.
|
||||
* bool Whether the STUN connection was successful.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERCLI_STUN_RESULT(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator informs the client/server of its STUN peer (the host:ip
|
||||
* of the other side). It should start a connect() to this peer ASAP with
|
||||
* the local address as used with the STUN request.
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
* uint8 Tracking number to track current connect request.
|
||||
* uint8 Interface number, as given during STUN request.
|
||||
* string Host of the peer.
|
||||
* uint16 Port of the peer.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_STUN_CONNECT(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator informs the client of updates for the NewGRFs lookup table
|
||||
* as used by the NewGRF deserialization in GC_LISTING.
|
||||
* This packet is sent after a CLIENT_LISTING request, but before GC_LISTING.
|
||||
*
|
||||
* uint32 Lookup table cursor.
|
||||
* uint16 Number of NewGRFs in the packet, with for each of the NewGRFs:
|
||||
* uint32 Lookup table index for the NewGRF.
|
||||
* uint32 Unique NewGRF ID.
|
||||
* byte[16] MD5 checksum of the NewGRF
|
||||
* string Name of the NewGRF.
|
||||
*
|
||||
* The lookup table built using these packets are used by the deserialisation
|
||||
* of the NewGRFs for servers in the GC_LISTING. These updates are additive,
|
||||
* i.e. each update will add NewGRFs but never remove them. However, this
|
||||
* lookup table is specific to the connection with the Game Coordinator, and
|
||||
* should be considered invalid after disconnecting from the Game Coordinator.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_NEWGRF_LOOKUP(Packet *p);
|
||||
|
||||
/**
|
||||
* Game Coordinator requests that we make a connection to the indicated
|
||||
* peer, which is a TURN server.
|
||||
*
|
||||
* string Token to track the current connect request.
|
||||
* uint8 Tracking number to track current connect request.
|
||||
* string Ticket to hand over to the TURN server.
|
||||
* string Connection string of the TURN server.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_GC_TURN_CONNECT(Packet *p);
|
||||
|
||||
bool HandlePacket(Packet *p);
|
||||
public:
|
||||
/**
|
||||
* Create a new cs socket handler for a given cs.
|
||||
* @param s The socket we are connected with.
|
||||
*/
|
||||
NetworkCoordinatorSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {}
|
||||
|
||||
bool ReceivePackets();
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_COORDINATOR_H */
|
||||
@@ -48,10 +48,10 @@ NetworkRecvStatus NetworkGameSocketHandler::CloseConnection(bool error)
|
||||
_networking = false;
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_LOSTCONNECTION, INVALID_STRING_ID, WL_CRITICAL);
|
||||
|
||||
return NETWORK_RECV_STATUS_CONN_LOST;
|
||||
return this->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
|
||||
}
|
||||
|
||||
return this->CloseConnection(error ? NETWORK_RECV_STATUS_SERVER_ERROR : NETWORK_RECV_STATUS_CONN_LOST);
|
||||
return this->CloseConnection(NETWORK_RECV_STATUS_CONNECTION_LOST);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +73,6 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
|
||||
case PACKET_SERVER_ERROR: return this->Receive_SERVER_ERROR(p);
|
||||
case PACKET_CLIENT_GAME_INFO: return this->Receive_CLIENT_GAME_INFO(p);
|
||||
case PACKET_SERVER_GAME_INFO: return this->Receive_SERVER_GAME_INFO(p);
|
||||
case PACKET_CLIENT_COMPANY_INFO: return this->Receive_CLIENT_COMPANY_INFO(p);
|
||||
case PACKET_SERVER_COMPANY_INFO: return this->Receive_SERVER_COMPANY_INFO(p);
|
||||
case PACKET_SERVER_CLIENT_INFO: return this->Receive_SERVER_CLIENT_INFO(p);
|
||||
case PACKET_SERVER_NEED_GAME_PASSWORD: return this->Receive_SERVER_NEED_GAME_PASSWORD(p);
|
||||
case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p);
|
||||
@@ -96,6 +94,7 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
|
||||
case PACKET_SERVER_COMMAND: return this->Receive_SERVER_COMMAND(p);
|
||||
case PACKET_CLIENT_CHAT: return this->Receive_CLIENT_CHAT(p);
|
||||
case PACKET_SERVER_CHAT: return this->Receive_SERVER_CHAT(p);
|
||||
case PACKET_SERVER_EXTERNAL_CHAT: return this->Receive_SERVER_EXTERNAL_CHAT(p);
|
||||
case PACKET_CLIENT_SET_PASSWORD: return this->Receive_CLIENT_SET_PASSWORD(p);
|
||||
case PACKET_CLIENT_SET_NAME: return this->Receive_CLIENT_SET_NAME(p);
|
||||
case PACKET_CLIENT_QUIT: return this->Receive_CLIENT_QUIT(p);
|
||||
@@ -117,9 +116,9 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
|
||||
this->CloseConnection();
|
||||
|
||||
if (this->HasClientQuit()) {
|
||||
DEBUG(net, 0, "[tcp/game] received invalid packet type %d from client %d", type, this->client_id);
|
||||
Debug(net, 0, "[tcp/game] Received invalid packet type {} from client {}", type, this->client_id);
|
||||
} else {
|
||||
DEBUG(net, 0, "[tcp/game] received illegal packet from client %d", this->client_id);
|
||||
Debug(net, 0, "[tcp/game] Received illegal packet from client {}", this->client_id);
|
||||
}
|
||||
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
}
|
||||
@@ -151,7 +150,7 @@ NetworkRecvStatus NetworkGameSocketHandler::ReceivePackets()
|
||||
*/
|
||||
NetworkRecvStatus NetworkGameSocketHandler::ReceiveInvalidPacket(PacketGameType type)
|
||||
{
|
||||
DEBUG(net, 0, "[tcp/game] received illegal packet type %d from client %d", type, this->client_id);
|
||||
Debug(net, 0, "[tcp/game] Received illegal packet type {} from client {}", type, this->client_id);
|
||||
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
}
|
||||
|
||||
@@ -161,8 +160,6 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_JOIN(Packet *p) { ret
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_ERROR); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_INFO); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_GAME_INFO); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_INFO); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_COMPANY_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_COMPANY_INFO); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_CLIENT_INFO); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_GAME_PASSWORD); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); }
|
||||
@@ -184,6 +181,7 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet *p) {
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_COMMAND); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_CHAT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_CHAT); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_CHAT); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_EXTERNAL_CHAT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_EXTERNAL_CHAT); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SET_PASSWORD); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SET_NAME); }
|
||||
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_QUIT); }
|
||||
|
||||
+15
-38
@@ -38,9 +38,9 @@ enum PacketGameType {
|
||||
PACKET_CLIENT_JOIN, ///< The client telling the server it wants to join.
|
||||
PACKET_SERVER_ERROR, ///< Server sending an error message to the client.
|
||||
|
||||
/* Packets used for the pre-game lobby. */
|
||||
PACKET_CLIENT_COMPANY_INFO, ///< Request information about all companies.
|
||||
PACKET_SERVER_COMPANY_INFO, ///< Information about a single company.
|
||||
/* Unused packet types, formerly used for the pre-game lobby. */
|
||||
PACKET_CLIENT_UNUSED, ///< Unused.
|
||||
PACKET_SERVER_UNUSED, ///< Unused.
|
||||
|
||||
/* Packets used to get the game info. */
|
||||
PACKET_SERVER_GAME_INFO, ///< Information about the server.
|
||||
@@ -99,6 +99,7 @@ enum PacketGameType {
|
||||
/* Human communication! */
|
||||
PACKET_CLIENT_CHAT, ///< Client said something that should be distributed.
|
||||
PACKET_SERVER_CHAT, ///< Server distributing the message of a client (or itself).
|
||||
PACKET_SERVER_EXTERNAL_CHAT, ///< Server distributing the message from external source.
|
||||
|
||||
/* Remote console. */
|
||||
PACKET_CLIENT_RCON, ///< Client asks the server to execute some command.
|
||||
@@ -200,40 +201,6 @@ protected:
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_SERVER_GAME_INFO(Packet *p);
|
||||
|
||||
/**
|
||||
* Request company information (in detail).
|
||||
* @param p The packet that was just received.
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_CLIENT_COMPANY_INFO(Packet *p);
|
||||
|
||||
/**
|
||||
* Sends information about the companies (one packet per company):
|
||||
* uint8 Version of the structure of this packet (NETWORK_COMPANY_INFO_VERSION).
|
||||
* bool Contains data (false marks the end of updates).
|
||||
* uint8 ID of the company.
|
||||
* string Name of the company.
|
||||
* uint32 Year the company was inaugurated.
|
||||
* uint64 Value.
|
||||
* uint64 Money.
|
||||
* uint64 Income.
|
||||
* uint16 Performance (last quarter).
|
||||
* bool Company is password protected.
|
||||
* uint16 Number of trains.
|
||||
* uint16 Number of lorries.
|
||||
* uint16 Number of busses.
|
||||
* uint16 Number of planes.
|
||||
* uint16 Number of ships.
|
||||
* uint16 Number of train stations.
|
||||
* uint16 Number of lorry stations.
|
||||
* uint16 Number of bus stops.
|
||||
* uint16 Number of airports and heliports.
|
||||
* uint16 Number of harbours.
|
||||
* bool Company is an AI.
|
||||
* string Client names (comma separated list)
|
||||
* @param p The packet that was just received.
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_SERVER_COMPANY_INFO(Packet *p);
|
||||
|
||||
/**
|
||||
* Send information about a client:
|
||||
* uint32 ID of the client (always unique on a server. 1 = server, 0 is invalid).
|
||||
@@ -274,7 +241,7 @@ protected:
|
||||
virtual NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p);
|
||||
|
||||
/**
|
||||
* The client is joined and ready to receive his map:
|
||||
* The client is joined and ready to receive their map:
|
||||
* uint32 Own client ID.
|
||||
* uint32 Generation seed.
|
||||
* string Network ID of the server.
|
||||
@@ -412,6 +379,16 @@ protected:
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_SERVER_CHAT(Packet *p);
|
||||
|
||||
/**
|
||||
* Sends a chat-packet for external source to the client:
|
||||
* string Name of the source this message came from.
|
||||
* uint16 TextColour to use for the message.
|
||||
* string Name of the user who sent the messsage.
|
||||
* string Message (max NETWORK_CHAT_LENGTH).
|
||||
* @param p The packet that was just received.
|
||||
*/
|
||||
virtual NetworkRecvStatus Receive_SERVER_EXTERNAL_CHAT(Packet *p);
|
||||
|
||||
/**
|
||||
* Set the password for the clients current company:
|
||||
* string The password.
|
||||
|
||||
@@ -32,7 +32,7 @@ static std::vector<NetworkHTTPSocketHandler *> _http_connections;
|
||||
* @param depth the depth (redirect recursion) of the queries
|
||||
*/
|
||||
NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
|
||||
HTTPCallback *callback, const char *host, const char *url,
|
||||
HTTPCallback *callback, const std::string &host, const char *url,
|
||||
const char *data, int depth) :
|
||||
NetworkSocketHandler(),
|
||||
recv_pos(0),
|
||||
@@ -42,19 +42,16 @@ NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
|
||||
redirect_depth(depth),
|
||||
sock(s)
|
||||
{
|
||||
size_t bufferSize = strlen(url) + strlen(host) + strlen(GetNetworkRevisionString()) + (data == nullptr ? 0 : strlen(data)) + 128;
|
||||
char *buffer = AllocaM(char, bufferSize);
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] requesting %s%s", host, url);
|
||||
Debug(net, 5, "[tcp/http] Requesting {}{}", host, url);
|
||||
std::string request;
|
||||
if (data != nullptr) {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, GetNetworkRevisionString(), (int)strlen(data), data);
|
||||
request = fmt::format("POST {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}\r\n", url, host, GetNetworkRevisionString(), strlen(data), data);
|
||||
} else {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\n\r\n", url, host, GetNetworkRevisionString());
|
||||
request = fmt::format("GET {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: OpenTTD/{}\r\n\r\n", url, host, GetNetworkRevisionString());
|
||||
}
|
||||
|
||||
ssize_t size = strlen(buffer);
|
||||
ssize_t res = send(this->sock, (const char*)buffer, size, 0);
|
||||
if (res != size) {
|
||||
ssize_t res = send(this->sock, request.data(), (int)request.size(), 0);
|
||||
if (res != (ssize_t)request.size()) {
|
||||
/* Sending all data failed. Socket can't handle this little bit
|
||||
* of information? Just fall back to the old system! */
|
||||
this->callback->OnFailure();
|
||||
@@ -68,24 +65,25 @@ NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
|
||||
/** Free whatever needs to be freed. */
|
||||
NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
|
||||
{
|
||||
this->CloseConnection();
|
||||
this->CloseSocket();
|
||||
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
free(this->data);
|
||||
}
|
||||
|
||||
NetworkRecvStatus NetworkHTTPSocketHandler::CloseConnection(bool error)
|
||||
/**
|
||||
* Close the actual socket of the connection.
|
||||
*/
|
||||
void NetworkHTTPSocketHandler::CloseSocket()
|
||||
{
|
||||
NetworkSocketHandler::CloseConnection(error);
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to simplify the error handling.
|
||||
* @param msg the error message to show.
|
||||
*/
|
||||
#define return_error(msg) { DEBUG(net, 0, msg); return -1; }
|
||||
#define return_error(msg) { Debug(net, 1, msg); return -1; }
|
||||
|
||||
static const char * const NEWLINE = "\r\n"; ///< End of line marker
|
||||
static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
|
||||
@@ -112,7 +110,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
/* We expect a HTTP/1.[01] reply */
|
||||
if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
|
||||
strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
|
||||
return_error("[tcp/http] received invalid HTTP reply");
|
||||
return_error("[tcp/http] Received invalid HTTP reply");
|
||||
}
|
||||
|
||||
char *status = this->recv_buffer + strlen(HTTP_1_0);
|
||||
@@ -121,7 +119,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
|
||||
/* Get the length of the document to receive */
|
||||
char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
|
||||
if (length == nullptr) return_error("[tcp/http] missing 'content-length' header");
|
||||
if (length == nullptr) return_error("[tcp/http] Missing 'content-length' header");
|
||||
|
||||
/* Skip the header */
|
||||
length += strlen(CONTENT_LENGTH);
|
||||
@@ -139,9 +137,9 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
/* Make sure we're going to download at least something;
|
||||
* zero sized files are, for OpenTTD's purposes, always
|
||||
* wrong. You can't have gzips of 0 bytes! */
|
||||
if (len == 0) return_error("[tcp/http] refusing to download 0 bytes");
|
||||
if (len == 0) return_error("[tcp/http] Refusing to download 0 bytes");
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] downloading %i bytes", len);
|
||||
Debug(net, 7, "[tcp/http] Downloading {} bytes", len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -154,15 +152,15 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
*strstr(status, NEWLINE) = '\0';
|
||||
DEBUG(net, 0, "[tcp/http] unhandled status reply %s", status);
|
||||
Debug(net, 1, "[tcp/http] Unhandled status reply {}", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this->redirect_depth == 5) return_error("[tcp/http] too many redirects, looping redirects?");
|
||||
if (this->redirect_depth == 5) return_error("[tcp/http] Too many redirects, looping redirects?");
|
||||
|
||||
/* Redirect to other URL */
|
||||
char *uri = strcasestr(this->recv_buffer, LOCATION);
|
||||
if (uri == nullptr) return_error("[tcp/http] missing 'location' header for redirect");
|
||||
if (uri == nullptr) return_error("[tcp/http] Missing 'location' header for redirect");
|
||||
|
||||
uri += strlen(LOCATION);
|
||||
|
||||
@@ -171,7 +169,7 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
char *end_of_line = strstr(uri, NEWLINE);
|
||||
*end_of_line = '\0';
|
||||
|
||||
DEBUG(net, 6, "[tcp/http] redirecting to %s", uri);
|
||||
Debug(net, 7, "[tcp/http] Redirecting to {}", uri);
|
||||
|
||||
int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
|
||||
if (ret != 0) return ret;
|
||||
@@ -194,20 +192,20 @@ int NetworkHTTPSocketHandler::HandleHeader()
|
||||
/* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
|
||||
{
|
||||
char *hname = strstr(uri, "://");
|
||||
if (hname == nullptr) return_error("[tcp/http] invalid location");
|
||||
if (hname == nullptr) return_error("[tcp/http] Invalid location");
|
||||
|
||||
hname += 3;
|
||||
|
||||
char *url = strchr(hname, '/');
|
||||
if (url == nullptr) return_error("[tcp/http] invalid location");
|
||||
if (url == nullptr) return_error("[tcp/http] Invalid location");
|
||||
|
||||
*url = '\0';
|
||||
|
||||
NetworkAddress address = ParseConnectionString(hname, 80);
|
||||
std::string hostname = std::string(hname);
|
||||
|
||||
/* Restore the URL. */
|
||||
*url = '/';
|
||||
new NetworkHTTPContentConnecter(address, callback, url, data, depth);
|
||||
new NetworkHTTPContentConnecter(hostname, callback, url, data, depth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -228,7 +226,7 @@ int NetworkHTTPSocketHandler::Receive()
|
||||
NetworkError err = NetworkError::GetLast();
|
||||
if (!err.WouldBlock()) {
|
||||
/* Something went wrong... */
|
||||
if (!err.IsConnectionReset()) DEBUG(net, 0, "recv failed with error %s", err.AsString());
|
||||
if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
|
||||
return -1;
|
||||
}
|
||||
/* Connection would block, so stop for now */
|
||||
@@ -256,7 +254,7 @@ int NetworkHTTPSocketHandler::Receive()
|
||||
|
||||
if (end_of_header == nullptr) {
|
||||
if (read == lengthof(this->recv_buffer)) {
|
||||
DEBUG(net, 0, "[tcp/http] header too big");
|
||||
Debug(net, 1, "[tcp/http] Header too big");
|
||||
return -1;
|
||||
}
|
||||
this->recv_pos = read;
|
||||
@@ -313,7 +311,7 @@ int NetworkHTTPSocketHandler::Receive()
|
||||
if (ret < 0) cur->callback->OnFailure();
|
||||
if (ret <= 0) {
|
||||
/* Then... the connection can be closed */
|
||||
cur->CloseConnection();
|
||||
cur->CloseSocket();
|
||||
iter = _http_connections.erase(iter);
|
||||
delete cur;
|
||||
continue;
|
||||
|
||||
+12
-12
@@ -58,10 +58,10 @@ public:
|
||||
return this->sock != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
void CloseSocket();
|
||||
|
||||
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback,
|
||||
const char *host, const char *url, const char *data, int depth);
|
||||
const std::string &host, const char *url, const char *data, int depth);
|
||||
|
||||
~NetworkHTTPSocketHandler();
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
|
||||
/** Connect with a HTTP server and do ONE query. */
|
||||
class NetworkHTTPContentConnecter : TCPConnecter {
|
||||
std::string hostname; ///< Hostname we are connecting to.
|
||||
HTTPCallback *callback; ///< Callback to tell that we received some data (or won't).
|
||||
const char *url; ///< The URL we want to get at the server.
|
||||
const char *data; ///< The data to send
|
||||
@@ -81,16 +82,15 @@ class NetworkHTTPContentConnecter : TCPConnecter {
|
||||
public:
|
||||
/**
|
||||
* Start the connecting.
|
||||
* @param address the address to connect to
|
||||
* @param callback the callback for HTTP retrieval
|
||||
* @param url the url at the server
|
||||
* @param data the data to send
|
||||
* @param depth the depth (redirect recursion) of the queries
|
||||
* @param hostname The hostname to connect to.
|
||||
* @param callback The callback for HTTP retrieval.
|
||||
* @param url The url at the server.
|
||||
* @param data The data to send.
|
||||
* @param depth The depth (redirect recursion) of the queries.
|
||||
*/
|
||||
NetworkHTTPContentConnecter(const NetworkAddress &address,
|
||||
HTTPCallback *callback, const char *url,
|
||||
const char *data = nullptr, int depth = 0) :
|
||||
TCPConnecter(address),
|
||||
NetworkHTTPContentConnecter(const std::string &hostname, HTTPCallback *callback, const char *url, const char *data = nullptr, int depth = 0) :
|
||||
TCPConnecter(hostname, 80),
|
||||
hostname(hostname),
|
||||
callback(callback),
|
||||
url(stredup(url)),
|
||||
data(data),
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
new NetworkHTTPSocketHandler(s, this->callback, this->address.GetHostname(), this->url, this->data, this->depth);
|
||||
new NetworkHTTPSocketHandler(s, this->callback, this->hostname, this->url, this->data, this->depth);
|
||||
/* We've relinquished control of data now. */
|
||||
this->data = nullptr;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,42 @@ class TCPListenHandler {
|
||||
static SocketList sockets;
|
||||
|
||||
public:
|
||||
static bool ValidateClient(SOCKET s, NetworkAddress &address)
|
||||
{
|
||||
/* Check if the client is banned. */
|
||||
for (const auto &entry : _network_ban_list) {
|
||||
if (address.IsInNetmask(entry)) {
|
||||
Packet p(Tban_packet);
|
||||
p.PrepareToSend();
|
||||
|
||||
Debug(net, 2, "[{}] Banned ip tried to join ({}), refused", Tsocket::GetName(), entry);
|
||||
|
||||
if (p.TransferOut<int>(send, s, 0) < 0) {
|
||||
Debug(net, 0, "[{}] send failed: {}", Tsocket::GetName(), NetworkError::GetLast().AsString());
|
||||
}
|
||||
closesocket(s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Can we handle a new client? */
|
||||
if (!Tsocket::AllowConnection()) {
|
||||
/* No more clients allowed?
|
||||
* Send to the client that we are full! */
|
||||
Packet p(Tfull_packet);
|
||||
p.PrepareToSend();
|
||||
|
||||
if (p.TransferOut<int>(send, s, 0) < 0) {
|
||||
Debug(net, 0, "[{}] send failed: {}", Tsocket::GetName(), NetworkError::GetLast().AsString());
|
||||
}
|
||||
closesocket(s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts clients from the sockets.
|
||||
* @param ls Socket to accept clients from.
|
||||
@@ -46,45 +82,11 @@ public:
|
||||
SetNonBlocking(s); // XXX error handling?
|
||||
|
||||
NetworkAddress address(sin, sin_len);
|
||||
DEBUG(net, 1, "[%s] Client connected from %s on frame %d", Tsocket::GetName(), address.GetHostname(), _frame_counter);
|
||||
Debug(net, 3, "[{}] Client connected from {} on frame {}", Tsocket::GetName(), address.GetHostname(), _frame_counter);
|
||||
|
||||
SetNoDelay(s); // XXX error handling?
|
||||
|
||||
/* Check if the client is banned */
|
||||
bool banned = false;
|
||||
for (const auto &entry : _network_ban_list) {
|
||||
banned = address.IsInNetmask(entry.c_str());
|
||||
if (banned) {
|
||||
Packet p(Tban_packet);
|
||||
p.PrepareToSend();
|
||||
|
||||
DEBUG(net, 1, "[%s] Banned ip tried to join (%s), refused", Tsocket::GetName(), entry.c_str());
|
||||
|
||||
if (p.TransferOut<int>(send, s, 0) < 0) {
|
||||
DEBUG(net, 0, "send failed with error %s", NetworkError::GetLast().AsString());
|
||||
}
|
||||
closesocket(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* If this client is banned, continue with next client */
|
||||
if (banned) continue;
|
||||
|
||||
/* Can we handle a new client? */
|
||||
if (!Tsocket::AllowConnection()) {
|
||||
/* no more clients allowed?
|
||||
* Send to the client that we are full! */
|
||||
Packet p(Tfull_packet);
|
||||
p.PrepareToSend();
|
||||
|
||||
if (p.TransferOut<int>(send, s, 0) < 0) {
|
||||
DEBUG(net, 0, "send failed with error %s", NetworkError::GetLast().AsString());
|
||||
}
|
||||
closesocket(s);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Tsocket::ValidateClient(s, address)) continue;
|
||||
Tsocket::AcceptConnection(s, address);
|
||||
}
|
||||
}
|
||||
@@ -147,7 +149,7 @@ public:
|
||||
}
|
||||
|
||||
if (sockets.size() == 0) {
|
||||
DEBUG(net, 0, "[server] could not start network: could not create listening socket");
|
||||
Debug(net, 0, "Could not start network: could not create listening socket");
|
||||
ShowNetworkError(STR_NETWORK_ERROR_SERVER_START);
|
||||
return false;
|
||||
}
|
||||
@@ -162,7 +164,7 @@ public:
|
||||
closesocket(s.second);
|
||||
}
|
||||
sockets.clear();
|
||||
DEBUG(net, 1, "[%s] closed listeners", Tsocket::GetName());
|
||||
Debug(net, 5, "[{}] Closed listeners", Tsocket::GetName());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_stun.cpp Basic functions to receive and send STUN packets.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "tcp_stun.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/**
|
||||
* Helper for logging receiving invalid packets.
|
||||
* @param type The received packet type.
|
||||
* @return Always false, as it's an error.
|
||||
*/
|
||||
bool NetworkStunSocketHandler::ReceiveInvalidPacket(PacketStunType type)
|
||||
{
|
||||
Debug(net, 0, "[tcp/stun] Received illegal packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkStunSocketHandler::Receive_SERCLI_STUN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_STUN_SERCLI_STUN); }
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_stun.h Basic functions to receive and send TCP packets to/from the STUN server.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_TCP_STUN_H
|
||||
#define NETWORK_CORE_TCP_STUN_H
|
||||
|
||||
#include "os_abstraction.h"
|
||||
#include "tcp.h"
|
||||
#include "packet.h"
|
||||
|
||||
/** Enum with all types of TCP STUN packets. The order MUST not be changed. **/
|
||||
enum PacketStunType {
|
||||
PACKET_STUN_SERCLI_STUN, ///< Send a STUN request to the STUN server.
|
||||
PACKET_STUN_END, ///< Must ALWAYS be on the end of this list!! (period)
|
||||
};
|
||||
|
||||
/** Base socket handler for all STUN TCP sockets. */
|
||||
class NetworkStunSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
bool ReceiveInvalidPacket(PacketStunType type);
|
||||
|
||||
/**
|
||||
* Send a STUN request to the STUN server letting the Game Coordinator know
|
||||
* what our actually public IP:port is.
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Token to track the current STUN request.
|
||||
* uint8 Which interface number this is (for example, IPv4 or IPv6).
|
||||
* The Game Coordinator relays this number back in later packets.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERCLI_STUN(Packet *p);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new cs socket handler for a given cs.
|
||||
* @param s the socket we are connected with.
|
||||
* @param address IP etc. of the client.
|
||||
*/
|
||||
NetworkStunSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {}
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_STUN_H */
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_turn.cpp Basic functions to receive and send TURN packets.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../date_func.h"
|
||||
#include "../../debug.h"
|
||||
#include "tcp_turn.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/**
|
||||
* Handle the given packet, i.e. pass it to the right
|
||||
* parser receive command.
|
||||
* @param p the packet to handle
|
||||
* @return true if we should immediately handle further packets, false otherwise
|
||||
*/
|
||||
bool NetworkTurnSocketHandler::HandlePacket(Packet *p)
|
||||
{
|
||||
PacketTurnType type = (PacketTurnType)p->Recv_uint8();
|
||||
|
||||
switch (type) {
|
||||
case PACKET_TURN_TURN_ERROR: return this->Receive_TURN_ERROR(p);
|
||||
case PACKET_TURN_SERCLI_CONNECT: return this->Receive_SERCLI_CONNECT(p);
|
||||
case PACKET_TURN_TURN_CONNECTED: return this->Receive_TURN_CONNECTED(p);
|
||||
|
||||
default:
|
||||
Debug(net, 0, "[tcp/turn] Received invalid packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a packet at TCP level
|
||||
* @return Whether at least one packet was received.
|
||||
*/
|
||||
bool NetworkTurnSocketHandler::ReceivePackets()
|
||||
{
|
||||
Packet *p;
|
||||
static const int MAX_PACKETS_TO_RECEIVE = 4;
|
||||
int i = MAX_PACKETS_TO_RECEIVE;
|
||||
while (--i != 0 && (p = this->ReceivePacket()) != nullptr) {
|
||||
bool cont = this->HandlePacket(p);
|
||||
delete p;
|
||||
if (!cont) return true;
|
||||
}
|
||||
|
||||
return i != MAX_PACKETS_TO_RECEIVE - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for logging receiving invalid packets.
|
||||
* @param type The received packet type.
|
||||
* @return Always false, as it's an error.
|
||||
*/
|
||||
bool NetworkTurnSocketHandler::ReceiveInvalidPacket(PacketTurnType type)
|
||||
{
|
||||
Debug(net, 0, "[tcp/turn] Received illegal packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NetworkTurnSocketHandler::Receive_TURN_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_TURN_ERROR); }
|
||||
bool NetworkTurnSocketHandler::Receive_SERCLI_CONNECT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_SERCLI_CONNECT); }
|
||||
bool NetworkTurnSocketHandler::Receive_TURN_CONNECTED(Packet *p) { return this->ReceiveInvalidPacket(PACKET_TURN_TURN_CONNECTED); }
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_turn.h Basic functions to receive and send TCP packets to/from the TURN server.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_TCP_TURN_H
|
||||
#define NETWORK_CORE_TCP_TURN_H
|
||||
|
||||
#include "os_abstraction.h"
|
||||
#include "tcp.h"
|
||||
#include "packet.h"
|
||||
#include "game_info.h"
|
||||
|
||||
/** Enum with all types of TCP TURN packets. The order MUST not be changed. **/
|
||||
enum PacketTurnType {
|
||||
PACKET_TURN_TURN_ERROR, ///< TURN server is unable to relay.
|
||||
PACKET_TURN_SERCLI_CONNECT, ///< Client or server is connecting to the TURN server.
|
||||
PACKET_TURN_TURN_CONNECTED, ///< TURN server indicates the socket is now being relayed.
|
||||
PACKET_TURN_END, ///< Must ALWAYS be on the end of this list!! (period)
|
||||
};
|
||||
|
||||
/** Base socket handler for all TURN TCP sockets. */
|
||||
class NetworkTurnSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
bool ReceiveInvalidPacket(PacketTurnType type);
|
||||
|
||||
/**
|
||||
* TURN server was unable to connect the client or server based on the
|
||||
* token. Most likely cause is an invalid token or the other side that
|
||||
* hasn't connected in a reasonable amount of time.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_TURN_ERROR(Packet *p);
|
||||
|
||||
/**
|
||||
* Client or servers wants to connect to the TURN server (on request by
|
||||
* the Game Coordinator).
|
||||
*
|
||||
* uint8 Game Coordinator protocol version.
|
||||
* string Token to track the current TURN request.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_SERCLI_CONNECT(Packet *p);
|
||||
|
||||
/**
|
||||
* TURN server has connected client and server together and will now relay
|
||||
* all packets to each other. No further TURN packets should be send over
|
||||
* this socket, and the socket should be handed over to the game protocol.
|
||||
*
|
||||
* string Hostname of the peer. This can be used to check if a client is not banned etc.
|
||||
*
|
||||
* @param p The packet that was just received.
|
||||
* @return True upon success, otherwise false.
|
||||
*/
|
||||
virtual bool Receive_TURN_CONNECTED(Packet *p);
|
||||
|
||||
bool HandlePacket(Packet *p);
|
||||
public:
|
||||
/**
|
||||
* Create a new cs socket handler for a given cs.
|
||||
* @param s the socket we are connected with.
|
||||
* @param address IP etc. of the client.
|
||||
*/
|
||||
NetworkTurnSocketHandler(SOCKET s = INVALID_SOCKET) : NetworkTCPSocketHandler(s) {}
|
||||
|
||||
bool ReceivePackets();
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_TURN_H */
|
||||
+13
-39
@@ -28,11 +28,11 @@ NetworkUDPSocketHandler::NetworkUDPSocketHandler(NetworkAddressList *bind)
|
||||
this->bind.push_back(addr);
|
||||
}
|
||||
} else {
|
||||
/* As hostname nullptr and port 0/nullptr don't go well when
|
||||
/* As an empty hostname and port 0 don't go well when
|
||||
* resolving it we need to add an address for each of
|
||||
* the address families we support. */
|
||||
this->bind.emplace_back(nullptr, 0, AF_INET);
|
||||
this->bind.emplace_back(nullptr, 0, AF_INET6);
|
||||
this->bind.emplace_back("", 0, AF_INET);
|
||||
this->bind.emplace_back("", 0, AF_INET6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ NetworkUDPSocketHandler::NetworkUDPSocketHandler(NetworkAddressList *bind)
|
||||
bool NetworkUDPSocketHandler::Listen()
|
||||
{
|
||||
/* Make sure socket is closed */
|
||||
this->Close();
|
||||
this->CloseSocket();
|
||||
|
||||
for (NetworkAddress &addr : this->bind) {
|
||||
addr.Listen(SOCK_DGRAM, &this->sockets);
|
||||
@@ -54,9 +54,9 @@ bool NetworkUDPSocketHandler::Listen()
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given UDP socket
|
||||
* Close the actual UDP socket.
|
||||
*/
|
||||
void NetworkUDPSocketHandler::Close()
|
||||
void NetworkUDPSocketHandler::CloseSocket()
|
||||
{
|
||||
for (auto &s : this->sockets) {
|
||||
closesocket(s.second);
|
||||
@@ -64,12 +64,6 @@ void NetworkUDPSocketHandler::Close()
|
||||
this->sockets.clear();
|
||||
}
|
||||
|
||||
NetworkRecvStatus NetworkUDPSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
NetworkSocketHandler::CloseConnection(error);
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet over UDP
|
||||
* @param p the packet to send
|
||||
@@ -95,16 +89,16 @@ void NetworkUDPSocketHandler::SendPacket(Packet *p, NetworkAddress *recv, bool a
|
||||
/* Enable broadcast */
|
||||
unsigned long val = 1;
|
||||
if (setsockopt(s.second, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) {
|
||||
DEBUG(net, 1, "[udp] setting broadcast failed with: %s", NetworkError::GetLast().AsString());
|
||||
Debug(net, 1, "Setting broadcast mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the buffer */
|
||||
ssize_t res = p->TransferOut<int>(sendto, s.second, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
|
||||
DEBUG(net, 7, "[udp] sendto(%s)", send.GetAddressAsString().c_str());
|
||||
Debug(net, 7, "sendto({})", send.GetAddressAsString());
|
||||
|
||||
/* Check for any errors, but ignore it otherwise */
|
||||
if (res == -1) DEBUG(net, 1, "[udp] sendto(%s) failed with: %s", send.GetAddressAsString().c_str(), NetworkError::GetLast().AsString());
|
||||
if (res == -1) Debug(net, 1, "sendto({}) failed: {}", send.GetAddressAsString(), NetworkError::GetLast().AsString());
|
||||
|
||||
if (!all) break;
|
||||
}
|
||||
@@ -137,7 +131,7 @@ void NetworkUDPSocketHandler::ReceivePackets()
|
||||
/* If the size does not match the packet must be corrupted.
|
||||
* Otherwise it will be marked as corrupted later on. */
|
||||
if (!p.ParsePacketSize() || (size_t)nbytes != p.Size()) {
|
||||
DEBUG(net, 1, "received a packet with mismatching size from %s", address.GetAddressAsString().c_str());
|
||||
Debug(net, 1, "Received a packet with mismatching size from {}", address.GetAddressAsString());
|
||||
continue;
|
||||
}
|
||||
p.PrepareToRead();
|
||||
@@ -165,22 +159,12 @@ void NetworkUDPSocketHandler::HandleUDPPacket(Packet *p, NetworkAddress *client_
|
||||
switch (this->HasClientQuit() ? PACKET_UDP_END : type) {
|
||||
case PACKET_UDP_CLIENT_FIND_SERVER: this->Receive_CLIENT_FIND_SERVER(p, client_addr); break;
|
||||
case PACKET_UDP_SERVER_RESPONSE: this->Receive_SERVER_RESPONSE(p, client_addr); break;
|
||||
case PACKET_UDP_CLIENT_DETAIL_INFO: this->Receive_CLIENT_DETAIL_INFO(p, client_addr); break;
|
||||
case PACKET_UDP_SERVER_DETAIL_INFO: this->Receive_SERVER_DETAIL_INFO(p, client_addr); break;
|
||||
case PACKET_UDP_SERVER_REGISTER: this->Receive_SERVER_REGISTER(p, client_addr); break;
|
||||
case PACKET_UDP_MASTER_ACK_REGISTER: this->Receive_MASTER_ACK_REGISTER(p, client_addr); break;
|
||||
case PACKET_UDP_CLIENT_GET_LIST: this->Receive_CLIENT_GET_LIST(p, client_addr); break;
|
||||
case PACKET_UDP_MASTER_RESPONSE_LIST: this->Receive_MASTER_RESPONSE_LIST(p, client_addr); break;
|
||||
case PACKET_UDP_SERVER_UNREGISTER: this->Receive_SERVER_UNREGISTER(p, client_addr); break;
|
||||
case PACKET_UDP_CLIENT_GET_NEWGRFS: this->Receive_CLIENT_GET_NEWGRFS(p, client_addr); break;
|
||||
case PACKET_UDP_SERVER_NEWGRFS: this->Receive_SERVER_NEWGRFS(p, client_addr); break;
|
||||
case PACKET_UDP_MASTER_SESSION_KEY: this->Receive_MASTER_SESSION_KEY(p, client_addr); break;
|
||||
|
||||
default:
|
||||
if (this->HasClientQuit()) {
|
||||
DEBUG(net, 0, "[udp] received invalid packet type %d from %s", type, client_addr->GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[udp] Received invalid packet type {} from {}", type, client_addr->GetAddressAsString());
|
||||
} else {
|
||||
DEBUG(net, 0, "[udp] received illegal packet from %s", client_addr->GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[udp] Received illegal packet from {}", client_addr->GetAddressAsString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -193,18 +177,8 @@ void NetworkUDPSocketHandler::HandleUDPPacket(Packet *p, NetworkAddress *client_
|
||||
*/
|
||||
void NetworkUDPSocketHandler::ReceiveInvalidPacket(PacketUDPType type, NetworkAddress *client_addr)
|
||||
{
|
||||
DEBUG(net, 0, "[udp] received packet type %d on wrong port from %s", type, client_addr->GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[udp] Received packet type {} on wrong port from {}", type, client_addr->GetAddressAsString());
|
||||
}
|
||||
|
||||
void NetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_FIND_SERVER, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_RESPONSE, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_DETAIL_INFO, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_DETAIL_INFO, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_REGISTER, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_ACK_REGISTER, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_LIST, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_RESPONSE_LIST, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_UNREGISTER, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_CLIENT_GET_NEWGRFS, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_SERVER_NEWGRFS, client_addr); }
|
||||
void NetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) { this->ReceiveInvalidPacket(PACKET_UDP_MASTER_SESSION_KEY, client_addr); }
|
||||
|
||||
+3
-139
@@ -19,28 +19,9 @@
|
||||
enum PacketUDPType {
|
||||
PACKET_UDP_CLIENT_FIND_SERVER, ///< Queries a game server for game information
|
||||
PACKET_UDP_SERVER_RESPONSE, ///< Reply of the game server with game information
|
||||
PACKET_UDP_CLIENT_DETAIL_INFO, ///< Queries a game server about details of the game, such as companies
|
||||
PACKET_UDP_SERVER_DETAIL_INFO, ///< Reply of the game server about details of the game, such as companies
|
||||
PACKET_UDP_SERVER_REGISTER, ///< Packet to register itself to the master server
|
||||
PACKET_UDP_MASTER_ACK_REGISTER, ///< Packet indicating registration has succeeded
|
||||
PACKET_UDP_CLIENT_GET_LIST, ///< Request for serverlist from master server
|
||||
PACKET_UDP_MASTER_RESPONSE_LIST, ///< Response from master server with server ip's + port's
|
||||
PACKET_UDP_SERVER_UNREGISTER, ///< Request to be removed from the server-list
|
||||
PACKET_UDP_CLIENT_GET_NEWGRFS, ///< Requests the name for a list of GRFs (GRF_ID and MD5)
|
||||
PACKET_UDP_SERVER_NEWGRFS, ///< Sends the list of NewGRF's requested.
|
||||
PACKET_UDP_MASTER_SESSION_KEY, ///< Sends a fresh session key to the client
|
||||
PACKET_UDP_END, ///< Must ALWAYS be on the end of this list!! (period)
|
||||
};
|
||||
|
||||
/** The types of server lists we can get */
|
||||
enum ServerListType {
|
||||
SLT_IPv4 = 0, ///< Get the IPv4 addresses
|
||||
SLT_IPv6 = 1, ///< Get the IPv6 addresses
|
||||
SLT_AUTODETECT, ///< Autodetect the type based on the connection
|
||||
|
||||
SLT_END = SLT_AUTODETECT, ///< End of 'arrays' marker
|
||||
};
|
||||
|
||||
/** Base socket handler for all UDP sockets */
|
||||
class NetworkUDPSocketHandler : public NetworkSocketHandler {
|
||||
protected:
|
||||
@@ -49,8 +30,6 @@ protected:
|
||||
/** The opened sockets. */
|
||||
SocketList sockets;
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
|
||||
void ReceiveInvalidPacket(PacketUDPType, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
@@ -61,136 +40,21 @@ protected:
|
||||
virtual void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* Return of server information to the client.
|
||||
* Serialized NetworkGameInfo. See game_info.h for details.
|
||||
* Response to a query letting the client know we are here.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* Query for detailed information about companies.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* Reply with detailed company information.
|
||||
* uint8 Version of the packet.
|
||||
* uint8 Number of companies.
|
||||
* For each company:
|
||||
* uint8 ID of the company.
|
||||
* string Name of the company.
|
||||
* uint32 Year the company was inaugurated.
|
||||
* uint64 Value.
|
||||
* uint64 Money.
|
||||
* uint64 Income.
|
||||
* uint16 Performance (last quarter).
|
||||
* bool Company is password protected.
|
||||
* uint16 Number of trains.
|
||||
* uint16 Number of lorries.
|
||||
* uint16 Number of busses.
|
||||
* uint16 Number of planes.
|
||||
* uint16 Number of ships.
|
||||
* uint16 Number of train stations.
|
||||
* uint16 Number of lorry stations.
|
||||
* uint16 Number of bus stops.
|
||||
* uint16 Number of airports and heliports.
|
||||
* uint16 Number of harbours.
|
||||
* bool Company is an AI.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_SERVER_DETAIL_INFO(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* Registers the server to the master server.
|
||||
* string The "welcome" message to root out other binary packets.
|
||||
* uint8 Version of the protocol.
|
||||
* uint16 The port to unregister.
|
||||
* uint64 The session key.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_SERVER_REGISTER(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The master server acknowledges the registration.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The client requests a list of servers.
|
||||
* uint8 The protocol version.
|
||||
* uint8 The type of server to look for: IPv4, IPv6 or based on the received packet.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_CLIENT_GET_LIST(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The server sends a list of servers.
|
||||
* uint8 The protocol version.
|
||||
* For each server:
|
||||
* 4 or 16 bytes of IPv4 or IPv6 address.
|
||||
* uint8 The port.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* A server unregisters itself at the master server.
|
||||
* uint8 Version of the protocol.
|
||||
* uint16 The port to unregister.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_SERVER_UNREGISTER(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The client requests information about some NewGRFs.
|
||||
* uint8 The number of NewGRFs information is requested about.
|
||||
* For each NewGRF:
|
||||
* uint32 The GRFID.
|
||||
* 16 * uint8 MD5 checksum of the GRF.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The server returns information about some NewGRFs.
|
||||
* uint8 The number of NewGRFs information is requested about.
|
||||
* For each NewGRF:
|
||||
* uint32 The GRFID.
|
||||
* 16 * uint8 MD5 checksum of the GRF.
|
||||
* string The name of the NewGRF.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
/**
|
||||
* The master server sends us a session key.
|
||||
* uint64 The session key.
|
||||
* @param p The received packet.
|
||||
* @param client_addr The origin of the packet.
|
||||
*/
|
||||
virtual void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr);
|
||||
|
||||
void HandleUDPPacket(Packet *p, NetworkAddress *client_addr);
|
||||
public:
|
||||
NetworkUDPSocketHandler(NetworkAddressList *bind = nullptr);
|
||||
|
||||
/** On destructing of this class, the socket needs to be closed */
|
||||
virtual ~NetworkUDPSocketHandler() { this->Close(); }
|
||||
virtual ~NetworkUDPSocketHandler() { this->CloseSocket(); }
|
||||
|
||||
bool Listen();
|
||||
void Close() override;
|
||||
void CloseSocket();
|
||||
|
||||
void SendPacket(Packet *p, NetworkAddress *recv, bool all = false, bool broadcast = false);
|
||||
void ReceivePackets();
|
||||
|
||||
+275
-202
@@ -14,11 +14,13 @@
|
||||
#include "../date_func.h"
|
||||
#include "network_admin.h"
|
||||
#include "network_client.h"
|
||||
#include "network_query.h"
|
||||
#include "network_server.h"
|
||||
#include "network_content.h"
|
||||
#include "network_udp.h"
|
||||
#include "network_gamelist.h"
|
||||
#include "network_base.h"
|
||||
#include "network_coordinator.h"
|
||||
#include "core/udp.h"
|
||||
#include "core/host.h"
|
||||
#include "network_gui.h"
|
||||
@@ -33,6 +35,9 @@
|
||||
#include "../core/pool_func.hpp"
|
||||
#include "../gfx_func.h"
|
||||
#include "../error.h"
|
||||
#include <charconv>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
@@ -57,7 +62,6 @@ bool _is_network_server; ///< Does this client wants to be a network-server?
|
||||
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
|
||||
ClientID _network_own_client_id; ///< Our client identifier.
|
||||
ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client.
|
||||
bool _network_need_advertise; ///< Whether we need to advertise.
|
||||
uint8 _network_reconnect; ///< Reconnect timeout
|
||||
StringList _network_bind_list; ///< The addresses to bind on.
|
||||
StringList _network_host_list; ///< The servers we know.
|
||||
@@ -75,8 +79,6 @@ uint32 _sync_frame; ///< The frame to perform the sync check.
|
||||
bool _network_first_time; ///< Whether we have finished joining or not.
|
||||
CompanyMask _network_company_passworded; ///< Bitmask of the password status of all companies.
|
||||
|
||||
/* Check whether NETWORK_NUM_LANDSCAPES is still in sync with NUM_LANDSCAPE */
|
||||
static_assert((int)NETWORK_NUM_LANDSCAPES == (int)NUM_LANDSCAPE);
|
||||
static_assert((int)NETWORK_COMPANY_NAME_LENGTH == MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH);
|
||||
|
||||
/** The amount of clients connected */
|
||||
@@ -151,9 +153,9 @@ byte NetworkSpectatorCount()
|
||||
* @param password The unhashed password we like to set ('*' or '' resets the password)
|
||||
* @return The password.
|
||||
*/
|
||||
const char *NetworkChangeCompanyPassword(CompanyID company_id, const char *password)
|
||||
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password)
|
||||
{
|
||||
if (strcmp(password, "*") == 0) password = "";
|
||||
if (password.compare("*") == 0) password = "";
|
||||
|
||||
if (_network_server) {
|
||||
NetworkServerSetCompanyPassword(company_id, password, false);
|
||||
@@ -171,33 +173,35 @@ const char *NetworkChangeCompanyPassword(CompanyID company_id, const char *passw
|
||||
* @param password_game_seed Game seed.
|
||||
* @return The hashed password.
|
||||
*/
|
||||
const char *GenerateCompanyPasswordHash(const char *password, const char *password_server_id, uint32 password_game_seed)
|
||||
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed)
|
||||
{
|
||||
if (StrEmpty(password)) return password;
|
||||
if (password.empty()) return password;
|
||||
|
||||
char salted_password[NETWORK_SERVER_ID_LENGTH];
|
||||
size_t password_length = strlen(password);
|
||||
size_t password_server_id_length = strlen(password_server_id);
|
||||
size_t password_length = password.size();
|
||||
size_t password_server_id_length = password_server_id.size();
|
||||
|
||||
/* Add the game seed and the server's ID as the salt. */
|
||||
std::ostringstream salted_password;
|
||||
/* Add the password with the server's ID and game seed as the salt. */
|
||||
for (uint i = 0; i < NETWORK_SERVER_ID_LENGTH - 1; i++) {
|
||||
char password_char = (i < password_length ? password[i] : 0);
|
||||
char server_id_char = (i < password_server_id_length ? password_server_id[i] : 0);
|
||||
char seed_char = password_game_seed >> (i % 32);
|
||||
salted_password[i] = password_char ^ server_id_char ^ seed_char;
|
||||
salted_password << (char)(password_char ^ server_id_char ^ seed_char); // Cast needed, otherwise interpreted as integer to format
|
||||
}
|
||||
|
||||
Md5 checksum;
|
||||
uint8 digest[16];
|
||||
static char hashed_password[NETWORK_SERVER_ID_LENGTH];
|
||||
|
||||
/* Generate the MD5 hash */
|
||||
checksum.Append(salted_password, sizeof(salted_password) - 1);
|
||||
std::string salted_password_string = salted_password.str();
|
||||
checksum.Append(salted_password_string.data(), salted_password_string.size());
|
||||
checksum.Finish(digest);
|
||||
|
||||
for (int di = 0; di < 16; di++) seprintf(hashed_password + di * 2, lastof(hashed_password), "%02x", digest[di]);
|
||||
std::ostringstream hashed_password;
|
||||
hashed_password << std::hex << std::setfill('0');
|
||||
for (int di = 0; di < 16; di++) hashed_password << std::setw(2) << (int)digest[di]; // Cast needed, otherwise interpreted as character to add
|
||||
|
||||
return hashed_password;
|
||||
return hashed_password.str();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +217,7 @@ bool NetworkCompanyIsPassworded(CompanyID company_id)
|
||||
/* This puts a text-message to the console, or in the future, the chat-box,
|
||||
* (to keep it all a bit more general)
|
||||
* If 'self_send' is true, this is the client who is sending the message */
|
||||
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const char *name, const char *str, int64 data)
|
||||
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const std::string &name, const std::string &str, int64 data, const std::string &data_str)
|
||||
{
|
||||
StringID strid;
|
||||
switch (action) {
|
||||
@@ -244,6 +248,7 @@ void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send,
|
||||
case NETWORK_ACTION_CHAT_COMPANY: strid = self_send ? STR_NETWORK_CHAT_TO_COMPANY : STR_NETWORK_CHAT_COMPANY; break;
|
||||
case NETWORK_ACTION_CHAT_CLIENT: strid = self_send ? STR_NETWORK_CHAT_TO_CLIENT : STR_NETWORK_CHAT_CLIENT; break;
|
||||
case NETWORK_ACTION_KICKED: strid = STR_NETWORK_MESSAGE_KICKED; break;
|
||||
case NETWORK_ACTION_EXTERNAL_CHAT: strid = STR_NETWORK_CHAT_EXTERNAL; break;
|
||||
default: strid = STR_NETWORK_CHAT_ALL; break;
|
||||
}
|
||||
|
||||
@@ -251,6 +256,7 @@ void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send,
|
||||
SetDParamStr(0, name);
|
||||
SetDParamStr(1, str);
|
||||
SetDParam(2, data);
|
||||
SetDParamStr(3, data_str);
|
||||
|
||||
/* All of these strings start with "***". These characters are interpreted as both left-to-right and
|
||||
* right-to-left characters depending on the context. As the next text might be an user's name, the
|
||||
@@ -259,17 +265,17 @@ void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send,
|
||||
char *msg_ptr = message + Utf8Encode(message, _current_text_dir == TD_LTR ? CHAR_TD_LRM : CHAR_TD_RLM);
|
||||
GetString(msg_ptr, strid, lastof(message));
|
||||
|
||||
DEBUG(desync, 1, "msg: %08x; %02x; %s", _date, _date_fract, message);
|
||||
IConsolePrintF(colour, "%s", message);
|
||||
NetworkAddChatMessage((TextColour)colour, _settings_client.gui.network_chat_timeout, "%s", message);
|
||||
Debug(desync, 1, "msg: {:08x}; {:02x}; {}", _date, _date_fract, message);
|
||||
IConsolePrint(colour, message);
|
||||
NetworkAddChatMessage(colour, _settings_client.gui.network_chat_timeout, message);
|
||||
}
|
||||
|
||||
/* Calculate the frame-lag of a client */
|
||||
uint NetworkCalculateLag(const NetworkClientSocket *cs)
|
||||
{
|
||||
int lag = cs->last_frame_server - cs->last_frame;
|
||||
/* This client has missed his ACK packet after 1 DAY_TICKS..
|
||||
* so we increase his lag for every frame that passes!
|
||||
/* This client has missed their ACK packet after 1 DAY_TICKS..
|
||||
* so we increase their lag for every frame that passes!
|
||||
* The packet can be out by a max of _net_frame_freq */
|
||||
if (cs->last_frame_server + DAY_TICKS + _settings_client.network.frame_freq < _frame_counter) {
|
||||
lag += _frame_counter - (cs->last_frame_server + DAY_TICKS + _settings_client.network.frame_freq);
|
||||
@@ -366,9 +372,7 @@ void NetworkHandlePauseChange(PauseMode prev_mode, PauseMode changed_mode)
|
||||
str = paused ? STR_NETWORK_SERVER_MESSAGE_GAME_PAUSED : STR_NETWORK_SERVER_MESSAGE_GAME_UNPAUSED;
|
||||
}
|
||||
|
||||
char buffer[DRAW_STRING_BUFFER];
|
||||
GetString(buffer, str, lastof(buffer));
|
||||
NetworkTextMessage(NETWORK_ACTION_SERVER_MESSAGE, CC_DEFAULT, false, nullptr, buffer);
|
||||
NetworkTextMessage(NETWORK_ACTION_SERVER_MESSAGE, CC_DEFAULT, false, "", GetString(str));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -449,40 +453,81 @@ static void CheckPauseOnJoin()
|
||||
CheckPauseHelper(NetworkHasJoiningClient(), PM_PAUSED_JOIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the company part ("#company" postfix) of a connecting string.
|
||||
* @param connection_string The string with the connection data.
|
||||
* @param company_id The company ID to set, if available.
|
||||
* @return A std::string_view into the connection string without the company part.
|
||||
*/
|
||||
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id)
|
||||
{
|
||||
std::string_view ip = connection_string;
|
||||
if (company_id == nullptr) return ip;
|
||||
|
||||
size_t offset = ip.find_last_of('#');
|
||||
if (offset != std::string::npos) {
|
||||
std::string_view company_string = ip.substr(offset + 1);
|
||||
ip = ip.substr(0, offset);
|
||||
|
||||
uint8 company_value;
|
||||
auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value);
|
||||
if (err == std::errc()) {
|
||||
if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) {
|
||||
if (company_value > MAX_COMPANIES || company_value == 0) {
|
||||
*company_id = COMPANY_SPECTATOR;
|
||||
} else {
|
||||
/* "#1" means the first company, which has index 0. */
|
||||
*company_id = (CompanyID)(company_value - 1);
|
||||
}
|
||||
} else {
|
||||
*company_id = (CompanyID)company_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to ip/port/company
|
||||
* Format: IP:port#company
|
||||
*
|
||||
* connection_string will be re-terminated to separate out the hostname, port will
|
||||
* be set to the port strings given by the user, inside the memory area originally
|
||||
* occupied by connection_string. Similar for company, if set.
|
||||
* Returns the IP part as a string view into the passed string. This view is
|
||||
* valid as long the passed connection string is valid. If there is no port
|
||||
* present in the connection string, the port reference will not be touched.
|
||||
* When there is no company ID present in the connection string or company_id
|
||||
* is nullptr, then company ID will not be touched.
|
||||
*
|
||||
* @param connection_string The string with the connection data.
|
||||
* @param port The port reference to set.
|
||||
* @param company_id The company ID to set, if available.
|
||||
* @return A std::string_view into the connection string with the (IP) address part.
|
||||
*/
|
||||
void ParseFullConnectionString(const char **company, const char **port, char *connection_string)
|
||||
std::string_view ParseFullConnectionString(const std::string &connection_string, uint16 &port, CompanyID *company_id)
|
||||
{
|
||||
bool ipv6 = (strchr(connection_string, ':') != strrchr(connection_string, ':'));
|
||||
for (char *p = connection_string; *p != '\0'; p++) {
|
||||
switch (*p) {
|
||||
case '[':
|
||||
ipv6 = true;
|
||||
break;
|
||||
std::string_view ip = ParseCompanyFromConnectionString(connection_string, company_id);
|
||||
|
||||
case ']':
|
||||
ipv6 = false;
|
||||
break;
|
||||
|
||||
case '#':
|
||||
if (company == nullptr) continue;
|
||||
*company = p + 1;
|
||||
*p = '\0';
|
||||
break;
|
||||
|
||||
case ':':
|
||||
if (ipv6) break;
|
||||
*port = p + 1;
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
size_t port_offset = ip.find_last_of(':');
|
||||
size_t ipv6_close = ip.find_last_of(']');
|
||||
if (port_offset != std::string::npos && (ipv6_close == std::string::npos || ipv6_close < port_offset)) {
|
||||
std::string_view port_string = ip.substr(port_offset + 1);
|
||||
ip = ip.substr(0, port_offset);
|
||||
std::from_chars(port_string.data(), port_string.data() + port_string.size(), port);
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a connection string. That is, ensure there is a port in the string.
|
||||
* @param connection_string The connection string to normalize.
|
||||
* @param default_port The port to use if none is given.
|
||||
* @return The normalized connection string.
|
||||
*/
|
||||
std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port)
|
||||
{
|
||||
uint16 port = default_port;
|
||||
std::string_view ip = ParseFullConnectionString(connection_string, port);
|
||||
return std::string(ip) + ":" + std::to_string(port);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -493,41 +538,11 @@ void ParseFullConnectionString(const char **company, const char **port, char *co
|
||||
* @param default_port The default port to set port to if not in connection_string.
|
||||
* @return A valid NetworkAddress of the parsed information.
|
||||
*/
|
||||
NetworkAddress ParseConnectionString(const std::string &connection_string, int default_port)
|
||||
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port)
|
||||
{
|
||||
char internal_connection_string[NETWORK_HOSTNAME_PORT_LENGTH];
|
||||
strecpy(internal_connection_string, connection_string.c_str(), lastof(internal_connection_string));
|
||||
|
||||
const char *port = nullptr;
|
||||
ParseFullConnectionString(nullptr, &port, internal_connection_string);
|
||||
|
||||
int rport = port != nullptr ? atoi(port) : default_port;
|
||||
return NetworkAddress(internal_connection_string, rport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing either "hostname" or "hostname:ip" to a
|
||||
* NetworkAddress, where the string can be postfixed with "#company" to
|
||||
* indicate the requested company.
|
||||
*
|
||||
* @param company Pointer to the company variable to set iff indicted.
|
||||
* @param connection_string The string to parse.
|
||||
* @param default_port The default port to set port to if not in connection_string.
|
||||
* @return A valid NetworkAddress of the parsed information.
|
||||
*/
|
||||
NetworkAddress ParseGameConnectionString(CompanyID *company, const std::string &connection_string, int default_port)
|
||||
{
|
||||
char internal_connection_string[NETWORK_HOSTNAME_PORT_LENGTH + 4]; // 4 extra for the "#" and company
|
||||
strecpy(internal_connection_string, connection_string.c_str(), lastof(internal_connection_string));
|
||||
|
||||
const char *port_s = nullptr;
|
||||
const char *company_s = nullptr;
|
||||
ParseFullConnectionString(&company_s, &port_s, internal_connection_string);
|
||||
|
||||
if (company_s != nullptr) *company = (CompanyID)atoi(company_s);
|
||||
|
||||
int port = port_s != nullptr ? atoi(port_s) : default_port;
|
||||
return NetworkAddress(internal_connection_string, port);
|
||||
uint16 port = default_port;
|
||||
std::string_view ip = ParseFullConnectionString(connection_string, port);
|
||||
return NetworkAddress(ip, port);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,13 +584,19 @@ void NetworkClose(bool close_admins)
|
||||
}
|
||||
|
||||
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
|
||||
cs->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
|
||||
cs->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
|
||||
}
|
||||
ServerNetworkGameSocketHandler::CloseListeners();
|
||||
ServerNetworkAdminSocketHandler::CloseListeners();
|
||||
} else if (MyClient::my_client != nullptr) {
|
||||
MyClient::SendQuit();
|
||||
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
|
||||
|
||||
_network_coordinator_client.CloseConnection();
|
||||
} else {
|
||||
if (MyClient::my_client != nullptr) {
|
||||
MyClient::SendQuit();
|
||||
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
|
||||
}
|
||||
|
||||
_network_coordinator_client.CloseAllConnections();
|
||||
}
|
||||
|
||||
TCPConnecter::KillAll();
|
||||
@@ -585,7 +606,7 @@ void NetworkClose(bool close_admins)
|
||||
|
||||
NetworkFreeLocalCommandQueue();
|
||||
|
||||
free(_network_company_states);
|
||||
delete[] _network_company_states;
|
||||
_network_company_states = nullptr;
|
||||
|
||||
InitializeNetworkPools(close_admins);
|
||||
@@ -595,7 +616,6 @@ void NetworkClose(bool close_admins)
|
||||
static void NetworkInitialize(bool close_admins = true)
|
||||
{
|
||||
InitializeNetworkPools(close_admins);
|
||||
NetworkUDPInitialize();
|
||||
|
||||
_sync_frame = 0;
|
||||
_network_first_time = true;
|
||||
@@ -603,40 +623,37 @@ static void NetworkInitialize(bool close_admins = true)
|
||||
_network_reconnect = 0;
|
||||
}
|
||||
|
||||
/** Non blocking connection create to query servers */
|
||||
class TCPQueryConnecter : TCPConnecter {
|
||||
/** Non blocking connection to query servers for their game info. */
|
||||
class TCPQueryConnecter : TCPServerConnecter {
|
||||
private:
|
||||
bool request_company_info;
|
||||
std::string connection_string;
|
||||
|
||||
public:
|
||||
TCPQueryConnecter(const NetworkAddress &address, bool request_company_info) : TCPConnecter(address), request_company_info(request_company_info) {}
|
||||
TCPQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
NetworkDisconnect();
|
||||
NetworkGameList *item = NetworkGameListAddItem(connection_string);
|
||||
item->status = NGLS_OFFLINE;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
_networking = true;
|
||||
new ClientNetworkGameSocketHandler(s, address);
|
||||
MyClient::SendInformationQuery(request_company_info);
|
||||
QueryNetworkGameSocketHandler::QueryServer(s, this->connection_string);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query a server to fetch his game-info.
|
||||
* @param address the address to query.
|
||||
* @param request_company_info Whether to request company info too.
|
||||
* Query a server to fetch the game-info.
|
||||
* @param connection_string the address to query.
|
||||
*/
|
||||
void NetworkTCPQueryServer(NetworkAddress address, bool request_company_info)
|
||||
void NetworkQueryServer(const std::string &connection_string)
|
||||
{
|
||||
if (!_network_available) return;
|
||||
|
||||
NetworkDisconnect();
|
||||
NetworkInitialize();
|
||||
|
||||
new TCPQueryConnecter(address, request_company_info);
|
||||
new TCPQueryConnecter(connection_string);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,26 +661,27 @@ void NetworkTCPQueryServer(NetworkAddress address, bool request_company_info)
|
||||
* the list. If you use this function, the games will be marked
|
||||
* as manually added.
|
||||
* @param connection_string The IP:port of the server to add.
|
||||
* @param manually Whether the enter should be marked as manual added.
|
||||
* @param never_expire Whether the entry can expire (removed when no longer found in the public listing).
|
||||
* @return The entry on the game list.
|
||||
*/
|
||||
NetworkGameList *NetworkAddServer(const std::string &connection_string)
|
||||
NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually, bool never_expire)
|
||||
{
|
||||
if (connection_string.empty()) return nullptr;
|
||||
|
||||
NetworkAddress address = ParseConnectionString(connection_string, NETWORK_DEFAULT_PORT);
|
||||
|
||||
/* Ensure the item already exists in the list */
|
||||
NetworkGameList *item = NetworkGameListAddItem(address);
|
||||
if (StrEmpty(item->info.server_name)) {
|
||||
NetworkGameList *item = NetworkGameListAddItem(connection_string);
|
||||
if (item->info.server_name.empty()) {
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
address.GetAddressAsString(item->info.server_name, lastof(item->info.server_name));
|
||||
item->manually = true;
|
||||
item->info.server_name = connection_string;
|
||||
|
||||
NetworkRebuildHostList();
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
NetworkQueryServer(connection_string);
|
||||
}
|
||||
|
||||
NetworkTCPQueryServer(address);
|
||||
if (manually) item->manually = true;
|
||||
if (never_expire) item->version = INT32_MAX;
|
||||
|
||||
return item;
|
||||
}
|
||||
@@ -693,14 +711,17 @@ void NetworkRebuildHostList()
|
||||
_network_host_list.clear();
|
||||
|
||||
for (NetworkGameList *item = _network_game_list; item != nullptr; item = item->next) {
|
||||
if (item->manually) _network_host_list.emplace_back(item->address.GetAddressAsString(false));
|
||||
if (item->manually) _network_host_list.emplace_back(item->connection_string);
|
||||
}
|
||||
}
|
||||
|
||||
/** Non blocking connection create to actually connect to servers */
|
||||
class TCPClientConnecter : TCPConnecter {
|
||||
class TCPClientConnecter : TCPServerConnecter {
|
||||
private:
|
||||
std::string connection_string;
|
||||
|
||||
public:
|
||||
TCPClientConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
|
||||
TCPClientConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
@@ -710,7 +731,7 @@ public:
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
_networking = true;
|
||||
new ClientNetworkGameSocketHandler(s, this->address);
|
||||
new ClientNetworkGameSocketHandler(s, this->connection_string);
|
||||
IConsoleCmdExec("exec scripts/on_client.scr 0");
|
||||
NetworkClient_Connected();
|
||||
}
|
||||
@@ -733,37 +754,15 @@ public:
|
||||
* @param join_company_password The password for the company.
|
||||
* @return Whether the join has started.
|
||||
*/
|
||||
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const char *join_server_password, const char *join_company_password)
|
||||
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password)
|
||||
{
|
||||
CompanyID join_as = default_company;
|
||||
NetworkAddress address = ParseGameConnectionString(&join_as, connection_string, NETWORK_DEFAULT_PORT);
|
||||
std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT, &join_as).connection_string;
|
||||
|
||||
if (join_as != COMPANY_NEW_COMPANY && join_as != COMPANY_SPECTATOR) {
|
||||
join_as--;
|
||||
if (join_as >= MAX_COMPANIES) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return NetworkClientConnectGame(address, join_as, join_server_password, join_company_password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a client to the server at the given address.
|
||||
* See the overloaded NetworkClientConnectGame for more details.
|
||||
*
|
||||
* @param address The network address of the server to join to.
|
||||
* @param join_as The company number to join as.
|
||||
* @param join_server_password The password for the server.
|
||||
* @param join_company_password The password for the company.
|
||||
* @return Whether the join has started.
|
||||
*/
|
||||
bool NetworkClientConnectGame(NetworkAddress &address, CompanyID join_as, const char *join_server_password, const char *join_company_password)
|
||||
{
|
||||
if (!_network_available) return false;
|
||||
if (!NetworkValidateClientName()) return false;
|
||||
if (!NetworkValidateOurClientName()) return false;
|
||||
|
||||
_network_join.address = address;
|
||||
_network_join.connection_string = resolved_connection_string;
|
||||
_network_join.company = join_as;
|
||||
_network_join.server_password = join_server_password;
|
||||
_network_join.company_password = join_company_password;
|
||||
@@ -792,19 +791,16 @@ void NetworkClientJoinGame()
|
||||
NetworkDisconnect();
|
||||
NetworkInitialize();
|
||||
|
||||
strecpy(_settings_client.network.last_joined, _network_join.address.GetAddressAsString(false).c_str(), lastof(_settings_client.network.last_joined));
|
||||
_settings_client.network.last_joined = _network_join.connection_string;
|
||||
_network_join_status = NETWORK_JOIN_STATUS_CONNECTING;
|
||||
ShowJoinStatusWindow();
|
||||
|
||||
new TCPClientConnecter(_network_join.address);
|
||||
new TCPClientConnecter(_network_join.connection_string);
|
||||
}
|
||||
|
||||
static void NetworkInitGameInfo()
|
||||
{
|
||||
if (StrEmpty(_settings_client.network.server_name)) {
|
||||
strecpy(_settings_client.network.server_name, "Unnamed Server", lastof(_settings_client.network.server_name));
|
||||
}
|
||||
|
||||
FillStaticNetworkServerGameInfo();
|
||||
/* The server is a client too */
|
||||
_network_game_info.clients_on = _network_dedicated ? 0 : 1;
|
||||
|
||||
@@ -813,7 +809,49 @@ static void NetworkInitGameInfo()
|
||||
NetworkClientInfo *ci = new NetworkClientInfo(CLIENT_ID_SERVER);
|
||||
ci->client_playas = _network_dedicated ? COMPANY_SPECTATOR : COMPANY_FIRST;
|
||||
|
||||
strecpy(ci->client_name, _settings_client.network.client_name, lastof(ci->client_name));
|
||||
ci->client_name = _settings_client.network.client_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the given server name in place, i.e. remove leading and trailing spaces.
|
||||
* After the trim check whether the server name is not empty.
|
||||
* When the server name is empty a GUI error message is shown telling the
|
||||
* user to set the servername and this function returns false.
|
||||
*
|
||||
* @param server_name The server name to validate. It will be trimmed of leading
|
||||
* and trailing spaces.
|
||||
* @return True iff the server name is valid.
|
||||
*/
|
||||
bool NetworkValidateServerName(std::string &server_name)
|
||||
{
|
||||
StrTrimInPlace(server_name);
|
||||
if (!server_name.empty()) return true;
|
||||
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_BAD_SERVER_NAME, INVALID_STRING_ID, WL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the client and server name are set, for a dedicated server and if not set them to some default
|
||||
* value and tell the user to change this as soon as possible.
|
||||
* If the saved name is the default value, then the user is told to override this value too.
|
||||
* This is only meant dedicated servers, as for the other servers the GUI ensures a name has been entered.
|
||||
*/
|
||||
static void CheckClientAndServerName()
|
||||
{
|
||||
static const std::string fallback_client_name = "Unnamed Client";
|
||||
StrTrimInPlace(_settings_client.network.client_name);
|
||||
if (_settings_client.network.client_name.empty() || _settings_client.network.client_name.compare(fallback_client_name) == 0) {
|
||||
Debug(net, 1, "No \"client_name\" has been set, using \"{}\" instead. Please set this now using the \"name <new name>\" command", fallback_client_name);
|
||||
_settings_client.network.client_name = fallback_client_name;
|
||||
}
|
||||
|
||||
static const std::string fallback_server_name = "Unnamed Server";
|
||||
StrTrimInPlace(_settings_client.network.server_name);
|
||||
if (_settings_client.network.server_name.empty() || _settings_client.network.server_name.compare(fallback_server_name) == 0) {
|
||||
Debug(net, 1, "No \"server_name\" has been set, using \"{}\" instead. Please set this now using the \"server_name <new name>\" command", fallback_server_name);
|
||||
_settings_client.network.server_name = fallback_server_name;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkServerStart()
|
||||
@@ -824,22 +862,26 @@ bool NetworkServerStart()
|
||||
IConsoleCmdExec("exec scripts/pre_server.scr 0");
|
||||
if (_network_dedicated) IConsoleCmdExec("exec scripts/pre_dedicated.scr 0");
|
||||
|
||||
/* Check for the client and server names to be set, but only after the scripts had a chance to set them.*/
|
||||
if (_network_dedicated) CheckClientAndServerName();
|
||||
|
||||
NetworkDisconnect(false, false);
|
||||
NetworkInitialize(false);
|
||||
DEBUG(net, 1, "starting listeners for clients");
|
||||
NetworkUDPInitialize();
|
||||
Debug(net, 5, "Starting listeners for clients");
|
||||
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
|
||||
|
||||
/* Only listen for admins when the password isn't empty. */
|
||||
if (!StrEmpty(_settings_client.network.admin_password)) {
|
||||
DEBUG(net, 1, "starting listeners for admins");
|
||||
if (!_settings_client.network.admin_password.empty()) {
|
||||
Debug(net, 5, "Starting listeners for admins");
|
||||
if (!ServerNetworkAdminSocketHandler::Listen(_settings_client.network.server_admin_port)) return false;
|
||||
}
|
||||
|
||||
/* Try to start UDP-server */
|
||||
DEBUG(net, 1, "starting listeners for incoming server queries");
|
||||
Debug(net, 5, "Starting listeners for incoming server queries");
|
||||
NetworkUDPServerListen();
|
||||
|
||||
_network_company_states = CallocT<NetworkCompanyState>(MAX_COMPANIES);
|
||||
_network_company_states = new NetworkCompanyState[MAX_COMPANIES];
|
||||
_network_server = true;
|
||||
_networking = true;
|
||||
_frame_counter = 0;
|
||||
@@ -853,15 +895,15 @@ bool NetworkServerStart()
|
||||
|
||||
NetworkInitGameInfo();
|
||||
|
||||
if (_settings_client.network.server_game_type != SERVER_GAME_TYPE_LOCAL) {
|
||||
_network_coordinator_client.Register();
|
||||
}
|
||||
|
||||
/* execute server initialization script */
|
||||
IConsoleCmdExec("exec scripts/on_server.scr 0");
|
||||
/* if the server is dedicated ... add some other script */
|
||||
if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0");
|
||||
|
||||
/* Try to register us to the master server */
|
||||
_network_need_advertise = true;
|
||||
NetworkUDPAdvertise();
|
||||
|
||||
/* welcome possibly still connected admins - this can only happen on a dedicated server. */
|
||||
if (_network_dedicated) ServerNetworkAdminSocketHandler::WelcomeAll();
|
||||
|
||||
@@ -910,9 +952,7 @@ void NetworkDisconnect(bool blocking, bool close_admins)
|
||||
}
|
||||
}
|
||||
|
||||
if (_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(blocking);
|
||||
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
|
||||
NetworkClose(close_admins);
|
||||
|
||||
@@ -920,6 +960,29 @@ void NetworkDisconnect(bool blocking, bool close_admins)
|
||||
NetworkUDPInitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting server_game_type was updated; possibly we need to take some
|
||||
* action.
|
||||
*/
|
||||
void NetworkUpdateServerGameType()
|
||||
{
|
||||
if (!_networking) return;
|
||||
|
||||
switch (_settings_client.network.server_game_type) {
|
||||
case SERVER_GAME_TYPE_LOCAL:
|
||||
_network_coordinator_client.CloseConnection();
|
||||
break;
|
||||
|
||||
case SERVER_GAME_TYPE_INVITE_ONLY:
|
||||
case SERVER_GAME_TYPE_PUBLIC:
|
||||
_network_coordinator_client.Register();
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives something from the network.
|
||||
* @return true if everything went fine, false when the connection got closed.
|
||||
@@ -953,8 +1016,10 @@ static void NetworkSend()
|
||||
void NetworkBackgroundLoop()
|
||||
{
|
||||
_network_content_client.SendReceive();
|
||||
_network_coordinator_client.SendReceive();
|
||||
TCPConnecter::CheckCallbacks();
|
||||
NetworkHTTPSocketHandler::HTTPReceive();
|
||||
QueryNetworkGameSocketHandler::SendReceive();
|
||||
|
||||
NetworkBackgroundUDPLoop();
|
||||
}
|
||||
@@ -973,7 +1038,7 @@ void NetworkGameLoop()
|
||||
/* We don't want to log multiple times if paused. */
|
||||
static Date last_log;
|
||||
if (last_log != _date) {
|
||||
DEBUG(desync, 1, "sync: %08x; %02x; %08x; %08x", _date, _date_fract, _random.state[0], _random.state[1]);
|
||||
Debug(desync, 1, "sync: {:08x}; {:02x}; {:08x}; {:08x}", _date, _date_fract, _random.state[0], _random.state[1]);
|
||||
last_log = _date;
|
||||
}
|
||||
}
|
||||
@@ -987,7 +1052,7 @@ void NetworkGameLoop()
|
||||
static bool check_sync_state = false;
|
||||
static uint32 sync_state[2];
|
||||
if (f == nullptr && next_date == 0) {
|
||||
DEBUG(net, 0, "Cannot open commands.log");
|
||||
Debug(desync, 0, "Cannot open commands.log");
|
||||
next_date = 1;
|
||||
}
|
||||
|
||||
@@ -995,15 +1060,15 @@ void NetworkGameLoop()
|
||||
if (_date == next_date && _date_fract == next_date_fract) {
|
||||
if (cp != nullptr) {
|
||||
NetworkSendCommand(cp->tile, cp->p1, cp->p2, cp->cmd & ~CMD_FLAGS_MASK, nullptr, cp->text, cp->company);
|
||||
DEBUG(net, 0, "injecting: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, cp->tile, cp->p1, cp->p2, cp->cmd, cp->text, GetCommandName(cp->cmd));
|
||||
free(cp);
|
||||
Debug(desync, 0, "Injecting: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, cp->tile, cp->p1, cp->p2, cp->cmd, cp->text, GetCommandName(cp->cmd));
|
||||
delete cp;
|
||||
cp = nullptr;
|
||||
}
|
||||
if (check_sync_state) {
|
||||
if (sync_state[0] == _random.state[0] && sync_state[1] == _random.state[1]) {
|
||||
DEBUG(net, 0, "sync check: %08x; %02x; match", _date, _date_fract);
|
||||
Debug(desync, 0, "Sync check: {:08x}; {:02x}; match", _date, _date_fract);
|
||||
} else {
|
||||
DEBUG(net, 0, "sync check: %08x; %02x; mismatch expected {%08x, %08x}, got {%08x, %08x}",
|
||||
Debug(desync, 0, "Sync check: {:08x}; {:02x}; mismatch expected {{:08x}, {:08x}}, got {{:08x}, {:08x}}",
|
||||
_date, _date_fract, sync_state[0], sync_state[1], _random.state[0], _random.state[1]);
|
||||
NOT_REACHED();
|
||||
}
|
||||
@@ -1031,10 +1096,11 @@ void NetworkGameLoop()
|
||||
) {
|
||||
p += 5;
|
||||
if (*p == ' ') p++;
|
||||
cp = CallocT<CommandPacket>(1);
|
||||
cp = new CommandPacket();
|
||||
int company;
|
||||
static_assert(sizeof(cp->text) == 128);
|
||||
int ret = sscanf(p, "%x; %x; %x; %x; %x; %x; %x; \"%127[^\"]\"", &next_date, &next_date_fract, &company, &cp->tile, &cp->p1, &cp->p2, &cp->cmd, cp->text);
|
||||
char buffer[128];
|
||||
int ret = sscanf(p, "%x; %x; %x; %x; %x; %x; %x; \"%127[^\"]\"", &next_date, &next_date_fract, &company, &cp->tile, &cp->p1, &cp->p2, &cp->cmd, buffer);
|
||||
cp->text = buffer;
|
||||
/* There are 8 pieces of data to read, however the last is a
|
||||
* string that might or might not exist. Ignore it if that
|
||||
* string misses because in 99% of the time it's not used. */
|
||||
@@ -1044,8 +1110,8 @@ void NetworkGameLoop()
|
||||
/* Manually insert a pause when joining; this way the client can join at the exact right time. */
|
||||
int ret = sscanf(p + 6, "%x; %x", &next_date, &next_date_fract);
|
||||
assert(ret == 2);
|
||||
DEBUG(net, 0, "injecting pause for join at %08x:%02x; please join when paused", next_date, next_date_fract);
|
||||
cp = CallocT<CommandPacket>(1);
|
||||
Debug(desync, 0, "Injecting pause for join at {:08x}:{:02x}; please join when paused", next_date, next_date_fract);
|
||||
cp = new CommandPacket();
|
||||
cp->company = COMPANY_SPECTATOR;
|
||||
cp->cmd = CMD_PAUSE;
|
||||
cp->p1 = PM_PAUSED_NORMAL;
|
||||
@@ -1060,16 +1126,16 @@ void NetworkGameLoop()
|
||||
/* A message that is not very important to the log playback, but part of the log. */
|
||||
#ifndef DEBUG_FAILED_DUMP_COMMANDS
|
||||
} else if (strncmp(p, "cmdf: ", 6) == 0) {
|
||||
DEBUG(net, 0, "Skipping replay of failed command: %s", p + 6);
|
||||
Debug(desync, 0, "Skipping replay of failed command: {}", p + 6);
|
||||
#endif
|
||||
} else {
|
||||
/* Can't parse a line; what's wrong here? */
|
||||
DEBUG(net, 0, "trying to parse: %s", p);
|
||||
Debug(desync, 0, "Trying to parse: {}", p);
|
||||
NOT_REACHED();
|
||||
}
|
||||
}
|
||||
if (f != nullptr && feof(f)) {
|
||||
DEBUG(net, 0, "End of commands.log");
|
||||
Debug(desync, 0, "End of commands.log");
|
||||
fclose(f);
|
||||
f = nullptr;
|
||||
}
|
||||
@@ -1144,45 +1210,52 @@ static void NetworkGenerateServerId()
|
||||
}
|
||||
|
||||
/* _settings_client.network.network_id is our id */
|
||||
seprintf(_settings_client.network.network_id, lastof(_settings_client.network.network_id), "%s", hex_output);
|
||||
_settings_client.network.network_id = hex_output;
|
||||
}
|
||||
|
||||
class TCPNetworkDebugConnecter : TCPConnecter {
|
||||
private:
|
||||
std::string connection_string;
|
||||
|
||||
public:
|
||||
TCPNetworkDebugConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_DEBUGLOG_PORT), connection_string(connection_string) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
Debug(net, 0, "Failed to open connection to {} for redirecting Debug()", this->connection_string);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
Debug(net, 3, "Redirecting Debug() to {}", this->connection_string);
|
||||
|
||||
extern SOCKET _debug_socket;
|
||||
_debug_socket = s;
|
||||
}
|
||||
};
|
||||
|
||||
void NetworkStartDebugLog(const std::string &connection_string)
|
||||
{
|
||||
extern SOCKET _debug_socket; // Comes from debug.c
|
||||
|
||||
NetworkAddress address = ParseConnectionString(connection_string, NETWORK_DEFAULT_DEBUGLOG_PORT);
|
||||
|
||||
DEBUG(net, 0, "Redirecting DEBUG() to %s", address.GetAddressAsString().c_str());
|
||||
|
||||
SOCKET s = address.Connect();
|
||||
if (s == INVALID_SOCKET) {
|
||||
DEBUG(net, 0, "Failed to open socket for redirection DEBUG()");
|
||||
return;
|
||||
}
|
||||
|
||||
_debug_socket = s;
|
||||
|
||||
DEBUG(net, 0, "DEBUG() is now redirected");
|
||||
new TCPNetworkDebugConnecter(connection_string);
|
||||
}
|
||||
|
||||
/** This tries to launch the network for a given OS */
|
||||
void NetworkStartUp()
|
||||
{
|
||||
DEBUG(net, 3, "[core] starting network...");
|
||||
Debug(net, 3, "Starting network");
|
||||
|
||||
/* Network is available */
|
||||
_network_available = NetworkCoreInitialize();
|
||||
_network_dedicated = false;
|
||||
_network_need_advertise = true;
|
||||
|
||||
/* Generate an server id when there is none yet */
|
||||
if (StrEmpty(_settings_client.network.network_id)) NetworkGenerateServerId();
|
||||
if (_settings_client.network.network_id.empty()) NetworkGenerateServerId();
|
||||
|
||||
memset(&_network_game_info, 0, sizeof(_network_game_info));
|
||||
_network_game_info = {};
|
||||
|
||||
NetworkInitialize();
|
||||
DEBUG(net, 3, "[core] network online, multiplayer available");
|
||||
NetworkUDPInitialize();
|
||||
Debug(net, 3, "Network online, multiplayer available");
|
||||
NetworkFindBroadcastIPs(&_broadcast_list);
|
||||
}
|
||||
|
||||
@@ -1192,7 +1265,7 @@ void NetworkShutDown()
|
||||
NetworkDisconnect(true);
|
||||
NetworkUDPClose();
|
||||
|
||||
DEBUG(net, 3, "[core] shutting down network");
|
||||
Debug(net, 3, "Shutting down network");
|
||||
|
||||
_network_available = false;
|
||||
|
||||
@@ -1204,7 +1277,7 @@ extern "C" {
|
||||
|
||||
void CDECL em_openttd_add_server(const char *connection_string)
|
||||
{
|
||||
NetworkAddServer(connection_string);
|
||||
NetworkAddServer(connection_string, false, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,8 +74,13 @@ ServerNetworkAdminSocketHandler::ServerNetworkAdminSocketHandler(SOCKET s) : Net
|
||||
ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
|
||||
{
|
||||
_network_admins_connected--;
|
||||
DEBUG(net, 1, "[admin] '%s' (%s) has disconnected", this->admin_name, this->admin_version);
|
||||
Debug(net, 3, "[admin] '{}' ({}) has disconnected", this->admin_name, this->admin_version);
|
||||
if (_redirect_console_to_admin == this->index) _redirect_console_to_admin = INVALID_ADMIN_ID;
|
||||
|
||||
if (this->update_frequency[ADMIN_UPDATE_CONSOLE] & ADMIN_FREQUENCY_AUTOMATIC) {
|
||||
this->update_frequency[ADMIN_UPDATE_CONSOLE] = (AdminUpdateFrequency)0;
|
||||
DebugReconsiderSendRemoteMessages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +89,7 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
|
||||
*/
|
||||
/* static */ bool ServerNetworkAdminSocketHandler::AllowConnection()
|
||||
{
|
||||
bool accept = !StrEmpty(_settings_client.network.admin_password) && _network_admins_connected < MAX_ADMINS;
|
||||
bool accept = !_settings_client.network.admin_password.empty() && _network_admins_connected < MAX_ADMINS;
|
||||
/* We can't go over the MAX_ADMINS limit here. However, if we accept
|
||||
* the connection, there has to be space in the pool. */
|
||||
static_assert(NetworkAdminSocketPool::MAX_SIZE == MAX_ADMINS);
|
||||
@@ -97,7 +102,7 @@ ServerNetworkAdminSocketHandler::~ServerNetworkAdminSocketHandler()
|
||||
{
|
||||
for (ServerNetworkAdminSocketHandler *as : ServerNetworkAdminSocketHandler::Iterate()) {
|
||||
if (as->status == ADMIN_STATUS_INACTIVE && std::chrono::steady_clock::now() > as->connect_time + ADMIN_AUTHORISATION_TIMEOUT) {
|
||||
DEBUG(net, 1, "[admin] Admin did not send its authorisation within %d seconds", (uint32)std::chrono::duration_cast<std::chrono::seconds>(ADMIN_AUTHORISATION_TIMEOUT).count());
|
||||
Debug(net, 2, "[admin] Admin did not send its authorisation within {} seconds", std::chrono::duration_cast<std::chrono::seconds>(ADMIN_AUTHORISATION_TIMEOUT).count());
|
||||
as->CloseConnection(true);
|
||||
continue;
|
||||
}
|
||||
@@ -133,11 +138,9 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendError(NetworkErrorCode er
|
||||
p->Send_uint8(error);
|
||||
this->SendPacket(p);
|
||||
|
||||
char str[100];
|
||||
StringID strid = GetNetworkErrorMsg(error);
|
||||
GetString(str, strid, lastof(str));
|
||||
std::string error_message = GetString(GetNetworkErrorMsg(error));
|
||||
|
||||
DEBUG(net, 1, "[admin] the admin '%s' (%s) made an error and has been disconnected. Reason: '%s'", this->admin_name, this->admin_version, str);
|
||||
Debug(net, 1, "[admin] The admin '{}' ({}) made an error and has been disconnected: '{}'", this->admin_name, this->admin_version, error_message);
|
||||
|
||||
return this->CloseConnection(true);
|
||||
}
|
||||
@@ -316,20 +319,13 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyNew(CompanyID comp
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company *c)
|
||||
{
|
||||
char company_name[NETWORK_COMPANY_NAME_LENGTH];
|
||||
char manager_name[NETWORK_COMPANY_NAME_LENGTH];
|
||||
|
||||
SetDParam(0, c->index);
|
||||
GetString(company_name, STR_COMPANY_NAME, lastof(company_name));
|
||||
|
||||
SetDParam(0, c->index);
|
||||
GetString(manager_name, STR_PRESIDENT_NAME, lastof(manager_name));
|
||||
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_COMPANY_INFO);
|
||||
|
||||
p->Send_uint8 (c->index);
|
||||
p->Send_string(company_name);
|
||||
p->Send_string(manager_name);
|
||||
SetDParam(0, c->index);
|
||||
p->Send_string(GetString(STR_COMPANY_NAME));
|
||||
SetDParam(0, c->index);
|
||||
p->Send_string(GetString(STR_PRESIDENT_NAME));
|
||||
p->Send_uint8 (c->colour);
|
||||
p->Send_bool (NetworkCompanyIsPassworded(c->index));
|
||||
p->Send_uint32(c->inaugurated_year);
|
||||
@@ -352,20 +348,13 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyInfo(const Company
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyUpdate(const Company *c)
|
||||
{
|
||||
char company_name[NETWORK_COMPANY_NAME_LENGTH];
|
||||
char manager_name[NETWORK_COMPANY_NAME_LENGTH];
|
||||
|
||||
SetDParam(0, c->index);
|
||||
GetString(company_name, STR_COMPANY_NAME, lastof(company_name));
|
||||
|
||||
SetDParam(0, c->index);
|
||||
GetString(manager_name, STR_PRESIDENT_NAME, lastof(manager_name));
|
||||
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_COMPANY_UPDATE);
|
||||
|
||||
p->Send_uint8 (c->index);
|
||||
p->Send_string(company_name);
|
||||
p->Send_string(manager_name);
|
||||
SetDParam(0, c->index);
|
||||
p->Send_string(GetString(STR_COMPANY_NAME));
|
||||
SetDParam(0, c->index);
|
||||
p->Send_string(GetString(STR_PRESIDENT_NAME));
|
||||
p->Send_uint8 (c->colour);
|
||||
p->Send_bool (NetworkCompanyIsPassworded(c->index));
|
||||
p->Send_uint8 (CeilDiv(c->months_of_bankruptcy, 3)); // send as quarters_of_bankruptcy
|
||||
@@ -466,7 +455,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCompanyStats()
|
||||
* @param msg The actual message.
|
||||
* @param data Arbitrary extra data.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendChat(NetworkAction action, DestType desttype, ClientID client_id, const char *msg, int64 data)
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendChat(NetworkAction action, DestType desttype, ClientID client_id, const std::string &msg, int64 data)
|
||||
{
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_CHAT);
|
||||
|
||||
@@ -484,7 +473,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendChat(NetworkAction action
|
||||
* Send a notification indicating the rcon command has completed.
|
||||
* @param command The original command sent.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRconEnd(const char *command)
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRconEnd(const std::string_view command)
|
||||
{
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_RCON_END);
|
||||
|
||||
@@ -499,7 +488,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRconEnd(const char *comma
|
||||
* @param colour The colour of the text.
|
||||
* @param result The result of the command.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRcon(uint16 colour, const char *result)
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendRcon(uint16 colour, const std::string_view result)
|
||||
{
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_RCON);
|
||||
|
||||
@@ -514,14 +503,12 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_RCON(Packet *p)
|
||||
{
|
||||
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||
|
||||
char command[NETWORK_RCONCOMMAND_LENGTH];
|
||||
std::string command = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH);
|
||||
|
||||
p->Recv_string(command, sizeof(command));
|
||||
|
||||
DEBUG(net, 2, "[admin] Rcon command from '%s' (%s): '%s'", this->admin_name, this->admin_version, command);
|
||||
Debug(net, 3, "[admin] Rcon command from '{}' ({}): {}", this->admin_name, this->admin_version, command);
|
||||
|
||||
_redirect_console_to_admin = this->index;
|
||||
IConsoleCmdExec(command);
|
||||
IConsoleCmdExec(command.c_str());
|
||||
_redirect_console_to_admin = INVALID_ADMIN_ID;
|
||||
return this->SendRconEnd(command);
|
||||
}
|
||||
@@ -530,11 +517,9 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_GAMESCRIPT(Pack
|
||||
{
|
||||
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||
|
||||
char json[NETWORK_GAMESCRIPT_JSON_LENGTH];
|
||||
std::string json = p->Recv_string(NETWORK_GAMESCRIPT_JSON_LENGTH);
|
||||
|
||||
p->Recv_string(json, sizeof(json));
|
||||
|
||||
DEBUG(net, 2, "[admin] GameScript JSON from '%s' (%s): '%s'", this->admin_name, this->admin_version, json);
|
||||
Debug(net, 6, "[admin] GameScript JSON from '{}' ({}): {}", this->admin_name, this->admin_version, json);
|
||||
|
||||
Game::NewEvent(new ScriptEventAdminPort(json));
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
@@ -546,7 +531,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_PING(Packet *p)
|
||||
|
||||
uint32 d1 = p->Recv_uint32();
|
||||
|
||||
DEBUG(net, 2, "[admin] Ping from '%s' (%s): '%d'", this->admin_name, this->admin_version, d1);
|
||||
Debug(net, 6, "[admin] Ping from '{}' ({}): {}", this->admin_name, this->admin_version, d1);
|
||||
|
||||
return this->SendPong(d1);
|
||||
}
|
||||
@@ -556,13 +541,13 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_PING(Packet *p)
|
||||
* @param origin The origin of the string.
|
||||
* @param string The string that's put on the console.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendConsole(const char *origin, const char *string)
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendConsole(const std::string_view origin, const std::string_view string)
|
||||
{
|
||||
/* If the length of both strings, plus the 2 '\0' terminations and 3 bytes of the packet
|
||||
* are bigger than the MTU, just ignore the message. Better safe than sorry. It should
|
||||
* never occur though as the longest strings are chat messages, which are still 30%
|
||||
* smaller than COMPAT_MTU. */
|
||||
if (strlen(origin) + strlen(string) + 2 + 3 >= COMPAT_MTU) return NETWORK_RECV_STATUS_OKAY;
|
||||
if (origin.size() + string.size() + 2 + 3 >= COMPAT_MTU) return NETWORK_RECV_STATUS_OKAY;
|
||||
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_CONSOLE);
|
||||
|
||||
@@ -577,12 +562,12 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendConsole(const char *origi
|
||||
* Send GameScript JSON output.
|
||||
* @param json The JSON string.
|
||||
*/
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendGameScript(const char *json)
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::SendGameScript(const std::string_view json)
|
||||
{
|
||||
/* At the moment we cannot transmit anything larger than MTU. So we limit
|
||||
* the maximum amount of json data that can be sent. Account also for
|
||||
* the trailing \0 of the string */
|
||||
if (strlen(json) + 1 >= NETWORK_GAMESCRIPT_JSON_LENGTH) return NETWORK_RECV_STATUS_OKAY;
|
||||
if (json.size() + 1 >= NETWORK_GAMESCRIPT_JSON_LENGTH) return NETWORK_RECV_STATUS_OKAY;
|
||||
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_GAMESCRIPT);
|
||||
|
||||
@@ -614,7 +599,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames()
|
||||
/* Should COMPAT_MTU be exceeded, start a new packet
|
||||
* (magic 5: 1 bool "more data" and one uint16 "command id", one
|
||||
* byte for string '\0' termination and 1 bool "no more data" */
|
||||
if (p->CanWriteToPacket(strlen(cmdname) + 5)) {
|
||||
if (!p->CanWriteToPacket(strlen(cmdname) + 5)) {
|
||||
p->Send_bool(false);
|
||||
this->SendPacket(p);
|
||||
|
||||
@@ -664,26 +649,25 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_JOIN(Packet *p)
|
||||
{
|
||||
if (this->status != ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||
|
||||
char password[NETWORK_PASSWORD_LENGTH];
|
||||
p->Recv_string(password, sizeof(password));
|
||||
std::string password = p->Recv_string(NETWORK_PASSWORD_LENGTH);
|
||||
|
||||
if (StrEmpty(_settings_client.network.admin_password) ||
|
||||
strcmp(password, _settings_client.network.admin_password) != 0) {
|
||||
if (_settings_client.network.admin_password.empty() ||
|
||||
_settings_client.network.admin_password.compare(password) != 0) {
|
||||
/* Password is invalid */
|
||||
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
|
||||
}
|
||||
|
||||
p->Recv_string(this->admin_name, sizeof(this->admin_name));
|
||||
p->Recv_string(this->admin_version, sizeof(this->admin_version));
|
||||
this->admin_name = p->Recv_string(NETWORK_CLIENT_NAME_LENGTH);
|
||||
this->admin_version = p->Recv_string(NETWORK_REVISION_LENGTH);
|
||||
|
||||
if (StrEmpty(this->admin_name) || StrEmpty(this->admin_version)) {
|
||||
if (this->admin_name.empty() || this->admin_version.empty()) {
|
||||
/* no name or version supplied */
|
||||
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
|
||||
}
|
||||
|
||||
this->status = ADMIN_STATUS_ACTIVE;
|
||||
|
||||
DEBUG(net, 1, "[admin] '%s' (%s) has connected", this->admin_name, this->admin_version);
|
||||
Debug(net, 3, "[admin] '{}' ({}) has connected", this->admin_name, this->admin_version);
|
||||
|
||||
return this->SendProtocol();
|
||||
}
|
||||
@@ -703,12 +687,14 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_UPDATE_FREQUENC
|
||||
|
||||
if (type >= ADMIN_UPDATE_END || (_admin_update_type_frequencies[type] & freq) != freq) {
|
||||
/* The server does not know of this UpdateType. */
|
||||
DEBUG(net, 3, "[admin] Not supported update frequency %d (%d) from '%s' (%s).", type, freq, this->admin_name, this->admin_version);
|
||||
Debug(net, 1, "[admin] Not supported update frequency {} ({}) from '{}' ({})", type, freq, this->admin_name, this->admin_version);
|
||||
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
|
||||
}
|
||||
|
||||
this->update_frequency[type] = freq;
|
||||
|
||||
if (type == ADMIN_UPDATE_CONSOLE) DebugReconsiderSendRemoteMessages();
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
@@ -771,7 +757,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_POLL(Packet *p)
|
||||
|
||||
default:
|
||||
/* An unsupported "poll" update type. */
|
||||
DEBUG(net, 3, "[admin] Not supported poll %d (%d) from '%s' (%s).", type, d1, this->admin_name, this->admin_version);
|
||||
Debug(net, 1, "[admin] Not supported poll {} ({}) from '{}' ({}).", type, d1, this->admin_name, this->admin_version);
|
||||
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
|
||||
}
|
||||
|
||||
@@ -786,8 +772,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet *p)
|
||||
DestType desttype = (DestType)p->Recv_uint8();
|
||||
int dest = p->Recv_uint32();
|
||||
|
||||
char msg[NETWORK_CHAT_LENGTH];
|
||||
p->Recv_string(msg, NETWORK_CHAT_LENGTH);
|
||||
std::string msg = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
|
||||
switch (action) {
|
||||
case NETWORK_ACTION_CHAT:
|
||||
@@ -798,13 +783,32 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_CHAT(Packet *p)
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG(net, 3, "[admin] Invalid chat action %d from admin '%s' (%s).", action, this->admin_name, this->admin_version);
|
||||
Debug(net, 1, "[admin] Invalid chat action {} from admin '{}' ({}).", action, this->admin_name, this->admin_version);
|
||||
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ServerNetworkAdminSocketHandler::Receive_ADMIN_EXTERNAL_CHAT(Packet *p)
|
||||
{
|
||||
if (this->status == ADMIN_STATUS_INACTIVE) return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
|
||||
|
||||
std::string source = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
TextColour colour = (TextColour)p->Recv_uint16();
|
||||
std::string user = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
std::string msg = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
|
||||
if (!IsValidConsoleColour(colour)) {
|
||||
Debug(net, 1, "[admin] Not supported chat colour {} ({}, {}, {}) from '{}' ({}).", (uint16)colour, source, user, msg, this->admin_name, this->admin_version);
|
||||
return this->SendError(NETWORK_ERROR_ILLEGAL_PACKET);
|
||||
}
|
||||
|
||||
NetworkServerSendExternalChat(source, colour, user, msg);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Useful wrapper functions
|
||||
*/
|
||||
@@ -874,7 +878,7 @@ void NetworkAdminClientError(ClientID client_id, NetworkErrorCode error_code)
|
||||
void NetworkAdminCompanyInfo(const Company *company, bool new_company)
|
||||
{
|
||||
if (company == nullptr) {
|
||||
DEBUG(net, 1, "[admin] Empty company given for update");
|
||||
Debug(net, 1, "[admin] Empty company given for update");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -919,7 +923,7 @@ void NetworkAdminCompanyRemove(CompanyID company_id, AdminCompanyRemoveReason bc
|
||||
/**
|
||||
* Send chat to the admin network (if they did opt in for the respective update).
|
||||
*/
|
||||
void NetworkAdminChat(NetworkAction action, DestType desttype, ClientID client_id, const char *msg, int64 data, bool from_admin)
|
||||
void NetworkAdminChat(NetworkAction action, DestType desttype, ClientID client_id, const std::string &msg, int64 data, bool from_admin)
|
||||
{
|
||||
if (from_admin) return;
|
||||
|
||||
@@ -936,7 +940,7 @@ void NetworkAdminChat(NetworkAction action, DestType desttype, ClientID client_i
|
||||
* @param colour_code The colour of the string.
|
||||
* @param string The string to show.
|
||||
*/
|
||||
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const char *string)
|
||||
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const std::string_view string)
|
||||
{
|
||||
ServerNetworkAdminSocketHandler::Get(admin_index)->SendRcon(colour_code, string);
|
||||
}
|
||||
@@ -946,7 +950,7 @@ void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code,
|
||||
* @param origin the origin of the message.
|
||||
* @param string the message as printed on the console.
|
||||
*/
|
||||
void NetworkAdminConsole(const char *origin, const char *string)
|
||||
void NetworkAdminConsole(const std::string_view origin, const std::string_view string)
|
||||
{
|
||||
for (ServerNetworkAdminSocketHandler *as : ServerNetworkAdminSocketHandler::IterateActive()) {
|
||||
if (as->update_frequency[ADMIN_UPDATE_CONSOLE] & ADMIN_FREQUENCY_AUTOMATIC) {
|
||||
@@ -959,7 +963,7 @@ void NetworkAdminConsole(const char *origin, const char *string)
|
||||
* Send GameScript JSON to the admin network (if they did opt in for the respective update).
|
||||
* @param json The JSON data as received from the GameScript.
|
||||
*/
|
||||
void NetworkAdminGameScript(const char *json)
|
||||
void NetworkAdminGameScript(const std::string_view json)
|
||||
{
|
||||
for (ServerNetworkAdminSocketHandler *as : ServerNetworkAdminSocketHandler::IterateActive()) {
|
||||
if (as->update_frequency[ADMIN_UPDATE_GAMESCRIPT] & ADMIN_FREQUENCY_AUTOMATIC) {
|
||||
|
||||
@@ -29,6 +29,7 @@ protected:
|
||||
NetworkRecvStatus Receive_ADMIN_UPDATE_FREQUENCY(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_POLL(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_CHAT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_EXTERNAL_CHAT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_RCON(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_GAMESCRIPT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_ADMIN_PING(Packet *p) override;
|
||||
@@ -61,13 +62,13 @@ public:
|
||||
NetworkRecvStatus SendCompanyEconomy();
|
||||
NetworkRecvStatus SendCompanyStats();
|
||||
|
||||
NetworkRecvStatus SendChat(NetworkAction action, DestType desttype, ClientID client_id, const char *msg, int64 data);
|
||||
NetworkRecvStatus SendRcon(uint16 colour, const char *command);
|
||||
NetworkRecvStatus SendConsole(const char *origin, const char *command);
|
||||
NetworkRecvStatus SendGameScript(const char *json);
|
||||
NetworkRecvStatus SendChat(NetworkAction action, DestType desttype, ClientID client_id, const std::string &msg, int64 data);
|
||||
NetworkRecvStatus SendRcon(uint16 colour, const std::string_view command);
|
||||
NetworkRecvStatus SendConsole(const std::string_view origin, const std::string_view command);
|
||||
NetworkRecvStatus SendGameScript(const std::string_view json);
|
||||
NetworkRecvStatus SendCmdNames();
|
||||
NetworkRecvStatus SendCmdLogging(ClientID client_id, const CommandPacket *cp);
|
||||
NetworkRecvStatus SendRconEnd(const char *command);
|
||||
NetworkRecvStatus SendRconEnd(const std::string_view command);
|
||||
|
||||
static void Send();
|
||||
static void AcceptConnection(SOCKET s, const NetworkAddress &address);
|
||||
@@ -106,11 +107,11 @@ void NetworkAdminCompanyInfo(const Company *company, bool new_company);
|
||||
void NetworkAdminCompanyUpdate(const Company *company);
|
||||
void NetworkAdminCompanyRemove(CompanyID company_id, AdminCompanyRemoveReason bcrr);
|
||||
|
||||
void NetworkAdminChat(NetworkAction action, DestType desttype, ClientID client_id, const char *msg, int64 data = 0, bool from_admin = false);
|
||||
void NetworkAdminChat(NetworkAction action, DestType desttype, ClientID client_id, const std::string &msg, int64 data = 0, bool from_admin = false);
|
||||
void NetworkAdminUpdate(AdminUpdateFrequency freq);
|
||||
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const char *string);
|
||||
void NetworkAdminConsole(const char *origin, const char *string);
|
||||
void NetworkAdminGameScript(const char *json);
|
||||
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const std::string_view string);
|
||||
void NetworkAdminConsole(const std::string_view origin, const std::string_view string);
|
||||
void NetworkAdminGameScript(const std::string_view json);
|
||||
void NetworkAdminCmdLogging(const NetworkClientSocket *owner, const CommandPacket *cp);
|
||||
|
||||
#endif /* NETWORK_ADMIN_H */
|
||||
|
||||
@@ -22,10 +22,10 @@ extern NetworkClientInfoPool _networkclientinfo_pool;
|
||||
|
||||
/** Container for all information known about a client. */
|
||||
struct NetworkClientInfo : NetworkClientInfoPool::PoolItem<&_networkclientinfo_pool> {
|
||||
ClientID client_id; ///< Client identifier (same as ClientState->client_id)
|
||||
char client_name[NETWORK_CLIENT_NAME_LENGTH]; ///< Name of the client
|
||||
CompanyID client_playas; ///< As which company is this client playing (CompanyID)
|
||||
Date join_date; ///< Gamedate the client has joined
|
||||
ClientID client_id; ///< Client identifier (same as ClientState->client_id)
|
||||
std::string client_name; ///< Name of the client
|
||||
CompanyID client_playas; ///< As which company is this client playing (CompanyID)
|
||||
Date join_date; ///< Gamedate the client has joined
|
||||
|
||||
/**
|
||||
* Create a new client.
|
||||
|
||||
@@ -39,7 +39,7 @@ static const uint NETWORK_CHAT_LINE_SPACING = 3;
|
||||
|
||||
/** Container for a message. */
|
||||
struct ChatMessage {
|
||||
char message[DRAW_STRING_BUFFER]; ///< The action message.
|
||||
std::string message; ///< The action message.
|
||||
TextColour colour; ///< The colour of the message.
|
||||
std::chrono::steady_clock::time_point remove_time; ///< The time to remove the message.
|
||||
};
|
||||
@@ -87,24 +87,15 @@ static inline bool HaveChatMessages(bool show_all)
|
||||
* @param duration The duration of the chat message in seconds
|
||||
* @param message message itself in printf() style
|
||||
*/
|
||||
void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const char *message, ...)
|
||||
void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const std::string &message)
|
||||
{
|
||||
char buf[DRAW_STRING_BUFFER];
|
||||
va_list va;
|
||||
|
||||
va_start(va, message);
|
||||
vseprintf(buf, lastof(buf), message, va);
|
||||
va_end(va);
|
||||
|
||||
Utf8TrimString(buf, DRAW_STRING_BUFFER);
|
||||
|
||||
if (_chatmsg_list.size() == MAX_CHAT_MESSAGES) {
|
||||
_chatmsg_list.pop_back();
|
||||
}
|
||||
|
||||
ChatMessage *cmsg = &_chatmsg_list.emplace_front();
|
||||
strecpy(cmsg->message, buf, lastof(cmsg->message));
|
||||
cmsg->colour = (colour & TC_IS_PALETTE_COLOUR) ? colour : TC_WHITE;
|
||||
cmsg->message = message;
|
||||
cmsg->colour = colour;
|
||||
cmsg->remove_time = std::chrono::steady_clock::now() + std::chrono::seconds(duration);
|
||||
|
||||
_chatmessage_dirty_time = std::chrono::steady_clock::now();
|
||||
@@ -137,7 +128,7 @@ void NetworkUndrawChatMessage()
|
||||
/* Sometimes we also need to hide the cursor
|
||||
* This is because both textmessage and the cursor take a shot of the
|
||||
* screen before drawing.
|
||||
* Now the textmessage takes his shot and paints his data before the cursor
|
||||
* Now the textmessage takes its shot and paints its data before the cursor
|
||||
* does, so in the shot of the cursor is the screen-data of the textmessage
|
||||
* included when the cursor hangs somewhere over the textmessage. To
|
||||
* avoid wrong repaints, we undraw the cursor in that case, and everything
|
||||
@@ -268,9 +259,9 @@ void NetworkDrawChatMessage()
|
||||
* @param type The type of destination.
|
||||
* @param dest The actual destination index.
|
||||
*/
|
||||
static void SendChat(const char *buf, DestType type, int dest)
|
||||
static void SendChat(const std::string &buf, DestType type, int dest)
|
||||
{
|
||||
if (StrEmpty(buf)) return;
|
||||
if (buf.empty()) return;
|
||||
if (!_network_server) {
|
||||
MyClient::SendChat((NetworkAction)(NETWORK_ACTION_CHAT + type), type, dest, buf, 0);
|
||||
} else {
|
||||
@@ -316,9 +307,10 @@ struct NetworkChatWindow : public Window {
|
||||
PositionNetworkChatWindow(this);
|
||||
}
|
||||
|
||||
~NetworkChatWindow()
|
||||
void Close() override
|
||||
{
|
||||
InvalidateWindowData(WC_NEWS_WINDOW, 0, 0);
|
||||
this->Window::Close();
|
||||
}
|
||||
|
||||
void FindWindowPlacementAndResize(int def_width, int def_height) override
|
||||
@@ -341,7 +333,7 @@ struct NetworkChatWindow : public Window {
|
||||
/* Skip inactive clients */
|
||||
for (NetworkClientInfo *ci : NetworkClientInfo::Iterate(*item)) {
|
||||
*item = ci->index;
|
||||
return ci->client_name;
|
||||
return ci->client_name.c_str();
|
||||
}
|
||||
*item = MAX_CLIENT_SLOTS;
|
||||
}
|
||||
@@ -479,7 +471,7 @@ struct NetworkChatWindow : public Window {
|
||||
FALLTHROUGH;
|
||||
|
||||
case WID_NC_CLOSE: /* Cancel */
|
||||
delete this;
|
||||
this->Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -506,7 +498,7 @@ struct NetworkChatWindow : public Window {
|
||||
*/
|
||||
void OnInvalidateData(int data = 0, bool gui_scope = true) override
|
||||
{
|
||||
if (data == this->dest) delete this;
|
||||
if (data == this->dest) this->Close();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -541,6 +533,6 @@ static WindowDesc _chat_window_desc(
|
||||
*/
|
||||
void ShowNetworkChatQueryWindow(DestType type, int dest)
|
||||
{
|
||||
DeleteWindowByClass(WC_SEND_NETWORK_MSG);
|
||||
CloseWindowByClass(WC_SEND_NETWORK_MSG);
|
||||
new NetworkChatWindow(&_chat_window_desc, type, dest);
|
||||
}
|
||||
|
||||
+103
-209
@@ -23,7 +23,6 @@
|
||||
#include "../gfx_func.h"
|
||||
#include "../error.h"
|
||||
#include "../rev.h"
|
||||
#include "core/game_info.h"
|
||||
#include "network.h"
|
||||
#include "network_base.h"
|
||||
#include "network_client.h"
|
||||
@@ -132,12 +131,8 @@ struct PacketReader : LoadFilter {
|
||||
*/
|
||||
void ClientNetworkEmergencySave()
|
||||
{
|
||||
if (!_settings_client.gui.autosave_on_network_disconnect) return;
|
||||
if (!_networking) return;
|
||||
|
||||
const char *filename = "netsave.sav";
|
||||
DEBUG(net, 0, "Client: Performing emergency save (%s)", filename);
|
||||
SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false);
|
||||
static FiosNumberedSaveName _netsave_ctr("netsave");
|
||||
DoAutoOrNetsave(_netsave_ctr);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +140,7 @@ void ClientNetworkEmergencySave()
|
||||
* Create a new socket for the client side of the game connection.
|
||||
* @param s The socket to connect with.
|
||||
*/
|
||||
ClientNetworkGameSocketHandler::ClientNetworkGameSocketHandler(SOCKET s, NetworkAddress address) : NetworkGameSocketHandler(s), address(address), savegame(nullptr), status(STATUS_INACTIVE)
|
||||
ClientNetworkGameSocketHandler::ClientNetworkGameSocketHandler(SOCKET s, const std::string &connection_string) : NetworkGameSocketHandler(s), connection_string(connection_string), savegame(nullptr), status(STATUS_INACTIVE)
|
||||
{
|
||||
assert(ClientNetworkGameSocketHandler::my_client == nullptr);
|
||||
ClientNetworkGameSocketHandler::my_client = this;
|
||||
@@ -158,31 +153,26 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler()
|
||||
ClientNetworkGameSocketHandler::my_client = nullptr;
|
||||
|
||||
delete this->savegame;
|
||||
delete this->GetInfo();
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status)
|
||||
{
|
||||
assert(status != NETWORK_RECV_STATUS_OKAY);
|
||||
/*
|
||||
* Sending a message just before leaving the game calls cs->SendPackets.
|
||||
* This might invoke this function, which means that when we close the
|
||||
* connection after cs->SendPackets we will close an already closed
|
||||
* connection. This handles that case gracefully without having to make
|
||||
* that code any more complex or more aware of the validity of the socket.
|
||||
*/
|
||||
if (this->sock == INVALID_SOCKET) return status;
|
||||
assert(this->sock != INVALID_SOCKET);
|
||||
|
||||
DEBUG(net, 1, "Closed client connection %d", this->client_id);
|
||||
if (!this->HasClientQuit()) {
|
||||
Debug(net, 3, "Closed client connection {}", this->client_id);
|
||||
|
||||
this->SendPackets(true);
|
||||
this->SendPackets(true);
|
||||
|
||||
/* Wait a number of ticks so our leave message can reach the server.
|
||||
* This is especially needed for Windows servers as they seem to get
|
||||
* the "socket is closed" message before receiving our leave message,
|
||||
* which would trigger the server to close the connection as well. */
|
||||
CSleep(3 * MILLISECONDS_PER_TICK);
|
||||
/* Wait a number of ticks so our leave message can reach the server.
|
||||
* This is especially needed for Windows servers as they seem to get
|
||||
* the "socket is closed" message before receiving our leave message,
|
||||
* which would trigger the server to close the connection as well. */
|
||||
CSleep(3 * MILLISECONDS_PER_TICK);
|
||||
}
|
||||
|
||||
delete this->GetInfo();
|
||||
delete this;
|
||||
|
||||
return status;
|
||||
@@ -194,17 +184,17 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
|
||||
*/
|
||||
void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
|
||||
{
|
||||
/* First, send a CLIENT_ERROR to the server, so he knows we are
|
||||
* disconnection (and why!) */
|
||||
/* First, send a CLIENT_ERROR to the server, so it knows we are
|
||||
* disconnected (and why!) */
|
||||
NetworkErrorCode errorno;
|
||||
|
||||
/* We just want to close the connection.. */
|
||||
if (res == NETWORK_RECV_STATUS_CLOSE_QUERY) {
|
||||
this->NetworkSocketHandler::CloseConnection();
|
||||
this->NetworkSocketHandler::MarkClosed();
|
||||
this->CloseConnection(res);
|
||||
_networking = false;
|
||||
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -260,7 +250,7 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
|
||||
/* static */ void ClientNetworkGameSocketHandler::Send()
|
||||
{
|
||||
my_client->SendPackets();
|
||||
my_client->CheckConnection();
|
||||
if (my_client != nullptr) my_client->CheckConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,15 +275,15 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
|
||||
if (_sync_seed_1 != _random.state[0]) {
|
||||
#endif
|
||||
ShowNetworkError(STR_NETWORK_ERROR_DESYNC);
|
||||
DEBUG(desync, 1, "sync_err: %08x; %02x", _date, _date_fract);
|
||||
DEBUG(net, 0, "Sync error detected!");
|
||||
Debug(desync, 1, "sync_err: {:08x}; {:02x}", _date, _date_fract);
|
||||
Debug(net, 0, "Sync error detected");
|
||||
my_client->ClientError(NETWORK_RECV_STATUS_DESYNC);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If this is the first time we have a sync-frame, we
|
||||
* need to let the server know that we are ready and at the same
|
||||
* frame as he is.. so we can start playing! */
|
||||
* frame as it is.. so we can start playing! */
|
||||
if (_network_first_time) {
|
||||
_network_first_time = false;
|
||||
SendAck();
|
||||
@@ -301,7 +291,7 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
|
||||
|
||||
_sync_frame = 0;
|
||||
} else if (_sync_frame < _frame_counter) {
|
||||
DEBUG(net, 1, "Missed frame for sync-test (%d / %d)", _sync_frame, _frame_counter);
|
||||
Debug(net, 1, "Missed frame for sync-test: {} / {}", _sync_frame, _frame_counter);
|
||||
_sync_frame = 0;
|
||||
}
|
||||
}
|
||||
@@ -319,12 +309,12 @@ static uint32 last_ack_frame;
|
||||
/** One bit of 'entropy' used to generate a salt for the company passwords. */
|
||||
static uint32 _password_game_seed;
|
||||
/** The other bit of 'entropy' used to generate a salt for the company passwords. */
|
||||
static char _password_server_id[NETWORK_SERVER_ID_LENGTH];
|
||||
static std::string _password_server_id;
|
||||
|
||||
/** Maximum number of companies of the currently joined server. */
|
||||
static uint8 _network_server_max_companies;
|
||||
/** Maximum number of spectators of the currently joined server. */
|
||||
static uint8 _network_server_max_spectators;
|
||||
/** The current name of the server you are on. */
|
||||
std::string _network_server_name;
|
||||
|
||||
/** Information about the game to join to. */
|
||||
NetworkJoinInfo _network_join;
|
||||
@@ -337,25 +327,6 @@ static_assert(NETWORK_SERVER_ID_LENGTH == 16 * 2 + 1);
|
||||
* DEF_CLIENT_SEND_COMMAND has no parameters
|
||||
************/
|
||||
|
||||
/**
|
||||
* Query the server for server information.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendInformationQuery(bool request_company_info)
|
||||
{
|
||||
my_client->status = STATUS_GAME_INFO;
|
||||
my_client->SendPacket(new Packet(PACKET_CLIENT_GAME_INFO));
|
||||
|
||||
if (request_company_info) {
|
||||
my_client->status = STATUS_COMPANY_INFO;
|
||||
_network_join_status = NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO;
|
||||
SetWindowDirty(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
|
||||
my_client->SendPacket(new Packet(PACKET_CLIENT_COMPANY_INFO));
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/** Tell the server we would like to join. */
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
|
||||
{
|
||||
@@ -385,7 +356,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
|
||||
* Set the game password as requested.
|
||||
* @param password The game password.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *password)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const std::string &password)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD);
|
||||
p->Send_string(password);
|
||||
@@ -397,7 +368,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *p
|
||||
* Set the company password as requested.
|
||||
* @param password The company password.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const char *password)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const std::string &password)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_COMPANY_PASSWORD);
|
||||
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
|
||||
@@ -450,7 +421,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendCommand(const CommandPacke
|
||||
}
|
||||
|
||||
/** Send a chat-packet over the network */
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendChat(NetworkAction action, DestType type, int dest, const char *msg, int64 data)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64 data)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_CHAT);
|
||||
|
||||
@@ -478,7 +449,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendError(NetworkErrorCode err
|
||||
* Tell the server that we like to change the password of the company.
|
||||
* @param password The new password.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const char *password)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const std::string &password)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_SET_PASSWORD);
|
||||
|
||||
@@ -491,7 +462,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const char *pa
|
||||
* Tell the server that we like to change the name of the client.
|
||||
* @param name The new name.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetName(const char *name)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetName(const std::string &name)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_SET_NAME);
|
||||
|
||||
@@ -516,7 +487,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit()
|
||||
* @param pass The password for the remote command.
|
||||
* @param command The actual command.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const char *pass, const char *command)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const std::string &pass, const std::string &command)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_RCON);
|
||||
p->Send_string(pass);
|
||||
@@ -530,7 +501,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const char *pass, con
|
||||
* @param company The company to move to.
|
||||
* @param password The password of the company to move to.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company, const char *password)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company, const std::string &password)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_CLIENT_MOVE);
|
||||
p->Send_uint8(company);
|
||||
@@ -560,7 +531,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FULL(Packet *p)
|
||||
{
|
||||
/* We try to join a server which is full */
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_SERVER_FULL, INVALID_STRING_ID, WL_CRITICAL);
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
|
||||
return NETWORK_RECV_STATUS_SERVER_FULL;
|
||||
}
|
||||
@@ -569,80 +540,11 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_BANNED(Packet *
|
||||
{
|
||||
/* We try to join a server where we are banned */
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_SERVER_BANNED, INVALID_STRING_ID, WL_CRITICAL);
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
|
||||
return NETWORK_RECV_STATUS_SERVER_BANNED;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet *p)
|
||||
{
|
||||
if (this->status != STATUS_COMPANY_INFO && this->status != STATUS_GAME_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
NetworkGameList *item = GetLobbyGameInfo();
|
||||
if (item == nullptr) {
|
||||
/* This is not the lobby, so add it to the game list. */
|
||||
item = NetworkGameListAddItem(this->address);
|
||||
}
|
||||
|
||||
/* Clear any existing GRFConfig chain. */
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
/* Retrieve the NetworkGameInfo from the packet. */
|
||||
DeserializeNetworkGameInfo(p, &item->info);
|
||||
/* Check for compatability with the client. */
|
||||
CheckGameCompatibility(item->info);
|
||||
/* Ensure we consider the server online. */
|
||||
item->online = true;
|
||||
|
||||
/* It could be either window, but only one is open, so redraw both. */
|
||||
SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_GAME);
|
||||
SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
|
||||
|
||||
/* We will receive company info next, so keep connection open. */
|
||||
if (this->status == STATUS_COMPANY_INFO) return NETWORK_RECV_STATUS_OKAY;
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMPANY_INFO(Packet *p)
|
||||
{
|
||||
if (this->status != STATUS_COMPANY_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
byte company_info_version = p->Recv_uint8();
|
||||
|
||||
if (!this->HasClientQuit() && company_info_version == NETWORK_COMPANY_INFO_VERSION) {
|
||||
/* We have received all data... (there are no more packets coming) */
|
||||
if (!p->Recv_bool()) return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
|
||||
CompanyID current = (Owner)p->Recv_uint8();
|
||||
if (current >= MAX_COMPANIES) return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
|
||||
NetworkCompanyInfo *company_info = GetLobbyCompanyInfo(current);
|
||||
if (company_info == nullptr) return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
|
||||
p->Recv_string(company_info->company_name, sizeof(company_info->company_name));
|
||||
company_info->inaugurated_year = p->Recv_uint32();
|
||||
company_info->company_value = p->Recv_uint64();
|
||||
company_info->money = p->Recv_uint64();
|
||||
company_info->income = p->Recv_uint64();
|
||||
company_info->performance = p->Recv_uint16();
|
||||
company_info->use_password = p->Recv_bool();
|
||||
for (uint i = 0; i < NETWORK_VEH_END; i++) {
|
||||
company_info->num_vehicle[i] = p->Recv_uint16();
|
||||
}
|
||||
for (uint i = 0; i < NETWORK_VEH_END; i++) {
|
||||
company_info->num_station[i] = p->Recv_uint16();
|
||||
}
|
||||
company_info->ai = p->Recv_bool();
|
||||
|
||||
p->Recv_string(company_info->clients, sizeof(company_info->clients));
|
||||
|
||||
SetWindowDirty(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_LOBBY);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
/* This packet contains info about the client (playas and name)
|
||||
* as client we save this in NetworkClientInfo, linked via 'client_id'
|
||||
* which is always an unique number on a server. */
|
||||
@@ -651,12 +553,11 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
|
||||
NetworkClientInfo *ci;
|
||||
ClientID client_id = (ClientID)p->Recv_uint32();
|
||||
CompanyID playas = (CompanyID)p->Recv_uint8();
|
||||
char name[NETWORK_NAME_LENGTH];
|
||||
|
||||
p->Recv_string(name, sizeof(name));
|
||||
std::string name = p->Recv_string(NETWORK_NAME_LENGTH);
|
||||
|
||||
if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CONN_LOST;
|
||||
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_CLIENT_QUIT;
|
||||
/* The server validates the name when receiving it from clients, so when it is wrong
|
||||
* here something went really wrong. In the best case the packet got malformed on its
|
||||
* way too us, in the worst case the server is broken or compromised. */
|
||||
@@ -664,7 +565,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
|
||||
|
||||
ci = NetworkClientInfo::GetByClientID(client_id);
|
||||
if (ci != nullptr) {
|
||||
if (playas == ci->client_playas && strcmp(name, ci->client_name) != 0) {
|
||||
if (playas == ci->client_playas && name.compare(ci->client_name) != 0) {
|
||||
/* Client name changed, display the change */
|
||||
NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, CC_DEFAULT, false, ci->client_name, name);
|
||||
} else if (playas != ci->client_playas) {
|
||||
@@ -677,7 +578,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
|
||||
if (client_id == _network_own_client_id) SetLocalCompany(!Company::IsValidID(playas) ? COMPANY_SPECTATOR : playas);
|
||||
|
||||
ci->client_playas = playas;
|
||||
strecpy(ci->client_name, name, lastof(ci->client_name));
|
||||
ci->client_name = name;
|
||||
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
@@ -696,7 +597,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
|
||||
ci->client_playas = playas;
|
||||
if (client_id == _network_own_client_id) this->SetInfo(ci);
|
||||
|
||||
strecpy(ci->client_name, name, lastof(ci->client_name));
|
||||
ci->client_name = name;
|
||||
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
@@ -732,21 +633,11 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet *p
|
||||
|
||||
NetworkErrorCode error = (NetworkErrorCode)p->Recv_uint8();
|
||||
|
||||
/* If we query a server that is 1.11.1 or older, we get an
|
||||
* NETWORK_ERROR_NOT_EXPECTED on requesting the game info. Show a special
|
||||
* error popup in that case.
|
||||
*/
|
||||
if (error == NETWORK_ERROR_NOT_EXPECTED && (this->status == STATUS_GAME_INFO || this->status == STATUS_COMPANY_INFO)) {
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_SERVER_TOO_OLD, INVALID_STRING_ID, WL_CRITICAL);
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
StringID err = STR_NETWORK_ERROR_LOSTCONNECTION;
|
||||
if (error < (ptrdiff_t)lengthof(network_error_strings)) err = network_error_strings[error];
|
||||
/* In case of kicking a client, we assume there is a kick message in the packet if we can read one byte */
|
||||
if (error == NETWORK_ERROR_KICKED && p->CanReadFromPacket(1)) {
|
||||
char kick_msg[255];
|
||||
p->Recv_string(kick_msg, sizeof(kick_msg));
|
||||
std::string kick_msg = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
SetDParamStr(0, kick_msg);
|
||||
ShowErrorMessage(err, STR_NETWORK_ERROR_KICK_MESSAGE, WL_CRITICAL);
|
||||
} else {
|
||||
@@ -756,7 +647,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet *p
|
||||
/* Perform an emergency save if we had already entered the game */
|
||||
if (this->status == STATUS_ACTIVE) ClientNetworkEmergencySave();
|
||||
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
|
||||
return NETWORK_RECV_STATUS_SERVER_ERROR;
|
||||
}
|
||||
@@ -779,7 +670,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHECK_NEWGRFS(P
|
||||
/* We do not know this GRF, bail out of initialization */
|
||||
char buf[sizeof(c.md5sum) * 2 + 1];
|
||||
md5sumToString(buf, lastof(buf), c.md5sum);
|
||||
DEBUG(grf, 0, "NewGRF %08X not found; checksum %s", BSWAP32(c.grfid), buf);
|
||||
Debug(grf, 0, "NewGRF {:08X} not found; checksum {}", BSWAP32(c.grfid), buf);
|
||||
ret = NETWORK_RECV_STATUS_NEWGRF_MISMATCH;
|
||||
}
|
||||
}
|
||||
@@ -799,9 +690,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSW
|
||||
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
this->status = STATUS_AUTH_GAME;
|
||||
|
||||
const char *password = _network_join.server_password;
|
||||
if (!StrEmpty(password)) {
|
||||
return SendGamePassword(password);
|
||||
if (!_network_join.server_password.empty()) {
|
||||
return SendGamePassword(_network_join.server_password);
|
||||
}
|
||||
|
||||
ShowNetworkNeedPassword(NETWORK_GAME_PASSWORD);
|
||||
@@ -815,12 +705,11 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA
|
||||
this->status = STATUS_AUTH_COMPANY;
|
||||
|
||||
_password_game_seed = p->Recv_uint32();
|
||||
p->Recv_string(_password_server_id, sizeof(_password_server_id));
|
||||
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
||||
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
const char *password = _network_join.company_password;
|
||||
if (!StrEmpty(password)) {
|
||||
return SendCompanyPassword(password);
|
||||
if (!_network_join.company_password.empty()) {
|
||||
return SendCompanyPassword(_network_join.company_password);
|
||||
}
|
||||
|
||||
ShowNetworkNeedPassword(NETWORK_COMPANY_PASSWORD);
|
||||
@@ -837,7 +726,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet
|
||||
|
||||
/* Initialize the password hash salting variables, even if they were previously. */
|
||||
_password_game_seed = p->Recv_uint32();
|
||||
p->Recv_string(_password_server_id, sizeof(_password_server_id));
|
||||
_password_server_id = p->Recv_string(NETWORK_SERVER_ID_LENGTH);
|
||||
|
||||
/* Start receiving the map */
|
||||
return SendGetMap();
|
||||
@@ -928,7 +817,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
|
||||
this->last_packet = std::chrono::steady_clock::now();
|
||||
|
||||
if (!load_success) {
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_SAVEGAMEERROR, INVALID_STRING_ID, WL_CRITICAL);
|
||||
return NETWORK_RECV_STATUS_SAVEGAME;
|
||||
}
|
||||
@@ -938,6 +827,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
|
||||
/* Say we received the map and loaded it correctly! */
|
||||
SendMapOk();
|
||||
|
||||
ShowClientList();
|
||||
|
||||
/* New company/spectator (invalid company) or company we want to join is not active
|
||||
* Switch local company to spectator and await the server's judgement */
|
||||
if (_network_join.company == COMPANY_NEW_COMPANY || !Company::IsValidID(_network_join.company)) {
|
||||
@@ -948,7 +839,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
|
||||
* the server will give us a client-id and let us in */
|
||||
_network_join_status = NETWORK_JOIN_STATUS_REGISTERING;
|
||||
ShowJoinStatusWindow();
|
||||
NetworkSendCommand(0, CCA_NEW, 0, CMD_COMPANY_CTRL, nullptr, nullptr, _local_company);
|
||||
NetworkSendCommand(0, CCA_NEW, 0, CMD_COMPANY_CTRL, nullptr, {}, _local_company);
|
||||
}
|
||||
} else {
|
||||
/* take control over an existing company */
|
||||
@@ -978,13 +869,13 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p
|
||||
/* Receive the token. */
|
||||
if (p->CanReadFromPacket(sizeof(uint8))) this->token = p->Recv_uint8();
|
||||
|
||||
DEBUG(net, 5, "Received FRAME %d", _frame_counter_server);
|
||||
Debug(net, 7, "Received FRAME {}", _frame_counter_server);
|
||||
|
||||
/* Let the server know that we received this frame correctly
|
||||
* We do this only once per day, to save some bandwidth ;) */
|
||||
if (!_network_first_time && last_ack_frame < _frame_counter) {
|
||||
last_ack_frame = _frame_counter + DAY_TICKS;
|
||||
DEBUG(net, 4, "Sent ACK at %d", _frame_counter);
|
||||
Debug(net, 7, "Sent ACK at {}", _frame_counter);
|
||||
SendAck();
|
||||
}
|
||||
|
||||
@@ -1014,7 +905,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet
|
||||
cp.my_cmd = p->Recv_bool();
|
||||
|
||||
if (err != nullptr) {
|
||||
IConsolePrintF(CC_ERROR, "WARNING: %s from server, dropping...", err);
|
||||
IConsolePrint(CC_WARNING, "Dropping server connection due to {}.", err);
|
||||
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
}
|
||||
|
||||
@@ -1027,13 +918,13 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
|
||||
{
|
||||
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
char name[NETWORK_NAME_LENGTH], msg[NETWORK_CHAT_LENGTH];
|
||||
std::string name;
|
||||
const NetworkClientInfo *ci = nullptr, *ci_to;
|
||||
|
||||
NetworkAction action = (NetworkAction)p->Recv_uint8();
|
||||
ClientID client_id = (ClientID)p->Recv_uint32();
|
||||
bool self_send = p->Recv_bool();
|
||||
p->Recv_string(msg, NETWORK_CHAT_LENGTH);
|
||||
std::string msg = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
int64 data = p->Recv_uint64();
|
||||
|
||||
ci_to = NetworkClientInfo::GetByClientID(client_id);
|
||||
@@ -1044,7 +935,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
|
||||
switch (action) {
|
||||
case NETWORK_ACTION_CHAT_CLIENT:
|
||||
/* For speaking to client we need the client-name */
|
||||
seprintf(name, lastof(name), "%s", ci_to->client_name);
|
||||
name = ci_to->client_name;
|
||||
ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
|
||||
break;
|
||||
|
||||
@@ -1053,7 +944,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
|
||||
StringID str = Company::IsValidID(ci_to->client_playas) ? STR_COMPANY_NAME : STR_NETWORK_SPECTATORS;
|
||||
SetDParam(0, ci_to->client_playas);
|
||||
|
||||
GetString(name, str, lastof(name));
|
||||
name = GetString(str);
|
||||
ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
|
||||
break;
|
||||
}
|
||||
@@ -1062,7 +953,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
|
||||
}
|
||||
} else {
|
||||
/* Display message from somebody else */
|
||||
seprintf(name, lastof(name), "%s", ci_to->client_name);
|
||||
name = ci_to->client_name;
|
||||
ci = ci_to;
|
||||
}
|
||||
|
||||
@@ -1072,6 +963,22 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_EXTERNAL_CHAT(Packet *p)
|
||||
{
|
||||
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
std::string source = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
TextColour colour = (TextColour)p->Recv_uint16();
|
||||
std::string user = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
std::string msg = p->Recv_string(NETWORK_CHAT_LENGTH);
|
||||
|
||||
if (!IsValidConsoleColour(colour)) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
NetworkTextMessage(NETWORK_ACTION_EXTERNAL_CHAT, colour, false, user, msg, 0, source);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Packet *p)
|
||||
{
|
||||
if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
@@ -1080,7 +987,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack
|
||||
|
||||
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
|
||||
if (ci != nullptr) {
|
||||
NetworkTextMessage(NETWORK_ACTION_LEAVE, CC_DEFAULT, false, ci->client_name, nullptr, GetNetworkErrorMsg((NetworkErrorCode)p->Recv_uint8()));
|
||||
NetworkTextMessage(NETWORK_ACTION_LEAVE, CC_DEFAULT, false, ci->client_name, "", GetNetworkErrorMsg((NetworkErrorCode)p->Recv_uint8()));
|
||||
delete ci;
|
||||
}
|
||||
|
||||
@@ -1097,10 +1004,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p)
|
||||
|
||||
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
|
||||
if (ci != nullptr) {
|
||||
NetworkTextMessage(NETWORK_ACTION_LEAVE, CC_DEFAULT, false, ci->client_name, nullptr, STR_NETWORK_MESSAGE_CLIENT_LEAVING);
|
||||
NetworkTextMessage(NETWORK_ACTION_LEAVE, CC_DEFAULT, false, ci->client_name, "", STR_NETWORK_MESSAGE_CLIENT_LEAVING);
|
||||
delete ci;
|
||||
} else {
|
||||
DEBUG(net, 0, "Unknown client (%d) is leaving the game", client_id);
|
||||
Debug(net, 1, "Unknown client ({}) is leaving the game", client_id);
|
||||
}
|
||||
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
@@ -1162,8 +1069,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_RCON(Packet *p)
|
||||
TextColour colour_code = (TextColour)p->Recv_uint16();
|
||||
if (!IsValidConsoleColour(colour_code)) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
char rcon_out[NETWORK_RCONCOMMAND_LENGTH];
|
||||
p->Recv_string(rcon_out, sizeof(rcon_out));
|
||||
std::string rcon_out = p->Recv_string(NETWORK_RCONCOMMAND_LENGTH);
|
||||
|
||||
IConsolePrint(colour_code, rcon_out);
|
||||
|
||||
@@ -1180,7 +1086,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MOVE(Packet *p)
|
||||
|
||||
if (client_id == 0) {
|
||||
/* definitely an invalid client id, debug message and do nothing. */
|
||||
DEBUG(net, 0, "[move] received invalid client index = 0");
|
||||
Debug(net, 1, "Received invalid client index = 0");
|
||||
return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
}
|
||||
|
||||
@@ -1203,7 +1109,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CONFIG_UPDATE(P
|
||||
if (this->status < STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
_network_server_max_companies = p->Recv_uint8();
|
||||
_network_server_max_spectators = p->Recv_uint8();
|
||||
_network_server_name = p->Recv_string(NETWORK_NAME_LENGTH);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -1263,7 +1169,7 @@ void NetworkClient_Connected()
|
||||
* @param password The password.
|
||||
* @param command The command to execute.
|
||||
*/
|
||||
void NetworkClientSendRcon(const char *password, const char *command)
|
||||
void NetworkClientSendRcon(const std::string &password, const std::string &command)
|
||||
{
|
||||
MyClient::SendRCon(password, command);
|
||||
}
|
||||
@@ -1274,7 +1180,7 @@ void NetworkClientSendRcon(const char *password, const char *command)
|
||||
* @param pass the password, is only checked on the server end if a password is needed.
|
||||
* @return void
|
||||
*/
|
||||
void NetworkClientRequestMove(CompanyID company_id, const char *pass)
|
||||
void NetworkClientRequestMove(CompanyID company_id, const std::string &pass)
|
||||
{
|
||||
MyClient::SendMove(company_id, pass);
|
||||
}
|
||||
@@ -1305,10 +1211,10 @@ void NetworkClientsToSpectators(CompanyID cid)
|
||||
* @param client_name The client name to check for validity.
|
||||
* @return True iff the name is valid.
|
||||
*/
|
||||
bool NetworkIsValidClientName(const char *client_name)
|
||||
bool NetworkIsValidClientName(const std::string_view client_name)
|
||||
{
|
||||
if (StrEmpty(client_name)) return false;
|
||||
if (*client_name == ' ') return false;
|
||||
if (client_name.empty()) return false;
|
||||
if (client_name[0] == ' ') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1327,7 +1233,7 @@ bool NetworkIsValidClientName(const char *client_name)
|
||||
* and trailing spaces.
|
||||
* @return True iff the client name is valid.
|
||||
*/
|
||||
bool NetworkValidateClientName(char *client_name)
|
||||
bool NetworkValidateClientName(std::string &client_name)
|
||||
{
|
||||
StrTrimInPlace(client_name);
|
||||
if (NetworkIsValidClientName(client_name)) return true;
|
||||
@@ -1343,33 +1249,30 @@ bool NetworkValidateClientName(char *client_name)
|
||||
* See \c NetworkValidateClientName(char*) for details about the functionality.
|
||||
* @return True iff the client name is valid.
|
||||
*/
|
||||
bool NetworkValidateClientName()
|
||||
bool NetworkValidateOurClientName()
|
||||
{
|
||||
return NetworkValidateClientName(_settings_client.network.client_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the server our name.
|
||||
* Send the server our name as callback from the setting.
|
||||
* @param newname The new client name.
|
||||
*/
|
||||
void NetworkUpdateClientName()
|
||||
void NetworkUpdateClientName(const std::string &client_name)
|
||||
{
|
||||
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
|
||||
|
||||
if (ci == nullptr) return;
|
||||
/* There is no validation on string settings, it is actually a post change callback.
|
||||
* This method is called from that post change callback. So, when the client name is
|
||||
* changed via the console there is no easy way to prevent an invalid name. Though,
|
||||
* we can prevent it getting sent here. */
|
||||
if (!NetworkValidateClientName()) return;
|
||||
|
||||
/* Don't change the name if it is the same as the old name */
|
||||
if (strcmp(ci->client_name, _settings_client.network.client_name) != 0) {
|
||||
if (client_name.compare(ci->client_name) != 0) {
|
||||
if (!_network_server) {
|
||||
MyClient::SendSetName(_settings_client.network.client_name);
|
||||
MyClient::SendSetName(client_name);
|
||||
} else {
|
||||
if (NetworkFindName(_settings_client.network.client_name, lastof(_settings_client.network.client_name))) {
|
||||
NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, CC_DEFAULT, false, ci->client_name, _settings_client.network.client_name);
|
||||
strecpy(ci->client_name, _settings_client.network.client_name, lastof(ci->client_name));
|
||||
/* Copy to a temporary buffer so no #n gets added after our name in the settings when there are duplicate names. */
|
||||
std::string temporary_name = client_name;
|
||||
if (NetworkMakeClientNameUnique(temporary_name)) {
|
||||
NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, CC_DEFAULT, false, ci->client_name, temporary_name);
|
||||
ci->client_name = temporary_name;
|
||||
NetworkUpdateClientInfo(CLIENT_ID_SERVER);
|
||||
}
|
||||
}
|
||||
@@ -1384,7 +1287,7 @@ void NetworkUpdateClientName()
|
||||
* @param msg The actual message.
|
||||
* @param data Arbitrary extra data.
|
||||
*/
|
||||
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const char *msg, int64 data)
|
||||
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64 data)
|
||||
{
|
||||
MyClient::SendChat(action, type, dest, msg, data);
|
||||
}
|
||||
@@ -1393,13 +1296,13 @@ void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const
|
||||
* Set/Reset company password on the client side.
|
||||
* @param password Password to be set.
|
||||
*/
|
||||
void NetworkClientSetCompanyPassword(const char *password)
|
||||
void NetworkClientSetCompanyPassword(const std::string &password)
|
||||
{
|
||||
MyClient::SendSetPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell whether the client has team members where he/she can chat to.
|
||||
* Tell whether the client has team members who they can chat to.
|
||||
* @param cio client to check members of.
|
||||
* @return true if there is at least one team member.
|
||||
*/
|
||||
@@ -1423,12 +1326,3 @@ bool NetworkMaxCompaniesReached()
|
||||
{
|
||||
return Company::GetNumItems() >= (_network_server ? _settings_client.network.max_companies : _network_server_max_companies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if max_spectatos has been reached on the server (local check only).
|
||||
* @return true if the max value has been reached or exceeded, false otherwise.
|
||||
*/
|
||||
bool NetworkMaxSpectatorsReached()
|
||||
{
|
||||
return NetworkSpectatorCount() >= (_network_server ? _settings_client.network.max_spectators : _network_server_max_spectators);
|
||||
}
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
/** Class for handling the client side of the game connection. */
|
||||
class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
|
||||
private:
|
||||
NetworkAddress address; ///< Address we are connected to.
|
||||
std::string connection_string; ///< Address we are connected to.
|
||||
struct PacketReader *savegame; ///< Packet reader for reading the savegame.
|
||||
byte token; ///< The token we need to send back to the server to prove we're the right client.
|
||||
|
||||
/** Status of the connection with the server. */
|
||||
enum ServerStatus {
|
||||
STATUS_INACTIVE, ///< The client is not connected nor active.
|
||||
STATUS_GAME_INFO, ///< We are trying to get the game information.
|
||||
STATUS_COMPANY_INFO, ///< We are trying to get company information.
|
||||
STATUS_JOIN, ///< We are trying to join a server.
|
||||
STATUS_NEWGRFS_CHECK, ///< Last action was checking NewGRFs.
|
||||
STATUS_AUTH_GAME, ///< Last action was requesting game (server) password.
|
||||
@@ -45,8 +43,6 @@ protected:
|
||||
NetworkRecvStatus Receive_SERVER_FULL(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_BANNED(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_ERROR(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_GAME_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_COMPANY_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) override;
|
||||
@@ -61,6 +57,7 @@ protected:
|
||||
NetworkRecvStatus Receive_SERVER_SYNC(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_COMMAND(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_CHAT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_EXTERNAL_CHAT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_QUIT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_ERROR_QUIT(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_SHUTDOWN(Packet *p) override;
|
||||
@@ -76,28 +73,26 @@ protected:
|
||||
static NetworkRecvStatus SendMapOk();
|
||||
void CheckConnection();
|
||||
public:
|
||||
ClientNetworkGameSocketHandler(SOCKET s, NetworkAddress address);
|
||||
ClientNetworkGameSocketHandler(SOCKET s, const std::string &connection_string);
|
||||
~ClientNetworkGameSocketHandler();
|
||||
|
||||
NetworkRecvStatus CloseConnection(NetworkRecvStatus status) override;
|
||||
void ClientError(NetworkRecvStatus res);
|
||||
|
||||
static NetworkRecvStatus SendInformationQuery(bool request_company_info);
|
||||
|
||||
static NetworkRecvStatus SendJoin();
|
||||
static NetworkRecvStatus SendCommand(const CommandPacket *cp);
|
||||
static NetworkRecvStatus SendError(NetworkErrorCode errorno);
|
||||
static NetworkRecvStatus SendQuit();
|
||||
static NetworkRecvStatus SendAck();
|
||||
|
||||
static NetworkRecvStatus SendGamePassword(const char *password);
|
||||
static NetworkRecvStatus SendCompanyPassword(const char *password);
|
||||
static NetworkRecvStatus SendGamePassword(const std::string &password);
|
||||
static NetworkRecvStatus SendCompanyPassword(const std::string &password);
|
||||
|
||||
static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const char *msg, int64 data);
|
||||
static NetworkRecvStatus SendSetPassword(const char *password);
|
||||
static NetworkRecvStatus SendSetName(const char *name);
|
||||
static NetworkRecvStatus SendRCon(const char *password, const char *command);
|
||||
static NetworkRecvStatus SendMove(CompanyID company, const char *password);
|
||||
static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64 data);
|
||||
static NetworkRecvStatus SendSetPassword(const std::string &password);
|
||||
static NetworkRecvStatus SendSetName(const std::string &name);
|
||||
static NetworkRecvStatus SendRCon(const std::string &password, const std::string &command);
|
||||
static NetworkRecvStatus SendMove(CompanyID company, const std::string &password);
|
||||
|
||||
static bool IsConnected();
|
||||
|
||||
@@ -110,15 +105,15 @@ public:
|
||||
typedef ClientNetworkGameSocketHandler MyClient;
|
||||
|
||||
void NetworkClient_Connected();
|
||||
void NetworkClientSetCompanyPassword(const char *password);
|
||||
void NetworkClientSetCompanyPassword(const std::string &password);
|
||||
|
||||
/** Information required to join a server. */
|
||||
struct NetworkJoinInfo {
|
||||
NetworkJoinInfo() : company(COMPANY_SPECTATOR), server_password(nullptr), company_password(nullptr) {}
|
||||
NetworkAddress address; ///< The address of the server to join.
|
||||
CompanyID company; ///< The company to join.
|
||||
const char *server_password; ///< The password of the server to join.
|
||||
const char *company_password; ///< The password of the company to join.
|
||||
NetworkJoinInfo() : company(COMPANY_SPECTATOR) {}
|
||||
std::string connection_string; ///< The address of the server to join.
|
||||
CompanyID company; ///< The company to join.
|
||||
std::string server_password; ///< The password of the server to join.
|
||||
std::string company_password; ///< The password of the company to join.
|
||||
};
|
||||
|
||||
extern NetworkJoinInfo _network_join;
|
||||
|
||||
@@ -56,7 +56,7 @@ static CommandCallback * const _callback_table[] = {
|
||||
*/
|
||||
void CommandQueue::Append(CommandPacket *p)
|
||||
{
|
||||
CommandPacket *add = MallocT<CommandPacket>(1);
|
||||
CommandPacket *add = new CommandPacket();
|
||||
*add = *p;
|
||||
add->next = nullptr;
|
||||
if (this->first == nullptr) {
|
||||
@@ -113,7 +113,7 @@ void CommandQueue::Free()
|
||||
{
|
||||
CommandPacket *cp;
|
||||
while ((cp = this->Pop()) != nullptr) {
|
||||
free(cp);
|
||||
delete cp;
|
||||
}
|
||||
assert(this->count == 0);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ static CommandQueue _local_execution_queue;
|
||||
* @param text The text to pass
|
||||
* @param company The company that wants to send the command
|
||||
*/
|
||||
void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company)
|
||||
void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const std::string &text, CompanyID company)
|
||||
{
|
||||
assert((cmd & CMD_FLAGS_MASK) == 0);
|
||||
|
||||
@@ -144,8 +144,7 @@ void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, Comman
|
||||
c.p2 = p2;
|
||||
c.cmd = cmd;
|
||||
c.callback = callback;
|
||||
|
||||
strecpy(c.text, (text != nullptr) ? text : "", lastof(c.text));
|
||||
c.text = text;
|
||||
|
||||
if (_network_server) {
|
||||
/* If we are the server, we queue the command in our 'special' queue.
|
||||
@@ -180,7 +179,7 @@ void NetworkSyncCommandQueue(NetworkClientSocket *cs)
|
||||
{
|
||||
for (CommandPacket *p = _local_execution_queue.Peek(); p != nullptr; p = p->next) {
|
||||
CommandPacket c = *p;
|
||||
c.callback = 0;
|
||||
c.callback = nullptr;
|
||||
cs->outgoing_queue.Append(&c);
|
||||
}
|
||||
}
|
||||
@@ -212,7 +211,7 @@ void NetworkExecuteLocalCommandQueue()
|
||||
DoCommandP(cp, cp->my_cmd);
|
||||
|
||||
queue.Pop();
|
||||
free(cp);
|
||||
delete cp;
|
||||
}
|
||||
|
||||
/* Local company may have changed, so we should not restore the old value */
|
||||
@@ -271,7 +270,7 @@ static void DistributeQueue(CommandQueue *queue, const NetworkClientSocket *owne
|
||||
while (--to_go >= 0 && (cp = queue->Pop(true)) != nullptr) {
|
||||
DistributeCommandPacket(*cp, owner);
|
||||
NetworkAdminCmdLogging(owner, cp);
|
||||
free(cp);
|
||||
delete cp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +303,7 @@ const char *NetworkGameSocketHandler::ReceiveCommand(Packet *p, CommandPacket *c
|
||||
cp->p1 = p->Recv_uint32();
|
||||
cp->p2 = p->Recv_uint32();
|
||||
cp->tile = p->Recv_uint32();
|
||||
p->Recv_string(cp->text, lengthof(cp->text), (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
|
||||
cp->text = p->Recv_string(NETWORK_COMPANY_NAME_LENGTH, (!_network_server && GetCommandFlags(cp->cmd) & CMD_STR_CTRL) != 0 ? SVS_ALLOW_CONTROL_CODE | SVS_REPLACE_WITH_QUESTION_MARK : SVS_REPLACE_WITH_QUESTION_MARK);
|
||||
|
||||
byte callback = p->Recv_uint8();
|
||||
if (callback >= lengthof(_callback_table)) return "invalid callback";
|
||||
@@ -333,7 +332,7 @@ void NetworkGameSocketHandler::SendCommand(Packet *p, const CommandPacket *cp)
|
||||
}
|
||||
|
||||
if (callback == lengthof(_callback_table)) {
|
||||
DEBUG(net, 0, "Unknown callback. (Pointer: %p) No callback sent", cp->callback);
|
||||
Debug(net, 0, "Unknown callback for command; no callback sent (command: {})", cp->cmd);
|
||||
callback = 0; // _callback_table[0] == nullptr
|
||||
}
|
||||
p->Send_uint8 (callback);
|
||||
|
||||
@@ -56,27 +56,31 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p)
|
||||
ci->id = (ContentID)p->Recv_uint32();
|
||||
ci->filesize = p->Recv_uint32();
|
||||
|
||||
p->Recv_string(ci->name, lengthof(ci->name));
|
||||
p->Recv_string(ci->version, lengthof(ci->version));
|
||||
p->Recv_string(ci->url, lengthof(ci->url));
|
||||
p->Recv_string(ci->description, lengthof(ci->description), SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE);
|
||||
ci->name = p->Recv_string(NETWORK_CONTENT_NAME_LENGTH);
|
||||
ci->version = p->Recv_string(NETWORK_CONTENT_VERSION_LENGTH);
|
||||
ci->url = p->Recv_string(NETWORK_CONTENT_URL_LENGTH);
|
||||
ci->description = p->Recv_string(NETWORK_CONTENT_DESC_LENGTH, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE);
|
||||
|
||||
ci->unique_id = p->Recv_uint32();
|
||||
for (uint j = 0; j < sizeof(ci->md5sum); j++) {
|
||||
ci->md5sum[j] = p->Recv_uint8();
|
||||
}
|
||||
|
||||
ci->dependency_count = p->Recv_uint8();
|
||||
ci->dependencies = MallocT<ContentID>(ci->dependency_count);
|
||||
for (uint i = 0; i < ci->dependency_count; i++) ci->dependencies[i] = (ContentID)p->Recv_uint32();
|
||||
uint dependency_count = p->Recv_uint8();
|
||||
ci->dependencies.reserve(dependency_count);
|
||||
for (uint i = 0; i < dependency_count; i++) {
|
||||
ContentID dependency_cid = (ContentID)p->Recv_uint32();
|
||||
ci->dependencies.push_back(dependency_cid);
|
||||
this->reverse_dependency_map.insert({ dependency_cid, ci->id });
|
||||
}
|
||||
|
||||
ci->tag_count = p->Recv_uint8();
|
||||
ci->tags = MallocT<char[32]>(ci->tag_count);
|
||||
for (uint i = 0; i < ci->tag_count; i++) p->Recv_string(ci->tags[i], lengthof(*ci->tags));
|
||||
uint tag_count = p->Recv_uint8();
|
||||
ci->tags.reserve(tag_count);
|
||||
for (uint i = 0; i < tag_count; i++) ci->tags.push_back(p->Recv_string(NETWORK_CONTENT_TAG_LENGTH));
|
||||
|
||||
if (!ci->IsValid()) {
|
||||
delete ci;
|
||||
this->Close();
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -143,16 +147,15 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p)
|
||||
if (ici->type == ci->type && ici->unique_id == ci->unique_id &&
|
||||
memcmp(ci->md5sum, ici->md5sum, sizeof(ci->md5sum)) == 0) {
|
||||
/* Preserve the name if possible */
|
||||
if (StrEmpty(ci->name)) strecpy(ci->name, ici->name, lastof(ci->name));
|
||||
if (ci->name.empty()) ci->name = ici->name;
|
||||
if (ici->IsSelected()) ci->state = ici->state;
|
||||
|
||||
/*
|
||||
* As ici might be selected by the content window we cannot delete that.
|
||||
* However, we want to keep most of the values of ci, except the values
|
||||
* we (just) already preserved.
|
||||
* So transfer data and ownership of allocated memory from ci to ici.
|
||||
*/
|
||||
ici->TransferFrom(ci);
|
||||
*ici = *ci;
|
||||
delete ci;
|
||||
|
||||
this->OnReceiveContentInfo(ici);
|
||||
@@ -169,8 +172,10 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p)
|
||||
this->infos.push_back(ci);
|
||||
|
||||
/* Incoming data means that we might need to reconsider dependencies */
|
||||
for (ContentInfo *ici : this->infos) {
|
||||
this->CheckDependencyState(ici);
|
||||
ConstContentVector parents;
|
||||
this->ReverseLookupTreeDependency(parents, ci);
|
||||
for (const ContentInfo *ici : parents) {
|
||||
this->CheckDependencyState(const_cast<ContentInfo *>(ici));
|
||||
}
|
||||
|
||||
this->OnReceiveContentInfo(ci);
|
||||
@@ -202,7 +207,20 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentType type)
|
||||
|
||||
Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_LIST);
|
||||
p->Send_uint8 ((byte)type);
|
||||
p->Send_uint32(_openttd_newgrf_version);
|
||||
p->Send_uint32(0xffffffff);
|
||||
p->Send_uint8 (1);
|
||||
p->Send_string("vanilla");
|
||||
p->Send_string(_openttd_content_version);
|
||||
|
||||
/* Patchpacks can extend the list with one. In BaNaNaS metadata you can
|
||||
* add a branch in the 'compatibility' list, to filter on this. If you want
|
||||
* your patchpack to be mentioned in the BaNaNaS web-interface, create an
|
||||
* issue on https://github.com/OpenTTD/bananas-api asking for this.
|
||||
|
||||
p->Send_string("patchpack"); // Or what-ever the name of your patchpack is.
|
||||
p->Send_string(_openttd_content_version_patchpack);
|
||||
|
||||
*/
|
||||
|
||||
this->SendPacket(p);
|
||||
}
|
||||
@@ -343,8 +361,7 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentHTTP(const Conten
|
||||
|
||||
this->http_response_index = -1;
|
||||
|
||||
NetworkAddress address(NETWORK_CONTENT_MIRROR_HOST, NETWORK_CONTENT_MIRROR_PORT);
|
||||
new NetworkHTTPContentConnecter(address, this, NETWORK_CONTENT_MIRROR_URL, content_request);
|
||||
new NetworkHTTPContentConnecter(NetworkContentMirrorConnectionString(), this, NETWORK_CONTENT_MIRROR_URL, content_request);
|
||||
/* NetworkHTTPContentConnecter takes over freeing of content_request! */
|
||||
}
|
||||
|
||||
@@ -411,7 +428,8 @@ static bool GunzipFile(const ContentInfo *ci)
|
||||
FILE *ftmp = fopen(GetFullFilename(ci, true).c_str(), "rb");
|
||||
if (ftmp == nullptr) return false;
|
||||
/* Duplicate the handle, and close the FILE*, to avoid double-closing the handle later. */
|
||||
gzFile fin = gzdopen(dup(fileno(ftmp)), "rb");
|
||||
int fdup = dup(fileno(ftmp));
|
||||
gzFile fin = gzdopen(fdup, "rb");
|
||||
fclose(ftmp);
|
||||
|
||||
FILE *fout = fopen(GetFullFilename(ci, false).c_str(), "wb");
|
||||
@@ -450,7 +468,12 @@ static bool GunzipFile(const ContentInfo *ci)
|
||||
}
|
||||
}
|
||||
|
||||
if (fin != nullptr) gzclose(fin);
|
||||
if (fin != nullptr) {
|
||||
gzclose(fin);
|
||||
} else if (fdup != -1) {
|
||||
/* Failing gzdopen does not close the passed file descriptor. */
|
||||
close(fdup);
|
||||
}
|
||||
if (fout != nullptr) fclose(fout);
|
||||
|
||||
return ret;
|
||||
@@ -480,19 +503,19 @@ bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p)
|
||||
this->curInfo->type = (ContentType)p->Recv_uint8();
|
||||
this->curInfo->id = (ContentID)p->Recv_uint32();
|
||||
this->curInfo->filesize = p->Recv_uint32();
|
||||
p->Recv_string(this->curInfo->filename, lengthof(this->curInfo->filename));
|
||||
this->curInfo->filename = p->Recv_string(NETWORK_CONTENT_FILENAME_LENGTH);
|
||||
|
||||
if (!this->BeforeDownload()) {
|
||||
this->Close();
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* We have a file opened, thus are downloading internal content */
|
||||
size_t toRead = p->RemainingBytesToTransfer();
|
||||
if (toRead != 0 && (size_t)p->TransferOut(TransferOutFWrite, this->curFile) != toRead) {
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
|
||||
this->Close();
|
||||
this->CloseConnection();
|
||||
fclose(this->curFile);
|
||||
this->curFile = nullptr;
|
||||
|
||||
@@ -524,7 +547,7 @@ bool ClientNetworkContentSocketHandler::BeforeDownload()
|
||||
std::string filename = GetFullFilename(this->curInfo, true);
|
||||
if (filename.empty() || (this->curFile = fopen(filename.c_str(), "wb")) == nullptr) {
|
||||
/* Unless that fails of course... */
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD, STR_CONTENT_ERROR_COULD_NOT_DOWNLOAD_FILE_NOT_WRITABLE, WL_ERROR);
|
||||
return false;
|
||||
}
|
||||
@@ -699,7 +722,7 @@ void ClientNetworkContentSocketHandler::OnReceiveData(const char *data, size_t l
|
||||
}
|
||||
|
||||
/* Copy the string, without extension, to the filename. */
|
||||
strecpy(this->curInfo->filename, tmp, lastof(this->curInfo->filename));
|
||||
this->curInfo->filename = tmp;
|
||||
|
||||
/* Request the next file. */
|
||||
if (!this->BeforeDownload()) {
|
||||
@@ -744,7 +767,7 @@ public:
|
||||
* Initiate the connecting.
|
||||
* @param address The address of the server.
|
||||
*/
|
||||
NetworkContentConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
|
||||
NetworkContentConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_CONTENT_SERVER_PORT) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
@@ -770,18 +793,22 @@ void ClientNetworkContentSocketHandler::Connect()
|
||||
{
|
||||
if (this->sock != INVALID_SOCKET || this->isConnecting) return;
|
||||
this->isConnecting = true;
|
||||
new NetworkContentConnecter(NetworkAddress(NETWORK_CONTENT_SERVER_HOST, NETWORK_CONTENT_SERVER_PORT, AF_UNSPEC));
|
||||
new NetworkContentConnecter(NetworkContentServerConnectionString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the content server.
|
||||
*/
|
||||
void ClientNetworkContentSocketHandler::Close()
|
||||
NetworkRecvStatus ClientNetworkContentSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
if (this->sock == INVALID_SOCKET) return;
|
||||
NetworkContentSocketHandler::Close();
|
||||
NetworkContentSocketHandler::CloseConnection();
|
||||
|
||||
if (this->sock == INVALID_SOCKET) return NETWORK_RECV_STATUS_OKAY;
|
||||
|
||||
this->CloseSocket();
|
||||
this->OnDisconnect();
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -793,7 +820,7 @@ void ClientNetworkContentSocketHandler::SendReceive()
|
||||
if (this->sock == INVALID_SOCKET || this->isConnecting) return;
|
||||
|
||||
if (std::chrono::steady_clock::now() > this->lastActivity + IDLE_TIMEOUT) {
|
||||
this->Close();
|
||||
this->CloseConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -825,7 +852,7 @@ void ClientNetworkContentSocketHandler::DownloadContentInfo(ContentID cid)
|
||||
* @param cid the ContentID to search for
|
||||
* @return the ContentInfo or nullptr if not found
|
||||
*/
|
||||
ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid)
|
||||
ContentInfo *ClientNetworkContentSocketHandler::GetContent(ContentID cid) const
|
||||
{
|
||||
for (ContentInfo *ci : this->infos) {
|
||||
if (ci->id == cid) return ci;
|
||||
@@ -915,15 +942,10 @@ void ClientNetworkContentSocketHandler::ToggleSelectedState(const ContentInfo *c
|
||||
*/
|
||||
void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVector &parents, const ContentInfo *child) const
|
||||
{
|
||||
for (const ContentInfo *ci : this->infos) {
|
||||
if (ci == child) continue;
|
||||
auto range = this->reverse_dependency_map.equal_range(child->id);
|
||||
|
||||
for (uint i = 0; i < ci->dependency_count; i++) {
|
||||
if (ci->dependencies[i] == child->id) {
|
||||
parents.push_back(ci);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto iter = range.first; iter != range.second; ++iter) {
|
||||
parents.push_back(GetContent(iter->second));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,10 +982,10 @@ void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
|
||||
/* Selection is easy; just walk all children and set the
|
||||
* autoselected state. That way we can see what we automatically
|
||||
* selected and thus can unselect when a dependency is removed. */
|
||||
for (uint i = 0; i < ci->dependency_count; i++) {
|
||||
ContentInfo *c = this->GetContent(ci->dependencies[i]);
|
||||
for (auto &dependency : ci->dependencies) {
|
||||
ContentInfo *c = this->GetContent(dependency);
|
||||
if (c == nullptr) {
|
||||
this->DownloadContentInfo(ci->dependencies[i]);
|
||||
this->DownloadContentInfo(dependency);
|
||||
} else if (c->state == ContentInfo::UNSELECTED) {
|
||||
c->state = ContentInfo::AUTOSELECTED;
|
||||
this->CheckDependencyState(c);
|
||||
@@ -986,10 +1008,10 @@ void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
|
||||
this->Unselect(c->id);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ci->dependency_count; i++) {
|
||||
const ContentInfo *c = this->GetContent(ci->dependencies[i]);
|
||||
for (auto &dependency : ci->dependencies) {
|
||||
const ContentInfo *c = this->GetContent(dependency);
|
||||
if (c == nullptr) {
|
||||
DownloadContentInfo(ci->dependencies[i]);
|
||||
DownloadContentInfo(dependency);
|
||||
continue;
|
||||
}
|
||||
if (c->state != ContentInfo::AUTOSELECTED) continue;
|
||||
@@ -1001,9 +1023,9 @@ void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
|
||||
/* First check whether anything depends on us */
|
||||
int sel_count = 0;
|
||||
bool force_selection = false;
|
||||
for (const ContentInfo *ci : parents) {
|
||||
if (ci->IsSelected()) sel_count++;
|
||||
if (ci->state == ContentInfo::SELECTED) force_selection = true;
|
||||
for (const ContentInfo *parent_ci : parents) {
|
||||
if (parent_ci->IsSelected()) sel_count++;
|
||||
if (parent_ci->state == ContentInfo::SELECTED) force_selection = true;
|
||||
}
|
||||
if (sel_count == 0) {
|
||||
/* Nothing depends on us */
|
||||
@@ -1018,8 +1040,8 @@ void ClientNetworkContentSocketHandler::CheckDependencyState(ContentInfo *ci)
|
||||
this->ReverseLookupTreeDependency(parents, c);
|
||||
|
||||
/* Is there anything that is "force" selected?, if so... we're done. */
|
||||
for (const ContentInfo *ci : parents) {
|
||||
if (ci->state != ContentInfo::SELECTED) continue;
|
||||
for (const ContentInfo *parent_ci : parents) {
|
||||
if (parent_ci->state != ContentInfo::SELECTED) continue;
|
||||
|
||||
force_selection = true;
|
||||
break;
|
||||
@@ -1048,6 +1070,7 @@ void ClientNetworkContentSocketHandler::Clear()
|
||||
|
||||
this->infos.clear();
|
||||
this->requested.clear();
|
||||
this->reverse_dependency_map.clear();
|
||||
}
|
||||
|
||||
/*** CALLBACK ***/
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "core/tcp_content.h"
|
||||
#include "core/tcp_http.h"
|
||||
#include <unordered_map>
|
||||
|
||||
/** Vector with content info */
|
||||
typedef std::vector<ContentInfo *> ContentVector;
|
||||
@@ -68,6 +69,7 @@ protected:
|
||||
std::vector<ContentCallback *> callbacks; ///< Callbacks to notify "the world"
|
||||
ContentIDList requested; ///< ContentIDs we already requested (so we don't do it again)
|
||||
ContentVector infos; ///< All content info we received
|
||||
std::unordered_multimap<ContentID, ContentID> reverse_dependency_map; ///< Content reverse dependency map
|
||||
std::vector<char> http_response; ///< The HTTP response to the requests we've been doing
|
||||
int http_response_index; ///< Where we are, in the response, with handling it
|
||||
|
||||
@@ -81,7 +83,7 @@ protected:
|
||||
bool Receive_SERVER_INFO(Packet *p) override;
|
||||
bool Receive_SERVER_CONTENT(Packet *p) override;
|
||||
|
||||
ContentInfo *GetContent(ContentID cid);
|
||||
ContentInfo *GetContent(ContentID cid) const;
|
||||
void DownloadContentInfo(ContentID cid);
|
||||
|
||||
void OnConnect(bool success) override;
|
||||
@@ -107,7 +109,7 @@ public:
|
||||
|
||||
void Connect();
|
||||
void SendReceive();
|
||||
void Close() override;
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
|
||||
void RequestContentList(ContentType type);
|
||||
void RequestContentList(uint count, const ContentID *content_ids);
|
||||
|
||||
@@ -76,7 +76,7 @@ struct ContentTextfileWindow : public TextfileWindow {
|
||||
|
||||
void ShowContentTextfileWindow(TextfileType file_type, const ContentInfo *ci)
|
||||
{
|
||||
DeleteWindowById(WC_TEXTFILE, file_type);
|
||||
CloseWindowById(WC_TEXTFILE, file_type);
|
||||
new ContentTextfileWindow(file_type, ci);
|
||||
}
|
||||
|
||||
@@ -111,9 +111,10 @@ BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(W
|
||||
this->InitNested(WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
}
|
||||
|
||||
BaseNetworkContentDownloadStatusWindow::~BaseNetworkContentDownloadStatusWindow()
|
||||
void BaseNetworkContentDownloadStatusWindow::Close()
|
||||
{
|
||||
_network_content_client.RemoveCallback(this);
|
||||
this->Window::Close();
|
||||
}
|
||||
|
||||
void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, int widget) const
|
||||
@@ -132,7 +133,7 @@ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, int widge
|
||||
StringID str;
|
||||
if (this->downloaded_bytes == this->total_bytes) {
|
||||
str = STR_CONTENT_DOWNLOAD_COMPLETE;
|
||||
} else if (!StrEmpty(this->name)) {
|
||||
} else if (!this->name.empty()) {
|
||||
SetDParamStr(0, this->name);
|
||||
SetDParam(1, this->downloaded_files);
|
||||
SetDParam(2, this->total_files);
|
||||
@@ -148,7 +149,7 @@ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, int widge
|
||||
void BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(const ContentInfo *ci, int bytes)
|
||||
{
|
||||
if (ci->id != this->cur_id) {
|
||||
strecpy(this->name, ci->filename, lastof(this->name));
|
||||
this->name = ci->filename;
|
||||
this->cur_id = ci->id;
|
||||
this->downloaded_files++;
|
||||
}
|
||||
@@ -173,8 +174,7 @@ public:
|
||||
this->parent = FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
|
||||
}
|
||||
|
||||
/** Free whatever we've allocated */
|
||||
~NetworkContentDownloadStatusWindow()
|
||||
void Close() override
|
||||
{
|
||||
TarScanner::Mode mode = TarScanner::NONE;
|
||||
for (auto ctype : this->receivedTypes) {
|
||||
@@ -256,18 +256,20 @@ public:
|
||||
|
||||
/* Always invalidate the download window; tell it we are going to be gone */
|
||||
InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST, 2);
|
||||
|
||||
this->BaseNetworkContentDownloadStatusWindow::Close();
|
||||
}
|
||||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget == WID_NCDS_CANCELOK) {
|
||||
if (this->downloaded_bytes != this->total_bytes) {
|
||||
_network_content_client.Close();
|
||||
delete this;
|
||||
_network_content_client.CloseConnection();
|
||||
this->Close();
|
||||
} else {
|
||||
/* If downloading succeeded, close the online content window. This will close
|
||||
* the current window as well. */
|
||||
DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
|
||||
CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,7 +410,7 @@ class NetworkContentListWindow : public Window, ContentCallback {
|
||||
/** Sort content by name. */
|
||||
static bool NameSorter(const ContentInfo * const &a, const ContentInfo * const &b)
|
||||
{
|
||||
return strnatcmp(a->name, b->name, true) < 0; // Sort by name (natural sorting).
|
||||
return strnatcmp(a->name.c_str(), b->name.c_str(), true) < 0; // Sort by name (natural sorting).
|
||||
}
|
||||
|
||||
/** Sort content by type. */
|
||||
@@ -443,10 +445,9 @@ class NetworkContentListWindow : public Window, ContentCallback {
|
||||
static bool CDECL TagNameFilter(const ContentInfo * const *a, ContentListFilterData &filter)
|
||||
{
|
||||
filter.string_filter.ResetState();
|
||||
for (int i = 0; i < (*a)->tag_count; i++) {
|
||||
filter.string_filter.AddLine((*a)->tags[i]);
|
||||
}
|
||||
filter.string_filter.AddLine((*a)->name);
|
||||
for (auto &tag : (*a)->tags) filter.string_filter.AddLine(tag.c_str());
|
||||
|
||||
filter.string_filter.AddLine((*a)->name.c_str());
|
||||
return filter.string_filter.GetState();
|
||||
}
|
||||
|
||||
@@ -551,10 +552,10 @@ public:
|
||||
this->InvalidateData();
|
||||
}
|
||||
|
||||
/** Free everything we allocated */
|
||||
~NetworkContentListWindow()
|
||||
void Close() override
|
||||
{
|
||||
_network_content_client.RemoveCallback(this);
|
||||
this->Window::Close();
|
||||
}
|
||||
|
||||
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
|
||||
@@ -706,17 +707,17 @@ public:
|
||||
SetDParamStr(0, this->selected->name);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_NAME);
|
||||
|
||||
if (!StrEmpty(this->selected->version)) {
|
||||
if (!this->selected->version.empty()) {
|
||||
SetDParamStr(0, this->selected->version);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_VERSION);
|
||||
}
|
||||
|
||||
if (!StrEmpty(this->selected->description)) {
|
||||
if (!this->selected->description.empty()) {
|
||||
SetDParamStr(0, this->selected->description);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DESCRIPTION);
|
||||
}
|
||||
|
||||
if (!StrEmpty(this->selected->url)) {
|
||||
if (!this->selected->url.empty()) {
|
||||
SetDParamStr(0, this->selected->url);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_URL);
|
||||
}
|
||||
@@ -728,20 +729,18 @@ public:
|
||||
SetDParam(0, this->selected->filesize);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_FILESIZE);
|
||||
|
||||
if (this->selected->dependency_count != 0) {
|
||||
if (!this->selected->dependencies.empty()) {
|
||||
/* List dependencies */
|
||||
char buf[DRAW_STRING_BUFFER] = "";
|
||||
char *p = buf;
|
||||
for (uint i = 0; i < this->selected->dependency_count; i++) {
|
||||
ContentID cid = this->selected->dependencies[i];
|
||||
|
||||
for (auto &cid : this->selected->dependencies) {
|
||||
/* Try to find the dependency */
|
||||
ConstContentIterator iter = _network_content_client.Begin();
|
||||
for (; iter != _network_content_client.End(); iter++) {
|
||||
const ContentInfo *ci = *iter;
|
||||
if (ci->id != cid) continue;
|
||||
|
||||
p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
|
||||
p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -749,12 +748,12 @@ public:
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DEPENDENCIES);
|
||||
}
|
||||
|
||||
if (this->selected->tag_count != 0) {
|
||||
if (!this->selected->tags.empty()) {
|
||||
/* List all tags */
|
||||
char buf[DRAW_STRING_BUFFER] = "";
|
||||
char *p = buf;
|
||||
for (uint i = 0; i < this->selected->tag_count; i++) {
|
||||
p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
|
||||
for (auto &tag : this->selected->tags) {
|
||||
p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", tag.c_str());
|
||||
}
|
||||
SetDParamStr(0, buf);
|
||||
y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TAGS);
|
||||
@@ -770,7 +769,7 @@ public:
|
||||
for (const ContentInfo *ci : tree) {
|
||||
if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
|
||||
|
||||
p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
|
||||
p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name.c_str());
|
||||
}
|
||||
if (p != buf) {
|
||||
SetDParamStr(0, buf);
|
||||
@@ -840,10 +839,14 @@ public:
|
||||
this->InvalidateData();
|
||||
break;
|
||||
|
||||
case WID_NCL_CANCEL:
|
||||
this->Close();
|
||||
break;
|
||||
|
||||
case WID_NCL_OPEN_URL:
|
||||
if (this->selected != nullptr) {
|
||||
extern void OpenBrowser(const char *url);
|
||||
OpenBrowser(this->selected->url);
|
||||
OpenBrowser(this->selected->url.c_str());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -941,7 +944,7 @@ public:
|
||||
{
|
||||
if (!success) {
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT, INVALID_STRING_ID, WL_ERROR);
|
||||
delete this;
|
||||
this->Close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -984,7 +987,7 @@ public:
|
||||
this->SetWidgetDisabledState(WID_NCL_UNSELECT, this->filesize_sum == 0);
|
||||
this->SetWidgetDisabledState(WID_NCL_SELECT_ALL, !show_select_all);
|
||||
this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE, !show_select_upgrade);
|
||||
this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == nullptr || StrEmpty(this->selected->url));
|
||||
this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == nullptr || this->selected->url.empty());
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_NCL_TEXTFILE + tft, this->selected == nullptr || this->selected->state != ContentInfo::ALREADY_HERE || this->selected->GetTextfile(tft) == nullptr);
|
||||
}
|
||||
@@ -1120,7 +1123,7 @@ void ShowNetworkContentListWindow(ContentVector *cv, ContentType type1, ContentT
|
||||
_network_content_client.RequestContentList(cv, true);
|
||||
}
|
||||
|
||||
DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
|
||||
CloseWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
|
||||
new NetworkContentListWindow(&_network_content_list_desc, cv != nullptr, types);
|
||||
#else
|
||||
ShowErrorMessage(STR_CONTENT_NO_ZLIB, STR_CONTENT_NO_ZLIB_SUB, WL_ERROR);
|
||||
|
||||
@@ -22,8 +22,8 @@ protected:
|
||||
uint total_files; ///< Number of files to download
|
||||
uint downloaded_files; ///< Number of files downloaded
|
||||
|
||||
uint32 cur_id; ///< The current ID of the downloaded file
|
||||
char name[48]; ///< The current name of the downloaded file
|
||||
uint32 cur_id; ///< The current ID of the downloaded file
|
||||
std::string name; ///< The current name of the downloaded file
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -32,11 +32,7 @@ public:
|
||||
*/
|
||||
BaseNetworkContentDownloadStatusWindow(WindowDesc *desc);
|
||||
|
||||
/**
|
||||
* Free everything associated with this window.
|
||||
*/
|
||||
~BaseNetworkContentDownloadStatusWindow();
|
||||
|
||||
void Close() override;
|
||||
void DrawWidget(const Rect &r, int widget) const override;
|
||||
void OnDownloadProgress(const ContentInfo *ci, int bytes) override;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_coordinator.cpp Game Coordinator sending/receiving part of the network protocol. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../debug.h"
|
||||
#include "../error.h"
|
||||
#include "../rev.h"
|
||||
#include "../settings_type.h"
|
||||
#include "../strings_func.h"
|
||||
#include "../window_func.h"
|
||||
#include "../window_type.h"
|
||||
#include "network.h"
|
||||
#include "network_coordinator.h"
|
||||
#include "network_gamelist.h"
|
||||
#include "network_gui.h"
|
||||
#include "network_internal.h"
|
||||
#include "network_server.h"
|
||||
#include "network_stun.h"
|
||||
#include "table/strings.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
static const auto NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES = std::chrono::seconds(30); ///< How many time between updates the server sends to the Game Coordinator.
|
||||
ClientNetworkCoordinatorSocketHandler _network_coordinator_client; ///< The connection to the Game Coordinator.
|
||||
ConnectionType _network_server_connection_type = CONNECTION_TYPE_UNKNOWN; ///< What type of connection the Game Coordinator detected we are on.
|
||||
std::string _network_server_invite_code = ""; ///< Our invite code as indicated by the Game Coordinator.
|
||||
|
||||
/** Connect to a game server by IP:port. */
|
||||
class NetworkDirectConnecter : public TCPConnecter {
|
||||
private:
|
||||
std::string token; ///< Token of this connection.
|
||||
uint8 tracking_number; ///< Tracking number of this connection.
|
||||
|
||||
public:
|
||||
/**
|
||||
* Try to establish a direct (hostname:port based) connection.
|
||||
* @param hostname The hostname of the server.
|
||||
* @param port The port of the server.
|
||||
* @param token The token as given by the Game Coordinator to track this connection attempt.
|
||||
* @param tracking_number The tracking number as given by the Game Coordinator to track this connection attempt.
|
||||
*/
|
||||
NetworkDirectConnecter(const std::string &hostname, uint16 port, const std::string &token, uint8 tracking_number) : TCPConnecter(hostname, port), token(token), tracking_number(tracking_number) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
NetworkAddress address = NetworkAddress::GetPeerAddress(s);
|
||||
_network_coordinator_client.ConnectSuccess(this->token, s, address);
|
||||
}
|
||||
};
|
||||
|
||||
/** Connecter used after STUN exchange to connect from both sides to each other. */
|
||||
class NetworkReuseStunConnecter : public TCPConnecter {
|
||||
private:
|
||||
std::string token; ///< Token of this connection.
|
||||
uint8 tracking_number; ///< Tracking number of this connection.
|
||||
uint8 family; ///< Family of this connection.
|
||||
|
||||
public:
|
||||
/**
|
||||
* Try to establish a STUN-based connection.
|
||||
* @param hostname The hostname of the peer.
|
||||
* @param port The port of the peer.
|
||||
* @param bind_address The local bind address used for this connection.
|
||||
* @param token The connection token.
|
||||
* @param tracking_number The tracking number of the connection.
|
||||
* @param family The family this connection is using.
|
||||
*/
|
||||
NetworkReuseStunConnecter(const std::string &hostname, uint16 port, const NetworkAddress &bind_address, std::string token, uint8 tracking_number, uint8 family) :
|
||||
TCPConnecter(hostname, port, bind_address),
|
||||
token(token),
|
||||
tracking_number(tracking_number),
|
||||
family(family)
|
||||
{
|
||||
}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
/* Close the STUN connection too, as it is no longer of use. */
|
||||
_network_coordinator_client.CloseStunHandler(this->token, this->family);
|
||||
|
||||
_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
NetworkAddress address = NetworkAddress::GetPeerAddress(s);
|
||||
_network_coordinator_client.ConnectSuccess(this->token, s, address);
|
||||
}
|
||||
};
|
||||
|
||||
/** Connect to the Game Coordinator server. */
|
||||
class NetworkCoordinatorConnecter : TCPConnecter {
|
||||
public:
|
||||
/**
|
||||
* Initiate the connecting.
|
||||
* @param connection_string The address of the Game Coordinator server.
|
||||
*/
|
||||
NetworkCoordinatorConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_COORDINATOR_SERVER_PORT) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
_network_coordinator_client.connecting = false;
|
||||
_network_coordinator_client.CloseConnection(true);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
assert(_network_coordinator_client.sock == INVALID_SOCKET);
|
||||
|
||||
_network_coordinator_client.sock = s;
|
||||
_network_coordinator_client.last_activity = std::chrono::steady_clock::now();
|
||||
_network_coordinator_client.connecting = false;
|
||||
}
|
||||
};
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p)
|
||||
{
|
||||
NetworkCoordinatorErrorType error = (NetworkCoordinatorErrorType)p->Recv_uint8();
|
||||
std::string detail = p->Recv_string(NETWORK_ERROR_DETAIL_LENGTH);
|
||||
|
||||
switch (error) {
|
||||
case NETWORK_COORDINATOR_ERROR_UNKNOWN:
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
|
||||
case NETWORK_COORDINATOR_ERROR_REGISTRATION_FAILED:
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, INVALID_STRING_ID, WL_ERROR);
|
||||
|
||||
/* To prevent that we constantly try to reconnect, switch to local game. */
|
||||
_settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL;
|
||||
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
|
||||
case NETWORK_COORDINATOR_ERROR_INVALID_INVITE_CODE: {
|
||||
auto connecter_pre_it = this->connecter_pre.find(detail);
|
||||
if (connecter_pre_it != this->connecter_pre.end()) {
|
||||
connecter_pre_it->second->SetFailure();
|
||||
this->connecter_pre.erase(connecter_pre_it);
|
||||
}
|
||||
|
||||
/* Mark the server as offline. */
|
||||
NetworkGameList *item = NetworkGameListAddItem(detail);
|
||||
item->status = NGLS_OFFLINE;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
case NETWORK_COORDINATOR_ERROR_REUSE_OF_INVITE_CODE:
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REUSE_OF_INVITE_CODE, INVALID_STRING_ID, WL_ERROR);
|
||||
|
||||
/* To prevent that we constantly battle for the same invite-code, switch to local game. */
|
||||
_settings_client.network.server_game_type = SERVER_GAME_TYPE_LOCAL;
|
||||
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
|
||||
default:
|
||||
Debug(net, 0, "Invalid error type {} received from Game Coordinator", error);
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
|
||||
{
|
||||
/* Schedule sending an update. */
|
||||
this->next_update = std::chrono::steady_clock::now();
|
||||
|
||||
_settings_client.network.server_invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
|
||||
_settings_client.network.server_invite_code_secret = p->Recv_string(NETWORK_INVITE_CODE_SECRET_LENGTH);
|
||||
_network_server_connection_type = (ConnectionType)p->Recv_uint8();
|
||||
|
||||
if (_network_server_connection_type == CONNECTION_TYPE_ISOLATED) {
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_ISOLATED, STR_NETWORK_ERROR_COORDINATOR_ISOLATED_DETAIL, WL_ERROR);
|
||||
}
|
||||
|
||||
/* Users can change the invite code in the settings, but this has no effect
|
||||
* on the invite code as assigned by the server. So
|
||||
* _network_server_invite_code contains the current invite code,
|
||||
* and _settings_client.network.server_invite_code contains the one we will
|
||||
* attempt to re-use when registering again. */
|
||||
_network_server_invite_code = _settings_client.network.server_invite_code;
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
|
||||
if (_network_dedicated) {
|
||||
std::string connection_type;
|
||||
switch (_network_server_connection_type) {
|
||||
case CONNECTION_TYPE_ISOLATED: connection_type = "Remote players can't connect"; break;
|
||||
case CONNECTION_TYPE_DIRECT: connection_type = "Public"; break;
|
||||
case CONNECTION_TYPE_STUN: connection_type = "Behind NAT"; break;
|
||||
case CONNECTION_TYPE_TURN: connection_type = "Via relay"; break;
|
||||
|
||||
case CONNECTION_TYPE_UNKNOWN: // Never returned from Game Coordinator.
|
||||
default: connection_type = "Unknown"; break; // Should never happen, but don't fail if it does.
|
||||
}
|
||||
|
||||
std::string game_type;
|
||||
switch (_settings_client.network.server_game_type) {
|
||||
case SERVER_GAME_TYPE_INVITE_ONLY: game_type = "Invite only"; break;
|
||||
case SERVER_GAME_TYPE_PUBLIC: game_type = "Public"; break;
|
||||
|
||||
case SERVER_GAME_TYPE_LOCAL: // Impossible to register local servers.
|
||||
default: game_type = "Unknown"; break; // Should never happen, but don't fail if it does.
|
||||
}
|
||||
|
||||
Debug(net, 3, "----------------------------------------");
|
||||
Debug(net, 3, "Your server is now registered with the Game Coordinator:");
|
||||
Debug(net, 3, " Game type: {}", game_type);
|
||||
Debug(net, 3, " Connection type: {}", connection_type);
|
||||
Debug(net, 3, " Invite code: {}", _network_server_invite_code);
|
||||
Debug(net, 3, "----------------------------------------");
|
||||
} else {
|
||||
Debug(net, 3, "Game Coordinator registered our server with invite code '{}'", _network_server_invite_code);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
|
||||
{
|
||||
uint8 servers = p->Recv_uint16();
|
||||
|
||||
/* End of list; we can now remove all expired items from the list. */
|
||||
if (servers == 0) {
|
||||
NetworkGameListRemoveExpired();
|
||||
return true;
|
||||
}
|
||||
|
||||
for (; servers > 0; servers--) {
|
||||
std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
|
||||
|
||||
/* Read the NetworkGameInfo from the packet. */
|
||||
NetworkGameInfo ngi = {};
|
||||
DeserializeNetworkGameInfo(p, &ngi, &this->newgrf_lookup_table);
|
||||
|
||||
/* Now we know the connection string, we can add it to our list. */
|
||||
NetworkGameList *item = NetworkGameListAddItem(connection_string);
|
||||
|
||||
/* Clear any existing GRFConfig chain. */
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
/* Copy the new NetworkGameInfo info. */
|
||||
item->info = ngi;
|
||||
/* Check for compatability with the client. */
|
||||
CheckGameCompatibility(item->info);
|
||||
/* Mark server as online. */
|
||||
item->status = NGLS_ONLINE;
|
||||
/* Mark the item as up-to-date. */
|
||||
item->version = _network_game_list_version;
|
||||
}
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECTING(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
std::string invite_code = p->Recv_string(NETWORK_INVITE_CODE_LENGTH);
|
||||
|
||||
/* Find the connecter based on the invite code. */
|
||||
auto connecter_pre_it = this->connecter_pre.find(invite_code);
|
||||
if (connecter_pre_it == this->connecter_pre.end()) {
|
||||
this->CloseConnection();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Now store it based on the token. */
|
||||
this->connecter[token] = {invite_code, connecter_pre_it->second};
|
||||
this->connecter_pre.erase(connecter_pre_it);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_CONNECT_FAILED(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
this->CloseToken(token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_DIRECT_CONNECT(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
uint8 tracking_number = p->Recv_uint8();
|
||||
std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH);
|
||||
uint16 port = p->Recv_uint16();
|
||||
|
||||
/* Ensure all other pending connection attempts are killed. */
|
||||
if (this->game_connecter != nullptr) {
|
||||
this->game_connecter->Kill();
|
||||
this->game_connecter = nullptr;
|
||||
}
|
||||
|
||||
this->game_connecter = new NetworkDirectConnecter(hostname, port, token, tracking_number);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_REQUEST(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
|
||||
this->stun_handlers[token][AF_INET6] = ClientNetworkStunSocketHandler::Stun(token, AF_INET6);
|
||||
this->stun_handlers[token][AF_INET] = ClientNetworkStunSocketHandler::Stun(token, AF_INET);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_STUN_CONNECT(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
uint8 tracking_number = p->Recv_uint8();
|
||||
uint8 family = p->Recv_uint8();
|
||||
std::string host = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
|
||||
uint16 port = p->Recv_uint16();
|
||||
|
||||
/* Check if we know this token. */
|
||||
auto stun_it = this->stun_handlers.find(token);
|
||||
if (stun_it == this->stun_handlers.end()) return true;
|
||||
auto family_it = stun_it->second.find(family);
|
||||
if (family_it == stun_it->second.end()) return true;
|
||||
|
||||
/* Ensure all other pending connection attempts are killed. */
|
||||
if (this->game_connecter != nullptr) {
|
||||
this->game_connecter->Kill();
|
||||
this->game_connecter = nullptr;
|
||||
}
|
||||
|
||||
/* We now mark the connection as closed, but we do not really close the
|
||||
* socket yet. We do this when the NetworkReuseStunConnecter is connected.
|
||||
* This prevents any NAT to already remove the route while we create the
|
||||
* second connection on top of the first. */
|
||||
family_it->second->CloseConnection(false);
|
||||
|
||||
/* Connect to our peer from the same local address as we use for the
|
||||
* STUN server. This means that if there is any NAT in the local network,
|
||||
* the public ip:port is still pointing to the local address, and as such
|
||||
* a connection can be established. */
|
||||
this->game_connecter = new NetworkReuseStunConnecter(host, port, family_it->second->local_addr, token, tracking_number, family);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_NEWGRF_LOOKUP(Packet *p)
|
||||
{
|
||||
this->newgrf_lookup_table_cursor = p->Recv_uint32();
|
||||
|
||||
uint16 newgrfs = p->Recv_uint16();
|
||||
for (; newgrfs> 0; newgrfs--) {
|
||||
uint32 index = p->Recv_uint32();
|
||||
DeserializeGRFIdentifierWithName(p, &this->newgrf_lookup_table[index]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientNetworkCoordinatorSocketHandler::Receive_GC_TURN_CONNECT(Packet *p)
|
||||
{
|
||||
std::string token = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
uint8 tracking_number = p->Recv_uint8();
|
||||
std::string ticket = p->Recv_string(NETWORK_TOKEN_LENGTH);
|
||||
std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
|
||||
|
||||
/* Ensure all other pending connection attempts are killed. */
|
||||
if (this->game_connecter != nullptr) {
|
||||
this->game_connecter->Kill();
|
||||
this->game_connecter = nullptr;
|
||||
}
|
||||
|
||||
this->turn_handlers[token] = ClientNetworkTurnSocketHandler::Turn(token, tracking_number, ticket, connection_string);
|
||||
|
||||
if (!_network_server) {
|
||||
auto connecter_it = this->connecter.find(token);
|
||||
if (connecter_it == this->connecter.end()) {
|
||||
/* Make sure we are still interested in connecting to this server. */
|
||||
this->ConnectFailure(token, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_settings_client.network.use_relay_service) {
|
||||
case URS_NEVER:
|
||||
this->ConnectFailure(token, 0);
|
||||
break;
|
||||
|
||||
case URS_ASK:
|
||||
ShowNetworkAskRelay(connecter_it->second.first, connection_string, token);
|
||||
break;
|
||||
|
||||
case URS_ALLOW:
|
||||
this->StartTurnConnection(token);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this->StartTurnConnection(token);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientNetworkCoordinatorSocketHandler::StartTurnConnection(std::string &token)
|
||||
{
|
||||
auto turn_it = this->turn_handlers.find(token);
|
||||
if (turn_it == this->turn_handlers.end()) return;
|
||||
|
||||
turn_it->second->Connect();
|
||||
}
|
||||
|
||||
void ClientNetworkCoordinatorSocketHandler::Connect()
|
||||
{
|
||||
/* We are either already connected or are trying to connect. */
|
||||
if (this->sock != INVALID_SOCKET || this->connecting) return;
|
||||
|
||||
this->Reopen();
|
||||
|
||||
this->connecting = true;
|
||||
this->last_activity = std::chrono::steady_clock::now();
|
||||
|
||||
new NetworkCoordinatorConnecter(NetworkCoordinatorConnectionString());
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkCoordinatorSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
NetworkCoordinatorSocketHandler::CloseConnection(error);
|
||||
|
||||
this->CloseSocket();
|
||||
this->connecting = false;
|
||||
|
||||
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
|
||||
this->next_update = {};
|
||||
|
||||
this->CloseAllConnections();
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our server to receive our invite code.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::Register()
|
||||
{
|
||||
_network_server_connection_type = CONNECTION_TYPE_UNKNOWN;
|
||||
this->next_update = {};
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
|
||||
this->Connect();
|
||||
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_SERVER_REGISTER);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_uint8(_settings_client.network.server_game_type);
|
||||
p->Send_uint16(_settings_client.network.server_port);
|
||||
if (_settings_client.network.server_invite_code.empty() || _settings_client.network.server_invite_code_secret.empty()) {
|
||||
p->Send_string("");
|
||||
p->Send_string("");
|
||||
} else {
|
||||
p->Send_string(_settings_client.network.server_invite_code);
|
||||
p->Send_string(_settings_client.network.server_invite_code_secret);
|
||||
}
|
||||
|
||||
this->SendPacket(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an update of our server status to the Game Coordinator.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::SendServerUpdate()
|
||||
{
|
||||
Debug(net, 6, "Sending server update to Game Coordinator");
|
||||
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_SERVER_UPDATE, TCP_MTU);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
SerializeNetworkGameInfo(p, GetCurrentNetworkServerGameInfo(), this->next_update.time_since_epoch() != std::chrono::nanoseconds::zero());
|
||||
|
||||
this->SendPacket(p);
|
||||
|
||||
this->next_update = std::chrono::steady_clock::now() + NETWORK_COORDINATOR_DELAY_BETWEEN_UPDATES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a listing of all public servers.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::GetListing()
|
||||
{
|
||||
this->Connect();
|
||||
|
||||
_network_game_list_version++;
|
||||
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_LISTING);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_uint8(NETWORK_GAME_INFO_VERSION);
|
||||
p->Send_string(_openttd_revision);
|
||||
p->Send_uint32(this->newgrf_lookup_table_cursor);
|
||||
|
||||
this->SendPacket(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a server based on an invite code.
|
||||
* @param invite_code The invite code of the server to connect to.
|
||||
* @param connecter The connecter of the request.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter)
|
||||
{
|
||||
assert(StrStartsWith(invite_code, "+"));
|
||||
|
||||
if (this->connecter_pre.find(invite_code) != this->connecter_pre.end()) {
|
||||
/* If someone is hammering the refresh key, one can sent out two
|
||||
* requests for the same invite code. There isn't really a great way
|
||||
* of handling this, so just ignore this request. */
|
||||
connecter->SetFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initially we store based on invite code; on first reply we know the
|
||||
* token, and will start using that key instead. */
|
||||
this->connecter_pre[invite_code] = connecter;
|
||||
|
||||
this->Connect();
|
||||
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECT);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(invite_code);
|
||||
|
||||
this->SendPacket(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from a Connecter to let the Game Coordinator know the connection failed.
|
||||
* @param token Token of the connecter that failed.
|
||||
* @param tracking_number Tracking number of the connecter that failed.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::ConnectFailure(const std::string &token, uint8 tracking_number)
|
||||
{
|
||||
/* Connecter will destroy itself. */
|
||||
this->game_connecter = nullptr;
|
||||
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_CONNECT_FAILED);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(token);
|
||||
p->Send_uint8(tracking_number);
|
||||
|
||||
this->SendPacket(p);
|
||||
|
||||
/* We do not close the associated connecter here yet, as the
|
||||
* Game Coordinator might have other methods of connecting available. */
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from a Connecter to let the Game Coordinator know the connection
|
||||
* to the game server is established.
|
||||
* @param token Token of the connecter that succeeded.
|
||||
* @param sock The socket that the connecter can now use.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address)
|
||||
{
|
||||
/* Connecter will destroy itself. */
|
||||
this->game_connecter = nullptr;
|
||||
|
||||
if (_network_server) {
|
||||
if (!ServerNetworkGameSocketHandler::ValidateClient(sock, address)) return;
|
||||
Debug(net, 3, "[{}] Client connected from {} on frame {}", ServerNetworkGameSocketHandler::GetName(), address.GetHostname(), _frame_counter);
|
||||
ServerNetworkGameSocketHandler::AcceptConnection(sock, address);
|
||||
} else {
|
||||
/* The client informs the Game Coordinator about the success. The server
|
||||
* doesn't have to, as it is implied by the client telling. */
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_CONNECTED);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(token);
|
||||
this->SendPacket(p);
|
||||
|
||||
/* Find the connecter; it can happen it no longer exist, in cases where
|
||||
* we aborted the connect but the Game Coordinator was already in the
|
||||
* processes of connecting us. */
|
||||
auto connecter_it = this->connecter.find(token);
|
||||
if (connecter_it != this->connecter.end()) {
|
||||
connecter_it->second.second->SetConnected(sock);
|
||||
this->connecter.erase(connecter_it);
|
||||
}
|
||||
}
|
||||
|
||||
/* Close all remaining connections. */
|
||||
this->CloseToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from the STUN connecter to inform the Game Coordinator about the
|
||||
* result of the STUN.
|
||||
*
|
||||
* This helps the Game Coordinator not to wait for a timeout on its end, but
|
||||
* rather react as soon as the client/server knows the result.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::StunResult(const std::string &token, uint8 family, bool result)
|
||||
{
|
||||
Packet *p = new Packet(PACKET_COORDINATOR_SERCLI_STUN_RESULT);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(token);
|
||||
p->Send_uint8(family);
|
||||
p->Send_bool(result);
|
||||
this->SendPacket(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the STUN handler.
|
||||
* @param token The token used for the STUN handlers.
|
||||
* @param family The family of STUN handlers to close. AF_UNSPEC to close all STUN handlers for this token.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::CloseStunHandler(const std::string &token, uint8 family)
|
||||
{
|
||||
auto stun_it = this->stun_handlers.find(token);
|
||||
if (stun_it == this->stun_handlers.end()) return;
|
||||
|
||||
if (family == AF_UNSPEC) {
|
||||
for (auto &[family, stun_handler] : stun_it->second) {
|
||||
stun_handler->CloseConnection();
|
||||
stun_handler->CloseSocket();
|
||||
}
|
||||
|
||||
this->stun_handlers.erase(stun_it);
|
||||
} else {
|
||||
auto family_it = stun_it->second.find(family);
|
||||
if (family_it == stun_it->second.end()) return;
|
||||
|
||||
family_it->second->CloseConnection();
|
||||
family_it->second->CloseSocket();
|
||||
|
||||
stun_it->second.erase(family_it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the TURN handler.
|
||||
* @param token The token used for the TURN handler.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::CloseTurnHandler(const std::string &token)
|
||||
{
|
||||
CloseWindowByClass(WC_NETWORK_ASK_RELAY);
|
||||
|
||||
auto turn_it = this->turn_handlers.find(token);
|
||||
if (turn_it == this->turn_handlers.end()) return;
|
||||
|
||||
turn_it->second->CloseConnection();
|
||||
turn_it->second->CloseSocket();
|
||||
|
||||
/* We don't remove turn_handler here, as we can be called from within that
|
||||
* turn_handler instance, so our object cannot be free'd yet. Instead, we
|
||||
* check later if the connection is closed, and free the object then. */
|
||||
}
|
||||
|
||||
/**
|
||||
* Close everything related to this connection token.
|
||||
* @param token The connection token to close.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::CloseToken(const std::string &token)
|
||||
{
|
||||
/* Close all remaining STUN / TURN connections. */
|
||||
this->CloseStunHandler(token);
|
||||
this->CloseTurnHandler(token);
|
||||
|
||||
/* Close the caller of the connection attempt. */
|
||||
auto connecter_it = this->connecter.find(token);
|
||||
if (connecter_it != this->connecter.end()) {
|
||||
connecter_it->second.second->SetFailure();
|
||||
this->connecter.erase(connecter_it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all pending connection tokens.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::CloseAllConnections()
|
||||
{
|
||||
/* Ensure all other pending connection attempts are also killed. */
|
||||
if (this->game_connecter != nullptr) {
|
||||
this->game_connecter->Kill();
|
||||
this->game_connecter = nullptr;
|
||||
}
|
||||
|
||||
/* Mark any pending connecters as failed. */
|
||||
for (auto &[token, it] : this->connecter) {
|
||||
this->CloseStunHandler(token);
|
||||
this->CloseTurnHandler(token);
|
||||
it.second->SetFailure();
|
||||
|
||||
/* Inform the Game Coordinator he can stop trying to connect us to the server. */
|
||||
this->ConnectFailure(token, 0);
|
||||
}
|
||||
this->stun_handlers.clear();
|
||||
this->turn_handlers.clear();
|
||||
this->connecter.clear();
|
||||
|
||||
/* Also close any pending invite-code requests. */
|
||||
for (auto &[invite_code, it] : this->connecter_pre) {
|
||||
it->SetFailure();
|
||||
}
|
||||
this->connecter_pre.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we received/can send some data from/to the Game Coordinator server and
|
||||
* when that's the case handle it appropriately.
|
||||
*/
|
||||
void ClientNetworkCoordinatorSocketHandler::SendReceive()
|
||||
{
|
||||
/* Private games are not listed via the Game Coordinator. */
|
||||
if (_network_server && _settings_client.network.server_game_type == SERVER_GAME_TYPE_LOCAL) {
|
||||
if (this->sock != INVALID_SOCKET) {
|
||||
this->CloseConnection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static int last_attempt_backoff = 1;
|
||||
static bool first_reconnect = true;
|
||||
|
||||
if (this->sock == INVALID_SOCKET) {
|
||||
static std::chrono::steady_clock::time_point last_attempt = {};
|
||||
|
||||
/* Don't auto-reconnect when we are not a server. */
|
||||
if (!_network_server) return;
|
||||
/* Don't reconnect if we are connecting. */
|
||||
if (this->connecting) return;
|
||||
/* Throttle how often we try to reconnect. */
|
||||
if (std::chrono::steady_clock::now() < last_attempt + std::chrono::seconds(1) * last_attempt_backoff) return;
|
||||
|
||||
last_attempt = std::chrono::steady_clock::now();
|
||||
/* Delay reconnecting with up to 32 seconds. */
|
||||
if (last_attempt_backoff < 32) {
|
||||
last_attempt_backoff *= 2;
|
||||
}
|
||||
|
||||
/* Do not reconnect on the first attempt, but only initialize the
|
||||
* last_attempt variables. Otherwise after an outage all servers
|
||||
* reconnect at the same time, potentially overwhelming the
|
||||
* Game Coordinator. */
|
||||
if (first_reconnect) {
|
||||
first_reconnect = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(net, 1, "Connection with Game Coordinator lost; reconnecting...");
|
||||
this->Register();
|
||||
return;
|
||||
}
|
||||
|
||||
last_attempt_backoff = 1;
|
||||
first_reconnect = true;
|
||||
|
||||
if (_network_server && _network_server_connection_type != CONNECTION_TYPE_UNKNOWN && std::chrono::steady_clock::now() > this->next_update) {
|
||||
this->SendServerUpdate();
|
||||
}
|
||||
|
||||
if (!_network_server && std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
|
||||
this->CloseConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->CanSendReceive()) {
|
||||
if (this->ReceivePackets()) {
|
||||
this->last_activity = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
this->SendPackets();
|
||||
|
||||
for (const auto &[token, families] : this->stun_handlers) {
|
||||
for (const auto &[family, stun_handler] : families) {
|
||||
stun_handler->SendReceive();
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for handlers that are not connecting nor connected. Destroy those objects. */
|
||||
for (auto turn_it = this->turn_handlers.begin(); turn_it != this->turn_handlers.end(); /* nothing */) {
|
||||
if (turn_it->second->connect_started && turn_it->second->connecter == nullptr && !turn_it->second->IsConnected()) {
|
||||
turn_it = this->turn_handlers.erase(turn_it);
|
||||
} else {
|
||||
turn_it++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[token, turn_handler] : this->turn_handlers) {
|
||||
turn_handler->SendReceive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_coordinator.h Part of the network protocol handling Game Coordinator requests. */
|
||||
|
||||
#ifndef NETWORK_COORDINATOR_H
|
||||
#define NETWORK_COORDINATOR_H
|
||||
|
||||
#include "core/tcp_coordinator.h"
|
||||
#include "network_stun.h"
|
||||
#include "network_turn.h"
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* Game Coordinator communication.
|
||||
* For more detail about what the Game Coordinator does, please see
|
||||
* docs/game_coordinator.md.
|
||||
*
|
||||
* For servers:
|
||||
* - Server sends SERVER_REGISTER.
|
||||
* - Game Coordinator probes server to check if it can directly connect.
|
||||
* - Game Coordinator sends GC_REGISTER_ACK with type of connection.
|
||||
* - Server sends every 30 seconds SERVER_UPDATE.
|
||||
*
|
||||
* For clients (listing):
|
||||
* - Client sends CLIENT_LISTING.
|
||||
* - Game Coordinator returns the full list of public servers via GC_LISTING (multiple packets).
|
||||
*
|
||||
* For clients (connecting):
|
||||
* - Client sends CLIENT_CONNECT.
|
||||
* - Game Coordinator checks what type of connections the servers supports:
|
||||
* 1) Direct connect?
|
||||
* - Send the client a GC_CONNECT with the peer address.
|
||||
* - a) Client connects, client sends CLIENT_CONNECTED to Game Coordinator.
|
||||
* - b) Client connect fails, client sends CLIENT_CONNECT_FAILED to Game Coordinator.
|
||||
* 2) STUN?
|
||||
* - Game Coordinator sends GC_STUN_REQUEST to server/client (asking for both IPv4 and IPv6 STUN requests).
|
||||
* - Game Coordinator collects what combination works and sends GC_STUN_CONNECT to server/client.
|
||||
* - a) Server/client connect, client sends CLIENT_CONNECTED to Game Coordinator.
|
||||
* - b) Server/client connect fails, both send SERCLI_CONNECT_FAILED to Game Coordinator.
|
||||
* - Game Coordinator tries other combination if available.
|
||||
* 3) TURN?
|
||||
* - Game Coordinator sends GC_TURN_CONNECT to server/client.
|
||||
* - a) Server/client connect, client sends CLIENT_CONNECTED to Game Coordinator.
|
||||
* - b) Server/client connect fails, both send SERCLI_CONNECT_FAILED to Game Coordinator.
|
||||
* - If all fails, Game Coordinator sends GC_CONNECT_FAILED to indicate no connection is possible.
|
||||
*/
|
||||
|
||||
/** Class for handling the client side of the Game Coordinator connection. */
|
||||
class ClientNetworkCoordinatorSocketHandler : public NetworkCoordinatorSocketHandler {
|
||||
private:
|
||||
std::chrono::steady_clock::time_point next_update; ///< When to send the next update (if server and public).
|
||||
std::map<std::string, std::pair<std::string, TCPServerConnecter *>> connecter; ///< Based on tokens, the current (invite-code, connecter) that are pending.
|
||||
std::map<std::string, TCPServerConnecter *> connecter_pre; ///< Based on invite codes, the current connecters that are pending.
|
||||
std::map<std::string, std::map<int, std::unique_ptr<ClientNetworkStunSocketHandler>>> stun_handlers; ///< All pending STUN handlers, stored by token:family.
|
||||
std::map<std::string, std::unique_ptr<ClientNetworkTurnSocketHandler>> turn_handlers; ///< Pending TURN handler (if any), stored by token.
|
||||
TCPConnecter *game_connecter = nullptr; ///< Pending connecter to the game server.
|
||||
|
||||
uint32 newgrf_lookup_table_cursor = 0; ///< Last received cursor for the #GameInfoNewGRFLookupTable updates.
|
||||
GameInfoNewGRFLookupTable newgrf_lookup_table; ///< Table to look up NewGRFs in the GC_LISTING packets.
|
||||
|
||||
protected:
|
||||
bool Receive_GC_ERROR(Packet *p) override;
|
||||
bool Receive_GC_REGISTER_ACK(Packet *p) override;
|
||||
bool Receive_GC_LISTING(Packet *p) override;
|
||||
bool Receive_GC_CONNECTING(Packet *p) override;
|
||||
bool Receive_GC_CONNECT_FAILED(Packet *p) override;
|
||||
bool Receive_GC_DIRECT_CONNECT(Packet *p) override;
|
||||
bool Receive_GC_STUN_REQUEST(Packet *p) override;
|
||||
bool Receive_GC_STUN_CONNECT(Packet *p) override;
|
||||
bool Receive_GC_NEWGRF_LOOKUP(Packet *p) override;
|
||||
bool Receive_GC_TURN_CONNECT(Packet *p) override;
|
||||
|
||||
public:
|
||||
/** The idle timeout; when to close the connection because it's idle. */
|
||||
static constexpr std::chrono::seconds IDLE_TIMEOUT = std::chrono::seconds(60);
|
||||
|
||||
std::chrono::steady_clock::time_point last_activity; ///< The last time there was network activity.
|
||||
bool connecting; ///< Are we connecting to the Game Coordinator?
|
||||
|
||||
ClientNetworkCoordinatorSocketHandler() : connecting(false) {}
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
void SendReceive();
|
||||
|
||||
void ConnectFailure(const std::string &token, uint8 tracking_number);
|
||||
void ConnectSuccess(const std::string &token, SOCKET sock, NetworkAddress &address);
|
||||
void StunResult(const std::string &token, uint8 family, bool result);
|
||||
|
||||
void Connect();
|
||||
void CloseToken(const std::string &token);
|
||||
void CloseAllConnections();
|
||||
void CloseStunHandler(const std::string &token, uint8 family = AF_UNSPEC);
|
||||
void CloseTurnHandler(const std::string &token);
|
||||
|
||||
void Register();
|
||||
void SendServerUpdate();
|
||||
void GetListing();
|
||||
|
||||
void ConnectToServer(const std::string &invite_code, TCPServerConnecter *connecter);
|
||||
void StartTurnConnection(std::string &token);
|
||||
};
|
||||
|
||||
extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client;
|
||||
|
||||
#endif /* NETWORK_COORDINATOR_H */
|
||||
+21
-19
@@ -28,38 +28,38 @@ extern NetworkCompanyState *_network_company_states;
|
||||
|
||||
extern ClientID _network_own_client_id;
|
||||
extern ClientID _redirect_console_to_client;
|
||||
extern bool _network_need_advertise;
|
||||
extern uint8 _network_reconnect;
|
||||
extern StringList _network_bind_list;
|
||||
extern StringList _network_host_list;
|
||||
extern StringList _network_ban_list;
|
||||
|
||||
byte NetworkSpectatorCount();
|
||||
bool NetworkIsValidClientName(const char *client_name);
|
||||
bool NetworkValidateClientName();
|
||||
bool NetworkValidateClientName(char *client_name);
|
||||
void NetworkUpdateClientName();
|
||||
bool NetworkIsValidClientName(const std::string_view client_name);
|
||||
bool NetworkValidateOurClientName();
|
||||
bool NetworkValidateClientName(std::string &client_name);
|
||||
bool NetworkValidateServerName(std::string &server_name);
|
||||
void NetworkUpdateClientName(const std::string &client_name);
|
||||
void NetworkUpdateServerGameType();
|
||||
bool NetworkCompanyHasClients(CompanyID company);
|
||||
const char *NetworkChangeCompanyPassword(CompanyID company_id, const char *password);
|
||||
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password);
|
||||
void NetworkReboot();
|
||||
void NetworkDisconnect(bool blocking = false, bool close_admins = true);
|
||||
void NetworkGameLoop();
|
||||
void NetworkBackgroundLoop();
|
||||
void ParseFullConnectionString(const char **company, const char **port, char *connection_string);
|
||||
std::string_view ParseFullConnectionString(const std::string &connection_string, uint16 &port, CompanyID *company_id = nullptr);
|
||||
void NetworkStartDebugLog(const std::string &connection_string);
|
||||
void NetworkPopulateCompanyStats(NetworkCompanyStats *stats);
|
||||
|
||||
void NetworkUpdateClientInfo(ClientID client_id);
|
||||
void NetworkClientsToSpectators(CompanyID cid);
|
||||
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const char *join_server_password = nullptr, const char *join_company_password = nullptr);
|
||||
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password = "", const std::string &join_company_password = "");
|
||||
void NetworkClientJoinGame();
|
||||
void NetworkClientRequestMove(CompanyID company, const char *pass = "");
|
||||
void NetworkClientSendRcon(const char *password, const char *command);
|
||||
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const char *msg, int64 data = 0);
|
||||
void NetworkClientRequestMove(CompanyID company, const std::string &pass = "");
|
||||
void NetworkClientSendRcon(const std::string &password, const std::string &command);
|
||||
void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, int64 data = 0);
|
||||
bool NetworkClientPreferTeamChat(const NetworkClientInfo *cio);
|
||||
bool NetworkCompanyIsPassworded(CompanyID company_id);
|
||||
bool NetworkMaxCompaniesReached();
|
||||
bool NetworkMaxSpectatorsReached();
|
||||
void NetworkPrintClients();
|
||||
void NetworkHandlePauseChange(PauseMode prev_mode, PauseMode changed_mode);
|
||||
|
||||
@@ -68,22 +68,24 @@ void NetworkServerDailyLoop();
|
||||
void NetworkServerMonthlyLoop();
|
||||
void NetworkServerYearlyLoop();
|
||||
void NetworkServerSendConfigUpdate();
|
||||
void NetworkServerUpdateGameInfo();
|
||||
void NetworkServerShowStatusToConsole();
|
||||
bool NetworkServerStart();
|
||||
void NetworkServerNewCompany(const Company *company, NetworkClientInfo *ci);
|
||||
bool NetworkServerChangeClientName(ClientID client_id, const char *new_name);
|
||||
bool NetworkServerChangeClientName(ClientID client_id, const std::string &new_name);
|
||||
|
||||
|
||||
void NetworkServerDoMove(ClientID client_id, CompanyID company_id);
|
||||
void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const char *string);
|
||||
void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const char *msg, ClientID from_id, int64 data = 0, bool from_admin = false);
|
||||
void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string);
|
||||
void NetworkServerSendChat(NetworkAction action, DestType type, int dest, const std::string &msg, ClientID from_id, int64 data = 0, bool from_admin = false);
|
||||
void NetworkServerSendExternalChat(const std::string &source, TextColour colour, const std::string &user, const std::string &msg);
|
||||
|
||||
void NetworkServerKickClient(ClientID client_id, const char *reason);
|
||||
uint NetworkServerKickOrBanIP(ClientID client_id, bool ban, const char *reason);
|
||||
uint NetworkServerKickOrBanIP(const char *ip, bool ban, const char *reason);
|
||||
void NetworkServerKickClient(ClientID client_id, const std::string &reason);
|
||||
uint NetworkServerKickOrBanIP(ClientID client_id, bool ban, const std::string &reason);
|
||||
uint NetworkServerKickOrBanIP(const std::string &ip, bool ban, const std::string &reason);
|
||||
|
||||
void NetworkInitChatMessage();
|
||||
void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const char *message, ...) WARN_FORMAT(3, 4);
|
||||
void CDECL NetworkAddChatMessage(TextColour colour, uint duration, const std::string &message);
|
||||
void NetworkUndrawChatMessage();
|
||||
void NetworkChatMessageLoop();
|
||||
|
||||
|
||||
@@ -20,66 +20,31 @@
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
NetworkGameList *_network_game_list = nullptr;
|
||||
|
||||
/** The games to insert when the GUI thread has time for us. */
|
||||
static std::atomic<NetworkGameList *> _network_game_delayed_insertion_list(nullptr);
|
||||
|
||||
/**
|
||||
* Add a new item to the linked gamelist, but do it delayed in the next tick
|
||||
* or so to prevent race conditions.
|
||||
* @param item the item to add. Will be freed once added.
|
||||
*/
|
||||
void NetworkGameListAddItemDelayed(NetworkGameList *item)
|
||||
{
|
||||
item->next = _network_game_delayed_insertion_list.load(std::memory_order_relaxed);
|
||||
while (!_network_game_delayed_insertion_list.compare_exchange_weak(item->next, item, std::memory_order_acq_rel)) {}
|
||||
}
|
||||
|
||||
/** Perform the delayed (thread safe) insertion into the game list */
|
||||
static void NetworkGameListHandleDelayedInsert()
|
||||
{
|
||||
while (true) {
|
||||
NetworkGameList *ins_item = _network_game_delayed_insertion_list.load(std::memory_order_relaxed);
|
||||
while (ins_item != nullptr && !_network_game_delayed_insertion_list.compare_exchange_weak(ins_item, ins_item->next, std::memory_order_acq_rel)) {}
|
||||
if (ins_item == nullptr) break; // No item left.
|
||||
|
||||
NetworkGameList *item = NetworkGameListAddItem(ins_item->address);
|
||||
|
||||
if (item != nullptr) {
|
||||
if (StrEmpty(item->info.server_name)) {
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
memset(&item->info, 0, sizeof(item->info));
|
||||
strecpy(item->info.server_name, ins_item->info.server_name, lastof(item->info.server_name));
|
||||
item->online = false;
|
||||
}
|
||||
item->manually |= ins_item->manually;
|
||||
if (item->manually) NetworkRebuildHostList();
|
||||
UpdateNetworkGameWindow();
|
||||
}
|
||||
free(ins_item);
|
||||
}
|
||||
}
|
||||
NetworkGameList *_network_game_list = nullptr; ///< Game list of this client.
|
||||
int _network_game_list_version = 0; ///< Current version of all items in the list.
|
||||
|
||||
/**
|
||||
* Add a new item to the linked gamelist. If the IP and Port match
|
||||
* return the existing item instead of adding it again
|
||||
* @param address the address of the to-be added item
|
||||
* @param connection_string the address of the to-be added item
|
||||
* @return a point to the newly added or already existing item
|
||||
*/
|
||||
NetworkGameList *NetworkGameListAddItem(NetworkAddress address)
|
||||
NetworkGameList *NetworkGameListAddItem(const std::string &connection_string)
|
||||
{
|
||||
NetworkGameList *item, *prev_item;
|
||||
|
||||
/* Parse the connection string to ensure the default port is there. */
|
||||
const std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT).connection_string;
|
||||
|
||||
prev_item = nullptr;
|
||||
for (item = _network_game_list; item != nullptr; item = item->next) {
|
||||
if (item->address == address) return item;
|
||||
if (item->connection_string == resolved_connection_string) return item;
|
||||
prev_item = item;
|
||||
}
|
||||
|
||||
item = CallocT<NetworkGameList>(1);
|
||||
item->next = nullptr;
|
||||
item->address = address;
|
||||
item = new NetworkGameList(resolved_connection_string);
|
||||
item->info.gamescript_version = -1;
|
||||
item->version = _network_game_list_version;
|
||||
|
||||
if (prev_item == nullptr) {
|
||||
_network_game_list = item;
|
||||
@@ -109,10 +74,8 @@ void NetworkGameListRemoveItem(NetworkGameList *remove)
|
||||
|
||||
/* Remove GRFConfig information */
|
||||
ClearGRFConfigList(&remove->info.grfconfig);
|
||||
free(remove);
|
||||
remove = nullptr;
|
||||
delete remove;
|
||||
|
||||
DEBUG(net, 4, "[gamelist] removed server from list");
|
||||
NetworkRebuildHostList();
|
||||
UpdateNetworkGameWindow();
|
||||
return;
|
||||
@@ -121,29 +84,31 @@ void NetworkGameListRemoveItem(NetworkGameList *remove)
|
||||
}
|
||||
}
|
||||
|
||||
static const uint MAX_GAME_LIST_REQUERY_COUNT = 10; ///< How often do we requery in number of times per server?
|
||||
static const uint REQUERY_EVERY_X_GAMELOOPS = 60; ///< How often do we requery in time?
|
||||
static const uint REFRESH_GAMEINFO_X_REQUERIES = 50; ///< Refresh the game info itself after REFRESH_GAMEINFO_X_REQUERIES * REQUERY_EVERY_X_GAMELOOPS game loops
|
||||
|
||||
/** Requeries the (game) servers we have not gotten a reply from */
|
||||
void NetworkGameListRequery()
|
||||
/**
|
||||
* Remove all servers that have not recently been updated.
|
||||
* Call this after you received all the servers from the Game Coordinator, so
|
||||
* the ones that are no longer listed are removed.
|
||||
*/
|
||||
void NetworkGameListRemoveExpired()
|
||||
{
|
||||
NetworkGameListHandleDelayedInsert();
|
||||
NetworkGameList **prev_item = &_network_game_list;
|
||||
|
||||
static uint8 requery_cnt = 0;
|
||||
for (NetworkGameList *item = _network_game_list; item != nullptr;) {
|
||||
if (!item->manually && item->version < _network_game_list_version) {
|
||||
NetworkGameList *remove = item;
|
||||
item = item->next;
|
||||
*prev_item = item;
|
||||
|
||||
if (++requery_cnt < REQUERY_EVERY_X_GAMELOOPS) return;
|
||||
requery_cnt = 0;
|
||||
|
||||
for (NetworkGameList *item = _network_game_list; item != nullptr; item = item->next) {
|
||||
item->retries++;
|
||||
if (item->retries < REFRESH_GAMEINFO_X_REQUERIES && (item->online || item->retries >= MAX_GAME_LIST_REQUERY_COUNT)) continue;
|
||||
|
||||
/* item gets mostly zeroed by NetworkUDPQueryServer */
|
||||
uint8 retries = item->retries;
|
||||
NetworkUDPQueryServer(item->address);
|
||||
item->retries = (retries >= REFRESH_GAMEINFO_X_REQUERIES) ? 0 : retries;
|
||||
/* Remove GRFConfig information */
|
||||
ClearGRFConfigList(&remove->info.grfconfig);
|
||||
delete remove;
|
||||
} else {
|
||||
prev_item = &item->next;
|
||||
item = item->next;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,10 +126,7 @@ void NetworkAfterNewGRFScan()
|
||||
|
||||
const GRFConfig *f = FindGRFConfig(c->ident.grfid, FGCM_EXACT, c->ident.md5sum);
|
||||
if (f == nullptr) {
|
||||
/* Don't know the GRF, so mark game incompatible and the (possibly)
|
||||
* already resolved name for this GRF (another server has sent the
|
||||
* name of the GRF already. */
|
||||
c->name = FindUnknownGRFName(c->ident.grfid, c->ident.md5sum, true);
|
||||
/* Don't know the GRF (anymore), so mark game incompatible. */
|
||||
c->status = GCS_NOT_FOUND;
|
||||
|
||||
/* If we miss a file, we're obviously incompatible. */
|
||||
|
||||
@@ -14,22 +14,33 @@
|
||||
#include "core/game_info.h"
|
||||
#include "network_type.h"
|
||||
|
||||
/** Structure with information shown in the game list (GUI) */
|
||||
struct NetworkGameList {
|
||||
NetworkGameInfo info; ///< The game information of this server
|
||||
NetworkAddress address; ///< The connection info of the game server
|
||||
bool online; ///< False if the server did not respond (default status)
|
||||
bool manually; ///< True if the server was added manually
|
||||
uint8 retries; ///< Number of retries (to stop requerying)
|
||||
NetworkGameList *next; ///< Next pointer to make a linked game list
|
||||
/** The status a server can be in. */
|
||||
enum NetworkGameListStatus {
|
||||
NGLS_OFFLINE, ///< Server is offline (or cannot be queried).
|
||||
NGLS_ONLINE, ///< Server is online.
|
||||
NGLS_FULL, ///< Server is full and cannot be queried.
|
||||
NGLS_BANNED, ///< You are banned from this server.
|
||||
NGLS_TOO_OLD, ///< Server is too old to query.
|
||||
};
|
||||
|
||||
/** Game list of this client */
|
||||
extern NetworkGameList *_network_game_list;
|
||||
/** Structure with information shown in the game list (GUI) */
|
||||
struct NetworkGameList {
|
||||
NetworkGameList(const std::string &connection_string) : connection_string(connection_string) {}
|
||||
|
||||
void NetworkGameListAddItemDelayed(NetworkGameList *item);
|
||||
NetworkGameList *NetworkGameListAddItem(NetworkAddress address);
|
||||
NetworkGameInfo info = {}; ///< The game information of this server.
|
||||
std::string connection_string; ///< Address of the server.
|
||||
NetworkGameListStatus status = NGLS_OFFLINE; ///< Stats of the server.
|
||||
bool manually = false; ///< True if the server was added manually.
|
||||
uint8 retries = 0; ///< Number of retries (to stop requerying).
|
||||
int version = 0; ///< Used to see which servers are no longer available on the Game Coordinator and can be removed.
|
||||
NetworkGameList *next = nullptr; ///< Next pointer to make a linked game list.
|
||||
};
|
||||
|
||||
extern NetworkGameList *_network_game_list;
|
||||
extern int _network_game_list_version;
|
||||
|
||||
NetworkGameList *NetworkGameListAddItem(const std::string &connection_string);
|
||||
void NetworkGameListRemoveItem(NetworkGameList *remove);
|
||||
void NetworkGameListRequery();
|
||||
void NetworkGameListRemoveExpired();
|
||||
|
||||
#endif /* NETWORK_GAMELIST_H */
|
||||
|
||||
+294
-455
File diff suppressed because it is too large
Load Diff
@@ -23,21 +23,20 @@ void ShowJoinStatusWindow();
|
||||
void ShowNetworkGameWindow();
|
||||
void ShowClientList();
|
||||
void ShowNetworkCompanyPasswordWindow(Window *parent);
|
||||
void ShowNetworkAskRelay(const std::string &server_connection_string, const std::string &relay_connection_string, const std::string &token);
|
||||
|
||||
|
||||
/** Company information stored at the client side */
|
||||
struct NetworkCompanyInfo : NetworkCompanyStats {
|
||||
char company_name[NETWORK_COMPANY_NAME_LENGTH]; ///< Company name
|
||||
Year inaugurated_year; ///< What year the company started in
|
||||
Money company_value; ///< The company value
|
||||
Money money; ///< The amount of money the company has
|
||||
Money income; ///< How much did the company earned last year
|
||||
uint16 performance; ///< What was his performance last month?
|
||||
bool use_password; ///< Is there a password
|
||||
char clients[NETWORK_CLIENTS_LENGTH]; ///< The clients that control this company (Name1, name2, ..)
|
||||
std::string company_name; ///< Company name
|
||||
Year inaugurated_year; ///< What year the company started in
|
||||
Money company_value; ///< The company value
|
||||
Money money; ///< The amount of money the company has
|
||||
Money income; ///< How much did the company earn last year
|
||||
uint16 performance; ///< What was his performance last month?
|
||||
bool use_password; ///< Is there a password
|
||||
std::string clients; ///< The clients that control this company (Name1, name2, ..)
|
||||
};
|
||||
|
||||
NetworkCompanyInfo *GetLobbyCompanyInfo(CompanyID company);
|
||||
NetworkGameList *GetLobbyGameInfo();
|
||||
|
||||
#endif /* NETWORK_GUI_H */
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define NETWORK_INTERNAL_H
|
||||
|
||||
#include "network_func.h"
|
||||
#include "core/tcp_coordinator.h"
|
||||
#include "core/tcp_game.h"
|
||||
|
||||
#include "../command_type.h"
|
||||
@@ -82,15 +83,20 @@ extern NetworkJoinStatus _network_join_status;
|
||||
extern uint8 _network_join_waiting;
|
||||
extern uint32 _network_join_bytes;
|
||||
extern uint32 _network_join_bytes_total;
|
||||
extern ConnectionType _network_server_connection_type;
|
||||
extern std::string _network_server_invite_code;
|
||||
|
||||
/* Variable available for clients. */
|
||||
extern std::string _network_server_name;
|
||||
|
||||
extern uint8 _network_reconnect;
|
||||
|
||||
extern CompanyMask _network_company_passworded;
|
||||
|
||||
void NetworkTCPQueryServer(NetworkAddress address, bool request_company_info = false);
|
||||
void NetworkQueryServer(const std::string &connection_string);
|
||||
|
||||
void GetBindAddresses(NetworkAddressList *addresses, uint16 port);
|
||||
struct NetworkGameList *NetworkAddServer(const std::string &connection_string);
|
||||
struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true, bool never_expire = false);
|
||||
void NetworkRebuildHostList();
|
||||
void UpdateNetworkGameWindow();
|
||||
|
||||
@@ -113,14 +119,14 @@ void NetworkFreeLocalCommandQueue();
|
||||
void NetworkSyncCommandQueue(NetworkClientSocket *cs);
|
||||
|
||||
void ShowNetworkError(StringID error_string);
|
||||
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const char *name, const char *str = "", int64 data = 0);
|
||||
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const std::string &name, const std::string &str = "", int64 data = 0, const std::string &data_str = "");
|
||||
uint NetworkCalculateLag(const NetworkClientSocket *cs);
|
||||
StringID GetNetworkErrorMsg(NetworkErrorCode err);
|
||||
bool NetworkFindName(char *new_name, const char *last);
|
||||
const char *GenerateCompanyPasswordHash(const char *password, const char *password_server_id, uint32 password_game_seed);
|
||||
bool NetworkMakeClientNameUnique(std::string &new_name);
|
||||
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed);
|
||||
|
||||
bool NetworkClientConnectGame(NetworkAddress &address, CompanyID join_as, const char *join_server_password = nullptr, const char *join_company_password = nullptr);
|
||||
NetworkAddress ParseConnectionString(const std::string &connection_string, int default_port);
|
||||
NetworkAddress ParseGameConnectionString(CompanyID *company, const std::string &connection_string, int default_port);
|
||||
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id);
|
||||
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port);
|
||||
std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port);
|
||||
|
||||
#endif /* NETWORK_INTERNAL_H */
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_query.cpp Query part of the network protocol. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "core/game_info.h"
|
||||
#include "network_query.h"
|
||||
#include "network_gamelist.h"
|
||||
#include "../error.h"
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
std::vector<std::unique_ptr<QueryNetworkGameSocketHandler>> QueryNetworkGameSocketHandler::queries = {};
|
||||
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status)
|
||||
{
|
||||
assert(status != NETWORK_RECV_STATUS_OKAY);
|
||||
assert(this->sock != INVALID_SOCKET);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the connection's state, i.e. is the connection still up?
|
||||
*/
|
||||
bool QueryNetworkGameSocketHandler::CheckConnection()
|
||||
{
|
||||
std::chrono::steady_clock::duration lag = std::chrono::steady_clock::now() - this->last_packet;
|
||||
|
||||
/* If there was no response in 5 seconds, terminate the query. */
|
||||
if (lag > std::chrono::seconds(5)) {
|
||||
this->CloseConnection(NETWORK_RECV_STATUS_CONNECTION_LOST);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we received/can send some data from/to the server and
|
||||
* when that's the case handle it appropriately.
|
||||
* @return true when everything went okay.
|
||||
*/
|
||||
bool QueryNetworkGameSocketHandler::Receive()
|
||||
{
|
||||
if (this->CanSendReceive()) {
|
||||
NetworkRecvStatus res = this->ReceivePackets();
|
||||
if (res != NETWORK_RECV_STATUS_OKAY) {
|
||||
this->CloseConnection(res);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Send the packets of this socket handler. */
|
||||
void QueryNetworkGameSocketHandler::Send()
|
||||
{
|
||||
this->SendPackets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the server for server information.
|
||||
*/
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::SendGameInfo()
|
||||
{
|
||||
this->SendPacket(new Packet(PACKET_CLIENT_GAME_INFO));
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::Receive_SERVER_FULL(Packet *p)
|
||||
{
|
||||
NetworkGameList *item = NetworkGameListAddItem(this->connection_string);
|
||||
item->status = NGLS_FULL;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::Receive_SERVER_BANNED(Packet *p)
|
||||
{
|
||||
NetworkGameList *item = NetworkGameListAddItem(this->connection_string);
|
||||
item->status = NGLS_BANNED;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet *p)
|
||||
{
|
||||
NetworkGameList *item = NetworkGameListAddItem(this->connection_string);
|
||||
|
||||
/* Clear any existing GRFConfig chain. */
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
/* Retrieve the NetworkGameInfo from the packet. */
|
||||
DeserializeNetworkGameInfo(p, &item->info);
|
||||
/* Check for compatability with the client. */
|
||||
CheckGameCompatibility(item->info);
|
||||
/* Ensure we consider the server online. */
|
||||
item->status = NGLS_ONLINE;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
NetworkRecvStatus QueryNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet *p)
|
||||
{
|
||||
NetworkErrorCode error = (NetworkErrorCode)p->Recv_uint8();
|
||||
|
||||
NetworkGameList *item = NetworkGameListAddItem(this->connection_string);
|
||||
|
||||
if (error == NETWORK_ERROR_NOT_EXPECTED) {
|
||||
/* If we query a server that is 1.11.1 or older, we get an
|
||||
* NETWORK_ERROR_NOT_EXPECTED on requesting the game info. Show to the
|
||||
* user this server is too old to query.
|
||||
*/
|
||||
item->status = NGLS_TOO_OLD;
|
||||
} else {
|
||||
item->status = NGLS_OFFLINE;
|
||||
}
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any query needs to send or receive.
|
||||
*/
|
||||
/* static */ void QueryNetworkGameSocketHandler::SendReceive()
|
||||
{
|
||||
for (auto it = QueryNetworkGameSocketHandler::queries.begin(); it != QueryNetworkGameSocketHandler::queries.end(); /* nothing */) {
|
||||
if (!(*it)->Receive()) {
|
||||
it = QueryNetworkGameSocketHandler::queries.erase(it);
|
||||
} else if (!(*it)->CheckConnection()) {
|
||||
it = QueryNetworkGameSocketHandler::queries.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &query : QueryNetworkGameSocketHandler::queries) {
|
||||
query->Send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_query.h Query part of the network protocol. */
|
||||
|
||||
#ifndef NETWORK_QUERY_H
|
||||
#define NETWORK_QUERY_H
|
||||
|
||||
#include "network_internal.h"
|
||||
|
||||
/** Class for handling the client side of quering a game server. */
|
||||
class QueryNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
|
||||
private:
|
||||
static std::vector<std::unique_ptr<QueryNetworkGameSocketHandler>> queries; ///< Pending queries.
|
||||
std::string connection_string; ///< Address we are connected to.
|
||||
|
||||
protected:
|
||||
NetworkRecvStatus Receive_SERVER_FULL(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_BANNED(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_ERROR(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_GAME_INFO(Packet *p) override;
|
||||
|
||||
NetworkRecvStatus SendGameInfo();
|
||||
|
||||
bool CheckConnection();
|
||||
void Send();
|
||||
bool Receive();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new socket for the client side of quering game server.
|
||||
* @param s The socket to connect with.
|
||||
* @param connection_string The connection string of the server.
|
||||
*/
|
||||
QueryNetworkGameSocketHandler(SOCKET s, const std::string &connection_string) : NetworkGameSocketHandler(s), connection_string(connection_string) {}
|
||||
|
||||
/**
|
||||
* Start to query a server based on an open socket.
|
||||
* @param s The socket to connect with.
|
||||
* @param connection_string The connection string of the server.
|
||||
*/
|
||||
static void QueryServer(SOCKET s, const std::string &connection_string)
|
||||
{
|
||||
auto query = std::make_unique<QueryNetworkGameSocketHandler>(s, connection_string);
|
||||
query->SendGameInfo();
|
||||
|
||||
QueryNetworkGameSocketHandler::queries.push_back(std::move(query));
|
||||
}
|
||||
|
||||
static void SendReceive();
|
||||
|
||||
NetworkRecvStatus CloseConnection(NetworkRecvStatus status) override;
|
||||
};
|
||||
|
||||
#endif /* NETWORK_QUERY_H */
|
||||
+188
-302
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,6 @@ class ServerNetworkGameSocketHandler : public NetworkClientSocketPool::PoolItem<
|
||||
protected:
|
||||
NetworkRecvStatus Receive_CLIENT_JOIN(Packet *p) override;
|
||||
NetworkRecvStatus Receive_CLIENT_GAME_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_CLIENT_COMPANY_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet *p) override;
|
||||
NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p) override;
|
||||
NetworkRecvStatus Receive_CLIENT_GETMAP(Packet *p) override;
|
||||
@@ -42,7 +41,6 @@ protected:
|
||||
NetworkRecvStatus Receive_CLIENT_MOVE(Packet *p) override;
|
||||
|
||||
NetworkRecvStatus SendGameInfo();
|
||||
NetworkRecvStatus SendCompanyInfo();
|
||||
NetworkRecvStatus SendNewGRFCheck();
|
||||
NetworkRecvStatus SendWelcome();
|
||||
NetworkRecvStatus SendNeedGamePassword();
|
||||
@@ -72,14 +70,14 @@ public:
|
||||
size_t receive_limit; ///< Amount of bytes that we can receive at this moment
|
||||
|
||||
struct PacketWriter *savegame; ///< Writer used to write the savegame.
|
||||
NetworkAddress client_address; ///< IP-address of the client (so he can be banned)
|
||||
NetworkAddress client_address; ///< IP-address of the client (so they can be banned)
|
||||
|
||||
ServerNetworkGameSocketHandler(SOCKET s);
|
||||
~ServerNetworkGameSocketHandler();
|
||||
|
||||
virtual Packet *ReceivePacket() override;
|
||||
NetworkRecvStatus CloseConnection(NetworkRecvStatus status) override;
|
||||
void GetClientName(char *client_name, const char *last) const;
|
||||
std::string GetClientName() const;
|
||||
|
||||
void CheckNextClientToSendMap(NetworkClientSocket *ignore_cs = nullptr);
|
||||
|
||||
@@ -89,12 +87,13 @@ public:
|
||||
NetworkRecvStatus SendQuit(ClientID client_id);
|
||||
NetworkRecvStatus SendShutdown();
|
||||
NetworkRecvStatus SendNewGame();
|
||||
NetworkRecvStatus SendRConResult(uint16 colour, const char *command);
|
||||
NetworkRecvStatus SendRConResult(uint16 colour, const std::string &command);
|
||||
NetworkRecvStatus SendMove(ClientID client_id, CompanyID company_id);
|
||||
|
||||
NetworkRecvStatus SendClientInfo(NetworkClientInfo *ci);
|
||||
NetworkRecvStatus SendError(NetworkErrorCode error, const char *reason = nullptr);
|
||||
NetworkRecvStatus SendChat(NetworkAction action, ClientID client_id, bool self_send, const char *msg, int64 data);
|
||||
NetworkRecvStatus SendError(NetworkErrorCode error, const std::string &reason = {});
|
||||
NetworkRecvStatus SendChat(NetworkAction action, ClientID client_id, bool self_send, const std::string &msg, int64 data);
|
||||
NetworkRecvStatus SendExternalChat(const std::string &source, TextColour colour, const std::string &user, const std::string &msg);
|
||||
NetworkRecvStatus SendJoin(ClientID client_id);
|
||||
NetworkRecvStatus SendFrame();
|
||||
NetworkRecvStatus SendSync();
|
||||
@@ -115,13 +114,13 @@ public:
|
||||
return "server";
|
||||
}
|
||||
|
||||
const char *GetClientIP();
|
||||
const std::string &GetClientIP();
|
||||
|
||||
static ServerNetworkGameSocketHandler *GetByClientID(ClientID client_id);
|
||||
};
|
||||
|
||||
void NetworkServer_Tick(bool send_frame);
|
||||
void NetworkServerSetCompanyPassword(CompanyID company_id, const char *password, bool already_hashed = true);
|
||||
void NetworkServerSetCompanyPassword(CompanyID company_id, const std::string &password, bool already_hashed = true);
|
||||
void NetworkServerUpdateCompanyPassworded(CompanyID company_id, bool passworded);
|
||||
|
||||
#endif /* NETWORK_SERVER_H */
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_stun.cpp STUN sending/receiving part of the network protocol. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../debug.h"
|
||||
#include "network.h"
|
||||
#include "network_coordinator.h"
|
||||
#include "network_stun.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
/** Connect to the STUN server. */
|
||||
class NetworkStunConnecter : public TCPConnecter {
|
||||
private:
|
||||
ClientNetworkStunSocketHandler *stun_handler;
|
||||
std::string token;
|
||||
uint8 family;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initiate the connecting.
|
||||
* @param stun_handler The handler for this request.
|
||||
* @param connection_string The address of the server.
|
||||
*/
|
||||
NetworkStunConnecter(ClientNetworkStunSocketHandler *stun_handler, const std::string &connection_string, const std::string &token, uint8 family) :
|
||||
TCPConnecter(connection_string, NETWORK_STUN_SERVER_PORT, NetworkAddress(), family),
|
||||
stun_handler(stun_handler),
|
||||
token(token),
|
||||
family(family)
|
||||
{
|
||||
}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
this->stun_handler->connecter = nullptr;
|
||||
|
||||
/* Connection to STUN server failed. For example, the client doesn't
|
||||
* support IPv6, which means it will fail that attempt. */
|
||||
|
||||
_network_coordinator_client.StunResult(this->token, this->family, false);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
this->stun_handler->connecter = nullptr;
|
||||
|
||||
assert(this->stun_handler->sock == INVALID_SOCKET);
|
||||
this->stun_handler->sock = s;
|
||||
|
||||
/* Store the local address; later connects will reuse it again.
|
||||
* This is what makes STUN work for most NATs. */
|
||||
this->stun_handler->local_addr = NetworkAddress::GetSockAddress(s);
|
||||
|
||||
/* We leave the connection open till the real connection is setup later. */
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the STUN server over either IPv4 or IPv6.
|
||||
* @param token The token as received from the Game Coordinator.
|
||||
* @param family What IP family to use.
|
||||
*/
|
||||
void ClientNetworkStunSocketHandler::Connect(const std::string &token, uint8 family)
|
||||
{
|
||||
this->token = token;
|
||||
this->family = family;
|
||||
|
||||
this->connecter = new NetworkStunConnecter(this, NetworkStunConnectionString(), token, family);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a STUN packet to the STUN server.
|
||||
* @param token The token as received from the Game Coordinator.
|
||||
* @param family What IP family this STUN request is for.
|
||||
* @return The handler for this STUN request.
|
||||
*/
|
||||
std::unique_ptr<ClientNetworkStunSocketHandler> ClientNetworkStunSocketHandler::Stun(const std::string &token, uint8 family)
|
||||
{
|
||||
auto stun_handler = std::make_unique<ClientNetworkStunSocketHandler>();
|
||||
|
||||
stun_handler->Connect(token, family);
|
||||
|
||||
Packet *p = new Packet(PACKET_STUN_SERCLI_STUN);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(token);
|
||||
p->Send_uint8(family);
|
||||
|
||||
stun_handler->SendPacket(p);
|
||||
|
||||
return stun_handler;
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkStunSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
NetworkStunSocketHandler::CloseConnection(error);
|
||||
|
||||
/* Also make sure any pending connecter is killed ASAP. */
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
ClientNetworkStunSocketHandler::~ClientNetworkStunSocketHandler()
|
||||
{
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we received/can send some data from/to the STUN server and
|
||||
* when that's the case handle it appropriately.
|
||||
*/
|
||||
void ClientNetworkStunSocketHandler::SendReceive()
|
||||
{
|
||||
if (this->sock == INVALID_SOCKET) return;
|
||||
|
||||
/* We never attempt to receive anything on a STUN socket. After
|
||||
* connecting a STUN connection, the local address will be reused to
|
||||
* to establish the connection with the real server. If we would be to
|
||||
* read this socket, some OSes get confused and deliver us packets meant
|
||||
* for the real connection. It appears most OSes play best when we simply
|
||||
* never attempt to read it to start with (and the packets will remain
|
||||
* available on the other socket).
|
||||
* Protocol-wise, the STUN server will never send any packet back anyway. */
|
||||
|
||||
this->CanSendReceive();
|
||||
if (this->SendPackets() == SPS_ALL_SENT && !this->sent_result) {
|
||||
/* We delay giving the GC the result this long, as to make sure we
|
||||
* have sent the STUN packet first. This means the GC is more likely
|
||||
* to have the result ready by the time our StunResult() packet
|
||||
* arrives. */
|
||||
this->sent_result = true;
|
||||
_network_coordinator_client.StunResult(this->token, this->family, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_stun.h Part of the network protocol handling STUN requests. */
|
||||
|
||||
#ifndef NETWORK_STUN_H
|
||||
#define NETWORK_STUN_H
|
||||
|
||||
#include "core/tcp_stun.h"
|
||||
|
||||
/** Class for handling the client side of the STUN connection. */
|
||||
class ClientNetworkStunSocketHandler : public NetworkStunSocketHandler {
|
||||
private:
|
||||
std::string token; ///< Token of this STUN handler.
|
||||
uint8 family = AF_UNSPEC; ///< Family of this STUN handler.
|
||||
bool sent_result = false; ///< Did we sent the result of the STUN connection?
|
||||
|
||||
public:
|
||||
TCPConnecter *connecter = nullptr; ///< Connecter instance.
|
||||
NetworkAddress local_addr; ///< Local addresses of the socket.
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
~ClientNetworkStunSocketHandler() override;
|
||||
void SendReceive();
|
||||
|
||||
void Connect(const std::string &token, uint8 family);
|
||||
|
||||
static std::unique_ptr<ClientNetworkStunSocketHandler> Stun(const std::string &token, uint8 family);
|
||||
};
|
||||
|
||||
#endif /* NETWORK_STUN_H */
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_turn.cpp TURN sending/receiving part of the network protocol. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../debug.h"
|
||||
#include "../error.h"
|
||||
#include "../strings_func.h"
|
||||
#include "network_coordinator.h"
|
||||
#include "network_turn.h"
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
/** Connect to the TURN server. */
|
||||
class NetworkTurnConnecter : public TCPConnecter {
|
||||
private:
|
||||
ClientNetworkTurnSocketHandler *handler;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initiate the connecting.
|
||||
* @param connection_string The address of the TURN server.
|
||||
*/
|
||||
NetworkTurnConnecter(ClientNetworkTurnSocketHandler *handler, const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_TURN_SERVER_PORT), handler(handler) {}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
this->handler->connecter = nullptr;
|
||||
|
||||
this->handler->ConnectFailure();
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
this->handler->connecter = nullptr;
|
||||
|
||||
handler->sock = s;
|
||||
}
|
||||
};
|
||||
|
||||
bool ClientNetworkTurnSocketHandler::Receive_TURN_ERROR(Packet *p)
|
||||
{
|
||||
this->ConnectFailure();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClientNetworkTurnSocketHandler::Receive_TURN_CONNECTED(Packet *p)
|
||||
{
|
||||
std::string hostname = p->Recv_string(NETWORK_HOSTNAME_LENGTH);
|
||||
|
||||
/* Act like we no longer have a socket, as we are handing it over to the
|
||||
* game handler. */
|
||||
SOCKET game_sock = this->sock;
|
||||
this->sock = INVALID_SOCKET;
|
||||
|
||||
NetworkAddress address = NetworkAddress(hostname, NETWORK_DEFAULT_PORT);
|
||||
_network_coordinator_client.ConnectSuccess(this->token, game_sock, address);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the TURN server.
|
||||
*/
|
||||
void ClientNetworkTurnSocketHandler::Connect()
|
||||
{
|
||||
this->connect_started = true;
|
||||
this->connecter = new NetworkTurnConnecter(this, this->connection_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a TURN connection.
|
||||
* Not until you run Connect() on the resulting instance will it start setting
|
||||
* up the TURN connection.
|
||||
* @param token The token as received from the Game Coordinator.
|
||||
* @param tracking_number The tracking number as recieved from the Game Coordinator.
|
||||
* @param ticket The ticket as received from the Game Coordinator.
|
||||
* @param connection_string Connection string of the TURN server.
|
||||
* @return The handler for this TURN connection.
|
||||
*/
|
||||
/* static */ std::unique_ptr<ClientNetworkTurnSocketHandler> ClientNetworkTurnSocketHandler::Turn(const std::string &token, uint8 tracking_number, const std::string &ticket, const std::string &connection_string)
|
||||
{
|
||||
auto turn_handler = std::make_unique<ClientNetworkTurnSocketHandler>(token, tracking_number, connection_string);
|
||||
|
||||
Packet *p = new Packet(PACKET_TURN_SERCLI_CONNECT);
|
||||
p->Send_uint8(NETWORK_COORDINATOR_VERSION);
|
||||
p->Send_string(ticket);
|
||||
|
||||
turn_handler->SendPacket(p);
|
||||
|
||||
return turn_handler;
|
||||
}
|
||||
|
||||
void ClientNetworkTurnSocketHandler::ConnectFailure()
|
||||
{
|
||||
_network_coordinator_client.ConnectFailure(this->token, this->tracking_number);
|
||||
}
|
||||
|
||||
NetworkRecvStatus ClientNetworkTurnSocketHandler::CloseConnection(bool error)
|
||||
{
|
||||
NetworkTurnSocketHandler::CloseConnection(error);
|
||||
|
||||
/* Also make sure any pending connecter is killed ASAP. */
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
ClientNetworkTurnSocketHandler::~ClientNetworkTurnSocketHandler()
|
||||
{
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we received/can send some data from/to the TURN server and
|
||||
* when that's the case handle it appropriately
|
||||
*/
|
||||
void ClientNetworkTurnSocketHandler::SendReceive()
|
||||
{
|
||||
if (this->sock == INVALID_SOCKET) return;
|
||||
|
||||
if (this->CanSendReceive()) {
|
||||
this->ReceivePackets();
|
||||
}
|
||||
|
||||
this->SendPackets();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_turn.h Part of the network protocol handling TURN requests. */
|
||||
|
||||
#ifndef NETWORK_TURN_H
|
||||
#define NETWORK_TURN_H
|
||||
|
||||
#include "core/tcp_turn.h"
|
||||
|
||||
/** Class for handling the client side of the TURN connection. */
|
||||
class ClientNetworkTurnSocketHandler : public NetworkTurnSocketHandler {
|
||||
private:
|
||||
std::string token; ///< Token of this connection.
|
||||
uint8 tracking_number; ///< Tracking number of this connection.
|
||||
std::string connection_string; ///< The connection string of the TURN server we are connecting to.
|
||||
|
||||
protected:
|
||||
bool Receive_TURN_ERROR(Packet *p) override;
|
||||
bool Receive_TURN_CONNECTED(Packet *p) override;
|
||||
|
||||
public:
|
||||
TCPConnecter *connecter = nullptr; ///< Connecter instance.
|
||||
bool connect_started = false; ///< Whether we started the connection.
|
||||
|
||||
ClientNetworkTurnSocketHandler(const std::string &token, uint8 tracking_number, const std::string &connection_string) : token(token), tracking_number(tracking_number), connection_string(connection_string) {}
|
||||
|
||||
NetworkRecvStatus CloseConnection(bool error = true) override;
|
||||
~ClientNetworkTurnSocketHandler() override;
|
||||
void SendReceive();
|
||||
|
||||
void Connect();
|
||||
void ConnectFailure();
|
||||
|
||||
static std::unique_ptr<ClientNetworkTurnSocketHandler> Turn(const std::string &token, uint8 tracking_number, const std::string &ticket, const std::string &connection_string);
|
||||
};
|
||||
|
||||
#endif /* NETWORK_TURN_H */
|
||||
@@ -10,8 +10,6 @@
|
||||
#ifndef NETWORK_TYPE_H
|
||||
#define NETWORK_TYPE_H
|
||||
|
||||
#include "core/config.h"
|
||||
|
||||
/** How many clients can we have */
|
||||
static const uint MAX_CLIENTS = 255;
|
||||
|
||||
@@ -35,6 +33,16 @@ enum NetworkVehicleType {
|
||||
NETWORK_VEH_END
|
||||
};
|
||||
|
||||
/**
|
||||
* Game type the server can be using.
|
||||
* Used on the network protocol to communicate with Game Coordinator.
|
||||
*/
|
||||
enum ServerGameType : uint8 {
|
||||
SERVER_GAME_TYPE_LOCAL = 0,
|
||||
SERVER_GAME_TYPE_PUBLIC,
|
||||
SERVER_GAME_TYPE_INVITE_ONLY,
|
||||
};
|
||||
|
||||
/** 'Unique' identifier to be given to clients */
|
||||
enum ClientID : uint32 {
|
||||
INVALID_CLIENT_ID = 0, ///< Client is not part of anything
|
||||
@@ -62,8 +70,8 @@ struct NetworkCompanyStats {
|
||||
|
||||
/** Some state information of a company, especially for servers */
|
||||
struct NetworkCompanyState {
|
||||
char password[NETWORK_PASSWORD_LENGTH]; ///< The password for the company
|
||||
uint16 months_empty; ///< How many months the company is empty
|
||||
std::string password; ///< The password for the company
|
||||
uint16 months_empty; ///< How many months the company is empty
|
||||
};
|
||||
|
||||
struct NetworkClientInfo;
|
||||
@@ -101,6 +109,7 @@ enum NetworkAction {
|
||||
NETWORK_ACTION_COMPANY_JOIN,
|
||||
NETWORK_ACTION_COMPANY_NEW,
|
||||
NETWORK_ACTION_KICKED,
|
||||
NETWORK_ACTION_EXTERNAL_CHAT,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+18
-493
@@ -8,7 +8,7 @@
|
||||
/**
|
||||
* @file network_udp.cpp This file handles the UDP related communication.
|
||||
*
|
||||
* This is the GameServer <-> MasterServer and GameServer <-> GameClient
|
||||
* This is the GameServer <-> GameClient
|
||||
* communication before the game is being joined.
|
||||
*/
|
||||
|
||||
@@ -23,128 +23,40 @@
|
||||
#include "network.h"
|
||||
#include "../core/endian_func.hpp"
|
||||
#include "../company_base.h"
|
||||
#include "../thread.h"
|
||||
#include "../rev.h"
|
||||
#include "../newgrf_text.h"
|
||||
#include "../strings_func.h"
|
||||
#include "table/strings.h"
|
||||
#include <mutex>
|
||||
|
||||
#include "core/udp.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
/** Session key to register ourselves to the master server */
|
||||
static uint64 _session_key = 0;
|
||||
|
||||
static const std::chrono::minutes ADVERTISE_NORMAL_INTERVAL(15); ///< interval between advertising.
|
||||
static const std::chrono::seconds ADVERTISE_RETRY_INTERVAL(10); ///< re-advertise when no response after this amount of time.
|
||||
static const uint32 ADVERTISE_RETRY_TIMES = 3; ///< give up re-advertising after this much failed retries
|
||||
|
||||
static bool _network_udp_server; ///< Is the UDP server started?
|
||||
static uint16 _network_udp_broadcast; ///< Timeout for the UDP broadcasts.
|
||||
static uint8 _network_advertise_retries; ///< The number of advertisement retries we did.
|
||||
|
||||
/** Some information about a socket, which exists before the actual socket has been created to provide locking and the likes. */
|
||||
struct UDPSocket {
|
||||
const std::string name; ///< The name of the socket.
|
||||
std::mutex mutex; ///< Mutex for everything that (indirectly) touches the sockets within the handler.
|
||||
NetworkUDPSocketHandler *socket; ///< The actual socket, which may be nullptr when not initialized yet.
|
||||
std::atomic<int> receive_iterations_locked; ///< The number of receive iterations the mutex was locked.
|
||||
|
||||
UDPSocket(const std::string &name_) : name(name_), socket(nullptr) {}
|
||||
UDPSocket(const std::string &name) : name(name), socket(nullptr) {}
|
||||
|
||||
void Close()
|
||||
void CloseSocket()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
socket->Close();
|
||||
delete socket;
|
||||
socket = nullptr;
|
||||
this->socket->CloseSocket();
|
||||
delete this->socket;
|
||||
this->socket = nullptr;
|
||||
}
|
||||
|
||||
void ReceivePackets()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
|
||||
if (!lock.try_lock()) {
|
||||
if (++receive_iterations_locked % 32 == 0) {
|
||||
DEBUG(net, 0, "[udp] %s background UDP loop processing appears to be blocked. Your OS may be low on UDP send buffers.", name.c_str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
receive_iterations_locked.store(0);
|
||||
socket->ReceivePackets();
|
||||
this->socket->ReceivePackets();
|
||||
}
|
||||
};
|
||||
|
||||
static UDPSocket _udp_client("Client"); ///< udp client socket
|
||||
static UDPSocket _udp_server("Server"); ///< udp server socket
|
||||
static UDPSocket _udp_master("Master"); ///< udp master socket
|
||||
|
||||
/**
|
||||
* Helper function doing the actual work for querying the server.
|
||||
* @param address The address of the server.
|
||||
* @param needs_mutex Whether we need to acquire locks when sending the packet or not.
|
||||
* @param manually Whether the address was entered manually.
|
||||
*/
|
||||
static void DoNetworkUDPQueryServer(NetworkAddress &address, bool needs_mutex, bool manually)
|
||||
{
|
||||
/* Clear item in gamelist */
|
||||
NetworkGameList *item = CallocT<NetworkGameList>(1);
|
||||
address.GetAddressAsString(item->info.server_name, lastof(item->info.server_name));
|
||||
item->address = address;
|
||||
item->manually = manually;
|
||||
NetworkGameListAddItemDelayed(item);
|
||||
|
||||
std::unique_lock<std::mutex> lock(_udp_client.mutex, std::defer_lock);
|
||||
if (needs_mutex) lock.lock();
|
||||
/* Init the packet */
|
||||
Packet p(PACKET_UDP_CLIENT_FIND_SERVER);
|
||||
if (_udp_client.socket != nullptr) _udp_client.socket->SendPacket(&p, &address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query a specific server.
|
||||
* @param address The address of the server.
|
||||
* @param manually Whether the address was entered manually.
|
||||
*/
|
||||
void NetworkUDPQueryServer(NetworkAddress address, bool manually)
|
||||
{
|
||||
if (address.IsResolved() || !StartNewThread(nullptr, "ottd:udp-query", &DoNetworkUDPQueryServer, std::move(address), true, std::move(manually))) {
|
||||
DoNetworkUDPQueryServer(address, true, manually);
|
||||
}
|
||||
}
|
||||
|
||||
///*** Communication with the masterserver ***/
|
||||
|
||||
/** Helper class for connecting to the master server. */
|
||||
class MasterNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
|
||||
protected:
|
||||
void Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr) override;
|
||||
public:
|
||||
/**
|
||||
* Create the socket.
|
||||
* @param addresses The addresses to bind on.
|
||||
*/
|
||||
MasterNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {}
|
||||
virtual ~MasterNetworkUDPSocketHandler() {}
|
||||
};
|
||||
|
||||
void MasterNetworkUDPSocketHandler::Receive_MASTER_ACK_REGISTER(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
_network_advertise_retries = 0;
|
||||
DEBUG(net, 2, "[udp] advertising on master server successful (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
|
||||
|
||||
/* We are advertised, but we don't want to! */
|
||||
if (!_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(false);
|
||||
}
|
||||
|
||||
void MasterNetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
_session_key = p->Recv_uint64();
|
||||
DEBUG(net, 2, "[udp] received new session key from master server (%s)", NetworkAddress::AddressFamilyAsString(client_addr->GetAddress()->ss_family));
|
||||
}
|
||||
|
||||
///*** Communication with clients (we are server) ***/
|
||||
|
||||
@@ -152,8 +64,6 @@ void MasterNetworkUDPSocketHandler::Receive_MASTER_SESSION_KEY(Packet *p, Networ
|
||||
class ServerNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
|
||||
protected:
|
||||
void Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr) override;
|
||||
public:
|
||||
/**
|
||||
* Create the socket.
|
||||
@@ -165,138 +75,10 @@ public:
|
||||
|
||||
void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
/* Just a fail-safe.. should never happen */
|
||||
if (!_network_udp_server) {
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkGameInfo ngi;
|
||||
FillNetworkGameInfo(ngi);
|
||||
|
||||
Packet packet(PACKET_UDP_SERVER_RESPONSE);
|
||||
SerializeNetworkGameInfo(&packet, &ngi);
|
||||
|
||||
/* Let the client know that we are here */
|
||||
this->SendPacket(&packet, client_addr);
|
||||
|
||||
DEBUG(net, 2, "[udp] queried from %s", client_addr->GetHostname());
|
||||
}
|
||||
|
||||
void ServerNetworkUDPSocketHandler::Receive_CLIENT_DETAIL_INFO(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
/* Just a fail-safe.. should never happen */
|
||||
if (!_network_udp_server) return;
|
||||
|
||||
Packet packet(PACKET_UDP_SERVER_DETAIL_INFO);
|
||||
|
||||
/* Send the amount of active companies */
|
||||
packet.Send_uint8 (NETWORK_COMPANY_INFO_VERSION);
|
||||
packet.Send_uint8 ((uint8)Company::GetNumItems());
|
||||
|
||||
/* Fetch the latest version of the stats */
|
||||
NetworkCompanyStats company_stats[MAX_COMPANIES];
|
||||
NetworkPopulateCompanyStats(company_stats);
|
||||
|
||||
/* The minimum company information "blob" size. */
|
||||
static const uint MIN_CI_SIZE = 54;
|
||||
uint max_cname_length = NETWORK_COMPANY_NAME_LENGTH;
|
||||
|
||||
if (!packet.CanWriteToPacket(Company::GetNumItems() * (MIN_CI_SIZE + NETWORK_COMPANY_NAME_LENGTH))) {
|
||||
/* Assume we can at least put the company information in the packets. */
|
||||
assert(packet.CanWriteToPacket(Company::GetNumItems() * MIN_CI_SIZE));
|
||||
|
||||
/* At this moment the company names might not fit in the
|
||||
* packet. Check whether that is really the case. */
|
||||
|
||||
for (;;) {
|
||||
size_t required = 0;
|
||||
for (const Company *company : Company::Iterate()) {
|
||||
char company_name[NETWORK_COMPANY_NAME_LENGTH];
|
||||
SetDParam(0, company->index);
|
||||
GetString(company_name, STR_COMPANY_NAME, company_name + max_cname_length - 1);
|
||||
required += MIN_CI_SIZE;
|
||||
required += strlen(company_name);
|
||||
}
|
||||
if (packet.CanWriteToPacket(required)) break;
|
||||
|
||||
/* Try again, with slightly shorter strings. */
|
||||
assert(max_cname_length > 0);
|
||||
max_cname_length--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Go through all the companies */
|
||||
for (const Company *company : Company::Iterate()) {
|
||||
/* Send the information */
|
||||
this->SendCompanyInformation(&packet, company, &company_stats[company->index], max_cname_length);
|
||||
}
|
||||
|
||||
this->SendPacket(&packet, client_addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* A client has requested the names of some NewGRFs.
|
||||
*
|
||||
* Replying this can be tricky as we have a limit of UDP_MTU bytes
|
||||
* in the reply packet and we can send up to 100 bytes per NewGRF
|
||||
* (GRF ID, MD5sum and NETWORK_GRF_NAME_LENGTH bytes for the name).
|
||||
* As UDP_MTU is _much_ less than 100 * NETWORK_MAX_GRF_COUNT, it
|
||||
* could be that a packet overflows. To stop this we only reply
|
||||
* with the first N NewGRFs so that if the first N + 1 NewGRFs
|
||||
* would be sent, the packet overflows.
|
||||
* in_reply and in_reply_count are used to keep a list of GRFs to
|
||||
* send in the reply.
|
||||
*/
|
||||
void ServerNetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
uint8 num_grfs;
|
||||
uint i;
|
||||
|
||||
const GRFConfig *in_reply[NETWORK_MAX_GRF_COUNT];
|
||||
uint8 in_reply_count = 0;
|
||||
size_t packet_len = 0;
|
||||
|
||||
DEBUG(net, 6, "[udp] newgrf data request from %s", client_addr->GetAddressAsString().c_str());
|
||||
|
||||
num_grfs = p->Recv_uint8 ();
|
||||
if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
|
||||
|
||||
for (i = 0; i < num_grfs; i++) {
|
||||
GRFIdentifier c;
|
||||
const GRFConfig *f;
|
||||
|
||||
DeserializeGRFIdentifier(p, &c);
|
||||
|
||||
/* Find the matching GRF file */
|
||||
f = FindGRFConfig(c.grfid, FGCM_EXACT, c.md5sum);
|
||||
if (f == nullptr) continue; // The GRF is unknown to this server
|
||||
|
||||
/* If the reply might exceed the size of the packet, only reply
|
||||
* the current list and do not send the other data.
|
||||
* The name could be an empty string, if so take the filename. */
|
||||
packet_len += sizeof(c.grfid) + sizeof(c.md5sum) +
|
||||
std::min(strlen(f->GetName()) + 1, (size_t)NETWORK_GRF_NAME_LENGTH);
|
||||
if (packet_len > UDP_MTU - 4) { // 4 is 3 byte header + grf count in reply
|
||||
break;
|
||||
}
|
||||
in_reply[in_reply_count] = f;
|
||||
in_reply_count++;
|
||||
}
|
||||
|
||||
if (in_reply_count == 0) return;
|
||||
|
||||
Packet packet(PACKET_UDP_SERVER_NEWGRFS);
|
||||
packet.Send_uint8(in_reply_count);
|
||||
for (i = 0; i < in_reply_count; i++) {
|
||||
char name[NETWORK_GRF_NAME_LENGTH];
|
||||
|
||||
/* The name could be an empty string, if so take the filename */
|
||||
strecpy(name, in_reply[i]->GetName(), lastof(name));
|
||||
SerializeGRFIdentifier(&packet, &in_reply[i]->ident);
|
||||
packet.Send_string(name);
|
||||
}
|
||||
|
||||
this->SendPacket(&packet, client_addr);
|
||||
Debug(net, 7, "Queried from {}", client_addr->GetHostname());
|
||||
}
|
||||
|
||||
///*** Communication with servers (we are client) ***/
|
||||
@@ -305,297 +87,48 @@ void ServerNetworkUDPSocketHandler::Receive_CLIENT_GET_NEWGRFS(Packet *p, Networ
|
||||
class ClientNetworkUDPSocketHandler : public NetworkUDPSocketHandler {
|
||||
protected:
|
||||
void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr) override;
|
||||
public:
|
||||
virtual ~ClientNetworkUDPSocketHandler() {}
|
||||
};
|
||||
|
||||
void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
NetworkGameList *item;
|
||||
Debug(net, 3, "Server response from {}", client_addr->GetAddressAsString());
|
||||
|
||||
/* Just a fail-safe.. should never happen */
|
||||
if (_network_udp_server) return;
|
||||
|
||||
DEBUG(net, 4, "[udp] server response from %s", client_addr->GetAddressAsString().c_str());
|
||||
|
||||
/* Find next item */
|
||||
item = NetworkGameListAddItem(*client_addr);
|
||||
|
||||
/* Clear any existing GRFConfig chain. */
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
/* Retrieve the NetworkGameInfo from the packet. */
|
||||
DeserializeNetworkGameInfo(p, &item->info);
|
||||
/* Check for compatability with the client. */
|
||||
CheckGameCompatibility(item->info);
|
||||
/* Ensure we consider the server online. */
|
||||
item->online = true;
|
||||
|
||||
{
|
||||
/* Checks whether there needs to be a request for names of GRFs and makes
|
||||
* the request if necessary. GRFs that need to be requested are the GRFs
|
||||
* that do not exist on the clients system and we do not have the name
|
||||
* resolved of, i.e. the name is still UNKNOWN_GRF_NAME_PLACEHOLDER.
|
||||
* The in_request array and in_request_count are used so there is no need
|
||||
* to do a second loop over the GRF list, which can be relatively expensive
|
||||
* due to the string comparisons. */
|
||||
const GRFConfig *in_request[NETWORK_MAX_GRF_COUNT];
|
||||
const GRFConfig *c;
|
||||
uint in_request_count = 0;
|
||||
|
||||
for (c = item->info.grfconfig; c != nullptr; c = c->next) {
|
||||
if (c->status != GCS_NOT_FOUND || strcmp(c->GetName(), UNKNOWN_GRF_NAME_PLACEHOLDER) != 0) continue;
|
||||
in_request[in_request_count] = c;
|
||||
in_request_count++;
|
||||
}
|
||||
|
||||
if (in_request_count > 0) {
|
||||
/* There are 'unknown' GRFs, now send a request for them */
|
||||
uint i;
|
||||
Packet packet(PACKET_UDP_CLIENT_GET_NEWGRFS);
|
||||
|
||||
packet.Send_uint8(in_request_count);
|
||||
for (i = 0; i < in_request_count; i++) {
|
||||
SerializeGRFIdentifier(&packet, &in_request[i]->ident);
|
||||
}
|
||||
|
||||
this->SendPacket(&packet, &item->address);
|
||||
}
|
||||
}
|
||||
|
||||
if (client_addr->GetAddress()->ss_family == AF_INET6) {
|
||||
strecat(item->info.server_name, " (IPv6)", lastof(item->info.server_name));
|
||||
}
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
}
|
||||
|
||||
void ClientNetworkUDPSocketHandler::Receive_MASTER_RESPONSE_LIST(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
/* packet begins with the protocol version (uint8)
|
||||
* then an uint16 which indicates how many
|
||||
* ip:port pairs are in this packet, after that
|
||||
* an uint32 (ip) and an uint16 (port) for each pair.
|
||||
*/
|
||||
|
||||
ServerListType type = (ServerListType)(p->Recv_uint8() - 1);
|
||||
|
||||
if (type < SLT_END) {
|
||||
for (int i = p->Recv_uint16(); i != 0 ; i--) {
|
||||
sockaddr_storage addr_storage;
|
||||
memset(&addr_storage, 0, sizeof(addr_storage));
|
||||
|
||||
if (type == SLT_IPv4) {
|
||||
addr_storage.ss_family = AF_INET;
|
||||
((sockaddr_in*)&addr_storage)->sin_addr.s_addr = TO_LE32(p->Recv_uint32());
|
||||
} else {
|
||||
assert(type == SLT_IPv6);
|
||||
addr_storage.ss_family = AF_INET6;
|
||||
byte *addr = (byte*)&((sockaddr_in6*)&addr_storage)->sin6_addr;
|
||||
for (uint i = 0; i < sizeof(in6_addr); i++) *addr++ = p->Recv_uint8();
|
||||
}
|
||||
NetworkAddress addr(addr_storage, type == SLT_IPv4 ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
|
||||
addr.SetPort(p->Recv_uint16());
|
||||
|
||||
/* Somehow we reached the end of the packet */
|
||||
if (this->HasClientQuit()) return;
|
||||
|
||||
DoNetworkUDPQueryServer(addr, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The return of the client's request of the names of some NewGRFs */
|
||||
void ClientNetworkUDPSocketHandler::Receive_SERVER_NEWGRFS(Packet *p, NetworkAddress *client_addr)
|
||||
{
|
||||
uint8 num_grfs;
|
||||
uint i;
|
||||
|
||||
DEBUG(net, 6, "[udp] newgrf data reply from %s", client_addr->GetAddressAsString().c_str());
|
||||
|
||||
num_grfs = p->Recv_uint8 ();
|
||||
if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
|
||||
|
||||
for (i = 0; i < num_grfs; i++) {
|
||||
char name[NETWORK_GRF_NAME_LENGTH];
|
||||
GRFIdentifier c;
|
||||
|
||||
DeserializeGRFIdentifier(p, &c);
|
||||
p->Recv_string(name, sizeof(name));
|
||||
|
||||
/* An empty name is not possible under normal circumstances
|
||||
* and causes problems when showing the NewGRF list. */
|
||||
if (StrEmpty(name)) continue;
|
||||
|
||||
/* Try to find the GRFTextWrapper for the name of this GRF ID and MD5sum tuple.
|
||||
* If it exists and not resolved yet, then name of the fake GRF is
|
||||
* overwritten with the name from the reply. */
|
||||
GRFTextWrapper unknown_name = FindUnknownGRFName(c.grfid, c.md5sum, false);
|
||||
if (unknown_name && strcmp(GetGRFStringFromGRFText(unknown_name), UNKNOWN_GRF_NAME_PLACEHOLDER) == 0) {
|
||||
AddGRFTextToList(unknown_name, name);
|
||||
}
|
||||
}
|
||||
NetworkAddServer(client_addr->GetAddressAsString(false), false, true);
|
||||
}
|
||||
|
||||
/** Broadcast to all ips */
|
||||
static void NetworkUDPBroadCast(NetworkUDPSocketHandler *socket)
|
||||
{
|
||||
for (NetworkAddress &addr : _broadcast_list) {
|
||||
Debug(net, 5, "Broadcasting to {}", addr.GetHostname());
|
||||
|
||||
Packet p(PACKET_UDP_CLIENT_FIND_SERVER);
|
||||
|
||||
DEBUG(net, 4, "[udp] broadcasting to %s", addr.GetHostname());
|
||||
|
||||
socket->SendPacket(&p, &addr, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Request the the server-list from the master server */
|
||||
void NetworkUDPQueryMasterServer()
|
||||
{
|
||||
Packet p(PACKET_UDP_CLIENT_GET_LIST);
|
||||
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
|
||||
|
||||
/* packet only contains protocol version */
|
||||
p.Send_uint8(NETWORK_MASTER_SERVER_VERSION);
|
||||
p.Send_uint8(SLT_AUTODETECT);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_udp_client.mutex);
|
||||
_udp_client.socket->SendPacket(&p, &out_addr, true);
|
||||
|
||||
DEBUG(net, 2, "[udp] master server queried at %s", out_addr.GetAddressAsString().c_str());
|
||||
}
|
||||
|
||||
/** Find all servers */
|
||||
void NetworkUDPSearchGame()
|
||||
{
|
||||
/* We are still searching.. */
|
||||
if (_network_udp_broadcast > 0) return;
|
||||
|
||||
DEBUG(net, 0, "[udp] searching server");
|
||||
Debug(net, 3, "Searching server");
|
||||
|
||||
NetworkUDPBroadCast(_udp_client.socket);
|
||||
_network_udp_broadcast = 300; // Stay searching for 300 ticks
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread entry point for de-advertising.
|
||||
*/
|
||||
static void NetworkUDPRemoveAdvertiseThread()
|
||||
{
|
||||
DEBUG(net, 1, "[udp] removing advertise from master server");
|
||||
|
||||
/* Find somewhere to send */
|
||||
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
|
||||
|
||||
/* Send the packet */
|
||||
Packet p(PACKET_UDP_SERVER_UNREGISTER);
|
||||
/* Packet is: Version, server_port */
|
||||
p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION);
|
||||
p.Send_uint16(_settings_client.network.server_port);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_udp_master.mutex);
|
||||
if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove our advertise from the master-server.
|
||||
* @param blocking whether to wait until the removal has finished.
|
||||
*/
|
||||
void NetworkUDPRemoveAdvertise(bool blocking)
|
||||
{
|
||||
/* Check if we are advertising */
|
||||
if (!_networking || !_network_server || !_network_udp_server) return;
|
||||
|
||||
if (blocking || !StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPRemoveAdvertiseThread)) {
|
||||
NetworkUDPRemoveAdvertiseThread();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread entry point for advertising.
|
||||
*/
|
||||
static void NetworkUDPAdvertiseThread()
|
||||
{
|
||||
/* Find somewhere to send */
|
||||
NetworkAddress out_addr(NETWORK_MASTER_SERVER_HOST, NETWORK_MASTER_SERVER_PORT);
|
||||
|
||||
DEBUG(net, 1, "[udp] advertising to master server");
|
||||
|
||||
/* Add a bit more messaging when we cannot get a session key */
|
||||
static byte session_key_retries = 0;
|
||||
if (_session_key == 0 && session_key_retries++ == 2) {
|
||||
DEBUG(net, 0, "[udp] advertising to the master server is failing");
|
||||
DEBUG(net, 0, "[udp] we are not receiving the session key from the server");
|
||||
DEBUG(net, 0, "[udp] please allow udp packets from %s to you to be delivered", out_addr.GetAddressAsString(false).c_str());
|
||||
DEBUG(net, 0, "[udp] please allow udp packets from you to %s to be delivered", out_addr.GetAddressAsString(false).c_str());
|
||||
}
|
||||
if (_session_key != 0 && _network_advertise_retries == 0) {
|
||||
DEBUG(net, 0, "[udp] advertising to the master server is failing");
|
||||
DEBUG(net, 0, "[udp] we are not receiving the acknowledgement from the server");
|
||||
DEBUG(net, 0, "[udp] this usually means that the master server cannot reach us");
|
||||
DEBUG(net, 0, "[udp] please allow udp and tcp packets to port %u to be delivered", _settings_client.network.server_port);
|
||||
DEBUG(net, 0, "[udp] please allow udp and tcp packets from port %u to be delivered", _settings_client.network.server_port);
|
||||
}
|
||||
|
||||
/* Send the packet */
|
||||
Packet p(PACKET_UDP_SERVER_REGISTER);
|
||||
/* Packet is: WELCOME_MESSAGE, Version, server_port */
|
||||
p.Send_string(NETWORK_MASTER_SERVER_WELCOME_MESSAGE);
|
||||
p.Send_uint8 (NETWORK_MASTER_SERVER_VERSION);
|
||||
p.Send_uint16(_settings_client.network.server_port);
|
||||
p.Send_uint64(_session_key);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_udp_master.mutex);
|
||||
if (_udp_master.socket != nullptr) _udp_master.socket->SendPacket(&p, &out_addr, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register us to the master server
|
||||
* This function checks if it needs to send an advertise
|
||||
*/
|
||||
void NetworkUDPAdvertise()
|
||||
{
|
||||
static std::chrono::steady_clock::time_point _last_advertisement = {}; ///< The last time we performed an advertisement.
|
||||
|
||||
/* Check if we should send an advertise */
|
||||
if (!_networking || !_network_server || !_network_udp_server || !_settings_client.network.server_advertise) return;
|
||||
|
||||
if (_network_need_advertise) {
|
||||
/* Forced advertisement. */
|
||||
_network_need_advertise = false;
|
||||
_network_advertise_retries = ADVERTISE_RETRY_TIMES;
|
||||
} else {
|
||||
/* Only send once every ADVERTISE_NORMAL_INTERVAL ticks */
|
||||
if (_network_advertise_retries == 0) {
|
||||
if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_NORMAL_INTERVAL) return;
|
||||
|
||||
_network_advertise_retries = ADVERTISE_RETRY_TIMES;
|
||||
} else {
|
||||
/* An actual retry. */
|
||||
if (std::chrono::steady_clock::now() <= _last_advertisement + ADVERTISE_RETRY_INTERVAL) return;
|
||||
}
|
||||
}
|
||||
|
||||
_network_advertise_retries--;
|
||||
_last_advertisement = std::chrono::steady_clock::now();
|
||||
|
||||
if (!StartNewThread(nullptr, "ottd:udp-advert", &NetworkUDPAdvertiseThread)) {
|
||||
NetworkUDPAdvertiseThread();
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize the whole UDP bit. */
|
||||
void NetworkUDPInitialize()
|
||||
{
|
||||
/* If not closed, then do it. */
|
||||
if (_udp_server.socket != nullptr) NetworkUDPClose();
|
||||
|
||||
DEBUG(net, 1, "[udp] initializing listeners");
|
||||
assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr && _udp_master.socket == nullptr);
|
||||
|
||||
std::scoped_lock lock(_udp_client.mutex, _udp_server.mutex, _udp_master.mutex);
|
||||
Debug(net, 3, "Initializing UDP listeners");
|
||||
assert(_udp_client.socket == nullptr && _udp_server.socket == nullptr);
|
||||
|
||||
_udp_client.socket = new ClientNetworkUDPSocketHandler();
|
||||
|
||||
@@ -603,32 +136,25 @@ void NetworkUDPInitialize()
|
||||
GetBindAddresses(&server, _settings_client.network.server_port);
|
||||
_udp_server.socket = new ServerNetworkUDPSocketHandler(&server);
|
||||
|
||||
server.clear();
|
||||
GetBindAddresses(&server, 0);
|
||||
_udp_master.socket = new MasterNetworkUDPSocketHandler(&server);
|
||||
|
||||
_network_udp_server = false;
|
||||
_network_udp_broadcast = 0;
|
||||
_network_advertise_retries = 0;
|
||||
}
|
||||
|
||||
/** Start the listening of the UDP server component. */
|
||||
void NetworkUDPServerListen()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_udp_server.mutex);
|
||||
_network_udp_server = _udp_server.socket->Listen();
|
||||
}
|
||||
|
||||
/** Close all UDP related stuff. */
|
||||
void NetworkUDPClose()
|
||||
{
|
||||
_udp_client.Close();
|
||||
_udp_server.Close();
|
||||
_udp_master.Close();
|
||||
_udp_client.CloseSocket();
|
||||
_udp_server.CloseSocket();
|
||||
|
||||
_network_udp_server = false;
|
||||
_network_udp_broadcast = 0;
|
||||
DEBUG(net, 1, "[udp] closed listeners");
|
||||
Debug(net, 5, "Closed UDP listeners");
|
||||
}
|
||||
|
||||
/** Receive the UDP packets. */
|
||||
@@ -636,7 +162,6 @@ void NetworkBackgroundUDPLoop()
|
||||
{
|
||||
if (_network_udp_server) {
|
||||
_udp_server.ReceivePackets();
|
||||
_udp_master.ReceivePackets();
|
||||
} else {
|
||||
_udp_client.ReceivePackets();
|
||||
if (_network_udp_broadcast > 0) _network_udp_broadcast--;
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
|
||||
void NetworkUDPInitialize();
|
||||
void NetworkUDPSearchGame();
|
||||
void NetworkUDPQueryMasterServer();
|
||||
void NetworkUDPQueryServer(NetworkAddress address, bool manually = false);
|
||||
void NetworkUDPAdvertise();
|
||||
void NetworkUDPRemoveAdvertise(bool blocking);
|
||||
void NetworkUDPClose();
|
||||
void NetworkUDPServerListen();
|
||||
void NetworkBackgroundUDPLoop();
|
||||
|
||||
Reference in New Issue
Block a user