From e373ea7096c741a8189f7a480863fd21dd6f6be1 Mon Sep 17 00:00:00 2001 From: KUDr Date: Wed, 10 Jan 2007 18:12:09 +0000 Subject: (svn r8033) [cpp] - Prepare for merge from branches/cpp (all .c files renamed to .cpp) --- src/network/core/core.c | 86 -- src/network/core/core.cpp | 86 ++ src/network/core/packet.c | 214 ----- src/network/core/packet.cpp | 214 +++++ src/network/core/tcp.c | 225 ----- src/network/core/tcp.cpp | 225 +++++ src/network/core/udp.c | 297 ------- src/network/core/udp.cpp | 297 +++++++ src/network/network.c | 1389 ------------------------------- src/network/network.cpp | 1389 +++++++++++++++++++++++++++++++ src/network/network_client.c | 819 ------------------ src/network/network_client.cpp | 819 ++++++++++++++++++ src/network/network_data.c | 106 --- src/network/network_data.cpp | 106 +++ src/network/network_gamelist.c | 74 -- src/network/network_gamelist.cpp | 74 ++ src/network/network_gui.c | 1706 -------------------------------------- src/network/network_gui.cpp | 1706 ++++++++++++++++++++++++++++++++++++++ src/network/network_server.c | 1528 ---------------------------------- src/network/network_server.cpp | 1528 ++++++++++++++++++++++++++++++++++ src/network/network_udp.c | 655 --------------- src/network/network_udp.cpp | 655 +++++++++++++++ 22 files changed, 7099 insertions(+), 7099 deletions(-) delete mode 100644 src/network/core/core.c create mode 100644 src/network/core/core.cpp delete mode 100644 src/network/core/packet.c create mode 100644 src/network/core/packet.cpp delete mode 100644 src/network/core/tcp.c create mode 100644 src/network/core/tcp.cpp delete mode 100644 src/network/core/udp.c create mode 100644 src/network/core/udp.cpp delete mode 100644 src/network/network.c create mode 100644 src/network/network.cpp delete mode 100644 src/network/network_client.c create mode 100644 src/network/network_client.cpp delete mode 100644 src/network/network_data.c create mode 100644 src/network/network_data.cpp delete mode 100644 src/network/network_gamelist.c create mode 100644 src/network/network_gamelist.cpp delete mode 100644 src/network/network_gui.c create mode 100644 src/network/network_gui.cpp delete mode 100644 src/network/network_server.c create mode 100644 src/network/network_server.cpp delete mode 100644 src/network/network_udp.c create mode 100644 src/network/network_udp.cpp (limited to 'src/network') diff --git a/src/network/core/core.c b/src/network/core/core.c deleted file mode 100644 index ca953e5f0..000000000 --- a/src/network/core/core.c +++ /dev/null @@ -1,86 +0,0 @@ -/* $Id$ */ - -#ifdef ENABLE_NETWORK - -#include "../../stdafx.h" -#include "../../debug.h" -#include "os_abstraction.h" - -#ifdef __MORPHOS__ -/* the library base is required here */ -struct Library *SocketBase = NULL; -#endif - -/** - * Initializes the network core (as that is needed for some platforms - */ -bool NetworkCoreInitialize(void) -{ -#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"); - return false; - } - -#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"); - return false; - } - } - } - } -#endif // __AMIGA__ -#endif // __MORPHOS__ / __AMIGA__ - -/* Let's load the network in windows */ -#ifdef 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"); - return false; - } - } -#endif /* WIN32 */ - - return true; -} - -/** - * Shuts down the network core (as that is needed for some platforms - */ -void NetworkCoreShutdown(void) -{ -#if defined(__MORPHOS__) || defined(__AMIGA__) - /* free allocated resources */ -#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/core/core.cpp b/src/network/core/core.cpp new file mode 100644 index 000000000..ca953e5f0 --- /dev/null +++ b/src/network/core/core.cpp @@ -0,0 +1,86 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../debug.h" +#include "os_abstraction.h" + +#ifdef __MORPHOS__ +/* the library base is required here */ +struct Library *SocketBase = NULL; +#endif + +/** + * Initializes the network core (as that is needed for some platforms + */ +bool NetworkCoreInitialize(void) +{ +#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"); + return false; + } + +#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"); + return false; + } + } + } + } +#endif // __AMIGA__ +#endif // __MORPHOS__ / __AMIGA__ + +/* Let's load the network in windows */ +#ifdef 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"); + return false; + } + } +#endif /* WIN32 */ + + return true; +} + +/** + * Shuts down the network core (as that is needed for some platforms + */ +void NetworkCoreShutdown(void) +{ +#if defined(__MORPHOS__) || defined(__AMIGA__) + /* free allocated resources */ +#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/core/packet.c b/src/network/core/packet.c deleted file mode 100644 index d75d78c7e..000000000 --- a/src/network/core/packet.c +++ /dev/null @@ -1,214 +0,0 @@ -/* $Id$ */ - -#ifdef ENABLE_NETWORK - -#include "../../stdafx.h" -#include "../../macros.h" -#include "../../string.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(const 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, const Packet *packet, const 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.cpp b/src/network/core/packet.cpp new file mode 100644 index 000000000..d75d78c7e --- /dev/null +++ b/src/network/core/packet.cpp @@ -0,0 +1,214 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../macros.h" +#include "../../string.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(const 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, const Packet *packet, const 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/tcp.c b/src/network/core/tcp.c deleted file mode 100644 index 9261ea049..000000000 --- a/src/network/core/tcp.c +++ /dev/null @@ -1,225 +0,0 @@ -/* $Id$ */ - -#ifdef ENABLE_NETWORK - -#include "../../stdafx.h" -#include "../../debug.h" -#include "../../openttd.h" -#include "../../variables.h" -#include "table/strings.h" -#include "../../functions.h" - -#include "../network_data.h" -#include "packet.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(const 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.cpp b/src/network/core/tcp.cpp new file mode 100644 index 000000000..9261ea049 --- /dev/null +++ b/src/network/core/tcp.cpp @@ -0,0 +1,225 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../openttd.h" +#include "../../variables.h" +#include "table/strings.h" +#include "../../functions.h" + +#include "../network_data.h" +#include "packet.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(const 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/udp.c b/src/network/core/udp.c deleted file mode 100644 index 6699b4b87..000000000 --- a/src/network/core/udp.c +++ /dev/null @@ -1,297 +0,0 @@ -/* $Id$ */ - -#ifdef ENABLE_NETWORK - -#include "../../stdafx.h" -#include "../../debug.h" -#include "../../macros.h" -#include "packet.h" -#include "udp.h" - -/** - * @file udp.c Basic functions to receive and send UDP packets. - */ - -/** - * 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, const uint32 host, const uint16 port, const bool broadcast) -{ - struct sockaddr_in sin; - - /* Make sure socket is closed */ - NetworkUDPClose(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; -} - -/** - * Close the given UDP socket - * @param udp the socket to close - */ -void NetworkUDPClose(SOCKET *udp) -{ - if (*udp == INVALID_SOCKET) return; - - closesocket(*udp); - *udp = INVALID_SOCKET; -} - - -/** - * 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(const SOCKET udp, Packet *p, const 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()); -} - -/** - * Receive a packet at UDP level - * @param udp the socket to receive the packet on - */ -void NetworkUDPReceive(const 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. */ - if (nbytes > 2) { - NetworkRecv_ReadPacketSize(&p); - - /* If the size does not match the packet must be corrupted. - * Otherwise it will be marked as corrupted later on. */ - if (nbytes != p.size) { - DEBUG(net, 1, "received a packet with mismatching size from %s:%d", - inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); - - return; - } - - /* Put the position on the right place */ - p.pos = 2; - p.next = NULL; - - /* Handle the packet */ - NetworkHandleUDPPacket(udp, &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! - */ - - /* Update the documentation in udp.h on changes - * to the NetworkGameInfo wire-protocol! */ - - /* 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! - */ - - /* Update the documentation in udp.h on changes - * to the NetworkGameInfo wire-protocol! */ - - 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.cpp b/src/network/core/udp.cpp new file mode 100644 index 000000000..6699b4b87 --- /dev/null +++ b/src/network/core/udp.cpp @@ -0,0 +1,297 @@ +/* $Id$ */ + +#ifdef ENABLE_NETWORK + +#include "../../stdafx.h" +#include "../../debug.h" +#include "../../macros.h" +#include "packet.h" +#include "udp.h" + +/** + * @file udp.c Basic functions to receive and send UDP packets. + */ + +/** + * 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, const uint32 host, const uint16 port, const bool broadcast) +{ + struct sockaddr_in sin; + + /* Make sure socket is closed */ + NetworkUDPClose(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; +} + +/** + * Close the given UDP socket + * @param udp the socket to close + */ +void NetworkUDPClose(SOCKET *udp) +{ + if (*udp == INVALID_SOCKET) return; + + closesocket(*udp); + *udp = INVALID_SOCKET; +} + + +/** + * 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(const SOCKET udp, Packet *p, const 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()); +} + +/** + * Receive a packet at UDP level + * @param udp the socket to receive the packet on + */ +void NetworkUDPReceive(const 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. */ + if (nbytes > 2) { + NetworkRecv_ReadPacketSize(&p); + + /* If the size does not match the packet must be corrupted. + * Otherwise it will be marked as corrupted later on. */ + if (nbytes != p.size) { + DEBUG(net, 1, "received a packet with mismatching size from %s:%d", + inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + + return; + } + + /* Put the position on the right place */ + p.pos = 2; + p.next = NULL; + + /* Handle the packet */ + NetworkHandleUDPPacket(udp, &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! + */ + + /* Update the documentation in udp.h on changes + * to the NetworkGameInfo wire-protocol! */ + + /* 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! + */ + + /* Update the documentation in udp.h on changes + * to the NetworkGameInfo wire-protocol! */ + + 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/network.c b/src/network/network.c deleted file mode 100644 index ac8dd82b7..000000000 --- a/src/network/network.c +++ /dev/null @@ -1,1389 +0,0 @@ -/* $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 "core/core.h" -#include "network_gui.h" -#include "../console.h" /* IConsoleCmdExec */ -#include /* va_list */ -#include "../md5.h" - -// 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 != endof(_clients); 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 # 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"); - NetworkUDPStop(); - } -} - -// 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(); - NetworkUDPStop(); - 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..."); - - /* Network is available */ - _network_available = NetworkCoreInitialize();; - _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; - } - - - NetworkInitialize(); - DEBUG(net, 3, "[core] network online, multiplayer available"); - NetworkFindIPs(); -} - -/** This shuts the network down */ -void NetworkShutDown(void) -{ - NetworkDisconnect(); - NetworkUDPStop(); - - DEBUG(net, 3, "[core] shutting down network"); - - _network_available = false; - - NetworkCoreShutdown(); -} - -#endif /* ENABLE_NETWORK */ diff --git a/src/network/network.cpp b/src/network/network.cpp new file mode 100644 index 000000000..ac8dd82b7 --- /dev/null +++ b/src/network/network.cpp @@ -0,0 +1,1389 @@ +/* $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 "core/core.h" +#include "network_gui.h" +#include "../console.h" /* IConsoleCmdExec */ +#include /* va_list */ +#include "../md5.h" + +// 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 != endof(_clients); 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 # 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"); + NetworkUDPStop(); + } +} + +// 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(); + NetworkUDPStop(); + 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..."); + + /* Network is available */ + _network_available = NetworkCoreInitialize();; + _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; + } + + + NetworkInitialize(); + DEBUG(net, 3, "[core] network online, multiplayer available"); + NetworkFindIPs(); +} + +/** This shuts the network down */ +void NetworkShutDown(void) +{ + NetworkDisconnect(); + NetworkUDPStop(); + + DEBUG(net, 3, "[core] shutting down network"); + + _network_available = false; + + NetworkCoreShutdown(); +} + +#endif /* ENABLE_NETWORK */ diff --git a/src/network/network_client.c b/src/network/network_client.c deleted file mode 100644 index 6e27eec56..000000000 --- a/src/network/network_client.c +++ /dev/null @@ -1,819 +0,0 @@ -/* $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: - // - // - 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: - // - // - - 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: - // - // - - 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.cpp b/src/network/network_client.cpp new file mode 100644 index 000000000..6e27eec56 --- /dev/null +++ b/src/network/network_client.cpp @@ -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: + // + // + 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: + // + // + + 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: + // + // + + 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_data.c b/src/network/network_data.c deleted file mode 100644 index 9e5e6424d..000000000 --- a/src/network/network_data.c +++ /dev/null @@ -1,106 +0,0 @@ -/* $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.cpp b/src/network/network_data.cpp new file mode 100644 index 000000000..9e5e6424d --- /dev/null +++ b/src/network/network_data.cpp @@ -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_gamelist.c b/src/network/network_gamelist.c deleted file mode 100644 index a830073ad..000000000 --- a/src/network/network_gamelist.c +++ /dev/null @@ -1,74 +0,0 @@ -/* $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.cpp b/src/network/network_gamelist.cpp new file mode 100644 index 000000000..a830073ad --- /dev/null +++ b/src/network/network_gamelist.cpp @@ -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_gui.c b/src/network/network_gui.c deleted file mode 100644 index dd865118e..000000000 --- a/src/network/network_gui.c +++ /dev/null @@ -1,1706 +0,0 @@ -/* $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.cpp b/src/network/network_gui.cpp new file mode 100644 index 000000000..dd865118e --- /dev/null +++ b/src/network/network_gui.cpp @@ -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_server.c b/src/network/network_server.c deleted file mode 100644 index 5a7109105..000000000 --- a/src/network/network_server.c +++ /dev/null @@ -1,1528 +0,0 @@ -/* $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, ""); - } 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: - // - // - - 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: - // - // - - 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 ( #1, #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.cpp b/src/network/network_server.cpp new file mode 100644 index 000000000..5a7109105 --- /dev/null +++ b/src/network/network_server.cpp @@ -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, ""); + } 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: + // + // + + 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: + // + // + + 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 ( #1, #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_udp.c b/src/network/network_udp.c deleted file mode 100644 index 5caec3b42..000000000 --- a/src/network/network_udp.c +++ /dev/null @@ -1,655 +0,0 @@ -/* $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, const 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(packet, &_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); - } - } -} - -/** - * Every type of UDP packet should only be received by a single socket; - * The socket communicating with the masterserver should receive the - * game information of some 'random' host. - */ -typedef struct NetworkUDPPacketAndSocket { - void (*callback)(Packet *p, const struct sockaddr_in *client_addr); - SOCKET *incoming_socket; -} NetworkUPDPacketAndSocket; - -static const NetworkUPDPacketAndSocket _network_udp_packet[PACKET_UDP_END] = { - { RECEIVE_COMMAND(PACKET_UDP_CLIENT_FIND_SERVER), &_udp_server_socket }, - { RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE), &_udp_client_socket }, - { RECEIVE_COMMAND(PACKET_UDP_CLIENT_DETAIL_INFO), &_udp_server_socket }, - { NULL, NULL }, - { NULL, NULL }, - { RECEIVE_COMMAND(PACKET_UDP_MASTER_ACK_REGISTER), &_udp_master_socket }, - { NULL, NULL }, - { RECEIVE_COMMAND(PACKET_UDP_MASTER_RESPONSE_LIST), &_udp_client_socket }, - { NULL, NULL }, - { RECEIVE_COMMAND(PACKET_UDP_CLIENT_GET_NEWGRFS), &_udp_server_socket }, - { RECEIVE_COMMAND(PACKET_UDP_SERVER_NEWGRFS), &_udp_client_socket }, -}; - -void NetworkHandleUDPPacket(const SOCKET udp, Packet *p, const 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].incoming_socket == udp && !_udp_cs.has_quit) { - _network_udp_packet[type].callback(p, client_addr); - } else { - if (*_network_udp_packet[type].incoming_socket != udp) { - DEBUG(net, 0, "[udp] received packet on wrong port from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); - } else if (!_udp_cs.has_quit) { - DEBUG(net, 0, "[udp] received invalid packet type %d from %s:%d", type, inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); - } else { - DEBUG(net, 0, "[udp] received illegal packet from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); - } - } -} - - -// Close UDP connection -void NetworkUDPStop(void) -{ - DEBUG(net, 1, "[udp] closed listeners"); - - if (_network_udp_server) { - NetworkUDPClose(&_udp_server_socket); - NetworkUDPClose(&_udp_master_socket); - } else { - NetworkUDPClose(&_udp_client_socket); - } - - _network_udp_server = false; - _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.cpp b/src/network/network_udp.cpp new file mode 100644 index 000000000..5caec3b42 --- /dev/null +++ b/src/network/network_udp.cpp @@ -0,0 +1,655 @@ +/* $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, const 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(packet, &_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); + } + } +} + +/** + * Every type of UDP packet should only be received by a single socket; + * The socket communicating with the masterserver should receive the + * game information of some 'random' host. + */ +typedef struct NetworkUDPPacketAndSocket { + void (*callback)(Packet *p, const struct sockaddr_in *client_addr); + SOCKET *incoming_socket; +} NetworkUPDPacketAndSocket; + +static const NetworkUPDPacketAndSocket _network_udp_packet[PACKET_UDP_END] = { + { RECEIVE_COMMAND(PACKET_UDP_CLIENT_FIND_SERVER), &_udp_server_socket }, + { RECEIVE_COMMAND(PACKET_UDP_SERVER_RESPONSE), &_udp_client_socket }, + { RECEIVE_COMMAND(PACKET_UDP_CLIENT_DETAIL_INFO), &_udp_server_socket }, + { NULL, NULL }, + { NULL, NULL }, + { RECEIVE_COMMAND(PACKET_UDP_MASTER_ACK_REGISTER), &_udp_master_socket }, + { NULL, NULL }, + { RECEIVE_COMMAND(PACKET_UDP_MASTER_RESPONSE_LIST), &_udp_client_socket }, + { NULL, NULL }, + { RECEIVE_COMMAND(PACKET_UDP_CLIENT_GET_NEWGRFS), &_udp_server_socket }, + { RECEIVE_COMMAND(PACKET_UDP_SERVER_NEWGRFS), &_udp_client_socket }, +}; + +void NetworkHandleUDPPacket(const SOCKET udp, Packet *p, const 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].incoming_socket == udp && !_udp_cs.has_quit) { + _network_udp_packet[type].callback(p, client_addr); + } else { + if (*_network_udp_packet[type].incoming_socket != udp) { + DEBUG(net, 0, "[udp] received packet on wrong port from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); + } else if (!_udp_cs.has_quit) { + DEBUG(net, 0, "[udp] received invalid packet type %d from %s:%d", type, inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); + } else { + DEBUG(net, 0, "[udp] received illegal packet from %s:%d", inet_ntoa(client_addr->sin_addr),ntohs(client_addr->sin_port)); + } + } +} + + +// Close UDP connection +void NetworkUDPStop(void) +{ + DEBUG(net, 1, "[udp] closed listeners"); + + if (_network_udp_server) { + NetworkUDPClose(&_udp_server_socket); + NetworkUDPClose(&_udp_master_socket); + } else { + NetworkUDPClose(&_udp_client_socket); + } + + _network_udp_server = false; + _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 */ -- cgit v1.2.3-70-g09d2