diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/core/config.h | 49 | ||||
-rw-r--r-- | src/network/core/game.h | 47 | ||||
-rw-r--r-- | src/network/core/os_abstraction.h | 181 | ||||
-rw-r--r-- | src/network/core/packet.c | 216 | ||||
-rw-r--r-- | src/network/core/packet.h | 67 | ||||
-rw-r--r-- | src/network/core/tcp.c | 227 | ||||
-rw-r--r-- | src/network/core/tcp.h | 60 | ||||
-rw-r--r-- | src/network/core/udp.c | 277 | ||||
-rw-r--r-- | src/network/core/udp.h | 62 | ||||
-rw-r--r-- | src/network/network.c | 1451 | ||||
-rw-r--r-- | src/network/network.h | 212 | ||||
-rw-r--r-- | src/network/network_client.c | 819 | ||||
-rw-r--r-- | src/network/network_client.h | 25 | ||||
-rw-r--r-- | src/network/network_data.c | 106 | ||||
-rw-r--r-- | src/network/network_data.h | 167 | ||||
-rw-r--r-- | src/network/network_gamelist.c | 74 | ||||
-rw-r--r-- | src/network/network_gamelist.h | 11 | ||||
-rw-r--r-- | src/network/network_gui.c | 1706 | ||||
-rw-r--r-- | src/network/network_gui.h | 26 | ||||
-rw-r--r-- | src/network/network_server.c | 1528 | ||||
-rw-r--r-- | src/network/network_server.h | 39 | ||||
-rw-r--r-- | src/network/network_udp.c | 663 | ||||
-rw-r--r-- | src/network/network_udp.h | 17 |
23 files changed, 8030 insertions, 0 deletions
diff --git a/src/network/core/config.h b/src/network/core/config.h new file mode 100644 index 000000000..0b80800f0 --- /dev/null +++ b/src/network/core/config.h @@ -0,0 +1,49 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_CONFIG_H +#define NETWORK_CORE_CONFIG_H + +#ifdef ENABLE_NETWORK + +/** DNS hostname of the masterserver */ +#define NETWORK_MASTER_SERVER_HOST "master.openttd.org" +/** Message sent to the masterserver to 'identify' this client as OpenTTD */ +#define NETWORK_MASTER_SERVER_WELCOME_MESSAGE "OpenTTDRegister" + +enum { + NETWORK_MASTER_SERVER_PORT = 3978, ///< The default port of the master server (UDP) + NETWORK_DEFAULT_PORT = 3979, ///< The default port of the game server (TCP & UDP) + + SEND_MTU = 1460, ///< Number of bytes we can pack in a single packet + + NETWORK_GAME_INFO_VERSION = 4, ///< What version of game-info do we use? + NETWORK_COMPANY_INFO_VERSION = 4, ///< What version of company info is this? + NETWORK_MASTER_SERVER_VERSION = 1, ///< What version of master-server-protocol do we use? + + NETWORK_NAME_LENGTH = 80, ///< The maximum length of the server name and map name, in bytes including '\0' + NETWORK_HOSTNAME_LENGTH = 80, ///< The maximum length of the host name, in bytes including '\0' + NETWORK_REVISION_LENGTH = 15, ///< The maximum length of the revision, in bytes including '\0' + NETWORK_PASSWORD_LENGTH = 20, ///< The maximum length of the password, in bytes including '\0' + NETWORK_PLAYERS_LENGTH = 200, ///< The maximum length for the list of players that controls a company, in bytes including '\0' + NETWORK_CLIENT_NAME_LENGTH = 25, ///< The maximum length of a player, in bytes including '\0' + NETWORK_RCONCOMMAND_LENGTH = 500, ///< The maximum length of a rconsole command, in bytes including '\0' + + NETWORK_GRF_NAME_LENGTH = 80, ///< Maximum length of the name of a GRF + /** + * Maximum number of GRFs that can be sent. + * This value is related to number of handles (files) OpenTTD can open. + * This is currently 64 and about 10 are currently used when OpenTTD loads + * without any NewGRFs. Therefore one can only load about 55 NewGRFs, so + * this is not a limit, but rather a way to easily check whether the limit + * imposed by the handle count is reached. Secondly it isn't possible to + * send much more GRF IDs + MD5sums in the PACKET_UDP_SERVER_RESPONSE, due + * to the limited size of UDP packets. + */ + NETWORK_MAX_GRF_COUNT = 55, + + NETWORK_NUM_LANGUAGES = 4, ///< Number of known languages (to the network protocol) + 1 for 'any'. +}; + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_CONFIG_H */ diff --git a/src/network/core/game.h b/src/network/core/game.h new file mode 100644 index 000000000..71268f7d2 --- /dev/null +++ b/src/network/core/game.h @@ -0,0 +1,47 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_GAME_H +#define NETWORK_CORE_GAME_H + +#ifdef ENABLE_NETWORK + +/** + * @file game.h Information about a game that is sent between a + * game server, game client and masterserver. + */ + +/** + * This is the struct used by both client and server + * some fields will be empty on the client (like game_password) by default + * and only filled with data a player enters. + */ +typedef struct NetworkGameInfo { + byte game_info_version; ///< Version of the game info + 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 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 + byte server_lang; ///< Language of the server (we should make a nice table for this) + byte use_password; ///< Is set to != 0 if it uses a password + char server_password[NETWORK_PASSWORD_LENGTH]; ///< On the server: the game password, on the client: != "" if server has password + byte clients_max; ///< Max clients allowed on server + byte clients_on; ///< Current count of clients on server + byte companies_max; ///< Max companies allowed on server + byte companies_on; ///< How many started companies do we have + byte spectators_max; ///< Max spectators allowed on server + byte spectators_on; ///< How many spectators do we have? + Date game_date; ///< Current date + Date start_date; ///< When the game started + char map_name[NETWORK_NAME_LENGTH]; ///< Map which is played ["random" for a randomized map] + uint16 map_width; ///< Map width + uint16 map_height; ///< Map height + byte map_set; ///< Graphical set + bool dedicated; ///< Is this a dedicated server? + char rcon_password[NETWORK_PASSWORD_LENGTH]; ///< RCon password for the server. "" if rcon is disabled + struct GRFConfig *grfconfig; ///< List of NewGRF files used +} NetworkGameInfo; + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_GAME_H */ diff --git a/src/network/core/os_abstraction.h b/src/network/core/os_abstraction.h new file mode 100644 index 000000000..c7df16a93 --- /dev/null +++ b/src/network/core/os_abstraction.h @@ -0,0 +1,181 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_OS_ABSTRACTION_H +#define NETWORK_CORE_OS_ABSTRACTION_H + +/** + * @file os_abstraction.h Network stuff has many things that needs to be + * included and/or implemented by default. + * All those things are in this file. + */ + +/* Include standard stuff per OS */ + +#ifdef ENABLE_NETWORK + +/* Windows stuff */ +#if defined(WIN32) || defined(WIN64) +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> + +#if !(defined(__MINGW32__) || defined(__CYGWIN__)) + /* Windows has some different names for some types */ + typedef SSIZE_T ssize_t; + typedef int socklen_t; +#endif + +#define GET_LAST_ERROR() WSAGetLastError() +#define EWOULDBLOCK WSAEWOULDBLOCK +/* Windows has some different names for some types */ +typedef unsigned long in_addr_t; +#endif /* WIN32 */ + +/* UNIX stuff */ +#if defined(UNIX) +# define SOCKET int +# define INVALID_SOCKET -1 +# if !defined(__MORPHOS__) && !defined(__AMIGA__) +# define ioctlsocket ioctl +# if !defined(BEOS_NET_SERVER) +# define closesocket close +# endif +# define GET_LAST_ERROR() (errno) +# endif +/* Need this for FIONREAD on solaris */ +# define BSD_COMP + +/* Includes needed for UNIX-like systems */ +# include <unistd.h> +# include <sys/ioctl.h> +# if defined(__BEOS__) && defined(BEOS_NET_SERVER) +# include <be/net/socket.h> +# include <be/kernel/OS.h> // snooze() +# include <be/net/netdb.h> + typedef unsigned long in_addr_t; +# define INADDR_NONE INADDR_BROADCAST +# else +# include <sys/socket.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# include <net/if.h> +/* According to glibc/NEWS, <ifaddrs.h> appeared in glibc-2.3. */ +# if !defined(__sgi__) && !defined(SUNOS) && !defined(__MORPHOS__) && !defined(__BEOS__) && !defined(__INNOTEK_LIBC__) \ + && !(defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 2)) && !defined(__dietlibc__) +/* If for any reason ifaddrs.h does not exist on your system, comment out + * the following two lines and an alternative way will be used to fetch + * the list of IPs from the system. */ +# include <ifaddrs.h> +# define HAVE_GETIFADDRS +# endif +# if defined(SUNOS) || defined(__MORPHOS__) || defined(__BEOS__) +# define INADDR_NONE 0xffffffff +# endif +# if defined(__BEOS__) && !defined(BEOS_NET_SERVER) + /* needed on Zeta */ +# include <sys/sockio.h> +# endif +# endif /* BEOS_NET_SERVER */ + +# if !defined(__BEOS__) && defined(__GLIBC__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 1) + typedef uint32_t in_addr_t; +# endif + +# include <errno.h> +# include <sys/time.h> +# include <netdb.h> +#endif // UNIX + +#ifdef __BEOS__ + typedef int socklen_t; +#endif + +/* OS/2 stuff */ +#if defined(__OS2__) +# define SOCKET int +# define INVALID_SOCKET -1 +# define ioctlsocket ioctl +# define closesocket close +# define GET_LAST_ERROR() (sock_errno()) + +/* Includes needed for OS/2 systems */ +# include <types.h> +# include <unistd.h> +# include <sys/ioctl.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# include <net/if.h> +# include <errno.h> +# include <sys/time.h> +# include <netdb.h> +# include <nerrno.h> +# define INADDR_NONE 0xffffffff + +typedef int socklen_t; +#if !defined(__INNOTEK_LIBC__) +typedef unsigned long in_addr_t; +#endif /* __INNOTEK_LIBC__ */ +#endif /* OS/2 */ + +/* MorphOS and Amiga stuff */ +#if defined(__MORPHOS__) || defined(__AMIGA__) +# include <exec/types.h> +# include <proto/exec.h> // required for Open/CloseLibrary() +# if defined(__MORPHOS__) +# include <sys/filio.h> // FIO* defines +# include <sys/sockio.h> // SIO* defines +# include <netinet/in.h> +# else /* __AMIGA__ */ +# include <proto/socket.h> +# endif + +/* Make the names compatible */ +# define closesocket(s) CloseSocket(s) +# define GET_LAST_ERROR() Errno() +# define ioctlsocket(s,request,status) IoctlSocket((LONG)s,(ULONG)request,(char*)status) +# define ioctl ioctlsocket + + typedef unsigned int in_addr_t; + typedef long socklen_t; + extern struct Library *SocketBase; + +# ifdef __AMIGA__ + /* for usleep() implementation */ + extern struct Device *TimerBase; + extern struct MsgPort *TimerPort; + extern struct timerequest *TimerRequest; +# endif +#endif // __MORPHOS__ || __AMIGA__ + +static inline bool SetNonBlocking(int d) +{ +#ifdef WIN32 + u_long nonblocking = 1; +#else + int nonblocking = 1; +#endif +#if defined(__BEOS__) && defined(BEOS_NET_SERVER) + return setsockopt(d, SOL_SOCKET, SO_NONBLOCK, &nonblocking, sizeof(nonblocking)) == 0; +#else + return ioctlsocket(d, FIONBIO, &nonblocking) == 0; +#endif +} + +static inline bool SetNoDelay(int d) +{ + /* XXX should this be done at all? */ +#if !defined(BEOS_NET_SERVER) // not implemented on BeOS net_server + int b = 1; + /* The (const char*) cast is needed for windows */ + return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b)) == 0; +#else + return true; +#endif +} + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_OS_ABSTRACTION_H */ diff --git a/src/network/core/packet.c b/src/network/core/packet.c new file mode 100644 index 000000000..957bd2ad6 --- /dev/null +++ b/src/network/core/packet.c @@ -0,0 +1,216 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../macros.h" +#include "../../string.h" + +#include "os_abstraction.h" +#include "config.h" +#include "packet.h" + +/** + * @file packet.h Basic functions to create, fill and read packets. + */ + + +/* Do not want to include functions.h and all required headers */ +extern void NORETURN CDECL error(const char *str, ...); + + +/** + * Create a packet for sending + * @param type the of packet + * @return the newly created packet + */ +Packet *NetworkSend_Init(PacketType type) +{ + Packet *packet = malloc(sizeof(Packet)); + /* An error is inplace here, because it simply means we ran out of memory. */ + if (packet == NULL) error("Failed to allocate Packet"); + + /* Skip the size so we can write that in before sending the packet */ + packet->size = sizeof(packet->size); + packet->buffer[packet->size++] = type; + packet->pos = 0; + + return packet; +} + +/** + * Writes the packet size from the raw packet from packet->size + * @param packet the packet to write the size of + */ +void NetworkSend_FillPacketSize(Packet *packet) +{ + packet->buffer[0] = GB(packet->size, 0, 8); + packet->buffer[1] = GB(packet->size, 8, 8); +} + +/** + * The next couple of functions make sure we can send + * uint8, uint16, uint32 and uint64 endian-safe + * over the network. The least significant bytes are + * sent first. + * + * So 0x01234567 would be sent as 67 45 23 01. + */ + +void NetworkSend_uint8(Packet *packet, uint8 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = data; +} + +void NetworkSend_uint16(Packet *packet, uint16 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); +} + +void NetworkSend_uint32(Packet *packet, uint32 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); + packet->buffer[packet->size++] = GB(data, 16, 8); + packet->buffer[packet->size++] = GB(data, 24, 8); +} + +void NetworkSend_uint64(Packet *packet, uint64 data) +{ + assert(packet->size < sizeof(packet->buffer) - sizeof(data)); + packet->buffer[packet->size++] = GB(data, 0, 8); + packet->buffer[packet->size++] = GB(data, 8, 8); + packet->buffer[packet->size++] = GB(data, 16, 8); + packet->buffer[packet->size++] = GB(data, 24, 8); + packet->buffer[packet->size++] = GB(data, 32, 8); + packet->buffer[packet->size++] = GB(data, 40, 8); + packet->buffer[packet->size++] = GB(data, 48, 8); + packet->buffer[packet->size++] = GB(data, 56, 8); +} + +/** + * Sends a string over the network. It sends out + * the string + '\0'. No size-byte or something. + */ +void NetworkSend_string(Packet *packet, const char* data) +{ + assert(data != NULL); + assert(packet->size < sizeof(packet->buffer) - strlen(data) - 1); + while ((packet->buffer[packet->size++] = *data++) != '\0') {} +} + + +/** + * Receiving commands + * Again, the next couple of functions are endian-safe + * see the comment before NetworkSend_uint8 for more info. + */ + + +extern uint CloseConnection(NetworkClientState *cs); + +/** Is it safe to read from the packet, i.e. didn't we run over the buffer ? */ +static inline bool CanReadFromPacket(NetworkClientState *cs, Packet *packet, uint bytes_to_read) +{ + /* Don't allow reading from a closed socket */ + if (HasClientQuit(cs)) return false; + + /* Check if variable is within packet-size */ + if (packet->pos + bytes_to_read > packet->size) { + CloseConnection(cs); + return false; + } + + return true; +} + +/** + * Reads the packet size from the raw packet and stores it in the packet->size + * @param packet the packet to read the size of + */ +void NetworkRecv_ReadPacketSize(Packet *packet) +{ + packet->size = (uint16)packet->buffer[0]; + packet->size += (uint16)packet->buffer[1] << 8; +} + +uint8 NetworkRecv_uint8(NetworkClientState *cs, Packet *packet) +{ + uint8 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = packet->buffer[packet->pos++]; + return n; +} + +uint16 NetworkRecv_uint16(NetworkClientState *cs, Packet *packet) +{ + uint16 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint16)packet->buffer[packet->pos++]; + n += (uint16)packet->buffer[packet->pos++] << 8; + return n; +} + +uint32 NetworkRecv_uint32(NetworkClientState *cs, Packet *packet) +{ + uint32 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint32)packet->buffer[packet->pos++]; + n += (uint32)packet->buffer[packet->pos++] << 8; + n += (uint32)packet->buffer[packet->pos++] << 16; + n += (uint32)packet->buffer[packet->pos++] << 24; + return n; +} + +uint64 NetworkRecv_uint64(NetworkClientState *cs, Packet *packet) +{ + uint64 n; + + if (!CanReadFromPacket(cs, packet, sizeof(n))) return 0; + + n = (uint64)packet->buffer[packet->pos++]; + n += (uint64)packet->buffer[packet->pos++] << 8; + n += (uint64)packet->buffer[packet->pos++] << 16; + n += (uint64)packet->buffer[packet->pos++] << 24; + n += (uint64)packet->buffer[packet->pos++] << 32; + n += (uint64)packet->buffer[packet->pos++] << 40; + n += (uint64)packet->buffer[packet->pos++] << 48; + n += (uint64)packet->buffer[packet->pos++] << 56; + return n; +} + +/** Reads a string till it finds a '\0' in the stream */ +void NetworkRecv_string(NetworkClientState *cs, Packet *p, char *buffer, size_t size) +{ + PacketSize pos; + char *bufp = buffer; + + /* Don't allow reading from a closed socket */ + if (HasClientQuit(cs)) return; + + pos = p->pos; + while (--size > 0 && pos < p->size && (*buffer++ = p->buffer[pos++]) != '\0') {} + + if (size == 0 || pos == p->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 < p->size && p->buffer[pos] != '\0') pos++; + pos++; + } + p->pos = pos; + + str_validate(bufp); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/core/packet.h b/src/network/core/packet.h new file mode 100644 index 000000000..2510c69a7 --- /dev/null +++ b/src/network/core/packet.h @@ -0,0 +1,67 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_PACKET_H +#define NETWORK_CORE_PACKET_H + +#ifdef ENABLE_NETWORK + +/** + * @file packet.h Basic functions to create, fill and read packets. + */ + +typedef struct NetworkClientState NetworkClientState; + +/** + * Queries the network client state struct to determine whether + * the client has quit. It indirectly also queries whether the + * packet is corrupt as the connection will be closed if it is + * reading beyond the boundary of the received packet. + * @param cs the state to query + * @param true if the connection should be considered dropped + */ +bool HasClientQuit(NetworkClientState *cs); + +typedef uint16 PacketSize; ///< Size of the whole packet. +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. + */ +typedef struct Packet { + /** The next packet. Used for queueing packets before sending. */ + struct 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 */ + byte buffer[SEND_MTU]; +} Packet; + + +Packet *NetworkSend_Init(PacketType type); +void NetworkSend_FillPacketSize(Packet *packet); +void NetworkSend_uint8 (Packet *packet, uint8 data); +void NetworkSend_uint16(Packet *packet, uint16 data); +void NetworkSend_uint32(Packet *packet, uint32 data); +void NetworkSend_uint64(Packet *packet, uint64 data); +void NetworkSend_string(Packet *packet, const char* data); + +void NetworkRecv_ReadPacketSize(Packet *packet); +uint8 NetworkRecv_uint8 (NetworkClientState *cs, Packet *packet); +uint16 NetworkRecv_uint16(NetworkClientState *cs, Packet *packet); +uint32 NetworkRecv_uint32(NetworkClientState *cs, Packet *packet); +uint64 NetworkRecv_uint64(NetworkClientState *cs, Packet *packet); +void NetworkRecv_string(NetworkClientState *cs, Packet *packet, char* buffer, size_t size); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_PACKET_H */ diff --git a/src/network/core/tcp.c b/src/network/core/tcp.c new file mode 100644 index 000000000..ec073c7ce --- /dev/null +++ b/src/network/core/tcp.c @@ -0,0 +1,227 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../openttd.h" +#include "../../variables.h" +#include "table/strings.h" +#include "../../functions.h" + +#include "os_abstraction.h" +#include "config.h" +#include "packet.h" +#include "../network_data.h" +#include "tcp.h" + +/** + * @file tcp.c Basic functions to receive and send TCP packets. + */ + +/** + * Functions to help NetworkRecv_Packet/NetworkSend_Packet a bit + * A socket can make errors. When that happens this handles what to do. + * For clients: close connection and drop back to main-menu + * For servers: close connection and that is it + * @param cs the client to close the connection of + * @return the new status + */ +NetworkRecvStatus CloseConnection(NetworkClientState *cs) +{ + NetworkCloseClient(cs); + + /* Clients drop back to the main menu */ + if (!_network_server && _networking) { + _switch_mode = SM_MENU; + _networking = false; + _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; + + return NETWORK_RECV_STATUS_CONN_LOST; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Whether the client has quit or not (used in packet.c) + * @param cs the client to check + * @return true if the client has quit + */ +bool HasClientQuit(NetworkClientState *cs) +{ + return cs->has_quit; +} + +/** + * This function puts the packet in the send-queue and it is send as + * soon as possible. This is the next tick, or maybe one tick later + * if the OS-network-buffer is full) + * @param packet the packet to send + * @param cs the client to send to + */ +void NetworkSend_Packet(Packet *packet, NetworkClientState *cs) +{ + Packet *p; + assert(packet != NULL); + + packet->pos = 0; + packet->next = NULL; + + NetworkSend_FillPacketSize(packet); + + /* Locate last packet buffered for the client */ + p = cs->packet_queue; + if (p == NULL) { + /* No packets yet */ + cs->packet_queue = packet; + } else { + /* Skip to the last packet */ + while (p->next != NULL) p = p->next; + p->next = packet; + } +} + +/** + * Sends all the buffered packets out for this client. It stops when: + * 1) all packets are send (queue is empty) + * 2) the OS reports back that it can not send any more + * data right now (full network-buffer, it happens ;)) + * 3) sending took too long + * @param cs the client to send the packets for + */ +bool NetworkSend_Packets(NetworkClientState *cs) +{ + ssize_t res; + Packet *p; + + /* We can not write to this socket!! */ + if (!cs->writable) return false; + if (cs->socket == INVALID_SOCKET) return false; + + p = cs->packet_queue; + while (p != NULL) { + res = send(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong.. close client! */ + DEBUG(net, 0, "send failed with error %d", err); + CloseConnection(cs); + return false; + } + return true; + } + if (res == 0) { + /* Client/server has left us :( */ + CloseConnection(cs); + return false; + } + + p->pos += res; + + /* Is this packet sent? */ + if (p->pos == p->size) { + /* Go to the next packet */ + cs->packet_queue = p->next; + free(p); + p = cs->packet_queue; + } else { + return true; + } + } + + return true; +} + +/** + * Receives a packet for the given client + * @param cs the client to (try to) receive a packet for + * @param status the variable to store the status into + * @return the received packet (or NULL when it didn't receive one) + */ +Packet *NetworkRecv_Packet(NetworkClientState *cs, NetworkRecvStatus *status) +{ + ssize_t res; + Packet *p; + + *status = NETWORK_RECV_STATUS_OKAY; + + if (cs->socket == INVALID_SOCKET) return NULL; + + if (cs->packet_recv == NULL) { + cs->packet_recv = malloc(sizeof(Packet)); + if (cs->packet_recv == NULL) error("Failed to allocate packet"); + /* Set pos to zero! */ + cs->packet_recv->pos = 0; + cs->packet_recv->size = 0; // Can be ommited, just for safety reasons + } + + p = cs->packet_recv; + + /* Read packet size */ + if (p->pos < sizeof(PacketSize)) { + while (p->pos < sizeof(PacketSize)) { + /* Read the size of the packet */ + res = recv(cs->socket, p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong... (104 is connection reset by peer) */ + if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + /* Connection would block, so stop for now */ + return NULL; + } + if (res == 0) { + /* Client/server has left */ + *status = CloseConnection(cs); + return NULL; + } + p->pos += res; + } + + NetworkRecv_ReadPacketSize(p); + + if (p->size > SEND_MTU) { + *status = CloseConnection(cs); + return NULL; + } + } + + /* Read rest of packet */ + while (p->pos < p->size) { + res = recv(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); + if (res == -1) { + int err = GET_LAST_ERROR(); + if (err != EWOULDBLOCK) { + /* Something went wrong... (104 is connection reset by peer) */ + if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); + *status = CloseConnection(cs); + return NULL; + } + /* Connection would block */ + return NULL; + } + if (res == 0) { + /* Client/server has left */ + *status = CloseConnection(cs); + return NULL; + } + + p->pos += res; + } + + /* We have a complete packet, return it! */ + p->pos = 2; + p->next = NULL; // Should not be needed, but who knows... + + /* Prepare for receiving a new packet */ + cs->packet_recv = NULL; + + return p; +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/core/tcp.h b/src/network/core/tcp.h new file mode 100644 index 000000000..e3c307353 --- /dev/null +++ b/src/network/core/tcp.h @@ -0,0 +1,60 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_TCP_H +#define NETWORK_CORE_TCP_H + +#ifdef ENABLE_NETWORK + +/** + * @file tcp.h Basic functions to receive and send TCP packets. + */ + +/** + * Enum with all types of UDP packets. + * The order of the first 4 packets MUST not be changed, as + * it protects old clients from joining newer servers + * (because SERVER_ERROR is the respond to a wrong revision) + */ +enum { + PACKET_SERVER_FULL, + PACKET_SERVER_BANNED, + PACKET_CLIENT_JOIN, + PACKET_SERVER_ERROR, + PACKET_CLIENT_COMPANY_INFO, + PACKET_SERVER_COMPANY_INFO, + PACKET_SERVER_CLIENT_INFO, + PACKET_SERVER_NEED_PASSWORD, + PACKET_CLIENT_PASSWORD, + PACKET_SERVER_WELCOME, + PACKET_CLIENT_GETMAP, + PACKET_SERVER_WAIT, + PACKET_SERVER_MAP, + PACKET_CLIENT_MAP_OK, + PACKET_SERVER_JOIN, + PACKET_SERVER_FRAME, + PACKET_SERVER_SYNC, + PACKET_CLIENT_ACK, + PACKET_CLIENT_COMMAND, + PACKET_SERVER_COMMAND, + PACKET_CLIENT_CHAT, + PACKET_SERVER_CHAT, + PACKET_CLIENT_SET_PASSWORD, + PACKET_CLIENT_SET_NAME, + PACKET_CLIENT_QUIT, + PACKET_CLIENT_ERROR, + PACKET_SERVER_QUIT, + PACKET_SERVER_ERROR_QUIT, + PACKET_SERVER_SHUTDOWN, + PACKET_SERVER_NEWGAME, + PACKET_SERVER_RCON, + PACKET_CLIENT_RCON, + PACKET_END ///< Must ALWAYS be on the end of this list!! (period) +}; + +void NetworkSend_Packet(Packet *packet, NetworkClientState *cs); +Packet *NetworkRecv_Packet(NetworkClientState *cs, NetworkRecvStatus *status); +bool NetworkSend_Packets(NetworkClientState *cs); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_TCP_H */ diff --git a/src/network/core/udp.c b/src/network/core/udp.c new file mode 100644 index 000000000..badd9a532 --- /dev/null +++ b/src/network/core/udp.c @@ -0,0 +1,277 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../date.h" +#include "../../debug.h" +#include "../../macros.h" +#include "../../newgrf_config.h" + +#include "os_abstraction.h" +#include "config.h" +#include "game.h" +#include "packet.h" +#include "udp.h" + +/** + * @file udp.c Basic functions to receive and send UDP packets. + */ + +/** + * Send a packet over UDP + * @param udp the socket to send over + * @param p the packet to send + * @param recv the receiver (target) of the packet + */ +void NetworkSendUDP_Packet(SOCKET udp, Packet *p, struct sockaddr_in *recv) +{ + int res; + + NetworkSend_FillPacketSize(p); + + /* Send the buffer */ + res = sendto(udp, p->buffer, p->size, 0, (struct sockaddr *)recv, sizeof(*recv)); + + /* Check for any errors, but ignore it otherwise */ + if (res == -1) DEBUG(net, 1, "[udp] sendto failed with: %i", GET_LAST_ERROR()); +} + +/** + * Start listening on the given host and port. + * @param udp the place where the (references to the) UDP are stored + * @param host the host (ip) to listen on + * @param port the port to listen on + * @param broadcast whether to allow broadcast sending/receiving + * @return true if the listening succeeded + */ +bool NetworkUDPListen(SOCKET *udp, uint32 host, uint16 port, bool broadcast) +{ + struct sockaddr_in sin; + + /* Make sure socket is closed */ + closesocket(*udp); + + *udp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (*udp == INVALID_SOCKET) { + DEBUG(net, 0, "[udp] failed to start UDP listener"); + return false; + } + + /* set nonblocking mode for socket */ + { + unsigned long blocking = 1; +#ifndef BEOS_NET_SERVER + ioctlsocket(*udp, FIONBIO, &blocking); +#else + setsockopt(*udp, SOL_SOCKET, SO_NONBLOCK, &blocking, NULL); +#endif + } + + sin.sin_family = AF_INET; + /* Listen on all IPs */ + sin.sin_addr.s_addr = host; + sin.sin_port = htons(port); + + if (bind(*udp, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + DEBUG(net, 0, "[udp] bind failed on %s:%i", inet_ntoa(*(struct in_addr *)&host), port); + return false; + } + + if (broadcast) { + /* Enable broadcast */ + unsigned long val = 1; +#ifndef BEOS_NET_SERVER // will work around this, some day; maybe. + setsockopt(*udp, SOL_SOCKET, SO_BROADCAST, (char *) &val , sizeof(val)); +#endif + } + + DEBUG(net, 1, "[udp] listening on port %s:%d", inet_ntoa(*(struct in_addr *)&host), port); + + return true; +} + +/** + * Receive a packet at UDP level + * @param udp the socket to receive the packet on + */ +void NetworkUDPReceive(SOCKET udp) +{ + struct sockaddr_in client_addr; + socklen_t client_len; + int nbytes; + Packet p; + int packet_len; + + packet_len = sizeof(p.buffer); + client_len = sizeof(client_addr); + + /* Try to receive anything */ + nbytes = recvfrom(udp, p.buffer, packet_len, 0, (struct sockaddr *)&client_addr, &client_len); + + /* We got some bytes for the base header of the packet. + * Assume we received the whole packet. */ + if (nbytes > 2) { + NetworkRecv_ReadPacketSize(&p); + + /* Put the position on the right place */ + p.pos = 2; + p.next = NULL; + + /* Handle the packet */ + NetworkHandleUDPPacket(&p, &client_addr); + } +} + + +/** + * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet + * @param p the packet to write the data to + * @param c the configuration to write the GRF ID and MD5 checksum from + */ +void NetworkSend_GRFIdentifier(Packet *p, const GRFConfig *c) +{ + uint j; + NetworkSend_uint32(p, c->grfid); + for (j = 0; j < sizeof(c->md5sum); j++) { + NetworkSend_uint8 (p, c->md5sum[j]); + } +} + +/** + * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet + * @param cs the client state (for closing connect on out-of-bounds reading etc) + * @param p the packet to read the data from + * @param c the configuration to write the GRF ID and MD5 checksum to + */ +void NetworkRecv_GRFIdentifier(NetworkClientState *cs, Packet *p, GRFConfig *c) +{ + uint j; + c->grfid = NetworkRecv_uint32(cs, p); + for (j = 0; j < sizeof(c->md5sum); j++) { + c->md5sum[j] = NetworkRecv_uint8(cs, p); + } +} + + +/** + * Serializes the NetworkGameInfo struct to the packet + * @param p the packet to write the data to + * @param info the NetworkGameInfo struct to serialize + */ +void NetworkSend_NetworkGameInfo(Packet *p, const NetworkGameInfo *info) +{ + NetworkSend_uint8 (p, NETWORK_GAME_INFO_VERSION); + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + + /* NETWORK_GAME_INFO_VERSION = 4 */ + { + /* Only send the GRF Identification (GRF_ID and MD5 checksum) of + * the GRFs that are needed, i.e. the ones that the server has + * selected in the NewGRF GUI and not the ones that are used due + * to the fact that they are in [newgrf-static] in openttd.cfg */ + const GRFConfig *c; + uint count = 0; + + /* Count number of GRFs to send information about */ + for (c = info->grfconfig; c != NULL; c = c->next) { + if (!HASBIT(c->flags, GCF_STATIC)) count++; + } + NetworkSend_uint8 (p, count); // Send number of GRFs + + /* Send actual GRF Identifications */ + for (c = info->grfconfig; c != NULL; c = c->next) { + if (!HASBIT(c->flags, GCF_STATIC)) NetworkSend_GRFIdentifier(p, c); + } + } + + /* NETWORK_GAME_INFO_VERSION = 3 */ + NetworkSend_uint32(p, info->game_date); + NetworkSend_uint32(p, info->start_date); + + /* NETWORK_GAME_INFO_VERSION = 2 */ + NetworkSend_uint8 (p, info->companies_max); + NetworkSend_uint8 (p, info->companies_on); + NetworkSend_uint8 (p, info->spectators_max); + + /* NETWORK_GAME_INFO_VERSION = 1 */ + NetworkSend_string(p, info->server_name); + NetworkSend_string(p, info->server_revision); + NetworkSend_uint8 (p, info->server_lang); + NetworkSend_uint8 (p, info->use_password); + NetworkSend_uint8 (p, info->clients_max); + NetworkSend_uint8 (p, info->clients_on); + NetworkSend_uint8 (p, info->spectators_on); + NetworkSend_string(p, info->map_name); + NetworkSend_uint16(p, info->map_width); + NetworkSend_uint16(p, info->map_height); + NetworkSend_uint8 (p, info->map_set); + NetworkSend_uint8 (p, info->dedicated); +} + +/** + * Deserializes the NetworkGameInfo struct from the packet + * @param cs the client state (for closing connect on out-of-bounds reading etc) + * @param p the packet to read the data from + * @param info the NetworkGameInfo to deserialize into + */ +void NetworkRecv_NetworkGameInfo(NetworkClientState *cs, Packet *p, NetworkGameInfo *info) +{ + info->game_info_version = NetworkRecv_uint8(cs, p); + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + switch (info->game_info_version) { + case 4: { + GRFConfig *c, **dst = &info->grfconfig; + uint i; + uint num_grfs = NetworkRecv_uint8(cs, p); + + for (i = 0; i < num_grfs; i++) { + c = calloc(1, sizeof(*c)); + NetworkRecv_GRFIdentifier(cs, p, c); + HandleIncomingNetworkGameInfoGRFConfig(c); + + /* Append GRFConfig to the list */ + *dst = c; + dst = &c->next; + } + } /* Fallthrough */ + case 3: + info->game_date = NetworkRecv_uint32(cs, p); + info->start_date = NetworkRecv_uint32(cs, p); + /* Fallthrough */ + case 2: + info->companies_max = NetworkRecv_uint8 (cs, p); + info->companies_on = NetworkRecv_uint8 (cs, p); + info->spectators_max = NetworkRecv_uint8 (cs, p); + /* Fallthrough */ + case 1: + NetworkRecv_string(cs, p, info->server_name, sizeof(info->server_name)); + NetworkRecv_string(cs, p, info->server_revision, sizeof(info->server_revision)); + info->server_lang = NetworkRecv_uint8 (cs, p); + info->use_password = NetworkRecv_uint8 (cs, p); + info->clients_max = NetworkRecv_uint8 (cs, p); + info->clients_on = NetworkRecv_uint8 (cs, p); + info->spectators_on = NetworkRecv_uint8 (cs, p); + if (info->game_info_version < 3) { // 16 bits dates got scrapped and are read earlier + info->game_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; + info->start_date = NetworkRecv_uint16(cs, p) + DAYS_TILL_ORIGINAL_BASE_YEAR; + } + NetworkRecv_string(cs, p, info->map_name, sizeof(info->map_name)); + info->map_width = NetworkRecv_uint16(cs, p); + info->map_height = NetworkRecv_uint16(cs, p); + info->map_set = NetworkRecv_uint8 (cs, p); + info->dedicated = NetworkRecv_uint8 (cs, p); + } +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/core/udp.h b/src/network/core/udp.h new file mode 100644 index 000000000..3221e664f --- /dev/null +++ b/src/network/core/udp.h @@ -0,0 +1,62 @@ +/* $Id$ */ + +#ifndef NETWORK_CORE_UDP_H +#define NETWORK_CORE_UDP_H + +#ifdef ENABLE_NETWORK + +/** + * @file udp.h Basic functions to receive and send UDP packets. + */ + +///** Sending/receiving of UDP packets **//// + +void NetworkSendUDP_Packet(SOCKET udp, Packet *p, struct sockaddr_in *recv); +bool NetworkUDPListen(SOCKET *udp, uint32 host, uint16 port, bool broadcast); +void NetworkUDPReceive(SOCKET udp); + +/** + * Function that is called for every received UDP packet. + * @param packet the received packet + * @param client_addr the address of the sender of the packet + */ +void NetworkHandleUDPPacket(Packet *p, struct sockaddr_in *client_addr); + + +///** Sending/receiving of (large) chuncks of UDP packets **//// + + +/** Enum with all types of UDP packets. The order MUST not be changed **/ +enum { + 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 succedeed + 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_END ///< Must ALWAYS be on the end of this list!! (period) +}; + +void NetworkSend_GRFIdentifier(Packet *p, const GRFConfig *c); +void NetworkSend_NetworkGameInfo(Packet *p, const NetworkGameInfo *info); + +void NetworkRecv_GRFIdentifier(NetworkClientState *cs, Packet *p, GRFConfig *c); +void NetworkRecv_NetworkGameInfo(NetworkClientState *cs, Packet *p, NetworkGameInfo *info); + +/** + * Function that is called for every GRFConfig that is read when receiving + * 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 + */ +void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CORE_UDP_H */ diff --git a/src/network/network.c b/src/network/network.c new file mode 100644 index 000000000..c8cb85804 --- /dev/null +++ b/src/network/network.c @@ -0,0 +1,1451 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "network_data.h" + +#if defined(WITH_REV) + extern const char _openttd_revision[]; +#elif defined(WITH_REV_HACK) + #define WITH_REV + const char _openttd_revision[] = WITH_REV_HACK; +#else + const char _openttd_revision[] = NOREV_STRING; +#endif + + +#ifdef ENABLE_NETWORK + +#include "../openttd.h" +#include "../debug.h" +#include "../functions.h" +#include "../string.h" +#include "../strings.h" +#include "../map.h" +#include "../command.h" +#include "../variables.h" +#include "../date.h" +#include "../newgrf_config.h" +#include "table/strings.h" +#include "network_client.h" +#include "network_server.h" +#include "network_udp.h" +#include "network_gamelist.h" +#include "core/udp.h" +#include "core/tcp.h" +#include "network_gui.h" +#include "../console.h" /* IConsoleCmdExec */ +#include <stdarg.h> /* va_list */ +#include "../md5.h" + +#ifdef __MORPHOS__ +// the library base is required here +struct Library *SocketBase = NULL; +#endif + +// The listen socket for the server +static SOCKET _listensocket; + +// The amount of clients connected +static byte _network_clients_connected = 0; +// The index counter for new clients (is never decreased) +static uint16 _network_client_index = NETWORK_SERVER_INDEX + 1; + +/* Some externs / forwards */ +extern void StateGameLoop(void); + +// Function that looks up the CI for a given client-index +NetworkClientInfo *NetworkFindClientInfoFromIndex(uint16 client_index) +{ + NetworkClientInfo *ci; + + for (ci = _network_client_info; ci != endof(_network_client_info); ci++) { + if (ci->client_index == client_index) return ci; + } + + return NULL; +} + +/** Return the CI for a given IP + * @param ip IP of the client we are looking for. This must be in string-format + * @return return a pointer to the corresponding NetworkClientInfo struct or NULL on failure */ +NetworkClientInfo *NetworkFindClientInfoFromIP(const char *ip) +{ + NetworkClientInfo *ci; + uint32 ip_number = inet_addr(ip); + + for (ci = _network_client_info; ci != endof(_network_client_info); ci++) { + if (ci->client_ip == ip_number) return ci; + } + + return NULL; +} + +// Function that looks up the CS for a given client-index +NetworkClientState *NetworkFindClientStateFromIndex(uint16 client_index) +{ + NetworkClientState *cs; + + for (cs = _clients; cs != &_clients[MAX_CLIENT_INFO]; cs++) { + if (cs->index == client_index) return cs; + } + + return NULL; +} + +// NetworkGetClientName is a server-safe function to get the name of the client +// if the user did not send it yet, Client #<no> is used. +void NetworkGetClientName(char *client_name, size_t size, const NetworkClientState *cs) +{ + const NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); + + if (ci->client_name[0] == '\0') { + snprintf(client_name, size, "Client #%4d", cs->index); + } else { + ttd_strlcpy(client_name, ci->client_name, size); + } +} + +byte NetworkSpectatorCount(void) +{ + const NetworkClientState *cs; + byte count = 0; + + FOR_ALL_CLIENTS(cs) { + if (DEREF_CLIENT_INFO(cs)->client_playas == PLAYER_SPECTATOR) count++; + } + + return count; +} + +// 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 CDECL NetworkTextMessage(NetworkAction action, uint16 color, bool self_send, const char *name, const char *str, ...) +{ + char buf[1024]; + va_list va; + const int duration = 10; // Game days the messages stay visible + char message[1024]; + char temp[1024]; + + va_start(va, str); + vsnprintf(buf, lengthof(buf), str, va); + va_end(va); + + switch (action) { + case NETWORK_ACTION_SERVER_MESSAGE: + color = 1; + snprintf(message, sizeof(message), "*** %s", buf); + break; + case NETWORK_ACTION_JOIN: + color = 1; + GetString(temp, STR_NETWORK_CLIENT_JOINED, lastof(temp)); + snprintf(message, sizeof(message), "*** %s %s", name, temp); + break; + case NETWORK_ACTION_LEAVE: + color = 1; + GetString(temp, STR_NETWORK_ERR_LEFT, lastof(temp)); + snprintf(message, sizeof(message), "*** %s %s (%s)", name, temp, buf); + break; + case NETWORK_ACTION_GIVE_MONEY: + if (self_send) { + SetDParamStr(0, name); + SetDParam(1, atoi(buf)); + GetString(temp, STR_NETWORK_GAVE_MONEY_AWAY, lastof(temp)); + snprintf(message, sizeof(message), "*** %s", temp); + } else { + SetDParam(0, atoi(buf)); + GetString(temp, STR_NETWORK_GIVE_MONEY, lastof(temp)); + snprintf(message, sizeof(message), "*** %s %s", name, temp); + } + break; + case NETWORK_ACTION_NAME_CHANGE: + GetString(temp, STR_NETWORK_NAME_CHANGE, lastof(temp)); + snprintf(message, sizeof(message), "*** %s %s %s", name, temp, buf); + break; + case NETWORK_ACTION_CHAT_COMPANY: + SetDParamStr(0, name); + SetDParamStr(1, buf); + GetString(temp, self_send ? STR_NETWORK_CHAT_TO_COMPANY : STR_NETWORK_CHAT_COMPANY, lastof(temp)); + ttd_strlcpy(message, temp, sizeof(message)); + break; + case NETWORK_ACTION_CHAT_CLIENT: + SetDParamStr(0, name); + SetDParamStr(1, buf); + GetString(temp, self_send ? STR_NETWORK_CHAT_TO_CLIENT : STR_NETWORK_CHAT_CLIENT, lastof(temp)); + ttd_strlcpy(message, temp, sizeof(message)); + break; + default: + SetDParamStr(0, name); + SetDParamStr(1, buf); + GetString(temp, STR_NETWORK_CHAT_ALL, lastof(temp)); + ttd_strlcpy(message, temp, sizeof(message)); + break; + } + + IConsolePrintF(color, "%s", message); + AddTextMessage(color, duration, "%s", message); +} + +// Calculate the frame-lag of a client +uint NetworkCalculateLag(const NetworkClientState *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! + // The packet can be out by a max of _net_frame_freq + if (cs->last_frame_server + DAY_TICKS + _network_frame_freq < _frame_counter) + lag += _frame_counter - (cs->last_frame_server + DAY_TICKS + _network_frame_freq); + + return lag; +} + + +// There was a non-recoverable error, drop back to the main menu with a nice +// error +static void NetworkError(StringID error_string) +{ + _switch_mode = SM_MENU; + _switch_mode_errorstr = error_string; +} + +static void ClientStartError(const char *error) +{ + DEBUG(net, 0, "[client] could not start network: %s",error); + NetworkError(STR_NETWORK_ERR_CLIENT_START); +} + +static void ServerStartError(const char *error) +{ + DEBUG(net, 0, "[server] could not start network: %s",error); + NetworkError(STR_NETWORK_ERR_SERVER_START); +} + +static void NetworkClientError(NetworkRecvStatus res, NetworkClientState* cs) +{ + // First, send a CLIENT_ERROR to the server, so he knows we are + // disconnection (and why!) + NetworkErrorCode errorno; + + // We just want to close the connection.. + if (res == NETWORK_RECV_STATUS_CLOSE_QUERY) { + cs->has_quit = true; + NetworkCloseClient(cs); + _networking = false; + + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + return; + } + + switch (res) { + case NETWORK_RECV_STATUS_DESYNC: errorno = NETWORK_ERROR_DESYNC; break; + case NETWORK_RECV_STATUS_SAVEGAME: errorno = NETWORK_ERROR_SAVEGAME_FAILED; break; + default: errorno = NETWORK_ERROR_GENERAL; break; + } + // This means we fucked up and the server closed the connection + if (res != NETWORK_RECV_STATUS_SERVER_ERROR && res != NETWORK_RECV_STATUS_SERVER_FULL && + res != NETWORK_RECV_STATUS_SERVER_BANNED) { + SEND_COMMAND(PACKET_CLIENT_ERROR)(errorno); + + // Dequeue all commands before closing the socket + NetworkSend_Packets(DEREF_CLIENT(0)); + } + + _switch_mode = SM_MENU; + NetworkCloseClient(cs); + _networking = false; +} + +/** Retrieve a string representation of an internal error number + * @param buf buffer where the error message will be stored + * @param err NetworkErrorCode + * @return returns a pointer to the error message (buf) */ +char* GetNetworkErrorMsg(char* buf, NetworkErrorCode err, const char* last) +{ + /* List of possible network errors, used by + * PACKET_SERVER_ERROR and PACKET_CLIENT_ERROR */ + static const StringID network_error_strings[] = { + STR_NETWORK_ERR_CLIENT_GENERAL, + STR_NETWORK_ERR_CLIENT_DESYNC, + STR_NETWORK_ERR_CLIENT_SAVEGAME, + STR_NETWORK_ERR_CLIENT_CONNECTION_LOST, + STR_NETWORK_ERR_CLIENT_PROTOCOL_ERROR, + STR_NETWORK_ERR_CLIENT_NOT_AUTHORIZED, + STR_NETWORK_ERR_CLIENT_NOT_EXPECTED, + STR_NETWORK_ERR_CLIENT_WRONG_REVISION, + STR_NETWORK_ERR_CLIENT_NAME_IN_USE, + STR_NETWORK_ERR_CLIENT_WRONG_PASSWORD, + STR_NETWORK_ERR_CLIENT_PLAYER_MISMATCH, + STR_NETWORK_ERR_CLIENT_KICKED, + STR_NETWORK_ERR_CLIENT_CHEATER, + STR_NETWORK_ERR_CLIENT_SERVER_FULL + }; + + if (err >= lengthof(network_error_strings)) err = 0; + + return GetString(buf, network_error_strings[err], last); +} + +/* Count the number of active clients connected */ +static uint NetworkCountPlayers(void) +{ + const NetworkClientState *cs; + uint count = 0; + + FOR_ALL_CLIENTS(cs) { + const NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); + if (IsValidPlayer(ci->client_playas)) count++; + } + + return count; +} + +static bool _min_players_paused = false; + +/* Check if the minimum number of players has been reached and pause or unpause the game as appropriate */ +void CheckMinPlayers(void) +{ + if (!_network_dedicated) return; + + if (NetworkCountPlayers() < _network_min_players) { + if (_min_players_paused) return; + + _min_players_paused = true; + DoCommandP(0, 1, 0, NULL, CMD_PAUSE); + NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game paused (not enough players)", NETWORK_SERVER_INDEX); + } else { + if (!_min_players_paused) return; + + _min_players_paused = false; + DoCommandP(0, 0, 0, NULL, CMD_PAUSE); + NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game unpaused (enough players)", NETWORK_SERVER_INDEX); + } +} + +// Find all IP-aliases for this host +static void NetworkFindIPs(void) +{ + int i; + +#if defined(BEOS_NET_SERVER) /* doesn't have neither getifaddrs or net/if.h */ + /* Based on Andrew Bachmann's netstat+.c. Big thanks to him! */ + 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; + } + + int output_length; + char *output_pointer = NULL; + char **output; + int sock = socket(AF_INET, SOCK_DGRAM, 0); + i = 0; + + // If something fails, make sure the list is empty + _broadcast_list[0] = 0; + + if (sock < 0) { + DEBUG(net, 0, "[core] error creating socket"); + return; + } + + output_length = _netstat(sock, &output_pointer, 1); + if (output_length < 0) { + DEBUG(net, 0, "[core] error running _netstat"); + return; + } + + output = &output_pointer; + if (seek_past_header(output, "IP Interfaces:") == B_OK) { + for (;;) { + uint32 n, fields, read; + uint8 i1, i2, i3, i4, j1, j2, j3, j4; + struct in_addr inaddr; + 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) { + inaddr.s_addr = htonl(ip | ~netmask); + _broadcast_list[i] = inaddr.s_addr; + i++; + } + if (read < 0) { + break; + } + *output += read; + } + /* XXX - Using either one of these crashes openttd heavily? - wber */ + /*free(output_pointer);*/ + /*free(output);*/ + closesocket(sock); + } +#elif defined(HAVE_GETIFADDRS) + struct ifaddrs *ifap, *ifa; + + // If something fails, make sure the list is empty + _broadcast_list[0] = 0; + + if (getifaddrs(&ifap) != 0) + return; + + i = 0; + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_BROADCAST)) continue; + if (ifa->ifa_broadaddr == NULL) continue; + if (ifa->ifa_broadaddr->sa_family != AF_INET) continue; + _broadcast_list[i] = ((struct sockaddr_in*)ifa->ifa_broadaddr)->sin_addr.s_addr; + i++; + } + freeifaddrs(ifap); + +#else /* not HAVE_GETIFADDRS */ + SOCKET sock; +#ifdef WIN32 + DWORD len = 0; + INTERFACE_INFO ifo[MAX_INTERFACES]; + uint j; +#else + char buf[4 * 1024]; // Arbitrary buffer size + struct ifconf ifconf; + const char* buf_end; + const char* p; +#endif + + // If something fails, make sure the list is empty + _broadcast_list[0] = 0; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == INVALID_SOCKET) return; + +#ifdef WIN32 + memset(&ifo[0], 0, sizeof(ifo)); + if ((WSAIoctl(sock, SIO_GET_INTERFACE_LIST, NULL, 0, &ifo[0], sizeof(ifo), &len, NULL, NULL)) != 0) { + closesocket(sock); + return; + } + + i = 0; + for (j = 0; j < len / sizeof(*ifo); j++) { + if (ifo[j].iiFlags & IFF_LOOPBACK) continue; + if (!(ifo[j].iiFlags & IFF_BROADCAST)) continue; + /* iiBroadcast is unusable, because it always seems to be set to + * 255.255.255.255. + */ + _broadcast_list[i++] = + ifo[j].iiAddress.AddressIn.sin_addr.s_addr | + ~ifo[j].iiNetmask.AddressIn.sin_addr.s_addr; + } +#else + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = buf; + if (ioctl(sock, SIOCGIFCONF, &ifconf) == -1) { + closesocket(sock); + return; + } + + i = 0; + buf_end = buf + ifconf.ifc_len; + for (p = buf; p < buf_end;) { + const struct ifreq* req = (const struct ifreq*)p; + + if (req->ifr_addr.sa_family == AF_INET) { + struct ifreq r; + + strncpy(r.ifr_name, req->ifr_name, lengthof(r.ifr_name)); + if (ioctl(sock, SIOCGIFFLAGS, &r) != -1 && + r.ifr_flags & IFF_BROADCAST && + ioctl(sock, SIOCGIFBRDADDR, &r) != -1) { + _broadcast_list[i++] = + ((struct sockaddr_in*)&r.ifr_broadaddr)->sin_addr.s_addr; + } + } + + p += sizeof(struct ifreq); +#ifdef AF_LINK + p += req->ifr_addr.sa_len - sizeof(struct sockaddr); +#endif + } +#endif + + closesocket(sock); +#endif /* not HAVE_GETIFADDRS */ + + _broadcast_list[i] = 0; + + DEBUG(net, 3, "Detected broadcast addresses:"); + // Now display to the debug all the detected ips + for (i = 0; _broadcast_list[i] != 0; i++) { + DEBUG(net, 3, "%d) %s", i, inet_ntoa(*(struct in_addr *)&_broadcast_list[i]));//inet_ntoa(inaddr)); + } +} + +// Resolve a hostname to a inet_addr +unsigned long NetworkResolveHost(const char *hostname) +{ + in_addr_t ip; + + // First try: is it an ip address? + ip = inet_addr(hostname); + + // If not try to resolve the name + if (ip == INADDR_NONE) { + struct hostent *he = gethostbyname(hostname); + if (he == NULL) { + DEBUG(net, 0, "Cannot resolve '%s'", hostname); + } else { + struct in_addr addr = *(struct in_addr *)he->h_addr_list[0]; + DEBUG(net, 1, "Resolved '%s' to %s", hostname, inet_ntoa(addr)); + ip = addr.s_addr; + } + } + return ip; +} + +// Converts a string to ip/port/player +// Format: IP#player:port +// +// connection_string will be re-terminated to seperate out the hostname, and player and port will +// be set to the player and port strings given by the user, inside the memory area originally +// occupied by connection_string. +void ParseConnectionString(const char **player, const char **port, char *connection_string) +{ + char *p; + for (p = connection_string; *p != '\0'; p++) { + if (*p == '#') { + *p = '\0'; + *player = ++p; + while (IsValidChar(*p, CS_NUMERAL)) p++; + if (*p == '\0') break; + } else if (*p == ':') { + *port = p + 1; + *p = '\0'; + } + } +} + +// Creates a new client from a socket +// Used both by the server and the client +static NetworkClientState *NetworkAllocClient(SOCKET s) +{ + NetworkClientState *cs; + byte client_no = 0; + + if (_network_server) { + // Can we handle a new client? + if (_network_clients_connected >= MAX_CLIENTS) return NULL; + if (_network_game_info.clients_on >= _network_game_info.clients_max) return NULL; + + // Register the login + client_no = _network_clients_connected++; + } + + cs = DEREF_CLIENT(client_no); + memset(cs, 0, sizeof(*cs)); + cs->socket = s; + cs->last_frame = 0; + cs->has_quit = false; + + cs->last_frame = _frame_counter; + cs->last_frame_server = _frame_counter; + + if (_network_server) { + NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); + memset(ci, 0, sizeof(*ci)); + + cs->index = _network_client_index++; + ci->client_index = cs->index; + ci->client_playas = PLAYER_INACTIVE_CLIENT; + ci->join_date = _date; + + InvalidateWindow(WC_CLIENT_LIST, 0); + } + + return cs; +} + +// Close a connection +void NetworkCloseClient(NetworkClientState *cs) +{ + NetworkClientInfo *ci; + // Socket is already dead + if (cs->socket == INVALID_SOCKET) { + cs->has_quit = true; + return; + } + + DEBUG(net, 1, "Closed client connection %d", cs->index); + + if (!cs->has_quit && _network_server && cs->status > STATUS_INACTIVE) { + // We did not receive a leave message from this client... + NetworkErrorCode errorno = NETWORK_ERROR_CONNECTION_LOST; + char str[100]; + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + NetworkClientState *new_cs; + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + GetNetworkErrorMsg(str, errorno, lastof(str)); + + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str); + + // Inform other clients of this... strange leaving ;) + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH && cs != new_cs) { + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno); + } + } + } + + /* When the client was PRE_ACTIVE, the server was in pause mode, so unpause */ + if (cs->status == STATUS_PRE_ACTIVE && _network_pause_on_join) { + DoCommandP(0, 0, 0, NULL, CMD_PAUSE); + NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game unpaused", NETWORK_SERVER_INDEX); + } + + closesocket(cs->socket); + cs->writable = false; + cs->has_quit = true; + + // Free all pending and partially received packets + while (cs->packet_queue != NULL) { + Packet *p = cs->packet_queue->next; + free(cs->packet_queue); + cs->packet_queue = p; + } + free(cs->packet_recv); + cs->packet_recv = NULL; + + while (cs->command_queue != NULL) { + CommandPacket *p = cs->command_queue->next; + free(cs->command_queue); + cs->command_queue = p; + } + + // Close the gap in the client-list + ci = DEREF_CLIENT_INFO(cs); + + if (_network_server) { + // We just lost one client :( + if (cs->status > STATUS_INACTIVE) _network_game_info.clients_on--; + _network_clients_connected--; + + while ((cs + 1) != DEREF_CLIENT(MAX_CLIENTS) && (cs + 1)->socket != INVALID_SOCKET) { + *cs = *(cs + 1); + *ci = *(ci + 1); + cs++; + ci++; + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + } + + // Reset the status of the last socket + cs->socket = INVALID_SOCKET; + cs->status = STATUS_INACTIVE; + cs->index = NETWORK_EMPTY_INDEX; + ci->client_index = NETWORK_EMPTY_INDEX; + + CheckMinPlayers(); +} + +// A client wants to connect to a server +static bool NetworkConnect(const char *hostname, int port) +{ + SOCKET s; + struct sockaddr_in sin; + + DEBUG(net, 1, "Connecting to %s %d", hostname, port); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) { + ClientStartError("socket() failed"); + return false; + } + + if (!SetNoDelay(s)) DEBUG(net, 1, "Setting TCP_NODELAY failed"); + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = NetworkResolveHost(hostname); + sin.sin_port = htons(port); + _network_last_host_ip = sin.sin_addr.s_addr; + + /* We failed to connect for which reason what so ever */ + if (connect(s, (struct sockaddr*) &sin, sizeof(sin)) != 0) return false; + + if (!SetNonBlocking(s)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error? + + // in client mode, only the first client field is used. it's pointing to the server. + NetworkAllocClient(s); + + _network_join_status = NETWORK_JOIN_STATUS_CONNECTING; + ShowJoinStatusWindow(); + + return true; +} + +// For the server, to accept new clients +static void NetworkAcceptClients(void) +{ + struct sockaddr_in sin; + NetworkClientState *cs; + uint i; + bool banned; + + // Should never ever happen.. is it possible?? + assert(_listensocket != INVALID_SOCKET); + + for (;;) { + socklen_t sin_len = sizeof(sin); + SOCKET s = accept(_listensocket, (struct sockaddr*)&sin, &sin_len); + if (s == INVALID_SOCKET) return; + + SetNonBlocking(s); // XXX error handling? + + DEBUG(net, 1, "Client connected from %s on frame %d", inet_ntoa(sin.sin_addr), _frame_counter); + + SetNoDelay(s); // XXX error handling? + + /* Check if the client is banned */ + banned = false; + for (i = 0; i < lengthof(_network_ban_list); i++) { + if (_network_ban_list[i] == NULL) continue; + + if (sin.sin_addr.s_addr == inet_addr(_network_ban_list[i])) { + Packet *p = NetworkSend_Init(PACKET_SERVER_BANNED); + + DEBUG(net, 1, "Banned ip tried to join (%s), refused", _network_ban_list[i]); + + p->buffer[0] = p->size & 0xFF; + p->buffer[1] = p->size >> 8; + + send(s, p->buffer, p->size, 0); + closesocket(s); + + free(p); + + banned = true; + break; + } + } + /* If this client is banned, continue with next client */ + if (banned) continue; + + cs = NetworkAllocClient(s); + if (cs == NULL) { + // no more clients allowed? + // Send to the client that we are full! + Packet *p = NetworkSend_Init(PACKET_SERVER_FULL); + + p->buffer[0] = p->size & 0xFF; + p->buffer[1] = p->size >> 8; + + send(s, p->buffer, p->size, 0); + closesocket(s); + + free(p); + + continue; + } + + // a new client has connected. We set him at inactive for now + // maybe he is only requesting server-info. Till he has sent a PACKET_CLIENT_MAP_OK + // the client stays inactive + cs->status = STATUS_INACTIVE; + + DEREF_CLIENT_INFO(cs)->client_ip = sin.sin_addr.s_addr; // Save the IP of the client + } +} + +// Set up the listen socket for the server +static bool NetworkListen(void) +{ + SOCKET ls; + struct sockaddr_in sin; + + DEBUG(net, 1, "Listening on %s:%d", _network_server_bind_ip_host, _network_server_port); + + ls = socket(AF_INET, SOCK_STREAM, 0); + if (ls == INVALID_SOCKET) { + ServerStartError("socket() on listen socket failed"); + return false; + } + + { // reuse the socket + int reuse = 1; + // The (const char*) cast is needed for windows!! + if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == -1) { + ServerStartError("setsockopt() on listen socket failed"); + return false; + } + } + + if (!SetNonBlocking(ls)) DEBUG(net, 0, "Setting non-blocking mode failed"); // XXX should this be an error? + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = _network_server_bind_ip; + sin.sin_port = htons(_network_server_port); + + if (bind(ls, (struct sockaddr*)&sin, sizeof(sin)) != 0) { + ServerStartError("bind() failed"); + return false; + } + + if (listen(ls, 1) != 0) { + ServerStartError("listen() failed"); + return false; + } + + _listensocket = ls; + + return true; +} + +// Close all current connections +static void NetworkClose(void) +{ + NetworkClientState *cs; + + FOR_ALL_CLIENTS(cs) { + if (!_network_server) { + SEND_COMMAND(PACKET_CLIENT_QUIT)("leaving"); + NetworkSend_Packets(cs); + } + NetworkCloseClient(cs); + } + + if (_network_server) { + // We are a server, also close the listensocket + closesocket(_listensocket); + _listensocket = INVALID_SOCKET; + DEBUG(net, 1, "Closed listener"); + NetworkUDPClose(); + } +} + +// Inits the network (cleans sockets and stuff) +static void NetworkInitialize(void) +{ + NetworkClientState *cs; + + _local_command_queue = NULL; + + // Clean all client-sockets + memset(_clients, 0, sizeof(_clients)); + for (cs = _clients; cs != &_clients[MAX_CLIENTS]; cs++) { + cs->socket = INVALID_SOCKET; + cs->status = STATUS_INACTIVE; + cs->command_queue = NULL; + } + + // Clean the client_info memory + memset(&_network_client_info, 0, sizeof(_network_client_info)); + memset(&_network_player_info, 0, sizeof(_network_player_info)); + + _sync_frame = 0; + _network_first_time = true; + + _network_reconnect = 0; + + NetworkUDPInitialize(); +} + +// Query a server to fetch his game-info +// If game_info is true, only the gameinfo is fetched, +// else only the client_info is fetched +NetworkGameList *NetworkQueryServer(const char* host, unsigned short port, bool game_info) +{ + if (!_network_available) return NULL; + + NetworkDisconnect(); + + if (game_info) return NetworkUDPQueryServer(host, port); + + NetworkInitialize(); + + _network_server = false; + + // Try to connect + _networking = NetworkConnect(host, port); + + // We are connected + if (_networking) { + SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO)(); + } else { // No networking, close everything down again + NetworkDisconnect(); + } + + return NULL; +} + +/* 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) +{ + if (*b != '\0') { + NetworkGameList *item; + const char *port = NULL; + const char *player = NULL; + char host[NETWORK_HOSTNAME_LENGTH]; + uint16 rport; + + ttd_strlcpy(host, b, lengthof(host)); + + ttd_strlcpy(_network_default_ip, b, lengthof(_network_default_ip)); + rport = NETWORK_DEFAULT_PORT; + + ParseConnectionString(&player, &port, host); + if (port != NULL) rport = atoi(port); + + item = NetworkQueryServer(host, rport, true); + item->manually = true; + } +} + +/* Generates the list of manually added hosts from NetworkGameList and + * dumps them into the array _network_host_list. This array is needed + * by the function that generates the config file. */ +void NetworkRebuildHostList(void) +{ + uint i = 0; + const NetworkGameList *item = _network_game_list; + while (item != NULL && i != lengthof(_network_host_list)) { + if (item->manually) { + free(_network_host_list[i]); + _network_host_list[i++] = str_fmt("%s:%i", item->info.hostname, item->port); + } + item = item->next; + } + + for (; i < lengthof(_network_host_list); i++) { + free(_network_host_list[i]); + _network_host_list[i] = NULL; + } +} + +// Used by clients, to connect to a server +bool NetworkClientConnectGame(const char *host, uint16 port) +{ + if (!_network_available) return false; + + if (port == 0) return false; + + ttd_strlcpy(_network_last_host, host, sizeof(_network_last_host)); + _network_last_port = port; + + NetworkDisconnect(); + NetworkUDPClose(); + NetworkInitialize(); + + // Try to connect + _networking = NetworkConnect(host, port); + + // We are connected + if (_networking) { + IConsoleCmdExec("exec scripts/on_client.scr 0"); + NetworkClient_Connected(); + } else { + // Connecting failed + NetworkError(STR_NETWORK_ERR_NOCONNECTION); + } + + return _networking; +} + +static void NetworkInitGameInfo(void) +{ + NetworkClientInfo *ci; + + ttd_strlcpy(_network_game_info.server_name, _network_server_name, sizeof(_network_game_info.server_name)); + ttd_strlcpy(_network_game_info.server_password, _network_server_password, sizeof(_network_server_password)); + ttd_strlcpy(_network_game_info.rcon_password, _network_rcon_password, sizeof(_network_rcon_password)); + if (_network_game_info.server_name[0] == '\0') + snprintf(_network_game_info.server_name, sizeof(_network_game_info.server_name), "Unnamed Server"); + + ttd_strlcpy(_network_game_info.server_revision, _openttd_revision, sizeof(_network_game_info.server_revision)); + + // The server is a client too ;) + if (_network_dedicated) { + _network_game_info.clients_on = 0; + _network_game_info.companies_on = 0; + _network_game_info.dedicated = true; + } else { + _network_game_info.clients_on = 1; + _network_game_info.companies_on = 1; + _network_game_info.dedicated = false; + } + + _network_game_info.spectators_on = 0; + + _network_game_info.game_date = _date; + _network_game_info.start_date = ConvertYMDToDate(_patches.starting_year, 0, 1); + _network_game_info.map_width = MapSizeX(); + _network_game_info.map_height = MapSizeY(); + _network_game_info.map_set = _opt.landscape; + + _network_game_info.use_password = (_network_server_password[0] != '\0'); + + // We use _network_client_info[MAX_CLIENT_INFO - 1] to store the server-data in it + // The index is NETWORK_SERVER_INDEX ( = 1) + ci = &_network_client_info[MAX_CLIENT_INFO - 1]; + memset(ci, 0, sizeof(*ci)); + + ci->client_index = NETWORK_SERVER_INDEX; + ci->client_playas = _network_dedicated ? PLAYER_SPECTATOR : _local_player; + + ttd_strlcpy(ci->client_name, _network_player_name, sizeof(ci->client_name)); + ttd_strlcpy(ci->unique_id, _network_unique_id, sizeof(ci->unique_id)); +} + +bool NetworkServerStart(void) +{ + if (!_network_available) return false; + + /* Call the pre-scripts */ + IConsoleCmdExec("exec scripts/pre_server.scr 0"); + if (_network_dedicated) IConsoleCmdExec("exec scripts/pre_dedicated.scr 0"); + + NetworkInitialize(); + if (!NetworkListen()) return false; + + // Try to start UDP-server + _network_udp_server = true; + _network_udp_server = NetworkUDPListen(&_udp_server_socket, _network_server_bind_ip, _network_server_port, false); + + _network_server = true; + _networking = true; + _frame_counter = 0; + _frame_counter_server = 0; + _frame_counter_max = 0; + _last_sync_frame = 0; + _network_own_client_index = NETWORK_SERVER_INDEX; + + /* Non-dedicated server will always be player #1 */ + if (!_network_dedicated) _network_playas = 0; + + _network_clients_connected = 0; + + NetworkInitGameInfo(); + + // 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"); + + _min_players_paused = false; + CheckMinPlayers(); + + /* Try to register us to the master server */ + _network_last_advertise_frame = 0; + _network_need_advertise = true; + NetworkUDPAdvertise(); + return true; +} + +// The server is rebooting... +// The only difference with NetworkDisconnect, is the packets that is sent +void NetworkReboot(void) +{ + if (_network_server) { + NetworkClientState *cs; + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_NEWGAME)(cs); + NetworkSend_Packets(cs); + } + } + + NetworkClose(); + + // Free all queued commands + while (_local_command_queue != NULL) { + CommandPacket *p = _local_command_queue; + _local_command_queue = _local_command_queue->next; + free(p); + } + + _networking = false; + _network_server = false; +} + +// We want to disconnect from the host/clients +void NetworkDisconnect(void) +{ + if (_network_server) { + NetworkClientState *cs; + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_SHUTDOWN)(cs); + NetworkSend_Packets(cs); + } + } + + if (_network_advertise) NetworkUDPRemoveAdvertise(); + + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + NetworkClose(); + + // Free all queued commands + while (_local_command_queue != NULL) { + CommandPacket *p = _local_command_queue; + _local_command_queue = _local_command_queue->next; + free(p); + } + + _networking = false; + _network_server = false; +} + +// Receives something from the network +static bool NetworkReceive(void) +{ + NetworkClientState *cs; + int n; + fd_set read_fd, write_fd; + struct timeval tv; + + FD_ZERO(&read_fd); + FD_ZERO(&write_fd); + + FOR_ALL_CLIENTS(cs) { + FD_SET(cs->socket, &read_fd); + FD_SET(cs->socket, &write_fd); + } + + // take care of listener port + if (_network_server) FD_SET(_listensocket, &read_fd); + + tv.tv_sec = tv.tv_usec = 0; // don't block at all. +#if !defined(__MORPHOS__) && !defined(__AMIGA__) + n = select(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv); +#else + n = WaitSelect(FD_SETSIZE, &read_fd, &write_fd, NULL, &tv, NULL); +#endif + if (n == -1 && !_network_server) NetworkError(STR_NETWORK_ERR_LOSTCONNECTION); + + // accept clients.. + if (_network_server && FD_ISSET(_listensocket, &read_fd)) NetworkAcceptClients(); + + // read stuff from clients + FOR_ALL_CLIENTS(cs) { + cs->writable = !!FD_ISSET(cs->socket, &write_fd); + if (FD_ISSET(cs->socket, &read_fd)) { + if (_network_server) { + NetworkServer_ReadPackets(cs); + } else { + NetworkRecvStatus res; + + // The client already was quiting! + if (cs->has_quit) return false; + + res = NetworkClient_ReadPackets(cs); + if (res != NETWORK_RECV_STATUS_OKAY) { + // The client made an error of which we can not recover + // close the client and drop back to main menu + NetworkClientError(res, cs); + return false; + } + } + } + } + return true; +} + +// This sends all buffered commands (if possible) +static void NetworkSend(void) +{ + NetworkClientState *cs; + FOR_ALL_CLIENTS(cs) { + if (cs->writable) { + NetworkSend_Packets(cs); + + if (cs->status == STATUS_MAP) { + // This client is in the middle of a map-send, call the function for that + SEND_COMMAND(PACKET_SERVER_MAP)(cs); + } + } + } +} + +// Handle the local-command-queue +static void NetworkHandleLocalQueue(void) +{ + CommandPacket *cp, **cp_prev; + + cp_prev = &_local_command_queue; + + while ( (cp = *cp_prev) != NULL) { + + // The queue is always in order, which means + // that the first element will be executed first. + if (_frame_counter < cp->frame) break; + + if (_frame_counter > cp->frame) { + // If we reach here, it means for whatever reason, we've already executed + // past the command we need to execute. + DEBUG(net, 0, "Trying to execute a packet in the past!"); + assert(0); + } + + // We can execute this command + NetworkExecuteCommand(cp); + + *cp_prev = cp->next; + free(cp); + } + + // Just a safety check, to be removed in the future. + // Make sure that no older command appears towards the end of the queue + // In that case we missed executing it. This will never happen. + for (cp = _local_command_queue; cp; cp = cp->next) { + assert(_frame_counter < cp->frame); + } + +} + +static bool NetworkDoClientLoop(void) +{ + _frame_counter++; + + NetworkHandleLocalQueue(); + + StateGameLoop(); + + // Check if we are in sync! + if (_sync_frame != 0) { + if (_sync_frame == _frame_counter) { +#ifdef NETWORK_SEND_DOUBLE_SEED + if (_sync_seed_1 != _random_seeds[0][0] || _sync_seed_2 != _random_seeds[0][1]) { +#else + if (_sync_seed_1 != _random_seeds[0][0]) { +#endif + NetworkError(STR_NETWORK_ERR_DESYNC); + DEBUG(net, 0, "Sync error detected!"); + NetworkClientError(NETWORK_RECV_STATUS_DESYNC, DEREF_CLIENT(0)); + 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! + if (_network_first_time) { + _network_first_time = false; + SEND_COMMAND(PACKET_CLIENT_ACK)(); + } + + _sync_frame = 0; + } else if (_sync_frame < _frame_counter) { + DEBUG(net, 1, "Missed frame for sync-test (%d / %d)", _sync_frame, _frame_counter); + _sync_frame = 0; + } + } + + return true; +} + +// We have to do some UDP checking +void NetworkUDPGameLoop(void) +{ + if (_network_udp_server) { + NetworkUDPReceive(_udp_server_socket); + if (_udp_master_socket != INVALID_SOCKET) { + NetworkUDPReceive(_udp_master_socket); + } + } else if (_udp_client_socket != INVALID_SOCKET) { + NetworkUDPReceive(_udp_client_socket); + if (_network_udp_broadcast > 0) _network_udp_broadcast--; + } +} + +// The main loop called from ttd.c +// Here we also have to do StateGameLoop if needed! +void NetworkGameLoop(void) +{ + if (!_networking) return; + + if (!NetworkReceive()) return; + + if (_network_server) { + bool send_frame = false; + + // We first increase the _frame_counter + _frame_counter++; + // Update max-frame-counter + if (_frame_counter > _frame_counter_max) { + _frame_counter_max = _frame_counter + _network_frame_freq; + send_frame = true; + } + + NetworkHandleLocalQueue(); + + // Then we make the frame + StateGameLoop(); + + _sync_seed_1 = _random_seeds[0][0]; +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = _random_seeds[0][1]; +#endif + + NetworkServer_Tick(send_frame); + } else { + // Client + + // Make sure we are at the frame were the server is (quick-frames) + if (_frame_counter_server > _frame_counter) { + while (_frame_counter_server > _frame_counter) { + if (!NetworkDoClientLoop()) break; + } + } else { + // Else, keep on going till _frame_counter_max + if (_frame_counter_max > _frame_counter) NetworkDoClientLoop(); + } + } + + NetworkSend(); +} + +static void NetworkGenerateUniqueId(void) +{ + md5_state_t state; + md5_byte_t digest[16]; + char hex_output[16*2 + 1]; + char coding_string[NETWORK_NAME_LENGTH]; + int di; + + snprintf(coding_string, sizeof(coding_string), "%d%s", (uint)Random(), "OpenTTD Unique ID"); + + /* Generate the MD5 hash */ + md5_init(&state); + md5_append(&state, (const md5_byte_t*)coding_string, strlen(coding_string)); + md5_finish(&state, digest); + + for (di = 0; di < 16; ++di) + sprintf(hex_output + di * 2, "%02x", digest[di]); + + /* _network_unique_id is our id */ + snprintf(_network_unique_id, sizeof(_network_unique_id), "%s", hex_output); +} + +// This tries to launch the network for a given OS +void NetworkStartUp(void) +{ + DEBUG(net, 3, "[core] starting network..."); + +#if defined(__MORPHOS__) || defined(__AMIGA__) + /* + * IMPORTANT NOTE: SocketBase needs to be initialized before we use _any_ + * network related function, else: crash. + */ + DEBUG(net, 3, "[core] loading bsd socket library"); + SocketBase = OpenLibrary("bsdsocket.library", 4); + if (SocketBase == NULL) { + DEBUG(net, 0, "[core] can't open bsdsocket.library version 4, network unavailable"); + _network_available = false; + return; + } + +#if defined(__AMIGA__) + // for usleep() implementation (only required for legacy AmigaOS builds) + TimerPort = CreateMsgPort(); + if (TimerPort != NULL) { + TimerRequest = (struct timerequest*)CreateIORequest(TimerPort, sizeof(struct timerequest); + if (TimerRequest != NULL) { + if (OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest*)TimerRequest, 0) == 0) { + TimerBase = TimerRequest->tr_node.io_Device; + if (TimerBase == NULL) { + // free ressources... + DEBUG(net, 0, "[core] can't initialize timer, network unavailable"); + _network_available = false; + return; + } + } + } + } +#endif // __AMIGA__ +#endif // __MORPHOS__ / __AMIGA__ + + // Network is available + _network_available = true; + _network_dedicated = false; + _network_last_advertise_frame = 0; + _network_need_advertise = true; + _network_advertise_retries = 0; + + /* Load the ip from the openttd.cfg */ + _network_server_bind_ip = inet_addr(_network_server_bind_ip_host); + /* And put the data back in it in case it was an invalid ip */ + snprintf(_network_server_bind_ip_host, sizeof(_network_server_bind_ip_host), "%s", inet_ntoa(*(struct in_addr *)&_network_server_bind_ip)); + + /* Generate an unique id when there is none yet */ + if (_network_unique_id[0] == '\0') NetworkGenerateUniqueId(); + + { + byte cl_max = _network_game_info.clients_max; + byte cp_max = _network_game_info.companies_max; + byte sp_max = _network_game_info.spectators_max; + + memset(&_network_game_info, 0, sizeof(_network_game_info)); + _network_game_info.clients_max = cl_max; + _network_game_info.companies_max = cp_max; + _network_game_info.spectators_max = sp_max; + } + + // Let's load the network in windows + #if defined(WIN32) + { + WSADATA wsa; + DEBUG(net, 3, "[core] loading windows socket library"); + if (WSAStartup(MAKEWORD(2,0), &wsa) != 0) { + DEBUG(net, 0, "[core] WSAStartup failed, network unavailable"); + _network_available = false; + return; + } + } + #endif // WIN32 + + NetworkInitialize(); + DEBUG(net, 3, "[core] network online, multiplayer available"); + NetworkFindIPs(); +} + +// This shuts the network down +void NetworkShutDown(void) +{ + NetworkDisconnect(); + NetworkUDPClose(); + + DEBUG(net, 3, "[core] shutting down network"); + + _network_available = false; + +#if defined(__MORPHOS__) || defined(__AMIGA__) + // free allocated ressources +#if defined(__AMIGA__) + if (TimerBase != NULL) CloseDevice((struct IORequest*)TimerRequest); // XXX This smells wrong + if (TimerRequest != NULL) DeleteIORequest(TimerRequest); + if (TimerPort != NULL) DeleteMsgPort(TimerPort); +#endif + + if (SocketBase != NULL) CloseLibrary(SocketBase); +#endif + +#if defined(WIN32) + WSACleanup(); +#endif +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network.h b/src/network/network.h new file mode 100644 index 000000000..779fe393e --- /dev/null +++ b/src/network/network.h @@ -0,0 +1,212 @@ +/* $Id$ */ + +#ifndef NETWORK_H +#define NETWORK_H + +#define NOREV_STRING "norev000" + +#ifdef ENABLE_NETWORK + +#include "../player.h" +#include "core/config.h" +#include "core/game.h" + +// If this line is enable, every frame will have a sync test +// this is not needed in normal games. Normal is like 1 sync in 100 +// frames. You can enable this if you have a lot of desyncs on a certain +// game. +// Remember: both client and server have to be compiled with this +// option enabled to make it to work. If one of the two has it disabled +// nothing will happen. +//#define ENABLE_NETWORK_SYNC_EVERY_FRAME + +// In theory sending 1 of the 2 seeds is enough to check for desyncs +// so in theory, this next define can be left off. +//#define NETWORK_SEND_DOUBLE_SEED + +// How many clients can we have? Like.. MAX_PLAYERS - 1 is the amount of +// players that can really play.. so.. a max of 4 spectators.. gives us.. +// MAX_PLAYERS + 3 +#define MAX_CLIENTS (MAX_PLAYERS + 3) + + +// Do not change this next line. It should _ALWAYS_ be MAX_CLIENTS + 1 +#define MAX_CLIENT_INFO (MAX_CLIENTS + 1) + +#define MAX_INTERFACES 9 + + +// How many vehicle/station types we put over the network +#define NETWORK_VEHICLE_TYPES 5 +#define NETWORK_STATION_TYPES 5 + +typedef struct NetworkPlayerInfo { + char company_name[NETWORK_NAME_LENGTH]; // Company name + char password[NETWORK_PASSWORD_LENGTH]; // The password for the player + Year inaugurated_year; // What year the company started in + int64 company_value; // The company value + int64 money; // The amount of money the company has + int64 income; // How much did the company earned last year + uint16 performance; // What was his performance last month? + byte use_password; // 0: No password 1: There is a password + uint16 num_vehicle[NETWORK_VEHICLE_TYPES]; // How many vehicles are there of this type? + uint16 num_station[NETWORK_STATION_TYPES]; // How many stations are there of this type? + char players[NETWORK_PLAYERS_LENGTH]; // The players that control this company (Name1, name2, ..) + uint16 months_empty; // How many months the company is empty +} NetworkPlayerInfo; + +typedef struct NetworkClientInfo { + uint16 client_index; // Index of the client (same as ClientState->index) + char client_name[NETWORK_CLIENT_NAME_LENGTH]; // Name of the client + byte client_lang; // The language of the client + byte client_playas; // As which player is this client playing (PlayerID) + uint32 client_ip; // IP-address of the client (so he can be banned) + Date join_date; // Gamedate the player has joined + char unique_id[NETWORK_NAME_LENGTH]; // Every play sends an unique id so we can indentify him +} NetworkClientInfo; + +typedef struct NetworkGameList { + NetworkGameInfo info; + uint32 ip; + uint16 port; + bool online; // False if the server did not respond (default status) + bool manually; // True if the server was added manually + struct NetworkGameList *next; +} NetworkGameList; + +typedef enum { + NETWORK_JOIN_STATUS_CONNECTING, + NETWORK_JOIN_STATUS_AUTHORIZING, + NETWORK_JOIN_STATUS_WAITING, + NETWORK_JOIN_STATUS_DOWNLOADING, + NETWORK_JOIN_STATUS_PROCESSING, + NETWORK_JOIN_STATUS_REGISTERING, + + NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO, +} NetworkJoinStatus; + +// language ids for server_lang and client_lang +typedef enum { + NETLANG_ANY = 0, + NETLANG_ENGLISH = 1, + NETLANG_GERMAN = 2, + NETLANG_FRENCH = 3, +} NetworkLanguage; + +VARDEF NetworkGameList *_network_game_list; + +VARDEF NetworkGameInfo _network_game_info; +VARDEF NetworkPlayerInfo _network_player_info[MAX_PLAYERS]; +VARDEF NetworkClientInfo _network_client_info[MAX_CLIENT_INFO]; + +VARDEF char _network_player_name[NETWORK_CLIENT_NAME_LENGTH]; +VARDEF char _network_default_ip[NETWORK_HOSTNAME_LENGTH]; + +VARDEF uint16 _network_own_client_index; +VARDEF char _network_unique_id[NETWORK_NAME_LENGTH]; // Our own unique ID + +VARDEF uint32 _frame_counter_server; // The frame_counter of the server, if in network-mode +VARDEF uint32 _frame_counter_max; // To where we may go with our clients + +VARDEF uint32 _last_sync_frame; // Used in the server to store the last time a sync packet was sent to clients. + +// networking settings +VARDEF uint32 _broadcast_list[MAX_INTERFACES + 1]; + +VARDEF uint16 _network_server_port; +/* We use bind_ip and bind_ip_host, where bind_ip_host is the readable form of + bind_ip_host, and bind_ip the numeric value, because we want a nice number + in the openttd.cfg, but we wants to use the uint32 internally.. */ +VARDEF uint32 _network_server_bind_ip; +VARDEF char _network_server_bind_ip_host[NETWORK_HOSTNAME_LENGTH]; +VARDEF bool _is_network_server; // Does this client wants to be a network-server? +VARDEF char _network_server_name[NETWORK_NAME_LENGTH]; +VARDEF char _network_server_password[NETWORK_PASSWORD_LENGTH]; +VARDEF char _network_rcon_password[NETWORK_PASSWORD_LENGTH]; + +VARDEF uint16 _network_max_join_time; ///< Time a client can max take to join +VARDEF bool _network_pause_on_join; ///< Pause the game when a client tries to join (more chance of succeeding join) + +VARDEF uint16 _redirect_console_to_client; + +VARDEF uint16 _network_sync_freq; +VARDEF uint8 _network_frame_freq; + +VARDEF uint32 _sync_seed_1, _sync_seed_2; +VARDEF uint32 _sync_frame; +VARDEF bool _network_first_time; +// Vars needed for the join-GUI +VARDEF NetworkJoinStatus _network_join_status; +VARDEF uint8 _network_join_waiting; +VARDEF uint16 _network_join_kbytes; +VARDEF uint16 _network_join_kbytes_total; + +VARDEF char _network_last_host[NETWORK_HOSTNAME_LENGTH]; +VARDEF short _network_last_port; +VARDEF uint32 _network_last_host_ip; +VARDEF uint8 _network_reconnect; + +VARDEF bool _network_udp_server; +VARDEF uint16 _network_udp_broadcast; + +VARDEF byte _network_lan_internet; + +VARDEF bool _network_need_advertise; +VARDEF uint32 _network_last_advertise_frame; +VARDEF uint8 _network_advertise_retries; + +VARDEF bool _network_autoclean_companies; +VARDEF uint8 _network_autoclean_unprotected; // Remove a company after X months +VARDEF uint8 _network_autoclean_protected; // Unprotect a company after X months + +VARDEF Year _network_restart_game_year; // If this year is reached, the server automaticly restarts +VARDEF uint8 _network_min_players; // Minimum number of players for game to unpause + +NetworkGameList *NetworkQueryServer(const char* host, unsigned short port, bool game_info); + +byte NetworkSpectatorCount(void); + +VARDEF char *_network_host_list[10]; +VARDEF char *_network_ban_list[25]; + +void ParseConnectionString(const char **player, const char **port, char *connection_string); +void NetworkUpdateClientInfo(uint16 client_index); +void NetworkAddServer(const char *b); +void NetworkRebuildHostList(void); +bool NetworkChangeCompanyPassword(byte argc, char *argv[]); +void NetworkPopulateCompanyInfo(void); +void UpdateNetworkGameWindow(bool unselect); +void CheckMinPlayers(void); + +void NetworkStartUp(void); +void NetworkUDPClose(void); +void NetworkShutDown(void); +void NetworkGameLoop(void); +void NetworkUDPGameLoop(void); +bool NetworkServerStart(void); +bool NetworkClientConnectGame(const char *host, uint16 port); +void NetworkReboot(void); +void NetworkDisconnect(void); + +VARDEF bool _networking; ///< are we in networking mode? +VARDEF bool _network_server; ///< network-server is active +VARDEF bool _network_available; ///< is network mode available? + +#else /* ENABLE_NETWORK */ +/* Network function stubs when networking is disabled */ + +static inline void NetworkStartUp(void) {} +static inline void NetworkShutDown(void) {} + +#define _networking 0 +#define _network_server 0 +#define _network_available 0 + +#endif /* ENABLE_NETWORK */ + +/* These variables must always be registered! */ +VARDEF bool _network_dedicated; ///< are we a dedicated server? +VARDEF bool _network_advertise; ///< is the server advertising to the master server? +VARDEF PlayerID _network_playas; ///< an id to play as.. (see players.h:Players) + +#endif /* NETWORK_H */ diff --git a/src/network/network_client.c b/src/network/network_client.c new file mode 100644 index 000000000..6e27eec56 --- /dev/null +++ b/src/network/network_client.c @@ -0,0 +1,819 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "../debug.h" +#include "../string.h" +#include "../strings.h" +#include "network_data.h" +#include "core/tcp.h" +#include "../date.h" +#include "table/strings.h" +#include "../functions.h" +#include "network_client.h" +#include "network_gamelist.h" +#include "network_gui.h" +#include "../saveload.h" +#include "../command.h" +#include "../window.h" +#include "../console.h" +#include "../variables.h" +#include "../ai/ai.h" + + +// This file handles all the client-commands + + +// So we don't make too much typos ;) +#define MY_CLIENT DEREF_CLIENT(0) + +static uint32 last_ack_frame; + +// ********** +// Sending functions +// DEF_CLIENT_SEND_COMMAND has no parameters +// ********** + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO) +{ + // + // Packet: CLIENT_COMPANY_INFO + // Function: Request company-info (in detail) + // Data: + // <none> + // + Packet *p; + _network_join_status = NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + p = NetworkSend_Init(PACKET_CLIENT_COMPANY_INFO); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_JOIN) +{ + // + // Packet: CLIENT_JOIN + // Function: Try to join the server + // Data: + // String: OpenTTD Revision (norev000 if no revision) + // String: Player Name (max NETWORK_NAME_LENGTH) + // uint8: Play as Player id (1..MAX_PLAYERS) + // uint8: Language ID + // String: Unique id to find the player back in server-listing + // + + extern const char _openttd_revision[]; + Packet *p; + _network_join_status = NETWORK_JOIN_STATUS_AUTHORIZING; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + p = NetworkSend_Init(PACKET_CLIENT_JOIN); + NetworkSend_string(p, _openttd_revision); + NetworkSend_string(p, _network_player_name); // Player name + NetworkSend_uint8(p, _network_playas); // PlayAs + NetworkSend_uint8(p, NETLANG_ANY); // Language + NetworkSend_string(p, _network_unique_id); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_PASSWORD)(NetworkPasswordType type, const char *password) +{ + // + // Packet: CLIENT_PASSWORD + // Function: Send a password to the server to authorize + // Data: + // uint8: NetworkPasswordType + // String: Password + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_PASSWORD); + NetworkSend_uint8(p, type); + NetworkSend_string(p, password); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_GETMAP) +{ + // + // Packet: CLIENT_GETMAP + // Function: Request the map from the server + // Data: + // <none> + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_GETMAP); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_MAP_OK) +{ + // + // Packet: CLIENT_MAP_OK + // Function: Tell the server that we are done receiving/loading the map + // Data: + // <none> + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_MAP_OK); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_ACK) +{ + // + // Packet: CLIENT_ACK + // Function: Tell the server we are done with this frame + // Data: + // uint32: current FrameCounter of the client + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_ACK); + + NetworkSend_uint32(p, _frame_counter); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send a command packet to the server +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_COMMAND)(CommandPacket *cp) +{ + // + // Packet: CLIENT_COMMAND + // Function: Send a DoCommand to the Server + // Data: + // uint8: PlayerID (0..MAX_PLAYERS-1) + // uint32: CommandID (see command.h) + // uint32: P1 (free variables used in DoCommand) + // uint32: P2 + // uint32: Tile + // string: text + // uint8: CallBackID (see callback_table.c) + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_COMMAND); + + NetworkSend_uint8(p, cp->player); + NetworkSend_uint32(p, cp->cmd); + NetworkSend_uint32(p, cp->p1); + NetworkSend_uint32(p, cp->p2); + NetworkSend_uint32(p, (uint32)cp->tile); + NetworkSend_string(p, cp->text); + NetworkSend_uint8(p, cp->callback); + + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send a chat-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_CHAT)(NetworkAction action, DestType type, int dest, const char *msg) +{ + // + // Packet: CLIENT_CHAT + // Function: Send a chat-packet to the serve + // Data: + // uint8: ActionID (see network_data.h, NetworkAction) + // uint8: Destination Type (see network_data.h, DestType); + // uint8: Destination Player (1..MAX_PLAYERS) + // String: Message (max MAX_TEXT_MSG_LEN) + // + + Packet *p = NetworkSend_Init(PACKET_CLIENT_CHAT); + + NetworkSend_uint8(p, action); + NetworkSend_uint8(p, type); + NetworkSend_uint8(p, dest); + NetworkSend_string(p, msg); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send an error-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_ERROR)(NetworkErrorCode errorno) +{ + // + // Packet: CLIENT_ERROR + // Function: The client made an error and is quiting the game + // Data: + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_ERROR); + + NetworkSend_uint8(p, errorno); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_PASSWORD)(const char *password) +{ + // + // Packet: PACKET_CLIENT_SET_PASSWORD + // Function: Set the password for the clients current company + // Data: + // String: Password + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_SET_PASSWORD); + + NetworkSend_string(p, password); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_NAME)(const char *name) +{ + // + // Packet: PACKET_CLIENT_SET_NAME + // Function: Gives the player a new name + // Data: + // String: Name + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_SET_NAME); + + NetworkSend_string(p, name); + NetworkSend_Packet(p, MY_CLIENT); +} + +// Send an quit-packet over the network +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_QUIT)(const char *leavemsg) +{ + // + // Packet: CLIENT_QUIT + // Function: The client is quiting the game + // Data: + // String: leave-message + // + Packet *p = NetworkSend_Init(PACKET_CLIENT_QUIT); + + NetworkSend_string(p, leavemsg); + NetworkSend_Packet(p, MY_CLIENT); +} + +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_RCON)(const char *pass, const char *command) +{ + Packet *p = NetworkSend_Init(PACKET_CLIENT_RCON); + NetworkSend_string(p, pass); + NetworkSend_string(p, command); + NetworkSend_Packet(p, MY_CLIENT); +} + + +// ********** +// Receiving functions +// DEF_CLIENT_RECEIVE_COMMAND has parameter: Packet *p +// ********** + +extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FULL) +{ + // We try to join a server which is full + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_FULL; + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + return NETWORK_RECV_STATUS_SERVER_FULL; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_BANNED) +{ + // We try to join a server where we are banned + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_BANNED; + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + return NETWORK_RECV_STATUS_SERVER_BANNED; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO) +{ + byte company_info_version; + int i; + + company_info_version = NetworkRecv_uint8(MY_CLIENT, p); + + if (!MY_CLIENT->has_quit && company_info_version == NETWORK_COMPANY_INFO_VERSION) { + byte total; + byte current; + + total = NetworkRecv_uint8(MY_CLIENT, p); + + // There is no data at all.. + if (total == 0) return NETWORK_RECV_STATUS_CLOSE_QUERY; + + current = NetworkRecv_uint8(MY_CLIENT, p); + if (!IsValidPlayer(current)) return NETWORK_RECV_STATUS_CLOSE_QUERY; + + NetworkRecv_string(MY_CLIENT, p, _network_player_info[current].company_name, sizeof(_network_player_info[current].company_name)); + _network_player_info[current].inaugurated_year = NetworkRecv_uint32(MY_CLIENT, p); + _network_player_info[current].company_value = NetworkRecv_uint64(MY_CLIENT, p); + _network_player_info[current].money = NetworkRecv_uint64(MY_CLIENT, p); + _network_player_info[current].income = NetworkRecv_uint64(MY_CLIENT, p); + _network_player_info[current].performance = NetworkRecv_uint16(MY_CLIENT, p); + _network_player_info[current].use_password = NetworkRecv_uint8(MY_CLIENT, p); + for (i = 0; i < NETWORK_VEHICLE_TYPES; i++) + _network_player_info[current].num_vehicle[i] = NetworkRecv_uint16(MY_CLIENT, p); + for (i = 0; i < NETWORK_STATION_TYPES; i++) + _network_player_info[current].num_station[i] = NetworkRecv_uint16(MY_CLIENT, p); + + NetworkRecv_string(MY_CLIENT, p, _network_player_info[current].players, sizeof(_network_player_info[current].players)); + + InvalidateWindow(WC_NETWORK_WINDOW, 0); + + return NETWORK_RECV_STATUS_OKAY; + } + + return NETWORK_RECV_STATUS_CLOSE_QUERY; +} + +// This packet contains info about the client (playas and name) +// as client we save this in NetworkClientInfo, linked via 'index' +// which is always an unique number on a server. +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_CLIENT_INFO) +{ + NetworkClientInfo *ci; + uint16 index = NetworkRecv_uint16(MY_CLIENT, p); + PlayerID playas = NetworkRecv_uint8(MY_CLIENT, p); + char name[NETWORK_NAME_LENGTH]; + char unique_id[NETWORK_NAME_LENGTH]; + + NetworkRecv_string(MY_CLIENT, p, name, sizeof(name)); + NetworkRecv_string(MY_CLIENT, p, unique_id, sizeof(unique_id)); + + if (MY_CLIENT->has_quit) return NETWORK_RECV_STATUS_CONN_LOST; + + /* Do we receive a change of data? Most likely we changed playas */ + if (index == _network_own_client_index) _network_playas = playas; + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + if (playas == ci->client_playas && strcmp(name, ci->client_name) != 0) { + // Client name changed, display the change + NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, false, ci->client_name, "%s", name); + } else if (playas != ci->client_playas) { + // The player changed from client-player.. + // Do not display that for now + } + + ci->client_playas = playas; + ttd_strlcpy(ci->client_name, name, sizeof(ci->client_name)); + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; + } + + // We don't have this index yet, find an empty index, and put the data there + ci = NetworkFindClientInfoFromIndex(NETWORK_EMPTY_INDEX); + if (ci != NULL) { + ci->client_index = index; + ci->client_playas = playas; + + ttd_strlcpy(ci->client_name, name, sizeof(ci->client_name)); + ttd_strlcpy(ci->unique_id, unique_id, sizeof(ci->unique_id)); + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; + } + + // Here the program should never ever come..... + return NETWORK_RECV_STATUS_MALFORMED_PACKET; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_ERROR) +{ + NetworkErrorCode error = NetworkRecv_uint8(MY_CLIENT, p); + + switch (error) { + /* We made an error in the protocol, and our connection is closed.... */ + case NETWORK_ERROR_NOT_AUTHORIZED: + case NETWORK_ERROR_NOT_EXPECTED: + case NETWORK_ERROR_PLAYER_MISMATCH: + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_ERROR; + break; + case NETWORK_ERROR_FULL: + _switch_mode_errorstr = STR_NETWORK_ERR_SERVER_FULL; + break; + case NETWORK_ERROR_WRONG_REVISION: + _switch_mode_errorstr = STR_NETWORK_ERR_WRONG_REVISION; + break; + case NETWORK_ERROR_WRONG_PASSWORD: + _switch_mode_errorstr = STR_NETWORK_ERR_WRONG_PASSWORD; + break; + case NETWORK_ERROR_KICKED: + _switch_mode_errorstr = STR_NETWORK_ERR_KICKED; + break; + case NETWORK_ERROR_CHEATER: + _switch_mode_errorstr = STR_NETWORK_ERR_CHEATER; + break; + default: + _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; + } + + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_NEED_PASSWORD) +{ + NetworkPasswordType type = NetworkRecv_uint8(MY_CLIENT, p); + + switch (type) { + case NETWORK_GAME_PASSWORD: + case NETWORK_COMPANY_PASSWORD: + ShowNetworkNeedPassword(type); + return NETWORK_RECV_STATUS_OKAY; + + default: return NETWORK_RECV_STATUS_MALFORMED_PACKET; + } +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_WELCOME) +{ + _network_own_client_index = NetworkRecv_uint16(MY_CLIENT, p); + + // Start receiving the map + SEND_COMMAND(PACKET_CLIENT_GETMAP)(); + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_WAIT) +{ + _network_join_status = NETWORK_JOIN_STATUS_WAITING; + _network_join_waiting = NetworkRecv_uint8(MY_CLIENT, p); + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + // We are put on hold for receiving the map.. we need GUI for this ;) + DEBUG(net, 1, "The server is currently busy sending the map to someone else, please wait..." ); + DEBUG(net, 1, "There are %d clients in front of you", _network_join_waiting); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_MAP) +{ + static char filename[256]; + static FILE *file_pointer; + + byte maptype; + + maptype = NetworkRecv_uint8(MY_CLIENT, p); + + if (MY_CLIENT->has_quit) return NETWORK_RECV_STATUS_CONN_LOST; + + // First packet, init some stuff + if (maptype == MAP_PACKET_START) { + // The name for the temp-map + snprintf(filename, lengthof(filename), "%s%snetwork_client.tmp", _paths.autosave_dir, PATHSEP); + + file_pointer = fopen(filename, "wb"); + if (file_pointer == NULL) { + _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; + return NETWORK_RECV_STATUS_SAVEGAME; + } + + _frame_counter = _frame_counter_server = _frame_counter_max = NetworkRecv_uint32(MY_CLIENT, p); + + _network_join_status = NETWORK_JOIN_STATUS_DOWNLOADING; + _network_join_kbytes = 0; + _network_join_kbytes_total = NetworkRecv_uint32(MY_CLIENT, p) / 1024; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + // The first packet does not contain any more data + return NETWORK_RECV_STATUS_OKAY; + } + + if (maptype == MAP_PACKET_NORMAL) { + // We are still receiving data, put it to the file + fwrite(p->buffer + p->pos, 1, p->size - p->pos, file_pointer); + + _network_join_kbytes = ftell(file_pointer) / 1024; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + } + + // Check if this was the last packet + if (maptype == MAP_PACKET_END) { + fclose(file_pointer); + + _network_join_status = NETWORK_JOIN_STATUS_PROCESSING; + InvalidateWindow(WC_NETWORK_STATUS_WINDOW, 0); + + /* The map is done downloading, load it */ + if (!SafeSaveOrLoad(filename, SL_LOAD, GM_NORMAL)) { + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + _switch_mode_errorstr = STR_NETWORK_ERR_SAVEGAMEERROR; + return NETWORK_RECV_STATUS_SAVEGAME; + } + /* If the savegame has successfully loaded, ALL windows have been removed, + * only toolbar/statusbar and gamefield are visible */ + + _opt_ptr = &_opt; // during a network game you are always in-game + + // Say we received the map and loaded it correctly! + SEND_COMMAND(PACKET_CLIENT_MAP_OK)(); + + /* New company/spectator (invalid player) or company we want to join is not active + * Switch local player to spectator and await the server's judgement */ + if (_network_playas == PLAYER_NEW_COMPANY || !IsValidPlayer(_network_playas) || + !GetPlayer(_network_playas)->is_active) { + + SetLocalPlayer(PLAYER_SPECTATOR); + + if (_network_playas != PLAYER_SPECTATOR) { + /* We have arrived and ready to start playing; send a command to make a new player; + * the server will give us a client-id and let us in */ + _network_join_status = NETWORK_JOIN_STATUS_REGISTERING; + ShowJoinStatusWindow(); + NetworkSend_Command(0, 0, 0, CMD_PLAYER_CTRL, NULL); + } + } else { + // take control over an existing company + SetLocalPlayer(_network_playas); + } + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FRAME) +{ + _frame_counter_server = NetworkRecv_uint32(MY_CLIENT, p); + _frame_counter_max = NetworkRecv_uint32(MY_CLIENT, p); +#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME + // Test if the server supports this option + // and if we are at the frame the server is + if (p->pos < p->size) { + _sync_frame = _frame_counter_server; + _sync_seed_1 = NetworkRecv_uint32(MY_CLIENT, p); +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = NetworkRecv_uint32(MY_CLIENT, p); +#endif + } +#endif + DEBUG(net, 5, "Received FRAME %d", _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); + SEND_COMMAND(PACKET_CLIENT_ACK)(); + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_SYNC) +{ + _sync_frame = NetworkRecv_uint32(MY_CLIENT, p); + _sync_seed_1 = NetworkRecv_uint32(MY_CLIENT, p); +#ifdef NETWORK_SEND_DOUBLE_SEED + _sync_seed_2 = NetworkRecv_uint32(MY_CLIENT, p); +#endif + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMMAND) +{ + CommandPacket *cp = malloc(sizeof(CommandPacket)); + cp->player = NetworkRecv_uint8(MY_CLIENT, p); + cp->cmd = NetworkRecv_uint32(MY_CLIENT, p); + cp->p1 = NetworkRecv_uint32(MY_CLIENT, p); + cp->p2 = NetworkRecv_uint32(MY_CLIENT, p); + cp->tile = NetworkRecv_uint32(MY_CLIENT, p); + NetworkRecv_string(MY_CLIENT, p, cp->text, sizeof(cp->text)); + cp->callback = NetworkRecv_uint8(MY_CLIENT, p); + cp->frame = NetworkRecv_uint32(MY_CLIENT, p); + cp->next = NULL; + + // The server did send us this command.. + // queue it in our own queue, so we can handle it in the upcoming frame! + + if (_local_command_queue == NULL) { + _local_command_queue = cp; + } else { + // Find last packet + CommandPacket *c = _local_command_queue; + while (c->next != NULL) c = c->next; + c->next = cp; + } + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_CHAT) +{ + char name[NETWORK_NAME_LENGTH], msg[MAX_TEXT_MSG_LEN]; + const NetworkClientInfo *ci = NULL, *ci_to; + + NetworkAction action = NetworkRecv_uint8(MY_CLIENT, p); + uint16 index = NetworkRecv_uint16(MY_CLIENT, p); + bool self_send = NetworkRecv_uint8(MY_CLIENT, p); + NetworkRecv_string(MY_CLIENT, p, msg, MAX_TEXT_MSG_LEN); + + ci_to = NetworkFindClientInfoFromIndex(index); + if (ci_to == NULL) return NETWORK_RECV_STATUS_OKAY; + + /* Did we initiate the action locally? */ + if (self_send) { + switch (action) { + case NETWORK_ACTION_CHAT_CLIENT: + /* For speaking to client we need the client-name */ + snprintf(name, sizeof(name), "%s", ci_to->client_name); + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + break; + + /* For speaking to company or giving money, we need the player-name */ + case NETWORK_ACTION_GIVE_MONEY: + if (!IsValidPlayer(ci_to->client_playas)) return NETWORK_RECV_STATUS_OKAY; + /* fallthrough */ + case NETWORK_ACTION_CHAT_COMPANY: { + StringID str = IsValidPlayer(ci_to->client_playas) ? GetPlayer(ci_to->client_playas)->name_1 : STR_NETWORK_SPECTATORS; + + GetString(name, str, lastof(name)); + ci = NetworkFindClientInfoFromIndex(_network_own_client_index); + } break; + + default: NOT_REACHED(); break; + } + } else { + /* Display message from somebody else */ + snprintf(name, sizeof(name), "%s", ci_to->client_name); + ci = ci_to; + } + + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas), self_send, name, "%s", msg); + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_ERROR_QUIT) +{ + char str[100]; + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(MY_CLIENT, p); + GetNetworkErrorMsg(str, NetworkRecv_uint8(MY_CLIENT, p), lastof(str)); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, ci->client_name, "%s", str); + + // The client is gone, give the NetworkClientInfo free + ci->client_index = NETWORK_EMPTY_INDEX; + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_QUIT) +{ + char str[100]; + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(MY_CLIENT, p); + NetworkRecv_string(MY_CLIENT, p, str, lengthof(str)); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) { + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, ci->client_name, "%s", str); + + // The client is gone, give the NetworkClientInfo free + ci->client_index = NETWORK_EMPTY_INDEX; + } else { + DEBUG(net, 0, "Unknown client (%d) is leaving the game", index); + } + + InvalidateWindow(WC_CLIENT_LIST, 0); + + // If we come here it means we could not locate the client.. strange :s + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_JOIN) +{ + uint16 index; + NetworkClientInfo *ci; + + index = NetworkRecv_uint16(MY_CLIENT, p); + + ci = NetworkFindClientInfoFromIndex(index); + if (ci != NULL) + NetworkTextMessage(NETWORK_ACTION_JOIN, 1, false, ci->client_name, ""); + + InvalidateWindow(WC_CLIENT_LIST, 0); + + return NETWORK_RECV_STATUS_OKAY; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_SHUTDOWN) +{ + _switch_mode_errorstr = STR_NETWORK_SERVER_SHUTDOWN; + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_NEWGAME) +{ + // To trottle the reconnects a bit, every clients waits + // his _local_player value before reconnecting + // PLAYER_SPECTATOR is currently 255, so to avoid long wait periods + // set the max to 10. + _network_reconnect = min(_local_player + 1, 10); + _switch_mode_errorstr = STR_NETWORK_SERVER_REBOOT; + + return NETWORK_RECV_STATUS_SERVER_ERROR; +} + +DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_RCON) +{ + char rcon_out[NETWORK_RCONCOMMAND_LENGTH]; + uint16 color_code; + + color_code = NetworkRecv_uint16(MY_CLIENT, p); + NetworkRecv_string(MY_CLIENT, p, rcon_out, sizeof(rcon_out)); + + IConsolePrint(color_code, rcon_out); + + return NETWORK_RECV_STATUS_OKAY; +} + + + +// The layout for the receive-functions by the client +typedef NetworkRecvStatus NetworkClientPacket(Packet *p); + +// This array matches PacketType. At an incoming +// packet it is matches against this array +// and that way the right function to handle that +// packet is found. +static NetworkClientPacket* const _network_client_packet[] = { + RECEIVE_COMMAND(PACKET_SERVER_FULL), + RECEIVE_COMMAND(PACKET_SERVER_BANNED), + NULL, /*PACKET_CLIENT_JOIN,*/ + RECEIVE_COMMAND(PACKET_SERVER_ERROR), + NULL, /*PACKET_CLIENT_COMPANY_INFO,*/ + RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO), + RECEIVE_COMMAND(PACKET_SERVER_CLIENT_INFO), + RECEIVE_COMMAND(PACKET_SERVER_NEED_PASSWORD), + NULL, /*PACKET_CLIENT_PASSWORD,*/ + RECEIVE_COMMAND(PACKET_SERVER_WELCOME), + NULL, /*PACKET_CLIENT_GETMAP,*/ + RECEIVE_COMMAND(PACKET_SERVER_WAIT), + RECEIVE_COMMAND(PACKET_SERVER_MAP), + NULL, /*PACKET_CLIENT_MAP_OK,*/ + RECEIVE_COMMAND(PACKET_SERVER_JOIN), + RECEIVE_COMMAND(PACKET_SERVER_FRAME), + RECEIVE_COMMAND(PACKET_SERVER_SYNC), + NULL, /*PACKET_CLIENT_ACK,*/ + NULL, /*PACKET_CLIENT_COMMAND,*/ + RECEIVE_COMMAND(PACKET_SERVER_COMMAND), + NULL, /*PACKET_CLIENT_CHAT,*/ + RECEIVE_COMMAND(PACKET_SERVER_CHAT), + NULL, /*PACKET_CLIENT_SET_PASSWORD,*/ + NULL, /*PACKET_CLIENT_SET_NAME,*/ + NULL, /*PACKET_CLIENT_QUIT,*/ + NULL, /*PACKET_CLIENT_ERROR,*/ + RECEIVE_COMMAND(PACKET_SERVER_QUIT), + RECEIVE_COMMAND(PACKET_SERVER_ERROR_QUIT), + RECEIVE_COMMAND(PACKET_SERVER_SHUTDOWN), + RECEIVE_COMMAND(PACKET_SERVER_NEWGAME), + RECEIVE_COMMAND(PACKET_SERVER_RCON), + NULL, /*PACKET_CLIENT_RCON,*/ +}; + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_client_packet) == PACKET_END); + +// Is called after a client is connected to the server +void NetworkClient_Connected(void) +{ + // Set the frame-counter to 0 so nothing happens till we are ready + _frame_counter = 0; + _frame_counter_server = 0; + last_ack_frame = 0; + // Request the game-info + SEND_COMMAND(PACKET_CLIENT_JOIN)(); +} + +// Reads the packets from the socket-stream, if available +NetworkRecvStatus NetworkClient_ReadPackets(NetworkClientState *cs) +{ + Packet *p; + NetworkRecvStatus res = NETWORK_RECV_STATUS_OKAY; + + while (res == NETWORK_RECV_STATUS_OKAY && (p = NetworkRecv_Packet(cs, &res)) != NULL) { + byte type = NetworkRecv_uint8(MY_CLIENT, p); + if (type < PACKET_END && _network_client_packet[type] != NULL && !MY_CLIENT->has_quit) { + res = _network_client_packet[type](p); + } else { + res = NETWORK_RECV_STATUS_MALFORMED_PACKET; + DEBUG(net, 0, "[client] received invalid packet type %d", type); + } + + free(p); + } + + return res; +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_client.h b/src/network/network_client.h new file mode 100644 index 000000000..2dd776ac1 --- /dev/null +++ b/src/network/network_client.h @@ -0,0 +1,25 @@ +/* $Id$ */ + +#ifndef NETWORK_CLIENT_H +#define NETWORK_CLIENT_H + +#ifdef ENABLE_NETWORK + +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_GAME_INFO); +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_COMPANY_INFO); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_COMMAND)(CommandPacket *cp); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_ERROR)(NetworkErrorCode errorno); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_QUIT)(const char *leavemsg); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_CHAT)(NetworkAction action, DestType desttype, int dest, const char *msg); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_PASSWORD)(NetworkPasswordType type, const char *password); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_PASSWORD)(const char *password); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_SET_NAME)(const char *name); +DEF_CLIENT_SEND_COMMAND(PACKET_CLIENT_ACK); +DEF_CLIENT_SEND_COMMAND_PARAM(PACKET_CLIENT_RCON)(const char *pass, const char *command); + +NetworkRecvStatus NetworkClient_ReadPackets(NetworkClientState *cs); +void NetworkClient_Connected(void); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_CLIENT_H */ diff --git a/src/network/network_data.c b/src/network/network_data.c new file mode 100644 index 000000000..9e5e6424d --- /dev/null +++ b/src/network/network_data.c @@ -0,0 +1,106 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "../debug.h" +#include "network_data.h" +#include "../string.h" +#include "network_client.h" +#include "../command.h" +#include "../callback_table.h" + +// Add a command to the local command queue +void NetworkAddCommandQueue(NetworkClientState *cs, CommandPacket *cp) +{ + CommandPacket* new_cp = malloc(sizeof(*new_cp)); + + *new_cp = *cp; + + if (cs->command_queue == NULL) { + cs->command_queue = new_cp; + } else { + CommandPacket *c = cs->command_queue; + while (c->next != NULL) c = c->next; + c->next = new_cp; + } +} + +// Prepare a DoCommand to be send over the network +void NetworkSend_Command(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback) +{ + CommandPacket *c = malloc(sizeof(CommandPacket)); + byte temp_callback; + + c->player = _local_player; + c->next = NULL; + c->tile = tile; + c->p1 = p1; + c->p2 = p2; + c->cmd = cmd; + c->callback = 0; + + temp_callback = 0; + + while (temp_callback < _callback_table_count && _callback_table[temp_callback] != callback) + temp_callback++; + if (temp_callback == _callback_table_count) { + DEBUG(net, 0, "Unknown callback. (Pointer: %p) No callback sent", callback); + temp_callback = 0; /* _callback_table[0] == NULL */ + } + + if (_network_server) { + // We are the server, so set the command to be executed next possible frame + c->frame = _frame_counter_max + 1; + } else { + c->frame = 0; // The client can't tell which frame, so just make it 0 + } + + ttd_strlcpy(c->text, (_cmd_text != NULL) ? _cmd_text : "", lengthof(c->text)); + + if (_network_server) { + // If we are the server, we queue the command in our 'special' queue. + // In theory, we could execute the command right away, but then the + // client on the server can do everything 1 tick faster than others. + // So to keep the game fair, we delay the command with 1 tick + // which gives about the same speed as most clients. + NetworkClientState *cs; + + // And we queue it for delivery to the clients + FOR_ALL_CLIENTS(cs) { + if (cs->status > STATUS_AUTH) NetworkAddCommandQueue(cs, c); + } + + // Only the server gets the callback, because clients should not get them + c->callback = temp_callback; + if (_local_command_queue == NULL) { + _local_command_queue = c; + } else { + // Find last packet + CommandPacket *cp = _local_command_queue; + while (cp->next != NULL) cp = cp->next; + cp->next = c; + } + + return; + } + + // Clients send their command to the server and forget all about the packet + c->callback = temp_callback; + SEND_COMMAND(PACKET_CLIENT_COMMAND)(c); +} + +// Execute a DoCommand we received from the network +void NetworkExecuteCommand(CommandPacket *cp) +{ + _current_player = cp->player; + _cmd_text = cp->text; + /* cp->callback is unsigned. so we don't need to do lower bounds checking. */ + if (cp->callback > _callback_table_count) { + DEBUG(net, 0, "Received out-of-bounds callback (%d)", cp->callback); + cp->callback = 0; + } + DoCommandP(cp->tile, cp->p1, cp->p2, _callback_table[cp->callback], cp->cmd | CMD_NETWORK_COMMAND); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_data.h b/src/network/network_data.h new file mode 100644 index 000000000..3e42e00cf --- /dev/null +++ b/src/network/network_data.h @@ -0,0 +1,167 @@ +/* $Id$ */ + +#ifndef NETWORK_DATA_H +#define NETWORK_DATA_H + +// Is the network enabled? +#ifdef ENABLE_NETWORK + +#include "../openttd.h" +#include "network.h" +#include "core/os_abstraction.h" +#include "core/config.h" +#include "core/packet.h" + +#define MAX_TEXT_MSG_LEN 1024 /* long long long long sentences :-) */ + +// The client-info-server-index is always 1 +#define NETWORK_SERVER_INDEX 1 +#define NETWORK_EMPTY_INDEX 0 + +typedef struct CommandPacket { + struct CommandPacket *next; + PlayerID player; /// player that is executing the command + uint32 cmd; /// command being executed + uint32 p1; /// parameter p1 + uint32 p2; /// parameter p2 + TileIndex tile; /// tile command being executed on + char text[80]; + uint32 frame; /// the frame in which this packet is executed + byte callback; /// any callback function executed upon successful completion of the command +} CommandPacket; + +typedef enum { + STATUS_INACTIVE, + STATUS_AUTH, // This means that the client is authorized + STATUS_MAP_WAIT, // This means that the client is put on hold because someone else is getting the map + STATUS_MAP, + STATUS_DONE_MAP, + STATUS_PRE_ACTIVE, + STATUS_ACTIVE, +} ClientStatus; + +typedef enum { + MAP_PACKET_START, + MAP_PACKET_NORMAL, + MAP_PACKET_END, +} MapPacket; + +typedef enum { + NETWORK_RECV_STATUS_OKAY, + NETWORK_RECV_STATUS_DESYNC, + NETWORK_RECV_STATUS_SAVEGAME, + NETWORK_RECV_STATUS_CONN_LOST, + NETWORK_RECV_STATUS_MALFORMED_PACKET, + NETWORK_RECV_STATUS_SERVER_ERROR, // The server told us we made an error + NETWORK_RECV_STATUS_SERVER_FULL, + NETWORK_RECV_STATUS_SERVER_BANNED, + NETWORK_RECV_STATUS_CLOSE_QUERY, // Done quering the server +} NetworkRecvStatus; + +typedef enum { + NETWORK_ERROR_GENERAL, // Try to use thisone like never + + // Signals from clients + NETWORK_ERROR_DESYNC, + NETWORK_ERROR_SAVEGAME_FAILED, + NETWORK_ERROR_CONNECTION_LOST, + NETWORK_ERROR_ILLEGAL_PACKET, + + // Signals from servers + NETWORK_ERROR_NOT_AUTHORIZED, + NETWORK_ERROR_NOT_EXPECTED, + NETWORK_ERROR_WRONG_REVISION, + NETWORK_ERROR_NAME_IN_USE, + NETWORK_ERROR_WRONG_PASSWORD, + NETWORK_ERROR_PLAYER_MISMATCH, // Happens in CLIENT_COMMAND + NETWORK_ERROR_KICKED, + NETWORK_ERROR_CHEATER, + NETWORK_ERROR_FULL, +} NetworkErrorCode; + +// Actions that can be used for NetworkTextMessage +typedef enum { + NETWORK_ACTION_JOIN, + NETWORK_ACTION_LEAVE, + NETWORK_ACTION_SERVER_MESSAGE, + NETWORK_ACTION_CHAT, + NETWORK_ACTION_CHAT_COMPANY, + NETWORK_ACTION_CHAT_CLIENT, + NETWORK_ACTION_GIVE_MONEY, + NETWORK_ACTION_NAME_CHANGE, +} NetworkAction; + +typedef enum { + NETWORK_GAME_PASSWORD, + NETWORK_COMPANY_PASSWORD, +} NetworkPasswordType; + +// To keep the clients all together +struct NetworkClientState { // Typedeffed in network_core/packet.h + SOCKET socket; + uint16 index; + uint32 last_frame; + uint32 last_frame_server; + byte lag_test; // This byte is used for lag-testing the client + + ClientStatus status; + bool writable; // is client ready to write to? + bool has_quit; + + Packet *packet_queue; // Packets that are awaiting delivery + Packet *packet_recv; // Partially received packet + + CommandPacket *command_queue; // The command-queue awaiting delivery +}; + +typedef enum { + DESTTYPE_BROADCAST, ///< Send message/notice to all players (All) + DESTTYPE_TEAM, ///< Send message/notice to everyone playing the same company (Team) + DESTTYPE_CLIENT, ///< Send message/notice to only a certain player (Private) +} DestType; + +CommandPacket *_local_command_queue; + +SOCKET _udp_client_socket; // udp client socket +SOCKET _udp_server_socket; // udp server socket +SOCKET _udp_master_socket; // udp master socket + +// Here we keep track of the clients +// (and the client uses [0] for his own communication) +NetworkClientState _clients[MAX_CLIENTS]; +#define DEREF_CLIENT(i) (&_clients[i]) +// This returns the NetworkClientInfo from a NetworkClientState +#define DEREF_CLIENT_INFO(cs) (&_network_client_info[cs - _clients]) + +// Macros to make life a bit more easier +#define DEF_CLIENT_RECEIVE_COMMAND(type) NetworkRecvStatus NetworkPacketReceive_ ## type ## _command(Packet *p) +#define DEF_CLIENT_SEND_COMMAND(type) void NetworkPacketSend_ ## type ## _command(void) +#define DEF_CLIENT_SEND_COMMAND_PARAM(type) void NetworkPacketSend_ ## type ## _command +#define DEF_SERVER_RECEIVE_COMMAND(type) void NetworkPacketReceive_ ## type ## _command(NetworkClientState *cs, Packet *p) +#define DEF_SERVER_SEND_COMMAND(type) void NetworkPacketSend_ ## type ## _command(NetworkClientState *cs) +#define DEF_SERVER_SEND_COMMAND_PARAM(type) void NetworkPacketSend_ ## type ## _command + +#define SEND_COMMAND(type) NetworkPacketSend_ ## type ## _command +#define RECEIVE_COMMAND(type) NetworkPacketReceive_ ## type ## _command + +#define FOR_ALL_CLIENTS(cs) for (cs = _clients; cs != endof(_clients) && cs->socket != INVALID_SOCKET; cs++) +#define FOR_ALL_ACTIVE_CLIENT_INFOS(ci) for (ci = _network_client_info; ci != endof(_network_client_info); ci++) if (ci->client_index != NETWORK_EMPTY_INDEX) + +void NetworkExecuteCommand(CommandPacket *cp); +void NetworkAddCommandQueue(NetworkClientState *cs, CommandPacket *cp); + +// from network.c +void NetworkCloseClient(NetworkClientState *cs); +void CDECL NetworkTextMessage(NetworkAction action, uint16 color, bool self_send, const char *name, const char *str, ...); +void NetworkGetClientName(char *clientname, size_t size, const NetworkClientState *cs); +uint NetworkCalculateLag(const NetworkClientState *cs); +byte NetworkGetCurrentLanguageIndex(void); +NetworkClientInfo *NetworkFindClientInfoFromIndex(uint16 client_index); +NetworkClientInfo *NetworkFindClientInfoFromIP(const char *ip); +NetworkClientState *NetworkFindClientStateFromIndex(uint16 client_index); +unsigned long NetworkResolveHost(const char *hostname); +char* GetNetworkErrorMsg(char* buf, NetworkErrorCode err, const char* last); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_DATA_H */ diff --git a/src/network/network_gamelist.c b/src/network/network_gamelist.c new file mode 100644 index 000000000..a830073ad --- /dev/null +++ b/src/network/network_gamelist.c @@ -0,0 +1,74 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "../debug.h" +#include "network_data.h" +#include "../newgrf_config.h" + +// This file handles the GameList +// Also, it handles the request to a server for data about the server + +/** Add a new item to the linked gamelist. If the IP and Port match + * return the existing item instead of adding it again + * @param ip the IP-address (inet_addr) of the to-be added item + * @param port the port the server is running on + * @return a point to the newly added or already existing item */ +NetworkGameList *NetworkGameListAddItem(uint32 ip, uint16 port) +{ + NetworkGameList *item, *prev_item; + + prev_item = NULL; + for (item = _network_game_list; item != NULL; item = item->next) { + if (item->ip == ip && item->port == port) return item; + prev_item = item; + } + + item = malloc(sizeof(*item)); + memset(item, 0, sizeof(*item)); + item->next = NULL; + item->ip = ip; + item->port = port; + + if (prev_item == NULL) { + _network_game_list = item; + } else { + prev_item->next = item; + } + DEBUG(net, 4, "[gamelist] added server to list"); + + UpdateNetworkGameWindow(false); + + return item; +} + +/** Remove an item from the gamelist linked list + * @param remove pointer to the item to be removed */ +void NetworkGameListRemoveItem(NetworkGameList *remove) +{ + NetworkGameList *item, *prev_item; + + prev_item = NULL; + for (item = _network_game_list; item != NULL; item = item->next) { + if (remove == item) { + if (prev_item == NULL) { + _network_game_list = remove->next; + } else { + prev_item->next = remove->next; + } + + /* Remove GRFConfig information */ + ClearGRFConfigList(&remove->info.grfconfig); + free(remove); + remove = NULL; + + DEBUG(net, 4, "[gamelist] removed server from list"); + UpdateNetworkGameWindow(false); + return; + } + prev_item = item; + } +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_gamelist.h b/src/network/network_gamelist.h new file mode 100644 index 000000000..c1a1a0904 --- /dev/null +++ b/src/network/network_gamelist.h @@ -0,0 +1,11 @@ +/* $Id$ */ + +#ifndef NETWORK_GAMELIST_H +#define NETWORK_GAMELIST_H + +void NetworkGameListClear(void); +NetworkGameList *NetworkGameListAddItem(uint32 ip, uint16 port); +void NetworkGameListRemoveItem(NetworkGameList *remove); +void NetworkGameListAddQueriedItem(const NetworkGameInfo *info, bool server_online); + +#endif /* NETWORK_GAMELIST_H */ diff --git a/src/network/network_gui.c b/src/network/network_gui.c new file mode 100644 index 000000000..dd865118e --- /dev/null +++ b/src/network/network_gui.c @@ -0,0 +1,1706 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK +#include "../stdafx.h" +#include "../openttd.h" +#include "../string.h" +#include "../strings.h" +#include "../table/sprites.h" +#include "network.h" +#include "../date.h" + +#include "../fios.h" +#include "table/strings.h" +#include "../functions.h" +#include "network_data.h" +#include "network_client.h" +#include "network_gui.h" +#include "network_gamelist.h" +#include "../window.h" +#include "../gui.h" +#include "../gfx.h" +#include "../command.h" +#include "../variables.h" +#include "network_server.h" +#include "network_udp.h" +#include "../settings.h" +#include "../string.h" +#include "../town.h" +#include "../newgrf.h" + +#define BGC 5 +#define BTC 15 + +typedef struct network_d { + PlayerID company; // select company in network lobby + byte field; // select text-field in start-server and game-listing + NetworkGameList *server; // selected server in lobby and game-listing + FiosItem *map; // selected map in start-server +} network_d; +assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_d)); + +typedef struct network_ql_d { + network_d n; // see above; general stuff + querystr_d q; // text-input in start-server and game-listing + NetworkGameList **sort_list; // list of games (sorted) + list_d l; // accompanying list-administration +} network_ql_d; +assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(network_ql_d)); + +/* Global to remember sorting after window has been closed */ +static Listing _ng_sorting; + +static char _edit_str_buf[150]; +static bool _chat_tab_completion_active; + +static void ShowNetworkStartServerWindow(void); +static void ShowNetworkLobbyWindow(NetworkGameList *ngl); +extern void SwitchMode(int new_mode); + +static const StringID _connection_types_dropdown[] = { + STR_NETWORK_LAN_INTERNET, + STR_NETWORK_INTERNET_ADVERTISE, + INVALID_STRING_ID +}; + +static const StringID _lan_internet_types_dropdown[] = { + STR_NETWORK_LAN, + STR_NETWORK_INTERNET, + INVALID_STRING_ID +}; + +static const StringID _players_dropdown[] = { + STR_NETWORK_0_PLAYERS, + STR_NETWORK_1_PLAYERS, + STR_NETWORK_2_PLAYERS, + STR_NETWORK_3_PLAYERS, + STR_NETWORK_4_PLAYERS, + STR_NETWORK_5_PLAYERS, + STR_NETWORK_6_PLAYERS, + STR_NETWORK_7_PLAYERS, + STR_NETWORK_8_PLAYERS, + STR_NETWORK_9_PLAYERS, + STR_NETWORK_10_PLAYERS, + INVALID_STRING_ID +}; + +static const StringID _language_dropdown[] = { + STR_NETWORK_LANG_ANY, + STR_NETWORK_LANG_ENGLISH, + STR_NETWORK_LANG_GERMAN, + STR_NETWORK_LANG_FRENCH, + INVALID_STRING_ID +}; + +enum { + NET_PRC__OFFSET_TOP_WIDGET = 54, + NET_PRC__OFFSET_TOP_WIDGET_COMPANY = 52, + NET_PRC__SIZE_OF_ROW = 14, +}; + +/** Update the network new window because a new server is + * found on the network. + * @param unselect unselect the currently selected item */ +void UpdateNetworkGameWindow(bool unselect) +{ + SendWindowMessage(WC_NETWORK_WINDOW, 0, unselect, 0, 0); +} + +static bool _internal_sort_order; // Used for Qsort order-flipping +typedef int CDECL NGameNameSortFunction(const void*, const void*); + +/** Qsort function to sort by name. */ +static int CDECL NGameNameSorter(const void *a, const void *b) +{ + const NetworkGameList *cmp1 = *(const NetworkGameList**)a; + const NetworkGameList *cmp2 = *(const NetworkGameList**)b; + int r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); + + return _internal_sort_order ? -r : r; +} + +/** Qsort function to sort by the amount of clients online on a + * server. If the two servers have the same amount, the one with the + * higher maximum is preferred. */ +static int CDECL NGameClientSorter(const void *a, const void *b) +{ + const NetworkGameList *cmp1 = *(const NetworkGameList**)a; + const NetworkGameList *cmp2 = *(const NetworkGameList**)b; + /* Reverse as per default we are interested in most-clients first */ + int r = cmp1->info.clients_on - cmp2->info.clients_on; + + if (r == 0) r = cmp1->info.clients_max - cmp2->info.clients_max; + if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); + + return _internal_sort_order ? -r : r; +} + +/** Qsort function to sort by joinability. If both servers are the + * same, prefer the non-passworded server first. */ +static int CDECL NGameAllowedSorter(const void *a, const void *b) +{ + const NetworkGameList *cmp1 = *(const NetworkGameList**)a; + const NetworkGameList *cmp2 = *(const NetworkGameList**)b; + /* Reverse default as we are interested in compatible clients first */ + int r = cmp2->info.compatible - cmp1->info.compatible; + + if (r == 0) r = cmp1->info.use_password - cmp2->info.use_password; + if (r == 0) r = strcasecmp(cmp1->info.server_name, cmp2->info.server_name); + + return _internal_sort_order ? -r : r; +} + +/** (Re)build the network game list as its amount has changed because + * an item has been added or deleted for example + * @param ngl list_d struct that contains all necessary information for sorting */ +static void BuildNetworkGameList(network_ql_d *nqld) +{ + NetworkGameList *ngl_temp; + uint n = 0; + + if (!(nqld->l.flags & VL_REBUILD)) return; + + /* Count the number of games in the list */ + for (ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) n++; + if (n == 0) return; + + /* Create temporary array of games to use for listing */ + free(nqld->sort_list); + nqld->sort_list = malloc(n * sizeof(nqld->sort_list[0])); + if (nqld->sort_list == NULL) error("Could not allocate memory for the network-game-sorting-list"); + nqld->l.list_length = n; + + for (n = 0, ngl_temp = _network_game_list; ngl_temp != NULL; ngl_temp = ngl_temp->next) { + nqld->sort_list[n++] = ngl_temp; + } + + /* Force resort */ + nqld->l.flags &= ~VL_REBUILD; + nqld->l.flags |= VL_RESORT; +} + +static void SortNetworkGameList(network_ql_d *nqld) +{ + static NGameNameSortFunction * const ngame_sorter[] = { + &NGameNameSorter, + &NGameClientSorter, + &NGameAllowedSorter + }; + + NetworkGameList *item; + uint i; + + if (!(nqld->l.flags & VL_RESORT)) return; + if (nqld->l.list_length == 0) return; + + _internal_sort_order = !!(nqld->l.flags & VL_DESC); + qsort(nqld->sort_list, nqld->l.list_length, sizeof(nqld->sort_list[0]), ngame_sorter[nqld->l.sort_type]); + + /* After sorting ngl->sort_list contains the sorted items. Put these back + * into the original list. Basically nothing has changed, we are only + * shuffling the ->next pointers */ + _network_game_list = nqld->sort_list[0]; + for (item = _network_game_list, i = 1; i != nqld->l.list_length; i++) { + item->next = nqld->sort_list[i]; + item = item->next; + } + item->next = NULL; + + nqld->l.flags &= ~VL_RESORT; +} + +/* Uses network_ql_d (network_d, querystr_d and list_d) WP macro */ +static void NetworkGameWindowWndProc(Window *w, WindowEvent *e) +{ + network_d *nd = &WP(w, network_ql_d).n; + list_d *ld = &WP(w, network_ql_d).l; + + switch (e->event) { + case WE_CREATE: /* Focus input box */ + nd->field = 3; + nd->server = NULL; + + WP(w, network_ql_d).sort_list = NULL; + ld->flags = VL_REBUILD | (_ng_sorting.order << (VL_DESC - 1)); + ld->sort_type = _ng_sorting.criteria; + break; + + case WE_PAINT: { + const NetworkGameList *sel = nd->server; + const char *arrow = (ld->flags & VL_DESC) ? DOWNARROW : UPARROW; + + if (ld->flags & VL_REBUILD) { + BuildNetworkGameList(&WP(w, network_ql_d)); + SetVScrollCount(w, ld->list_length); + } + if (ld->flags & VL_RESORT) SortNetworkGameList(&WP(w, network_ql_d)); + + SetWindowWidgetDisabledState(w, 17, sel == NULL); + /* Join Button disabling conditions */ + SetWindowWidgetDisabledState(w, 16, sel == NULL || // no Selected Server + !sel->online || // Server offline + sel->info.clients_on >= sel->info.clients_max || // Server full + !sel->info.compatible); // Revision mismatch + + SetWindowWidgetHiddenState(w, 18, sel == NULL || + !sel->online || + sel->info.grfconfig == NULL); + + SetDParam(0, 0x00); + SetDParam(7, _lan_internet_types_dropdown[_network_lan_internet]); + DrawWindowWidgets(w); + + DrawEditBox(w, &WP(w, network_ql_d).q, 3); + + DrawString(9, 23, STR_NETWORK_CONNECTION, 2); + DrawString(210, 23, STR_NETWORK_PLAYER_NAME, 2); + + /* Sort based on widgets: name, clients, compatibility */ + switch (ld->sort_type) { + case 6 - 6: DoDrawString(arrow, w->widget[6].right - 10, 42, 0x10); break; + case 7 - 6: DoDrawString(arrow, w->widget[7].right - 10, 42, 0x10); break; + case 8 - 6: DoDrawString(arrow, w->widget[8].right - 10, 42, 0x10); break; + } + + { // draw list of games + uint16 y = NET_PRC__OFFSET_TOP_WIDGET + 3; + int32 n = 0; + int32 pos = w->vscroll.pos; + uint max_name_width = w->widget[6].right - w->widget[6].left - 5; + const NetworkGameList *cur_item = _network_game_list; + + while (pos > 0 && cur_item != NULL) { + pos--; + cur_item = cur_item->next; + } + + while (cur_item != NULL) { + // show highlighted item with a different colour + if (cur_item == sel) GfxFillRect(w->widget[6].left + 1, y - 2, w->widget[8].right - 1, y + 9, 10); + + SetDParamStr(0, cur_item->info.server_name); + DrawStringTruncated(w->widget[6].left + 5, y, STR_02BD, 16, max_name_width); + + SetDParam(0, cur_item->info.clients_on); + SetDParam(1, cur_item->info.clients_max); + SetDParam(2, cur_item->info.companies_on); + SetDParam(3, cur_item->info.companies_max); + DrawStringCentered(210, y, STR_NETWORK_GENERAL_ONLINE, 2); + + // only draw icons if the server is online + if (cur_item->online) { + // draw a lock if the server is password protected. + if (cur_item->info.use_password) DrawSprite(SPR_LOCK, w->widget[8].left + 5, y - 1); + + // draw red or green icon, depending on compatibility with server. + DrawSprite(SPR_BLOT | (cur_item->info.compatible ? PALETTE_TO_GREEN : (cur_item->info.version_compatible ? PALETTE_TO_YELLOW : PALETTE_TO_RED)), w->widget[8].left + 15, y); + + // draw flag according to server language + DrawSprite(SPR_FLAGS_BASE + cur_item->info.server_lang, w->widget[8].left + 25, y); + } + + cur_item = cur_item->next; + y += NET_PRC__SIZE_OF_ROW; + if (++n == w->vscroll.cap) break; // max number of games in the window + } + } + + /* Draw the right menu */ + GfxFillRect(311, 43, 539, 92, 157); + if (sel == NULL) { + DrawStringCentered(425, 58, STR_NETWORK_GAME_INFO, 0); + } else if (!sel->online) { + SetDParamStr(0, sel->info.server_name); + DrawStringCentered(425, 68, STR_ORANGE, 0); // game name + + DrawStringCentered(425, 132, STR_NETWORK_SERVER_OFFLINE, 0); // server offline + } else { // show game info + uint16 y = 100; + const uint16 x = w->widget[15].left + 5; + + DrawStringCentered(425, 48, STR_NETWORK_GAME_INFO, 0); + + + SetDParamStr(0, sel->info.server_name); + DrawStringCenteredTruncated(w->widget[15].left, w->widget[15].right, 62, STR_ORANGE, 16); // game name + + SetDParamStr(0, sel->info.map_name); + DrawStringCenteredTruncated(w->widget[15].left, w->widget[15].right, 74, STR_02BD, 16); // map name + + SetDParam(0, sel->info.clients_on); + SetDParam(1, sel->info.clients_max); + SetDParam(2, sel->info.companies_on); + SetDParam(3, sel->info.companies_max); + DrawString(x, y, STR_NETWORK_CLIENTS, 2); + y += 10; + + SetDParam(0, _language_dropdown[sel->info.server_lang]); + DrawString(x, y, STR_NETWORK_LANGUAGE, 2); // server language + y += 10; + + SetDParam(0, STR_TEMPERATE_LANDSCAPE + sel->info.map_set); + DrawString(x, y, STR_NETWORK_TILESET, 2); // tileset + y += 10; + + SetDParam(0, sel->info.map_width); + SetDParam(1, sel->info.map_height); + DrawString(x, y, STR_NETWORK_MAP_SIZE, 2); // map size + y += 10; + + SetDParamStr(0, sel->info.server_revision); + DrawString(x, y, STR_NETWORK_SERVER_VERSION, 2); // server version + y += 10; + + SetDParamStr(0, sel->info.hostname); + SetDParam(1, sel->port); + DrawString(x, y, STR_NETWORK_SERVER_ADDRESS, 2); // server address + y += 10; + + SetDParam(0, sel->info.start_date); + DrawString(x, y, STR_NETWORK_START_DATE, 2); // start date + y += 10; + + SetDParam(0, sel->info.game_date); + DrawString(x, y, STR_NETWORK_CURRENT_DATE, 2); // current date + y += 10; + + y += 2; + + if (!sel->info.compatible) { + DrawStringCentered(425, y, sel->info.version_compatible ? STR_NETWORK_GRF_MISMATCH : STR_NETWORK_VERSION_MISMATCH, 0); // server mismatch + } else if (sel->info.clients_on == sel->info.clients_max) { + // Show: server full, when clients_on == clients_max + DrawStringCentered(425, y, STR_NETWORK_SERVER_FULL, 0); // server full + } else if (sel->info.use_password) { + DrawStringCentered(425, y, STR_NETWORK_PASSWORD, 0); // password warning + } + + y += 10; + } + } break; + + case WE_CLICK: + nd->field = e->we.click.widget; + switch (e->we.click.widget) { + case 0: case 14: /* Close 'X' | Cancel button */ + DeleteWindowById(WC_NETWORK_WINDOW, 0); + break; + case 4: case 5: + ShowDropDownMenu(w, _lan_internet_types_dropdown, _network_lan_internet, 5, 0, 0); // do it for widget 5 + break; + case 6: /* Sort by name */ + case 7: /* Sort by connected clients */ + case 8: /* Connectivity (green dot) */ + if (ld->sort_type == e->we.click.widget - 6) ld->flags ^= VL_DESC; + ld->flags |= VL_RESORT; + ld->sort_type = e->we.click.widget - 6; + + _ng_sorting.order = !!(ld->flags & VL_DESC); + _ng_sorting.criteria = ld->sort_type; + SetWindowDirty(w); + break; + case 9: { /* Matrix to show networkgames */ + NetworkGameList *cur_item; + uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET) / NET_PRC__SIZE_OF_ROW; + + if (id_v >= w->vscroll.cap) return; // click out of bounds + id_v += w->vscroll.pos; + + cur_item = _network_game_list; + for (; id_v > 0 && cur_item != NULL; id_v--) cur_item = cur_item->next; + + nd->server = cur_item; + SetWindowDirty(w); + } break; + case 11: /* Find server automatically */ + switch (_network_lan_internet) { + case 0: NetworkUDPSearchGame(); break; + case 1: NetworkUDPQueryMasterServer(); break; + } + break; + case 12: { // Add a server + ShowQueryString( + BindCString(_network_default_ip), + STR_NETWORK_ENTER_IP, + 31 | 0x1000, // maximum number of characters OR + 250, // characters up to this width pixels, whichever is satisfied first + w, CS_ALPHANUMERAL); + } break; + case 13: /* Start server */ + ShowNetworkStartServerWindow(); + break; + case 16: /* Join Game */ + if (nd->server != NULL) { + snprintf(_network_last_host, sizeof(_network_last_host), "%s", inet_ntoa(*(struct in_addr *)&nd->server->ip)); + _network_last_port = nd->server->port; + ShowNetworkLobbyWindow(nd->server); + } + break; + case 17: // Refresh + if (nd->server != NULL) + NetworkQueryServer(nd->server->info.hostname, nd->server->port, true); + break; + case 18: // NewGRF Settings + if (nd->server != NULL) ShowNewGRFSettings(false, false, false, &nd->server->info.grfconfig); + break; + + } break; + + case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ + switch (e->we.dropdown.button) { + case 5: + _network_lan_internet = e->we.dropdown.index; + break; + } + + SetWindowDirty(w); + break; + + case WE_MOUSELOOP: + if (nd->field == 3) HandleEditBox(w, &WP(w, network_ql_d).q, 3); + break; + + case WE_MESSAGE: + if (e->we.message.msg != 0) nd->server = NULL; + ld->flags |= VL_REBUILD; + SetWindowDirty(w); + break; + + case WE_KEYPRESS: + if (nd->field != 3) { + if (nd->server != NULL) { + if (e->we.keypress.keycode == WKC_DELETE) { /* Press 'delete' to remove servers */ + NetworkGameListRemoveItem(nd->server); + NetworkRebuildHostList(); + nd->server = NULL; + } + } + break; + } + + if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, 3, e) == 1) break; // enter pressed + + // The name is only allowed when it starts with a letter! + if (_edit_str_buf[0] != '\0' && _edit_str_buf[0] != ' ') { + ttd_strlcpy(_network_player_name, _edit_str_buf, lengthof(_network_player_name)); + } else { + ttd_strlcpy(_network_player_name, "Player", lengthof(_network_player_name)); + } + + break; + + case WE_ON_EDIT_TEXT: + NetworkAddServer(e->we.edittext.str); + NetworkRebuildHostList(); + break; + + case WE_DESTROY: /* Nicely clean up the sort-list */ + free(WP(w, network_ql_d).sort_list); + break; + } +} + +static const Widget _network_game_window_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, BGC, 11, 549, 0, 13, STR_NETWORK_MULTIPLAYER, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, BGC, 0, 549, 14, 263, 0x0, STR_NULL}, + +/* LEFT SIDE */ +{ WWT_PANEL, RESIZE_NONE, BGC, 310, 461, 22, 33, 0x0, STR_NETWORK_ENTER_NAME_TIP}, + +{ WWT_INSET, RESIZE_NONE, BGC, 90, 181, 22, 33, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 170, 180, 23, 32, STR_0225, STR_NETWORK_CONNECTION_TIP}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 170, 42, 53, STR_NETWORK_GAME_NAME, STR_NETWORK_GAME_NAME_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 171, 250, 42, 53, STR_NETWORK_CLIENTS_CAPTION, STR_NETWORK_CLIENTS_CAPTION_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 251, 290, 42, 53, STR_EMPTY, STR_NETWORK_INFO_ICONS_TIP}, + +{ WWT_MATRIX, RESIZE_NONE, BGC, 10, 290, 54, 236, (13 << 8) + 1, STR_NETWORK_CLICK_GAME_TO_SELECT}, +{ WWT_SCROLLBAR, RESIZE_NONE, BGC, 291, 302, 42, 236, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 30, 130, 246, 257, STR_NETWORK_FIND_SERVER, STR_NETWORK_FIND_SERVER_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 180, 280, 246, 257, STR_NETWORK_ADD_SERVER, STR_NETWORK_ADD_SERVER_TIP}, + +/* RIGHT SIDE */ +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 315, 415, 246, 257, STR_NETWORK_START_SERVER, STR_NETWORK_START_SERVER_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 246, 257, STR_012E_CANCEL, STR_NULL}, + +{ WWT_PANEL, RESIZE_NONE, BGC, 310, 540, 42, 236, 0x0, STR_NULL}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 315, 415, 215, 226, STR_NETWORK_JOIN_GAME, STR_NULL}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 430, 535, 197, 208, STR_NEWGRF_SETTINGS_BUTTON, STR_NULL}, + +{ WIDGETS_END}, +}; + +static const WindowDesc _network_game_window_desc = { + WDP_CENTER, WDP_CENTER, 550, 264, + WC_NETWORK_WINDOW,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _network_game_window_widgets, + NetworkGameWindowWndProc, +}; + +void ShowNetworkGameWindow(void) +{ + static bool first = true; + Window *w; + DeleteWindowById(WC_NETWORK_WINDOW, 0); + + /* Only show once */ + if (first) { + char* const *srv; + + first = false; + // add all servers from the config file to our list + for (srv = &_network_host_list[0]; srv != endof(_network_host_list) && *srv != NULL; srv++) { + NetworkAddServer(*srv); + } + + _ng_sorting.criteria = 2; // sort default by collectivity (green-dots on top) + _ng_sorting.order = 0; // sort ascending by default + } + + w = AllocateWindowDesc(&_network_game_window_desc); + if (w != NULL) { + querystr_d *querystr = &WP(w, network_ql_d).q; + + ttd_strlcpy(_edit_str_buf, _network_player_name, lengthof(_edit_str_buf)); + w->vscroll.cap = 13; + + querystr->afilter = CS_ALPHANUMERAL; + InitializeTextBuffer(&querystr->text, _edit_str_buf, lengthof(_edit_str_buf), 120); + + UpdateNetworkGameWindow(true); + } +} + +enum { + NSSWND_START = 64, + NSSWND_ROWSIZE = 12 +}; + +/* Uses network_ql_d (network_d, querystr_d and list_d) WP macro */ +static void NetworkStartServerWindowWndProc(Window *w, WindowEvent *e) +{ + network_d *nd = &WP(w, network_ql_d).n; + + switch (e->event) { + case WE_CREATE: /* focus input box */ + nd->field = 3; + _network_game_info.use_password = (_network_server_password[0] != '\0'); + break; + + case WE_PAINT: { + int y = NSSWND_START, pos; + const FiosItem *item; + + SetDParam( 7, _connection_types_dropdown[_network_advertise]); + SetDParam( 9, _players_dropdown[_network_game_info.clients_max]); + SetDParam(11, _players_dropdown[_network_game_info.companies_max]); + SetDParam(13, _players_dropdown[_network_game_info.spectators_max]); + SetDParam(15, _language_dropdown[_network_game_info.server_lang]); + DrawWindowWidgets(w); + + GfxFillRect(11, 63, 258, 215, 0xD7); + DrawEditBox(w, &WP(w, network_ql_d).q, 3); + + DrawString(10, 22, STR_NETWORK_NEW_GAME_NAME, 2); + + DrawString(10, 43, STR_NETWORK_SELECT_MAP, 2); + + DrawString(280, 63, STR_NETWORK_CONNECTION, 2); + DrawString(280, 95, STR_NETWORK_NUMBER_OF_CLIENTS, 2); + DrawString(280, 127, STR_NETWORK_NUMBER_OF_COMPANIES, 2); + DrawString(280, 159, STR_NETWORK_NUMBER_OF_SPECTATORS, 2); + DrawString(280, 191, STR_NETWORK_LANGUAGE_SPOKEN, 2); + + if (_network_game_info.use_password) DoDrawString("*", 408, 23, 3); + + // draw list of maps + pos = w->vscroll.pos; + while (pos < _fios_num + 1) { + item = _fios_list + pos - 1; + if (item == nd->map || (pos == 0 && nd->map == NULL)) + GfxFillRect(11, y - 1, 258, y + 10, 155); // show highlighted item with a different colour + + if (pos == 0) { + DrawString(14, y, STR_4010_GENERATE_RANDOM_NEW_GAME, 9); + } else { + DoDrawString(item->title, 14, y, _fios_colors[item->type] ); + } + pos++; + y += NSSWND_ROWSIZE; + + if (y >= w->vscroll.cap * NSSWND_ROWSIZE + NSSWND_START) break; + } + } break; + + case WE_CLICK: + nd->field = e->we.click.widget; + switch (e->we.click.widget) { + case 0: /* Close 'X' */ + case 19: /* Cancel button */ + ShowNetworkGameWindow(); + break; + + case 4: /* Set password button */ + ShowQueryString(BindCString(_network_server_password), STR_NETWORK_SET_PASSWORD, 20, 250, w, CS_ALPHANUMERAL); + break; + + case 5: { /* Select map */ + int y = (e->we.click.pt.y - NSSWND_START) / NSSWND_ROWSIZE; + + y += w->vscroll.pos; + if (y >= w->vscroll.count) return; + + nd->map = (y == 0) ? NULL : _fios_list + y - 1; + SetWindowDirty(w); + } break; + case 7: case 8: /* Connection type */ + ShowDropDownMenu(w, _connection_types_dropdown, _network_advertise, 8, 0, 0); // do it for widget 8 + break; + case 9: case 10: /* Number of Players (hide 0 and 1 players) */ + ShowDropDownMenu(w, _players_dropdown, _network_game_info.clients_max, 10, 0, 3); + break; + case 11: case 12: /* Number of Companies (hide 0, 9 and 10 companies; max is 8) */ + ShowDropDownMenu(w, _players_dropdown, _network_game_info.companies_max, 12, 0, 1537); + break; + case 13: case 14: /* Number of Spectators */ + ShowDropDownMenu(w, _players_dropdown, _network_game_info.spectators_max, 14, 0, 0); + break; + case 15: case 16: /* Language */ + ShowDropDownMenu(w, _language_dropdown, _network_game_info.server_lang, 16, 0, 0); + break; + case 17: /* Start game */ + _is_network_server = true; + + if (nd->map == NULL) { // start random new game + ShowGenerateLandscape(); + } else { // load a scenario + char *name = FiosBrowseTo(nd->map); + if (name != NULL) { + SetFiosType(nd->map->type); + ttd_strlcpy(_file_to_saveload.name, name, sizeof(_file_to_saveload.name)); + ttd_strlcpy(_file_to_saveload.title, nd->map->title, sizeof(_file_to_saveload.title)); + + DeleteWindow(w); + SwitchMode(SM_START_SCENARIO); + } + } + break; + case 18: /* Load game */ + _is_network_server = true; + /* XXX - WC_NETWORK_WINDOW should stay, but if it stays, it gets + * copied all the elements of 'load game' and upon closing that, it segfaults */ + DeleteWindowById(WC_NETWORK_WINDOW, 0); + ShowSaveLoadDialog(SLD_LOAD_GAME); + break; + } + break; + + case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ + switch (e->we.dropdown.button) { + case 8: _network_advertise = (e->we.dropdown.index != 0); break; + case 10: _network_game_info.clients_max = e->we.dropdown.index; break; + case 12: _network_game_info.companies_max = e->we.dropdown.index; break; + case 14: _network_game_info.spectators_max = e->we.dropdown.index; break; + case 16: _network_game_info.server_lang = e->we.dropdown.index; break; + } + + SetWindowDirty(w); + break; + + case WE_MOUSELOOP: + if (nd->field == 3) HandleEditBox(w, &WP(w, network_ql_d).q, 3); + break; + + case WE_KEYPRESS: + if (nd->field == 3) { + if (HandleEditBoxKey(w, &WP(w, network_ql_d).q, 3, e) == 1) break; // enter pressed + + ttd_strlcpy(_network_server_name, WP(w, network_ql_d).q.text.buf, sizeof(_network_server_name)); + UpdateTextBufferSize(&WP(w, network_ql_d).q.text); + } + break; + + case WE_ON_EDIT_TEXT: { + ttd_strlcpy(_network_server_password, e->we.edittext.str, lengthof(_network_server_password)); + _network_game_info.use_password = (_network_server_password[0] != '\0'); + SetWindowDirty(w); + } break; + } +} + +static const Widget _network_start_server_window_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_START_GAME_WINDOW, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 243, 0x0, STR_NULL}, + +{ WWT_PANEL, RESIZE_NONE, BGC, 100, 272, 22, 33, 0x0, STR_NETWORK_NEW_GAME_NAME_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 285, 405, 22, 33, STR_NETWORK_SET_PASSWORD, STR_NETWORK_PASSWORD_TIP}, + +{ WWT_INSET, RESIZE_NONE, BGC, 10, 271, 62, 216, 0x0, STR_NETWORK_SELECT_MAP_TIP}, +{ WWT_SCROLLBAR, RESIZE_NONE, BGC, 259, 270, 63, 215, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +/* Combo boxes to control Connection Type / Max Clients / Max Companies / Max Observers / Language */ +{ WWT_INSET, RESIZE_NONE, BGC, 280, 410, 77, 88, STR_NETWORK_COMBO1, STR_NETWORK_CONNECTION_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 78, 87, STR_0225, STR_NETWORK_CONNECTION_TIP}, +{ WWT_INSET, RESIZE_NONE, BGC, 280, 410, 109, 120, STR_NETWORK_COMBO2, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 110, 119, STR_0225, STR_NETWORK_NUMBER_OF_CLIENTS_TIP}, +{ WWT_INSET, RESIZE_NONE, BGC, 280, 410, 141, 152, STR_NETWORK_COMBO3, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 142, 151, STR_0225, STR_NETWORK_NUMBER_OF_COMPANIES_TIP}, +{ WWT_INSET, RESIZE_NONE, BGC, 280, 410, 173, 184, STR_NETWORK_COMBO4, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 174, 183, STR_0225, STR_NETWORK_NUMBER_OF_SPECTATORS_TIP}, +{ WWT_INSET, RESIZE_NONE, BGC, 280, 410, 205, 216, STR_NETWORK_COMBO5, STR_NETWORK_LANGUAGE_TIP}, +{ WWT_TEXTBTN, RESIZE_NONE, BGC, 399, 409, 206, 215, STR_0225, STR_NETWORK_LANGUAGE_TIP}, + +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 40, 140, 224, 235, STR_NETWORK_START_GAME, STR_NETWORK_START_GAME_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 150, 250, 224, 235, STR_NETWORK_LOAD_GAME, STR_NETWORK_LOAD_GAME_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 260, 360, 224, 235, STR_012E_CANCEL, STR_NULL}, +{ WIDGETS_END}, +}; + +static const WindowDesc _network_start_server_window_desc = { + WDP_CENTER, WDP_CENTER, 420, 244, + WC_NETWORK_WINDOW,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _network_start_server_window_widgets, + NetworkStartServerWindowWndProc, +}; + +static void ShowNetworkStartServerWindow(void) +{ + Window *w; + DeleteWindowById(WC_NETWORK_WINDOW, 0); + + w = AllocateWindowDesc(&_network_start_server_window_desc); + ttd_strlcpy(_edit_str_buf, _network_server_name, lengthof(_edit_str_buf)); + + _saveload_mode = SLD_NEW_GAME; + BuildFileList(); + w->vscroll.cap = 12; + w->vscroll.count = _fios_num+1; + + WP(w, network_ql_d).q.afilter = CS_ALPHANUMERAL; + InitializeTextBuffer(&WP(w, network_ql_d).q.text, _edit_str_buf, lengthof(_edit_str_buf), 160); +} + +static byte NetworkLobbyFindCompanyIndex(byte pos) +{ + byte i; + + /* Scroll through all _network_player_info and get the 'pos' item + that is not empty */ + for (i = 0; i < MAX_PLAYERS; i++) { + if (_network_player_info[i].company_name[0] != '\0') { + if (pos-- == 0) return i; + } + } + + return 0; +} + +/* uses network_d WP macro */ +static void NetworkLobbyWindowWndProc(Window *w, WindowEvent *e) +{ + network_d *nd = &WP(w, network_d); + + switch (e->event) { + case WE_CREATE: + nd->company = (byte)-1; + break; + + case WE_PAINT: { + const NetworkGameInfo *gi = &nd->server->info; + int y = NET_PRC__OFFSET_TOP_WIDGET_COMPANY, pos; + + SetWindowWidgetDisabledState(w, 7, nd->company == (byte)-1); + SetWindowWidgetDisabledState(w, 8, gi->companies_on >= gi->companies_max); + /* You can not join a server as spectator when it has no companies active.. + * it causes some nasty crashes */ + SetWindowWidgetDisabledState(w, 9, gi->spectators_on >= gi->spectators_max || + gi->companies_on == 0); + + DrawWindowWidgets(w); + + SetDParamStr(0, gi->server_name); + DrawString(10, 22, STR_NETWORK_PREPARE_TO_JOIN, 2); + + /* Draw company list */ + pos = w->vscroll.pos; + while (pos < gi->companies_on) { + byte company = NetworkLobbyFindCompanyIndex(pos); + bool income = false; + if (nd->company == company) + GfxFillRect(11, y - 1, 154, y + 10, 10); // show highlighted item with a different colour + + DoDrawStringTruncated(_network_player_info[company].company_name, 13, y, 16, 135 - 13); + if (_network_player_info[company].use_password != 0) DrawSprite(SPR_LOCK, 135, y); + + /* If the company's income was positive puts a green dot else a red dot */ + if (_network_player_info[company].income >= 0) income = true; + DrawSprite(SPR_BLOT | (income ? PALETTE_TO_GREEN : PALETTE_TO_RED), 145, y); + + pos++; + y += NET_PRC__SIZE_OF_ROW; + if (pos >= w->vscroll.cap) break; + } + + /* Draw info about selected company when it is selected in the left window */ + GfxFillRect(174, 39, 403, 75, 157); + DrawStringCentered(290, 50, STR_NETWORK_COMPANY_INFO, 0); + if (nd->company != (byte)-1) { + const uint x = 183; + const uint trunc_width = w->widget[6].right - x; + y = 80; + + SetDParam(0, nd->server->info.clients_on); + SetDParam(1, nd->server->info.clients_max); + SetDParam(2, nd->server->info.companies_on); + SetDParam(3, nd->server->info.companies_max); + DrawString(x, y, STR_NETWORK_CLIENTS, 2); + y += 10; + + SetDParamStr(0, _network_player_info[nd->company].company_name); + DrawStringTruncated(x, y, STR_NETWORK_COMPANY_NAME, 2, trunc_width); + y += 10; + + SetDParam(0, _network_player_info[nd->company].inaugurated_year); + DrawString(x, y, STR_NETWORK_INAUGURATION_YEAR, 2); // inauguration year + y += 10; + + SetDParam64(0, _network_player_info[nd->company].company_value); + DrawString(x, y, STR_NETWORK_VALUE, 2); // company value + y += 10; + + SetDParam64(0, _network_player_info[nd->company].money); + DrawString(x, y, STR_NETWORK_CURRENT_BALANCE, 2); // current balance + y += 10; + + SetDParam64(0, _network_player_info[nd->company].income); + DrawString(x, y, STR_NETWORK_LAST_YEARS_INCOME, 2); // last year's income + y += 10; + + SetDParam(0, _network_player_info[nd->company].performance); + DrawString(x, y, STR_NETWORK_PERFORMANCE, 2); // performance + y += 10; + + SetDParam(0, _network_player_info[nd->company].num_vehicle[0]); + SetDParam(1, _network_player_info[nd->company].num_vehicle[1]); + SetDParam(2, _network_player_info[nd->company].num_vehicle[2]); + SetDParam(3, _network_player_info[nd->company].num_vehicle[3]); + SetDParam(4, _network_player_info[nd->company].num_vehicle[4]); + DrawString(x, y, STR_NETWORK_VEHICLES, 2); // vehicles + y += 10; + + SetDParam(0, _network_player_info[nd->company].num_station[0]); + SetDParam(1, _network_player_info[nd->company].num_station[1]); + SetDParam(2, _network_player_info[nd->company].num_station[2]); + SetDParam(3, _network_player_info[nd->company].num_station[3]); + SetDParam(4, _network_player_info[nd->company].num_station[4]); + DrawString(x, y, STR_NETWORK_STATIONS, 2); // stations + y += 10; + + SetDParamStr(0, _network_player_info[nd->company].players); + DrawStringTruncated(x, y, STR_NETWORK_PLAYERS, 2, trunc_width); // players + } + } break; + + case WE_CLICK: + switch (e->we.click.widget) { + case 0: case 11: /* Close 'X' | Cancel button */ + ShowNetworkGameWindow(); + break; + case 4: { /* Company list */ + uint32 id_v = (e->we.click.pt.y - NET_PRC__OFFSET_TOP_WIDGET_COMPANY) / NET_PRC__SIZE_OF_ROW; + + if (id_v >= w->vscroll.cap) return; + + id_v += w->vscroll.pos; + nd->company = (id_v >= nd->server->info.companies_on) ? (byte)-1 : NetworkLobbyFindCompanyIndex(id_v); + SetWindowDirty(w); + } break; + case 7: /* Join company */ + if (nd->company != (byte)-1) { + _network_playas = nd->company; + NetworkClientConnectGame(_network_last_host, _network_last_port); + } + break; + case 8: /* New company */ + _network_playas = PLAYER_NEW_COMPANY; + NetworkClientConnectGame(_network_last_host, _network_last_port); + break; + case 9: /* Spectate game */ + _network_playas = PLAYER_SPECTATOR; + NetworkClientConnectGame(_network_last_host, _network_last_port); + break; + case 10: /* Refresh */ + NetworkQueryServer(_network_last_host, _network_last_port, false); // company info + NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data + break; + } break; + + case WE_MESSAGE: + SetWindowDirty(w); + break; + } +} + +static const Widget _network_lobby_window_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, BGC, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, RESIZE_NONE, BGC, 11, 419, 0, 13, STR_NETWORK_GAME_LOBBY, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, BGC, 0, 419, 14, 234, 0x0, STR_NULL}, + +// company list +{ WWT_PANEL, RESIZE_NONE, BTC, 10, 155, 38, 49, 0x0, STR_NULL}, +{ WWT_MATRIX, RESIZE_NONE, BGC, 10, 155, 50, 190, (10 << 8) + 1, STR_NETWORK_COMPANY_LIST_TIP}, +{ WWT_SCROLLBAR, RESIZE_NONE, BGC, 156, 167, 38, 190, STR_NULL, STR_0190_SCROLL_BAR_SCROLLS_LIST}, + +// company/player info +{ WWT_PANEL, RESIZE_NONE, BGC, 173, 404, 38, 190, 0x0, STR_NULL}, + +// buttons +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 200, 211, STR_NETWORK_JOIN_COMPANY, STR_NETWORK_JOIN_COMPANY_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 10, 151, 215, 226, STR_NETWORK_NEW_COMPANY, STR_NETWORK_NEW_COMPANY_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 200, 211, STR_NETWORK_SPECTATE_GAME, STR_NETWORK_SPECTATE_GAME_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 158, 268, 215, 226, STR_NETWORK_REFRESH, STR_NETWORK_REFRESH_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 278, 388, 200, 211, STR_012E_CANCEL, STR_NULL}, + +{ WIDGETS_END}, +}; + +static const WindowDesc _network_lobby_window_desc = { + WDP_CENTER, WDP_CENTER, 420, 235, + WC_NETWORK_WINDOW,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _network_lobby_window_widgets, + NetworkLobbyWindowWndProc, +}; + +/* Show the networklobbywindow with the selected server + * @param ngl Selected game pointer which is passed to the new window */ +static void ShowNetworkLobbyWindow(NetworkGameList *ngl) +{ + Window *w; + DeleteWindowById(WC_NETWORK_WINDOW, 0); + + NetworkQueryServer(_network_last_host, _network_last_port, false); // company info + NetworkUDPQueryServer(_network_last_host, _network_last_port); // general data + + w = AllocateWindowDesc(&_network_lobby_window_desc); + if (w != NULL) { + WP(w, network_ql_d).n.server = ngl; + strcpy(_edit_str_buf, ""); + w->vscroll.cap = 10; + } +} + +// The window below gives information about the connected clients +// and also makes able to give money to them, kick them (if server) +// and stuff like that. + +extern void DrawPlayerIcon(PlayerID pid, int x, int y); + +// Every action must be of this form +typedef void ClientList_Action_Proc(byte client_no); + +// Max 10 actions per client +#define MAX_CLIENTLIST_ACTION 10 + +// Some standard bullshit.. defines variables ;) +static void ClientListWndProc(Window *w, WindowEvent *e); +static void ClientListPopupWndProc(Window *w, WindowEvent *e); +static byte _selected_clientlist_item = 255; +static byte _selected_clientlist_y = 0; +static char _clientlist_action[MAX_CLIENTLIST_ACTION][50]; +static ClientList_Action_Proc *_clientlist_proc[MAX_CLIENTLIST_ACTION]; + +enum { + CLNWND_OFFSET = 16, + CLNWND_ROWSIZE = 10 +}; + +static const Widget _client_list_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 14, 11, 249, 0, 13, STR_NETWORK_CLIENT_LIST, STR_018C_WINDOW_TITLE_DRAG_THIS}, + +{ WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 14 + CLNWND_ROWSIZE + 1, 0x0, STR_NULL}, +{ WIDGETS_END}, +}; + +static const Widget _client_list_popup_widgets[] = { +{ WWT_PANEL, RESIZE_NONE, 14, 0, 99, 0, 0, 0, STR_NULL}, +{ WIDGETS_END}, +}; + +static WindowDesc _client_list_desc = { + WDP_AUTO, WDP_AUTO, 250, 1, + WC_CLIENT_LIST,0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _client_list_widgets, + ClientListWndProc +}; + +// Finds the Xth client-info that is active +static const NetworkClientInfo *NetworkFindClientInfo(byte client_no) +{ + const NetworkClientInfo *ci; + + FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { + if (client_no == 0) return ci; + client_no--; + } + + return NULL; +} + +// Here we start to define the options out of the menu +static void ClientList_Kick(byte client_no) +{ + if (client_no < MAX_PLAYERS) + SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); +} + +static void ClientList_Ban(byte client_no) +{ + uint i; + uint32 ip = NetworkFindClientInfo(client_no)->client_ip; + + for (i = 0; i < lengthof(_network_ban_list); i++) { + if (_network_ban_list[i] == NULL) { + _network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ip)); + break; + } + } + + if (client_no < MAX_PLAYERS) + SEND_COMMAND(PACKET_SERVER_ERROR)(DEREF_CLIENT(client_no), NETWORK_ERROR_KICKED); +} + +static void ClientList_GiveMoney(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkGiveMoneyWindow(NetworkFindClientInfo(client_no)->client_playas); +} + +static void ClientList_SpeakToClient(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, NetworkFindClientInfo(client_no)->client_index); +} + +static void ClientList_SpeakToCompany(byte client_no) +{ + if (NetworkFindClientInfo(client_no) != NULL) + ShowNetworkChatQueryWindow(DESTTYPE_TEAM, NetworkFindClientInfo(client_no)->client_playas); +} + +static void ClientList_SpeakToAll(byte client_no) +{ + ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); +} + +static void ClientList_None(byte client_no) +{ + // No action ;) +} + + + +// Help, a action is clicked! What do we do? +static void HandleClientListPopupClick(byte index, byte clientno) { + // A click on the Popup of the ClientList.. handle the command + if (index < MAX_CLIENTLIST_ACTION && _clientlist_proc[index] != NULL) { + _clientlist_proc[index](clientno); + } +} + +// Finds the amount of clients and set the height correct +static bool CheckClientListHeight(Window *w) +{ + int num = 0; + const NetworkClientInfo *ci; + + // Should be replaced with a loop through all clients + FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { + num++; + } + + num *= CLNWND_ROWSIZE; + + // If height is changed + if (w->height != CLNWND_OFFSET + num + 1) { + // XXX - magic unfortunately; (num + 2) has to be one bigger than heigh (num + 1) + SetWindowDirty(w); + w->widget[2].bottom = w->widget[2].top + num + 2; + w->height = CLNWND_OFFSET + num + 1; + SetWindowDirty(w); + return false; + } + return true; +} + +// Finds the amount of actions in the popup and set the height correct +static uint ClientListPopupHeigth(void) { + int i, num = 0; + + // Find the amount of actions + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { + if (_clientlist_action[i][0] == '\0') continue; + if (_clientlist_proc[i] == NULL) continue; + num++; + } + + num *= CLNWND_ROWSIZE; + + return num + 1; +} + +// Show the popup (action list) +static Window *PopupClientList(Window *w, int client_no, int x, int y) +{ + int i, h; + const NetworkClientInfo *ci; + DeleteWindowById(WC_TOOLBAR_MENU, 0); + + // Clean the current actions + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++) { + _clientlist_action[i][0] = '\0'; + _clientlist_proc[i] = NULL; + } + + // Fill the actions this client has + // Watch is, max 50 chars long! + + ci = NetworkFindClientInfo(client_no); + if (ci == NULL) return NULL; + + i = 0; + if (_network_own_client_index != ci->client_index) { + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_SpeakToClient; + } + + if (IsValidPlayer(ci->client_playas) || ci->client_playas == PLAYER_SPECTATOR) { + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_SpeakToCompany; + } + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_SpeakToAll; + + if (_network_own_client_index != ci->client_index) { + /* We are no spectator and the player we want to give money to is no spectator */ + if (IsValidPlayer(_network_playas) && IsValidPlayer(ci->client_playas)) { + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_GIVE_MONEY, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_GiveMoney; + } + } + + // A server can kick clients (but not himself) + if (_network_server && _network_own_client_index != ci->client_index) { + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_Kick; + + sprintf(_clientlist_action[i],"Ban"); // XXX GetString? + _clientlist_proc[i++] = &ClientList_Ban; + } + + if (i == 0) { + GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_NONE, lastof(_clientlist_action[i])); + _clientlist_proc[i++] = &ClientList_None; + } + + /* Calculate the height */ + h = ClientListPopupHeigth(); + + // Allocate the popup + w = AllocateWindow(x, y, 150, h + 1, ClientListPopupWndProc, WC_TOOLBAR_MENU, _client_list_popup_widgets); + w->widget[0].bottom = w->widget[0].top + h; + w->widget[0].right = w->widget[0].left + 150; + + w->flags4 &= ~WF_WHITE_BORDER_MASK; + WP(w,menu_d).item_count = 0; + // Save our client + WP(w,menu_d).main_button = client_no; + WP(w,menu_d).sel_index = 0; + // We are a popup + _popup_menu_active = true; + + return w; +} + +/** Main handle for the client popup list + * uses menu_d WP macro */ +static void ClientListPopupWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + int i, y, sel; + byte colour; + DrawWindowWidgets(w); + + // Draw the actions + sel = WP(w,menu_d).sel_index; + y = 1; + for (i = 0; i < MAX_CLIENTLIST_ACTION; i++, y += CLNWND_ROWSIZE) { + if (_clientlist_action[i][0] == '\0') continue; + if (_clientlist_proc[i] == NULL) continue; + + if (sel-- == 0) { // Selected item, highlight it + GfxFillRect(1, y, 150 - 2, y + CLNWND_ROWSIZE - 1, 0); + colour = 0xC; + } else { + colour = 0x10; + } + + DoDrawString(_clientlist_action[i], 4, y, colour); + } + } break; + + case WE_POPUPMENU_SELECT: { + // We selected an action + int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; + + if (index >= 0 && e->we.popupmenu.pt.y >= w->top) + HandleClientListPopupClick(index, WP(w,menu_d).main_button); + + DeleteWindowById(WC_TOOLBAR_MENU, 0); + } break; + + case WE_POPUPMENU_OVER: { + // Our mouse hoovers over an action? Select it! + int index = (e->we.popupmenu.pt.y - w->top) / CLNWND_ROWSIZE; + + if (index == -1 || index == WP(w,menu_d).sel_index) return; + + WP(w,menu_d).sel_index = index; + SetWindowDirty(w); + } break; + + } +} + +// Main handle for clientlist +static void ClientListWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + NetworkClientInfo *ci; + int y, i = 0; + byte colour; + + // Check if we need to reset the height + if (!CheckClientListHeight(w)) break; + + DrawWindowWidgets(w); + + y = CLNWND_OFFSET; + + FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { + if (_selected_clientlist_item == i++) { // Selected item, highlight it + GfxFillRect(1, y, 248, y + CLNWND_ROWSIZE - 1, 0); + colour = 0xC; + } else { + colour = 0x10; + } + + if (ci->client_index == NETWORK_SERVER_INDEX) { + DrawString(4, y, STR_NETWORK_SERVER, colour); + } else { + DrawString(4, y, STR_NETWORK_CLIENT, colour); + } + + // Filter out spectators + if (IsValidPlayer(ci->client_playas)) DrawPlayerIcon(ci->client_playas, 64, y + 1); + + DoDrawString(ci->client_name, 81, y, colour); + + y += CLNWND_ROWSIZE; + } + } break; + + case WE_CLICK: + // Show the popup with option + if (_selected_clientlist_item != 255) { + PopupClientList(w, _selected_clientlist_item, e->we.click.pt.x + w->left, e->we.click.pt.y + w->top); + } + + break; + + case WE_MOUSEOVER: + // -1 means we left the current window + if (e->we.mouseover.pt.y == -1) { + _selected_clientlist_y = 0; + _selected_clientlist_item = 255; + SetWindowDirty(w); + break; + } + // It did not change.. no update! + if (e->we.mouseover.pt.y == _selected_clientlist_y) break; + + // Find the new selected item (if any) + _selected_clientlist_y = e->we.mouseover.pt.y; + if (e->we.mouseover.pt.y > CLNWND_OFFSET) { + _selected_clientlist_item = (e->we.mouseover.pt.y - CLNWND_OFFSET) / CLNWND_ROWSIZE; + } else { + _selected_clientlist_item = 255; + } + + // Repaint + SetWindowDirty(w); + break; + + case WE_DESTROY: case WE_CREATE: + // When created or destroyed, data is reset + _selected_clientlist_item = 255; + _selected_clientlist_y = 0; + break; + } +} + +void ShowClientList(void) +{ + AllocateWindowDescFront(&_client_list_desc, 0); +} + + +static NetworkPasswordType pw_type; + + +void ShowNetworkNeedPassword(NetworkPasswordType npt) +{ + StringID caption; + + pw_type = npt; + switch (npt) { + default: NOT_REACHED(); + case NETWORK_GAME_PASSWORD: caption = STR_NETWORK_NEED_GAME_PASSWORD_CAPTION; break; + case NETWORK_COMPANY_PASSWORD: caption = STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION; break; + } + ShowQueryString(STR_EMPTY, caption, 20, 180, FindWindowById(WC_NETWORK_STATUS_WINDOW, 0), CS_ALPHANUMERAL); +} + + +static void NetworkJoinStatusWindowWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + uint8 progress; // used for progress bar + DrawWindowWidgets(w); + + DrawStringCentered(125, 35, STR_NETWORK_CONNECTING_1 + _network_join_status, 14); + switch (_network_join_status) { + case NETWORK_JOIN_STATUS_CONNECTING: case NETWORK_JOIN_STATUS_AUTHORIZING: + case NETWORK_JOIN_STATUS_GETTING_COMPANY_INFO: + progress = 10; // first two stages 10% + break; + case NETWORK_JOIN_STATUS_WAITING: + SetDParam(0, _network_join_waiting); + DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_WAITING, 14); + progress = 15; // third stage is 15% + break; + case NETWORK_JOIN_STATUS_DOWNLOADING: + SetDParam(0, _network_join_kbytes); + SetDParam(1, _network_join_kbytes_total); + DrawStringCentered(125, 46, STR_NETWORK_CONNECTING_DOWNLOADING, 14); + /* Fallthrough */ + default: /* Waiting is 15%, so the resting receivement of map is maximum 70% */ + progress = 15 + _network_join_kbytes * (100 - 15) / _network_join_kbytes_total; + } + + /* Draw nice progress bar :) */ + DrawFrameRect(20, 18, (int)((w->width - 20) * progress / 100), 28, 10, 0); + } break; + + case WE_CLICK: + switch (e->we.click.widget) { + case 2: /* Disconnect button */ + NetworkDisconnect(); + DeleteWindow(w); + SwitchMode(SM_MENU); + ShowNetworkGameWindow(); + break; + } + break; + + /* If the server asks for a password, we need to fill it in */ + case WE_ON_EDIT_TEXT_CANCEL: + NetworkDisconnect(); + ShowNetworkGameWindow(); + break; + + case WE_ON_EDIT_TEXT: + SEND_COMMAND(PACKET_CLIENT_PASSWORD)(pw_type, e->we.edittext.str); + break; + } +} + +static const Widget _network_join_status_window_widget[] = { +{ WWT_CAPTION, RESIZE_NONE, 14, 0, 249, 0, 13, STR_NETWORK_CONNECTING, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_PANEL, RESIZE_NONE, 14, 0, 249, 14, 84, 0x0, STR_NULL}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, BTC, 75, 175, 69, 80, STR_NETWORK_DISCONNECT, STR_NULL}, +{ WIDGETS_END}, +}; + +static const WindowDesc _network_join_status_window_desc = { + WDP_CENTER, WDP_CENTER, 250, 85, + WC_NETWORK_STATUS_WINDOW, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_MODAL, + _network_join_status_window_widget, + NetworkJoinStatusWindowWndProc, +}; + +void ShowJoinStatusWindow(void) +{ + Window *w; + DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0); + w = AllocateWindowDesc(&_network_join_status_window_desc); + /* Parent the status window to the lobby */ + if (w != NULL) w->parent = FindWindowById(WC_NETWORK_WINDOW, 0); +} + +static void SendChat(const char *buf, DestType type, byte dest) +{ + if (buf[0] == '\0') return; + if (!_network_server) { + SEND_COMMAND(PACKET_CLIENT_CHAT)(NETWORK_ACTION_CHAT + type, type, dest, buf); + } else { + NetworkServer_HandleChat(NETWORK_ACTION_CHAT + type, type, dest, buf, NETWORK_SERVER_INDEX); + } +} + +/** + * Find the next item of the list of things that can be auto-completed. + * @param item The current indexed item to return. This function can, and most + * likely will, alter item, to skip empty items in the arrays. + * @return Returns the char that matched to the index. + */ +static const char *ChatTabCompletionNextItem(uint *item) +{ + static char chat_tab_temp_buffer[64]; + + /* First, try clients */ + if (*item < MAX_CLIENT_INFO) { + /* Skip inactive clients */ + while (_network_client_info[*item].client_index == NETWORK_EMPTY_INDEX && *item < MAX_CLIENT_INFO) (*item)++; + if (*item < MAX_CLIENT_INFO) return _network_client_info[*item].client_name; + } + + /* Then, try townnames */ + /* Not that the following assumes all town indices are adjacent, ie no + * towns have been deleted. */ + if (*item <= (uint)MAX_CLIENT_INFO + GetMaxTownIndex()) { + const Town *t; + + FOR_ALL_TOWNS_FROM(t, *item - MAX_CLIENT_INFO) { + /* Get the town-name via the string-system */ + SetDParam(0, t->townnameparts); + GetString(chat_tab_temp_buffer, t->townnametype, lastof(chat_tab_temp_buffer)); + return &chat_tab_temp_buffer[0]; + } + } + + return NULL; +} + +/** + * Find what text to complete. It scans for a space from the left and marks + * the word right from that as to complete. It also writes a \0 at the + * position of the space (if any). If nothing found, buf is returned. + */ +static char *ChatTabCompletionFindText(char *buf) +{ + char *p = strrchr(buf, ' '); + if (p == NULL) return buf; + + *p = '\0'; + return p + 1; +} + +/** + * See if we can auto-complete the current text of the user. + */ +static void ChatTabCompletion(Window *w) +{ + static char _chat_tab_completion_buf[lengthof(_edit_str_buf)]; + Textbuf *tb = &WP(w, querystr_d).text; + uint len, tb_len; + uint item; + char *tb_buf, *pre_buf; + const char *cur_name; + bool second_scan = false; + + item = 0; + + /* Copy the buffer so we can modify it without damaging the real data */ + pre_buf = (_chat_tab_completion_active) ? strdup(_chat_tab_completion_buf) : strdup(tb->buf); + + tb_buf = ChatTabCompletionFindText(pre_buf); + tb_len = strlen(tb_buf); + + while ((cur_name = ChatTabCompletionNextItem(&item)) != NULL) { + item++; + + if (_chat_tab_completion_active) { + /* We are pressing TAB again on the same name, is there an other name + * that starts with this? */ + if (!second_scan) { + uint offset; + uint length; + + /* If we are completing at the begin of the line, skip the ': ' we added */ + if (tb_buf == pre_buf) { + offset = 0; + length = tb->length - 2; + } else { + /* Else, find the place we are completing at */ + offset = strlen(pre_buf) + 1; + length = tb->length - offset; + } + + /* Compare if we have a match */ + if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true; + + continue; + } + + /* Now any match we make on _chat_tab_completion_buf after this, is perfect */ + } + + len = strlen(cur_name); + if (tb_len < len && strncasecmp(cur_name, tb_buf, tb_len) == 0) { + /* Save the data it was before completion */ + if (!second_scan) snprintf(_chat_tab_completion_buf, lengthof(_chat_tab_completion_buf), "%s", tb->buf); + _chat_tab_completion_active = true; + + /* Change to the found name. Add ': ' if we are at the start of the line (pretty) */ + if (pre_buf == tb_buf) { + snprintf(tb->buf, lengthof(_edit_str_buf), "%s: ", cur_name); + } else { + snprintf(tb->buf, lengthof(_edit_str_buf), "%s %s", pre_buf, cur_name); + } + + /* Update the textbuffer */ + UpdateTextBufferSize(&WP(w, querystr_d).text); + + SetWindowDirty(w); + free(pre_buf); + return; + } + } + + if (second_scan) { + /* We walked all posibilities, and the user presses tab again.. revert to original text */ + strcpy(tb->buf, _chat_tab_completion_buf); + _chat_tab_completion_active = false; + + /* Update the textbuffer */ + UpdateTextBufferSize(&WP(w, querystr_d).text); + + SetWindowDirty(w); + } + free(pre_buf); +} + +/* uses querystr_d WP macro + * uses querystr_d->caption to store + * - type of chat message (Private/Team/All) in bytes 0-7 + * - destination of chat message in the case of Team/Private in bytes 8-15 */ +static void ChatWindowWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_CREATE: + SendWindowMessage(WC_NEWS_WINDOW, 0, WE_CREATE, w->height, 0); + SETBIT(_no_scroll, SCROLL_CHAT); // do not scroll the game with the arrow-keys + break; + + case WE_PAINT: { + static const StringID chat_captions[] = { + STR_NETWORK_CHAT_ALL_CAPTION, + STR_NETWORK_CHAT_COMPANY_CAPTION, + STR_NETWORK_CHAT_CLIENT_CAPTION + }; + StringID msg; + + DrawWindowWidgets(w); + + assert(GB(WP(w, querystr_d).caption, 0, 8) < lengthof(chat_captions)); + msg = chat_captions[GB(WP(w, querystr_d).caption, 0, 8)]; + DrawStringRightAligned(w->widget[2].left - 2, w->widget[2].top + 1, msg, 16); + DrawEditBox(w, &WP(w, querystr_d), 2); + } break; + + case WE_CLICK: + switch (e->we.click.widget) { + case 3: { /* Send */ + DestType type = GB(WP(w, querystr_d).caption, 0, 8); + byte dest = GB(WP(w, querystr_d).caption, 8, 8); + SendChat(WP(w, querystr_d).text.buf, type, dest); + } /* FALLTHROUGH */ + case 0: /* Cancel */ DeleteWindow(w); break; + } + break; + + case WE_MOUSELOOP: + HandleEditBox(w, &WP(w, querystr_d), 2); + break; + + case WE_KEYPRESS: + if (e->we.keypress.keycode == WKC_TAB) { + ChatTabCompletion(w); + } else { + _chat_tab_completion_active = false; + switch (HandleEditBoxKey(w, &WP(w, querystr_d), 2, e)) { + case 1: { /* Return */ + DestType type = GB(WP(w, querystr_d).caption, 0, 8); + byte dest = GB(WP(w, querystr_d).caption, 8, 8); + SendChat(WP(w, querystr_d).text.buf, type, dest); + } /* FALLTHROUGH */ + case 2: /* Escape */ DeleteWindow(w); break; + } + } + break; + + case WE_DESTROY: + SendWindowMessage(WC_NEWS_WINDOW, 0, WE_DESTROY, 0, 0); + CLRBIT(_no_scroll, SCROLL_CHAT); + break; + } +} + +static const Widget _chat_window_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_PANEL, RESIZE_NONE, 14, 11, 639, 0, 13, 0x0, STR_NULL}, // background +{ WWT_PANEL, RESIZE_NONE, 14, 75, 577, 1, 12, 0x0, STR_NULL}, // text box +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 578, 639, 1, 12, STR_NETWORK_SEND, STR_NULL}, // send button +{ WIDGETS_END}, +}; + +static const WindowDesc _chat_window_desc = { + WDP_CENTER, -26, 640, 14, // x, y, width, height + WC_SEND_NETWORK_MSG,0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET, + _chat_window_widgets, + ChatWindowWndProc +}; + +void ShowNetworkChatQueryWindow(DestType type, byte dest) +{ + Window *w; + + DeleteWindowById(WC_SEND_NETWORK_MSG, 0); + + _edit_str_buf[0] = '\0'; + _chat_tab_completion_active = false; + + w = AllocateWindowDesc(&_chat_window_desc); + + LowerWindowWidget(w, 2); + WP(w, querystr_d).caption = GB(type, 0, 8) | (dest << 8); // Misuse of caption + WP(w, querystr_d).afilter = CS_ALPHANUMERAL; + InitializeTextBuffer(&WP(w, querystr_d).text, _edit_str_buf, lengthof(_edit_str_buf), 0); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_gui.h b/src/network/network_gui.h new file mode 100644 index 000000000..aa4313eba --- /dev/null +++ b/src/network/network_gui.h @@ -0,0 +1,26 @@ +/* $Id$ */ + +#ifndef NETWORK_GUI_H +#define NETWORK_GUI_H + +#ifdef ENABLE_NETWORK + +#include "network_data.h" + +void ShowNetworkNeedPassword(NetworkPasswordType npt); +void ShowNetworkGiveMoneyWindow(byte player); // PlayerID +void ShowNetworkChatQueryWindow(DestType type, byte dest); +void ShowJoinStatusWindow(void); +void ShowNetworkGameWindow(void); +void ShowClientList(void); + +#else /* ENABLE_NETWORK */ +/* Network function stubs when networking is disabled */ + +static inline void ShowNetworkChatQueryWindow(byte desttype, byte dest) {} +static inline void ShowClientList(void) {} +static inline void ShowNetworkGameWindow(void) {} + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_GUI_H */ diff --git a/src/network/network_server.c b/src/network/network_server.c new file mode 100644 index 000000000..5a7109105 --- /dev/null +++ b/src/network/network_server.c @@ -0,0 +1,1528 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "../openttd.h" // XXX StringID +#include "../debug.h" +#include "../string.h" +#include "../strings.h" +#include "network_data.h" +#include "core/tcp.h" +#include "../train.h" +#include "../date.h" +#include "table/strings.h" +#include "../functions.h" +#include "network_server.h" +#include "network_udp.h" +#include "../console.h" +#include "../command.h" +#include "../saveload.h" +#include "../vehicle.h" +#include "../station.h" +#include "../variables.h" +#include "../genworld.h" + +// This file handles all the server-commands + +static void NetworkHandleCommandQueue(NetworkClientState* cs); + +// ********** +// Sending functions +// DEF_SERVER_SEND_COMMAND has parameter: NetworkClientState *cs +// ********** + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CLIENT_INFO)(NetworkClientState *cs, NetworkClientInfo *ci) +{ + // + // Packet: SERVER_CLIENT_INFO + // Function: Sends info about a client + // Data: + // uint16: The index of the client (always unique on a server. 1 = server) + // uint8: As which player the client is playing + // String: The name of the client + // String: The unique id of the client + // + + if (ci->client_index != NETWORK_EMPTY_INDEX) { + Packet *p = NetworkSend_Init(PACKET_SERVER_CLIENT_INFO); + NetworkSend_uint16(p, ci->client_index); + NetworkSend_uint8 (p, ci->client_playas); + NetworkSend_string(p, ci->client_name); + NetworkSend_string(p, ci->unique_id); + + NetworkSend_Packet(p, cs); + } +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_COMPANY_INFO) +{ +// + // Packet: SERVER_COMPANY_INFO + // Function: Sends info about the companies + // Data: + // + + int i; + + Player *player; + Packet *p; + + byte active = ActivePlayerCount(); + + if (active == 0) { + p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO); + + NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (p, active); + + NetworkSend_Packet(p, cs); + return; + } + + NetworkPopulateCompanyInfo(); + + FOR_ALL_PLAYERS(player) { + if (!player->is_active) continue; + + p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO); + + NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (p, active); + NetworkSend_uint8 (p, player->index); + + NetworkSend_string(p, _network_player_info[player->index].company_name); + NetworkSend_uint32(p, _network_player_info[player->index].inaugurated_year); + NetworkSend_uint64(p, _network_player_info[player->index].company_value); + NetworkSend_uint64(p, _network_player_info[player->index].money); + NetworkSend_uint64(p, _network_player_info[player->index].income); + NetworkSend_uint16(p, _network_player_info[player->index].performance); + + /* Send 1 if there is a passord for the company else send 0 */ + if (_network_player_info[player->index].password[0] != '\0') { + NetworkSend_uint8(p, 1); + } else { + NetworkSend_uint8(p, 0); + } + + for (i = 0; i < NETWORK_VEHICLE_TYPES; i++) { + NetworkSend_uint16(p, _network_player_info[player->index].num_vehicle[i]); + } + + for (i = 0; i < NETWORK_STATION_TYPES; i++) { + NetworkSend_uint16(p, _network_player_info[player->index].num_station[i]); + } + + if (_network_player_info[player->index].players[0] == '\0') { + NetworkSend_string(p, "<none>"); + } else { + NetworkSend_string(p, _network_player_info[player->index].players); + } + + NetworkSend_Packet(p, cs); + } + + p = NetworkSend_Init(PACKET_SERVER_COMPANY_INFO); + + NetworkSend_uint8 (p, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (p, 0); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(NetworkClientState *cs, NetworkErrorCode error) +{ + // + // Packet: SERVER_ERROR + // Function: The client made an error + // Data: + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + + char str[100]; + Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR); + + NetworkSend_uint8(p, error); + NetworkSend_Packet(p, cs); + + GetNetworkErrorMsg(str, error, lastof(str)); + + // Only send when the current client was in game + if (cs->status > STATUS_AUTH) { + NetworkClientState *new_cs; + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + DEBUG(net, 1, "'%s' made an error and has been disconnected. Reason: '%s'", client_name, str); + + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH && new_cs != cs) { + // Some errors we filter to a more general error. Clients don't have to know the real + // reason a joining failed. + if (error == NETWORK_ERROR_NOT_AUTHORIZED || error == NETWORK_ERROR_NOT_EXPECTED || error == NETWORK_ERROR_WRONG_REVISION) + error = NETWORK_ERROR_ILLEGAL_PACKET; + + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, error); + } + } + } else { + DEBUG(net, 1, "Client %d made an error and has been disconnected. Reason: '%s'", cs->index, str); + } + + cs->has_quit = true; + + // Make sure the data get's there before we close the connection + NetworkSend_Packets(cs); + + // The client made a mistake, so drop his connection now! + NetworkCloseClient(cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_NEED_PASSWORD)(NetworkClientState *cs, NetworkPasswordType type) +{ + // + // Packet: SERVER_NEED_PASSWORD + // Function: Indication to the client that the server needs a password + // Data: + // uint8: Type of password + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_NEED_PASSWORD); + NetworkSend_uint8(p, type); + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WELCOME) +{ + // + // Packet: SERVER_WELCOME + // Function: The client is joined and ready to receive his map + // Data: + // uint16: Own ClientID + // + + Packet *p; + const NetworkClientState *new_cs; + + // Invalid packet when status is AUTH or higher + if (cs->status >= STATUS_AUTH) return; + + cs->status = STATUS_AUTH; + _network_game_info.clients_on++; + + p = NetworkSend_Init(PACKET_SERVER_WELCOME); + NetworkSend_uint16(p, cs->index); + NetworkSend_Packet(p, cs); + + // Transmit info about all the active clients + FOR_ALL_CLIENTS(new_cs) { + if (new_cs != cs && new_cs->status > STATUS_AUTH) + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, DEREF_CLIENT_INFO(new_cs)); + } + // Also send the info of the server + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX)); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_WAIT) +{ + // + // Packet: PACKET_SERVER_WAIT + // Function: The client can not receive the map at the moment because + // someone else is already receiving the map + // Data: + // uint8: Clients awaiting map + // + int waiting = 0; + NetworkClientState *new_cs; + Packet *p; + + // Count how many players are waiting in the queue + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP_WAIT) waiting++; + } + + p = NetworkSend_Init(PACKET_SERVER_WAIT); + NetworkSend_uint8(p, waiting); + NetworkSend_Packet(p, cs); +} + +// This sends the map to the client +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP) +{ + // + // Packet: SERVER_MAP + // Function: Sends the map to the client, or a part of it (it is splitted in + // a lot of multiple packets) + // Data: + // uint8: packet-type (MAP_PACKET_START, MAP_PACKET_NORMAL and MAP_PACKET_END) + // if MAP_PACKET_START: + // uint32: The current FrameCounter + // if MAP_PACKET_NORMAL: + // piece of the map (till max-size of packet) + // if MAP_PACKET_END: + // uint32: seed0 of player + // uint32: seed1 of player + // last 2 are repeated MAX_PLAYERS time + // + + static FILE *file_pointer; + static uint sent_packets; // How many packets we did send succecfully last time + + if (cs->status < STATUS_AUTH) { + // Illegal call, return error and ignore the packet + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED); + return; + } + + if (cs->status == STATUS_AUTH) { + char filename[256]; + Packet *p; + + // Make a dump of the current game + snprintf(filename, lengthof(filename), "%s%snetwork_server.tmp", _paths.autosave_dir, PATHSEP); + if (SaveOrLoad(filename, SL_SAVE) != SL_OK) error("network savedump failed"); + + file_pointer = fopen(filename, "rb"); + fseek(file_pointer, 0, SEEK_END); + + // Now send the _frame_counter and how many packets are coming + p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_START); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, ftell(file_pointer)); + NetworkSend_Packet(p, cs); + + fseek(file_pointer, 0, SEEK_SET); + + sent_packets = 4; // We start with trying 4 packets + + cs->status = STATUS_MAP; + /* Mark the start of download */ + cs->last_frame = _frame_counter; + cs->last_frame_server = _frame_counter; + } + + if (cs->status == STATUS_MAP) { + uint i; + int res; + for (i = 0; i < sent_packets; i++) { + Packet *p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_NORMAL); + res = (int)fread(p->buffer + p->size, 1, SEND_MTU - p->size, file_pointer); + + if (ferror(file_pointer)) error("Error reading temporary network savegame!"); + + p->size += res; + NetworkSend_Packet(p, cs); + if (feof(file_pointer)) { + // Done reading! + Packet *p = NetworkSend_Init(PACKET_SERVER_MAP); + NetworkSend_uint8(p, MAP_PACKET_END); + NetworkSend_Packet(p, cs); + + // Set the status to DONE_MAP, no we will wait for the client + // to send it is ready (maybe that happens like never ;)) + cs->status = STATUS_DONE_MAP; + fclose(file_pointer); + + { + NetworkClientState *new_cs; + bool new_map_client = false; + // Check if there is a client waiting for receiving the map + // and start sending him the map + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP_WAIT) { + // Check if we already have a new client to send the map to + if (!new_map_client) { + // If not, this client will get the map + new_cs->status = STATUS_AUTH; + new_map_client = true; + SEND_COMMAND(PACKET_SERVER_MAP)(new_cs); + } else { + // Else, send the other clients how many clients are in front of them + SEND_COMMAND(PACKET_SERVER_WAIT)(new_cs); + } + } + } + } + + // There is no more data, so break the for + break; + } + } + + // Send all packets (forced) and check if we have send it all + NetworkSend_Packets(cs); + if (cs->packet_queue == NULL) { + // All are sent, increase the sent_packets + sent_packets *= 2; + } else { + // Not everything is sent, decrease the sent_packets + if (sent_packets > 1) sent_packets /= 2; + } + } +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_JOIN)(NetworkClientState *cs, uint16 client_index) +{ + // + // Packet: SERVER_JOIN + // Function: A client is joined (all active clients receive this after a + // PACKET_CLIENT_MAP_OK) Mostly what directly follows is a + // PACKET_SERVER_CLIENT_INFO + // Data: + // uint16: Client-Index + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_JOIN); + + NetworkSend_uint16(p, client_index); + + NetworkSend_Packet(p, cs); +} + + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_FRAME) +{ + // + // Packet: SERVER_FRAME + // Function: Sends the current frame-counter to the client + // Data: + // uint32: Frame Counter + // uint32: Frame Counter Max (how far may the client walk before the server?) + // [uint32: general-seed-1] + // [uint32: general-seed-2] + // (last two depends on compile-settings, and are not default settings) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_FRAME); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, _frame_counter_max); +#ifdef ENABLE_NETWORK_SYNC_EVERY_FRAME + NetworkSend_uint32(p, _sync_seed_1); +#ifdef NETWORK_SEND_DOUBLE_SEED + NetworkSend_uint32(p, _sync_seed_2); +#endif +#endif + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SYNC) +{ + // + // Packet: SERVER_SYNC + // Function: Sends a sync-check to the client + // Data: + // uint32: Frame Counter + // uint32: General-seed-1 + // [uint32: general-seed-2] + // (last one depends on compile-settings, and are not default settings) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_SYNC); + NetworkSend_uint32(p, _frame_counter); + NetworkSend_uint32(p, _sync_seed_1); + +#ifdef NETWORK_SEND_DOUBLE_SEED + NetworkSend_uint32(p, _sync_seed_2); +#endif + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_COMMAND)(NetworkClientState *cs, CommandPacket *cp) +{ + // + // Packet: SERVER_COMMAND + // Function: Sends a DoCommand to the client + // Data: + // uint8: PlayerID (0..MAX_PLAYERS-1) + // uint32: CommandID (see command.h) + // uint32: P1 (free variables used in DoCommand) + // uint32: P2 + // uint32: Tile + // string: text + // uint8: CallBackID (see callback_table.c) + // uint32: Frame of execution + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_COMMAND); + + NetworkSend_uint8(p, cp->player); + NetworkSend_uint32(p, cp->cmd); + NetworkSend_uint32(p, cp->p1); + NetworkSend_uint32(p, cp->p2); + NetworkSend_uint32(p, cp->tile); + NetworkSend_string(p, cp->text); + NetworkSend_uint8(p, cp->callback); + NetworkSend_uint32(p, cp->frame); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_CHAT)(NetworkClientState *cs, NetworkAction action, uint16 client_index, bool self_send, const char *msg) +{ + // + // Packet: SERVER_CHAT + // Function: Sends a chat-packet to the client + // Data: + // uint8: ActionID (see network_data.h, NetworkAction) + // uint16: Client-index + // String: Message (max MAX_TEXT_MSG_LEN) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_CHAT); + + NetworkSend_uint8(p, action); + NetworkSend_uint16(p, client_index); + NetworkSend_uint8(p, self_send); + NetworkSend_string(p, msg); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR_QUIT)(NetworkClientState *cs, uint16 client_index, NetworkErrorCode errorno) +{ + // + // Packet: SERVER_ERROR_QUIT + // Function: One of the clients made an error and is quiting the game + // This packet informs the other clients of that. + // Data: + // uint16: Client-index + // uint8: ErrorID (see network_data.h, NetworkErrorCode) + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_ERROR_QUIT); + + NetworkSend_uint16(p, client_index); + NetworkSend_uint8(p, errorno); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_QUIT)(NetworkClientState *cs, uint16 client_index, const char *leavemsg) +{ + // + // Packet: SERVER_ERROR_QUIT + // Function: A client left the game, and this packets informs the other clients + // of that. + // Data: + // uint16: Client-index + // String: leave-message + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_QUIT); + + NetworkSend_uint16(p, client_index); + NetworkSend_string(p, leavemsg); + + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN) +{ + // + // Packet: SERVER_SHUTDOWN + // Function: Let the clients know that the server is closing + // Data: + // <none> + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_SHUTDOWN); + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME) +{ + // + // Packet: PACKET_SERVER_NEWGAME + // Function: Let the clients know that the server is loading a new map + // Data: + // <none> + // + + Packet *p = NetworkSend_Init(PACKET_SERVER_NEWGAME); + NetworkSend_Packet(p, cs); +} + +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_RCON)(NetworkClientState *cs, uint16 color, const char *command) +{ + Packet *p = NetworkSend_Init(PACKET_SERVER_RCON); + + NetworkSend_uint16(p, color); + NetworkSend_string(p, command); + NetworkSend_Packet(p, cs); +} + +// ********** +// Receiving functions +// DEF_SERVER_RECEIVE_COMMAND has parameter: NetworkClientState *cs, Packet *p +// ********** + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO) +{ + SEND_COMMAND(PACKET_SERVER_COMPANY_INFO)(cs); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_JOIN) +{ + char name[NETWORK_CLIENT_NAME_LENGTH]; + char unique_id[NETWORK_NAME_LENGTH]; + NetworkClientInfo *ci; + byte playas; + NetworkLanguage client_lang; + char client_revision[NETWORK_REVISION_LENGTH]; + + NetworkRecv_string(cs, p, client_revision, sizeof(client_revision)); + +#if defined(WITH_REV) || defined(WITH_REV_HACK) + // Check if the client has revision control enabled + if (strcmp(NOREV_STRING, client_revision) != 0 && + strcmp(_network_game_info.server_revision, client_revision) != 0) { + // Different revisions!! + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_REVISION); + return; + } +#endif + + NetworkRecv_string(cs, p, name, sizeof(name)); + playas = NetworkRecv_uint8(cs, p); + client_lang = NetworkRecv_uint8(cs, p); + NetworkRecv_string(cs, p, unique_id, sizeof(unique_id)); + + if (cs->has_quit) return; + + // join another company does not affect these values + switch (playas) { + case PLAYER_NEW_COMPANY: /* New company */ + if (ActivePlayerCount() >= _network_game_info.companies_max) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_FULL); + return; + } + break; + case PLAYER_SPECTATOR: /* Spectator */ + if (NetworkSpectatorCount() >= _network_game_info.spectators_max) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_FULL); + return; + } + break; + default: /* Join another company (companies 1-8 (index 0-7)) */ + if (!IsValidPlayer(playas)) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_PLAYER_MISMATCH); + return; + } + break; + } + + // We need a valid name.. make it Player + if (*name == '\0') ttd_strlcpy(name, "Player", sizeof(name)); + + if (!NetworkFindName(name)) { // Change name if duplicate + // We could not create a name for this player + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NAME_IN_USE); + return; + } + + ci = DEREF_CLIENT_INFO(cs); + + ttd_strlcpy(ci->client_name, name, sizeof(ci->client_name)); + ttd_strlcpy(ci->unique_id, unique_id, sizeof(ci->unique_id)); + ci->client_playas = playas; + ci->client_lang = client_lang; + + // We now want a password from the client + // else we do not allow him in! + if (_network_game_info.use_password) { + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_GAME_PASSWORD); + } else { + if (IsValidPlayer(ci->client_playas) && _network_player_info[ci->client_playas].password[0] != '\0') { + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD); + } else { + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + } + } + + /* Make sure companies to which people try to join are not autocleaned */ + if (IsValidPlayer(playas)) _network_player_info[playas].months_empty = 0; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD) +{ + NetworkPasswordType type; + char password[NETWORK_PASSWORD_LENGTH]; + const NetworkClientInfo *ci; + + type = NetworkRecv_uint8(cs, p); + NetworkRecv_string(cs, p, password, sizeof(password)); + + if (cs->status == STATUS_INACTIVE && type == NETWORK_GAME_PASSWORD) { + // Check game-password + if (strcmp(password, _network_game_info.server_password) != 0) { + // Password is invalid + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD); + return; + } + + ci = DEREF_CLIENT_INFO(cs); + + if (IsValidPlayer(ci->client_playas) && _network_player_info[ci->client_playas].password[0] != '\0') { + SEND_COMMAND(PACKET_SERVER_NEED_PASSWORD)(cs, NETWORK_COMPANY_PASSWORD); + return; + } + + // Valid password, allow user + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + return; + } else if (cs->status == STATUS_INACTIVE && type == NETWORK_COMPANY_PASSWORD) { + ci = DEREF_CLIENT_INFO(cs); + + if (strcmp(password, _network_player_info[ci->client_playas].password) != 0) { + // Password is invalid + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_WRONG_PASSWORD); + return; + } + + SEND_COMMAND(PACKET_SERVER_WELCOME)(cs); + return; + } + + + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + return; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_GETMAP) +{ + const NetworkClientState *new_cs; + + // The client was never joined.. so this is impossible, right? + // Ignore the packet, give the client a warning, and close his connection + if (cs->status < STATUS_AUTH || cs->has_quit) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_AUTHORIZED); + return; + } + + // Check if someone else is receiving the map + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status == STATUS_MAP) { + // Tell the new client to wait + cs->status = STATUS_MAP_WAIT; + SEND_COMMAND(PACKET_SERVER_WAIT)(cs); + return; + } + } + + // We receive a request to upload the map.. give it to the client! + SEND_COMMAND(PACKET_SERVER_MAP)(cs); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK) +{ + // Client has the map, now start syncing + if (cs->status == STATUS_DONE_MAP && !cs->has_quit) { + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + NetworkClientState *new_cs; + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + NetworkTextMessage(NETWORK_ACTION_JOIN, 1, false, client_name, ""); + + // Mark the client as pre-active, and wait for an ACK + // so we know he is done loading and in sync with us + cs->status = STATUS_PRE_ACTIVE; + NetworkHandleCommandQueue(cs); + SEND_COMMAND(PACKET_SERVER_FRAME)(cs); + SEND_COMMAND(PACKET_SERVER_SYNC)(cs); + + // This is the frame the client receives + // we need it later on to make sure the client is not too slow + cs->last_frame = _frame_counter; + cs->last_frame_server = _frame_counter; + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(new_cs, DEREF_CLIENT_INFO(cs)); + SEND_COMMAND(PACKET_SERVER_JOIN)(new_cs, cs->index); + } + } + + if (_network_pause_on_join) { + /* Now pause the game till the client is in sync */ + DoCommandP(0, 1, 0, NULL, CMD_PAUSE); + + NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game paused (incoming client)", NETWORK_SERVER_INDEX); + } + } else { + // Wrong status for this packet, give a warning to client, and close connection + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + } +} + +/** Enforce the command flags. + * Eg a server-only command can only be executed by a server, etc. + * @param *cp the commandpacket that is going to be checked + * @param *ci client information for debugging output to console + */ +static bool CheckCommandFlags(const CommandPacket *cp, const NetworkClientInfo *ci) +{ + byte flags = GetCommandFlags(cp->cmd); + + if (flags & CMD_SERVER && ci->client_index != NETWORK_SERVER_INDEX) { + IConsolePrintF(_icolour_err, "WARNING: server only command from client %d (IP: %s), kicking...", ci->client_index, GetPlayerIP(ci)); + return false; + } + + if (flags & CMD_OFFLINE) { + IConsolePrintF(_icolour_err, "WARNING: offline only command from client %d (IP: %s), kicking...", ci->client_index, GetPlayerIP(ci)); + return false; + } + + return true; +} + +/** The client has done a command and wants us to handle it + * @param *cs the connected client that has sent the command + * @param *p the packet in which the command was sent + */ +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_COMMAND) +{ + NetworkClientState *new_cs; + const NetworkClientInfo *ci; + byte callback; + + CommandPacket *cp = malloc(sizeof(CommandPacket)); + + // The client was never joined.. so this is impossible, right? + // Ignore the packet, give the client a warning, and close his connection + if (cs->status < STATUS_DONE_MAP || cs->has_quit) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + return; + } + + cp->player = NetworkRecv_uint8(cs, p); + cp->cmd = NetworkRecv_uint32(cs, p); + cp->p1 = NetworkRecv_uint32(cs, p); + cp->p2 = NetworkRecv_uint32(cs, p); + cp->tile = NetworkRecv_uint32(cs, p); + NetworkRecv_string(cs, p, cp->text, lengthof(cp->text)); + + callback = NetworkRecv_uint8(cs, p); + + if (cs->has_quit) return; + + ci = DEREF_CLIENT_INFO(cs); + + /* Check if cp->cmd is valid */ + if (!IsValidCommand(cp->cmd)) { + IConsolePrintF(_icolour_err, "WARNING: invalid command from client %d (IP: %s).", ci->client_index, GetPlayerIP(ci)); + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_NOT_EXPECTED); + return; + } + + if (!CheckCommandFlags(cp, ci)) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_KICKED); + return; + } + + /** Only CMD_PLAYER_CTRL is always allowed, for the rest, playas needs + * to match the player in the packet. If it doesn't, the client has done + * something pretty naughty (or a bug), and will be kicked + */ + if (!(cp->cmd == CMD_PLAYER_CTRL && cp->p1 == 0) && ci->client_playas != cp->player) { + IConsolePrintF(_icolour_err, "WARNING: player %d (IP: %s) tried to execute a command as player %d, kicking...", + ci->client_playas + 1, GetPlayerIP(ci), cp->player + 1); + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_PLAYER_MISMATCH); + return; + } + + /** @todo CMD_PLAYER_CTRL with p1 = 0 announces a new player to the server. To give the + * player the correct ID, the server injects p2 and executes the command. Any other p1 + * is prohibited. Pretty ugly and should be redone together with its function. + * @see CmdPlayerCtrl() players.c:655 + */ + if (cp->cmd == CMD_PLAYER_CTRL) { + if (cp->p1 != 0) { + SEND_COMMAND(PACKET_SERVER_ERROR)(cs, NETWORK_ERROR_CHEATER); + return; + } + + /* XXX - Execute the command as a valid player. Normally this would be done by a + * spectator, but that is not allowed any commands. So do an impersonation. The drawback + * of this is that the first company's last_built_tile is also updated... */ + cp->player = 0; + cp->p2 = cs - _clients; // XXX - UGLY! p2 is mis-used to get the client-id in CmdPlayerCtrl + } + + // The frame can be executed in the same frame as the next frame-packet + // That frame just before that frame is saved in _frame_counter_max + cp->frame = _frame_counter_max + 1; + cp->next = NULL; + + // Queue the command for the clients (are send at the end of the frame + // if they can handle it ;)) + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status >= STATUS_MAP) { + // Callbacks are only send back to the client who sent them in the + // first place. This filters that out. + cp->callback = (new_cs != cs) ? 0 : callback; + NetworkAddCommandQueue(new_cs, cp); + } + } + + cp->callback = 0; + // Queue the command on the server + if (_local_command_queue == NULL) { + _local_command_queue = cp; + } else { + // Find last packet + CommandPacket *c = _local_command_queue; + while (c->next != NULL) c = c->next; + c->next = cp; + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ERROR) +{ + // This packets means a client noticed an error and is reporting this + // to us. Display the error and report it to the other clients + NetworkClientState *new_cs; + char str[100]; + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + NetworkErrorCode errorno = NetworkRecv_uint8(cs, p); + + // The client was never joined.. thank the client for the packet, but ignore it + if (cs->status < STATUS_DONE_MAP || cs->has_quit) { + cs->has_quit = true; + return; + } + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + GetNetworkErrorMsg(str, errorno, lastof(str)); + + DEBUG(net, 2, "'%s' reported an error and is closing its connection (%s)", client_name, str); + + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_ERROR_QUIT)(new_cs, cs->index, errorno); + } + } + + cs->has_quit = true; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_QUIT) +{ + // The client wants to leave. Display this and report it to the other + // clients. + NetworkClientState *new_cs; + char str[100]; + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + + // The client was never joined.. thank the client for the packet, but ignore it + if (cs->status < STATUS_DONE_MAP || cs->has_quit) { + cs->has_quit = true; + return; + } + + NetworkRecv_string(cs, p, str, lengthof(str)); + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + NetworkTextMessage(NETWORK_ACTION_LEAVE, 1, false, client_name, "%s", str); + + FOR_ALL_CLIENTS(new_cs) { + if (new_cs->status > STATUS_AUTH) { + SEND_COMMAND(PACKET_SERVER_QUIT)(new_cs, cs->index, str); + } + } + + cs->has_quit = true; +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_ACK) +{ + uint32 frame = NetworkRecv_uint32(cs, p); + + /* The client is trying to catch up with the server */ + if (cs->status == STATUS_PRE_ACTIVE) { + /* The client is not yet catched up? */ + if (frame + DAY_TICKS < _frame_counter) return; + + /* Now he is! Unpause the game */ + cs->status = STATUS_ACTIVE; + + if (_network_pause_on_join) { + DoCommandP(0, 0, 0, NULL, CMD_PAUSE); + NetworkServer_HandleChat(NETWORK_ACTION_SERVER_MESSAGE, DESTTYPE_BROADCAST, 0, "Game unpaused (client connected)", NETWORK_SERVER_INDEX); + } + + CheckMinPlayers(); + + /* Execute script for, e.g. MOTD */ + IConsoleCmdExec("exec scripts/on_server_connect.scr 0"); + } + + // The client received the frame, make note of it + cs->last_frame = frame; + // With those 2 values we can calculate the lag realtime + cs->last_frame_server = _frame_counter; +} + + + +void NetworkServer_HandleChat(NetworkAction action, DestType desttype, int dest, const char *msg, uint16 from_index) +{ + NetworkClientState *cs; + const NetworkClientInfo *ci, *ci_own, *ci_to; + + switch (desttype) { + case DESTTYPE_CLIENT: + /* Are we sending to the server? */ + if (dest == NETWORK_SERVER_INDEX) { + ci = NetworkFindClientInfoFromIndex(from_index); + /* Display the text locally, and that is it */ + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas), false, ci->client_name, "%s", msg); + } else { + /* Else find the client to send the message to */ + FOR_ALL_CLIENTS(cs) { + if (cs->index == dest) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg); + break; + } + } + } + + // Display the message locally (so you know you have sent it) + if (from_index != dest) { + if (from_index == NETWORK_SERVER_INDEX) { + ci = NetworkFindClientInfoFromIndex(from_index); + ci_to = NetworkFindClientInfoFromIndex(dest); + if (ci != NULL && ci_to != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas), true, ci_to->client_name, "%s", msg); + } else { + FOR_ALL_CLIENTS(cs) { + if (cs->index == from_index) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, dest, true, msg); + break; + } + } + } + } + break; + case DESTTYPE_TEAM: { + bool show_local = true; // If this is false, the message is already displayed + // on the client who did sent it. + /* Find all clients that belong to this player */ + ci_to = NULL; + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (ci->client_playas == dest) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg); + if (cs->index == from_index) show_local = false; + ci_to = ci; // Remember a client that is in the company for company-name + } + } + + ci = NetworkFindClientInfoFromIndex(from_index); + ci_own = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (ci != NULL && ci_own != NULL && ci_own->client_playas == dest) { + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas), false, ci->client_name, "%s", msg); + if (from_index == NETWORK_SERVER_INDEX) show_local = false; + ci_to = ci_own; + } + + /* There is no such player */ + if (ci_to == NULL) break; + + // Display the message locally (so you know you have sent it) + if (ci != NULL && show_local) { + if (from_index == NETWORK_SERVER_INDEX) { + char name[NETWORK_NAME_LENGTH]; + StringID str = IsValidPlayer(ci_to->client_playas) ? GetPlayer(ci_to->client_playas)->name_1 : STR_NETWORK_SPECTATORS; + GetString(name, str, lastof(name)); + NetworkTextMessage(action, GetDrawStringPlayerColor(ci_own->client_playas), true, name, "%s", msg); + } else { + FOR_ALL_CLIENTS(cs) { + if (cs->index == from_index) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, ci_to->client_index, true, msg); + } + } + } + } + } + break; + default: + DEBUG(net, 0, "[server] received unknown chat destination type %d. Doing broadcast instead", desttype); + /* fall-through to next case */ + case DESTTYPE_BROADCAST: + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_CHAT)(cs, action, from_index, false, msg); + } + ci = NetworkFindClientInfoFromIndex(from_index); + if (ci != NULL) + NetworkTextMessage(action, GetDrawStringPlayerColor(ci->client_playas), false, ci->client_name, "%s", msg); + break; + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_CHAT) +{ + NetworkAction action = NetworkRecv_uint8(cs, p); + DestType desttype = NetworkRecv_uint8(cs, p); + int dest = NetworkRecv_uint8(cs, p); + char msg[MAX_TEXT_MSG_LEN]; + + NetworkRecv_string(cs, p, msg, MAX_TEXT_MSG_LEN); + + NetworkServer_HandleChat(action, desttype, dest, msg, cs->index); +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD) +{ + char password[NETWORK_PASSWORD_LENGTH]; + const NetworkClientInfo *ci; + + NetworkRecv_string(cs, p, password, sizeof(password)); + ci = DEREF_CLIENT_INFO(cs); + + if (IsValidPlayer(ci->client_playas)) { + ttd_strlcpy(_network_player_info[ci->client_playas].password, password, sizeof(_network_player_info[0].password)); + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME) +{ + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + NetworkClientInfo *ci; + + NetworkRecv_string(cs, p, client_name, sizeof(client_name)); + ci = DEREF_CLIENT_INFO(cs); + + if (cs->has_quit) return; + + if (ci != NULL) { + // Display change + if (NetworkFindName(client_name)) { + NetworkTextMessage(NETWORK_ACTION_NAME_CHANGE, 1, false, ci->client_name, "%s", client_name); + ttd_strlcpy(ci->client_name, client_name, sizeof(ci->client_name)); + NetworkUpdateClientInfo(ci->client_index); + } + } +} + +DEF_SERVER_RECEIVE_COMMAND(PACKET_CLIENT_RCON) +{ + char pass[NETWORK_PASSWORD_LENGTH]; + char command[NETWORK_RCONCOMMAND_LENGTH]; + + if (_network_game_info.rcon_password[0] == '\0') return; + + NetworkRecv_string(cs, p, pass, sizeof(pass)); + NetworkRecv_string(cs, p, command, sizeof(command)); + + if (strcmp(pass, _network_game_info.rcon_password) != 0) { + DEBUG(net, 0, "[rcon] wrong password from client-id %d", cs->index); + return; + } + + DEBUG(net, 0, "[rcon] client-id %d executed: '%s'", cs->index, command); + + _redirect_console_to_client = cs->index; + IConsoleCmdExec(command); + _redirect_console_to_client = 0; + return; +} + +// The layout for the receive-functions by the server +typedef void NetworkServerPacket(NetworkClientState *cs, Packet *p); + + +// This array matches PacketType. At an incoming +// packet it is matches against this array +// and that way the right function to handle that +// packet is found. +static NetworkServerPacket* const _network_server_packet[] = { + NULL, /*PACKET_SERVER_FULL,*/ + NULL, /*PACKET_SERVER_BANNED,*/ + RECEIVE_COMMAND(PACKET_CLIENT_JOIN), + NULL, /*PACKET_SERVER_ERROR,*/ + RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO), + NULL, /*PACKET_SERVER_COMPANY_INFO,*/ + NULL, /*PACKET_SERVER_CLIENT_INFO,*/ + NULL, /*PACKET_SERVER_NEED_PASSWORD,*/ + RECEIVE_COMMAND(PACKET_CLIENT_PASSWORD), + NULL, /*PACKET_SERVER_WELCOME,*/ + RECEIVE_COMMAND(PACKET_CLIENT_GETMAP), + NULL, /*PACKET_SERVER_WAIT,*/ + NULL, /*PACKET_SERVER_MAP,*/ + RECEIVE_COMMAND(PACKET_CLIENT_MAP_OK), + NULL, /*PACKET_SERVER_JOIN,*/ + NULL, /*PACKET_SERVER_FRAME,*/ + NULL, /*PACKET_SERVER_SYNC,*/ + RECEIVE_COMMAND(PACKET_CLIENT_ACK), + RECEIVE_COMMAND(PACKET_CLIENT_COMMAND), + NULL, /*PACKET_SERVER_COMMAND,*/ + RECEIVE_COMMAND(PACKET_CLIENT_CHAT), + NULL, /*PACKET_SERVER_CHAT,*/ + RECEIVE_COMMAND(PACKET_CLIENT_SET_PASSWORD), + RECEIVE_COMMAND(PACKET_CLIENT_SET_NAME), + RECEIVE_COMMAND(PACKET_CLIENT_QUIT), + RECEIVE_COMMAND(PACKET_CLIENT_ERROR), + NULL, /*PACKET_SERVER_QUIT,*/ + NULL, /*PACKET_SERVER_ERROR_QUIT,*/ + NULL, /*PACKET_SERVER_SHUTDOWN,*/ + NULL, /*PACKET_SERVER_NEWGAME,*/ + NULL, /*PACKET_SERVER_RCON,*/ + RECEIVE_COMMAND(PACKET_CLIENT_RCON), +}; + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_server_packet) == PACKET_END); + +// This update the company_info-stuff +void NetworkPopulateCompanyInfo(void) +{ + char password[NETWORK_PASSWORD_LENGTH]; + const Player *p; + const Vehicle *v; + const Station *s; + const NetworkClientState *cs; + const NetworkClientInfo *ci; + uint i; + uint16 months_empty; + + FOR_ALL_PLAYERS(p) { + if (!p->is_active) { + memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo)); + continue; + } + + // Clean the info but not the password + ttd_strlcpy(password, _network_player_info[p->index].password, sizeof(password)); + months_empty = _network_player_info[p->index].months_empty; + memset(&_network_player_info[p->index], 0, sizeof(NetworkPlayerInfo)); + _network_player_info[p->index].months_empty = months_empty; + ttd_strlcpy(_network_player_info[p->index].password, password, sizeof(_network_player_info[p->index].password)); + + // Grap the company name + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + GetString(_network_player_info[p->index].company_name, STR_JUST_STRING, lastof(_network_player_info[p->index].company_name)); + + // Check the income + if (_cur_year - 1 == p->inaugurated_year) { + // The player is here just 1 year, so display [2], else display[1] + for (i = 0; i < lengthof(p->yearly_expenses[2]); i++) { + _network_player_info[p->index].income -= p->yearly_expenses[2][i]; + } + } else { + for (i = 0; i < lengthof(p->yearly_expenses[1]); i++) { + _network_player_info[p->index].income -= p->yearly_expenses[1][i]; + } + } + + // Set some general stuff + _network_player_info[p->index].inaugurated_year = p->inaugurated_year; + _network_player_info[p->index].company_value = p->old_economy[0].company_value; + _network_player_info[p->index].money = p->money64; + _network_player_info[p->index].performance = p->old_economy[0].performance_history; + } + + // Go through all vehicles and count the type of vehicles + FOR_ALL_VEHICLES(v) { + if (!IsValidPlayer(v->owner)) continue; + + switch (v->type) { + case VEH_Train: + if (IsFrontEngine(v)) _network_player_info[v->owner].num_vehicle[0]++; + break; + + case VEH_Road: + if (v->cargo_type != CT_PASSENGERS) { + _network_player_info[v->owner].num_vehicle[1]++; + } else { + _network_player_info[v->owner].num_vehicle[2]++; + } + break; + + case VEH_Aircraft: + if (v->subtype <= 2) _network_player_info[v->owner].num_vehicle[3]++; + break; + + case VEH_Ship: + _network_player_info[v->owner].num_vehicle[4]++; + break; + + case VEH_Special: + case VEH_Disaster: + break; + } + } + + // Go through all stations and count the types of stations + FOR_ALL_STATIONS(s) { + if (IsValidPlayer(s->owner)) { + NetworkPlayerInfo *npi = &_network_player_info[s->owner]; + + if (s->facilities & FACIL_TRAIN) npi->num_station[0]++; + if (s->facilities & FACIL_TRUCK_STOP) npi->num_station[1]++; + if (s->facilities & FACIL_BUS_STOP) npi->num_station[2]++; + if (s->facilities & FACIL_AIRPORT) npi->num_station[3]++; + if (s->facilities & FACIL_DOCK) npi->num_station[4]++; + } + } + + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + // Register local player (if not dedicated) + if (ci != NULL && IsValidPlayer(ci->client_playas)) + ttd_strlcpy(_network_player_info[ci->client_playas].players, ci->client_name, sizeof(_network_player_info[0].players)); + + FOR_ALL_CLIENTS(cs) { + char client_name[NETWORK_CLIENT_NAME_LENGTH]; + + NetworkGetClientName(client_name, sizeof(client_name), cs); + + ci = DEREF_CLIENT_INFO(cs); + if (ci != NULL && IsValidPlayer(ci->client_playas)) { + if (strlen(_network_player_info[ci->client_playas].players) != 0) + ttd_strlcat(_network_player_info[ci->client_playas].players, ", ", lengthof(_network_player_info[0].players)); + + ttd_strlcat(_network_player_info[ci->client_playas].players, client_name, lengthof(_network_player_info[0].players)); + } + } +} + +// Send a packet to all clients with updated info about this client_index +void NetworkUpdateClientInfo(uint16 client_index) +{ + NetworkClientState *cs; + NetworkClientInfo *ci = NetworkFindClientInfoFromIndex(client_index); + + if (ci == NULL) return; + + FOR_ALL_CLIENTS(cs) { + SEND_COMMAND(PACKET_SERVER_CLIENT_INFO)(cs, ci); + } +} + +/* Check if we want to restart the map */ +static void NetworkCheckRestartMap(void) +{ + if (_network_restart_game_year != 0 && _cur_year >= _network_restart_game_year) { + DEBUG(net, 0, "Auto-restarting map. Year %d reached", _cur_year); + + StartNewGameWithoutGUI(GENERATE_NEW_SEED); + } +} + +/* Check if the server has autoclean_companies activated + Two things happen: + 1) If a company is not protected, it is closed after 1 year (for example) + 2) If a company is protected, protection is disabled after 3 years (for example) + (and item 1. happens a year later) */ +static void NetworkAutoCleanCompanies(void) +{ + const NetworkClientState *cs; + const NetworkClientInfo *ci; + const Player *p; + bool clients_in_company[MAX_PLAYERS]; + + if (!_network_autoclean_companies) return; + + memset(clients_in_company, 0, sizeof(clients_in_company)); + + /* Detect the active companies */ + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (IsValidPlayer(ci->client_playas)) clients_in_company[ci->client_playas] = true; + } + + if (!_network_dedicated) { + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (IsValidPlayer(ci->client_playas)) clients_in_company[ci->client_playas] = true; + } + + /* Go through all the comapnies */ + FOR_ALL_PLAYERS(p) { + /* Skip the non-active once */ + if (!p->is_active || p->is_ai) continue; + + if (!clients_in_company[p->index]) { + /* The company is empty for one month more */ + _network_player_info[p->index].months_empty++; + + /* Is the company empty for autoclean_unprotected-months, and is there no protection? */ + if (_network_player_info[p->index].months_empty > _network_autoclean_unprotected && _network_player_info[p->index].password[0] == '\0') { + /* Shut the company down */ + DoCommandP(0, 2, p->index, NULL, CMD_PLAYER_CTRL); + IConsolePrintF(_icolour_def, "Auto-cleaned company #%d", p->index + 1); + } + /* Is the compnay empty for autoclean_protected-months, and there is a protection? */ + if (_network_player_info[p->index].months_empty > _network_autoclean_protected && _network_player_info[p->index].password[0] != '\0') { + /* Unprotect the company */ + _network_player_info[p->index].password[0] = '\0'; + IConsolePrintF(_icolour_def, "Auto-removed protection from company #%d", p->index+1); + _network_player_info[p->index].months_empty = 0; + } + } else { + /* It is not empty, reset the date */ + _network_player_info[p->index].months_empty = 0; + } + } +} + +// This function changes new_name to a name that is unique (by adding #1 ...) +// and it returns true if that succeeded. +bool NetworkFindName(char new_name[NETWORK_CLIENT_NAME_LENGTH]) +{ + NetworkClientState *new_cs; + bool found_name = false; + byte number = 0; + char original_name[NETWORK_CLIENT_NAME_LENGTH]; + + // We use NETWORK_CLIENT_NAME_LENGTH in here, because new_name is really a pointer + ttd_strlcpy(original_name, new_name, NETWORK_CLIENT_NAME_LENGTH); + + while (!found_name) { + const NetworkClientInfo *ci; + + found_name = true; + FOR_ALL_CLIENTS(new_cs) { + ci = DEREF_CLIENT_INFO(new_cs); + if (strcmp(ci->client_name, new_name) == 0) { + // Name already in use + found_name = false; + break; + } + } + // Check if it is the same as the server-name + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (ci != NULL) { + if (strcmp(ci->client_name, new_name) == 0) found_name = false; // name already in use + } + + if (!found_name) { + // Try a new name (<name> #1, <name> #2, and so on) + + // Stop if we tried for more than 50 times.. + if (number++ > 50) break; + snprintf(new_name, NETWORK_CLIENT_NAME_LENGTH, "%s #%d", original_name, number); + } + } + + return found_name; +} + +// Reads a packet from the stream +bool NetworkServer_ReadPackets(NetworkClientState *cs) +{ + Packet *p; + NetworkRecvStatus res; + while ((p = NetworkRecv_Packet(cs, &res)) != NULL) { + byte type = NetworkRecv_uint8(cs, p); + if (type < PACKET_END && _network_server_packet[type] != NULL && !cs->has_quit) { + _network_server_packet[type](cs, p); + } else { + DEBUG(net, 0, "[server] received invalid packet type %d", type); + } + free(p); + } + + return true; +} + +// Handle the local command-queue +static void NetworkHandleCommandQueue(NetworkClientState* cs) +{ + CommandPacket *cp; + + while ( (cp = cs->command_queue) != NULL) { + SEND_COMMAND(PACKET_SERVER_COMMAND)(cs, cp); + + cs->command_queue = cp->next; + free(cp); + } +} + +// This is called every tick if this is a _network_server +void NetworkServer_Tick(bool send_frame) +{ + NetworkClientState *cs; +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + bool send_sync = false; +#endif + +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + if (_frame_counter >= _last_sync_frame + _network_sync_freq) { + _last_sync_frame = _frame_counter; + send_sync = true; + } +#endif + + // Now we are done with the frame, inform the clients that they can + // do their frame! + FOR_ALL_CLIENTS(cs) { + // Check if the speed of the client is what we can expect from a client + if (cs->status == STATUS_ACTIVE) { + // 1 lag-point per day + int lag = NetworkCalculateLag(cs) / DAY_TICKS; + if (lag > 0) { + if (lag > 3) { + // Client did still not report in after 4 game-day, drop him + // (that is, the 3 of above, + 1 before any lag is counted) + IConsolePrintF(_icolour_err,"Client #%d is dropped because the client did not respond for more than 4 game-days", cs->index); + NetworkCloseClient(cs); + continue; + } + + // Report once per time we detect the lag + if (cs->lag_test == 0) { + IConsolePrintF(_icolour_warn,"[%d] Client #%d is slow, try increasing *net_frame_freq to a higher value!", _frame_counter, cs->index); + cs->lag_test = 1; + } + } else { + cs->lag_test = 0; + } + } else if (cs->status == STATUS_PRE_ACTIVE) { + int lag = NetworkCalculateLag(cs); + if (lag > _network_max_join_time) { + IConsolePrintF(_icolour_err,"Client #%d is dropped because it took longer than %d ticks for him to join", cs->index, _network_max_join_time); + NetworkCloseClient(cs); + } + } + + if (cs->status >= STATUS_PRE_ACTIVE) { + // Check if we can send command, and if we have anything in the queue + NetworkHandleCommandQueue(cs); + + // Send an updated _frame_counter_max to the client + if (send_frame) SEND_COMMAND(PACKET_SERVER_FRAME)(cs); + +#ifndef ENABLE_NETWORK_SYNC_EVERY_FRAME + // Send a sync-check packet + if (send_sync) SEND_COMMAND(PACKET_SERVER_SYNC)(cs); +#endif + } + } + + /* See if we need to advertise */ + NetworkUDPAdvertise(); +} + +void NetworkServerYearlyLoop(void) +{ + NetworkCheckRestartMap(); +} + +void NetworkServerMonthlyLoop(void) +{ + NetworkAutoCleanCompanies(); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_server.h b/src/network/network_server.h new file mode 100644 index 000000000..c5b2baade --- /dev/null +++ b/src/network/network_server.h @@ -0,0 +1,39 @@ +/* $Id$ */ + +#ifndef NETWORK_SERVER_H +#define NETWORK_SERVER_H + +#ifdef ENABLE_NETWORK + +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_MAP); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR_QUIT)(NetworkClientState *cs, uint16 client_index, NetworkErrorCode errorno); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_ERROR)(NetworkClientState *cs, NetworkErrorCode error); +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_SHUTDOWN); +DEF_SERVER_SEND_COMMAND(PACKET_SERVER_NEWGAME); +DEF_SERVER_SEND_COMMAND_PARAM(PACKET_SERVER_RCON)(NetworkClientState *cs, uint16 color, const char *command); + +bool NetworkFindName(char new_name[NETWORK_CLIENT_NAME_LENGTH]); +void NetworkServer_HandleChat(NetworkAction action, DestType type, int dest, const char *msg, uint16 from_index); + +bool NetworkServer_ReadPackets(NetworkClientState *cs); +void NetworkServer_Tick(bool send_frame); +void NetworkServerMonthlyLoop(void); +void NetworkServerYearlyLoop(void); + +static inline const char* GetPlayerIP(const NetworkClientInfo* ci) +{ + struct in_addr addr; + + addr.s_addr = ci->client_ip; + return inet_ntoa(addr); +} + +#else /* ENABLE_NETWORK */ +/* Network function stubs when networking is disabled */ + +static inline void NetworkServerMonthlyLoop(void) {} +static inline void NetworkServerYearlyLoop(void) {} + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_SERVER_H */ diff --git a/src/network/network_udp.c b/src/network/network_udp.c new file mode 100644 index 000000000..aeef75e77 --- /dev/null +++ b/src/network/network_udp.c @@ -0,0 +1,663 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../stdafx.h" +#include "../debug.h" +#include "../string.h" +#include "network_data.h" +#include "../date.h" +#include "../map.h" +#include "network_gamelist.h" +#include "network_udp.h" +#include "../variables.h" +#include "../newgrf_config.h" + +#include "core/udp.h" + +/** + * @file network_udp.c This file handles the UDP related communication. + * + * This is the GameServer <-> MasterServer and GameServer <-> GameClient + * communication before the game is being joined. + */ + +enum { + ADVERTISE_NORMAL_INTERVAL = 30000, // interval between advertising in ticks (15 minutes) + ADVERTISE_RETRY_INTERVAL = 300, // readvertise when no response after this many ticks (9 seconds) + ADVERTISE_RETRY_TIMES = 3 // give up readvertising after this much failed retries +}; + +#define DEF_UDP_RECEIVE_COMMAND(type) void NetworkPacketReceive_ ## type ## _command(Packet *p, struct sockaddr_in *client_addr) + +static NetworkClientState _udp_cs; + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_FIND_SERVER) +{ + Packet *packet; + // Just a fail-safe.. should never happen + if (!_network_udp_server) + return; + + packet = NetworkSend_Init(PACKET_UDP_SERVER_RESPONSE); + + // Update some game_info + _network_game_info.game_date = _date; + _network_game_info.map_width = MapSizeX(); + _network_game_info.map_height = MapSizeY(); + _network_game_info.map_set = _opt.landscape; + _network_game_info.companies_on = ActivePlayerCount(); + _network_game_info.spectators_on = NetworkSpectatorCount(); + _network_game_info.grfconfig = _grfconfig; + + NetworkSend_NetworkGameInfo(p, &_network_game_info); + + // Let the client know that we are here + NetworkSendUDP_Packet(_udp_server_socket, packet, client_addr); + + free(packet); + + DEBUG(net, 2, "[udp] queried from '%s'", inet_ntoa(client_addr->sin_addr)); +} + +void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config) +{ + /* Find the matching GRF file */ + const GRFConfig *f = FindGRFConfig(config->grfid, config->md5sum); + if (f == NULL) { + /* 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->grfid, config->md5sum, true); + SETBIT(config->flags, GCF_NOT_FOUND); + } else { + config->filename = f->filename; + config->name = f->name; + config->info = f->info; + } + SETBIT(config->flags, GCF_COPY); +} + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE) +{ + extern const char _openttd_revision[]; + NetworkGameList *item; + + // Just a fail-safe.. should never happen + if (_network_udp_server || _udp_cs.has_quit) return; + + DEBUG(net, 4, "[udp] server response from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); + + // Find next item + item = NetworkGameListAddItem(inet_addr(inet_ntoa(client_addr->sin_addr)), ntohs(client_addr->sin_port)); + + NetworkRecv_NetworkGameInfo(&_udp_cs, 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; + struct sockaddr_in out_addr; + + for (c = item->info.grfconfig; c != NULL; c = c->next) { + if (HASBIT(c->flags, GCF_NOT_FOUND)) item->info.compatible = false; + if (!HASBIT(c->flags, GCF_NOT_FOUND) || strcmp(c->name, 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 = NetworkSend_Init(PACKET_UDP_CLIENT_GET_NEWGRFS); + + NetworkSend_uint8 (packet, in_request_count); + for (i = 0; i < in_request_count; i++) { + NetworkSend_GRFIdentifier(packet, in_request[i]); + } + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(item->port); + out_addr.sin_addr.s_addr = item->ip; + NetworkSendUDP_Packet(_udp_client_socket, packet, &out_addr); + free(packet); + } + } + + if (item->info.server_lang >= NETWORK_NUM_LANGUAGES) item->info.server_lang = 0; + if (item->info.map_set >= NUM_LANDSCAPE ) item->info.map_set = 0; + + if (item->info.hostname[0] == '\0') + snprintf(item->info.hostname, sizeof(item->info.hostname), "%s", inet_ntoa(client_addr->sin_addr)); + + /* Check if we are allowed on this server based on the revision-match */ + item->info.version_compatible = + strcmp(item->info.server_revision, _openttd_revision) == 0 || + strcmp(item->info.server_revision, NOREV_STRING) == 0; + item->info.compatible &= item->info.version_compatible; // Already contains match for GRFs + + item->online = true; + + UpdateNetworkGameWindow(false); +} + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_DETAIL_INFO) +{ + NetworkClientState *cs; + NetworkClientInfo *ci; + Packet *packet; + Player *player; + byte current = 0; + int i; + + // Just a fail-safe.. should never happen + if (!_network_udp_server) return; + + packet = NetworkSend_Init(PACKET_UDP_SERVER_DETAIL_INFO); + + /* Send the amount of active companies */ + NetworkSend_uint8 (packet, NETWORK_COMPANY_INFO_VERSION); + NetworkSend_uint8 (packet, ActivePlayerCount()); + + /* Fetch the latest version of everything */ + NetworkPopulateCompanyInfo(); + + /* Go through all the players */ + FOR_ALL_PLAYERS(player) { + /* Skip non-active players */ + if (!player->is_active) continue; + + current++; + + /* Send the information */ + NetworkSend_uint8(packet, current); + + NetworkSend_string(packet, _network_player_info[player->index].company_name); + NetworkSend_uint32(packet, _network_player_info[player->index].inaugurated_year); + NetworkSend_uint64(packet, _network_player_info[player->index].company_value); + NetworkSend_uint64(packet, _network_player_info[player->index].money); + NetworkSend_uint64(packet, _network_player_info[player->index].income); + NetworkSend_uint16(packet, _network_player_info[player->index].performance); + + /* Send 1 if there is a passord for the company else send 0 */ + if (_network_player_info[player->index].password[0] != '\0') { + NetworkSend_uint8(packet, 1); + } else { + NetworkSend_uint8(packet, 0); + } + + for (i = 0; i < NETWORK_VEHICLE_TYPES; i++) + NetworkSend_uint16(packet, _network_player_info[player->index].num_vehicle[i]); + + for (i = 0; i < NETWORK_STATION_TYPES; i++) + NetworkSend_uint16(packet, _network_player_info[player->index].num_station[i]); + + /* Find the clients that are connected to this player */ + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (ci->client_playas == player->index) { + /* The uint8 == 1 indicates that a client is following */ + NetworkSend_uint8(packet, 1); + NetworkSend_string(packet, ci->client_name); + NetworkSend_string(packet, ci->unique_id); + NetworkSend_uint32(packet, ci->join_date); + } + } + /* Also check for the server itself */ + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (ci->client_playas == player->index) { + /* The uint8 == 1 indicates that a client is following */ + NetworkSend_uint8(packet, 1); + NetworkSend_string(packet, ci->client_name); + NetworkSend_string(packet, ci->unique_id); + NetworkSend_uint32(packet, ci->join_date); + } + + /* Indicates end of client list */ + NetworkSend_uint8(packet, 0); + } + + /* And check if we have any spectators */ + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (!IsValidPlayer(ci->client_playas)) { + /* The uint8 == 1 indicates that a client is following */ + NetworkSend_uint8(packet, 1); + NetworkSend_string(packet, ci->client_name); + NetworkSend_string(packet, ci->unique_id); + NetworkSend_uint32(packet, ci->join_date); + } + } + + /* Also check for the server itself */ + ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + if (!IsValidPlayer(ci->client_playas)) { + /* The uint8 == 1 indicates that a client is following */ + NetworkSend_uint8(packet, 1); + NetworkSend_string(packet, ci->client_name); + NetworkSend_string(packet, ci->unique_id); + NetworkSend_uint32(packet, ci->join_date); + } + + /* Indicates end of client list */ + NetworkSend_uint8(packet, 0); + + NetworkSendUDP_Packet(_udp_server_socket, packet, client_addr); + + free(packet); +} + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_MASTER_RESPONSE_LIST) +{ + int i; + struct in_addr ip; + uint16 port; + uint8 ver; + + /* 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 + */ + + ver = NetworkRecv_uint8(&_udp_cs, p); + + if (_udp_cs.has_quit) return; + + if (ver == 1) { + for (i = NetworkRecv_uint16(&_udp_cs, p); i != 0 ; i--) { + ip.s_addr = TO_LE32(NetworkRecv_uint32(&_udp_cs, p)); + port = NetworkRecv_uint16(&_udp_cs, p); + NetworkUDPQueryServer(inet_ntoa(ip), port); + } + } +} + +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_MASTER_ACK_REGISTER) +{ + _network_advertise_retries = 0; + DEBUG(net, 2, "[udp] advertising on master server successfull"); + + /* We are advertised, but we don't want to! */ + if (!_network_advertise) NetworkUDPRemoveAdvertise(); +} + +/** + * 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. + */ +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_CLIENT_GET_NEWGRFS) +{ + uint8 num_grfs; + uint i; + + const GRFConfig *in_reply[NETWORK_MAX_GRF_COUNT]; + Packet *packet; + uint8 in_reply_count = 0; + uint packet_len = 0; + + /* Just a fail-safe.. should never happen */ + if (_udp_cs.has_quit) return; + + DEBUG(net, 6, "[udp] newgrf data request from %s:%d", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); + + num_grfs = NetworkRecv_uint8 (&_udp_cs, p); + if (num_grfs > NETWORK_MAX_GRF_COUNT) return; + + for (i = 0; i < num_grfs; i++) { + GRFConfig c; + const GRFConfig *f; + + NetworkRecv_GRFIdentifier(&_udp_cs, p, &c); + + /* Find the matching GRF file */ + f = FindGRFConfig(c.grfid, c.md5sum); + if (f == NULL) 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) + + min(strlen((f->name != NULL && strlen(f->name) > 0) ? f->name : f->filename) + 1, 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 = NetworkSend_Init(PACKET_UDP_SERVER_NEWGRFS); + NetworkSend_uint8 (packet, 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 */ + ttd_strlcpy(name, (in_reply[i]->name != NULL && strlen(in_reply[i]->name) > 0) ? + in_reply[i]->name : in_reply[i]->filename, sizeof(name)); + NetworkSend_GRFIdentifier(packet, in_reply[i]); + NetworkSend_string(packet, name); + } + + NetworkSendUDP_Packet(_udp_server_socket, packet, client_addr); + free(packet); +} + +/** The return of the client's request of the names of some NewGRFs */ +DEF_UDP_RECEIVE_COMMAND(PACKET_UDP_SERVER_NEWGRFS) +{ + uint8 num_grfs; + uint i; + + /* Just a fail-safe.. should never happen */ + if (_udp_cs.has_quit) return; + + DEBUG(net, 6, "[udp] newgrf data reply from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); + + num_grfs = NetworkRecv_uint8 (&_udp_cs, p); + if (num_grfs > NETWORK_MAX_GRF_COUNT) return; + + for (i = 0; i < num_grfs; i++) { + char *unknown_name; + char name[NETWORK_GRF_NAME_LENGTH]; + GRFConfig c; + + NetworkRecv_GRFIdentifier(&_udp_cs, p, &c); + NetworkRecv_string(&_udp_cs, p, name, sizeof(name)); + + /* An empty name is not possible under normal circumstances + * and causes problems when showing the NewGRF list. */ + if (strlen(name) == 0) continue; + + /* Finds the fake GRFConfig for the just read 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. */ + unknown_name = FindUnknownGRFName(c.grfid, c.md5sum, false); + if (unknown_name != NULL && strcmp(unknown_name, UNKNOWN_GRF_NAME_PLACEHOLDER) == 0) { + ttd_strlcpy(unknown_name, name, NETWORK_GRF_NAME_LENGTH); + } + } +} + + +// The layout for the receive-functions by UDP +typedef void NetworkUDPPacket(Packet *p, struct sockaddr_in *client_addr); + +static NetworkUDPPacket* const _network_udp_packet[] = { + RECEIVE_COMMAND(PACKET_UDP_CLIENT_FIND_SERVER), + RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE), + RECEIVE_COMMAND(PACKET_UDP_CLIENT_DETAIL_INFO), + NULL, + NULL, + RECEIVE_COMMAND(PACKET_UDP_MASTER_ACK_REGISTER), + NULL, + RECEIVE_COMMAND(PACKET_UDP_MASTER_RESPONSE_LIST), + NULL, + RECEIVE_COMMAND(PACKET_UDP_CLIENT_GET_NEWGRFS), + RECEIVE_COMMAND(PACKET_UDP_SERVER_NEWGRFS), +}; + + +// If this fails, check the array above with network_data.h +assert_compile(lengthof(_network_udp_packet) == PACKET_UDP_END); + + +void NetworkHandleUDPPacket(Packet *p, struct sockaddr_in *client_addr) +{ + byte type; + + /* Fake a client, so we can see when there is an illegal packet */ + _udp_cs.socket = INVALID_SOCKET; + _udp_cs.has_quit = false; + + type = NetworkRecv_uint8(&_udp_cs, p); + + if (type < PACKET_UDP_END && _network_udp_packet[type] != NULL && !_udp_cs.has_quit) { + _network_udp_packet[type](p, client_addr); + } else { + if (!_udp_cs.has_quit) { + DEBUG(net, 0, "[udp] received invalid packet type %d", type); + } else { + DEBUG(net, 0, "[udp] received illegal packet"); + } + } +} + + +// Close UDP connection +void NetworkUDPClose(void) +{ + DEBUG(net, 1, "[udp] closed listeners"); + + if (_network_udp_server) { + if (_udp_server_socket != INVALID_SOCKET) { + closesocket(_udp_server_socket); + _udp_server_socket = INVALID_SOCKET; + } + + if (_udp_master_socket != INVALID_SOCKET) { + closesocket(_udp_master_socket); + _udp_master_socket = INVALID_SOCKET; + } + + _network_udp_server = false; + _network_udp_broadcast = 0; + } else { + if (_udp_client_socket != INVALID_SOCKET) { + closesocket(_udp_client_socket); + _udp_client_socket = INVALID_SOCKET; + } + _network_udp_broadcast = 0; + } +} + +// Broadcast to all ips +static void NetworkUDPBroadCast(SOCKET udp) +{ + Packet* p = NetworkSend_Init(PACKET_UDP_CLIENT_FIND_SERVER); + uint i; + + for (i = 0; _broadcast_list[i] != 0; i++) { + struct sockaddr_in out_addr; + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(_network_server_port); + out_addr.sin_addr.s_addr = _broadcast_list[i]; + + DEBUG(net, 4, "[udp] broadcasting to %s", inet_ntoa(out_addr.sin_addr)); + + NetworkSendUDP_Packet(udp, p, &out_addr); + } + + free(p); +} + + +// Request the the server-list from the master server +void NetworkUDPQueryMasterServer(void) +{ + struct sockaddr_in out_addr; + Packet *p; + + if (_udp_client_socket == INVALID_SOCKET) + if (!NetworkUDPListen(&_udp_client_socket, 0, 0, true)) + return; + + p = NetworkSend_Init(PACKET_UDP_CLIENT_GET_LIST); + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(NETWORK_MASTER_SERVER_PORT); + out_addr.sin_addr.s_addr = NetworkResolveHost(NETWORK_MASTER_SERVER_HOST); + + // packet only contains protocol version + NetworkSend_uint8(p, NETWORK_MASTER_SERVER_VERSION); + + NetworkSendUDP_Packet(_udp_client_socket, p, &out_addr); + + DEBUG(net, 2, "[udp] master server queried at %s:%d", inet_ntoa(out_addr.sin_addr),ntohs(out_addr.sin_port)); + + free(p); +} + +// Find all servers +void NetworkUDPSearchGame(void) +{ + // We are still searching.. + if (_network_udp_broadcast > 0) return; + + // No UDP-socket yet.. + if (_udp_client_socket == INVALID_SOCKET) + if (!NetworkUDPListen(&_udp_client_socket, 0, 0, true)) + return; + + DEBUG(net, 0, "[udp] searching server"); + + NetworkUDPBroadCast(_udp_client_socket); + _network_udp_broadcast = 300; // Stay searching for 300 ticks +} + +NetworkGameList *NetworkUDPQueryServer(const char* host, unsigned short port) +{ + struct sockaddr_in out_addr; + Packet *p; + NetworkGameList *item; + + // No UDP-socket yet.. + if (_udp_client_socket == INVALID_SOCKET) + if (!NetworkUDPListen(&_udp_client_socket, 0, 0, true)) + return NULL; + + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(port); + out_addr.sin_addr.s_addr = NetworkResolveHost(host); + + // Clear item in gamelist + item = NetworkGameListAddItem(inet_addr(inet_ntoa(out_addr.sin_addr)), ntohs(out_addr.sin_port)); + memset(&item->info, 0, sizeof(item->info)); + ttd_strlcpy(item->info.server_name, host, lengthof(item->info.server_name)); + ttd_strlcpy(item->info.hostname, host, lengthof(item->info.hostname)); + item->online = false; + + // Init the packet + p = NetworkSend_Init(PACKET_UDP_CLIENT_FIND_SERVER); + + NetworkSendUDP_Packet(_udp_client_socket, p, &out_addr); + + free(p); + + UpdateNetworkGameWindow(false); + return item; +} + +/* Remove our advertise from the master-server */ +void NetworkUDPRemoveAdvertise(void) +{ + struct sockaddr_in out_addr; + Packet *p; + + /* Check if we are advertising */ + if (!_networking || !_network_server || !_network_udp_server) return; + + /* check for socket */ + if (_udp_master_socket == INVALID_SOCKET) + if (!NetworkUDPListen(&_udp_master_socket, _network_server_bind_ip, 0, false)) + return; + + DEBUG(net, 1, "[udp] removing advertise from master server"); + + /* Find somewhere to send */ + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(NETWORK_MASTER_SERVER_PORT); + out_addr.sin_addr.s_addr = NetworkResolveHost(NETWORK_MASTER_SERVER_HOST); + + /* Send the packet */ + p = NetworkSend_Init(PACKET_UDP_SERVER_UNREGISTER); + /* Packet is: Version, server_port */ + NetworkSend_uint8(p, NETWORK_MASTER_SERVER_VERSION); + NetworkSend_uint16(p, _network_server_port); + NetworkSendUDP_Packet(_udp_master_socket, p, &out_addr); + + free(p); +} + +/* Register us to the master server + This function checks if it needs to send an advertise */ +void NetworkUDPAdvertise(void) +{ + struct sockaddr_in out_addr; + Packet *p; + + /* Check if we should send an advertise */ + if (!_networking || !_network_server || !_network_udp_server || !_network_advertise) + return; + + /* check for socket */ + if (_udp_master_socket == INVALID_SOCKET) + if (!NetworkUDPListen(&_udp_master_socket, _network_server_bind_ip, 0, false)) + return; + + if (_network_need_advertise) { + _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 ((_network_last_advertise_frame + ADVERTISE_NORMAL_INTERVAL) > _frame_counter) + return; + _network_advertise_retries = ADVERTISE_RETRY_TIMES; + } + + if ((_network_last_advertise_frame + ADVERTISE_RETRY_INTERVAL) > _frame_counter) + return; + } + + _network_advertise_retries--; + _network_last_advertise_frame = _frame_counter; + + /* Find somewhere to send */ + out_addr.sin_family = AF_INET; + out_addr.sin_port = htons(NETWORK_MASTER_SERVER_PORT); + out_addr.sin_addr.s_addr = NetworkResolveHost(NETWORK_MASTER_SERVER_HOST); + + DEBUG(net, 1, "[udp] advertising to master server"); + + /* Send the packet */ + p = NetworkSend_Init(PACKET_UDP_SERVER_REGISTER); + /* Packet is: WELCOME_MESSAGE, Version, server_port */ + NetworkSend_string(p, NETWORK_MASTER_SERVER_WELCOME_MESSAGE); + NetworkSend_uint8(p, NETWORK_MASTER_SERVER_VERSION); + NetworkSend_uint16(p, _network_server_port); + NetworkSendUDP_Packet(_udp_master_socket, p, &out_addr); + + free(p); +} + +void NetworkUDPInitialize(void) +{ + _udp_client_socket = INVALID_SOCKET; + _udp_server_socket = INVALID_SOCKET; + _udp_master_socket = INVALID_SOCKET; + + _network_udp_server = false; + _network_udp_broadcast = 0; +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_udp.h b/src/network/network_udp.h new file mode 100644 index 000000000..4488d19d4 --- /dev/null +++ b/src/network/network_udp.h @@ -0,0 +1,17 @@ +/* $Id$ */ + +#ifndef NETWORK_UDP_H +#define NETWORK_UDP_H + +#ifdef ENABLE_NETWORK + +void NetworkUDPInitialize(void); +void NetworkUDPSearchGame(void); +void NetworkUDPQueryMasterServer(void); +NetworkGameList *NetworkUDPQueryServer(const char* host, unsigned short port); +void NetworkUDPAdvertise(void); +void NetworkUDPRemoveAdvertise(void); + +#endif /* ENABLE_NETWORK */ + +#endif /* NETWORK_UDP_H */ |