From 3f367c22eeb1a52ea38139e4acc0d6c28330a5ef Mon Sep 17 00:00:00 2001 From: Ziek Date: Sun, 1 Feb 2015 16:43:34 -0800 Subject: [PATCH 1/8] Adding enet library for future replacement of tcp --- Externals/enet/CMakeLists.txt | 15 + Externals/enet/LICENSE | 7 + Externals/enet/README | 15 + Externals/enet/callbacks.c | 53 + Externals/enet/callbacks.h | 27 + Externals/enet/compress.c | 654 +++++++++++ Externals/enet/enet.h | 592 ++++++++++ Externals/enet/enet.vcxproj | 78 ++ Externals/enet/git-revision | 1 + Externals/enet/host.c | 492 +++++++++ Externals/enet/list.c | 75 ++ Externals/enet/list.h | 43 + Externals/enet/packet.c | 165 +++ Externals/enet/peer.c | 1004 +++++++++++++++++ Externals/enet/protocol.c | 1907 +++++++++++++++++++++++++++++++++ Externals/enet/protocol.h | 198 ++++ Externals/enet/time.h | 18 + Externals/enet/types.h | 13 + Externals/enet/unix.c | 557 ++++++++++ Externals/enet/unix.h | 47 + Externals/enet/update-enet.sh | 20 + Externals/enet/utility.h | 12 + Externals/enet/win32.c | 421 ++++++++ Externals/enet/win32.h | 57 + Source/dolphin-emu.sln | 227 +++- 25 files changed, 6697 insertions(+), 1 deletion(-) create mode 100644 Externals/enet/CMakeLists.txt create mode 100644 Externals/enet/LICENSE create mode 100644 Externals/enet/README create mode 100644 Externals/enet/callbacks.c create mode 100644 Externals/enet/callbacks.h create mode 100644 Externals/enet/compress.c create mode 100644 Externals/enet/enet.h create mode 100644 Externals/enet/enet.vcxproj create mode 100644 Externals/enet/git-revision create mode 100644 Externals/enet/host.c create mode 100644 Externals/enet/list.c create mode 100644 Externals/enet/list.h create mode 100644 Externals/enet/packet.c create mode 100644 Externals/enet/peer.c create mode 100644 Externals/enet/protocol.c create mode 100644 Externals/enet/protocol.h create mode 100644 Externals/enet/time.h create mode 100644 Externals/enet/types.h create mode 100644 Externals/enet/unix.c create mode 100644 Externals/enet/unix.h create mode 100644 Externals/enet/update-enet.sh create mode 100644 Externals/enet/utility.h create mode 100644 Externals/enet/win32.c create mode 100644 Externals/enet/win32.h diff --git a/Externals/enet/CMakeLists.txt b/Externals/enet/CMakeLists.txt new file mode 100644 index 0000000000..822060ef43 --- /dev/null +++ b/Externals/enet/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SRCS + callbacks.c + compress.c + host.c + list.c + packet.c + peer.c + protocol.c) +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(SRCS ${SRCS} win32.c) +else() + set(SRCS ${SRCS} unix.c) +endif() +add_definitions(-Wno-parentheses-equality -DHAS_SOCKLEN_T) +add_library(enet STATIC ${SRCS}) diff --git a/Externals/enet/LICENSE b/Externals/enet/LICENSE new file mode 100644 index 0000000000..ebb7cf4bae --- /dev/null +++ b/Externals/enet/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2002-2014 Lee Salzman + +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. diff --git a/Externals/enet/README b/Externals/enet/README new file mode 100644 index 0000000000..54b2d21304 --- /dev/null +++ b/Externals/enet/README @@ -0,0 +1,15 @@ +Please visit the ENet homepage at http://enet.bespin.org for installation +and usage instructions. + +If you obtained this package from github, the quick description on how to build +is: + +# Generate the build system. + +autoreconf -vfi + +# Compile and install the library. + +./configure && make && make install + + diff --git a/Externals/enet/callbacks.c b/Externals/enet/callbacks.c new file mode 100644 index 0000000000..b3990af1fb --- /dev/null +++ b/Externals/enet/callbacks.c @@ -0,0 +1,53 @@ +/** + @file callbacks.c + @brief ENet callback functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +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 -> no_memory != NULL) + callbacks.no_memory = inits -> no_memory; + + return enet_initialize (); +} + +ENetVersion +enet_linked_version (void) +{ + return ENET_VERSION; +} + +void * +enet_malloc (size_t size) +{ + void * memory = callbacks.malloc (size); + + if (memory == NULL) + callbacks.no_memory (); + + return memory; +} + +void +enet_free (void * memory) +{ + callbacks.free (memory); +} + diff --git a/Externals/enet/callbacks.h b/Externals/enet/callbacks.h new file mode 100644 index 0000000000..340a4a9896 --- /dev/null +++ b/Externals/enet/callbacks.h @@ -0,0 +1,27 @@ +/** + @file callbacks.h + @brief ENet callbacks +*/ +#ifndef __ENET_CALLBACKS_H__ +#define __ENET_CALLBACKS_H__ + +#include + +typedef struct _ENetCallbacks +{ + void * (ENET_CALLBACK * malloc) (size_t size); + void (ENET_CALLBACK * free) (void * memory); + void (ENET_CALLBACK * no_memory) (void); +} ENetCallbacks; + +/** @defgroup callbacks ENet internal callbacks + @{ + @ingroup private +*/ +extern void * enet_malloc (size_t); +extern void enet_free (void *); + +/** @} */ + +#endif /* __ENET_CALLBACKS_H__ */ + diff --git a/Externals/enet/compress.c b/Externals/enet/compress.c new file mode 100644 index 0000000000..784489a787 --- /dev/null +++ b/Externals/enet/compress.c @@ -0,0 +1,654 @@ +/** + @file compress.c + @brief An adaptive order-2 PPM range coder +*/ +#define ENET_BUILDING_LIB 1 +#include +#include "enet/enet.h" + +typedef struct _ENetSymbol +{ + /* binary indexed tree of symbols */ + enet_uint8 value; + enet_uint8 count; + enet_uint16 under; + enet_uint16 left, right; + + /* context defined by this symbol */ + enet_uint16 symbols; + enet_uint16 escapes; + enet_uint16 total; + enet_uint16 parent; +} ENetSymbol; + +/* adaptation constants tuned aggressively for small packet sizes rather than large file compression */ +enum +{ + ENET_RANGE_CODER_TOP = 1<<24, + ENET_RANGE_CODER_BOTTOM = 1<<16, + + ENET_CONTEXT_SYMBOL_DELTA = 3, + ENET_CONTEXT_SYMBOL_MINIMUM = 1, + ENET_CONTEXT_ESCAPE_MINIMUM = 1, + + ENET_SUBCONTEXT_ORDER = 2, + ENET_SUBCONTEXT_SYMBOL_DELTA = 2, + ENET_SUBCONTEXT_ESCAPE_DELTA = 5 +}; + +/* context exclusion roughly halves compression speed, so disable for now */ +#undef ENET_CONTEXT_EXCLUSION + +typedef struct _ENetRangeCoder +{ + /* only allocate enough symbols for reasonable MTUs, would need to be larger for large file compression */ + ENetSymbol symbols[4096]; +} ENetRangeCoder; + +void * +enet_range_coder_create (void) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) enet_malloc (sizeof (ENetRangeCoder)); + if (rangeCoder == NULL) + return NULL; + + return rangeCoder; +} + +void +enet_range_coder_destroy (void * context) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + if (rangeCoder == NULL) + return; + + enet_free (rangeCoder); +} + +#define ENET_SYMBOL_CREATE(symbol, value_, count_) \ +{ \ + symbol = & rangeCoder -> symbols [nextSymbol ++]; \ + symbol -> value = value_; \ + symbol -> count = count_; \ + symbol -> under = count_; \ + symbol -> left = 0; \ + symbol -> right = 0; \ + symbol -> symbols = 0; \ + symbol -> escapes = 0; \ + symbol -> total = 0; \ + symbol -> parent = 0; \ +} + +#define ENET_CONTEXT_CREATE(context, escapes_, minimum) \ +{ \ + ENET_SYMBOL_CREATE (context, 0, 0); \ + (context) -> escapes = escapes_; \ + (context) -> total = escapes_ + 256*minimum; \ + (context) -> symbols = 0; \ +} + +static enet_uint16 +enet_symbol_rescale (ENetSymbol * symbol) +{ + enet_uint16 total = 0; + for (;;) + { + symbol -> count -= symbol->count >> 1; + symbol -> under = symbol -> count; + if (symbol -> left) + symbol -> under += enet_symbol_rescale (symbol + symbol -> left); + total += symbol -> under; + if (! symbol -> right) break; + symbol += symbol -> right; + } + return total; +} + +#define ENET_CONTEXT_RESCALE(context, minimum) \ +{ \ + (context) -> total = (context) -> symbols ? enet_symbol_rescale ((context) + (context) -> symbols) : 0; \ + (context) -> escapes -= (context) -> escapes >> 1; \ + (context) -> total += (context) -> escapes + 256*minimum; \ +} + +#define ENET_RANGE_CODER_OUTPUT(value) \ +{ \ + if (outData >= outEnd) \ + return 0; \ + * outData ++ = value; \ +} + +#define ENET_RANGE_CODER_ENCODE(under, count, total) \ +{ \ + encodeRange /= (total); \ + encodeLow += (under) * encodeRange; \ + encodeRange *= (count); \ + for (;;) \ + { \ + if((encodeLow ^ (encodeLow + encodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(encodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + encodeRange = -encodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeRange <<= 8; \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FLUSH \ +{ \ + while (encodeLow) \ + { \ + ENET_RANGE_CODER_OUTPUT (encodeLow >> 24); \ + encodeLow <<= 8; \ + } \ +} + +#define ENET_RANGE_CODER_FREE_SYMBOLS \ +{ \ + if (nextSymbol >= sizeof (rangeCoder -> symbols) / sizeof (ENetSymbol) - ENET_SUBCONTEXT_ORDER ) \ + { \ + nextSymbol = 0; \ + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); \ + predicted = 0; \ + order = 0; \ + } \ +} + +#define ENET_CONTEXT_ENCODE(context, symbol_, value_, under_, count_, update, minimum) \ +{ \ + under_ = value*minimum; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + if (value_ < node -> value) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + } \ + else \ + if (value_ > node -> value) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + } \ + else \ + { \ + count_ += node -> count; \ + under_ += node -> under - node -> count; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#ifdef ENET_CONTEXT_EXCLUSION +static const ENetSymbol emptyContext = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +#define ENET_CONTEXT_WALK(context, body) \ +{ \ + const ENetSymbol * node = (context) + (context) -> symbols; \ + const ENetSymbol * stack [256]; \ + size_t stackSize = 0; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + for (;;) \ + { \ + body; \ + if (node -> right) \ + { \ + node += node -> right; \ + while (node -> left) \ + { \ + stack [stackSize ++] = node; \ + node += node -> left; \ + } \ + } \ + else \ + if (stackSize <= 0) \ + break; \ + else \ + node = stack [-- stackSize]; \ + } \ +} + +#define ENET_CONTEXT_ENCODE_EXCLUDE(context, value_, under, total, minimum) \ +ENET_CONTEXT_WALK(context, { \ + if (node -> value != value_) \ + { \ + enet_uint16 parentCount = rangeCoder -> symbols [node -> parent].count + minimum; \ + if (node -> value < value_) \ + under -= parentCount; \ + total -= parentCount; \ + } \ +}) +#endif + +size_t +enet_range_coder_compress (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inData, * inEnd; + enet_uint32 encodeLow = 0, encodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; + + if (rangeCoder == NULL || inBufferCount <= 0 || inLimit <= 0) + return 0; + + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + for (;;) + { + ENetSymbol * subcontext, * symbol; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value; + enet_uint16 count, under, * parent = & predicted, total; + if (inData >= inEnd) + { + if (inBufferCount <= 0) + break; + inData = (const enet_uint8 *) inBuffers -> data; + inEnd = & inData [inBuffers -> dataLength]; + inBuffers ++; + inBufferCount --; + } + value = * inData ++; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + ENET_CONTEXT_ENCODE (subcontext, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, 0); +#endif + if (count > 0) + { + ENET_RANGE_CODER_ENCODE (subcontext -> escapes + under, count, total); + } + else + { + if (subcontext -> escapes > 0 && subcontext -> escapes < total) + ENET_RANGE_CODER_ENCODE (0, subcontext -> escapes, total); + subcontext -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + subcontext -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + if (count > 0) goto nextInput; + } + + ENET_CONTEXT_ENCODE (root, symbol, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > ENET_SUBCONTEXT_SYMBOL_DELTA + ENET_SUBCONTEXT_ESCAPE_DELTA) + ENET_CONTEXT_ENCODE_EXCLUDE (childContext, value, under, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + ENET_RANGE_CODER_ENCODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + nextInput: + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + ENET_RANGE_CODER_FLUSH; + + return (size_t) (outData - outStart); +} + +#define ENET_RANGE_CODER_SEED \ +{ \ + if (inData < inEnd) decodeCode |= * inData ++ << 24; \ + if (inData < inEnd) decodeCode |= * inData ++ << 16; \ + if (inData < inEnd) decodeCode |= * inData ++ << 8; \ + if (inData < inEnd) decodeCode |= * inData ++; \ +} + +#define ENET_RANGE_CODER_READ(total) ((decodeCode - decodeLow) / (decodeRange /= (total))) + +#define ENET_RANGE_CODER_DECODE(under, count, total) \ +{ \ + decodeLow += (under) * decodeRange; \ + decodeRange *= (count); \ + for (;;) \ + { \ + if((decodeLow ^ (decodeLow + decodeRange)) >= ENET_RANGE_CODER_TOP) \ + { \ + if(decodeRange >= ENET_RANGE_CODER_BOTTOM) break; \ + decodeRange = -decodeLow & (ENET_RANGE_CODER_BOTTOM - 1); \ + } \ + decodeCode <<= 8; \ + if (inData < inEnd) \ + decodeCode |= * inData ++; \ + decodeRange <<= 8; \ + decodeLow <<= 8; \ + } \ +} + +#define ENET_CONTEXT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, createRoot, visitNode, createRight, createLeft) \ +{ \ + under_ = 0; \ + count_ = minimum; \ + if (! (context) -> symbols) \ + { \ + createRoot; \ + } \ + else \ + { \ + ENetSymbol * node = (context) + (context) -> symbols; \ + for (;;) \ + { \ + enet_uint16 after = under_ + node -> under + (node -> value + 1)*minimum, before = node -> count + minimum; \ + visitNode; \ + if (code >= after) \ + { \ + under_ += node -> under; \ + if (node -> right) { node += node -> right; continue; } \ + createRight; \ + } \ + else \ + if (code < after - before) \ + { \ + node -> under += update; \ + if (node -> left) { node += node -> left; continue; } \ + createLeft; \ + } \ + else \ + { \ + value_ = node -> value; \ + count_ += node -> count; \ + under_ = after - before; \ + node -> under += update; \ + node -> count += update; \ + symbol_ = node; \ + } \ + break; \ + } \ + } \ +} + +#define ENET_CONTEXT_TRY_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, return 0, exclude (node -> value, after, before), return 0, return 0) + +#define ENET_CONTEXT_ROOT_DECODE(context, symbol_, code, value_, under_, count_, update, minimum, exclude) \ +ENET_CONTEXT_DECODE (context, symbol_, code, value_, under_, count_, update, minimum, \ + { \ + value_ = code / minimum; \ + under_ = code - code%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + (context) -> symbols = symbol_ - (context); \ + }, \ + exclude (node -> value, after, before), \ + { \ + value_ = node->value + 1 + (code - after)/minimum; \ + under_ = code - (code - after)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> right = symbol_ - node; \ + }, \ + { \ + value_ = node->value - 1 - (after - before - code - 1)/minimum; \ + under_ = code - (after - before - code - 1)%minimum; \ + ENET_SYMBOL_CREATE (symbol_, value_, update); \ + node -> left = symbol_ - node; \ + }) \ + +#ifdef ENET_CONTEXT_EXCLUSION +typedef struct _ENetExclude +{ + enet_uint8 value; + enet_uint16 under; +} ENetExclude; + +#define ENET_CONTEXT_DECODE_EXCLUDE(context, total, minimum) \ +{ \ + enet_uint16 under = 0; \ + nextExclude = excludes; \ + ENET_CONTEXT_WALK (context, { \ + under += rangeCoder -> symbols [node -> parent].count + minimum; \ + nextExclude -> value = node -> value; \ + nextExclude -> under = under; \ + nextExclude ++; \ + }); \ + total -= under; \ +} + +#define ENET_CONTEXT_EXCLUDED(value_, after, before) \ +{ \ + size_t low = 0, high = nextExclude - excludes; \ + for(;;) \ + { \ + size_t mid = (low + high) >> 1; \ + const ENetExclude * exclude = & excludes [mid]; \ + if (value_ < exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + high = mid; \ + continue; \ + } \ + if (exclude > excludes) \ + after -= exclude [-1].under; \ + } \ + else \ + { \ + if (value_ > exclude -> value) \ + { \ + if (low + 1 < high) \ + { \ + low = mid; \ + continue; \ + } \ + } \ + else \ + before = 0; \ + after -= exclude -> under; \ + } \ + break; \ + } \ +} +#endif + +#define ENET_CONTEXT_NOT_EXCLUDED(value_, after, before) + +size_t +enet_range_coder_decompress (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit) +{ + ENetRangeCoder * rangeCoder = (ENetRangeCoder *) context; + enet_uint8 * outStart = outData, * outEnd = & outData [outLimit]; + const enet_uint8 * inEnd = & inData [inLimit]; + enet_uint32 decodeLow = 0, decodeCode = 0, decodeRange = ~0; + ENetSymbol * root; + enet_uint16 predicted = 0; + size_t order = 0, nextSymbol = 0; +#ifdef ENET_CONTEXT_EXCLUSION + ENetExclude excludes [256]; + ENetExclude * nextExclude = excludes; +#endif + + if (rangeCoder == NULL || inLimit <= 0) + return 0; + + ENET_CONTEXT_CREATE (root, ENET_CONTEXT_ESCAPE_MINIMUM, ENET_CONTEXT_SYMBOL_MINIMUM); + + ENET_RANGE_CODER_SEED; + + for (;;) + { + ENetSymbol * subcontext, * symbol, * patch; +#ifdef ENET_CONTEXT_EXCLUSION + const ENetSymbol * childContext = & emptyContext; +#endif + enet_uint8 value = 0; + enet_uint16 code, under, count, bottom, * parent = & predicted, total; + + for (subcontext = & rangeCoder -> symbols [predicted]; + subcontext != root; +#ifdef ENET_CONTEXT_EXCLUSION + childContext = subcontext, +#endif + subcontext = & rangeCoder -> symbols [subcontext -> parent]) + { + if (subcontext -> escapes <= 0) + continue; + total = subcontext -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, 0); +#endif + if (subcontext -> escapes >= total) + continue; + code = ENET_RANGE_CODER_READ (total); + if (code < subcontext -> escapes) + { + ENET_RANGE_CODER_DECODE (0, subcontext -> escapes, total); + continue; + } + code -= subcontext -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_TRY_DECODE (subcontext, symbol, code, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (subcontext -> escapes + under, count, total); + subcontext -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || subcontext -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (subcontext, 0); + goto patchContexts; + } + + total = root -> total; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + ENET_CONTEXT_DECODE_EXCLUDE (childContext, total, ENET_CONTEXT_SYMBOL_MINIMUM); +#endif + code = ENET_RANGE_CODER_READ (total); + if (code < root -> escapes) + { + ENET_RANGE_CODER_DECODE (0, root -> escapes, total); + break; + } + code -= root -> escapes; +#ifdef ENET_CONTEXT_EXCLUSION + if (childContext -> total > 0) + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_EXCLUDED); + } + else +#endif + { + ENET_CONTEXT_ROOT_DECODE (root, symbol, code, value, under, count, ENET_CONTEXT_SYMBOL_DELTA, ENET_CONTEXT_SYMBOL_MINIMUM, ENET_CONTEXT_NOT_EXCLUDED); + } + bottom = symbol - rangeCoder -> symbols; + ENET_RANGE_CODER_DECODE (root -> escapes + under, count, total); + root -> total += ENET_CONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_CONTEXT_SYMBOL_DELTA + ENET_CONTEXT_SYMBOL_MINIMUM || root -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (root, ENET_CONTEXT_SYMBOL_MINIMUM); + + patchContexts: + for (patch = & rangeCoder -> symbols [predicted]; + patch != subcontext; + patch = & rangeCoder -> symbols [patch -> parent]) + { + ENET_CONTEXT_ENCODE (patch, symbol, value, under, count, ENET_SUBCONTEXT_SYMBOL_DELTA, 0); + * parent = symbol - rangeCoder -> symbols; + parent = & symbol -> parent; + if (count <= 0) + { + patch -> escapes += ENET_SUBCONTEXT_ESCAPE_DELTA; + patch -> total += ENET_SUBCONTEXT_ESCAPE_DELTA; + } + patch -> total += ENET_SUBCONTEXT_SYMBOL_DELTA; + if (count > 0xFF - 2*ENET_SUBCONTEXT_SYMBOL_DELTA || patch -> total > ENET_RANGE_CODER_BOTTOM - 0x100) + ENET_CONTEXT_RESCALE (patch, 0); + } + * parent = bottom; + + ENET_RANGE_CODER_OUTPUT (value); + + if (order >= ENET_SUBCONTEXT_ORDER) + predicted = rangeCoder -> symbols [predicted].parent; + else + order ++; + ENET_RANGE_CODER_FREE_SYMBOLS; + } + + return (size_t) (outData - outStart); +} + +/** @defgroup host ENet host functions + @{ +*/ + +/** Sets the packet compressor the host should use to the default range coder. + @param host host to enable the range coder for + @returns 0 on success, < 0 on failure +*/ +int +enet_host_compress_with_range_coder (ENetHost * host) +{ + ENetCompressor compressor; + memset (& compressor, 0, sizeof (compressor)); + compressor.context = enet_range_coder_create(); + if (compressor.context == NULL) + return -1; + compressor.compress = enet_range_coder_compress; + compressor.decompress = enet_range_coder_decompress; + compressor.destroy = enet_range_coder_destroy; + enet_host_compress (host, & compressor); + return 0; +} + +/** @} */ + + diff --git a/Externals/enet/enet.h b/Externals/enet/enet.h new file mode 100644 index 0000000000..ddae001a74 --- /dev/null +++ b/Externals/enet/enet.h @@ -0,0 +1,592 @@ +/** + @file enet.h + @brief ENet public header file +*/ +#ifndef __ENET_ENET_H__ +#define __ENET_ENET_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#ifdef _WIN32 +#include "enet/win32.h" +#else +#include "enet/unix.h" +#endif + +#include "enet/types.h" +#include "enet/protocol.h" +#include "enet/list.h" +#include "enet/callbacks.h" + +#define ENET_VERSION_MAJOR 1 +#define ENET_VERSION_MINOR 3 +#define ENET_VERSION_PATCH 12 +#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) + +typedef enet_uint32 ENetVersion; + +struct _ENetHost; +struct _ENetEvent; +struct _ENetPacket; + +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 +} ENetSocketOption; + +typedef enum _ENetSocketShutdown +{ + ENET_SOCKET_SHUTDOWN_READ = 0, + ENET_SOCKET_SHUTDOWN_WRITE = 1, + ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 +} ENetSocketShutdown; + +#define ENET_HOST_ANY 0 +#define ENET_HOST_BROADCAST 0xFFFFFFFFU +#define ENET_PORT_ANY 0 + +/** + * Portable internet address structure. + * + * The host must be specified in network byte-order, and the port must be in host + * byte-order. The constant ENET_HOST_ANY may be used to specify the default + * server host. The constant ENET_HOST_BROADCAST may be used to specify the + * broadcast address (255.255.255.255). This makes sense for enet_host_connect, + * but not for enet_host_create. Once a server responds to a broadcast, the + * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. + */ +typedef struct _ENetAddress +{ + enet_uint32 host; + enet_uint16 port; +} ENetAddress; + +/** + * Packet flag bit constants. + * + * The host must be specified in network byte-order, and the port must be in + * host byte-order. The constant ENET_HOST_ANY may be used to specify the + * default server host. + + @sa ENetPacket +*/ +typedef enum _ENetPacketFlag +{ + /** packet must be received by the target peer and resend attempts should be + * made until the packet is delivered */ + ENET_PACKET_FLAG_RELIABLE = (1 << 0), + /** packet will not be sequenced with other packets + * not supported for reliable packets + */ + ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), + /** packet will not allocate data, and user must supply it instead */ + ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), + /** packet will be fragmented using unreliable (instead of reliable) sends + * if it exceeds the MTU */ + ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3), + + /** whether the packet has been sent from all queues it has been entered into */ + ENET_PACKET_FLAG_SENT = (1<<8) +} ENetPacketFlag; + +typedef void (ENET_CALLBACK * ENetPacketFreeCallback) (struct _ENetPacket *); + +/** + * ENet packet structure. + * + * An ENet data packet that may be sent to or received from a peer. The shown + * fields should only be read and never modified. The data field contains the + * allocated data for the packet. The dataLength fields specifies the length + * of the allocated data. The flags field is either 0 (specifying no flags), + * or a bitwise-or of any combination of the following flags: + * + * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer + * and resend attempts should be made until the packet is delivered + * + * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets + * (not supported for reliable packets) + * + * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead + + @sa ENetPacketFlag + */ +typedef struct _ENetPacket +{ + size_t referenceCount; /**< internal use only */ + enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ + enet_uint8 * data; /**< allocated data for packet */ + size_t dataLength; /**< length of data */ + ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ + void * userData; /**< application private data, may be freely modified */ +} ENetPacket; + +typedef struct _ENetAcknowledgement +{ + ENetListNode acknowledgementList; + enet_uint32 sentTime; + ENetProtocol command; +} ENetAcknowledgement; + +typedef struct _ENetOutgoingCommand +{ + ENetListNode outgoingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + enet_uint32 sentTime; + enet_uint32 roundTripTimeout; + enet_uint32 roundTripTimeoutLimit; + enet_uint32 fragmentOffset; + enet_uint16 fragmentLength; + enet_uint16 sendAttempts; + ENetProtocol command; + ENetPacket * packet; +} ENetOutgoingCommand; + +typedef struct _ENetIncomingCommand +{ + ENetListNode incomingCommandList; + enet_uint16 reliableSequenceNumber; + enet_uint16 unreliableSequenceNumber; + ENetProtocol command; + enet_uint32 fragmentCount; + enet_uint32 fragmentsRemaining; + enet_uint32 * 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; + +#ifndef ENET_BUFFER_MAXIMUM +#define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) +#endif + +enum +{ + ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, + ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, + ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, + ENET_HOST_DEFAULT_MTU = 1400, + ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, + ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, + + ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, + ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, + 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_PACKET_LOSS_SCALE = (1 << 16), + ENET_PEER_PACKET_LOSS_INTERVAL = 10000, + 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 = 500, + 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 +{ + enet_uint16 outgoingReliableSequenceNumber; + enet_uint16 outgoingUnreliableSequenceNumber; + enet_uint16 usedReliableWindows; + enet_uint16 reliableWindows [ENET_PEER_RELIABLE_WINDOWS]; + enet_uint16 incomingReliableSequenceNumber; + enet_uint16 incomingUnreliableSequenceNumber; + ENetList incomingReliableCommands; + ENetList incomingUnreliableCommands; +} ENetChannel; + +/** + * An ENet peer which data packets may be sent or received from. + * + * No fields should be modified unless otherwise specified. + */ +typedef struct _ENetPeer +{ + ENetListNode dispatchList; + struct _ENetHost * host; + enet_uint16 outgoingPeerID; + enet_uint16 incomingPeerID; + enet_uint32 connectID; + enet_uint8 outgoingSessionID; + enet_uint8 incomingSessionID; + ENetAddress address; /**< Internet address of the peer */ + void * data; /**< Application private data, may be freely modified */ + ENetPeerState state; + ENetChannel * channels; + size_t channelCount; /**< Number of channels allocated for communication with peer */ + enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ + enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ + enet_uint32 incomingBandwidthThrottleEpoch; + enet_uint32 outgoingBandwidthThrottleEpoch; + enet_uint32 incomingDataTotal; + enet_uint32 outgoingDataTotal; + enet_uint32 lastSendTime; + enet_uint32 lastReceiveTime; + enet_uint32 nextTimeout; + enet_uint32 earliestTimeout; + enet_uint32 packetLossEpoch; + enet_uint32 packetsSent; + enet_uint32 packetsLost; + enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ + enet_uint32 packetLossVariance; + enet_uint32 packetThrottle; + enet_uint32 packetThrottleLimit; + enet_uint32 packetThrottleCounter; + enet_uint32 packetThrottleEpoch; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 packetThrottleInterval; + enet_uint32 pingInterval; + enet_uint32 timeoutLimit; + enet_uint32 timeoutMinimum; + enet_uint32 timeoutMaximum; + enet_uint32 lastRoundTripTime; + enet_uint32 lowestRoundTripTime; + enet_uint32 lastRoundTripTimeVariance; + enet_uint32 highestRoundTripTimeVariance; + enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ + enet_uint32 roundTripTimeVariance; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 reliableDataInTransit; + enet_uint16 outgoingReliableSequenceNumber; + ENetList acknowledgements; + ENetList sentReliableCommands; + ENetList sentUnreliableCommands; + ENetList outgoingReliableCommands; + ENetList outgoingUnreliableCommands; + ENetList dispatchedCommands; + int needsDispatch; + enet_uint16 incomingUnsequencedGroup; + enet_uint16 outgoingUnsequencedGroup; + enet_uint32 unsequencedWindow [ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; + enet_uint32 eventData; + size_t totalWaitingData; +} ENetPeer; + +/** An ENet packet compressor for compressing UDP packets before socket sends or receives. + */ +typedef struct _ENetCompressor +{ + /** Context data for the compressor. Must be non-NULL. */ + void * context; + /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * compress) (void * context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ + size_t (ENET_CALLBACK * decompress) (void * context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); + /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ + void (ENET_CALLBACK * destroy) (void * context); +} ENetCompressor; + +/** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ +typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback) (const ENetBuffer * buffers, size_t bufferCount); + +/** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */ +typedef int (ENET_CALLBACK * ENetInterceptCallback) (struct _ENetHost * host, struct _ENetEvent * event); + +/** An ENet host for communicating with peers. + * + * No fields should be modified unless otherwise stated. + + @sa enet_host_create() + @sa enet_host_destroy() + @sa enet_host_connect() + @sa enet_host_service() + @sa enet_host_flush() + @sa enet_host_broadcast() + @sa enet_host_compress() + @sa enet_host_compress_with_range_coder() + @sa enet_host_channel_limit() + @sa enet_host_bandwidth_limit() + @sa enet_host_bandwidth_throttle() + */ +typedef struct _ENetHost +{ + ENetSocket socket; + ENetAddress address; /**< Internet address of the host */ + enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ + enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ + enet_uint32 bandwidthThrottleEpoch; + enet_uint32 mtu; + enet_uint32 randomSeed; + int recalculateBandwidthLimits; + ENetPeer * peers; /**< array of peers allocated for this host */ + size_t peerCount; /**< number of peers allocated for this host */ + size_t channelLimit; /**< maximum number of channels allowed for connected peers */ + enet_uint32 serviceTime; + ENetList dispatchQueue; + int continueSending; + size_t packetSize; + enet_uint16 headerFlags; + ENetProtocol commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; + size_t commandCount; + ENetBuffer buffers [ENET_BUFFER_MAXIMUM]; + size_t bufferCount; + ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ + ENetCompressor compressor; + enet_uint8 packetData [2][ENET_PROTOCOL_MAXIMUM_MTU]; + ENetAddress receivedAddress; + enet_uint8 * receivedData; + size_t receivedDataLength; + enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ + enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ + ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */ + size_t connectedPeers; + size_t bandwidthLimitedPeers; + size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */ + size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */ + size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */ +} ENetHost; + +/** + * An ENet event type, as specified in @ref ENetEvent. + */ +typedef enum _ENetEventType +{ + /** no event occurred within the specified time limit */ + ENET_EVENT_TYPE_NONE = 0, + + /** a connection request initiated by enet_host_connect has completed. + * The peer field contains the peer which successfully connected. + */ + ENET_EVENT_TYPE_CONNECT = 1, + + /** a peer has disconnected. This event is generated on a successful + * completion of a disconnect initiated by enet_pper_disconnect, if + * a peer has timed out, or if a connection request intialized by + * enet_host_connect has timed out. The peer field contains the peer + * which disconnected. The data field contains user supplied data + * describing the disconnection, or 0, if none is available. + */ + ENET_EVENT_TYPE_DISCONNECT = 2, + + /** a packet has been received from a peer. The peer field specifies the + * peer which sent the packet. The channelID field specifies the channel + * number upon which the packet was received. The packet field contains + * the packet that was received; this packet must be destroyed with + * enet_packet_destroy after use. + */ + ENET_EVENT_TYPE_RECEIVE = 3 +} ENetEventType; + +/** + * An ENet event as returned by enet_host_service(). + + @sa enet_host_service + */ +typedef struct _ENetEvent +{ + ENetEventType type; /**< type of the event */ + ENetPeer * peer; /**< peer that generated a connect, disconnect or receive event */ + enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ + enet_uint32 data; /**< data associated with the event, if appropriate */ + ENetPacket * packet; /**< packet associated with the event, if appropriate */ +} ENetEvent; + +/** @defgroup global ENet global functions + @{ +*/ + +/** + Initializes ENet globally. Must be called prior to using any functions in + ENet. + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize (void); + +/** + Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. + + @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use + @param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults + @returns 0 on success, < 0 on failure +*/ +ENET_API int enet_initialize_with_callbacks (ENetVersion version, const ENetCallbacks * inits); + +/** + Shuts down ENet globally. Should be called when a program that has + initialized ENet exits. +*/ +ENET_API void enet_deinitialize (void); + +/** + Gives the linked version of the ENet library. + @returns the version number +*/ +ENET_API ENetVersion enet_linked_version (void); + +/** @} */ + +/** @defgroup private ENet private implementation functions */ + +/** + Returns the wall-time in milliseconds. Its initial value is unspecified + unless otherwise set. + */ +ENET_API enet_uint32 enet_time_get (void); +/** + Sets the current wall-time in milliseconds. + */ +ENET_API void enet_time_set (enet_uint32); + +/** @defgroup socket ENet socket functions + @{ +*/ +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, enet_uint32 *, enet_uint32); +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_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32); + +/** @} */ + +/** @defgroup Address ENet address functions + @{ +*/ +/** Attempts to resolve the host named by the parameter hostName and sets + the host field in the address parameter if successful. + @param address destination to store resolved address + @param hostName host name to lookup + @retval 0 on success + @retval < 0 on failure + @returns the address of the given hostName in address on success +*/ +ENET_API int enet_address_set_host (ENetAddress * address, const char * hostName); + +/** Gives the printable form of the IP address specified in the address parameter. + @param address address printed + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host_ip (const ENetAddress * address, char * hostName, size_t nameLength); + +/** Attempts to do a reverse lookup of the host field in the address parameter. + @param address address used for reverse lookup + @param hostName destination for name, must not be NULL + @param nameLength maximum length of hostName. + @returns the null-terminated name of the host in hostName on success + @retval 0 on success + @retval < 0 on failure +*/ +ENET_API int enet_address_get_host (const ENetAddress * address, char * hostName, size_t nameLength); + +/** @} */ + +ENET_API ENetPacket * enet_packet_create (const void *, size_t, enet_uint32); +ENET_API void enet_packet_destroy (ENetPacket *); +ENET_API int enet_packet_resize (ENetPacket *, size_t); +ENET_API enet_uint32 enet_crc32 (const ENetBuffer *, size_t); + +ENET_API ENetHost * enet_host_create (const ENetAddress *, size_t, size_t, enet_uint32, enet_uint32); +ENET_API void enet_host_destroy (ENetHost *); +ENET_API ENetPeer * enet_host_connect (ENetHost *, const ENetAddress *, size_t, enet_uint32); +ENET_API int enet_host_check_events (ENetHost *, ENetEvent *); +ENET_API int enet_host_service (ENetHost *, ENetEvent *, enet_uint32); +ENET_API void enet_host_flush (ENetHost *); +ENET_API void enet_host_broadcast (ENetHost *, enet_uint8, ENetPacket *); +ENET_API void enet_host_compress (ENetHost *, const ENetCompressor *); +ENET_API int enet_host_compress_with_range_coder (ENetHost * host); +ENET_API void enet_host_channel_limit (ENetHost *, size_t); +ENET_API void enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32); +extern void enet_host_bandwidth_throttle (ENetHost *); +extern enet_uint32 enet_host_random_seed (void); + +ENET_API int enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *); +ENET_API ENetPacket * enet_peer_receive (ENetPeer *, enet_uint8 * channelID); +ENET_API void enet_peer_ping (ENetPeer *); +ENET_API void enet_peer_ping_interval (ENetPeer *, enet_uint32); +ENET_API void enet_peer_timeout (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); +ENET_API void enet_peer_reset (ENetPeer *); +ENET_API void enet_peer_disconnect (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_now (ENetPeer *, enet_uint32); +ENET_API void enet_peer_disconnect_later (ENetPeer *, enet_uint32); +ENET_API void enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32); +extern int enet_peer_throttle (ENetPeer *, enet_uint32); +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 *, enet_uint32, enet_uint16); +extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32); +extern ENetAcknowledgement * enet_peer_queue_acknowledgement (ENetPeer *, const ENetProtocol *, enet_uint16); +extern void enet_peer_dispatch_incoming_unreliable_commands (ENetPeer *, ENetChannel *); +extern void enet_peer_dispatch_incoming_reliable_commands (ENetPeer *, ENetChannel *); +extern void enet_peer_on_connect (ENetPeer *); +extern void enet_peer_on_disconnect (ENetPeer *); + +ENET_API void * enet_range_coder_create (void); +ENET_API void enet_range_coder_destroy (void *); +ENET_API size_t enet_range_coder_compress (void *, const ENetBuffer *, size_t, size_t, enet_uint8 *, size_t); +ENET_API size_t enet_range_coder_decompress (void *, const enet_uint8 *, size_t, enet_uint8 *, size_t); + +extern size_t enet_protocol_command_size (enet_uint8); + +#ifdef __cplusplus +} +#endif + +#endif /* __ENET_ENET_H__ */ + diff --git a/Externals/enet/enet.vcxproj b/Externals/enet/enet.vcxproj new file mode 100644 index 0000000000..e51e549e49 --- /dev/null +++ b/Externals/enet/enet.vcxproj @@ -0,0 +1,78 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {CBC76802-C128-4B17-BF6C-23B08C313E5E} + + + + StaticLibrary + v120 + Unicode + + + true + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Externals/enet/git-revision b/Externals/enet/git-revision new file mode 100644 index 0000000000..28a33b319e --- /dev/null +++ b/Externals/enet/git-revision @@ -0,0 +1 @@ +48571bb05fcdb420da2b0b38cdaf2488bd031d20 diff --git a/Externals/enet/host.c b/Externals/enet/host.c new file mode 100644 index 0000000000..3be6c0922c --- /dev/null +++ b/Externals/enet/host.c @@ -0,0 +1,492 @@ +/** + @file host.c + @brief ENet host management functions +*/ +#define ENET_BUILDING_LIB 1 +#include +#include "enet/enet.h" + +/** @defgroup host ENet host functions + @{ +*/ + +/** Creates a host for communicating to peers. + + @param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host. + @param peerCount the maximum number of peers that should be allocated for the host. + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT + @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. + + @returns the host on success and NULL on failure + + @remarks ENet will strategically drop packets on specific sides of a connection between hosts + to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine + the window size of a connection which limits the amount of reliable packets that may be in transit + at any given time. +*/ +ENetHost * +enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + 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 || (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; + } + + 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, ENET_HOST_RECEIVE_BUFFER_SIZE); + enet_socket_set_option (host -> socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE); + + 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 = (enet_uint32) (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 -> mtu = ENET_HOST_DEFAULT_MTU; + host -> peerCount = peerCount; + host -> commandCount = 0; + host -> bufferCount = 0; + host -> checksum = NULL; + host -> receivedAddress.host = 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 -> compressor.context = NULL; + host -> compressor.compress = NULL; + host -> compressor.decompress = NULL; + host -> compressor.destroy = NULL; + + host -> intercept = 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 (& currentPeer -> acknowledgements); + enet_list_clear (& currentPeer -> sentReliableCommands); + enet_list_clear (& currentPeer -> sentUnreliableCommands); + enet_list_clear (& currentPeer -> outgoingReliableCommands); + enet_list_clear (& currentPeer -> outgoingUnreliableCommands); + enet_list_clear (& currentPeer -> dispatchedCommands); + + enet_peer_reset (currentPeer); + } + + return host; +} + +/** Destroys the host and all resources associated with it. + @param host pointer to the host to destroy +*/ +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); + } + + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + enet_free (host -> peers); + enet_free (host); +} + +/** Initiates a connection to a foreign host. + @param host host seeking the connection + @param address destination for the connection + @param channelCount number of channels to allocate + @param data user data supplied to the receiving host + @returns a peer representing the foreign host on success, NULL on failure + @remarks The peer returned will have not completed the connection until enet_host_service() + notifies of an ENET_EVENT_TYPE_CONNECT event for the peer. +*/ +ENetPeer * +enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelCount, enet_uint32 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 < & currentPeer -> 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; +} + +/** Queues a packet to be sent to all peers associated with the host. + @param host host on which to broadcast the packet + @param channelID channel on which to broadcast + @param packet packet to broadcast +*/ +void +enet_host_broadcast (ENetHost * host, enet_uint8 channelID, ENetPacket * packet) +{ + ENetPeer * currentPeer; + + 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 -> referenceCount == 0) + enet_packet_destroy (packet); +} + +/** Sets the packet compressor the host should use to compress and decompress packets. + @param host host to enable or disable compression for + @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled +*/ +void +enet_host_compress (ENetHost * host, const ENetCompressor * compressor) +{ + if (host -> compressor.context != NULL && host -> compressor.destroy) + (* host -> compressor.destroy) (host -> compressor.context); + + if (compressor) + host -> compressor = * compressor; + else + host -> compressor.context = NULL; +} + +/** Limits the maximum allowed channels of future incoming connections. + @param host host to limit + @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT +*/ +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; +} + + +/** Adjusts the bandwidth limits of a host. + @param host host to adjust + @param incomingBandwidth new incoming bandwidth + @param outgoingBandwidth new outgoing bandwidth + @remarks the incoming and outgoing bandwidth parameters are identical in function to those + specified in enet_host_create(). +*/ +void +enet_host_bandwidth_limit (ENetHost * host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) +{ + host -> incomingBandwidth = incomingBandwidth; + host -> outgoingBandwidth = outgoingBandwidth; + host -> recalculateBandwidthLimits = 1; +} + +void +enet_host_bandwidth_throttle (ENetHost * host) +{ + enet_uint32 timeCurrent = enet_time_get (), + elapsedTime = timeCurrent - host -> bandwidthThrottleEpoch, + peersRemaining = (enet_uint32) host -> connectedPeers, + dataTotal = ~0, + bandwidth = ~0, + throttle = 0, + bandwidthLimit = 0; + int needsAdjustment = host -> bandwidthLimitedPeers > 0 ? 1 : 0; + ENetPeer * peer; + ENetProtocol command; + + if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) + 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) + { + enet_uint32 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 = (enet_uint32) 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); + } + } +} + +/** @} */ diff --git a/Externals/enet/list.c b/Externals/enet/list.c new file mode 100644 index 0000000000..1c1a8dfaaf --- /dev/null +++ b/Externals/enet/list.c @@ -0,0 +1,75 @@ +/** + @file list.c + @brief ENet linked list functions +*/ +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** + @defgroup list ENet linked list utility functions + @ingroup private + @{ +*/ +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, + 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; +} + +/** @} */ diff --git a/Externals/enet/list.h b/Externals/enet/list.h new file mode 100644 index 0000000000..d7b2600848 --- /dev/null +++ b/Externals/enet/list.h @@ -0,0 +1,43 @@ +/** + @file list.h + @brief ENet list management +*/ +#ifndef __ENET_LIST_H__ +#define __ENET_LIST_H__ + +#include + +typedef struct _ENetListNode +{ + struct _ENetListNode * next; + struct _ENetListNode * previous; +} ENetListNode; + +typedef ENetListNode * ENetListIterator; + +typedef struct _ENetList +{ + ENetListNode sentinel; +} ENetList; + +extern void enet_list_clear (ENetList *); + +extern ENetListIterator enet_list_insert (ENetListIterator, void *); +extern void * enet_list_remove (ENetListIterator); +extern ENetListIterator enet_list_move (ENetListIterator, void *, void *); + +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) + +#endif /* __ENET_LIST_H__ */ + diff --git a/Externals/enet/packet.c b/Externals/enet/packet.c new file mode 100644 index 0000000000..5fa78b28ae --- /dev/null +++ b/Externals/enet/packet.c @@ -0,0 +1,165 @@ +/** + @file packet.c + @brief ENet packet management functions +*/ +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup Packet ENet packet functions + @{ +*/ + +/** Creates a packet that may be sent to a peer. + @param data initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL. + @param dataLength size of the data allocated for this packet + @param flags flags for this packet as described for the ENetPacket structure. + @returns the packet on success, NULL on failure +*/ +ENetPacket * +enet_packet_create (const void * data, size_t dataLength, enet_uint32 flags) +{ + ENetPacket * packet = (ENetPacket *) enet_malloc (sizeof (ENetPacket)); + if (packet == NULL) + return NULL; + + if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) + packet -> data = (enet_uint8 *) data; + else + if (dataLength <= 0) + packet -> data = NULL; + else + { + packet -> data = (enet_uint8 *) enet_malloc (dataLength); + if (packet -> data == NULL) + { + enet_free (packet); + return NULL; + } + + 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; +} + +/** Destroys the packet and deallocates its data. + @param packet packet to be destroyed +*/ +void +enet_packet_destroy (ENetPacket * packet) +{ + if (packet == NULL) + return; + + if (packet -> freeCallback != NULL) + (* packet -> freeCallback) (packet); + if (! (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE) && + packet -> data != NULL) + enet_free (packet -> data); + enet_free (packet); +} + +/** Attempts to resize the data in the packet to length specified in the + dataLength parameter + @param packet packet to resize + @param dataLength new size for the packet data + @returns 0 on success, < 0 on failure +*/ +int +enet_packet_resize (ENetPacket * packet, size_t dataLength) +{ + enet_uint8 * newData; + + if (dataLength <= packet -> dataLength || (packet -> flags & ENET_PACKET_FLAG_NO_ALLOCATE)) + { + packet -> dataLength = dataLength; + + return 0; + } + + newData = (enet_uint8 *) enet_malloc (dataLength); + if (newData == NULL) + return -1; + + memcpy (newData, packet -> data, packet -> dataLength); + enet_free (packet -> data); + + packet -> data = newData; + packet -> dataLength = dataLength; + + return 0; +} + +static int initializedCRC32 = 0; +static enet_uint32 crcTable [256]; + +static enet_uint32 +reflect_crc (int val, int bits) +{ + int result = 0, bit; + + for (bit = 0; bit < bits; bit ++) + { + if(val & 1) result |= 1 << (bits - 1 - bit); + val >>= 1; + } + + return result; +} + +static void +initialize_crc32 (void) +{ + int byte; + + for (byte = 0; byte < 256; ++ byte) + { + enet_uint32 crc = reflect_crc (byte, 8) << 24; + int offset; + + for(offset = 0; offset < 8; ++ offset) + { + if (crc & 0x80000000) + crc = (crc << 1) ^ 0x04c11db7; + else + crc <<= 1; + } + + crcTable [byte] = reflect_crc (crc, 32); + } + + initializedCRC32 = 1; +} + +enet_uint32 +enet_crc32 (const ENetBuffer * buffers, size_t bufferCount) +{ + enet_uint32 crc = 0xFFFFFFFF; + + if (! initializedCRC32) initialize_crc32 (); + + while (bufferCount -- > 0) + { + const enet_uint8 * data = (const enet_uint8 *) buffers -> data, + * dataEnd = & data [buffers -> dataLength]; + + while (data < dataEnd) + { + crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++]; + } + + ++ buffers; + } + + return ENET_HOST_TO_NET_32 (~ crc); +} + +/** @} */ diff --git a/Externals/enet/peer.c b/Externals/enet/peer.c new file mode 100644 index 0000000000..e2d0872bd3 --- /dev/null +++ b/Externals/enet/peer.c @@ -0,0 +1,1004 @@ +/** + @file peer.c + @brief ENet peer management functions +*/ +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +/** @defgroup peer ENet peer functions + @{ +*/ + +/** Configures throttle parameter for a peer. + + Unreliable packets are dropped by ENet in response to the varying conditions + of the Internet connection to the peer. The throttle represents a probability + that an unreliable packet should not be dropped and thus sent by ENet to the peer. + The lowest mean round trip time from the sending of a reliable packet to the + receipt of its acknowledgement is measured over an amount of time specified by + the interval parameter in milliseconds. If a measured round trip time happens to + be significantly less than the mean round trip time measured over the interval, + then the throttle probability is increased to allow more traffic by an amount + specified in the acceleration parameter, which is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE + constant. If a measured round trip time happens to be significantly greater than + the mean round trip time measured over the interval, then the throttle probability + is decreased to limit traffic by an amount specified in the deceleration parameter, which + is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE constant. When the throttle has + a value of ENET_PEER_PACKET_THROTTLE_SCALE, no unreliable packets are dropped by + ENet, and so 100% of all unreliable packets will be sent. When the throttle has a + value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable + packets will be sent. Intermediate values for the throttle represent intermediate + probabilities between 0% and 100% of unreliable packets being sent. The bandwidth + limits of the local and foreign hosts are taken into account to determine a + sensible limit for the throttle probability above which it should not raise even in + the best of conditions. + + @param peer peer to configure + @param interval interval, in milliseconds, over which to measure lowest mean RTT; the default value is ENET_PEER_PACKET_THROTTLE_INTERVAL. + @param acceleration rate at which to increase the throttle probability as mean RTT declines + @param deceleration rate at which to decrease the throttle probability as mean RTT increases +*/ +void +enet_peer_throttle_configure (ENetPeer * peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration) +{ + ENetProtocol command; + + 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, enet_uint32 rtt) +{ + if (peer -> lastRoundTripTime <= peer -> lastRoundTripTimeVariance) + { + peer -> packetThrottle = peer -> packetThrottleLimit; + } + else + if (rtt < peer -> lastRoundTripTime) + { + peer -> packetThrottle += peer -> packetThrottleAcceleration; + + if (peer -> packetThrottle > peer -> packetThrottleLimit) + peer -> packetThrottle = peer -> packetThrottleLimit; + + return 1; + } + else + if (rtt > peer -> lastRoundTripTime + 2 * peer -> lastRoundTripTimeVariance) + { + if (peer -> packetThrottle > peer -> packetThrottleDeceleration) + peer -> packetThrottle -= peer -> packetThrottleDeceleration; + else + peer -> packetThrottle = 0; + + return -1; + } + + return 0; +} + +/** Queues a packet to be sent. + @param peer destination for the packet + @param channelID channel on which to send + @param packet packet to send + @retval 0 on success + @retval < 0 on failure +*/ +int +enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet) +{ + ENetChannel * channel = & peer -> channels [channelID]; + ENetProtocol command; + size_t fragmentLength; + + if (peer -> state != ENET_PEER_STATE_CONNECTED || + channelID >= peer -> channelCount || + packet -> dataLength > peer -> host -> maximumPacketSize) + return -1; + + fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment); + if (peer -> host -> checksum != NULL) + fragmentLength -= sizeof(enet_uint32); + + if (packet -> dataLength > fragmentLength) + { + enet_uint32 fragmentCount = (packet -> dataLength + fragmentLength - 1) / fragmentLength, + fragmentNumber, + fragmentOffset; + enet_uint8 commandNumber; + enet_uint16 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_FRAGMENT)) == ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT && + 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; + + return 0; +} + +/** Attempts to dequeue any incoming queued packet. + @param peer peer to dequeue packets from + @param channelID holds the channel ID of the channel the packet was received on success + @returns a pointer to the packet, or NULL if there are no available incoming queued packets +*/ +ENetPacket * +enet_peer_receive (ENetPeer * peer, enet_uint8 * 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) +{ + ENetListIterator currentCommand; + + for (currentCommand = startCommand; currentCommand != endCommand; ) + { + ENetIncomingCommand * incomingCommand = (ENetIncomingCommand *) currentCommand; + + currentCommand = enet_list_next (currentCommand); + + 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)); +} + +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 -> outgoingReliableCommands); + enet_peer_reset_outgoing_commands (& peer -> outgoingUnreliableCommands); + 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; + } +} + +/** Forcefully disconnects a peer. + @param peer peer to forcefully disconnect + @remarks The foreign host represented by the peer is not notified of the disconnection and will timeout + on its connection to the local host. +*/ +void +enet_peer_reset (ENetPeer * peer) +{ + enet_peer_on_disconnect (peer); + + peer -> outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; + peer -> connectID = 0; + + peer -> state = ENET_PEER_STATE_DISCONNECTED; + + peer -> incomingBandwidth = 0; + peer -> outgoingBandwidth = 0; + peer -> incomingBandwidthThrottleEpoch = 0; + peer -> outgoingBandwidthThrottleEpoch = 0; + peer -> incomingDataTotal = 0; + peer -> outgoingDataTotal = 0; + peer -> lastSendTime = 0; + peer -> lastReceiveTime = 0; + peer -> nextTimeout = 0; + peer -> earliestTimeout = 0; + peer -> packetLossEpoch = 0; + peer -> packetsSent = 0; + peer -> packetsLost = 0; + peer -> packetLoss = 0; + peer -> packetLossVariance = 0; + peer -> packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; + 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 = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; + 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); +} + +/** Sends a ping request to a peer. + @param peer destination for the ping request + @remarks ping requests factor into the mean round trip time as designated by the + roundTripTime field in the ENetPeer structure. ENet automatically pings all connected + peers at regular intervals, however, this function may be called to ensure more + frequent ping requests. +*/ +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); +} + +/** Sets the interval at which pings will be sent to a peer. + + Pings are used both to monitor the liveness of the connection and also to dynamically + adjust the throttle during periods of low traffic so that the throttle has reasonable + responsiveness during traffic spikes. + + @param peer the peer to adjust + @param pingInterval the interval at which to send pings; defaults to ENET_PEER_PING_INTERVAL if 0 +*/ +void +enet_peer_ping_interval (ENetPeer * peer, enet_uint32 pingInterval) +{ + peer -> pingInterval = pingInterval ? pingInterval : ENET_PEER_PING_INTERVAL; +} + +/** Sets the timeout parameters for a peer. + + The timeout parameter control how and when a peer will timeout from a failure to acknowledge + reliable traffic. Timeout values use an exponential backoff mechanism, where if a reliable + packet is not acknowledge within some multiple of the average RTT plus a variance tolerance, + the timeout will be doubled until it reaches a set limit. If the timeout is thus at this + limit and reliable packets have been sent but not acknowledged within a certain minimum time + period, the peer will be disconnected. Alternatively, if reliable packets have been sent + but not acknowledged for a certain maximum time period, the peer will be disconnected regardless + of the current timeout limit value. + + @param peer the peer to adjust + @param timeoutLimit the timeout limit; defaults to ENET_PEER_TIMEOUT_LIMIT if 0 + @param timeoutMinimum the timeout minimum; defaults to ENET_PEER_TIMEOUT_MINIMUM if 0 + @param timeoutMaximum the timeout maximum; defaults to ENET_PEER_TIMEOUT_MAXIMUM if 0 +*/ + +void +enet_peer_timeout (ENetPeer * peer, enet_uint32 timeoutLimit, enet_uint32 timeoutMinimum, enet_uint32 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; +} + +/** Force an immediate disconnection from a peer. + @param peer peer to disconnect + @param data data describing the disconnection + @remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not + guaranteed to receive the disconnect notification, and is reset immediately upon + return from this function. +*/ +void +enet_peer_disconnect_now (ENetPeer * peer, enet_uint32 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); +} + +/** Request a disconnection from a peer. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect (ENetPeer * peer, enet_uint32 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); + } +} + +/** Request a disconnection from a peer, but only after all queued outgoing packets are sent. + @param peer peer to request a disconnection + @param data data describing the disconnection + @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() + once the disconnection is complete. +*/ +void +enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data) +{ + if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && + ! (enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + 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, enet_uint16 sentTime) +{ + ENetAcknowledgement * acknowledgement; + + if (command -> header.channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint16 reliableWindow = command -> header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE, + 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; + } + + if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) + enet_list_insert (enet_list_end (& peer -> outgoingReliableCommands), outgoingCommand); + else + enet_list_insert (enet_list_end (& peer -> outgoingUnreliableCommands), outgoingCommand); +} + +ENetOutgoingCommand * +enet_peer_queue_outgoing_command (ENetPeer * peer, const ENetProtocol * command, ENetPacket * packet, enet_uint32 offset, enet_uint16 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) +{ + 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 + { + enet_uint16 reliableWindow = incomingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE, + 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); +} + +void +enet_peer_dispatch_incoming_reliable_commands (ENetPeer * peer, ENetChannel * channel) +{ + 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 != (enet_uint16) (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); +} + +ENetIncomingCommand * +enet_peer_queue_incoming_command (ENetPeer * peer, const ENetProtocol * command, const void * data, size_t dataLength, enet_uint32 flags, enet_uint32 fragmentCount) +{ + static ENetIncomingCommand dummyCommand; + + ENetChannel * channel = & peer -> channels [command -> header.channelID]; + enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber = 0; + enet_uint16 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 = (enet_uint32 *) enet_malloc ((fragmentCount + 31) / 32 * sizeof (enet_uint32)); + if (incomingCommand -> fragments == NULL) + { + enet_free (incomingCommand); + + goto notifyError; + } + memset (incomingCommand -> fragments, 0, (fragmentCount + 31) / 32 * sizeof (enet_uint32)); + } + + 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); + break; + + default: + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); + 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; +} + +/** @} */ diff --git a/Externals/enet/protocol.c b/Externals/enet/protocol.c new file mode 100644 index 0000000000..101d923e0a --- /dev/null +++ b/Externals/enet/protocol.c @@ -0,0 +1,1907 @@ +/** + @file protocol.c + @brief ENet protocol functions +*/ +#include +#include +#define ENET_BUILDING_LIB 1 +#include "enet/utility.h" +#include "enet/time.h" +#include "enet/enet.h" + +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 (enet_uint8 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); + + 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_remove_sent_unreliable_commands (ENetPeer * peer) +{ + ENetOutgoingCommand * outgoingCommand; + + while (! enet_list_empty (& peer -> sentUnreliableCommands)) + { + 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); + } +} + +static ENetProtocolCommand +enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 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 -> outgoingReliableCommands); + currentCommand != enet_list_end (& peer -> outgoingReliableCommands); + 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 -> outgoingReliableCommands)) + return ENET_PROTOCOL_COMMAND_NONE; + + wasSent = 0; + } + + if (outgoingCommand == NULL) + return ENET_PROTOCOL_COMMAND_NONE; + + if (channelID < peer -> channelCount) + { + ENetChannel * channel = & peer -> channels [channelID]; + enet_uint16 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) +{ + enet_uint8 incomingSessionID, outgoingSessionID; + enet_uint32 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 && + currentPeer -> address.host == host -> receivedAddress.host) + { + 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, enet_uint8 ** 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 enet_uint8 *) 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, enet_uint8 ** currentData) +{ + enet_uint32 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 >= (enet_uint32) 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 enet_uint8 *) 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, enet_uint8 ** 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 enet_uint8 *) 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, enet_uint8 ** currentData) +{ + enet_uint32 fragmentNumber, + fragmentCount, + fragmentOffset, + fragmentLength, + startSequenceNumber, + totalLength; + ENetChannel * channel; + enet_uint16 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, + (enet_uint8 *) command + sizeof (ENetProtocolSendFragment), + fragmentLength); + + if (startCommand -> fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_reliable_commands (peer, channel); + } + + return 0; +} + +static int +enet_protocol_handle_send_unreliable_fragment (ENetHost * host, ENetPeer * peer, const ENetProtocol * command, enet_uint8 ** currentData) +{ + enet_uint32 fragmentNumber, + fragmentCount, + fragmentOffset, + fragmentLength, + reliableSequenceNumber, + startSequenceNumber, + totalLength; + enet_uint16 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_FRAGMENT, 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, + (enet_uint8 *) command + sizeof (ENetProtocolSendFragment), + fragmentLength); + + if (startCommand -> fragmentsRemaining <= 0) + enet_peer_dispatch_incoming_unreliable_commands (peer, channel); + } + + 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 + 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) +{ + enet_uint32 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; + + peer -> lastReceiveTime = host -> serviceTime; + peer -> earliestTimeout = 0; + + roundTripTime = ENET_TIME_DIFFERENCE (host -> serviceTime, receivedSentTime); + + enet_peer_throttle (peer, roundTripTime); + + peer -> roundTripTimeVariance -= peer -> roundTripTimeVariance / 4; + + if (roundTripTime >= peer -> roundTripTime) + { + peer -> roundTripTime += (roundTripTime - peer -> roundTripTime) / 8; + peer -> roundTripTimeVariance += (roundTripTime - peer -> roundTripTime) / 4; + } + else + { + peer -> roundTripTime -= (peer -> roundTripTime - roundTripTime) / 8; + peer -> roundTripTimeVariance += (peer -> roundTripTime - roundTripTime) / 4; + } + + 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; + } + + 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 -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + 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) +{ + enet_uint32 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; + enet_uint8 * currentData; + size_t headerSize; + enet_uint16 peerID, flags; + enet_uint8 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 -> checksum != NULL) + headerSize += sizeof (enet_uint32); + + 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 || + ((host -> receivedAddress.host != peer -> address.host || + host -> receivedAddress.port != peer -> address.port) && + peer -> address.host != ENET_HOST_BROADCAST) || + (peer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && + sessionID != peer -> incomingSessionID)) + return 0; + } + + if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) + { + size_t originalSize; + if (host -> compressor.context == NULL || host -> compressor.decompress == NULL) + return 0; + + originalSize = host -> compressor.decompress (host -> compressor.context, + host -> receivedData + headerSize, + host -> receivedDataLength - headerSize, + host -> packetData [1] + headerSize, + sizeof (host -> packetData [1]) - headerSize); + if (originalSize <= 0 || originalSize > sizeof (host -> packetData [1]) - headerSize) + return 0; + + memcpy (host -> packetData [1], header, headerSize); + host -> receivedData = host -> packetData [1]; + host -> receivedDataLength = headerSize + originalSize; + } + + if (host -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & host -> receivedData [headerSize - sizeof (enet_uint32)], + desiredChecksum = * checksum; + ENetBuffer buffer; + + * checksum = peer != NULL ? peer -> connectID : 0; + + buffer.data = host -> receivedData; + buffer.dataLength = host -> receivedDataLength; + + if (host -> checksum (& buffer, 1) != desiredChecksum) + return 0; + } + + if (peer != NULL) + { + peer -> address.host = host -> receivedAddress.host; + peer -> address.port = host -> receivedAddress.port; + peer -> incomingDataTotal += host -> receivedDataLength; + } + + currentData = host -> receivedData + headerSize; + + while (currentData < & host -> receivedData [host -> receivedDataLength]) + { + enet_uint8 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) + 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; + 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, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: + if (enet_protocol_handle_send_unreliable (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: + if (enet_protocol_handle_send_unsequenced (host, peer, command, & currentData)) + goto commandError; + break; + + case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: + if (enet_protocol_handle_send_fragment (host, peer, command, & currentData)) + 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, & currentData)) + goto commandError; + break; + + default: + goto commandError; + } + + if (peer != NULL && + (command -> header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) + { + enet_uint16 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) +{ + for (;;) + { + int receivedLength; + ENetBuffer buffer; + + buffer.data = host -> packetData [0]; + buffer.dataLength = sizeof (host -> packetData [0]); + + receivedLength = enet_socket_receive (host -> socket, + & host -> receivedAddress, + & buffer, + 1); + + 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 -> intercept != NULL) + { + switch (host -> intercept (host, event)) + { + 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 -1; +} + +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; + enet_uint16 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 void +enet_protocol_send_unreliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + + currentCommand = enet_list_begin (& peer -> outgoingUnreliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingUnreliableCommands)) + { + size_t commandSize; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + 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 && + peer -> mtu - host -> packetSize < commandSize + outgoingCommand -> fragmentLength)) + { + host -> continueSending = 1; + + break; + } + + currentCommand = enet_list_next (currentCommand); + + if (outgoingCommand -> packet != NULL && outgoingCommand -> fragmentOffset == 0) + { + peer -> packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; + peer -> packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; + + if (peer -> packetThrottleCounter > peer -> packetThrottle) + { + enet_uint16 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 -> outgoingUnreliableCommands)) + break; + + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + if (outgoingCommand -> reliableSequenceNumber != reliableSequenceNumber || + outgoingCommand -> unreliableSequenceNumber != unreliableSequenceNumber) + break; + + currentCommand = enet_list_next (currentCommand); + } + + continue; + } + } + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + + * command = outgoingCommand -> command; + + enet_list_remove (& outgoingCommand -> outgoingCommandList); + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset; + buffer -> dataLength = outgoingCommand -> fragmentLength; + + host -> packetSize += buffer -> dataLength; + + enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand); + } + else + enet_free (outgoingCommand); + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; + + if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER && + enet_list_empty (& peer -> outgoingReliableCommands) && + enet_list_empty (& peer -> outgoingUnreliableCommands) && + enet_list_empty (& peer -> sentReliableCommands)) + enet_peer_disconnect (peer, peer -> eventData); +} + +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 -> outgoingReliableCommands); + + 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 (host, peer, event); + + return 1; + } + + if (outgoingCommand -> packet != NULL) + peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength; + + ++ peer -> packetsLost; + + outgoingCommand -> roundTripTimeout *= 2; + + 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_send_reliable_outgoing_commands (ENetHost * host, ENetPeer * peer) +{ + ENetProtocol * command = & host -> commands [host -> commandCount]; + ENetBuffer * buffer = & host -> buffers [host -> bufferCount]; + ENetOutgoingCommand * outgoingCommand; + ENetListIterator currentCommand; + ENetChannel *channel; + enet_uint16 reliableWindow; + size_t commandSize; + int windowExceeded = 0, windowWrap = 0, canPing = 1; + + currentCommand = enet_list_begin (& peer -> outgoingReliableCommands); + + while (currentCommand != enet_list_end (& peer -> outgoingReliableCommands)) + { + outgoingCommand = (ENetOutgoingCommand *) currentCommand; + + 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) - 1) << reliableWindow) | + (((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOW_SIZE - reliableWindow))))) + windowWrap = 1; + if (windowWrap) + { + currentCommand = enet_list_next (currentCommand); + + continue; + } + } + + if (outgoingCommand -> packet != NULL) + { + if (! windowExceeded) + { + enet_uint32 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 && + (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength))) + { + host -> continueSending = 1; + + break; + } + + currentCommand = enet_list_next (currentCommand); + + 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; + + buffer -> data = command; + buffer -> dataLength = commandSize; + + host -> packetSize += buffer -> dataLength; + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; + + * command = outgoingCommand -> command; + + if (outgoingCommand -> packet != NULL) + { + ++ buffer; + + buffer -> data = outgoingCommand -> packet -> data + outgoingCommand -> fragmentOffset; + buffer -> dataLength = outgoingCommand -> fragmentLength; + + host -> packetSize += outgoingCommand -> fragmentLength; + + peer -> reliableDataInTransit += outgoingCommand -> fragmentLength; + } + + ++ peer -> packetsSent; + + ++ command; + ++ buffer; + } + + host -> commandCount = command - host -> commands; + host -> bufferCount = buffer - host -> buffers; + + return canPing; +} + +static int +enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int checkForTimeouts) +{ + enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)]; + ENetProtocolHeader * header = (ENetProtocolHeader *) headerData; + ENetPeer * currentPeer; + int sentLength; + size_t shouldCompress = 0; + + 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 (! enet_list_empty (& currentPeer -> acknowledgements)) + enet_protocol_send_acknowledgements (host, currentPeer); + + if (checkForTimeouts != 0 && + ! enet_list_empty (& currentPeer -> 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 (& currentPeer -> outgoingReliableCommands) || + enet_protocol_send_reliable_outgoing_commands (host, currentPeer)) && + enet_list_empty (& currentPeer -> sentReliableCommands) && + ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval && + currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing)) + { + enet_peer_ping (currentPeer); + enet_protocol_send_reliable_outgoing_commands (host, currentPeer); + } + + if (! enet_list_empty (& currentPeer -> outgoingUnreliableCommands)) + enet_protocol_send_unreliable_outgoing_commands (host, currentPeer); + + if (host -> commandCount == 0) + continue; + + if (currentPeer -> packetLossEpoch == 0) + currentPeer -> packetLossEpoch = host -> serviceTime; + else + if (ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL && + currentPeer -> packetsSent > 0) + { + enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent; + +#ifdef ENET_DEBUG + printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u/%u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingReliableCommands), enet_list_size (& currentPeer -> outgoingUnreliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0); +#endif + + currentPeer -> packetLossVariance -= currentPeer -> packetLossVariance / 4; + + if (packetLoss >= currentPeer -> packetLoss) + { + currentPeer -> packetLoss += (packetLoss - currentPeer -> packetLoss) / 8; + currentPeer -> packetLossVariance += (packetLoss - currentPeer -> packetLoss) / 4; + } + else + { + currentPeer -> packetLoss -= (currentPeer -> packetLoss - packetLoss) / 8; + currentPeer -> packetLossVariance += (currentPeer -> packetLoss - packetLoss) / 4; + } + + currentPeer -> packetLossEpoch = host -> serviceTime; + currentPeer -> packetsSent = 0; + currentPeer -> packetsLost = 0; + } + + 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; + + shouldCompress = 0; + if (host -> compressor.context != NULL && host -> compressor.compress != NULL) + { + size_t originalSize = host -> packetSize - sizeof(ENetProtocolHeader), + compressedSize = host -> compressor.compress (host -> compressor.context, + & host -> buffers [1], host -> bufferCount - 1, + originalSize, + host -> packetData [1], + originalSize); + if (compressedSize > 0 && compressedSize < originalSize) + { + host -> headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; + shouldCompress = compressedSize; +#ifdef ENET_DEBUG_COMPRESS + printf ("peer %u: compressed %u -> %u (%u%%)\n", currentPeer -> incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize); +#endif + } + } + + 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 -> checksum != NULL) + { + enet_uint32 * checksum = (enet_uint32 *) & headerData [host -> buffers -> dataLength]; + * checksum = currentPeer -> outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer -> connectID : 0; + host -> buffers -> dataLength += sizeof (enet_uint32); + * checksum = host -> checksum (host -> buffers, host -> bufferCount); + } + + if (shouldCompress > 0) + { + host -> buffers [1].data = host -> packetData [1]; + host -> buffers [1].dataLength = shouldCompress; + host -> bufferCount = 2; + } + + currentPeer -> lastSendTime = host -> serviceTime; + + sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount); + + enet_protocol_remove_sent_unreliable_commands (currentPeer); + + if (sentLength < 0) + return -1; + + host -> totalSentData += sentLength; + host -> totalSentPackets ++; + } + + return 0; +} + +/** Sends any queued packets on the host specified to its designated peers. + + @param host host to flush + @remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service(). + @ingroup host +*/ +void +enet_host_flush (ENetHost * host) +{ + host -> serviceTime = enet_time_get (); + + enet_protocol_send_outgoing_commands (host, NULL, 0); +} + +/** Checks for any queued events on the host and dispatches one if available. + + @param host host to check for events + @param event an event structure where event details will be placed if available + @retval > 0 if an event was dispatched + @retval 0 if no events are available + @retval < 0 on failure + @ingroup host +*/ +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); +} + +/** Waits for events on the host specified and shuttles packets between + the host and its peers. + + @param host host to service + @param event an event structure where event details will be placed if one occurs + if event == NULL then no events will be delivered + @param timeout number of milliseconds that ENet should wait for events + @retval > 0 if an event occurred within the specified time limit + @retval 0 if no event occurred + @retval < 0 on failure + @remarks enet_host_service should be called fairly regularly for adequate performance + @ingroup host +*/ +int +enet_host_service (ENetHost * host, ENetEvent * event, enet_uint32 timeout) +{ + enet_uint32 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; +} + diff --git a/Externals/enet/protocol.h b/Externals/enet/protocol.h new file mode 100644 index 0000000000..f8c73d8a66 --- /dev/null +++ b/Externals/enet/protocol.h @@ -0,0 +1,198 @@ +/** + @file protocol.h + @brief ENet protocol +*/ +#ifndef __ENET_PROTOCOL_H__ +#define __ENET_PROTOCOL_H__ + +#include "enet/types.h" + +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_COMPRESSED = (1 << 14), + ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), + ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | 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 +{ + enet_uint16 peerID; + enet_uint16 sentTime; +} ENET_PACKED ENetProtocolHeader; + +typedef struct _ENetProtocolCommandHeader +{ + enet_uint8 command; + enet_uint8 channelID; + enet_uint16 reliableSequenceNumber; +} ENET_PACKED ENetProtocolCommandHeader; + +typedef struct _ENetProtocolAcknowledge +{ + ENetProtocolCommandHeader header; + enet_uint16 receivedReliableSequenceNumber; + enet_uint16 receivedSentTime; +} ENET_PACKED ENetProtocolAcknowledge; + +typedef struct _ENetProtocolConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; + enet_uint32 data; +} ENET_PACKED ENetProtocolConnect; + +typedef struct _ENetProtocolVerifyConnect +{ + ENetProtocolCommandHeader header; + enet_uint16 outgoingPeerID; + enet_uint8 incomingSessionID; + enet_uint8 outgoingSessionID; + enet_uint32 mtu; + enet_uint32 windowSize; + enet_uint32 channelCount; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; + enet_uint32 connectID; +} ENET_PACKED ENetProtocolVerifyConnect; + +typedef struct _ENetProtocolBandwidthLimit +{ + ENetProtocolCommandHeader header; + enet_uint32 incomingBandwidth; + enet_uint32 outgoingBandwidth; +} ENET_PACKED ENetProtocolBandwidthLimit; + +typedef struct _ENetProtocolThrottleConfigure +{ + ENetProtocolCommandHeader header; + enet_uint32 packetThrottleInterval; + enet_uint32 packetThrottleAcceleration; + enet_uint32 packetThrottleDeceleration; +} ENET_PACKED ENetProtocolThrottleConfigure; + +typedef struct _ENetProtocolDisconnect +{ + ENetProtocolCommandHeader header; + enet_uint32 data; +} ENET_PACKED ENetProtocolDisconnect; + +typedef struct _ENetProtocolPing +{ + ENetProtocolCommandHeader header; +} ENET_PACKED ENetProtocolPing; + +typedef struct _ENetProtocolSendReliable +{ + ENetProtocolCommandHeader header; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendReliable; + +typedef struct _ENetProtocolSendUnreliable +{ + ENetProtocolCommandHeader header; + enet_uint16 unreliableSequenceNumber; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnreliable; + +typedef struct _ENetProtocolSendUnsequenced +{ + ENetProtocolCommandHeader header; + enet_uint16 unsequencedGroup; + enet_uint16 dataLength; +} ENET_PACKED ENetProtocolSendUnsequenced; + +typedef struct _ENetProtocolSendFragment +{ + ENetProtocolCommandHeader header; + enet_uint16 startSequenceNumber; + enet_uint16 dataLength; + enet_uint32 fragmentCount; + enet_uint32 fragmentNumber; + enet_uint32 totalLength; + enet_uint32 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 + +#endif /* __ENET_PROTOCOL_H__ */ + diff --git a/Externals/enet/time.h b/Externals/enet/time.h new file mode 100644 index 0000000000..c82a546035 --- /dev/null +++ b/Externals/enet/time.h @@ -0,0 +1,18 @@ +/** + @file time.h + @brief ENet time constants and macros +*/ +#ifndef __ENET_TIME_H__ +#define __ENET_TIME_H__ + +#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)) + +#endif /* __ENET_TIME_H__ */ + diff --git a/Externals/enet/types.h b/Externals/enet/types.h new file mode 100644 index 0000000000..ab010a4b13 --- /dev/null +++ b/Externals/enet/types.h @@ -0,0 +1,13 @@ +/** + @file types.h + @brief type definitions for ENet +*/ +#ifndef __ENET_TYPES_H__ +#define __ENET_TYPES_H__ + +typedef unsigned char enet_uint8; /**< unsigned 8-bit type */ +typedef unsigned short enet_uint16; /**< unsigned 16-bit type */ +typedef unsigned int enet_uint32; /**< unsigned 32-bit type */ + +#endif /* __ENET_TYPES_H__ */ + diff --git a/Externals/enet/unix.c b/Externals/enet/unix.c new file mode 100644 index 0000000000..5e7c731435 --- /dev/null +++ b/Externals/enet/unix.c @@ -0,0 +1,557 @@ +/** + @file unix.c + @brief ENet Unix system specific functions +*/ +#ifndef _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" + +#ifdef __APPLE__ +#ifdef HAS_POLL +#undef HAS_POLL +#endif +#ifndef HAS_FCNTL +#define HAS_FCNTL 1 +#endif +#ifndef HAS_INET_PTON +#define HAS_INET_PTON 1 +#endif +#ifndef HAS_INET_NTOP +#define HAS_INET_NTOP 1 +#endif +#ifndef HAS_MSGHDR_FLAGS +#define HAS_MSGHDR_FLAGS 1 +#endif +#ifndef HAS_SOCKLEN_T +#define HAS_SOCKLEN_T 1 +#endif +#endif + +#ifdef HAS_FCNTL +#include +#endif + +#ifdef HAS_POLL +#include +#endif + +#ifndef HAS_SOCKLEN_T +typedef int socklen_t; +#endif + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + return 0; +} + +void +enet_deinitialize (void) +{ +} + +enet_uint32 +enet_host_random_seed (void) +{ + return (enet_uint32) time (NULL); +} + +enet_uint32 +enet_time_get (void) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + return timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + struct timeval timeVal; + + gettimeofday (& timeVal, NULL); + + timeBase = timeVal.tv_sec * 1000 + timeVal.tv_usec / 1000 - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYNAME_R + struct hostent hostData; + char buffer [2048]; + int errnum; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + hostEntry = gethostbyname (name); +#endif + + if (hostEntry == NULL || + hostEntry -> h_addrtype != AF_INET) + { +#ifdef HAS_INET_PTON + if (! inet_pton (AF_INET, name, & address -> host)) +#else + if (! inet_aton (name, (struct in_addr *) & address -> host)) +#endif + return -1; + return 0; + } + + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ +#ifdef HAS_INET_NTOP + if (inet_ntop (AF_INET, & address -> host, name, nameLength) == NULL) +#else + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr != NULL) + { + size_t addrLen = strlen(addr); + if (addrLen >= nameLength) + return -1; + memcpy (name, addr, addrLen + 1); + } + else +#endif + return -1; + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ + struct in_addr in; + struct hostent * hostEntry = NULL; +#ifdef HAS_GETHOSTBYADDR_R + struct hostent hostData; + char buffer [2048]; + int errnum; + + in.s_addr = address -> host; + +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); +#else + hostEntry = gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & errnum); +#endif +#else + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); +#endif + + if (hostEntry == NULL) + return enet_address_get_host_ip (address, name, nameLength); + else + { + size_t hostLen = strlen (hostEntry -> h_name); + if (hostLen >= nameLength) + return -1; + memcpy (name, hostEntry -> h_name, hostLen + 1); + } + + return 0; +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)); +} + +int +enet_socket_get_address (ENetSocket socket, ENetAddress * address) +{ + struct sockaddr_in sin; + socklen_t sinLength = sizeof (struct sockaddr_in); + + if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1) + return -1; + + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + + return 0; +} + +int +enet_socket_listen (ENetSocket socket, int backlog) +{ + return listen (socket, backlog < 0 ? SOMAXCONN : backlog); +} + +ENetSocket +enet_socket_create (ENetSocketType type) +{ + return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); +} + +int +enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) +{ + int result = -1; + switch (option) + { + case ENET_SOCKOPT_NONBLOCK: +#ifdef HAS_FCNTL + result = fcntl (socket, F_SETFL, O_NONBLOCK | fcntl (socket, F_GETFL)); +#else + result = ioctl (socket, FIONBIO, & value); +#endif + 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; + + 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) +{ + struct sockaddr_in sin; + int result; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)); + if (result == -1 && errno == EINPROGRESS) + return 0; + + return result; +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + int result; + struct sockaddr_in sin; + socklen_t sinLength = sizeof (struct sockaddr_in); + + result = accept (socket, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? & sinLength : NULL); + + if (result == -1) + return ENET_SOCKET_NULL; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return result; +} + +int +enet_socket_shutdown (ENetSocket socket, ENetSocketShutdown how) +{ + return shutdown (socket, (int) how); +} + +void +enet_socket_destroy (ENetSocket socket) +{ + if (socket != -1) + close (socket); +} + +int +enet_socket_send (ENetSocket socket, + const ENetAddress * address, + const ENetBuffer * buffers, + size_t bufferCount) +{ + struct msghdr msgHdr; + struct sockaddr_in sin; + int sentLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + 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_in sin; + int recvLength; + + memset (& msgHdr, 0, sizeof (struct msghdr)); + + if (address != NULL) + { + msgHdr.msg_name = & sin; + msgHdr.msg_namelen = sizeof (struct sockaddr_in); + } + + 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; + } + +#ifdef HAS_MSGHDR_FLAGS + if (msgHdr.msg_flags & MSG_TRUNC) + return -1; +#endif + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 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, enet_uint32 * condition, enet_uint32 timeout) +{ +#ifdef HAS_POLL + 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; +#else + 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) + { + if (errno == EINTR && * condition & ENET_SOCKET_WAIT_INTERRUPT) + { + * condition = ENET_SOCKET_WAIT_INTERRUPT; + + return 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 +} + +#endif + diff --git a/Externals/enet/unix.h b/Externals/enet/unix.h new file mode 100644 index 0000000000..a59e340606 --- /dev/null +++ b/Externals/enet/unix.h @@ -0,0 +1,47 @@ +/** + @file unix.h + @brief ENet Unix header +*/ +#ifndef __ENET_UNIX_H__ +#define __ENET_UNIX_H__ + +#include +#include +#include +#include +#include +#include + +#ifdef MSG_MAXIOVLEN +#define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN +#endif + +typedef int ENetSocket; + +#define ENET_SOCKET_NULL -1 + +#define ENET_HOST_TO_NET_16(value) (htons (value)) /**< macro that converts host to net byte-order of a 16-bit value */ +#define ENET_HOST_TO_NET_32(value) (htonl (value)) /**< macro that converts host to net byte-order of a 32-bit value */ + +#define ENET_NET_TO_HOST_16(value) (ntohs (value)) /**< macro that converts net to host byte-order of a 16-bit value */ +#define ENET_NET_TO_HOST_32(value) (ntohl (value)) /**< macro that converts net to host byte-order of a 32-bit value */ + +typedef struct +{ + void * data; + size_t dataLength; +} ENetBuffer; + +#define ENET_CALLBACK + +#define ENET_API extern + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_UNIX_H__ */ + diff --git a/Externals/enet/update-enet.sh b/Externals/enet/update-enet.sh new file mode 100644 index 0000000000..b4231aa9c7 --- /dev/null +++ b/Externals/enet/update-enet.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -xe +rm -rf _enet +git clone https://github.com/lsalzman/enet.git _enet +if [ -a git-revision ]; then + cd _enet + git checkout $(cat ../git-revision) + cp ../*.h include/ + cp ../*.c . + git stash + git checkout MASTER + git stash pop + cd .. +fi +cd _enet; git rev-parse HEAD > ../git-revision; cd .. +git rm -rf --ignore-unmatch *.[ch] +mv _enet/*.c _enet/include/enet/*.h _enet/LICENSE . +git add *.[ch] LICENSE git-revision +rm -rf _enet +echo 'Make sure to update CMakeLists.txt.' diff --git a/Externals/enet/utility.h b/Externals/enet/utility.h new file mode 100644 index 0000000000..e48a476be3 --- /dev/null +++ b/Externals/enet/utility.h @@ -0,0 +1,12 @@ +/** + @file utility.h + @brief ENet utility header +*/ +#ifndef __ENET_UTILITY_H__ +#define __ENET_UTILITY_H__ + +#define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) + +#endif /* __ENET_UTILITY_H__ */ + diff --git a/Externals/enet/win32.c b/Externals/enet/win32.c new file mode 100644 index 0000000000..b7ab336a64 --- /dev/null +++ b/Externals/enet/win32.c @@ -0,0 +1,421 @@ +/** + @file win32.c + @brief ENet Win32 system specific functions +*/ +#ifdef _WIN32 + +#include +#define ENET_BUILDING_LIB 1 +#include "enet/enet.h" +#include +static enet_uint32 timeBase = 0; + +int +enet_initialize (void) +{ + WORD versionRequested = MAKEWORD (1, 1); + WSADATA wsaData; + + if (WSAStartup (versionRequested, & wsaData)) + return -1; + + if (LOBYTE (wsaData.wVersion) != 1|| + HIBYTE (wsaData.wVersion) != 1) + { + WSACleanup (); + + return -1; + } + + timeBeginPeriod (1); + + return 0; +} + +void +enet_deinitialize (void) +{ + timeEndPeriod (1); + + WSACleanup (); +} + +enet_uint32 +enet_host_random_seed (void) +{ + return (enet_uint32) timeGetTime (); +} + +enet_uint32 +enet_time_get (void) +{ + return (enet_uint32) timeGetTime () - timeBase; +} + +void +enet_time_set (enet_uint32 newTimeBase) +{ + timeBase = (enet_uint32) timeGetTime () - newTimeBase; +} + +int +enet_address_set_host (ENetAddress * address, const char * name) +{ + struct hostent * hostEntry; + + hostEntry = gethostbyname (name); + if (hostEntry == NULL || + hostEntry -> h_addrtype != AF_INET) + { + unsigned long host = inet_addr (name); + if (host == INADDR_NONE) + return -1; + address -> host = host; + return 0; + } + + address -> host = * (enet_uint32 *) hostEntry -> h_addr_list [0]; + + return 0; +} + +int +enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameLength) +{ + char * addr = inet_ntoa (* (struct in_addr *) & address -> host); + if (addr == NULL) + return -1; + else + { + size_t addrLen = strlen(addr); + if (addrLen >= nameLength) + return -1; + memcpy (name, addr, addrLen + 1); + } + return 0; +} + +int +enet_address_get_host (const ENetAddress * address, char * name, size_t nameLength) +{ + struct in_addr in; + struct hostent * hostEntry; + + in.s_addr = address -> host; + + hostEntry = gethostbyaddr ((char *) & in, sizeof (struct in_addr), AF_INET); + if (hostEntry == NULL) + return enet_address_get_host_ip (address, name, nameLength); + else + { + size_t hostLen = strlen (hostEntry -> h_name); + if (hostLen >= nameLength) + return -1; + memcpy (name, hostEntry -> h_name, hostLen + 1); + } + + return 0; +} + +int +enet_socket_bind (ENetSocket socket, const ENetAddress * address) +{ + struct sockaddr_in sin; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + + if (address != NULL) + { + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + else + { + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + } + + return bind (socket, + (struct sockaddr *) & sin, + sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0; +} + +int +enet_socket_get_address (ENetSocket socket, ENetAddress * address) +{ + struct sockaddr_in sin; + int sinLength = sizeof (struct sockaddr_in); + + if (getsockname (socket, (struct sockaddr *) & sin, & sinLength) == -1) + return -1; + + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_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_INET, 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; + + 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) +{ + struct sockaddr_in sin; + int result; + + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + + result = connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)); + if (result == SOCKET_ERROR && WSAGetLastError () != WSAEWOULDBLOCK) + return -1; + + return 0; +} + +ENetSocket +enet_socket_accept (ENetSocket socket, ENetAddress * address) +{ + SOCKET result; + struct sockaddr_in sin; + int sinLength = sizeof (struct sockaddr_in); + + 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 -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_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_in sin; + DWORD sentLength; + + if (address != NULL) + { + memset (& sin, 0, sizeof (struct sockaddr_in)); + + sin.sin_family = AF_INET; + sin.sin_port = ENET_HOST_TO_NET_16 (address -> port); + sin.sin_addr.s_addr = address -> host; + } + + if (WSASendTo (socket, + (LPWSABUF) buffers, + (DWORD) bufferCount, + & sentLength, + 0, + address != NULL ? (struct sockaddr *) & sin : NULL, + address != NULL ? sizeof (struct sockaddr_in) : 0, + NULL, + NULL) == SOCKET_ERROR) + { + if (WSAGetLastError () == WSAEWOULDBLOCK) + return 0; + + return -1; + } + + return (int) sentLength; +} + +int +enet_socket_receive (ENetSocket socket, + ENetAddress * address, + ENetBuffer * buffers, + size_t bufferCount) +{ + INT sinLength = sizeof (struct sockaddr_in); + DWORD flags = 0, + recvLength; + struct sockaddr_in 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 -1; + + if (address != NULL) + { + address -> host = (enet_uint32) sin.sin_addr.s_addr; + address -> port = ENET_NET_TO_HOST_16 (sin.sin_port); + } + + return (int) recvLength; +} + +int +enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 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, enet_uint32 * condition, enet_uint32 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 + diff --git a/Externals/enet/win32.h b/Externals/enet/win32.h new file mode 100644 index 0000000000..e73ca9d052 --- /dev/null +++ b/Externals/enet/win32.h @@ -0,0 +1,57 @@ +/** + @file win32.h + @brief ENet Win32 header +*/ +#ifndef __ENET_WIN32_H__ +#define __ENET_WIN32_H__ + +#ifdef _MSC_VER +#ifdef ENET_BUILDING_LIB +#pragma warning (disable: 4267) // size_t to int conversion +#pragma warning (disable: 4244) // 64bit to 32bit int +#pragma warning (disable: 4018) // signed/unsigned mismatch +#pragma warning (disable: 4146) // unary minus operator applied to unsigned type +#endif +#endif + +#include +#include + +typedef SOCKET ENetSocket; + +#define ENET_SOCKET_NULL INVALID_SOCKET + +#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)) + +typedef struct +{ + size_t dataLength; + void * data; +} ENetBuffer; + +#define ENET_CALLBACK __cdecl + +#ifdef ENET_DLL +#ifdef ENET_BUILDING_LIB +#define ENET_API __declspec( dllexport ) +#else +#define ENET_API __declspec( dllimport ) +#endif /* ENET_BUILDING_LIB */ +#else /* !ENET_DLL */ +#define ENET_API extern +#endif /* ENET_DLL */ + +typedef fd_set ENetSocketSet; + +#define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO (& (sockset)) +#define ENET_SOCKETSET_ADD(sockset, socket) FD_SET (socket, & (sockset)) +#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR (socket, & (sockset)) +#define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET (socket, & (sockset)) + +#endif /* __ENET_WIN32_H__ */ + + diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln index 232b9b6115..be749a56ae 100644 --- a/Source/dolphin-emu.sln +++ b/Source/dolphin-emu.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 -VisualStudioVersion = 12.0.30723.0 +VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dolphin", "Core\DolphinWX\DolphinWX.vcxproj", "{47411FDB-1BF2-48D0-AB4E-C7C41160F898}" EndProject @@ -69,132 +69,356 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pch", "PCH\pch.vcxproj", "{ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "UnitTests\UnitTests.vcxproj", "{474661E7-C73A-43A6-AFEE-EE1EC433D49E}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "enet", "..\Externals\enet\enet.vcxproj", "{CBC76802-C128-4B17-BF6C-23B08C313E5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|Win32.ActiveCfg = Debug|x64 {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|x64.ActiveCfg = Debug|x64 {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Debug|x64.Build.0 = Debug|x64 + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|Mixed Platforms.Build.0 = Release|x64 + {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|Win32.ActiveCfg = Release|x64 {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|x64.ActiveCfg = Release|x64 {47411FDB-1BF2-48D0-AB4E-C7C41160F898}.Release|x64.Build.0 = Release|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Debug|Win32.ActiveCfg = Debug|x64 {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Debug|x64.ActiveCfg = Debug|x64 {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Debug|x64.Build.0 = Debug|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Release|Mixed Platforms.Build.0 = Release|x64 + {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Release|Win32.ActiveCfg = Release|x64 {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Release|x64.ActiveCfg = Release|x64 {69D2B16E-122A-4E5D-8C37-8EC7B0F7CEB0}.Release|x64.Build.0 = Release|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|Win32.ActiveCfg = Debug|x64 {E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.ActiveCfg = Debug|x64 {E54CF649-140E-4255-81A5-30A673C1FB36}.Debug|x64.Build.0 = Debug|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Release|Mixed Platforms.Build.0 = Release|x64 + {E54CF649-140E-4255-81A5-30A673C1FB36}.Release|Win32.ActiveCfg = Release|x64 {E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.ActiveCfg = Release|x64 {E54CF649-140E-4255-81A5-30A673C1FB36}.Release|x64.Build.0 = Release|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|Win32.ActiveCfg = Debug|x64 {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.ActiveCfg = Debug|x64 {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Debug|x64.Build.0 = Debug|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|Mixed Platforms.Build.0 = Release|x64 + {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|Win32.ActiveCfg = Release|x64 {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.ActiveCfg = Release|x64 {54AA7840-5BEB-4A0C-9452-74BA4CC7FD44}.Release|x64.Build.0 = Release|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|Win32.ActiveCfg = Debug|x64 {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.ActiveCfg = Debug|x64 {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Debug|x64.Build.0 = Debug|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|Mixed Platforms.Build.0 = Release|x64 + {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|Win32.ActiveCfg = Release|x64 {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.ActiveCfg = Release|x64 {2E6C348C-C75C-4D94-8D1E-9C1FCBF3EFE4}.Release|x64.Build.0 = Release|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|Win32.ActiveCfg = Debug|x64 {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.ActiveCfg = Debug|x64 {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Debug|x64.Build.0 = Debug|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|Mixed Platforms.Build.0 = Release|x64 + {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|Win32.ActiveCfg = Release|x64 {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.ActiveCfg = Release|x64 {160BDC25-5626-4B0D-BDD8-2953D9777FB5}.Release|x64.Build.0 = Release|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|Win32.ActiveCfg = Debug|x64 {6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.ActiveCfg = Debug|x64 {6BBD47CF-91FD-4077-B676-8B76980178A9}.Debug|x64.Build.0 = Debug|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|Mixed Platforms.Build.0 = Release|x64 + {6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|Win32.ActiveCfg = Release|x64 {6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.ActiveCfg = Release|x64 {6BBD47CF-91FD-4077-B676-8B76980178A9}.Release|x64.Build.0 = Release|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|Win32.ActiveCfg = Debug|x64 {604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.ActiveCfg = Debug|x64 {604C8368-F34A-4D55-82C8-CC92A0C13254}.Debug|x64.Build.0 = Debug|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|Mixed Platforms.Build.0 = Release|x64 + {604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|Win32.ActiveCfg = Release|x64 {604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.ActiveCfg = Release|x64 {604C8368-F34A-4D55-82C8-CC92A0C13254}.Release|x64.Build.0 = Release|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|Win32.ActiveCfg = Debug|x64 {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.ActiveCfg = Debug|x64 {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Debug|x64.Build.0 = Debug|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|Mixed Platforms.Build.0 = Release|x64 + {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|Win32.ActiveCfg = Release|x64 {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.ActiveCfg = Release|x64 {3DE9EE35-3E91-4F27-A014-2866AD8C3FE3}.Release|x64.Build.0 = Release|x64 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|Win32.ActiveCfg = Debug|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|Win32.Build.0 = Debug|Win32 {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.ActiveCfg = Debug|x64 {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Debug|x64.Build.0 = Debug|x64 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|Mixed Platforms.Build.0 = Release|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|Win32.ActiveCfg = Release|Win32 + {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|Win32.Build.0 = Release|Win32 {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.ActiveCfg = Release|x64 {8ADA04D7-6DB1-4DA4-AB55-64FB12A0997B}.Release|x64.Build.0 = Release|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|Win32.ActiveCfg = Debug|x64 {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.ActiveCfg = Debug|x64 {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Debug|x64.Build.0 = Debug|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|Mixed Platforms.Build.0 = Release|x64 + {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|Win32.ActiveCfg = Release|x64 {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.ActiveCfg = Release|x64 {0E033BE3-2E08-428E-9AE9-BC673EFA12B5}.Release|x64.Build.0 = Release|x64 + {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|Win32.ActiveCfg = Debug|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|Win32.Build.0 = Debug|Win32 {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.ActiveCfg = Debug|x64 {AB993F38-C31D-4897-B139-A620C42BC565}.Debug|x64.Build.0 = Debug|x64 + {AB993F38-C31D-4897-B139-A620C42BC565}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Release|Mixed Platforms.Build.0 = Release|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Release|Win32.ActiveCfg = Release|Win32 + {AB993F38-C31D-4897-B139-A620C42BC565}.Release|Win32.Build.0 = Release|Win32 {AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.ActiveCfg = Release|x64 {AB993F38-C31D-4897-B139-A620C42BC565}.Release|x64.Build.0 = Release|x64 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|Win32.ActiveCfg = Debug|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|Win32.Build.0 = Debug|Win32 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.ActiveCfg = Debug|x64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Debug|x64.Build.0 = Debug|x64 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|Mixed Platforms.Build.0 = Release|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|Win32.ActiveCfg = Release|Win32 + {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|Win32.Build.0 = Release|Win32 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.ActiveCfg = Release|x64 {31643FDB-1BB8-4965-9DE7-000FC88D35AE}.Release|x64.Build.0 = Release|x64 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|Win32.ActiveCfg = Debug|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|Win32.Build.0 = Debug|Win32 {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.ActiveCfg = Debug|x64 {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Debug|x64.Build.0 = Debug|x64 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|Mixed Platforms.Build.0 = Release|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|Win32.ActiveCfg = Release|Win32 + {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|Win32.Build.0 = Release|Win32 {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.ActiveCfg = Release|x64 {4C9F135B-A85E-430C-BAD4-4C67EF5FC12C}.Release|x64.Build.0 = Release|x64 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|Win32.ActiveCfg = Debug|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|Win32.Build.0 = Debug|Win32 {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|x64.ActiveCfg = Debug|x64 {B441CC62-877E-4B3F-93E0-0DE80544F705}.Debug|x64.Build.0 = Debug|x64 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|Mixed Platforms.Build.0 = Release|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|Win32.ActiveCfg = Release|Win32 + {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|Win32.Build.0 = Release|Win32 {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|x64.ActiveCfg = Release|x64 {B441CC62-877E-4B3F-93E0-0DE80544F705}.Release|x64.Build.0 = Release|x64 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|Win32.ActiveCfg = Debug|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|Win32.Build.0 = Debug|Win32 {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.ActiveCfg = Debug|x64 {677EA016-1182-440C-9345-DC88D1E98C0C}.Debug|x64.Build.0 = Debug|x64 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|Mixed Platforms.Build.0 = Release|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|Win32.ActiveCfg = Release|Win32 + {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|Win32.Build.0 = Release|Win32 {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.ActiveCfg = Release|x64 {677EA016-1182-440C-9345-DC88D1E98C0C}.Release|x64.Build.0 = Release|x64 + {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|Win32.ActiveCfg = Debug|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|Win32.Build.0 = Debug|Win32 {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.ActiveCfg = Debug|x64 {FF213B23-2C26-4214-9F88-85271E557E87}.Debug|x64.Build.0 = Debug|x64 + {FF213B23-2C26-4214-9F88-85271E557E87}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Release|Mixed Platforms.Build.0 = Release|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Release|Win32.ActiveCfg = Release|Win32 + {FF213B23-2C26-4214-9F88-85271E557E87}.Release|Win32.Build.0 = Release|Win32 {FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.ActiveCfg = Release|x64 {FF213B23-2C26-4214-9F88-85271E557E87}.Release|x64.Build.0 = Release|x64 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|Win32.Build.0 = Debug|Win32 {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.ActiveCfg = Debug|x64 {EC082900-B4D8-42E9-9663-77F02F6936AE}.Debug|x64.Build.0 = Debug|x64 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|Mixed Platforms.Build.0 = Release|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|Win32.ActiveCfg = Release|Win32 + {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|Win32.Build.0 = Release|Win32 {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.ActiveCfg = Release|x64 {EC082900-B4D8-42E9-9663-77F02F6936AE}.Release|x64.Build.0 = Release|x64 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|Win32.ActiveCfg = Debug|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|Win32.Build.0 = Debug|Win32 {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|x64.ActiveCfg = Debug|x64 {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Debug|x64.Build.0 = Debug|x64 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|Mixed Platforms.Build.0 = Release|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|Win32.ActiveCfg = Release|Win32 + {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|Win32.Build.0 = Release|Win32 {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|x64.ActiveCfg = Release|x64 {1C8436C9-DBAF-42BE-83BC-CF3EC9175ABE}.Release|x64.Build.0 = Release|x64 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|Win32.ActiveCfg = Debug|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|Win32.Build.0 = Debug|Win32 {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.ActiveCfg = Debug|x64 {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Debug|x64.Build.0 = Debug|x64 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|Mixed Platforms.Build.0 = Release|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|Win32.ActiveCfg = Release|Win32 + {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|Win32.Build.0 = Release|Win32 {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.ActiveCfg = Release|x64 {BDB6578B-0691-4E80-A46C-DF21639FD3B8}.Release|x64.Build.0 = Release|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|Win32.ActiveCfg = Debug|x64 {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.ActiveCfg = Debug|x64 {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Debug|x64.Build.0 = Debug|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|Mixed Platforms.Build.0 = Release|x64 + {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|Win32.ActiveCfg = Release|x64 {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.ActiveCfg = Release|x64 {41279555-F94F-4EBC-99DE-AF863C10C5C4}.Release|x64.Build.0 = Release|x64 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|Win32.ActiveCfg = Debug|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|Win32.Build.0 = Debug|Win32 {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.ActiveCfg = Debug|x64 {93D73454-2512-424E-9CDA-4BB357FE13DD}.Debug|x64.Build.0 = Debug|x64 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|Mixed Platforms.Build.0 = Release|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|Win32.ActiveCfg = Release|Win32 + {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|Win32.Build.0 = Release|Win32 {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.ActiveCfg = Release|x64 {93D73454-2512-424E-9CDA-4BB357FE13DD}.Release|x64.Build.0 = Release|x64 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|Win32.ActiveCfg = Debug|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|Win32.Build.0 = Debug|Win32 {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.ActiveCfg = Debug|x64 {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Debug|x64.Build.0 = Debug|x64 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|Mixed Platforms.Build.0 = Release|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|Win32.ActiveCfg = Release|Win32 + {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|Win32.Build.0 = Release|Win32 {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.ActiveCfg = Release|x64 {349EE8F9-7D25-4909-AAF5-FF3FADE72187}.Release|x64.Build.0 = Release|x64 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|Win32.Build.0 = Debug|Win32 {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|x64.ActiveCfg = Debug|x64 {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Debug|x64.Build.0 = Debug|x64 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|Mixed Platforms.Build.0 = Release|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|Win32.ActiveCfg = Release|Win32 + {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|Win32.Build.0 = Release|Win32 {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|x64.ActiveCfg = Release|x64 {0A18A071-125E-442F-AFF7-A3F68ABECF99}.Release|x64.Build.0 = Release|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|Win32.ActiveCfg = Debug|x64 {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.ActiveCfg = Debug|x64 {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.Build.0 = Debug|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|Mixed Platforms.Build.0 = Release|x64 + {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|Win32.ActiveCfg = Release|x64 {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.ActiveCfg = Release|x64 {1970D175-3DE8-4738-942A-4D98D1CDBF64}.Release|x64.Build.0 = Release|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|Win32.ActiveCfg = Debug|x64 {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.ActiveCfg = Debug|x64 {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Debug|x64.Build.0 = Debug|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|Mixed Platforms.Build.0 = Release|x64 + {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|Win32.ActiveCfg = Release|x64 {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.ActiveCfg = Release|x64 {96020103-4BA5-4FD2-B4AA-5B6D24492D4E}.Release|x64.Build.0 = Release|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|Win32.ActiveCfg = Debug|x64 {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.ActiveCfg = Debug|x64 {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Debug|x64.Build.0 = Debug|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|Mixed Platforms.Build.0 = Release|x64 + {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|Win32.ActiveCfg = Release|x64 {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.ActiveCfg = Release|x64 {EC1A314C-5588-4506-9C1E-2E58E5817F75}.Release|x64.Build.0 = Release|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|Win32.ActiveCfg = Debug|x64 {A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.ActiveCfg = Debug|x64 {A4C423AA-F57C-46C7-A172-D1A777017D29}.Debug|x64.Build.0 = Debug|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|Mixed Platforms.Build.0 = Release|x64 + {A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|Win32.ActiveCfg = Release|x64 {A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.ActiveCfg = Release|x64 {A4C423AA-F57C-46C7-A172-D1A777017D29}.Release|x64.Build.0 = Release|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|Win32.ActiveCfg = Debug|x64 {76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.ActiveCfg = Debug|x64 {76563A7F-1011-4EAD-B667-7BB18D09568E}.Debug|x64.Build.0 = Debug|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|Mixed Platforms.Build.0 = Release|x64 + {76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|Win32.ActiveCfg = Release|x64 {76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.ActiveCfg = Release|x64 {76563A7F-1011-4EAD-B667-7BB18D09568E}.Release|x64.Build.0 = Release|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|Win32.ActiveCfg = Debug|x64 {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.ActiveCfg = Debug|x64 {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Debug|x64.Build.0 = Debug|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|Mixed Platforms.Build.0 = Release|x64 + {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|Win32.ActiveCfg = Release|x64 {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.ActiveCfg = Release|x64 {474661E7-C73A-43A6-AFEE-EE1EC433D49E}.Release|x64.Build.0 = Release|x64 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|Win32.ActiveCfg = Debug|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|Win32.Build.0 = Debug|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.ActiveCfg = Debug|x64 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.Build.0 = Debug|x64 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.Build.0 = Release|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Win32.ActiveCfg = Release|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Win32.Build.0 = Release|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.ActiveCfg = Release|x64 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -225,5 +449,6 @@ Global {A4C423AA-F57C-46C7-A172-D1A777017D29} = {AAD1BCD6-9804-44A5-A5FC-4782EA00E9D4} {AAD1BCD6-9804-44A5-A5FC-4782EA00E9D4} = {15670B2E-CED6-4ED5-94CE-A00B1B2B5BA6} {76563A7F-1011-4EAD-B667-7BB18D09568E} = {15670B2E-CED6-4ED5-94CE-A00B1B2B5BA6} + {CBC76802-C128-4B17-BF6C-23B08C313E5E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677} EndGlobalSection EndGlobal From 461a54338b2b16c0752fded71e31a4a9591b0ddd Mon Sep 17 00:00:00 2001 From: Ziek Date: Mon, 2 Feb 2015 01:27:06 -0800 Subject: [PATCH 2/8] Switched tcp over to enet --- Source/Core/Core/Core.vcxproj | 3 + Source/Core/Core/NetPlayClient.cpp | 585 +++++++++++++++++------------ Source/Core/Core/NetPlayClient.h | 23 +- Source/Core/Core/NetPlayServer.cpp | 458 ++++++++++++---------- Source/Core/Core/NetPlayServer.h | 34 +- 5 files changed, 622 insertions(+), 481 deletions(-) diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 7343c193a7..226a45bf85 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -482,6 +482,9 @@ {3de9ee35-3e91-4f27-a014-2866ad8c3fe3} + + {cbc76802-c128-4b17-bf6c-23b08c313e5e} + diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 677db50aae..8bb26feeb8 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -4,8 +4,6 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" -#include "Core/Movie.h" -#include "Core/NetPlayClient.h" #include "Core/HW/EXI_DeviceIPL.h" #include "Core/HW/SI.h" #include "Core/HW/SI_DeviceDanceMat.h" @@ -15,7 +13,8 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h" #include "Core/IPC_HLE/WII_IPC_HLE_WiiMote.h" - +#include "Core/Movie.h" +#include "Core/NetPlayClient.h" static std::mutex crit_netplay_client; static NetPlayClient * netplay_client = nullptr; @@ -33,72 +32,60 @@ NetPlayClient::~NetPlayClient() m_do_loop = false; m_thread.join(); } + + if (m_server) + { + Disconnect(); + } + } // called from ---GUI--- thread -NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name) : m_dialog(dialog), m_is_running(false), m_do_loop(true) +NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name) + : m_dialog(dialog) + , m_client(nullptr) + , m_server(nullptr) + , m_is_running(false) + , m_do_loop(true) + , m_target_buffer_size() + , m_local_player(nullptr) + , m_current_game(0) + , m_is_recording(false) + , m_pid(0) + , m_connecting(false) { m_target_buffer_size = 20; ClearBuffers(); is_connected = false; - if (m_socket.connect(address, port, sf::seconds(5)) == sf::Socket::Done) + m_player_name = name; + + //Direct Connection + m_client = enet_host_create(nullptr, 1, 3, 0, 0); + + if (m_client == nullptr) { - // send connect message - sf::Packet spac; - spac << NETPLAY_VERSION; - spac << netplay_dolphin_ver; - spac << name; - m_socket.send(spac); + PanicAlertT("Couldn't Create Client"); + } - sf::Packet rpac; - // TODO: make this not hang - m_socket.receive(rpac); - MessageId error; - rpac >> error; + ENetAddress addr; + enet_address_set_host(&addr, address.c_str()); + addr.port = port; - // got error message - if (error) - { - switch (error) - { - case CON_ERR_SERVER_FULL : - PanicAlertT("The server is full!"); - break; - case CON_ERR_VERSION_MISMATCH : - PanicAlertT("The server and client's NetPlay versions are incompatible!"); - break; - case CON_ERR_GAME_RUNNING : - PanicAlertT("The server responded: the game is currently running!"); - break; - default : - PanicAlertT("The server sent an unknown error message!"); - break; - } - m_socket.disconnect(); - } - else - { - rpac >> m_pid; + m_server = enet_host_connect(m_client, &addr, 3, 0); - Player player; - player.name = name; - player.pid = m_pid; - player.revision = netplay_dolphin_ver; + if (m_server == nullptr) + { + PanicAlertT("Couldn't create peer."); + } - // add self to player list - m_players[m_pid] = player; - m_local_player = &m_players[m_pid]; - - m_dialog->Update(); - - //PanicAlertT("Connection successful: assigned player id: %d", m_pid); - is_connected = true; - - m_selector.add(m_socket); + ENetEvent netEvent; + int net = enet_host_service(m_client, &netEvent, 5000); + if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) + { + if (Connect()) m_thread = std::thread(&NetPlayClient::ThreadFunc, this); - } } else { @@ -106,6 +93,73 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay } } +bool NetPlayClient::Connect() +{ + // send connect message + sf::Packet spac; + spac << NETPLAY_VERSION; + spac << netplay_dolphin_ver; + spac << m_player_name; + Send(spac); + enet_host_flush(m_client); + sf::Packet rpac; + // TODO: make this not hang + ENetEvent netEvent; + if (enet_host_service(m_client, &netEvent, 5000) > 0 && netEvent.type == ENET_EVENT_TYPE_RECEIVE) + { + rpac.append(netEvent.packet->data, netEvent.packet->dataLength); + } + else + { + return false; + } + + MessageId error; + rpac >> error; + + // got error message + if (error) + { + switch (error) + { + case CON_ERR_SERVER_FULL: + PanicAlertT("The server is full!"); + break; + case CON_ERR_VERSION_MISMATCH: + PanicAlertT("The server and client's NetPlay versions are incompatible!"); + break; + case CON_ERR_GAME_RUNNING: + PanicAlertT("The server responded: the game is currently running!"); + break; + default: + PanicAlertT("The server sent an unknown error message!"); + break; + } + + Disconnect(); + return false; + } + else + { + rpac >> m_pid; + + Player player; + player.name = m_player_name; + player.pid = m_pid; + player.revision = netplay_dolphin_ver; + + // add self to player list + m_players[m_pid] = player; + m_local_player = &m_players[m_pid]; + + m_dialog->Update(); + + is_connected = true; + + return true; + } +} + // called from ---NETPLAY--- thread unsigned int NetPlayClient::OnData(sf::Packet& packet) { @@ -114,135 +168,135 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) switch (mid) { - case NP_MSG_PLAYER_JOIN : - { - Player player; - packet >> player.pid; - packet >> player.name; - packet >> player.revision; + case NP_MSG_PLAYER_JOIN: + { + Player player; + packet >> player.pid; + packet >> player.name; + packet >> player.revision; - { + { std::lock_guard lkp(m_crit.players); m_players[player.pid] = player; - } - - m_dialog->Update(); } - break; - case NP_MSG_PLAYER_LEAVE : + m_dialog->Update(); + } + break; + + case NP_MSG_PLAYER_LEAVE: + { + PlayerId pid; + packet >> pid; + { - PlayerId pid; - packet >> pid; - - { std::lock_guard lkp(m_crit.players); m_players.erase(m_players.find(pid)); - } - - m_dialog->Update(); } - break; - case NP_MSG_CHAT_MESSAGE : + m_dialog->Update(); + } + break; + + case NP_MSG_CHAT_MESSAGE: + { + PlayerId pid; + packet >> pid; + std::string msg; + packet >> msg; + + // don't need lock to read in this thread + const Player& player = m_players[pid]; + + // add to gui + std::ostringstream ss; + ss << player.name << '[' << (char)(pid + '0') << "]: " << msg; + + m_dialog->AppendChat(ss.str()); + } + break; + + case NP_MSG_PAD_MAPPING: + { + for (PadMapping& mapping : m_pad_map) { - PlayerId pid; - packet >> pid; - std::string msg; - packet >> msg; - - // don't need lock to read in this thread - const Player& player = m_players[pid]; - - // add to gui - std::ostringstream ss; - ss << player.name << '[' << (char)(pid+'0') << "]: " << msg; - - m_dialog->AppendChat(ss.str()); + packet >> mapping; } - break; - case NP_MSG_PAD_MAPPING : + UpdateDevices(); + + m_dialog->Update(); + } + break; + + case NP_MSG_WIIMOTE_MAPPING: + { + for (PadMapping& mapping : m_wiimote_map) { - for (PadMapping& mapping : m_pad_map) - { - packet >> mapping; - } - - UpdateDevices(); - - m_dialog->Update(); + packet >> mapping; } - break; - case NP_MSG_WIIMOTE_MAPPING : + m_dialog->Update(); + } + break; + + case NP_MSG_PAD_DATA: + { + PadMapping map = 0; + GCPadStatus pad; + packet >> map >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight; + + // trusting server for good map value (>=0 && <4) + // add to pad buffer + m_pad_buffer[map].Push(pad); + } + break; + + case NP_MSG_WIIMOTE_DATA: + { + PadMapping map = 0; + NetWiimote nw; + u8 size; + packet >> map >> size; + + nw.resize(size); + u8* data = new u8[size]; + for (unsigned int i = 0; i < size; ++i) + packet >> data[i]; + nw.assign(data, data + size); + delete[] data; + + // trusting server for good map value (>=0 && <4) + // add to Wiimote buffer + m_wiimote_buffer[(unsigned)map].Push(nw); + } + break; + + + case NP_MSG_PAD_BUFFER: + { + u32 size = 0; + packet >> size; + + m_target_buffer_size = size; + } + break; + + case NP_MSG_CHANGE_GAME: + { { - for (PadMapping& mapping : m_wiimote_map) - { - packet >> mapping; - } - - m_dialog->Update(); - } - break; - - case NP_MSG_PAD_DATA : - { - PadMapping map = 0; - GCPadStatus pad; - packet >> map >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight; - - // trusting server for good map value (>=0 && <4) - // add to pad buffer - m_pad_buffer[map].Push(pad); - } - break; - - case NP_MSG_WIIMOTE_DATA : - { - PadMapping map = 0; - NetWiimote nw; - u8 size; - packet >> map >> size; - - nw.resize(size); - u8* data = new u8[size]; - for (unsigned int i = 0; i < size; ++i) - packet >> data[i]; - nw.assign(data,data+size); - delete[] data; - - // trusting server for good map value (>=0 && <4) - // add to Wiimote buffer - m_wiimote_buffer[(unsigned)map].Push(nw); - } - break; - - - case NP_MSG_PAD_BUFFER : - { - u32 size = 0; - packet >> size; - - m_target_buffer_size = size; - } - break; - - case NP_MSG_CHANGE_GAME : - { - { std::lock_guard lkg(m_crit.game); packet >> m_selected_game; - } - - // update gui - m_dialog->OnMsgChangeGame(m_selected_game); } - break; - case NP_MSG_START_GAME : + // update gui + m_dialog->OnMsgChangeGame(m_selected_game); + } + break; + + case NP_MSG_START_GAME: + { { - { std::lock_guard lkg(m_crit.game); packet >> m_current_game; packet >> g_NetPlaySettings.m_CPUthread; @@ -252,62 +306,63 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> g_NetPlaySettings.m_WriteToMemcard; packet >> g_NetPlaySettings.m_OCEnable; packet >> g_NetPlaySettings.m_OCFactor; + int tmp; packet >> tmp; - g_NetPlaySettings.m_EXIDevice[0] = (TEXIDevices) tmp; + g_NetPlaySettings.m_EXIDevice[0] = (TEXIDevices)tmp; packet >> tmp; - g_NetPlaySettings.m_EXIDevice[1] = (TEXIDevices) tmp; - } - - m_dialog->OnMsgStartGame(); + g_NetPlaySettings.m_EXIDevice[1] = (TEXIDevices)tmp; } - break; - case NP_MSG_STOP_GAME : - { - m_dialog->OnMsgStopGame(); - } - break; + m_dialog->OnMsgStartGame(); + } + break; - case NP_MSG_DISABLE_GAME : - { - PanicAlertT("Other client disconnected while game is running!! NetPlay is disabled. You manually stop the game."); - std::lock_guard lkg(m_crit.game); - m_is_running = false; - NetPlay_Disable(); - } - break; + case NP_MSG_STOP_GAME: + { + m_dialog->OnMsgStopGame(); + } + break; - case NP_MSG_PING : - { - u32 ping_key = 0; - packet >> ping_key; + case NP_MSG_DISABLE_GAME: + { + PanicAlertT("Other client disconnected while game is running!! NetPlay is disabled. You manually stop the game."); + std::lock_guard lkg(m_crit.game); + m_is_running = false; + NetPlay_Disable(); + } + break; - sf::Packet spac; - spac << (MessageId)NP_MSG_PONG; - spac << ping_key; + case NP_MSG_PING: + { + u32 ping_key = 0; + packet >> ping_key; - std::lock_guard lks(m_crit.send); - m_socket.send(spac); - } - break; + sf::Packet spac; + spac << (MessageId)NP_MSG_PONG; + spac << ping_key; + + std::lock_guard lks(m_crit.send); + Send(spac); + } + break; case NP_MSG_PLAYER_PING_DATA: - { - PlayerId pid; - packet >> pid; + { + PlayerId pid; + packet >> pid; - { + { std::lock_guard lkp(m_crit.players); Player& player = m_players[pid]; packet >> player.ping; - } - - m_dialog->Update(); } - break; - default : + m_dialog->Update(); + } + break; + + default: PanicAlertT("Unknown message received with id : %d", mid); break; @@ -316,34 +371,70 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) return 0; } +void NetPlayClient::Send(sf::Packet& packet) +{ + ENetPacket* epac = enet_packet_create(packet.getData(), packet.getDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(m_server, 0, epac); +} + +void NetPlayClient::Disconnect() +{ + ENetEvent netEvent; + enet_peer_disconnect(m_server, 0); + while (enet_host_service(m_client, &netEvent, 3000) > 0) + { + switch (netEvent.type) + { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy(netEvent.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + m_server = nullptr; + return; + } + } + //didn't disconnect gracefully force disconnect + enet_peer_reset(m_server); + m_server = nullptr; +} + // called from ---NETPLAY--- thread void NetPlayClient::ThreadFunc() { while (m_do_loop) { - if (m_selector.wait(sf::milliseconds(10))) + ENetEvent netEvent; + int net; + { + std::lock_guard lks(m_crit.send); + net = enet_host_service(m_client, &netEvent, 4); + } + if (net > 0) { sf::Packet rpac; - switch (m_socket.receive(rpac)) + switch (netEvent.type) { - case sf::Socket::Done : + case ENET_EVENT_TYPE_RECEIVE: + rpac.append(netEvent.packet->data, netEvent.packet->dataLength); OnData(rpac); - break; - //case sf::Socket::Disconnected : - default : + enet_packet_destroy(netEvent.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: m_is_running = false; NetPlay_Disable(); m_dialog->AppendChat("< LOST CONNECTION TO SERVER >"); PanicAlertT("Lost connection to server!"); m_do_loop = false; + + netEvent.peer->data = nullptr; break; } } + } - m_socket.disconnect(); - + Disconnect(); return; } @@ -357,7 +448,7 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector& pid_list) std::map::const_iterator i = m_players.begin(), e = m_players.end(); - for ( ; i!=e; ++i) + for (; i != e; ++i) { const Player *player = &(i->second); ss << player->name << "[" << (int)player->pid << "] : " << player->revision << " | "; @@ -389,7 +480,7 @@ void NetPlayClient::GetPlayers(std::vector &player_list) std::map::const_iterator i = m_players.begin(), e = m_players.end(); - for ( ; i!=e; ++i) + for (; i != e; ++i) { const Player *player = &(i->second); player_list.push_back(player); @@ -405,7 +496,7 @@ void NetPlayClient::SendChatMessage(const std::string& msg) spac << msg; std::lock_guard lks(m_crit.send); - m_socket.send(spac); + Send(spac); } // called from ---CPU--- thread @@ -418,7 +509,7 @@ void NetPlayClient::SendPadState(const PadMapping in_game_pad, const GCPadStatus spac << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight; std::lock_guard lks(m_crit.send); - m_socket.send(spac); + Send(spac); } // called from ---CPU--- thread @@ -435,7 +526,7 @@ void NetPlayClient::SendWiimoteState(const PadMapping in_game_pad, const NetWiim } std::lock_guard lks(m_crit.send); - m_socket.send(spac); + Send(spac); } // called from ---GUI--- thread @@ -450,7 +541,7 @@ bool NetPlayClient::StartGame(const std::string &path) spac << (char *)&g_NetPlaySettings; std::lock_guard lks(m_crit.send); - m_socket.send(spac); + Send(spac); if (m_is_running) { @@ -529,7 +620,7 @@ void NetPlayClient::UpdateDevices() void NetPlayClient::ClearBuffers() { // clear pad buffers, Clear method isn't thread safe - for (unsigned int i=0; i<4; ++i) + for (unsigned int i = 0; i<4; ++i) { while (m_pad_buffer[i].Size()) m_pad_buffer[i].Pop(); @@ -616,44 +707,44 @@ bool NetPlayClient::GetNetPads(const u8 pad_nb, GCPadStatus* pad_status) bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const u8 size) { NetWiimote nw; - static u8 previousSize[4] = {4,4,4,4}; + static u8 previousSize[4] = { 4, 4, 4, 4 }; { - std::lock_guard lkp(m_crit.players); + std::lock_guard lkp(m_crit.players); - // in game mapping for this local Wiimote - unsigned int in_game_num = LocalWiimoteToInGameWiimote(_number); - // does this local Wiimote map in game? - if (in_game_num < 4) - { - if (previousSize[in_game_num] == size) + // in game mapping for this local Wiimote + unsigned int in_game_num = LocalWiimoteToInGameWiimote(_number); + // does this local Wiimote map in game? + if (in_game_num < 4) { - nw.assign(data, data + size); - do + if (previousSize[in_game_num] == size) { - // add to buffer - m_wiimote_buffer[in_game_num].Push(nw); + nw.assign(data, data + size); + do + { + // add to buffer + m_wiimote_buffer[in_game_num].Push(nw); - SendWiimoteState(in_game_num, nw); - } while (m_wiimote_buffer[in_game_num].Size() <= m_target_buffer_size * 200 / 120); // TODO: add a seperate setting for wiimote buffer? - } - else - { - while (m_wiimote_buffer[in_game_num].Size() > 0) - { - // Reporting mode changed, so previous buffer is no good. - m_wiimote_buffer[in_game_num].Pop(); + SendWiimoteState(in_game_num, nw); + } while (m_wiimote_buffer[in_game_num].Size() <= m_target_buffer_size * 200 / 120); // TODO: add a seperate setting for wiimote buffer? } - nw.resize(size, 0); + else + { + while (m_wiimote_buffer[in_game_num].Size() > 0) + { + // Reporting mode changed, so previous buffer is no good. + m_wiimote_buffer[in_game_num].Pop(); + } + nw.resize(size, 0); - m_wiimote_buffer[in_game_num].Push(nw); - m_wiimote_buffer[in_game_num].Push(nw); - m_wiimote_buffer[in_game_num].Push(nw); - m_wiimote_buffer[in_game_num].Push(nw); - m_wiimote_buffer[in_game_num].Push(nw); - m_wiimote_buffer[in_game_num].Push(nw); - previousSize[in_game_num] = size; + m_wiimote_buffer[in_game_num].Push(nw); + m_wiimote_buffer[in_game_num].Push(nw); + m_wiimote_buffer[in_game_num].Push(nw); + m_wiimote_buffer[in_game_num].Push(nw); + m_wiimote_buffer[in_game_num].Push(nw); + m_wiimote_buffer[in_game_num].Push(nw); + previousSize[in_game_num] = size; + } } - } } // unlock players @@ -753,7 +844,7 @@ void NetPlayClient::Stop() { sf::Packet spac; spac << (MessageId)NP_MSG_STOP_GAME; - m_socket.send(spac); + Send(spac); } } @@ -844,7 +935,7 @@ u32 CEXIIPL::NetPlay_GetGCTime() std::lock_guard lk(crit_netplay_client); if (netplay_client) - return NETPLAY_INITIAL_GCTIME; // watev + return NETPLAY_INITIAL_GCTIME; else return 0; } diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 521b0bb63c..6d923f0697 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -8,16 +8,14 @@ #include #include -#include - +#include "enet/enet.h" #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" #include "Common/Thread.h" #include "Common/Timer.h" - #include "Core/NetPlayProto.h" - #include "InputCommon/GCPadStatus.h" +#include class NetPlayUI { @@ -38,7 +36,7 @@ public: class Player { - public: +public: PlayerId pid; std::string name; std::string revision; @@ -87,9 +85,10 @@ protected: Common::FifoQueue m_wiimote_buffer[4]; NetPlayUI* m_dialog; - sf::TcpSocket m_socket; - std::thread m_thread; - sf::SocketSelector m_selector; + + ENetHost* m_client; + ENetPeer* m_server; + std::thread m_thread; std::string m_selected_game; volatile bool m_is_running; @@ -111,9 +110,17 @@ private: void SendPadState(const PadMapping in_game_pad, const GCPadStatus& np); void SendWiimoteState(const PadMapping in_game_pad, const NetWiimote& nw); unsigned int OnData(sf::Packet& packet); + void Send(sf::Packet& packet); + void Disconnect(); + bool Connect(); + + void OnTraversalDisconnect(int fail); PlayerId m_pid; std::map m_players; + std::string m_host_spec; + std::string m_player_name; + bool m_connecting; }; void NetPlay_Enable(NetPlayClient* const np); diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 9b7dced971..f8861c32e4 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -7,6 +7,8 @@ #include "Common/StdMakeUnique.h" #include "Common/StringUtil.h" +#include "Core/HW/EXI_DeviceIPL.h" +#include "Core/NetPlayClient.h" //for NetPlayUI #include "Core/NetPlayServer.h" #include "InputCommon/GCPadStatus.h" @@ -16,7 +18,7 @@ NetPlayServer::~NetPlayServer() { m_do_loop = false; m_thread.join(); - m_socket.close(); + enet_host_destroy(m_server); } #ifdef USE_UPNP @@ -28,15 +30,35 @@ NetPlayServer::~NetPlayServer() } // called from ---GUI--- thread -NetPlayServer::NetPlayServer(const u16 port) : is_connected(false), m_is_running(false) +NetPlayServer::NetPlayServer(const u16 port) + : is_connected(false) + , m_is_running(false) + , m_do_loop(false) + , m_ping_key(0) + , m_update_pings(false) + , m_current_game(0) + , m_target_buffer_size(0) + , m_selected_game("") + , m_server(nullptr) { + //--use server time + if (enet_initialize() != 0) + { + PanicAlertT("Enet Didn't Initialize"); + } + memset(m_pad_map, -1, sizeof(m_pad_map)); memset(m_wiimote_map, -1, sizeof(m_wiimote_map)); - if (m_socket.listen(port) == sf::Socket::Done) + + ENetAddress serverAddr; + serverAddr.host = ENET_HOST_ANY; + serverAddr.port = port; + m_server = enet_host_create(&serverAddr, 10, 3, 0, 0); + + if (m_server != nullptr) { is_connected = true; m_do_loop = true; - m_selector.add(m_socket); m_thread = std::thread(&NetPlayServer::ThreadFunc, this); m_target_buffer_size = 5; } @@ -48,10 +70,9 @@ void NetPlayServer::ThreadFunc() while (m_do_loop) { // update pings every so many seconds + if ((m_ping_timer.GetTimeElapsed() > 1000) || m_update_pings) { - //PanicAlertT("Sending pings"); - m_ping_key = Common::Timer::GetTimeMs(); sf::Packet spac; @@ -61,23 +82,26 @@ void NetPlayServer::ThreadFunc() std::lock_guard lks(m_crit.send); m_ping_timer.Start(); SendToClients(spac); - m_update_pings = false; } - // check if any sockets need attention - if (m_selector.wait(sf::milliseconds(10))) + ENetEvent netEvent; + int net; { - // listening socket - if (m_selector.isReady(m_socket)) + std::lock_guard lks(m_crit.send); + net = enet_host_service(m_server, &netEvent, 4); + } + if (net > 0) + { + switch (netEvent.type) { - auto accept_socket = std::make_unique(); - m_socket.accept(*accept_socket); - + case ENET_EVENT_TYPE_CONNECT: + { + ENetPeer* accept_peer = netEvent.peer; unsigned int error; { - std::lock_guard lkg(m_crit.game); - error = OnConnect(accept_socket); + std::lock_guard lkg(m_crit.game); + error = OnConnect(accept_peer); } if (error) @@ -85,51 +109,61 @@ void NetPlayServer::ThreadFunc() sf::Packet spac; spac << (MessageId)error; // don't need to lock, this client isn't in the client map - accept_socket->send(spac); - accept_socket->disconnect(); + std::lock_guard lks(m_crit.send); + Send(accept_peer, spac); + + enet_peer_disconnect(accept_peer, 0); } } - // client sockets - for (auto it = m_players.begin(); it != m_players.end();) + break; + case ENET_EVENT_TYPE_RECEIVE: { - // move iterator on immediately so client can be removed - Client& client = *it; - it++; + sf::Packet rpac; + rpac.append(netEvent.packet->data, netEvent.packet->dataLength); - if (m_selector.isReady(*client.socket)) + auto it = m_players.find(netEvent.peer->connectID); + Client& client = it->second; + if (OnData(rpac, client) != 0) { - sf::Packet rpac; - switch (client.socket->receive(rpac)) - { - case sf::Socket::Done : - // if a bad packet is received, disconnect the client - if (0 == OnData(rpac, client)) - break; - - //case sf::Socket::Disconnected : - default : - { - std::lock_guard lkg(m_crit.game); - OnDisconnect(client); - break; - } - } + // if a bad packet is received, disconnect the client + std::lock_guard lkg(m_crit.game); + OnDisconnect(client); } + enet_packet_destroy(netEvent.packet); + } + break; + case ENET_EVENT_TYPE_DISCONNECT: + { + std::lock_guard lkg(m_crit.game); + auto it = m_players.find(netEvent.peer->connectID); + if (it != m_players.end()) + { + Client& client = it->second; + OnDisconnect(client); + + netEvent.peer->data = nullptr; + } + } + break; } } } // close listening socket and client sockets for (auto& player_entry : m_players) - player_entry.socket->disconnect(); + enet_peer_disconnect(player_entry.second.socket, 0); } // called from ---NETPLAY--- thread -unsigned int NetPlayServer::OnConnect(std::unique_ptr& socket) +unsigned int NetPlayServer::OnConnect(ENetPeer* socket) { sf::Packet rpac; - // TODO: make this not hang / check if good packet - socket->receive(rpac); + ENetPacket* epack; + do + { + epack = enet_peer_receive(socket, 0); + } while (epack == nullptr); + rpac.append(epack->data, epack->dataLength); std::string npver; rpac >> npver; @@ -149,15 +183,17 @@ unsigned int NetPlayServer::OnConnect(std::unique_ptr& socket) m_update_pings = true; Client player; - player.socket = std::move(socket); + player.socket = socket; rpac >> player.revision; rpac >> player.name; + enet_packet_destroy(epack); + // give new client first available id PlayerId pid = 1; for (auto i = m_players.begin(); i != m_players.end(); ++i) { - if (i->pid == pid) + if (i->second.pid == pid) { pid++; i = m_players.begin(); @@ -176,56 +212,53 @@ unsigned int NetPlayServer::OnConnect(std::unique_ptr& socket) } { - std::lock_guard lks(m_crit.send); + std::lock_guard lks(m_crit.send); - // send join message to already connected clients - sf::Packet spac; - spac << (MessageId)NP_MSG_PLAYER_JOIN; - spac << player.pid << player.name << player.revision; - SendToClients(spac); - - // send new client success message with their id - spac.clear(); - spac << (MessageId)0; - spac << player.pid; - player.socket->send(spac); - - // send new client the selected game - if (m_selected_game != "") - { - spac.clear(); - spac << (MessageId)NP_MSG_CHANGE_GAME; - spac << m_selected_game; - player.socket->send(spac); - } - - // send the pad buffer value - spac.clear(); - spac << (MessageId)NP_MSG_PAD_BUFFER; - spac << (u32)m_target_buffer_size; - player.socket->send(spac); - - // sync values with new client - for (const auto& p : m_players) - { - spac.clear(); + // send join message to already connected clients + sf::Packet spac; spac << (MessageId)NP_MSG_PLAYER_JOIN; - spac << p.pid << p.name << p.revision; - player.socket->send(spac); - } + spac << player.pid << player.name << player.revision; + SendToClients(spac); + + // send new client success message with their id + spac.clear(); + spac << (MessageId)0; + spac << player.pid; + Send(player.socket, spac); + + // send new client the selected game + if (m_selected_game != "") + { + spac.clear(); + spac << (MessageId)NP_MSG_CHANGE_GAME; + spac << m_selected_game; + Send(player.socket, spac); + } + + // send the pad buffer value + spac.clear(); + spac << (MessageId)NP_MSG_PAD_BUFFER; + spac << (u32)m_target_buffer_size; + Send(player.socket, spac); + + // sync values with new client + for (const auto& p : m_players) + { + spac.clear(); + spac << (MessageId)NP_MSG_PLAYER_JOIN; + spac << p.second.pid << p.second.name << p.second.revision; + Send(player.socket, spac); + } } // unlock send - // add client to selector/ used for receiving - m_selector.add(*player.socket); - // add client to the player list { - std::lock_guard lkp(m_crit.players); - m_players.push_back(std::move(player)); - std::lock_guard lks(m_crit.send); - UpdatePadMapping(); // sync pad mappings with everyone - UpdateWiimoteMapping(); + std::lock_guard lkp(m_crit.players); + m_players.emplace(player.socket->connectID, player); + std::lock_guard lks(m_crit.send); + UpdatePadMapping(); // sync pad mappings with everyone + UpdateWiimoteMapping(); } return 0; @@ -260,10 +293,12 @@ unsigned int NetPlayServer::OnDisconnect(Client& player) spac << (MessageId)NP_MSG_PLAYER_LEAVE; spac << pid; - m_selector.remove(*player.socket); + enet_peer_disconnect(player.socket, 0); std::lock_guard lkp(m_crit.players); - m_players.remove(player); + auto it = m_players.find(player.pid); + if (it != m_players.end()) + m_players.erase(it); // alert other players of disconnect std::lock_guard lks(m_crit.send); @@ -371,124 +406,124 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) switch (mid) { - case NP_MSG_CHAT_MESSAGE : + case NP_MSG_CHAT_MESSAGE: + { + std::string msg; + packet >> msg; + + // send msg to other clients + sf::Packet spac; + spac << (MessageId)NP_MSG_CHAT_MESSAGE; + spac << player.pid; + spac << msg; + { - std::string msg; - packet >> msg; - - // send msg to other clients - sf::Packet spac; - spac << (MessageId)NP_MSG_CHAT_MESSAGE; - spac << player.pid; - spac << msg; - - { - std::lock_guard lks(m_crit.send); - SendToClients(spac, player.pid); - } - } - break; - - case NP_MSG_PAD_DATA : - { - // if this is pad data from the last game still being received, ignore it - if (player.current_game != m_current_game) - break; - - PadMapping map = 0; - GCPadStatus pad; - packet >> map >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight; - - // If the data is not from the correct player, - // then disconnect them. - if (m_pad_map[map] != player.pid) - return 1; - - // Relay to clients - sf::Packet spac; - spac << (MessageId)NP_MSG_PAD_DATA; - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight; - std::lock_guard lks(m_crit.send); SendToClients(spac, player.pid); } - break; + } + break; - case NP_MSG_WIIMOTE_DATA : + case NP_MSG_PAD_DATA: + { + // if this is pad data from the last game still being received, ignore it + if (player.current_game != m_current_game) + break; + + PadMapping map = 0; + GCPadStatus pad; + packet >> map >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight; + + // If the data is not from the correct player, + // then disconnect them. + if (m_pad_map[map] != player.pid) + return 1; + + // Relay to clients + sf::Packet spac; + spac << (MessageId)NP_MSG_PAD_DATA; + spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight; + + std::lock_guard lks(m_crit.send); + SendToClients(spac, player.pid); + } + break; + + case NP_MSG_WIIMOTE_DATA: + { + // if this is Wiimote data from the last game still being received, ignore it + if (player.current_game != m_current_game) + break; + + PadMapping map = 0; + u8 size; + packet >> map >> size; + std::vector data(size); + for (size_t i = 0; i < data.size(); ++i) + packet >> data[i]; + + // If the data is not from the correct player, + // then disconnect them. + if (m_wiimote_map[map] != player.pid) { - // if this is Wiimote data from the last game still being received, ignore it - if (player.current_game != m_current_game) - break; - - PadMapping map = 0; - u8 size; - packet >> map >> size; - std::vector data(size); - for (size_t i = 0; i < data.size(); ++i) - packet >> data[i]; - - // If the data is not from the correct player, - // then disconnect them. - if (m_wiimote_map[map] != player.pid) - { - return 1; - } - - // relay to clients - sf::Packet spac; - spac << (MessageId)NP_MSG_WIIMOTE_DATA; - spac << map; - spac << size; - for (const u8& byte : data) - spac << byte; - - std::lock_guard lks(m_crit.send); - SendToClients(spac, player.pid); + return 1; } - break; - case NP_MSG_PONG : + // relay to clients + sf::Packet spac; + spac << (MessageId)NP_MSG_WIIMOTE_DATA; + spac << map; + spac << size; + for (const u8& byte : data) + spac << byte; + + std::lock_guard lks(m_crit.send); + SendToClients(spac, player.pid); + } + break; + + case NP_MSG_PONG: + { + const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); + u32 ping_key = 0; + packet >> ping_key; + + if (m_ping_key == ping_key) { - const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); - u32 ping_key = 0; - packet >> ping_key; - - if (m_ping_key == ping_key) - { - player.ping = ping; - } - - sf::Packet spac; - spac << (MessageId)NP_MSG_PLAYER_PING_DATA; - spac << player.pid; - spac << player.ping; - - std::lock_guard lks(m_crit.send); - SendToClients(spac); + player.ping = ping; } - break; - case NP_MSG_START_GAME : - { - packet >> player.current_game; - } - break; + sf::Packet spac; + spac << (MessageId)NP_MSG_PLAYER_PING_DATA; + spac << player.pid; + spac << player.ping; + + std::lock_guard lks(m_crit.send); + SendToClients(spac); + } + break; + + case NP_MSG_START_GAME: + { + packet >> player.current_game; + } + break; case NP_MSG_STOP_GAME: - { - // tell clients to stop game - sf::Packet spac; - spac << (MessageId)NP_MSG_STOP_GAME; + { + // tell clients to stop game + sf::Packet spac; + spac << (MessageId)NP_MSG_STOP_GAME; - std::lock_guard lkp(m_crit.players); - std::lock_guard lks(m_crit.send); - SendToClients(spac); + std::lock_guard lkp(m_crit.players); + std::lock_guard lks(m_crit.send); + SendToClients(spac); - m_is_running = false; - } - break; + m_is_running = false; + } + break; - default : + default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); // unknown message, kick the client return 1; @@ -573,25 +608,36 @@ void NetPlayServer::SendToClients(sf::Packet& packet, const PlayerId skip_pid) { for (auto& p : m_players) { - if (p.pid && p.pid != skip_pid) + if (p.second.pid && p.second.pid != skip_pid) { - p.socket->send(packet); + Send(p.second.socket, packet); } } } -void NetPlayServer::KickPlayer(u8 player) +void NetPlayServer::Send(ENetPeer* socket, sf::Packet& packet) +{ + ENetPacket* epac = enet_packet_create(packet.getData(), packet.getDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(socket, 0, epac); +} + +void NetPlayServer::KickPlayer(PlayerId player) { for (auto& current_player : m_players) { - if (current_player.pid == player) + if (current_player.second.pid == player) { - current_player.socket->disconnect(); + enet_peer_disconnect(current_player.second.socket, 0); return; } } } +u16 NetPlayServer::GetPort() +{ + return m_server->address.port; +} + #ifdef USE_UPNP #include #include @@ -615,7 +661,11 @@ void NetPlayServer::TryPortmapping(u16 port) // UPnP thread: try to map a port void NetPlayServer::mapPortThread(const u16 port) { - std::string ourIP = sf::IpAddress::getLocalAddress().toString(); + ENetAddress adr; + char cIP[20]; + + enet_address_get_host_ip(&adr, cIP, 20); + std::string ourIP(cIP); if (!m_upnp_inited) if (!initUPnP()) @@ -677,7 +727,7 @@ bool NetPlayServer::initUPnP() for (const UPNPDev* dev : igds) { - char* descXML = (char*) miniwget(dev->descURL, &descXMLsize, 0); + char* descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0); if (descXML) { parserootdesc(descXML, descXMLsize, &m_upnp_data); @@ -708,9 +758,9 @@ bool NetPlayServer::UPnPMapPort(const std::string& addr, const u16 port) std::string port_str = StringFromFormat("%d", port); int result = UPNP_AddPortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, - port_str.c_str(), port_str.c_str(), addr.c_str(), - (std::string("dolphin-emu TCP on ") + addr).c_str(), - "TCP", nullptr, nullptr); + port_str.c_str(), port_str.c_str(), addr.c_str(), + (std::string("dolphin-emu TCP on ") + addr).c_str(), + "TCP", nullptr, nullptr); if (result != 0) return false; @@ -732,7 +782,7 @@ bool NetPlayServer::UPnPUnmapPort(const u16 port) { std::string port_str = StringFromFormat("%d", port); UPNP_DeletePortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, - port_str.c_str(), "TCP", nullptr); + port_str.c_str(), "TCP", nullptr); return true; } diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 9f51f760eb..d82195f5f7 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -7,15 +7,13 @@ #include #include #include +#include -#include - -#include "Common/CommonTypes.h" -#include "Common/CommonTypes.h" +#include "enet/enet.h" #include "Common/Thread.h" #include "Common/Timer.h" - #include "Core/NetPlayProto.h" +#include class NetPlayServer { @@ -40,7 +38,9 @@ public: void AdjustPadBufferSize(unsigned int size); - void KickPlayer(u8 player); + void KickPlayer(PlayerId player); + + u16 GetPort(); bool is_connected; @@ -56,20 +56,10 @@ private: std::string name; std::string revision; - std::unique_ptr socket; + ENetPeer* socket; u32 ping; u32 current_game; - // VS2013 does not generate the right constructors here automatically - // like GCC does, so we implement them manually - Client() = default; - Client(const Client& other) = delete; - Client(Client&& other) - : pid(other.pid), name(std::move(other.name)), revision(std::move(other.revision)), - socket(std::move(other.socket)), ping(other.ping), current_game(other.current_game) - { - } - bool operator==(const Client& other) const { return this == &other; @@ -77,7 +67,8 @@ private: }; void SendToClients(sf::Packet& packet, const PlayerId skip_pid = 0); - unsigned int OnConnect(std::unique_ptr& socket); + void Send(ENetPeer* socket, sf::Packet& packet); + unsigned int OnConnect(ENetPeer* socket); unsigned int OnDisconnect(Client& player); unsigned int OnData(sf::Packet& packet, Client& player); void UpdatePadMapping(); @@ -95,7 +86,7 @@ private: PadMapping m_pad_map[4]; PadMapping m_wiimote_map[4]; - std::list m_players; + std::map m_players; struct { @@ -105,10 +96,9 @@ private: } m_crit; std::string m_selected_game; - - sf::TcpListener m_socket; std::thread m_thread; - sf::SocketSelector m_selector; + + ENetHost* m_server; #ifdef USE_UPNP static void mapPortThread(const u16 port); From 4cdc307b8795eeb5fa52a1bc533bcde08b0f3876 Mon Sep 17 00:00:00 2001 From: Ziek Date: Mon, 2 Feb 2015 01:56:53 -0800 Subject: [PATCH 3/8] Moved Traversal Client code over from old netplay Moved over gui code for copying host code added gui to netplay diag setup to switch between direct and traversal connection --- Source/Core/Common/Common.vcxproj | 5 +- Source/Core/Common/Common.vcxproj.filters | 7 +- Source/Core/Common/TraversalClient.cpp | 370 +++++++++++++++++++ Source/Core/Common/TraversalClient.h | 88 +++++ Source/Core/Common/TraversalProto.h | 96 +++++ Source/Core/Core/NetPlayClient.cpp | 159 +++++++-- Source/Core/Core/NetPlayClient.h | 22 +- Source/Core/Core/NetPlayProto.h | 37 +- Source/Core/Core/NetPlayServer.cpp | 139 +++++++- Source/Core/Core/NetPlayServer.h | 18 +- Source/Core/DolphinWX/NetWindow.cpp | 413 +++++++++++++++++----- Source/Core/DolphinWX/NetWindow.h | 41 ++- 12 files changed, 1240 insertions(+), 155 deletions(-) create mode 100644 Source/Core/Common/TraversalClient.cpp create mode 100644 Source/Core/Common/TraversalClient.h create mode 100644 Source/Core/Common/TraversalProto.h diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index dfe91db48b..fdd3183587 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -80,6 +80,8 @@ + + @@ -116,6 +118,7 @@ + @@ -142,4 +145,4 @@ - + \ No newline at end of file diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 2f121ca882..da11bd379b 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -71,6 +71,9 @@ + + + @@ -117,8 +120,10 @@ + + - + \ No newline at end of file diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp new file mode 100644 index 0000000000..9ac5eca238 --- /dev/null +++ b/Source/Core/Common/TraversalClient.cpp @@ -0,0 +1,370 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#include "Common/TraversalClient.h" +#include "enet/enet.h" +#include "Timer.h" + +static void GetRandomishBytes(u8* buf, size_t size) +{ + // We don't need high quality random numbers (which might not be available), + // just non-repeating numbers! + srand(enet_time_get()); + for (size_t i = 0; i < size; i++) + buf[i] = rand() & 0xff; +} + +TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server) + : m_NetHost(netHost) + , m_Server(server) + , m_Client(nullptr) + , m_FailureReason(0) + , m_ConnectRequestId(0) + , m_PendingConnect(false) + , m_PingTime(0) +{ + netHost->intercept = TraversalClient::InterceptCallback; + + Reset(); + + ReconnectToServer(); +} + +TraversalClient::~TraversalClient() +{ +} + +void TraversalClient::ReconnectToServer() +{ + m_Server = "vps.qoid.us"; // XXX + if (enet_address_set_host(&m_ServerAddress, m_Server.c_str())) + { + OnFailure(BadHost); + return; + } + m_ServerAddress.port = 6262; + + m_State = Connecting; + + TraversalPacket hello = {}; + hello.type = TraversalPacketHelloFromClient; + hello.helloFromClient.protoVersion = TraversalProtoVersion; + SendTraversalPacket(hello); + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +static ENetAddress MakeENetAddress(TraversalInetAddress* address) +{ + ENetAddress eaddr; + if (address->isIPV6) + { + eaddr.port = 0; // no support yet :( + } + else + { + eaddr.host = address->address[0]; + eaddr.port = ntohs(address->port); + } + return eaddr; +} + +void TraversalClient::ConnectToClient(const std::string& host) +{ + if (host.size() > sizeof(TraversalHostId)) + { + PanicAlert("host too long"); + return; + } + TraversalPacket packet = {}; + packet.type = TraversalPacketConnectPlease; + memcpy(packet.connectPlease.hostId.data(), host.c_str(), host.size()); + m_ConnectRequestId = SendTraversalPacket(packet); + m_PendingConnect = true; +} + +bool TraversalClient::TestPacket(u8* data, size_t size, ENetAddress* from) +{ + if (from->host == m_ServerAddress.host && + from->port == m_ServerAddress.port) + { + if (size < sizeof(TraversalPacket)) + { + ERROR_LOG(NETPLAY, "Received too-short traversal packet."); + } + else + { + HandleServerPacket((TraversalPacket*) data); + return true; + } + } + return false; +} + +//--Temporary until more of the old netplay branch is moved over +void TraversalClient::Update() +{ + ENetEvent netEvent; + if (enet_host_service(m_NetHost, &netEvent, 4) > 0) + { + switch (netEvent.type) + { + case ENET_EVENT_TYPE_RECEIVE: + TestPacket(netEvent.packet->data, netEvent.packet->dataLength, &netEvent.peer->address); + + enet_packet_destroy(netEvent.packet); + break; + } + } + HandleResends(); +} + +void TraversalClient::HandleServerPacket(TraversalPacket* packet) +{ + u8 ok = 1; + switch (packet->type) + { + case TraversalPacketAck: + if (!packet->ack.ok) + { + OnFailure(ServerForgotAboutUs); + break; + } + for (auto it = m_OutgoingTraversalPackets.begin(); it != m_OutgoingTraversalPackets.end(); ++it) + { + if (it->packet.requestId == packet->requestId) + { + m_OutgoingTraversalPackets.erase(it); + break; + } + } + break; + case TraversalPacketHelloFromServer: + if (m_State != Connecting) + break; + if (!packet->helloFromServer.ok) + { + OnFailure(VersionTooOld); + break; + } + m_HostId = packet->helloFromServer.yourHostId; + m_State = Connected; + if (m_Client) + m_Client->OnTraversalStateChanged(); + break; + case TraversalPacketPleaseSendPacket: + { + // security is overrated. + ENetAddress addr = MakeENetAddress(&packet->pleaseSendPacket.address); + if (addr.port != 0) + { + char message[] = "Hello from Dolphin Netplay..."; + ENetBuffer buf; + buf.data = message; + buf.dataLength = sizeof(message) - 1; + enet_socket_send(m_NetHost->socket, &addr, &buf, 1); + } + else + { + // invalid IPV6 + ok = 0; + } + break; + } + case TraversalPacketConnectReady: + case TraversalPacketConnectFailed: + { + if (!m_PendingConnect || packet->connectReady.requestId != m_ConnectRequestId) + break; + + m_PendingConnect = false; + + if (!m_Client) + break; + + if (packet->type == TraversalPacketConnectReady) + m_Client->OnConnectReady(MakeENetAddress(&packet->connectReady.address)); + else + m_Client->OnConnectFailed(packet->connectFailed.reason); + break; + } + default: + WARN_LOG(NETPLAY, "Received unknown packet with type %d", packet->type); + break; + } + if (packet->type != TraversalPacketAck) + { + TraversalPacket ack = {}; + ack.type = TraversalPacketAck; + ack.requestId = packet->requestId; + ack.ack.ok = ok; + + ENetBuffer buf; + buf.data = &ack; + buf.dataLength = sizeof(ack); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); + } +} + +void TraversalClient::OnFailure(int reason) +{ + m_State = Failure; + m_FailureReason = reason; + + switch (reason) + { + case TraversalClient::BadHost: + { + auto server = "dolphin-emu.org"; + PanicAlertT("Couldn't look up central server %s", server); + break; + } + case TraversalClient::VersionTooOld: + PanicAlertT("Dolphin too old for traversal server"); + break; + case TraversalClient::ServerForgotAboutUs: + PanicAlertT("Disconnected from traversal server"); + break; + case TraversalClient::SocketSendError: + PanicAlertT("Socket error sending to traversal server"); + break; + case TraversalClient::ResendTimeout: + PanicAlertT("Timeout connecting to traversal server"); + break; + default: + PanicAlertT("Unknown error %x", reason); + break; + } + + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +void TraversalClient::ResendPacket(OutgoingTraversalPacketInfo* info) +{ + info->sendTime = enet_time_get(); + info->tries++; + ENetBuffer buf; + buf.data = &info->packet; + buf.dataLength = sizeof(info->packet); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); +} + +void TraversalClient::HandleResends() +{ + enet_uint32 now = enet_time_get(); + for (auto& tpi : m_OutgoingTraversalPackets) + { + if (now - tpi.sendTime >= (u32) (300 * tpi.tries)) + { + if (tpi.tries >= 5) + { + OnFailure(ResendTimeout); + m_OutgoingTraversalPackets.clear(); + break; + } + else + { + ResendPacket(&tpi); + } + } + } + HandlePing(); +} + +void TraversalClient::HandlePing() +{ + enet_uint32 now = enet_time_get(); + if (m_State == Connected && now - m_PingTime >= 500) + { + TraversalPacket ping = {0}; + ping.type = TraversalPacketPing; + ping.ping.hostId = m_HostId; + SendTraversalPacket(ping); + m_PingTime = now; + } +} + +TraversalRequestId TraversalClient::SendTraversalPacket(const TraversalPacket& packet) +{ + OutgoingTraversalPacketInfo info; + info.packet = packet; + GetRandomishBytes((u8*) &info.packet.requestId, sizeof(info.packet.requestId)); + info.tries = 0; + m_OutgoingTraversalPackets.push_back(info); + ResendPacket(&m_OutgoingTraversalPackets.back()); + return info.packet.requestId; +} + +void TraversalClient::Reset() +{ + m_PendingConnect = false; + m_Client = nullptr; +} + +int ENET_CALLBACK TraversalClient::InterceptCallback(ENetHost* host, ENetEvent* event) +{ + auto traversalClient = g_TraversalClient.get(); + if (traversalClient->TestPacket(host->receivedData, host->receivedDataLength, &host->receivedAddress)) + { + event->type = (ENetEventType)42; + return 1; + } + return 0; +} + +std::unique_ptr g_TraversalClient; +std::unique_ptr g_MainNetHost; + +// The settings at the previous TraversalClient reset - notably, we +// need to know not just what port it's on, but whether it was +// explicitly requested. +static std::string g_OldServer; +static u16 g_OldPort; + +bool EnsureTraversalClient(const std::string& server, u16 port) +{ + if (!g_MainNetHost || !g_TraversalClient || server != g_OldServer || port != g_OldPort) + { + g_OldServer = server; + g_OldPort = port; + + ENetAddress addr = { ENET_HOST_ANY, port }; + ENetHost* host = enet_host_create( + &addr, // address + 50, // peerCount + 1, // channelLimit + 0, // incomingBandwidth + 0); // outgoingBandwidth + if (!host) + { + g_MainNetHost.reset(); + return false; + } + g_MainNetHost.reset(host); + + g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server)); + + } + return true; +} + +void ReleaseTraversalClient() +{ + if (!g_TraversalClient) + return; + + if (g_OldPort != 0) + { + // If we were listening at a specific port, kill the + // TraversalClient to avoid hanging on to the port. + g_TraversalClient.reset(); + g_MainNetHost.reset(); + } + else + { + // Reset any pending connection attempts. + g_TraversalClient->Reset(); + } +} diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h new file mode 100644 index 0000000000..838ea6448a --- /dev/null +++ b/Source/Core/Common/TraversalClient.h @@ -0,0 +1,88 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include +#include +#include +#include "Common/Common.h" +#include "Common/Thread.h" +#include "Common/TraversalProto.h" + +#include "enet/enet.h" + +class TraversalClientClient +{ +public: + virtual ~TraversalClientClient(){}; + virtual void OnTraversalStateChanged()=0; + virtual void OnConnectReady(ENetAddress addr)=0; + virtual void OnConnectFailed(u8 reason)=0; +}; + +class TraversalClient +{ +public: + enum State + { + Connecting, + Connected, + Failure + }; + + enum FailureReason + { + BadHost = 0x300, + VersionTooOld, + ServerForgotAboutUs, + SocketSendError, + ResendTimeout, + ConnectFailedError = 0x400, + }; + + TraversalClient(ENetHost* netHost, const std::string& server); + ~TraversalClient(); + void Reset(); + void ConnectToClient(const std::string& host); + void ReconnectToServer(); + void Update(); + + // called from NetHost + bool TestPacket(u8* data, size_t size, ENetAddress* from); + void HandleResends(); + + ENetHost* m_NetHost; + TraversalClientClient* m_Client; + TraversalHostId m_HostId; + State m_State; + int m_FailureReason; + +private: + struct OutgoingTraversalPacketInfo + { + TraversalPacket packet; + int tries; + enet_uint32 sendTime; + }; + + void HandleServerPacket(TraversalPacket* packet); + void ResendPacket(OutgoingTraversalPacketInfo* info); + TraversalRequestId SendTraversalPacket(const TraversalPacket& packet); + void OnFailure(int reason); + void HandlePing(); + static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event); + + TraversalRequestId m_ConnectRequestId; + bool m_PendingConnect; + std::list m_OutgoingTraversalPackets; + ENetAddress m_ServerAddress; + std::string m_Server; + enet_uint32 m_PingTime; +}; + +extern std::unique_ptr g_TraversalClient; +// the NetHost connected to the TraversalClient. +extern std::unique_ptr g_MainNetHost; + +// Create g_TraversalClient and g_MainNetHost if necessary. +bool EnsureTraversalClient(const std::string& server, u16 port); +void ReleaseTraversalClient(); diff --git a/Source/Core/Common/TraversalProto.h b/Source/Core/Common/TraversalProto.h new file mode 100644 index 0000000000..32891beac6 --- /dev/null +++ b/Source/Core/Common/TraversalProto.h @@ -0,0 +1,96 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include +#include "Common/CommonTypes.h" + + +typedef std::array TraversalHostId; +typedef u64 TraversalRequestId; + +enum TraversalPacketType +{ + // [*->*] + TraversalPacketAck = 0, + // [c->s] + TraversalPacketPing = 1, + // [c->s] + TraversalPacketHelloFromClient = 2, + // [s->c] + TraversalPacketHelloFromServer = 3, + // [c->s] When connecting, first the client asks the central server... + TraversalPacketConnectPlease = 4, + // [s->c] ...who asks the game host to send a UDP packet to the + // client... (an ack implies success) + TraversalPacketPleaseSendPacket = 5, + // [s->c] ...which the central server relays back to the client. + TraversalPacketConnectReady = 6, + // [s->c] Alternately, the server might not have heard of this host. + TraversalPacketConnectFailed = 7 +}; + +enum +{ + TraversalProtoVersion = 0 +}; + +enum TraversalConnectFailedReason +{ + TraversalConnectFailedClientDidntRespond = 0, + TraversalConnectFailedClientFailure, + TraversalConnectFailedNoSuchClient +}; + +#pragma pack(push, 1) +struct TraversalInetAddress +{ + u8 isIPV6; + u32 address[4]; + u16 port; +}; +struct TraversalPacket +{ + u8 type; + TraversalRequestId requestId; + union + { + struct + { + u8 ok; + } ack; + struct + { + TraversalHostId hostId; + } ping; + struct + { + u8 protoVersion; + } helloFromClient; + struct + { + u8 ok; + TraversalHostId yourHostId; + TraversalInetAddress yourAddress; // currently unused + } helloFromServer; + struct + { + TraversalHostId hostId; + } connectPlease; + struct + { + TraversalInetAddress address; + } pleaseSendPacket; + struct + { + TraversalRequestId requestId; + TraversalInetAddress address; + } connectReady; + struct + { + TraversalRequestId requestId; + u8 reason; + } connectFailed; + }; +}; +#pragma pack(pop) + diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 8bb26feeb8..77c9a06e71 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -38,10 +38,25 @@ NetPlayClient::~NetPlayClient() Disconnect(); } + if (g_MainNetHost.get() == m_client) + { + g_MainNetHost.release(); + } + if (m_client) + { + enet_host_destroy(m_client); + m_client = nullptr; + } + + if (m_traversal_client) + { + ReleaseTraversalClient(); + } + } // called from ---GUI--- thread -NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name) +NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal) : m_dialog(dialog) , m_client(nullptr) , m_server(nullptr) @@ -53,6 +68,8 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay , m_is_recording(false) , m_pid(0) , m_connecting(false) + , m_traversal_client(nullptr) + , m_state(Failure) { m_target_buffer_size = 20; ClearBuffers(); @@ -61,35 +78,81 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay m_player_name = name; - //Direct Connection - m_client = enet_host_create(nullptr, 1, 3, 0, 0); - - if (m_client == nullptr) + if (!traversal) { - PanicAlertT("Couldn't Create Client"); - } + //Direct Connection + m_client = enet_host_create(nullptr, 1, 3, 0, 0); - ENetAddress addr; - enet_address_set_host(&addr, address.c_str()); - addr.port = port; + if (m_client == nullptr) + { + PanicAlertT("Couldn't Create Client"); + } - m_server = enet_host_connect(m_client, &addr, 3, 0); + ENetAddress addr; + enet_address_set_host(&addr, address.c_str()); + addr.port = port; - if (m_server == nullptr) - { - PanicAlertT("Couldn't create peer."); - } + m_server = enet_host_connect(m_client, &addr, 3, 0); + + if (m_server == nullptr) + { + PanicAlertT("Couldn't create peer."); + } + + ENetEvent netEvent; + int net = enet_host_service(m_client, &netEvent, 5000); + if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) + { + if (Connect()) + m_thread = std::thread(&NetPlayClient::ThreadFunc, this); + } + else + { + PanicAlertT("Failed to Connect!"); + } - ENetEvent netEvent; - int net = enet_host_service(m_client, &netEvent, 5000); - if (net > 0 && netEvent.type == ENET_EVENT_TYPE_CONNECT) - { - if (Connect()) - m_thread = std::thread(&NetPlayClient::ThreadFunc, this); } else { - PanicAlertT("Failed to Connect!"); + //Traversal Server + if (!EnsureTraversalClient("dolphin-emu.org", 0)) + return; + m_client = g_MainNetHost.get(); + + m_traversal_client = g_TraversalClient.get(); + + // If we were disconnected in the background, reconnect. + if (m_traversal_client->m_State == TraversalClient::Failure) + m_traversal_client->ReconnectToServer(); + m_traversal_client->m_Client = this; + m_host_spec = address; + m_state = WaitingForTraversalClientConnection; + OnTraversalStateChanged(); + m_connecting = true; + + while (m_connecting) + { + ENetEvent netEvent; + if (m_traversal_client) + m_traversal_client->HandleResends(); + + while (enet_host_service(m_client, &netEvent, 4) > 0) + { + sf::Packet rpac; + switch (netEvent.type) + { + case ENET_EVENT_TYPE_CONNECT: + m_server = netEvent.peer; + if (Connect()) + { + m_state = Connected; + m_thread = std::thread(&NetPlayClient::ThreadFunc, this); + } + return; + } + } + } + PanicAlertT("Failed To Connect!"); } } @@ -380,6 +443,7 @@ void NetPlayClient::Send(sf::Packet& packet) void NetPlayClient::Disconnect() { ENetEvent netEvent; + m_state = Failure; enet_peer_disconnect(m_server, 0); while (enet_host_service(m_client, &netEvent, 3000) > 0) { @@ -407,6 +471,8 @@ void NetPlayClient::ThreadFunc() int net; { std::lock_guard lks(m_crit.send); + if (m_traversal_client) + m_traversal_client->HandleResends(); net = enet_host_service(m_client, &netEvent, 4); } if (net > 0) @@ -630,6 +696,55 @@ void NetPlayClient::ClearBuffers() } } +// called from ---NETPLAY--- thread +void NetPlayClient::OnTraversalStateChanged() +{ + if (m_state == WaitingForTraversalClientConnection && + m_traversal_client->m_State == TraversalClient::Connected) + { + m_state = WaitingForTraversalClientConnectReady; + m_traversal_client->ConnectToClient(m_host_spec); + } + else if (m_state != Failure && + m_traversal_client->m_State == TraversalClient::Failure) + { + Disconnect(); + } +} + +// called from ---NETPLAY--- thread +void NetPlayClient::OnConnectReady(ENetAddress addr) +{ + if (m_state == WaitingForTraversalClientConnectReady) + { + m_state = Connecting; + enet_host_connect(m_client, &addr, 0, 0); + } +} + +// called from ---NETPLAY--- thread +void NetPlayClient::OnConnectFailed(u8 reason) +{ + m_connecting = false; + m_state = Failure; + int swtch = TraversalClient::ConnectFailedError + reason; + switch (swtch) + { + case TraversalClient::ConnectFailedError + TraversalConnectFailedClientDidntRespond: + PanicAlertT("Traversal server timed out connecting to the host"); + break; + case TraversalClient::ConnectFailedError + TraversalConnectFailedClientFailure: + PanicAlertT("Server rejected traversal attempt"); + break; + case TraversalClient::ConnectFailedError + TraversalConnectFailedNoSuchClient: + PanicAlertT("Invalid host"); + break; + default: + PanicAlertT("Unknown error %x", swtch); + break; + } +} + // called from ---CPU--- thread bool NetPlayClient::GetNetPads(const u8 pad_nb, GCPadStatus* pad_status) { diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 6d923f0697..fab8e6548f 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -7,12 +7,11 @@ #include #include #include - -#include "enet/enet.h" #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" #include "Common/Thread.h" #include "Common/Timer.h" +#include "Common/TraversalClient.h" #include "Core/NetPlayProto.h" #include "InputCommon/GCPadStatus.h" #include @@ -43,12 +42,12 @@ public: u32 ping; }; -class NetPlayClient +class NetPlayClient : public TraversalClientClient { public: void ThreadFunc(); - NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name); + NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal); ~NetPlayClient(); void GetPlayerList(std::string& list, std::vector& pid_list); @@ -66,11 +65,25 @@ public: bool WiimoteUpdate(int _number, u8* data, const u8 size); bool GetNetPads(const u8 pad_nb, GCPadStatus* pad_status); + void OnTraversalStateChanged() override; + void OnConnectReady(ENetAddress addr) override; + void OnConnectFailed(u8 reason) override; + u8 LocalPadToInGamePad(u8 localPad); u8 InGamePadToLocalPad(u8 localPad); u8 LocalWiimoteToInGameWiimote(u8 local_pad); + enum State + { + WaitingForTraversalClientConnection, + WaitingForTraversalClientConnectReady, + Connecting, + WaitingForHelloResponse, + Connected, + Failure + } m_state; + protected: void ClearBuffers(); @@ -121,6 +134,7 @@ private: std::string m_host_spec; std::string m_player_name; bool m_connecting; + TraversalClient* m_traversal_client; }; void NetPlay_Enable(NetPlayClient* const np); diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 255635810b..b7409fa54b 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -34,32 +34,31 @@ typedef std::vector NetWiimote; static const int NETPLAY_INITIAL_GCTIME = 1272737767; - // messages enum { - NP_MSG_PLAYER_JOIN = 0x10, - NP_MSG_PLAYER_LEAVE = 0x11, + NP_MSG_PLAYER_JOIN = 0x10, + NP_MSG_PLAYER_LEAVE = 0x11, - NP_MSG_CHAT_MESSAGE = 0x30, + NP_MSG_CHAT_MESSAGE = 0x30, - NP_MSG_PAD_DATA = 0x60, - NP_MSG_PAD_MAPPING = 0x61, - NP_MSG_PAD_BUFFER = 0x62, + NP_MSG_PAD_DATA = 0x60, + NP_MSG_PAD_MAPPING = 0x61, + NP_MSG_PAD_BUFFER = 0x62, - NP_MSG_WIIMOTE_DATA = 0x70, - NP_MSG_WIIMOTE_MAPPING = 0x71, + NP_MSG_WIIMOTE_DATA = 0x70, + NP_MSG_WIIMOTE_MAPPING = 0x71, - NP_MSG_START_GAME = 0xA0, - NP_MSG_CHANGE_GAME = 0xA1, - NP_MSG_STOP_GAME = 0xA2, - NP_MSG_DISABLE_GAME = 0xA3, + NP_MSG_START_GAME = 0xA0, + NP_MSG_CHANGE_GAME = 0xA1, + NP_MSG_STOP_GAME = 0xA2, + NP_MSG_DISABLE_GAME = 0xA3, - NP_MSG_READY = 0xD0, - NP_MSG_NOT_READY = 0xD1, + NP_MSG_READY = 0xD0, + NP_MSG_NOT_READY = 0xD1, - NP_MSG_PING = 0xE0, - NP_MSG_PONG = 0xE1, + NP_MSG_PING = 0xE0, + NP_MSG_PONG = 0xE1, NP_MSG_PLAYER_PING_DATA = 0xE2, }; @@ -70,8 +69,8 @@ typedef u32 FrameNum; enum { - CON_ERR_SERVER_FULL = 1, - CON_ERR_GAME_RUNNING = 2, + CON_ERR_SERVER_FULL = 1, + CON_ERR_GAME_RUNNING = 2, CON_ERR_VERSION_MISMATCH = 3 }; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index f8861c32e4..9282d71dbc 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -19,6 +19,17 @@ NetPlayServer::~NetPlayServer() m_do_loop = false; m_thread.join(); enet_host_destroy(m_server); + + if (g_MainNetHost.get() == m_server) + { + g_MainNetHost.release(); + } + + if (m_traversal_client) + { + g_TraversalClient->m_Client = nullptr; + ReleaseTraversalClient(); + } } #ifdef USE_UPNP @@ -30,7 +41,7 @@ NetPlayServer::~NetPlayServer() } // called from ---GUI--- thread -NetPlayServer::NetPlayServer(const u16 port) +NetPlayServer::NetPlayServer(const u16 port, bool traversal) : is_connected(false) , m_is_running(false) , m_do_loop(false) @@ -40,6 +51,8 @@ NetPlayServer::NetPlayServer(const u16 port) , m_target_buffer_size(0) , m_selected_game("") , m_server(nullptr) + , m_traversal_client(nullptr) + , m_dialog(nullptr) { //--use server time if (enet_initialize() != 0) @@ -49,11 +62,26 @@ NetPlayServer::NetPlayServer(const u16 port) memset(m_pad_map, -1, sizeof(m_pad_map)); memset(m_wiimote_map, -1, sizeof(m_wiimote_map)); - - ENetAddress serverAddr; - serverAddr.host = ENET_HOST_ANY; - serverAddr.port = port; - m_server = enet_host_create(&serverAddr, 10, 3, 0, 0); + if (traversal) + { + if (!EnsureTraversalClient("dolphin-emu.org", 0)) + return; + + g_TraversalClient->m_Client = this; + m_traversal_client = g_TraversalClient.get(); + + m_server = g_MainNetHost.get(); + + if (g_TraversalClient->m_State == TraversalClient::Failure) + g_TraversalClient->ReconnectToServer(); + } + else + { + ENetAddress serverAddr; + serverAddr.host = ENET_HOST_ANY; + serverAddr.port = port; + m_server = enet_host_create(&serverAddr, 10, 3, 0, 0); + } if (m_server != nullptr) { @@ -89,6 +117,8 @@ void NetPlayServer::ThreadFunc() int net; { std::lock_guard lks(m_crit.send); + if (m_traversal_client) + m_traversal_client->HandleResends(); net = enet_host_service(m_server, &netEvent, 4); } if (net > 0) @@ -533,6 +563,13 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) return 0; } + +void NetPlayServer::OnTraversalStateChanged() +{ + if (m_dialog) + m_dialog->Update(); +} + // called from ---GUI--- thread / and ---NETPLAY--- thread void NetPlayServer::SendChatMessage(const std::string& msg) { @@ -638,6 +675,96 @@ u16 NetPlayServer::GetPort() return m_server->address.port; } +void NetPlayServer::SetNetPlayUI(NetPlayUI* dialog) +{ + m_dialog = dialog; +} + +// called from ---GUI--- thread +std::unordered_set NetPlayServer::GetInterfaceSet() +{ + std::unordered_set result; + auto lst = GetInterfaceListInternal(); + for (auto list_entry : lst) + result.emplace(list_entry.first); + return result; +} + +// called from ---GUI--- thread +std::string NetPlayServer::GetInterfaceHost(const std::string inter) +{ + char buf[16]; + sprintf(buf, ":%d", GetPort()); + auto lst = GetInterfaceListInternal(); + for (const auto& list_entry : lst) + { + if (list_entry.first == inter) + { + return list_entry.second + buf; + } + } + return "?"; +} + +// called from ---GUI--- thread +std::vector> NetPlayServer::GetInterfaceListInternal() +{ + std::vector> result; +#if defined(_WIN32) + +#elif defined(__APPLE__) + // we do this to get the friendly names rather than the BSD ones. ew. + if (m_dynamic_store && m_prefs) + { + CFArrayRef ary = SCNetworkServiceCopyAll((SCPreferencesRef)m_prefs); + for (CFIndex i = 0; i < CFArrayGetCount(ary); i++) + { + SCNetworkServiceRef ifr = (SCNetworkServiceRef)CFArrayGetValueAtIndex(ary, i); + std::string name = CFStrToStr(SCNetworkServiceGetName(ifr)); + CFStringRef key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, SCNetworkServiceGetServiceID(ifr), kSCEntNetIPv4); + CFDictionaryRef props = (CFDictionaryRef)SCDynamicStoreCopyValue((SCDynamicStoreRef)m_dynamic_store, key); + CFRelease(key); + if (!props) + continue; + CFArrayRef ipary = (CFArrayRef)CFDictionaryGetValue(props, kSCPropNetIPv4Addresses); + if (ipary) + { + for (CFIndex j = 0; j < CFArrayGetCount(ipary); j++) + result.emplace_back(std::make_pair(name, CFStrToStr((CFStringRef)CFArrayGetValueAtIndex(ipary, j)))); + CFRelease(ipary); + } + } + CFRelease(ary); + } +#elif defined(ANDROID) + // Android has no getifaddrs for some stupid reason. If this + // functionality ends up actually being used on Android, fix this. +#else + ifaddrs* ifp; + char buf[512]; + if (getifaddrs(&ifp) != -1) + { + for (struct ifaddrs* curifp = ifp; curifp; curifp = curifp->ifa_next) + { + struct sockaddr* sa = curifp->ifa_addr; + if (sa->sa_family != AF_INET) + continue; + struct sockaddr_in* sai = (struct sockaddr_in*) sa; + if (ntohl(((struct sockaddr_in*) sa)->sin_addr.s_addr) == 0x7f000001) + continue; + const char* ip = inet_ntop(sa->sa_family, &sai->sin_addr, buf, sizeof(buf)); + if (ip == nullptr) + continue; + result.emplace_back(std::make_pair(curifp->ifa_name, ip)); + } + freeifaddrs(ifp); + } +#endif + if (result.empty()) + result.push_back(std::make_pair("!local!", "127.0.0.1")); + return result; +} + #ifdef USE_UPNP #include #include diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index d82195f5f7..6fa7909822 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -9,18 +9,20 @@ #include #include -#include "enet/enet.h" #include "Common/Thread.h" +#include "Common/TraversalClient.h" #include "Common/Timer.h" #include "Core/NetPlayProto.h" #include -class NetPlayServer +class NetPlayUI; + +class NetPlayServer : public TraversalClientClient { public: void ThreadFunc(); - NetPlayServer(const u16 port); + NetPlayServer(const u16 port, bool traversal); ~NetPlayServer(); bool ChangeGame(const std::string& game); @@ -42,6 +44,10 @@ public: u16 GetPort(); + void SetNetPlayUI(NetPlayUI* dialog); + std::unordered_set GetInterfaceSet(); + std::string GetInterfaceHost(const std::string inter); + bool is_connected; #ifdef USE_UPNP @@ -71,8 +77,12 @@ private: unsigned int OnConnect(ENetPeer* socket); unsigned int OnDisconnect(Client& player); unsigned int OnData(sf::Packet& packet, Client& player); + virtual void OnTraversalStateChanged(); + virtual void OnConnectReady(ENetAddress addr) {} + virtual void OnConnectFailed(u8 reason) {} void UpdatePadMapping(); void UpdateWiimoteMapping(); + std::vector> GetInterfaceListInternal(); NetSettings m_settings; @@ -99,6 +109,8 @@ private: std::thread m_thread; ENetHost* m_server; + TraversalClient* m_traversal_client; + NetPlayUI* m_dialog; #ifdef USE_UPNP static void mapPortThread(const u16 port); diff --git a/Source/Core/DolphinWX/NetWindow.cpp b/Source/Core/DolphinWX/NetWindow.cpp index eb15e533a0..8d99a9403b 100644 --- a/Source/Core/DolphinWX/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetWindow.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" @@ -53,6 +54,26 @@ static NetPlayServer* netplay_server = nullptr; static NetPlayClient* netplay_client = nullptr; NetPlayDiag *NetPlayDiag::npd = nullptr; + +static wxString FailureReasonStringForHostLabel(int reason) +{ + switch (reason) + { + case TraversalClient::BadHost: + return _("(Error: Bad host)"); + case TraversalClient::VersionTooOld: + return _("(Error: Dolphin too old)"); + case TraversalClient::ServerForgotAboutUs: + return _("(Error: Disconnected)"); + case TraversalClient::SocketSendError: + return _("(Error: Socket)"); + case TraversalClient::ResendTimeout: + return _("(Error: Timeout)"); + default: + return _("(Error: Unknown)"); + } +} + static std::string BuildGameName(const GameListItem& game) { // Lang needs to be consistent @@ -93,7 +114,6 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* nick_szr->Add(nick_lbl, 0, wxCENTER); nick_szr->Add(m_nickname_text, 0, wxALL, 5); - // tabs wxNotebook* const notebook = new wxNotebook(panel, wxID_ANY); wxPanel* const connect_tab = new wxPanel(notebook, wxID_ANY); @@ -101,85 +121,91 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* wxPanel* const host_tab = new wxPanel(notebook, wxID_ANY); notebook->AddPage(host_tab, _("Host")); + m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(75, -1)); + m_direct_traversal->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlaySetupDiag::OnChoice, this); + m_direct_traversal->Append(_("Traversal")); + m_direct_traversal->Append(_("Direct")); + m_direct_traversal->Select(0); // connect tab { - wxStaticText* const ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Address :")); + m_ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Host Code :")); - std::string address; - netplay_section.Get("Address", &address, "localhost"); - m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); + std::string address; + netplay_section.Get("HostCode", &address, "00000000"); + m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); - wxStaticText* const port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); + m_client_port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); - // string? w/e - std::string port; - netplay_section.Get("ConnectPort", &port, "2626"); - m_connect_port_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(port)); + // string? w/e + std::string port; + netplay_section.Get("ConnectPort", &port, "2626"); + m_connect_port_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(port)); - wxButton* const connect_btn = new wxButton(connect_tab, wxID_ANY, _("Connect")); - connect_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnJoin, this); + wxButton* const connect_btn = new wxButton(connect_tab, wxID_ANY, _("Connect")); + connect_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnJoin, this); - wxStaticText* const alert_lbl = new wxStaticText(connect_tab, wxID_ANY, - _("ALERT:\n\n" - "Netplay will only work with the following settings:\n" - " - DSP Emulator Engine Must be the same on all computers!\n" - " - DSP on Dedicated Thread [OFF]\n" - " - Manually set the extensions for each Wiimote\n" - "\n" - "All players should use the same Dolphin version and settings.\n" - "All memory cards must be identical between players or disabled.\n" - "Wiimote support is probably terrible. Don't use it.\n" - "\n" - "The host must have the chosen TCP port open/forwarded!\n")); + wxStaticText* const alert_lbl = new wxStaticText(connect_tab, wxID_ANY, + _("ALERT:\n\n" + "Netplay will only work with the following settings:\n" + " - DSP Emulator Engine Must be the same on all computers!\n" + " - DSP on Dedicated Thread [OFF]\n" + " - Manually set the extensions for each Wiimote\n" + "\n" + "All players should use the same Dolphin version and settings.\n" + "All memory cards must be identical between players or disabled.\n" + "Wiimote support is probably terrible. Don't use it.\n" + "\n" + "If connecting directly host must have the chosen UDP port open/forwarded!\n")); - wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - top_szr->Add(ip_lbl, 0, wxCENTER | wxRIGHT, 5); - top_szr->Add(m_connect_ip_text, 3); - top_szr->Add(port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); - top_szr->Add(m_connect_port_text, 1); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); - con_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); - con_szr->AddStretchSpacer(1); - con_szr->Add(alert_lbl, 0, wxLEFT | wxRIGHT | wxEXPAND, 5); - con_szr->AddStretchSpacer(1); - con_szr->Add(connect_btn, 0, wxALL | wxALIGN_RIGHT, 5); + top_szr->Add(m_ip_lbl, 0, wxCENTER | wxRIGHT, 5); + top_szr->Add(m_connect_ip_text, 3); + top_szr->Add(m_client_port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); + top_szr->Add(m_connect_port_text, 1); - connect_tab->SetSizerAndFit(con_szr); + wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); + con_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); + con_szr->AddStretchSpacer(1); + con_szr->Add(alert_lbl, 0, wxLEFT | wxRIGHT | wxEXPAND, 5); + con_szr->AddStretchSpacer(1); + con_szr->Add(connect_btn, 0, wxALL | wxALIGN_RIGHT, 5); + + connect_tab->SetSizerAndFit(con_szr); } // host tab { - wxStaticText* const port_lbl = new wxStaticText(host_tab, wxID_ANY, _("Port :")); + m_host_port_lbl = new wxStaticText(host_tab, wxID_ANY, _("Port :")); - // string? w/e - std::string port; - netplay_section.Get("HostPort", &port, "2626"); - m_host_port_text = new wxTextCtrl(host_tab, wxID_ANY, StrToWxStr(port)); + // string? w/e + std::string port; + netplay_section.Get("HostPort", &port, "2626"); + m_host_port_text = new wxTextCtrl(host_tab, wxID_ANY, StrToWxStr(port)); - wxButton* const host_btn = new wxButton(host_tab, wxID_ANY, _("Host")); - host_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnHost, this); + wxButton* const host_btn = new wxButton(host_tab, wxID_ANY, _("Host")); + host_btn->Bind(wxEVT_BUTTON, &NetPlaySetupDiag::OnHost, this); - m_game_lbox = new wxListBox(host_tab, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SORT); - m_game_lbox->Bind(wxEVT_LISTBOX_DCLICK, &NetPlaySetupDiag::OnHost, this); + m_game_lbox = new wxListBox(host_tab, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SORT); + m_game_lbox->Bind(wxEVT_LISTBOX_DCLICK, &NetPlaySetupDiag::OnHost, this); - FillWithGameNames(m_game_lbox, *game_list); + FillWithGameNames(m_game_lbox, *game_list); - wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); - top_szr->Add(port_lbl, 0, wxCENTER | wxRIGHT, 5); - top_szr->Add(m_host_port_text, 0); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); + top_szr->Add(m_host_port_lbl, 0, wxCENTER | wxRIGHT, 5); + top_szr->Add(m_host_port_text, 0); #ifdef USE_UPNP - m_upnp_chk = new wxCheckBox(host_tab, wxID_ANY, _("Forward port (UPnP)")); - top_szr->Add(m_upnp_chk, 0, wxALL | wxALIGN_RIGHT, 5); + m_upnp_chk = new wxCheckBox(host_tab, wxID_ANY, _("Forward port (UPnP)")); + top_szr->Add(m_upnp_chk, 0, wxALL | wxALIGN_RIGHT, 5); #endif - wxBoxSizer* const host_szr = new wxBoxSizer(wxVERTICAL); - host_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); - host_szr->Add(m_game_lbox, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); - host_szr->Add(host_btn, 0, wxALL | wxALIGN_RIGHT, 5); + wxBoxSizer* const host_szr = new wxBoxSizer(wxVERTICAL); + host_szr->Add(top_szr, 0, wxALL | wxEXPAND, 5); + host_szr->Add(m_game_lbox, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); + host_szr->Add(host_btn, 0, wxALL | wxALIGN_RIGHT, 5); - host_tab->SetSizerAndFit(host_szr); + host_tab->SetSizerAndFit(host_szr); } // bottom row @@ -202,6 +228,21 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* Center(); Show(); + + // Needs to be done last or it set up the spacing on the page correctly + //client tab + { + m_client_port_lbl->Show(false); + m_connect_port_text->Show(false); + } + + //server tab + { + m_host_port_lbl->Show(false); + m_host_port_text->Show(false); + m_upnp_chk->Show(false); + } + } NetPlaySetupDiag::~NetPlaySetupDiag() @@ -212,7 +253,14 @@ NetPlaySetupDiag::~NetPlaySetupDiag() IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); netplay_section.Set("Nickname", WxStrToStr(m_nickname_text->GetValue())); - netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); + if (m_direct_traversal->GetCurrentSelection() == 1) + { + netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); + } + else + { + netplay_section.Set("HostCode", WxStrToStr(m_connect_ip_text->GetValue())); + } netplay_section.Set("ConnectPort", WxStrToStr(m_connect_port_text->GetValue())); netplay_section.Set("HostPort", WxStrToStr(m_host_port_text->GetValue())); @@ -230,7 +278,13 @@ void NetPlaySetupDiag::MakeNetPlayDiag(int port, const std::string &game, bool i else ip = WxStrToStr(m_connect_ip_text->GetValue()); - netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue())); + bool trav; + if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 0) + trav = true; + else + trav = false; + + netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue()), trav); if (netplay_client->is_connected) { npd->Show(); @@ -259,9 +313,15 @@ void NetPlaySetupDiag::OnHost(wxCommandEvent&) std::string game(WxStrToStr(m_game_lbox->GetStringSelection())); + bool trav; + if (m_direct_traversal->GetCurrentSelection() == 0) + trav = true; + else + trav = false; + unsigned long port = 0; m_host_port_text->GetValue().ToULong(&port); - netplay_server = new NetPlayServer(u16(port)); + netplay_server = new NetPlayServer(u16(port), trav); netplay_server->ChangeGame(game); netplay_server->AdjustPadBufferSize(INITIAL_PAD_BUFFER_SIZE); if (netplay_server->is_connected) @@ -270,7 +330,8 @@ void NetPlaySetupDiag::OnHost(wxCommandEvent&) if (m_upnp_chk->GetValue()) netplay_server->TryPortmapping(port); #endif - MakeNetPlayDiag(port, game, true); + MakeNetPlayDiag(netplay_server->GetPort(), game, true); + netplay_server->SetNetPlayUI(NetPlayDiag::GetInstance()); } else { @@ -292,17 +353,75 @@ void NetPlaySetupDiag::OnJoin(wxCommandEvent&) MakeNetPlayDiag(port, "", false); } +void NetPlaySetupDiag::OnChoice(wxCommandEvent& event) +{ + int sel = m_direct_traversal->GetSelection(); + IniFile inifile; + inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); + + if (sel == 0) + { + //Traversal + //client tab + { + m_ip_lbl->SetLabelText("Host Code: "); + + std::string address; + netplay_section.Get("HostCode", &address, "00000000"); + m_connect_ip_text->SetLabelText(address); + + m_client_port_lbl->Show(false); + m_connect_port_text->Show(false); + } + + //server tab + { + m_host_port_lbl->Show(false); + m_host_port_text->Show(false); + m_upnp_chk->Show(false); + } + } + else + { + //Direct + //client tab + { + m_ip_lbl->SetLabelText("IP Address :"); + + std::string address; + netplay_section.Get("Address", &address, "127.0.0.1"); + m_connect_ip_text->SetLabelText(address); + + m_client_port_lbl->Show(true); + m_connect_port_text->Show(true); + } + + //server tab + { + m_host_port_lbl->Show(true); + m_host_port_text->Show(true); + m_upnp_chk->Show(true); + } + } +} + void NetPlaySetupDiag::OnQuit(wxCommandEvent&) { Destroy(); } NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game_list, - const std::string& game, const bool is_hosting) + const std::string& game, const bool is_hosting) : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay")) , m_selected_game(game) , m_start_btn(nullptr) , m_game_list(game_list) + , m_host_type_choice(nullptr) + , m_host_label(nullptr) + , m_host_copy_btn(nullptr) + , m_host_copy_btn_is_retry(false) + , m_is_hosting(is_hosting) { Bind(wxEVT_THREAD, &NetPlayDiag::OnThread, this); @@ -310,10 +429,10 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game // top crap m_game_btn = new wxButton(panel, wxID_ANY, - StrToWxStr(m_selected_game).Prepend(_(" Game : ")), - wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + StrToWxStr(m_selected_game).Prepend(_(" Game : ")), + wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - if (is_hosting) + if (m_is_hosting) m_game_btn->Bind(wxEVT_BUTTON, &NetPlayDiag::OnChangeGame, this); else m_game_btn->Disable(); @@ -343,9 +462,34 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game m_player_lbox = new wxListBox(panel, wxID_ANY, wxDefaultPosition, wxSize(256, -1)); wxStaticBoxSizer* const player_szr = new wxStaticBoxSizer(wxVERTICAL, panel, _("Players")); - player_szr->Add(m_player_lbox, 1, wxEXPAND); + // player list - if (is_hosting) + if (m_is_hosting && g_TraversalClient) + { + wxBoxSizer* const host_szr = new wxBoxSizer(wxHORIZONTAL); + m_host_type_choice = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(60, -1)); + m_host_type_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlayDiag::OnChoice, this); + m_host_type_choice->Append(_("ID:")); + host_szr->Add(m_host_type_choice); + + m_host_label = new wxStaticText(panel, wxID_ANY, "555.555.555.555:55555", wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE | wxALIGN_LEFT); + // Update() should fix this immediately. + m_host_label->SetLabel(_("")); + host_szr->Add(m_host_label, 1, wxLEFT | wxCENTER, 5); + + m_host_copy_btn = new wxButton(panel, wxID_ANY, _("Copy")); + m_host_copy_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &NetPlayDiag::OnCopyIP, this); + m_host_copy_btn->Disable(); + host_szr->Add(m_host_copy_btn, 0, wxLEFT | wxCENTER, 5); + player_szr->Add(host_szr, 0, wxEXPAND | wxBOTTOM, 5); + m_host_type_choice->Select(0); + + UpdateHostLabel(); + } + + player_szr->Add(m_player_lbox, 1, wxEXPAND); + + if (m_is_hosting) { m_player_lbox->Bind(wxEVT_LISTBOX, &NetPlayDiag::OnPlayerSelect, this); m_kick_btn = new wxButton(panel, wxID_ANY, _("Kick Player")); @@ -398,7 +542,7 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game panel->SetSizerAndFit(main_szr); main_szr->SetSizeHints(this); - SetSize(512, 512-128); + SetSize(512, 512 - 128); Center(); } @@ -531,6 +675,11 @@ void NetPlayDiag::OnQuit(wxCommandEvent&) // update gui void NetPlayDiag::OnThread(wxThreadEvent& event) { + if (m_is_hosting && m_host_label && g_TraversalClient) + { + UpdateHostLabel(); + } + // player list m_playerids.clear(); std::string tmps; @@ -563,8 +712,8 @@ void NetPlayDiag::OnThread(wxThreadEvent& event) // flash window in taskbar when someone joins if window isn't active static u8 numPlayers = 1; bool focus = (wxWindow::FindFocus() == this || (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() == this) || - (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() != nullptr - && wxWindow::FindFocus()->GetParent()->GetParent() == this)); + (wxWindow::FindFocus() != nullptr && wxWindow::FindFocus()->GetParent() != nullptr + && wxWindow::FindFocus()->GetParent()->GetParent() == this)); if (netplay_server != nullptr && numPlayers < m_playerids.size() && !focus) { RequestUserAttention(); @@ -575,25 +724,25 @@ void NetPlayDiag::OnThread(wxThreadEvent& event) { case NP_GUI_EVT_CHANGE_GAME: // update selected game :/ - { + { m_selected_game.assign(WxStrToStr(event.GetString())); wxString button_label = event.GetString(); m_game_btn->SetLabel(button_label.Prepend(_(" Game : "))); - } - break; - case NP_GUI_EVT_START_GAME : + } + break; + case NP_GUI_EVT_START_GAME: // client start game :/ - { + { netplay_client->StartGame(FindGame()); - } - break; - case NP_GUI_EVT_STOP_GAME : + } + break; + case NP_GUI_EVT_STOP_GAME: // client stop game - { + { netplay_client->StopGame(); - } - break; + } + break; } // chat messages @@ -660,6 +809,98 @@ bool NetPlayDiag::IsRecording() return m_record_chkbox->GetValue(); } + +void NetPlayDiag::OnCopyIP(wxCommandEvent&) +{ + if (m_host_copy_btn_is_retry) + { + g_TraversalClient->ReconnectToServer(); + Update(); + } + else + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(m_host_label->GetLabel())); + wxTheClipboard->Close(); + } + } +} + +void NetPlayDiag::OnChoice(wxCommandEvent& event) +{ + UpdateHostLabel(); +} + + +void NetPlayDiag::UpdateHostLabel() +{ + wxString label = _(" (internal IP)"); + auto DeLabel = [=](wxString str) { + if (str == _("Localhost")) + return std::string("!local!"); + return WxStrToStr(str.Left(str.Len() - label.Len())); + }; + auto EnLabel = [=](std::string str) -> wxString { + if (str == "!local!") + return _("Localhost"); + return StrToWxStr(str) + label; + }; + int sel = m_host_type_choice->GetSelection(); + if (sel == 0) + { + // the traversal ID + switch (g_TraversalClient->m_State) + { + case TraversalClient::Connecting: + m_host_label->SetForegroundColour(*wxLIGHT_GREY); + m_host_label->SetLabel("..."); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Disable(); + break; + case TraversalClient::Connected: + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(wxString(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size())); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = false; + break; + case TraversalClient::Failure: + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(FailureReasonStringForHostLabel(g_TraversalClient->m_FailureReason)); + m_host_copy_btn->SetLabel(_("Retry")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = true; + break; + } + } + else if (sel != wxNOT_FOUND) // wxNOT_FOUND shouldn't generally happen + { + m_host_label->SetForegroundColour(*wxBLACK); + m_host_label->SetLabel(netplay_server->GetInterfaceHost(DeLabel(m_host_type_choice->GetString(sel)))); + m_host_copy_btn->SetLabel(_("Copy")); + m_host_copy_btn->Enable(); + m_host_copy_btn_is_retry = false; + } + + auto set = netplay_server->GetInterfaceSet(); + for (const std::string& iface : set) + { + wxString wxIface = EnLabel(iface); + if (m_host_type_choice->FindString(wxIface) == wxNOT_FOUND) + m_host_type_choice->Append(wxIface); + } + for (unsigned i = 1, count = m_host_type_choice->GetCount(); i != count; i++) + { + if (set.find(DeLabel(m_host_type_choice->GetString(i))) == set.end()) + { + m_host_type_choice->Delete(i); + i--; + count--; + } + } +} + ChangeGameDiag::ChangeGameDiag(wxWindow* const parent, const CGameListCtrl* const game_list, wxString& game_name) : wxDialog(parent, wxID_ANY, _("Change Game")) , m_game_name(game_name) @@ -728,16 +969,16 @@ PadMapDiag::PadMapDiag(wxWindow* const parent, PadMapping map[], PadMapping wiim v_szr->Add(new wxStaticText(this, wxID_ANY, (wxString(_("Wiimote ")) + (wxChar)('0' + i))), 1, wxALIGN_CENTER_HORIZONTAL); - m_map_cbox[i+4] = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, player_names); - m_map_cbox[i+4]->Bind(wxEVT_CHOICE, &PadMapDiag::OnAdjust, this); + m_map_cbox[i + 4] = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, player_names); + m_map_cbox[i + 4]->Bind(wxEVT_CHOICE, &PadMapDiag::OnAdjust, this); if (m_wiimapping[i] == -1) - m_map_cbox[i+4]->Select(0); + m_map_cbox[i + 4]->Select(0); else for (unsigned int j = 0; j < m_player_list.size(); j++) if (m_wiimapping[i] == m_player_list[j]->pid) - m_map_cbox[i+4]->Select(j + 1); + m_map_cbox[i + 4]->Select(j + 1); - v_szr->Add(m_map_cbox[i+4], 1); + v_szr->Add(m_map_cbox[i + 4], 1); h_szr->Add(v_szr, 1, wxTOP | wxEXPAND, 20); h_szr->AddSpacer(10); @@ -763,7 +1004,7 @@ void PadMapDiag::OnAdjust(wxCommandEvent& event) else m_mapping[i] = -1; - player_idx = m_map_cbox[i+4]->GetSelection(); + player_idx = m_map_cbox[i + 4]->GetSelection(); if (player_idx > 0) m_wiimapping[i] = m_player_list[player_idx - 1]->pid; else diff --git a/Source/Core/DolphinWX/NetWindow.h b/Source/Core/DolphinWX/NetWindow.h index 90d0dbd8c4..31cbf17b71 100644 --- a/Source/Core/DolphinWX/NetWindow.h +++ b/Source/Core/DolphinWX/NetWindow.h @@ -42,10 +42,16 @@ private: void MakeNetPlayDiag(int port, const std::string &game, bool is_hosting); - wxTextCtrl* m_nickname_text; - wxTextCtrl* m_host_port_text; - wxTextCtrl* m_connect_port_text; - wxTextCtrl* m_connect_ip_text; + void OnChoice(wxCommandEvent& event); + + wxStaticText* m_ip_lbl; + wxStaticText* m_client_port_lbl; + wxTextCtrl* m_nickname_text; + wxStaticText* m_host_port_lbl; + wxTextCtrl* m_host_port_text; + wxTextCtrl* m_connect_port_text; + wxTextCtrl* m_connect_ip_text; + wxChoice* m_direct_traversal; wxListBox* m_game_lbox; #ifdef USE_UPNP @@ -93,16 +99,25 @@ private: void GetNetSettings(NetSettings &settings); std::string FindGame(); - wxListBox* m_player_lbox; - wxTextCtrl* m_chat_text; - wxTextCtrl* m_chat_msg_text; - wxCheckBox* m_memcard_write; - wxCheckBox* m_record_chkbox; + void OnCopyIP(wxCommandEvent&); + void OnChoice(wxCommandEvent& event); + void UpdateHostLabel(); - std::string m_selected_game; - wxButton* m_game_btn; - wxButton* m_start_btn; - wxButton* m_kick_btn; + wxListBox* m_player_lbox; + wxTextCtrl* m_chat_text; + wxTextCtrl* m_chat_msg_text; + wxCheckBox* m_memcard_write; + wxCheckBox* m_record_chkbox; + + std::string m_selected_game; + wxButton* m_game_btn; + wxButton* m_start_btn; + wxButton* m_kick_btn; + wxStaticText* m_host_label; + wxChoice* m_host_type_choice; + wxButton* m_host_copy_btn; + bool m_host_copy_btn_is_retry; + bool m_is_hosting; std::vector m_playerids; From 074d688884b70eb094661e70894b85470e0f8ff8 Mon Sep 17 00:00:00 2001 From: Ziek Date: Mon, 2 Feb 2015 02:08:58 -0800 Subject: [PATCH 4/8] Change netplay initial gctime to be determined by the hosts initial time --- CMakeLists.txt | 2 + Source/Core/Common/CMakeLists.txt | 3 +- Source/Core/Common/TraversalClient.cpp | 9 ++--- Source/Core/Common/TraversalClient.h | 8 ---- Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/Movie.cpp | 2 +- Source/Core/Core/NetPlayClient.cpp | 18 ++++++--- Source/Core/Core/NetPlayClient.h | 5 ++- Source/Core/Core/NetPlayProto.h | 2 +- Source/Core/Core/NetPlayServer.cpp | 54 +++++++++++--------------- Source/Core/Core/NetPlayServer.h | 11 +++--- Source/Core/DolphinWX/NetWindow.cpp | 7 ++-- Source/Core/DolphinWX/NetWindow.h | 4 +- Source/dolphin-emu.sln | 4 +- 14 files changed, 62 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85a98e4c16..a32aa40fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -534,6 +534,8 @@ include_directories(Source/Core) # add_subdirectory(Externals/Bochs_disasm) include_directories(Externals/Bochs_disasm) +include_directories(Externals) +add_subdirectory(Externals/enet) if(NOT XXHASH_FOUND) message("Using static xxhash from Externals") diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 95ff8b99b3..e4054d286e 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -24,6 +24,7 @@ set(SRCS BreakPoints.cpp SysConf.cpp Thread.cpp Timer.cpp + TraversalClient.cpp Version.cpp x64ABI.cpp x64Analyzer.cpp @@ -33,7 +34,7 @@ set(SRCS BreakPoints.cpp Logging/ConsoleListener.cpp Logging/LogManager.cpp) - +set(LIBS enet) if(_M_ARM) if (_M_ARM_32) #ARMv7 set(SRCS ${SRCS} diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index 9ac5eca238..17e77780c6 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -1,8 +1,7 @@ // This file is public domain, in case it's useful to anyone. -comex -#include "Common/TraversalClient.h" -#include "enet/enet.h" #include "Timer.h" +#include "Common/TraversalClient.h" static void GetRandomishBytes(u8* buf, size_t size) { @@ -15,11 +14,11 @@ static void GetRandomishBytes(u8* buf, size_t size) TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server) : m_NetHost(netHost) - , m_Server(server) , m_Client(nullptr) , m_FailureReason(0) , m_ConnectRequestId(0) , m_PendingConnect(false) + , m_Server(server) , m_PingTime(0) { netHost->intercept = TraversalClient::InterceptCallback; @@ -113,6 +112,8 @@ void TraversalClient::Update() enet_packet_destroy(netEvent.packet); break; + default: + break; } } HandleResends(); @@ -343,9 +344,7 @@ bool EnsureTraversalClient(const std::string& server, u16 port) return false; } g_MainNetHost.reset(host); - g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server)); - } return true; } diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h index 838ea6448a..c248e919e6 100644 --- a/Source/Core/Common/TraversalClient.h +++ b/Source/Core/Common/TraversalClient.h @@ -7,7 +7,6 @@ #include "Common/Common.h" #include "Common/Thread.h" #include "Common/TraversalProto.h" - #include "enet/enet.h" class TraversalClientClient @@ -28,7 +27,6 @@ public: Connected, Failure }; - enum FailureReason { BadHost = 0x300, @@ -38,14 +36,12 @@ public: ResendTimeout, ConnectFailedError = 0x400, }; - TraversalClient(ENetHost* netHost, const std::string& server); ~TraversalClient(); void Reset(); void ConnectToClient(const std::string& host); void ReconnectToServer(); void Update(); - // called from NetHost bool TestPacket(u8* data, size_t size, ENetAddress* from); void HandleResends(); @@ -63,14 +59,12 @@ private: int tries; enet_uint32 sendTime; }; - void HandleServerPacket(TraversalPacket* packet); void ResendPacket(OutgoingTraversalPacketInfo* info); TraversalRequestId SendTraversalPacket(const TraversalPacket& packet); void OnFailure(int reason); void HandlePing(); static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event); - TraversalRequestId m_ConnectRequestId; bool m_PendingConnect; std::list m_OutgoingTraversalPackets; @@ -78,11 +72,9 @@ private: std::string m_Server; enet_uint32 m_PingTime; }; - extern std::unique_ptr g_TraversalClient; // the NetHost connected to the TraversalClient. extern std::unique_ptr g_MainNetHost; - // Create g_TraversalClient and g_MainNetHost if necessary. bool EnsureTraversalClient(const std::string& server, u16 port); void ReleaseTraversalClient(); diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index b3c0ec8fba..e98dbed7b7 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -247,6 +247,7 @@ set(LIBS bdisasm common discio + enet inputcommon ${LZO} sfml-network diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 3f2cfd8c6c..18264cac55 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -452,7 +452,7 @@ bool BeginRecordingInput(int controllers) if (NetPlay::IsNetPlayRunning()) { s_bNetPlay = true; - s_recordingStartTime = NETPLAY_INITIAL_GCTIME; + s_recordingStartTime = g_netplay_initial_gctime; } else { diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 77c9a06e71..55759b6f00 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -4,6 +4,8 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" +#include "Core/Movie.h" +#include "Core/NetPlayClient.h" #include "Core/HW/EXI_DeviceIPL.h" #include "Core/HW/SI.h" #include "Core/HW/SI_DeviceDanceMat.h" @@ -13,8 +15,6 @@ #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IPC_HLE/WII_IPC_HLE_Device_usb.h" #include "Core/IPC_HLE/WII_IPC_HLE_WiiMote.h" -#include "Core/Movie.h" -#include "Core/NetPlayClient.h" static std::mutex crit_netplay_client; static NetPlayClient * netplay_client = nullptr; @@ -57,7 +57,8 @@ NetPlayClient::~NetPlayClient() // called from ---GUI--- thread NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal) - : m_dialog(dialog) + : m_state(Failure) + , m_dialog(dialog) , m_client(nullptr) , m_server(nullptr) , m_is_running(false) @@ -69,7 +70,6 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay , m_pid(0) , m_connecting(false) , m_traversal_client(nullptr) - , m_state(Failure) { m_target_buffer_size = 20; ClearBuffers(); @@ -149,6 +149,8 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay m_thread = std::thread(&NetPlayClient::ThreadFunc, this); } return; + default: + break; } } } @@ -375,6 +377,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) g_NetPlaySettings.m_EXIDevice[0] = (TEXIDevices)tmp; packet >> tmp; g_NetPlaySettings.m_EXIDevice[1] = (TEXIDevices)tmp; + + packet >> g_netplay_initial_gctime; } m_dialog->OnMsgStartGame(); @@ -455,6 +459,8 @@ void NetPlayClient::Disconnect() case ENET_EVENT_TYPE_DISCONNECT: m_server = nullptr; return; + default: + break; } } //didn't disconnect gracefully force disconnect @@ -495,6 +501,8 @@ void NetPlayClient::ThreadFunc() netEvent.peer->data = nullptr; break; + default: + break; } } @@ -1050,7 +1058,7 @@ u32 CEXIIPL::NetPlay_GetGCTime() std::lock_guard lk(crit_netplay_client); if (netplay_client) - return NETPLAY_INITIAL_GCTIME; + return g_netplay_initial_gctime; else return 0; } diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index fab8e6548f..c6e39eb099 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" #include "Common/Thread.h" @@ -14,7 +15,7 @@ #include "Common/TraversalClient.h" #include "Core/NetPlayProto.h" #include "InputCommon/GCPadStatus.h" -#include + class NetPlayUI { @@ -97,7 +98,7 @@ protected: Common::FifoQueue m_pad_buffer[4]; Common::FifoQueue m_wiimote_buffer[4]; - NetPlayUI* m_dialog; + NetPlayUI* m_dialog; ENetHost* m_client; ENetPeer* m_server; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index b7409fa54b..8017933149 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -32,7 +32,7 @@ typedef std::vector NetWiimote; #define NETPLAY_VERSION "Dolphin NetPlay 2014-01-08" -static const int NETPLAY_INITIAL_GCTIME = 1272737767; +extern int g_netplay_initial_gctime; // messages enum diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 9282d71dbc..00b278ff87 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -4,13 +4,22 @@ #include #include - #include "Common/StdMakeUnique.h" #include "Common/StringUtil.h" -#include "Core/HW/EXI_DeviceIPL.h" #include "Core/NetPlayClient.h" //for NetPlayUI #include "Core/NetPlayServer.h" +#include "Core/HW/EXI_DeviceIPL.h" #include "InputCommon/GCPadStatus.h" +#if !defined(_WIN32) +#include +#include +#ifndef ANDROID +#include +#endif +#include +#endif + +int g_netplay_initial_gctime = 1272737767; NetPlayServer::~NetPlayServer() { @@ -175,6 +184,8 @@ void NetPlayServer::ThreadFunc() } } break; + default: + break; } } } @@ -285,7 +296,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket) // add client to the player list { std::lock_guard lkp(m_crit.players); - m_players.emplace(player.socket->connectID, player); + m_players.insert(std::pair(player.socket->connectID, player)); std::lock_guard lks(m_crit.send); UpdatePadMapping(); // sync pad mappings with everyone UpdateWiimoteMapping(); @@ -617,6 +628,8 @@ bool NetPlayServer::StartGame() // no change, just update with clients AdjustPadBufferSize(m_target_buffer_size); + g_netplay_initial_gctime = CEXIIPL::GetGCTime(); + // tell clients to start game sf::Packet spac; spac << (MessageId)NP_MSG_START_GAME; @@ -630,6 +643,7 @@ bool NetPlayServer::StartGame() spac << m_settings.m_OCFactor; spac << m_settings.m_EXIDevice[0]; spac << m_settings.m_EXIDevice[1]; + spac << g_netplay_initial_gctime; std::lock_guard lkp(m_crit.players); std::lock_guard lks(m_crit.send); @@ -712,44 +726,20 @@ std::vector> NetPlayServer::GetInterfaceList std::vector> result; #if defined(_WIN32) -#elif defined(__APPLE__) - // we do this to get the friendly names rather than the BSD ones. ew. - if (m_dynamic_store && m_prefs) - { - CFArrayRef ary = SCNetworkServiceCopyAll((SCPreferencesRef)m_prefs); - for (CFIndex i = 0; i < CFArrayGetCount(ary); i++) - { - SCNetworkServiceRef ifr = (SCNetworkServiceRef)CFArrayGetValueAtIndex(ary, i); - std::string name = CFStrToStr(SCNetworkServiceGetName(ifr)); - CFStringRef key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, SCNetworkServiceGetServiceID(ifr), kSCEntNetIPv4); - CFDictionaryRef props = (CFDictionaryRef)SCDynamicStoreCopyValue((SCDynamicStoreRef)m_dynamic_store, key); - CFRelease(key); - if (!props) - continue; - CFArrayRef ipary = (CFArrayRef)CFDictionaryGetValue(props, kSCPropNetIPv4Addresses); - if (ipary) - { - for (CFIndex j = 0; j < CFArrayGetCount(ipary); j++) - result.emplace_back(std::make_pair(name, CFStrToStr((CFStringRef)CFArrayGetValueAtIndex(ipary, j)))); - CFRelease(ipary); - } - } - CFRelease(ary); - } #elif defined(ANDROID) // Android has no getifaddrs for some stupid reason. If this // functionality ends up actually being used on Android, fix this. #else - ifaddrs* ifp; + ifaddrs* ifp = nullptr; char buf[512]; if (getifaddrs(&ifp) != -1) { - for (struct ifaddrs* curifp = ifp; curifp; curifp = curifp->ifa_next) + for (ifaddrs* curifp = ifp; curifp; curifp = curifp->ifa_next) { - struct sockaddr* sa = curifp->ifa_addr; + sockaddr* sa = curifp->ifa_addr; if (sa->sa_family != AF_INET) continue; - struct sockaddr_in* sai = (struct sockaddr_in*) sa; + sockaddr_in* sai = (struct sockaddr_in*) sa; if (ntohl(((struct sockaddr_in*) sa)->sin_addr.s_addr) == 0x7f000001) continue; const char* ip = inet_ntop(sa->sa_family, &sai->sin_addr, buf, sizeof(buf)); @@ -761,7 +751,7 @@ std::vector> NetPlayServer::GetInterfaceList } #endif if (result.empty()) - result.push_back(std::make_pair("!local!", "127.0.0.1")); + result.emplace_back(std::make_pair("!local!", "127.0.0.1")); return result; } diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 6fa7909822..3a589aa2ca 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -8,12 +8,11 @@ #include #include #include - -#include "Common/Thread.h" -#include "Common/TraversalClient.h" -#include "Common/Timer.h" -#include "Core/NetPlayProto.h" #include +#include "Common/Thread.h" +#include "Common/Timer.h" +#include "Common/TraversalClient.h" +#include "Core/NetPlayProto.h" class NetPlayUI; @@ -127,4 +126,4 @@ private: static bool m_upnp_error; static std::thread m_upnp_thread; #endif -}; +}; \ No newline at end of file diff --git a/Source/Core/DolphinWX/NetWindow.cpp b/Source/Core/DolphinWX/NetWindow.cpp index 8d99a9403b..c06348be5f 100644 --- a/Source/Core/DolphinWX/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetWindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +28,7 @@ #include #include #include -#include + #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" @@ -416,12 +417,12 @@ NetPlayDiag::NetPlayDiag(wxWindow* const parent, const CGameListCtrl* const game : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay")) , m_selected_game(game) , m_start_btn(nullptr) - , m_game_list(game_list) - , m_host_type_choice(nullptr) , m_host_label(nullptr) + , m_host_type_choice(nullptr) , m_host_copy_btn(nullptr) , m_host_copy_btn_is_retry(false) , m_is_hosting(is_hosting) + , m_game_list(game_list) { Bind(wxEVT_THREAD, &NetPlayDiag::OnThread, this); diff --git a/Source/Core/DolphinWX/NetWindow.h b/Source/Core/DolphinWX/NetWindow.h index 31cbf17b71..03dc0b5def 100644 --- a/Source/Core/DolphinWX/NetWindow.h +++ b/Source/Core/DolphinWX/NetWindow.h @@ -105,8 +105,8 @@ private: wxListBox* m_player_lbox; wxTextCtrl* m_chat_text; - wxTextCtrl* m_chat_msg_text; - wxCheckBox* m_memcard_write; + wxTextCtrl* m_chat_msg_text; + wxCheckBox* m_memcard_write; wxCheckBox* m_record_chkbox; std::string m_selected_game; diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln index be749a56ae..1e21befeca 100644 --- a/Source/dolphin-emu.sln +++ b/Source/dolphin-emu.sln @@ -413,8 +413,8 @@ Global {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|Win32.Build.0 = Debug|Win32 {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.ActiveCfg = Debug|x64 {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Debug|x64.Build.0 = Debug|x64 - {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.ActiveCfg = Release|Win32 - {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.Build.0 = Release|Win32 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Mixed Platforms.Build.0 = Release|x64 {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Win32.ActiveCfg = Release|Win32 {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|Win32.Build.0 = Release|Win32 {CBC76802-C128-4B17-BF6C-23B08C313E5E}.Release|x64.ActiveCfg = Release|x64 From 1dea3780a7269cd7ec298578a335b065676b4ac9 Mon Sep 17 00:00:00 2001 From: Ziek Date: Thu, 5 Feb 2015 22:16:34 -0800 Subject: [PATCH 5/8] Fixed issue where players were not disconnecting correctly --- Source/Core/Common/TraversalClient.cpp | 16 +++----------- Source/Core/Core/NetPlayClient.cpp | 1 - Source/Core/Core/NetPlayServer.cpp | 29 +++++++++++++++++++++----- Source/Core/Core/NetPlayServer.h | 4 ++-- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index 17e77780c6..736263090d 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -354,16 +354,6 @@ void ReleaseTraversalClient() if (!g_TraversalClient) return; - if (g_OldPort != 0) - { - // If we were listening at a specific port, kill the - // TraversalClient to avoid hanging on to the port. - g_TraversalClient.reset(); - g_MainNetHost.reset(); - } - else - { - // Reset any pending connection attempts. - g_TraversalClient->Reset(); - } -} + g_TraversalClient.release(); + g_MainNetHost.release(); +} \ No newline at end of file diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 55759b6f00..47ae698b86 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -52,7 +52,6 @@ NetPlayClient::~NetPlayClient() { ReleaseTraversalClient(); } - } // called from ---GUI--- thread diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 00b278ff87..03ef95523c 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -150,7 +150,11 @@ void NetPlayServer::ThreadFunc() // don't need to lock, this client isn't in the client map std::lock_guard lks(m_crit.send); Send(accept_peer, spac); - + if (netEvent.peer->data) + { + delete (PlayerId *) netEvent.peer->data; + netEvent.peer->data = nullptr; + } enet_peer_disconnect(accept_peer, 0); } } @@ -160,13 +164,19 @@ void NetPlayServer::ThreadFunc() sf::Packet rpac; rpac.append(netEvent.packet->data, netEvent.packet->dataLength); - auto it = m_players.find(netEvent.peer->connectID); + auto it = m_players.find(*(PlayerId *)netEvent.peer->data); Client& client = it->second; if (OnData(rpac, client) != 0) { // if a bad packet is received, disconnect the client std::lock_guard lkg(m_crit.game); OnDisconnect(client); + + if (netEvent.peer->data) + { + delete (PlayerId *)netEvent.peer->data; + netEvent.peer->data = nullptr; + } } enet_packet_destroy(netEvent.packet); } @@ -174,13 +184,17 @@ void NetPlayServer::ThreadFunc() case ENET_EVENT_TYPE_DISCONNECT: { std::lock_guard lkg(m_crit.game); - auto it = m_players.find(netEvent.peer->connectID); + auto it = m_players.find(*(PlayerId *)netEvent.peer->data); if (it != m_players.end()) { Client& client = it->second; OnDisconnect(client); - netEvent.peer->data = nullptr; + if (netEvent.peer->data) + { + delete (PlayerId *)netEvent.peer->data; + netEvent.peer->data = nullptr; + } } } break; @@ -192,7 +206,11 @@ void NetPlayServer::ThreadFunc() // close listening socket and client sockets for (auto& player_entry : m_players) + { + delete (PlayerId *)player_entry.second.socket->data; + player_entry.second.socket->data = nullptr; enet_peer_disconnect(player_entry.second.socket, 0); + } } // called from ---NETPLAY--- thread @@ -241,6 +259,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket) } } player.pid = pid; + socket->data = new PlayerId(pid); // try to automatically assign new user a pad for (PadMapping& mapping : m_pad_map) @@ -296,7 +315,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket) // add client to the player list { std::lock_guard lkp(m_crit.players); - m_players.insert(std::pair(player.socket->connectID, player)); + m_players.insert(std::pair(*(PlayerId *)player.socket->data, player)); std::lock_guard lks(m_crit.send); UpdatePadMapping(); // sync pad mappings with everyone UpdateWiimoteMapping(); diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 3a589aa2ca..93e28afb57 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -95,7 +95,7 @@ private: PadMapping m_pad_map[4]; PadMapping m_wiimote_map[4]; - std::map m_players; + std::map m_players; struct { @@ -126,4 +126,4 @@ private: static bool m_upnp_error; static std::thread m_upnp_thread; #endif -}; \ No newline at end of file +}; From a629555e6bb227c2616fdc3312ffbcb80715af99 Mon Sep 17 00:00:00 2001 From: Ziek Date: Sat, 14 Feb 2015 19:51:08 -0800 Subject: [PATCH 6/8] Added Gui elements in Netplay Setup Diag so a person can choose a traversal server Fixed bug with UPnP so that it will grab the proper address and protocal Fixed bug that caused dolphin to freeze when host codes were to large --- Source/Core/Common/TraversalClient.cpp | 13 ++-- Source/Core/Common/TraversalClient.h | 3 +- Source/Core/Common/TraversalProto.h | 4 +- Source/Core/Core/NetPlayClient.cpp | 20 +++-- Source/Core/Core/NetPlayClient.h | 2 +- Source/Core/Core/NetPlayServer.cpp | 15 ++-- Source/Core/Core/NetPlayServer.h | 2 +- Source/Core/DolphinWX/NetWindow.cpp | 104 ++++++++++++++++++------- Source/Core/DolphinWX/NetWindow.h | 4 + 9 files changed, 116 insertions(+), 51 deletions(-) diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index 736263090d..e9e6c5939b 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -12,13 +12,14 @@ static void GetRandomishBytes(u8* buf, size_t size) buf[i] = rand() & 0xff; } -TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server) +TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server, const u16 port) : m_NetHost(netHost) , m_Client(nullptr) , m_FailureReason(0) , m_ConnectRequestId(0) , m_PendingConnect(false) , m_Server(server) + , m_port(port) , m_PingTime(0) { netHost->intercept = TraversalClient::InterceptCallback; @@ -34,13 +35,12 @@ TraversalClient::~TraversalClient() void TraversalClient::ReconnectToServer() { - m_Server = "vps.qoid.us"; // XXX if (enet_address_set_host(&m_ServerAddress, m_Server.c_str())) { OnFailure(BadHost); return; } - m_ServerAddress.port = 6262; + m_ServerAddress.port = m_port; m_State = Connecting; @@ -326,12 +326,13 @@ static u16 g_OldPort; bool EnsureTraversalClient(const std::string& server, u16 port) { + if (!g_MainNetHost || !g_TraversalClient || server != g_OldServer || port != g_OldPort) { g_OldServer = server; - g_OldPort = port; + g_OldPort = port ; - ENetAddress addr = { ENET_HOST_ANY, port }; + ENetAddress addr = { ENET_HOST_ANY, 0 }; ENetHost* host = enet_host_create( &addr, // address 50, // peerCount @@ -344,7 +345,7 @@ bool EnsureTraversalClient(const std::string& server, u16 port) return false; } g_MainNetHost.reset(host); - g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server)); + g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server, port)); } return true; } diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h index c248e919e6..ff76d7fc71 100644 --- a/Source/Core/Common/TraversalClient.h +++ b/Source/Core/Common/TraversalClient.h @@ -36,7 +36,7 @@ public: ResendTimeout, ConnectFailedError = 0x400, }; - TraversalClient(ENetHost* netHost, const std::string& server); + TraversalClient(ENetHost* netHost, const std::string& server, const u16 port); ~TraversalClient(); void Reset(); void ConnectToClient(const std::string& host); @@ -70,6 +70,7 @@ private: std::list m_OutgoingTraversalPackets; ENetAddress m_ServerAddress; std::string m_Server; + u16 m_port; enet_uint32 m_PingTime; }; extern std::unique_ptr g_TraversalClient; diff --git a/Source/Core/Common/TraversalProto.h b/Source/Core/Common/TraversalProto.h index 32891beac6..5036c3dbcc 100644 --- a/Source/Core/Common/TraversalProto.h +++ b/Source/Core/Common/TraversalProto.h @@ -4,8 +4,8 @@ #include #include "Common/CommonTypes.h" - -typedef std::array TraversalHostId; +#define NETPLAY_CODE_SIZE 8 +typedef std::array TraversalHostId; typedef u64 TraversalRequestId; enum TraversalPacketType diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 47ae698b86..3c38fac05c 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -55,7 +55,7 @@ NetPlayClient::~NetPlayClient() } // called from ---GUI--- thread -NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal) +NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal, std::string centralServer, u16 centralPort) : m_state(Failure) , m_dialog(dialog) , m_client(nullptr) @@ -113,8 +113,13 @@ NetPlayClient::NetPlayClient(const std::string& address, const u16 port, NetPlay } else { - //Traversal Server - if (!EnsureTraversalClient("dolphin-emu.org", 0)) + if (address.size() > NETPLAY_CODE_SIZE) + { + PanicAlertT("Host code size is to large.\nPlease recheck that you have the correct code"); + return; + } + + if (!EnsureTraversalClient(centralServer, centralPort)) return; m_client = g_MainNetHost.get(); @@ -446,8 +451,13 @@ void NetPlayClient::Send(sf::Packet& packet) void NetPlayClient::Disconnect() { ENetEvent netEvent; + m_connecting = false; m_state = Failure; - enet_peer_disconnect(m_server, 0); + if (m_server) + enet_peer_disconnect(m_server, 0); + else + return; + while (enet_host_service(m_client, &netEvent, 3000) > 0) { switch (netEvent.type) @@ -539,7 +549,7 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector& pid_list) else ss << '-'; } - ss << " | " << player->ping << "ms\n"; + ss << " |\nPing: " << player->ping << "ms\n\n"; pid_list.push_back(player->pid); } diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index c6e39eb099..ab5763d313 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -48,7 +48,7 @@ class NetPlayClient : public TraversalClientClient public: void ThreadFunc(); - NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal); + NetPlayClient(const std::string& address, const u16 port, NetPlayUI* dialog, const std::string& name, bool traversal, std::string centralServer, u16 centralPort); ~NetPlayClient(); void GetPlayerList(std::string& list, std::vector& pid_list); diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 03ef95523c..79acbb6918 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -4,6 +4,7 @@ #include #include +#include "Common/IniFile.h" #include "Common/StdMakeUnique.h" #include "Common/StringUtil.h" #include "Core/NetPlayClient.h" //for NetPlayUI @@ -50,7 +51,7 @@ NetPlayServer::~NetPlayServer() } // called from ---GUI--- thread -NetPlayServer::NetPlayServer(const u16 port, bool traversal) +NetPlayServer::NetPlayServer(const u16 port, bool traversal, std::string centralServer, u16 centralPort) : is_connected(false) , m_is_running(false) , m_do_loop(false) @@ -73,7 +74,7 @@ NetPlayServer::NetPlayServer(const u16 port, bool traversal) memset(m_wiimote_map, -1, sizeof(m_wiimote_map)); if (traversal) { - if (!EnsureTraversalClient("dolphin-emu.org", 0)) + if (!EnsureTraversalClient(centralServer, centralPort)) return; g_TraversalClient->m_Client = this; @@ -797,10 +798,10 @@ void NetPlayServer::TryPortmapping(u16 port) // UPnP thread: try to map a port void NetPlayServer::mapPortThread(const u16 port) { - ENetAddress adr; + ENetAddress adr = { ENET_HOST_ANY, port }; char cIP[20]; - enet_address_get_host_ip(&adr, cIP, 20); + enet_address_get_host(&adr, cIP, 20); std::string ourIP(cIP); if (!m_upnp_inited) @@ -895,8 +896,8 @@ bool NetPlayServer::UPnPMapPort(const std::string& addr, const u16 port) std::string port_str = StringFromFormat("%d", port); int result = UPNP_AddPortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, port_str.c_str(), port_str.c_str(), addr.c_str(), - (std::string("dolphin-emu TCP on ") + addr).c_str(), - "TCP", nullptr, nullptr); + (std::string("dolphin-emu UDP on ") + addr).c_str(), + "UDP", nullptr, nullptr); if (result != 0) return false; @@ -918,7 +919,7 @@ bool NetPlayServer::UPnPUnmapPort(const u16 port) { std::string port_str = StringFromFormat("%d", port); UPNP_DeletePortMapping(m_upnp_urls.controlURL, m_upnp_data.first.servicetype, - port_str.c_str(), "TCP", nullptr); + port_str.c_str(), "UDP", nullptr); return true; } diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 93e28afb57..5e8ac76411 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -21,7 +21,7 @@ class NetPlayServer : public TraversalClientClient public: void ThreadFunc(); - NetPlayServer(const u16 port, bool traversal); + NetPlayServer(const u16 port, bool traversal, std::string centralServer, u16 centralPort); ~NetPlayServer(); bool ChangeGame(const std::string& game); diff --git a/Source/Core/DolphinWX/NetWindow.cpp b/Source/Core/DolphinWX/NetWindow.cpp index c06348be5f..0768b615ed 100644 --- a/Source/Core/DolphinWX/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetWindow.cpp @@ -105,16 +105,49 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* wxPanel* const panel = new wxPanel(this); // top row - wxStaticText* const nick_lbl = new wxStaticText(panel, wxID_ANY, _("Nickname :")); + wxBoxSizer* const trav_szr = new wxBoxSizer(wxHORIZONTAL); + m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(75, -1)); + m_direct_traversal->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlaySetupDiag::OnChoice, this); + m_direct_traversal->Append(_("Direct")); + m_direct_traversal->Append(_("Traversal")); + + std::string travChoice; + netplay_section.Get("TraversalChoice", &travChoice, "direct"); + + if (travChoice == "traversal") + { + m_direct_traversal->Select(1); + } + else + { + m_direct_traversal->Select(0); + } + + trav_szr->Add(m_direct_traversal, 0, wxRIGHT); + + wxBoxSizer* const nick_szr = new wxBoxSizer(wxHORIZONTAL); + wxStaticText* const nick_lbl = new wxStaticText(panel, wxID_ANY, _("Nickname :")); std::string nickname; netplay_section.Get("Nickname", &nickname, "Player"); m_nickname_text = new wxTextCtrl(panel, wxID_ANY, StrToWxStr(nickname)); - - wxBoxSizer* const nick_szr = new wxBoxSizer(wxHORIZONTAL); nick_szr->Add(nick_lbl, 0, wxCENTER); nick_szr->Add(m_nickname_text, 0, wxALL, 5); + std::string centralServer; + netplay_section.Get("TraversalServer", ¢ralServer, "vps.qoid.us"); + m_traversal_server_lbl = new wxStaticText(panel, wxID_ANY, _("Traversal:")); + m_traversal_server = new wxTextCtrl(panel, wxID_ANY, StrToWxStr(centralServer)); + nick_szr->Add(m_traversal_server_lbl, 0, wxCENTER); + nick_szr->Add(m_traversal_server, 0, wxALL, 5); + + std::string centralPort; + netplay_section.Get("TraversalPort", ¢ralPort, "6262"); + m_traversal_port_lbl = new wxStaticText(panel, wxID_ANY, _("Port:")); + m_traversal_port = new wxTextCtrl(panel, wxID_ANY, StrToWxStr(centralPort)); + nick_szr->Add(m_traversal_port_lbl, 0, wxCENTER); + nick_szr->Add(m_traversal_port, 0, wxALL, 5); + // tabs wxNotebook* const notebook = new wxNotebook(panel, wxID_ANY); wxPanel* const connect_tab = new wxPanel(notebook, wxID_ANY); @@ -122,12 +155,6 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* wxPanel* const host_tab = new wxPanel(notebook, wxID_ANY); notebook->AddPage(host_tab, _("Host")); - m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(75, -1)); - m_direct_traversal->Bind(wxEVT_COMMAND_CHOICE_SELECTED, &NetPlaySetupDiag::OnChoice, this); - m_direct_traversal->Append(_("Traversal")); - m_direct_traversal->Append(_("Direct")); - m_direct_traversal->Select(0); - // connect tab { m_ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Host Code :")); @@ -215,7 +242,8 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* // main sizer wxBoxSizer* const main_szr = new wxBoxSizer(wxVERTICAL); - main_szr->Add(nick_szr, 0, wxALL | wxALIGN_RIGHT, 5); + main_szr->Add(trav_szr, 0, wxALL | wxALIGN_LEFT); + main_szr->Add(nick_szr, 0, wxALL | wxALIGN_LEFT, 5); main_szr->Add(notebook, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); main_szr->Add(quit_btn, 0, wxALL | wxALIGN_RIGHT, 5); @@ -231,18 +259,8 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* Show(); // Needs to be done last or it set up the spacing on the page correctly - //client tab - { - m_client_port_lbl->Show(false); - m_connect_port_text->Show(false); - } - - //server tab - { - m_host_port_lbl->Show(false); - m_host_port_text->Show(false); - m_upnp_chk->Show(false); - } + wxCommandEvent ev; + OnChoice(ev); } @@ -253,8 +271,22 @@ NetPlaySetupDiag::~NetPlaySetupDiag() inifile.Load(dolphin_ini); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); + std::string travChoice = "traversal"; + if (m_direct_traversal->GetSelection() == 1) + { + netplay_section.Set("TraversalChoice", travChoice); + } + else + { + travChoice = "direct"; + netplay_section.Set("TraversalChoice", travChoice); + } + netplay_section.Set("Nickname", WxStrToStr(m_nickname_text->GetValue())); - if (m_direct_traversal->GetCurrentSelection() == 1) + netplay_section.Set("TraversalServer", WxStrToStr(m_traversal_server->GetValue())); + netplay_section.Set("TraversalPort", WxStrToStr(m_traversal_port->GetValue())); + + if (m_direct_traversal->GetCurrentSelection() == 0) { netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); } @@ -280,12 +312,14 @@ void NetPlaySetupDiag::MakeNetPlayDiag(int port, const std::string &game, bool i ip = WxStrToStr(m_connect_ip_text->GetValue()); bool trav; - if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 0) + if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 1) trav = true; else trav = false; - netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue()), trav); + unsigned long centralPort = 0; + m_traversal_port->GetValue().ToULong(¢ralPort); + netplay_client = new NetPlayClient(ip, (u16)port, npd, WxStrToStr(m_nickname_text->GetValue()), trav, WxStrToStr(m_traversal_server->GetValue()), (u16)centralPort); if (netplay_client->is_connected) { npd->Show(); @@ -315,14 +349,17 @@ void NetPlaySetupDiag::OnHost(wxCommandEvent&) std::string game(WxStrToStr(m_game_lbox->GetStringSelection())); bool trav; - if (m_direct_traversal->GetCurrentSelection() == 0) + if (m_direct_traversal->GetCurrentSelection() == 1) trav = true; else trav = false; unsigned long port = 0; m_host_port_text->GetValue().ToULong(&port); - netplay_server = new NetPlayServer(u16(port), trav); + + unsigned long centralPort = 0; + m_traversal_port->GetValue().ToULong(¢ralPort); + netplay_server = new NetPlayServer(u16(port), trav, WxStrToStr(m_traversal_server->GetValue()), u16(centralPort)); netplay_server->ChangeGame(game); netplay_server->AdjustPadBufferSize(INITIAL_PAD_BUFFER_SIZE); if (netplay_server->is_connected) @@ -361,8 +398,14 @@ void NetPlaySetupDiag::OnChoice(wxCommandEvent& event) inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - if (sel == 0) + if (sel == 1) { + m_traversal_server_lbl->Show(true); + m_traversal_server->Show(true); + + m_traversal_port_lbl->Show(true); + m_traversal_port->Show(true); + //Traversal //client tab { @@ -385,6 +428,11 @@ void NetPlaySetupDiag::OnChoice(wxCommandEvent& event) } else { + m_traversal_server_lbl->Show(false); + m_traversal_server->Show(false); + + m_traversal_port_lbl->Show(false); + m_traversal_port->Show(false); //Direct //client tab { diff --git a/Source/Core/DolphinWX/NetWindow.h b/Source/Core/DolphinWX/NetWindow.h index 03dc0b5def..d5b2ef12b6 100644 --- a/Source/Core/DolphinWX/NetWindow.h +++ b/Source/Core/DolphinWX/NetWindow.h @@ -52,6 +52,10 @@ private: wxTextCtrl* m_connect_port_text; wxTextCtrl* m_connect_ip_text; wxChoice* m_direct_traversal; + wxStaticText* m_traversal_server_lbl; + wxTextCtrl* m_traversal_server; + wxStaticText* m_traversal_port_lbl; + wxTextCtrl* m_traversal_port; wxListBox* m_game_lbox; #ifdef USE_UPNP From 619a3a5171f24d999e5b41ae54746d0b31babcf3 Mon Sep 17 00:00:00 2001 From: Ziek Date: Sun, 15 Feb 2015 04:34:44 -0800 Subject: [PATCH 7/8] Added all of enet lib, changed Cmake files accordingly --- CMakeLists.txt | 9 +- Externals/enet/CMakeLists.txt | 70 +- Externals/enet/ChangeLog | 171 ++ Externals/enet/Doxyfile | 2303 +++++++++++++++++ Externals/enet/DoxygenLayout.xml | 191 ++ Externals/enet/LICENSE | 2 +- Externals/enet/Makefile.am | 22 + Externals/enet/configure.ac | 26 + Externals/enet/docs/FAQ.dox | 24 + Externals/enet/docs/design.dox | 126 + Externals/enet/docs/install.dox | 63 + Externals/enet/docs/license.dox | 26 + Externals/enet/docs/mainpage.dox | 59 + Externals/enet/docs/tutorial.dox | 366 +++ Externals/enet/enet.dsp | 168 ++ Externals/enet/enet.vcxproj | 62 +- Externals/enet/enet.vcxproj.filters | 55 + Externals/enet/enet_dll.cbp | 86 + Externals/enet/git-revision | 1 - Externals/enet/{ => include/enet}/callbacks.h | 0 Externals/enet/{ => include/enet}/enet.h | 12 +- Externals/enet/{ => include/enet}/list.h | 0 Externals/enet/{ => include/enet}/protocol.h | 2 +- Externals/enet/{ => include/enet}/time.h | 0 Externals/enet/{ => include/enet}/types.h | 0 Externals/enet/{ => include/enet}/unix.h | 0 Externals/enet/{ => include/enet}/utility.h | 0 Externals/enet/{ => include/enet}/win32.h | 0 Externals/enet/libenet.pc.in | 10 + Externals/enet/m4/.keep | 0 Externals/enet/premake4.lua | 59 + Externals/enet/protocol.c | 4 +- Externals/enet/unix.c | 6 +- Externals/enet/update-enet.sh | 20 - Externals/enet/win32.c | 3 +- Source/Core/Common/TraversalClient.h | 2 +- 36 files changed, 3868 insertions(+), 80 deletions(-) create mode 100644 Externals/enet/ChangeLog create mode 100644 Externals/enet/Doxyfile create mode 100644 Externals/enet/DoxygenLayout.xml create mode 100644 Externals/enet/Makefile.am create mode 100644 Externals/enet/configure.ac create mode 100644 Externals/enet/docs/FAQ.dox create mode 100644 Externals/enet/docs/design.dox create mode 100644 Externals/enet/docs/install.dox create mode 100644 Externals/enet/docs/license.dox create mode 100644 Externals/enet/docs/mainpage.dox create mode 100644 Externals/enet/docs/tutorial.dox create mode 100644 Externals/enet/enet.dsp create mode 100644 Externals/enet/enet.vcxproj.filters create mode 100644 Externals/enet/enet_dll.cbp delete mode 100644 Externals/enet/git-revision rename Externals/enet/{ => include/enet}/callbacks.h (100%) rename Externals/enet/{ => include/enet}/enet.h (99%) rename Externals/enet/{ => include/enet}/list.h (100%) rename Externals/enet/{ => include/enet}/protocol.h (99%) rename Externals/enet/{ => include/enet}/time.h (100%) rename Externals/enet/{ => include/enet}/types.h (100%) rename Externals/enet/{ => include/enet}/unix.h (100%) rename Externals/enet/{ => include/enet}/utility.h (100%) rename Externals/enet/{ => include/enet}/win32.h (100%) create mode 100644 Externals/enet/libenet.pc.in create mode 100644 Externals/enet/m4/.keep create mode 100644 Externals/enet/premake4.lua delete mode 100644 Externals/enet/update-enet.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index a32aa40fa3..6f1d81947e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -534,8 +534,13 @@ include_directories(Source/Core) # add_subdirectory(Externals/Bochs_disasm) include_directories(Externals/Bochs_disasm) -include_directories(Externals) -add_subdirectory(Externals/enet) + +if(NOT ENET_FOUND) + message("Using static enet from Externals") + include_directories(Externals) + add_subdirectory(Externals/enet) +endif() +LIST(APPEND LIBS enet) if(NOT XXHASH_FOUND) message("Using static xxhash from Externals") diff --git a/Externals/enet/CMakeLists.txt b/Externals/enet/CMakeLists.txt index 822060ef43..ce6dc8ff42 100644 --- a/Externals/enet/CMakeLists.txt +++ b/Externals/enet/CMakeLists.txt @@ -1,15 +1,57 @@ -set(SRCS - callbacks.c - compress.c - host.c - list.c - packet.c - peer.c - protocol.c) -if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - set(SRCS ${SRCS} win32.c) -else() - set(SRCS ${SRCS} unix.c) +cmake_minimum_required(VERSION 2.6) + +project(enet) + +# The "configure" step. +include(CheckFunctionExists) +include(CheckStructHasMember) +include(CheckTypeSize) +check_function_exists("fcntl" HAS_FCNTL) +check_function_exists("poll" HAS_POLL) +check_function_exists("gethostbyname_r" HAS_GETHOSTBYNAME_R) +check_function_exists("gethostbyaddr_r" HAS_GETHOSTBYADDR_R) +check_function_exists("inet_pton" HAS_INET_PTON) +check_function_exists("inet_ntop" HAS_INET_NTOP) +check_struct_has_member("struct msghdr" "msg_flags" "sys/types.h;sys/socket.h" HAS_MSGHDR_FLAGS) +set(CMAKE_EXTRA_INCLUDE_FILES "sys/types.h" "sys/socket.h") +check_type_size("socklen_t" HAS_SOCKLEN_T BUILTIN_TYPES_ONLY) +unset(CMAKE_EXTRA_INCLUDE_FILES) + +if(HAS_FCNTL) + add_definitions(-DHAS_FCNTL=1) endif() -add_definitions(-Wno-parentheses-equality -DHAS_SOCKLEN_T) -add_library(enet STATIC ${SRCS}) +if(HAS_POLL) + add_definitions(-DHAS_POLL=1) +endif() +if(HAS_GETHOSTBYNAME_R) + add_definitions(-DHAS_GETHOSTBYNAME_R=1) +endif() +if(HAS_GETHOSTBYADDR_R) + add_definitions(-DHAS_GETHOSTBYADDR_R=1) +endif() +if(HAS_INET_PTON) + add_definitions(-DHAS_INET_PTON=1) +endif() +if(HAS_INET_NTOP) + add_definitions(-DHAS_INET_NTOP=1) +endif() +if(HAS_MSGHDR_FLAGS) + add_definitions(-DHAS_MSGHDR_FLAGS=1) +endif() +if(HAS_SOCKLEN_T) + add_definitions(-DHAS_SOCKLEN_T=1) +endif() + +include_directories(${PROJECT_SOURCE_DIR}/include) + +add_library(enet STATIC + callbacks.c + compress.c + host.c + list.c + packet.c + peer.c + protocol.c + unix.c + win32.c + ) diff --git a/Externals/enet/ChangeLog b/Externals/enet/ChangeLog new file mode 100644 index 0000000000..727f853cb5 --- /dev/null +++ b/Externals/enet/ChangeLog @@ -0,0 +1,171 @@ +ENet 1.3.12 (April 24, 2014): + +* added maximumPacketSize and maximumWaitingData fields to ENetHost to limit the amount of +data waiting to be delivered on a peer (beware that the default maximumPacketSize is +32MB and should be set higher if desired as should maximumWaitingData) + +ENet 1.3.11 (December 26, 2013): + +* allow an ENetHost to connect to itself +* fixed possible bug with disconnect notifications during connect attempts +* fixed some preprocessor definition bugs + +ENet 1.3.10 (October 23, 2013); + +* doubled maximum reliable window size +* fixed RCVTIMEO/SNDTIMEO socket options and also added NODELAY + +ENet 1.3.9 (August 19, 2013): + +* added duplicatePeers option to ENetHost which can limit the number of peers from duplicate IPs +* added enet_socket_get_option() and ENET_SOCKOPT_ERROR +* added enet_host_random_seed() platform stub + +ENet 1.3.8 (June 2, 2013): + +* added enet_linked_version() for checking the linked version +* added enet_socket_get_address() for querying the local address of a socket +* silenced some debugging prints unless ENET_DEBUG is defined during compilation +* handle EINTR in enet_socket_wait() so that enet_host_service() doesn't propagate errors from signals +* optimized enet_host_bandwidth_throttle() to be less expensive for large numbers of peers + +ENet 1.3.7 (March 6, 2013): + +* added ENET_PACKET_FLAG_SENT to indicate that a packet is being freed because it has been sent +* added userData field to ENetPacket +* changed how random seed is generated on Windows to avoid import warnings +* fixed case where disconnects could be generated with no preceding connect event + +ENet 1.3.6 (December 11, 2012): + +* added support for intercept callback in ENetHost that can be used to process raw packets before ENet +* added enet_socket_shutdown() for issuing shutdown on a socket +* fixed enet_socket_connect() to not error on non-blocking connects +* fixed bug in MTU negotiation during connections + +ENet 1.3.5 (July 31, 2012): + +* fixed bug in unreliable packet fragment queuing + +ENet 1.3.4 (May 29, 2012): + +* added enet_peer_ping_interval() for configuring per-peer ping intervals +* added enet_peer_timeout() for configuring per-peer timeouts +* added protocol packet size limits + +ENet 1.3.3 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.3.2 (May 31, 2011): + +* added support for unreliable packet fragmenting via the packet flag +ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.3.1 (February 10, 2011): + +* fixed bug in tracking of reliable data in transit +* reliable data window size now scales with the throttle +* fixed bug in fragment length calculation when checksums are used + +ENet 1.3.0 (June 5, 2010): + +* enet_host_create() now requires the channel limit to be specified as +a parameter +* enet_host_connect() now accepts a data parameter which is supplied +to the receiving receiving host in the event data field for a connect event +* added an adaptive order-2 PPM range coder as a built-in compressor option +which can be set with enet_host_compress_with_range_coder() +* added support for packet compression configurable with a callback +* improved session number handling to not rely on the packet checksum +field, saving 4 bytes per packet unless the checksum option is used +* removed the dependence on the rand callback for session number handling + +Caveats: This version is not protocol compatible with the 1.2 series or +earlier. The enet_host_connect and enet_host_create API functions require +supplying additional parameters. + +ENet 1.2.5 (June 28, 2011): + +* fixed bug with simultaneous disconnects not dispatching events + +ENet 1.2.4 (May 31, 2011): + +* fixed regression in unreliable packet queuing +* added check against received port to limit some forms of IP-spoofing + +ENet 1.2.3 (February 10, 2011): + +* fixed bug in tracking reliable data in transit + +ENet 1.2.2 (June 5, 2010): + +* checksum functionality is now enabled by setting a checksum callback +inside ENetHost instead of being a configure script option +* added totalSentData, totalSentPackets, totalReceivedData, and +totalReceivedPackets counters inside ENetHost for getting usage +statistics +* added enet_host_channel_limit() for limiting the maximum number of +channels allowed by connected peers +* now uses dispatch queues for event dispatch rather than potentially +unscalable array walking +* added no_memory callback that is called when a malloc attempt fails, +such that if no_memory returns rather than aborts (the default behavior), +then the error is propagated to the return value of the API calls +* now uses packed attribute for protocol structures on platforms with +strange alignment rules +* improved autoconf build system contributed by Nathan Brink allowing +for easier building as a shared library + +Caveats: If you were using the compile-time option that enabled checksums, +make sure to set the checksum callback inside ENetHost to enet_crc32 to +regain the old behavior. The ENetCallbacks structure has added new fields, +so make sure to clear the structure to zero before use if +using enet_initialize_with_callbacks(). + +ENet 1.2.1 (November 12, 2009): + +* fixed bug that could cause disconnect events to be dropped +* added thin wrapper around select() for portable usage +* added ENET_SOCKOPT_REUSEADDR socket option +* factored enet_socket_bind()/enet_socket_listen() out of enet_socket_create() +* added contributed Code::Blocks build file + +ENet 1.2 (February 12, 2008): + +* fixed bug in VERIFY_CONNECT acknowledgement that could cause connect +attempts to occasionally timeout +* fixed acknowledgements to check both the outgoing and sent queues +when removing acknowledged packets +* fixed accidental bit rot in the MSVC project file +* revised sequence number overflow handling to address some possible +disconnect bugs +* added enet_host_check_events() for getting only local queued events +* factored out socket option setting into enet_socket_set_option() so +that socket options are now set separately from enet_socket_create() + +Caveats: While this release is superficially protocol compatible with 1.1, +differences in the sequence number overflow handling can potentially cause +random disconnects. + +ENet 1.1 (June 6, 2007): + +* optional CRC32 just in case someone needs a stronger checksum than UDP +provides (--enable-crc32 configure option) +* the size of packet headers are half the size they used to be (so less +overhead when sending small packets) +* enet_peer_disconnect_later() that waits till all queued outgoing +packets get sent before issuing an actual disconnect +* freeCallback field in individual packets for notification of when a +packet is about to be freed +* ENET_PACKET_FLAG_NO_ALLOCATE for supplying pre-allocated data to a +packet (can be used in concert with freeCallback to support some custom +allocation schemes that the normal memory allocation callbacks would +normally not allow) +* enet_address_get_host_ip() for printing address numbers +* promoted the enet_socket_*() functions to be part of the API now +* a few stability/crash fixes + + diff --git a/Externals/enet/Doxyfile b/Externals/enet/Doxyfile new file mode 100644 index 0000000000..e62ffbc596 --- /dev/null +++ b/Externals/enet/Doxyfile @@ -0,0 +1,2303 @@ +# Doxyfile 1.8.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "ENet" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = v1.3.12 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "Reliable UDP networking library" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = YES + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = YES + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = YES + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = DoxygenLayout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c *.h *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = ${CMAKE_CURRENT_SOURCE_DIR} + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = NO + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 1 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 118 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 240 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 0 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = YES + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , / - + + + $(ExternalsDir)enet\include;%(AdditionalIncludeDirectories) + - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Externals/enet/enet.vcxproj.filters b/Externals/enet/enet.vcxproj.filters new file mode 100644 index 0000000000..5ad022de37 --- /dev/null +++ b/Externals/enet/enet.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + {a5756b80-36f2-45f6-b1f1-b67082477376} + + + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + include + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Externals/enet/enet_dll.cbp b/Externals/enet/enet_dll.cbp new file mode 100644 index 0000000000..961274c760 --- /dev/null +++ b/Externals/enet/enet_dll.cbp @@ -0,0 +1,86 @@ + + + + + + diff --git a/Externals/enet/git-revision b/Externals/enet/git-revision deleted file mode 100644 index 28a33b319e..0000000000 --- a/Externals/enet/git-revision +++ /dev/null @@ -1 +0,0 @@ -48571bb05fcdb420da2b0b38cdaf2488bd031d20 diff --git a/Externals/enet/callbacks.h b/Externals/enet/include/enet/callbacks.h similarity index 100% rename from Externals/enet/callbacks.h rename to Externals/enet/include/enet/callbacks.h diff --git a/Externals/enet/enet.h b/Externals/enet/include/enet/enet.h similarity index 99% rename from Externals/enet/enet.h rename to Externals/enet/include/enet/enet.h index ddae001a74..a6f9c0be54 100644 --- a/Externals/enet/enet.h +++ b/Externals/enet/include/enet/enet.h @@ -13,15 +13,15 @@ extern "C" #include #ifdef _WIN32 -#include "enet/win32.h" +#include "win32.h" #else -#include "enet/unix.h" +#include "unix.h" #endif -#include "enet/types.h" -#include "enet/protocol.h" -#include "enet/list.h" -#include "enet/callbacks.h" +#include "types.h" +#include "protocol.h" +#include "list.h" +#include "callbacks.h" #define ENET_VERSION_MAJOR 1 #define ENET_VERSION_MINOR 3 diff --git a/Externals/enet/list.h b/Externals/enet/include/enet/list.h similarity index 100% rename from Externals/enet/list.h rename to Externals/enet/include/enet/list.h diff --git a/Externals/enet/protocol.h b/Externals/enet/include/enet/protocol.h similarity index 99% rename from Externals/enet/protocol.h rename to Externals/enet/include/enet/protocol.h index f8c73d8a66..5a2970e188 100644 --- a/Externals/enet/protocol.h +++ b/Externals/enet/include/enet/protocol.h @@ -5,7 +5,7 @@ #ifndef __ENET_PROTOCOL_H__ #define __ENET_PROTOCOL_H__ -#include "enet/types.h" +#include "types.h" enum { diff --git a/Externals/enet/time.h b/Externals/enet/include/enet/time.h similarity index 100% rename from Externals/enet/time.h rename to Externals/enet/include/enet/time.h diff --git a/Externals/enet/types.h b/Externals/enet/include/enet/types.h similarity index 100% rename from Externals/enet/types.h rename to Externals/enet/include/enet/types.h diff --git a/Externals/enet/unix.h b/Externals/enet/include/enet/unix.h similarity index 100% rename from Externals/enet/unix.h rename to Externals/enet/include/enet/unix.h diff --git a/Externals/enet/utility.h b/Externals/enet/include/enet/utility.h similarity index 100% rename from Externals/enet/utility.h rename to Externals/enet/include/enet/utility.h diff --git a/Externals/enet/win32.h b/Externals/enet/include/enet/win32.h similarity index 100% rename from Externals/enet/win32.h rename to Externals/enet/include/enet/win32.h diff --git a/Externals/enet/libenet.pc.in b/Externals/enet/libenet.pc.in new file mode 100644 index 0000000000..7af85adbf4 --- /dev/null +++ b/Externals/enet/libenet.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE_NAME@ +Description: Low-latency UDP networking library supporting optional reliability +Version: @PACKAGE_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lenet diff --git a/Externals/enet/m4/.keep b/Externals/enet/m4/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Externals/enet/premake4.lua b/Externals/enet/premake4.lua new file mode 100644 index 0000000000..0e6e7adf6d --- /dev/null +++ b/Externals/enet/premake4.lua @@ -0,0 +1,59 @@ +solution "enet" + configurations { "Debug", "Release" } + platforms { "x32", "x64" } + + project "enet_static" + kind "StaticLib" + language "C" + + files { "*.c" } + + includedirs { "include/" } + + configuration "Debug" + targetsuffix "d" + + defines({ "DEBUG" }) + + flags { "Symbols" } + + configuration "Release" + defines({ "NDEBUG" }) + + flags { "Optimize" } + + configuration { "Debug", "x64" } + targetsuffix "64d" + + configuration { "Release", "x64" } + targetsuffix "64" + + project "enet" + kind "SharedLib" + language "C" + + files { "*.c" } + + includedirs { "include/" } + + defines({"ENET_DLL=1" }) + + configuration "Debug" + targetsuffix "d" + + defines({ "DEBUG" }) + + flags { "Symbols" } + + configuration "Release" + defines({ "NDEBUG" }) + + flags { "Optimize" } + + configuration { "Debug", "x64" } + targetsuffix "64d" + + configuration { "Release", "x64" } + targetsuffix "64" + + \ No newline at end of file diff --git a/Externals/enet/protocol.c b/Externals/enet/protocol.c index 101d923e0a..3a7dd3680c 100644 --- a/Externals/enet/protocol.c +++ b/Externals/enet/protocol.c @@ -1191,7 +1191,9 @@ commandError: static int enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event) { - for (;;) + int packets; + + for (packets = 0; packets < 256; ++ packets) { int receivedLength; ENetBuffer buffer; diff --git a/Externals/enet/unix.c b/Externals/enet/unix.c index 5e7c731435..19ee76f34e 100644 --- a/Externals/enet/unix.c +++ b/Externals/enet/unix.c @@ -104,7 +104,7 @@ enet_address_set_host (ENetAddress * address, const char * name) char buffer [2048]; int errnum; -#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); #else hostEntry = gethostbyname_r (name, & hostData, buffer, sizeof (buffer), & errnum); @@ -162,7 +162,7 @@ enet_address_get_host (const ENetAddress * address, char * name, size_t nameLeng in.s_addr = address -> host; -#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & hostEntry, & errnum); #else hostEntry = gethostbyaddr_r ((char *) & in, sizeof (struct in_addr), AF_INET, & hostData, buffer, sizeof (buffer), & errnum); @@ -246,7 +246,7 @@ enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value) { case ENET_SOCKOPT_NONBLOCK: #ifdef HAS_FCNTL - result = fcntl (socket, F_SETFL, O_NONBLOCK | fcntl (socket, F_GETFL)); + result = fcntl (socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl (socket, F_GETFL) & ~O_NONBLOCK)); #else result = ioctl (socket, FIONBIO, & value); #endif diff --git a/Externals/enet/update-enet.sh b/Externals/enet/update-enet.sh deleted file mode 100644 index b4231aa9c7..0000000000 --- a/Externals/enet/update-enet.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -xe -rm -rf _enet -git clone https://github.com/lsalzman/enet.git _enet -if [ -a git-revision ]; then - cd _enet - git checkout $(cat ../git-revision) - cp ../*.h include/ - cp ../*.c . - git stash - git checkout MASTER - git stash pop - cd .. -fi -cd _enet; git rev-parse HEAD > ../git-revision; cd .. -git rm -rf --ignore-unmatch *.[ch] -mv _enet/*.c _enet/include/enet/*.h _enet/LICENSE . -git add *.[ch] LICENSE git-revision -rm -rf _enet -echo 'Make sure to update CMakeLists.txt.' diff --git a/Externals/enet/win32.c b/Externals/enet/win32.c index b7ab336a64..5cc167997c 100644 --- a/Externals/enet/win32.c +++ b/Externals/enet/win32.c @@ -4,10 +4,11 @@ */ #ifdef _WIN32 -#include #define ENET_BUILDING_LIB 1 #include "enet/enet.h" +#include #include + static enet_uint32 timeBase = 0; int diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h index ff76d7fc71..804f3226f8 100644 --- a/Source/Core/Common/TraversalClient.h +++ b/Source/Core/Common/TraversalClient.h @@ -7,7 +7,7 @@ #include "Common/Common.h" #include "Common/Thread.h" #include "Common/TraversalProto.h" -#include "enet/enet.h" +#include "enet/include/enet/enet.h" class TraversalClientClient { From 779f275486dc720858c7f91416803fe5f6b43e74 Mon Sep 17 00:00:00 2001 From: Ziek Date: Mon, 16 Feb 2015 22:53:50 -0800 Subject: [PATCH 8/8] Added TraversalServer.cpp to Core/Common --- Externals/enet/enet.vcxproj | 5 + Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/TraversalClient.cpp | 12 +- Source/Core/Common/TraversalClient.h | 4 +- Source/Core/Common/TraversalServer.cpp | 458 +++++++++++++++++++++++++ Source/Core/DolphinWX/NetWindow.cpp | 4 +- 6 files changed, 472 insertions(+), 12 deletions(-) create mode 100644 Source/Core/Common/TraversalServer.cpp diff --git a/Externals/enet/enet.vcxproj b/Externals/enet/enet.vcxproj index b188b20267..f19b35a8a8 100644 --- a/Externals/enet/enet.vcxproj +++ b/Externals/enet/enet.vcxproj @@ -76,6 +76,11 @@ $(ExternalsDir)enet\include;%(AdditionalIncludeDirectories) + + + $(ExternalsDir)enet\include;%(AdditionalIncludeDirectories) + + diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index e4054d286e..bd6f21a81f 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -67,3 +67,4 @@ if(NOT APPLE AND NOT ANDROID) endif() add_dolphin_library(common "${SRCS}" "${LIBS}") +add_executable(traversal_server TraversalServer.cpp) diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index e9e6c5939b..574f002e4e 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -1,6 +1,6 @@ // This file is public domain, in case it's useful to anyone. -comex -#include "Timer.h" +#include "Common/Timer.h" #include "Common/TraversalClient.h" static void GetRandomishBytes(u8* buf, size_t size) @@ -207,7 +207,7 @@ void TraversalClient::HandleServerPacket(TraversalPacket* packet) } } -void TraversalClient::OnFailure(int reason) +void TraversalClient::OnFailure(FailureReason reason) { m_State = Failure; m_FailureReason = reason; @@ -216,8 +216,7 @@ void TraversalClient::OnFailure(int reason) { case TraversalClient::BadHost: { - auto server = "dolphin-emu.org"; - PanicAlertT("Couldn't look up central server %s", server); + PanicAlertT("Couldn't look up central server %s", m_Server.c_str()); break; } case TraversalClient::VersionTooOld: @@ -232,9 +231,6 @@ void TraversalClient::OnFailure(int reason) case TraversalClient::ResendTimeout: PanicAlertT("Timeout connecting to traversal server"); break; - default: - PanicAlertT("Unknown error %x", reason); - break; } if (m_Client) @@ -279,7 +275,7 @@ void TraversalClient::HandlePing() enet_uint32 now = enet_time_get(); if (m_State == Connected && now - m_PingTime >= 500) { - TraversalPacket ping = {0}; + TraversalPacket ping = {}; ping.type = TraversalPacketPing; ping.ping.hostId = m_HostId; SendTraversalPacket(ping); diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h index 804f3226f8..7dbc92286e 100644 --- a/Source/Core/Common/TraversalClient.h +++ b/Source/Core/Common/TraversalClient.h @@ -4,10 +4,10 @@ #include #include #include +#include #include "Common/Common.h" #include "Common/Thread.h" #include "Common/TraversalProto.h" -#include "enet/include/enet/enet.h" class TraversalClientClient { @@ -62,7 +62,7 @@ private: void HandleServerPacket(TraversalPacket* packet); void ResendPacket(OutgoingTraversalPacketInfo* info); TraversalRequestId SendTraversalPacket(const TraversalPacket& packet); - void OnFailure(int reason); + void OnFailure(FailureReason reason); void HandlePing(); static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event); TraversalRequestId m_ConnectRequestId; diff --git a/Source/Core/Common/TraversalServer.cpp b/Source/Core/Common/TraversalServer.cpp new file mode 100644 index 0000000000..777078bccb --- /dev/null +++ b/Source/Core/Common/TraversalServer.cpp @@ -0,0 +1,458 @@ +// This file is public domain, in case it's useful to anyone. -comex + +// The central server implementation. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common/TraversalProto.h" + +#define DEBUG 1 +#define NUMBER_OF_TRIES 5 + +static u64 currentTime; + +struct OutgoingPacketInfo +{ + TraversalPacket packet; + TraversalRequestId misc; + sockaddr_in6 dest; + int tries; + u64 sendTime; +}; + +template +struct EvictEntry +{ + u64 updateTime; + T value; +}; + +template +struct EvictFindResult +{ + bool found; + V* value; +}; + +template +EvictFindResult EvictFind(std::unordered_map>& map, const K& key, bool refresh = false) +{ + retry: + const u64 expiryTime = 30 * 1000000; // 30s + EvictFindResult result; + if (map.bucket_count()) + { + auto bucket = map.bucket(key); + auto it = map.begin(bucket); + for (; it != map.end(bucket); ++it) + { + if (currentTime - it->second.updateTime > expiryTime) + { + map.erase(it->first); + goto retry; + } + if (it->first == key) + { + if (refresh) + it->second.updateTime = currentTime; + result.found = true; + result.value = &it->second.value; + return result; + } + } + } +#if DEBUG + printf("failed to find key '"); + for (size_t i = 0; i < sizeof(key); i++) { + printf("%02x", ((u8 *) &key)[i]); + } + printf("'\n"); +#endif + result.found = false; + return result; +} + +template +V* EvictSet(std::unordered_map>& map, const K& key) +{ + // can't use a local_iterator to emplace... + auto& result = map[key]; + result.updateTime = currentTime; + return &result.value; +} + +namespace std +{ + template <> + struct hash + { + size_t operator()(const TraversalHostId& id) const + { + auto p = (u32*) id.data(); + return p[0] ^ ((p[1] << 13) | (p[1] >> 19)); + } + }; +} + +static int sock; +static int urandomFd; +static std::unordered_map< + TraversalRequestId, + OutgoingPacketInfo +> outgoingPackets; +static std::unordered_map< + TraversalHostId, + EvictEntry +> connectedClients; + +static TraversalInetAddress MakeInetAddress(const sockaddr_in6& addr) +{ + if (addr.sin6_family != AF_INET6) + { + fprintf(stderr, "bad sockaddr_in6\n"); + exit(1); + } + u32* words = (u32*) addr.sin6_addr.s6_addr; + TraversalInetAddress result = {0}; + if (words[0] == 0 && words[1] == 0 && words[2] == 0xffff0000) + { + result.isIPV6 = false; + result.address[0] = words[3]; + } + else + { + result.isIPV6 = true; + memcpy(result.address, words, sizeof(result.address)); + } + result.port = addr.sin6_port; + return result; +} + +static sockaddr_in6 MakeSinAddr(const TraversalInetAddress& addr) +{ + sockaddr_in6 result; +#ifdef SIN6_LEN + result.sin6_len = sizeof(result); +#endif + result.sin6_family = AF_INET6; + result.sin6_port = addr.port; + result.sin6_flowinfo = 0; + if (addr.isIPV6) + { + memcpy(&result.sin6_addr, addr.address, 16); + } + else + { + u32* words = (u32*) result.sin6_addr.s6_addr; + words[0] = 0; + words[1] = 0; + words[2] = 0xffff0000; + words[3] = addr.address[0]; + } + result.sin6_scope_id = 0; + return result; +} + +static void GetRandomBytes(void* output, size_t size) +{ + static u8 bytes[8192]; + static size_t bytesLeft = 0; + if (bytesLeft < size) + { + ssize_t rv = read(urandomFd, bytes, sizeof(bytes)); + if (rv != sizeof(bytes)) + { + perror("read from /dev/urandom"); + exit(1); + } + bytesLeft = sizeof(bytes); + } + memcpy(output, bytes + (bytesLeft -= size), size); +} + +static void GetRandomHostId(TraversalHostId* hostId) +{ + char buf[9]; + u32 num; + GetRandomBytes(&num, sizeof(num)); + sprintf(buf, "%08x", num); + memcpy(hostId->data(), buf, 8); +} + +static const char* SenderName(sockaddr_in6* addr) +{ + static char buf[INET6_ADDRSTRLEN + 10]; + inet_ntop(PF_INET6, &addr->sin6_addr, buf, sizeof(buf)); + sprintf(buf + strlen(buf), ":%d", ntohs(addr->sin6_port)); + return buf; +} + +static void TrySend(const void* buffer, size_t size, sockaddr_in6* addr) +{ +#if DEBUG + printf("-> %d %lu %s\n", ((TraversalPacket*) buffer)->type, ((TraversalPacket*) buffer)->requestId, SenderName(addr)); +#endif + if ((size_t) sendto(sock, buffer, size, 0, (sockaddr*) addr, sizeof(*addr)) != size) + { + perror("sendto"); + } +} + +static TraversalPacket* AllocPacket(const sockaddr_in6& dest, TraversalRequestId misc = 0) +{ + TraversalRequestId requestId; + GetRandomBytes(&requestId, sizeof(requestId)); + OutgoingPacketInfo* info = &outgoingPackets[requestId]; + info->dest = dest; + info->misc = misc; + info->tries = 0; + info->sendTime = currentTime; + TraversalPacket* result = &info->packet; + memset(result, 0, sizeof(*result)); + result->requestId = requestId; + return result; +} + +static void SendPacket(OutgoingPacketInfo* info) +{ + info->tries++; + info->sendTime = currentTime; + TrySend(&info->packet, sizeof(info->packet), &info->dest); +} + + +static void ResendPackets() +{ + std::vector> todoFailures; + todoFailures.clear(); + for (auto it = outgoingPackets.begin(); it != outgoingPackets.end();) + { + OutgoingPacketInfo* info = &it->second; + if (currentTime - info->sendTime >= (u64) (300000 * info->tries)) + { + if (info->tries >= NUMBER_OF_TRIES) + { + if (info->packet.type == TraversalPacketPleaseSendPacket) + { + todoFailures.push_back(std::make_pair(info->packet.pleaseSendPacket.address, info->misc)); + } + it = outgoingPackets.erase(it); + continue; + } + else + { + SendPacket(info); + } + } + ++it; + } + + for (const auto& p : todoFailures) + { + TraversalPacket* fail = AllocPacket(MakeSinAddr(p.first)); + fail->type = TraversalPacketConnectFailed; + fail->connectFailed.requestId = p.second; + fail->connectFailed.reason = TraversalConnectFailedClientDidntRespond; + } +} + +static void HandlePacket(TraversalPacket* packet, sockaddr_in6* addr) +{ +#if DEBUG + printf("<- %d %lu %s\n", packet->type, packet->requestId, SenderName(addr)); +#endif + bool packetOk = true; + switch (packet->type) + { + case TraversalPacketAck: + { + auto it = outgoingPackets.find(packet->requestId); + if (it == outgoingPackets.end()) + break; + + OutgoingPacketInfo* info = &it->second; + + if (info->packet.type == TraversalPacketPleaseSendPacket) + { + TraversalPacket* ready = AllocPacket(MakeSinAddr(info->packet.pleaseSendPacket.address)); + if (packet->ack.ok) + { + ready->type = TraversalPacketConnectReady; + ready->connectReady.requestId = info->misc; + ready->connectReady.address = MakeInetAddress(info->dest); + } + else + { + ready->type = TraversalPacketConnectFailed; + ready->connectFailed.requestId = info->misc; + ready->connectFailed.reason = TraversalConnectFailedClientFailure; + } + } + + outgoingPackets.erase(it); + break; + } + case TraversalPacketPing: + { + auto r = EvictFind(connectedClients, packet->ping.hostId, true); + packetOk = r.found; + break; + } + case TraversalPacketHelloFromClient: + { + u8 ok = packet->helloFromClient.protoVersion <= TraversalProtoVersion; + TraversalPacket* reply = AllocPacket(*addr); + reply->type = TraversalPacketHelloFromServer; + reply->helloFromServer.ok = ok; + if (ok) + { + TraversalHostId hostId; + TraversalInetAddress* iaddr; + // not that there is any significant change of + // duplication, but... + GetRandomHostId(&hostId); + while (true) + { + auto r = EvictFind(connectedClients, hostId); + if (!r.found) + { + iaddr = EvictSet(connectedClients, hostId); + break; + } + } + + *iaddr = MakeInetAddress(*addr); + + reply->helloFromServer.yourAddress = *iaddr; + reply->helloFromServer.yourHostId = hostId; + } + break; + } + case TraversalPacketConnectPlease: + { + TraversalHostId& hostId = packet->connectPlease.hostId; + auto r = EvictFind(connectedClients, hostId); + if (!r.found) + { + TraversalPacket* reply = AllocPacket(*addr); + reply->type = TraversalPacketConnectFailed; + reply->connectFailed.requestId = packet->requestId; + reply->connectFailed.reason = TraversalConnectFailedNoSuchClient; + } + else + { + TraversalPacket* please = AllocPacket(MakeSinAddr(*r.value), packet->requestId); + please->type = TraversalPacketPleaseSendPacket; + please->pleaseSendPacket.address = MakeInetAddress(*addr); + } + break; + } + default: + fprintf(stderr, "received unknown packet type %d from %s\n", packet->type, SenderName(addr)); + } + if (packet->type != TraversalPacketAck) + { + TraversalPacket ack = {}; + ack.type = TraversalPacketAck; + ack.requestId = packet->requestId; + ack.ack.ok = packetOk; + TrySend(&ack, sizeof(ack), addr); + } +} + +int main() +{ + int rv; + + urandomFd = open("/dev/urandom", O_RDONLY); + if (urandomFd < 0) + { + perror("open /dev/urandom"); + return 1; + } + + sock = socket(PF_INET6, SOCK_DGRAM, 0); + if (sock == -1) + { + perror("socket"); + return 1; + } + int no = 0; + rv = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no)); + if (rv < 0) + { + perror("setsockopt IPV6_V6ONLY"); + return 1; + } + in6_addr any = IN6ADDR_ANY_INIT; + sockaddr_in6 addr; +#ifdef SIN6_LEN + addr.sin6_len = sizeof(addr); +#endif + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(6262); + addr.sin6_flowinfo = 0; + addr.sin6_addr = any; + addr.sin6_scope_id = 0; + + rv = bind(sock, (sockaddr*) &addr, sizeof(addr)); + if (rv < 0) + { + perror("bind"); + return 1; + } + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 300000; + rv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + if (rv < 0) + { + perror("setsockopt SO_RCVTIMEO"); + return 1; + } + + while (true) + { + sockaddr_in6 raddr; + socklen_t addrLen = sizeof(raddr); + TraversalPacket packet; + // note: switch to recvmmsg (yes, mmsg) if this becomes + // expensive + rv = recvfrom(sock, &packet, sizeof(packet), 0, (sockaddr*) &raddr, &addrLen); + if (gettimeofday(&tv, NULL) < 0) + { + perror("gettimeofday"); + exit(1); + } + currentTime = (u64) tv.tv_sec * 1000000 + tv.tv_usec; + if (rv < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + perror("recvfrom"); + return 1; + } + } + else if ((size_t) rv < sizeof(packet)) + { + fprintf(stderr, "received short packet from %s\n", SenderName(&raddr)); + } + else + { + HandlePacket(&packet, &raddr); + } + ResendPackets(); + } +} diff --git a/Source/Core/DolphinWX/NetWindow.cpp b/Source/Core/DolphinWX/NetWindow.cpp index 0768b615ed..02e16cda3b 100644 --- a/Source/Core/DolphinWX/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetWindow.cpp @@ -135,14 +135,14 @@ NetPlaySetupDiag::NetPlaySetupDiag(wxWindow* const parent, const CGameListCtrl* nick_szr->Add(m_nickname_text, 0, wxALL, 5); std::string centralServer; - netplay_section.Get("TraversalServer", ¢ralServer, "vps.qoid.us"); + netplay_section.Get("TraversalServer", ¢ralServer, ""); m_traversal_server_lbl = new wxStaticText(panel, wxID_ANY, _("Traversal:")); m_traversal_server = new wxTextCtrl(panel, wxID_ANY, StrToWxStr(centralServer)); nick_szr->Add(m_traversal_server_lbl, 0, wxCENTER); nick_szr->Add(m_traversal_server, 0, wxALL, 5); std::string centralPort; - netplay_section.Get("TraversalPort", ¢ralPort, "6262"); + netplay_section.Get("TraversalPort", ¢ralPort, ""); m_traversal_port_lbl = new wxStaticText(panel, wxID_ANY, _("Port:")); m_traversal_port = new wxTextCtrl(panel, wxID_ANY, StrToWxStr(centralPort)); nick_szr->Add(m_traversal_port_lbl, 0, wxCENTER);