From 6fc99e4c23b8ba94b28280445ce366741c8d5fe3 Mon Sep 17 00:00:00 2001 From: fgsfds Date: Sun, 17 Dec 2023 12:54:48 +0100 Subject: [PATCH] port: net: basic networking with barely working position sync --- Makefile.port | 2 +- port/external/enet.c | 3 + port/external/minimp3.c | 2 + port/include/external/enet.h | 5098 ++++++++++++++++++++++++++++++++++ port/include/input.h | 15 + port/include/net/net.h | 123 + port/include/net/netbuf.h | 47 + port/include/net/netenet.h | 10 + port/include/net/netmsg.h | 36 + port/src/main.c | 2 + port/src/mixer.c | 1 - port/src/net/net.c | 622 +++++ port/src/net/netbuf.c | 270 ++ port/src/net/netmenu.c | 362 +++ port/src/net/netmsg.c | 403 +++ port/src/pdmain.c | 4 + port/src/pdsched.c | 4 + port/src/system.c | 1 + src/game/activemenu.c | 56 +- src/game/bondeyespy.c | 2 +- src/game/bondgun.c | 2 +- src/game/bondmove.c | 113 +- src/game/bondwalk.c | 69 +- src/game/chraction.c | 79 + src/game/hudmsg.c | 32 +- src/game/lv.c | 15 + src/game/mainmenu.c | 25 +- src/game/menu.c | 14 + src/game/menuitem.c | 4 +- src/game/menutick.c | 9 + src/game/mplayer/mplayer.c | 30 +- src/game/mplayer/scenarios.c | 2 +- src/game/music.c | 2 +- src/game/nbomb.c | 2 +- src/game/player.c | 24 +- src/game/playermgr.c | 23 + src/game/prop.c | 10 + src/game/propobj.c | 19 +- src/game/propsnd.c | 6 +- src/game/radar.c | 4 +- src/game/sight.c | 6 +- src/game/zbuf.c | 8 +- src/include/constants.h | 10 + src/include/data.h | 1 + src/include/game/chraction.h | 1 + src/include/types.h | 8 +- src/lib/vi.c | 20 +- 47 files changed, 7500 insertions(+), 101 deletions(-) create mode 100644 port/external/enet.c create mode 100644 port/external/minimp3.c create mode 100644 port/include/external/enet.h create mode 100644 port/include/net/net.h create mode 100644 port/include/net/netbuf.h create mode 100644 port/include/net/netenet.h create mode 100644 port/include/net/netmsg.h create mode 100644 port/src/net/net.c create mode 100644 port/src/net/netbuf.c create mode 100644 port/src/net/netmenu.c create mode 100644 port/src/net/netmsg.c diff --git a/Makefile.port b/Makefile.port index db1d2bbd9..7de750b25 100644 --- a/Makefile.port +++ b/Makefile.port @@ -142,7 +142,7 @@ G_DIR := src/generated/$(ROMID) ifneq (,$(findstring windows,$(TARGET_PLATFORM))) TARGET_CFLAGS := $(shell pkg-config sdl2 --cflags-only-I) - TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lz -lopengl32 -static-libstdc++ -ldbghelp + TARGET_LDFLAGS := $(shell pkg-config sdl2 --libs) -lz -lopengl32 -static-libstdc++ -lws2_32 -lwinmm -ldbghelp # on windows/mingw we need this to be built with a 32-bit compiler so it finds the correct libs TOOLCHAIN ?= i686-w64-mingw32- else # assume *nix diff --git a/port/external/enet.c b/port/external/enet.c new file mode 100644 index 000000000..0bba1b919 --- /dev/null +++ b/port/external/enet.c @@ -0,0 +1,3 @@ +#define ENET_IMPLEMENTATION 1 +#define ENET_NO_PRAGMA_LINK 1 +#include "external/enet.h" diff --git a/port/external/minimp3.c b/port/external/minimp3.c new file mode 100644 index 000000000..5bfd5b6a9 --- /dev/null +++ b/port/external/minimp3.c @@ -0,0 +1,2 @@ +#define MINIMP3_IMPLEMENTATION +#include "external/minimp3.h" diff --git a/port/include/external/enet.h b/port/include/external/enet.h new file mode 100644 index 000000000..2263bb1af --- /dev/null +++ b/port/include/external/enet.h @@ -0,0 +1,5098 @@ +/* + * ENet reliable UDP networking library + * Copyright (c) 2018 Lee Salzman, Vladyslav Hrytsenko, Dominik Madarász, Stanislav Denisov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ENET_H +#define ENET_H + +#include +#include +#include +#include + +#define ENET_VERSION_MAJOR 2 +#define ENET_VERSION_MINOR 4 +#define ENET_VERSION_PATCH 8 +#define ENET_VERSION_CREATE(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) +#define ENET_VERSION_GET_MAJOR(version) (((version) >> 16) & 0xFF) +#define ENET_VERSION_GET_MINOR(version) (((version) >> 8) & 0xFF) +#define ENET_VERSION_GET_PATCH(version) ((version) & 0xFF) +#define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) + +#define ENET_TIME_OVERFLOW 86400000 +#define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) +#define ENET_TIME_LESS_EQUAL(a, b) (!ENET_TIME_GREATER(a, b)) +#define ENET_TIME_GREATER_EQUAL(a, b) (!ENET_TIME_LESS(a, b)) +#define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) + +#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) + +/* +======================================================================= + + System differences + +======================================================================= +*/ + +#ifdef _WIN32 + #ifndef ENET_NO_PRAGMA_LINK + #pragma comment(lib, "ws2_32.lib") + #pragma comment(lib, "winmm.lib") + #endif + + #if _MSC_VER >= 1910 + /* It looks like there were changes as of Visual Studio 2017 and there are no 32/64 bit + versions of _InterlockedExchange[operation], only InterlockedExchange[operation] + (without leading underscore), so we have to distinguish between compiler versions */ + #define ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + #endif + + #include + #include + #include + #include + + #if defined(_WIN32) && defined(_MSC_VER) + #if _MSC_VER < 1900 + typedef struct timespec { + long tv_sec; + long tv_nsec; + }; + #endif + #define CLOCK_MONOTONIC 0 + #endif + + typedef SOCKET ENetSocket; + + #define ENET_SOCKET_NULL INVALID_SOCKET + + typedef struct { + size_t dataLength; + void* data; + } ENetBuffer; + + #define ENET_CALLBACK __cdecl + + #ifdef ENET_DLL + #ifdef ENET_IMPLEMENTATION + #define ENET_API __declspec(dllexport) + #else + #define ENET_API __declspec(dllimport) + #endif + #else + #define ENET_API extern + #endif +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #ifdef __APPLE__ + #include + #include + #include + #endif + + #ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 + #endif + + #ifdef MSG_MAXIOVLEN + #define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN + #endif + + typedef int ENetSocket; + + #define ENET_SOCKET_NULL -1 + + typedef struct { + void* data; + size_t dataLength; + } ENetBuffer; + + #define ENET_CALLBACK + #define ENET_API extern +#endif + +#ifndef ENET_BUFFER_MAXIMUM + #define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) +#endif + +#define ENET_HOST_ANY in6addr_any +#define ENET_PORT_ANY 0 +#define ENET_HOST_SIZE 1025 + +#define ENET_HOST_TO_NET_16(value) (htons(value)) +#define ENET_HOST_TO_NET_32(value) (htonl(value)) +#define ENET_NET_TO_HOST_16(value) (ntohs(value)) +#define ENET_NET_TO_HOST_32(value) (ntohl(value)) + +#if __BIG_ENDIAN__ + #define ENET_HOST_TO_NET_64(x) (x) + #define ENET_NET_TO_HOST_64(x) (x) +#else + #define ENET_HOST_TO_NET_64(x) (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) + #define ENET_NET_TO_HOST_64(x) (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* +======================================================================= + + Internals + +======================================================================= +*/ + + typedef uint32_t ENetVersion; + + typedef fd_set ENetSocketSet; + + typedef struct _ENetCallbacks { + void* (ENET_CALLBACK *malloc)(size_t size); + void (ENET_CALLBACK *free)(void* memory); + void (ENET_CALLBACK *noMemory)(void); + } ENetCallbacks; + + extern void* enet_malloc(size_t); + extern void enet_free(void*); + + typedef struct _ENetListNode { + struct _ENetListNode* next; + struct _ENetListNode* previous; + } ENetListNode; + + typedef ENetListNode* ENetListIterator; + + typedef struct _ENetList { + ENetListNode sentinel; + } ENetList; + + extern ENetListIterator enet_list_insert(ENetListIterator, void*); + extern ENetListIterator enet_list_move(ENetListIterator, void*, void*); + extern void* enet_list_remove(ENetListIterator); + extern void enet_list_clear(ENetList*); + extern size_t enet_list_size(ENetList*); + + #define enet_list_begin(list) ((list)->sentinel.next) + #define enet_list_end(list) (&(list)->sentinel) + #define enet_list_empty(list) (enet_list_begin(list) == enet_list_end(list)) + #define enet_list_next(iterator) ((iterator)->next) + #define enet_list_previous(iterator) ((iterator)->previous) + #define enet_list_front(list) ((void*)(list)->sentinel.next) + #define enet_list_back(list) ((void*)(list)->sentinel.previous) + + #define enet_in6_equal(a, b) (memcmp(&a, &b, sizeof(struct in6_addr)) == 0) + +/* +======================================================================= + + Protocol + +======================================================================= +*/ + + enum { + ENET_PROTOCOL_MINIMUM_MTU = 576, + ENET_PROTOCOL_MAXIMUM_MTU = 4096, + ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, + ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, + ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536, + ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, + ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, + ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF, + ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024 + }; + + typedef enum _ENetProtocolCommand { + ENET_PROTOCOL_COMMAND_NONE = 0, + ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, + ENET_PROTOCOL_COMMAND_CONNECT = 2, + ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, + ENET_PROTOCOL_COMMAND_DISCONNECT = 4, + ENET_PROTOCOL_COMMAND_PING = 5, + ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, + ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, + ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, + ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, + ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, + ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12, + ENET_PROTOCOL_COMMAND_COUNT = 13, + ENET_PROTOCOL_COMMAND_MASK = 0x0F + } ENetProtocolCommand; + + typedef enum _ENetProtocolFlag { + ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), + ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), + ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 14), + ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, + ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), + ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 + } ENetProtocolFlag; + + #ifdef _MSC_VER + #pragma pack(push, 1) + #define ENET_PACKED + #elif defined(__GNUC__) || defined(__clang__) + #define ENET_PACKED __attribute__ ((packed)) + #else + #define ENET_PACKED + #endif + + typedef struct _ENetProtocolHeader { + uint16_t peerID; + uint16_t sentTime; + } ENET_PACKED ENetProtocolHeader; + + typedef struct _ENetProtocolCommandHeader { + uint8_t command; + uint8_t channelID; + uint16_t reliableSequenceNumber; + } ENET_PACKED ENetProtocolCommandHeader; + + typedef struct _ENetProtocolAcknowledge { + ENetProtocolCommandHeader header; + uint16_t receivedReliableSequenceNumber; + uint16_t receivedSentTime; + } ENET_PACKED ENetProtocolAcknowledge; + + typedef struct _ENetProtocolConnect { + ENetProtocolCommandHeader header; + uint16_t outgoingPeerID; + uint8_t incomingSessionID; + uint8_t outgoingSessionID; + uint32_t mtu; + uint32_t windowSize; + uint32_t channelCount; + uint32_t incomingBandwidth; + uint32_t outgoingBandwidth; + uint32_t packetThrottleInterval; + uint32_t packetThrottleAcceleration; + uint32_t packetThrottleDeceleration; + uint32_t connectID; + uint32_t data; + } ENET_PACKED ENetProtocolConnect; + + typedef struct _ENetProtocolVerifyConnect { + ENetProtocolCommandHeader header; + uint16_t outgoingPeerID; + uint8_t incomingSessionID; + uint8_t outgoingSessionID; + uint32_t mtu; + uint32_t windowSize; + uint32_t channelCount; + uint32_t incomingBandwidth; + uint32_t outgoingBandwidth; + uint32_t packetThrottleInterval; + uint32_t packetThrottleAcceleration; + uint32_t packetThrottleDeceleration; + uint32_t connectID; + } ENET_PACKED ENetProtocolVerifyConnect; + + typedef struct _ENetProtocolBandwidthLimit { + ENetProtocolCommandHeader header; + uint32_t incomingBandwidth; + uint32_t outgoingBandwidth; + } ENET_PACKED ENetProtocolBandwidthLimit; + + typedef struct _ENetProtocolThrottleConfigure { + ENetProtocolCommandHeader header; + uint32_t packetThrottleInterval; + uint32_t packetThrottleAcceleration; + uint32_t packetThrottleDeceleration; + } ENET_PACKED ENetProtocolThrottleConfigure; + + typedef struct _ENetProtocolDisconnect { + ENetProtocolCommandHeader header; + uint32_t data; + } ENET_PACKED ENetProtocolDisconnect; + + typedef struct _ENetProtocolPing { + ENetProtocolCommandHeader header; + } ENET_PACKED ENetProtocolPing; + + typedef struct _ENetProtocolSendReliable { + ENetProtocolCommandHeader header; + uint16_t dataLength; + } ENET_PACKED ENetProtocolSendReliable; + + typedef struct _ENetProtocolSendUnreliable { + ENetProtocolCommandHeader header; + uint16_t unreliableSequenceNumber; + uint16_t dataLength; + } ENET_PACKED ENetProtocolSendUnreliable; + + typedef struct _ENetProtocolSendUnsequenced { + ENetProtocolCommandHeader header; + uint16_t unsequencedGroup; + uint16_t dataLength; + } ENET_PACKED ENetProtocolSendUnsequenced; + + typedef struct _ENetProtocolSendFragment { + ENetProtocolCommandHeader header; + uint16_t startSequenceNumber; + uint16_t dataLength; + uint32_t fragmentCount; + uint32_t fragmentNumber; + uint32_t totalLength; + uint32_t fragmentOffset; + } ENET_PACKED ENetProtocolSendFragment; + + typedef union _ENetProtocol { + ENetProtocolCommandHeader header; + ENetProtocolAcknowledge acknowledge; + ENetProtocolConnect connect; + ENetProtocolVerifyConnect verifyConnect; + ENetProtocolDisconnect disconnect; + ENetProtocolPing ping; + ENetProtocolSendReliable sendReliable; + ENetProtocolSendUnreliable sendUnreliable; + ENetProtocolSendUnsequenced sendUnsequenced; + ENetProtocolSendFragment sendFragment; + ENetProtocolBandwidthLimit bandwidthLimit; + ENetProtocolThrottleConfigure throttleConfigure; + } ENET_PACKED ENetProtocol; + + #ifdef _MSC_VER + #pragma pack(pop) + #endif + +/* +======================================================================= + + General structs/enums + +======================================================================= +*/ + + typedef enum _ENetSocketType { + ENET_SOCKET_TYPE_STREAM = 1, + ENET_SOCKET_TYPE_DATAGRAM = 2 + } ENetSocketType; + + typedef enum _ENetSocketWait { + ENET_SOCKET_WAIT_NONE = 0, + ENET_SOCKET_WAIT_SEND = (1 << 0), + ENET_SOCKET_WAIT_RECEIVE = (1 << 1), + ENET_SOCKET_WAIT_INTERRUPT = (1 << 2) + } ENetSocketWait; + + typedef enum _ENetSocketOption { + ENET_SOCKOPT_NONBLOCK = 1, + ENET_SOCKOPT_BROADCAST = 2, + ENET_SOCKOPT_RCVBUF = 3, + ENET_SOCKOPT_SNDBUF = 4, + ENET_SOCKOPT_REUSEADDR = 5, + ENET_SOCKOPT_RCVTIMEO = 6, + ENET_SOCKOPT_SNDTIMEO = 7, + ENET_SOCKOPT_ERROR = 8, + ENET_SOCKOPT_NODELAY = 9, + ENET_SOCKOPT_IPV6_V6ONLY = 10 + } ENetSocketOption; + + typedef enum _ENetSocketShutdown { + ENET_SOCKET_SHUTDOWN_READ = 0, + ENET_SOCKET_SHUTDOWN_WRITE = 1, + ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 + } ENetSocketShutdown; + + typedef struct _ENetAddress { + union { + struct in6_addr ipv6; + struct { + uint8_t zeros[10]; + uint16_t ffff; + struct in_addr ip; + } ipv4; + }; + uint16_t port; + } ENetAddress; + + typedef enum _ENetPacketFlag { + ENET_PACKET_FLAG_NONE = 0, + ENET_PACKET_FLAG_RELIABLE = (1 << 0), + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENTED = (1 << 3), + ENET_PACKET_FLAG_INSTANT = (1 << 4), + ENET_PACKET_FLAG_UNTHROTTLED = (1 << 5), + ENET_PACKET_FLAG_SENT = (1 << 8) + } ENetPacketFlag; + + typedef void (ENET_CALLBACK *ENetPacketFreeCallback)(void*); + + typedef struct _ENetPacket { + uint32_t flags; + uint32_t dataLength; + uint8_t* data; + ENetPacketFreeCallback freeCallback; + uint32_t referenceCount; + void* userData; + } ENetPacket; + + typedef struct _ENetAcknowledgement { + ENetListNode acknowledgementList; + uint32_t sentTime; + ENetProtocol command; + } ENetAcknowledgement; + + typedef struct _ENetOutgoingCommand { + ENetListNode outgoingCommandList; + uint16_t reliableSequenceNumber; + uint16_t unreliableSequenceNumber; + uint32_t sentTime; + uint32_t roundTripTimeout; + uint32_t roundTripTimeoutLimit; + uint32_t fragmentOffset; + uint16_t fragmentLength; + uint16_t sendAttempts; + ENetProtocol command; + ENetPacket* packet; + } ENetOutgoingCommand; + + typedef struct _ENetIncomingCommand { + ENetListNode incomingCommandList; + uint16_t reliableSequenceNumber; + uint16_t unreliableSequenceNumber; + ENetProtocol command; + uint32_t fragmentCount; + uint32_t fragmentsRemaining; + uint32_t* fragments; + ENetPacket* packet; + } ENetIncomingCommand; + + typedef enum _ENetPeerState { + ENET_PEER_STATE_DISCONNECTED = 0, + ENET_PEER_STATE_CONNECTING = 1, + ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, + ENET_PEER_STATE_CONNECTION_PENDING = 3, + ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, + ENET_PEER_STATE_CONNECTED = 5, + ENET_PEER_STATE_DISCONNECT_LATER = 6, + ENET_PEER_STATE_DISCONNECTING = 7, + ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, + ENET_PEER_STATE_ZOMBIE = 9 + } ENetPeerState; + + enum { + ENET_HOST_BUFFER_SIZE_MIN = 256 * 1024, + ENET_HOST_BUFFER_SIZE_MAX = 1024 * 1024, + ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, + ENET_HOST_DEFAULT_MTU = 1280, + ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, + ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, + ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 1, + ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, + ENET_PEER_PACKET_THROTTLE_THRESHOLD = 40, + ENET_PEER_PACKET_THROTTLE_SCALE = 32, + ENET_PEER_PACKET_THROTTLE_COUNTER = 7, + ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, + ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, + ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, + ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, + ENET_PEER_TIMEOUT_LIMIT = 32, + ENET_PEER_TIMEOUT_MINIMUM = 5000, + ENET_PEER_TIMEOUT_MAXIMUM = 30000, + ENET_PEER_PING_INTERVAL = 250, + ENET_PEER_UNSEQUENCED_WINDOWS = 64, + ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, + ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, + ENET_PEER_RELIABLE_WINDOWS = 16, + ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, + ENET_PEER_FREE_RELIABLE_WINDOWS = 8 + }; + + typedef struct _ENetChannel { + uint16_t outgoingReliableSequenceNumber; + uint16_t outgoingUnreliableSequenceNumber; + uint16_t usedReliableWindows; + uint16_t reliableWindows[ENET_PEER_RELIABLE_WINDOWS]; + uint16_t incomingReliableSequenceNumber; + uint16_t incomingUnreliableSequenceNumber; + ENetList incomingReliableCommands; + ENetList incomingUnreliableCommands; + } ENetChannel; + + typedef struct _ENetPeer { + ENetListNode dispatchList; + struct _ENetHost* host; + uint16_t outgoingPeerID; + uint16_t incomingPeerID; + uint32_t connectID; + uint8_t outgoingSessionID; + uint8_t incomingSessionID; + ENetAddress address; + void* data; + ENetPeerState state; + ENetChannel* channels; + size_t channelCount; + uint32_t incomingBandwidth; + uint32_t outgoingBandwidth; + uint32_t incomingBandwidthThrottleEpoch; + uint32_t outgoingBandwidthThrottleEpoch; + uint32_t incomingDataTotal; + uint64_t totalDataReceived; + uint32_t outgoingDataTotal; + uint64_t totalDataSent; + uint32_t lastSendTime; + uint32_t lastReceiveTime; + uint32_t nextTimeout; + uint32_t earliestTimeout; + uint64_t totalPacketsSent; + uint64_t totalPacketsLost; + uint32_t packetThrottle; + uint32_t packetThrottleThreshold; + uint32_t packetThrottleLimit; + uint32_t packetThrottleCounter; + uint32_t packetThrottleEpoch; + uint32_t packetThrottleAcceleration; + uint32_t packetThrottleDeceleration; + uint32_t packetThrottleInterval; + uint32_t pingInterval; + uint32_t timeoutLimit; + uint32_t timeoutMinimum; + uint32_t timeoutMaximum; + uint32_t lastRoundTripTime; + uint32_t lowestRoundTripTime; + uint32_t lastRoundTripTimeVariance; + uint32_t highestRoundTripTimeVariance; + uint32_t roundTripTime; + uint32_t roundTripTimeVariance; + uint32_t mtu; + uint32_t windowSize; + uint32_t reliableDataInTransit; + uint16_t outgoingReliableSequenceNumber; + ENetList acknowledgements; + ENetList sentReliableCommands; + ENetList sentUnreliableCommands; + ENetList outgoingCommands; + ENetList dispatchedCommands; + int needsDispatch; + uint16_t incomingUnsequencedGroup; + uint16_t outgoingUnsequencedGroup; + uint32_t unsequencedWindow[ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; + uint32_t eventData; + size_t totalWaitingData; + } ENetPeer; + + typedef enum _ENetEventType { + ENET_EVENT_TYPE_NONE = 0, + ENET_EVENT_TYPE_CONNECT = 1, + ENET_EVENT_TYPE_DISCONNECT = 2, + ENET_EVENT_TYPE_RECEIVE = 3, + ENET_EVENT_TYPE_DISCONNECT_TIMEOUT = 4 + } ENetEventType; + + typedef struct _ENetEvent { + ENetEventType type; + ENetPeer* peer; + uint8_t channelID; + uint32_t data; + ENetPacket* packet; + } ENetEvent; + + typedef uint64_t (ENET_CALLBACK *ENetChecksumCallback)(const ENetBuffer* buffers, int bufferCount); + + typedef int (ENET_CALLBACK *ENetInterceptCallback)(ENetEvent* event, ENetAddress* address, uint8_t* receivedData, int receivedDataLength); + + typedef struct _ENetHost { + ENetSocket socket; + ENetAddress address; + uint32_t incomingBandwidth; + uint32_t outgoingBandwidth; + uint32_t bandwidthThrottleEpoch; + uint32_t mtu; + uint32_t randomSeed; + int recalculateBandwidthLimits; + uint8_t preventConnections; + ENetPeer* peers; + size_t peerCount; + size_t channelLimit; + uint32_t serviceTime; + ENetList dispatchQueue; + int continueSending; + size_t packetSize; + uint16_t headerFlags; + uint32_t totalSentData; + uint32_t totalSentPackets; + uint32_t totalReceivedData; + uint32_t totalReceivedPackets; + ENetProtocol commands[ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; + size_t commandCount; + ENetBuffer buffers[ENET_BUFFER_MAXIMUM]; + size_t bufferCount; + ENetChecksumCallback checksumCallback; + uint8_t packetData[2][ENET_PROTOCOL_MAXIMUM_MTU]; + ENetAddress receivedAddress; + uint8_t* receivedData; + size_t receivedDataLength; + ENetInterceptCallback interceptCallback; + size_t connectedPeers; + size_t bandwidthLimitedPeers; + size_t duplicatePeers; + size_t maximumPacketSize; + size_t maximumWaitingData; + } ENetHost; + +/* +======================================================================= + + Public API + +======================================================================= +*/ + + ENET_API int enet_initialize(void); + ENET_API int enet_initialize_with_callbacks(ENetVersion, const ENetCallbacks*); + ENET_API void enet_deinitialize(void); + ENET_API ENetVersion enet_linked_version(void); + ENET_API int enet_array_is_zeroed(const uint8_t*, int); + ENET_API uint32_t enet_time_get(void); + ENET_API uint64_t enet_crc64(const ENetBuffer*, int); + + ENET_API ENetPacket* enet_packet_create(const void*, size_t, uint32_t); + ENET_API ENetPacket* enet_packet_create_offset(const void*, size_t, size_t, uint32_t); + ENET_API void enet_packet_destroy(ENetPacket*); + + ENET_API int enet_peer_send(ENetPeer*, uint8_t, ENetPacket*); + ENET_API ENetPacket* enet_peer_receive(ENetPeer*, uint8_t*); + ENET_API void enet_peer_ping(ENetPeer*); + ENET_API void enet_peer_ping_interval(ENetPeer*, uint32_t); + ENET_API void enet_peer_timeout(ENetPeer*, uint32_t, uint32_t, uint32_t); + ENET_API void enet_peer_reset(ENetPeer*); + ENET_API void enet_peer_disconnect(ENetPeer*, uint32_t); + ENET_API void enet_peer_disconnect_now(ENetPeer*, uint32_t); + ENET_API void enet_peer_disconnect_later(ENetPeer*, uint32_t); + ENET_API void enet_peer_throttle_configure(ENetPeer*, uint32_t, uint32_t, uint32_t, uint32_t); + + ENET_API ENetHost* enet_host_create(const ENetAddress*, size_t, size_t, uint32_t, uint32_t, int); + ENET_API void enet_host_destroy(ENetHost*); + ENET_API void enet_host_prevent_connections(ENetHost*, uint8_t); + ENET_API ENetPeer* enet_host_connect(ENetHost*, const ENetAddress*, size_t, uint32_t); + ENET_API int enet_host_check_events(ENetHost*, ENetEvent*); + ENET_API int enet_host_service(ENetHost*, ENetEvent*, uint32_t); + ENET_API void enet_host_flush(ENetHost*); + ENET_API void enet_host_broadcast(ENetHost*, uint8_t, ENetPacket*); + ENET_API void enet_host_broadcast_exclude(ENetHost*, uint8_t, ENetPacket*, ENetPeer*); + ENET_API void enet_host_broadcast_selective(ENetHost*, uint8_t, ENetPacket*, ENetPeer**, size_t); + ENET_API void enet_host_channel_limit(ENetHost*, size_t); + ENET_API void enet_host_bandwidth_limit(ENetHost*, uint32_t, uint32_t); + + ENET_API int enet_address_set_ip(ENetAddress*, const char*); + ENET_API int enet_address_set_hostname(ENetAddress*, const char*); + ENET_API int enet_address_get_ip(const ENetAddress*, char*, size_t); + ENET_API int enet_address_get_hostname(const ENetAddress*, char*, size_t); + + ENET_API ENetSocket enet_socket_create(ENetSocketType); + ENET_API int enet_socket_bind(ENetSocket, const ENetAddress*); + ENET_API int enet_socket_get_address(ENetSocket, ENetAddress*); + ENET_API int enet_socket_listen(ENetSocket, int); + ENET_API ENetSocket enet_socket_accept(ENetSocket, ENetAddress*); + ENET_API int enet_socket_connect(ENetSocket, const ENetAddress*); + ENET_API int enet_socket_send(ENetSocket, const ENetAddress*, const ENetBuffer*, size_t); + ENET_API int enet_socket_receive(ENetSocket, ENetAddress*, ENetBuffer*, size_t); + ENET_API int enet_socket_wait(ENetSocket, uint32_t*, uint64_t); + ENET_API int enet_socket_set_option(ENetSocket, ENetSocketOption, int); + ENET_API int enet_socket_get_option(ENetSocket, ENetSocketOption, int*); + ENET_API int enet_socket_shutdown(ENetSocket, ENetSocketShutdown); + ENET_API void enet_socket_destroy(ENetSocket); + ENET_API int enet_socket_set_select(ENetSocket, ENetSocketSet*, ENetSocketSet*, uint32_t); + + /* Extended API for easier binding in other programming languages */ + ENET_API void* enet_packet_get_data(const ENetPacket*); + ENET_API void* enet_packet_get_user_data(const ENetPacket*); + ENET_API void enet_packet_set_user_data(ENetPacket*, void* userData); + ENET_API int enet_packet_get_length(const ENetPacket*); + ENET_API void enet_packet_set_free_callback(ENetPacket*, ENetPacketFreeCallback); + ENET_API int enet_packet_check_references(const ENetPacket*); + ENET_API void enet_packet_dispose(ENetPacket*); + + ENET_API uint32_t enet_host_get_peers_count(const ENetHost*); + ENET_API uint32_t enet_host_get_packets_sent(const ENetHost*); + ENET_API uint32_t enet_host_get_packets_received(const ENetHost*); + ENET_API uint32_t enet_host_get_bytes_sent(const ENetHost*); + ENET_API uint32_t enet_host_get_bytes_received(const ENetHost*); + ENET_API void enet_host_set_max_duplicate_peers(ENetHost*, uint16_t); + ENET_API void enet_host_set_intercept_callback(ENetHost*, ENetInterceptCallback); + ENET_API void enet_host_set_checksum_callback(ENetHost*, ENetChecksumCallback); + + ENET_API uint32_t enet_peer_get_id(const ENetPeer*); + ENET_API int enet_peer_get_ip(const ENetPeer*, char*, size_t); + ENET_API uint16_t enet_peer_get_port(const ENetPeer*); + ENET_API uint32_t enet_peer_get_mtu(const ENetPeer*); + ENET_API ENetPeerState enet_peer_get_state(const ENetPeer*); + ENET_API uint32_t enet_peer_get_rtt(const ENetPeer*); + ENET_API uint32_t enet_peer_get_last_rtt(const ENetPeer* peer); + ENET_API uint32_t enet_peer_get_lastsendtime(const ENetPeer*); + ENET_API uint32_t enet_peer_get_lastreceivetime(const ENetPeer*); + ENET_API uint64_t enet_peer_get_packets_sent(const ENetPeer*); + ENET_API uint64_t enet_peer_get_packets_lost(const ENetPeer*); + ENET_API float enet_peer_get_packets_throttle(const ENetPeer*); + ENET_API uint64_t enet_peer_get_bytes_sent(const ENetPeer*); + ENET_API uint64_t enet_peer_get_bytes_received(const ENetPeer*); + ENET_API void* enet_peer_get_data(const ENetPeer*); + ENET_API void enet_peer_set_data(ENetPeer*, const void*); + +/* +======================================================================= + + Private API + +======================================================================= +*/ + + extern void enet_host_bandwidth_throttle(ENetHost*); + extern uint64_t enet_host_random_seed(void); + + extern int enet_peer_throttle(ENetPeer*, uint32_t); + extern void enet_peer_reset_queues(ENetPeer*); + extern void enet_peer_setup_outgoing_command(ENetPeer*, ENetOutgoingCommand*); + extern ENetOutgoingCommand* enet_peer_queue_outgoing_command(ENetPeer*, const ENetProtocol*, ENetPacket*, uint32_t, uint16_t); + extern ENetIncomingCommand* enet_peer_queue_incoming_command(ENetPeer*, const ENetProtocol*, const void*, size_t, uint32_t, uint32_t); + extern ENetAcknowledgement* enet_peer_queue_acknowledgement(ENetPeer*, const ENetProtocol*, uint16_t); + extern void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer*, ENetChannel*, ENetIncomingCommand*); + extern void enet_peer_dispatch_incoming_reliable_commands(ENetPeer*, ENetChannel*, ENetIncomingCommand*); + extern void enet_peer_on_connect(ENetPeer*); + extern void enet_peer_on_disconnect(ENetPeer*); + + extern size_t enet_protocol_command_size(uint8_t); + +#ifdef __cplusplus +} +#endif + +#if defined(ENET_IMPLEMENTATION) && !defined(ENET_IMPLEMENTATION_DONE) + #define ENET_IMPLEMENTATION_DONE 1 + + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4244) /* 64-bit to 32-bit integer conversion */ + #pragma warning(disable: 4267) /* size_t to integer conversion */ + #endif + +/* +======================================================================= + + Atomics + +======================================================================= +*/ + + #ifdef _MSC_VER + #define ENET_AT_CASSERT_PRED(predicate) sizeof(char[2 * !!(predicate) - 1]) + #define ENET_IS_SUPPORTED_ATOMIC(size) ENET_AT_CASSERT_PRED(size == 1 || size == 2 || size == 4 || size == 8) + #define ENET_ATOMIC_SIZEOF(variable) (ENET_IS_SUPPORTED_ATOMIC(sizeof(*(variable))), sizeof(*(variable))) + + __inline int64_t enet_at_atomic_read(char* ptr, size_t size) { + switch (size) { + case 1: + return _InterlockedExchangeAdd8((volatile char*)ptr, 0); + + case 2: + return _InterlockedExchangeAdd16((volatile SHORT*)ptr, 0); + + case 4: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd((volatile LONG*)ptr, 0); + #else + return _InterlockedExchangeAdd((volatile LONG*)ptr, 0); + #endif + + case 8: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd64((volatile LONGLONG*)ptr, 0); + #else + return _InterlockedExchangeAdd64((volatile LONGLONG*)ptr, 0); + #endif + + default: + return 0x0; + } + } + + __inline int64_t enet_at_atomic_write(char* ptr, int64_t value, size_t size) { + switch (size) { + case 1: + return _InterlockedExchange8((volatile char*)ptr, (char)value); + + case 2: + return _InterlockedExchange16((volatile SHORT*)ptr, (SHORT)value); + + case 4: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchange((volatile LONG*)ptr, (LONG)value); + #else + return _InterlockedExchange((volatile LONG*)ptr, (LONG)value); + #endif + + case 8: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchange64((volatile LONGLONG*)ptr, (LONGLONG)value); + #else + return _InterlockedExchange64((volatile LONGLONG*)ptr, (LONGLONG)value); + #endif + + default: + return 0x0; + } + } + + __inline int64_t enet_at_atomic_cas(char* ptr, int64_t new_val, int64_t old_val, size_t size) { + switch (size) { + case 1: + return _InterlockedCompareExchange8((volatile char*)ptr, (char)new_val, (char)old_val); + + case 2: + return _InterlockedCompareExchange16((volatile SHORT*)ptr, (SHORT)new_val, (SHORT)old_val); + + case 4: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedCompareExchange((volatile LONG*)ptr, (LONG)new_val, (LONG)old_val); + #else + return _InterlockedCompareExchange((volatile LONG*)ptr, (LONG)new_val, (LONG)old_val); + #endif + + case 8: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedCompareExchange64((volatile LONGLONG*)ptr, (LONGLONG)new_val, (LONGLONG)old_val); + #else + return _InterlockedCompareExchange64((volatile LONGLONG*)ptr, (LONGLONG)new_val, (LONGLONG)old_val); + #endif + + default: + return 0x0; + } + } + + __inline int64_t enet_at_atomic_inc(char* ptr, int64_t delta, size_t data_size) { + switch (data_size) { + case 1: + return _InterlockedExchangeAdd8((volatile char*)ptr, (char)delta); + + case 2: + return _InterlockedExchangeAdd16((volatile SHORT*)ptr, (SHORT)delta); + + case 4: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd((volatile LONG*)ptr, (LONG)delta); + #else + return _InterlockedExchangeAdd((volatile LONG*)ptr, (LONG)delta); + #endif + + case 8: + #ifdef ENET_NOT_UNDERSCORED_INTERLOCKED_EXCHANGE + return InterlockedExchangeAdd64((volatile LONGLONG*)ptr, (LONGLONG)delta); + #else + return _InterlockedExchangeAdd64((volatile LONGLONG*)ptr, (LONGLONG)delta); + #endif + + default: + return 0x0; + } + } + + #define ENET_ATOMIC_READ(variable) enet_at_atomic_read((char*)(variable), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_WRITE(variable, new_val) enet_at_atomic_write((char*)(variable), (int64_t)(new_val), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_CAS(variable, old_value, new_val) enet_at_atomic_cas((char*)(variable), (int64_t)(new_val), (int64_t)(old_value), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_INC(variable) enet_at_atomic_inc((char*)(variable), 1, ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_DEC(variable) enet_at_atomic_inc((char*)(variable), -1, ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_INC_BY(variable, delta) enet_at_atomic_inc((char*)(variable), (delta), ENET_ATOMIC_SIZEOF(variable)) + #define ENET_ATOMIC_DEC_BY(variable, delta) enet_at_atomic_inc((char*)(variable), -(delta), ENET_ATOMIC_SIZEOF(variable)) + #elif defined(__GNUC__) || defined(__clang__) + #if defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) + #define AT_HAVE_ATOMICS + #endif + + /* We want to use __atomic built-ins if possible because the __sync primitives are + deprecated, because the __atomic build-ins allow us to use ENET_ATOMIC_WRITE on + uninitialized memory without running into undefined behavior, and because the + __atomic versions generate more efficient code since we don't need to rely on + CAS when we don't actually want it. + + Note that we use acquire-release memory order (like mutexes do). We could use + sequentially consistent memory order but that has lower performance and is + almost always unneeded. */ + #ifdef AT_HAVE_ATOMICS + #define ENET_ATOMIC_READ(ptr) __atomic_load_n((ptr), __ATOMIC_ACQUIRE) + #define ENET_ATOMIC_WRITE(ptr, value) __atomic_store_n((ptr), (value), __ATOMIC_RELEASE) + + #ifndef typeof + #define typeof __typeof__ + #endif + + /* clang_analyzer doesn't know that CAS writes to memory so it complains about + potentially lost data. Replace the code with the equivalent non-sync code. */ + #ifdef __clang_analyzer__ + #define ENET_ATOMIC_CAS(ptr, old_value, new_value)\ + ({\ + typeof(*(ptr)) ENET_ATOMIC_CAS_old_actual_ = (*(ptr));\ + if (ATOMIC_CAS_old_actual_ == (old_value))\ + *(ptr) = new_value;\ + ENET_ATOMIC_CAS_old_actual_;\ + }) + #else + /* Could use __auto_type instead of typeof but that shouldn't work in C++. + The ({ }) syntax is a GCC extension called statement expression. It lets + us return a value out of the macro. + + TODO We should return bool here instead of the old value to avoid the ABA + problem. */ + #define ENET_ATOMIC_CAS(ptr, old_value, new_value)\ + ({\ + typeof(*(ptr)) ENET_ATOMIC_CAS_expected_ = (old_value);\ + __atomic_compare_exchange_n((ptr), &ENET_ATOMIC_CAS_expected_, (new_value), false,\ + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);\ + ENET_ATOMIC_CAS_expected_;\ + }) + #endif + + #define ENET_ATOMIC_INC(ptr) __atomic_fetch_add((ptr), 1, __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_DEC(ptr) __atomic_fetch_sub((ptr), 1, __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_INC_BY(ptr, delta) __atomic_fetch_add((ptr), (delta), __ATOMIC_ACQ_REL) + #define ENET_ATOMIC_DEC_BY(ptr, delta) __atomic_fetch_sub((ptr), (delta), __ATOMIC_ACQ_REL) + #else + #define ENET_ATOMIC_READ(variable) __sync_fetch_and_add(variable, 0) + #define ENET_ATOMIC_WRITE(variable, new_val) (void)__sync_val_compare_and_swap((variable), *(variable), (new_val)) + #define ENET_ATOMIC_CAS(variable, old_value, new_val) __sync_val_compare_and_swap((variable), (old_value), (new_val)) + #define ENET_ATOMIC_INC(variable) __sync_fetch_and_add((variable), 1) + #define ENET_ATOMIC_DEC(variable) __sync_fetch_and_sub((variable), 1) + #define ENET_ATOMIC_INC_BY(variable, delta) __sync_fetch_and_add((variable), (delta), 1) + #define ENET_ATOMIC_DEC_BY(variable, delta) __sync_fetch_and_sub((variable), (delta), 1) + #endif + #undef AT_HAVE_ATOMICS + #endif + +/* +======================================================================= + + Callbacks + +======================================================================= +*/ + + static ENetCallbacks callbacks = { + malloc, + free, + abort + }; + + int enet_initialize_with_callbacks(ENetVersion version, const ENetCallbacks* inits) { + if (version < ENET_VERSION_CREATE(1, 3, 0)) + return -1; + + if (inits->malloc != NULL || inits->free != NULL) { + if (inits->malloc == NULL || inits->free == NULL) + return -1; + + callbacks.malloc = inits->malloc; + callbacks.free = inits->free; + } + + if (inits->noMemory != NULL) + callbacks.noMemory = inits->noMemory; + + return enet_initialize(); + } + + void* enet_malloc(size_t size) { + void* memory = callbacks.malloc(size); + + if (memory == NULL) + callbacks.noMemory(); + + return memory; + } + + void enet_free(void* memory) { + callbacks.free(memory); + } + +/* +======================================================================= + + List + +======================================================================= +*/ + + void enet_list_clear(ENetList* list) { + list->sentinel.next = &list->sentinel; + list->sentinel.previous = &list->sentinel; + } + + ENetListIterator enet_list_insert(ENetListIterator position, void* data) { + ENetListIterator result = (ENetListIterator)data; + result->previous = position->previous; + result->next = position; + result->previous->next = result; + position->previous = result; + + return result; + } + + void* enet_list_remove(ENetListIterator position) { + position->previous->next = position->next; + position->next->previous = position->previous; + + return position; + } + + ENetListIterator enet_list_move(ENetListIterator position, void* dataFirst, void* dataLast) { + ENetListIterator first = (ENetListIterator)dataFirst; + ENetListIterator last = (ENetListIterator)dataLast; + first->previous->next = last->next; + last->next->previous = first->previous; + first->previous = position->previous; + last->next = position; + first->previous->next = first; + position->previous = last; + + return first; + } + + size_t enet_list_size(ENetList* list) { + size_t size = 0; + ENetListIterator position; + + for (position = enet_list_begin(list); position != enet_list_end(list); position = enet_list_next(position)) { + ++size; + } + + return size; + } + +/* +======================================================================= + + Utilities + +======================================================================= +*/ + + ENetVersion enet_linked_version(void) { + return ENET_VERSION; + } + + int enet_array_is_zeroed(const uint8_t* array, int length) { + size_t i; + + for (i = 0; i < length; i++) { + if (array[i] != 0) + return -1; + } + + return 0; + } + +/* +======================================================================= + + Time + +======================================================================= +*/ + + #ifdef _WIN32 + static LARGE_INTEGER gettime_offset(void) { + SYSTEMTIME s; + FILETIME f; + LARGE_INTEGER t; + s.wYear = 1970; + s.wMonth = 1; + s.wDay = 1; + s.wHour = 0; + s.wMinute = 0; + s.wSecond = 0; + s.wMilliseconds = 0; + + SystemTimeToFileTime(&s, &f); + + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + + return t; + } + + int clock_gettime(int X, struct timespec* tv) { + LARGE_INTEGER t; + FILETIME f; + double microseconds; + + static LARGE_INTEGER offset; + static double frequencyToMicroseconds; + static int initialized = 0; + static BOOL usePerformanceCounter = 0; + + if (!initialized) { + LARGE_INTEGER performanceFrequency; + initialized = 1; + usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); + + if (usePerformanceCounter) { + QueryPerformanceCounter(&offset); + + frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; + } else { + offset = gettime_offset(); + frequencyToMicroseconds = 10.; + } + } + + if (usePerformanceCounter) { + QueryPerformanceCounter(&t); + } else { + GetSystemTimeAsFileTime(&f); + + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + } + + t.QuadPart -= offset.QuadPart; + microseconds = (double)t.QuadPart / frequencyToMicroseconds; + t.QuadPart = (LONGLONG)microseconds; + tv->tv_sec = (long)(t.QuadPart / 1000000); + tv->tv_nsec = t.QuadPart % 1000000 * 1000; + + return 0; + } + #elif __APPLE__ && (__MAC_OS_X_VERSION_MIN_REQUIRED < 101200 || __IPHONE_OS_VERSION_MIN_REQUIRED < 100000) && !defined(CLOCK_MONOTONIC) + #define CLOCK_MONOTONIC 0 + + int clock_gettime(int X, struct timespec* ts) { + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; + + return 0; + } + #endif + + uint32_t enet_time_get(void) { + static uint64_t start_time_ns = 0; + + struct timespec ts; + + #ifdef CLOCK_MONOTONIC_RAW + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + #else + clock_gettime(CLOCK_MONOTONIC, &ts); + #endif + + static const uint64_t ns_in_s = 1000 * 1000 * 1000; + static const uint64_t ns_in_ms = 1000 * 1000; + + uint64_t current_time_ns = ts.tv_nsec + (uint64_t)ts.tv_sec * ns_in_s; + uint64_t offset_ns = ENET_ATOMIC_READ(&start_time_ns); + + if (offset_ns == 0) { + uint64_t want_value = current_time_ns - 1 * ns_in_ms; + uint64_t old_value = ENET_ATOMIC_CAS(&start_time_ns, 0, want_value); + offset_ns = old_value == 0 ? want_value : old_value; + } + + uint64_t result_in_ns = current_time_ns - offset_ns; + + return (uint32_t)(result_in_ns / ns_in_ms); + } + +/* +======================================================================= + + Checksum + +======================================================================= +*/ + + typedef uint64_t enet_checksum; + + static const uint64_t crcTable[256] = { + UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979), + UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b), + UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6), + UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04), + UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c), + UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe), + UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183), + UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371), + UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8), + UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a), + UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077), + UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285), + UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d), + UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f), + UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02), + UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0), + UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b), + UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489), + UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4), + UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206), + UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e), + UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc), + UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81), + UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73), + UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa), + UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08), + UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75), + UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87), + UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f), + UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d), + UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100), + UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2), + UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416), + UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4), + UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299), + UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b), + UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63), + UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891), + UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec), + UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e), + UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97), + UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965), + UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18), + UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea), + UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2), + UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710), + UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d), + UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f), + UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14), + UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6), + UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b), + UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69), + UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561), + UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793), + UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee), + UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c), + UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495), + UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667), + UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a), + UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8), + UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0), + UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812), + UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f), + UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d), + UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc), + UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e), + UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643), + UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1), + UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9), + UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b), + UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836), + UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4), + UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d), + UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf), + UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2), + UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30), + UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138), + UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca), + UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7), + UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545), + UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce), + UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c), + UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941), + UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3), + UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb), + UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349), + UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734), + UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6), + UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f), + UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd), + UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0), + UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432), + UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a), + UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8), + UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5), + UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47), + UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3), + UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51), + UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c), + UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de), + UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6), + UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124), + UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559), + UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab), + UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222), + UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0), + UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad), + UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f), + UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57), + UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5), + UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8), + UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a), + UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1), + UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053), + UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e), + UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc), + UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4), + UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26), + UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b), + UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9), + UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20), + UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2), + UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf), + UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d), + UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355), + UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7), + UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da), + UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728), + }; + + uint64_t enet_crc64(const ENetBuffer* buffers, int bufferCount) { + uint64_t crc = 0xFFFFFFFFFFFFFFFF; + + while (bufferCount-- > 0) { + const uint8_t* data = (const uint8_t*)buffers->data; + const uint8_t* dataEnd = &data[buffers->dataLength]; + + while (data < dataEnd) { + crc = (crc >> 8) ^ crcTable[(uint8_t)crc ^* data++]; + } + + ++buffers; + } + + return ENET_HOST_TO_NET_64(~crc); + } + +/* +======================================================================= + + Packet + +======================================================================= +*/ + + ENetPacket* enet_packet_create(const void* data, size_t dataLength, uint32_t flags) { + ENetPacket* packet; + + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { + packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket)); + + if (packet == NULL) + return NULL; + + packet->data = (uint8_t*)data; + } else { + packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket) + dataLength); + + if (packet == NULL) + return NULL; + + packet->data = (uint8_t*)packet + sizeof(ENetPacket); + + if (data != NULL) + memcpy(packet->data, data, dataLength); + } + + packet->referenceCount = 0; + packet->flags = flags; + packet->dataLength = dataLength; + packet->freeCallback = NULL; + packet->userData = NULL; + + return packet; + } + + ENetPacket* enet_packet_create_offset(const void* data, size_t dataLength, size_t dataOffset, uint32_t flags) { + ENetPacket* packet; + + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { + packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket)); + + if (packet == NULL) + return NULL; + + packet->data = (uint8_t*)data; + } else { + packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket) + dataLength - dataOffset); + + if (packet == NULL) + return NULL; + + packet->data = (uint8_t*)packet + sizeof(ENetPacket); + + if (data != NULL) + memcpy(packet->data, (char*)data + dataOffset, dataLength - dataOffset); + } + + packet->referenceCount = 0; + packet->flags = flags; + packet->dataLength = dataLength - dataOffset; + packet->freeCallback = NULL; + packet->userData = NULL; + + return packet; + } + + void enet_packet_destroy(ENetPacket* packet) { + if (packet == NULL) + return; + + if (packet->freeCallback != NULL) + (*packet->freeCallback)((void*)packet); + + enet_free(packet); + } + +/* +======================================================================= + + Protocol + +======================================================================= +*/ + + static size_t commandSizes[ENET_PROTOCOL_COMMAND_COUNT] = { + 0, + sizeof(ENetProtocolAcknowledge), + sizeof(ENetProtocolConnect), + sizeof(ENetProtocolVerifyConnect), + sizeof(ENetProtocolDisconnect), + sizeof(ENetProtocolPing), + sizeof(ENetProtocolSendReliable), + sizeof(ENetProtocolSendUnreliable), + sizeof(ENetProtocolSendFragment), + sizeof(ENetProtocolSendUnsequenced), + sizeof(ENetProtocolBandwidthLimit), + sizeof(ENetProtocolThrottleConfigure), + sizeof(ENetProtocolSendFragment) + }; + + size_t enet_protocol_command_size(uint8_t commandNumber) { + return commandSizes[commandNumber & ENET_PROTOCOL_COMMAND_MASK]; + } + + static void enet_protocol_change_state(ENetHost* host, ENetPeer* peer, ENetPeerState state) { + if (state == ENET_PEER_STATE_CONNECTED || state == ENET_PEER_STATE_DISCONNECT_LATER) + enet_peer_on_connect(peer); + else + enet_peer_on_disconnect(peer); + + peer->state = state; + } + + static void enet_protocol_dispatch_state(ENetHost* host, ENetPeer* peer, ENetPeerState state) { + enet_protocol_change_state(host, peer, state); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); + + peer->needsDispatch = 1; + } + } + + static int enet_protocol_dispatch_incoming_commands(ENetHost* host, ENetEvent* event) { + while (!enet_list_empty(&host->dispatchQueue)) { + ENetPeer* peer = (ENetPeer*)enet_list_remove(enet_list_begin(&host->dispatchQueue)); + peer->needsDispatch = 0; + + switch (peer->state) { + case ENET_PEER_STATE_CONNECTION_PENDING: + case ENET_PEER_STATE_CONNECTION_SUCCEEDED: + enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); + + event->type = ENET_EVENT_TYPE_CONNECT; + event->peer = peer; + event->data = peer->eventData; + + return 1; + + case ENET_PEER_STATE_ZOMBIE: + host->recalculateBandwidthLimits = 1; + event->type = ENET_EVENT_TYPE_DISCONNECT; + event->peer = peer; + event->data = peer->eventData; + + enet_peer_reset(peer); + + return 1; + + case ENET_PEER_STATE_CONNECTED: + if (enet_list_empty(&peer->dispatchedCommands)) + continue; + + event->packet = enet_peer_receive(peer, &event->channelID); + + if (event->packet == NULL) + continue; + + event->type = ENET_EVENT_TYPE_RECEIVE; + event->peer = peer; + + if (!enet_list_empty(&peer->dispatchedCommands)) { + peer->needsDispatch = 1; + + enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); + } + + return 1; + + default: + break; + } + } + + return 0; + } + + static void enet_protocol_notify_connect(ENetHost* host, ENetPeer* peer, ENetEvent* event) { + host->recalculateBandwidthLimits = 1; + + if (event != NULL) { + enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); + + peer->totalDataSent = 0; + peer->totalDataReceived = 0; + peer->totalPacketsSent = 0; + peer->totalPacketsLost = 0; + event->type = ENET_EVENT_TYPE_CONNECT; + event->peer = peer; + event->data = peer->eventData; + } else { + enet_protocol_dispatch_state(host, peer, peer->state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING); + } + } + + static void enet_protocol_notify_disconnect(ENetHost* host, ENetPeer* peer, ENetEvent* event) { + if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) + host->recalculateBandwidthLimits = 1; + + if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { + enet_peer_reset(peer); + } else if (event != NULL) { + event->type = ENET_EVENT_TYPE_DISCONNECT; + event->peer = peer; + event->data = 0; + + enet_peer_reset(peer); + } else { + peer->eventData = 0; + + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + } + + static void enet_protocol_notify_disconnect_timeout(ENetHost* host, ENetPeer* peer, ENetEvent* event) { + if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) + host->recalculateBandwidthLimits = 1; + + if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { + enet_peer_reset(peer); + } else if (event != NULL) { + event->type = ENET_EVENT_TYPE_DISCONNECT_TIMEOUT; + event->peer = peer; + event->data = 0; + + enet_peer_reset(peer); + } else { + peer->eventData = 0; + + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + } + + static void enet_protocol_remove_sent_unreliable_commands(ENetPeer* peer) { + ENetOutgoingCommand* outgoingCommand; + + if (enet_list_empty(&peer->sentUnreliableCommands)) + return; + + do { + outgoingCommand = (ENetOutgoingCommand*)enet_list_front(&peer->sentUnreliableCommands); + + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; + + enet_packet_destroy(outgoingCommand->packet); + } + } + + enet_free(outgoingCommand); + } + + while (!enet_list_empty(&peer->sentUnreliableCommands)); + + if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER && enet_list_empty(&peer->outgoingCommands) && enet_list_empty(&peer->sentReliableCommands)) + enet_peer_disconnect(peer, peer->eventData); + } + + static ENetProtocolCommand enet_protocol_remove_sent_reliable_command(ENetPeer* peer, uint16_t reliableSequenceNumber, uint8_t channelID) { + ENetOutgoingCommand* outgoingCommand = NULL; + ENetListIterator currentCommand; + ENetProtocolCommand commandNumber; + + int wasSent = 1; + + for (currentCommand = enet_list_begin(&peer->sentReliableCommands); currentCommand != enet_list_end(&peer->sentReliableCommands); currentCommand = enet_list_next(currentCommand)) { + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + + if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end(&peer->sentReliableCommands)) { + for (currentCommand = enet_list_begin(&peer->outgoingCommands); currentCommand != enet_list_end(&peer->outgoingCommands); currentCommand = enet_list_next(currentCommand)) { + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + + if (outgoingCommand->sendAttempts < 1) + return ENET_PROTOCOL_COMMAND_NONE; + + if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) + break; + } + + if (currentCommand == enet_list_end(&peer->outgoingCommands)) + return ENET_PROTOCOL_COMMAND_NONE; + + wasSent = 0; + } + + if (outgoingCommand == NULL) + return ENET_PROTOCOL_COMMAND_NONE; + + if (channelID < peer->channelCount) { + ENetChannel* channel = &peer->channels[channelID]; + uint16_t reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (channel->reliableWindows[reliableWindow] > 0) { + --channel->reliableWindows[reliableWindow]; + + if (!channel->reliableWindows[reliableWindow]) + channel->usedReliableWindows &= ~(1 << reliableWindow); + } + } + + commandNumber = (ENetProtocolCommand)(outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK); + + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) { + if (wasSent) + peer->reliableDataInTransit -= outgoingCommand->fragmentLength; + + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) { + outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; + + enet_packet_destroy(outgoingCommand->packet); + } + } + + enet_free(outgoingCommand); + + if (enet_list_empty(&peer->sentReliableCommands)) + return commandNumber; + + outgoingCommand = (ENetOutgoingCommand*)enet_list_front(&peer->sentReliableCommands); + peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; + + return commandNumber; + } + + static ENetPeer* enet_protocol_handle_connect(ENetHost* host, ENetProtocolHeader* header, ENetProtocol* command) { + uint8_t incomingSessionID, outgoingSessionID; + uint32_t mtu, windowSize; + ENetChannel* channel; + size_t channelCount, duplicatePeers = 0; + ENetPeer* currentPeer, *peer = NULL; + ENetProtocol verifyCommand; + channelCount = ENET_NET_TO_HOST_32(command->connect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + return NULL; + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) { + if (peer == NULL) + peer = currentPeer; + } else if (currentPeer->state != ENET_PEER_STATE_CONNECTING && enet_in6_equal(currentPeer->address.ipv6, host->receivedAddress.ipv6)) { + if (currentPeer->address.port == host->receivedAddress.port && currentPeer->connectID == command->connect.connectID) + return NULL; + + ++duplicatePeers; + } + } + + if (peer == NULL || duplicatePeers >= host->duplicatePeers) + return NULL; + + if (channelCount > host->channelLimit) + channelCount = host->channelLimit; + + peer->channels = (ENetChannel*)enet_malloc(channelCount * sizeof(ENetChannel)); + + if (peer->channels == NULL) + return NULL; + + peer->channelCount = channelCount; + peer->state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; + peer->connectID = command->connect.connectID; + peer->address = host->receivedAddress; + peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->connect.outgoingPeerID); + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->connect.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->connect.outgoingBandwidth); + peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->connect.packetThrottleInterval); + peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleAcceleration); + peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleDeceleration); + peer->eventData = ENET_NET_TO_HOST_32(command->connect.data); + incomingSessionID = command->connect.incomingSessionID == 0xFF ? peer->outgoingSessionID : command->connect.incomingSessionID; + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + + if (incomingSessionID == peer->outgoingSessionID) + incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + + peer->outgoingSessionID = incomingSessionID; + outgoingSessionID = command->connect.outgoingSessionID == 0xFF ? peer->incomingSessionID : command->connect.outgoingSessionID; + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + + if (outgoingSessionID == peer->incomingSessionID) + outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); + + peer->incomingSessionID = outgoingSessionID; + + for (channel = peer->channels; channel < &peer->channels[channelCount]; ++channel) { + channel->outgoingReliableSequenceNumber = 0; + channel->outgoingUnreliableSequenceNumber = 0; + channel->incomingReliableSequenceNumber = 0; + channel->incomingUnreliableSequenceNumber = 0; + + enet_list_clear(&channel->incomingReliableCommands); + enet_list_clear(&channel->incomingUnreliableCommands); + + channel->usedReliableWindows = 0; + + memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); + } + + mtu = ENET_NET_TO_HOST_32(command->connect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + peer->mtu = mtu; + + if (host->outgoingBandwidth == 0 && peer->incomingBandwidth == 0) + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else if (host->outgoingBandwidth == 0 || peer->incomingBandwidth == 0) + peer->windowSize = (ENET_MAX(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + peer->windowSize = (ENET_MIN(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (host->incomingBandwidth == 0) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + windowSize = (host->incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_NET_TO_HOST_32(command->connect.windowSize)) + windowSize = ENET_NET_TO_HOST_32(command->connect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + verifyCommand.header.channelID = 0xFF; + verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16(peer->incomingPeerID); + verifyCommand.verifyConnect.incomingSessionID = incomingSessionID; + verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID; + verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_32(peer->mtu); + verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32(windowSize); + verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32(channelCount); + verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); + verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32(peer->packetThrottleInterval); + verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(peer->packetThrottleAcceleration); + verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(peer->packetThrottleDeceleration); + verifyCommand.verifyConnect.connectID = peer->connectID; + + enet_peer_queue_outgoing_command(peer, &verifyCommand, NULL, 0, 0); + + return peer; + } + + static int enet_protocol_handle_send_reliable(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, uint8_t** currentData) { + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16(command->sendReliable.dataLength); + *currentData += dataLength; + + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) + return -1; + + if (enet_peer_queue_incoming_command(peer, command, (const uint8_t*)command + sizeof(ENetProtocolSendReliable), dataLength, ENET_PACKET_FLAG_RELIABLE, 0) == NULL) + return -1; + + return 0; + } + + static int enet_protocol_handle_send_unsequenced(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, uint8_t** currentData) { + uint32_t unsequencedGroup, index; + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16(command->sendUnsequenced.dataLength); + *currentData += dataLength; + + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) + return -1; + + unsequencedGroup = ENET_NET_TO_HOST_16(command->sendUnsequenced.unsequencedGroup); + index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE; + + if (unsequencedGroup < peer->incomingUnsequencedGroup) + unsequencedGroup += 0x10000; + + if (unsequencedGroup >= (uint32_t)peer->incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE) + return 0; + + unsequencedGroup &= 0xFFFF; + + if (unsequencedGroup - index != peer->incomingUnsequencedGroup) { + peer->incomingUnsequencedGroup = unsequencedGroup - index; + + memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); + } else if (peer->unsequencedWindow[index / 32] & (1 << (index % 32))) { + return 0; + } + + if (enet_peer_queue_incoming_command(peer, command, (const uint8_t*)command + sizeof(ENetProtocolSendUnsequenced), dataLength, ENET_PACKET_FLAG_UNSEQUENCED, 0) == NULL) + return -1; + + peer->unsequencedWindow[index / 32] |= 1 << (index % 32); + + return 0; + } + + static int enet_protocol_handle_send_unreliable(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, uint8_t** currentData) { + size_t dataLength; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + dataLength = ENET_NET_TO_HOST_16(command->sendUnreliable.dataLength); + *currentData += dataLength; + + if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) + return -1; + + if (enet_peer_queue_incoming_command(peer, command, (const uint8_t*)command + sizeof(ENetProtocolSendUnreliable), dataLength, 0, 0) == NULL) + return -1; + + return 0; + } + + static int enet_protocol_handle_send_fragment(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, uint8_t** currentData) { + uint32_t fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, startSequenceNumber, totalLength; + ENetChannel* channel; + uint16_t startWindow, currentWindow; + ENetListIterator currentCommand; + ENetIncomingCommand* startCommand = NULL; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); + *currentData += fragmentLength; + + if (fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) + return -1; + + channel = &peer->channels[command->header.channelID]; + startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); + startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (startSequenceNumber < channel->incomingReliableSequenceNumber) + startWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + return 0; + + fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || fragmentNumber >= fragmentCount || totalLength > host->maximumPacketSize || fragmentOffset >= totalLength || fragmentLength > totalLength - fragmentOffset) + return -1; + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_previous(currentCommand)) { + ENetIncomingCommand* incomingCommand = (ENetIncomingCommand*)currentCommand; + + if (startSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) + continue; + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber <= startSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < startSequenceNumber) + break; + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT || totalLength != incomingCommand->packet->dataLength || fragmentCount != incomingCommand->fragmentCount) + return -1; + + startCommand = incomingCommand; + + break; + } + } + + if (startCommand == NULL) { + ENetProtocol hostCommand = *command; + hostCommand.header.reliableSequenceNumber = startSequenceNumber; + startCommand = enet_peer_queue_incoming_command(peer, &hostCommand, NULL, totalLength, ENET_PACKET_FLAG_RELIABLE, fragmentCount); + + if (startCommand == NULL) + return -1; + } + + if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { + --startCommand->fragmentsRemaining; + startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) + fragmentLength = startCommand->packet->dataLength - fragmentOffset; + + memcpy(startCommand->packet->data + fragmentOffset, (uint8_t*)command + sizeof(ENetProtocolSendFragment), fragmentLength); + + if (startCommand->fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_reliable_commands(peer, channel, NULL); + } + + return 0; + } + + static int enet_protocol_handle_send_unreliable_fragment(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, uint8_t** currentData) { + uint32_t fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, reliableSequenceNumber, startSequenceNumber, totalLength; + uint16_t reliableWindow, currentWindow; + ENetChannel* channel; + ENetListIterator currentCommand; + ENetIncomingCommand* startCommand = NULL; + + if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) + return -1; + + fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); + *currentData += fragmentLength; + + if (fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) + return -1; + + channel = &peer->channels[command->header.channelID]; + reliableSequenceNumber = command->header.reliableSequenceNumber; + startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + return 0; + + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && startSequenceNumber <= channel->incomingUnreliableSequenceNumber) + return 0; + + fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); + fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); + fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); + totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || fragmentNumber >= fragmentCount || totalLength > host->maximumPacketSize || fragmentOffset >= totalLength || fragmentLength > totalLength - fragmentOffset) + return -1; + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_previous(currentCommand)) { + ENetIncomingCommand* incomingCommand = (ENetIncomingCommand*)currentCommand; + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) + continue; + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) + break; + + if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) + continue; + + if (incomingCommand->unreliableSequenceNumber <= startSequenceNumber) { + if (incomingCommand->unreliableSequenceNumber < startSequenceNumber) + break; + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT || totalLength != incomingCommand->packet->dataLength || fragmentCount != incomingCommand->fragmentCount) + return -1; + + startCommand = incomingCommand; + + break; + } + } + + if (startCommand == NULL) { + startCommand = enet_peer_queue_incoming_command(peer, command, NULL, totalLength, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENTED, fragmentCount); + + if (startCommand == NULL) + return -1; + } + + if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { + --startCommand->fragmentsRemaining; + startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); + + if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) + fragmentLength = startCommand->packet->dataLength - fragmentOffset; + + memcpy(startCommand->packet->data + fragmentOffset, (uint8_t*)command + sizeof(ENetProtocolSendFragment), fragmentLength); + + if (startCommand->fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_unreliable_commands(peer, channel, NULL); + } + + return 0; + } + + static int enet_protocol_handle_ping(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + return 0; + } + + static int enet_protocol_handle_bandwidth_limit(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + if (peer->incomingBandwidth != 0) + --host->bandwidthLimitedPeers; + + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.outgoingBandwidth); + + if (peer->incomingBandwidth != 0) + ++host->bandwidthLimitedPeers; + + if (peer->incomingBandwidth == 0 && host->outgoingBandwidth == 0) + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else if (peer->incomingBandwidth == 0 || host->outgoingBandwidth == 0) + peer->windowSize = (ENET_MAX(peer->incomingBandwidth, host->outgoingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else + peer->windowSize = (ENET_MIN(peer->incomingBandwidth, host->outgoingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + return 0; + } + + static int enet_protocol_handle_throttle_configure(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) + return -1; + + peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleInterval); + peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleAcceleration); + peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleDeceleration); + + return 0; + } + + static int enet_protocol_handle_disconnect(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { + if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE || peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT) + return 0; + + enet_peer_reset_queues(peer); + + if (peer->state == ENET_PEER_STATE_CONNECTION_SUCCEEDED || peer->state == ENET_PEER_STATE_DISCONNECTING || peer->state == ENET_PEER_STATE_CONNECTING) { + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } else if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->state == ENET_PEER_STATE_CONNECTION_PENDING) + host->recalculateBandwidthLimits = 1; + + enet_peer_reset(peer); + } else if (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + enet_protocol_change_state(host, peer, ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT); + } else { + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + } + + if (peer->state != ENET_PEER_STATE_DISCONNECTED) + peer->eventData = ENET_NET_TO_HOST_32(command->disconnect.data); + + return 0; + } + + static int enet_protocol_handle_acknowledge(ENetHost* host, ENetEvent* event, ENetPeer* peer, const ENetProtocol* command) { + uint32_t roundTripTime, receivedSentTime, receivedReliableSequenceNumber; + ENetProtocolCommand commandNumber; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE) + return 0; + + receivedSentTime = ENET_NET_TO_HOST_16(command->acknowledge.receivedSentTime); + receivedSentTime |= host->serviceTime & 0xFFFF0000; + + if ((receivedSentTime & 0x8000) > (host->serviceTime & 0x8000)) + receivedSentTime -= 0x10000; + + if (ENET_TIME_LESS(host->serviceTime, receivedSentTime)) + return 0; + + roundTripTime = ENET_TIME_DIFFERENCE(host->serviceTime, receivedSentTime); + + if (roundTripTime == 0) + roundTripTime = 1; + + enet_peer_throttle(peer, roundTripTime); + + if (peer->lastReceiveTime > 0) { + if (roundTripTime >= peer->roundTripTime) { + uint32_t diff = roundTripTime - peer->roundTripTime; + peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 4; + peer->roundTripTimeVariance += diff / 4; + peer->roundTripTime += diff / 8; + } else { + uint32_t diff = peer->roundTripTime - roundTripTime; + + if (diff <= peer->roundTripTimeVariance) { + peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 4; + peer->roundTripTimeVariance += diff / 4; + } else { + peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 32; + peer->roundTripTimeVariance += diff / 32; + } + + peer->roundTripTime -= diff / 8; + } + } else { + peer->roundTripTime = roundTripTime; + peer->roundTripTimeVariance = roundTripTime / 2; + } + + if (peer->roundTripTime < peer->lowestRoundTripTime) + peer->lowestRoundTripTime = peer->roundTripTime; + + if (peer->roundTripTimeVariance > peer->highestRoundTripTimeVariance) + peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; + + if (peer->packetThrottleEpoch == 0 || ENET_TIME_DIFFERENCE(host->serviceTime, peer->packetThrottleEpoch) >= peer->packetThrottleInterval) { + peer->lastRoundTripTime = peer->lowestRoundTripTime; + peer->lastRoundTripTimeVariance = peer->highestRoundTripTimeVariance; + peer->lowestRoundTripTime = peer->roundTripTime; + peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; + peer->packetThrottleEpoch = host->serviceTime; + } + + peer->lastReceiveTime = ENET_MAX(host->serviceTime, 1); + peer->earliestTimeout = 0; + + receivedReliableSequenceNumber = ENET_NET_TO_HOST_16(command->acknowledge.receivedReliableSequenceNumber); + commandNumber = enet_protocol_remove_sent_reliable_command(peer, receivedReliableSequenceNumber, command->header.channelID); + + switch (peer->state) { + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT) + return -1; + + enet_protocol_notify_connect(host, peer, event); + + break; + + case ENET_PEER_STATE_DISCONNECTING: + if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT) + return -1; + + enet_protocol_notify_disconnect(host, peer, event); + + break; + + case ENET_PEER_STATE_DISCONNECT_LATER: + if (enet_list_empty(&peer->outgoingCommands) && enet_list_empty(&peer->sentReliableCommands)) + enet_peer_disconnect(peer, peer->eventData); + + break; + + default: + break; + } + + return 0; + } + + static int enet_protocol_handle_verify_connect(ENetHost* host, ENetEvent* event, ENetPeer* peer, const ENetProtocol* command) { + uint32_t mtu, windowSize; + size_t channelCount; + + if (peer->state != ENET_PEER_STATE_CONNECTING) + return 0; + + channelCount = ENET_NET_TO_HOST_32(command->verifyConnect.channelCount); + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleInterval) != peer->packetThrottleInterval || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleAcceleration) != peer->packetThrottleAcceleration || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleDeceleration) != peer->packetThrottleDeceleration || command->verifyConnect.connectID != peer->connectID) { + peer->eventData = 0; + + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + + return -1; + } + + enet_protocol_remove_sent_reliable_command(peer, 1, 0xFF); + + if (channelCount < peer->channelCount) + peer->channelCount = channelCount; + + peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->verifyConnect.outgoingPeerID); + peer->incomingSessionID = command->verifyConnect.incomingSessionID; + peer->outgoingSessionID = command->verifyConnect.outgoingSessionID; + mtu = ENET_NET_TO_HOST_32(command->verifyConnect.mtu); + + if (mtu < ENET_PROTOCOL_MINIMUM_MTU) + mtu = ENET_PROTOCOL_MINIMUM_MTU; + else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) + mtu = ENET_PROTOCOL_MAXIMUM_MTU; + + if (mtu < peer->mtu) + peer->mtu = mtu; + + windowSize = ENET_NET_TO_HOST_32(command->verifyConnect.windowSize); + + if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + if (windowSize < peer->windowSize) + peer->windowSize = windowSize; + + peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.incomingBandwidth); + peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.outgoingBandwidth); + + enet_protocol_notify_connect(host, peer, event); + + return 0; + } + + static int enet_protocol_handle_incoming_commands(ENetHost* host, ENetEvent* event) { + ENetProtocolHeader* header; + ENetProtocol* command; + ENetPeer* peer; + uint8_t* currentData; + size_t headerSize; + uint16_t peerID, flags; + uint8_t sessionID; + + if (host->receivedDataLength < (size_t)&((ENetProtocolHeader*)0)->sentTime) + return 0; + + header = (ENetProtocolHeader*)host->receivedData; + peerID = ENET_NET_TO_HOST_16(header->peerID); + sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT; + flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK; + peerID &= ~(ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK); + headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof(ENetProtocolHeader) : (size_t)&((ENetProtocolHeader*)0)->sentTime); + + if (host->checksumCallback != NULL) + headerSize += sizeof(enet_checksum); + + if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID) { + peer = NULL; + } else if (peerID >= host->peerCount) { + return 0; + } else { + peer = &host->peers[peerID]; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE || ((!enet_in6_equal(host->receivedAddress.ipv6, peer->address.ipv6) || host->receivedAddress.port != peer->address.port) && peer->address.ipv4.ip.s_addr != INADDR_BROADCAST) || (peer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && sessionID != peer->incomingSessionID)) + return 0; + } + + if (host->checksumCallback != NULL) { + enet_checksum* checksum = (enet_checksum*)&host->receivedData[headerSize - sizeof(enet_checksum)]; + enet_checksum desiredChecksum = *checksum; + ENetBuffer buffer; + *checksum = peer != NULL ? peer->connectID : 0; + buffer.data = host->receivedData; + buffer.dataLength = host->receivedDataLength; + + if (host->checksumCallback(&buffer, 1) != desiredChecksum) + return 0; + } + + if (peer != NULL) { + peer->address.ipv6 = host->receivedAddress.ipv6; + peer->address.port = host->receivedAddress.port; + peer->incomingDataTotal += host->receivedDataLength; + peer->totalDataReceived += host->receivedDataLength; + } + + currentData = host->receivedData + headerSize; + + while (currentData < &host->receivedData[host->receivedDataLength]) { + uint8_t commandNumber; + size_t commandSize; + command = (ENetProtocol*)currentData; + + if (currentData + sizeof(ENetProtocolCommandHeader) > &host->receivedData[host->receivedDataLength]) + break; + + commandNumber = command->header.command & ENET_PROTOCOL_COMMAND_MASK; + + if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) + break; + + commandSize = commandSizes[commandNumber]; + + if (commandSize == 0 || currentData + commandSize > &host->receivedData[host->receivedDataLength]) + break; + + currentData += commandSize; + + if (peer == NULL && (commandNumber != ENET_PROTOCOL_COMMAND_CONNECT || currentData < &host->receivedData[host->receivedDataLength])) + break; + + command->header.reliableSequenceNumber = ENET_NET_TO_HOST_16(command->header.reliableSequenceNumber); + + switch (commandNumber) { + case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE: + if (enet_protocol_handle_acknowledge(host, event, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_CONNECT: + if (peer != NULL) + goto commandError; + + if (host->preventConnections == 0) { + peer = enet_protocol_handle_connect(host, header, command); + + if (peer == NULL) + goto commandError; + } + + break; + + case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT: + if (enet_protocol_handle_verify_connect(host, event, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_DISCONNECT: + if (enet_protocol_handle_disconnect(host, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_PING: + if (enet_protocol_handle_ping(host, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (enet_protocol_handle_send_reliable(host, peer, command, ¤tData)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + if (enet_protocol_handle_send_unreliable(host, peer, command, ¤tData)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + if (enet_protocol_handle_send_unsequenced(host, peer, command, ¤tData)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + if (enet_protocol_handle_send_fragment(host, peer, command, ¤tData)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT: + if (enet_protocol_handle_bandwidth_limit(host, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE: + if (enet_protocol_handle_throttle_configure(host, peer, command)) + goto commandError; + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + if (enet_protocol_handle_send_unreliable_fragment(host, peer, command, ¤tData)) + goto commandError; + + break; + + default: + goto commandError; + } + + if (peer != NULL && (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) { + uint16_t sentTime; + + if (!(flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)) + break; + + sentTime = ENET_NET_TO_HOST_16(header->sentTime); + + switch (peer->state) { + case ENET_PEER_STATE_DISCONNECTING: + case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: + case ENET_PEER_STATE_DISCONNECTED: + case ENET_PEER_STATE_ZOMBIE: + break; + + case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT: + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_peer_queue_acknowledgement(peer, command, sentTime); + + break; + + default: + enet_peer_queue_acknowledgement(peer, command, sentTime); + + break; + } + } + } + + commandError: + + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) + return 1; + + return 0; + } + + static int enet_protocol_receive_incoming_commands(ENetHost* host, ENetEvent* event) { + int packets; + + for (packets = 0; packets < 256; ++packets) { + int receivedLength; + ENetBuffer buffer; + buffer.data = host->packetData[0]; + buffer.dataLength = host->mtu; + receivedLength = enet_socket_receive(host->socket, &host->receivedAddress, &buffer, 1); + + if (receivedLength == -2) + continue; + + if (receivedLength < 0) + return -1; + + if (receivedLength == 0) + return 0; + + host->receivedData = host->packetData[0]; + host->receivedDataLength = receivedLength; + host->totalReceivedData += receivedLength; + host->totalReceivedPackets++; + + if (host->interceptCallback != NULL) { + switch (host->interceptCallback(event, &host->receivedAddress, host->receivedData, host->receivedDataLength)) { + case 1: + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) + return 1; + + continue; + + case -1: + return -1; + + default: + break; + } + } + + switch (enet_protocol_handle_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + return -1; + + default: + break; + } + } + + return 0; + } + + static void enet_protocol_send_acknowledgements(ENetHost* host, ENetPeer* peer) { + ENetProtocol* command = &host->commands[host->commandCount]; + ENetBuffer* buffer = &host->buffers[host->bufferCount]; + ENetAcknowledgement* acknowledgement; + ENetListIterator currentAcknowledgement; + uint16_t reliableSequenceNumber; + currentAcknowledgement = enet_list_begin(&peer->acknowledgements); + + while (currentAcknowledgement != enet_list_end(&peer->acknowledgements)) { + if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || buffer >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || peer->mtu - host->packetSize < sizeof(ENetProtocolAcknowledge)) { + host->continueSending = 1; + + break; + } + + acknowledgement = (ENetAcknowledgement*)currentAcknowledgement; + currentAcknowledgement = enet_list_next(currentAcknowledgement); + buffer->data = command; + buffer->dataLength = sizeof(ENetProtocolAcknowledge); + host->packetSize += buffer->dataLength; + reliableSequenceNumber = ENET_HOST_TO_NET_16(acknowledgement->command.header.reliableSequenceNumber); + command->header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE; + command->header.channelID = acknowledgement->command.header.channelID; + command->header.reliableSequenceNumber = reliableSequenceNumber; + command->acknowledge.receivedReliableSequenceNumber = reliableSequenceNumber; + command->acknowledge.receivedSentTime = ENET_HOST_TO_NET_16(acknowledgement->sentTime); + + if ((acknowledgement->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) + enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); + + enet_list_remove(&acknowledgement->acknowledgementList); + enet_free(acknowledgement); + + ++command; + ++buffer; + } + + host->commandCount = command - host->commands; + host->bufferCount = buffer - host->buffers; + } + + static int enet_protocol_check_timeouts(ENetHost* host, ENetPeer* peer, ENetEvent* event) { + ENetOutgoingCommand* outgoingCommand; + ENetListIterator currentCommand, insertPosition; + currentCommand = enet_list_begin(&peer->sentReliableCommands); + insertPosition = enet_list_begin(&peer->outgoingCommands); + + while (currentCommand != enet_list_end(&peer->sentReliableCommands)) { + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + currentCommand = enet_list_next(currentCommand); + + if (ENET_TIME_DIFFERENCE(host->serviceTime, outgoingCommand->sentTime) < outgoingCommand->roundTripTimeout) + continue; + + if (peer->earliestTimeout == 0 || ENET_TIME_LESS(outgoingCommand->sentTime, peer->earliestTimeout)) + peer->earliestTimeout = outgoingCommand->sentTime; + + if (peer->earliestTimeout != 0 && (ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMaximum || (outgoingCommand->roundTripTimeout >= outgoingCommand->roundTripTimeoutLimit && ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMinimum))) { + enet_protocol_notify_disconnect_timeout(host, peer, event); + + return 1; + } + + if (outgoingCommand->packet != NULL) + peer->reliableDataInTransit -= outgoingCommand->fragmentLength; + + ++peer->totalPacketsLost; + outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; + outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; + + enet_list_insert(insertPosition, enet_list_remove(&outgoingCommand->outgoingCommandList)); + + if (currentCommand == enet_list_begin(&peer->sentReliableCommands) && !enet_list_empty(&peer->sentReliableCommands)) { + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; + } + } + + return 0; + } + + static int enet_protocol_check_outgoing_commands(ENetHost* host, ENetPeer* peer) { + ENetProtocol* command = &host->commands[host->commandCount]; + ENetBuffer* buffer = &host->buffers[host->bufferCount]; + ENetOutgoingCommand* outgoingCommand; + ENetListIterator currentCommand; + ENetChannel* channel = NULL; + uint16_t reliableWindow = 0; + size_t commandSize = 0; + int windowExceeded = 0, windowWrap = 0, canPing = 1; + currentCommand = enet_list_begin(&peer->outgoingCommands); + + while (currentCommand != enet_list_end(&peer->outgoingCommands)) { + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + + if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + channel = outgoingCommand->command.header.channelID < peer->channelCount ? &peer->channels[outgoingCommand->command.header.channelID] : NULL; + reliableWindow = outgoingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (channel != NULL) { + if (!windowWrap && outgoingCommand->sendAttempts < 1 && !(outgoingCommand->reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && (channel->reliableWindows[(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || channel->usedReliableWindows & ((((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) << reliableWindow) | (((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow))))) + windowWrap = 1; + + if (windowWrap) { + currentCommand = enet_list_next(currentCommand); + + continue; + } + } + + if (outgoingCommand->packet != NULL) { + if (!windowExceeded) { + uint32_t windowSize = (peer->packetThrottle * peer->windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer->reliableDataInTransit + outgoingCommand->fragmentLength > ENET_MAX(windowSize, peer->mtu)) + windowExceeded = 1; + } + + if (windowExceeded) { + currentCommand = enet_list_next(currentCommand); + + continue; + } + } + + canPing = 0; + } + + commandSize = commandSizes[outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK]; + + if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || buffer + 1 >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || peer->mtu - host->packetSize < commandSize || (outgoingCommand->packet != NULL && (uint16_t)(peer->mtu - host->packetSize) < (uint16_t)(commandSize + outgoingCommand->fragmentLength))) { + host->continueSending = 1; + + break; + } + + currentCommand = enet_list_next(currentCommand); + + if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + if (channel != NULL && outgoingCommand->sendAttempts < 1) { + channel->usedReliableWindows |= 1 << reliableWindow; + ++channel->reliableWindows[reliableWindow]; + } + + ++outgoingCommand->sendAttempts; + + if (outgoingCommand->roundTripTimeout == 0) { + outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; + outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; + } + + if (enet_list_empty(&peer->sentReliableCommands)) + peer->nextTimeout = host->serviceTime + outgoingCommand->roundTripTimeout; + + enet_list_insert(enet_list_end(&peer->sentReliableCommands), + enet_list_remove(&outgoingCommand->outgoingCommandList)); + + outgoingCommand->sentTime = host->serviceTime; + host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; + peer->reliableDataInTransit += outgoingCommand->fragmentLength; + } else { + if (outgoingCommand->packet != NULL && outgoingCommand->fragmentOffset == 0 && !(outgoingCommand->packet->flags & (ENET_PACKET_FLAG_UNTHROTTLED))) { + peer->packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; + peer->packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer->packetThrottleCounter > peer->packetThrottle) { + uint16_t reliableSequenceNumber = outgoingCommand->reliableSequenceNumber, + + unreliableSequenceNumber = outgoingCommand->unreliableSequenceNumber; + + for (;;) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) + enet_packet_destroy(outgoingCommand->packet); + + enet_list_remove(&outgoingCommand->outgoingCommandList); + enet_free(outgoingCommand); + + if (currentCommand == enet_list_end(&peer->outgoingCommands)) + break; + + outgoingCommand = (ENetOutgoingCommand*)currentCommand; + + if (outgoingCommand->reliableSequenceNumber != reliableSequenceNumber || outgoingCommand->unreliableSequenceNumber != unreliableSequenceNumber) + break; + + currentCommand = enet_list_next(currentCommand); + } + + continue; + } + } + + enet_list_remove(&outgoingCommand->outgoingCommandList); + + if (outgoingCommand->packet != NULL) + enet_list_insert(enet_list_end(&peer->sentUnreliableCommands), outgoingCommand); + } + + buffer->data = command; + buffer->dataLength = commandSize; + host->packetSize += buffer->dataLength; + *command = outgoingCommand->command; + + if (outgoingCommand->packet != NULL) { + ++buffer; + buffer->data = outgoingCommand->packet->data + outgoingCommand->fragmentOffset; + buffer->dataLength = outgoingCommand->fragmentLength; + host->packetSize += outgoingCommand->fragmentLength; + } else if (!(outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)) { + enet_free(outgoingCommand); + } + + ++peer->totalPacketsSent; + ++command; + ++buffer; + } + + host->commandCount = command - host->commands; + host->bufferCount = buffer - host->buffers; + + if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER && enet_list_empty(&peer->outgoingCommands) && enet_list_empty(&peer->sentReliableCommands) && enet_list_empty(&peer->sentUnreliableCommands)) + enet_peer_disconnect(peer, peer->eventData); + + return canPing; + } + + static int enet_protocol_send_outgoing_commands(ENetHost* host, ENetEvent* event, int checkForTimeouts) { + uint8_t headerData[sizeof(ENetProtocolHeader) + sizeof(enet_checksum)]; + ENetProtocolHeader* header = (ENetProtocolHeader*)headerData; + ENetPeer* currentPeer; + int sentLength; + host->continueSending = 1; + + while (host->continueSending) { + for (host->continueSending = 0, currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED || currentPeer->state == ENET_PEER_STATE_ZOMBIE) + continue; + + host->headerFlags = 0; + host->commandCount = 0; + host->bufferCount = 1; + host->packetSize = sizeof(ENetProtocolHeader); + + if (host->checksumCallback != NULL) + host->packetSize += sizeof(enet_checksum); + + if (!enet_list_empty(¤tPeer->acknowledgements)) + enet_protocol_send_acknowledgements(host, currentPeer); + + if (checkForTimeouts != 0 && !enet_list_empty(¤tPeer->sentReliableCommands) && ENET_TIME_GREATER_EQUAL(host->serviceTime, currentPeer->nextTimeout) && enet_protocol_check_timeouts(host, currentPeer, event) == 1) { + if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) + return 1; + else + continue; + } + + if ((enet_list_empty(¤tPeer->outgoingCommands) || enet_protocol_check_outgoing_commands(host, currentPeer)) && enet_list_empty(¤tPeer->sentReliableCommands) && ENET_TIME_DIFFERENCE(host->serviceTime, currentPeer->lastReceiveTime) >= currentPeer->pingInterval && currentPeer->mtu - host->packetSize >= sizeof(ENetProtocolPing)) { + enet_peer_ping(currentPeer); + enet_protocol_check_outgoing_commands(host, currentPeer); + } + + if (host->commandCount == 0) + continue; + + host->buffers->data = headerData; + + if (host->headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) { + header->sentTime = ENET_HOST_TO_NET_16(host->serviceTime & 0xFFFF); + host->buffers->dataLength = sizeof(ENetProtocolHeader); + } else { + host->buffers->dataLength = (size_t)&((ENetProtocolHeader*)0)->sentTime; + } + + if (currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID) + host->headerFlags |= currentPeer->outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT; + + header->peerID = ENET_HOST_TO_NET_16(currentPeer->outgoingPeerID | host->headerFlags); + + if (host->checksumCallback != NULL) { + enet_checksum* checksum = (enet_checksum*)&headerData[host->buffers->dataLength]; + *checksum = currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer->connectID : 0; + host->buffers->dataLength += sizeof(enet_checksum); + *checksum = host->checksumCallback(host->buffers, host->bufferCount); + } + + currentPeer->lastSendTime = host->serviceTime; + sentLength = enet_socket_send(host->socket, ¤tPeer->address, host->buffers, host->bufferCount); + + enet_protocol_remove_sent_unreliable_commands(currentPeer); + + if (sentLength < 0) + return -1; + + host->totalSentData += sentLength; + currentPeer->totalDataSent += sentLength; + host->totalSentPackets++; + } + } + + return 0; + } + + void enet_host_flush(ENetHost* host) { + host->serviceTime = enet_time_get(); + + enet_protocol_send_outgoing_commands(host, NULL, 0); + } + + int enet_host_check_events(ENetHost* host, ENetEvent* event) { + if (event == NULL) + return -1; + + event->type = ENET_EVENT_TYPE_NONE; + event->peer = NULL; + event->packet = NULL; + + return enet_protocol_dispatch_incoming_commands(host, event); + } + + int enet_host_service(ENetHost* host, ENetEvent* event, uint32_t timeout) { + uint32_t waitCondition; + + if (event != NULL) { + event->type = ENET_EVENT_TYPE_NONE; + event->peer = NULL; + event->packet = NULL; + + switch (enet_protocol_dispatch_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error dispatching incoming packets"); + #endif + + return -1; + + default: + break; + } + } + + host->serviceTime = enet_time_get(); + timeout += host->serviceTime; + + do { + if (ENET_TIME_DIFFERENCE(host->serviceTime, host->bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + enet_host_bandwidth_throttle(host); + + switch (enet_protocol_send_outgoing_commands(host, event, 1)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error sending outgoing packets"); + #endif + + return -1; + + default: + break; + } + + switch (enet_protocol_receive_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error receiving incoming packets"); + #endif + + return -1; + + default: + break; + } + + switch (enet_protocol_send_outgoing_commands(host, event, 1)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error sending outgoing packets"); + #endif + + return -1; + + default: + break; + } + + if (event != NULL) { + switch (enet_protocol_dispatch_incoming_commands(host, event)) { + case 1: + return 1; + + case -1: + #ifdef ENET_DEBUG + perror("Error dispatching incoming packets"); + #endif + + return -1; + + default: + break; + } + } + + if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) + return 0; + + do { + host->serviceTime = enet_time_get(); + + if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) + return 0; + + waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT; + + if (enet_socket_wait(host->socket, &waitCondition, ENET_TIME_DIFFERENCE(timeout, host->serviceTime)) != 0) + return -1; + } + + while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT); + + host->serviceTime = enet_time_get(); + } + + while (waitCondition & ENET_SOCKET_WAIT_RECEIVE); + + return 0; + } + +/* +======================================================================= + + Peer + +======================================================================= +*/ + + void enet_peer_throttle_configure(ENetPeer* peer, uint32_t interval, uint32_t acceleration, uint32_t deceleration, uint32_t threshold) { + ENetProtocol command; + peer->packetThrottleThreshold = threshold; + peer->packetThrottleInterval = interval; + peer->packetThrottleAcceleration = acceleration; + peer->packetThrottleDeceleration = deceleration; + command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32(interval); + command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32(acceleration); + command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32(deceleration); + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + + int enet_peer_throttle(ENetPeer* peer, uint32_t rtt) { + if (peer->lastRoundTripTime <= peer->lastRoundTripTimeVariance) { + peer->packetThrottle = peer->packetThrottleLimit; + } else if (rtt < peer->lastRoundTripTime + (peer->lastRoundTripTimeVariance + 1) / 2) { + peer->packetThrottle += peer->packetThrottleAcceleration; + + if (peer->packetThrottle > peer->packetThrottleLimit) + peer->packetThrottle = peer->packetThrottleLimit; + + return 1; + } else if (rtt > peer->lastRoundTripTime + peer->packetThrottleThreshold + 2 * peer->lastRoundTripTimeVariance) { + if (peer->packetThrottle > peer->packetThrottleDeceleration) + peer->packetThrottle -= peer->packetThrottleDeceleration; + else + peer->packetThrottle = 0; + + return -1; + } + + return 0; + } + + int enet_peer_send(ENetPeer* peer, uint8_t channelID, ENetPacket* packet) { + ENetChannel* channel; + ENetProtocol command; + size_t fragmentLength; + + if (peer->state != ENET_PEER_STATE_CONNECTED || channelID >= peer->channelCount || packet->dataLength > peer->host->maximumPacketSize) + return -1; + + channel = &peer->channels[channelID]; + fragmentLength = peer->mtu - sizeof(ENetProtocolHeader) - sizeof(ENetProtocolSendFragment) - sizeof(ENetProtocolAcknowledge); + + if (peer->host->checksumCallback != NULL) + fragmentLength -= sizeof(enet_checksum); + + if (packet->dataLength > fragmentLength) { + uint32_t fragmentCount = (packet->dataLength + fragmentLength - 1) / fragmentLength, fragmentNumber, fragmentOffset; + uint8_t commandNumber; + uint16_t startSequenceNumber; + ENetList fragments; + ENetOutgoingCommand* fragment; + + if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) + return -1; + + if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENTED)) == ENET_PACKET_FLAG_UNRELIABLE_FRAGMENTED && channel->outgoingUnreliableSequenceNumber < 0xFFFF) { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT; + startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingUnreliableSequenceNumber + 1); + } else { + commandNumber = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingReliableSequenceNumber + 1); + } + + enet_list_clear(&fragments); + + for (fragmentNumber = 0, fragmentOffset = 0; fragmentOffset < packet->dataLength; ++fragmentNumber, fragmentOffset += fragmentLength) { + if (packet->dataLength - fragmentOffset < fragmentLength) + fragmentLength = packet->dataLength - fragmentOffset; + + fragment = (ENetOutgoingCommand*)enet_malloc(sizeof(ENetOutgoingCommand)); + + if (fragment == NULL) { + while (!enet_list_empty(&fragments)) { + fragment = (ENetOutgoingCommand*)enet_list_remove(enet_list_begin(&fragments)); + + enet_free(fragment); + } + + return -1; + } + + fragment->fragmentOffset = fragmentOffset; + fragment->fragmentLength = fragmentLength; + fragment->packet = packet; + fragment->command.header.command = commandNumber; + fragment->command.header.channelID = channelID; + fragment->command.sendFragment.startSequenceNumber = startSequenceNumber; + fragment->command.sendFragment.dataLength = ENET_HOST_TO_NET_16(fragmentLength); + fragment->command.sendFragment.fragmentCount = ENET_HOST_TO_NET_32(fragmentCount); + fragment->command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32(fragmentNumber); + fragment->command.sendFragment.totalLength = ENET_HOST_TO_NET_32(packet->dataLength); + fragment->command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32(fragmentOffset); + + enet_list_insert(enet_list_end(&fragments), fragment); + } + + packet->referenceCount += fragmentNumber; + + while (!enet_list_empty(&fragments)) { + fragment = (ENetOutgoingCommand*)enet_list_remove(enet_list_begin(&fragments)); + + enet_peer_setup_outgoing_command(peer, fragment); + } + + return 0; + } + + command.header.channelID = channelID; + + if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED)) == ENET_PACKET_FLAG_UNSEQUENCED) { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } else if (packet->flags & ENET_PACKET_FLAG_RELIABLE || channel->outgoingUnreliableSequenceNumber >= 0xFFFF) { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.sendReliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } else { + command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE; + command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); + } + + if (enet_peer_queue_outgoing_command(peer, &command, packet, 0, packet->dataLength) == NULL) + return -1; + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + enet_host_flush(peer->host); + + return 0; + } + + ENetPacket* enet_peer_receive(ENetPeer* peer, uint8_t* channelID) { + ENetIncomingCommand* incomingCommand; + ENetPacket* packet; + + if (enet_list_empty(&peer->dispatchedCommands)) + return NULL; + + incomingCommand = (ENetIncomingCommand*)enet_list_remove(enet_list_begin(&peer->dispatchedCommands)); + + if (channelID != NULL) + *channelID = incomingCommand->command.header.channelID; + + packet = incomingCommand->packet; + --packet->referenceCount; + + if (incomingCommand->fragments != NULL) + enet_free(incomingCommand->fragments); + + enet_free(incomingCommand); + + peer->totalWaitingData -= packet->dataLength; + + return packet; + } + + static void enet_peer_reset_outgoing_commands(ENetList* queue) { + ENetOutgoingCommand* outgoingCommand; + + while (!enet_list_empty(queue)) { + outgoingCommand = (ENetOutgoingCommand*)enet_list_remove(enet_list_begin(queue)); + + if (outgoingCommand->packet != NULL) { + --outgoingCommand->packet->referenceCount; + + if (outgoingCommand->packet->referenceCount == 0) + enet_packet_destroy(outgoingCommand->packet); + } + + enet_free(outgoingCommand); + } + } + + static void enet_peer_remove_incoming_commands(ENetList* queue, ENetListIterator startCommand, ENetListIterator endCommand, ENetIncomingCommand* excludeCommand) { + ENetListIterator currentCommand; + + for (currentCommand = startCommand; currentCommand != endCommand;) { + ENetIncomingCommand* incomingCommand = (ENetIncomingCommand*)currentCommand; + currentCommand = enet_list_next(currentCommand); + + if (incomingCommand == excludeCommand) + continue; + + enet_list_remove(&incomingCommand->incomingCommandList); + + if (incomingCommand->packet != NULL) { + --incomingCommand->packet->referenceCount; + + if (incomingCommand->packet->referenceCount == 0) + enet_packet_destroy(incomingCommand->packet); + } + + if (incomingCommand->fragments != NULL) + enet_free(incomingCommand->fragments); + + enet_free(incomingCommand); + } + } + + static void enet_peer_reset_incoming_commands(ENetList* queue) { + enet_peer_remove_incoming_commands(queue, enet_list_begin(queue), enet_list_end(queue), NULL); + } + + void enet_peer_reset_queues(ENetPeer* peer) { + ENetChannel* channel; + + if (peer->needsDispatch) { + enet_list_remove(&peer->dispatchList); + + peer->needsDispatch = 0; + } + + while (!enet_list_empty(&peer->acknowledgements)) { + enet_free(enet_list_remove(enet_list_begin(&peer->acknowledgements))); + } + + enet_peer_reset_outgoing_commands(&peer->sentReliableCommands); + enet_peer_reset_outgoing_commands(&peer->sentUnreliableCommands); + enet_peer_reset_outgoing_commands(&peer->outgoingCommands); + enet_peer_reset_incoming_commands(&peer->dispatchedCommands); + + if (peer->channels != NULL && peer->channelCount > 0) { + for (channel = peer->channels; channel < &peer->channels[peer->channelCount]; ++channel) { + enet_peer_reset_incoming_commands(&channel->incomingReliableCommands); + enet_peer_reset_incoming_commands(&channel->incomingUnreliableCommands); + } + + enet_free(peer->channels); + } + + peer->channels = NULL; + peer->channelCount = 0; + } + + void enet_peer_on_connect(ENetPeer* peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->incomingBandwidth != 0) + ++peer->host->bandwidthLimitedPeers; + + ++peer->host->connectedPeers; + } + } + + void enet_peer_on_disconnect(ENetPeer* peer) { + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + if (peer->incomingBandwidth != 0) + --peer->host->bandwidthLimitedPeers; + + --peer->host->connectedPeers; + } + } + + void enet_peer_reset(ENetPeer* peer) { + enet_peer_on_disconnect(peer); + + peer->outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; + peer->state = ENET_PEER_STATE_DISCONNECTED; + peer->incomingBandwidth = 0; + peer->outgoingBandwidth = 0; + peer->incomingBandwidthThrottleEpoch = 0; + peer->outgoingBandwidthThrottleEpoch = 0; + peer->incomingDataTotal = 0; + peer->totalDataReceived = 0; + peer->outgoingDataTotal = 0; + peer->totalDataSent = 0; + peer->lastSendTime = 0; + peer->lastReceiveTime = 0; + peer->nextTimeout = 0; + peer->earliestTimeout = 0; + peer->totalPacketsSent = 0; + peer->totalPacketsLost = 0; + peer->packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; + peer->packetThrottleThreshold = ENET_PEER_PACKET_THROTTLE_THRESHOLD; + peer->packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE; + peer->packetThrottleCounter = 0; + peer->packetThrottleEpoch = 0; + peer->packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION; + peer->packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION; + peer->packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL; + peer->pingInterval = ENET_PEER_PING_INTERVAL; + peer->timeoutLimit = ENET_PEER_TIMEOUT_LIMIT; + peer->timeoutMinimum = ENET_PEER_TIMEOUT_MINIMUM; + peer->timeoutMaximum = ENET_PEER_TIMEOUT_MAXIMUM; + peer->lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer->lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + peer->lastRoundTripTimeVariance = 0; + peer->highestRoundTripTimeVariance = 0; + peer->roundTripTime = 1; + peer->roundTripTimeVariance = 0; + peer->mtu = peer->host->mtu; + peer->reliableDataInTransit = 0; + peer->outgoingReliableSequenceNumber = 0; + peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + peer->incomingUnsequencedGroup = 0; + peer->outgoingUnsequencedGroup = 0; + peer->eventData = 0; + peer->totalWaitingData = 0; + + memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); + + enet_peer_reset_queues(peer); + } + + void enet_peer_ping(ENetPeer* peer) { + ENetProtocol command; + + if (peer->state != ENET_PEER_STATE_CONNECTED) + return; + + command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + + void enet_peer_ping_interval(ENetPeer* peer, uint32_t pingInterval) { + peer->pingInterval = pingInterval ? pingInterval : ENET_PEER_PING_INTERVAL; + } + + void enet_peer_timeout(ENetPeer* peer, uint32_t timeoutLimit, uint32_t timeoutMinimum, uint32_t timeoutMaximum) { + peer->timeoutLimit = timeoutLimit ? timeoutLimit : ENET_PEER_TIMEOUT_LIMIT; + peer->timeoutMinimum = timeoutMinimum ? timeoutMinimum : ENET_PEER_TIMEOUT_MINIMUM; + peer->timeoutMaximum = timeoutMaximum ? timeoutMaximum : ENET_PEER_TIMEOUT_MAXIMUM; + } + + void enet_peer_disconnect_now(ENetPeer* peer, uint32_t data) { + ENetProtocol command; + + if (peer->state == ENET_PEER_STATE_DISCONNECTED) + return; + + if (peer->state != ENET_PEER_STATE_ZOMBIE && peer->state != ENET_PEER_STATE_DISCONNECTING) { + enet_peer_reset_queues(peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32(data); + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + enet_host_flush(peer->host); + } + + enet_peer_reset(peer); + } + + void enet_peer_disconnect(ENetPeer* peer, uint32_t data) { + ENetProtocol command; + + if (peer->state == ENET_PEER_STATE_DISCONNECTING || peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || peer->state == ENET_PEER_STATE_ZOMBIE) + return; + + enet_peer_reset_queues(peer); + + command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT; + command.header.channelID = 0xFF; + command.disconnect.data = ENET_HOST_TO_NET_32(data); + + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + else + command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + + if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { + enet_peer_on_disconnect(peer); + + peer->state = ENET_PEER_STATE_DISCONNECTING; + } else { + enet_host_flush(peer->host); + enet_peer_reset(peer); + } + } + + void enet_peer_disconnect_later(ENetPeer* peer, uint32_t data) { + if ((peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) && !(enet_list_empty(&peer->outgoingCommands) && enet_list_empty(&peer->sentReliableCommands))) { + peer->state = ENET_PEER_STATE_DISCONNECT_LATER; + peer->eventData = data; + } else { + enet_peer_disconnect(peer, data); + } + } + + ENetAcknowledgement* enet_peer_queue_acknowledgement(ENetPeer* peer, const ENetProtocol* command, uint16_t sentTime) { + ENetAcknowledgement* acknowledgement; + + if (command->header.channelID < peer->channelCount) { + ENetChannel* channel = &peer->channels[command->header.channelID]; + uint16_t reliableWindow = command->header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + uint16_t currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (command->header.reliableSequenceNumber < channel->incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS) + return NULL; + } + + acknowledgement = (ENetAcknowledgement*)enet_malloc(sizeof(ENetAcknowledgement)); + + if (acknowledgement == NULL) + return NULL; + + peer->outgoingDataTotal += sizeof(ENetProtocolAcknowledge); + acknowledgement->sentTime = sentTime; + acknowledgement->command = *command; + + enet_list_insert(enet_list_end(&peer->acknowledgements), acknowledgement); + + return acknowledgement; + } + + void enet_peer_setup_outgoing_command(ENetPeer* peer, ENetOutgoingCommand* outgoingCommand) { + ENetChannel* channel = &peer->channels[outgoingCommand->command.header.channelID]; + peer->outgoingDataTotal += enet_protocol_command_size(outgoingCommand->command.header.command) + outgoingCommand->fragmentLength; + + if (outgoingCommand->command.header.channelID == 0xFF) { + ++peer->outgoingReliableSequenceNumber; + outgoingCommand->reliableSequenceNumber = peer->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = 0; + } else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { + ++channel->outgoingReliableSequenceNumber; + channel->outgoingUnreliableSequenceNumber = 0; + outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = 0; + } else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) { + ++peer->outgoingUnsequencedGroup; + outgoingCommand->reliableSequenceNumber = 0; + outgoingCommand->unreliableSequenceNumber = 0; + } else { + if (outgoingCommand->fragmentOffset == 0) + ++channel->outgoingUnreliableSequenceNumber; + + outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; + outgoingCommand->unreliableSequenceNumber = channel->outgoingUnreliableSequenceNumber; + } + + outgoingCommand->sendAttempts = 0; + outgoingCommand->sentTime = 0; + outgoingCommand->roundTripTimeout = 0; + outgoingCommand->roundTripTimeoutLimit = 0; + outgoingCommand->command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->reliableSequenceNumber); + + switch (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + outgoingCommand->command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->unreliableSequenceNumber); + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + outgoingCommand->command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16(peer->outgoingUnsequencedGroup); + + break; + + default: + break; + } + + enet_list_insert(enet_list_end(&peer->outgoingCommands), outgoingCommand); + } + + ENetOutgoingCommand* enet_peer_queue_outgoing_command(ENetPeer* peer, const ENetProtocol* command, ENetPacket* packet, uint32_t offset, uint16_t length) { + ENetOutgoingCommand* outgoingCommand = (ENetOutgoingCommand*)enet_malloc(sizeof(ENetOutgoingCommand)); + + if (outgoingCommand == NULL) + return NULL; + + outgoingCommand->command = *command; + outgoingCommand->fragmentOffset = offset; + outgoingCommand->fragmentLength = length; + outgoingCommand->packet = packet; + + if (packet != NULL) + ++packet->referenceCount; + + enet_peer_setup_outgoing_command(peer, outgoingCommand); + + return outgoingCommand; + } + + void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer* peer, ENetChannel* channel, ENetIncomingCommand* queuedCommand) { + ENetListIterator droppedCommand, startCommand, currentCommand; + + for (droppedCommand = startCommand = currentCommand = enet_list_begin(&channel->incomingUnreliableCommands); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_next(currentCommand)) { + ENetIncomingCommand* incomingCommand = (ENetIncomingCommand*)currentCommand; + + if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + continue; + + if (incomingCommand->reliableSequenceNumber == channel->incomingReliableSequenceNumber) { + if (incomingCommand->fragmentsRemaining <= 0) { + channel->incomingUnreliableSequenceNumber = incomingCommand->unreliableSequenceNumber; + + continue; + } + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + + peer->needsDispatch = 1; + } + + droppedCommand = currentCommand; + } else if (droppedCommand != currentCommand) { + droppedCommand = enet_list_previous(currentCommand); + } + } else { + uint16_t reliableWindow = incomingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + uint16_t currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow >= currentWindow && reliableWindow < currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + break; + + droppedCommand = enet_list_next(currentCommand); + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + + peer->needsDispatch = 1; + } + } + } + + startCommand = enet_list_next(currentCommand); + } + + if (startCommand != currentCommand) { + enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + + peer->needsDispatch = 1; + } + + droppedCommand = currentCommand; + } + + enet_peer_remove_incoming_commands(&channel->incomingUnreliableCommands, enet_list_begin(&channel->incomingUnreliableCommands), droppedCommand, queuedCommand); + } + + void enet_peer_dispatch_incoming_reliable_commands(ENetPeer* peer, ENetChannel* channel, ENetIncomingCommand* queuedCommand) { + ENetListIterator currentCommand; + + for (currentCommand = enet_list_begin(&channel->incomingReliableCommands); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_next(currentCommand)) { + ENetIncomingCommand* incomingCommand = (ENetIncomingCommand*)currentCommand; + + if (incomingCommand->fragmentsRemaining > 0 || incomingCommand->reliableSequenceNumber != (uint16_t)(channel->incomingReliableSequenceNumber + 1)) + break; + + channel->incomingReliableSequenceNumber = incomingCommand->reliableSequenceNumber; + + if (incomingCommand->fragmentCount > 0) + channel->incomingReliableSequenceNumber += incomingCommand->fragmentCount - 1; + } + + if (currentCommand == enet_list_begin(&channel->incomingReliableCommands)) + return; + + channel->incomingUnreliableSequenceNumber = 0; + + enet_list_move(enet_list_end(&peer->dispatchedCommands), enet_list_begin(&channel->incomingReliableCommands), enet_list_previous(currentCommand)); + + if (!peer->needsDispatch) { + enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); + + peer->needsDispatch = 1; + } + + if (!enet_list_empty(&channel->incomingUnreliableCommands)) + enet_peer_dispatch_incoming_unreliable_commands(peer, channel, queuedCommand); + } + + ENetIncomingCommand* enet_peer_queue_incoming_command(ENetPeer* peer, const ENetProtocol* command, const void* data, size_t dataLength, uint32_t flags, uint32_t fragmentCount) { + static ENetIncomingCommand dummyCommand; + + ENetChannel* channel = &peer->channels[command->header.channelID]; + uint32_t unreliableSequenceNumber = 0, reliableSequenceNumber = 0; + uint16_t reliableWindow, currentWindow; + ENetIncomingCommand* incomingCommand; + ENetListIterator currentCommand; + ENetPacket* packet = NULL; + + if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER) + goto discardCommand; + + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { + reliableSequenceNumber = command->header.reliableSequenceNumber; + reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; + + if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) + reliableWindow += ENET_PEER_RELIABLE_WINDOWS; + + if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) + goto discardCommand; + } + + switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber) + goto discardCommand; + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_previous(currentCommand)) { + incomingCommand = (ENetIncomingCommand*)currentCommand; + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) + continue; + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber <= reliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) + break; + + goto discardCommand; + } + } + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: + unreliableSequenceNumber = ENET_NET_TO_HOST_16(command->sendUnreliable.unreliableSequenceNumber); + + if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && unreliableSequenceNumber <= channel->incomingUnreliableSequenceNumber) + goto discardCommand; + + for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_previous(currentCommand)) { + incomingCommand = (ENetIncomingCommand*)currentCommand; + + if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) + continue; + + if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) + continue; + } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { + break; + } + + if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) + break; + + if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) + continue; + + if (incomingCommand->unreliableSequenceNumber <= unreliableSequenceNumber) { + if (incomingCommand->unreliableSequenceNumber < unreliableSequenceNumber) + break; + + goto discardCommand; + } + } + + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + currentCommand = enet_list_end(&channel->incomingUnreliableCommands); + + break; + + default: + goto discardCommand; + } + + if (peer->totalWaitingData >= peer->host->maximumWaitingData) + goto notifyError; + + packet = enet_packet_create(data, dataLength, flags); + + if (packet == NULL) + goto notifyError; + + incomingCommand = (ENetIncomingCommand*)enet_malloc(sizeof(ENetIncomingCommand)); + + if (incomingCommand == NULL) + goto notifyError; + + incomingCommand->reliableSequenceNumber = command->header.reliableSequenceNumber; + incomingCommand->unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF; + incomingCommand->command = *command; + incomingCommand->fragmentCount = fragmentCount; + incomingCommand->fragmentsRemaining = fragmentCount; + incomingCommand->packet = packet; + incomingCommand->fragments = NULL; + + if (fragmentCount > 0) { + if (fragmentCount <= ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) + incomingCommand->fragments = (uint32_t*)enet_malloc((fragmentCount + 31) / 32 * sizeof(uint32_t)); + + if (incomingCommand->fragments == NULL) { + enet_free(incomingCommand); + + goto notifyError; + } + + memset(incomingCommand->fragments, 0, (fragmentCount + 31) / 32 * sizeof(uint32_t)); + } + + if (packet != NULL) { + ++packet->referenceCount; + peer->totalWaitingData += packet->dataLength; + } + + enet_list_insert(enet_list_next(currentCommand), incomingCommand); + + switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: + enet_peer_dispatch_incoming_reliable_commands(peer, channel, incomingCommand); + + break; + + default: + enet_peer_dispatch_incoming_unreliable_commands(peer, channel, incomingCommand); + + break; + } + + return incomingCommand; + + discardCommand: + + if (fragmentCount > 0) + goto notifyError; + + if (packet != NULL && packet->referenceCount == 0) + enet_packet_destroy(packet); + + return &dummyCommand; + + notifyError: + + if (packet != NULL && packet->referenceCount == 0) + enet_packet_destroy(packet); + + return NULL; + } + +/* +======================================================================= + + Host + +======================================================================= +*/ + + ENetHost* enet_host_create(const ENetAddress* address, size_t peerCount, size_t channelLimit, uint32_t incomingBandwidth, uint32_t outgoingBandwidth, int bufferSize) { + ENetHost* host; + ENetPeer* currentPeer; + + if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID) + return NULL; + + host = (ENetHost*)enet_malloc(sizeof(ENetHost)); + + if (host == NULL) + return NULL; + + memset(host, 0, sizeof(ENetHost)); + + host->peers = (ENetPeer*)enet_malloc(peerCount * sizeof(ENetPeer)); + + if (host->peers == NULL) { + enet_free(host); + + return NULL; + } + + memset(host->peers, 0, peerCount * sizeof(ENetPeer)); + + host->socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); + + if (host->socket != ENET_SOCKET_NULL) + enet_socket_set_option(host->socket, ENET_SOCKOPT_IPV6_V6ONLY, 0); + + if (host->socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind(host->socket, address) < 0)) { + if (host->socket != ENET_SOCKET_NULL) + enet_socket_destroy(host->socket); + + enet_free(host->peers); + enet_free(host); + + return NULL; + } + + if (bufferSize > ENET_HOST_BUFFER_SIZE_MAX) + bufferSize = ENET_HOST_BUFFER_SIZE_MAX; + else if (bufferSize < ENET_HOST_BUFFER_SIZE_MIN) + bufferSize = ENET_HOST_BUFFER_SIZE_MIN; + + enet_socket_set_option(host->socket, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(host->socket, ENET_SOCKOPT_BROADCAST, 1); + enet_socket_set_option(host->socket, ENET_SOCKOPT_RCVBUF, bufferSize); + enet_socket_set_option(host->socket, ENET_SOCKOPT_SNDBUF, bufferSize); + + if (address != NULL && enet_socket_get_address(host->socket, &host->address) < 0) + host->address = *address; + + if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host->randomSeed = (uint32_t)(size_t)host; + host->randomSeed += enet_host_random_seed(); + host->randomSeed = (host->randomSeed << 16) | (host->randomSeed >> 16); + host->channelLimit = channelLimit; + host->incomingBandwidth = incomingBandwidth; + host->outgoingBandwidth = outgoingBandwidth; + host->bandwidthThrottleEpoch = 0; + host->recalculateBandwidthLimits = 0; + host->preventConnections = 0; + host->mtu = ENET_HOST_DEFAULT_MTU; + host->peerCount = peerCount; + host->commandCount = 0; + host->bufferCount = 0; + host->checksumCallback = NULL; + host->receivedAddress.ipv6 = ENET_HOST_ANY; + host->receivedAddress.port = 0; + host->receivedData = NULL; + host->receivedDataLength = 0; + host->totalSentData = 0; + host->totalSentPackets = 0; + host->totalReceivedData = 0; + host->totalReceivedPackets = 0; + host->connectedPeers = 0; + host->bandwidthLimitedPeers = 0; + host->duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID; + host->maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE; + host->maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA; + host->interceptCallback = NULL; + + enet_list_clear(&host->dispatchQueue); + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + currentPeer->host = host; + currentPeer->incomingPeerID = currentPeer - host->peers; + currentPeer->outgoingSessionID = currentPeer->incomingSessionID = 0xFF; + currentPeer->data = NULL; + + enet_list_clear(¤tPeer->acknowledgements); + enet_list_clear(¤tPeer->sentReliableCommands); + enet_list_clear(¤tPeer->sentUnreliableCommands); + enet_list_clear(¤tPeer->outgoingCommands); + enet_list_clear(¤tPeer->dispatchedCommands); + enet_peer_reset(currentPeer); + } + + return host; + } + + void enet_host_destroy(ENetHost* host) { + ENetPeer* currentPeer; + + if (host == NULL) + return; + + enet_socket_destroy(host->socket); + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + enet_peer_reset(currentPeer); + } + + enet_free(host->peers); + enet_free(host); + } + + void enet_host_prevent_connections(ENetHost* host, uint8_t state) { + if (host == NULL) + return; + + host->preventConnections = state; + } + + ENetPeer* enet_host_connect(ENetHost* host, const ENetAddress* address, size_t channelCount, uint32_t data) { + ENetPeer* currentPeer; + ENetChannel* channel; + ENetProtocol command; + + if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + else if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) + break; + } + + if (currentPeer >= &host->peers[host->peerCount]) + return NULL; + + currentPeer->channels = (ENetChannel*)enet_malloc(channelCount * sizeof(ENetChannel)); + + if (currentPeer->channels == NULL) + return NULL; + + currentPeer->channelCount = channelCount; + currentPeer->state = ENET_PEER_STATE_CONNECTING; + currentPeer->address = *address; + currentPeer->connectID = ++host->randomSeed; + + if (host->outgoingBandwidth == 0) + currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + else + currentPeer->windowSize = (host->outgoingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + + if (currentPeer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) + currentPeer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; + else if (currentPeer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) + currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; + + for (channel = currentPeer->channels; channel < ¤tPeer->channels[channelCount]; ++channel) { + channel->outgoingReliableSequenceNumber = 0; + channel->outgoingUnreliableSequenceNumber = 0; + channel->incomingReliableSequenceNumber = 0; + channel->incomingUnreliableSequenceNumber = 0; + + enet_list_clear(&channel->incomingReliableCommands); + enet_list_clear(&channel->incomingUnreliableCommands); + + channel->usedReliableWindows = 0; + + memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); + } + + command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.connect.outgoingPeerID = ENET_HOST_TO_NET_16(currentPeer->incomingPeerID); + command.connect.incomingSessionID = currentPeer->incomingSessionID; + command.connect.outgoingSessionID = currentPeer->outgoingSessionID; + command.connect.mtu = ENET_HOST_TO_NET_32(currentPeer->mtu); + command.connect.windowSize = ENET_HOST_TO_NET_32(currentPeer->windowSize); + command.connect.channelCount = ENET_HOST_TO_NET_32(channelCount); + command.connect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); + command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32(currentPeer->packetThrottleInterval); + command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleAcceleration); + command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleDeceleration); + command.connect.connectID = currentPeer->connectID; + command.connect.data = ENET_HOST_TO_NET_32(data); + + enet_peer_queue_outgoing_command(currentPeer, &command, NULL, 0, 0); + + return currentPeer; + } + + void enet_host_broadcast(ENetHost* host, uint8_t channelID, ENetPacket* packet) { + ENetPeer* currentPeer; + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + ++packet->referenceCount; + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state != ENET_PEER_STATE_CONNECTED) + continue; + + enet_peer_send(currentPeer, channelID, packet); + } + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + --packet->referenceCount; + + if (packet->referenceCount == 0) + enet_packet_destroy(packet); + } + + void enet_host_broadcast_exclude(ENetHost* host, uint8_t channelID, ENetPacket* packet, ENetPeer* excludedPeer) { + ENetPeer* currentPeer; + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + ++packet->referenceCount; + + for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { + if (currentPeer->state != ENET_PEER_STATE_CONNECTED || currentPeer == excludedPeer) + continue; + + enet_peer_send(currentPeer, channelID, packet); + } + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + --packet->referenceCount; + + if (packet->referenceCount == 0) + enet_packet_destroy(packet); + } + + void enet_host_broadcast_selective(ENetHost* host, uint8_t channelID, ENetPacket* packet, ENetPeer** peers, size_t length) { + ENetPeer* currentPeer; + size_t i; + + if (host == NULL) + return; + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + ++packet->referenceCount; + + for (i = 0; i < length; i++) { + currentPeer = peers[i]; + + if (currentPeer == NULL || currentPeer->state != ENET_PEER_STATE_CONNECTED) + continue; + + enet_peer_send(currentPeer, channelID, packet); + } + + if (packet->flags & ENET_PACKET_FLAG_INSTANT) + --packet->referenceCount; + + if (packet->referenceCount == 0) + enet_packet_destroy(packet); + } + + void enet_host_channel_limit(ENetHost* host, size_t channelLimit) { + if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; + else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) + channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; + + host->channelLimit = channelLimit; + } + + void enet_host_bandwidth_limit(ENetHost* host, uint32_t incomingBandwidth, uint32_t outgoingBandwidth) { + host->incomingBandwidth = incomingBandwidth; + host->outgoingBandwidth = outgoingBandwidth; + host->recalculateBandwidthLimits = 1; + } + + void enet_host_bandwidth_throttle(ENetHost* host) { + uint32_t timeCurrent = enet_time_get(); + uint32_t elapsedTime = timeCurrent - host->bandwidthThrottleEpoch; + uint32_t peersRemaining = (uint32_t)host->connectedPeers; + uint32_t dataTotal = ~0; + uint32_t bandwidth = ~0; + uint32_t throttle = 0; + uint32_t bandwidthLimit = 0; + + int needsAdjustment = host->bandwidthLimitedPeers > 0 ? 1 : 0; + ENetPeer* peer; + ENetProtocol command; + + if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + return; + + if (host->outgoingBandwidth == 0 && host->incomingBandwidth == 0) + return; + + host->bandwidthThrottleEpoch = timeCurrent; + + if (peersRemaining == 0) + return; + + if (host->outgoingBandwidth != 0) { + dataTotal = 0; + bandwidth = (host->outgoingBandwidth * elapsedTime) / 1000; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + dataTotal += peer->outgoingDataTotal; + } + } + + while (peersRemaining > 0 && needsAdjustment != 0) { + needsAdjustment = 0; + + if (dataTotal <= bandwidth) + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + else + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + uint32_t peerBandwidth; + + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->incomingBandwidth == 0 || peer->outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peerBandwidth = (peer->incomingBandwidth * elapsedTime) / 1000; + + if ((throttle * peer->outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth) + continue; + + peer->packetThrottleLimit = (peerBandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / peer->outgoingDataTotal; + + if (peer->packetThrottleLimit == 0) + peer->packetThrottleLimit = 1; + + if (peer->packetThrottle > peer->packetThrottleLimit) + peer->packetThrottle = peer->packetThrottleLimit; + + peer->outgoingBandwidthThrottleEpoch = timeCurrent; + peer->incomingDataTotal = 0; + peer->outgoingDataTotal = 0; + needsAdjustment = 1; + --peersRemaining; + bandwidth -= peerBandwidth; + dataTotal -= peerBandwidth; + } + } + + if (peersRemaining > 0) { + if (dataTotal <= bandwidth) + throttle = ENET_PEER_PACKET_THROTTLE_SCALE; + else + throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->outgoingBandwidthThrottleEpoch == timeCurrent) + continue; + + peer->packetThrottleLimit = throttle; + + if (peer->packetThrottle > peer->packetThrottleLimit) + peer->packetThrottle = peer->packetThrottleLimit; + + peer->incomingDataTotal = 0; + peer->outgoingDataTotal = 0; + } + } + + if (host->recalculateBandwidthLimits) { + host->recalculateBandwidthLimits = 0; + peersRemaining = (uint32_t)host->connectedPeers; + bandwidth = host->incomingBandwidth; + needsAdjustment = 1; + + if (bandwidth == 0) { + bandwidthLimit = 0; + } else { + while (peersRemaining > 0 && needsAdjustment != 0) { + needsAdjustment = 0; + bandwidthLimit = bandwidth / peersRemaining; + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->incomingBandwidthThrottleEpoch == timeCurrent) + continue; + + if (peer->outgoingBandwidth > 0 && peer->outgoingBandwidth >= bandwidthLimit) + continue; + + peer->incomingBandwidthThrottleEpoch = timeCurrent; + needsAdjustment = 1; + --peersRemaining; + bandwidth -= peer->outgoingBandwidth; + } + } + } + + for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { + if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) + continue; + + command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; + command.header.channelID = 0xFF; + command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); + + if (peer->incomingBandwidthThrottleEpoch == timeCurrent) + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(peer->outgoingBandwidth); + else + command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(bandwidthLimit); + + enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); + } + } + } + +/* +======================================================================= + + Address + +======================================================================= +*/ + + int enet_address_set_ip(ENetAddress* address, const char* ip) { + int type = AF_INET6; + void* destination = &address->ipv6; + + if (strchr(ip, ':') == NULL) { + type = AF_INET; + + memset(address, 0, sizeof(address->ipv4.zeros)); + + address->ipv4.ffff = 0xFFFF; + destination = &address->ipv4.ip; + } + + if (!inet_pton(type, ip, destination)) + return -1; + + return 0; + } + + int enet_address_set_hostname(ENetAddress* address, const char* name) { + struct addrinfo hints, *resultList = NULL, *result = NULL; + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; + + if (getaddrinfo(name, NULL, &hints, &resultList) != 0) + return -1; + + for (result = resultList; result != NULL; result = result->ai_next) { + if (result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { + if (result->ai_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)result->ai_addr; + + memset(address, 0, sizeof(address->ipv4.zeros)); + + address->ipv4.ffff = 0xFFFF; + address->ipv4.ip.s_addr = sin->sin_addr.s_addr; + + freeaddrinfo(resultList); + + return 0; + } else if (result->ai_family == AF_INET6) { + struct sockaddr_in6* sin = (struct sockaddr_in6*)result->ai_addr; + + address->ipv6 = sin->sin6_addr; + + freeaddrinfo(resultList); + + return 0; + } + } + } + + if (resultList != NULL) + freeaddrinfo(resultList); + + return enet_address_set_ip(address, name); + } + + int enet_address_get_ip(const ENetAddress* address, char* ip, size_t ipLength) { + if (address->ipv4.ffff == 0xFFFF && enet_array_is_zeroed(address->ipv4.zeros, sizeof(address->ipv4.zeros)) == 0) { + if (inet_ntop(AF_INET, &address->ipv4.ip, ip, ipLength) == NULL) + return -1; + } else if (inet_ntop(AF_INET6, &address->ipv6, ip, ipLength) == NULL) { + return -1; + } + + return 0; + } + + int enet_address_get_hostname(const ENetAddress* address, char* name, size_t nameLength) { + struct sockaddr_in6 sin; + int err; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + + err = getnameinfo((struct sockaddr*)&sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); + + if (!err) { + if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) + return -1; + + return 0; + } + + if (err != EAI_NONAME) + return -1; + + return enet_address_get_ip(address, name, nameLength); + } + +/* +======================================================================= + + Platform-specific (Unix) + +======================================================================= +*/ + + #ifndef _WIN32 + int enet_initialize(void) { + return 0; + } + + void enet_deinitialize(void) { } + + uint64_t enet_host_random_seed(void) { + struct timeval timeVal; + + gettimeofday(&timeVal, NULL); + + return (timeVal.tv_sec * 1000) ^ (timeVal.tv_usec / 1000); + } + + int enet_socket_bind(ENetSocket socket, const ENetAddress* address) { + struct sockaddr_in6 sin; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + + if (address != NULL) { + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + } else { + sin.sin6_port = 0; + sin.sin6_addr = ENET_HOST_ANY; + } + + return bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)); + } + + int enet_socket_get_address(ENetSocket socket, ENetAddress* address) { + struct sockaddr_storage ss; + socklen_t saLength = sizeof(ss); + + if (getsockname(socket, (struct sockaddr*)&ss, &saLength) == -1) + return -1; + + if (ss.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&ss; + + memset(address, 0, sizeof(address->ipv4.zeros)); + + address->ipv4.ffff = 0xFFFF; + address->ipv4.ip = sin->sin_addr; + address->port = ENET_NET_TO_HOST_16(sin->sin_port); + } else if (ss.ss_family == AF_INET6) { + struct sockaddr_in6* sin = (struct sockaddr_in6*)&ss; + + address->ipv6 = sin->sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin->sin6_port); + } + + return 0; + } + + int enet_socket_listen(ENetSocket socket, int backlog) { + return listen(socket, backlog < 0 ? SOMAXCONN : backlog); + } + + ENetSocket enet_socket_create(ENetSocketType type) { + int socketType = (type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM); + + #ifdef SOCK_CLOEXEC + socketType |= SOCK_CLOEXEC; + #endif + + return socket(PF_INET6, socketType, 0); + } + + int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { + int result = -1; + + switch (option) { + case ENET_SOCKOPT_NONBLOCK: + result = fcntl(socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl(socket, F_GETFL) & ~O_NONBLOCK)); + + break; + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_RCVTIMEO: { + struct timeval timeVal; + + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeVal, sizeof(struct timeval)); + + break; + } + + case ENET_SOCKOPT_SNDTIMEO: { + struct timeval timeVal; + + timeVal.tv_sec = value / 1000; + timeVal.tv_usec = (value % 1000) * 1000; + result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeVal, sizeof(struct timeval)); + + break; + } + + case ENET_SOCKOPT_NODELAY: + result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_IPV6_V6ONLY: + result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&value, sizeof(int)); + + break; + + default: + break; + } + + return result == -1 ? -1 : 0; + } + + int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int* value) { + int result = -1; + socklen_t len; + + switch (option) { + case ENET_SOCKOPT_ERROR: + len = sizeof(int); + result = getsockopt(socket, SOL_SOCKET, SO_ERROR, value, &len); + + break; + + default: + break; + } + + return result == -1 ? -1 : 0; + } + + int enet_socket_connect(ENetSocket socket, const ENetAddress* address) { + int result = -1; + struct sockaddr_in6 sin; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + + result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)); + + if (result == -1 && errno == EINPROGRESS) + return 0; + + return result; + } + + ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress* address) { + int result = -1; + struct sockaddr_in6 sin; + socklen_t sinLength = sizeof(struct sockaddr_in6); + + result = accept(socket, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); + + if (result == -1) + return ENET_SOCKET_NULL; + + if (address != NULL) { + address->ipv6 = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + } + + return result; + } + + int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { + return shutdown(socket, (int)how); + } + + void enet_socket_destroy(ENetSocket socket) { + if (socket != ENET_SOCKET_NULL) + close(socket); + } + + int enet_socket_send(ENetSocket socket, const ENetAddress* address, const ENetBuffer* buffers, size_t bufferCount) { + struct msghdr msgHdr; + struct sockaddr_in6 sin; + int sentLength; + + memset(&msgHdr, 0, sizeof(struct msghdr)); + + if (address != NULL) { + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + msgHdr.msg_name = &sin; + msgHdr.msg_namelen = sizeof(struct sockaddr_in6); + } + + msgHdr.msg_iov = (struct iovec*)buffers; + msgHdr.msg_iovlen = bufferCount; + sentLength = sendmsg(socket, &msgHdr, MSG_NOSIGNAL); + + if (sentLength == -1) { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + + return sentLength; + } + + int enet_socket_receive(ENetSocket socket, ENetAddress* address, ENetBuffer* buffers, size_t bufferCount) { + struct msghdr msgHdr; + struct sockaddr_in6 sin; + int recvLength; + + memset(&msgHdr, 0, sizeof(struct msghdr)); + + if (address != NULL) { + msgHdr.msg_name = &sin; + msgHdr.msg_namelen = sizeof(struct sockaddr_in6); + } + + msgHdr.msg_iov = (struct iovec*)buffers; + msgHdr.msg_iovlen = bufferCount; + recvLength = recvmsg(socket, &msgHdr, MSG_NOSIGNAL); + + if (recvLength == -1) { + if (errno == EWOULDBLOCK) + return 0; + + return -1; + } + + if (msgHdr.msg_flags & MSG_TRUNC) + return -2; + + if (address != NULL) { + address->ipv6 = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + } + + return recvLength; + } + + int enet_socket_set_select(ENetSocket maxSocket, ENetSocketSet* readSet, ENetSocketSet* writeSet, uint32_t timeout) { + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); + } + + int enet_socket_wait(ENetSocket socket, uint32_t* condition, uint64_t timeout) { + struct pollfd pollSocket; + int pollCount; + + pollSocket.fd = socket; + pollSocket.events = 0; + + if (*condition & ENET_SOCKET_WAIT_SEND) + pollSocket.events |= POLLOUT; + + if (*condition & ENET_SOCKET_WAIT_RECEIVE) + pollSocket.events |= POLLIN; + + pollCount = poll(&pollSocket, 1, timeout); + + if (pollCount < 0) { + if (errno == EINTR && *condition & ENET_SOCKET_WAIT_INTERRUPT) { + *condition = ENET_SOCKET_WAIT_INTERRUPT; + + return 0; + } + + return -1; + } + + *condition = ENET_SOCKET_WAIT_NONE; + + if (pollCount == 0) + return 0; + + if (pollSocket.revents & POLLOUT) + *condition |= ENET_SOCKET_WAIT_SEND; + + if (pollSocket.revents & POLLIN) + *condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; + } + #endif + +/* +======================================================================= + + Platform-specific (Windows) + +======================================================================= +*/ + + #ifdef _WIN32 + int enet_initialize(void) { + WORD versionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + + if (WSAStartup(versionRequested, &wsaData)) + return -1; + + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { + WSACleanup(); + + return -1; + } + + timeBeginPeriod(1); + + return 0; + } + + void enet_deinitialize(void) { + timeEndPeriod(1); + WSACleanup(); + } + + uint64_t enet_host_random_seed(void) { + return (uint64_t)timeGetTime(); + } + + int enet_socket_bind(ENetSocket socket, const ENetAddress* address) { + struct sockaddr_in6 sin; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + + if (address != NULL) { + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + } else { + sin.sin6_port = 0; + sin.sin6_addr = in6addr_any; + } + + return bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)) == SOCKET_ERROR ? -1 : 0; + } + + int enet_socket_get_address(ENetSocket socket, ENetAddress* address) { + struct sockaddr_storage ss; + int saLength = sizeof(ss); + + if (getsockname(socket, (struct sockaddr*)&ss, &saLength) == -1) + return -1; + + if (ss.ss_family == AF_INET) { + struct sockaddr_in* sin = (struct sockaddr_in*)&ss; + + memset(address, 0, sizeof(address->ipv4.zeros)); + + address->ipv4.ffff = 0xFFFF; + address->ipv4.ip = sin->sin_addr; + address->port = ENET_NET_TO_HOST_16(sin->sin_port); + } else if (ss.ss_family == AF_INET6) { + struct sockaddr_in6* sin = (struct sockaddr_in6*)&ss; + + address->ipv6 = sin->sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin->sin6_port); + } + + return 0; + } + + int enet_socket_listen(ENetSocket socket, int backlog) { + return listen(socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0; + } + + ENetSocket enet_socket_create(ENetSocketType type) { + return socket(PF_INET6, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); + } + + int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { + int result = SOCKET_ERROR; + + switch (option) { + case ENET_SOCKOPT_NONBLOCK: { + u_long nonBlocking = (u_long)value; + + result = ioctlsocket(socket, FIONBIO, &nonBlocking); + + break; + } + + case ENET_SOCKOPT_BROADCAST: + result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_REUSEADDR: + result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_RCVBUF: + result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_SNDBUF: + result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_RCVTIMEO: + result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_SNDTIMEO: + result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_NODELAY: + result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char*)&value, sizeof(int)); + + break; + + case ENET_SOCKOPT_IPV6_V6ONLY: + result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&value, sizeof(int)); + + break; + + default: + break; + } + + return result == SOCKET_ERROR ? -1 : 0; + } + + int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int* value) { + int result = SOCKET_ERROR, len; + + switch (option) { + case ENET_SOCKOPT_ERROR: + len = sizeof(int); + result = getsockopt(socket, SOL_SOCKET, SO_ERROR, (char*)value, &len); + + break; + + default: + break; + } + + return result == SOCKET_ERROR ? -1 : 0; + } + + int enet_socket_connect(ENetSocket socket, const ENetAddress* address) { + int result = -1; + struct sockaddr_in6 sin; + + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)); + + if (result == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) + return -1; + + return 0; + } + + ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress* address) { + SOCKET result; + struct sockaddr_in6 sin; + int sinLength = sizeof(struct sockaddr_in6); + + result = accept(socket, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); + + if (result == INVALID_SOCKET) + return ENET_SOCKET_NULL; + + if (address != NULL) { + address->ipv6 = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + } + + return result; + } + + int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { + return shutdown(socket, (int)how) == SOCKET_ERROR ? -1 : 0; + } + + void enet_socket_destroy(ENetSocket socket) { + if (socket != INVALID_SOCKET) + closesocket(socket); + } + + int enet_socket_send(ENetSocket socket, const ENetAddress* address, const ENetBuffer* buffers, size_t bufferCount) { + struct sockaddr_in6 sin; + DWORD sentLength = 0; + + if (address != NULL) { + memset(&sin, 0, sizeof(struct sockaddr_in6)); + + sin.sin6_family = AF_INET6; + sin.sin6_port = ENET_HOST_TO_NET_16(address->port); + sin.sin6_addr = address->ipv6; + } + + if (WSASendTo(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &sentLength, 0, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? sizeof(struct sockaddr_in6) : 0, NULL, NULL) == SOCKET_ERROR) + return (WSAGetLastError() == WSAEWOULDBLOCK) ? 0 : -1; + + return (int)sentLength; + } + + int enet_socket_receive(ENetSocket socket, ENetAddress* address, ENetBuffer* buffers, size_t bufferCount) { + INT sinLength = sizeof(struct sockaddr_in6); + DWORD flags = 0, recvLength = 0; + struct sockaddr_in6 sin; + + if (WSARecvFrom(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &recvLength, &flags, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL, NULL, NULL) == SOCKET_ERROR) { + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: + case WSAECONNRESET: + return 0; + } + + return -1; + } + + if (flags & MSG_PARTIAL) + return -2; + + if (address != NULL) { + address->ipv6 = sin.sin6_addr; + address->port = ENET_NET_TO_HOST_16(sin.sin6_port); + } + + return (int)recvLength; + } + + int enet_socket_set_select(ENetSocket maxSocket, ENetSocketSet* readSet, ENetSocketSet* writeSet, uint32_t timeout) { + struct timeval timeVal; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); + } + + int enet_socket_wait(ENetSocket socket, uint32_t* condition, uint64_t timeout) { + fd_set readSet, writeSet; + struct timeval timeVal; + int selectCount; + + timeVal.tv_sec = timeout / 1000; + timeVal.tv_usec = (timeout % 1000) * 1000; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + + if (*condition & ENET_SOCKET_WAIT_SEND) + FD_SET(socket, &writeSet); + + if (*condition & ENET_SOCKET_WAIT_RECEIVE) + FD_SET(socket, &readSet); + + selectCount = select(socket + 1, &readSet, &writeSet, NULL, &timeVal); + + if (selectCount < 0) + return -1; + + *condition = ENET_SOCKET_WAIT_NONE; + + if (selectCount == 0) + return 0; + + if (FD_ISSET(socket, &writeSet)) + *condition |= ENET_SOCKET_WAIT_SEND; + + if (FD_ISSET(socket, &readSet)) + *condition |= ENET_SOCKET_WAIT_RECEIVE; + + return 0; + } + #endif + +/* +======================================================================= + + Extended functionality + +======================================================================= +*/ + + void* enet_packet_get_data(const ENetPacket* packet) { + return (void*)packet->data; + } + + void* enet_packet_get_user_data(const ENetPacket* packet) { + return packet->userData; + } + + void enet_packet_set_user_data(ENetPacket* packet, void* userData) { + packet->userData = userData; + } + + int enet_packet_get_length(const ENetPacket* packet) { + return packet->dataLength; + } + + void enet_packet_set_free_callback(ENetPacket* packet, ENetPacketFreeCallback callback) { + packet->freeCallback = callback; + } + + int enet_packet_check_references(const ENetPacket* packet) { + return (int)packet->referenceCount; + } + + void enet_packet_dispose(ENetPacket* packet) { + if (packet->referenceCount == 0) + enet_packet_destroy(packet); + } + + uint32_t enet_host_get_peers_count(const ENetHost* host) { + return host->connectedPeers; + } + + uint32_t enet_host_get_packets_sent(const ENetHost* host) { + return host->totalSentPackets; + } + + uint32_t enet_host_get_packets_received(const ENetHost* host) { + return host->totalReceivedPackets; + } + + uint32_t enet_host_get_bytes_sent(const ENetHost* host) { + return host->totalSentData; + } + + uint32_t enet_host_get_bytes_received(const ENetHost* host) { + return host->totalReceivedData; + } + + void enet_host_set_max_duplicate_peers(ENetHost* host, uint16_t number) { + if (number < 1) + number = 1; + + if (number > ENET_PROTOCOL_MAXIMUM_PEER_ID) + number = ENET_PROTOCOL_MAXIMUM_PEER_ID; + + host->duplicatePeers = number; + } + + void enet_host_set_intercept_callback(ENetHost* host, ENetInterceptCallback callback) { + host->interceptCallback = callback; + } + + void enet_host_set_checksum_callback(ENetHost* host, ENetChecksumCallback callback) { + host->checksumCallback = callback; + } + + uint32_t enet_peer_get_id(const ENetPeer* peer) { + return peer->incomingPeerID; + } + + int enet_peer_get_ip(const ENetPeer* peer, char* ip, size_t ipLength) { + return enet_address_get_ip(&peer->address, ip, ipLength); + } + + uint16_t enet_peer_get_port(const ENetPeer* peer) { + return peer->address.port; + } + + uint32_t enet_peer_get_mtu(const ENetPeer* peer) { + return peer->mtu; + } + + ENetPeerState enet_peer_get_state(const ENetPeer* peer) { + return peer->state; + } + + uint32_t enet_peer_get_rtt(const ENetPeer* peer) { + return peer->roundTripTime; + } + + uint32_t enet_peer_get_last_rtt(const ENetPeer* peer) { + return peer->lastRoundTripTime; + } + + uint32_t enet_peer_get_lastsendtime(const ENetPeer* peer) { + return peer->lastSendTime; + } + + uint32_t enet_peer_get_lastreceivetime(const ENetPeer* peer) { + return peer->lastReceiveTime; + } + + uint64_t enet_peer_get_packets_sent(const ENetPeer* peer) { + return peer->totalPacketsSent; + } + + uint64_t enet_peer_get_packets_lost(const ENetPeer* peer) { + return peer->totalPacketsLost; + } + + float enet_peer_get_packets_throttle(const ENetPeer* peer) { + return peer->packetThrottle / (float)ENET_PEER_PACKET_THROTTLE_SCALE * 100.0f; + } + + uint64_t enet_peer_get_bytes_sent(const ENetPeer* peer) { + return peer->totalDataSent; + } + + uint64_t enet_peer_get_bytes_received(const ENetPeer* peer) { + return peer->totalDataReceived; + } + + void* enet_peer_get_data(const ENetPeer* peer) { + return (void*)peer->data; + } + + void enet_peer_set_data(ENetPeer* peer, const void* data) { + peer->data = (uint32_t*)data; + } + + #ifdef _MSC_VER + #pragma warning(pop) + #endif + +#endif // ENET_IMPLEMENTATION + +#endif // ENET_H diff --git a/port/include/input.h b/port/include/input.h index 2ddaab94a..1921ed1ed 100644 --- a/port/include/input.h +++ b/port/include/input.h @@ -19,9 +19,24 @@ enum virtkey { /* same order as SDL scancodes */ VK_KEYBOARD_BEGIN = 0, + VK_A = 4, + VK_Z = 29, + VK_1 = 30, + VK_9 = 38, + VK_0 = 39, VK_RETURN = 40, VK_ESCAPE = 41, + VK_BACKSPACE = 42, + VK_SPACE = 44, + VK_MINUS = 45, + VK_LEFTBRACKET = 47, + VK_RIGHTBRACKET = 48, + VK_SEMICOLON = 51, + VK_COMMA = 54, + VK_PERIOD = 55, VK_DELETE = 76, + VK_LSHIFT = 225, + VK_RSHIFT = 229, /* same order as SDL mouse buttons */ VK_MOUSE_BEGIN = 512, diff --git a/port/include/net/net.h b/port/include/net/net.h new file mode 100644 index 000000000..5fda28c6f --- /dev/null +++ b/port/include/net/net.h @@ -0,0 +1,123 @@ +#ifndef _IN_NET_H +#define _IN_NET_H + +#include "types.h" +#include "constants.h" +#include "net/netbuf.h" + +#define NET_PROTOCOL_VER 1 + +#define NET_MAX_CLIENTS MAX_PLAYERS +#define NET_MAX_NAME MAX_PLAYERNAME + +#define NET_BUFSIZE 1440 + +#define NET_DEFAULT_PORT 27100 + +#define NETCHAN_DEFAULT 0 +#define NETCHAN_COUNT 1 + +#define DISCONNECT_UNKNOWN 0 +#define DISCONNECT_SHUTDOWN 1 +#define DISCONNECT_VERSION 2 +#define DISCONNECT_KICKED 3 +#define DISCONNECT_BANNED 4 +#define DISCONNECT_TIMEOUT 5 +#define DISCONNECT_FULL 6 + +#define CLSTATE_DISCONNECTED 0 +#define CLSTATE_CONNECTING 1 +#define CLSTATE_AUTH 2 +#define CLSTATE_LOBBY 3 +#define CLSTATE_GAME 4 + +#define UCMD_FIRE (1 << 0) +#define UCMD_ACTIVATE (1 << 1) +#define UCMD_RELOAD (1 << 2) +#define UCMD_FIREMODE (1 << 3) +#define UCMD_AIMMODE (1 << 4) +#define UCMD_DUCK (1 << 5) +#define UCMD_SQUAT (1 << 6) +#define UCMD_ZOOMIN (1 << 7) +#define UCMD_SELECT (1 << 8) +#define UCMD_CHAT (1 << 19) +#define UCMD_FL_FORCEPOS (1 << 29) +#define UCMD_FL_FORCEANGLE (1 << 30) +#define UCMD_FL_FORCEGROUND (1 << 31) +#define UCMD_FL_FORCEMASK (UCMD_FL_FORCEPOS | UCMD_FL_FORCEANGLE | UCMD_FL_FORCEGROUND) + +struct netplayermove { + u32 tick; // g_NetTIck value when this struct was written; if 0, this struct is invalid + u32 ucmd; // player commands (UCMD_) + f32 leanofs; // analog lean value (-1 .. 1; equal to player->swaytarget / 75.f) + f32 crouchofs; // analog crouch value (-90 for SQUAT, 0 for STAND; player->crouchofs) + f32 movespeed[2]; // move inputs, [0] is forward, [1] is sideways; used mostly for animation + f32 lookspeed[2]; // look inputs, [0] is theta, [1] is verta + f32 angles[2]; // view angles, [0] is theta, [1] is verta + struct coord pos; // player position at g_NetTick == tick + s16 rooms[2]; // cam_room, floorroom +}; + +struct netclient { + struct _ENetPeer *peer; + u32 id; // remote client number, server is always 0, even on clients + u32 state; // CLSTATE_ + u32 flags; // CLFLAG_ + + struct { + char name[NET_MAX_NAME]; + u8 headnum; + u8 bodynum; + } settings; + + struct mpplayerconfig *config; + struct player *player; + u8 playernum; + + struct netplayermove outmove[2]; // last 2 outgoing player inputs, newest one first + struct netplayermove inmove[2]; // last 2 incoming player inputs, newest one first + u32 inmovetick; // last inmove tick which was applied to the player + u32 outmoveack; // last acked outmove tick + u32 forcetick; // tick on which the client's position was forced, or 0 if not forcing + u32 lerpticks; // how many ticks we've been lerping the position + + struct netbuf out; // outbound messages are written here, except broadcasts + struct netbuf in; // incoming packets are fed here + + u8 out_data[NET_BUFSIZE]; // buffer for out +}; + +extern s32 g_NetMode; + +// net frame, ticks at 60 fps, starts at 0 when the server is started +extern u32 g_NetTick; + +extern u32 g_NetInterpTicks; +extern u32 g_NetExterpTicks; + +extern s32 g_NetMaxClients; +extern s32 g_NetNumClients; +extern struct netclient g_NetClients[NET_MAX_CLIENTS + 1]; // last is an extra temporary client +extern struct netclient *g_NetLocalClient; + +extern struct netbuf g_NetMsg; +extern struct netbuf g_NetMsgRel; + +const char *netFormatClientAddr(const struct netclient *cl); + +void netInit(void); +s32 netDisconnect(void); +void netStartFrame(void); +void netEndFrame(void); + +s32 netStartServer(u16 port, s32 maxclients); +s32 netStartClient(const char *addr); + +u32 netSend(struct netclient *dstcl, struct netbuf *buf, const s32 reliable); + +void netServerStageStart(void); +void netServerStageEnd(void); + +void netPlayersAllocate(void); + +#endif // _IN_NET_H diff --git a/port/include/net/netbuf.h b/port/include/net/netbuf.h new file mode 100644 index 000000000..01dfd523c --- /dev/null +++ b/port/include/net/netbuf.h @@ -0,0 +1,47 @@ +#ifndef _IN_NETBUF_H +#define _IN_NETBUF_H + +#include "types.h" + +struct netbuf { + u8 *data; + u32 size; + u32 rp; + u32 wp; + u32 error; +}; + +void netbufStartRead(struct netbuf *buf); +void netbufStartReadData(struct netbuf *buf, const void *data, u32 size); +s32 netbufReadLeft(const struct netbuf *buf); +u32 netbufReadData(struct netbuf *buf, void *out, u32 num); +u8 netbufReadU8(struct netbuf *buf); +u16 netbufReadU16(struct netbuf *buf); +u32 netbufReadU32(struct netbuf *buf); +u64 netbufReadU64(struct netbuf *buf); +s8 netbufReadS8(struct netbuf *buf); +s16 netbufReadS16(struct netbuf *buf); +s32 netbufReadS32(struct netbuf *buf); +s64 netbufReadS64(struct netbuf *buf); +f32 netbufReadF32(struct netbuf *buf); +char *netbufReadStr(struct netbuf *buf); +u32 netbufReadCoord(struct netbuf *buf, struct coord *out); + +void netbufStartWrite(struct netbuf *buf); +s32 netbufWriteLeft(const struct netbuf *buf); +u32 netbufWriteData(struct netbuf *buf, const void *data, u32 num); +u32 netbufWriteU8(struct netbuf *buf, const u8 v); +u32 netbufWriteU16(struct netbuf *buf, const u16 v); +u32 netbufWriteU32(struct netbuf *buf, const u32 v); +u32 netbufWriteU64(struct netbuf *buf, const u64 v); +u32 netbufWriteS8(struct netbuf *buf, const s8 v); +u32 netbufWriteS16(struct netbuf *buf, const s16 v); +u32 netbufWriteS32(struct netbuf *buf, const s32 v); +u32 netbufWriteS64(struct netbuf *buf, const s64 v); +u32 netbufWriteF32(struct netbuf *buf, const f32 v); +u32 netbufWriteStr(struct netbuf *buf, const char *v); +u32 netbufWriteCoord(struct netbuf *buf, const struct coord *v); + +void netbufReset(struct netbuf *buf); + +#endif diff --git a/port/include/net/netenet.h b/port/include/net/netenet.h new file mode 100644 index 000000000..e0af16f1d --- /dev/null +++ b/port/include/net/netenet.h @@ -0,0 +1,10 @@ +#ifndef _IN_NETENET_H +#define _IN_NETENET_H + +#define ENET_NO_PRAGMA_LINK 1 +#include "external/enet.h" +#undef bool +#undef near +#undef far + +#endif diff --git a/port/include/net/netmsg.h b/port/include/net/netmsg.h new file mode 100644 index 000000000..a8fb41fc9 --- /dev/null +++ b/port/include/net/netmsg.h @@ -0,0 +1,36 @@ +#ifndef _IN_NETMSG_H +#define _IN_NETMSG_H + +#include +#include "net/net.h" +#include "net/netbuf.h" + +#define SVC_BAD 0 // trash +#define SVC_NOP 1 // does nothing +#define SVC_AUTH 2 // auth response, sent in response to CLC_AUTH +#define SVC_CHAT 3 // chat message +#define SVC_STAGE_START 4 // start level +#define SVC_STAGE_END 5 // end level +#define SVC_PLAYER_MOVE 6 // player movement + +#define CLC_BAD 0 // trash +#define CLC_NOP 1 // does nothing +#define CLC_AUTH 2 // auth request, sent immediately after connecting +#define CLC_CHAT 3 // chat message +#define CLC_MOVE 4 // player input + +u32 netmsgClcAuthWrite(struct netbuf *dst); +u32 netmsgClcAuthRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgClcMoveWrite(struct netbuf *dst); +u32 netmsgClcMoveRead(struct netbuf *src, struct netclient *srccl); + +u32 netmsgSvcAuthWrite(struct netbuf *dst, struct netclient *authcl); +u32 netmsgSvcAuthRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcStageStartWrite(struct netbuf *dst); +u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcStageEndWrite(struct netbuf *dst); +u32 netmsgSvcStageEndRead(struct netbuf *src, struct netclient *srccl); +u32 netmsgSvcPlayerMoveWrite(struct netbuf *dst, struct netclient *movecl); +u32 netmsgSvcPlayerMoveRead(struct netbuf *src, struct netclient *srccl); + +#endif diff --git a/port/src/main.c b/port/src/main.c index 38d4da332..b13ddf0c1 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -17,6 +17,7 @@ #include "mod.h" #include "system.h" #include "utils.h" +#include "net/net.h" u32 g_OsMemSize = 0; s32 g_OsMemSizeMb = 16; @@ -104,6 +105,7 @@ int main(int argc, const char **argv) inputInit(); audioInit(); romdataInit(); + netInit(); gameInit(); diff --git a/port/src/mixer.c b/port/src/mixer.c index 1a42ae2ae..e14d095f2 100644 --- a/port/src/mixer.c +++ b/port/src/mixer.c @@ -6,7 +6,6 @@ #include "mixer.h" #include "platform.h" -#define MINIMP3_IMPLEMENTATION #include "external/minimp3.h" #ifdef __SSE4_1__ diff --git a/port/src/net/net.c b/port/src/net/net.c new file mode 100644 index 000000000..f94b728c3 --- /dev/null +++ b/port/src/net/net.c @@ -0,0 +1,622 @@ +#include +#include +#include +#include +#include "platform.h" +#include "net/netenet.h" +#include "net/net.h" +#include "net/netbuf.h" +#include "net/netmsg.h" +#include "types.h" +#include "constants.h" +#include "data.h" +#include "bss.h" +#include "game/chraction.h" +#include "game/bot.h" +#include "game/botact.h" +#include "config.h" +#include "system.h" +#include "utils.h" + +s32 g_NetMode = NETMODE_NONE; + +u32 g_NetTick = 0; + +u32 g_NetInterpTicks = 6; +u32 g_NetExterpTicks = 6; + +s32 g_NetSimPacketLoss = 20; + +s32 g_NetMaxClients = NET_MAX_CLIENTS; +s32 g_NetNumClients = 0; +struct netclient g_NetClients[NET_MAX_CLIENTS + 1]; // last is an extra temporary client +struct netclient *g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS]; + +static u8 g_NetMsgBuf[NET_BUFSIZE]; +struct netbuf g_NetMsg = { .data = g_NetMsgBuf, .size = sizeof(g_NetMsgBuf) }; + +static u8 g_NetMsgRelBuf[NET_BUFSIZE]; +struct netbuf g_NetMsgRel = { .data = g_NetMsgRelBuf, .size = sizeof(g_NetMsgRelBuf) }; + +static s32 g_NetInit = false; +static ENetHost *g_NetHost; +static ENetAddress g_NetLocalAddr; +static ENetAddress g_NetRemoteAddr; + +static s32 netParseAddr(ENetAddress *out, const char *str) +{ + char tmp[256] = { 0 }; + + if (!str || !str[0]) { + return false; + } + + strncpy(tmp, str, sizeof(tmp) - 1); + + char *host = tmp; + char *port = NULL; + + if (tmp[0] == '[') { + // ipv6 with port: [ADDR]:PORT + host = tmp + 1; // skip [ + port = strrchr(host, ']'); // find ] + if (port) { + if (port[1] != ':' || !isdigit(port[2])) { + return false; + } + *port = '\0'; // terminate ip + port += 2; // skip ]: + } + } else if (strchr(host, '.')) { + // ipv4 or hostname + port = strrchr(host, ':'); + if (port) { + if (!isdigit(port[1])) { + return false; + } + *port = '\0'; // terminate address + ++port; // skip : + } + } + + if (!host[0]) { + return false; + } + + const s32 portval = port ? atoi(port) : NET_DEFAULT_PORT; + if (portval < 0 || portval > 0xFFFF) { + return false; + } + + memset(out, 0, sizeof(*out)); + out->port = portval; + + if (isdigit(host[0]) || strchr(host, ':')) { + // we stripped off the :PORT at this point and domain names can't start with a digit + // so this must be an IP address + return (enet_address_set_ip(out, host) == 0); + } + + // must be a domain name; do a lookup + return (enet_address_set_hostname(out, host) == 0); +} + +static const char *netFormatAddr(const ENetAddress *addr) +{ + static char str[256]; + char tmp[256]; + if (addr && enet_address_get_ip(addr, tmp, sizeof(tmp) - 1) == 0) { + if (tmp[0]) { + if (strchr(tmp, ':')) { + // ipv6 + snprintf(str, sizeof(str) - 1, "[%s]:%u", tmp, addr->port); + } else { + // ipv4 + snprintf(str, sizeof(str) - 1, "%s:%u", tmp, addr->port); + } + return str; + } + } + return NULL; +} + +static inline const char *netFormatPeerAddr(const ENetPeer *peer) +{ + return netFormatAddr(&peer->address); +} + +const char *netFormatClientAddr(const struct netclient *cl) +{ + return cl->peer ? netFormatPeerAddr(cl->peer) : ""; +} + +static inline void netClientReset(struct netclient *cl) +{ + memset(cl, 0, sizeof(*cl)); + cl->out.data = cl->out_data; + cl->out.size = sizeof(cl->out_data); + cl->id = cl - g_NetClients; +} + +static inline void netClientResetAll(void) +{ + g_NetMaxClients = NET_MAX_CLIENTS; + g_NetNumClients = 1; // always at least one client, which is us + for (u32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) { + netClientReset(&g_NetClients[i]); + } +} + +static inline void netClientRecordMove(struct netclient *cl, const struct player *pl) +{ + // make space in the move stack + memmove(cl->outmove + 1, cl->outmove, sizeof(cl->outmove) - sizeof(*cl->outmove)); + + struct netplayermove *move = &cl->outmove[0]; + + move->tick = g_NetTick; + move->crouchofs = pl->crouchoffset; + move->leanofs = pl->swaytarget / 75.f; + move->movespeed[0] = pl->speedforwards; + move->movespeed[1] = pl->speedsideways; + move->lookspeed[0] = pl->speedtheta; + move->lookspeed[1] = pl->speedverta; + move->angles[0] = pl->vv_theta; + move->angles[1] = pl->vv_verta; + move->pos = (pl->prop) ? pl->prop->pos : pl->cam_pos; + move->rooms[0] = pl->cam_room; + move->rooms[1] = pl->floorroom; + move->ucmd = pl->ucmd; + + if (pl->crouchpos == CROUCHPOS_DUCK) { + move->ucmd |= UCMD_DUCK; + } else if (pl->crouchpos == CROUCHPOS_SQUAT) { + move->ucmd |= UCMD_SQUAT; + } + + if (pl->bondactivateorreload & JO_ACTION_ACTIVATE) { + move->ucmd |= UCMD_ACTIVATE; + } + if (pl->bondactivateorreload & JO_ACTION_RELOAD) { + move->ucmd |= UCMD_RELOAD; + } + + if (cl != g_NetLocalClient && !cl->forcetick && (move->ucmd & UCMD_FL_FORCEMASK)) { + cl->forcetick = move->tick; + sysLogPrintf(LOG_NOTE, "NET: forcing client %u to move at tick %u", cl->id, cl->forcetick); + } +} + +void netInit(void) +{ + if (enet_initialize() < 0) { + sysLogPrintf(LOG_ERROR, "NET: could not init ENet, disabling networking"); + return; + } + + g_NetInit = true; +} + +s32 netStartServer(u16 port, s32 maxclients) +{ + if (g_NetMode || !g_NetInit) { + return -1; + } + + memset(&g_NetLocalAddr, 0, sizeof(g_NetLocalAddr)); + g_NetLocalAddr.port = port; + g_NetHost = enet_host_create(&g_NetLocalAddr, maxclients, NETCHAN_COUNT, 0, 0, 0); + if (!g_NetHost) { + sysLogPrintf(LOG_ERROR, "NET: could not create ENet host"); + return -2; + } + + netClientResetAll(); + + // the server's local client is client 0 + g_NetLocalClient = &g_NetClients[0]; + g_NetLocalClient->state = CLSTATE_LOBBY; // local client doesn't need auth + g_NetLocalClient->settings.bodynum = g_PlayerConfigsArray[0].base.mpbodynum; + g_NetLocalClient->settings.headnum = g_PlayerConfigsArray[0].base.mpheadnum; + memcpy(g_NetLocalClient->settings.name, g_PlayerConfigsArray[0].base.name, sizeof(g_NetLocalClient->settings.name)); + + g_NetMode = NETMODE_SERVER; + + g_NetTick = 0; + + sysLogPrintf(LOG_NOTE, "NET: created server on port %u", port); + + return 0; +} + +void netServerStageStart(void) +{ + if (g_NetMode != NETMODE_SERVER) { + return; + } + + g_NetLocalClient->state = CLSTATE_GAME; + + netbufStartWrite(&g_NetMsgRel); + netmsgSvcStageStartWrite(&g_NetMsgRel); + netSend(NULL, &g_NetMsgRel, true); +} + +void netServerStageEnd(void) +{ + if (g_NetMode != NETMODE_SERVER) { + return; + } + + g_NetLocalClient->state = CLSTATE_LOBBY; + netbufStartWrite(&g_NetMsgRel); + netmsgSvcStageEndWrite(&g_NetMsgRel); + netSend(NULL, &g_NetMsgRel, true); +} + +s32 netStartClient(const char *addr) +{ + if (g_NetMode || !g_NetInit) { + return -1; + } + + if (!netParseAddr(&g_NetRemoteAddr, addr)) { + sysLogPrintf(LOG_ERROR, "NET: `%s` is not a valid address", addr); + return -2; + } + + memset(&g_NetLocalAddr, 0, sizeof(g_NetLocalAddr)); + g_NetHost = enet_host_create(&g_NetLocalAddr, 1, NETCHAN_COUNT, 0, 0, 0); + if (!g_NetHost) { + sysLogPrintf(LOG_ERROR, "NET: could not create ENet host"); + return -3; + } + + // we'll use the whole array to store what we know of other clients + netClientResetAll(); + + // for now use last client struct + g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS]; + + sysLogPrintf(LOG_NOTE, "NET: connecting to %s...", addr); + + g_NetLocalClient->peer = enet_host_connect(g_NetHost, &g_NetRemoteAddr, NETCHAN_COUNT, NET_PROTOCOL_VER); + if (!g_NetLocalClient->peer) { + sysLogPrintf(LOG_WARNING, "NET: could not connect to %s", addr); + enet_host_destroy(g_NetHost); + g_NetHost = NULL; + return -4; + } + + g_NetLocalClient->state = CLSTATE_CONNECTING; + g_NetLocalClient->settings.bodynum = g_PlayerConfigsArray[0].base.mpbodynum; + g_NetLocalClient->settings.headnum = g_PlayerConfigsArray[0].base.mpheadnum; + memcpy(g_NetLocalClient->settings.name, g_PlayerConfigsArray[0].base.name, sizeof(g_NetLocalClient->settings.name)); + + g_NetMode = NETMODE_CLIENT; + + g_NetTick = 0; + + sysLogPrintf(LOG_NOTE, "NET: waiting for response from %s...", addr); + + return 0; +} + +s32 netDisconnect(void) +{ + if (!g_NetMode) { + return -1; + } + + for (s32 i = 0; i < NET_MAX_CLIENTS + 1; ++i) { + if (g_NetClients[i].peer) { + enet_peer_disconnect_now(g_NetClients[i].peer, DISCONNECT_SHUTDOWN); + } + netClientReset(&g_NetClients[i]); + } + + g_NetLocalClient = &g_NetClients[NET_MAX_CLIENTS]; + + // flush pending packets + enet_host_flush(g_NetHost); + + // service for a bit just to ensure disconnect gets to peer(s) + enet_host_service(g_NetHost, NULL, 10); + + enet_host_destroy(g_NetHost); + + g_NetHost = NULL; + g_NetMode = NETMODE_NONE; + + sysLogPrintf(LOG_NOTE, "NET: disconnected"); + + return 0; +} + +static void netServerEvConnect(ENetPeer *peer, const u32 data) +{ + const char *addrstr = netFormatPeerAddr(peer); + + sysLogPrintf(LOG_NOTE, "NET: connection attempt from %s", addrstr); + + if (data != NET_PROTOCOL_VER) { + sysLogPrintf(LOG_NOTE, "NET: %s rejected: protocol mismatch", addrstr); + enet_peer_disconnect(peer, DISCONNECT_VERSION); + return; + } + + struct netclient *cl = NULL; + + // id 0 is the local client + for (s32 i = 1; i < g_NetMaxClients; ++i) { + if (!g_NetClients[i].state) { + cl = &g_NetClients[i]; + break; + } + } + + if (!cl) { + sysLogPrintf(LOG_NOTE, "NET: %s rejected: server is full", addrstr); + enet_peer_disconnect(peer, DISCONNECT_FULL); + return; + } + + netClientReset(cl); + cl->state = CLSTATE_AUTH; // skip CLSTATE_CONNECTING, since we already know it connected + cl->peer = peer; + enet_peer_set_data(peer, cl); + + ++g_NetNumClients; +} + +static void netServerEvDisconnect(struct netclient *cl) +{ + sysLogPrintf(LOG_NOTE, "NET: disconnect event from %s", netFormatClientAddr(cl)); + + if (cl->peer) { + enet_peer_reset(cl->peer); + } + + netClientReset(cl); + + --g_NetNumClients; +} + +static void netServerEvReceive(struct netclient *cl) +{ + u32 rc = 0; + u8 msgid = 0; + + while (!rc && netbufReadLeft(&cl->in) > 0) { + msgid = netbufReadU8(&cl->in); + switch (msgid) { + case CLC_NOP: rc = 0; break; + case CLC_AUTH: rc = netmsgClcAuthRead(&cl->in, cl); break; + case CLC_CHAT: rc = 0; break; + case CLC_MOVE: rc = netmsgClcMoveRead(&cl->in, cl); break; + default: + rc = 1; + break; + } + } + + if (rc) { + sysLogPrintf(LOG_WARNING, "NET: malformed or unknown message 0x%02x from client %s", msgid, netFormatClientAddr(cl)); + } +} + +static void netClientEvConnect(const u32 data) +{ + sysLogPrintf(LOG_NOTE, "NET: connected to server, sending CLC_AUTH"); + + g_NetLocalClient->state = CLSTATE_AUTH; + + // send auth request + netbufStartWrite(&g_NetMsgRel); + netmsgClcAuthWrite(&g_NetMsgRel); + netSend(g_NetLocalClient, &g_NetMsgRel, true); +} + +static void netClientEvDisconnect(const u32 reason) +{ + sysLogPrintf(LOG_NOTE, "NET: kicked from server, reason: %u", reason); + netDisconnect(); +} + +static void netClientEvReceive(struct netclient *cl) +{ + u32 rc = 0; + u8 msgid = 0; + + while (!rc && netbufReadLeft(&cl->in) > 0) { + msgid = netbufReadU8(&cl->in); + switch (msgid) { + case SVC_NOP: rc = 0; break; + case SVC_AUTH: rc = netmsgSvcAuthRead(&cl->in, cl); break; + case SVC_CHAT: rc = 0; break; + case SVC_STAGE_START: rc = netmsgSvcStageStartRead(&cl->in, cl); break; + case SVC_STAGE_END: rc = netmsgSvcStageEndRead(&cl->in, cl); break; + case SVC_PLAYER_MOVE: rc = netmsgSvcPlayerMoveRead(&cl->in, cl); break; + default: + rc = 1; + break; + } + } + + if (rc) { + sysLogPrintf(LOG_WARNING, "NET: malformed or unknown message 0x%02x from server", msgid); + } +} + +void netStartFrame(void) +{ + if (!g_NetMode) { + return; + } + + ++g_NetTick; + + const bool isClient = (g_NetMode == NETMODE_CLIENT); + s32 polled = false; + ENetEvent ev = { .type = ENET_EVENT_TYPE_NONE }; + while (!polled) { + if (enet_host_check_events(g_NetHost, &ev) <= 0) { + if (enet_host_service(g_NetHost, &ev, 1) <= 0) { + break; + } + polled = true; + } + + switch (ev.type) { + case ENET_EVENT_TYPE_CONNECT: + if (isClient) { + netClientEvConnect(ev.data); + } else if (ev.peer) { + netServerEvConnect(ev.peer, ev.data); + } + break; + case ENET_EVENT_TYPE_DISCONNECT: + case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: + if (isClient) { + netClientEvDisconnect(ev.type == ENET_EVENT_TYPE_DISCONNECT_TIMEOUT ? DISCONNECT_TIMEOUT : ev.data); + } else if (ev.peer) { + struct netclient *cl = enet_peer_get_data(ev.peer); + if (cl) { + netServerEvDisconnect(cl); + } else { + sysLogPrintf(LOG_WARNING, "NET: disconnect from %s without attached client", netFormatPeerAddr(ev.peer)); + } + } + break; + case ENET_EVENT_TYPE_RECEIVE: + if (ev.peer) { + struct netclient *cl = (g_NetMode == NETMODE_CLIENT) ? g_NetLocalClient : enet_peer_get_data(ev.peer); + if (cl && cl->state) { + if (ev.packet->data && ev.packet->dataLength) { + netbufStartReadData(&cl->in, ev.packet->data, ev.packet->dataLength); + if (isClient) { + netClientEvReceive(cl); + } else { + netServerEvReceive(cl); + } + netbufReset(&cl->in); + } + } else if (!isClient) { + sysLogPrintf(LOG_WARNING, "NET: receive from %s without attached client", netFormatPeerAddr(ev.peer)); + } + } + enet_packet_dispose(ev.packet); + break; + default: + break; + } + } + + netbufStartWrite(&g_NetMsg); + netbufStartWrite(&g_NetMsgRel); +} + +void netEndFrame(void) +{ + if (!g_NetMode) { + return; + } + + if (g_NetLocalClient->state == CLSTATE_GAME && g_NetLocalClient->player && g_NetLocalClient->player->prop) { + if (g_NetMode == NETMODE_CLIENT) { + netClientRecordMove(g_NetLocalClient, g_NetLocalClient->player); + netmsgClcMoveWrite(&g_NetMsg); + } else { + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *cl = &g_NetClients[i]; + if (cl->state >= CLSTATE_GAME && cl->player) { + netClientRecordMove(cl, cl->player); + netmsgSvcPlayerMoveWrite(cl->forcetick ? &g_NetMsgRel : &g_NetMsg, cl); + } + } + } + } + + if (g_NetMsgRel.wp) { + if (g_NetMsgRel.error) { + sysLogPrintf(LOG_WARNING, "NET: reliable out buffer overflow"); + } + netSend(NULL, &g_NetMsgRel, true); + } + + if (g_NetMsg.wp) { + if (g_NetMsg.error) { + sysLogPrintf(LOG_WARNING, "NET: unreliable out buffer overflow"); + } + netSend(NULL, &g_NetMsg, false); + } + + enet_host_flush(g_NetHost); +} + +u32 netSend(struct netclient *dstcl, struct netbuf *buf, const s32 reliable) +{ + if (g_NetMode == NETMODE_CLIENT) { + dstcl = g_NetLocalClient; + } + + if (buf == NULL) { + if (dstcl) { + buf = &dstcl->out; + } else { + buf = reliable ? &g_NetMsgRel : &g_NetMsg; + } + } + + if (reliable || !g_NetSimPacketLoss || (rand() % g_NetSimPacketLoss) == 0) { + const u32 flags = (reliable ? ENET_PACKET_FLAG_RELIABLE : 0); + ENetPacket *p = enet_packet_create(buf->data, buf->wp, flags); + if (!p) { + sysLogPrintf(LOG_ERROR, "NET: could not alloc %u bytes for packet", buf->wp); + return 0; + } + + if (dstcl == NULL) { + enet_host_broadcast(g_NetHost, NETCHAN_DEFAULT, p); + } else { + enet_peer_send(dstcl->peer, NETCHAN_DEFAULT, p); + } + } + + const u32 ret = buf->wp; + + netbufStartWrite(buf); + + return ret; +} + +void netPlayersAllocate(void) +{ + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *cl = &g_NetClients[i]; + if (cl->state < CLSTATE_LOBBY) { + continue; + } + + if (cl == g_NetLocalClient) { + // we always put the local player at index 0, even client-side + cl->playernum = 0; + } else { + // which means that clientside we have to put the server's player into our slot + cl->playernum = (g_NetMode == NETMODE_CLIENT && i == 0) ? g_NetLocalClient->id : cl->id; + // disable controls for the remote pawns and set their settings + // TODO: backup the player configs or something + struct mpplayerconfig *cfg = &g_PlayerConfigsArray[cl->playernum]; + cfg->controlmode = CONTROLMODE_NA; + cfg->base.mpbodynum = cl->settings.bodynum; + cfg->base.mpheadnum = cl->settings.headnum; + memcpy(cfg->base.name, cl->settings.name, MAX_PLAYERNAME); + } + + cl->player = g_Vars.players[cl->playernum]; + cl->config = &g_PlayerConfigsArray[cl->playernum]; + cl->config->client = cl; + cl->player->client = cl; + cl->player->isremote = (cl != g_NetLocalClient); + } +} diff --git a/port/src/net/netbuf.c b/port/src/net/netbuf.c new file mode 100644 index 000000000..de9836973 --- /dev/null +++ b/port/src/net/netbuf.c @@ -0,0 +1,270 @@ +#include +#include "types.h" +#include "platform.h" +#include "system.h" +#include "net/netbuf.h" + +/* read */ + +void netbufStartRead(struct netbuf *buf) +{ + buf->error = 0; + buf->wp = buf->size; + buf->rp = 0; +} + +void netbufStartReadData(struct netbuf *buf, const void *data, u32 size) +{ + buf->size = size; + buf->data = (void *)data; + netbufStartRead(buf); +} + +s32 netbufReadLeft(const struct netbuf *buf) +{ + return buf->wp - buf->rp; +} + +static inline u32 netbufCanRead(struct netbuf *buf, const u32 num) +{ + if (buf->error || buf->rp + num > buf->wp) { + sysLogPrintf(LOG_ERROR, "NET: could not read %u bytes", num); + __builtin_trap(); + buf->error = 1; + return false; + } + return true; +} + +u32 netbufReadData(struct netbuf *buf, void *out, u32 num) +{ + if (netbufCanRead(buf, num)) { + memcpy(out, buf->data + buf->rp, num); + buf->rp += num; + return num; + } + return 0; +} + +u8 netbufReadU8(struct netbuf *buf) +{ + if (netbufCanRead(buf, 1)) { + return buf->data[buf->rp++]; + } + return 0; +} + +u16 netbufReadU16(struct netbuf *buf) +{ + if (netbufCanRead(buf, 2)) { + const u16 ret = *(u16 *)&buf->data[buf->rp]; + buf->rp += sizeof(ret); + return PD_LE16(ret); + } + return 0; +} + +u32 netbufReadU32(struct netbuf *buf) +{ + if (netbufCanRead(buf, 4)) { + const u32 ret = *(u32 *)&buf->data[buf->rp]; + buf->rp += sizeof(ret); + return PD_LE32(ret); + } + return 0; +} + +u64 netbufReadU64(struct netbuf *buf) +{ + if (netbufCanRead(buf, 8)) { + const u64 ret = *(u64 *)&buf->data[buf->rp]; + buf->rp += sizeof(ret); + return PD_LE64(ret); + } + return 0; +} + +s8 netbufReadS8(struct netbuf *buf) { + return (s8)netbufReadU8(buf); +} + +s16 netbufReadS16(struct netbuf *buf) { + return (s16)netbufReadU16(buf); +} + +s32 netbufReadS32(struct netbuf *buf) { + return (s32)netbufReadU32(buf); +} + +s64 netbufReadS64(struct netbuf *buf) { + return (s64)netbufReadU64(buf); +} + +f32 netbufReadF32(struct netbuf *buf) +{ + union { + f32 f; + u32 i; + } hack; + hack.i = netbufReadU32(buf); + return hack.f; +} + +char *netbufReadStr(struct netbuf *buf) +{ + const u16 len = netbufReadU16(buf); + if (netbufCanRead(buf, len)) { + char *ret = (char *)&buf->data[buf->rp]; + buf->rp += len; + return ret; + } + return NULL; +} + +u32 netbufReadCoord(struct netbuf *buf, struct coord *out) +{ + if (netbufCanRead(buf, sizeof(*out))) { + out->x = netbufReadF32(buf); + out->y = netbufReadF32(buf); + out->z = netbufReadF32(buf); + return sizeof(*out); + } + return 0; +} + +/* write */ + +void netbufStartWrite(struct netbuf *buf) +{ + buf->wp = 0; + buf->error = 0; +} + +static inline u32 netbufCanWrite(struct netbuf *buf, const u32 num) +{ + if (buf->error || buf->wp + num > buf->size) { + buf->error = 1; + return false; + } + return true; +} + +s32 netbufWriteLeft(const struct netbuf *buf) +{ + return buf->size - buf->wp; +} + +u32 netbufWriteData(struct netbuf *buf, const void *data, u32 num) +{ + if (netbufCanWrite(buf, num)) { + memcpy(buf->data + buf->wp, data, num); + buf->wp += num; + return num; + } + return 0; +} + +u32 netbufWriteU8(struct netbuf *buf, const u8 v) +{ + if (netbufCanWrite(buf, sizeof(v))) { + buf->data[buf->wp++] = v; + return sizeof(v); + } + return 0; +} + +u32 netbufWriteU16(struct netbuf *buf, const u16 v) +{ + if (netbufCanWrite(buf, sizeof(v))) { + *(u16 *)&buf->data[buf->wp] = PD_LE16(v); + buf->wp += sizeof(v); + return sizeof(v); + } + return 0; +} + +u32 netbufWriteU32(struct netbuf *buf, const u32 v) +{ + if (netbufCanWrite(buf, sizeof(v))) { + *(u32 *)&buf->data[buf->wp] = PD_LE32(v); + buf->wp += sizeof(v); + return sizeof(v); + } + return 0; +} + +u32 netbufWriteU64(struct netbuf *buf, const u64 v) +{ + if (netbufCanWrite(buf, sizeof(v))) { + *(u64 *)&buf->data[buf->wp] = PD_LE64(v); + buf->wp += sizeof(v); + return sizeof(v); + } + return 0; +} + +u32 netbufWriteS8(struct netbuf *buf, const s8 v) +{ + return netbufWriteU8(buf, (u8)v); +} + +u32 netbufWriteS16(struct netbuf *buf, const s16 v) +{ + return netbufWriteU16(buf, (u16)v); +} + +u32 netbufWriteS32(struct netbuf *buf, const s32 v) +{ + return netbufWriteU32(buf, (u32)v); +} + +u32 netbufWriteS64(struct netbuf *buf, const s64 v) +{ + return netbufWriteU64(buf, (u64)v); +} + +u32 netbufWriteF32(struct netbuf *buf, const f32 v) +{ + const union { + f32 f; + u32 i; + } hack = { .f = v }; + return netbufWriteU32(buf, hack.i); +} + +u32 netbufWriteStr(struct netbuf *buf, const char *v) +{ + if (!v) { + return 0; + } + + u32 len = strlen(v) + 1; + if (len > 0xFFFF) { + len = 0xFFFF; + } + + if (netbufCanWrite(buf, len + sizeof(u16))) { + netbufWriteU16(buf, len); + netbufWriteData(buf, v, len); + buf->data[buf->wp - 1] = 0; + return sizeof(u16) + len; + } + + return 0; +} + +u32 netbufWriteCoord(struct netbuf *buf, const struct coord *v) +{ + if (netbufCanWrite(buf, sizeof(*v))) { + netbufWriteF32(buf, v->x); + netbufWriteF32(buf, v->y); + netbufWriteF32(buf, v->z); + return sizeof(*v); + } + return 0; +} + +void netbufReset(struct netbuf *buf) +{ + memset(buf, 0, sizeof(*buf)); +} diff --git a/port/src/net/netmenu.c b/port/src/net/netmenu.c new file mode 100644 index 000000000..7384a16a4 --- /dev/null +++ b/port/src/net/netmenu.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include +#include +#include "platform.h" +#include "data.h" +#include "types.h" +#include "game/mainmenu.h" +#include "game/menu.h" +#include "game/gamefile.h" +#include "video.h" +#include "input.h" +#include "config.h" +#include "net/net.h" + +extern MenuItemHandlerResult menuhandlerMainMenuCombatSimulator(s32 operation, struct menuitem *item, union handlerdata *data); + +static s32 g_NetMenuMaxPlayers = NET_MAX_CLIENTS; +static s32 g_NetMenuPort = NET_DEFAULT_PORT; +static char g_NetJoinAddr[256] = "127.0.0.1:27100"; +static s32 g_NetJoinAddrPtr = 0; + +/* host */ + +static MenuItemHandlerResult menuhandlerHostMaxPlayers(s32 operation, struct menuitem *item, union handlerdata *data) +{ + switch (operation) { + case MENUOP_GETSLIDER: + data->slider.value = g_NetMenuMaxPlayers; + break; + case MENUOP_SET: + if (data->slider.value) { + g_NetMenuMaxPlayers = data->slider.value; + } + break; + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerHostStart(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + if (netStartServer(g_NetMenuPort, g_NetMenuMaxPlayers) == 0) { + menuhandlerMainMenuCombatSimulator(MENUOP_SET, NULL, NULL); + } + } + + return 0; +} + +struct menuitem g_NetHostMenuItems[] = { + { + MENUITEMTYPE_SLIDER, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Max Players", + NET_MAX_CLIENTS, + menuhandlerHostMaxPlayers, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + 0, + L_MPMENU_036, // "Start Game" + 0, + menuhandlerHostStart, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_NetHostMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Host Network Game", + g_NetHostMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT, + NULL, +}; + +/* join */ + +static const char *menutextJoinAddress(struct menuitem *item) +{ + static char tmp[256 + 1]; + if (item && item->flags & MENUITEMFLAG_SELECTABLE_CENTRE) { + // in centered dialog + if (g_NetMode == NETMODE_NONE) { + snprintf(tmp, sizeof(tmp), "%s_\n", g_NetJoinAddr); + } else if (g_NetLocalClient->state == CLSTATE_CONNECTING) { + snprintf(tmp, sizeof(tmp), "Connecting to %s...\n", g_NetJoinAddr); + } else if (g_NetLocalClient->state == CLSTATE_AUTH) { + snprintf(tmp, sizeof(tmp), "Authenticating with %s...\n", g_NetJoinAddr); + } else if (g_NetLocalClient->state == CLSTATE_LOBBY) { + snprintf(tmp, sizeof(tmp), "Waiting for host...\n"); + } + } else { + // label + snprintf(tmp, sizeof(tmp), "%s\n", g_NetJoinAddr); + } + return tmp; +} + +static MenuItemHandlerResult menuhandlerJoining(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (inputKeyPressed(VK_ESCAPE)) { + netDisconnect(); + menuPopDialog(); + } + + return 0; +} + +struct menuitem g_NetJoiningMenuItems[] = { + { + MENUITEMTYPE_LABEL, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE, + (uintptr_t)&menutextJoinAddress, + 0, + NULL, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"ESC to abort\n", + 0, + menuhandlerJoining, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_NetJoiningDialog = { + MENUDIALOGTYPE_SUCCESS, + (uintptr_t)"Joining Game...", + g_NetJoiningMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT | MENUDIALOGFLAG_IGNOREBACK | MENUDIALOGFLAG_STARTSELECTS, + NULL, +}; + +static MenuItemHandlerResult menuhandlerEnterJoinAddress(s32 operation, struct menuitem *item, union handlerdata *data); + +struct menuitem g_NetJoinAddressMenuItems[] = { + { + MENUITEMTYPE_LABEL, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE, + (uintptr_t)&menutextJoinAddress, + 0, + NULL, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CENTRE | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"ESC to return\n", + 0, + menuhandlerEnterJoinAddress, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_NetJoinAddressDialog = { + MENUDIALOGTYPE_SUCCESS, + (uintptr_t)"Enter Address", + g_NetJoinAddressMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT | MENUDIALOGFLAG_IGNOREBACK | MENUDIALOGFLAG_STARTSELECTS, + NULL, +}; + +static MenuItemHandlerResult menuhandlerEnterJoinAddress(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (!menuIsDialogOpen(&g_NetJoinAddressDialog)) { + return 0; + } + + const s32 key = inputGetLastKey(); + char chr = '\0'; + switch (key) { + case 0: + return 0; + case VK_ESCAPE: + menuPopDialog(); + break; + case VK_BACKSPACE: + if (g_NetJoinAddrPtr) { + g_NetJoinAddr[--g_NetJoinAddrPtr] = '\0'; + } else { + g_NetJoinAddr[0] = '\0'; + } + break; + case VK_A ... VK_Z: chr = 'a' + key - VK_A; break; + case VK_1 ... VK_9: chr = '1' + key - VK_1; break; + case VK_0: chr = '0'; break; + case VK_PERIOD: chr = '.'; break; + case VK_SEMICOLON: chr = ':'; break; + case VK_LEFTBRACKET: chr = '['; break; + case VK_RIGHTBRACKET: chr = ']'; break; + case VK_MINUS: chr = '-'; break; + default: + break; + } + + if (chr && g_NetJoinAddrPtr < sizeof(g_NetJoinAddr) - 1) { + g_NetJoinAddr[g_NetJoinAddrPtr++] = chr; + g_NetJoinAddr[g_NetJoinAddrPtr + 1] = '\0'; + } + + inputClearLastKey(); + + return 0; +} + +static MenuItemHandlerResult menuhandlerJoinAddress(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + inputClearLastKey(); + g_NetJoinAddrPtr = strlen(g_NetJoinAddr); + menuPushDialog(&g_NetJoinAddressDialog); + } + + return 0; +} + +static MenuItemHandlerResult menuhandlerJoinStart(s32 operation, struct menuitem *item, union handlerdata *data) +{ + if (operation == MENUOP_SET) { + if (netStartClient(g_NetJoinAddr) == 0) { + menuPushDialog(&g_NetJoiningDialog); + } + } + + return 0; +} + +struct menuitem g_NetJoinMenuItems[] = { + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Address: \n", + (uintptr_t)&menutextJoinAddress, + menuhandlerJoinAddress, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + 0, + L_MPMENU_036, // "Start Game" + 0, + menuhandlerJoinStart, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_NetJoinMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Join Network Game", + g_NetJoinMenuItems, + NULL, + MENUDIALOGFLAG_LITERAL_TEXT | MENUDIALOGFLAG_STARTSELECTS | MENUDIALOGFLAG_IGNOREBACK, + NULL, +}; + +/* main */ + +struct menuitem g_NetMenuItems[] = { + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Host Game\n", + 0, + (void *)&g_NetHostMenuDialog, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Join Game\n", + 0, + (void *)&g_NetJoinMenuDialog, + }, + { + MENUITEMTYPE_SEPARATOR, + 0, + 0, + 0, + 0, + NULL, + }, + { + MENUITEMTYPE_SELECTABLE, + 0, + MENUITEMFLAG_SELECTABLE_CLOSESDIALOG, + L_OPTIONS_213, // "Back" + 0, + NULL, + }, + { MENUITEMTYPE_END }, +}; + +struct menudialogdef g_NetMenuDialog = { + MENUDIALOGTYPE_DEFAULT, + (uintptr_t)"Network Game", + g_NetMenuItems, + NULL, + MENUDIALOGFLAG_MPLOCKABLE | MENUDIALOGFLAG_LITERAL_TEXT, + NULL, +}; diff --git a/port/src/net/netmsg.c b/port/src/net/netmsg.c new file mode 100644 index 000000000..6ef1cd013 --- /dev/null +++ b/port/src/net/netmsg.c @@ -0,0 +1,403 @@ +#include +#include "types.h" +#include "data.h" +#include "bss.h" +#include "lib/main.h" +#include "game/mplayer/mplayer.h" +#include "game/chr.h" +#include "game/chraction.h" +#include "system.h" +#include "net/net.h" +#include "net/netbuf.h" +#include "net/netmsg.h" + +extern s32 g_StageNum; +extern u64 g_RngSeed; +extern u64 g_Rng2Seed; + +/* utils */ + +static inline u32 netbufWriteRooms(struct netbuf *buf, const s16 *rooms, const s32 num) { + for (s32 i = 0; i < num; ++i) { + netbufWriteS16(buf, rooms[i]); + } + return buf->error; +} + +static inline u32 netbufReadRooms(struct netbuf *buf, s16 *rooms, const s32 num) { + for (s32 i = 0; i < num; ++i) { + rooms[i] = netbufReadS16(buf); + } + return buf->error; +} + +static inline u32 netbufWritePlayerMove(struct netbuf *buf, const struct netplayermove *in) +{ + netbufWriteU32(buf, in->tick); + netbufWriteU32(buf, in->ucmd); + netbufWriteF32(buf, in->leanofs); + netbufWriteF32(buf, in->crouchofs); + netbufWriteF32(buf, in->movespeed[0]); + netbufWriteF32(buf, in->movespeed[1]); + netbufWriteF32(buf, in->lookspeed[0]); + netbufWriteF32(buf, in->lookspeed[1]); + netbufWriteF32(buf, in->angles[0]); + netbufWriteF32(buf, in->angles[1]); + netbufWriteCoord(buf, &in->pos); + netbufWriteRooms(buf, in->rooms, ARRAYCOUNT(in->rooms)); + return buf->error; +} + +static inline u32 netbufReadPlayerMove(struct netbuf *buf, struct netplayermove *in) +{ + in->tick = netbufReadU32(buf); + in->ucmd = netbufReadU32(buf); + in->leanofs = netbufReadF32(buf); + in->crouchofs = netbufReadF32(buf); + in->movespeed[0] = netbufReadF32(buf); + in->movespeed[1] = netbufReadF32(buf); + in->lookspeed[0] = netbufReadF32(buf); + in->lookspeed[1] = netbufReadF32(buf); + in->angles[0] = netbufReadF32(buf); + in->angles[1] = netbufReadF32(buf); + netbufReadCoord(buf, &in->pos); + netbufReadRooms(buf, in->rooms, ARRAYCOUNT(in->rooms)); + return buf->error; +} + +/* client -> server */ + +u32 netmsgClcAuthWrite(struct netbuf *dst) +{ + netbufWriteU8(dst, CLC_AUTH); + netbufWriteU8(dst, 1); // TODO: number of local players + netbufWriteU8(dst, g_PlayerConfigsArray[0].base.mpbodynum); + netbufWriteU8(dst, g_PlayerConfigsArray[0].base.mpheadnum); + netbufWriteStr(dst, g_PlayerConfigsArray[0].base.name); + return dst->error; +} + +u32 netmsgClcAuthRead(struct netbuf *src, struct netclient *srccl) +{ + if (srccl->state != CLSTATE_AUTH) { + sysLogPrintf(LOG_WARNING, "NET: CLC_AUTH from %s, who is not in CLSTATE_AUTH", netFormatClientAddr(srccl)); + return 1; + } + + const u8 players = netbufReadU8(src); + const u8 bodynum = netbufReadU8(src); + const u8 headnum = netbufReadU8(src); + char *name = netbufReadStr(src); + if (src->error) { + sysLogPrintf(LOG_WARNING, "NET: malformed CLC_AUTH from %s", netFormatClientAddr(srccl)); + return 1; + } + + strncpy(srccl->settings.name, name, sizeof(srccl->settings.name) - 1); + srccl->settings.bodynum = bodynum; + srccl->settings.headnum = headnum; + srccl->state = CLSTATE_LOBBY; + + sysLogPrintf(LOG_NOTE, "NET: CLC_AUTH from %s (%s), responding", netFormatClientAddr(srccl), srccl->settings.name); + + netbufStartWrite(&srccl->out); + netmsgSvcAuthWrite(&srccl->out, srccl); + netSend(srccl, NULL, true); + + return 0; +} + +u32 netmsgClcMoveWrite(struct netbuf *dst) +{ + netbufWriteU8(dst, CLC_MOVE); + netbufWriteU32(dst, g_NetLocalClient->inmove[0].tick); + netbufWritePlayerMove(dst, &g_NetLocalClient->outmove[0]); + return dst->error; +} + +u32 netmsgClcMoveRead(struct netbuf *src, struct netclient *srccl) +{ + if (srccl->state != CLSTATE_GAME) { + sysLogPrintf(LOG_WARNING, "NET: CLC_MOVE from %s, who is not in CLSTATE_GAME", netFormatClientAddr(srccl)); + return src->error; + } + + struct netplayermove newmove; + srccl->outmoveack = netbufReadU32(src); + netbufReadPlayerMove(src, &newmove); + + if (!src->error) { + // enforce teleports and such + if (srccl->forcetick && srccl->player) { + if (srccl->outmoveack >= srccl->forcetick) { + // client has acknowledged our last sent move, clear the force flags + srccl->forcetick = 0; + srccl->player->ucmd &= ~UCMD_FL_FORCEMASK; + sysLogPrintf(LOG_NOTE, "NET: client %u successfully forcemoved", srccl->id); + } else { + // client hasn't teleported yet, discard the new position from the input command + if (srccl->player->ucmd & UCMD_FL_FORCEPOS) { + newmove.pos = srccl->player->prop->pos; + } + if (srccl->player->ucmd & UCMD_FL_FORCEANGLE) { + newmove.angles[0] = srccl->player->vv_theta; + newmove.angles[1] = srccl->player->vv_verta; + } + } + } + // make space in the move stack + memmove(srccl->inmove + 1, srccl->inmove, sizeof(srccl->inmove) - sizeof(*srccl->inmove)); + srccl->inmove[0] = newmove; + srccl->lerpticks = 0; + } + + return src->error; +} + +/* server -> client */ + +u32 netmsgSvcAuthWrite(struct netbuf *dst, struct netclient *authcl) +{ + netbufWriteU8(dst, SVC_AUTH); + netbufWriteU8(dst, authcl - g_NetClients); + netbufWriteU8(dst, g_NetMaxClients); + netbufWriteU32(dst, g_NetTick); + return dst->error; +} + +u32 netmsgSvcAuthRead(struct netbuf *src, struct netclient *srccl) +{ + if (g_NetLocalClient->state != CLSTATE_AUTH) { + sysLogPrintf(LOG_WARNING, "NET: SVC_AUTH from server but we're not in AUTH state"); + return 1; + } + + const u8 id = netbufReadU8(src); + const u8 maxclients = netbufReadU8(src); + g_NetTick = netbufReadU32(src); + if (g_NetLocalClient->in.error || id == 0xFF || id == 0 || maxclients == 0) { + sysLogPrintf(LOG_WARNING, "NET: malformed SVC_AUTH from server"); + return 1; + } + + sysLogPrintf(LOG_NOTE, "NET: SVC_AUTH from server, our ID is %u", id); + + // there's at least one client, which is us, and we know maxclients as well + g_NetMaxClients = maxclients; + g_NetNumClients = 1; + + // we now know our proper ID, so move to the appropriate client slot and reset the old one + g_NetLocalClient = &g_NetClients[id]; + g_NetClients[id] = g_NetClients[NET_MAX_CLIENTS]; + g_NetLocalClient->out.data = g_NetLocalClient->out_data; + g_NetLocalClient->id = id; + + // clear out the old slot + g_NetClients[NET_MAX_CLIENTS].id = NET_MAX_CLIENTS; + g_NetClients[NET_MAX_CLIENTS].state = 0; + g_NetClients[NET_MAX_CLIENTS].peer = NULL; + + // the server's client probably is in the lobby state by now + g_NetClients[0].state = CLSTATE_LOBBY; + + g_NetLocalClient->state = CLSTATE_LOBBY; + + return src->error; +} + +u32 netmsgSvcStageStartWrite(struct netbuf *dst) +{ + netbufWriteU8(dst, SVC_STAGE_START); + + netbufWriteU32(dst, g_NetTick); + + netbufWriteU64(dst, g_RngSeed); + netbufWriteU64(dst, g_Rng2Seed); + + netbufWriteU8(dst, g_StageNum); + + // game settings + netbufWriteU8(dst, 0); // 0 for combat sim TODO: coop/anti + netbufWriteU8(dst, g_MpSetup.scenario); + netbufWriteU8(dst, g_MpSetup.scorelimit); + netbufWriteU8(dst, g_MpSetup.timelimit); + netbufWriteU16(dst, g_MpSetup.teamscorelimit); + netbufWriteU16(dst, g_MpSetup.chrslots); + netbufWriteU32(dst, g_MpSetup.options); + netbufWriteData(dst, g_MpSetup.weapons, sizeof(g_MpSetup.weapons)); + + // who the fuck is in the game + netbufWriteU8(dst, g_NetNumClients); + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *ncl = &g_NetClients[i]; + if (ncl->state) { + netbufWriteU8(dst, ncl->id); + netbufWriteU8(dst, ncl->settings.bodynum); + netbufWriteU8(dst, ncl->settings.headnum); + netbufWriteStr(dst, ncl->settings.name); + ncl->state = CLSTATE_GAME; + } + } + + return dst->error; +} + +u32 netmsgSvcStageStartRead(struct netbuf *src, struct netclient *srccl) +{ + if (srccl->state != CLSTATE_LOBBY) { + sysLogPrintf(LOG_WARNING, "NET: SVC_STAGE from server but we're not in LOBBY state"); + return 1; + } + + g_NetTick = netbufReadU32(src); + + g_RngSeed = netbufReadU64(src); + g_Rng2Seed = netbufReadU64(src); + + const u8 stagenum = netbufReadU8(src); + const u8 mode = netbufReadU8(src); // TODO: coop, anti + + g_MpSetup.stagenum = stagenum; + g_MpSetup.scenario = netbufReadU8(src); + g_MpSetup.scorelimit = netbufReadU8(src); + g_MpSetup.timelimit = netbufReadU8(src); + g_MpSetup.teamscorelimit = netbufReadU16(src); + g_MpSetup.chrslots = netbufReadU16(src); + g_MpSetup.options = netbufReadU32(src); + netbufReadData(src, g_MpSetup.weapons, sizeof(g_MpSetup.weapons)); + strcpy(g_MpSetup.name, "server"); + + if (src->error) { + sysLogPrintf(LOG_WARNING, "NET: malformed SVC_STAGE from server"); + return 1; + } + + // read players + const u8 numplayers = netbufReadU8(src); + if (src->error || !numplayers || numplayers > g_NetMaxClients + 1) { + sysLogPrintf(LOG_WARNING, "NET: malformed SVC_STAGE from server"); + return 2; + } + + for (u8 i = 0; i < numplayers; ++i) { + const u8 id = netbufReadU8(src); + struct netclient *ncl = &g_NetClients[id]; + if (ncl != g_NetLocalClient) { + ncl->id = id; + ncl->settings.bodynum = netbufReadU8(src); + ncl->settings.headnum = netbufReadU8(src); + char *name = netbufReadStr(src); + if (name) { + strncpy(ncl->settings.name, name, sizeof(ncl->settings.name) - 1); + } else { + sysLogPrintf(LOG_WARNING, "NET: malformed SVC_STAGE from server"); + return 3; + } + } else { + // skip our own settings + netbufReadU8(src); + netbufReadU8(src); + netbufReadStr(src); + } + ncl->state = CLSTATE_GAME; + ncl->player = NULL; + } + + if (src->error) { + return src->error; + } + + g_NetNumClients = numplayers; + + sysLogPrintf(LOG_NOTE, "NET: SVC_STAGE from server: going to stage 0x%02x with %u players", g_MpSetup.stagenum, numplayers); + + mpStartMatch(); + menuStop(); + + return 0; +} + +u32 netmsgSvcStageEndWrite(struct netbuf *dst) +{ + netbufWriteU8(dst, SVC_STAGE_END); + + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *ncl = &g_NetClients[i]; + if (ncl->state) { + ncl->state = CLSTATE_LOBBY; + ncl->player = NULL; + ncl->config = NULL; + } + } + + return dst->error; +} + +u32 netmsgSvcStageEndRead(struct netbuf *src, struct netclient *srccl) +{ + for (s32 i = 0; i < g_NetMaxClients; ++i) { + struct netclient *ncl = &g_NetClients[i]; + if (ncl->state) { + ncl->state = CLSTATE_LOBBY; + ncl->player = NULL; + ncl->config = NULL; + } + } + + mainEndStage(); + + return src->error; +} + +u32 netmsgSvcPlayerMoveWrite(struct netbuf *dst, struct netclient *movecl) +{ + if (movecl->state < CLSTATE_GAME || !movecl->player || !movecl->player->prop) { + return dst->error; + } + + netbufWriteU8(dst, SVC_PLAYER_MOVE); + netbufWriteU8(dst, movecl->id); + netbufWriteU32(dst, movecl->inmove[0].tick); + netbufWritePlayerMove(dst, &movecl->outmove[0]); + + return dst->error; +} + +u32 netmsgSvcPlayerMoveRead(struct netbuf *src, struct netclient *srccl) +{ + if (srccl->state != CLSTATE_GAME) { + sysLogPrintf(LOG_WARNING, "NET: SVC_PLAYER_MOVE from server but we're not in GAME state"); + return 1; + } + + u8 id = 0; + u32 outmoveack = 0; + struct netplayermove newmove; + + id = netbufReadU8(src); + outmoveack = netbufReadU32(src); + netbufReadPlayerMove(src, &newmove); + + if (src->error) { + return src->error; + } + + struct netclient *movecl = &g_NetClients[id]; + + // make space in the move stack + memmove(movecl->inmove + 1, movecl->inmove, sizeof(movecl->inmove) - sizeof(*movecl->inmove)); + movecl->inmove[0] = newmove; + movecl->outmoveack = outmoveack; + movecl->lerpticks = 0; + + if (movecl == g_NetLocalClient && (newmove.ucmd & UCMD_FL_FORCEMASK)) { + // server wants to teleport us + if (movecl->player && movecl->player->prop) { + RoomNum newrooms[8] = { newmove.rooms[0], -1 }; + chrSetPos(movecl->player->prop->chr, &newmove.pos, newrooms, newmove.angles[0], (newmove.ucmd & UCMD_FL_FORCEGROUND) != 0); + sysLogPrintf(LOG_NOTE, "NET: server is forcing us to move"); + } + } + + return src->error; +} diff --git a/port/src/pdmain.c b/port/src/pdmain.c index 77c031800..c568e3ac3 100644 --- a/port/src/pdmain.c +++ b/port/src/pdmain.c @@ -72,6 +72,8 @@ #include "lib/str.h" #include "data.h" #include "types.h" +#include "net/net.h" +#include "net/netmsg.h" extern u8 *g_MempHeap; extern u32 g_MempHeapSize; @@ -608,6 +610,8 @@ void mainEndStage(void) endscreenPrepare(); musicStartMenu(); } + + netServerStageEnd(); } g_MainIsEndscreen = true; diff --git a/port/src/pdsched.c b/port/src/pdsched.c index 1f8800565..9880db4f9 100644 --- a/port/src/pdsched.c +++ b/port/src/pdsched.c @@ -25,6 +25,7 @@ #include "audio.h" #include "input.h" #include "mixer.h" +#include "net/net.h" /* * private typedefs and defines @@ -240,6 +241,7 @@ void schedSubmitTask(OSSched *sc, OSScTask *t) void schedStartFrame(OSSched *sc) { videoStartFrame(); + netStartFrame(); } void schedAudioFrame(OSSched *sc) @@ -294,6 +296,8 @@ void schedEndFrame(OSSched *sc) joyReadData(); joy00014238(); + netEndFrame(); + sndHandleRetrace(); schedAudioFrame(sc); schedRenderCrashPeriodically(sc->frameCount); diff --git a/port/src/system.c b/port/src/system.c index d01e88060..e8433cf82 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -119,6 +119,7 @@ void sysLogPrintf(s32 level, const char *fmt, ...) FILE *fout = (level == LOG_NOTE) ? stdout : stderr; fprintf(fout, "%s%s\n", prefix[level], logmsg); + fflush(fout); } void sysFatalError(const char *fmt, ...) diff --git a/src/game/activemenu.c b/src/game/activemenu.c index 917504112..e0477e4b4 100644 --- a/src/game/activemenu.c +++ b/src/game/activemenu.c @@ -560,7 +560,7 @@ void amReset(void) g_AmFont1 = g_CharsHandelGothicSm; g_AmFont2 = g_FontHandelGothicSm; #else - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { g_AmFont1 = g_CharsHandelGothicXs; g_AmFont2 = g_FontHandelGothicXs; } else { @@ -591,7 +591,7 @@ s16 amCalculateSlotWidth(void) } #if VERSION >= VERSION_NTSC_1_0 - if (PLAYERCOUNT() > 1) { + if (LOCALPLAYERCOUNT() > 1) { max += 3; } else { max += 4; @@ -775,32 +775,32 @@ void amClose(void) bool amIsCramped(void) { #if VERSION == VERSION_JPN_FINAL - if (PLAYERCOUNT() >= 3 && g_AmMenus[g_AmIndex].screenindex != 1) { + if (LOCALPLAYERCOUNT() >= 3 && g_AmMenus[g_AmIndex].screenindex != 1) { return true; } - if (IS4MB() && PLAYERCOUNT() == 2) { + if (IS4MB() && LOCALPLAYERCOUNT() == 2) { return true; } if (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL - && PLAYERCOUNT() == 2 + && LOCALPLAYERCOUNT() == 2 && g_AmMenus[g_AmIndex].screenindex != 1) { return true; } return false; #else - return (g_AmMenus[g_AmIndex].screenindex == 0 && PLAYERCOUNT() >= 3) - || (IS4MB() && PLAYERCOUNT() == 2) - || (PLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL); + return (g_AmMenus[g_AmIndex].screenindex == 0 && LOCALPLAYERCOUNT() >= 3) + || (IS4MB() && LOCALPLAYERCOUNT() == 2) + || (LOCALPLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL); #endif } void amCalculateSlotPosition(s16 column, s16 row, s16 *x, s16 *y) { #if VERSION == VERSION_JPN_FINAL - s32 playercount = PLAYERCOUNT(); + s32 playercount = LOCALPLAYERCOUNT(); *x = g_AmMenus[g_AmIndex].xradius * (column - 1); *y = (row - 1) * 50; @@ -854,7 +854,7 @@ void amCalculateSlotPosition(s16 column, s16 row, s16 *x, s16 *y) } } #elif VERSION >= VERSION_NTSC_1_0 - s32 playercount = PLAYERCOUNT(); + s32 playercount = LOCALPLAYERCOUNT(); *x = g_AmMenus[g_AmIndex].xradius * (column - 1); *y = (row - 1) * 50; @@ -906,7 +906,7 @@ void amCalculateSlotPosition(s16 column, s16 row, s16 *x, s16 *y) } } #else - s32 playercount = PLAYERCOUNT(); + s32 playercount = LOCALPLAYERCOUNT(); *x = g_AmMenus[g_AmIndex].xradius * (column - 1); *y = (row - 1) * 50; @@ -986,15 +986,15 @@ Gfx *amRenderAibotInfo(Gfx *gdl, s32 buddynum) #endif #if VERSION >= VERSION_NTSC_1_0 - if (PLAYERCOUNT() == 1 && optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) { + if (LOCALPLAYERCOUNT() == 1 && optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) { wide = true; } #endif #if VERSION >= VERSION_NTSC_1_0 - if ((PLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) || PLAYERCOUNT() >= 3) + if ((LOCALPLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) || LOCALPLAYERCOUNT() >= 3) #else - if ((PLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) || PLAYERCOUNT() >= 3) + if ((LOCALPLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) || LOCALPLAYERCOUNT() >= 3) #endif { if ((g_Vars.currentplayernum % 2) == 0) { @@ -1027,7 +1027,7 @@ Gfx *amRenderAibotInfo(Gfx *gdl, s32 buddynum) - (s32)(textwidth * 0.5f) + offset; - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { y = viGetViewTop() + 5; } else { y = viGetViewTop() + 10; @@ -1043,12 +1043,12 @@ Gfx *amRenderAibotInfo(Gfx *gdl, s32 buddynum) gdl = func0f1574d0jf(gdl, &x, &y, aibotname, g_AmFont1, g_AmFont2, -1, 0x000000ff, SCREEN_320, SCREEN_240, 0, 0); - y += (PLAYERCOUNT() >= 2) ? 0 : (s32)(textheight * 1.1f); + y += (LOCALPLAYERCOUNT() >= 2) ? 0 : (s32)(textheight * 1.1f); #else gdl = textRender(gdl, &x, &y, aibotname, g_AmFont1, g_AmFont2, -1, 0x000000ff, SCREEN_320, SCREEN_240, 0, 0); - y += (PLAYERCOUNT() >= 2) ? 0 : (s32)(textheight * 1.1f); + y += (LOCALPLAYERCOUNT() >= 2) ? 0 : (s32)(textheight * 1.1f); textMeasure(&textheight, &textwidth, weaponname, g_AmFont1, g_AmFont2, 0); x = viGetViewLeft() / g_ScaleX @@ -1077,7 +1077,7 @@ Gfx *amRenderAibotInfo(Gfx *gdl, s32 buddynum) - (s32)(textwidth * 0.5f) + offset; - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { y = viGetViewTop() + 5; } else { y = viGetViewTop() + 10; @@ -1136,7 +1136,7 @@ Gfx *amRenderSlot(Gfx *gdl, char *text, s16 x, s16 y, s32 mode, s32 flags) paddingtop = 6; paddingbottom = 6; - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { paddingtop = 5; paddingbottom = 3; } @@ -1275,7 +1275,7 @@ Gfx *amRender(Gfx *gdl) #endif #ifndef PLATFORM_N64 - if (PLAYERCOUNT() == 1) { + if (LOCALPLAYERCOUNT() == 1) { gSPSetExtraGeometryModeEXT(gdl++, G_ASPECT_CENTER_EXT); } #endif @@ -1499,7 +1499,7 @@ Gfx *amRender(Gfx *gdl) above = 6; below = 6; - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { above = 5; below = 3; } @@ -1522,7 +1522,7 @@ Gfx *amRender(Gfx *gdl) halfwidth = 1; above = 2; below = 0; - } else if (PLAYERCOUNT() >= 2) { + } else if (LOCALPLAYERCOUNT() >= 2) { s32 textheight; s32 textwidth; char text[32]; @@ -1604,16 +1604,16 @@ Gfx *amRender(Gfx *gdl) } #endif - barwidth = PLAYERCOUNT() >= 2 ? 48 : 64; - barheight = PLAYERCOUNT() >= 2 ? 7 : 11; + barwidth = LOCALPLAYERCOUNT() >= 2 ? 48 : 64; + barheight = LOCALPLAYERCOUNT() >= 2 ? 7 : 11; xoffset = 0; #if VERSION >= VERSION_NTSC_1_0 - if ((PLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) || PLAYERCOUNT() >= 3) { + if ((LOCALPLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) || LOCALPLAYERCOUNT() >= 3) { xoffset = (g_Vars.currentplayernum & 1) == 0 ? 8 : -8; } - if (PLAYERCOUNT() == 1 && optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) { + if (LOCALPLAYERCOUNT() == 1 && optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) { part1left = viGetViewLeft() / g_ScaleX + 32; } else { part1left = (s32) ((viGetViewWidth() / g_ScaleX) * 0.5f) @@ -1622,7 +1622,7 @@ Gfx *amRender(Gfx *gdl) + xoffset; } #else - if ((PLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) || PLAYERCOUNT() >= 3) { + if ((LOCALPLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) || LOCALPLAYERCOUNT() >= 3) { xoffset = (g_Vars.currentplayernum & 1) == 0 ? 8 : -8; } @@ -1648,7 +1648,7 @@ Gfx *amRender(Gfx *gdl) gDPSetRenderMode(gdl++, G_RM_XLU_SURF, G_RM_XLU_SURF2); gDPSetCombineMode(gdl++, G_CC_PRIMITIVE, G_CC_PRIMITIVE); - y = viGetViewTop() + viGetViewHeight() - (PLAYERCOUNT() >= 2 ? 19 : 34); + y = viGetViewTop() + viGetViewHeight() - (LOCALPLAYERCOUNT() >= 2 ? 19 : 34); // NTSC beta doesn't scale the health bar when hi-res is on, // and it only matches if part2left is an inline expression diff --git a/src/game/bondeyespy.c b/src/game/bondeyespy.c index cd15f48e3..efdaaf549 100644 --- a/src/game/bondeyespy.c +++ b/src/game/bondeyespy.c @@ -1016,7 +1016,7 @@ void eyespyProcessInput(bool allowbuttons) // Make eyespy look horizontally if (domovecentre) { if (g_Vars.currentplayer->eyespy->verta > 0.0f && forwardspeed != 0) { - if (controlmode != CONTROLMODE_PC) { + if (controlmode < CONTROLMODE_PC) { if (g_Vars.currentplayer->eyespy->verta < 180.0f) { tmp = g_Vars.currentplayer->eyespy->verta; diff --git a/src/game/bondgun.c b/src/game/bondgun.c index b63ab0d2c..356952cc7 100644 --- a/src/game/bondgun.c +++ b/src/game/bondgun.c @@ -12730,7 +12730,7 @@ Gfx *bgunDrawHud(Gfx *gdl) { struct player *player = g_Vars.currentplayer; s32 bottom = viGetViewTop() + viGetViewHeight() - 13; - s32 playercount = PLAYERCOUNT(); + s32 playercount = LOCALPLAYERCOUNT(); s32 playernum = g_Vars.currentplayernum; struct gunctrl *ctrl; s32 secs60; diff --git a/src/game/bondmove.c b/src/game/bondmove.c index 9b59f3847..7a5314979 100644 --- a/src/game/bondmove.c +++ b/src/game/bondmove.c @@ -42,6 +42,8 @@ #include #include "input.h" #include "video.h" +#include "system.h" +#include "net/net.h" static void bgunProcessQuickDetonate(struct movedata *data, u32 c1buttons, u32 c1buttonsthisframe, u32 buttons1, u32 buttons2) { if ((((c1buttons & (buttons1)) && (c1buttonsthisframe & (buttons2))) @@ -105,6 +107,94 @@ static void bgunProcessInputAltButton(struct movedata *data, s8 contpad, s32 i) } } +static inline void bmoveProcessRemoteInput(void) +{ + struct player *pl = g_Vars.currentplayer; + struct netplayermove *inmove = &pl->client->inmove[0]; + + if (inmove->ucmd & UCMD_DUCK) { + pl->crouchpos = CROUCHPOS_DUCK; + } else if (inmove->ucmd & UCMD_SQUAT) { + pl->crouchpos = CROUCHPOS_SQUAT; + } else { + pl->crouchpos = CROUCHPOS_STAND; + } + + pl->bondactivateorreload = 0; + + if (g_NetMode == NETMODE_SERVER) { + if (inmove->ucmd & UCMD_ACTIVATE) { + pl->bondactivateorreload |= JO_ACTION_ACTIVATE; + } + if (inmove->ucmd & UCMD_RELOAD) { + pl->bondactivateorreload |= JO_ACTION_RELOAD; + } + + if (pl->bondactivateorreload) { + bmoveHandleActivate(); + } + + bgunTickGameplay((inmove->ucmd & UCMD_FIRE) != 0); + + if (g_Vars.bondvisible && (bgunIsFiring(HAND_RIGHT) || bgunIsFiring(HAND_LEFT))) { + f32 noiseradius = 0.f; + if (bgunIsFiring(HAND_RIGHT) && bgunGetNoiseRadius(HAND_RIGHT) > noiseradius) { + noiseradius = bgunGetNoiseRadius(HAND_RIGHT); + } + if (bgunIsFiring(HAND_LEFT) && bgunGetNoiseRadius(HAND_LEFT) > noiseradius) { + noiseradius = bgunGetNoiseRadius(HAND_LEFT); + } + chrsCheckForNoise(noiseradius); + } + } + + bgunSetSightVisible(GUNSIGHTREASON_NOTAIMING, (inmove->ucmd & UCMD_AIMMODE) != 0); + + pl->speedgo = inmove->movespeed[0]; + pl->speedforwards = inmove->movespeed[0]; + pl->speedstrafe = inmove->movespeed[1]; + pl->speedsideways = inmove->movespeed[1]; + pl->speedtheta = inmove->lookspeed[0]; + pl->speedverta = inmove->lookspeed[1]; + pl->vv_theta = inmove->angles[0]; + pl->vv_verta = inmove->angles[1]; + + pl->crouchoffset = inmove->crouchofs; + + if (pl->bondmovemode == MOVEMODE_GRAB) { + bgrabUpdateSpeedTheta(); + } else if (pl->bondmovemode == MOVEMODE_WALK) { + if (bmoveGetCrouchPos() != CROUCHPOS_STAND) { + pl->speedmaxtime60 = 0; + } + bwalkSetSwayTargetf(inmove->leanofs); + } else if (pl->bondmovemode == MOVEMODE_BIKE) { + // TODO + } + + if (inmove->movespeed[0] > 0.95f) { + pl->speedmaxtime60 += g_Vars.lvupdate60; + } else { + pl->speedmaxtime60 = 0; + } + + if (pl->speedforwards > 1) { + pl->speedforwards = 1; + } + if (pl->speedforwards < -1) { + pl->speedforwards = -1; + } + if (pl->speedsideways > 1) { + pl->speedsideways = 1; + } + if (pl->speedsideways < -1) { + pl->speedsideways = -1; + } + + pl->speedforwards *= 1.08f; + pl->speedforwards *= pl->speedboost; +} + #endif // PLATFORM_N64 void bmoveSetControlDef(u32 controldef) @@ -313,6 +403,12 @@ void bmoveApplyMoveData(struct movedata *data) } else if (g_Vars.currentplayer->bondmovemode == MOVEMODE_WALK) { bwalkApplyMoveData(data); } +#ifndef PLATFORM_N64 + // record some inputs if this is a local player + if (g_NetMode && !g_Vars.currentplayer->isremote) { + g_Vars.currentplayer->ucmd = 0; + } +#endif } void bmoveUpdateSpeedTheta(void) @@ -715,6 +811,11 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i const f32 mlookscale = g_Vars.lvupdate240 ? (4.f / (f32)g_Vars.lvupdate240) : 4.f; const bool allowmlook = (g_Vars.currentplayernum == 0) && (allowc1x || allowc1y); bool allowmcross = false; + + if (g_Vars.currentplayer->isremote) { + bmoveProcessRemoteInput(); + return; + } #endif controlmode = optionsGetControlMode(g_Vars.currentplayerstats->mpindex); @@ -1025,7 +1126,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i } // Handle B button activation - if (allowc1buttons && controlmode != CONTROLMODE_PC) { + if (allowc1buttons && controlmode < CONTROLMODE_PC) { for (i = 0; i < numsamples; i++) { if (joyGetButtonsOnSample(i, contpad1, c1allowedbuttons & B_BUTTON) || joyGetButtonsOnSample(i, contpad2, c2allowedbuttons & B_BUTTON)) { @@ -1493,7 +1594,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i for (i = 0; i < numsamples; i++) { if (joyGetButtonsOnSample(i, contpad1, c1allowedbuttons & usemask)) { if (g_Vars.currentplayer->usedowntime >= -1) { - if (controlmode != CONTROLMODE_PC) { + if (controlmode < CONTROLMODE_PC) { if (joyGetButtonsPressedOnSample(i, contpad1, shootbuttons & c1allowedbuttons) && g_Vars.currentplayer->usedowntime >= 0 && bgunConsiderToggleGunFunction(g_Vars.currentplayer->usedowntime, true, false, 0) != USETIMER_CONTINUE) { @@ -1518,7 +1619,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i } } } else { - if ((controlmode != CONTROLMODE_PC) && g_Vars.currentplayer->usedowntime >= -2) { + if ((controlmode < CONTROLMODE_PC) && g_Vars.currentplayer->usedowntime >= -2) { bgunConsiderToggleGunFunction(g_Vars.currentplayer->usedowntime, false, false, 0); } } @@ -1664,7 +1765,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i #ifdef PLATFORM_N64 if (allowc1buttons) { #else - if (allowc1buttons && (controlmode != CONTROLMODE_PC || (PLAYER_EXTCFG().crouchmode & CROUCHMODE_ANALOG))) { + if (allowc1buttons && (controlmode < CONTROLMODE_PC || (PLAYER_EXTCFG().crouchmode & CROUCHMODE_ANALOG))) { #endif for (i = 0; i < numsamples; i++) { if (!canmanualzoom && aimonhist[i]) { @@ -1748,7 +1849,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i } // Handle mine detonation - if (controlmode != CONTROLMODE_PC) { + if (controlmode < CONTROLMODE_PC) { if ((((c1buttons & invbuttons) && (c1buttonsthisframe & B_BUTTON)) || ((c1buttons & B_BUTTON) && (c1buttonsthisframe & invbuttons))) && weaponnum == WEAPON_REMOTEMINE) { @@ -1781,7 +1882,7 @@ void bmoveProcessInput(bool allowc1x, bool allowc1y, bool allowc1buttons, bool i movedata.triggeron = g_Vars.currentplayer->waitforzrelease == false && (c1buttons & shootbuttons) && g_Vars.currentplayer->pausemode == PAUSEMODE_UNPAUSED; - if (controlmode != CONTROLMODE_PC) { + if (controlmode < CONTROLMODE_PC) { movedata.triggeron = movedata.triggeron && ((c1buttons & invbuttons) == 0); } } diff --git a/src/game/bondwalk.c b/src/game/bondwalk.c index edbfeaba4..bc3929429 100644 --- a/src/game/bondwalk.c +++ b/src/game/bondwalk.c @@ -27,7 +27,67 @@ #include "data.h" #include "types.h" #ifndef PLATFORM_N64 -extern f32 fabsf(f32); +#include "net/net.h" +#include "system.h" + +static void bwalkUpdateRemote(void) +{ + struct player *pl = g_Vars.currentplayer; + const struct netplayermove *inmove = &pl->client->inmove[0]; + const struct netplayermove *inmoveprev = &pl->client->inmove[1]; + struct coord delta, target; + f32 spc8, spc4; + s32 moveticks = inmove->tick - inmoveprev->tick; + if (moveticks > g_NetInterpTicks) { + moveticks = g_NetInterpTicks; + } + + const bool forcepos = !inmoveprev->tick || !moveticks || + (inmove->ucmd & UCMD_FL_FORCEPOS) || + (fabsf(pl->prop->pos.x - inmove->pos.x) > 256.f) || + (fabsf(pl->prop->pos.y - inmove->pos.y) > 256.f) || + (fabsf(pl->prop->pos.z - inmove->pos.z) > 256.f); + + pl->client->inmovetick = inmove->tick; + + pl->bondshotspeed.x = 0.f; + pl->bondshotspeed.y = 0.f; + pl->bondshotspeed.z = 0.f; + pl->bondbreathing = 0.f; + + if (forcepos) { + // no interpolation, set position and lock the lad in place + delta.x = 0.f; + delta.y = 0.f; + delta.z = 0.f; + pl->prop->pos = inmove->pos; + pl->client->lerpticks = g_NetInterpTicks + g_NetExterpTicks; + } else if (pl->client->lerpticks <= moveticks) { + // lerp towards the new position + const f32 t = (f32)pl->client->lerpticks / (f32)moveticks; + target.x = inmoveprev->pos.x + (inmove->pos.x - inmoveprev->pos.x) * t; + target.y = inmoveprev->pos.y + (inmove->pos.y - inmoveprev->pos.y) * t; + target.z = inmoveprev->pos.z + (inmove->pos.z - inmoveprev->pos.z) * t; + delta.x = target.x - pl->prop->pos.x; + delta.y = target.y - pl->prop->pos.y; + delta.z = target.z - pl->prop->pos.z; + pl->client->lerpticks += g_Vars.lvupdate60; + } else if (pl->client->lerpticks <= moveticks + g_NetExterpTicks) { + // try extrapolating based on inputs for a while + pl->client->lerpticks += g_Vars.lvupdate60; + bwalk0f0c69b8(); + return; + } else { + // we're too far gone, lock position + delta.x = 0.f; + delta.y = 0.f; + delta.z = 0.f; + pl->prop->pos = inmove->pos; + } + + bwalkUpdateCrouchOffset(); + bwalk0f0c63bc(&delta, g_Vars.currentplayer->swaytarget == 0.0f, CDTYPE_ALL); +} #endif void bwalkInit(void) @@ -1767,7 +1827,14 @@ void bwalkTick(void) bwalkUpdatePrevPos(); bwalkUpdateTheta(); bmoveUpdateVerta(); + +#ifndef PLATFORM_N64 + if (g_Vars.currentplayer->isremote) { + bwalkUpdateRemote(); + } else +#endif bwalk0f0c69b8(); + bwalkUpdateVertical(); #if VERSION >= VERSION_NTSC_1_0 diff --git a/src/game/chraction.c b/src/game/chraction.c index a770ee77c..beeaf1b28 100644 --- a/src/game/chraction.c +++ b/src/game/chraction.c @@ -56,6 +56,10 @@ #include "lib/vi.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#include "net/netmsg.h" +#endif s32 g_RecentQuipsPlayed[5]; u32 var8009cd84; @@ -4272,6 +4276,13 @@ void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gse s32 aplayernum = -1; s32 choketype = CHOKETYPE_NONE; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_CLIENT) { + // don't do anything if we're not the authority + return; + } +#endif + if (hitpart == HITPART_HEAD) { choketype = CHOKETYPE_GURGLE; } @@ -15287,6 +15298,68 @@ void func0f04b740(void) // empty } +bool chrSetPos(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 theta, bool findground) +{ + const f32 angle = BADDEG2RAD(360.f - theta); + + if (findground) { + const u32 oldhidden = chr->hidden; + chr->hidden |= CHRHFLAG_WARPONSCREEN; + const bool ret = chrMoveToPos(chr, pos, rooms, angle, true); + if ((oldhidden & CHRHFLAG_WARPONSCREEN) == 0) { + chr->hidden &= ~CHRHFLAG_WARPONSCREEN; + } + } + + bool newrooms = false; + for (s32 i = 0; i < ARRAYCOUNT(chr->prop->rooms) && rooms[i] >= 0; ++i) { + if (chr->prop->rooms[i] != rooms[i]) { + newrooms = true; + break; + } + } + + propSetPerimEnabled(chr->prop, false); + + chr->prop->pos = *pos; + + const f32 ground = cdFindGroundInfoAtCyl(pos, chr->radius, rooms, &chr->floorcol, + &chr->floortype, NULL, &chr->floorroom, NULL, NULL); + + chr->ground = ground; + chr->manground = ground; + chr->sumground = ground * (PAL ? 8.4175090789795f : 9.999998f); + + if (newrooms) { + propDeregisterRooms(chr->prop); + roomsCopy(rooms, chr->prop->rooms); + chr0f0220ac(chr); + } + + modelSetRootPosition(chr->model, pos); + + const u16 nodetype = chr->model->definition->rootnode->type; + + if ((nodetype & 0xff) == MODELNODETYPE_CHRINFO) { + union modelrwdata *rwdata = modelGetNodeRwData(chr->model, chr->model->definition->rootnode); + rwdata->chrinfo.ground = ground; + } + + chrSetLookAngle(chr, angle); + + if (chr->prop->type == PROPTYPE_PLAYER) { + struct player *player = g_Vars.players[playermgrGetPlayerNumByProp(chr->prop)]; + player->vv_manground = ground; + player->vv_ground = ground; + player->vv_theta = theta; + player->unk1c64 = 1; + } + + propSetPerimEnabled(chr->prop, true); + + return true; +} + bool chrMoveToPos(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 angle, bool force) { struct coord pos2; @@ -15344,6 +15417,12 @@ bool chrMoveToPos(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 an player->unk1c64 = 1; } +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER && player->isremote) { + player->ucmd |= UCMD_FL_FORCEPOS | UCMD_FL_FORCEANGLE | UCMD_FL_FORCEGROUND; + } +#endif + result = true; } diff --git a/src/game/hudmsg.c b/src/game/hudmsg.c index 650906509..a71f7a4fb 100644 --- a/src/game/hudmsg.c +++ b/src/game/hudmsg.c @@ -86,7 +86,7 @@ u8 hudmsgsAreActive(void) s32 hudmsgIsZoomRangeVisible(void) { return optionsGetShowZoomRange(g_Vars.currentplayerstats->mpindex) - && (PLAYERCOUNT() == 1 + && (LOCALPLAYERCOUNT() == 1 || !g_Vars.mplayerisrunning || g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) @@ -131,7 +131,7 @@ Gfx *hudmsgRenderMissionTimer(Gfx *gdl, u32 alpha) viewleft = viGetViewLeft() / g_ScaleX; viewtop = viGetViewTop(); viewheight = viGetViewHeight(); - playercount = PLAYERCOUNT(); + playercount = LOCALPLAYERCOUNT(); playernum = g_Vars.currentplayernum; timery = viewheight; @@ -190,7 +190,7 @@ Gfx *hudmsgRenderMissionTimer(Gfx *gdl, u32 alpha) y = timery; #ifndef PLATFORM_N64 - if (PLAYERCOUNT() < 2) { + if (LOCALPLAYERCOUNT() < 2) { gSPExtraGeometryModeEXT(gdl++, G_ASPECT_MODE_EXT, g_HudAlignModeL); } #endif @@ -234,7 +234,7 @@ Gfx *hudmsgRenderZoomRange(Gfx *gdl, u32 alpha) texty = viewheight + viewtop - 1; maxzoom = 1.0f; weaponnum = g_Vars.currentplayer->hands[0].gset.weaponnum; - playercount = PLAYERCOUNT(); + playercount = LOCALPLAYERCOUNT(); texty -= 17; @@ -374,7 +374,7 @@ s32 hudmsg0f0ddb1c(s32 *arg0, s32 arg1) *arg0 = 24; - if (PLAYERCOUNT() == 2 + if (LOCALPLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL && (!g_InCutscene || g_MainIsEndscreen)) { result -= *arg0 * 2 / 3; @@ -388,7 +388,7 @@ s32 hudmsg0f0ddb1c(s32 *arg0, s32 arg1) result = result + viewwidth - *arg0 - arg1 - 11; - if (PLAYERCOUNT() == 1 || (PLAYERCOUNT() == 2 && g_InCutscene && !g_MainIsEndscreen)) { + if (LOCALPLAYERCOUNT() == 1 || (LOCALPLAYERCOUNT() == 2 && g_InCutscene && !g_MainIsEndscreen)) { result -= 16; #if VERSION < VERSION_JPN_FINAL @@ -403,7 +403,7 @@ s32 hudmsg0f0ddb1c(s32 *arg0, s32 arg1) *arg0 = 24; - if (PLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) { + if (LOCALPLAYERCOUNT() == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) { result -= *arg0 * 2 / 3; if (g_Vars.currentplayernum == 0) { @@ -415,7 +415,7 @@ s32 hudmsg0f0ddb1c(s32 *arg0, s32 arg1) result = result + viewwidth - *arg0 - arg1 - 11; - if (PLAYERCOUNT() == 1) { + if (LOCALPLAYERCOUNT() == 1) { result -= 16; } #endif @@ -857,7 +857,7 @@ void hudmsgCalculatePosition(struct hudmessage *msg) #if VERSION >= VERSION_NTSC_1_0 s32 offset = (msg->alignh == HUDMSGALIGN_XMIDDLE) ? 10 : 0; - if (PLAYERCOUNT() >= 3) { + if (LOCALPLAYERCOUNT() >= 3) { viewwidth -= offset; if (g_Vars.currentplayernum == 0 || g_Vars.currentplayernum == 2) { @@ -865,7 +865,7 @@ void hudmsgCalculatePosition(struct hudmessage *msg) } } - if (PLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) { + if (LOCALPLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB())) { #if VERSION >= VERSION_PAL_FINAL if (!g_InCutscene || g_MainIsEndscreen) #endif @@ -888,7 +888,7 @@ void hudmsgCalculatePosition(struct hudmessage *msg) x = viewleft + v0 + msg->xmargin + 3; - if (PLAYERCOUNT() == 2 + if (LOCALPLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || IS4MB()) && (!g_InCutscene || g_MainIsEndscreen)) { if (IS4MB()) { @@ -904,7 +904,7 @@ void hudmsgCalculatePosition(struct hudmessage *msg) x += 4; } } - } else if (PLAYERCOUNT() >= 3) { + } else if (LOCALPLAYERCOUNT() >= 3) { if ((msg->playernum % 2) == 0) { x--; } else { @@ -933,13 +933,13 @@ void hudmsgCalculatePosition(struct hudmessage *msg) case HUDMSGALIGN_BOTTOM: y = viewtop + viewheight - msg->height - msg->ymargin - 14; - if (PLAYERCOUNT() == 2 && (g_InCutscene == 0 || g_MainIsEndscreen)) { + if (LOCALPLAYERCOUNT() == 2 && (g_InCutscene == 0 || g_MainIsEndscreen)) { if (IS4MB() || (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && msg->playernum == 0)) { y += 8; } else { y += 3; } - } else if (PLAYERCOUNT() >= 3) { + } else if (LOCALPLAYERCOUNT() >= 3) { if (msg->playernum <= 1) { y += 8; } else { @@ -1284,7 +1284,7 @@ void hudmsgsTick(void) && msg->type != HUDMSGTYPE_CUTSCENESUBTITLE && msg->type != HUDMSGTYPE_INGAMESUBTITLE #endif - && PLAYERCOUNT() == 1) { + && LOCALPLAYERCOUNT() == 1) { #if VERSION >= VERSION_NTSC_1_0 sndStart(var80095200, SFX_HUDMSG, NULL, -1, -1, -1, -1, -1); #else @@ -1383,7 +1383,7 @@ Gfx *hudmsgsRender(Gfx *gdl) s32 timerthing = 255; s32 spdc = true; #ifndef PLATFORM_N64 - const s32 playercount = PLAYERCOUNT(); + const s32 playercount = LOCALPLAYERCOUNT(); #endif #if PAL diff --git a/src/game/lv.c b/src/game/lv.c index 1c746b77d..0b63f8142 100644 --- a/src/game/lv.c +++ b/src/game/lv.c @@ -96,6 +96,10 @@ #include "lib/vars.h" #include "lib/vi.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#include "net/netmsg.h" +#endif struct sndstate *g_MiscSfxAudioHandles[3]; u32 var800aa5bc; @@ -360,6 +364,11 @@ void lvReset(s32 stagenum) g_Vars.playerstats[i].kills[j] = 0; } } + +#ifndef PLATFORM_N64 + // if we're a server, signal to clients that the level is changing + netServerStageStart(); +#endif } mpSetDefaultNamesIfEmpty(); @@ -1083,6 +1092,12 @@ Gfx *lvRender(Gfx *gdl) struct player *player; struct chrdata *chr; +#ifndef PLATFORM_N64 + if (g_NetMode) { + forcesingleplayer = true; + } +#endif + playercount = forcesingleplayer ? 1 : PLAYERCOUNT(); gSPClipRatio(gdl++, FRUSTRATIO_2); diff --git a/src/game/mainmenu.c b/src/game/mainmenu.c index 663e6160e..3c28cb34a 100644 --- a/src/game/mainmenu.c +++ b/src/game/mainmenu.c @@ -33,6 +33,9 @@ #include "lib/str.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif u8 g_InventoryWeapon; @@ -41,6 +44,7 @@ struct menudialogdef g_CiControlPlayer2MenuDialog; struct menudialogdef g_CinemaMenuDialog; #ifndef PLATFORM_N64 extern struct menudialogdef g_ExtendedMenuDialog; +extern struct menudialogdef g_NetMenuDialog; #endif char *menuTextCurrentStageName(struct menuitem *item) @@ -4865,6 +4869,11 @@ MenuDialogHandlerResult menudialogMainMenu(s32 operation, struct menudialogdef * switch (operation) { case MENUOP_OPEN: g_Menus[g_MpPlayerNum].main.unke2c = 0; +#ifndef PLATFORM_N64 + if (g_NetMode) { + netDisconnect(); + } +#endif break; case MENUOP_TICK: if (g_Menus[g_MpPlayerNum].curdialog && @@ -4942,24 +4951,36 @@ struct menuitem g_MainMenuMenuItems[] = { 0x00000005, menuhandlerMainMenuCounterOperative, }, +#ifndef PLATFORM_N64 + { + MENUITEMTYPE_SELECTABLE, + 4, + MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_BIGFONT | MENUITEMFLAG_LITERAL_TEXT, + (uintptr_t)"Network Game", + 0x00000006, + (void *)&g_NetMenuDialog, + }, +#endif { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_BIGFONT, L_OPTIONS_187, // "Change Agent..." - 0x00000006, + 0x00000007, (void *)&g_ChangeAgentMenuDialog, }, +/* #ifndef PLATFORM_N64 { MENUITEMTYPE_SELECTABLE, 0, MENUITEMFLAG_SELECTABLE_OPENSDIALOG | MENUITEMFLAG_BIGFONT | MENUITEMFLAG_LITERAL_TEXT, (uintptr_t)"Exit Game", - 0x00000007, + 0x00000008, (void *)&g_ExitGameMenuDialog, }, #endif +*/ { MENUITEMTYPE_END }, }; diff --git a/src/game/menu.c b/src/game/menu.c index b63ad6315..60ede6647 100644 --- a/src/game/menu.c +++ b/src/game/menu.c @@ -50,6 +50,7 @@ #include "types.h" #ifndef PLATFORM_N64 #include "video.h" +#include "net/net.h" #define BLUR_OFS 10 #else #define BLUR_OFS 30 @@ -5375,6 +5376,19 @@ Gfx *menuRender(Gfx *gdl) gdl = text0f153628(gdl); +#ifndef PLATFORM_N64 + if (g_NetMode) { + if (g_NetMode == NETMODE_SERVER) { + sprintf(text, "Server: %d/%d", g_NetNumClients, g_NetMaxClients); + } else { + sprintf(text, "Client: ID %u", g_NetLocalClient->id); + } + x = viewleft + 2; + y = viewtop + 2; + colour = 0x5070ffff; + gdl = textRenderProjected(gdl, &x, &y, text, g_CharsHandelGothicSm, g_FontHandelGothicSm, colour, viGetWidth(), viGetHeight(), 0, 0); + } else +#endif for (i = 0; i < MAX_PLAYERS; i++) { // Figure out what text will be displayed. The text calculated // here is for measuring purposes only and isn't rendered. diff --git a/src/game/menuitem.c b/src/game/menuitem.c index 11e53274d..e42b04fbc 100644 --- a/src/game/menuitem.c +++ b/src/game/menuitem.c @@ -4117,7 +4117,7 @@ Gfx *menuitemControllerRenderText(Gfx *gdl, s32 curmode, struct menurendercontex // during development the second player in the 2.x styles had to // choose their control style separately to player 1, in which case // there would have been 2.5, 2.6, 2.7 and 2.8 for player 2. - if (curmode > CONTROLMODE_24 && curmode != CONTROLMODE_PC) { + if (curmode > CONTROLMODE_24 && curmode < CONTROLMODE_PC) { if (textnum == menuitemControllerGetButtonAction(prevmode + 4, i)) { colour = labelcolour; } @@ -4202,7 +4202,7 @@ Gfx *menuitemControllerRender(Gfx *gdl, struct menurendercontext *context) data->prevmode = -1; } } else { - if (g_Menus[g_MpPlayerNum].main.controlmode >= CONTROLMODE_21 && g_Menus[g_MpPlayerNum].main.controlmode != CONTROLMODE_PC) { + if (g_Menus[g_MpPlayerNum].main.controlmode >= CONTROLMODE_21 && g_Menus[g_MpPlayerNum].main.controlmode < CONTROLMODE_PC) { data->controlgroup = 1; data->contfadetimer = 0; data->prevmode = -1; diff --git a/src/game/menutick.c b/src/game/menutick.c index 5a795da9d..ee4c8b5e9 100644 --- a/src/game/menutick.c +++ b/src/game/menutick.c @@ -27,6 +27,9 @@ #include "lib/snd.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif u8 g_FileState = 0; u8 var80062944 = 0; @@ -355,6 +358,12 @@ void menuTick(void) canjoin = true; } +#ifndef PLATFORM_N64 + if (g_NetMode) { + canjoin = false; + } +#endif + if (g_BossFile.locktype == MPLOCKTYPE_CHALLENGE) { g_PlayerConfigsArray[i].base.team = 0; } diff --git a/src/game/mplayer/mplayer.c b/src/game/mplayer/mplayer.c index d91f60a4e..5dedcac62 100644 --- a/src/game/mplayer/mplayer.c +++ b/src/game/mplayer/mplayer.c @@ -28,6 +28,9 @@ #include "lib/lib_317f0.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif // bss struct chrdata *g_MpAllChrPtrs[MAX_MPCHRS]; @@ -176,14 +179,29 @@ void mpStartMatch(void) s32 numplayers = 0; s32 stagenum; - mpConfigureQuickTeamSimulants(); - - if (!challengeIsFeatureUnlocked(MPFEATURE_ONEHITKILLS)) { - g_MpSetup.options &= ~MPOPTION_ONEHITKILLS; +#ifndef PLATFORM_N64 + if (g_NetMode == NETMODE_SERVER) { + g_MpSetup.chrslots &= ~0x000f; + g_MpSetup.chrslots |= 1; + for (s32 i = 1; i < g_NetMaxClients; ++i) { + if (g_NetClients[i].state >= CLSTATE_LOBBY) { + g_MpSetup.chrslots |= (1 << i); + } + } } - if (!challengeIsFeatureUnlocked(MPFEATURE_SLOWMOTION)) { - g_MpSetup.options &= ~(MPOPTION_SLOWMOTION_ON | MPOPTION_SLOWMOTION_SMART); + if (g_NetMode != NETMODE_CLIENT) +#endif + { + mpConfigureQuickTeamSimulants(); + + if (!challengeIsFeatureUnlocked(MPFEATURE_ONEHITKILLS)) { + g_MpSetup.options &= ~MPOPTION_ONEHITKILLS; + } + + if (!challengeIsFeatureUnlocked(MPFEATURE_SLOWMOTION)) { + g_MpSetup.options &= ~(MPOPTION_SLOWMOTION_ON | MPOPTION_SLOWMOTION_SMART); + } } for (i = 0; i < MAX_PLAYERS; i++) { diff --git a/src/game/mplayer/scenarios.c b/src/game/mplayer/scenarios.c index e2c144d21..01aa36af6 100644 --- a/src/game/mplayer/scenarios.c +++ b/src/game/mplayer/scenarios.c @@ -1037,7 +1037,7 @@ struct prop *scenarioCreateObj(s32 modelnum, s16 padnum, f32 arg2, u32 flags, u3 */ void scenarioCreateHudmsg(s32 playernum, char *message) { - if (playernum >= 0 && playernum < PLAYERCOUNT()) { + if (playernum >= 0 && playernum < LOCALPLAYERCOUNT()) { s32 prevplayernum = g_Vars.currentplayernum; setCurrentPlayerNum(playernum); diff --git a/src/game/music.c b/src/game/music.c index a3c3840f0..91b9967ed 100644 --- a/src/game/music.c +++ b/src/game/music.c @@ -343,7 +343,7 @@ bool musicIsAnyPlayerInAmbientRoom(void) return false; } - for (i = 0; i < PLAYERCOUNT(); i++) { + for (i = 0; i < LOCALPLAYERCOUNT(); i++) { if (g_Vars.players[i]->prop && g_Vars.players[i]->prop->rooms && g_Vars.players[i]->prop->rooms[0] != -1) { diff --git a/src/game/nbomb.c b/src/game/nbomb.c index b5f4fa72b..add22b7d9 100644 --- a/src/game/nbomb.c +++ b/src/game/nbomb.c @@ -319,7 +319,7 @@ Gfx *nbombCreateGdl(void) Gfx *gdl; s32 index = 0; - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { index = 1; } diff --git a/src/game/player.c b/src/game/player.c index 3ae336630..5ae586ffa 100644 --- a/src/game/player.c +++ b/src/game/player.c @@ -2815,6 +2815,12 @@ s16 playerGetFbHeight(void) #if VERSION >= VERSION_NTSC_1_0 bool playerHasSharedViewport(void) { +#ifndef PLATFORM_N64 + if (g_NetMode) { + return true; + } +#endif + if ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) && menuGetRoot() == MENUROOT_MPENDSCREEN && var8009dfc0 == 0) { @@ -2835,14 +2841,14 @@ s16 playerGetViewportWidth(void) if ((!g_InCutscene || g_MainIsEndscreen) && menuGetRoot() != MENUROOT_COOPCONTINUE) #endif { - if (PLAYERCOUNT() >= 3) { + if (LOCALPLAYERCOUNT() >= 3) { // 3/4 players width = g_ViModes[g_ViRes].width / 2; if (g_Vars.currentplayernum == 0 || g_Vars.currentplayernum == 2) { width--; } - } else if (PLAYERCOUNT() == 2) { + } else if (LOCALPLAYERCOUNT() == 2) { if (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || g_Vars.fourmeg2player) { // 2 players vsplit width = g_ViModes[g_ViRes].width / 2; @@ -2875,7 +2881,7 @@ s16 playerGetViewportLeft(void) #endif s16 left; - if (PLAYERCOUNT() >= 3 && something != 0) { + if (LOCALPLAYERCOUNT() >= 3 && something != 0) { if (g_Vars.currentplayernum == 1 || g_Vars.currentplayernum == 3) { // 3/4 players - right side left = g_ViModes[g_ViRes].width / 2 + g_ViModes[g_ViRes].fbwidth - g_ViModes[g_ViRes].width; @@ -2883,7 +2889,7 @@ s16 playerGetViewportLeft(void) // 3/4 players - left side left = g_ViModes[g_ViRes].fbwidth - g_ViModes[g_ViRes].width; } - } else if (PLAYERCOUNT() == 2 && something != 0) { + } else if (LOCALPLAYERCOUNT() == 2 && something != 0) { if (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || g_Vars.fourmeg2player) { if (g_Vars.currentplayernum == 1) { // 2 players vsplit - right side @@ -2908,7 +2914,7 @@ s16 playerGetViewportHeight(void) { s16 height; - if (PLAYERCOUNT() >= 2 + if (LOCALPLAYERCOUNT() >= 2 #if VERSION >= VERSION_NTSC_1_0 && !playerHasSharedViewport() #else @@ -2923,7 +2929,7 @@ s16 playerGetViewportHeight(void) height = tmp / 2; } - if (PLAYERCOUNT() == 2) { + if (LOCALPLAYERCOUNT() == 2) { if (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) { height = tmp; } else if (g_Vars.currentplayernum == 0 && IS8MB()) { @@ -2959,7 +2965,7 @@ s16 playerGetViewportTop(void) { s16 top; - if (PLAYERCOUNT() >= 2 + if (LOCALPLAYERCOUNT() >= 2 #if VERSION >= VERSION_NTSC_1_0 && !playerHasSharedViewport() #else @@ -2970,12 +2976,12 @@ s16 playerGetViewportTop(void) top = g_ViModes[g_ViRes].fulltop; #if VERSION >= VERSION_NTSC_1_0 - if (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL || PLAYERCOUNT() != 2) + if (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL || LOCALPLAYERCOUNT() != 2) #else if (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL) #endif { - if (PLAYERCOUNT() == 2 + if (LOCALPLAYERCOUNT() == 2 && g_Vars.currentplayernum == 1 && optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && !g_Vars.fourmeg2player) { diff --git a/src/game/playermgr.c b/src/game/playermgr.c index 01d47c65e..ab1f006b7 100644 --- a/src/game/playermgr.c +++ b/src/game/playermgr.c @@ -10,6 +10,9 @@ #include "lib/rng.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif void playermgrInit(void) { @@ -60,6 +63,12 @@ void playermgrAllocatePlayers(s32 count) playermgrAllocatePlayer(i); } +#ifndef PLATFORM_N64 + if (g_NetMode) { + netPlayersAllocate(); + } +#endif + setCurrentPlayerNum(0); g_Vars.bond = g_Vars.players[g_Vars.bondplayernum]; @@ -643,6 +652,12 @@ void playermgrAllocatePlayer(s32 index) g_Vars.players[index]->disguised = false; g_Vars.players[index]->dostartnewlife = false; +#ifndef PLATFORM_N64 + g_Vars.players[index]->client = NULL; + g_Vars.players[index]->ucmd = (g_NetMode == NETMODE_SERVER) ? UCMD_FL_FORCEMASK : 0; + g_Vars.players[index]->isremote = false; +#endif + g_Vars.bondvisible = true; g_Vars.bondcollisions = true; } @@ -806,6 +821,14 @@ void playermgrShuffle(void) g_Vars.playerorder[i] = i; } +#ifndef PLATFORM_N64 + if (g_NetMode) { + // don't shuffle in netgames + // why is this a thing anyway? + return; + } +#endif + // Randomly swap numbers with later elements for (i = 0; i < MAX_PLAYERS - 1; i++) { s32 otherindex = random() % (MAX_PLAYERS - i); diff --git a/src/game/prop.c b/src/game/prop.c index 786a64db1..d775f5ac8 100644 --- a/src/game/prop.c +++ b/src/game/prop.c @@ -39,6 +39,9 @@ #include "lib/lib_317f0.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif s16 *g_RoomPropListChunkIndexes; struct roomproplistchunk *g_RoomPropListChunks; @@ -1500,6 +1503,13 @@ bool currentPlayerInteract(bool eyespy) prop = propFindForInteract(eyespy); if (prop) { +#ifndef PLATFORM_N64 + // if we aren't the authority, don't do anything + if (g_NetMode == NETMODE_CLIENT) { + return false; + } +#endif + switch (prop->type) { case PROPTYPE_OBJ: case PROPTYPE_WEAPON: diff --git a/src/game/propobj.c b/src/game/propobj.c index 3ba8b6dab..297edcbbd 100644 --- a/src/game/propobj.c +++ b/src/game/propobj.c @@ -77,6 +77,9 @@ #include "textures.h" #include "types.h" #include "string.h" +#ifndef PLATFORM_N64 +#include "net/net.h" +#endif void rng2SetSeed(u32 seed); @@ -15354,6 +15357,13 @@ void objTakeGunfire(struct defaultobj *obj, f32 damage, struct coord *pos, s32 w void objDamage(struct defaultobj *obj, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum) { +#ifndef PLATFORM_N64 + // if we aren't the authority, don't do anything + if (g_NetMode == NETMODE_CLIENT) { + return; + } +#endif + // Store the attacker playernum into the object's "hidden" field #if VERSION >= VERSION_NTSC_1_0 // ...but not for deployed laptop guns in multiplayer, because those bits @@ -17472,6 +17482,13 @@ s32 objTestForPickup(struct prop *prop) return TICKOP_NONE; } +#ifndef PLATFORM_N64 + // if we aren't the authority, don't do anything + if (g_NetMode == NETMODE_CLIENT) { + return TICKOP_NONE; + } +#endif + if (func0f085194(obj) && obj->type != OBJTYPE_HAT) { if (obj->flags & OBJFLAG_UNCOLLECTABLE) { return TICKOP_NONE; @@ -20943,7 +20960,7 @@ Gfx *countdownTimerRender(Gfx *gdl) u32 stack; s32 viewright = viGetViewLeft() + (viGetViewWidth() >> 1); s32 y = viGetViewTop() + viGetViewHeight() - 18; - s32 playercount = PLAYERCOUNT(); + s32 playercount = LOCALPLAYERCOUNT(); char *fmt = ":\n"; if (playercount == 2) { diff --git a/src/game/propsnd.c b/src/game/propsnd.c index 6cd41bd3c..9e6ab03ae 100644 --- a/src/game/propsnd.c +++ b/src/game/propsnd.c @@ -338,7 +338,7 @@ void psTickChannel(s32 channelnum) if (lvIsPaused() || (mpIsPaused() && (channel->flags2 & PSFLAG2_MPPAUSABLE)) - || (mpIsPaused() && PLAYERCOUNT() == 1)) { + || (mpIsPaused() && LOCALPLAYERCOUNT() == 1)) { channel->currentvol = -1; newvol = 0; } @@ -1171,7 +1171,7 @@ s32 psCalculateVol(struct coord *pos, f32 dist1, f32 dist2, f32 dist3, RoomNum * } // Figure out which player is closest and store their distance in playerdist - for (i = 0; i < PLAYERCOUNT(); i++) { + for (i = 0; i < LOCALPLAYERCOUNT(); i++) { struct player *player = g_Vars.players[i]; s32 camroom; @@ -1283,7 +1283,7 @@ s32 psCalculatePan2(struct coord *pos, s32 arg1, f32 arg2, struct pschannel *cha s32 degrees; f32 f2; - if (PLAYERCOUNT() < 2) { + if (LOCALPLAYERCOUNT() < 2) { struct coord *campos = &g_Vars.currentplayer->cam_pos; f32 sp3c; f32 sp38; diff --git a/src/game/radar.c b/src/game/radar.c index e963747ba..b3366a044 100644 --- a/src/game/radar.c +++ b/src/game/radar.c @@ -252,7 +252,7 @@ Gfx *radarRender(Gfx *gdl) tconfig = g_TexRadarConfigs; playernum = g_Vars.currentplayernum; - playercount = PLAYERCOUNT(); + playercount = LOCALPLAYERCOUNT(); if (g_Vars.mplayerisrunning) { if (g_Vars.normmplayerisrunning && (g_MpSetup.options & MPOPTION_NORADAR)) { @@ -331,7 +331,7 @@ Gfx *radarRender(Gfx *gdl) gdl = func0f153134(gdl); // Draw dots for human players - for (i = 0; i < playercount; i++) { + for (i = 0; i < PLAYERCOUNT(); i++) { if (i != playernum) { if (g_Vars.players[i]->isdead == false && (g_Vars.players[i]->prop->chr->hidden & CHRHFLAG_CLOAKED) == 0 diff --git a/src/game/sight.c b/src/game/sight.c index 6dc76bd64..c7bfd5442 100644 --- a/src/game/sight.c +++ b/src/game/sight.c @@ -467,7 +467,7 @@ Gfx *sightDrawAimer(Gfx *gdl, s32 x, s32 y, s32 radius, s32 cornergap, u32 colou #endif // Draw the lines that span most of the viewport - if (PLAYERCOUNT() == 1) { + if (LOCALPLAYERCOUNT() == 1) { gDPHudRectangle(gdl++, viewleft + 48, y, x - radius + 2, y); gDPHudRectangle(gdl++, x + radius - 2, y, viewright - 49, y); gDPHudRectangle(gdl++, x, viewtop + 10, x, y - radius + 2); @@ -1262,7 +1262,7 @@ Gfx *sightDrawZoom(Gfx *gdl, bool sighton) cornerheight *= frac; } - if (PLAYERCOUNT() >= 2) { + if (LOCALPLAYERCOUNT() >= 2) { cornerheight *= 2; } @@ -1522,7 +1522,7 @@ Gfx *sightDraw(Gfx *gdl, bool sighton, s32 sight) } #endif - if (PLAYERCOUNT() >= 2 && g_Vars.coopplayernum < 0 && g_Vars.antiplayernum < 0) { + if (LOCALPLAYERCOUNT() >= 2 && g_Vars.coopplayernum < 0 && g_Vars.antiplayernum < 0) { sight = SIGHT_DEFAULT; } diff --git a/src/game/zbuf.c b/src/game/zbuf.c index 67f7a026c..a97a56adc 100644 --- a/src/game/zbuf.c +++ b/src/game/zbuf.c @@ -53,7 +53,7 @@ void zbufAllocate(void) if (IS4MB()) { g_ZbufWidth = MAX(320, FBALLOC_WIDTH_LO); - if (g_Vars.normmplayerisrunning && PLAYERCOUNT() >= 2) { + if (g_Vars.normmplayerisrunning && LOCALPLAYERCOUNT() >= 2) { g_ZbufHeight = MAX(220, FBALLOC_HEIGHT_LO) / 2; } else { g_ZbufHeight = MAX(220, FBALLOC_HEIGHT_LO); @@ -61,7 +61,7 @@ void zbufAllocate(void) } else { g_ZbufWidth = MAX(640, FBALLOC_WIDTH_HI); - if (g_Vars.normmplayerisrunning && PLAYERCOUNT() >= 2) { + if (g_Vars.normmplayerisrunning && LOCALPLAYERCOUNT() >= 2) { g_ZbufHeight = MAX(220, FBALLOC_HEIGHT_HI); } else { g_ZbufHeight = MAX(220, FBALLOC_HEIGHT_HI); @@ -101,7 +101,7 @@ Gfx *zbufConfigureRdp(Gfx *gdl) uintptr_t addr; if (g_Vars.normmplayerisrunning - && (g_Vars.currentplayernum >= 2 || (PLAYERCOUNT() == 2 && g_Vars.currentplayernum == 1))) { + && (g_Vars.currentplayernum >= 2 || (LOCALPLAYERCOUNT() == 2 && g_Vars.currentplayernum == 1))) { subamount = playerGetFbWidth() * playerGetFbHeight(); if (IS4MB() || optionsGetScreenSplit() == SCREENSPLIT_VERTICAL) { @@ -137,7 +137,7 @@ Gfx *zbufClear(Gfx *gdl) gDPSetFillColor(gdl++, 0xfffcfffc); gDPSetScissorFrac(gdl++, G_SC_NON_INTERLACE, 0, 0, playerGetFbWidth() * 4.0f, playerGetFbHeight() * 4.0f); - if (PLAYERCOUNT() <= 2) { + if (LOCALPLAYERCOUNT() <= 2) { left = 0; right = playerGetFbWidth() - 1; } else if (g_Vars.currentplayernum == 0 || g_Vars.currentplayernum == 2) { diff --git a/src/include/constants.h b/src/include/constants.h index 943f172d3..2ebee8874 100644 --- a/src/include/constants.h +++ b/src/include/constants.h @@ -28,6 +28,7 @@ #define MAX_ROOMPROPLISTCHUNKS 256 #define MAX_SQUADRONS 16 #define MAX_TEAMS 8 +#define MAX_PLAYERNAME 15 #define NUM_BOTDIFFS 6 #define NUM_CYCLEABLE_WEAPONS 45 @@ -695,6 +696,7 @@ #define CONTROLMODE_23 6 #define CONTROLMODE_24 7 #define CONTROLMODE_PC 8 // "pc port" controls; enabled in the .ini file +#define CONTROLMODE_NA 9 // dummy controls for remote players #define COUNTDOWNTIMERREASON_AI 0x01 #define COUNTDOWNTIMERREASON_NOCONTROL 0x10 @@ -4682,6 +4684,8 @@ enum weaponnum { #define BUTTON_ACCEPT BUTTON_ACCEPT_WPNFORWARD #define BUTTON_WPNFORWARD BUTTON_ACCEPT_WPNFORWARD +#define LOCALPLAYERCOUNT() PLAYERCOUNT() + #else // xbla behavior @@ -4713,6 +4717,12 @@ enum weaponnum { #define CROUCHMODE_TOGGLE 2 // press the crouch buttons to toggle stance #define CROUCHMODE_TOGGLE_ANALOG (CROUCHMODE_ANALOG | CROUCHMODE_TOGGLE) +#define NETMODE_NONE 0 +#define NETMODE_SERVER 1 +#define NETMODE_CLIENT 2 + +#define LOCALPLAYERCOUNT() (g_NetMode ? 1 : PLAYERCOUNT()) + #endif #endif diff --git a/src/include/data.h b/src/include/data.h index 2f0afbe88..b6ddd6691 100644 --- a/src/include/data.h +++ b/src/include/data.h @@ -548,6 +548,7 @@ extern s32 g_BlurFbCapTimer; extern bool g_BlurFbDirty; extern s32 g_TickRateDiv; extern s32 g_MusicDisableMpDeath; +extern s32 g_NetMode; #define PLAYER_EXTCFG() g_PlayerExtCfg[g_Vars.currentplayerstats->mpindex & 3] #define PLAYER_DEFAULT_FOV (PLAYER_EXTCFG().fovy) diff --git a/src/include/game/chraction.h b/src/include/game/chraction.h index ec85d4269..d6b9c9292 100644 --- a/src/include/game/chraction.h +++ b/src/include/game/chraction.h @@ -203,6 +203,7 @@ bool chrAdjustPosForSpawn(f32 chrradius, struct coord *pos, RoomNum *rooms, f32 struct prop *chrSpawnAtCoord(s32 body, s32 head, struct coord *pos, RoomNum *room, f32 angle, u8 *ailist, u32 spawnflags); bool chrIsPropPresetBlockingSightToTarget(struct chrdata *chr); +bool chrSetPos(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 angle, bool findground); bool chrMoveToPos(struct chrdata *chr, struct coord *pos, RoomNum *room, f32 angle, bool ignorebg); bool chrCheckCoverOutOfSight(struct chrdata *chr, s32 covernum, bool soft); s32 chrAssignCoverByCriteria(struct chrdata *chr, u16 arg1, s32 arg2); diff --git a/src/include/types.h b/src/include/types.h index 67a8eddaf..58c735156 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -2820,6 +2820,9 @@ struct player { /*0x1c72*/ s16 amdowntime; // for alt-modes, used like invdowntime and amdowntime #ifndef PLATFORM_N64 /*0x1c74*/ f32 swivelpos[2]; + /*0x1c84*/ u32 ucmd; + /*0x1c88*/ bool isremote; + /*0x1c8c*/ struct netclient *client; #endif }; @@ -4002,7 +4005,7 @@ struct gamefile { }; struct mpchrconfig { - /*0x00*/ char name[15]; + /*0x00*/ char name[MAX_PLAYERNAME]; /*0x0f*/ u8 mpheadnum; /*0x10*/ u8 mpbodynum; /*0x11*/ u8 team; @@ -4046,6 +4049,9 @@ struct mpplayerconfig { /*0x96*/ u8 newtitle; /*0x97*/ u8 gunfuncs[6]; /*0x9d*/ u8 handicap; +#ifndef PLATFORM_N64 + /*0x9e*/ struct netclient *client; +#endif }; struct mpbotconfig { diff --git a/src/lib/vi.c b/src/lib/vi.c index f76788821..9ad662b5b 100644 --- a/src/lib/vi.c +++ b/src/lib/vi.c @@ -204,7 +204,7 @@ void viReset(s32 stagenum) ? FBALLOC_WIDTH_LO * FBALLOC_HEIGHT_LO * NUM_FRAMEBUFFERS : FBALLOC_WIDTH_HI * FBALLOC_HEIGHT_HI * NUM_FRAMEBUFFERS; - if (IS4MB() && PLAYERCOUNT() == 2) { + if (IS4MB() && LOCALPLAYERCOUNT() == 2) { // 4MB 2-player: The viewports are 110px tall #if VERSION >= VERSION_NTSC_1_0 fbsize = FBALLOC_WIDTH_LO * (FBALLOC_HEIGHT_LO / 2) * NUM_FRAMEBUFFERS; @@ -212,7 +212,7 @@ void viReset(s32 stagenum) fbsize = SCREEN_320 * (SCREEN_240 / 2) * NUM_FRAMEBUFFERS; #endif g_Vars.fourmeg2player = true; - } else if ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) && PLAYERCOUNT() == 2) { + } else if ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) && LOCALPLAYERCOUNT() == 2) { // PAL is using its correct size fbsize = SCREEN_WIDTH_LO * SCREEN_HEIGHT_LO * NUM_FRAMEBUFFERS; } @@ -676,11 +676,11 @@ Gfx *viRenderViewportEdges(Gfx *gdl) gDPSetFillColor(gdl++, GPACK_RGBA5551(0, 0, 0, 1) << 16 | GPACK_RGBA5551(0, 0, 0, 1)); #if VERSION >= VERSION_NTSC_1_0 - if (PLAYERCOUNT() == 1 + if (LOCALPLAYERCOUNT() == 1 || ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) && playerHasSharedViewport() && g_Vars.currentplayernum == 0)) #else - if (PLAYERCOUNT() == 1 + if (LOCALPLAYERCOUNT() == 1 || ((g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) && ( (g_InCutscene && !g_MainIsEndscreen) || menuGetRoot() == MENUROOT_COOPCONTINUE @@ -707,10 +707,10 @@ Gfx *viRenderViewportEdges(Gfx *gdl) s32 bottomplayernum = 0; s32 tmpplayernum = 0; - if (PLAYERCOUNT() == 2) { + if (LOCALPLAYERCOUNT() == 2) { bottomplayernum = 1; tmpplayernum = 1; - } else if (PLAYERCOUNT() >= 3) { + } else if (LOCALPLAYERCOUNT() >= 3) { bottomplayernum = 2; tmpplayernum = 2; } @@ -735,9 +735,9 @@ Gfx *viRenderViewportEdges(Gfx *gdl) viGetWidth() - 1, g_Vars.players[tmpplayernum]->viewtop - 1); gDPPipeSync(gdl++); - if (PLAYERCOUNT() >= 3 || - (PLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || g_Vars.fourmeg2player))) { - if (PLAYERCOUNT() == 2) { + if (LOCALPLAYERCOUNT() >= 3 || + (LOCALPLAYERCOUNT() == 2 && (optionsGetScreenSplit() == SCREENSPLIT_VERTICAL || g_Vars.fourmeg2player))) { + if (LOCALPLAYERCOUNT() == 2) { tmpplayernum = 0; } @@ -748,7 +748,7 @@ Gfx *viRenderViewportEdges(Gfx *gdl) gDPPipeSync(gdl++); } - if (PLAYERCOUNT() == 3) { + if (LOCALPLAYERCOUNT() == 3) { // Blank square in P4 spot gDPFillRectangle(gdl++, g_Vars.players[tmpplayernum]->viewleft + g_Vars.players[tmpplayernum]->viewwidth + 1, g_Vars.players[tmpplayernum]->viewtop,