Update to 12.0-beta1
This commit is contained in:
@@ -14,6 +14,8 @@ 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
|
||||
@@ -22,6 +24,10 @@ add_files(
|
||||
network_internal.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
|
||||
@@ -20,11 +21,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
-110
@@ -10,6 +10,7 @@
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "address.h"
|
||||
#include "../network_internal.h"
|
||||
#include "../../debug.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
@@ -19,11 +20,13 @@
|
||||
* 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;
|
||||
}
|
||||
@@ -69,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?)" : "{}:{}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +93,7 @@ void NetworkAddress::GetAddressAsString(char *buffer, const char *last, bool wit
|
||||
*/
|
||||
std::string NetworkAddress::GetAddressAsString(bool with_family)
|
||||
{
|
||||
/* 6 = for the : and 5 for the decimal port number */
|
||||
char buf[NETWORK_HOSTNAME_LENGTH + 6 + 7];
|
||||
this->GetAddressAsString(buf, lastof(buf), with_family);
|
||||
return buf;
|
||||
return fmt::format(GetAddressFormatString(this->GetAddress()->ss_family, with_family), this->GetHostname(), this->GetPort());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,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();
|
||||
@@ -163,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);
|
||||
}
|
||||
@@ -232,31 +222,31 @@ 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",
|
||||
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;
|
||||
@@ -302,59 +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);
|
||||
char address[NETWORK_HOSTNAME_LENGTH + 6 + 7];
|
||||
NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString(address, lastof(address));
|
||||
|
||||
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);
|
||||
|
||||
int err = connect(sock, runp->ai_addr, (int)runp->ai_addrlen);
|
||||
#ifdef __EMSCRIPTEN__
|
||||
/* Emscripten is asynchronous, and as such a connect() is still in
|
||||
* progress by the time the call returns. */
|
||||
if (err != 0 && !NetworkError::GetLast().IsConnectInProgress())
|
||||
#else
|
||||
if (err != 0)
|
||||
#endif
|
||||
{
|
||||
DEBUG(net, 1, "[%s] could not connect %s socket: %s", type, family, NetworkError::GetLast().AsString());
|
||||
closesocket(sock);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/* Connection succeeded */
|
||||
if (!SetNonBlocking(sock)) DEBUG(net, 0, "[%s] setting non-blocking mode failed", type);
|
||||
|
||||
DEBUG(net, 1, "[%s] connected to %s", type, address);
|
||||
|
||||
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
|
||||
@@ -362,50 +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);
|
||||
char address[NETWORK_HOSTNAME_LENGTH + 6 + 7];
|
||||
NetworkAddress(runp->ai_addr, (int)runp->ai_addrlen).GetAddressAsString(address, lastof(address));
|
||||
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, 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);
|
||||
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, 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, 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, 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, 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);
|
||||
|
||||
DEBUG(net, 1, "[%s] listening on %s port %s", type, family, address);
|
||||
if (!SetNonBlocking(sock)) {
|
||||
Debug(net, 0, "Setting non-blocking mode failed: {}", NetworkError::GetLast().AsString());
|
||||
}
|
||||
|
||||
Debug(net, 3, "Listening on {}", address);
|
||||
return sock;
|
||||
}
|
||||
|
||||
@@ -418,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 {
|
||||
@@ -460,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");
|
||||
}
|
||||
+70
-45
@@ -12,61 +12,86 @@
|
||||
#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 SEND_MTU = 1460; ///< Number of bytes we can pack in a single 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.
|
||||
*
|
||||
* Packets up to 32 KiB have the high bit not set:
|
||||
* 00000000 00000000 0bbbbbbb aaaaaaaa -> aaaaaaaa 0bbbbbbb
|
||||
* Send_uint16(GB(size, 0, 15)
|
||||
*
|
||||
* Packets up to 1 GiB, first uint16 has high bit set so it knows to read a
|
||||
* next uint16 for the remaining bits of the size.
|
||||
* 00dddddd cccccccc bbbbbbbb aaaaaaaa -> cccccccc 10dddddd aaaaaaaa bbbbbbbb
|
||||
* 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 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 = 5; ///< 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_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 = SEND_MTU - 3; ///< The maximum length of a gamescript json string, in bytes including '\0'. Must not be longer than SEND_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 SEND_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;
|
||||
|
||||
static const uint NETWORK_NUM_LANGUAGES = 36; ///< Number of known languages (to the network protocol) + 1 for 'any'.
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
|
||||
+155
-86
@@ -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,47 +87,70 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill a NetworkGameInfo structure with the latest information of the server.
|
||||
* @param ngi the NetworkGameInfo struct to fill with data.
|
||||
* Check if an game entry is compatible with our client.
|
||||
*/
|
||||
void FillNetworkGameInfo(NetworkGameInfo &ngi)
|
||||
void CheckGameCompatibility(NetworkGameInfo &ngi)
|
||||
{
|
||||
/* Update some game_info */
|
||||
ngi.clients_on = _network_game_info.clients_on;
|
||||
ngi.start_date = ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1);
|
||||
/* Check if we are allowed on this server based on the revision-check. */
|
||||
ngi.version_compatible = IsNetworkCompatibleVersion(ngi.server_revision);
|
||||
ngi.compatible = ngi.version_compatible;
|
||||
|
||||
ngi.server_lang = _settings_client.network.server_lang;
|
||||
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;
|
||||
/* Check if we have all the GRFs on the client-system too. */
|
||||
for (const GRFConfig *c = ngi.grfconfig; c != nullptr; c = c->next) {
|
||||
if (c->status == GCS_NOT_FOUND) ngi.compatible = false;
|
||||
}
|
||||
}
|
||||
|
||||
strecpy(ngi.map_name, _network_game_info.map_name, lastof(ngi.map_name));
|
||||
strecpy(ngi.server_name, _settings_client.network.server_name, lastof(ngi.server_name));
|
||||
strecpy(ngi.server_revision, GetNetworkRevisionString(), lastof(ngi.server_revision));
|
||||
/**
|
||||
* 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 FillStaticNetworkServerGameInfo()
|
||||
{
|
||||
_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;
|
||||
|
||||
_network_game_info.server_name = _settings_client.network.server_name;
|
||||
_network_game_info.server_revision = GetNetworkRevisionString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,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;
|
||||
@@ -166,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);
|
||||
|
||||
@@ -178,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
|
||||
@@ -195,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,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 (info->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(info->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);
|
||||
}
|
||||
|
||||
@@ -228,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.
|
||||
@@ -242,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;
|
||||
@@ -252,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;
|
||||
@@ -271,29 +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));
|
||||
info->server_lang = p->Recv_uint8 ();
|
||||
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;
|
||||
}
|
||||
p->Recv_string(info->map_name, sizeof(info->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->server_lang >= NETWORK_NUM_LANGUAGES) info->server_lang = 0;
|
||||
if (info->map_set >= NETWORK_NUM_LANDSCAPES) info->map_set = 0;
|
||||
if (info->landscape >= NUM_LANDSCAPE) info->landscape = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,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,52 +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 {
|
||||
char map_name[NETWORK_NAME_LENGTH]; ///< Map which is played ["random" for a randomized map]
|
||||
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 hostname[NETWORK_HOSTNAME_LENGTH]; ///< Hostname of the server (if any)
|
||||
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 server_lang; ///< Language of the server (we should make a nice table for this)
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,6 +159,23 @@ bool SetNoDelay(SOCKET d)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#ifndef NETWORK_CORE_OS_ABSTRACTION_H
|
||||
#define NETWORK_CORE_OS_ABSTRACTION_H
|
||||
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Abstraction of a network error where all implementation details of the
|
||||
* error codes are encapsulated in this class and the abstraction layer.
|
||||
@@ -31,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();
|
||||
};
|
||||
@@ -110,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 */
|
||||
@@ -191,6 +196,7 @@ static inline socklen_t FixAddrLenForEmscripten(struct sockaddr_storage &address
|
||||
|
||||
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 */
|
||||
|
||||
+179
-82
@@ -17,44 +17,66 @@
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/**
|
||||
* Create a packet that is used to read from a network socket
|
||||
* @param cs the socket handler associated with the socket we are reading from
|
||||
* Create a packet that is used to read from a network socket.
|
||||
* @param cs The socket handler associated with the socket we are reading from.
|
||||
* @param limit The maximum size of packets to accept.
|
||||
* @param initial_read_size The initial amount of data to transfer from the socket into the
|
||||
* packet. This defaults to just the required bytes to determine the
|
||||
* packet's size. That default is the wanted for streams such as TCP
|
||||
* as you do not want to read data of the next packet yet. For UDP
|
||||
* you need to read the whole packet at once otherwise you might
|
||||
* loose some the data of the packet, so there you pass the maximum
|
||||
* size for the packet you expect from the network.
|
||||
*/
|
||||
Packet::Packet(NetworkSocketHandler *cs)
|
||||
Packet::Packet(NetworkSocketHandler *cs, size_t limit, size_t initial_read_size) : next(nullptr), pos(0), limit(limit)
|
||||
{
|
||||
assert(cs != nullptr);
|
||||
|
||||
this->cs = cs;
|
||||
this->next = nullptr;
|
||||
this->pos = 0; // We start reading from here
|
||||
this->size = 0;
|
||||
this->buffer = MallocT<byte>(SEND_MTU);
|
||||
this->cs = cs;
|
||||
this->buffer.resize(initial_read_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet to send
|
||||
* @param type of the packet to send
|
||||
* @param type The type of the packet to send
|
||||
* @param limit The maximum number of bytes the packet may have. Default is COMPAT_MTU.
|
||||
* Be careful of compatibility with older clients/servers when changing
|
||||
* the limit as it might break things if the other side is not expecting
|
||||
* much larger packets than what they support.
|
||||
*/
|
||||
Packet::Packet(PacketType type)
|
||||
Packet::Packet(PacketType type, size_t limit) : next(nullptr), pos(0), limit(limit), cs(nullptr)
|
||||
{
|
||||
this->cs = nullptr;
|
||||
this->next = nullptr;
|
||||
|
||||
/* Skip the size so we can write that in before sending the packet */
|
||||
this->pos = 0;
|
||||
this->size = sizeof(PacketSize);
|
||||
this->buffer = MallocT<byte>(SEND_MTU);
|
||||
this->buffer[this->size++] = type;
|
||||
/* Allocate space for the the size so we can write that in just before sending the packet. */
|
||||
this->Send_uint16(0);
|
||||
this->Send_uint8(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the buffer of this packet.
|
||||
* Add the given Packet to the end of the queue of packets.
|
||||
* @param queue The pointer to the begin of the queue.
|
||||
* @param packet The packet to append to the queue.
|
||||
*/
|
||||
Packet::~Packet()
|
||||
/* static */ void Packet::AddToQueue(Packet **queue, Packet *packet)
|
||||
{
|
||||
free(this->buffer);
|
||||
while (*queue != nullptr) queue = &(*queue)->next;
|
||||
*queue = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop the packet from the begin of the queue and set the
|
||||
* begin of the queue to the second element in the queue.
|
||||
* @param queue The pointer to the begin of the queue.
|
||||
* @return The Packet that used to be a the begin of the queue.
|
||||
*/
|
||||
/* static */ Packet *Packet::PopFromQueue(Packet **queue)
|
||||
{
|
||||
Packet *p = *queue;
|
||||
*queue = p->next;
|
||||
p->next = nullptr;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the packet size from the raw packet from packet->size
|
||||
*/
|
||||
@@ -62,10 +84,21 @@ void Packet::PrepareToSend()
|
||||
{
|
||||
assert(this->cs == nullptr && this->next == nullptr);
|
||||
|
||||
this->buffer[0] = GB(this->size, 0, 8);
|
||||
this->buffer[1] = GB(this->size, 8, 8);
|
||||
this->buffer[0] = GB(this->Size(), 0, 8);
|
||||
this->buffer[1] = GB(this->Size(), 8, 8);
|
||||
|
||||
this->pos = 0; // We start reading from here
|
||||
this->buffer.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it safe to write to the packet, i.e. didn't we run over the buffer?
|
||||
* @param bytes_to_write The amount of bytes we want to try to write.
|
||||
* @return True iff the given amount of bytes can be written to the packet.
|
||||
*/
|
||||
bool Packet::CanWriteToPacket(size_t bytes_to_write)
|
||||
{
|
||||
return this->Size() + bytes_to_write <= this->limit;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -95,8 +128,8 @@ void Packet::Send_bool(bool data)
|
||||
*/
|
||||
void Packet::Send_uint8(uint8 data)
|
||||
{
|
||||
assert(this->size < SEND_MTU - sizeof(data));
|
||||
this->buffer[this->size++] = data;
|
||||
assert(this->CanWriteToPacket(sizeof(data)));
|
||||
this->buffer.emplace_back(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,9 +138,9 @@ void Packet::Send_uint8(uint8 data)
|
||||
*/
|
||||
void Packet::Send_uint16(uint16 data)
|
||||
{
|
||||
assert(this->size < SEND_MTU - sizeof(data));
|
||||
this->buffer[this->size++] = GB(data, 0, 8);
|
||||
this->buffer[this->size++] = GB(data, 8, 8);
|
||||
assert(this->CanWriteToPacket(sizeof(data)));
|
||||
this->buffer.emplace_back(GB(data, 0, 8));
|
||||
this->buffer.emplace_back(GB(data, 8, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,11 +149,11 @@ void Packet::Send_uint16(uint16 data)
|
||||
*/
|
||||
void Packet::Send_uint32(uint32 data)
|
||||
{
|
||||
assert(this->size < SEND_MTU - sizeof(data));
|
||||
this->buffer[this->size++] = GB(data, 0, 8);
|
||||
this->buffer[this->size++] = GB(data, 8, 8);
|
||||
this->buffer[this->size++] = GB(data, 16, 8);
|
||||
this->buffer[this->size++] = GB(data, 24, 8);
|
||||
assert(this->CanWriteToPacket(sizeof(data)));
|
||||
this->buffer.emplace_back(GB(data, 0, 8));
|
||||
this->buffer.emplace_back(GB(data, 8, 8));
|
||||
this->buffer.emplace_back(GB(data, 16, 8));
|
||||
this->buffer.emplace_back(GB(data, 24, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,15 +162,15 @@ void Packet::Send_uint32(uint32 data)
|
||||
*/
|
||||
void Packet::Send_uint64(uint64 data)
|
||||
{
|
||||
assert(this->size < SEND_MTU - sizeof(data));
|
||||
this->buffer[this->size++] = GB(data, 0, 8);
|
||||
this->buffer[this->size++] = GB(data, 8, 8);
|
||||
this->buffer[this->size++] = GB(data, 16, 8);
|
||||
this->buffer[this->size++] = GB(data, 24, 8);
|
||||
this->buffer[this->size++] = GB(data, 32, 8);
|
||||
this->buffer[this->size++] = GB(data, 40, 8);
|
||||
this->buffer[this->size++] = GB(data, 48, 8);
|
||||
this->buffer[this->size++] = GB(data, 56, 8);
|
||||
assert(this->CanWriteToPacket(sizeof(data)));
|
||||
this->buffer.emplace_back(GB(data, 0, 8));
|
||||
this->buffer.emplace_back(GB(data, 8, 8));
|
||||
this->buffer.emplace_back(GB(data, 16, 8));
|
||||
this->buffer.emplace_back(GB(data, 24, 8));
|
||||
this->buffer.emplace_back(GB(data, 32, 8));
|
||||
this->buffer.emplace_back(GB(data, 40, 8));
|
||||
this->buffer.emplace_back(GB(data, 48, 8));
|
||||
this->buffer.emplace_back(GB(data, 56, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,14 +178,27 @@ 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);
|
||||
/* The <= *is* valid due to the fact that we are comparing sizes and not the index. */
|
||||
assert(this->size + strlen(data) + 1 <= SEND_MTU);
|
||||
while ((this->buffer[this->size++] = *data++) != '\0') {}
|
||||
assert(this->CanWriteToPacket(data.size() + 1));
|
||||
this->buffer.insert(this->buffer.end(), data.begin(), data.end());
|
||||
this->buffer.emplace_back('\0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send as many of the bytes as possible in the packet. This can mean
|
||||
* that it is possible that not all bytes are sent. To cope with this
|
||||
* the function returns the amount of bytes that were actually sent.
|
||||
* @param begin The begin of the buffer to send.
|
||||
* @param end The end of the buffer to send.
|
||||
* @return The number of bytes that were added to this packet.
|
||||
*/
|
||||
size_t Packet::Send_bytes(const byte *begin, const byte *end)
|
||||
{
|
||||
size_t amount = std::min<size_t>(end - begin, this->limit - this->Size());
|
||||
this->buffer.insert(this->buffer.end(), begin, begin + amount);
|
||||
return amount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Receiving commands
|
||||
@@ -162,18 +208,21 @@ void Packet::Send_string(const char *data)
|
||||
|
||||
|
||||
/**
|
||||
* Is it safe to read from the packet, i.e. didn't we run over the buffer ?
|
||||
* @param bytes_to_read The amount of bytes we want to try to read.
|
||||
* Is it safe to read from the packet, i.e. didn't we run over the buffer?
|
||||
* In case \c close_connection is true, the connection will be closed when one would
|
||||
* overrun the buffer. When it is false, the connection remains untouched.
|
||||
* @param bytes_to_read The amount of bytes we want to try to read.
|
||||
* @param close_connection Whether to close the connection if one cannot read that amount.
|
||||
* @return True if that is safe, otherwise false.
|
||||
*/
|
||||
bool Packet::CanReadFromPacket(uint bytes_to_read)
|
||||
bool Packet::CanReadFromPacket(size_t bytes_to_read, bool close_connection)
|
||||
{
|
||||
/* Don't allow reading from a quit client/client who send bad data */
|
||||
if (this->cs->HasClientQuit()) return false;
|
||||
|
||||
/* Check if variable is within packet-size */
|
||||
if (this->pos + bytes_to_read > this->size) {
|
||||
this->cs->NetworkSocketHandler::CloseConnection();
|
||||
if (this->pos + bytes_to_read > this->Size()) {
|
||||
if (close_connection) this->cs->NetworkSocketHandler::MarkClosed();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -181,13 +230,45 @@ bool Packet::CanReadFromPacket(uint bytes_to_read)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the packet size from the raw packet and stores it in the packet->size
|
||||
* Check whether the packet, given the position of the "write" pointer, has read
|
||||
* enough of the packet to contain its size.
|
||||
* @return True iff there is enough data in the packet to contain the packet's size.
|
||||
*/
|
||||
void Packet::ReadRawPacketSize()
|
||||
bool Packet::HasPacketSizeData() const
|
||||
{
|
||||
return this->pos >= sizeof(PacketSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in the packet.
|
||||
* When sending a packet this is the size of the data up to that moment.
|
||||
* When receiving a packet (before PrepareToRead) this is the allocated size for the data to be read.
|
||||
* When reading a packet (after PrepareToRead) this is the full size of the packet.
|
||||
* @return The packet's size.
|
||||
*/
|
||||
size_t Packet::Size() const
|
||||
{
|
||||
return this->buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the packet size from the raw packet and stores it in the packet->size
|
||||
* @return True iff the packet size seems plausible.
|
||||
*/
|
||||
bool Packet::ParsePacketSize()
|
||||
{
|
||||
assert(this->cs != nullptr && this->next == nullptr);
|
||||
this->size = (PacketSize)this->buffer[0];
|
||||
this->size += (PacketSize)this->buffer[1] << 8;
|
||||
size_t size = (size_t)this->buffer[0];
|
||||
size += (size_t)this->buffer[1] << 8;
|
||||
|
||||
/* If the size of the packet is less than the bytes required for the size and type of
|
||||
* the packet, or more than the allowed limit, then something is wrong with the packet.
|
||||
* In those cases the packet can generally be regarded as containing garbage data. */
|
||||
if (size < sizeof(PacketSize) + sizeof(PacketType) || size > this->limit) return false;
|
||||
|
||||
this->buffer.resize(size);
|
||||
this->pos = sizeof(PacketSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,12 +276,20 @@ void Packet::ReadRawPacketSize()
|
||||
*/
|
||||
void Packet::PrepareToRead()
|
||||
{
|
||||
this->ReadRawPacketSize();
|
||||
|
||||
/* Put the position on the right place */
|
||||
this->pos = sizeof(PacketSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the \c PacketType from this packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
PacketType Packet::GetPacketType() const
|
||||
{
|
||||
assert(this->Size() >= sizeof(PacketSize) + sizeof(PacketType));
|
||||
return static_cast<PacketType>(buffer[sizeof(PacketSize)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a boolean from the packet.
|
||||
* @return The read data.
|
||||
@@ -218,7 +307,7 @@ uint8 Packet::Recv_uint8()
|
||||
{
|
||||
uint8 n;
|
||||
|
||||
if (!this->CanReadFromPacket(sizeof(n))) return 0;
|
||||
if (!this->CanReadFromPacket(sizeof(n), true)) return 0;
|
||||
|
||||
n = this->buffer[this->pos++];
|
||||
return n;
|
||||
@@ -232,7 +321,7 @@ uint16 Packet::Recv_uint16()
|
||||
{
|
||||
uint16 n;
|
||||
|
||||
if (!this->CanReadFromPacket(sizeof(n))) return 0;
|
||||
if (!this->CanReadFromPacket(sizeof(n), true)) return 0;
|
||||
|
||||
n = (uint16)this->buffer[this->pos++];
|
||||
n += (uint16)this->buffer[this->pos++] << 8;
|
||||
@@ -247,7 +336,7 @@ uint32 Packet::Recv_uint32()
|
||||
{
|
||||
uint32 n;
|
||||
|
||||
if (!this->CanReadFromPacket(sizeof(n))) return 0;
|
||||
if (!this->CanReadFromPacket(sizeof(n), true)) return 0;
|
||||
|
||||
n = (uint32)this->buffer[this->pos++];
|
||||
n += (uint32)this->buffer[this->pos++] << 8;
|
||||
@@ -264,7 +353,7 @@ uint64 Packet::Recv_uint64()
|
||||
{
|
||||
uint64 n;
|
||||
|
||||
if (!this->CanReadFromPacket(sizeof(n))) return 0;
|
||||
if (!this->CanReadFromPacket(sizeof(n), true)) return 0;
|
||||
|
||||
n = (uint64)this->buffer[this->pos++];
|
||||
n += (uint64)this->buffer[this->pos++] << 8;
|
||||
@@ -278,31 +367,39 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of bytes that are still available for the Transfer functions.
|
||||
* @return The number of bytes that still have to be transfered.
|
||||
*/
|
||||
size_t Packet::RemainingBytesToTransfer() const
|
||||
{
|
||||
return this->Size() - this->pos;
|
||||
}
|
||||
|
||||
+130
-25
@@ -12,9 +12,12 @@
|
||||
#ifndef NETWORK_CORE_PACKET_H
|
||||
#define NETWORK_CORE_PACKET_H
|
||||
|
||||
#include "os_abstraction.h"
|
||||
#include "config.h"
|
||||
#include "core.h"
|
||||
#include "../../string_type.h"
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
typedef uint16 PacketSize; ///< Size of the whole packet.
|
||||
typedef uint8 PacketType; ///< Identifier for the packet
|
||||
@@ -23,10 +26,11 @@ typedef uint8 PacketType; ///< Identifier for the packet
|
||||
* Internal entity of a packet. As everything is sent as a packet,
|
||||
* all network communication will need to call the functions that
|
||||
* populate the packet.
|
||||
* Every packet can be at most SEND_MTU bytes. Overflowing this
|
||||
* limit will give an assertion when sending (i.e. writing) the
|
||||
* packet. Reading past the size of the packet when receiving
|
||||
* will return all 0 values and "" in case of the string.
|
||||
* Every packet can be at most a limited number bytes set in the
|
||||
* constructor. Overflowing this limit will give an assertion when
|
||||
* sending (i.e. writing) the packet. Reading past the size of the
|
||||
* packet when receiving will return all 0 values and "" in case of
|
||||
* the string.
|
||||
*
|
||||
* --- Points of attention ---
|
||||
* - all > 1 byte integral values are written in little endian,
|
||||
@@ -38,49 +42,150 @@ typedef uint8 PacketType; ///< Identifier for the packet
|
||||
* (year % 4 == 0) and ((year % 100 != 0) or (year % 400 == 0))
|
||||
*/
|
||||
struct Packet {
|
||||
private:
|
||||
/** The next packet. Used for queueing packets before sending. */
|
||||
Packet *next;
|
||||
/**
|
||||
* The size of the whole packet for received packets. For packets
|
||||
* that will be sent, the value is filled in just before the
|
||||
* actual transmission.
|
||||
*/
|
||||
PacketSize size;
|
||||
/** The current read/write position in the packet */
|
||||
PacketSize pos;
|
||||
/** The buffer of this packet, of basically variable length up to SEND_MTU. */
|
||||
byte *buffer;
|
||||
/** The buffer of this packet. */
|
||||
std::vector<byte> buffer;
|
||||
/** The limit for the packet size. */
|
||||
size_t limit;
|
||||
|
||||
private:
|
||||
/** Socket we're associated with. */
|
||||
NetworkSocketHandler *cs;
|
||||
|
||||
public:
|
||||
Packet(NetworkSocketHandler *cs);
|
||||
Packet(PacketType type);
|
||||
~Packet();
|
||||
Packet(NetworkSocketHandler *cs, size_t limit, size_t initial_read_size = sizeof(PacketSize));
|
||||
Packet(PacketType type, size_t limit = COMPAT_MTU);
|
||||
|
||||
static void AddToQueue(Packet **queue, Packet *packet);
|
||||
static Packet *PopFromQueue(Packet **queue);
|
||||
|
||||
/* Sending/writing of packets */
|
||||
void PrepareToSend();
|
||||
|
||||
void Send_bool (bool data);
|
||||
void Send_uint8 (uint8 data);
|
||||
void Send_uint16(uint16 data);
|
||||
void Send_uint32(uint32 data);
|
||||
void Send_uint64(uint64 data);
|
||||
void Send_string(const char *data);
|
||||
bool CanWriteToPacket(size_t bytes_to_write);
|
||||
void Send_bool (bool data);
|
||||
void Send_uint8 (uint8 data);
|
||||
void Send_uint16(uint16 data);
|
||||
void Send_uint32(uint32 data);
|
||||
void Send_uint64(uint64 data);
|
||||
void Send_string(const std::string_view data);
|
||||
size_t Send_bytes (const byte *begin, const byte *end);
|
||||
|
||||
/* Reading/receiving of packets */
|
||||
void ReadRawPacketSize();
|
||||
bool HasPacketSizeData() const;
|
||||
bool ParsePacketSize();
|
||||
size_t Size() const;
|
||||
void PrepareToRead();
|
||||
PacketType GetPacketType() const;
|
||||
|
||||
bool CanReadFromPacket (uint bytes_to_read);
|
||||
bool CanReadFromPacket(size_t bytes_to_read, bool close_connection = false);
|
||||
bool Recv_bool ();
|
||||
uint8 Recv_uint8 ();
|
||||
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;
|
||||
|
||||
/**
|
||||
* Transfer data from the packet to the given function. It starts reading at the
|
||||
* position the last transfer stopped.
|
||||
* See Packet::TransferIn for more information about transferring data to functions.
|
||||
* @param transfer_function The function to pass the buffer as second parameter and the
|
||||
* amount to write as third parameter. It returns the amount that
|
||||
* was written or -1 upon errors.
|
||||
* @param limit The maximum amount of bytes to transfer.
|
||||
* @param destination The first parameter of the transfer function.
|
||||
* @param args The fourth and further parameters to the transfer function, if any.
|
||||
* @return The return value of the transfer_function.
|
||||
*/
|
||||
template <
|
||||
typename A = size_t, ///< The type for the amount to be passed, so it can be cast to the right type.
|
||||
typename F, ///< The type of the function.
|
||||
typename D, ///< The type of the destination.
|
||||
typename ... Args> ///< The types of the remaining arguments to the function.
|
||||
ssize_t TransferOutWithLimit(F transfer_function, size_t limit, D destination, Args&& ... args)
|
||||
{
|
||||
size_t amount = std::min(this->RemainingBytesToTransfer(), limit);
|
||||
if (amount == 0) return 0;
|
||||
|
||||
assert(this->pos < this->buffer.size());
|
||||
assert(this->pos + amount <= this->buffer.size());
|
||||
/* Making buffer a char means casting a lot in the Recv/Send functions. */
|
||||
const char *output_buffer = reinterpret_cast<const char*>(this->buffer.data() + this->pos);
|
||||
ssize_t bytes = transfer_function(destination, output_buffer, static_cast<A>(amount), std::forward<Args>(args)...);
|
||||
if (bytes > 0) this->pos += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer data from the packet to the given function. It starts reading at the
|
||||
* position the last transfer stopped.
|
||||
* See Packet::TransferIn for more information about transferring data to functions.
|
||||
* @param transfer_function The function to pass the buffer as second parameter and the
|
||||
* amount to write as third parameter. It returns the amount that
|
||||
* was written or -1 upon errors.
|
||||
* @param destination The first parameter of the transfer function.
|
||||
* @param args The fourth and further parameters to the transfer function, if any.
|
||||
* @tparam A The type for the amount to be passed, so it can be cast to the right type.
|
||||
* @tparam F The type of the transfer_function.
|
||||
* @tparam D The type of the destination.
|
||||
* @tparam Args The types of the remaining arguments to the function.
|
||||
* @return The return value of the transfer_function.
|
||||
*/
|
||||
template <typename A = size_t, typename F, typename D, typename ... Args>
|
||||
ssize_t TransferOut(F transfer_function, D destination, Args&& ... args)
|
||||
{
|
||||
return TransferOutWithLimit<A>(transfer_function, std::numeric_limits<size_t>::max(), destination, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer data from the given function into the packet. It starts writing at the
|
||||
* position the last transfer stopped.
|
||||
*
|
||||
* Examples of functions that can be used to transfer data into a packet are TCP's
|
||||
* recv and UDP's recvfrom functions. They will directly write their data into the
|
||||
* packet without an intermediate buffer.
|
||||
* Examples of functions that can be used to transfer data from a packet are TCP's
|
||||
* send and UDP's sendto functions. They will directly read the data from the packet's
|
||||
* buffer without an intermediate buffer.
|
||||
* These are functions are special in a sense as even though the packet can send or
|
||||
* receive an amount of data, those functions can say they only processed a smaller
|
||||
* amount, so special handling is required to keep the position pointers correct.
|
||||
* Most of these transfer functions are in the form function(source, buffer, amount, ...),
|
||||
* so the template of this function will assume that as the base parameter order.
|
||||
*
|
||||
* This will attempt to write all the remaining bytes into the packet. It updates the
|
||||
* position based on how many bytes were actually written by the called transfer_function.
|
||||
* @param transfer_function The function to pass the buffer as second parameter and the
|
||||
* amount to read as third parameter. It returns the amount that
|
||||
* was read or -1 upon errors.
|
||||
* @param source The first parameter of the transfer function.
|
||||
* @param args The fourth and further parameters to the transfer function, if any.
|
||||
* @tparam A The type for the amount to be passed, so it can be cast to the right type.
|
||||
* @tparam F The type of the transfer_function.
|
||||
* @tparam S The type of the source.
|
||||
* @tparam Args The types of the remaining arguments to the function.
|
||||
* @return The return value of the transfer_function.
|
||||
*/
|
||||
template <typename A = size_t, typename F, typename S, typename ... Args>
|
||||
ssize_t TransferIn(F transfer_function, S source, Args&& ... args)
|
||||
{
|
||||
size_t amount = this->RemainingBytesToTransfer();
|
||||
if (amount == 0) return 0;
|
||||
|
||||
assert(this->pos < this->buffer.size());
|
||||
assert(this->pos + amount <= this->buffer.size());
|
||||
/* Making buffer a char means casting a lot in the Recv/Send functions. */
|
||||
char *input_buffer = reinterpret_cast<char*>(this->buffer.data() + this->pos);
|
||||
ssize_t bytes = transfer_function(source, input_buffer, static_cast<A>(amount), std::forward<Args>(args)...);
|
||||
if (bytes > 0) this->pos += bytes;
|
||||
return bytes;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_PACKET_H */
|
||||
|
||||
+46
-53
@@ -29,25 +29,45 @@ NetworkTCPSocketHandler::NetworkTCPSocketHandler(SOCKET s) :
|
||||
|
||||
NetworkTCPSocketHandler::~NetworkTCPSocketHandler()
|
||||
{
|
||||
this->CloseConnection();
|
||||
this->EmptyPacketQueue();
|
||||
this->CloseSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Free all pending and partially received packets.
|
||||
*/
|
||||
void NetworkTCPSocketHandler::EmptyPacketQueue()
|
||||
{
|
||||
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;
|
||||
NetworkSocketHandler::CloseConnection(error);
|
||||
|
||||
/* Free all pending and partially received packets */
|
||||
while (this->packet_queue != nullptr) {
|
||||
Packet *p = this->packet_queue->next;
|
||||
delete this->packet_queue;
|
||||
this->packet_queue = p;
|
||||
}
|
||||
delete this->packet_recv;
|
||||
this->packet_recv = nullptr;
|
||||
this->EmptyPacketQueue();
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -60,26 +80,10 @@ NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection(bool error)
|
||||
*/
|
||||
void NetworkTCPSocketHandler::SendPacket(Packet *packet)
|
||||
{
|
||||
Packet *p;
|
||||
assert(packet != nullptr);
|
||||
|
||||
packet->PrepareToSend();
|
||||
|
||||
/* Reallocate the packet as in 99+% of the times we send at most 25 bytes and
|
||||
* keeping the other 1400+ bytes wastes memory, especially when someone tries
|
||||
* to do a denial of service attack! */
|
||||
packet->buffer = ReallocT(packet->buffer, packet->size);
|
||||
|
||||
/* Locate last packet buffered for the client */
|
||||
p = this->packet_queue;
|
||||
if (p == nullptr) {
|
||||
/* No packets yet */
|
||||
this->packet_queue = packet;
|
||||
} else {
|
||||
/* Skip to the last packet */
|
||||
while (p->next != nullptr) p = p->next;
|
||||
p->next = packet;
|
||||
}
|
||||
Packet::AddToQueue(&this->packet_queue, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,15 +105,14 @@ SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
|
||||
if (!this->writable) return SPS_NONE_SENT;
|
||||
if (!this->IsConnected()) return SPS_CLOSED;
|
||||
|
||||
p = this->packet_queue;
|
||||
while (p != nullptr) {
|
||||
res = send(this->sock, (const char*)p->buffer + p->pos, p->size - p->pos, 0);
|
||||
while ((p = this->packet_queue) != nullptr) {
|
||||
res = p->TransferOut<int>(send, this->sock, 0);
|
||||
if (res == -1) {
|
||||
NetworkError err = NetworkError::GetLast();
|
||||
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;
|
||||
@@ -122,14 +125,10 @@ SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
|
||||
return SPS_CLOSED;
|
||||
}
|
||||
|
||||
p->pos += res;
|
||||
|
||||
/* Is this packet sent? */
|
||||
if (p->pos == p->size) {
|
||||
if (p->RemainingBytesToTransfer() == 0) {
|
||||
/* Go to the next packet */
|
||||
this->packet_queue = p->next;
|
||||
delete p;
|
||||
p = this->packet_queue;
|
||||
delete Packet::PopFromQueue(&this->packet_queue);
|
||||
} else {
|
||||
return SPS_PARTLY_SENT;
|
||||
}
|
||||
@@ -149,21 +148,20 @@ Packet *NetworkTCPSocketHandler::ReceivePacket()
|
||||
if (!this->IsConnected()) return nullptr;
|
||||
|
||||
if (this->packet_recv == nullptr) {
|
||||
this->packet_recv = new Packet(this);
|
||||
this->packet_recv = new Packet(this, TCP_MTU);
|
||||
}
|
||||
|
||||
Packet *p = this->packet_recv;
|
||||
|
||||
/* Read packet size */
|
||||
if (p->pos < sizeof(PacketSize)) {
|
||||
while (p->pos < sizeof(PacketSize)) {
|
||||
/* Read the size of the packet */
|
||||
res = recv(this->sock, (char*)p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0);
|
||||
if (!p->HasPacketSizeData()) {
|
||||
while (p->RemainingBytesToTransfer() != 0) {
|
||||
res = p->TransferIn<int>(recv, this->sock, 0);
|
||||
if (res == -1) {
|
||||
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;
|
||||
}
|
||||
@@ -175,26 +173,23 @@ Packet *NetworkTCPSocketHandler::ReceivePacket()
|
||||
this->CloseConnection();
|
||||
return nullptr;
|
||||
}
|
||||
p->pos += res;
|
||||
}
|
||||
|
||||
/* Read the packet size from the received packet */
|
||||
p->ReadRawPacketSize();
|
||||
|
||||
if (p->size > SEND_MTU) {
|
||||
/* Parse the size in the received packet and if not valid, close the connection. */
|
||||
if (!p->ParsePacketSize()) {
|
||||
this->CloseConnection();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read rest of packet */
|
||||
while (p->pos < p->size) {
|
||||
res = recv(this->sock, (char*)p->buffer + p->pos, p->size - p->pos, 0);
|
||||
while (p->RemainingBytesToTransfer() != 0) {
|
||||
res = p->TransferIn<int>(recv, this->sock, 0);
|
||||
if (res == -1) {
|
||||
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;
|
||||
}
|
||||
@@ -206,8 +201,6 @@ Packet *NetworkTCPSocketHandler::ReceivePacket()
|
||||
this->CloseConnection();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
p->pos += res;
|
||||
}
|
||||
|
||||
/* Prepare for receiving a new packet */
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,9 +87,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 +123,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +109,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);
|
||||
|
||||
@@ -222,7 +222,7 @@ protected:
|
||||
|
||||
/**
|
||||
* Welcome a connected admin to the game:
|
||||
* string Name of the Server (e.g. as advertised to master server).
|
||||
* string Name of the Server.
|
||||
* string OpenTTD version string.
|
||||
* bool Server is dedicated.
|
||||
* string Name of the Map.
|
||||
@@ -482,7 +482,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,439 @@
|
||||
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) {
|
||||
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 +469,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,62 +10,16 @@
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#ifndef OPENTTD_MSU
|
||||
#include "../../textfile_gui.h"
|
||||
#include "../../newgrf_config.h"
|
||||
#include "../../base_media_base.h"
|
||||
#include "../../ai/ai.hpp"
|
||||
#include "../../game/game.hpp"
|
||||
#include "../../fios.h"
|
||||
#endif /* OPENTTD_MSU */
|
||||
#include "tcp_content.h"
|
||||
|
||||
#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
|
||||
@@ -92,7 +46,6 @@ bool ContentInfo::IsValid() const
|
||||
return this->state < ContentInfo::INVALID && this->type >= CONTENT_TYPE_BEGIN && this->type < CONTENT_TYPE_END;
|
||||
}
|
||||
|
||||
#ifndef OPENTTD_MSU
|
||||
/**
|
||||
* Search a textfile file next to this file in the content list.
|
||||
* @param type The type of the textfile to search for.
|
||||
@@ -139,16 +92,6 @@ const char *ContentInfo::GetTextfile(TextfileType type) const
|
||||
if (tmp == nullptr) return nullptr;
|
||||
return ::GetTextfile(type, GetContentInfoSubDir(this->type), tmp);
|
||||
}
|
||||
#endif /* OPENTTD_MSU */
|
||||
|
||||
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
|
||||
@@ -171,9 +114,9 @@ bool NetworkContentSocketHandler::HandlePacket(Packet *p)
|
||||
|
||||
default:
|
||||
if (this->HasClientQuit()) {
|
||||
DEBUG(net, 0, "[tcp/content] received invalid packet type %d from %s", type, this->client_addr.GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[tcp/content] Received invalid packet type {}", type);
|
||||
} else {
|
||||
DEBUG(net, 0, "[tcp/content] received illegal packet from %s", this->client_addr.GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[tcp/content] Received illegal packet");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -224,7 +167,7 @@ bool NetworkContentSocketHandler::ReceivePackets()
|
||||
*/
|
||||
bool NetworkContentSocketHandler::ReceiveInvalidPacket(PacketContentType type)
|
||||
{
|
||||
DEBUG(net, 0, "[tcp/content] received illegal packet type %d from %s", type, this->client_addr.GetAddressAsString().c_str());
|
||||
Debug(net, 0, "[tcp/content] Received illegal packet type {}", type);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -236,7 +179,6 @@ bool NetworkContentSocketHandler::Receive_SERVER_INFO(Packet *p) { return this->
|
||||
bool NetworkContentSocketHandler::Receive_CLIENT_CONTENT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_CLIENT_CONTENT); }
|
||||
bool NetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CONTENT_SERVER_CONTENT); }
|
||||
|
||||
#ifndef OPENTTD_MSU
|
||||
/**
|
||||
* Helper to get the subdirectory a #ContentInfo is located in.
|
||||
* @param type The type of content.
|
||||
@@ -261,4 +203,3 @@ Subdirectory GetContentInfoSubDir(ContentType type)
|
||||
case CONTENT_TYPE_HEIGHTMAP: return HEIGHTMAP_DIR;
|
||||
}
|
||||
}
|
||||
#endif /* OPENTTD_MSU */
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
/** Base socket handler for all Content TCP sockets */
|
||||
class NetworkContentSocketHandler : public NetworkTCPSocketHandler {
|
||||
protected:
|
||||
NetworkAddress client_addr; ///< The address we're connected to.
|
||||
void Close() override;
|
||||
|
||||
bool ReceiveInvalidPacket(PacketContentType type);
|
||||
|
||||
/**
|
||||
@@ -119,20 +116,21 @@ public:
|
||||
* @param s the socket we are connected with
|
||||
* @param address IP etc. of the client
|
||||
*/
|
||||
NetworkContentSocketHandler(SOCKET s = INVALID_SOCKET, const NetworkAddress &address = NetworkAddress()) :
|
||||
NetworkTCPSocketHandler(s),
|
||||
client_addr(address)
|
||||
NetworkContentSocketHandler(SOCKET s = INVALID_SOCKET) :
|
||||
NetworkTCPSocketHandler(s)
|
||||
{
|
||||
}
|
||||
|
||||
/** 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();
|
||||
};
|
||||
|
||||
#ifndef OPENTTD_MSU
|
||||
Subdirectory GetContentInfoSubDir(ContentType type);
|
||||
#endif /* OPENTTD_MSU */
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_CONTENT_H */
|
||||
|
||||
@@ -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,34 +58,24 @@ 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;
|
||||
#ifndef OPENTTD_MSU
|
||||
const char *GetTextfile(TextfileType type) const;
|
||||
#endif /* OPENTTD_MSU */
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_CONTENT_TYPE_H */
|
||||
|
||||
@@ -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,318 @@
|
||||
/*
|
||||
* 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.
|
||||
};
|
||||
|
||||
/** 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);
|
||||
@@ -117,9 +115,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 +149,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 +159,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); }
|
||||
|
||||
@@ -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.
|
||||
@@ -200,40 +200,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 +240,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.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../rev.h"
|
||||
#include "../network_func.h"
|
||||
#include "../network_internal.h"
|
||||
#include "game_info.h"
|
||||
|
||||
#include "tcp_http.h"
|
||||
@@ -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,26 +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';
|
||||
|
||||
/* Fetch the hostname, and possible port number. */
|
||||
const char *company = nullptr;
|
||||
const char *port = nullptr;
|
||||
ParseConnectionString(&company, &port, hname);
|
||||
if (company != nullptr) return_error("[tcp/http] invalid hostname");
|
||||
|
||||
NetworkAddress address(hname, port == nullptr ? 80 : atoi(port));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -234,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 */
|
||||
@@ -262,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;
|
||||
@@ -319,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.
|
||||
@@ -49,45 +85,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 (send(s, (const char*)p.buffer, p.size, 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 (send(s, (const char*)p.buffer, p.size, 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);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +152,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;
|
||||
}
|
||||
@@ -165,7 +167,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 */
|
||||
+19
-44
@@ -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 */
|
||||
int res = sendto(s.second, (const char*)p->buffer, p->size, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
|
||||
DEBUG(net, 7, "[udp] sendto(%s)", send.GetAddressAsString().c_str());
|
||||
ssize_t res = p->TransferOut<int>(sendto, s.second, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
|
||||
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;
|
||||
}
|
||||
@@ -120,12 +114,13 @@ void NetworkUDPSocketHandler::ReceivePackets()
|
||||
struct sockaddr_storage client_addr;
|
||||
memset(&client_addr, 0, sizeof(client_addr));
|
||||
|
||||
Packet p(this);
|
||||
/* The limit is UDP_MTU, but also allocate that much as we need to read the whole packet in one go. */
|
||||
Packet p(this, UDP_MTU, UDP_MTU);
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
|
||||
/* Try to receive anything */
|
||||
SetNonBlocking(s.second); // Some OSes seem to lose the non-blocking status of the socket
|
||||
int nbytes = recvfrom(s.second, (char*)p.buffer, SEND_MTU, 0, (struct sockaddr *)&client_addr, &client_len);
|
||||
ssize_t nbytes = p.TransferIn<int>(recvfrom, s.second, 0, (struct sockaddr *)&client_addr, &client_len);
|
||||
|
||||
/* Did we get the bytes for the base header of the packet? */
|
||||
if (nbytes <= 0) break; // No data, i.e. no packet
|
||||
@@ -135,14 +130,14 @@ void NetworkUDPSocketHandler::ReceivePackets()
|
||||
#endif
|
||||
|
||||
NetworkAddress address(client_addr, client_len);
|
||||
p.PrepareToRead();
|
||||
|
||||
/* If the size does not match the packet must be corrupted.
|
||||
* Otherwise it will be marked as corrupted later on. */
|
||||
if (nbytes != p.size) {
|
||||
DEBUG(net, 1, "received a packet with mismatching size from %s", address.GetAddressAsString().c_str());
|
||||
if (!p.ParsePacketSize() || (size_t)nbytes != p.Size()) {
|
||||
Debug(net, 1, "Received a packet with mismatching size from {}", address.GetAddressAsString());
|
||||
continue;
|
||||
}
|
||||
p.PrepareToRead();
|
||||
|
||||
/* Handle the packet */
|
||||
this->HandleUDPPacket(&p, &address);
|
||||
@@ -167,22 +162,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;
|
||||
}
|
||||
@@ -195,18 +180,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();
|
||||
|
||||
+321
-150
@@ -19,6 +19,7 @@
|
||||
#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 +34,9 @@
|
||||
#include "../core/pool_func.hpp"
|
||||
#include "../gfx_func.h"
|
||||
#include "../error.h"
|
||||
#include <charconv>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
@@ -57,7 +61,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 +78,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 +152,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 +172,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 +216,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)
|
||||
{
|
||||
StringID strid;
|
||||
switch (action) {
|
||||
@@ -259,17 +262,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((TextColour)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);
|
||||
@@ -316,6 +319,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
|
||||
STR_NETWORK_ERROR_CLIENT_TIMEOUT_COMPUTER,
|
||||
STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP,
|
||||
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
|
||||
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
|
||||
};
|
||||
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
|
||||
|
||||
@@ -365,9 +369,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;
|
||||
}
|
||||
|
||||
@@ -448,40 +450,96 @@ 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, and company and port will
|
||||
* be set to the company and port strings given by the user, inside the memory area originally
|
||||
* occupied by connection_string.
|
||||
* 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 ParseConnectionString(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, ':'));
|
||||
char *p;
|
||||
for (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 '#':
|
||||
*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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing either "hostname" or "hostname:ip" to a
|
||||
* NetworkAddress.
|
||||
*
|
||||
* @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 ParseConnectionString(const std::string &connection_string, uint16 default_port)
|
||||
{
|
||||
uint16 port = default_port;
|
||||
std::string_view ip = ParseFullConnectionString(connection_string, port);
|
||||
return NetworkAddress(ip, port);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,9 +552,10 @@ void ParseConnectionString(const char **company, const char **port, char *connec
|
||||
/* Register the login */
|
||||
_network_clients_connected++;
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
ServerNetworkGameSocketHandler *cs = new ServerNetworkGameSocketHandler(s);
|
||||
cs->client_address = address; // Save the IP of the client
|
||||
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,13 +581,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();
|
||||
@@ -538,7 +603,7 @@ void NetworkClose(bool close_admins)
|
||||
|
||||
NetworkFreeLocalCommandQueue();
|
||||
|
||||
free(_network_company_states);
|
||||
delete[] _network_company_states;
|
||||
_network_company_states = nullptr;
|
||||
|
||||
InitializeNetworkPools(close_admins);
|
||||
@@ -548,7 +613,6 @@ void NetworkClose(bool close_admins)
|
||||
static void NetworkInitialize(bool close_admins = true)
|
||||
{
|
||||
InitializeNetworkPools(close_admins);
|
||||
NetworkUDPInitialize();
|
||||
|
||||
_sync_frame = 0;
|
||||
_network_first_time = true;
|
||||
@@ -556,59 +620,71 @@ 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:
|
||||
std::string connection_string;
|
||||
|
||||
public:
|
||||
TCPQueryConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
|
||||
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->online = false;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
_networking = true;
|
||||
new ClientNetworkGameSocketHandler(s);
|
||||
MyClient::SendCompanyInformationQuery();
|
||||
new ClientNetworkGameSocketHandler(s, this->connection_string);
|
||||
MyClient::SendInformationQuery();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Query a server to fetch his game-info.
|
||||
* @param address the address to query.
|
||||
* Query a server to fetch the game-info.
|
||||
* @param connection_string the address to query.
|
||||
*/
|
||||
void NetworkTCPQueryServer(NetworkAddress address)
|
||||
void NetworkQueryServer(const std::string &connection_string)
|
||||
{
|
||||
if (!_network_available) return;
|
||||
|
||||
NetworkDisconnect();
|
||||
NetworkInitialize();
|
||||
|
||||
new TCPQueryConnecter(address);
|
||||
new TCPQueryConnecter(connection_string);
|
||||
}
|
||||
|
||||
/* Validates an address entered as a string and adds the server to
|
||||
/**
|
||||
* Validates an address entered as a string and adds the server to
|
||||
* the list. If you use this function, the games will be marked
|
||||
* as manually added. */
|
||||
void NetworkAddServer(const char *b)
|
||||
* 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, bool manually, bool never_expire)
|
||||
{
|
||||
if (*b != '\0') {
|
||||
const char *port = nullptr;
|
||||
const char *company = nullptr;
|
||||
char host[NETWORK_HOSTNAME_LENGTH];
|
||||
uint16 rport;
|
||||
if (connection_string.empty()) return nullptr;
|
||||
|
||||
strecpy(host, b, lastof(host));
|
||||
/* Ensure the item already exists in the list */
|
||||
NetworkGameList *item = NetworkGameListAddItem(connection_string);
|
||||
if (item->info.server_name.empty()) {
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
item->info.server_name = connection_string;
|
||||
|
||||
strecpy(_settings_client.network.connect_to_ip, b, lastof(_settings_client.network.connect_to_ip));
|
||||
rport = NETWORK_DEFAULT_PORT;
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
ParseConnectionString(&company, &port, host);
|
||||
if (port != nullptr) rport = atoi(port);
|
||||
|
||||
NetworkUDPQueryServer(NetworkAddress(host, rport), true);
|
||||
NetworkQueryServer(connection_string);
|
||||
}
|
||||
|
||||
if (manually) item->manually = true;
|
||||
if (never_expire) item->version = INT32_MAX;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -636,14 +712,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
|
||||
{
|
||||
@@ -653,25 +732,41 @@ public:
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
_networking = true;
|
||||
new ClientNetworkGameSocketHandler(s);
|
||||
new ClientNetworkGameSocketHandler(s, this->connection_string);
|
||||
IConsoleCmdExec("exec scripts/on_client.scr 0");
|
||||
NetworkClient_Connected();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* Used by clients, to connect to a server */
|
||||
void NetworkClientConnectGame(const char *hostname, uint16 port, CompanyID join_as, const char *join_server_password, const char *join_company_password)
|
||||
/**
|
||||
* Join a client to the server at with the given connection string.
|
||||
* The default for the passwords is \c nullptr. When the server or company needs a
|
||||
* password and none is given, the user is asked to enter the password in the GUI.
|
||||
* This function will return false whenever some information required to join is not
|
||||
* correct such as the company number or the client's name, or when there is not
|
||||
* networking avalabile at all. If the function returns false the connection with
|
||||
* the existing server is not disconnected.
|
||||
* It will return true when it starts the actual join process, i.e. when it
|
||||
* actually shows the join status window.
|
||||
*
|
||||
* @param connection_string The IP address, port and company number to join as.
|
||||
* @param default_company The company number to join as when none is given.
|
||||
* @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(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password)
|
||||
{
|
||||
if (!_network_available) return;
|
||||
CompanyID join_as = default_company;
|
||||
std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT, &join_as).connection_string;
|
||||
|
||||
if (port == 0) return;
|
||||
if (!_network_available) return false;
|
||||
if (!NetworkValidateOurClientName()) return false;
|
||||
|
||||
strecpy(_settings_client.network.last_host, hostname, lastof(_settings_client.network.last_host));
|
||||
_settings_client.network.last_port = port;
|
||||
_network_join_as = join_as;
|
||||
_network_join_server_password = join_server_password;
|
||||
_network_join_company_password = join_company_password;
|
||||
_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;
|
||||
|
||||
if (_game_mode == GM_MENU) {
|
||||
/* From the menu we can immediately continue with the actual join. */
|
||||
@@ -684,6 +779,7 @@ void NetworkClientConnectGame(const char *hostname, uint16 port, CompanyID join_
|
||||
*/
|
||||
_switch_mode = SM_JOIN_GAME;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,18 +792,16 @@ void NetworkClientJoinGame()
|
||||
NetworkDisconnect();
|
||||
NetworkInitialize();
|
||||
|
||||
_settings_client.network.last_joined = _network_join.connection_string;
|
||||
_network_join_status = NETWORK_JOIN_STATUS_CONNECTING;
|
||||
ShowJoinStatusWindow();
|
||||
|
||||
new TCPClientConnecter(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
|
||||
new TCPClientConnecter(_network_join.connection_string);
|
||||
}
|
||||
|
||||
static void NetworkInitGameInfo()
|
||||
{
|
||||
if (StrEmpty(_settings_client.network.server_name)) {
|
||||
seprintf(_settings_client.network.server_name, lastof(_settings_client.network.server_name), "Unnamed Server");
|
||||
}
|
||||
|
||||
FillStaticNetworkServerGameInfo();
|
||||
/* The server is a client too */
|
||||
_network_game_info.clients_on = _network_dedicated ? 0 : 1;
|
||||
|
||||
@@ -716,7 +810,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()
|
||||
@@ -727,22 +863,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;
|
||||
@@ -756,15 +896,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();
|
||||
|
||||
@@ -813,9 +953,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);
|
||||
|
||||
@@ -823,6 +961,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.
|
||||
@@ -856,6 +1017,7 @@ static void NetworkSend()
|
||||
void NetworkBackgroundLoop()
|
||||
{
|
||||
_network_content_client.SendReceive();
|
||||
_network_coordinator_client.SendReceive();
|
||||
TCPConnecter::CheckCallbacks();
|
||||
NetworkHTTPSocketHandler::HTTPReceive();
|
||||
|
||||
@@ -876,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;
|
||||
}
|
||||
}
|
||||
@@ -890,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;
|
||||
}
|
||||
|
||||
@@ -898,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();
|
||||
}
|
||||
@@ -934,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. */
|
||||
@@ -947,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;
|
||||
@@ -963,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;
|
||||
}
|
||||
@@ -1047,44 +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;
|
||||
}
|
||||
|
||||
void NetworkStartDebugLog(const char *hostname, uint16 port)
|
||||
{
|
||||
extern SOCKET _debug_socket; // Comes from debug.c
|
||||
class TCPNetworkDebugConnecter : TCPConnecter {
|
||||
private:
|
||||
std::string connection_string;
|
||||
|
||||
DEBUG(net, 0, "Redirecting DEBUG() to %s:%d", hostname, port);
|
||||
public:
|
||||
TCPNetworkDebugConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_DEBUGLOG_PORT), connection_string(connection_string) {}
|
||||
|
||||
NetworkAddress address(hostname, port);
|
||||
SOCKET s = address.Connect();
|
||||
if (s == INVALID_SOCKET) {
|
||||
DEBUG(net, 0, "Failed to open socket for redirection DEBUG()");
|
||||
return;
|
||||
void OnFailure() override
|
||||
{
|
||||
Debug(net, 0, "Failed to open connection to {} for redirecting Debug()", this->connection_string);
|
||||
}
|
||||
|
||||
_debug_socket = s;
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
Debug(net, 3, "Redirecting Debug() to {}", this->connection_string);
|
||||
|
||||
DEBUG(net, 0, "DEBUG() is now redirected");
|
||||
extern SOCKET _debug_socket;
|
||||
_debug_socket = s;
|
||||
}
|
||||
};
|
||||
|
||||
void NetworkStartDebugLog(const std::string &connection_string)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1094,7 +1265,7 @@ void NetworkShutDown()
|
||||
NetworkDisconnect(true);
|
||||
NetworkUDPClose();
|
||||
|
||||
DEBUG(net, 3, "[core] shutting down network");
|
||||
Debug(net, 3, "Shutting down network");
|
||||
|
||||
_network_available = false;
|
||||
|
||||
@@ -1104,9 +1275,9 @@ void NetworkShutDown()
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern "C" {
|
||||
|
||||
void CDECL em_openttd_add_server(const char *host, int port)
|
||||
void CDECL em_openttd_add_server(const char *connection_string)
|
||||
{
|
||||
NetworkUDPQueryServer(NetworkAddress(host, port), true);
|
||||
NetworkAddServer(connection_string, false, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,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 +97,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 +133,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);
|
||||
}
|
||||
@@ -171,7 +169,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendWelcome()
|
||||
p->Send_string(GetNetworkRevisionString());
|
||||
p->Send_bool (_network_dedicated);
|
||||
|
||||
p->Send_string(_network_game_info.map_name);
|
||||
p->Send_string(""); // Used to be map-name.
|
||||
p->Send_uint32(_settings_game.game_creation.generation_seed);
|
||||
p->Send_uint8 (_settings_game.game_creation.landscape);
|
||||
p->Send_uint32(ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1));
|
||||
@@ -239,7 +237,7 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendClientInfo(const NetworkC
|
||||
p->Send_uint32(ci->client_id);
|
||||
p->Send_string(cs == nullptr ? "" : const_cast<NetworkAddress &>(cs->client_address).GetHostname());
|
||||
p->Send_string(ci->client_name);
|
||||
p->Send_uint8 (ci->client_lang);
|
||||
p->Send_uint8 (0); // Used to be language
|
||||
p->Send_uint32(ci->join_date);
|
||||
p->Send_uint8 (ci->client_playas);
|
||||
|
||||
@@ -316,20 +314,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 +343,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 +450,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 +468,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 +483,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 +498,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 +512,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 +526,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 +536,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 SEND_MTU. */
|
||||
if (strlen(origin) + strlen(string) + 2 + 3 >= SEND_MTU) return NETWORK_RECV_STATUS_OKAY;
|
||||
* smaller than COMPAT_MTU. */
|
||||
if (origin.size() + string.size() + 2 + 3 >= COMPAT_MTU) return NETWORK_RECV_STATUS_OKAY;
|
||||
|
||||
Packet *p = new Packet(ADMIN_PACKET_SERVER_CONSOLE);
|
||||
|
||||
@@ -577,12 +557,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);
|
||||
|
||||
@@ -611,10 +591,10 @@ NetworkRecvStatus ServerNetworkAdminSocketHandler::SendCmdNames()
|
||||
for (uint i = 0; i < CMD_END; i++) {
|
||||
const char *cmdname = GetCommandName(i);
|
||||
|
||||
/* Should SEND_MTU be exceeded, start a new packet
|
||||
/* 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->size + strlen(cmdname) + 5 >= SEND_MTU) {
|
||||
if (p->CanWriteToPacket(strlen(cmdname) + 5)) {
|
||||
p->Send_bool(false);
|
||||
this->SendPacket(p);
|
||||
|
||||
@@ -664,26 +644,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,7 +682,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -771,7 +750,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 +765,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,7 +776,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -874,7 +852,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 +897,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 +914,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 +924,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 +937,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) {
|
||||
|
||||
@@ -61,13 +61,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 +106,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,11 +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
|
||||
byte client_lang; ///< The language 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.
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
/** @file network_chat_gui.cpp GUI for handling chat messages. */
|
||||
|
||||
#include <stdarg.h> /* va_list */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "../strings_func.h"
|
||||
#include "../blitter/factory.hpp"
|
||||
@@ -27,6 +25,9 @@
|
||||
|
||||
#include "table/strings.h"
|
||||
|
||||
#include <stdarg.h> /* va_list */
|
||||
#include <deque>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
/** The draw buffer must be able to contain the chat message, client name and the "[All]" message,
|
||||
@@ -38,18 +39,24 @@ 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.
|
||||
};
|
||||
|
||||
/* used for chat window */
|
||||
static ChatMessage *_chatmsg_list = nullptr; ///< The actual chat message list.
|
||||
static std::deque<ChatMessage> _chatmsg_list; ///< The actual chat message list.
|
||||
static bool _chatmessage_dirty = false; ///< Does the chat message need repainting?
|
||||
static bool _chatmessage_visible = false; ///< Is a chat message visible.
|
||||
static bool _chat_tab_completion_active; ///< Whether tab completion is active.
|
||||
static uint MAX_CHAT_MESSAGES = 0; ///< The limit of chat messages to show.
|
||||
|
||||
/**
|
||||
* Time the chat history was marked dirty. This is used to determine if expired
|
||||
* messages have recently expired and should cause a redraw to hide them.
|
||||
*/
|
||||
static std::chrono::steady_clock::time_point _chatmessage_dirty_time;
|
||||
|
||||
/**
|
||||
* The chatbox grows from the bottom so the coordinates are pixels from
|
||||
* the left and pixels from the bottom. The height is the maximum height.
|
||||
@@ -58,17 +65,20 @@ static PointDimension _chatmsg_box;
|
||||
static uint8 *_chatmessage_backup = nullptr; ///< Backup in case text is moved.
|
||||
|
||||
/**
|
||||
* Count the chat messages.
|
||||
* @return The number of chat messages.
|
||||
* Test if there are any chat messages to display.
|
||||
* @param show_all Set if all messages should be included, instead of unexpired only.
|
||||
* @return True iff there are chat messages to display.
|
||||
*/
|
||||
static inline uint GetChatMessageCount()
|
||||
static inline bool HaveChatMessages(bool show_all)
|
||||
{
|
||||
uint i = 0;
|
||||
for (; i < MAX_CHAT_MESSAGES; i++) {
|
||||
if (_chatmsg_list[i].message[0] == '\0') break;
|
||||
if (show_all) return _chatmsg_list.size() != 0;
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for (auto &cmsg : _chatmsg_list) {
|
||||
if (cmsg.remove_time >= now) return true;
|
||||
}
|
||||
|
||||
return i;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,28 +87,18 @@ static inline uint GetChatMessageCount()
|
||||
* @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);
|
||||
|
||||
uint msg_count = GetChatMessageCount();
|
||||
if (MAX_CHAT_MESSAGES == msg_count) {
|
||||
memmove(&_chatmsg_list[0], &_chatmsg_list[1], sizeof(_chatmsg_list[0]) * (msg_count - 1));
|
||||
msg_count = MAX_CHAT_MESSAGES - 1;
|
||||
if (_chatmsg_list.size() == MAX_CHAT_MESSAGES) {
|
||||
_chatmsg_list.pop_back();
|
||||
}
|
||||
|
||||
ChatMessage *cmsg = &_chatmsg_list[msg_count++];
|
||||
strecpy(cmsg->message, buf, lastof(cmsg->message));
|
||||
ChatMessage *cmsg = &_chatmsg_list.emplace_front();
|
||||
cmsg->message = message;
|
||||
cmsg->colour = (colour & TC_IS_PALETTE_COLOUR) ? colour : TC_WHITE;
|
||||
cmsg->remove_time = std::chrono::steady_clock::now() + std::chrono::seconds(duration);
|
||||
|
||||
_chatmessage_dirty_time = std::chrono::steady_clock::now();
|
||||
_chatmessage_dirty = true;
|
||||
}
|
||||
|
||||
@@ -115,15 +115,11 @@ void NetworkInitChatMessage()
|
||||
{
|
||||
MAX_CHAT_MESSAGES = _settings_client.gui.network_chat_box_height;
|
||||
|
||||
_chatmsg_list = ReallocT(_chatmsg_list, _settings_client.gui.network_chat_box_height);
|
||||
_chatmsg_list.clear();
|
||||
_chatmsg_box.x = 10;
|
||||
_chatmsg_box.width = _settings_client.gui.network_chat_box_width_pct * _screen.width / 100;
|
||||
NetworkReInitChatBoxSize();
|
||||
_chatmessage_visible = false;
|
||||
|
||||
for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
|
||||
_chatmsg_list[i].message[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the chatbox */
|
||||
@@ -132,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
|
||||
@@ -168,6 +164,7 @@ void NetworkUndrawChatMessage()
|
||||
/* And make sure it is updated next time */
|
||||
VideoDriver::GetInstance()->MakeDirty(x, y, width, height);
|
||||
|
||||
_chatmessage_dirty_time = std::chrono::steady_clock::now();
|
||||
_chatmessage_dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -175,21 +172,13 @@ void NetworkUndrawChatMessage()
|
||||
/** Check if a message is expired. */
|
||||
void NetworkChatMessageLoop()
|
||||
{
|
||||
for (uint i = 0; i < MAX_CHAT_MESSAGES; i++) {
|
||||
ChatMessage *cmsg = &_chatmsg_list[i];
|
||||
if (cmsg->message[0] == '\0') continue;
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for (auto &cmsg : _chatmsg_list) {
|
||||
/* Message has expired, remove from the list */
|
||||
if (std::chrono::steady_clock::now() > cmsg->remove_time) {
|
||||
/* Move the remaining messages over the current message */
|
||||
if (i != MAX_CHAT_MESSAGES - 1) memmove(cmsg, cmsg + 1, sizeof(*cmsg) * (MAX_CHAT_MESSAGES - i - 1));
|
||||
|
||||
/* Mark the last item as empty */
|
||||
_chatmsg_list[MAX_CHAT_MESSAGES - 1].message[0] = '\0';
|
||||
if (now > cmsg.remove_time && _chatmessage_dirty_time < cmsg.remove_time) {
|
||||
_chatmessage_dirty_time = now;
|
||||
_chatmessage_dirty = true;
|
||||
|
||||
/* Go one item back, because we moved the array 1 to the left */
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,14 +189,16 @@ void NetworkDrawChatMessage()
|
||||
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
|
||||
if (!_chatmessage_dirty) return;
|
||||
|
||||
const Window *w = FindWindowByClass(WC_SEND_NETWORK_MSG);
|
||||
bool show_all = (w != nullptr);
|
||||
|
||||
/* First undraw if needed */
|
||||
NetworkUndrawChatMessage();
|
||||
|
||||
if (_iconsole_mode == ICONSOLE_FULL) return;
|
||||
|
||||
/* Check if we have anything to draw at all */
|
||||
uint count = GetChatMessageCount();
|
||||
if (count == 0) return;
|
||||
if (!HaveChatMessages(show_all)) return;
|
||||
|
||||
int x = _chatmsg_box.x;
|
||||
int y = _screen.height - _chatmsg_box.y - _chatmsg_box.height;
|
||||
@@ -229,9 +220,11 @@ void NetworkDrawChatMessage()
|
||||
|
||||
_cur_dpi = &_screen; // switch to _screen painting
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
int string_height = 0;
|
||||
for (uint i = 0; i < count; i++) {
|
||||
SetDParamStr(0, _chatmsg_list[i].message);
|
||||
for (auto &cmsg : _chatmsg_list) {
|
||||
if (!show_all && cmsg.remove_time < now) continue;
|
||||
SetDParamStr(0, cmsg.message);
|
||||
string_height += GetStringLineCount(STR_JUST_RAW_STRING, width - 1) * FONT_HEIGHT_NORMAL + NETWORK_CHAT_LINE_SPACING;
|
||||
}
|
||||
|
||||
@@ -247,8 +240,9 @@ void NetworkDrawChatMessage()
|
||||
/* Paint the chat messages starting with the lowest at the bottom */
|
||||
int ypos = bottom - 2;
|
||||
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
ypos = DrawStringMultiLine(_chatmsg_box.x + 3, _chatmsg_box.x + _chatmsg_box.width - 1, top, ypos, _chatmsg_list[i].message, _chatmsg_list[i].colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - NETWORK_CHAT_LINE_SPACING;
|
||||
for (auto &cmsg : _chatmsg_list) {
|
||||
if (!show_all && cmsg.remove_time < now) continue;
|
||||
ypos = DrawStringMultiLine(_chatmsg_box.x + 3, _chatmsg_box.x + _chatmsg_box.width - 1, top, ypos, cmsg.message, cmsg.colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - NETWORK_CHAT_LINE_SPACING;
|
||||
if (ypos < top) break;
|
||||
}
|
||||
|
||||
@@ -265,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 {
|
||||
@@ -278,7 +272,6 @@ static void SendChat(const char *buf, DestType type, int dest)
|
||||
/** Window to enter the chat message in. */
|
||||
struct NetworkChatWindow : public Window {
|
||||
DestType dtype; ///< The type of destination.
|
||||
StringID dest_string; ///< String representation of the destination.
|
||||
int dest; ///< The identifier of the destination.
|
||||
QueryString message_editbox; ///< Message editbox.
|
||||
|
||||
@@ -302,9 +295,10 @@ struct NetworkChatWindow : public Window {
|
||||
STR_NETWORK_CHAT_CLIENT_CAPTION
|
||||
};
|
||||
assert((uint)this->dtype < lengthof(chat_captions));
|
||||
this->dest_string = chat_captions[this->dtype];
|
||||
|
||||
this->InitNested(type);
|
||||
this->CreateNestedTree();
|
||||
this->GetWidget<NWidgetCore>(WID_NC_DESTINATION)->widget_data = chat_captions[this->dtype];
|
||||
this->FinishInitNested(type);
|
||||
|
||||
this->SetFocusedWidget(WID_NC_TEXTBOX);
|
||||
InvalidateWindowData(WC_NEWS_WINDOW, 0, this->height);
|
||||
@@ -313,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
|
||||
@@ -338,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;
|
||||
}
|
||||
@@ -459,27 +454,13 @@ struct NetworkChatWindow : public Window {
|
||||
return pt;
|
||||
}
|
||||
|
||||
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
|
||||
void SetStringParameters(int widget) const override
|
||||
{
|
||||
if (widget != WID_NC_DESTINATION) return;
|
||||
|
||||
if (this->dtype == DESTTYPE_CLIENT) {
|
||||
SetDParamStr(0, NetworkClientInfo::GetByClientID((ClientID)this->dest)->client_name);
|
||||
}
|
||||
Dimension d = GetStringBoundingBox(this->dest_string);
|
||||
d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT;
|
||||
d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
|
||||
*size = maxdim(*size, d);
|
||||
}
|
||||
|
||||
void DrawWidget(const Rect &r, int widget) const override
|
||||
{
|
||||
if (widget != WID_NC_DESTINATION) return;
|
||||
|
||||
if (this->dtype == DESTTYPE_CLIENT) {
|
||||
SetDParamStr(0, NetworkClientInfo::GetByClientID((ClientID)this->dest)->client_name);
|
||||
}
|
||||
DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, this->dest_string, TC_BLACK, SA_RIGHT);
|
||||
}
|
||||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
@@ -490,7 +471,7 @@ struct NetworkChatWindow : public Window {
|
||||
FALLTHROUGH;
|
||||
|
||||
case WID_NC_CLOSE: /* Cancel */
|
||||
delete this;
|
||||
this->Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -517,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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -527,7 +508,7 @@ static const NWidgetPart _nested_chat_window_widgets[] = {
|
||||
NWidget(WWT_CLOSEBOX, COLOUR_GREY, WID_NC_CLOSE),
|
||||
NWidget(WWT_PANEL, COLOUR_GREY, WID_NC_BACKGROUND),
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_TEXT, COLOUR_GREY, WID_NC_DESTINATION), SetMinimalSize(62, 12), SetPadding(1, 0, 1, 0), SetDataTip(STR_NULL, STR_NULL),
|
||||
NWidget(WWT_TEXT, COLOUR_GREY, WID_NC_DESTINATION), SetMinimalSize(62, 12), SetPadding(1, 0, 1, 0), SetTextColour(TC_BLACK), SetAlignment(SA_TOP | SA_RIGHT), SetDataTip(STR_NULL, STR_NULL),
|
||||
NWidget(WWT_EDITBOX, COLOUR_GREY, WID_NC_TEXTBOX), SetMinimalSize(100, 12), SetPadding(1, 0, 1, 0), SetResize(1, 0),
|
||||
SetDataTip(STR_NETWORK_CHAT_OSKTITLE, STR_NULL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NC_SENDBUTTON), SetMinimalSize(62, 12), SetPadding(1, 0, 1, 0), SetDataTip(STR_NETWORK_CHAT_SEND, STR_NULL),
|
||||
@@ -552,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);
|
||||
}
|
||||
|
||||
+217
-196
@@ -27,6 +27,7 @@
|
||||
#include "network.h"
|
||||
#include "network_base.h"
|
||||
#include "network_client.h"
|
||||
#include "network_gamelist.h"
|
||||
#include "../core/backup_type.hpp"
|
||||
#include "../thread.h"
|
||||
|
||||
@@ -36,7 +37,6 @@
|
||||
|
||||
/* This file handles all the client-commands */
|
||||
|
||||
|
||||
/** Read some packets, and when do use that data as initial load filter. */
|
||||
struct PacketReader : LoadFilter {
|
||||
static const size_t CHUNK = 32 * 1024; ///< 32 KiB chunks of memory.
|
||||
@@ -60,35 +60,38 @@ struct PacketReader : LoadFilter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper around fwrite to be able to pass it to Packet's TransferOut.
|
||||
* @param destination The reader to add the data to.
|
||||
* @param source The buffer to read data from.
|
||||
* @param amount The number of bytes to copy.
|
||||
* @return The number of bytes that were copied.
|
||||
*/
|
||||
static inline ssize_t TransferOutMemCopy(PacketReader *destination, const char *source, size_t amount)
|
||||
{
|
||||
memcpy(destination->buf, source, amount);
|
||||
destination->buf += amount;
|
||||
destination->written_bytes += amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a packet to this buffer.
|
||||
* @param p The packet to add.
|
||||
*/
|
||||
void AddPacket(const Packet *p)
|
||||
void AddPacket(Packet *p)
|
||||
{
|
||||
assert(this->read_bytes == 0);
|
||||
|
||||
size_t in_packet = p->size - p->pos;
|
||||
size_t to_write = std::min<size_t>(this->bufe - this->buf, in_packet);
|
||||
const byte *pbuf = p->buffer + p->pos;
|
||||
|
||||
this->written_bytes += in_packet;
|
||||
if (to_write != 0) {
|
||||
memcpy(this->buf, pbuf, to_write);
|
||||
this->buf += to_write;
|
||||
}
|
||||
p->TransferOutWithLimit(TransferOutMemCopy, this->bufe - this->buf, this);
|
||||
|
||||
/* Did everything fit in the current chunk, then we're done. */
|
||||
if (to_write == in_packet) return;
|
||||
if (p->RemainingBytesToTransfer() == 0) return;
|
||||
|
||||
/* Allocate a new chunk and add the remaining data. */
|
||||
pbuf += to_write;
|
||||
to_write = in_packet - to_write;
|
||||
this->blocks.push_back(this->buf = CallocT<byte>(CHUNK));
|
||||
this->bufe = this->buf + CHUNK;
|
||||
|
||||
memcpy(this->buf, pbuf, to_write);
|
||||
this->buf += to_write;
|
||||
p->TransferOutWithLimit(TransferOutMemCopy, this->bufe - this->buf, this);
|
||||
}
|
||||
|
||||
size_t Read(byte *rbuf, size_t size) override
|
||||
@@ -129,12 +132,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +141,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) : NetworkGameSocketHandler(s), 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;
|
||||
@@ -155,31 +154,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;
|
||||
@@ -191,17 +185,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;
|
||||
}
|
||||
|
||||
@@ -257,7 +251,7 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
|
||||
/* static */ void ClientNetworkGameSocketHandler::Send()
|
||||
{
|
||||
my_client->SendPackets();
|
||||
my_client->CheckConnection();
|
||||
if (my_client != nullptr) my_client->CheckConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,15 +276,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();
|
||||
@@ -298,7 +292,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;
|
||||
}
|
||||
}
|
||||
@@ -316,20 +310,15 @@ 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;
|
||||
|
||||
/** Who would we like to join as. */
|
||||
CompanyID _network_join_as;
|
||||
|
||||
/** Login password from -p argument */
|
||||
const char *_network_join_server_password = nullptr;
|
||||
/** Company password from -P argument */
|
||||
const char *_network_join_company_password = nullptr;
|
||||
/** Information about the game to join to. */
|
||||
NetworkJoinInfo _network_join;
|
||||
|
||||
/** Make sure the server ID length is the same as a md5 hash. */
|
||||
static_assert(NETWORK_SERVER_ID_LENGTH == 16 * 2 + 1);
|
||||
@@ -339,15 +328,14 @@ static_assert(NETWORK_SERVER_ID_LENGTH == 16 * 2 + 1);
|
||||
* DEF_CLIENT_SEND_COMMAND has no parameters
|
||||
************/
|
||||
|
||||
/** Query the server for company information. */
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyInformationQuery()
|
||||
/**
|
||||
* Query the server for server information.
|
||||
*/
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::SendInformationQuery()
|
||||
{
|
||||
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->status = STATUS_GAME_INFO;
|
||||
my_client->SendPacket(new Packet(PACKET_CLIENT_GAME_INFO));
|
||||
|
||||
Packet *p = new Packet(PACKET_CLIENT_COMPANY_INFO);
|
||||
my_client->SendPacket(p);
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
@@ -362,8 +350,8 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendJoin()
|
||||
p->Send_string(GetNetworkRevisionString());
|
||||
p->Send_uint32(_openttd_newgrf_version);
|
||||
p->Send_string(_settings_client.network.client_name); // Client name
|
||||
p->Send_uint8 (_network_join_as); // PlayAs
|
||||
p->Send_uint8 (NETLANG_ANY); // Language
|
||||
p->Send_uint8 (_network_join.company); // PlayAs
|
||||
p->Send_uint8 (0); // Used to be language
|
||||
my_client->SendPacket(p);
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -380,7 +368,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);
|
||||
@@ -392,7 +380,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));
|
||||
@@ -445,7 +433,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);
|
||||
|
||||
@@ -473,7 +461,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);
|
||||
|
||||
@@ -486,7 +474,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);
|
||||
|
||||
@@ -511,7 +499,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);
|
||||
@@ -525,7 +513,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);
|
||||
@@ -555,7 +543,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;
|
||||
}
|
||||
@@ -564,48 +552,27 @@ 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_COMPANY_INFO(Packet *p)
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_GAME_INFO(Packet *p)
|
||||
{
|
||||
if (this->status != STATUS_COMPANY_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
if (this->status != STATUS_GAME_INFO) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
byte company_info_version = p->Recv_uint8();
|
||||
NetworkGameList *item = NetworkGameListAddItem(this->connection_string);
|
||||
|
||||
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;
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
return NETWORK_RECV_STATUS_CLOSE_QUERY;
|
||||
}
|
||||
@@ -618,16 +585,19 @@ 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. */
|
||||
if (!NetworkIsValidClientName(name)) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
|
||||
|
||||
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) {
|
||||
@@ -640,9 +610,9 @@ 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;
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -659,9 +629,9 @@ 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;
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -669,37 +639,46 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac
|
||||
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR(Packet *p)
|
||||
{
|
||||
static const StringID network_error_strings[] = {
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_GENERAL
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_DESYNC
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_SAVEGAME_FAILED
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_CONNECTION_LOST
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_ILLEGAL_PACKET
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_NEWGRF_MISMATCH
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NOT_AUTHORIZED
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NOT_EXPECTED
|
||||
STR_NETWORK_ERROR_WRONG_REVISION, // NETWORK_ERROR_WRONG_REVISION
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_NAME_IN_USE
|
||||
STR_NETWORK_ERROR_WRONG_PASSWORD, // NETWORK_ERROR_WRONG_PASSWORD
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_COMPANY_MISMATCH
|
||||
STR_NETWORK_ERROR_KICKED, // NETWORK_ERROR_KICKED
|
||||
STR_NETWORK_ERROR_CHEATER, // NETWORK_ERROR_CHEATER
|
||||
STR_NETWORK_ERROR_SERVER_FULL, // NETWORK_ERROR_FULL
|
||||
STR_NETWORK_ERROR_TOO_MANY_COMMANDS, // NETWORK_ERROR_TOO_MANY_COMMANDS
|
||||
STR_NETWORK_ERROR_TIMEOUT_PASSWORD, // NETWORK_ERROR_TIMEOUT_PASSWORD
|
||||
STR_NETWORK_ERROR_TIMEOUT_COMPUTER, // NETWORK_ERROR_TIMEOUT_COMPUTER
|
||||
STR_NETWORK_ERROR_TIMEOUT_MAP, // NETWORK_ERROR_TIMEOUT_MAP
|
||||
STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_GENERAL
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_DESYNC
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_SAVEGAME_FAILED
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_CONNECTION_LOST
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_ILLEGAL_PACKET
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_NEWGRF_MISMATCH
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NOT_AUTHORIZED
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_NOT_EXPECTED
|
||||
STR_NETWORK_ERROR_WRONG_REVISION, // NETWORK_ERROR_WRONG_REVISION
|
||||
STR_NETWORK_ERROR_LOSTCONNECTION, // NETWORK_ERROR_NAME_IN_USE
|
||||
STR_NETWORK_ERROR_WRONG_PASSWORD, // NETWORK_ERROR_WRONG_PASSWORD
|
||||
STR_NETWORK_ERROR_SERVER_ERROR, // NETWORK_ERROR_COMPANY_MISMATCH
|
||||
STR_NETWORK_ERROR_KICKED, // NETWORK_ERROR_KICKED
|
||||
STR_NETWORK_ERROR_CHEATER, // NETWORK_ERROR_CHEATER
|
||||
STR_NETWORK_ERROR_SERVER_FULL, // NETWORK_ERROR_FULL
|
||||
STR_NETWORK_ERROR_TOO_MANY_COMMANDS, // NETWORK_ERROR_TOO_MANY_COMMANDS
|
||||
STR_NETWORK_ERROR_TIMEOUT_PASSWORD, // NETWORK_ERROR_TIMEOUT_PASSWORD
|
||||
STR_NETWORK_ERROR_TIMEOUT_COMPUTER, // NETWORK_ERROR_TIMEOUT_COMPUTER
|
||||
STR_NETWORK_ERROR_TIMEOUT_MAP, // NETWORK_ERROR_TIMEOUT_MAP
|
||||
STR_NETWORK_ERROR_TIMEOUT_JOIN, // NETWORK_ERROR_TIMEOUT_JOIN
|
||||
STR_NETWORK_ERROR_INVALID_CLIENT_NAME, // NETWORK_ERROR_INVALID_CLIENT_NAME
|
||||
};
|
||||
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
|
||||
|
||||
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) {
|
||||
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 {
|
||||
@@ -709,7 +688,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;
|
||||
}
|
||||
@@ -732,7 +711,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;
|
||||
}
|
||||
}
|
||||
@@ -752,9 +731,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);
|
||||
@@ -768,12 +746,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);
|
||||
@@ -790,7 +767,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();
|
||||
@@ -881,7 +858,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;
|
||||
}
|
||||
@@ -891,21 +868,23 @@ 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_as == COMPANY_NEW_COMPANY || !Company::IsValidID(_network_join_as)) {
|
||||
if (_network_join.company == COMPANY_NEW_COMPANY || !Company::IsValidID(_network_join.company)) {
|
||||
SetLocalCompany(COMPANY_SPECTATOR);
|
||||
|
||||
if (_network_join_as != COMPANY_SPECTATOR) {
|
||||
if (_network_join.company != COMPANY_SPECTATOR) {
|
||||
/* We have arrived and ready to start playing; send a command to make a new company;
|
||||
* 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 */
|
||||
SetLocalCompany(_network_join_as);
|
||||
SetLocalCompany(_network_join.company);
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
@@ -929,15 +908,15 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p
|
||||
}
|
||||
#endif
|
||||
/* Receive the token. */
|
||||
if (p->pos != p->size) this->token = p->Recv_uint8();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -967,7 +946,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;
|
||||
}
|
||||
|
||||
@@ -980,13 +959,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);
|
||||
@@ -997,7 +976,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;
|
||||
|
||||
@@ -1006,7 +985,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;
|
||||
}
|
||||
@@ -1015,7 +994,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;
|
||||
}
|
||||
|
||||
@@ -1033,11 +1012,11 @@ 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;
|
||||
}
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -1050,13 +1029,13 @@ 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);
|
||||
}
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
/* If we come here it means we could not locate the client.. strange :s */
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
@@ -1073,7 +1052,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_JOIN(Packet *p)
|
||||
NetworkTextMessage(NETWORK_ACTION_JOIN, CC_DEFAULT, false, ci->client_name);
|
||||
}
|
||||
|
||||
SetWindowDirty(WC_CLIENT_LIST, 0);
|
||||
InvalidateWindowData(WC_CLIENT_LIST, 0);
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
@@ -1115,8 +1094,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);
|
||||
|
||||
@@ -1133,7 +1111,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;
|
||||
}
|
||||
|
||||
@@ -1156,7 +1134,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;
|
||||
}
|
||||
@@ -1216,7 +1194,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);
|
||||
}
|
||||
@@ -1227,7 +1205,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);
|
||||
}
|
||||
@@ -1252,22 +1230,74 @@ void NetworkClientsToSpectators(CompanyID cid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the server our name.
|
||||
* Check whether the given client name is deemed valid for use in network games.
|
||||
* An empty name (null or '') is not valid as that is essentially no name at all.
|
||||
* A name starting with white space is not valid for tab completion purposes.
|
||||
* @param client_name The client name to check for validity.
|
||||
* @return True iff the name is valid.
|
||||
*/
|
||||
void NetworkUpdateClientName()
|
||||
bool NetworkIsValidClientName(const std::string_view client_name)
|
||||
{
|
||||
if (client_name.empty()) return false;
|
||||
if (client_name[0] == ' ') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the given client name in place, i.e. remove leading and trailing spaces.
|
||||
* After the trim check whether the client name is valid. A client name is valid
|
||||
* whenever the name is not empty and does not start with spaces. This check is
|
||||
* done via \c NetworkIsValidClientName.
|
||||
* When the client name is valid, this function returns true.
|
||||
* When the client name is not valid a GUI error message is shown telling the
|
||||
* user to set the client name and this function returns false.
|
||||
*
|
||||
* This function is not suitable for ensuring a valid client name at the server
|
||||
* as the error message will then be shown to the host instead of the client.
|
||||
* @param client_name The client name to validate. It will be trimmed of leading
|
||||
* and trailing spaces.
|
||||
* @return True iff the client name is valid.
|
||||
*/
|
||||
bool NetworkValidateClientName(std::string &client_name)
|
||||
{
|
||||
StrTrimInPlace(client_name);
|
||||
if (NetworkIsValidClientName(client_name)) return true;
|
||||
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_BAD_PLAYER_NAME, INVALID_STRING_ID, WL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for NetworkValidateClientName on _settings_client.network.client_name.
|
||||
* It trims the client name and checks whether it is empty. When it is empty
|
||||
* an error message is shown to the GUI user.
|
||||
* See \c NetworkValidateClientName(char*) for details about the functionality.
|
||||
* @return True iff the client name is valid.
|
||||
*/
|
||||
bool NetworkValidateOurClientName()
|
||||
{
|
||||
return NetworkValidateClientName(_settings_client.network.client_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the server our name as callback from the setting.
|
||||
* @param newname The new client name.
|
||||
*/
|
||||
void NetworkUpdateClientName(const std::string &client_name)
|
||||
{
|
||||
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
|
||||
|
||||
if (ci == nullptr) 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);
|
||||
}
|
||||
}
|
||||
@@ -1282,7 +1312,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);
|
||||
}
|
||||
@@ -1291,13 +1321,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.
|
||||
*/
|
||||
@@ -1321,12 +1351,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,13 +15,14 @@
|
||||
/** Class for handling the client side of the game connection. */
|
||||
class ClientNetworkGameSocketHandler : public ZeroedMemoryAllocator, public NetworkGameSocketHandler {
|
||||
private:
|
||||
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_COMPANY_INFO, ///< We are trying to get company information.
|
||||
STATUS_GAME_INFO, ///< We are trying to get the game 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.
|
||||
@@ -43,7 +44,7 @@ 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_COMPANY_INFO(Packet *p) override;
|
||||
NetworkRecvStatus Receive_SERVER_GAME_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;
|
||||
@@ -73,13 +74,13 @@ protected:
|
||||
static NetworkRecvStatus SendMapOk();
|
||||
void CheckConnection();
|
||||
public:
|
||||
ClientNetworkGameSocketHandler(SOCKET s);
|
||||
ClientNetworkGameSocketHandler(SOCKET s, const std::string &connection_string);
|
||||
~ClientNetworkGameSocketHandler();
|
||||
|
||||
NetworkRecvStatus CloseConnection(NetworkRecvStatus status) override;
|
||||
void ClientError(NetworkRecvStatus res);
|
||||
|
||||
static NetworkRecvStatus SendCompanyInformationQuery();
|
||||
static NetworkRecvStatus SendInformationQuery();
|
||||
|
||||
static NetworkRecvStatus SendJoin();
|
||||
static NetworkRecvStatus SendCommand(const CommandPacket *cp);
|
||||
@@ -87,14 +88,14 @@ public:
|
||||
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();
|
||||
|
||||
@@ -107,11 +108,17 @@ public:
|
||||
typedef ClientNetworkGameSocketHandler MyClient;
|
||||
|
||||
void NetworkClient_Connected();
|
||||
void NetworkClientSetCompanyPassword(const char *password);
|
||||
void NetworkClientSetCompanyPassword(const std::string &password);
|
||||
|
||||
extern CompanyID _network_join_as;
|
||||
/** Information required to join a server. */
|
||||
struct NetworkJoinInfo {
|
||||
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 const char *_network_join_server_password;
|
||||
extern const char *_network_join_company_password;
|
||||
extern NetworkJoinInfo _network_join;
|
||||
|
||||
#endif /* NETWORK_CLIENT_H */
|
||||
|
||||
@@ -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,27 @@ 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++) ci->dependencies.push_back((ContentID)p->Recv_uint32());
|
||||
|
||||
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 +143,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);
|
||||
@@ -221,9 +220,9 @@ void ClientNetworkContentSocketHandler::RequestContentList(uint count, const Con
|
||||
* A packet begins with the packet size and a byte for the type.
|
||||
* Then this packet adds a uint16 for the count in this packet.
|
||||
* The rest of the packet can be used for the IDs. */
|
||||
uint p_count = std::min<uint>(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
|
||||
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
|
||||
|
||||
Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID);
|
||||
Packet *p = new Packet(PACKET_CONTENT_CLIENT_INFO_ID, TCP_MTU);
|
||||
p->Send_uint16(p_count);
|
||||
|
||||
for (uint i = 0; i < p_count; i++) {
|
||||
@@ -248,10 +247,10 @@ void ClientNetworkContentSocketHandler::RequestContentList(ContentVector *cv, bo
|
||||
this->Connect();
|
||||
|
||||
assert(cv->size() < 255);
|
||||
assert(cv->size() < (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) /
|
||||
assert(cv->size() < (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint8)) /
|
||||
(sizeof(uint8) + sizeof(uint32) + (send_md5sum ? /*sizeof(ContentInfo::md5sum)*/16 : 0)));
|
||||
|
||||
Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID);
|
||||
Packet *p = new Packet(send_md5sum ? PACKET_CONTENT_CLIENT_INFO_EXTID_MD5 : PACKET_CONTENT_CLIENT_INFO_EXTID, TCP_MTU);
|
||||
p->Send_uint8((uint8)cv->size());
|
||||
|
||||
for (const ContentInfo *ci : *cv) {
|
||||
@@ -343,8 +342,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! */
|
||||
}
|
||||
|
||||
@@ -363,9 +361,9 @@ void ClientNetworkContentSocketHandler::DownloadSelectedContentFallback(const Co
|
||||
* A packet begins with the packet size and a byte for the type.
|
||||
* Then this packet adds a uint16 for the count in this packet.
|
||||
* The rest of the packet can be used for the IDs. */
|
||||
uint p_count = std::min<uint>(count, (SEND_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
|
||||
uint p_count = std::min<uint>(count, (TCP_MTU - sizeof(PacketSize) - sizeof(byte) - sizeof(uint16)) / sizeof(uint32));
|
||||
|
||||
Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT);
|
||||
Packet *p = new Packet(PACKET_CONTENT_CLIENT_CONTENT, TCP_MTU);
|
||||
p->Send_uint16(p_count);
|
||||
|
||||
for (uint i = 0; i < p_count; i++) {
|
||||
@@ -411,7 +409,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 +449,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;
|
||||
@@ -459,6 +463,18 @@ static bool GunzipFile(const ContentInfo *ci)
|
||||
#endif /* defined(WITH_ZLIB) */
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper around fwrite to be able to pass it to Packet's TransferOut.
|
||||
* @param file The file to write data to.
|
||||
* @param buffer The buffer to write to the file.
|
||||
* @param amount The number of bytes to write.
|
||||
* @return The number of bytes that were written.
|
||||
*/
|
||||
static inline ssize_t TransferOutFWrite(FILE *file, const char *buffer, size_t amount)
|
||||
{
|
||||
return fwrite(buffer, 1, amount, file);
|
||||
}
|
||||
|
||||
bool ClientNetworkContentSocketHandler::Receive_SERVER_CONTENT(Packet *p)
|
||||
{
|
||||
if (this->curFile == nullptr) {
|
||||
@@ -468,19 +484,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 = (size_t)(p->size - p->pos);
|
||||
if (fwrite(p->buffer + p->pos, 1, toRead, this->curFile) != toRead) {
|
||||
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
|
||||
size_t toRead = p->RemainingBytesToTransfer();
|
||||
if (toRead != 0 && (size_t)p->TransferOut(TransferOutFWrite, this->curFile) != toRead) {
|
||||
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;
|
||||
|
||||
@@ -512,7 +528,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;
|
||||
}
|
||||
@@ -687,7 +703,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()) {
|
||||
@@ -732,7 +748,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
|
||||
{
|
||||
@@ -758,18 +774,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -781,7 +801,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;
|
||||
}
|
||||
|
||||
@@ -906,8 +926,8 @@ void ClientNetworkContentSocketHandler::ReverseLookupDependency(ConstContentVect
|
||||
for (const ContentInfo *ci : this->infos) {
|
||||
if (ci == child) continue;
|
||||
|
||||
for (uint i = 0; i < ci->dependency_count; i++) {
|
||||
if (ci->dependencies[i] == child->id) {
|
||||
for (auto &dependency : ci->dependencies) {
|
||||
if (dependency == child->id) {
|
||||
parents.push_back(ci);
|
||||
break;
|
||||
}
|
||||
@@ -948,10 +968,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);
|
||||
@@ -974,10 +994,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;
|
||||
@@ -989,9 +1009,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 */
|
||||
@@ -1006,8 +1026,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;
|
||||
|
||||
@@ -107,7 +107,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);
|
||||
|
||||
@@ -74,7 +74,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);
|
||||
}
|
||||
|
||||
@@ -109,9 +109,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
|
||||
@@ -130,7 +131,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);
|
||||
@@ -146,7 +147,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++;
|
||||
}
|
||||
@@ -171,8 +172,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) {
|
||||
@@ -254,18 +254,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,7 +408,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. */
|
||||
@@ -441,10 +443,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();
|
||||
}
|
||||
|
||||
@@ -549,10 +550,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
|
||||
@@ -702,17 +703,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);
|
||||
}
|
||||
@@ -724,20 +725,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;
|
||||
}
|
||||
}
|
||||
@@ -745,12 +744,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);
|
||||
@@ -766,7 +765,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);
|
||||
@@ -837,13 +836,13 @@ public:
|
||||
break;
|
||||
|
||||
case WID_NCL_CANCEL:
|
||||
delete this;
|
||||
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;
|
||||
|
||||
@@ -863,55 +862,31 @@ public:
|
||||
|
||||
EventState OnKeyPress(WChar key, uint16 keycode) override
|
||||
{
|
||||
switch (keycode) {
|
||||
case WKC_UP:
|
||||
/* scroll up by one */
|
||||
if (this->list_pos > 0) this->list_pos--;
|
||||
break;
|
||||
case WKC_DOWN:
|
||||
/* scroll down by one */
|
||||
if (this->list_pos < (int)this->content.size() - 1) this->list_pos++;
|
||||
break;
|
||||
case WKC_PAGEUP:
|
||||
/* scroll up a page */
|
||||
this->list_pos = (this->list_pos < this->vscroll->GetCapacity()) ? 0 : this->list_pos - this->vscroll->GetCapacity();
|
||||
break;
|
||||
case WKC_PAGEDOWN:
|
||||
/* scroll down a page */
|
||||
this->list_pos = std::min(this->list_pos + this->vscroll->GetCapacity(), (int)this->content.size() - 1);
|
||||
break;
|
||||
case WKC_HOME:
|
||||
/* jump to beginning */
|
||||
this->list_pos = 0;
|
||||
break;
|
||||
case WKC_END:
|
||||
/* jump to end */
|
||||
this->list_pos = (int)this->content.size() - 1;
|
||||
break;
|
||||
|
||||
case WKC_SPACE:
|
||||
case WKC_RETURN:
|
||||
if (keycode == WKC_RETURN || !IsWidgetFocused(WID_NCL_FILTER)) {
|
||||
if (this->selected != nullptr) {
|
||||
_network_content_client.ToggleSelectedState(this->selected);
|
||||
this->content.ForceResort();
|
||||
this->InvalidateData();
|
||||
if (this->vscroll->UpdateListPositionOnKeyPress(this->list_pos, keycode) == ES_NOT_HANDLED) {
|
||||
switch (keycode) {
|
||||
case WKC_SPACE:
|
||||
case WKC_RETURN:
|
||||
if (keycode == WKC_RETURN || !IsWidgetFocused(WID_NCL_FILTER)) {
|
||||
if (this->selected != nullptr) {
|
||||
_network_content_client.ToggleSelectedState(this->selected);
|
||||
this->content.ForceResort();
|
||||
this->InvalidateData();
|
||||
}
|
||||
if (this->filter_data.types.any()) {
|
||||
this->content.ForceRebuild();
|
||||
this->InvalidateData();
|
||||
}
|
||||
return ES_HANDLED;
|
||||
}
|
||||
if (this->filter_data.types.any()) {
|
||||
this->content.ForceRebuild();
|
||||
this->InvalidateData();
|
||||
}
|
||||
return ES_HANDLED;
|
||||
}
|
||||
/* space is pressed and filter is focused. */
|
||||
FALLTHROUGH;
|
||||
/* space is pressed and filter is focused. */
|
||||
FALLTHROUGH;
|
||||
|
||||
default:
|
||||
return ES_NOT_HANDLED;
|
||||
default:
|
||||
return ES_NOT_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->content.size() == 0) {
|
||||
this->list_pos = 0; // above stuff may result in "-1".
|
||||
if (this->UpdateFilterState()) {
|
||||
this->content.ForceRebuild();
|
||||
this->InvalidateData();
|
||||
@@ -965,7 +940,7 @@ public:
|
||||
{
|
||||
if (!success) {
|
||||
ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT, INVALID_STRING_ID, WL_ERROR);
|
||||
delete this;
|
||||
this->Close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1008,7 +983,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);
|
||||
}
|
||||
@@ -1152,7 +1127,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,781 @@
|
||||
/*
|
||||
* 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:
|
||||
SetDParamStr(0, detail);
|
||||
ShowErrorMessage(STR_NETWORK_ERROR_COORDINATOR_REGISTRATION_FAILED, STR_JUST_RAW_STRING, 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->online = false;
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
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->online = true;
|
||||
/* 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] = 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) {
|
||||
switch (_settings_client.network.use_relay_service) {
|
||||
case URS_NEVER:
|
||||
this->ConnectFailure(token, 0);
|
||||
break;
|
||||
|
||||
case URS_ASK:
|
||||
ShowNetworkAskRelay(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->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->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->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, TCPServerConnecter *> connecter; ///< Based on tokens, the current connecters 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
-17
@@ -28,35 +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();
|
||||
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 ParseConnectionString(const char **company, const char **port, char *connection_string);
|
||||
void NetworkStartDebugLog(const char *hostname, uint16 port);
|
||||
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);
|
||||
void NetworkClientConnectGame(const char *hostname, uint16 port, CompanyID join_as, 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);
|
||||
|
||||
@@ -65,22 +68,23 @@ 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 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,83 +20,36 @@
|
||||
|
||||
#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));
|
||||
strecpy(item->info.hostname, ins_item->info.hostname, lastof(item->info.hostname));
|
||||
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)
|
||||
{
|
||||
const char *hostname = address.GetHostname();
|
||||
|
||||
/* Do not query the 'any' address. */
|
||||
if (StrEmpty(hostname) ||
|
||||
strcmp(hostname, "0.0.0.0") == 0 ||
|
||||
strcmp(hostname, "::") == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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->version = _network_game_list_version;
|
||||
|
||||
if (prev_item == nullptr) {
|
||||
_network_game_list = item;
|
||||
} else {
|
||||
prev_item->next = item;
|
||||
}
|
||||
DEBUG(net, 4, "[gamelist] added server to list");
|
||||
|
||||
UpdateNetworkGameWindow();
|
||||
|
||||
@@ -120,10 +73,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;
|
||||
@@ -132,29 +83,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(NetworkAddress(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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,10 +125,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. */
|
||||
|
||||
@@ -16,20 +16,22 @@
|
||||
|
||||
/** 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
|
||||
NetworkGameList(const std::string &connection_string) : connection_string(connection_string) {}
|
||||
|
||||
NetworkGameInfo info = {}; ///< The game information of this server
|
||||
std::string connection_string; ///< Address of the server
|
||||
bool online = false; ///< False if the server did not respond (default status)
|
||||
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
|
||||
};
|
||||
|
||||
/** Game list of this client */
|
||||
extern NetworkGameList *_network_game_list;
|
||||
extern int _network_game_list_version;
|
||||
|
||||
void NetworkGameListAddItemDelayed(NetworkGameList *item);
|
||||
NetworkGameList *NetworkGameListAddItem(NetworkAddress address);
|
||||
NetworkGameList *NetworkGameListAddItem(const std::string &connection_string);
|
||||
void NetworkGameListRemoveItem(NetworkGameList *remove);
|
||||
void NetworkGameListRequery();
|
||||
void NetworkGameListRemoveExpired();
|
||||
|
||||
#endif /* NETWORK_GAMELIST_H */
|
||||
|
||||
+982
-743
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
#include "../economy_type.h"
|
||||
#include "../window_type.h"
|
||||
#include "network_type.h"
|
||||
#include "network_gamelist.h"
|
||||
|
||||
void ShowNetworkNeedPassword(NetworkPasswordType npt);
|
||||
void ShowNetworkChatQueryWindow(DestType type, int dest);
|
||||
@@ -26,16 +27,16 @@ void ShowNetworkCompanyPasswordWindow(Window *parent);
|
||||
|
||||
/** 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);
|
||||
void ShowNetworkAskRelay(const std::string &connection_string, const std::string &token);
|
||||
|
||||
#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"
|
||||
@@ -62,47 +63,6 @@ enum NetworkJoinStatus {
|
||||
NETWORK_JOIN_STATUS_END,
|
||||
};
|
||||
|
||||
/** Language ids for server_lang and client_lang. Do NOT modify the order. */
|
||||
enum NetworkLanguage {
|
||||
NETLANG_ANY = 0,
|
||||
NETLANG_ENGLISH,
|
||||
NETLANG_GERMAN,
|
||||
NETLANG_FRENCH,
|
||||
NETLANG_BRAZILIAN,
|
||||
NETLANG_BULGARIAN,
|
||||
NETLANG_CHINESE,
|
||||
NETLANG_CZECH,
|
||||
NETLANG_DANISH,
|
||||
NETLANG_DUTCH,
|
||||
NETLANG_ESPERANTO,
|
||||
NETLANG_FINNISH,
|
||||
NETLANG_HUNGARIAN,
|
||||
NETLANG_ICELANDIC,
|
||||
NETLANG_ITALIAN,
|
||||
NETLANG_JAPANESE,
|
||||
NETLANG_KOREAN,
|
||||
NETLANG_LITHUANIAN,
|
||||
NETLANG_NORWEGIAN,
|
||||
NETLANG_POLISH,
|
||||
NETLANG_PORTUGUESE,
|
||||
NETLANG_ROMANIAN,
|
||||
NETLANG_RUSSIAN,
|
||||
NETLANG_SLOVAK,
|
||||
NETLANG_SLOVENIAN,
|
||||
NETLANG_SPANISH,
|
||||
NETLANG_SWEDISH,
|
||||
NETLANG_TURKISH,
|
||||
NETLANG_UKRAINIAN,
|
||||
NETLANG_AFRIKAANS,
|
||||
NETLANG_CROATIAN,
|
||||
NETLANG_CATALAN,
|
||||
NETLANG_ESTONIAN,
|
||||
NETLANG_GALICIAN,
|
||||
NETLANG_GREEK,
|
||||
NETLANG_LATVIAN,
|
||||
NETLANG_COUNT
|
||||
};
|
||||
|
||||
extern uint32 _frame_counter_server; // The frame_counter of the server, if in network-mode
|
||||
extern uint32 _frame_counter_max; // To where we may go with our clients
|
||||
extern uint32 _frame_counter;
|
||||
@@ -123,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);
|
||||
void NetworkQueryServer(const std::string &connection_string);
|
||||
|
||||
void GetBindAddresses(NetworkAddressList *addresses, uint16 port);
|
||||
void NetworkAddServer(const char *b);
|
||||
struct NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually = true, bool never_expire = false);
|
||||
void NetworkRebuildHostList();
|
||||
void UpdateNetworkGameWindow();
|
||||
|
||||
@@ -154,10 +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);
|
||||
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);
|
||||
|
||||
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 */
|
||||
|
||||
+187
-383
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();
|
||||
@@ -69,17 +67,17 @@ public:
|
||||
uint32 last_token_frame; ///< The last frame we received the right token
|
||||
ClientStatus status; ///< Status of this client
|
||||
CommandQueue outgoing_queue; ///< The command-queue awaiting delivery
|
||||
int receive_limit; ///< Amount of bytes that we can receive at this moment
|
||||
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,12 @@ 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 SendJoin(ClientID client_id);
|
||||
NetworkRecvStatus SendFrame();
|
||||
NetworkRecvStatus SendSync();
|
||||
@@ -115,13 +113,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,140 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* If our connecter is still pending, shut it down too. Otherwise the
|
||||
* callback of the connecter can call into us, and our object is most
|
||||
* likely about to be destroyed. */
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,34 @@
|
||||
/*
|
||||
* 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;
|
||||
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,135 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* If our connecter is still pending, shut it down too. Otherwise the
|
||||
* callback of the connecter can call into us, and our object is most
|
||||
* likely about to be destroyed. */
|
||||
if (this->connecter != nullptr) {
|
||||
this->connecter->Kill();
|
||||
this->connecter = nullptr;
|
||||
}
|
||||
|
||||
return NETWORK_RECV_STATUS_OKAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,41 @@
|
||||
/*
|
||||
* 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;
|
||||
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;
|
||||
@@ -132,6 +140,7 @@ enum NetworkErrorCode {
|
||||
NETWORK_ERROR_TIMEOUT_COMPUTER,
|
||||
NETWORK_ERROR_TIMEOUT_MAP,
|
||||
NETWORK_ERROR_TIMEOUT_JOIN,
|
||||
NETWORK_ERROR_INVALID_CLIENT_NAME,
|
||||
|
||||
NETWORK_ERROR_END,
|
||||
};
|
||||
|
||||
+18
-500
@@ -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,129 +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));
|
||||
strecpy(item->info.hostname, address.GetHostname(), lastof(item->info.hostname));
|
||||
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) ***/
|
||||
|
||||
@@ -153,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.
|
||||
@@ -166,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 (Company::GetNumItems() * (MIN_CI_SIZE + NETWORK_COMPANY_NAME_LENGTH) >= (uint)SEND_MTU - packet.size) {
|
||||
/* Assume we can at least put the company information in the packets. */
|
||||
assert(Company::GetNumItems() * MIN_CI_SIZE < (uint)SEND_MTU - packet.size);
|
||||
|
||||
/* At this moment the company names might not fit in the
|
||||
* packet. Check whether that is really the case. */
|
||||
|
||||
for (;;) {
|
||||
int free = SEND_MTU - packet.size;
|
||||
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);
|
||||
free -= MIN_CI_SIZE;
|
||||
free -= (int)strlen(company_name);
|
||||
}
|
||||
if (free >= 0) 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 SEND_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 SEND_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 > SEND_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) ***/
|
||||
@@ -306,303 +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);
|
||||
|
||||
ClearGRFConfigList(&item->info.grfconfig);
|
||||
DeserializeNetworkGameInfo(p, &item->info);
|
||||
|
||||
item->info.compatible = 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) item->info.compatible = false;
|
||||
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 (item->info.hostname[0] == '\0') {
|
||||
seprintf(item->info.hostname, lastof(item->info.hostname), "%s", client_addr->GetHostname());
|
||||
}
|
||||
|
||||
if (client_addr->GetAddress()->ss_family == AF_INET6) {
|
||||
strecat(item->info.server_name, " (IPv6)", lastof(item->info.server_name));
|
||||
}
|
||||
|
||||
/* Check if we are allowed on this server based on the revision-match */
|
||||
item->info.version_compatible = IsNetworkCompatibleVersion(item->info.server_revision);
|
||||
item->info.compatible &= item->info.version_compatible; // Already contains match for GRFs
|
||||
|
||||
item->online = true;
|
||||
|
||||
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();
|
||||
|
||||
@@ -610,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. */
|
||||
@@ -643,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