diff --git a/bot/bot.vcproj b/bot/bot.vcproj index 73905fa..20010e1 100644 --- a/bot/bot.vcproj +++ b/bot/bot.vcproj @@ -212,45 +212,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -260,12 +521,27 @@ + + + + + + + + + + @@ -308,6 +584,36 @@ + + + + + + + + + + + + + + + + + + + + @@ -317,6 +623,9 @@ + + @@ -332,6 +641,9 @@ + + @@ -347,6 +659,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -359,6 +707,18 @@ + + + + + + + + diff --git a/raknet/AsynchronousFileIO.cpp b/raknet/AsynchronousFileIO.cpp new file mode 100644 index 0000000..895908f --- /dev/null +++ b/raknet/AsynchronousFileIO.cpp @@ -0,0 +1,323 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +// No longer used as I no longer support IO Completion ports +/* + +#ifdef __USE_IO_COMPLETION_PORTS + +#include "AsynchronousFileIO.h" +#include "ClientContextStruct.h" +#include +#include "ExtendedOverlappedPool.h" +#include +#include + +// All these are used for the Read callback. For general Asynch file IO you would change these +#include "NetworkTypes.h" + +class RakPeer; + +#ifdef _WIN32 +extern void __stdcall ProcessNetworkPacket( unsigned int binaryAddress, unsigned short port, const char *data, int length, RakPeer *rakPeer ); +#else +extern void ProcessNetworkPacket( unsigned int binaryAddress, unsigned short port, const char *data, int length, RakPeer *rakPeer ); +#endif + +AsynchronousFileIO AsynchronousFileIO::I; + +AsynchronousFileIO::AsynchronousFileIO() +{ + userCount = 0; + threadCount = 0; + completionPort = NULL; + + // Determine how many processors are on the system. + GetSystemInfo( &systemInfo ); +} + +void AsynchronousFileIO::IncreaseUserCount() +{ + userCountMutex.Lock(); + ++userCount; + + if ( userCount == 1 ) + { + + // Create the completion port that will be used by all the worker + // threads. + completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, systemInfo.dwNumberOfProcessors * 2 ); + + if ( completionPort == NULL ) + { + userCount = 0; + userCountMutex.Unlock(); + return ; + } + + UINT nThreadID; + HANDLE workerHandle; + + // Create worker threads + + // One worker thread per processor + + for ( DWORD i = 0; i < systemInfo.dwNumberOfProcessors * 2; i++ ) + // In debug just make one worker thread so it's easier to trace + //for ( i = 0; i < systemInfo.dwNumberOfProcessors * 1; i++ ) + { + workerHandle = ( HANDLE ) _beginthreadex( NULL, // Security + 0, // Stack size - use default + ThreadPoolFunc, // Thread fn entry point + ( void* ) completionPort, // Param for thread + 0, // Init flag + &nThreadID ); // Thread address + + + // Feel free to comment this out for regular thread priority + SetThreadPriority( workerHandle, THREAD_PRIORITY_HIGHEST ); + + CloseHandle( workerHandle ); + } + + + // Wait for the threads to start + while ( threadCount < systemInfo.dwNumberOfProcessors * 2 ) + Sleep( 0 ); + } + + userCountMutex.Unlock(); +} + +void AsynchronousFileIO::DecreaseUserCount() +{ + userCountMutex.Lock(); + + assert( userCount > 0 ); + + if ( userCount == 0 ) + return ; + + userCount--; + + if ( userCount == 0 ) + Shutdown(); + + userCountMutex.Unlock(); +} + +void AsynchronousFileIO::Shutdown( void ) +{ + killThreads = true; + + if ( completionPort != NULL ) + for ( DWORD i = 0; i < systemInfo.dwNumberOfProcessors * 2; i++ ) + PostQueuedCompletionStatus( completionPort, 0, 0 , 0 ); + + // Kill worker threads + while ( threadCount > 0 ) + Sleep( 0 ); + + if ( completionPort != NULL ) + CloseHandle( completionPort ); +} + +int AsynchronousFileIO::GetUserCount( void ) +{ + return userCount; +} + +AsynchronousFileIO::~AsynchronousFileIO() +{ + if ( threadCount > 0 ) + Shutdown(); +} + +bool AsynchronousFileIO::AssociateSocketWithCompletionPort( SOCKET socket, DWORD dwCompletionKey ) +{ + HANDLE h = CreateIoCompletionPort( ( HANDLE ) socket, completionPort, dwCompletionKey, 0 ); + return h == completionPort; +} + +BOOL ReadAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ) +{ + BOOL success; + extended->read = true; + success = ReadFile( handle, extended->data, extended->length, 0, ( LPOVERLAPPED ) extended ); + + if ( !success ) + { + DWORD dwErrCode = GetLastError(); + + if ( dwErrCode != ERROR_IO_PENDING ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErrCode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "ReadFile failed:Error code - %d\n%s", dwErrCode, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return FALSE; + } + } + + return TRUE; +} + +void WriteAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ) +{ + //printf("Beginning asynch write of %i bytes.\n",extended->length); + //for (int i=0; i < extended->length && i < 10; i++) + // printf("%i ", extended->data[i]); + //printf("\n\n"); + BOOL success; + extended->read = false; + success = WriteFile( handle, extended->data, extended->length, 0, ( LPOVERLAPPED ) extended ); + + if ( !success ) + { + DWORD dwErrCode = GetLastError(); + + if ( dwErrCode != ERROR_IO_PENDING ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErrCode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "WriteFile failed:Error code - %d\n%s", dwErrCode, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + } +} + +unsigned __stdcall ThreadPoolFunc( LPVOID arguments ) +{ + DWORD dwIoSize; + ClientContextStruct* lpClientContext; + ExtendedOverlappedStruct* lpOverlapped; + LPOVERLAPPED temp; + BOOL bError; + + HANDLE *completionPort = ( HANDLE * ) arguments; + AsynchronousFileIO::Instance()->threadCount++; + + while ( 1 ) + { + // Get a completed IO request. + BOOL returnValue = GetQueuedCompletionStatus( + completionPort, + &dwIoSize, + ( LPDWORD ) & lpClientContext, + &temp, INFINITE ); + + lpOverlapped = ( ExtendedOverlappedStruct* ) temp; + + DWORD dwIOError = GetLastError(); + + if ( lpOverlapped == 0 ) + break; // Cancelled thread + + if ( !returnValue && dwIOError != WAIT_TIMEOUT ) + { + if ( dwIOError != ERROR_OPERATION_ABORTED ) + { + // Print all but this very common error message +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "GetQueuedCompletionStatus failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + +HANDLE_ERROR: + // Some kind of error. Erase the data for this call + bError = true; + + // This socket is no longer used + + if ( lpOverlapped ) + delete lpOverlapped; + + if ( lpClientContext ) + delete lpClientContext; + + // If we are killing the threads, then we keep posting fake completion statuses until we get a fake one through the queue (i.e. lpOverlapped==0 as above) + // This way we delete all the data from the real calls before exiting the thread + if ( AsynchronousFileIO::Instance()->killThreads ) + { + PostQueuedCompletionStatus( completionPort, 0, 0, 0 ); + } + + } + + else + bError = false; + + if ( !bError ) + { + if ( returnValue && NULL != lpOverlapped && NULL != lpClientContext ) + { + if ( lpOverlapped->read == true ) + { + assert( dwIoSize > 0 ); + + ProcessNetworkPacket( lpOverlapped->binaryAddress, lpOverlapped->port, lpOverlapped->data, dwIoSize, lpOverlapped->rakPeer ); + + // Issue a new read so we always have one outstanding read per socket + // Finished a read. Reuse the overlapped pointer + bError = ReadAsynch( lpClientContext->handle, lpOverlapped ); + + if ( !bError ) + goto HANDLE_ERROR; // Windows is super unreliable! + } + + else + { + // AsynchronousFileIO::Instance()->Write(lpClientContext); + // Finished a write + ExtendedOverlappedPool::Instance()->ReleasePointer( lpOverlapped ); + } + } + + else + assert( 0 ); + } + } + + AsynchronousFileIO::Instance()->threadCount--; + return 0; +} + +#endif +*/ diff --git a/raknet/AsynchronousFileIO.h b/raknet/AsynchronousFileIO.h new file mode 100644 index 0000000..2d4b325 --- /dev/null +++ b/raknet/AsynchronousFileIO.h @@ -0,0 +1,91 @@ +/// \file +/// \brief \b [Internal] Depreciated, used for windows back when I supported IO completion ports. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS + +#ifndef __ASYNCHRONOUS_FILE_IO_H +#define __ASYNCHRONOUS_FILE_IO_H + +#ifdef _COMPATIBILITY_1 +#elif defined(_WIN32) +// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib +// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly +//#include +//#include +#endif +#include "SimpleMutex.h" + +struct ExtendedOverlappedStruct; + +/// Provides asynch file input and ouput, either for sockets or files +class AsynchronousFileIO +{ + +public: + + /// Default Constructor + AsynchronousFileIO(); + + /// Destructor + ~AsynchronousFileIO(); + + +/// Associate a socket with a completion port +/// \param[in] socket the socket used for communication +/// \param[in] dwCompletionKey the completion port key + bool AssociateSocketWithCompletionPort( SOCKET socket, DWORD dwCompletionKey );if + + /// Singleton instance + static inline AsynchronousFileIO* Instance() + { + return & I; + } + + /// Increase the number of users of this instance + void IncreaseUserCount( void ); + + /// Decrease the number of users of this instance + void DecreaseUserCount( void ); + + /// Stop using asynchronous IO + void Shutdown( void ); + + /// Get the number of user of the instance + int GetUserCount( void ); + + unsigned threadCount; + bool killThreads; + +private: + HANDLE completionPort; + SimpleMutex userCountMutex; + SYSTEM_INFO systemInfo; + int userCount; + + static AsynchronousFileIO I; +}; + +unsigned __stdcall ThreadPoolFunc( LPVOID arguments ); + +void WriteAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ); + +BOOL ReadAsynch( HANDLE handle, ExtendedOverlappedStruct *extended ); + +#endif +*/ \ No newline at end of file diff --git a/raknet/AutopatcherPatchContext.h b/raknet/AutopatcherPatchContext.h new file mode 100644 index 0000000..1adb6dd --- /dev/null +++ b/raknet/AutopatcherPatchContext.h @@ -0,0 +1,15 @@ +#ifndef __AUTOPATCHER_PATCH_CONTEXT_H +#define __AUTOPATCHER_PATCH_CONTEXT_H + +enum PatchContext +{ + PC_HASH_WITH_PATCH, + PC_WRITE_FILE, + PC_ERROR_FILE_WRITE_FAILURE, + PC_ERROR_PATCH_TARGET_MISSING, + PC_ERROR_PATCH_APPLICATION_FAILURE, + PC_ERROR_PATCH_RESULT_CHECKSUM_FAILURE, + PC_NOTICE_WILL_COPY_ON_RESTART, +}; + +#endif diff --git a/raknet/AutopatcherRepositoryInterface.h b/raknet/AutopatcherRepositoryInterface.h new file mode 100644 index 0000000..ed7716a --- /dev/null +++ b/raknet/AutopatcherRepositoryInterface.h @@ -0,0 +1,52 @@ +/// \file +/// \brief An interface used by AutopatcherServer to get the data necessary to run an autopatcher. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + + +#ifndef __AUTOPATCHER_REPOSITORY_INTERFACE_H +#define __AUTOPATCHER_REPOSITORY_INTERFACE_H + +class FileList; +namespace RakNet +{ + class BitStream; +} + +/// An interface used by AutopatcherServer to get the data necessary to run an autopatcher. This is up to you to implement for custom repository solutions. +class AutopatcherRepositoryInterface +{ +public: + /// Get list of files added and deleted since a certain date. This is used by AutopatcherServer and not usually explicitly called. + /// \param[in] applicationName A null terminated string identifying the application + /// \param[out] addedFiles A list of the current versions of filenames with hashes as their data that were created after \a sinceData + /// \param[out] deletedFiles A list of the current versions of filenames that were deleted after \a sinceData + /// \param[in] An input date, in whatever format your repository uses + /// \param[out] currentDate The current server date, in whatever format your repository uses + /// \return True on success, false on failure. + virtual bool GetChangelistSinceDate(const char *applicationName, FileList *addedFiles, FileList *deletedFiles, const char *sinceDate, char currentDate[64])=0; + + /// Get patches (or files) for every file in input, assuming that input has a hash for each of those files. + /// \param[in] applicationName A null terminated string identifying the application + /// \param[in] input A list of files with SHA1_LENGTH byte hashes to get from the database. + /// \param[out] patchList You should return list of files with either the filedata or the patch. This is a subset of \a input. The context data for each file will be either PC_WRITE_FILE (to just write the file) or PC_HASH_WITH_PATCH (to patch). If PC_HASH_WITH_PATCH, then the file contains a SHA1_LENGTH byte patch followed by the hash. The datalength is patchlength + SHA1_LENGTH + /// \return True on success, false on failure. + virtual bool GetPatches(const char *applicationName, FileList *input, FileList *patchList)=0; + + /// \return Whatever this function returns is sent from the AutopatcherServer to the AutopatcherClient when one of the above functions returns false. + virtual char *GetLastError(void) const=0; +}; + +#endif diff --git a/raknet/BigTypes.h b/raknet/BigTypes.h new file mode 100644 index 0000000..2f04810 --- /dev/null +++ b/raknet/BigTypes.h @@ -0,0 +1,1820 @@ +/// \file +/// \brief \b [Internal] Used for RSA generation. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// (128)2^7-bit to (32768)2^14-bit signed 2's complement & unsigned extended arithmetic +/// +/// catid(cat02e@fsu.edu) +/// +/// 7/30/2004 Fixed VS6 compat +/// 7/28/2004 Fixed macros so they can be used outside of the big namespace +/// Now using pre-processor definitions from types.h for inline assembly +/// 7/26/2004 Removed a lot of assembly, made add/sub assembly optional +/// 7/25/2004 Merged the wrapper class Int from older code +/// 7/24/2004 Replaced trivial assembly code with std:: functions +/// Refined some assembly code with Art of Assembly chapter 9 +/// Added binary ops +/// 7/23/2004 Finished assembly coding +/// Removed Int class, for now +/// Added old C++ code back in with USEASSEMBLY +/// 7/22/2004 Signed arithmetic (needed for ext. Euclidean algo) +/// Cleaned up coding style +/// Began rewriting parts in assembly +/// 7/21/2004 Began writing +/// +/// Tabs: 4 spaces +/// Dist: public + +#ifndef BIGTYPES_H +#define BIGTYPES_H + +#if !defined(_COMPATIBILITY_1) + +#include "Types.h" + +//#define BIG_USES_STRINGS /* undefining this means you cannot convert bigs to strings or from strings */ + +#ifdef BIG_USES_STRINGS +# include +#endif + + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +namespace big +{ + + using namespace cat; + + //// basic definitions //// + + // word size + typedef u32 word; // assembly implementation is for 32-bit word size + const u32 WORDBITS = sizeof( word ) * 8; + const u32 HALFWORDBITS = sizeof( word ) * 8 / 2; + const word WORDHIGHBIT = ( word ) 1 << ( WORDBITS - 1 ); + const word WORDALLBITS = ( word ) 0 - 1; + const word WORDLOBITS = ( ( word ) 1 << HALFWORDBITS ) - 1; + const word WORDHIBITS = WORDALLBITS ^ WORDLOBITS; +#define BIGHIGHBIT(n) ((n)[sizeof(n) / sizeof(big::word) - 1] & WORDHIGHBIT) + + // template operator parameter modes +#define BIGONETYPE template /* supports only one class */ +#define BIGTWOTYPES template /* sizeof Bigger >= sizeof T */ +#define BIGSMALLTYPE template /* sizeof self >= sizeof Smaller */ + + + //// big types //// + +#define BIGWORDCOUNT_FROMBITCOUNT(bits) ((bits) / 8 / sizeof(big::word)) +#define BIGWORDCOUNT(T) (sizeof(T) / sizeof(big::word)) +#define BIGBITCOUNT(T) (sizeof(T) * 8) + + // low words -- [0] < [1] < [2] < [3] -- high words + typedef word u128[ BIGWORDCOUNT_FROMBITCOUNT( 128 ) ]; + typedef word u256[ BIGWORDCOUNT_FROMBITCOUNT( 256 ) ]; + typedef word u512[ BIGWORDCOUNT_FROMBITCOUNT( 512 ) ]; + typedef word u1024[ BIGWORDCOUNT_FROMBITCOUNT( 1024 ) ]; + typedef word u2048[ BIGWORDCOUNT_FROMBITCOUNT( 2048 ) ]; + typedef word u4096[ BIGWORDCOUNT_FROMBITCOUNT( 4096 ) ]; + typedef word u8192[ BIGWORDCOUNT_FROMBITCOUNT( 8192 ) ]; + typedef word u16384[ BIGWORDCOUNT_FROMBITCOUNT( 16384 ) ]; + typedef word u32768[ BIGWORDCOUNT_FROMBITCOUNT( 32768 ) ]; + + // use these macros to create temporary variables when + // those variables are to be twice/half the size of another + // variable of varying size. +#define BIGDOUBLESIZE(T, var_name) big::word (var_name)[BIGWORDCOUNT(T) * 2] /* WARNING: invalid w/ u32768 */ +#define BIGHALFSIZE(T, var_name) big::word (var_name)[BIGWORDCOUNT(T) / 2] /* WARNING: invalid w/ u128 */ + + + //// library summary //// + + // assignment + BIGONETYPE INLINE void zero( T &n ); // n = 0 + BIGONETYPE INLINE void usetw( T &a, word b ); // a = b, zero-extend + BIGONETYPE INLINE void ssetw( T &a, word b ); // a = b, sign-extend + + BIGONETYPE INLINE void set ( T &a, T &b ) + + ; // a = b + BIGTWOTYPES INLINE void usetlow( Bigger &a, T &b ); // a_low = b (zero-extend) + + BIGTWOTYPES INLINE void ssetlow( Bigger &a, T &b ); // a_low = b (sign-extend) + + BIGTWOTYPES INLINE void sethigh( Bigger &a, T &b ); // a_high = b + + BIGTWOTYPES INLINE void takelow( T &a, Bigger &b ); // a = b_low + + BIGTWOTYPES INLINE void takehigh( T &a, Bigger &b ); // a = b_high + + // comparison + BIGONETYPE bool ugreater( T &a, T &b ); // a > b (unsigned) + + BIGONETYPE bool ugreaterOrEqual( T &a, T &b ); // a >= b (unsigned) + + BIGONETYPE bool sgreater( T &a, T &b ); // a > b (signed) + + BIGONETYPE bool sgreaterOrEqual( T &a, T &b ); // a >= b (signed) + + BIGONETYPE INLINE bool equal( T &a, T &b ); // a == b + + BIGONETYPE INLINE bool isZero( T &n ); // a == 0 + + // binary + BIGONETYPE void bAND( T &a, T &b ); // a &= b + + BIGONETYPE void bOR( T &a, T &b ); // a |= b + + BIGONETYPE void bXOR( T &a, T &b ); // a ^= b + + BIGONETYPE void bNOT( T &n ); // n = ~n + + // shifting + BIGONETYPE void shiftLeft1( T &n ); // n <<= 1 + + BIGONETYPE void shiftLeft( T &n, u32 s ); // n <<= s (s <= WORDBITS) + + BIGONETYPE void ushiftRight1( T &n ); // n >>= 1 (unsigned) + + BIGONETYPE void ushiftRight( T &n, u32 s ); // n >>= s (unsigned) (s <= WORDBITS) + + BIGONETYPE void sshiftRight1( T &n ); // n >>= 1 (signed) + + BIGONETYPE void sshiftRight( T &n, u32 s ); // n >>= s (signed) (s <= WORDBITS) + + // addition/subtraction + BIGONETYPE void add ( T &a, T &b ) + + ; // a += b + BIGONETYPE void increment( T &n ); // ++n + + BIGONETYPE void subtract( T &a, T &b ); // a -= b + + BIGONETYPE void decrement( T &n ); // --n + + // negation + BIGONETYPE void negate( T &n ); // n = -n + + // multiplication + BIGONETYPE void usquare( T &a ); // a *= a, signed + + BIGTWOTYPES void umultiply( T &a, T &b, Bigger &m ); // m = a * b (&a != &b != &m), unsigned + + BIGTWOTYPES void umultiply( Bigger &a, T &b ); // a *= b (&a != &b), unsigned + + BIGONETYPE void ssquare( T &a ); // a *= a, signed + + BIGTWOTYPES void smultiply( T &a, T &b, Bigger &m ); // m = a * b (&a != &b != &m), signed + + BIGTWOTYPES void smultiply( Bigger &a, T &b ); // a *= b (&a != &b), signed + + // division/remainder + BIGONETYPE void udivide( T &a, T &b, T &q, T &r ); // {q, r} = a / b (&q != &r), unsigned + + BIGONETYPE void umodulo( T &a, T &b, T &r ); // r = a Mod b, unsigned + + BIGONETYPE void sdivide( T &a, T &b, T &q, T &r ); // {q, r} = a / b (&q != &r), signed + + BIGONETYPE void smodulo( T &a, T &b, T &r ); // r = a Mod b, signed + +#ifdef BIG_USES_STRINGS + // converting to/from strings + BIGONETYPE std::string toString( T &n, bool sign, u16 radix ); // n -> string + + BIGONETYPE void fromString( std::string s, T &n, bool sign, u16 radix ); // s -> n + +#endif + + + //////// wrapper class //////// + +#define BIGINTFAST INLINE Int & /* operation is done to self, returns itself */ +#define BIGINTSLOW Int /* new object is created and returned */ + + BIGONETYPE class Int + { + + protected: + T raw; + + public: + operator T &(); // automatic casting to T: you may use BigInt classes as parameters to the functions + + public: + Int(); + Int( word n ); +#ifdef BIG_USES_STRINGS + + Int( std::string &s ); +#endif + + Int( T &n ); + + public: + BIGINTFAST zero(); + BIGINTFAST operator=( word n ); + BIGINTFAST operator=( T &n ); + + public: + BIGINTFAST operator<<=( u32 s ); + BIGINTSLOW operator<<( u32 s ); + BIGINTFAST operator>>=( u32 s ); + BIGINTSLOW operator>>( u32 s ); + + public: + BIGINTFAST operator+=( T &n ); + BIGINTSLOW operator+( T &n ); + BIGINTFAST operator-=( T &n ); + BIGINTSLOW operator-( T &n ); + BIGINTFAST operator++(); // prefix + BIGINTSLOW operator++( int ); // postfix + BIGINTFAST operator--(); // prefix + BIGINTSLOW operator--( int ); // postfix + + public: + BIGINTSLOW operator-( int ); // negation + + public: + BIGSMALLTYPE BIGINTFAST operator*=( Smaller &n ) + { + smultiply( raw, n ); + return *this; + } + + BIGINTSLOW operator*( T &n ); + BIGINTFAST square(); + + public: + BIGINTFAST operator/=( T &n ); + BIGINTSLOW operator/( T &n ); + BIGINTFAST operator%=( T &n ); + BIGINTSLOW operator%( T &n ); + + public: + /* fast */ + bool operator>( T &n ); + /* fast */ + bool operator>=( T &n ); + /* fast */ + bool operator<( T &n ); + /* fast */ + bool operator<=( T &n ); + /* fast */ + bool operator==( T &n ); + /* fast */ + bool operator!=( T &n ); + /* fast */ + bool operator!(); + + public: +#ifdef BIG_USES_STRINGS + /* fast */ + std::string str(); + BIGINTFAST operator=( std::string &s ); + BIGINTFAST operator=( const char *s ); +#endif + + }; + + + //////// assignment //////// + + // n = 0 + BIGONETYPE INLINE void zero( T &n ) + { + memset( n, 0, sizeof( T ) ); + } + + // a = b, zero-extend + BIGONETYPE INLINE void usetw( T &a, word b ) + { + a[ 0 ] = b; + memset( a + 1, 0, sizeof( T ) - sizeof( word ) ); + } + + // a = b, sign-extend + BIGONETYPE INLINE void ssetw( T &a, word b ) + { + a[ 0 ] = b; + memset( a + 1, ( b & WORDHIGHBIT ) ? WORDALLBITS : 0, sizeof( T ) - sizeof( word ) ); + } + + // a = b + BIGONETYPE INLINE void set ( T &a, T &b ) + { + memcpy( a, b, sizeof( T ) ); + } + + // a_low = b (zero-extend) + BIGTWOTYPES INLINE void usetlow( Bigger &a, T &b ) + { + memcpy( a, b, sizeof( T ) ); +#ifdef _MSC_VER +#pragma warning( disable : 4318 ) // warning C4318: passing constant zero as the length to memset +#endif + memset( a + BIGWORDCOUNT( T ), 0, sizeof( Bigger ) - sizeof( T ) ); + } + + // a_low = b (sign-extend) + BIGTWOTYPES INLINE void ssetlow( Bigger &a, T &b ) + { + memcpy( a, b, sizeof( T ) ); + memset( a + BIGWORDCOUNT( T ), BIGHIGHBIT( b ) ? WORDALLBITS : 0, sizeof( Bigger ) - sizeof( T ) ); + } + + // a_high = b + BIGTWOTYPES INLINE void sethigh( Bigger &a, T &b ) + { + memcpy( a + BIGWORDCOUNT( Bigger ) - BIGWORDCOUNT( T ), b, sizeof( T ) ); + memset( a, 0, sizeof( Bigger ) - sizeof( T ) ); + } + + // a = b_low + BIGTWOTYPES INLINE void takelow( T &a, Bigger &b ) + { + memcpy( a, b, sizeof( T ) ); + } + + // a = b_high + BIGTWOTYPES INLINE void takehigh( T &a, Bigger &b ) + { + memcpy( a, b + BIGWORDCOUNT( Bigger ) - BIGWORDCOUNT( T ), sizeof( T ) ); + } + + + //////// comparison //////// + + // a > b + BIGONETYPE bool ugreater( T &a, T &b ) + { + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + if ( a[ ii ] > b[ ii ] ) + return true; + + if ( a[ ii ] < b[ ii ] ) + return false; + } + + return false; + } + + // a >= b + BIGONETYPE bool ugreaterOrEqual( T &a, T &b ) + { + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + if ( a[ ii ] > b[ ii ] ) + return true; + + if ( a[ ii ] < b[ ii ] ) + return false; + } + + return true; + } + + // a > b + BIGONETYPE bool sgreater( T &a, T &b ) + { + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + if ( a[ ii ] > b[ ii ] ) + return BIGHIGHBIT( a ) == 0; + + if ( a[ ii ] < b[ ii ] ) + return BIGHIGHBIT( b ) != 0; + } + + return false; + } + + // a >= b + BIGONETYPE bool sgreaterOrEqual( T &a, T &b ) + { + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + if ( a[ ii ] > b[ ii ] ) + return BIGHIGHBIT( a ) == 0; + + if ( a[ ii ] < b[ ii ] ) + return BIGHIGHBIT( b ) != 0; + } + + return true; + } + + // a == b + BIGONETYPE INLINE bool equal( T &a, T &b ) + { + return memcmp( a, b, sizeof( T ) ) == 0; + } + + // a == 0 + BIGONETYPE INLINE bool isZero( T &n ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + if ( n[ ii ] ) + return false; + + return true; + } + + + //////// binary //////// + + // a &= b + BIGONETYPE void bAND( T &a, T &b ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + a[ ii ] &= b[ ii ]; + } + + // a |= b + BIGONETYPE void bOR( T &a, T &b ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + a[ ii ] |= b[ ii ]; + } + + // a ^= b + BIGONETYPE void bXOR( T &a, T &b ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + a[ ii ] ^= b[ ii ]; + } + + // n = ~n + BIGONETYPE void bNOT( T &n ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + n[ ii ] = ~n[ ii ]; + } + + + //////// shifting //////// + + // n <<= 1 + BIGONETYPE void shiftLeft1( T &n ) + { + register word w_i, carry = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + w_i = n[ ii ]; + + n[ ii ] = ( w_i << 1 ) | carry; + carry = w_i >> ( WORDBITS - 1 ); + } + } + + // n <<= s (s <= WORDBITS) + BIGONETYPE void shiftLeft( T &n, u32 s ) + { + register s32 ii; + register u32 bases = s / WORDBITS; + u32 bits = s % WORDBITS; + + // move whole bases first + + if ( bases ) + { + // shift bases + + for ( ii = BIGWORDCOUNT( T ) - 1 - bases; ii >= 0; --ii ) + n[ ii + bases ] = n[ ii ]; + + // clear the original locii of those bases + memset( n, 0, bases * sizeof( word ) ); + } + + if ( bits ) + { + register word w_i, carry = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + w_i = n[ ii ]; + + n[ ii ] = ( w_i << bits ) | carry; + carry = w_i >> ( WORDBITS - bits ); + } + } + } + + // n >>= 1 (unsigned) + BIGONETYPE void ushiftRight1( T &n ) + { + register word w_i, carry = 0; + + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + w_i = n[ ii ]; + + n[ ii ] = carry | ( w_i >> 1 ); + carry = w_i << ( WORDBITS - 1 ); + } + } + + // n >>= s (unsigned) (s <= WORDBITS) + BIGONETYPE void ushiftRight( T &n, u32 s ) + { + register s32 ii; + register u32 bases = s / WORDBITS; + register u32 bits = s % WORDBITS; + + // move whole bases first + + if ( bases ) + { + // shift bases + + for ( ii = bases; ii < BIGWORDCOUNT( T ); ++ii ) + n[ ii - bases ] = n[ ii ]; + + // clear the original locii of those bases + memset( n + BIGWORDCOUNT( T ) - bases, 0, bases * sizeof( word ) ); + } + + if ( bits ) + { + register word w_i, carry = 0; + + for ( ii = BIGWORDCOUNT( T ) - 1 - bases; ii >= 0; --ii ) + { + w_i = n[ ii ]; + + n[ ii ] = carry | ( w_i >> bits ); + carry = w_i << ( WORDBITS - bits ); + } + } + } + + // n >>= 1 (signed) + BIGONETYPE void sshiftRight1( T &n ) + { + register word w_i, carry = BIGHIGHBIT( n ) ? 1 : 0; + + for ( s32 ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + w_i = n[ ii ]; + + n[ ii ] = carry | ( w_i >> 1 ); + carry = w_i << ( WORDBITS - 1 ); + } + } + + // n >>= s (signed) (s <= WORDBITS) + BIGONETYPE void sshiftRight( T &n, u32 s ) + { + register s32 ii; + register u32 bases = s / WORDBITS; + register u32 bits = s % WORDBITS; + + word filler = BIGHIGHBIT( n ) ? WORDALLBITS : 0; + + // move whole bases first + + if ( bases ) + { + // shift bases + + for ( ii = bases; ii < BIGWORDCOUNT( T ); ++ii ) + n[ ii - bases ] = n[ ii ]; + + // clear the original locii of those bases + memset( n + BIGWORDCOUNT( T ) - bases, filler, bases * sizeof( word ) ); + } + + if ( bits ) + { + register word w_i, carry = filler << ( WORDBITS - bits ); + + for ( ii = BIGWORDCOUNT( T ) - 1 - bases; ii >= 0; --ii ) + { + w_i = n[ ii ]; + + n[ ii ] = carry | ( w_i >> bits ); + carry = w_i << ( WORDBITS - bits ); + } + } + } + + + //////// addition/subtraction //////// + +#if defined(NO_TEMPLATE_INLINE_ASSEMBLY) && defined(ASSEMBLY_INTEL_SYNTAX) + void BorlandAdd( void *a, void *b, u32 c ) + { + ASSEMBLY_BLOCK // BorlandC, x86, 32-bit words + { + mov esi, b + mov edi, a + mov ecx, c + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi ] + xor edx, edx // edx used later to index later words >= 4 + + add [ edi ] + , eax // and now we can use ADD instead of ADC on the first addition + mov eax, [ esi + 4 ] + adc [ edi + 4 ], eax + mov eax, [ esi + 8 ] + adc [ edi + 8 ], eax + mov eax, [ esi + 12 ] + adc [ edi + 12 ], eax + + jecxz done_already + + next_word: + inc edx + inc edx + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi + edx * 8 ] + adc [ edi + edx * 8 ], eax + mov eax, [ esi + edx * 8 + 4 ] + adc [ edi + edx * 8 + 4 ], eax + mov eax, [ esi + edx * 8 + 8 ] + adc [ edi + edx * 8 + 8 ], eax + mov eax, [ esi + edx * 8 + 12 ] + adc [ edi + edx * 8 + 12 ], eax + + loop next_word + + done_already: + } + } + +#endif + + // a += b + BIGONETYPE void add ( T &a, T &b ) + { +#if defined(NO_TEMPLATE_INLINE_ASSEMBLY) && defined(ASSEMBLY_INTEL_SYNTAX) + const u32 qc1 = BIGWORDCOUNT( T ) / 4 - 1; + + BorlandAdd( a, b, qc1 ); + +#elif defined(ASSEMBLY_INTEL_SYNTAX) + + const u32 qc1 = BIGWORDCOUNT( T ) / 4 - 1; + + ASSEMBLY_BLOCK // VS.NET, x86, 32-bit words + { + mov esi, b + mov edi, a + mov ecx, qc1 + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi ] + xor edx, edx // edx used later to index later words >= 4 + + add [ edi ], eax // and now we can use ADD instead of ADC on the first addition + mov eax, [ esi + 4 ] + adc [ edi + 4 ], eax + mov eax, [ esi + 8 ] + adc [ edi + 8 ], eax + mov eax, [ esi + 12 ] + adc [ edi + 12 ], eax + + jecxz done_already + + next_word: + inc edx + inc edx + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi + edx * 8 ] + adc [ edi + edx * 8 ], eax + mov eax, [ esi + edx * 8 + 4 ] + adc [ edi + edx * 8 + 4 ], eax + mov eax, [ esi + edx * 8 + 8 ] + adc [ edi + edx * 8 + 8 ], eax + mov eax, [ esi + edx * 8 + 12 ] + adc [ edi + edx * 8 + 12 ], eax + + loop next_word + + done_already: + } +#else + register word carry = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word a_i = a[ ii ]; + word b_i = b[ ii ]; + a[ ii ] += b_i + carry; + + carry = ( ( a_i & ( WORDALLBITS >> 1 ) ) + ( b_i & ( WORDALLBITS >> 1 ) ) + carry ) >> ( WORDBITS - 1 ); + carry += ( a_i >> ( WORDBITS - 1 ) ) + ( b_i >> ( WORDBITS - 1 ) ); + carry >>= 1; + } + +#endif + + } + + // ++n + BIGONETYPE void increment( T &n ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + if ( ++n[ ii ] ) + break; + } + +#if defined(NO_TEMPLATE_INLINE_ASSEMBLY) && defined(ASSEMBLY_INTEL_SYNTAX) + void BorlandSubtract( void *a, void *b, u32 c ) + { + ASSEMBLY_BLOCK // BorlandC, x86, 32-bit words + { + mov esi, b + mov edi, a + mov ecx, c + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi ] + xor edx, edx // edx used later to index later words >= 4 + + add [ edi ] + , eax // and now we can use ADD instead of ADC on the first addition + mov eax, [ esi + 4 ] + adc [ edi + 4 ], eax + mov eax, [ esi + 8 ] + adc [ edi + 8 ], eax + mov eax, [ esi + 12 ] + adc [ edi + 12 ], eax + + jecxz done_already + + next_word: + inc edx + inc edx + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi + edx * 8 ] + adc [ edi + edx * 8 ], eax + mov eax, [ esi + edx * 8 + 4 ] + adc [ edi + edx * 8 + 4 ], eax + mov eax, [ esi + edx * 8 + 8 ] + adc [ edi + edx * 8 + 8 ], eax + mov eax, [ esi + edx * 8 + 12 ] + adc [ edi + edx * 8 + 12 ], eax + + loop next_word + + done_already: + } + } + +#endif + + // a -= b + BIGONETYPE void subtract( T &a, T &b ) + { +#if defined(NO_TEMPLATE_INLINE_ASSEMBLY) && defined(ASSEMBLY_INTEL_SYNTAX) + const u32 qc1 = BIGWORDCOUNT( T ) / 4 - 1; + + BorlandSubtract( a, b, qc1 ); + +#elif defined(ASSEMBLY_INTEL_SYNTAX) + + const u32 qc1 = BIGWORDCOUNT( T ) / 4 - 1; + + ASSEMBLY_BLOCK // VS.NET, x86, 32-bit words + { + mov esi, b + mov edi, a + mov ecx, qc1 + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi ] + xor edx, edx // edx used later to index later words >= 4 + sub [ edi ], eax // first subtraction doesn't need to borrow + mov eax, [ esi + 4 ] + sbb [ edi + 4 ], eax + mov eax, [ esi + 8 ] + sbb [ edi + 8 ], eax + mov eax, [ esi + 12 ] + sbb [ edi + 12 ], eax + + jecxz done_already + + next_word: + inc edx + inc edx + + // unrolled loop since word count is a multiple of 4 >= 4 + mov eax, [ esi + edx * 8 ] + sbb [ edi + edx * 8 ], eax + mov eax, [ esi + edx * 8 + 4 ] + sbb [ edi + edx * 8 + 4 ], eax + mov eax, [ esi + edx * 8 + 8 ] + sbb [ edi + edx * 8 + 8 ], eax + mov eax, [ esi + edx * 8 + 12 ] + sbb [ edi + edx * 8 + 12 ], eax + + loop next_word + + done_already: + } +#else + register word borrow = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word a_i = a[ ii ]; + word b_i = b[ ii ]; + a[ ii ] -= b_i + borrow; + + borrow = ( ( a_i & ( WORDALLBITS >> 1 ) ) - ( b_i & ( WORDALLBITS >> 1 ) ) - borrow ) >> ( WORDBITS - 1 ); + borrow += ( b_i >> ( WORDBITS - 1 ) ) - ( a_i >> ( WORDBITS - 1 ) ); + ++borrow; + borrow >>= 1; + } + +#endif + + } + + // --n + BIGONETYPE void decrement( T &n ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + if ( n[ ii ] -- ) + break; + } + + + //////// negation //////// + + // n = -n + BIGONETYPE void negate( T &n ) + { + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + n[ ii ] = ~n[ ii ]; + + increment( n ); + } + + + //////// multiplication //////// + + // a *= a, unsigned + BIGONETYPE void usquare( T &a ) + { + T a0, a1; + + set ( a0, a ) + + ; + set ( a1, a ) + + ; + zero( a ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = a0[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a1, shifts ); + shifts = 0; + } + + add ( a, a1 ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + } + + // m = a * b (&a != &b != &m), unsigned + BIGTWOTYPES void umultiply( T &a0, T &b0, Bigger &m ) + { + Bigger a; + usetlow( a, a0 ); + + zero( m ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = b0[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a, shifts ); + shifts = 0; + } + + add ( m, a ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + } + + // a *= b (&a != &b), unsigned + BIGTWOTYPES void umultiply( Bigger &a0, T &b0 ) + { + Bigger a; + + set ( a, a0 ) + + ; + + zero( a0 ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = b0[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a, shifts ); + shifts = 0; + } + + add ( a0, a ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + } + + // a *= a, signed + BIGONETYPE void ssquare( T &a ) + { + T a0, a1; + + if ( BIGHIGHBIT( a ) ) + negate( a ); + + set ( a0, a ) + + ; + set ( a1, a ) + + ; + zero( a ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = a0[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a1, shifts ); + shifts = 0; + } + + add ( a, a1 ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + } + + // m = a * b (&a != &b != &m), signed + BIGTWOTYPES void smultiply( T &a0, T &b0, Bigger &m ) + { + Bigger a; + ssetlow( a, a0 ); + + word sign_a = BIGHIGHBIT( a ); + + if ( sign_a ) + negate( a ); + + T b; + + set ( b, b0 ) + + ; + + word sign_b = BIGHIGHBIT( b ); + + if ( sign_b ) + negate( b ); + + zero( m ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = b[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a, shifts ); + shifts = 0; + } + + add ( m, a ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + + if ( sign_a ^ sign_b ) + negate( m ); + } + + // a *= b (&a != &b), signed + BIGTWOTYPES void smultiply( Bigger &a0, T &b0 ) + { + Bigger a; + ssetlow( a, a0 ); + + word sign_a = BIGHIGHBIT( a ); + + if ( sign_a ) + negate( a ); + + T b; + + set ( b, b0 ) + + ; + + word sign_b = BIGHIGHBIT( b ); + + if ( sign_b ) + negate( b ); + + zero( a0 ); + + u32 shifts = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word w_i = b[ ii ]; + + u16 ctr = WORDBITS; + + while ( w_i ) + { + if ( w_i & 1 ) + { + if ( shifts ) + { + shiftLeft( a, shifts ); + shifts = 0; + } + + add ( a0, a ) + + ; + } + + w_i >>= 1; + ++shifts; + --ctr; + } + + shifts += ctr; + } + + if ( sign_a ^ sign_b ) + negate( a0 ); + } + + + //////// division/remainder //////// + + // {q, r} = a / b (&q != &r), unsigned + BIGONETYPE void udivide( T &a, T &b0, T &q, T &r ) + { + T b; + + set ( b, b0 ) + + ; + set ( r, a ) + + ; + zero( q ); + + u32 shifts = 1; + + // sort of: shift b left until b > r, then shift back one + if ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + s32 ii, jj; + + // shift by words if possible + + for ( ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + if ( r[ ii ] ) + break; + + for ( jj = ii; jj >= 0; --jj ) + if ( b[ jj ] ) + break; + + if ( ii != jj ) + { + shifts = ( ii - jj ) * WORDBITS; + shiftLeft( b, shifts ); + ++shifts; + } + + while ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + shiftLeft1( b ); + + ++shifts; + } + + while ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + } + + else + if ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + + u32 qshifts = 0; + + while ( shifts-- ) + { + ++qshifts; + + if ( !ugreater( b, r ) ) + { + subtract( r, b ); + + shiftLeft( q, qshifts ); + qshifts = 0; + + q[ 0 ] |= 1; + } + + ushiftRight1( b ); + } + + shiftLeft( q, qshifts ); + } + + // r = a Mod b, unsigned + BIGONETYPE void umodulo( T &a, T &b0, T &r ) + { + T b; + u32 shifts = 1; + + set ( b, b0 ) + + ; + set ( r, a ) + + ; + + if ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + s32 ii, jj; + + // shift by words if possible + + for ( ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + if ( r[ ii ] ) + break; + + for ( jj = ii; jj >= 0; --jj ) + if ( b[ jj ] ) + break; + + if ( ii != jj ) + { + shifts = ( ii - jj ) * WORDBITS; + shiftLeft( b, shifts ); + ++shifts; + } + + while ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + shiftLeft1( b ); + + ++shifts; + } + + while ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + } + + else + if ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + + while ( shifts-- ) + { + if ( !ugreater( b, r ) ) + subtract( r, b ); + + ushiftRight1( b ); + } + } + + // {q, r} = a / b (&q != &r), signed + BIGONETYPE void sdivide( T &a, T &b0, T &q, T &r ) + { + T b; + + set ( b, b0 ) + + ; + set ( r, a ) + + ; + zero( q ); + + word sign_a = BIGHIGHBIT( a ); + + if ( sign_a ) + negate( r ); + + word sign_b = BIGHIGHBIT( b ); + + if ( sign_b ) + negate( b ); + + u32 shifts = 1; + + if ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + s32 ii, jj; + + // shift by words if possible + + for ( ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + if ( r[ ii ] ) + break; + + for ( jj = ii; jj >= 0; --jj ) + if ( b[ jj ] ) + break; + + if ( ii != jj ) + { + shifts = ( ii - jj ) * WORDBITS; + shiftLeft( b, shifts ); + ++shifts; + } + + while ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + shiftLeft1( b ); + + ++shifts; + } + + while ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + } + + else + if ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + + u32 qshifts = 0; + + while ( shifts-- ) + { + ++qshifts; + + if ( !ugreater( b, r ) ) + { + subtract( r, b ); + + shiftLeft( q, qshifts ); + qshifts = 0; + + q[ 0 ] |= 1; + } + + ushiftRight1( b ); + } + + shiftLeft( q, qshifts ); + + if ( sign_a ^ sign_b ) + negate( q ); + + if ( sign_a ) + negate( r ); + } + + // r = a Mod b, signed + BIGONETYPE void smodulo( T &a, T &b0, T &r ) + { + T b; + u32 shifts = 1; + + set ( b, b0 ) + + ; + set ( r, a ) + + ; + + word sign_a = BIGHIGHBIT( a ); + + if ( sign_a ) + negate( r ); + + word sign_b = BIGHIGHBIT( b ); + + if ( sign_b ) + negate( b ); + + if ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + s32 ii, jj; + + // shift by words if possible + + for ( ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + if ( r[ ii ] ) + break; + + for ( jj = ii; jj >= 0; --jj ) + if ( b[ jj ] ) + break; + + if ( ii != jj ) + { + shifts = ( ii - jj ) * WORDBITS; + shiftLeft( b, shifts ); + ++shifts; + } + + while ( !BIGHIGHBIT( b ) && ugreater( r, b ) ) + { + shiftLeft1( b ); + + ++shifts; + } + + while ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + } + + else + if ( ugreater( b, r ) ) + { + ushiftRight1( b ); + + --shifts; + } + + while ( shifts-- ) + { + if ( !ugreater( b, r ) ) + subtract( r, b ); + + ushiftRight1( b ); + } + + if ( sign_a ) + negate( r ); + } + + + //////// converting to/from strings //////// + +#ifdef BIG_USES_STRINGS + + // n -> string + BIGONETYPE std::string toString( T &n0, bool sign = true, u16 radix = 10 ) + { + T n, base, r; + std::string s; + + set ( n, n0 ) + + ; + usetw( base, radix ); + + word sign_n = 0; + + if ( sign && ( sign_n = BIGHIGHBIT( n ) ) ) + negate( n ); + + do // always allow first iteration for zero + { + // {q, r} = n / base + udivide( n, base, n, r ); + + char ch = ( char ) r[ 0 ]; + + if ( ch >= 10 ) + ch += 'a' - 10; + else + ch += '0'; + + // insert character + s = ch + s; + } + + while ( !isZero( n ) ); + + if ( sign_n ) + s = '-' + s; + + return s; + } + + // s -> n, signed + BIGONETYPE void fromString( std::string s, T &n, bool sign = true, u16 radix = 10 ) + { + T acc, base, temp; + + usetw( acc, 1 ); + usetw( base, radix ); + zero( n ); + + u32 len = ( u32 ) s.length(); + const char *citer = s.c_str() + len; + + while ( len-- ) + { + char ch = *( --citer ); + + if ( IS_ALPHA( ch ) ) // assumes alpha characters only up to radix + ch = TO_LOWER( ch ) - 'a' + 10; + else + if ( sign && ch == '-' ) // '-' should be first character + { + negate( n ); + break; + } + else // assumes it's alphanumeric/- + ch -= '0'; + + usetw( temp, ch ); + + umultiply( temp, acc ); + + add ( n, temp ) + + ; + + umultiply( acc, base ); + } + } + +#endif // BIG_USES_STRINGS + + + //////// class wrapper //////// + + BIGONETYPE INLINE Int::Int() + { + big::zero( raw ); + } + + BIGONETYPE INLINE Int::Int( word n ) + { + ssetw( raw, n ); + } + +#ifdef BIG_USES_STRINGS + + BIGONETYPE INLINE Int::Int( std::string &s ) + { + fromString( s, raw ); + } + +#endif + + BIGONETYPE INLINE Int::Int( T &n ) + { + set ( raw, n ) + + ; + } + + BIGONETYPE INLINE Int::operator T &() + { + return raw; + } + + + BIGONETYPE BIGINTFAST Int::zero() + { + big::zero( raw ); + return *this; + } + + BIGONETYPE BIGINTFAST Int::operator=( word n ) + { + ssetw( raw, n ); + return *this; + } + + BIGONETYPE BIGINTFAST Int::operator=( T &n ) + { + set ( raw, n ) + + ; + return *this; + } + + + BIGONETYPE BIGINTFAST Int::operator<<=( u32 s ) + { + shiftLeft( raw, s ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator<<( u32 s ) + { + Int temp( raw ); + return temp <<= s; + } + + BIGONETYPE BIGINTFAST Int::operator>>=( u32 s ) + { + shiftRight( raw, s ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator>>( u32 s ) + { + Int temp( raw ); + return temp >>= s; + } + + + BIGONETYPE BIGINTFAST Int::operator+=( T &n ) + { + add ( raw, n ) + + ; + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator+( T &n ) + { + Int temp( raw ); + return temp += n; + } + + BIGONETYPE BIGINTFAST Int::operator-=( T &n ) + { + subtract( raw, n ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator-( T &n ) + { + Int temp( raw ); + return temp -= n; + } + + BIGONETYPE BIGINTFAST Int::operator++() // prefix + { + increment( raw ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator++( int ) // postfix + { + Int temp( raw ); + increment( raw ); + return temp; + } + + BIGONETYPE BIGINTFAST Int::operator--() // prefix + { + decrement( raw ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator--( int ) // postfix + { + Int temp( raw ); + decrement( raw ); + return temp; + } + + + BIGONETYPE BIGINTSLOW Int::operator-( int ) // negation + { + Int temp( raw ); + negate( temp ); + return temp; + } + + + BIGONETYPE BIGINTSLOW Int::operator*( T &n ) + { + Int temp( raw ); + return temp *= n; + } + + BIGONETYPE BIGINTFAST Int::square() + { + square( raw ); + return *this; + } + + + BIGONETYPE BIGINTFAST Int::operator/=( T &n ) + { + T discard; + divide( raw, n, raw, discard ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator/( T &n ) + { + Int temp( raw ); + return temp /= n; + } + + BIGONETYPE BIGINTFAST Int::operator%=( T &n ) + { + modulus( raw, n, raw ); + return *this; + } + + BIGONETYPE BIGINTSLOW Int::operator%( T &n ) + { + Int temp( raw ); + return temp %= n; + } + + + BIGONETYPE INLINE /* fast */ bool Int::operator>( T &n ) + { + return sgreater( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator>=( T &n ) + { + return sgreaterOrEqual( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator<( T &n ) + { + return !sgreaterOrEqual( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator<=( T &n ) + { + return !sgreater( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator==( T &n ) + { + return equal( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator!=( T &n ) + { + return !equal( raw, n ); + } + + BIGONETYPE INLINE /* fast */ bool Int::operator!() + { + return isZero( raw ); + } + +#ifdef BIG_USES_STRINGS + + BIGONETYPE INLINE /* fast */ std::string Int::str() + { + return toString( raw ); + } + + BIGONETYPE BIGINTFAST Int::operator=( std::string &s ) + { + fromString( s, raw ); + return *this; + } + + BIGONETYPE BIGINTFAST Int::operator=( const char *s ) + { + fromString( std::string( s ), raw ); + return *this; + } + +#endif // BIG_USES_STRINGS +} + +#endif // #if !defined(_COMPATIBILITY_1) + +#endif // BIGTYPES_H + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/BitStream.cpp b/raknet/BitStream.cpp index 1bf745c..9961de0 100644 --- a/raknet/BitStream.cpp +++ b/raknet/BitStream.cpp @@ -1,11 +1,59 @@ -// TODO: Implement BitStream.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization +#include "BitStream_NoTemplate.cpp" +#else #include "BitStream.h" #include +#include +#include #include +#include +#include +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +#include // htonl +#else +#include +#endif + +// Was included for memset which now comes from string.h instead +/* +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ ) + #include +#elif !defined(_COMPATIBILITY_2) + #include +#endif + + */ + +// MSWin uses _copysign, others use copysign... +#ifndef _WIN32 +#define _copysign copysign +#endif using namespace RakNet; +#ifdef _MSC_VER +#pragma warning( push ) +#endif + BitStream::BitStream() { numberOfBitsUsed = 0; @@ -14,8 +62,8 @@ BitStream::BitStream() readOffset = 0; //data = ( unsigned char* ) malloc( 32 ); data = ( unsigned char* ) stackData; - -#ifdef _DEBUG + +#ifdef _DEBUG // assert( data ); #endif //memset(data, 0, 32); @@ -49,42 +97,7 @@ BitStream::BitStream( unsigned char* _data, unsigned int lengthInBytes, bool _co readOffset = 0; copyData = _copyData; numberOfBitsAllocated = lengthInBytes << 3; - - if ( copyData ) - { - if ( lengthInBytes > 0 ) - { - if (lengthInBytes < BITSTREAM_STACK_ALLOCATION_SIZE) - { - data = ( unsigned char* ) stackData; - numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE << 3; - } - else - { - data = ( unsigned char* ) malloc( lengthInBytes ); - } -#ifdef _DEBUG - assert( data ); -#endif - memcpy( data, _data, lengthInBytes ); - } - else - data = 0; - } - else - data = ( unsigned char* ) _data; -} - -// SAMPSRV (adding this just as a tag for next RakNet upgrade) -BitStream::BitStream( char* _dataC, unsigned int lengthInBytes, bool _copyData ) -{ - unsigned char* _data = reinterpret_cast(_dataC); - - numberOfBitsUsed = lengthInBytes << 3; - readOffset = 0; - copyData = _copyData; - numberOfBitsAllocated = lengthInBytes << 3; - + if ( copyData ) { if ( lengthInBytes > 0 ) @@ -115,7 +128,7 @@ void BitStream::SetNumberOfBitsAllocated( const unsigned int lengthInBits ) { #ifdef _DEBUG assert( lengthInBits >= ( unsigned int ) numberOfBitsAllocated ); -#endif +#endif numberOfBitsAllocated = lengthInBits; } @@ -130,23 +143,640 @@ void BitStream::Reset( void ) // Note: Do NOT reallocate memory because BitStream is used // in places to serialize/deserialize a buffer. Reallocation // is a dangerous operation (may result in leaks). - + if ( numberOfBitsUsed > 0 ) { // memset(data, 0, BITS_TO_BYTES(numberOfBitsUsed)); } - + // Don't free memory here for speed efficiency //free(data); // Use realloc and free so we are more efficient than delete and new for resizing numberOfBitsUsed = 0; - + //numberOfBitsAllocated=8; readOffset = 0; - + //data=(unsigned char*)malloc(1); // if (numberOfBitsAllocated>0) // memset(data, 0, BITS_TO_BYTES(numberOfBitsAllocated)); } +// Write an array or casted stream +void BitStream::Write( const char* input, const int numberOfBytes ) +{ + if (numberOfBytes==0) + return; + + // Optimization: + if ((numberOfBitsUsed & 7) == 0) + { + AddBitsAndReallocate( BYTES_TO_BITS(numberOfBytes) ); + memcpy(data+BITS_TO_BYTES(numberOfBitsUsed), input, numberOfBytes); + numberOfBitsUsed+=BYTES_TO_BITS(numberOfBytes); + } + else + { + WriteBits( ( unsigned char* ) input, numberOfBytes * 8, true ); + } + +} +void BitStream::Write( BitStream *bitStream) +{ + Write(bitStream, bitStream->GetNumberOfBitsUsed()); +} +void BitStream::Write( BitStream *bitStream, int numberOfBits ) +{ + AddBitsAndReallocate( numberOfBits ); + int numberOfBitsMod8; + + while (numberOfBits-->0 && bitStream->readOffset + 1 <= bitStream->numberOfBitsUsed) + { + numberOfBitsMod8 = numberOfBitsUsed & 7; + if ( numberOfBitsMod8 == 0 ) + { + // New byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset++ % 8 ) ) ) + { + // Write 1 + data[ numberOfBitsUsed >> 3 ] = 0x80; + } + else + { + // Write 0 + data[ numberOfBitsUsed >> 3 ] = 0; + } + } + else + { + // Existing byte + if (bitStream->data[ bitStream->readOffset >> 3 ] & ( 0x80 >> ( bitStream->readOffset++ % 8 ) ) ) + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + // else 0, do nothing + } + + numberOfBitsUsed++; + } +} + +// Read an array or casted stream +bool BitStream::Read( char* output, const int numberOfBytes ) +{ + // Optimization: + if ((readOffset & 7) == 0) + { + if ( readOffset + ( numberOfBytes << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( output, data + ( readOffset >> 3 ), numberOfBytes ); + + readOffset += numberOfBytes << 3; + return true; + } + else + { + return ReadBits( ( unsigned char* ) output, numberOfBytes * 8 ); + } +} + +// Sets the read pointer back to the beginning of your data. +void BitStream::ResetReadPointer( void ) +{ + readOffset = 0; +} + +// Sets the write pointer back to the beginning of your data. +void BitStream::ResetWritePointer( void ) +{ + numberOfBitsUsed = 0; +} + +// Write a 0 +void BitStream::Write0( void ) +{ + AddBitsAndReallocate( 1 ); + + // New bytes need to be zeroed + if ( ( numberOfBitsUsed & 7 ) == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0; + + numberOfBitsUsed++; +} + +// Write a 1 +void BitStream::Write1( void ) +{ + AddBitsAndReallocate( 1 ); + + int numberOfBitsMod8 = numberOfBitsUsed & 7; + + if ( numberOfBitsMod8 == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0x80; + else + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + + numberOfBitsUsed++; +} + +#ifdef _MSC_VER +#pragma warning( disable : 4800 ) // warning C4100: : unreferenced formal parameter +#endif +// Returns true if the next data read is a 1, false if it is a 0 +bool BitStream::ReadBit( void ) +{ + return ( bool ) ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset++ & 7 ) ) ); +} + +// Align the bitstream to the byte boundary and then write the specified number of bits. +// This is faster than WriteBits but wastes the bits to do the alignment and requires you to call +// SetReadToByteAlignment at the corresponding read position +void BitStream::WriteAlignedBytes( const unsigned char* input, + const int numberOfBytesToWrite ) +{ +#ifdef _DEBUG + assert( numberOfBytesToWrite > 0 ); +#endif + + AlignWriteToByteBoundary(); + Write((const char*) input, numberOfBytesToWrite); +} + +// Read bits, starting at the next aligned bits. Note that the modulus 8 starting offset of the +// sequence must be the same as was used with WriteBits. This will be a problem with packet coalescence +// unless you byte align the coalesced packets. +bool BitStream::ReadAlignedBytes( unsigned char* output, const int numberOfBytesToRead ) +{ +#ifdef _DEBUG + assert( numberOfBytesToRead > 0 ); +#endif + + if ( numberOfBytesToRead <= 0 ) + return false; + + // Byte align + AlignReadToByteBoundary(); + + if ( readOffset + ( numberOfBytesToRead << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( output, data + ( readOffset >> 3 ), numberOfBytesToRead ); + + readOffset += numberOfBytesToRead << 3; + + return true; +} + +// Align the next write and/or read to a byte boundary. This can be used to 'waste' bits to byte align for efficiency reasons +void BitStream::AlignWriteToByteBoundary( void ) +{ + if ( numberOfBitsUsed ) + numberOfBitsUsed += 8 - ( (( numberOfBitsUsed - 1 ) & 7) + 1 ); +} + +// Align the next write and/or read to a byte boundary. This can be used to 'waste' bits to byte align for efficiency reasons +void BitStream::AlignReadToByteBoundary( void ) +{ + if ( readOffset ) + readOffset += 8 - ( (( readOffset - 1 ) & 7 ) + 1 ); +} + +// Write numberToWrite bits from the input source +void BitStream::WriteBits( const unsigned char *input, int numberOfBitsToWrite, const bool rightAlignedBits ) +{ + if (numberOfBitsToWrite<=0) + return; + + AddBitsAndReallocate( numberOfBitsToWrite ); + int offset = 0; + unsigned char dataByte; + int numberOfBitsUsedMod8; + + numberOfBitsUsedMod8 = numberOfBitsUsed & 7; + + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToWrite > 0 ) + //do + { + dataByte = *( input + offset ); + + if ( numberOfBitsToWrite < 8 && rightAlignedBits ) // rightAlignedBits means in the case of a partial byte, the bits are aligned from the right (bit 0) rather than the left (as in the normal internal representation) + dataByte <<= 8 - numberOfBitsToWrite; // shift left to get the bits on the left, as in our internal representation + + // Writing to a new byte each time + if ( numberOfBitsUsedMod8 == 0 ) + * ( data + ( numberOfBitsUsed >> 3 ) ) = dataByte; + else + { + // Copy over the new data. + *( data + ( numberOfBitsUsed >> 3 ) ) |= dataByte >> ( numberOfBitsUsedMod8 ); // First half + + if ( 8 - ( numberOfBitsUsedMod8 ) < 8 && 8 - ( numberOfBitsUsedMod8 ) < numberOfBitsToWrite ) // If we didn't write it all out in the first half (8 - (numberOfBitsUsed%8) is the number we wrote in the first half) + { + *( data + ( numberOfBitsUsed >> 3 ) + 1 ) = (unsigned char) ( dataByte << ( 8 - ( numberOfBitsUsedMod8 ) ) ); // Second half (overlaps byte boundary) + } + } + + if ( numberOfBitsToWrite >= 8 ) + numberOfBitsUsed += 8; + else + numberOfBitsUsed += numberOfBitsToWrite; + + numberOfBitsToWrite -= 8; + + offset++; + } + // } while(numberOfBitsToWrite>0); +} + +// Set the stream to some initial data. For internal use +void BitStream::SetData( unsigned char *input ) +{ + data=input; + copyData=false; +} + +// Assume the input source points to a native type, compress and write it +void BitStream::WriteCompressed( const unsigned char* input, + const int size, const bool unsignedData ) +{ + int currentByte = ( size >> 3 ) - 1; // PCs + + unsigned char byteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + } + + else + { + byteMatch = 0xFF; + } + + // Write upper bytes with a single 1 + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + if ( input[ currentByte ] == byteMatch ) // If high byte is byteMatch (0 of 0xff) then it would have the same value shifted + { + bool b = true; + Write( b ); + } + else + { + // Write the remainder of the data after writing 0 + bool b = false; + Write( b ); + + WriteBits( input, ( currentByte + 1 ) << 3, true ); + // currentByte--; + + + return ; + } + + currentByte--; + } + + // If the upper half of the last byte is a 0 (positive) or 16 (negative) then write a 1 and the remaining 4 bits. Otherwise write a 0 and the 8 bites. + if ( ( unsignedData && ( ( *( input + currentByte ) ) & 0xF0 ) == 0x00 ) || + ( unsignedData == false && ( ( *( input + currentByte ) ) & 0xF0 ) == 0xF0 ) ) + { + bool b = true; + Write( b ); + WriteBits( input + currentByte, 4, true ); + } + + else + { + bool b = false; + Write( b ); + WriteBits( input + currentByte, 8, true ); + } +} + +// Read numberOfBitsToRead bits to the output source +// alignBitsToRight should be set to true to convert internal bitstream data to userdata +// It should be false if you used WriteBits with rightAlignedBits false +bool BitStream::ReadBits( unsigned char* output, int numberOfBitsToRead, const bool alignBitsToRight ) +{ +#ifdef _DEBUG + assert( numberOfBitsToRead > 0 ); +#endif + if (numberOfBitsToRead<=0) + return false; + + if ( readOffset + numberOfBitsToRead > numberOfBitsUsed ) + return false; + + int readOffsetMod8; + + int offset = 0; + + memset( output, 0, BITS_TO_BYTES( numberOfBitsToRead ) ); + + readOffsetMod8 = readOffset & 7; + + // do + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToRead > 0 ) + { + *( output + offset ) |= *( data + ( readOffset >> 3 ) ) << ( readOffsetMod8 ); // First half + + if ( readOffsetMod8 > 0 && numberOfBitsToRead > 8 - ( readOffsetMod8 ) ) // If we have a second half, we didn't read enough bytes in the first half + *( output + offset ) |= *( data + ( readOffset >> 3 ) + 1 ) >> ( 8 - ( readOffsetMod8 ) ); // Second half (overlaps byte boundary) + + numberOfBitsToRead -= 8; + + if ( numberOfBitsToRead < 0 ) // Reading a partial byte for the last byte, shift right so the data is aligned on the right + { + + if ( alignBitsToRight ) + * ( output + offset ) >>= -numberOfBitsToRead; + + readOffset += 8 + numberOfBitsToRead; + } + else + readOffset += 8; + + offset++; + + } + + //} while(numberOfBitsToRead>0); + + return true; +} + +// Assume the input source points to a compressed native type. Decompress and read it +bool BitStream::ReadCompressed( unsigned char* output, + const int size, const bool unsignedData ) +{ + int currentByte = ( size >> 3 ) - 1; + + + unsigned char byteMatch, halfByteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + halfByteMatch = 0; + } + + else + { + byteMatch = 0xFF; + halfByteMatch = 0xF0; + } + + // Upper bytes are specified with a single 1 if they match byteMatch + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + // If we read a 1 then the data is byteMatch. + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + output[ currentByte ] = byteMatch; + currentByte--; + } + else + { + // Read the rest of the bytes + + if ( ReadBits( output, ( currentByte + 1 ) << 3 ) == false ) + return false; + + return true; + } + } + + // All but the first bytes are byteMatch. If the upper half of the last byte is a 0 (positive) or 16 (negative) then what we read will be a 1 and the remaining 4 bits. + // Otherwise we read a 0 and the 8 bytes + //assert(readOffset+1 <=numberOfBitsUsed); // If this assert is hit the stream wasn't long enough to read from + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + + if ( ReadBits( output + currentByte, 4 ) == false ) + return false; + + output[ currentByte ] |= halfByteMatch; // We have to set the high 4 bits since these are set to 0 by ReadBits + } + else + { + if ( ReadBits( output + currentByte, 8 ) == false ) + return false; + } + + return true; +} + +// Reallocates (if necessary) in preparation of writing numberOfBitsToWrite +void BitStream::AddBitsAndReallocate( const int numberOfBitsToWrite ) +{ + if (numberOfBitsToWrite <= 0) + return; + + int newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed; + + if ( numberOfBitsToWrite + numberOfBitsUsed > 0 && ( ( numberOfBitsAllocated - 1 ) >> 3 ) < ( ( newNumberOfBitsAllocated - 1 ) >> 3 ) ) // If we need to allocate 1 or more new bytes + { +#ifdef _DEBUG + // If this assert hits then we need to specify true for the third parameter in the constructor + // It needs to reallocate to hold all the data and can't do it unless we allocated to begin with + assert( copyData == true ); +#endif + + // Less memory efficient but saves on news and deletes + newNumberOfBitsAllocated = ( numberOfBitsToWrite + numberOfBitsUsed ) * 2; +// int newByteOffset = BITS_TO_BYTES( numberOfBitsAllocated ); + // Use realloc and free so we are more efficient than delete and new for resizing + int amountToAllocate = BITS_TO_BYTES( newNumberOfBitsAllocated ); + if (data==(unsigned char*)stackData) + { + if (amountToAllocate > BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) malloc( amountToAllocate ); + + // need to copy the stack data over to our new memory area too + memcpy ((void *)data, (void *)stackData, BITS_TO_BYTES( numberOfBitsAllocated )); + } + } + else + { + data = ( unsigned char* ) realloc( data, amountToAllocate ); + } + +#ifdef _DEBUG + assert( data ); // Make sure realloc succeeded +#endif + // memset(data+newByteOffset, 0, ((newNumberOfBitsAllocated-1)>>3) - ((numberOfBitsAllocated-1)>>3)); // Set the new data block to 0 + } + + if ( newNumberOfBitsAllocated > numberOfBitsAllocated ) + numberOfBitsAllocated = newNumberOfBitsAllocated; +} + +// Should hit if reads didn't match writes +void BitStream::AssertStreamEmpty( void ) +{ + assert( readOffset == numberOfBitsUsed ); +} + +void BitStream::PrintBits( void ) const +{ + if ( numberOfBitsUsed <= 0 ) + { + printf( "No bits\n" ); + return ; + } + + for ( int counter = 0; counter < BITS_TO_BYTES( numberOfBitsUsed ); counter++ ) + { + int stop; + + if ( counter == ( numberOfBitsUsed - 1 ) >> 3 ) + stop = 8 - ( ( ( numberOfBitsUsed - 1 ) & 7 ) + 1 ); + else + stop = 0; + + for ( int counter2 = 7; counter2 >= stop; counter2-- ) + { + if ( ( data[ counter ] >> counter2 ) & 1 ) + putchar( '1' ); + else + putchar( '0' ); + } + + putchar( ' ' ); + } + + putchar( '\n' ); +} +// Exposes the data for you to look at, like PrintBits does. +// Data will point to the stream. Returns the length in bits of the stream. +int BitStream::CopyData( unsigned char** _data ) const +{ +#ifdef _DEBUG + assert( numberOfBitsUsed > 0 ); +#endif + + *_data = new unsigned char [ BITS_TO_BYTES( numberOfBitsUsed ) ]; + memcpy( *_data, data, sizeof(unsigned char) * ( BITS_TO_BYTES( numberOfBitsUsed ) ) ); + return numberOfBitsUsed; +} + +// Ignore data we don't intend to read +void BitStream::IgnoreBits( const int numberOfBits ) +{ + readOffset += numberOfBits; +} + +// Move the write pointer to a position on the array. Dangerous if you don't know what you are doing! +void BitStream::SetWriteOffset( const int offset ) +{ + numberOfBitsUsed = offset; +} + +/* +int BitStream::GetWriteOffset( void ) const +{ + return numberOfBitsUsed; +} + +// Returns the length in bits of the stream +int BitStream::GetNumberOfBitsUsed( void ) const +{ + return GetWriteOffset(); +} + +// Returns the length in bytes of the stream +int BitStream::GetNumberOfBytesUsed( void ) const +{ + return BITS_TO_BYTES( numberOfBitsUsed ); +} + +// Returns the number of bits into the stream that we have read +int BitStream::GetReadOffset( void ) const +{ + return readOffset; +} + + +// Sets the read bit index +void BitStream::SetReadOffset( int newReadOffset ) +{ + readOffset=newReadOffset; +} + +// Returns the number of bits left in the stream that haven't been read +int BitStream::GetNumberOfUnreadBits( void ) const +{ + return numberOfBitsUsed - readOffset; +} +// Exposes the internal data +unsigned char* BitStream::GetData( void ) const +{ + return data; +} + +*/ +// If we used the constructor version with copy data off, this makes sure it is set to on and the data pointed to is copied. +void BitStream::AssertCopyData( void ) +{ + if ( copyData == false ) + { + copyData = true; + + if ( numberOfBitsAllocated > 0 ) + { + unsigned char * newdata = ( unsigned char* ) malloc( BITS_TO_BYTES( numberOfBitsAllocated ) ); +#ifdef _DEBUG + + assert( data ); +#endif + + memcpy( newdata, data, BITS_TO_BYTES( numberOfBitsAllocated ) ); + data = newdata; + } + + else + data = 0; + } +} +void BitStream::ReverseBytes(unsigned char *input, unsigned char *output, int length) +{ + for (int i=0; i < length; i++) + output[i]=input[length-i-1]; +} +bool BitStream::DoEndianSwap(void) const +{ +#ifndef __BITSTREAM_NATIVE_END + static bool swap=htonl(12345) == 12345; + return swap; +#else + return false; +#endif +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // #if _MSC_VER < 1299 diff --git a/raknet/BitStream.h b/raknet/BitStream.h index 5b8ca77..fe1ff09 100644 --- a/raknet/BitStream.h +++ b/raknet/BitStream.h @@ -1,11 +1,38 @@ -// TODO: Implement BitStream.h +/// \file +/// \brief This class allows you to write and read native types as a string of bits. BitStream is used extensively throughout RakNet and is designed to be used by users as well. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization +#include "BitStream_NoTemplate.h" +#else #ifndef __BITSTREAM_H #define __BITSTREAM_H +#include "RakNetDefines.h" #include "Export.h" #include "NetworkTypes.h" #include +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif /// Arbitrary size, just picking something likely to be larger than most packets #define BITSTREAM_STACK_ALLOCATION_SIZE 256 @@ -18,16 +45,17 @@ namespace RakNet /// \sa BitStreamSample.txt class RAK_DLL_EXPORT BitStream { + public: /// Default Constructor BitStream(); - + /// Create the bitstream, with some number of bytes to immediately allocate. /// There is no benefit to calling this, unless you know exactly how many bytes you need and it is greater than BITSTREAM_STACK_ALLOCATION_SIZE. /// In that case all it does is save you one or more realloc calls. /// \param[in] initialBytesToAllocate the number of bytes to pre-allocate. BitStream( int initialBytesToAllocate ); - + /// Initialize the BitStream, immediately setting the data it contains to a predefined pointer. /// Set \a _copyData to true if you want to make an internal copy of the data you are passing. Set it to false to just save a pointer to the data. /// You shouldn't call Write functions with \a _copyData as false, as this will write to unallocated memory @@ -39,39 +67,1370 @@ namespace RakNet /// \param[in] lengthInBytes Size of the \a _data. /// \param[in] _copyData true or false to make a copy of \a _data or not. BitStream( unsigned char* _data, unsigned int lengthInBytes, bool _copyData ); - -// SAMPSRV (adding this just as a tag for next RakNet upgrade) - BitStream( char* _dataC, unsigned int lengthInBytes, bool _copyData ); -// SAMPSRV end - + /// Destructor ~BitStream(); - + /// Resets the bitstream for reuse. void Reset( void ); + /// Bidirectional serialize/deserialize any integral type to/from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] var The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool Serialize(bool writeToBitstream, templateType &var); + + /// Bidirectional serialize/deserialize any integral type to/from a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeDelta(bool writeToBitstream, templateType ¤tValue, templateType lastValue); + + /// Bidirectional version of SerializeDelta when you don't know what the last value is, or there is no last value. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeDelta(bool writeToBitstream, templateType ¤tValue); + + /// Bidirectional serialize/deserialize any integral type to/from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] var The value to write + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeCompressed(bool writeToBitstream, templateType &var); + + /// Bidirectional serialize/deserialize any integral type to/from a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against. Only used if \a writeToBitstream is true. + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template + bool SerializeCompressedDelta(bool writeToBitstream, templateType ¤tValue, templateType lastValue); + + /// Save as SerializeCompressedDelta(templateType ¤tValue, templateType lastValue) when we have an unknown second parameter + template + bool SerializeCompressedDelta(bool writeToBitstream, templateType ¤tValue); + + /// Bidirectional serialize/deserialize an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] input a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool Serialize(bool writeToBitstream, char* input, const int numberOfBytes ); + + /// Bidirectional serialize/deserialize a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeNormVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ); + + /// Bidirectional serialize/deserialize a vector, using 10 bytes instead of 12. + /// Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ); + + /// Bidirectional serialize/deserialize a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + template // templateType for this function must be a float or double + bool SerializeNormQuat(bool writeToBitstream, templateType &w, templateType &x, templateType &y, templateType &z); + + /// Bidirectional serialize/deserialize an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each + /// for 6 bytes instead of 36 + /// Lossy, although the result is renormalized + template // templateType for this function must be a float or double + bool SerializeOrthMatrix( + bool writeToBitstream, + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ); + + /// Bidirectional serialize/deserialize numberToSerialize bits to/from the input. Right aligned + /// data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another + /// \param[in] writeToBitstream true to write from your data to this bitstream. False to read from this bitstream and write to your data + /// \param[in] input The data + /// \param[in] numberOfBitsToSerialize The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + /// \return true if \a writeToBitstream is true. true if \a writeToBitstream is false and the read was successful. false if \a writeToBitstream is false and the read was not successful. + bool SerializeBits(bool writeToBitstream, unsigned char* input, int numberOfBitsToSerialize, const bool rightAlignedBits = true ); + + /// Write any integral type to a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] var The value to write + template + void Write(templateType var); + + /// Write any integral type to a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + void WriteDelta(templateType currentValue, templateType lastValue); + + /// WriteDelta when you don't know what the last value is, or there is no last value. + /// \param[in] currentValue The current value to write + template + void WriteDelta(templateType currentValue); + + /// Write any integral type to a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// \param[in] var The value to write + template + void WriteCompressed(templateType var); + + /// Write any integral type to a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + void WriteCompressedDelta(templateType currentValue, templateType lastValue); + + /// Save as WriteCompressedDelta(templateType currentValue, templateType lastValue) when we have an unknown second parameter + template + void WriteCompressedDelta(templateType currentValue); + + /// Read any integral type from a bitstream. Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] var The value to read + template + bool Read(templateType &var); + + /// Read any integral type from a bitstream. If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// ReadDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + template + bool ReadDelta(templateType &var); + + /// Read any integral type from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] var The value to read + template + bool ReadCompressed(templateType &var); + + /// Read any integral type from a bitstream. If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// the current value will be updated. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// ReadCompressedDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + template + bool ReadCompressedDelta(templateType &var); + + /// Write an array or casted stream or raw data. This does NOT do endian swapping. + /// \param[in] input a byte buffer + /// \param[in] numberOfBytes the size of \a input in bytes + void Write( const char* input, const int numberOfBytes ); + + /// Write one bitstream to another + /// \param[in] numberOfBits bits to write + /// \param bitStream the bitstream to copy from + void Write( BitStream *bitStream, int numberOfBits ); + void Write( BitStream *bitStream ); + + /// Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteNormVector( templateType x, templateType y, templateType z ); + + /// Write a vector, using 10 bytes instead of 12. + /// Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteVector( templateType x, templateType y, templateType z ); + + /// Write a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + void WriteNormQuat( templateType w, templateType x, templateType y, templateType z); + + /// Write an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each + /// for 6 bytes instead of 36 + /// Lossy, although the result is renormalized + template // templateType for this function must be a float or double + void WriteOrthMatrix( + templateType m00, templateType m01, templateType m02, + templateType m10, templateType m11, templateType m12, + templateType m20, templateType m21, templateType m22 ); + + /// Read an array or casted stream of byte. The array + /// is raw data. There is no automatic endian conversion with this function + /// \param[in] output The result byte array. It should be larger than @em numberOfBytes. + /// \param[in] numberOfBytes The number of byte to read + /// \return true on success false if there is some missing bytes. + bool Read( char* output, const int numberOfBytes ); + + /// Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12-24 bytes. Will further compress y or z axis aligned vectors. + /// Accurate to 1/32767.5. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + bool ReadNormVector( templateType &x, templateType &y, templateType &z ); + + /// Read 3 floats or doubles, using 10 bytes, where those float or doubles comprise a vector + /// Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + bool ReadVector( templateType &x, templateType &y, templateType &z ); + + /// Read a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. + /// \param[in] w w + /// \param[in] x x + /// \param[in] y y + /// \param[in] z z + template // templateType for this function must be a float or double + bool ReadNormQuat( templateType &w, templateType &x, templateType &y, templateType &z); + + /// Read an orthogonal matrix from a quaternion, reading 3 components of the quaternion in 2 bytes each and extrapolatig the 4th. + /// for 6 bytes instead of 36 + /// Lossy, although the result is renormalized + template // templateType for this function must be a float or double + bool ReadOrthMatrix( + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ); + + ///Sets the read pointer back to the beginning of your data. + void ResetReadPointer( void ); + + /// Sets the write pointer back to the beginning of your data. + void ResetWritePointer( void ); + + ///This is good to call when you are done with the stream to make + /// sure you didn't leave any data left over void + void AssertStreamEmpty( void ); + + /// printf the bits in the stream. Great for debugging. + void PrintBits( void ) const; + + /// Ignore data we don't intend to read + /// \param[in] numberOfBits The number of bits to ignore + void IgnoreBits( const int numberOfBits ); + + ///Move the write pointer to a position on the array. + /// \param[in] offset the offset from the start of the array. + /// \attention + /// Dangerous if you don't know what you are doing! + /// For efficiency reasons you can only write mid-stream if your data is byte aligned. + void SetWriteOffset( const int offset ); + + /// Returns the length in bits of the stream + inline int GetNumberOfBitsUsed( void ) const {return GetWriteOffset();} + inline int GetWriteOffset( void ) const {return numberOfBitsUsed;} + + ///Returns the length in bytes of the stream + inline int GetNumberOfBytesUsed( void ) const {return BITS_TO_BYTES( numberOfBitsUsed );} + + ///Returns the number of bits into the stream that we have read + inline int GetReadOffset( void ) const {return readOffset;} + + // Sets the read bit index + inline void SetReadOffset( int newReadOffset ) {readOffset=newReadOffset;} + + ///Returns the number of bits left in the stream that haven't been read + inline int GetNumberOfUnreadBits( void ) const {return numberOfBitsUsed - readOffset;} + + /// Makes a copy of the internal data for you \a _data will point to + /// the stream. Returns the length in bits of the stream. Partial + /// bytes are left aligned + /// \param[out] _data The allocated copy of GetData() + int CopyData( unsigned char** _data ) const; + + /// Set the stream to some initial data. + /// \internal + void SetData( unsigned char *input ); + + /// Gets the data that BitStream is writing to / reading from + /// Partial bytes are left aligned. + /// \return A pointer to the internal state + inline unsigned char* GetData( void ) const {return data;} + + /// Write numberToWrite bits from the input source Right aligned + /// data means in the case of a partial byte, the bits are aligned + /// from the right (bit 0) rather than the left (as in the normal + /// internal representation) You would set this to true when + /// writing user data, and false when copying bitstream data, such + /// as writing one bitstream to another + /// \param[in] input The data + /// \param[in] numberOfBitsToWrite The number of bits to write + /// \param[in] rightAlignedBits if true data will be right aligned + void WriteBits( const unsigned char* input, int numberOfBitsToWrite, const bool rightAlignedBits = true ); + + /// Align the bitstream to the byte boundary and then write the + /// specified number of bits. This is faster than WriteBits but + /// wastes the bits to do the alignment and requires you to call + /// ReadAlignedBits at the corresponding read position. + /// \param[in] input The data + /// \param[in] numberOfBytesToWrite The size of data. + void WriteAlignedBytes( const unsigned char *input, const int numberOfBytesToWrite ); + + /// Read bits, starting at the next aligned bits. Note that the + /// modulus 8 starting offset of the sequence must be the same as + /// was used with WriteBits. This will be a problem with packet + /// coalescence unless you byte align the coalesced packets. + /// \param[in] output The byte array larger than @em numberOfBytesToRead + /// \param[in] numberOfBytesToRead The number of byte to read from the internal state + /// \return true if there is enough byte. + bool ReadAlignedBytes( unsigned char *output, const int numberOfBytesToRead ); + + /// Align the next write and/or read to a byte boundary. This can + /// be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + void AlignWriteToByteBoundary( void ); + + /// Align the next write and/or read to a byte boundary. This can + /// be used to 'waste' bits to byte align for efficiency reasons It + /// can also be used to force coalesced bitstreams to start on byte + /// boundaries so so WriteAlignedBits and ReadAlignedBits both + /// calculate the same offset when aligning. + void AlignReadToByteBoundary( void ); + + /// Read \a numberOfBitsToRead bits to the output source + /// alignBitsToRight should be set to true to convert internal + /// bitstream data to userdata. It should be false if you used + /// WriteBits with rightAlignedBits false + /// \param[in] output The resulting bits array + /// \param[in] numberOfBitsToRead The number of bits to read + /// \param[in] alignBitsToRight if true bits will be right aligned. + /// \return true if there is enough bits to read + bool ReadBits( unsigned char *output, int numberOfBitsToRead, const bool alignBitsToRight = true ); + + /// Write a 0 + void Write0( void ); + + /// Write a 1 + void Write1( void ); + + /// Reads 1 bit and returns true if that bit is 1 and false if it is 0 + bool ReadBit( void ); + + /// If we used the constructor version with copy data off, this + /// *makes sure it is set to on and the data pointed to is copied. + void AssertCopyData( void ); + /// Use this if you pass a pointer copy to the constructor /// *(_copyData==false) and want to overallocate to prevent /// *reallocation void SetNumberOfBitsAllocated( const unsigned int lengthInBits ); + /// Reallocates (if necessary) in preparation of writing numberOfBitsToWrite + void AddBitsAndReallocate( const int numberOfBitsToWrite ); + + /// ---- Member function template specialization declarations ---- + // Used for VC7 +#if defined(_MSC_VER) && _MSC_VER == 1300 + /// Write a bool to a bitstream + /// \param[in] var The value to write + template <> + void Write(bool var); + + /// Write a playerID to a bitstream + /// \param[in] var The value to write + template <> + void Write(PlayerID var); + + /// Write a playerID. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(PlayerID currentValue, PlayerID lastValue); + + /// Write an networkID to a bitstream + /// \param[in] var The value to write + template <> + void Write(NetworkID var); + + /// Write an networkID. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(NetworkID currentValue, NetworkID lastValue); + + /// Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteDelta(bool currentValue, bool lastValue); + + template <> + void WriteCompressed(PlayerID var); + + template <> + void WriteCompressed(NetworkID var); + + template <> + void WriteCompressed(bool var); + + /// For values between -1 and 1 + template <> + void WriteCompressed(float var); + + /// For values between -1 and 1 + template <> + void WriteCompressed(double var); + + /// Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + void WriteCompressedDelta(bool currentValue, bool lastValue); + + /// Save as WriteCompressedDelta(bool currentValue, templateType lastValue) when we have an unknown second bool + template <> + void WriteCompressedDelta(bool currentValue); + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + bool Read(bool &var); + + /// Read a playerID from a bitstream + /// \param[in] var The value to read + template <> + bool Read(PlayerID &var); + + /// Read an NetworkID from a bitstream + /// \param[in] var The value to read + template <> + bool Read(NetworkID &var); + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + bool ReadDelta(bool &var); + + template <> + bool ReadCompressed(PlayerID &var); + + template <> + bool ReadCompressed(NetworkID &var); + + template <> + bool ReadCompressed(bool &var); + + template <> + bool ReadCompressed(float &var); + + /// For values between -1 and 1 + template <> + bool ReadCompressed(double &var); + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + bool ReadCompressedDelta(bool &var); +#endif private: + /// Assume the input source points to a native type, compress and write it. + void WriteCompressed( const unsigned char* input, const int size, const bool unsignedData ); + + /// Assume the input source points to a compressed native type. Decompress and read it. + bool ReadCompressed( unsigned char* output, const int size, const bool unsignedData ); + + void ReverseBytes(unsigned char *input, unsigned char *output, int length); + bool DoEndianSwap(void) const; + int numberOfBitsUsed; - + int numberOfBitsAllocated; - + int readOffset; - + unsigned char *data; - + /// true if the internal buffer is copy of the data passed to the constructor bool copyData; /// BitStreams that use less than BITSTREAM_STACK_ALLOCATION_SIZE use the stack, rather than the heap to store data. It switches over if BITSTREAM_STACK_ALLOCATION_SIZE is exceeded unsigned char stackData[BITSTREAM_STACK_ALLOCATION_SIZE]; }; + + template + inline bool BitStream::Serialize(bool writeToBitstream, templateType &var) + { + if (writeToBitstream) + Write(var); + else + return Read(var); + return true; + } + + template + inline bool BitStream::SerializeDelta(bool writeToBitstream, templateType ¤tValue, templateType lastValue) + { + if (writeToBitstream) + WriteDelta(currentValue, lastValue); + else + return ReadDelta(currentValue); + return true; + } + + template + inline bool BitStream::SerializeDelta(bool writeToBitstream, templateType ¤tValue) + { + if (writeToBitstream) + WriteDelta(currentValue); + else + return ReadDelta(currentValue); + return true; + } + + template + inline bool BitStream::SerializeCompressed(bool writeToBitstream, templateType &var) + { + if (writeToBitstream) + WriteCompressed(var); + else + return ReadCompressed(var); + return true; + } + + template + inline bool BitStream::SerializeCompressedDelta(bool writeToBitstream, templateType ¤tValue, templateType lastValue) + { + if (writeToBitstream) + WriteCompressedDelta(currentValue,lastValue); + else + return ReadCompressedDelta(currentValue); + return true; + } + + template + inline bool BitStream::SerializeCompressedDelta(bool writeToBitstream, templateType ¤tValue) + { + if (writeToBitstream) + WriteCompressedDelta(currentValue); + else + return ReadCompressedDelta(currentValue); + return true; + } + + inline bool BitStream::Serialize(bool writeToBitstream, char* input, const int numberOfBytes ) + { + if (writeToBitstream) + Write(input, numberOfBytes); + else + return Read(input, numberOfBytes); + return true; + } + + template + inline bool BitStream::SerializeNormVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ) + { + if (writeToBitstream) + WriteNormVector(x,y,z); + else + return ReadNormVector(x,y,z); + return true; + } + + template + inline bool BitStream::SerializeVector(bool writeToBitstream, templateType &x, templateType &y, templateType &z ) + { + if (writeToBitstream) + WriteVector(x,y,z); + else + return ReadVector(x,y,z); + return true; + } + + template + inline bool BitStream::SerializeNormQuat(bool writeToBitstream, templateType &w, templateType &x, templateType &y, templateType &z) + { + if (writeToBitstream) + WriteNormQuat(w,x,y,z); + else + return ReadNormQuat(w,x,y,z); + return true; + } + + template + inline bool BitStream::SerializeOrthMatrix( + bool writeToBitstream, + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ) + { + if (writeToBitstream) + WriteOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); + else + return ReadOrthMatrix(m00,m01,m02,m10,m11,m12,m20,m21,m22); + return true; + } + + inline bool BitStream::SerializeBits(bool writeToBitstream, unsigned char* input, int numberOfBitsToSerialize, const bool rightAlignedBits ) + { + if (writeToBitstream) + WriteBits(input,numberOfBitsToSerialize,rightAlignedBits); + else + return ReadBits(input,numberOfBitsToSerialize,rightAlignedBits); + return true; + } + + template + inline void BitStream::Write(templateType var) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(var)==1) + WriteBits( ( unsigned char* ) & var, sizeof( templateType ) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + ReverseBytes((unsigned char*)&var, output, sizeof(templateType)); + WriteBits( ( unsigned char* ) output, sizeof(templateType) * 8, true ); + } + else +#endif + WriteBits( ( unsigned char* ) & var, sizeof(templateType) * 8, true ); + } + } + + /// Write a bool to a bitstream + /// \param[in] var The value to write + template <> + inline void BitStream::Write(bool var) + { + if ( var ) + Write1(); + else + Write0(); + } + + /// Write a playerID to a bitstream + /// \param[in] var The value to write + template <> + inline void BitStream::Write(PlayerID var) + { + Write(var.binaryAddress); + Write(var.port); + } + + /// Write an networkID to a bitstream + /// \param[in] var The value to write + template <> + inline void BitStream::Write(NetworkID var) + { + if (NetworkID::IsPeerToPeerMode()) // Use the function rather than directly access the member or DLL users will get an undefined external error + Write(var.playerId); + Write(var.localSystemId); + } + + /// Write any integral type to a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + inline void BitStream::WriteDelta(templateType currentValue, templateType lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue); + } + } + + /// Write a playerID. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(PlayerID currentValue, PlayerID lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + Write(currentValue.binaryAddress); + Write(currentValue.port); + } + } + + /// Write a playerID. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(NetworkID currentValue, NetworkID lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + if (NetworkID::IsPeerToPeerMode()) // Use the function rather than directly access the member or DLL users will get an undefined external error + Write(currentValue.playerId); + Write(currentValue.localSystemId); + } + } + + /// Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteDelta(bool currentValue, bool lastValue) + { +#ifdef _MSC_VER +#pragma warning(disable:4100) // warning C4100: 'lastValue' : unreferenced formal parameter +#endif + Write(currentValue); + } + + /// WriteDelta when you don't know what the last value is, or there is no last value. + /// \param[in] currentValue The current value to write + template + inline void BitStream::WriteDelta(templateType currentValue) + { + Write(true); + Write(currentValue); + } + + /// Write any integral type to a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] var The value to write + template + inline void BitStream::WriteCompressed(templateType var) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(var)==1) + WriteCompressed( ( unsigned char* ) & var, sizeof( templateType ) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END +#ifdef _MSC_VER +#pragma warning(disable:4244) // '=' : conversion from 'unsigned long' to 'unsigned short', possible loss of data +#endif + + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + ReverseBytes((unsigned char*)&var, output, sizeof(templateType)); + WriteCompressed( ( unsigned char* ) output, sizeof(templateType) * 8, true ); + } + else +#endif + WriteCompressed( ( unsigned char* ) & var, sizeof(templateType) * 8, true ); + } + } + + template <> + inline void BitStream::WriteCompressed(PlayerID var) + { + Write(var); + } + + template <> + inline void BitStream::WriteCompressed(NetworkID var) + { + Write(var); + } + + template <> + inline void BitStream::WriteCompressed(bool var) + { + Write(var); + } + + /// For values between -1 and 1 + template <> + inline void BitStream::WriteCompressed(float var) + { + assert(var > -1.01f && var < 1.01f); + if (var < -1.0f) + var=-1.0f; + if (var > 1.0f) + var=1.0f; + Write((unsigned short)((var+1.0f)*32767.5f)); + } + + /// For values between -1 and 1 + template <> + inline void BitStream::WriteCompressed(double var) + { + assert(var > -1.01 && var < 1.01); + if (var < -1.0f) + var=-1.0f; + if (var > 1.0f) + var=1.0f; +#ifdef _DEBUG + assert(sizeof(unsigned long)==4); +#endif + Write((unsigned long)((var+1.0)*2147483648.0)); + } + + /// Write any integral type to a bitstream. If the current value is different from the last value + /// the current value will be written. Otherwise, a single bit will be written + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template + inline void BitStream::WriteCompressedDelta(templateType currentValue, templateType lastValue) + { + if (currentValue==lastValue) + { + Write(false); + } + else + { + Write(true); + WriteCompressed(currentValue); + } + } + + /// Write a bool delta. Same thing as just calling Write + /// \param[in] currentValue The current value to write + /// \param[in] lastValue The last value to compare against + template <> + inline void BitStream::WriteCompressedDelta(bool currentValue, bool lastValue) + { +#ifdef _MSC_VER +#pragma warning(disable:4100) // warning C4100: 'lastValue' : unreferenced formal parameter +#endif + Write(currentValue); + } + + /// Save as WriteCompressedDelta(templateType currentValue, templateType lastValue) when we have an unknown second parameter + template + inline void BitStream::WriteCompressedDelta(templateType currentValue) + { + Write(true); + WriteCompressed(currentValue); + } + + /// Save as WriteCompressedDelta(bool currentValue, templateType lastValue) when we have an unknown second bool + template <> + inline void BitStream::WriteCompressedDelta(bool currentValue) + { + Write(currentValue); + } + + /// Read any integral type from a bitstream. Define __BITSTREAM_NATIVE_END if you need endian swapping. + /// \param[in] var The value to read + template + inline bool BitStream::Read(templateType &var) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(var)==1) + return ReadBits( ( unsigned char* ) &var, sizeof(templateType) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END +#ifdef _MSC_VER +#pragma warning(disable:4244) // '=' : conversion from 'unsigned long' to 'unsigned short', possible loss of data +#endif + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + if (ReadBits( ( unsigned char* ) output, sizeof(templateType) * 8, true )) + { + ReverseBytes(output, (unsigned char*)&var, sizeof(templateType)); + return true; + } + return false; + } + else +#endif + return ReadBits( ( unsigned char* ) & var, sizeof(templateType) * 8, true ); + } + } + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + inline bool BitStream::Read(bool &var) + { + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + if ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset++ % 8 ) ) ) // Is it faster to just write it out here? + var = true; + else + var = false; + + return true; + } + + /// Read a playerID from a bitstream + /// \param[in] var The value to read + template <> + inline bool BitStream::Read(PlayerID &var) + { + Read(var.binaryAddress); + return Read(var.port); + } + + /// Read an networkID from a bitstream + /// \param[in] var The value to read + template <> + inline bool BitStream::Read(NetworkID &var) + { + if (NetworkID::IsPeerToPeerMode()) // Use the function rather than directly access the member or DLL users will get an undefined external error + Read(var.playerId); + return Read(var.localSystemId); + } + + /// Read any integral type from a bitstream. If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// ReadDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + template + inline bool BitStream::ReadDelta(templateType &var) + { + bool dataWritten; + bool success; + success=Read(dataWritten); + if (dataWritten) + success=Read(var); + return success; + } + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + inline bool BitStream::ReadDelta(bool &var) + { + return Read(var); + } + + /// Read any integral type from a bitstream. Undefine __BITSTREAM_NATIVE_END if you need endian swapping. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// \param[in] var The value to read + template + inline bool BitStream::ReadCompressed(templateType &var) + { +#ifdef _MSC_VER +#pragma warning(disable:4127) // conditional expression is constant +#endif + if (sizeof(var)==1) + return ReadCompressed( ( unsigned char* ) &var, sizeof(templateType) * 8, true ); + else + { +#ifndef __BITSTREAM_NATIVE_END + if (DoEndianSwap()) + { + unsigned char output[sizeof(templateType)]; + if (ReadCompressed( ( unsigned char* ) output, sizeof(templateType) * 8, true )) + { + ReverseBytes(output, (unsigned char*)&var, sizeof(templateType)); + return true; + } + return false; + } + else +#endif + return ReadCompressed( ( unsigned char* ) & var, sizeof(templateType) * 8, true ); + } + } + + template <> + inline bool BitStream::ReadCompressed(PlayerID &var) + { + return Read(var); + } + + template <> + inline bool BitStream::ReadCompressed(NetworkID &var) + { + return Read(var); + } + + template <> + inline bool BitStream::ReadCompressed(bool &var) + { + return Read(var); + } + + /// For values between -1 and 1 + template <> + inline bool BitStream::ReadCompressed(float &var) + { + unsigned short compressedFloat; + if (Read(compressedFloat)) + { + var = ((float)compressedFloat / 32767.5f - 1.0f); + return true; + } + return false; + } + + /// For values between -1 and 1 + template <> + inline bool BitStream::ReadCompressed(double &var) + { + unsigned long compressedFloat; + if (Read(compressedFloat)) + { + var = ((double)compressedFloat / 2147483648.0 - 1.0); + return true; + } + return false; + } + + + /// Read any integral type from a bitstream. If the written value differed from the value compared against in the write function, + /// var will be updated. Otherwise it will retain the current value. + /// the current value will be updated. + /// For floating point, this is lossy, using 2 bytes for a float and 4 for a double. The range must be between -1 and +1. + /// For non-floating point, this is lossless, but only has benefit if you use less than half the range of the type + /// If you are not using __BITSTREAM_NATIVE_END the opposite is true for types larger than 1 byte + /// ReadCompressedDelta is only valid from a previous call to WriteDelta + /// \param[in] var The value to read + template + inline bool BitStream::ReadCompressedDelta(templateType &var) + { + bool dataWritten; + bool success; + success=Read(dataWritten); + if (dataWritten) + success=ReadCompressed(var); + return success; + } + + /// Read a bool from a bitstream + /// \param[in] var The value to read + template <> + inline bool BitStream::ReadCompressedDelta(bool &var) + { + return Read(var); + } + + + template // templateType for this function must be a float or double + void BitStream::WriteNormVector( templateType x, templateType y, templateType z ) + { +#ifdef _DEBUG + assert(x <= 1.01 && y <= 1.01 && z <= 1.01 && x >= -1.01 && y >= -1.01 && z >= -1.01); +#endif + if (x>1.0) + x=1.0; + if (y>1.0) + y=1.0; + if (z>1.0) + z=1.0; + if (x<-1.0) + x=-1.0; + if (y<-1.0) + y=-1.0; + if (z<-1.0) + z=-1.0; + + Write((bool) (x < 0.0)); + if (y==0.0) + Write(true); + else + { + Write(false); + WriteCompressed((float)y); + //Write((unsigned short)((y+1.0f)*32767.5f)); + } + if (z==0.0) + Write(true); + else + { + Write(false); + WriteCompressed((float)z); + //Write((unsigned short)((z+1.0f)*32767.5f)); + } + } + + template // templateType for this function must be a float or double + void BitStream::WriteVector( templateType x, templateType y, templateType z ) + { + templateType magnitude = sqrt(x * x + y * y + z * z); + Write((float)magnitude); + if (magnitude > 0.0) + { + WriteCompressed((float)(x/magnitude)); + WriteCompressed((float)(y/magnitude)); + WriteCompressed((float)(z/magnitude)); + // Write((unsigned short)((x/magnitude+1.0f)*32767.5f)); + // Write((unsigned short)((y/magnitude+1.0f)*32767.5f)); + // Write((unsigned short)((z/magnitude+1.0f)*32767.5f)); + } + } + + template // templateType for this function must be a float or double + void BitStream::WriteNormQuat( templateType w, templateType x, templateType y, templateType z) + { + Write((bool)(w<0.0)); + Write((bool)(x<0.0)); + Write((bool)(y<0.0)); + Write((bool)(z<0.0)); + Write((unsigned short)(fabs(x)*65535.0)); + Write((unsigned short)(fabs(y)*65535.0)); + Write((unsigned short)(fabs(z)*65535.0)); + // Leave out w and calculate it on the target + } + + template // templateType for this function must be a float or double + void BitStream::WriteOrthMatrix( + templateType m00, templateType m01, templateType m02, + templateType m10, templateType m11, templateType m12, + templateType m20, templateType m21, templateType m22 ) + { + double qw; + double qx; + double qy; + double qz; + +#ifdef _MSC_VER +#pragma warning(disable:4100) // m10, m01 : unreferenced formal parameter +#endif + + // Convert matrix to quat + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ + float sum; + sum = 1 + m00 + m11 + m22; + if (sum < 0.0f) sum=0.0f; + qw = sqrt( sum ) / 2; + sum = 1 + m00 - m11 - m22; + if (sum < 0.0f) sum=0.0f; + qx = sqrt( sum ) / 2; + sum = 1 - m00 + m11 - m22; + if (sum < 0.0f) sum=0.0f; + qy = sqrt( sum ) / 2; + sum = 1 - m00 - m11 + m22; + if (sum < 0.0f) sum=0.0f; + qz = sqrt( sum ) / 2; + if (qw < 0.0) qw=0.0; + if (qx < 0.0) qx=0.0; + if (qy < 0.0) qy=0.0; + if (qz < 0.0) qz=0.0; + qx = _copysign( qx, m21 - m12 ); + qy = _copysign( qy, m02 - m20 ); + qz = _copysign( qz, m10 - m01 ); + + WriteNormQuat(qw,qx,qy,qz); + } + + template // templateType for this function must be a float or double + bool BitStream::ReadNormVector( templateType &x, templateType &y, templateType &z ) + { + // unsigned short sy, sz; + bool yZero, zZero; + bool xNeg; + float cy,cz; + + Read(xNeg); + + Read(yZero); + if (yZero) + y=0.0; + else + { + ReadCompressed((float)cy); + y=cy; + //Read(sy); + //y=((float)sy / 32767.5f - 1.0f); + } + + if (!Read(zZero)) + return false; + + if (zZero) + z=0.0; + else + { + // if (!Read(sz)) + // return false; + + // z=((float)sz / 32767.5f - 1.0f); + if (!ReadCompressed((float)cz)) + return false; + z=cz; + } + + x = (templateType) (sqrtf((templateType)1.0 - y*y - z*z)); + if (xNeg) + x=-x; + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadVector( templateType &x, templateType &y, templateType &z ) + { + float magnitude; + //unsigned short sx,sy,sz; + if (!Read(magnitude)) + return false; + if (magnitude!=0.0) + { + // Read(sx); + // Read(sy); + // if (!Read(sz)) + // return false; + // x=((float)sx / 32767.5f - 1.0f) * magnitude; + // y=((float)sy / 32767.5f - 1.0f) * magnitude; + // z=((float)sz / 32767.5f - 1.0f) * magnitude; + float cx,cy,cz; + ReadCompressed(cx); + ReadCompressed(cy); + if (!ReadCompressed(cz)) + return false; + x=cx; + y=cy; + z=cz; + x*=magnitude; + y*=magnitude; + z*=magnitude; + } + else + { + x=0.0; + y=0.0; + z=0.0; + } + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadNormQuat( templateType &w, templateType &x, templateType &y, templateType &z) + { + bool cwNeg, cxNeg, cyNeg, czNeg; + unsigned short cx,cy,cz; + Read(cwNeg); + Read(cxNeg); + Read(cyNeg); + Read(czNeg); + Read(cx); + Read(cy); + if (!Read(cz)) + return false; + + // Calculate w from x,y,z + x=(templateType)(cx/65535.0); + y=(templateType)(cy/65535.0); + z=(templateType)(cz/65535.0); + if (cxNeg) x=-x; + if (cyNeg) y=-y; + if (czNeg) z=-z; + float difference = 1.0 - x*x - y*y - z*z; + if (difference < 0.0f) + difference=0.0f; + w = (templateType)(sqrt(difference)); + if (cwNeg) + w=-w; + return true; + } + + template // templateType for this function must be a float or double + bool BitStream::ReadOrthMatrix( + templateType &m00, templateType &m01, templateType &m02, + templateType &m10, templateType &m11, templateType &m12, + templateType &m20, templateType &m21, templateType &m22 ) + { + float qw,qx,qy,qz; + if (!ReadNormQuat(qw,qx,qy,qz)) + return false; + + // Quat to orthogonal rotation matrix + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm + double sqw = (double)qw*(double)qw; + double sqx = (double)qx*(double)qx; + double sqy = (double)qy*(double)qy; + double sqz = (double)qz*(double)qz; + m00 = (templateType)(sqx - sqy - sqz + sqw); // since sqw + sqx + sqy + sqz =1 + m11 = (templateType)(-sqx + sqy - sqz + sqw); + m22 = (templateType)(-sqx - sqy + sqz + sqw); + + double tmp1 = (double)qx*(double)qy; + double tmp2 = (double)qz*(double)qw; + m10 = (templateType)(2.0 * (tmp1 + tmp2)); + m01 = (templateType)(2.0 * (tmp1 - tmp2)); + + tmp1 = (double)qx*(double)qz; + tmp2 = (double)qy*(double)qw; + m20 =(templateType)(2.0 * (tmp1 - tmp2)); + m02 = (templateType)(2.0 * (tmp1 + tmp2)); + tmp1 = (double)qy*(double)qz; + tmp2 = (double)qx*(double)qw; + m21 = (templateType)(2.0 * (tmp1 + tmp2)); + m12 = (templateType)(2.0 * (tmp1 - tmp2)); + + return true; + } } +#ifdef _MSC_VER +#pragma warning( pop ) #endif + +#endif + +#endif // VC6 diff --git a/raknet/BitStream_NoTemplate.cpp b/raknet/BitStream_NoTemplate.cpp new file mode 100644 index 0000000..2d2b337 --- /dev/null +++ b/raknet/BitStream_NoTemplate.cpp @@ -0,0 +1,1875 @@ +/// \depreciated + +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization + +#include "RakNetDefines.h" +#include "BitStream.h" +#include +#include +#include +#include +#include +#include +#include + +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ ) + #include +#else + #include +#endif + +#ifdef __BITSTREAM_BIG_END +// Set up the read/write routines to produce Big-End network streams. +#define B16_1 0 +#define B16_0 1 + +#define B32_3 0 +#define B32_2 1 +#define B32_1 2 +#define B32_0 3 + +#define B64_7 0 +#define B64_6 1 +#define B64_5 2 +#define B64_4 3 +#define B64_3 4 +#define B64_2 5 +#define B64_1 6 +#define B64_0 7 + +#else +// Default to producing Little-End network streams. +#define B16_1 1 +#define B16_0 0 + +#define B32_3 3 +#define B32_2 2 +#define B32_1 1 +#define B32_0 0 + +#define B64_7 7 +#define B64_6 6 +#define B64_5 5 +#define B64_4 4 +#define B64_3 3 +#define B64_2 2 +#define B64_1 1 +#define B64_0 0 +#endif + +using namespace RakNet; + +BitStream::BitStream() +{ + numberOfBitsUsed = 0; + //numberOfBitsAllocated = 32 * 8; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + readOffset = 0; + //data = ( unsigned char* ) malloc( 32 ); + data = ( unsigned char* ) stackData; + +#ifdef _DEBUG +// assert( data ); +#endif + //memset(data, 0, 32); + copyData = true; +} + +BitStream::BitStream( int initialBytesToAllocate ) +{ + numberOfBitsUsed = 0; + readOffset = 0; + if (initialBytesToAllocate <= BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE * 8; + } + else + { + data = ( unsigned char* ) malloc( initialBytesToAllocate ); + numberOfBitsAllocated = initialBytesToAllocate << 3; + } +#ifdef _DEBUG + assert( data ); +#endif + // memset(data, 0, initialBytesToAllocate); + copyData = true; +} + +BitStream::BitStream( char* _data, unsigned int lengthInBytes, bool _copyData ) +{ + numberOfBitsUsed = lengthInBytes << 3; + readOffset = 0; + copyData = _copyData; + numberOfBitsAllocated = lengthInBytes << 3; + + if ( copyData ) + { + if ( lengthInBytes > 0 ) + { + if (lengthInBytes < BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) stackData; + numberOfBitsAllocated = BITSTREAM_STACK_ALLOCATION_SIZE << 3; + } + else + { + data = ( unsigned char* ) malloc( lengthInBytes ); + } +#ifdef _DEBUG + assert( data ); +#endif + memcpy( data, _data, lengthInBytes ); + } + else + data = 0; + } + else + data = ( unsigned char* ) _data; +} + +// Use this if you pass a pointer copy to the constructor (_copyData==false) and want to overallocate to prevent reallocation +void BitStream::SetNumberOfBitsAllocated( const unsigned int lengthInBits ) +{ +#ifdef _DEBUG + assert( lengthInBits >= ( unsigned int ) numberOfBitsAllocated ); +#endif + numberOfBitsAllocated = lengthInBits; +} + +BitStream::~BitStream() +{ + if ( copyData && numberOfBitsAllocated > BITSTREAM_STACK_ALLOCATION_SIZE << 3) + free( data ); // Use realloc and free so we are more efficient than delete and new for resizing +} + +void BitStream::Reset( void ) +{ + // Note: Do NOT reallocate memory because BitStream is used + // in places to serialize/deserialize a buffer. Reallocation + // is a dangerous operation (may result in leaks). + + if ( numberOfBitsUsed > 0 ) + { + // memset(data, 0, BITS_TO_BYTES(numberOfBitsUsed)); + } + + // Don't free memory here for speed efficiency + //free(data); // Use realloc and free so we are more efficient than delete and new for resizing + numberOfBitsUsed = 0; + + //numberOfBitsAllocated=8; + readOffset = 0; + + //data=(unsigned char*)malloc(1); + // if (numberOfBitsAllocated>0) + // memset(data, 0, BITS_TO_BYTES(numberOfBitsAllocated)); +} + +// Write the native types to the end of the buffer +void BitStream::Write( const bool input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 0; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + if ( input ) + Write1(); + else + Write0(); +} + +void BitStream::Write( const unsigned char input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 1; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +} + +void BitStream::Write( const char input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 2; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +} + +void BitStream::Write( const unsigned short input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 3; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint16w[2]; + uint16w[B16_1] = (input >> 8)&(0xff); + uint16w[B16_0] = input&(0xff); + + WriteBits( uint16w, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::Write( const short input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 4; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int16w[2]; + int16w[B16_1] = (input >> 8)&(0xff); + int16w[B16_0] = input&(0xff); + + WriteBits( int16w, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::Write( const unsigned int input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 5; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint32w[4]; + uint32w[B32_3] = (input >> 24)&(0x000000ff); + uint32w[B32_2] = (input >> 16)&(0x000000ff); + uint32w[B32_1] = (input >> 8)&(0x000000ff); + uint32w[B32_0] = (input)&(0x000000ff); + + WriteBits( uint32w, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::Write( const int input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 6; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int32w[4]; + int32w[B32_3] = (input >> 24)&(0x000000ff); + int32w[B32_2] = (input >> 16)&(0x000000ff); + int32w[B32_1] = (input >> 8)&(0x000000ff); + int32w[B32_0] = (input)&(0x000000ff); + + WriteBits( int32w, sizeof( input ) * 8, true ); +#endif +} + +// This doesn't compile on windows. Find another way to output the warning +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ )|| defined ( _WIN32 ) +//#warning Do NOT use 'long' for network data - it is not cross-compiler nor 32/64-bit safe +void BitStream::Write( const unsigned long input ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + Write( (const unsigned int) input ); +} +void BitStream::Write( const long input ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + Write( (const int) input ); +} +#endif + +#ifdef HAS_INT64 +void BitStream::Write( const uint64_t input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 7; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint64w[8]; + uint64w[B64_7] = (input >> 56) & 0xff; + uint64w[B64_6] = (input >> 48) & 0xff; + uint64w[B64_5] = (input >> 40) & 0xff; + uint64w[B64_4] = (input >> 32) & 0xff; + uint64w[B64_3] = (input >> 24) & 0xff; + uint64w[B64_2] = (input >> 16) & 0xff; + uint64w[B64_1] = (input >> 8) & 0xff; + uint64w[B64_0] = input & 0xff; + + WriteBits( uint64w, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::Write( const int64_t input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 8; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int64w[8]; + int64w[B64_7] = (input >> 56) & 0xff; + int64w[B64_6] = (input >> 48) & 0xff; + int64w[B64_5] = (input >> 40) & 0xff; + int64w[B64_4] = (input >> 32) & 0xff; + int64w[B64_3] = (input >> 24) & 0xff; + int64w[B64_2] = (input >> 16) & 0xff; + int64w[B64_1] = (input >> 8) & 0xff; + int64w[B64_0] = input & 0xff; + + WriteBits( int64w, sizeof( input ) * 8, true ); +#endif +} + +#endif + +void BitStream::Write( const float input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 9; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifndef __BITSTREAM_NATIVE_END + unsigned int intval = *((unsigned int *)(&input)); + Write(intval); +#else + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::Write( const double input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 10; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#if defined ( __BITSTREAM_NATIVE_END ) || ( ! defined (HAS_INT64) ) + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + uint64_t intval = *((uint64_t *)(&input)); + Write(intval); +#endif +} +// Write an array or casted stream +void BitStream::Write( const char* input, const int numberOfBytes ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 11; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); + WriteBits( ( unsigned char* ) & numberOfBytes, sizeof( int ) * 8, true ); +#endif + + WriteBits( ( unsigned char* ) input, numberOfBytes * 8, true ); +} +void BitStream::Write( const BitStream *bitStream ) +{ + WriteBits(bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), false); +} + +void BitStream::Write( const NetworkID networkId ) +{ + Write(networkId.playerId.binaryAddress); + Write(networkId.playerId.port); + Write(networkId.localSystemId); +} + +// Write the native types with simple compression. +// Best used with negatives and positives close to 0 +void BitStream::WriteCompressed( const unsigned char input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 12; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +} + +void BitStream::WriteCompressed( const char input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 13; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, false ); +} + +void BitStream::WriteCompressed( const unsigned short input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 14; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint16wc[2]; + uint16wc[B16_1] = (input >> 8)&(0xff); + uint16wc[B16_0] = input&(0xff); + + WriteCompressed( uint16wc, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::WriteCompressed( const short input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 15; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int16wc[2]; + int16wc[B16_1] = (input >> 8)&(0xff); + int16wc[B16_0] = input&(0xff); + + WriteCompressed( int16wc, sizeof( input ) * 8, false ); +#endif +} + +void BitStream::WriteCompressed( const unsigned int input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 16; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint32wc[4]; + uint32wc[B32_3] = (input >> 24)&(0x000000ff); + uint32wc[B32_2] = (input >> 16)&(0x000000ff); + uint32wc[B32_1] = (input >> 8)&(0x000000ff); + uint32wc[B32_0] = (input)&(0x000000ff); + + WriteCompressed( uint32wc, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::WriteCompressed( const int input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 17; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int32wc[4]; + int32wc[B32_3] = (input >> 24)&(0x000000ff); + int32wc[B32_2] = (input >> 16)&(0x000000ff); + int32wc[B32_1] = (input >> 8)&(0x000000ff); + int32wc[B32_0] = (input)&(0x000000ff); + + WriteCompressed( int32wc, sizeof( input ) * 8, false ); +#endif +} + +// Doesn't work on windows. Find another way to output the warning +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ )|| defined ( _WIN32 ) +void BitStream::WriteCompressed( const unsigned long input ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + WriteCompressed( (const unsigned int) input ); +} +void BitStream::WriteCompressed( const long input ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + WriteCompressed( (const int) input ); +} +#endif + +#ifdef HAS_INT64 +void BitStream::WriteCompressed( const uint64_t input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 18; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char uint64wc[8]; + uint64wc[B64_7] = (input >> 56) & 0xff; + uint64wc[B64_6] = (input >> 48) & 0xff; + uint64wc[B64_5] = (input >> 40) & 0xff; + uint64wc[B64_4] = (input >> 32) & 0xff; + uint64wc[B64_3] = (input >> 24) & 0xff; + uint64wc[B64_2] = (input >> 16) & 0xff; + uint64wc[B64_1] = (input >> 8) & 0xff; + uint64wc[B64_0] = input & 0xff; + + WriteCompressed( uint64wc, sizeof( input ) * 8, true ); +#endif +} + +void BitStream::WriteCompressed( const int64_t input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 19; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +#ifdef __BITSTREAM_NATIVE_END + WriteCompressed( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + static unsigned char int64wc[8]; + int64wc[B64_7] = (input >> 56) & 0xff; + int64wc[B64_6] = (input >> 48) & 0xff; + int64wc[B64_5] = (input >> 40) & 0xff; + int64wc[B64_4] = (input >> 32) & 0xff; + int64wc[B64_3] = (input >> 24) & 0xff; + int64wc[B64_2] = (input >> 16) & 0xff; + int64wc[B64_1] = (input >> 8) & 0xff; + int64wc[B64_0] = input & 0xff; + + WriteCompressed( int64wc, sizeof( input ) * 8, false ); +#endif +} +#endif + + +void BitStream::WriteCompressed( const float input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 20; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + +// Not yet implemented (no compression) +#if defined ( __BITSTREAM_NATIVE_END ) + WriteBits( ( unsigned char* ) &input, sizeof( input ) * 8, true ); +#else + Write( input ); +#endif +} + +void BitStream::WriteNormVector( float x, float y, float z ) +{ +#ifdef _DEBUG + assert(x <= 1.01f && y <= 1.01f && z <= 1.01f && x >= -1.01f && y >= -1.01f && z >= -1.01f); +#endif + if (x>1.0f) + x=1.0f; + if (y>1.0f) + y=1.0f; + if (z>1.0f) + z=1.0f; + if (x<-1.0f) + x=-1.0f; + if (y<-1.0f) + y=-1.0f; + if (z<-1.0f) + z=-1.0f; + + Write((bool) (x < 0.0f)); + + if (y==0.0f) + Write(true); + else + { + Write(false); + Write((unsigned short)((y+1.0f)*32767.5f)); + } + if (z==0.0f) + Write(true); + else + { + Write(false); + Write((unsigned short)((z+1.0f)*32767.5f)); + } +} +void BitStream::WriteVector( float x, float y, float z ) +{ + float magnitude = sqrtf(x * x + y * y + z * z); + Write(magnitude); + if (magnitude > 0.0f) + { + Write((unsigned short)((x/magnitude+1.0f)*32767.5f)); + Write((unsigned short)((y/magnitude+1.0f)*32767.5f)); + Write((unsigned short)((z/magnitude+1.0f)*32767.5f)); + } +} +void BitStream::WriteNormQuat( float w, float x, float y, float z) +{ + Write((bool)(w<0.0f)); + Write((bool)(x<0.0f)); + Write((bool)(y<0.0f)); + Write((bool)(z<0.0f)); + Write((unsigned short)(fabs(x)*65535.0)); + Write((unsigned short)(fabs(y)*65535.0)); + Write((unsigned short)(fabs(z)*65535.0)); + // Leave out w and calcuate it on the target +} +void BitStream::WriteOrthMatrix( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 ) +{ + double qw; + double qx; + double qy; + double qz; + + // Convert matrix to quat + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ + qw = sqrt( 1 + m00 + m11 + m22 ) / 2; + qx = sqrt( 1 + m00 - m11 - m22 ) / 2; + qy = sqrt( 1 - m00 + m11 - m22 ) / 2; + qz = sqrt( 1 - m00 - m11 + m22 ) / 2; + if (qw < 0.0) qw=0.0; + if (qx < 0.0) qx=0.0; + if (qy < 0.0) qy=0.0; + if (qz < 0.0) qz=0.0; + qx = _copysign( qx, m21 - m12 ); + qy = _copysign( qy, m02 - m20 ); + qz = _copysign( qz, m20 - m02 ); + + WriteNormQuat((float)qw,(float)qx,(float)qy,(float)qz); +} + +void BitStream::WriteCompressed( const double input ) +{ +#ifdef TYPE_CHECKING + unsigned char ID = 21; + WriteBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8, true ); +#endif + + // Not yet implemented (no compression) +#if defined ( __BITSTREAM_NATIVE_END ) + WriteBits( ( unsigned char* ) & input, sizeof( input ) * 8, true ); +#else + Write( input ); +#endif +} + +// Read the native types from the front of the buffer +// Write the native types to the end of the buffer +bool BitStream::Read( bool& output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + +#ifdef _DEBUG + + assert( ID == 0 ); + +#endif +#endif + + //assert(readOffset+1 <=numberOfBitsUsed); // If this assert is hit the stream wasn't long enough to read from + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + //if (ReadBit()) // Check that bit + if ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset++ % 8 ) ) ) // Is it faster to just write it out here? + output = true; + else + output = false; + + return true; +} + +bool BitStream::Read( unsigned char &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 1 ); + +#endif + + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +} + +bool BitStream::Read( char &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 2 ); + +#endif + + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +} + +bool BitStream::Read( unsigned short &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 3 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char uint16r[2]; + if (ReadBits( uint16r, sizeof( output ) * 8 ) != true) return false; + output = (((unsigned short) uint16r[B16_1])<<8)|((unsigned short)uint16r[B16_0]); + return true; +#endif +} + +bool BitStream::Read( short &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 4 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char int16r[2]; + if (ReadBits( int16r, sizeof( output ) * 8 ) != true) return false; + output = (((unsigned short) int16r[B16_1])<<8)|((unsigned short)int16r[B16_0]); + return true; +#endif +} + +bool BitStream::Read( unsigned int &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 5 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char uint32r[4]; + if(ReadBits( uint32r, sizeof( output ) * 8 ) != true) + return false; + output = (((unsigned int) uint32r[B32_3])<<24)| + (((unsigned int) uint32r[B32_2])<<16)| + (((unsigned int) uint32r[B32_1])<<8)| + ((unsigned int) uint32r[B32_0]); + return true; +#endif +} + +bool BitStream::Read( int &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 6 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char int32r[4]; + if(ReadBits( int32r, sizeof( output ) * 8 ) != true) + return false; + output = (((unsigned int) int32r[B32_3])<<24)| + (((unsigned int) int32r[B32_2])<<16)| + (((unsigned int) int32r[B32_1])<<8)| + ((unsigned int) int32r[B32_0]); + return true; +#endif +} + +// This doesn't compile on windows. Find another way to output the warning +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ )|| defined ( _WIN32 ) +//#warning Do NOT use 'long' for network data - it is not cross-compiler nor 32/64-bit safe +bool BitStream::Read( unsigned long &output ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + unsigned int tmp; + if ( !Read(tmp) ) return false; + output = tmp; + return true; +} +bool BitStream::Read( long &output ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + int tmp; + if ( !Read(tmp) ) return false; + output = tmp; + return true; +} +#endif + +#ifdef HAS_INT64 +bool BitStream::Read( uint64_t &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 7 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char uint64r[8]; + if(ReadBits( uint64r, sizeof( output ) * 8 ) != true) + return false; + output = (((uint64_t) uint64r[B64_7])<<56)|(((uint64_t) uint64r[B64_6])<<48)| + (((uint64_t) uint64r[B64_5])<<40)|(((uint64_t) uint64r[B64_4])<<32)| + (((uint64_t) uint64r[B64_3])<<24)|(((uint64_t) uint64r[B64_2])<<16)| + (((uint64_t) uint64r[B64_1])<<8)|((uint64_t) uint64r[B64_0]); + return true; +#endif +} + +bool BitStream::Read( int64_t &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 8 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + static unsigned char int64r[8]; + if(ReadBits( int64r, sizeof( output ) * 8 ) != true) + return false; + output = (((uint64_t) int64r[B64_7])<<56)|(((uint64_t) int64r[B64_6])<<48)| + (((uint64_t) int64r[B64_5])<<40)|(((uint64_t) int64r[B64_4])<<32)| + (((uint64_t) int64r[B64_3])<<24)|(((uint64_t) int64r[B64_2])<<16)| + (((uint64_t) int64r[B64_1])<<8)|((uint64_t) int64r[B64_0]); + return true; +#endif +} +#endif + +bool BitStream::Read( float &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 9 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + unsigned int val; + if (Read(val) == false) return false; + output = *((float *)(&val)); + return true; +#endif +} + +bool BitStream::Read( double &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 10 ); + +#endif + +#if defined ( __BITSTREAM_NATIVE_END ) || ( ! defined ( HAS_INT64 ) ) + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + uint64_t val; + if (Read(val) == false) return false; + output = *((double *)(&val)); + return true; +#endif +} +// Read an array or casted stream +bool BitStream::Read( char* output, const int numberOfBytes ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 11 ); + + int NOB; + + ReadBits( ( unsigned char* ) & NOB, sizeof( int ) * 8 ); + + assert( NOB == numberOfBytes ); + +#endif + + return ReadBits( ( unsigned char* ) output, numberOfBytes * 8 ); +} + +bool BitStream::Read( NetworkID &output) +{ + Read(output.playerId.binaryAddress); + Read(output.playerId.port); + return Read(output.localSystemId); +} +// Read the types you wrote with WriteCompressed +bool BitStream::ReadCompressed( unsigned char & output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 12 ); + +#endif + + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +} + +bool BitStream::ReadCompressed( char &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 13 ); + +#endif + + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, false ); +} + +bool BitStream::ReadCompressed( unsigned short &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 14 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char uint16rc[2]; + if (ReadCompressed( uint16rc, sizeof( output ) * 8, true ) != true) return false; + output = (((unsigned short) uint16rc[B16_1])<<8)| + ((unsigned short)uint16rc[B16_0]); + return true; +#endif +} + +bool BitStream::ReadCompressed( short &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 15 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char int16rc[2]; + if (ReadCompressed( int16rc, sizeof( output ) * 8, false ) != true) return false; + output = (((unsigned short) int16rc[B16_1])<<8)|((unsigned short)int16rc[B16_0]); + return true; +#endif +} + +bool BitStream::ReadCompressed( unsigned int &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 16 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char uint32rc[4]; + if(ReadCompressed( uint32rc, sizeof( output ) * 8, true ) != true) + return false; + output = (((unsigned int) uint32rc[B32_3])<<24)| + (((unsigned int) uint32rc[B32_2])<<16)| + (((unsigned int) uint32rc[B32_1])<<8)| + ((unsigned int) uint32rc[B32_0]); + return true; +#endif +} + +bool BitStream::ReadCompressed( int &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 17 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char int32rc[4]; + if(ReadCompressed( int32rc, sizeof( output ) * 8, false ) != true) + return false; + output = (((unsigned int) int32rc[B32_3])<<24)| + (((unsigned int) int32rc[B32_2])<<16)| + (((unsigned int) int32rc[B32_1])<<8)| + ((unsigned int) int32rc[B32_0]); + return true; +#endif +} + +// This doesn't compile on windows. Find another way to output the warning +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ )|| defined ( _WIN32 ) +//#warning Do NOT use 'long' for network data - it is not cross-compiler nor 32/64-bit safe +bool BitStream::ReadCompressed( unsigned long &output ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + unsigned int tmp; + if ( !ReadCompressed(tmp) ) return false; + output = tmp; + return true; +} +bool BitStream::ReadCompressed( long &output ) +{ + printf("*** WARNING: Do not use 'long' to declare network data.\n"); + printf("*** It is not safe betwen compilers or between 32/64-bit systems.\n"); + int tmp; + if ( !ReadCompressed(tmp) ) return false; + output = tmp; + return true; +} +#endif + +#ifdef HAS_INT64 +bool BitStream::ReadCompressed( uint64_t &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 18 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char uint64rc[8]; + if(ReadCompressed( uint64rc, sizeof( output ) * 8, true ) != true) + return false; + output = (((uint64_t) uint64rc[B64_7])<<56)|(((uint64_t) uint64rc[B64_6])<<48)| + (((uint64_t) uint64rc[B64_5])<<40)|(((uint64_t) uint64rc[B64_4])<<32)| + (((uint64_t) uint64rc[B64_3])<<24)|(((uint64_t) uint64rc[B64_2])<<16)| + (((uint64_t) uint64rc[B64_1])<<8)|((uint64_t) uint64rc[B64_0]); + return true; +#endif +} + +bool BitStream::ReadCompressed( int64_t& output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 19 ); + +#endif + +#ifdef __BITSTREAM_NATIVE_END + return ReadCompressed( ( unsigned char* ) & output, sizeof( output ) * 8, true ); +#else + static unsigned char int64rc[8]; + if(ReadCompressed( int64rc, sizeof( output ) * 8, false ) != true) + return false; + output = (((uint64_t) int64rc[B64_7])<<56)|(((uint64_t) int64rc[B64_6])<<48)| + (((uint64_t) int64rc[B64_5])<<40)|(((uint64_t) int64rc[B64_4])<<32)| + (((uint64_t) int64rc[B64_3])<<24)|(((uint64_t) int64rc[B64_2])<<16)| + (((uint64_t) int64rc[B64_1])<<8)|((uint64_t) int64rc[B64_0]); + return true; +#endif +} +#endif + +bool BitStream::ReadCompressed( float &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 20 ); + +#endif + + + // Not yet implemented +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + return Read( output ); +#endif +} +bool BitStream::ReadNormVector( float &x, float &y, float &z ) +{ + unsigned short sy, sz; + bool yZero, zZero; + bool xNeg; + + Read(xNeg); + + Read(yZero); + if (yZero) + y=0.0f; + else + { + Read(sy); + y=((float)sy / 32767.5f - 1.0f); + } + + if (!Read(zZero)) + return false; + + if (zZero) + z=0.0f; + else + { + if (!Read(sz)) + return false; + + z=((float)sz / 32767.5f - 1.0f); + } + + x = sqrtf(1.0f - y*y - z*z); + if (xNeg) + x=-x; + return true; +} +bool BitStream::ReadVector( float &x, float &y, float &z ) +{ + float magnitude; + unsigned short sx,sy,sz; + if (!Read(magnitude)) + return false; + if (magnitude!=0.0f) + { + Read(sx); + Read(sy); + if (!Read(sz)) + return false; + x=((float)sx / 32767.5f - 1.0f) * magnitude; + y=((float)sy / 32767.5f - 1.0f) * magnitude; + z=((float)sz / 32767.5f - 1.0f) * magnitude; + } + else + { + x=0.0f; + y=0.0f; + z=0.0f; + } + return true; +} +bool BitStream::ReadNormQuat( float &w, float &x, float &y, float &z) +{ + bool cwNeg, cxNeg, cyNeg, czNeg; + unsigned short cx,cy,cz; + Read(cwNeg); + Read(cxNeg); + Read(cyNeg); + Read(czNeg); + Read(cx); + Read(cy); + if (!Read(cz)) + return false; + + // Calculate w from x,y,z + x=cx/65535.0f; + y=cy/65535.0f; + z=cz/65535.0f; + if (cxNeg) x=-x; + if (cyNeg) y=-y; + if (czNeg) z=-z; + w = sqrt(1.0f - x*x - y*y - z*z); + if (cwNeg) + w=-w; + return true; +} +bool BitStream::ReadOrthMatrix( + float &m00, float &m01, float &m02, + float &m10, float &m11, float &m12, + float &m20, float &m21, float &m22 ) +{ + float qw,qx,qy,qz; + if (!ReadNormQuat(qw,qx,qy,qz)) + return false; + + // Quat to orthogonal rotation matrix + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm + double sqw = (double)qw*(double)qw; + double sqx = (double)qx*(double)qx; + double sqy = (double)qy*(double)qy; + double sqz = (double)qz*(double)qz; + m00 = (float)(sqx - sqy - sqz + sqw); // since sqw + sqx + sqy + sqz =1 + m11 = (float)(-sqx + sqy - sqz + sqw); + m22 = (float)(-sqx - sqy + sqz + sqw); + + double tmp1 = (double)qx*(double)qy; + double tmp2 = (double)qz*(double)qw; + m10 = (float)(2.0 * (tmp1 + tmp2)); + m01 = (float)(2.0 * (tmp1 - tmp2)); + + tmp1 = (double)qx*(double)qz; + tmp2 = (double)qy*(double)qw; + m20 =(float)(2.0 * (tmp1 - tmp2)); + m02 = (float)(2.0 * (tmp1 + tmp2)); + tmp1 = (double)qy*(double)qz; + tmp2 = (double)qx*(double)qw; + m21 = (float)(2.0 * (tmp1 + tmp2)); + m12 = (float)(2.0 * (tmp1 - tmp2)); + + return true; +} + +bool BitStream::ReadCompressed( double &output ) +{ +#ifdef TYPE_CHECKING + unsigned char ID; + + if ( ReadBits( ( unsigned char* ) & ID, sizeof(unsigned char) * 8 ) == false ) + return false; + + assert( ID == 21 ); + +#endif + +// ReadCompressed using int has no effect on this data format and would make the data bigger! +#ifdef __BITSTREAM_NATIVE_END + return ReadBits( ( unsigned char* ) & output, sizeof( output ) * 8 ); +#else + return Read( output ); +#endif +} + +// Sets the read pointer back to the beginning of your data. +void BitStream::ResetReadPointer( void ) +{ + readOffset = 0; +} + +// Sets the write pointer back to the beginning of your data. +void BitStream::ResetWritePointer( void ) +{ + numberOfBitsUsed = 0; +} + +// Write a 0 +void BitStream::Write0( void ) +{ + AddBitsAndReallocate( 1 ); + + // New bytes need to be zeroed + + if ( ( numberOfBitsUsed % 8 ) == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0; + + numberOfBitsUsed++; +} + +// Write a 1 +void BitStream::Write1( void ) +{ + AddBitsAndReallocate( 1 ); + + int numberOfBitsMod8 = numberOfBitsUsed % 8; + + if ( numberOfBitsMod8 == 0 ) + data[ numberOfBitsUsed >> 3 ] = 0x80; + else + data[ numberOfBitsUsed >> 3 ] |= 0x80 >> ( numberOfBitsMod8 ); // Set the bit to 1 + + numberOfBitsUsed++; +} + +// Returns true if the next data read is a 1, false if it is a 0 +bool BitStream::ReadBit( void ) +{ +#pragma warning( disable : 4800 ) + return ( bool ) ( data[ readOffset >> 3 ] & ( 0x80 >> ( readOffset++ % 8 ) ) ); +#pragma warning( default : 4800 ) +} + +// Align the bitstream to the byte boundary and then write the specified number of bits. +// This is faster than WriteBits but wastes the bits to do the alignment and requires you to call +// SetReadToByteAlignment at the corresponding read position +void BitStream::WriteAlignedBytes( const unsigned char* input, + const int numberOfBytesToWrite ) +{ +#ifdef _DEBUG + assert( numberOfBytesToWrite > 0 ); +#endif + + AlignWriteToByteBoundary(); + // Allocate enough memory to hold everything + AddBitsAndReallocate( numberOfBytesToWrite << 3 ); + + // Write the data + memcpy( data + ( numberOfBitsUsed >> 3 ), input, numberOfBytesToWrite ); + + numberOfBitsUsed += numberOfBytesToWrite << 3; +} + +// Read bits, starting at the next aligned bits. Note that the modulus 8 starting offset of the +// sequence must be the same as was used with WriteBits. This will be a problem with packet coalescence +// unless you byte align the coalesced packets. +bool BitStream::ReadAlignedBytes( unsigned char* output, + const int numberOfBytesToRead ) +{ +#ifdef _DEBUG + assert( numberOfBytesToRead > 0 ); +#endif + + if ( numberOfBytesToRead <= 0 ) + return false; + + // Byte align + AlignReadToByteBoundary(); + + if ( readOffset + ( numberOfBytesToRead << 3 ) > numberOfBitsUsed ) + return false; + + // Write the data + memcpy( output, data + ( readOffset >> 3 ), numberOfBytesToRead ); + + readOffset += numberOfBytesToRead << 3; + + return true; +} + +// Align the next write and/or read to a byte boundary. This can be used to 'waste' bits to byte align for efficiency reasons +void BitStream::AlignWriteToByteBoundary( void ) +{ + if ( numberOfBitsUsed ) + numberOfBitsUsed += 8 - ( ( numberOfBitsUsed - 1 ) % 8 + 1 ); +} + +// Align the next write and/or read to a byte boundary. This can be used to 'waste' bits to byte align for efficiency reasons +void BitStream::AlignReadToByteBoundary( void ) +{ + if ( readOffset ) + readOffset += 8 - ( ( readOffset - 1 ) % 8 + 1 ); +} + +// Write numberToWrite bits from the input source +void BitStream::WriteBits( const unsigned char *input, + int numberOfBitsToWrite, const bool rightAlignedBits ) +{ + // if (numberOfBitsToWrite<=0) + // return; + + AddBitsAndReallocate( numberOfBitsToWrite ); + int offset = 0; + unsigned char dataByte; + int numberOfBitsUsedMod8; + + numberOfBitsUsedMod8 = numberOfBitsUsed % 8; + + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToWrite > 0 ) + //do + { + dataByte = *( input + offset ); + + if ( numberOfBitsToWrite < 8 && rightAlignedBits ) // rightAlignedBits means in the case of a partial byte, the bits are aligned from the right (bit 0) rather than the left (as in the normal internal representation) + dataByte <<= 8 - numberOfBitsToWrite; // shift left to get the bits on the left, as in our internal representation + + // Writing to a new byte each time + if ( numberOfBitsUsedMod8 == 0 ) + * ( data + ( numberOfBitsUsed >> 3 ) ) = dataByte; + else + { + // Copy over the new data. + *( data + ( numberOfBitsUsed >> 3 ) ) |= dataByte >> ( numberOfBitsUsedMod8 ); // First half + + if ( 8 - ( numberOfBitsUsedMod8 ) < 8 && 8 - ( numberOfBitsUsedMod8 ) < numberOfBitsToWrite ) // If we didn't write it all out in the first half (8 - (numberOfBitsUsed%8) is the number we wrote in the first half) + { + *( data + ( numberOfBitsUsed >> 3 ) + 1 ) = (unsigned char) ( dataByte << ( 8 - ( numberOfBitsUsedMod8 ) ) ); // Second half (overlaps byte boundary) + } + } + + if ( numberOfBitsToWrite >= 8 ) + numberOfBitsUsed += 8; + else + numberOfBitsUsed += numberOfBitsToWrite; + + numberOfBitsToWrite -= 8; + + offset++; + } + // } while(numberOfBitsToWrite>0); +} + +// Set the stream to some initial data. For internal use +void BitStream::SetData( const unsigned char* input, const int numberOfBits ) +{ +#ifdef _DEBUG + assert( numberOfBitsUsed == 0 ); // Make sure the stream is clear +#endif + + if ( numberOfBits <= 0 ) + return ; + + AddBitsAndReallocate( numberOfBits ); + + memcpy( data, input, BITS_TO_BYTES( numberOfBits ) ); + + numberOfBitsUsed = numberOfBits; +} + +// Assume the input source points to a native type, compress and write it +void BitStream::WriteCompressed( const unsigned char* input, + const int size, const bool unsignedData ) +{ + int currentByte = ( size >> 3 ) - 1; // PCs + + unsigned char byteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + } + + else + { + byteMatch = 0xFF; + } + + // Write upper bytes with a single 1 + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + if ( input[ currentByte ] == byteMatch ) // If high byte is byteMatch (0 of 0xff) then it would have the same value shifted + { + bool b = true; + Write( b ); + } + else + { + // Write the remainder of the data after writing 0 + bool b = false; + Write( b ); + + WriteBits( input, ( currentByte + 1 ) << 3, true ); + // currentByte--; + + + return ; + } + + currentByte--; + } + + // If the upper half of the last byte is a 0 (positive) or 16 (negative) then write a 1 and the remaining 4 bits. Otherwise write a 0 and the 8 bites. + if ( ( unsignedData && ( ( *( input + currentByte ) ) & 0xF0 ) == 0x00 ) || + ( unsignedData == false && ( ( *( input + currentByte ) ) & 0xF0 ) == 0xF0 ) ) + { + bool b = true; + Write( b ); + WriteBits( input + currentByte, 4, true ); + } + + else + { + bool b = false; + Write( b ); + WriteBits( input + currentByte, 8, true ); + } +} + +// Read numberOfBitsToRead bits to the output source +// alignBitsToRight should be set to true to convert internal bitstream data to userdata +// It should be false if you used WriteBits with rightAlignedBits false +bool BitStream::ReadBits( unsigned char* output, + int numberOfBitsToRead, const bool alignBitsToRight ) +{ +#ifdef _DEBUG + assert( numberOfBitsToRead > 0 ); +#endif + // if (numberOfBitsToRead<=0) + // return false; + + if ( readOffset + numberOfBitsToRead > numberOfBitsUsed ) + return false; + + int readOffsetMod8; + + int offset = 0; + + memset( output, 0, BITS_TO_BYTES( numberOfBitsToRead ) ); + + readOffsetMod8 = readOffset % 8; + + // do + // Faster to put the while at the top surprisingly enough + while ( numberOfBitsToRead > 0 ) + { + *( output + offset ) |= *( data + ( readOffset >> 3 ) ) << ( readOffsetMod8 ); // First half + + if ( readOffsetMod8 > 0 && numberOfBitsToRead > 8 - ( readOffsetMod8 ) ) // If we have a second half, we didn't read enough bytes in the first half + *( output + offset ) |= *( data + ( readOffset >> 3 ) + 1 ) >> ( 8 - ( readOffsetMod8 ) ); // Second half (overlaps byte boundary) + + numberOfBitsToRead -= 8; + + if ( numberOfBitsToRead < 0 ) // Reading a partial byte for the last byte, shift right so the data is aligned on the right + { + + if ( alignBitsToRight ) + * ( output + offset ) >>= -numberOfBitsToRead; + + readOffset += 8 + numberOfBitsToRead; + } + else + readOffset += 8; + + offset++; + + } + + //} while(numberOfBitsToRead>0); + + return true; +} + +// Assume the input source points to a compressed native type. Decompress and read it +bool BitStream::ReadCompressed( unsigned char* output, + const int size, const bool unsignedData ) +{ + int currentByte = ( size >> 3 ) - 1; + + + unsigned char byteMatch, halfByteMatch; + + if ( unsignedData ) + { + byteMatch = 0; + halfByteMatch = 0; + } + + else + { + byteMatch = 0xFF; + halfByteMatch = 0xF0; + } + + // Upper bytes are specified with a single 1 if they match byteMatch + // From high byte to low byte, if high byte is a byteMatch then write a 1 bit. Otherwise write a 0 bit and then write the remaining bytes + while ( currentByte > 0 ) + { + // If we read a 1 then the data is byteMatch. + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + output[ currentByte ] = byteMatch; + currentByte--; + } + else + { + // Read the rest of the bytes + + if ( ReadBits( output, ( currentByte + 1 ) << 3 ) == false ) + return false; + + return true; + } + } + + // All but the first bytes are byteMatch. If the upper half of the last byte is a 0 (positive) or 16 (negative) then what we read will be a 1 and the remaining 4 bits. + // Otherwise we read a 0 and the 8 bytes + //assert(readOffset+1 <=numberOfBitsUsed); // If this assert is hit the stream wasn't long enough to read from + if ( readOffset + 1 > numberOfBitsUsed ) + return false; + + bool b; + + if ( Read( b ) == false ) + return false; + + if ( b ) // Check that bit + { + + if ( ReadBits( output + currentByte, 4 ) == false ) + return false; + + output[ currentByte ] |= halfByteMatch; // We have to set the high 4 bits since these are set to 0 by ReadBits + } + else + { + if ( ReadBits( output + currentByte, 8 ) == false ) + return false; + } + + return true; +} + +// Reallocates (if necessary) in preparation of writing numberOfBitsToWrite +void BitStream::AddBitsAndReallocate( const int numberOfBitsToWrite ) +{ + if ( numberOfBitsToWrite <= 0 ) + return; + + int newNumberOfBitsAllocated = numberOfBitsToWrite + numberOfBitsUsed; + + if ( numberOfBitsToWrite + numberOfBitsUsed > 0 && ( ( numberOfBitsAllocated - 1 ) >> 3 ) < ( ( newNumberOfBitsAllocated - 1 ) >> 3 ) ) // If we need to allocate 1 or more new bytes + { +#ifdef _DEBUG + // If this assert hits then we need to specify true for the third parameter in the constructor + // It needs to reallocate to hold all the data and can't do it unless we allocated to begin with + assert( copyData == true ); +#endif + + // Less memory efficient but saves on news and deletes + newNumberOfBitsAllocated = ( numberOfBitsToWrite + numberOfBitsUsed ) * 2; +// int newByteOffset = BITS_TO_BYTES( numberOfBitsAllocated ); + // Use realloc and free so we are more efficient than delete and new for resizing + int amountToAllocate = BITS_TO_BYTES( newNumberOfBitsAllocated ); + if (data==(unsigned char*)stackData) + { + if (amountToAllocate > BITSTREAM_STACK_ALLOCATION_SIZE) + { + data = ( unsigned char* ) malloc( amountToAllocate ); + + // need to copy the stack data over to our new memory area too + memcpy ((void *)data, (void *)stackData, BITS_TO_BYTES( numberOfBitsAllocated )); + } + } + else + { + data = ( unsigned char* ) realloc( data, amountToAllocate ); + } + +#ifdef _DEBUG + assert( data ); // Make sure realloc succeeded +#endif + // memset(data+newByteOffset, 0, ((newNumberOfBitsAllocated-1)>>3) - ((numberOfBitsAllocated-1)>>3)); // Set the new data block to 0 + } + + if ( newNumberOfBitsAllocated > numberOfBitsAllocated ) + numberOfBitsAllocated = newNumberOfBitsAllocated; +} + +// Should hit if reads didn't match writes +void BitStream::AssertStreamEmpty( void ) +{ + assert( readOffset == numberOfBitsUsed ); +} + +void BitStream::PrintBits( void ) const +{ + if ( numberOfBitsUsed <= 0 ) + { + printf( "No bits\n" ); + return ; + } + + for ( int counter = 0; counter < BITS_TO_BYTES( numberOfBitsUsed ); counter++ ) + { + int stop; + + if ( counter == ( numberOfBitsUsed - 1 ) >> 3 ) + stop = 8 - ( ( ( numberOfBitsUsed - 1 ) % 8 ) + 1 ); + else + stop = 0; + + for ( int counter2 = 7; counter2 >= stop; counter2-- ) + { + if ( ( data[ counter ] >> counter2 ) & 1 ) + putchar( '1' ); + else + putchar( '0' ); + } + + putchar( ' ' ); + } + + putchar( '\n' ); +} + + +// Exposes the data for you to look at, like PrintBits does. +// Data will point to the stream. Returns the length in bits of the stream. +int BitStream::CopyData( unsigned char** _data ) const +{ +#ifdef _DEBUG + assert( numberOfBitsUsed > 0 ); +#endif + + *_data = new unsigned char [ BITS_TO_BYTES( numberOfBitsUsed ) ]; + memcpy( *_data, data, sizeof(unsigned char) * ( BITS_TO_BYTES( numberOfBitsUsed ) ) ); + return numberOfBitsUsed; +} + +// Ignore data we don't intend to read +void BitStream::IgnoreBits( const int numberOfBits ) +{ + readOffset += numberOfBits; +} + +// Move the write pointer to a position on the array. Dangerous if you don't know what you are doing! +void BitStream::SetWriteOffset( const int offset ) +{ + numberOfBitsUsed = offset; +} + +// Returns the length in bits of the stream +int BitStream::GetNumberOfBitsUsed( void ) const +{ + return numberOfBitsUsed; +} + +// Returns the length in bytes of the stream +int BitStream::GetNumberOfBytesUsed( void ) const +{ + return BITS_TO_BYTES( numberOfBitsUsed ); +} + +// Returns the number of bits into the stream that we have read +int BitStream::GetReadOffset( void ) const +{ + return readOffset; +} + +// Returns the number of bits left in the stream that haven't been read +int BitStream::GetNumberOfUnreadBits( void ) const +{ + return numberOfBitsUsed - readOffset; +} + +// Exposes the internal data +unsigned char* BitStream::GetData( void ) const +{ + return data; +} + +// If we used the constructor version with copy data off, this makes sure it is set to on and the data pointed to is copied. +void BitStream::AssertCopyData( void ) +{ + if ( copyData == false ) + { + copyData = true; + + if ( numberOfBitsAllocated > 0 ) + { + unsigned char * newdata = ( unsigned char* ) malloc( BITS_TO_BYTES( numberOfBitsAllocated ) ); +#ifdef _DEBUG + + assert( data ); +#endif + + memcpy( newdata, data, BITS_TO_BYTES( numberOfBitsAllocated ) ); + data = newdata; + } + + else + data = 0; + } +} + +#endif diff --git a/raknet/BitStream_NoTemplate.h b/raknet/BitStream_NoTemplate.h new file mode 100644 index 0000000..0259880 --- /dev/null +++ b/raknet/BitStream_NoTemplate.h @@ -0,0 +1,713 @@ +/// \depreciated +#if defined(_MSC_VER) && _MSC_VER < 1299 // VC6 doesn't support template specialization + +#ifndef __BITSTREAM_H +#define __BITSTREAM_H + +#ifdef _WIN32 +#if defined (_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64 +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#define HAS_INT64 +#endif +#else +#include +#define HAS_INT64 +#endif + +// Arbitrary size, just picking something likely to be larger than most packets +#define BITSTREAM_STACK_ALLOCATION_SIZE 256 + +/** \note If you want the default network byte stream to be + in Network Byte Order (Big Endian) then #define __BITSTREAM_BIG_END + otherwise the default is 'Little Endian'. If your CPU has the same + Byte Order as your network stream, you can cut out some overheads + using #define __BITSTREAM_NATIVE_END --- if this is defined, + the __BITSTREAM_BIG_END flag becomes ineffective. + */ + +#include "NetworkTypes.h" + +namespace RakNet +{ + /** + * This macro transforms a bit in byte + * @param x Transform a bit to a byte + */ +#define BITS_TO_BYTES(x) (((x)+7)>>3) + +#define BYTES_TO_BITS(x) (x<<3) + + /** + * @brief Packets encoding and decoding facilities + * + * Helper class to encode and decode packets. + * + */ + + class BitStream + { + + public: + /** + * Default Constructor + */ + BitStream(); + /** + * Preallocate some memory for the construction of the packet + * @param initialBytesToAllocate the amount of byte to pre-allocate. + */ + BitStream( int initialBytesToAllocate ); + + /** + * Initialize the BitStream object using data from the network. + * Set _copyData to true if you want to make an internal copy of + * the data you are passing. You can then Write and do all other + * operations Set it to false if you want to just use a pointer to + * the data you are passing, in order to save memory and speed. + * You should only then do read operations. + * @param _data An array of bytes. + * @param lengthInBytes Size of the @em _data. + * @param _copyData Does a copy of the input data. + */ + BitStream( char* _data, unsigned int lengthInBytes, bool _copyData ); + /** + * Destructor + */ + ~BitStream(); + /** + * Reset the bitstream for reuse + */ + void Reset( void ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const bool input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const unsigned char input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const char input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const unsigned short input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const short input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const unsigned int input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const int input ); +#if defined ( __APPLE__ ) || defined (__APPLE_CC__ )||defined ( _WIN32 ) + // These are only provided for MS Windows and + // Mac OSX (G4 processor) convenience and are + // equivalent to the (int) versions. + // The use of 'long' for any network data is + // a fault since it will not be portable to 64-bit CPUs. + void Write( const unsigned long input ); + void Write( const long input ); +#endif + +#ifdef HAS_INT64 + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const uint64_t input ); + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const int64_t input ); +#endif + + /** + * Write the native types to the end of the buffer + * without any compression mecanism. + * @param input The data + */ + void Write( const float input ); + /** + * Write the native types to the end of the buffer + * without any compression mechanism. + * @param input The data + */ + void Write( const double input ); + /** + * Write an array or casted stream. It is supposed to + * be raw data. It is also not possible to deal with endian problem + * @param input a byte buffer + * @param numberOfBytes the size of the byte buffer + */ + void Write( const char* input, const int numberOfBytes ); + + void Write( const NetworkID networkId ); + + /** + * Copy from another bitstream + * @bitStream the bitstream to copy from + */ + void Write( const BitStream *bitStream ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const unsigned char input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const char input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const unsigned short input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const short input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const unsigned int input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const int input ); +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ ) || defined ( _WIN32 ) + // These are only provided for MS Windows and + // Mac OSX (G4 processor) convenience and are + // equivalent to the (int) versions. + // The use of 'long' for any network data is + // a fault since it will not be portable to 64-bit CPUs. + void WriteCompressed( const unsigned long input ); + void WriteCompressed( const long input ); +#endif + +#ifdef HAS_INT64 + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const uint64_t input ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const int64_t input ); +#endif + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const float input ); + /** + * Write a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12 bytes. Will further compress y or z axis aligned vectors. + * Accurate to 1/32767.5. + * @param x x + * @param y y + * @param z z + */ + void WriteNormVector( float x, float y, float z ); + + /** + * Write a vector, using 10 bytes instead of 12. + * Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + * @param x x + * @param y y + * @param z z + */ + void WriteVector( float x, float y, float z ); + + /** + * Write a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + * @param w w + * @param x x + * @param y y + * @param z z + */ + void WriteNormQuat( float w, float x, float y, float z); + + /** + * Write an orthogonal matrix by creating a quaternion, and writing 3 components of the quaternion in 2 bytes each + * for 6 bytes instead of 36 + */ + void WriteOrthMatrix( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 ); + /** + * Write the native types with simple compression. + * Best used with negatives and positives close to 0 + * @param input The data. + */ + void WriteCompressed( const double input ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( bool &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( unsigned char &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( char &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( unsigned short &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( short &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( unsigned int &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( int &output ); +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ ) || defined ( _WIN32 ) + // These are only provided for MS Windows and + // Mac OSX (G4 processor) convenience and are + // equivalent to the (int) versions. + // The use of 'long' for any network data is + // a fault since it will not be portable to 64-bit CPUs. + bool Read( unsigned long &output ); + bool Read( long &output ); +#endif + +#ifdef HAS_INT64 + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( uint64_t &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( int64_t &output ); +#endif + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( float &output ); + /** + * Read the native types from the front of the buffer + * @param output The readed value. + * @return true on success false otherwise. The result of a reading + * can only be wrong in the case we reach the end of the BitStream + * with some missing bits. + */ + bool Read( double &output ); + /** + * Read an array or casted stream of byte. The array + * is raw data. There is no automatic conversion on + * big endian arch + * @param output The result byte array. It should be larger than @em numberOfBytes. + * @param numberOfBytes The number of byte to read + * @return true on success false if there is some missing bytes. + */ + bool Read( char* output, const int numberOfBytes ); + + bool Read( NetworkID &output); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( unsigned char & output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( char &output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( unsigned short &output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( short &output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( unsigned int &output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( int &output ); + +#if defined ( __APPLE__ ) || defined ( __APPLE_CC__ )|| defined ( _WIN32 ) + // These are only provided for MS Windows and + // Mac OSX (G4 processor) convenience and are + // equivalent to the (int) versions. + // The use of 'long' for any network data is + // a fault since it will not be portable to 64-bit CPUs. + bool ReadCompressed( unsigned long &output ); + bool ReadCompressed( long &output ); +#endif + +#ifdef HAS_INT64 + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( uint64_t &output ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( int64_t &output ); +#endif + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( float &output ); + /** + * Read a normalized 3D vector, using (at most) 4 bytes + 3 bits instead of 12 bytes. Will further compress y or z axis aligned vectors. + * Accurate to 1/32767.5. + * @param x x + * @param y y + * @param z z + */ + bool ReadNormVector( float &x, float &y, float &z ); + + /** + * Read 3 floats, using 10 bytes, where those floats comprise a vector + * Loses accuracy to about 3/10ths and only saves 2 bytes, so only use if accuracy is not important. + * @param x x + * @param y y + * @param z z + */ + bool ReadVector( float &x, float &y, float &z ); + /** + * Read a normalized quaternion in 6 bytes + 4 bits instead of 16 bytes. Slightly lossy. + * @param w w + * @param x x + * @param y y + * @param z z + */ + bool ReadNormQuat( float &w, float &x, float &y, float &z); + /** + * Read an orthogonal matrix from a quaternion, reading 3 components of the quaternion in 2 bytes each and extrapolatig the 4th. + * for 6 bytes instead of 36 + */ + bool ReadOrthMatrix( + float &m00, float &m01, float &m02, + float &m10, float &m11, float &m12, + float &m20, float &m21, float &m22 ); + /** + * Read the types you wrote with WriteCompressed + * @param output The read value + * @return true on success, false on not enough data to read + */ + bool ReadCompressed( double &output ); + /** + * Sets the read pointer back to the beginning of your data. + */ + void ResetReadPointer( void ); + /** + * Sets the write pointer back to the beginning of your data. + */ + void ResetWritePointer( void ); + /** + * This is good to call when you are done with the stream to make + * sure you didn't leave any data left over void + */ + void AssertStreamEmpty( void ); + /** + * print to the standard output the state of the stream bit by bit + */ + void PrintBits( void ) const; + + /** + * Ignore data we don't intend to read + * @param numberOfBits The number of bits to ignore + */ + void IgnoreBits( const int numberOfBits ); + + /** + * Move the write pointer to a position on the array. + * @param offset the offset from the start of the array. + * @attention + * Dangerous if you don't know what you are doing! + * + */ + void SetWriteOffset( const int offset ); + /** + * Returns the length in bits of the stream + */ + int GetNumberOfBitsUsed( void ) const; + /** + * Returns the length in bytes of the stream + */ + int GetNumberOfBytesUsed( void ) const; + /** + * Returns the number of bits into the stream that we have read + */ + int GetReadOffset( void ) const; + /** + * Returns the number of bits left in the stream that haven't been read + */ + int GetNumberOfUnreadBits( void ) const; + /** + * Makes a copy of the internal data for you Data will point to + * the stream. Returns the length in bits of the stream. Partial + * bytes are left aligned + * @param _data the resulting byte copy of the internal state. + */ + int CopyData( unsigned char** _data ) const; + /** + * Set the stream to some initial data. For internal use + * Partial bytes are left aligned + * @param input The data + * @param numberOfBits the number of bits set in the data buffer + */ + void SetData( const unsigned char* input, const int numberOfBits ); + /** + * Exposes the internal data. + * Partial bytes are left aligned. + * @return A pointer to the internal state + */ + unsigned char* GetData( void ) const; + /** + * Write numberToWrite bits from the input source Right aligned + * data means in the case of a partial byte, the bits are aligned + * from the right (bit 0) rather than the left (as in the normal + * internal representation) You would set this to true when + * writing user data, and false when copying bitstream data, such + * as writing one bitstream to another + * @param input The data + * @param numberOfBitsToWrite The number of bits to write + * @param rightAlignedBits if true data will be right aligned + */ + void WriteBits( const unsigned char* input, + int numberOfBitsToWrite, const bool rightAlignedBits = true ); + /** + * Align the bitstream to the byte boundary and then write the + * specified number of bits. This is faster than WriteBits but + * wastes the bits to do the alignment and requires you to call + * ReadAlignedBits at the corresponding read position. + * @param input The data + * @param numberOfBytesToWrite The size of data. + */ + void WriteAlignedBytes( const unsigned char* input, + const int numberOfBytesToWrite ); + /** + * Read bits, starting at the next aligned bits. Note that the + * modulus 8 starting offset of the sequence must be the same as + * was used with WriteBits. This will be a problem with packet + * coalescence unless you byte align the coalesced packets. + * @param output The byte array larger than @em numberOfBytesToRead + * @param numberOfBytesToRead The number of byte to read from the internal state + * @return true if there is enough byte. + */ + bool ReadAlignedBytes( unsigned char* output, + const int numberOfBytesToRead ); + /** + * Align the next write and/or read to a byte boundary. This can + * be used to 'waste' bits to byte align for efficiency reasons It + * can also be used to force coalesced bitstreams to start on byte + * boundaries so so WriteAlignedBits and ReadAlignedBits both + * calculate the same offset when aligning. + */ + void AlignWriteToByteBoundary( void ); + /** + * Align the next write and/or read to a byte boundary. This can + * be used to 'waste' bits to byte align for efficiency reasons It + * can also be used to force coalesced bitstreams to start on byte + * boundaries so so WriteAlignedBits and ReadAlignedBits both + * calculate the same offset when aligning. + */ + void AlignReadToByteBoundary( void ); + + /** + * Read numberOfBitsToRead bits to the output source + * alignBitsToRight should be set to true to convert internal + * bitstream data to userdata It should be false if you used + * WriteBits with rightAlignedBits false + * @param output The resulting bits array + * @param numberOfBitsToRead The number of bits to read + * @param alignsBitsToRight if true bits will be right aligned. + * @return true if there is enough bits to read + */ + bool ReadBits( unsigned char* output, int numberOfBitsToRead, + const bool alignBitsToRight = true ); + + /** + * --- Low level functions --- + * These are for when you want to deal + * with bits and don't care about type checking + * Write a 0 + */ + void Write0( void ); + /** + * --- Low level functions --- + * These are for when you want to deal + * with bits and don't care about type checking + * Write a 1 + */ + void Write1( void ); + /** + * --- Low level functions --- + * These are for when you want to deal + * with bits and don't care about type checking + * Reads 1 bit and returns true if that bit is 1 and false if it is 0 + */ + bool ReadBit( void ); + /** + * If we used the constructor version with copy data off, this + * makes sure it is set to on and the data pointed to is copied. + */ + void AssertCopyData( void ); + /** + * Use this if you pass a pointer copy to the constructor + * (_copyData==false) and want to overallocate to prevent + * reallocation + */ + void SetNumberOfBitsAllocated( const unsigned int lengthInBits ); + + private: + /** + * Assume the input source points to a native type, compress and write it. + */ + void WriteCompressed( const unsigned char* input, + const int size, const bool unsignedData ); + + /** + * Assume the input source points to a compressed native type. + * Decompress and read it. + */ + bool ReadCompressed( unsigned char* output, + const int size, const bool unsignedData ); + + /** + * Reallocates (if necessary) in preparation of writing + * numberOfBitsToWrite + */ + void AddBitsAndReallocate( const int numberOfBitsToWrite ); + + /** + * Number of bits currently used + */ + int numberOfBitsUsed; + /** + * Number of bits currently allocated + */ + int numberOfBitsAllocated; + /** + * Current readOffset + */ + int readOffset; + /** + * array of byte storing the data. Points to stackData or if is bigger than that then is allocated + */ + unsigned char *data; + /** + * true if the internal buffer is copy of the data passed to the + * constructor + */ + bool copyData; + + unsigned char stackData[BITSTREAM_STACK_ALLOCATION_SIZE]; + }; +} + +#endif + +#endif \ No newline at end of file diff --git a/raknet/CheckSum.cpp b/raknet/CheckSum.cpp new file mode 100644 index 0000000..91ecc3f --- /dev/null +++ b/raknet/CheckSum.cpp @@ -0,0 +1,97 @@ +/** +* @file +* @brief CheckSum implementation from http://www.flounder.com/checksum.htm +* +*/ +#include "CheckSum.h" + +/**************************************************************************** +* CheckSum::add +* Inputs: +* unsigned int d: word to add +* Result: void +* +* Effect: +* Adds the bytes of the unsigned int to the CheckSum +****************************************************************************/ + +void CheckSum::Add ( unsigned int value ) +{ + union + { + unsigned int value; + unsigned char bytes[ 4 ]; + } + + data; + data.value = value; + + for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ ) + Add ( data.bytes[ i ] ) + + ; +} // CheckSum::add(unsigned int) + +/**************************************************************************** +* CheckSum::add +* Inputs: +* unsigned short value: +* Result: void +* +* Effect: +* Adds the bytes of the unsigned short value to the CheckSum +****************************************************************************/ + +void CheckSum::Add ( unsigned short value ) +{ + union + { + unsigned short value; + unsigned char bytes[ 2 ]; + } + + data; + data.value = value; + + for ( unsigned int i = 0; i < sizeof( data.bytes ); i++ ) + Add ( data.bytes[ i ] ) + + ; +} // CheckSum::add(unsigned short) + +/**************************************************************************** +* CheckSum::add +* Inputs: +* unsigned char value: +* Result: void +* +* Effect: +* Adds the byte to the CheckSum +****************************************************************************/ + +void CheckSum::Add ( unsigned char value ) +{ + unsigned char cipher = (unsigned char)( value ^ ( r >> 8 ) ); + r = ( cipher + r ) * c1 + c2; + sum += cipher; +} // CheckSum::add(unsigned char) + + +/**************************************************************************** +* CheckSum::add +* Inputs: +* LPunsigned char b: pointer to byte array +* unsigned int length: count +* Result: void +* +* Effect: +* Adds the bytes to the CheckSum +****************************************************************************/ + +void CheckSum::Add ( unsigned char *b, unsigned int length ) +{ + for ( unsigned int i = 0; i < length; i++ ) + Add ( b[ i ] ) + + ; +} // CheckSum::add(LPunsigned char, unsigned int) diff --git a/raknet/CheckSum.h b/raknet/CheckSum.h new file mode 100644 index 0000000..1710fb8 --- /dev/null +++ b/raknet/CheckSum.h @@ -0,0 +1,52 @@ +/// \file +/// \brief \b [Internal] Generates and validates checksums +/// +/// \note I didn't write this, but took it from http://www.flounder.com/checksum.htm +/// + +#ifndef __CHECKSUM_H +#define __CHECKSUM_H + +/// Generates and validates checksums +class CheckSum +{ + +public: + + /// Default constructor + + CheckSum() + { + Clear(); + } + + void Clear() + { + sum = 0; + r = 55665; + c1 = 52845; + c2 = 22719; + } + + void Add ( unsigned int w ); + + + void Add ( unsigned short w ); + + void Add ( unsigned char* b, unsigned int length ); + + void Add ( unsigned char b ); + + unsigned int Get () + { + return sum; + } + +protected: + unsigned short r; + unsigned short c1; + unsigned short c2; + unsigned int sum; +}; + +#endif diff --git a/raknet/ClientContextStruct.h b/raknet/ClientContextStruct.h new file mode 100644 index 0000000..805e4f7 --- /dev/null +++ b/raknet/ClientContextStruct.h @@ -0,0 +1,50 @@ +/// \file +/// \brief \b [Internal] Depreciated, back from when I supported IO Completion ports. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __CLIENT_CONTEXT_STRUCT_H +#define __CLIENT_CONTEXT_STRUCT_H + +#ifdef _COMPATIBILITY_1 +#elif defined(_WIN32) +//#include +#endif +#include "NetworkTypes.h" +#include "MTUSize.h" + +class RakPeer; + +#ifdef __USE_IO_COMPLETION_PORTS + +struct ClientContextStruct +{ + HANDLE handle; // The socket, also used as a file handle +}; + +struct ExtendedOverlappedStruct +{ + OVERLAPPED overlapped; + char data[ MAXIMUM_MTU_SIZE ]; // Used to hold data to send + int length; // Length of the actual data to send, always under MAXIMUM_MTU_SIZE + unsigned int binaryAddress; + unsigned short port; + RakPeer *rakPeer; + bool read; // Set to true for reads, false for writes +}; + +#endif + +#endif diff --git a/raknet/CommandParserInterface.cpp b/raknet/CommandParserInterface.cpp new file mode 100644 index 0000000..0b681dd --- /dev/null +++ b/raknet/CommandParserInterface.cpp @@ -0,0 +1,174 @@ +#include "CommandParserInterface.h" +#include "TransportInterface.h" +#include +#include +#include +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib +// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly +#include +#else +#include +#include +#include +#endif + +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(_stricmp) +#define _stricmp strcasecmp +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +const unsigned char CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS=255; + +int RegisteredCommandComp( const char* const & key, const RegisteredCommand &data ) +{ + return _stricmp(key,data.command); +} + +CommandParserInterface::CommandParserInterface() {} +CommandParserInterface::~CommandParserInterface() {} + +void CommandParserInterface::ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength) +{ + unsigned strIndex, parameterListIndex; + unsigned strLen; + bool replaceDelineator=true; + + strLen = (unsigned) strlen(str); + + // Replace every instance of delineator, \n, \r with 0 + for (strIndex=0; strIndex < strLen; strIndex++) + { + if (str[strIndex]==delineator && replaceDelineator) + str[strIndex]=0; + + if (str[strIndex]=='\n' || str[strIndex]=='\r') + str[strIndex]=0; + + if (str[strIndex]==delineatorToggle) + { + str[strIndex]=0; + replaceDelineator=!replaceDelineator; + } + } + + // Fill up parameterList starting at each non-0 + for (strIndex=0, parameterListIndex=0; strIndex < strLen; ) + { + if (str[strIndex]!=0) + { + parameterList[parameterListIndex]=str+strIndex; + parameterListIndex++; + assert(parameterListIndex < parameterListLength); + if (parameterListIndex >= parameterListLength) + break; + + strIndex++; + while (str[strIndex]!=0 && strIndex < strLen) + strIndex++; + } + else + strIndex++; + } + + parameterList[parameterListIndex]=0; + *numParameters=parameterListIndex; +} +void CommandParserInterface::SendCommandList(TransportInterface *transport, PlayerID playerId) +{ + unsigned i; + if (commandList.Size()) + { + for (i=0; i < commandList.Size(); i++) + { + transport->Send(playerId, "%s", commandList[i].command); + if (i < commandList.Size()-1) + transport->Send(playerId, ", "); + } + transport->Send(playerId, "\r\n"); + } + else + transport->Send(playerId, "No registered commands\r\n"); +} +void CommandParserInterface::RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp) +{ + RegisteredCommand rc; + rc.command=command; + rc.commandHelp=commandHelp; + rc.parameterCount=parameterCount; + commandList.Insert( command, rc); +} +bool CommandParserInterface::GetRegisteredCommand(const char *command, RegisteredCommand *rc) +{ + bool objectExists; + unsigned index; + index=commandList.GetIndexFromKey(command, &objectExists); + if (objectExists) + *rc=commandList[index]; + return objectExists; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void CommandParserInterface::OnTransportChange(TransportInterface *transport) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void CommandParserInterface::OnNewIncomingConnection(PlayerID playerId, TransportInterface *transport) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void CommandParserInterface::OnConnectionLost(PlayerID playerId, TransportInterface *transport) +{ +} +void CommandParserInterface::ReturnResult(bool res, const char *command,TransportInterface *transport, PlayerID playerId) +{ + if (res) + transport->Send(playerId, "%s returned true.\r\n", command); + else + transport->Send(playerId, "%s returned false.\r\n", command); +} +void CommandParserInterface::ReturnResult(int res, const char *command,TransportInterface *transport, PlayerID playerId) +{ + transport->Send(playerId, "%s returned %i.\r\n", command, res); +} +void CommandParserInterface::ReturnResult(const char *command, TransportInterface *transport, PlayerID playerId) +{ + transport->Send(playerId, "Successfully called %s.\r\n", command); +} +void CommandParserInterface::ReturnResult(char *res, const char *command, TransportInterface *transport, PlayerID playerId) +{ + transport->Send(playerId, "%s returned %s.\r\n", command, res); +} +void CommandParserInterface::ReturnResult(PlayerID res, const char *command, TransportInterface *transport, PlayerID playerId) +{ +#if !defined(_COMPATIBILITY_1) + in_addr in; + in.s_addr = playerId.binaryAddress; + inet_ntoa( in ); + transport->Send(playerId, "%s returned %s %i:%i\r\n", command,inet_ntoa( in ),res.binaryAddress, res.port); +#else + transport->Send(playerId, "%s returned %i:%i\r\n", command,res.binaryAddress, res.port); +#endif +} +PlayerID CommandParserInterface::IntegersToPlayerID(int binaryAddress, int port) +{ + PlayerID playerId; + playerId.binaryAddress=binaryAddress; + playerId.port=(unsigned short)port; + return playerId; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + diff --git a/raknet/CommandParserInterface.h b/raknet/CommandParserInterface.h new file mode 100644 index 0000000..69724a5 --- /dev/null +++ b/raknet/CommandParserInterface.h @@ -0,0 +1,148 @@ +/// \file +/// \brief Contains CommandParserInterface , from which you derive custom command parsers +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __COMMAND_PARSER_INTERFACE +#define __COMMAND_PARSER_INTERFACE + +#include "NetworkTypes.h" +#include "DS_OrderedList.h" +#include "Export.h" + +class TransportInterface; + +/// \internal +/// Contains the information related to one command registered with RegisterCommand() +/// Implemented so I can have an automatic help system via SendCommandList() +struct RAK_DLL_EXPORT RegisteredCommand +{ + const char *command; + const char *commandHelp; + unsigned char parameterCount; +}; + +/// List of commands registered with RegisterCommand() +int RAK_DLL_EXPORT RegisteredCommandComp( const char* const & key, const RegisteredCommand &data ); + +/// CommandParserInterface provides a set of functions and interfaces that plug into the ConsoleServer class. +/// Each CommandParserInterface works at the same time as other interfaces in the system. +/// \brief The interface used by command parsers. +class RAK_DLL_EXPORT CommandParserInterface +{ +public: + CommandParserInterface(); + virtual ~CommandParserInterface(); + + /// You are responsible for overriding this function and returning a static string, which will identifier your parser. + /// This should return a static string + /// \return The name that you return. + virtual char *GetName(void) const=0; + + /// A callback for when \a playerId has connected to us. + /// \param[in] playerId The player that has connected. + /// \param[in] transport The transport interface that sent us this information. Can be used to send messages to this or other players. + virtual void OnNewIncomingConnection(PlayerID playerId, TransportInterface *transport); + + /// A callback for when \a playerId has disconnected, either gracefully or forcefully + /// \param[in] playerId The player that has disconnected. + /// \param[in] transport The transport interface that sent us this information. + virtual void OnConnectionLost(PlayerID playerId, TransportInterface *transport); + + /// A callback for when you are expected to send a brief description of your parser to \a playerId + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that requested help. + virtual void SendHelp(TransportInterface *transport, PlayerID playerId)=0; + + /// Given \a command with parameters \a parameterList , do whatever processing you wish. + /// \param[in] command The command to process + /// \param[in] numParameters How many parameters were passed along with the command + /// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on. + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that sent this command. + /// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing + virtual bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString)=0; + + /// This is called every time transport interface is registered. If you want to save a copy of the TransportInterface pointer + /// This is the place to do it + /// \param[in] transport The new TransportInterface + virtual void OnTransportChange(TransportInterface *transport); + + /// \internal + /// Scan commandList and return the associated array + /// \param[in] command The string to find + /// \param[out] rc Contains the result of this operation + /// \return True if we found the command, false otherwise + virtual bool GetRegisteredCommand(const char *command, RegisteredCommand *rc); + + /// \internal + /// Goes through str, replacing the delineating character with 0's. + /// \param[in] str The string sent by the transport interface + /// \param[in] delineator The character to scan for to use as a delineator + /// \param[in] delineatorToggle When encountered the delineator replacement is toggled on and off + /// \param[out] numParameters How many pointers were written to \a parameterList + /// \param[out] parameterList An array of pointers to characters. Will hold pointers to locations inside \a str + /// \param[in] parameterListLength How big the \a parameterList array is + static void ParseConsoleString(char *str, const char delineator, unsigned char delineatorToggle, unsigned *numParameters, char **parameterList, unsigned parameterListLength); + + /// \internal + /// Goes through the variable commandList and sends the command portion of each struct + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player to write to + virtual void SendCommandList(TransportInterface *transport, PlayerID playerId); + + static const unsigned char VARIABLE_NUMBER_OF_PARAMETERS; + +protected: + // Currently only takes static strings - doesn't make a copy of what you pass. + // parameterCount is the number of parameters that the sender has to include with the command. + // Pass 255 to parameterCount to indicate variable number of parameters + + /// Registers a command. + /// \param[in] parameterCount How many parameters your command requires. If you want to accept a variable number of commands, pass CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS + /// \param[in] command A pointer to a STATIC string that has your command. I keep a copy of the pointer here so don't deallocate the string. + /// \param[in] commandHelp A pointer to a STATIC string that has the help information for your command. I keep a copy of the pointer here so don't deallocate the string. + virtual void RegisterCommand(unsigned char parameterCount, const char *command, const char *commandHelp); + + /// Just writes a string to the remote system based on the result ( \a res )of your operation + /// This is not necessary to call, but makes it easier to return results of function calls + /// \param[in] res The result to write + /// \param[in] command The command that this result came from + /// \param[in] transport The transport interface that will be written to + /// \param[in] playerId The player this result will be sent to + virtual void ReturnResult(bool res, const char *command, TransportInterface *transport, PlayerID playerId); + virtual void ReturnResult(char *res, const char *command, TransportInterface *transport, PlayerID playerId); + virtual void ReturnResult(PlayerID res, const char *command, TransportInterface *transport, PlayerID playerId); + virtual void ReturnResult(int res, const char *command,TransportInterface *transport, PlayerID playerId); + + /// Just writes a string to the remote system when you are calling a function that has no return value + /// This is not necessary to call, but makes it easier to return results of function calls + /// \param[in] res The result to write + /// \param[in] command The command that this result came from + /// \param[in] transport The transport interface that will be written to + /// \param[in] playerId The player this result will be sent to + virtual void ReturnResult(const char *command,TransportInterface *transport, PlayerID playerId); + + + /// Since there's no way to specify a playerID directly, the user needs to specify both the binary address and port. + /// Given those parameters, this returns the corresponding PlayerID + /// \param[in] binaryAddress The binaryAddress portion of PlayerID + /// \param[in] port The port portion of PlayerID + PlayerID IntegersToPlayerID(int binaryAddress, int port); + + DataStructures::OrderedList commandList; +}; + +#endif diff --git a/raknet/ConnectionGraph.cpp b/raknet/ConnectionGraph.cpp new file mode 100644 index 0000000..c4ba0e1 --- /dev/null +++ b/raknet/ConnectionGraph.cpp @@ -0,0 +1,623 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "ConnectionGraph.h" +#include "RakPeerInterface.h" +#include "PacketEnumerations.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "GetTime.h" +#include +#include "RakAssert.h" +#include "SHA1.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +extern Packet *AllocPacket(unsigned dataSize); + +static const int connectionGraphChannel=31; + +ConnectionGraph::PlayerIdAndGroupId::PlayerIdAndGroupId() +{ + +} +ConnectionGraph::PlayerIdAndGroupId::~PlayerIdAndGroupId() +{ + +} +ConnectionGraph::PlayerIdAndGroupId::PlayerIdAndGroupId(PlayerID playerId, ConnectionGraphGroupID groupID) +{ + this->playerId=playerId; + this->groupId=groupId; +} +bool ConnectionGraph::PlayerIdAndGroupId::operator==( const ConnectionGraph::PlayerIdAndGroupId& right ) const +{ + return playerId==right.playerId; +} +bool ConnectionGraph::PlayerIdAndGroupId::operator!=( const ConnectionGraph::PlayerIdAndGroupId& right ) const +{ + return playerId!=right.playerId; +} +bool ConnectionGraph::PlayerIdAndGroupId::operator > ( const ConnectionGraph::PlayerIdAndGroupId& right ) const +{ + return playerId>right.playerId; +} +bool ConnectionGraph::PlayerIdAndGroupId::operator < ( const ConnectionGraph::PlayerIdAndGroupId& right ) const +{ + return playerId::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); + + subscribedGroups.Insert(0,0); +} + +ConnectionGraph::~ConnectionGraph() +{ + if (pw) + delete [] pw; +} + +void ConnectionGraph::SetPassword(const char *password) +{ + if (pw) + { + delete [] pw; + pw=0; + } + + if (password && password[0]) + { + assert(strlen(password)<256); + pw=new char [strlen(password)+1]; + strcpy(pw, password); + } +} +DataStructures::WeightedGraph *ConnectionGraph::GetGraph(void) +{ + return &graph; +} +void ConnectionGraph::SetAutoAddNewConnections(bool autoAdd) +{ + autoAddNewConnections=autoAdd; +} + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void ConnectionGraph::OnDisconnect(RakPeerInterface *peer) +{ + graph.Clear(); + participantList.Clear(); +// forceBroadcastTime=0; +} +void ConnectionGraph::Update(RakPeerInterface *peer) +{ +// RakNetTime time = RakNet::GetTime(); + + // If the time is past the next weight update time, then refresh all pings of all connected participants and send these out if substantially different. +// if (forceBroadcastTime && time > forceBroadcastTime) +// { +// DataStructures::OrderedList none; + // BroadcastGraphUpdate(none, peer); +// forceBroadcastTime=0; +// } +} +PluginReceiveResult ConnectionGraph::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + switch (packet->data[0]) + { + case ID_NEW_INCOMING_CONNECTION: + OnNewIncomingConnection(peer, packet); + return RR_CONTINUE_PROCESSING; + case ID_CONNECTION_REQUEST_ACCEPTED: + OnConnectionRequestAccepted(peer, packet); + return RR_CONTINUE_PROCESSING; + case ID_CONNECTION_GRAPH_REQUEST: + OnConnectionGraphRequest(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_CONNECTION_GRAPH_REPLY: + OnConnectionGraphReply(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_CONNECTION_GRAPH_UPDATE: + OnConnectionGraphUpdate(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_CONNECTION_GRAPH_NEW_CONNECTION: + OnNewConnection(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + // Remove connection lost + case ID_CONNECTION_GRAPH_CONNECTION_LOST: + case ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION: + if (OnConnectionLost(peer, packet, packet->data[0])) + { + if (packet->data[0]==ID_CONNECTION_GRAPH_CONNECTION_LOST) + packet->data[0]=ID_REMOTE_CONNECTION_LOST; + else + packet->data[0]=ID_REMOTE_DISCONNECTION_NOTIFICATION; + return RR_CONTINUE_PROCESSING; // Return this packet to the user + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; + // Local connection lost + case ID_CONNECTION_LOST: + case ID_DISCONNECTION_NOTIFICATION: + { + unsigned char packetId; + // Change toe remote connection lost and relay the message + if (packet->data[0]==ID_CONNECTION_LOST) + packetId=ID_CONNECTION_GRAPH_CONNECTION_LOST; + else + packetId=ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION; + HandleDroppedConnection(peer, packet->playerId, packetId); + } + } + + return RR_CONTINUE_PROCESSING; +} +void ConnectionGraph::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + HandleDroppedConnection(peer, playerId, ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION); +} + +void ConnectionGraph::HandleDroppedConnection(RakPeerInterface *peer, PlayerID playerId, unsigned char packetId) +{ + assert(peer); + RemoveParticipant(playerId); + DataStructures::OrderedList ignoreList; + RemoveAndRelayConnection(ignoreList, packetId, playerId, peer->GetExternalID(playerId), peer); +} + +void ConnectionGraph::OnNewIncomingConnection(RakPeerInterface *peer, Packet *packet) +{ + if (autoAddNewConnections==false) + return; + + // 0 is the default groupId + AddNewConnection(peer, packet->playerId, 0); +} +void ConnectionGraph::OnConnectionRequestAccepted(RakPeerInterface *peer, Packet *packet) +{ + if (autoAddNewConnections==false) + return; + + RequestConnectionGraph(peer, packet->playerId); + + // 0 is the default groupId + AddNewConnection(peer, packet->playerId, 0); +} +void ConnectionGraph::SetGroupId(ConnectionGraphGroupID groupId) +{ + myGroupId=groupId; +} +void ConnectionGraph::AddNewConnection(RakPeerInterface *peer, PlayerID playerId, ConnectionGraphGroupID groupId) +{ + if (autoAddNewConnections==false && subscribedGroups.HasData(groupId)==false) + return; + + DataStructures::OrderedList ignoreList; + + PlayerIdAndGroupId first, second; + first.playerId=playerId; + first.groupId=groupId; + second.playerId=peer->GetExternalID(playerId); + second.groupId=myGroupId; + + AddAndRelayConnection(ignoreList, first, second, (unsigned short)peer->GetAveragePing(playerId), peer); +} +void ConnectionGraph::SubscribeToGroup(ConnectionGraphGroupID groupId) +{ + subscribedGroups.Insert(groupId, groupId); +} +void ConnectionGraph::UnsubscribeFromGroup(ConnectionGraphGroupID groupId) +{ + subscribedGroups.Remove(groupId); +} +void ConnectionGraph::RequestConnectionGraph(RakPeerInterface *peer, PlayerID playerId) +{ + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_CONNECTION_GRAPH_REQUEST); + stringCompressor->EncodeString(pw,256,&outBitstream); + peer->Send(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, playerId, false); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("ID_CONNECTION_GRAPH_REQUEST from %i to %i\n", peer->GetInternalID().port, playerId.port); +#endif +} +void ConnectionGraph::OnConnectionGraphRequest(RakPeerInterface *peer, Packet *packet) +{ + char password[256]; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + stringCompressor->DecodeString(password,256,&inBitstream); + if (pw && pw[0] && strcmp(pw, password)!=0) + return; + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("ID_CONNECTION_GRAPH_REPLY "); +#endif + + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_CONNECTION_GRAPH_REPLY); + stringCompressor->EncodeString(pw,256,&outBitstream); + SerializeWeightedGraph(&outBitstream, graph); + peer->Send(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, packet->playerId, false); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("from %i to %i\n", peer->GetInternalID().port, packet->playerId.port); +#endif + + // Add packet->playerId to the participant list if it is not already there + AddParticipant(packet->playerId); +} +void ConnectionGraph::OnConnectionGraphReply(RakPeerInterface *peer, Packet *packet) +{ + unsigned char password[256]; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + stringCompressor->DecodeString((char*)password,256,&inBitstream); + if (pw && pw[0] && strcmp(pw, (const char*)password)!=0) + return; + + // Serialize the weighted graph and send it to them + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_CONNECTION_GRAPH_UPDATE); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("ID_CONNECTION_GRAPH_UPDATE "); +#endif + + // Send our current graph to the sender + SerializeWeightedGraph(&outBitstream, graph); + + + // Write the systems that have processed this graph so we don't resend to these systems + outBitstream.Write((unsigned short) 1); + outBitstream.Write(peer->GetExternalID(packet->playerId)); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("from %i to %i\n", peer->GetInternalID().port, packet->playerId.port); +#endif + + peer->Send(&outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, packet->playerId, false); + + // Add packet->playerId to the participant list if it is not already there + AddParticipant(packet->playerId); + + if (DeserializeWeightedGraph(&inBitstream, peer)==false) + return; + + // Forward the updated graph to all current participants + DataStructures::OrderedList ignoreList; + ignoreList.Insert(packet->playerId,packet->playerId); + BroadcastGraphUpdate(ignoreList, peer); +} +void ConnectionGraph::OnConnectionGraphUpdate(RakPeerInterface *peer, Packet *packet) +{ + // Only accept from participants + if (participantList.HasData(packet->playerId)==false) + return; + + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + + if (DeserializeWeightedGraph(&inBitstream, peer)==false) + return; + + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + + // Forward the updated graph to all participants. + if (ignoreList.HasData(packet->playerId)==false) + ignoreList.Insert(packet->playerId,packet->playerId); + BroadcastGraphUpdate(ignoreList, peer); +} +void ConnectionGraph::OnNewConnection(RakPeerInterface *peer, Packet *packet) +{ + // Only accept from participants + if (participantList.HasData(packet->playerId)==false) + return; + + PlayerIdAndGroupId node1, node2; + unsigned short ping; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + inBitstream.Read(node1.playerId); + inBitstream.Read(node1.groupId); + inBitstream.Read(node2.playerId); + inBitstream.Read(node2.groupId); + if (inBitstream.Read(ping)==false) + return; + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + if (ignoreList.HasData(packet->playerId)==false) + ignoreList.Insert(packet->playerId,packet->playerId); + AddAndRelayConnection(ignoreList, node1, node2, ping, peer); +} +bool ConnectionGraph::OnConnectionLost(RakPeerInterface *peer, Packet *packet, unsigned char packetId) +{ + // Only accept from participants + if (participantList.HasData(packet->playerId)==false) + return false; + + PlayerID node1, node2; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + // This is correct - group IDs are not written for removal, only addition. + inBitstream.Read(node1); + if (inBitstream.Read(node2)==false) + return false; + DataStructures::OrderedList ignoreList; + DeserializeIgnoreList(ignoreList, &inBitstream); + if (ignoreList.HasData(packet->playerId)==false) + ignoreList.Insert(packet->playerId, packet->playerId); + + return RemoveAndRelayConnection(ignoreList, packetId, node1, node2, peer); +} +bool ConnectionGraph::DeserializeIgnoreList(DataStructures::OrderedList &ignoreList, RakNet::BitStream *inBitstream ) +{ + unsigned short count; + PlayerID temp; + unsigned i; + inBitstream->Read(count); + for (i=0; i < count; i++) + { + if (inBitstream->Read(temp)==false) + { + assert(0); + return false; + } + ignoreList.Insert(temp,temp); + } + return true; +} +void ConnectionGraph::SerializeWeightedGraph(RakNet::BitStream *out, const DataStructures::WeightedGraph &g) const +{ + unsigned nodeIndex, connectionIndex; + unsigned countOffset, oldOffset; + unsigned short count; + PlayerIdAndGroupId node1, node2; + unsigned short weight; + out->WriteCompressed(g.GetNodeCount()); + for (nodeIndex=0; nodeIndex < g.GetNodeCount(); nodeIndex++) + { + // Write the node + node1=g.GetNodeAtIndex(nodeIndex); +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("[%i] ", node1.playerId.port); +#endif + out->Write(node1.playerId); + out->Write(node1.groupId); + + // Write the adjacency list count + count=(unsigned short)g.GetConnectionCount(nodeIndex); + out->AlignWriteToByteBoundary(); + countOffset=out->GetWriteOffset(); + out->Write(count); + count=0; + for (connectionIndex=0; connectionIndex < g.GetConnectionCount(nodeIndex); connectionIndex++) + { + g.GetConnectionAtIndex(nodeIndex, connectionIndex, node2, weight); + // For efficiencies' sake, only serialize the upper half of the connection pairs + if (node2 > node1) + { + count++; + out->Write(node2.playerId); + out->Write(node2.groupId); + out->Write(weight); + +#ifdef _CONNECTION_GRAPH_DEBUG_PRINT + printf("(%i) ", node2.playerId.port); +#endif + } + } + + // Go back and change how many elements were written + oldOffset=out->GetWriteOffset(); + out->SetWriteOffset(countOffset); + out->Write(count); + out->SetWriteOffset(oldOffset); + } +} +bool ConnectionGraph::DeserializeWeightedGraph(RakNet::BitStream *inBitstream, RakPeerInterface *peer) +{ + unsigned nodeCount, nodeIndex, connectionIndex; + unsigned short connectionCount; + PlayerIdAndGroupId node, connection; + bool anyConnectionsNew=false; + unsigned short weight; + inBitstream->ReadCompressed(nodeCount); + for (nodeIndex=0; nodeIndex < nodeCount; nodeIndex++) + { + inBitstream->Read(node.playerId); + inBitstream->Read(node.groupId); + + inBitstream->AlignReadToByteBoundary(); + if (inBitstream->Read(connectionCount)==false) + { + assert(0); + return false; + } + for (connectionIndex=0; connectionIndex < connectionCount; connectionIndex++) + { + inBitstream->Read(connection.playerId); + inBitstream->Read(connection.groupId); + if (inBitstream->Read(weight)==false) + { + assert(0); + return false; + } + if (subscribedGroups.HasData(connection.groupId)==false || + subscribedGroups.HasData(node.groupId)==false) + continue; + RakAssert(node.playerId!=UNASSIGNED_PLAYER_ID); + RakAssert(connection.playerId!=UNASSIGNED_PLAYER_ID); + if (IsNewRemoteConnection(node,connection,peer)) + NotifyUserOfRemoteConnection(node,connection,weight,peer); + + if (graph.HasConnection(node,connection)==false) + anyConnectionsNew=true; + + graph.AddConnection(node,connection,weight); + } + } + return anyConnectionsNew; +} +void ConnectionGraph::RemoveParticipant(PlayerID playerId) +{ + unsigned index; + bool objectExists; + index=participantList.GetIndexFromKey(playerId, &objectExists); + if (objectExists) + participantList.RemoveAtIndex(index); +} + +void ConnectionGraph::AddParticipant(PlayerID playerId) +{ + if (participantList.HasData(playerId)==false) + participantList.Insert(playerId,playerId); +} + +void ConnectionGraph::AddAndRelayConnection(DataStructures::OrderedList &ignoreList, const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2, unsigned short ping, RakPeerInterface *peer) +{ + if (graph.HasConnection(conn1,conn2)) + return; + + if (ping==65535) + ping=0; + assert(conn1.playerId!=UNASSIGNED_PLAYER_ID); + assert(conn2.playerId!=UNASSIGNED_PLAYER_ID); + + graph.AddConnection(conn1,conn2,ping); + + if (IsNewRemoteConnection(conn1,conn2,peer)) + { + NotifyUserOfRemoteConnection(conn1,conn2,ping,peer); + return; + } + + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_CONNECTION_GRAPH_NEW_CONNECTION); + outBitstream.Write(conn1.playerId); + outBitstream.Write(conn1.groupId); + outBitstream.Write(conn2.playerId); + outBitstream.Write(conn2.groupId); + outBitstream.Write(ping); + if (ignoreList.HasData(conn2.playerId)==false) + ignoreList.Insert(conn2.playerId,conn2.playerId); + if (ignoreList.HasData(conn1.playerId)==false) + ignoreList.Insert(conn1.playerId,conn1.playerId); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); +} +bool ConnectionGraph::RemoveAndRelayConnection(DataStructures::OrderedList &ignoreList, unsigned char packetId, const PlayerID node1, const PlayerID node2, RakPeerInterface *peer) +{ + PlayerIdAndGroupId n1, n2; + n1.playerId=node1; + n2.playerId=node2; + if (graph.HasConnection(n1,n2)==false) + return false; + graph.RemoveConnection(n1,n2); + + RakNet::BitStream outBitstream; + outBitstream.Write(packetId); + outBitstream.Write(node1); + outBitstream.Write(node2); + + if (ignoreList.HasData(node1)==false) + ignoreList.Insert(node1,node1); + if (ignoreList.HasData(node2)==false) + ignoreList.Insert(node2,node2); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); + + return true; +} + +void ConnectionGraph::BroadcastGraphUpdate(DataStructures::OrderedList &ignoreList, RakPeerInterface *peer) +{ + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_CONNECTION_GRAPH_UPDATE); + SerializeWeightedGraph(&outBitstream, graph); + SerializeIgnoreListAndBroadcast(&outBitstream, ignoreList, peer); +} +void ConnectionGraph::SerializeIgnoreListAndBroadcast(RakNet::BitStream *outBitstream, DataStructures::OrderedList &ignoreList, RakPeerInterface *peer) +{ + DataStructures::List sendList; + unsigned i; + for (i=0; i < participantList.Size(); i++) + { + if (ignoreList.HasData(participantList[i])==false) + sendList.Insert(participantList[i]); + } + if (sendList.Size()==0) + return; + + PlayerID self = peer->GetExternalID(sendList[0]); + if (ignoreList.HasData(self)==false) + ignoreList.Insert(self,self); + outBitstream->Write((unsigned short) (ignoreList.Size()+sendList.Size())); + for (i=0; i < ignoreList.Size(); i++) + outBitstream->Write(ignoreList[i]); + for (i=0; i < sendList.Size(); i++) + outBitstream->Write(sendList[i]); + + for (i=0; i < sendList.Size(); i++) + { + peer->Send(outBitstream, LOW_PRIORITY, RELIABLE_ORDERED, connectionGraphChannel, sendList[i], false); + } +} +bool ConnectionGraph::IsNewRemoteConnection(const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2,RakPeerInterface *peer) +{ + if (graph.HasConnection(conn1,conn2)==false && + subscribedGroups.HasData(conn1.groupId) && + subscribedGroups.HasData(conn2.groupId) && + (peer->GetIndexFromPlayerID(conn1.playerId)==-1 || peer->GetIndexFromPlayerID(conn2.playerId)==-1)) + { + PlayerID externalId1, externalId2; + externalId1=peer->GetExternalID(conn1.playerId); + externalId2=peer->GetExternalID(conn2.playerId); + return (externalId1!=conn1.playerId && externalId1!=conn2.playerId && + externalId2!=conn1.playerId && externalId2!=conn2.playerId); + } + return false; +} +void ConnectionGraph::NotifyUserOfRemoteConnection(const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2,unsigned short ping, RakPeerInterface *peer) +{ + // Create a packet to tell the user of this event + static const int length=sizeof(MessageID) + (sizeof(PlayerID) + sizeof(ConnectionGraphGroupID)) * 2 + sizeof(unsigned short); + Packet *p = AllocPacket(length); + RakNet::BitStream b(p->data, length, false); + p->bitSize=p->length*8; + b.SetWriteOffset(0); + b.Write((MessageID)ID_REMOTE_NEW_INCOMING_CONNECTION); + b.Write(conn1.playerId); + b.Write(conn1.groupId); + b.Write(conn2.playerId); + b.Write(conn2.groupId); + b.Write(ping); + peer->PushBackPacket(p, false); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/ConnectionGraph.h b/raknet/ConnectionGraph.h new file mode 100644 index 0000000..b11f263 --- /dev/null +++ b/raknet/ConnectionGraph.h @@ -0,0 +1,165 @@ +/// \file +/// \brief Connection graph plugin. This maintains a graph of connections for the entire network, so every peer knows about every other peer. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __CONNECTION_GRAPH_H +#define __CONNECTION_GRAPH_H + +class RakPeerInterface; +#include "NetworkTypes.h" +#include "PluginInterface.h" +#include "DS_List.h" +#include "DS_WeightedGraph.h" +#include "GetTime.h" +#include "Export.h" + +// If you need more than 255 groups just change this typedef +typedef unsigned char ConnectionGraphGroupID; + +/// \defgroup CONNECTION_GRAPH_GROUP ConnectionGraph +/// \ingroup PLUGINS_GROUP + +/// \ingroup CONNECTION_GRAPH_GROUP +/// \brief A connection graph. Each peer will know about all other peers. +class RAK_DLL_EXPORT ConnectionGraph : public PluginInterface +{ +public: + ConnectionGraph(); + ~ConnectionGraph(); + + /// A node in the connection graph + struct RAK_DLL_EXPORT PlayerIdAndGroupId + { + PlayerIdAndGroupId(); + ~PlayerIdAndGroupId(); + PlayerIdAndGroupId(PlayerID playerId, ConnectionGraphGroupID groupID); + + PlayerID playerId; + ConnectionGraphGroupID groupId; + + bool operator==( const PlayerIdAndGroupId& right ) const; + bool operator!=( const PlayerIdAndGroupId& right ) const; + bool operator > ( const PlayerIdAndGroupId& right ) const; + bool operator < ( const PlayerIdAndGroupId& right ) const; + }; + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// Plaintext encoding of the password, or 0 for none. If you use a password, use secure connections + void SetPassword(const char *password); + + /// Returns the connection graph + /// \return The connection graph, stored as map of adjacency lists + DataStructures::WeightedGraph *GetGraph(void); + + /// Automatically add new connections to the connection graph + /// Defaults to true + /// If true, then the system will automatically add all new connections for you, assigning groupId 0 to these systems. + /// If you want more control, you should call SetAutoAddNewConnections(false); + /// When false, it is up to you to call RequestConnectionGraph and AddNewConnection to complete the graph + /// However, this way you can choose which nodes are on the graph for this system and can assign groupIds to those nodes + /// \param[in] autoAdd true to automatically add new connections to the connection graph. False to not do so. + void SetAutoAddNewConnections(bool autoAdd); + + /// Requests the connection graph from another system + /// Only necessary to call if SetAutoAddNewConnections(false) is called. + /// You should call this sometime after getting ID_CONNECTION_REQUEST_ACCEPTED and \a playerId is or should be a node on the connection graph + /// \param[in] peer The instance of RakPeer to send through + /// \param[in] playerId The system to send to + void RequestConnectionGraph(RakPeerInterface *peer, PlayerID playerId); + + /// Adds a new connection to the connection graph from this system to the specified system. Also assigns a group identifier for that system + /// Only used and valid when SetAutoAddNewConnections(false) is called. + /// Call this for this system sometime after getting ID_NEW_INCOMING_CONNECTION or ID_CONNECTION_REQUEST_ACCEPTED for systems that contain a connection graph + /// Groups are sets of one or more nodes in the total graph + /// We only add to the graph groups which we subscribe to + /// \param[in] peer The instance of RakPeer to send through + /// \param[in] playerId The system that is connected to us. + /// \param[in] groupId Just a number representing a group. Important: 0 is reserved to mean unassigned group ID and is assigned to all systems added with SetAutoAddNewConnections(true) + void AddNewConnection(RakPeerInterface *peer, PlayerID playerId, ConnectionGraphGroupID groupId); + + /// Sets our own group ID + /// Only used and valid when SetAutoAddNewConnections(false) is called. + /// Defaults to 0 + /// \param[in] groupId Our group ID + void SetGroupId(ConnectionGraphGroupID groupId); + + /// Allows adding to the connection graph nodes with this groupId. + /// By default, you subscribe to group 0, which are all systems automatically added with SetAutoAddNewConnections(true) + /// Calling this does not add nodes which were previously rejected due to an unsubscribed group - it only allows addition of nodes after the fact + /// \param[in] groupId Just a number representing a group. 0 is reserved to mean unassigned group ID, automatically added with SetAutoAddNewConnections(true) + void SubscribeToGroup(ConnectionGraphGroupID groupId); + + /// Disables addition of graph nodes with this groupId + /// Calling this does not add remove nodes with this groupId which are already present in the graph. It only disables addition of nodes after the fact + /// \param[in] groupId Just a number representing a group. 0 is reserved to mean unassigned group ID, automatically added with SetAutoAddNewConnections(true) + void UnsubscribeFromGroup(ConnectionGraphGroupID groupId); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + /// \internal + virtual void OnDisconnect(RakPeerInterface *peer); + /// \internal + virtual void Update(RakPeerInterface *peer); + /// \internal + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + /// \internal + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + +protected: + void HandleDroppedConnection(RakPeerInterface *peer, PlayerID playerId, unsigned char packetId); + void DeleteFromPeerList(PlayerID playerId); + + void OnNewIncomingConnection(RakPeerInterface *peer, Packet *packet); + void OnConnectionRequestAccepted(RakPeerInterface *peer, Packet *packet); + void OnConnectionGraphRequest(RakPeerInterface *peer, Packet *packet); + void OnConnectionGraphReply(RakPeerInterface *peer, Packet *packet); + void OnConnectionGraphUpdate(RakPeerInterface *peer, Packet *packet); + void OnNewConnection(RakPeerInterface *peer, Packet *packet); + bool OnConnectionLost(RakPeerInterface *peer, Packet *packet, unsigned char packetId); + void OnConnectionAddition(RakPeerInterface *peer, Packet *packet); + void OnConnectionRemoval(RakPeerInterface *peer, Packet *packet); + void SendConnectionGraph(PlayerID target, unsigned char packetId, RakPeerInterface *peer); + void SerializeWeightedGraph(RakNet::BitStream *out, const DataStructures::WeightedGraph &g) const; + bool DeserializeWeightedGraph(RakNet::BitStream *inBitstream, RakPeerInterface *peer); + void AddAndRelayConnection(DataStructures::OrderedList &ignoreList, const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2, unsigned short ping, RakPeerInterface *peer); + bool RemoveAndRelayConnection(DataStructures::OrderedList &ignoreList, unsigned char packetId, const PlayerID node1, const PlayerID node2, RakPeerInterface *peer); + void RemoveParticipant(PlayerID playerId); + void AddParticipant(PlayerID playerId); + void BroadcastGraphUpdate(DataStructures::OrderedList &ignoreList, RakPeerInterface *peer); + void NotifyUserOfRemoteConnection(const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2,unsigned short ping, RakPeerInterface *peer); + bool IsNewRemoteConnection(const PlayerIdAndGroupId &conn1, const PlayerIdAndGroupId &conn2,RakPeerInterface *peer); + bool DeserializeIgnoreList(DataStructures::OrderedList &ignoreList, RakNet::BitStream *inBitstream ); + void SerializeIgnoreListAndBroadcast(RakNet::BitStream *outBitstream, DataStructures::OrderedList &ignoreList, RakPeerInterface *peer); + + RakNetTime nextWeightUpdate; + char *pw; + DataStructures::OrderedList participantList; + + DataStructures::WeightedGraph graph; + bool autoAddNewConnections; + ConnectionGraphGroupID myGroupId; + + DataStructures::OrderedList subscribedGroups; + + // Used to broadcast new connections after some time so the pings are correct + //RakNetTime forceBroadcastTime; + +}; + +#endif diff --git a/raknet/ConsoleServer.cpp b/raknet/ConsoleServer.cpp new file mode 100644 index 0000000..a025748 --- /dev/null +++ b/raknet/ConsoleServer.cpp @@ -0,0 +1,273 @@ +#include "ConsoleServer.h" +#include "TransportInterface.h" +#include "CommandParserInterface.h" +#include +#include + +#define COMMAND_DELINATOR ' ' +#define COMMAND_DELINATOR_TOGGLE '"' + +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(_stricmp) +#define _stricmp strcasecmp +#endif + +ConsoleServer::ConsoleServer() +{ + transport=0; + password[0]=0; +} +ConsoleServer::~ConsoleServer() +{ +} +void ConsoleServer::SetTransportProvider(TransportInterface *transportInterface, unsigned short port) +{ + // Replace the current TransportInterface, stopping the old one, if present, and starting the new one. + if (transportInterface) + { + if (transport) + { + RemoveCommandParser(transport->GetCommandParser()); + transport->Stop(); + } + transport=transportInterface; + transport->Start(port, true); + + unsigned i; + for (i=0; i < commandParserList.Size(); i++) + commandParserList[i]->OnTransportChange(transport); + + // The transport itself might have a command parser - for example password for the RakNet transport + AddCommandParser(transport->GetCommandParser()); + } +} +void ConsoleServer::AddCommandParser(CommandParserInterface *commandParserInterface) +{ + if (commandParserInterface==0) + return; + + // Non-duplicate insertion + unsigned i; + for (i=0; i < commandParserList.Size(); i++) + { + if (commandParserList[i]==commandParserInterface) + return; + + if (_stricmp(commandParserList[i]->GetName(), commandParserInterface->GetName())==0) + { + // Naming conflict between two command parsers + assert(0); + return; + } + } + + commandParserList.Insert(commandParserInterface); + if (transport) + commandParserInterface->OnTransportChange(transport); +} +void ConsoleServer::RemoveCommandParser(CommandParserInterface *commandParserInterface) +{ + if (commandParserInterface==0) + return; + + // Overwrite the element we are removing from the back of the list and delete the back of the list + unsigned i; + for (i=0; i < commandParserList.Size(); i++) + { + if (commandParserList[i]==commandParserInterface) + { + commandParserList[i]=commandParserList[commandParserList.Size()-1]; + commandParserList.Del(); + return; + } + } +} +void ConsoleServer::Update(void) +{ + unsigned i; + char *parameterList[20]; // Up to 20 parameters + unsigned numParameters; + PlayerID newOrLostConnectionId; + Packet *p; + RegisteredCommand rc; + + p = transport->Receive(); + newOrLostConnectionId=transport->HasNewConnection(); + + if (newOrLostConnectionId!=UNASSIGNED_PLAYER_ID) + { + for (i=0; i < commandParserList.Size(); i++) + { + commandParserList[i]->OnNewIncomingConnection(newOrLostConnectionId, transport); + } + + transport->Send(newOrLostConnectionId, "Connected to remote command console.\r\nType 'help' for help.\r\n"); + ListParsers(newOrLostConnectionId); + } + + newOrLostConnectionId=transport->HasLostConnection(); + if (newOrLostConnectionId!=UNASSIGNED_PLAYER_ID) + { + for (i=0; i < commandParserList.Size(); i++) + commandParserList[i]->OnConnectionLost(newOrLostConnectionId, transport); + } + + while (p) + { + bool commandParsed=false; + char copy[REMOTE_MAX_TEXT_INPUT]; + memcpy(copy, p->data, p->length); + copy[p->length]=0; + CommandParserInterface::ParseConsoleString((char*)p->data, COMMAND_DELINATOR, COMMAND_DELINATOR_TOGGLE, &numParameters, parameterList, 20); // Up to 20 parameters + if (_stricmp(*parameterList, "help")==0 && numParameters<=2) + { + // Find the parser specified and display help for it + if (numParameters==1) + { + transport->Send(p->playerId, "\r\nINSTRUCTIONS:\r\n"); + transport->Send(p->playerId, "Enter commands on your keyboard, using spaces to delineate parameters.\r\n"); + transport->Send(p->playerId, "You can use quotation marks to toggle space delineation.\r\n"); + transport->Send(p->playerId, "You can connect multiple times from the same computer.\r\n"); + transport->Send(p->playerId, "You can direct commands to a parser by prefixing the parser name or number.\r\n"); + transport->Send(p->playerId, "COMMANDS:\r\n"); + transport->Send(p->playerId, "help Show this display.\r\n"); + transport->Send(p->playerId, "help Show help on a particular parser.\r\n"); + transport->Send(p->playerId, "help Show help on a particular command.\r\n"); + transport->Send(p->playerId, "quit Disconnects from the server.\r\n"); + transport->Send(p->playerId, "[] [] Execute a command\r\n"); + transport->Send(p->playerId, "[] [] Execute a command\r\n"); + ListParsers(p->playerId); + } + else // numParameters == 2, including the help tag + { + for (i=0; i < commandParserList.Size(); i++) + { + if (_stricmp(parameterList[1], commandParserList[i]->GetName())==0) + { + commandParsed=true; + commandParserList[i]->SendHelp(transport, p->playerId); + transport->Send(p->playerId, "COMMAND LIST:\r\n"); + commandParserList[i]->SendCommandList(transport, p->playerId); + transport->Send(p->playerId, "\r\n"); + break; + } + } + + if (commandParsed==false) + { + // Try again, for all commands for all parsers. + RegisteredCommand rc; + for (i=0; i < commandParserList.Size(); i++) + { + if (commandParserList[i]->GetRegisteredCommand(parameterList[1], &rc)) + { + if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS) + transport->Send(p->playerId, "(Variable parms): %s %s\r\n", rc.command, rc.commandHelp); + else + transport->Send(p->playerId, "(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp); + commandParsed=true; + break; + } + } + } + + if (commandParsed==false) + { + // Don't know what to do + transport->Send(p->playerId, "Unknown help topic: %s.\r\n", parameterList[1]); + } + } + } + else if (_stricmp(*parameterList, "quit")==0 && numParameters==1) + { + transport->Send(p->playerId, "Goodbye!"); + transport->CloseConnection(p->playerId); + } + else + { + bool tryAllParsers=true; + bool failed=false; + + if (numParameters >=2) // At minimum + { + unsigned commandParserIndex=(unsigned)-1; + // Prefixing with numbers directs to a particular parser + if (**parameterList>='0' && **parameterList<='9') + { + commandParserIndex=atoi(*parameterList); // Use specified parser unless it's an invalid number + commandParserIndex--; // Subtract 1 since we displayed numbers starting at index+1 + if (commandParserIndex >= commandParserList.Size()) + { + transport->Send(p->playerId, "Invalid index.\r\n"); + failed=true; + } + } + else + { + // // Prefixing with the name of a command parser directs to that parser. See if the first word matches a parser + for (i=0; i < commandParserList.Size(); i++) + { + if (_stricmp(parameterList[0], commandParserList[i]->GetName())==0) + { + commandParserIndex=i; // Matches parser at index i + break; + } + } + } + + if (failed==false) + { + // -1 means undirected, so otherwise this is directed to a target + if (commandParserIndex!=(unsigned)-1) + { + // Only this parser should use this command + tryAllParsers=false; + if (commandParserList[commandParserIndex]->GetRegisteredCommand(parameterList[1], &rc)) + { + commandParsed=true; + if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-2) + commandParserList[commandParserIndex]->OnCommand(rc.command, numParameters-2, parameterList+2, transport, p->playerId, copy); + else + transport->Send(p->playerId, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp); + } + } + } + } + + if (failed == false && tryAllParsers) + { + for (i=0; i < commandParserList.Size(); i++) + { + // Undirected command. Try all the parsers to see if they understand the command + // Pass the 1nd element as the command, and the remainder as the parameter list + if (commandParserList[i]->GetRegisteredCommand(parameterList[0], &rc)) + { + commandParsed=true; + + if (rc.parameterCount==CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS || rc.parameterCount==numParameters-1) + commandParserList[i]->OnCommand(rc.command, numParameters-1, parameterList+1, transport, p->playerId, copy); + else + transport->Send(p->playerId, "Invalid parameter count.\r\n(%i parms): %s %s\r\n", rc.parameterCount, rc.command, rc.commandHelp); + } + } + } + if (commandParsed==false && commandParserList.Size() > 0) + { + transport->Send(p->playerId, "Unknown command: Type 'help' for help.\r\n"); + } + } + + + transport->DeallocatePacket(p); + p = transport->Receive(); + } +} + +void ConsoleServer::ListParsers(PlayerID playerId) +{ + transport->Send(playerId,"INSTALLED PARSERS:\r\n"); + unsigned i; + for (i=0; i < commandParserList.Size(); i++) + { + transport->Send(playerId, "%i. %s\r\n", i+1, commandParserList[i]->GetName()); + } +} diff --git a/raknet/ConsoleServer.h b/raknet/ConsoleServer.h new file mode 100644 index 0000000..47410cd --- /dev/null +++ b/raknet/ConsoleServer.h @@ -0,0 +1,62 @@ +/// \file +/// \brief Contains ConsoleServer , used to plugin to your game to accept remote console-based connections +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __CONSOLE_SERVER_H +#define __CONSOLE_SERVER_H + +class TransportInterface; +class CommandParserInterface; + +#include "DS_List.h" +#include "NetworkTypes.h" +#include "Export.h" + +/// \brief The main entry point for the server portion of your remote console application support. +/// ConsoleServer takes one TransportInterface and one or more CommandParserInterface (s) +/// The TransportInterface will be used to send data between the server and the client. The connecting client must support the +/// protocol used by your derivation of TransportInterface . TelnetTransport and RakNetTransport are two such derivations . +/// When a command is sent by a remote console, it will be processed by your implementations of CommandParserInterface +class RAK_DLL_EXPORT ConsoleServer +{ +public: + ConsoleServer(); + ~ConsoleServer(); + + /// Call this with a derivation of TransportInterface so that the console server can send and receive commands + /// \param[in] transportInterface Your interface to use. + /// \param[in] port The port to host on. Telnet uses port 23 by default. RakNet can use whatever you want. + void SetTransportProvider(TransportInterface *transportInterface, unsigned short port); + + /// Add an implementation of CommandParserInterface to the list of command parsers. + /// \param[in] commandParserInterface The command parser referred to + void AddCommandParser(CommandParserInterface *commandParserInterface); + + /// Remove an implementation of CommandParserInterface previously added with AddCommandParser() + /// \param[in] commandParserInterface The command parser referred to + void RemoveCommandParser(CommandParserInterface *commandParserInterface); + + /// Call update to read packet sent from your TransportInterface. + /// You should do this fairly frequently. + void Update(void); +protected: + void ListParsers(PlayerID playerId); + TransportInterface *transport; + DataStructures::List commandParserList; + char* password[256]; +}; + +#endif diff --git a/raknet/DS_BPlusTree.h b/raknet/DS_BPlusTree.h new file mode 100644 index 0000000..5cdfe45 --- /dev/null +++ b/raknet/DS_BPlusTree.h @@ -0,0 +1,1157 @@ +#ifndef __B_PLUS_TREE_CPP +#define __B_PLUS_TREE_CPP + +#include "DS_MemoryPool.h" +#include "DS_Queue.h" +#include + +// Java +// http://www.seanster.com/BplusTree/BplusTree.html + +// Overview +// http://babbage.clarku.edu/~achou/cs160/B+Trees/B+Trees.htm + +// Deletion +// http://dbpubs.stanford.edu:8090/pub/1995-19 + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + /// Used in the BPlusTree. Used for both leaf and index nodes. + template + struct Page + { + // We use the same data structure for both leaf and index nodes. It uses a little more memory for index nodes but reduces + // memory fragmentation, allocations, and deallocations. + bool isLeaf; + + // Used for both leaf and index nodes. + // For a leaf it means the number of elements in data + // For an index it means the number of keys and is one less than the number of children pointers. + int size; + + // Used for both leaf and index nodes. + KeyType keys[order]; + + // Used only for leaf nodes. Data is the actual data, while next is the pointer to the next leaf (for B+) + DataType data[order]; + Page *next; + Page *previous; + + // Used only for index nodes. Pointers to the children of this node. + Page *children[order+1]; + }; + + /// A BPlus tree + /// Written with efficiency and speed in mind. + template + class BPlusTree + { + public: + struct ReturnAction + { + KeyType key1; + KeyType key2; + enum + { + NO_ACTION, + REPLACE_KEY1_WITH_KEY2, + PUSH_KEY_TO_PARENT, + SET_BRANCH_KEY, + } action; // 0=none, 1=replace key1 with key2 + }; + + BPlusTree(); + ~BPlusTree(); + bool Get(const KeyType key, DataType &out) const; + bool Delete(const KeyType key); + bool Delete(const KeyType key, DataType &out); + bool Insert(const KeyType key, const DataType &data); + void Preallocate(const int size); + void Clear(void); + unsigned Size(void) const; + bool IsEmpty(void) const; + Page *GetListHead(void) const; + DataType GetDataHead(void) const; + void PrintLeaves(void); + void ForEachLeaf(void (*func)(Page * leaf, int index)); + void ForEachData(void (*func)(DataType input, int index)); + void PrintGraph(void); + void ValidateTree(void); + protected: + void ValidateTreeRecursive(Page *cur); + void DeleteFromPageAtIndex(const int index, Page *cur); + static void PrintLeaf(Page * leaf, int index); + void FreePages(void); + bool GetIndexOf(const KeyType key, Page *page, int *out) const; + void ShiftKeysLeft(Page *cur); + bool CanRotateLeft(Page *cur, int childIndex); + bool CanRotateRight(Page *cur, int childIndex); + void RotateRight(Page *cur, int childIndex, ReturnAction *returnAction); + void RotateLeft(Page *cur, int childIndex, ReturnAction *returnAction); + Page* InsertIntoNode(const KeyType key, const DataType &childData, int insertionIndex, Page *nodeData, Page *cur, ReturnAction* returnAction); + Page* InsertBranchDown(const KeyType key, const DataType &data,Page *cur, ReturnAction* returnAction, bool *success); + Page* GetLeafFromKey(const KeyType key) const; + bool FindDeleteRebalance(const KeyType key, Page *cur, bool *underflow, KeyType rightRootKey, ReturnAction *returnAction, DataType &out); + bool FixUnderflow(int branchIndex, Page *cur, KeyType rightRootKey, ReturnAction *returnAction); + void ShiftNodeLeft(Page *cur); + void ShiftNodeRight(Page *cur); + + MemoryPool > pagePool; + Page *root, *leftmostLeaf; + }; + + template + BPlusTree::BPlusTree () + { + assert(order>1); + root=0; + leftmostLeaf=0; + } + template + BPlusTree::~BPlusTree () + { + Clear(); + } + template + bool BPlusTree::Get(const KeyType key, DataType &out) const + { + if (root==0) + return false; + + Page* leaf = GetLeafFromKey(key); + int childIndex; + + if (GetIndexOf(key, leaf, &childIndex)) + { + out=leaf->data[childIndex]; + return true; + } + return false; + } + template + void BPlusTree::DeleteFromPageAtIndex(const int index, Page *cur) + { + int i; + for (i=index; i < cur->size-1; i++) + cur->keys[i]=cur->keys[i+1]; + if (cur->isLeaf) + { + for (i=index; i < cur->size-1; i++) + cur->data[i]=cur->data[i+1]; + } + else + { + for (i=index; i < cur->size-1; i++) + cur->children[i+1]=cur->children[i+2]; + } + cur->size--; + } + template + bool BPlusTree::Delete(const KeyType key) + { + DataType temp; + return Delete(key, temp); + } + template + bool BPlusTree::Delete(const KeyType key, DataType &out) + { + if (root==0) + return false; + + ReturnAction returnAction; + returnAction.action=ReturnAction::NO_ACTION; + int childIndex; + bool underflow=false; + if (root==leftmostLeaf) + { + if (GetIndexOf(key, root, &childIndex)==false) + return false; + out=root->data[childIndex]; + DeleteFromPageAtIndex(childIndex,root); + if (root->size==0) + { + pagePool.Release(root); + memset(root,0,sizeof(root)); + root=0; + leftmostLeaf=0; + } + return true; + } + else if (FindDeleteRebalance(key, root, &underflow,root->keys[0], &returnAction, out)==false) + return false; + +// assert(returnAction.action==ReturnAction::NO_ACTION); + + if (underflow && root->size==0) + { + // Move the root down. + Page *oldRoot=root; + root=root->children[0]; + pagePool.Release(oldRoot); + memset(oldRoot,0,sizeof(root)); + } + + return true; + } + template + bool BPlusTree::FindDeleteRebalance(const KeyType key, Page *cur, bool *underflow, KeyType rightRootKey, ReturnAction *returnAction, DataType &out) + { + // Get index of child to follow. + int branchIndex, childIndex; + if (GetIndexOf(key, cur, &childIndex)) + branchIndex=childIndex+1; + else + branchIndex=childIndex; + + // If child is not a leaf, call recursively + if (cur->children[branchIndex]->isLeaf==false) + { + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + + if (FindDeleteRebalance(key, cur->children[branchIndex], underflow, rightRootKey, returnAction, out)==false) + return false; + + // Call again in case the root key changed + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + + if (returnAction->action==ReturnAction::SET_BRANCH_KEY && branchIndex!=childIndex) + { + returnAction->action=ReturnAction::NO_ACTION; + cur->keys[childIndex]=returnAction->key1; + + if (branchIndexsize) + rightRootKey=cur->keys[branchIndex]; // Shift right to left + else + rightRootKey=cur->keys[branchIndex-1]; // Shift center to left + } + } + else + { + // If child is a leaf, get the index of the key. If the item is not found, cancel delete. + if (GetIndexOf(key, cur->children[branchIndex], &childIndex)==false) + return false; + + // Delete: + // Remove childIndex from the child at branchIndex + out=cur->children[branchIndex]->data[childIndex]; + DeleteFromPageAtIndex(childIndex, cur->children[branchIndex]); + + if (childIndex==0) + { + if (branchIndex>0) + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + if (branchIndex==0) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=cur->children[0]->keys[0]; + } + } + + if (cur->children[branchIndex]->size < order/2) + *underflow=true; + else + *underflow=false; + } + + // Fix underflow: + if (*underflow) + { + *underflow=FixUnderflow(branchIndex, cur, rightRootKey, returnAction); + } + + return true; + } + template + bool BPlusTree::FixUnderflow(int branchIndex, Page *cur, KeyType rightRootKey, ReturnAction *returnAction) + { + // Borrow from a neighbor that has excess. + Page *source; + Page *dest; + + if (branchIndex>0 && cur->children[branchIndex-1]->size > order/2) + { + dest=cur->children[branchIndex]; + source=cur->children[branchIndex-1]; + + // Left has excess + ShiftNodeRight(dest); + if (dest->isLeaf) + { + dest->keys[0]=source->keys[source->size-1]; + dest->data[0]=source->data[source->size-1]; + } + else + { + dest->children[0]=source->children[source->size]; + dest->keys[0]=cur->keys[branchIndex-1]; + } + // Update the parent key for the child (middle) + cur->keys[branchIndex-1]=source->keys[source->size-1]; + source->size--; + + // if (branchIndex==0) + // { + // returnAction->action=ReturnAction::SET_BRANCH_KEY; + // returnAction->key1=dest->keys[0]; + // } + + // No underflow + return false; + } + else if (branchIndexsize && cur->children[branchIndex+1]->size > order/2) + { + dest=cur->children[branchIndex]; + source=cur->children[branchIndex+1]; + + // Right has excess + if (dest->isLeaf) + { + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + + // The first key in the leaf after shifting is the parent key for the right branch + cur->keys[branchIndex]=source->keys[1]; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (order<=3 && dest->size==0) + { + if (branchIndex==0) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + else + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + } + } + else + { + if (returnAction->action==ReturnAction::NO_ACTION) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + + dest->keys[dest->size]=rightRootKey; + dest->children[dest->size+1]=source->children[0]; + + // The shifted off key is the leftmost key for a node + cur->keys[branchIndex]=source->keys[0]; + } + + + dest->size++; + ShiftNodeLeft(source); + + //cur->keys[branchIndex]=source->keys[0]; + +// returnAction->action=ReturnAction::SET_BRANCH_KEY; +// returnAction->key1=dest->keys[dest->size-1]; + + // No underflow + return false; + } + else + { + int sourceIndex; + + // If no neighbors have excess, merge two branches. + // + // To merge two leaves, just copy the data and keys over. + // + // To merge two branches, copy the pointers and keys over, using rightRootKey as the key for the extra pointer + if (branchIndexsize) + { + // Merge right child to current child and delete right child. + dest=cur->children[branchIndex]; + source=cur->children[branchIndex+1]; + } + else + { + // Move current child to left and delete current child + dest=cur->children[branchIndex-1]; + source=cur->children[branchIndex]; + } + + // Merge + if (dest->isLeaf) + { + for (sourceIndex=0; sourceIndexsize; sourceIndex++) + { + dest->keys[dest->size]=source->keys[sourceIndex]; + dest->data[dest->size++]=source->data[sourceIndex]; + } + } + else + { + // We want the tree root key of the source, not the current. + dest->keys[dest->size]=rightRootKey; + dest->children[dest->size++ + 1]=source->children[0]; + for (sourceIndex=0; sourceIndexsize; sourceIndex++) + { + dest->keys[dest->size]=source->keys[sourceIndex]; + dest->children[dest->size++ + 1]=source->children[sourceIndex + 1]; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (order<=3 && branchIndex>0 && cur->children[branchIndex]->isLeaf) // With order==2 it is possible to delete data[0], which is not possible with higher orders. + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + if (branchIndexsize) + { + // Update the parent key, removing the source (right) + DeleteFromPageAtIndex(branchIndex, cur); + } + else + { + if (branchIndex>0) + { + // Update parent key, removing the source (current) + DeleteFromPageAtIndex(branchIndex-1, cur); + } + } + + if (branchIndex==0 && dest->isLeaf) + { + returnAction->action=ReturnAction::SET_BRANCH_KEY; + returnAction->key1=dest->keys[0]; + } + + if (source==leftmostLeaf) + leftmostLeaf=source->next; + + if (source->isLeaf) + { + if (source->previous) + source->previous->next=source->next; + if (source->next) + source->next->previous=source->previous; + } + + // Free the source node + pagePool.Release(source); + memset(source,0,sizeof(root)); + + // Return underflow or not of parent. + return cur->size < order/2; + } + } + template + void BPlusTree::ShiftNodeRight(Page *cur) + { + int i; + for (i=cur->size; i>0; i--) + cur->keys[i]=cur->keys[i-1]; + if (cur->isLeaf) + { + for (i=cur->size; i>0; i--) + cur->data[i]=cur->data[i-1]; + } + else + { + for (i=cur->size+1; i>0; i--) + cur->children[i]=cur->children[i-1]; + } + + cur->size++; + } + template + void BPlusTree::ShiftNodeLeft(Page *cur) + { + int i; + for (i=0; i < cur->size-1; i++) + cur->keys[i]=cur->keys[i+1]; + if (cur->isLeaf) + { + for (i=0; i < cur->size; i++) + cur->data[i]=cur->data[i+1]; + } + else + { + for (i=0; i < cur->size; i++) + cur->children[i]=cur->children[i+1]; + } + cur->size--; + } + template + Page* BPlusTree::InsertIntoNode(const KeyType key, const DataType &leafData, int insertionIndex, Page *nodeData, Page *cur, ReturnAction* returnAction) + { + int i; + if (cur->size < order) + { + for (i=cur->size; i > insertionIndex; i--) + cur->keys[i]=cur->keys[i-1]; + if (cur->isLeaf) + { + for (i=cur->size; i > insertionIndex; i--) + cur->data[i]=cur->data[i-1]; + } + else + { + for (i=cur->size+1; i > insertionIndex+1; i--) + cur->children[i]=cur->children[i-1]; + } + cur->keys[insertionIndex]=key; + if (cur->isLeaf) + cur->data[insertionIndex]=leafData; + else + cur->children[insertionIndex+1]=nodeData; + + cur->size++; + } + else + { + Page* newPage = pagePool.Allocate(); + newPage->isLeaf=cur->isLeaf; + if (cur->isLeaf) + { + newPage->next=cur->next; + if (cur->next) + cur->next->previous=newPage; + newPage->previous=cur; + cur->next=newPage; + } + + int destIndex, sourceIndex; + + if (insertionIndex>=(order+1)/2) + { + destIndex=0; + sourceIndex=order/2; + + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->keys[destIndex]=cur->keys[sourceIndex]; + } + newPage->keys[destIndex++]=key; + for (; sourceIndex < order; sourceIndex++, destIndex++) + { + newPage->keys[destIndex]=cur->keys[sourceIndex]; + } + + destIndex=0; + sourceIndex=order/2; + if (cur->isLeaf) + { + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->data[destIndex]=cur->data[sourceIndex]; + } + newPage->data[destIndex++]=leafData; + for (; sourceIndex < order; sourceIndex++, destIndex++) + { + newPage->data[destIndex]=cur->data[sourceIndex]; + } + } + else + { + + for (; sourceIndex < insertionIndex; sourceIndex++, destIndex++) + { + newPage->children[destIndex]=cur->children[sourceIndex+1]; + } + newPage->children[destIndex++]=nodeData; + + // sourceIndex+1 is sort of a hack but it works - because there is one extra child than keys + // skip past the last child for cur + for (; sourceIndex+1 < cur->size+1; sourceIndex++, destIndex++) + { + newPage->children[destIndex]=cur->children[sourceIndex+1]; + } + + // the first key is the middle key. Remove it from the page and push it to the parent + returnAction->action=ReturnAction::PUSH_KEY_TO_PARENT; + returnAction->key1=newPage->keys[0]; + for (int i=0; i < destIndex-1; i++) + newPage->keys[i]=newPage->keys[i+1]; + + } + cur->size=order/2; + } + else + { + destIndex=0; + sourceIndex=(order+1)/2-1; + for (; sourceIndex < order; sourceIndex++, destIndex++) + newPage->keys[destIndex]=cur->keys[sourceIndex]; + destIndex=0; + if (cur->isLeaf) + { + sourceIndex=(order+1)/2-1; + for (; sourceIndex < order; sourceIndex++, destIndex++) + newPage->data[destIndex]=cur->data[sourceIndex]; + } + else + { + sourceIndex=(order+1)/2; + for (; sourceIndex < order+1; sourceIndex++, destIndex++) + newPage->children[destIndex]=cur->children[sourceIndex]; + + // the first key is the middle key. Remove it from the page and push it to the parent + returnAction->action=ReturnAction::PUSH_KEY_TO_PARENT; + returnAction->key1=newPage->keys[0]; + for (int i=0; i < destIndex-1; i++) + newPage->keys[i]=newPage->keys[i+1]; + } + cur->size=(order+1)/2-1; + if (cur->size) + { + bool b = GetIndexOf(key, cur, &insertionIndex); + assert(b==false); + } + else + insertionIndex=0; + InsertIntoNode(key, leafData, insertionIndex, nodeData, cur, returnAction); + } + + newPage->size=destIndex; + + return newPage; + } + + return 0; + } + + template + bool BPlusTree::CanRotateLeft(Page *cur, int childIndex) + { + return childIndex>0 && cur->children[childIndex-1]->size + void BPlusTree::RotateLeft(Page *cur, int childIndex, ReturnAction *returnAction) + { + Page *dest = cur->children[childIndex-1]; + Page *source = cur->children[childIndex]; + returnAction->key1=source->keys[0]; + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + dest->size++; + for (int i=0; i < source->size-1; i++) + { + source->keys[i]=source->keys[i+1]; + source->data[i]=source->data[i+1]; + } + source->size--; + cur->keys[childIndex-1]=source->keys[0]; + returnAction->key2=source->keys[0]; + } + + template + bool BPlusTree::CanRotateRight(Page *cur, int childIndex) + { + return childIndex < cur->size && cur->children[childIndex+1]->size + void BPlusTree::RotateRight(Page *cur, int childIndex, ReturnAction *returnAction) + { + Page *dest = cur->children[childIndex+1]; + Page *source = cur->children[childIndex]; + returnAction->key1=dest->keys[0]; + for (int i= dest->size; i > 0; i--) + { + dest->keys[i]=dest->keys[i-1]; + dest->data[i]=dest->data[i-1]; + } + dest->keys[0]=source->keys[source->size-1]; + dest->data[0]=source->data[source->size-1]; + dest->size++; + source->size--; + + cur->keys[childIndex]=dest->keys[0]; + returnAction->key2=dest->keys[0]; + } + template + Page* BPlusTree::GetLeafFromKey(const KeyType key) const + { + Page* cur = root; + int childIndex; + while (cur->isLeaf==false) + { + // When searching, if we match the exact key we go down the pointer after that index + if (GetIndexOf(key, cur, &childIndex)) + childIndex++; + cur = cur->children[childIndex]; + } + return cur; + } + + template + Page* BPlusTree::InsertBranchDown(const KeyType key, const DataType &data,Page *cur, ReturnAction *returnAction, bool *success) + { + int childIndex; + int branchIndex; + if (GetIndexOf(key, cur, &childIndex)) + branchIndex=childIndex+1; + else + branchIndex=childIndex; + Page* newPage; + if (cur->isLeaf==false) + { + if (cur->children[branchIndex]->isLeaf==true && cur->children[branchIndex]->size==order) + { + if (branchIndex==childIndex+1) + { + *success=false; + return 0; // Already exists + } + + if (CanRotateLeft(cur, branchIndex)) + { + returnAction->action=ReturnAction::REPLACE_KEY1_WITH_KEY2; + if (key > cur->children[branchIndex]->keys[0]) + { + RotateLeft(cur, branchIndex, returnAction); + + int insertionIndex; + GetIndexOf(key, cur->children[branchIndex], &insertionIndex); + InsertIntoNode(key, data, insertionIndex, 0, cur->children[branchIndex], 0); + } + else + { + // Move head element to left and replace it with key,data + Page* dest=cur->children[branchIndex-1]; + Page* source=cur->children[branchIndex]; + returnAction->key1=source->keys[0]; + returnAction->key2=key; + dest->keys[dest->size]=source->keys[0]; + dest->data[dest->size]=source->data[0]; + dest->size++; + source->keys[0]=key; + source->data[0]=data; + } + cur->keys[branchIndex-1]=cur->children[branchIndex]->keys[0]; + + return 0; + } + else if (CanRotateRight(cur, branchIndex)) + { + returnAction->action=ReturnAction::REPLACE_KEY1_WITH_KEY2; + + if (key < cur->children[branchIndex]->keys[cur->children[branchIndex]->size-1]) + { + RotateRight(cur, branchIndex, returnAction); + + int insertionIndex; + GetIndexOf(key, cur->children[branchIndex], &insertionIndex); + InsertIntoNode(key, data, insertionIndex, 0, cur->children[branchIndex], 0); + + } + else + { + // Insert to the head of the right leaf instead and change our key + returnAction->key1=cur->children[branchIndex+1]->keys[0]; + InsertIntoNode(key, data, 0, 0, cur->children[branchIndex+1], 0); + returnAction->key2=key; + } + cur->keys[branchIndex]=cur->children[branchIndex+1]->keys[0]; + return 0; + } + } + + newPage=InsertBranchDown(key,data,cur->children[branchIndex], returnAction, success); + if (returnAction->action==ReturnAction::REPLACE_KEY1_WITH_KEY2) + { + if (branchIndex>0 && cur->keys[branchIndex-1]==returnAction->key1) + cur->keys[branchIndex-1]=returnAction->key2; + } + if (newPage) + { + if (newPage->isLeaf==false) + { + assert(returnAction->action==ReturnAction::PUSH_KEY_TO_PARENT); + newPage->size--; + return InsertIntoNode(returnAction->key1, data, branchIndex, newPage, cur, returnAction); + } + else + { + return InsertIntoNode(newPage->keys[0], data, branchIndex, newPage, cur, returnAction); + } + } + } + else + { + if (branchIndex==childIndex+1) + { + *success=false; + return 0; // Already exists + } + else + { + return InsertIntoNode(key, data, branchIndex, 0, cur, returnAction); + } + } + + return 0; + } + template + bool BPlusTree::Insert(const KeyType key, const DataType &data) + { + if (root==0) + { + // Allocate root and make root a leaf + root = pagePool.Allocate(); + root->isLeaf=true; + leftmostLeaf=root; + root->size=1; + root->keys[0]=key; + root->data[0]=data; + root->next=0; + root->previous=0; + } + else + { + bool success=true; + ReturnAction returnAction; + returnAction.action=ReturnAction::NO_ACTION; + Page* newPage = InsertBranchDown(key, data, root, &returnAction, &success); + if (success==false) + return false; + if (newPage) + { + KeyType newKey; + if (newPage->isLeaf==false) + { + // One key is pushed up through the stack. I store that at keys[0] but it has to be removed for the page to be correct + assert(returnAction.action==ReturnAction::PUSH_KEY_TO_PARENT); + newKey=returnAction.key1; + newPage->size--; + } + else + newKey = newPage->keys[0]; + // propagate the root + Page* newRoot = pagePool.Allocate(); + newRoot->isLeaf=false; + newRoot->size=1; + newRoot->keys[0]=newKey; + newRoot->children[0]=root; + newRoot->children[1]=newPage; + root=newRoot; + } + } + + return true; + } + template + void BPlusTree::Preallocate(const int size) + { + pagePool.Preallocate(size); + } + template + void BPlusTree::ShiftKeysLeft(Page *cur) + { + int i; + for (i=0; i < cur->size; i++) + cur->keys[i]=cur->keys[i+1]; + } + template + void BPlusTree::Clear(void) + { + if (root) + { + FreePages(); + leftmostLeaf=0; + root=0; + } + } + template + unsigned BPlusTree::Size(void) const + { + int count=0; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + count+=cur->size; + cur=cur->next; + } + return count; + } + template + bool BPlusTree::IsEmpty(void) const + { + return root==0; + } + template + bool BPlusTree::GetIndexOf(const KeyType key, Page *page, int *out) const + { + assert(page->size>0); + int index, upperBound, lowerBound; + upperBound=page->size-1; + lowerBound=0; + index = page->size/2; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + if (key==page->keys[index]) + { + *out=index; + return true; + } + else if (keykeys[index]) + upperBound=index-1; + else + lowerBound=index+1; + + index=lowerBound+(upperBound-lowerBound)/2; + + if (lowerBound>upperBound) + { + *out=lowerBound; + return false; // No match + } + } + } + template + void BPlusTree::FreePages(void) + { + DataStructures::Queue *> queue; + DataStructures::Page *ptr; + int i; + queue.Push(root); + while (queue.Size()) + { + ptr=queue.Pop(); + if (ptr->isLeaf==false) + { + for (i=0; i < ptr->size+1; i++) + queue.Push(ptr->children[i]); + } + pagePool.Release(ptr); + memset(ptr,0,sizeof(root)); + }; + } + template + Page *BPlusTree::GetListHead(void) const + { + return leftmostLeaf; + } + template + DataType BPlusTree::GetDataHead(void) const + { + return leftmostLeaf->data[0]; + } + template + void BPlusTree::ForEachLeaf(void (*func)(Page * leaf, int index)) + { + int count=0; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + func(cur, count++); + cur=cur->next; + } + } + template + void BPlusTree::ForEachData(void (*func)(DataType input, int index)) + { + int count=0,i; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + for (i=0; i < cur->size; i++) + func(cur->data[i], count++); + cur=cur->next; + } + } + template + void BPlusTree::PrintLeaf(Page * leaf, int index) + { + int i; + printf("%i] SELF=%p\n", index+1, leaf); + for (i=0; i < leaf->size; i++) + printf(" %i. %i\n", i+1, leaf->data[i]); + } + template + void BPlusTree::PrintLeaves(void) + { + ForEachLeaf(PrintLeaf); + } + + template + void BPlusTree::ValidateTree(void) + { + int i, last=-9999; + DataStructures::Page *cur = GetListHead(); + while (cur) + { + assert(cur->size>0); + for (i=0; i < cur->size; i++) + { + assert(cur->data[i]==cur->keys[i]); + if (last!=-9999) + { + assert(cur->data[i]>last); + } + last=cur->data[i]; + } + cur=cur->next; + } + if (root && root->isLeaf==false) + ValidateTreeRecursive(root); + } + template + void BPlusTree::ValidateTreeRecursive(Page *cur) + { + assert(cur==root || cur->size>=order/2); + + if (cur->children[0]->isLeaf) + { + assert(cur->children[0]->keys[0] < cur->keys[0]); + for (int i=0; i < cur->size; i++) + { + assert(cur->children[i+1]->keys[0]==cur->keys[i]); + } + } + else + { + for (int i=0; i < cur->size+1; i++) + ValidateTreeRecursive(cur->children[i]); + } + } + + template + void BPlusTree::PrintGraph(void) + { + DataStructures::Queue *> queue; + queue.Push(root); + queue.Push(0); + DataStructures::Page *ptr; + int i,j; + if (root) + { + printf("%p(", root); + for (i=0; i < root->size; i++) + { + printf("%i ", root->keys[i]); + } + printf(") "); + printf("\n"); + } + while (queue.Size()) + { + ptr=queue.Pop(); + if (ptr==0) + printf("\n"); + else if (ptr->isLeaf==false) + { + for (i=0; i < ptr->size+1; i++) + { + printf("%p(", ptr->children[i]); + //printf("(", ptr->children[i]); + for (j=0; j < ptr->children[i]->size; j++) + printf("%i ", ptr->children[i]->keys[j]); + printf(") "); + queue.Push(ptr->children[i]); + } + queue.Push(0); + printf(" -- "); + } + } + printf("\n"); + } +} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + +// Code to test this hellish data structure. +/* +#include "DS_BPlusTree.h" +#include + +// Handle underflow on root. If there is only one item left then I can go downwards. +// Make sure I keep the leftmost pointer valid by traversing it +// When I free a leaf, be sure to adjust the pointers around it. + +#include "Rand.h" + +void main(void) +{ + DataStructures::BPlusTree btree; + DataStructures::List haveList, removedList; + int temp; + int i, j, index; + int testSize; + bool b; + + for (testSize=0; testSize < 514; testSize++) + { + printf("TestSize=%i\n", testSize); + + for (i=0; i < testSize; i++) + haveList.Insert(i); + + for (i=0; i < testSize; i++) + { + index=i+randomMT()%(testSize-i); + temp=haveList[index]; + haveList[index]=haveList[i]; + haveList[i]=temp; + } + + for (i=0; i + * + * OR + * + * AVLBalancedBinarySearchTree + * + * Use the AVL balanced tree if you want the tree to be balanced after every deletion and addition. This avoids the potential + * worst case scenario where ordered input to a binary search tree gives linear search time results. It's not needed + * if input will be evenly distributed, in which case the search time is O (log n). The search time for the AVL + * balanced binary tree is O (log n) irregardless of input. + * + * Has the following member functions + * unsigned int Height() - Returns the height of the tree at the optional specified starting index. Default is the root + * add(element) - adds an element to the BinarySearchTree + * bool del(element) - deletes the node containing element if the element is in the tree as defined by a comparison with the == operator. Returns true on success, false if the element is not found + * bool IsInelement) - returns true if element is in the tree as defined by a comparison with the == operator. Otherwise returns false + * DisplayInorder(array) - Fills an array with an inorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayPreorder(array) - Fills an array with an preorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayPostorder(array) - Fills an array with an postorder search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * DisplayBreadthFirstSearch(array) - Fills an array with a breadth first search of the elements in the tree. USER IS REPONSIBLE FOR ALLOCATING THE ARRAY!. + * clear - Destroys the tree. Same as calling the destructor + * unsigned int Height() - Returns the height of the tree + * unsigned int size() - returns the size of the BinarySearchTree + * GetPointerToNode(element) - returns a pointer to the comparision element in the tree, allowing for direct modification when necessary with complex data types. + * Be warned, it is possible to corrupt the tree if the element used for comparisons is modified. Returns NULL if the item is not found + * + * + * EXAMPLE + * @code + * BinarySearchTree A; + * A.Add(10); + * A.Add(15); + * A.Add(5); + * int* array = new int [A.Size()]; + * A.DisplayInorder(array); + * array[0]; // returns 5 + * array[1]; // returns 10 + * array[2]; // returns 15 + * @endcode + * compress - reallocates memory to fit the number of elements. Best used when the number of elements decreases + * + * clear - empties the BinarySearchTree and returns storage + * The assignment and copy constructors are defined + * + * \note The template type must have the copy constructor and + * assignment operator defined and must work with >, <, and == All + * elements in the tree MUST be distinct The assignment operator is + * defined between BinarySearchTree and AVLBalancedBinarySearchTree + * as long as they are of the same template type. However, passing a + * BinarySearchTree to an AVLBalancedBinarySearchTree will lose its + * structure unless it happened to be AVL balanced to begin with + * Requires queue_linked_list.cpp for the breadth first search used + * in the copy constructor, overloaded assignment operator, and + * display_breadth_first_search. + * + * + */ + template + class RAK_DLL_EXPORT BinarySearchTree + { + + public: + + struct node + { + BinarySearchTreeType* item; + node* left; + node* right; + }; + + BinarySearchTree(); + virtual ~BinarySearchTree(); + BinarySearchTree( const BinarySearchTree& original_type ); + BinarySearchTree& operator= ( const BinarySearchTree& original_copy ); + unsigned int Size( void ); + void Clear( void ); + unsigned int Height( node* starting_node = 0 ); + node* Add ( const BinarySearchTreeType& input ); + node* Del( const BinarySearchTreeType& input ); + bool IsIn( const BinarySearchTreeType& input ); + void DisplayInorder( BinarySearchTreeType* return_array ); + void DisplayPreorder( BinarySearchTreeType* return_array ); + void DisplayPostorder( BinarySearchTreeType* return_array ); + void DisplayBreadthFirstSearch( BinarySearchTreeType* return_array ); + BinarySearchTreeType*& GetPointerToNode( const BinarySearchTreeType& element ); + + protected: + + node* root; + + enum Direction_Types + { + NOT_FOUND, LEFT, RIGHT, ROOT + } + + direction; + unsigned int HeightRecursive( node* current ); + unsigned int BinarySearchTree_size; + node*& Find( const BinarySearchTreeType& element, node** parent ); + node*& FindParent( const BinarySearchTreeType& element ); + void DisplayPostorderRecursive( node* current, BinarySearchTreeType* return_array, unsigned int& index ); + void FixTree( node* current ); + + }; + + /// An AVLBalancedBinarySearchTree is a binary tree that is always balanced + template + class RAK_DLL_EXPORT AVLBalancedBinarySearchTree : public BinarySearchTree + { + + public: + AVLBalancedBinarySearchTree() {} + virtual ~AVLBalancedBinarySearchTree(); + void Add ( const BinarySearchTreeType& input ); + void Del( const BinarySearchTreeType& input ); + BinarySearchTree& operator= ( BinarySearchTree& original_copy ) + { + return BinarySearchTree::operator= ( original_copy ); + } + + private: + void BalanceTree( typename BinarySearchTree::node* current, bool rotateOnce ); + void RotateRight( typename BinarySearchTree::node *C ); + void RotateLeft( typename BinarySearchTree::node* C ); + void DoubleRotateRight( typename BinarySearchTree::node *A ); + void DoubleRotateLeft( typename BinarySearchTree::node* A ); + bool RightHigher( typename BinarySearchTree::node* A ); + bool LeftHigher( typename BinarySearchTree::node* A ); + }; + + template + void AVLBalancedBinarySearchTree::BalanceTree( typename BinarySearchTree::node* current, bool rotateOnce ) + { + int left_height, right_height; + + while ( current ) + { + if ( current->left == 0 ) + left_height = 0; + else + left_height = Height( current->left ); + + if ( current->right == 0 ) + right_height = 0; + else + right_height = Height( current->right ); + + if ( right_height - left_height == 2 ) + { + if ( RightHigher( current->right ) ) + RotateLeft( current->right ); + else + DoubleRotateLeft( current ); + + if ( rotateOnce ) + break; + } + + else + if ( right_height - left_height == -2 ) + { + if ( LeftHigher( current->left ) ) + RotateRight( current->left ); + else + DoubleRotateRight( current ); + + if ( rotateOnce ) + break; + } + + if ( current == this->root ) + break; + + current = FindParent( *( current->item ) ); + + } + } + + template + void AVLBalancedBinarySearchTree::Add ( const BinarySearchTreeType& input ) + { + + typename BinarySearchTree::node * current = BinarySearchTree::Add ( input ); + BalanceTree( current, true ); + } + + template + void AVLBalancedBinarySearchTree::Del( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * current = BinarySearchTree::Del( input ); + BalanceTree( current, false ); + + } + + template + bool AVLBalancedBinarySearchTree::RightHigher( typename BinarySearchTree::node *A ) + { + if ( A == 0 ) + return false; + + return Height( A->right ) > Height( A->left ); + } + + template + bool AVLBalancedBinarySearchTree::LeftHigher( typename BinarySearchTree::node *A ) + { + if ( A == 0 ) + return false; + + return Height( A->left ) > Height( A->right ); + } + + template + void AVLBalancedBinarySearchTree::RotateRight( typename BinarySearchTree::node *C ) + { + typename BinarySearchTree::node * A, *B, *D; + /* + RIGHT ROTATION + + A = parent(b) + b= parent(c) + c = node to rotate around + + A + | // Either direction + B + / \ + C + / \ + D + + TO + + A + | // Either Direction + C + / \ + B + / \ + D + + + + + */ + + B = FindParent( *( C->item ) ); + A = FindParent( *( B->item ) ); + D = C->right; + + if ( A ) + { + // Direction was set by the last find_parent call + + if ( this->direction == this->LEFT ) + A->left = C; + else + A->right = C; + } + + else + this->root = C; // If B has no parent parent then B must have been the root node + + B->left = D; + + C->right = B; + } + + template + void AVLBalancedBinarySearchTree::DoubleRotateRight( typename BinarySearchTree::node *A ) + { + // The left side of the left child must be higher for the tree to balance with a right rotation. If it isn't, rotate it left before the normal rotation so it is. + RotateLeft( A->left->right ); + RotateRight( A->left ); + } + + template + void AVLBalancedBinarySearchTree::RotateLeft( typename BinarySearchTree::node *C ) + { + typename BinarySearchTree::node * A, *B, *D; + /* + RIGHT ROTATION + + A = parent(b) + b= parent(c) + c = node to rotate around + + A + | // Either direction + B + / \ + C + / \ + D + + TO + + A + | // Either Direction + C + / \ + B + / \ + D + + + + + */ + + B = FindParent( *( C->item ) ); + A = FindParent( *( B->item ) ); + D = C->left; + + if ( A ) + { + // Direction was set by the last find_parent call + + if ( this->direction == this->LEFT ) + A->left = C; + else + A->right = C; + } + + else + this->root = C; // If B has no parent parent then B must have been the root node + + B->right = D; + + C->left = B; + } + + template + void AVLBalancedBinarySearchTree::DoubleRotateLeft( typename BinarySearchTree::node *A ) + { + // The left side of the right child must be higher for the tree to balance with a left rotation. If it isn't, rotate it right before the normal rotation so it is. + RotateRight( A->right->left ); + RotateLeft( A->right ); + } + + template + AVLBalancedBinarySearchTree::~AVLBalancedBinarySearchTree() + { + this->Clear(); + } + + template + unsigned int BinarySearchTree::Size( void ) + { + return BinarySearchTree_size; + } + + template + unsigned int BinarySearchTree::Height( typename BinarySearchTree::node* starting_node ) + { + if ( BinarySearchTree_size == 0 || starting_node == 0 ) + return 0; + else + return HeightRecursive( starting_node ); + } + + // Recursively return the height of a binary tree + template + unsigned int BinarySearchTree::HeightRecursive( typename BinarySearchTree::node* current ) + { + unsigned int left_height = 0, right_height = 0; + + if ( ( current->left == 0 ) && ( current->right == 0 ) ) + return 1; // Leaf + + if ( current->left != 0 ) + left_height = 1 + HeightRecursive( current->left ); + + if ( current->right != 0 ) + right_height = 1 + HeightRecursive( current->right ); + + if ( left_height > right_height ) + return left_height; + else + return right_height; + } + + template + BinarySearchTree::BinarySearchTree() + { + BinarySearchTree_size = 0; + root = 0; + } + + template + BinarySearchTree::~BinarySearchTree() + { + this->Clear(); + } + + template + BinarySearchTreeType*& BinarySearchTree::GetPointerToNode( const BinarySearchTreeType& element ) + { + static typename BinarySearchTree::node * tempnode; + static BinarySearchTreeType* dummyptr = 0; + tempnode = Find ( element, &tempnode ); + + if ( this->direction == this->NOT_FOUND ) + return dummyptr; + + return tempnode->item; + } + + template + typename BinarySearchTree::node*& BinarySearchTree::Find( const BinarySearchTreeType& element, typename BinarySearchTree::node** parent ) + { + static typename BinarySearchTree::node * current; + + current = this->root; + *parent = 0; + this->direction = this->ROOT; + + if ( BinarySearchTree_size == 0 ) + { + this->direction = this->NOT_FOUND; + return current = 0; + } + + // Check if the item is at the root + if ( element == *( current->item ) ) + { + this->direction = this->ROOT; + return current; + } + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + // Move pointer + + if ( element < *( current->item ) ) + { + *parent = current; + this->direction = this->LEFT; + current = current->left; + } + + else + if ( element > *( current->item ) ) + { + *parent = current; + this->direction = this->RIGHT; + current = current->right; + } + + if ( current == 0 ) + break; + + // Check if new position holds the item + if ( element == *( current->item ) ) + { + return current; + } + } + + + this->direction = this->NOT_FOUND; + return current = 0; + } + + template + typename BinarySearchTree::node*& BinarySearchTree::FindParent( const BinarySearchTreeType& element ) + { + static typename BinarySearchTree::node * parent; + Find ( element, &parent ); + return parent; + } + + // Performs a series of value swaps starting with current to fix the tree if needed + template + void BinarySearchTree::FixTree( typename BinarySearchTree::node* current ) + { + BinarySearchTreeType temp; + + while ( 1 ) + { + if ( ( ( current->left ) != 0 ) && ( *( current->item ) < *( current->left->item ) ) ) + { + // Swap the current value with the one to the left + temp = *( current->left->item ); + *( current->left->item ) = *( current->item ); + *( current->item ) = temp; + current = current->left; + } + + else + if ( ( ( current->right ) != 0 ) && ( *( current->item ) > *( current->right->item ) ) ) + { + // Swap the current value with the one to the right + temp = *( current->right->item ); + *( current->right->item ) = *( current->item ); + *( current->item ) = temp; + current = current->right; + } + + else + break; // current points to the right place so quit + } + } + + template + typename BinarySearchTree::node* BinarySearchTree::Del( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * node_to_delete, *current, *parent; + + if ( BinarySearchTree_size == 0 ) + return 0; + + if ( BinarySearchTree_size == 1 ) + { + Clear(); + return 0; + } + + node_to_delete = Find( input, &parent ); + + if ( direction == NOT_FOUND ) + return 0; // Couldn't find the element + + current = node_to_delete; + + // Replace the deleted node with the appropriate value + if ( ( current->right ) == 0 && ( current->left ) == 0 ) // Leaf node, just remove it + { + + if ( parent ) + { + if ( direction == LEFT ) + parent->left = 0; + else + parent->right = 0; + } + + delete node_to_delete->item; + delete node_to_delete; + BinarySearchTree_size--; + return parent; + } + else + if ( ( current->right ) != 0 && ( current->left ) == 0 ) // Node has only one child, delete it and cause the parent to point to that child + { + + if ( parent ) + { + if ( direction == RIGHT ) + parent->right = current->right; + else + parent->left = current->right; + } + + else + root = current->right; // Without a parent this must be the root node + + delete node_to_delete->item; + + delete node_to_delete; + + BinarySearchTree_size--; + + return parent; + } + else + if ( ( current->right ) == 0 && ( current->left ) != 0 ) // Node has only one child, delete it and cause the parent to point to that child + { + + if ( parent ) + { + if ( direction == RIGHT ) + parent->right = current->left; + else + parent->left = current->left; + } + + else + root = current->left; // Without a parent this must be the root node + + delete node_to_delete->item; + + delete node_to_delete; + + BinarySearchTree_size--; + + return parent; + } + else // Go right, then as left as far as you can + { + parent = current; + direction = RIGHT; + current = current->right; // Must have a right branch because the if statements above indicated that it has 2 branches + + while ( current->left ) + { + direction = LEFT; + parent = current; + current = current->left; + } + + // Replace the value held by the node to delete with the value pointed to by current; + *( node_to_delete->item ) = *( current->item ); + + // Delete current. + // If it is a leaf node just delete it + if ( current->right == 0 ) + { + if ( direction == RIGHT ) + parent->right = 0; + else + parent->left = 0; + + delete current->item; + + delete current; + + BinarySearchTree_size--; + + return parent; + } + + else + { + // Skip this node and make its parent point to its right branch + + if ( direction == RIGHT ) + parent->right = current->right; + else + parent->left = current->right; + + delete current->item; + + delete current; + + BinarySearchTree_size--; + + return parent; + } + } + } + + template + typename BinarySearchTree::node* BinarySearchTree::Add ( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * current, *parent; + + // Add the new element to the tree according to the following alogrithm: + // 1. If the current node is empty add the new leaf + // 2. If the element is less than the current node then go down the left branch + // 3. If the element is greater than the current node then go down the right branch + + if ( BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 1; + root = new typename BinarySearchTree::node; + root->item = new BinarySearchTreeType; + *( root->item ) = input; + root->left = 0; + root->right = 0; + + return root; + } + + else + { + // start at the root + current = parent = root; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) // This loop traverses the tree to find a spot for insertion + { + + if ( input < *( current->item ) ) + { + if ( current->left == 0 ) + { + current->left = new typename BinarySearchTree::node; + current->left->item = new BinarySearchTreeType; + current = current->left; + current->left = 0; + current->right = 0; + *( current->item ) = input; + + BinarySearchTree_size++; + return current; + } + + else + { + parent = current; + current = current->left; + } + } + + else + if ( input > *( current->item ) ) + { + if ( current->right == 0 ) + { + current->right = new typename BinarySearchTree::node; + current->right->item = new BinarySearchTreeType; + current = current->right; + current->left = 0; + current->right = 0; + *( current->item ) = input; + + BinarySearchTree_size++; + return current; + } + + else + { + parent = current; + current = current->right; + } + } + + else + return 0; // ((input == current->item) == true) which is not allowed since the tree only takes discrete values. Do nothing + } + } + } + + template + bool BinarySearchTree::IsIn( const BinarySearchTreeType& input ) + { + typename BinarySearchTree::node * parent; + find( input, &parent ); + + if ( direction != NOT_FOUND ) + return true; + else + return false; + } + + + template + void BinarySearchTree::DisplayInorder( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current, *parent; + bool just_printed = false; + + unsigned int index = 0; + + current = root; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + + direction = ROOT; // Reset the direction + + while ( index != BinarySearchTree_size ) + { + // direction is set by the find function and holds the direction of the parent to the last node visited. It is used to prevent revisiting nodes + + if ( ( current->left != 0 ) && ( direction != LEFT ) && ( direction != RIGHT ) ) + { + // Go left if the following 2 conditions are true + // I can go left + // I did not just move up from a right child + // I did not just move up from a left child + + current = current->left; + direction = ROOT; // Reset the direction + } + + else + if ( ( direction != RIGHT ) && ( just_printed == false ) ) + { + // Otherwise, print the current node if the following 3 conditions are true: + // I did not just move up from a right child + // I did not print this ndoe last cycle + + return_array[ index++ ] = *( current->item ); + just_printed = true; + } + + else + if ( ( current->right != 0 ) && ( direction != RIGHT ) ) + { + // Otherwise, go right if the following 2 conditions are true + // I did not just move up from a right child + // I can go right + + current = current->right; + direction = ROOT; // Reset the direction + just_printed = false; + } + + else + { + // Otherwise I've done everything I can. Move up the tree one node + parent = FindParent( *( current->item ) ); + current = parent; + just_printed = false; + } + } + } + + template + void BinarySearchTree::DisplayPreorder( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current, *parent; + + unsigned int index = 0; + + current = root; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + + direction = ROOT; // Reset the direction + return_array[ index++ ] = *( current->item ); + + while ( index != BinarySearchTree_size ) + { + // direction is set by the find function and holds the direction of the parent to the last node visited. It is used to prevent revisiting nodes + + if ( ( current->left != 0 ) && ( direction != LEFT ) && ( direction != RIGHT ) ) + { + + current = current->left; + direction = ROOT; + + // Everytime you move a node print it + return_array[ index++ ] = *( current->item ); + } + + else + if ( ( current->right != 0 ) && ( direction != RIGHT ) ) + { + current = current->right; + direction = ROOT; + + // Everytime you move a node print it + return_array[ index++ ] = *( current->item ); + } + + else + { + // Otherwise I've done everything I can. Move up the tree one node + parent = FindParent( *( current->item ) ); + current = parent; + } + } + } + + template + inline void BinarySearchTree::DisplayPostorder( BinarySearchTreeType* return_array ) + { + unsigned int index = 0; + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + DisplayPostorderRecursive( root, return_array, index ); + } + + + // Recursively do a postorder traversal + template + void BinarySearchTree::DisplayPostorderRecursive( typename BinarySearchTree::node* current, BinarySearchTreeType* return_array, unsigned int& index ) + { + if ( current->left != 0 ) + DisplayPostorderRecursive( current->left, return_array, index ); + + if ( current->right != 0 ) + DisplayPostorderRecursive( current->right, return_array, index ); + + return_array[ index++ ] = *( current->item ); + + } + + + template + void BinarySearchTree::DisplayBreadthFirstSearch( BinarySearchTreeType* return_array ) + { + typename BinarySearchTree::node * current; + unsigned int index = 0; + + // Display the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + + if ( BinarySearchTree_size == 0 ) + return ; // Do nothing for an empty tree + + else + if ( BinarySearchTree_size == 1 ) + { + return_array[ 0 ] = *( root->item ); + return ; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( root ); + + do + { + current = tree_queue.Pop(); + return_array[ index++ ] = *( current->item ); + + // Add the child or children of the tree I am copying from to the queue + + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + } + + + template + BinarySearchTree::BinarySearchTree( const BinarySearchTree& original_copy ) + { + typename BinarySearchTree::node * current; + // Copy the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + + // This is a copy of the constructor. A bug in Visual C++ made it so if I just put the constructor call here the variable assignments were ignored. + BinarySearchTree_size = 0; + root = 0; + + if ( original_copy.BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 0; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( original_copy.root ); + + do + { + current = tree_queue.Pop(); + + Add ( *( current->item ) ) + + ; + + // Add the child or children of the tree I am copying from to the queue + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + } + + template + BinarySearchTree& BinarySearchTree::operator= ( const BinarySearchTree& original_copy ) + { + typename BinarySearchTree::node * current; + + if ( ( &original_copy ) == this ) + return *this; + + Clear(); // Remove the current tree + + // This is a copy of the constructor. A bug in Visual C++ made it so if I just put the constructor call here the variable assignments were ignored. + BinarySearchTree_size = 0; + + root = 0; + + + // Copy the tree using a breadth first search + // Put the children of the current node into the queue + // Pop the queue, put its children into the queue, repeat until queue is empty + if ( original_copy.BinarySearchTree_size == 0 ) + { + BinarySearchTree_size = 0; + } + + else + { + DataStructures::QueueLinkedList tree_queue; + + // Add the root of the tree I am copying from + tree_queue.Push( original_copy.root ); + + do + { + current = tree_queue.Pop(); + + Add ( *( current->item ) ) + + ; + + // Add the child or children of the tree I am copying from to the queue + if ( current->left != 0 ) + tree_queue.Push( current->left ); + + if ( current->right != 0 ) + tree_queue.Push( current->right ); + + } + + while ( tree_queue.Size() > 0 ); + } + + return *this; + } + + template + inline void BinarySearchTree::Clear ( void ) + { + typename BinarySearchTree::node * current, *parent; + + current = root; + + while ( BinarySearchTree_size > 0 ) + { + if ( BinarySearchTree_size == 1 ) + { + delete root->item; + delete root; + root = 0; + BinarySearchTree_size = 0; + } + + else + { + if ( current->left != 0 ) + { + current = current->left; + } + + else + if ( current->right != 0 ) + { + current = current->right; + } + + else // leaf + { + // Not root node so must have a parent + parent = FindParent( *( current->item ) ); + + if ( ( parent->left ) == current ) + parent->left = 0; + else + parent->right = 0; + + delete current->item; + + delete current; + + current = parent; + + BinarySearchTree_size--; + } + } + } + } + +} // End namespace + +#endif + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/DS_ByteQueue.cpp b/raknet/DS_ByteQueue.cpp new file mode 100644 index 0000000..0243625 --- /dev/null +++ b/raknet/DS_ByteQueue.cpp @@ -0,0 +1,101 @@ +#include "DS_ByteQueue.h" +#include // Memmove +//#include // PS3 doesn't have this +#include // realloc +#include + +using namespace DataStructures; + +ByteQueue::ByteQueue() +{ + readOffset=writeOffset=lengthAllocated=0; + data=0; +} +ByteQueue::~ByteQueue() +{ + Clear(); + + +} +void ByteQueue::WriteBytes(const char *in, unsigned length) +{ + unsigned bytesWritten; + bytesWritten=GetBytesWritten(); + if (lengthAllocated==0 || length > lengthAllocated-bytesWritten-1) + { + unsigned oldLengthAllocated=lengthAllocated; + unsigned newAmountToAllocate=length*2; + if (newAmountToAllocate<256) + newAmountToAllocate=256; + lengthAllocated=lengthAllocated + newAmountToAllocate; + data=(char*)realloc(data, lengthAllocated); + if (writeOffset < readOffset) + { + if (writeOffset <= newAmountToAllocate) + { + memcpy(data + oldLengthAllocated, data, writeOffset); + writeOffset=readOffset+bytesWritten; + } + else + { + memcpy(data + oldLengthAllocated, data, newAmountToAllocate); + memmove(data, data+newAmountToAllocate, writeOffset-newAmountToAllocate); + writeOffset-=newAmountToAllocate; + } + } + } + + if (length <= lengthAllocated-writeOffset) + memcpy(data+writeOffset, in, length); + else + { + // Wrap + memcpy(data+writeOffset, in, lengthAllocated-writeOffset); + memcpy(data, in+(lengthAllocated-writeOffset), length-(lengthAllocated-writeOffset)); + } + writeOffset=(writeOffset+length) % lengthAllocated; +} +bool ByteQueue::ReadBytes(char *out, unsigned length, bool peek) +{ + if (GetBytesWritten() < length) + return false; + + if (length <= lengthAllocated-readOffset) + memcpy(out, data+readOffset, length); + else + { + // Wrap + memcpy(out, data+readOffset, lengthAllocated-readOffset); + memcpy(out+(lengthAllocated-readOffset), data, length-(lengthAllocated-readOffset)); + } + + if (peek==false) + IncrementReadOffset(length); + + return true; +} +void ByteQueue::Clear(void) +{ + if (lengthAllocated) + free(data); + readOffset=writeOffset=lengthAllocated=0; + data=0; +} +unsigned ByteQueue::GetBytesWritten(void) const +{ + if (writeOffset>=readOffset) + return writeOffset-readOffset; + else + return (writeOffset-1)+(lengthAllocated-readOffset); +} +void ByteQueue::IncrementReadOffset(unsigned length) +{ + readOffset=(readOffset+length) % lengthAllocated; +} +void ByteQueue::Print(void) +{ + unsigned i; + for (i=readOffset; i!=writeOffset; i++) + printf("%i ", data[i]); + printf("\n"); +} diff --git a/raknet/DS_ByteQueue.h b/raknet/DS_ByteQueue.h new file mode 100644 index 0000000..c259e23 --- /dev/null +++ b/raknet/DS_ByteQueue.h @@ -0,0 +1,45 @@ +/// \file +/// \brief \b [Internal] Byte queue +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __BYTE_QUEUE_H +#define __BYTE_QUEUE_H + +#include "Export.h" + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + class ByteQueue + { + public: + ByteQueue(); + ~ByteQueue(); + void WriteBytes(const char *in, unsigned length); + bool ReadBytes(char *out, unsigned length, bool peek); + unsigned GetBytesWritten(void) const; + void IncrementReadOffset(unsigned length); + void Clear(void); + void Print(void); + + protected: + char *data; + unsigned readOffset, writeOffset, lengthAllocated; + }; +} + +#endif diff --git a/raknet/DS_Heap.h b/raknet/DS_Heap.h new file mode 100644 index 0000000..2b5ea08 --- /dev/null +++ b/raknet/DS_Heap.h @@ -0,0 +1,250 @@ +/// \file +/// \brief \b [Internal] Heap (Also serves as a priority queue) +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __RAKNET_HEAP_H +#define __RAKNET_HEAP_H + +#include "DS_List.h" +#include "Export.h" +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + template + class RAK_DLL_EXPORT Heap + { + public: + struct HeapNode + { + HeapNode() {} + HeapNode(const weight_type &w, const data_type &d) : weight(w), data(d) {} + weight_type weight; // I'm assuming key is a native numerical type - float or int + data_type data; + }; + + Heap(); + ~Heap(); + void Push(const weight_type &weight, const data_type &data); + data_type Pop(const unsigned startingIndex); + data_type Peek(const unsigned startingIndex=0) const; + weight_type PeekWeight(const unsigned startingIndex=0) const; + void Clear(void); + data_type& operator[] ( const unsigned int position ) const; + unsigned Size(void) const; + + protected: + unsigned LeftChild(const unsigned i) const; + unsigned RightChild(const unsigned i) const; + unsigned Parent(const unsigned i) const; + void Swap(const unsigned i, const unsigned j); + DataStructures::List heap; + }; + + template + Heap::Heap() + { + } + + template + Heap::~Heap() + { + Clear(); + } + + template + void Heap::Push(const weight_type &weight, const data_type &data) + { + unsigned currentIndex = heap.Size(); + unsigned parentIndex; + heap.Insert(HeapNode(weight, data)); + while (currentIndex!=0) + { + parentIndex = Parent(currentIndex); +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (isMaxHeap) + { + if (heap[parentIndex].weight < weight) + { + Swap(currentIndex, parentIndex); + currentIndex=parentIndex; + } + else + break; + } + else + { + if (heap[parentIndex].weight > weight) + { + Swap(currentIndex, parentIndex); + currentIndex=parentIndex; + } + else + break; + } + } + } + + template + data_type Heap::Pop(const unsigned startingIndex) + { + // While we have children, swap out with the larger of the two children. + + // This line will assert on an empty heap + data_type returnValue=heap[0].data; + + // Move the last element to the head, and re-heapify + heap[startingIndex]=heap[heap.Size()-1]; + + unsigned currentIndex,leftChild,rightChild; + weight_type currentWeight; + currentIndex=startingIndex; + currentWeight=heap[startingIndex].weight; + heap.Del(); + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + leftChild=LeftChild(currentIndex); + rightChild=RightChild(currentIndex); + if (leftChild >= heap.Size()) + { + // Done + return returnValue; + } + if (rightChild >= heap.Size()) + { + // Only left node. + if ((isMaxHeap==true && currentWeight < heap[leftChild].weight) || + (isMaxHeap==false && currentWeight > heap[leftChild].weight)) + Swap(leftChild, currentIndex); + + return returnValue; + } + else + { + // Swap with the bigger/smaller of the two children and continue + if (isMaxHeap) + { + if (heap[leftChild].weight <= currentWeight && heap[rightChild].weight <= currentWeight) + return returnValue; + + if (heap[leftChild].weight > heap[rightChild].weight) + { + Swap(leftChild, currentIndex); + currentIndex=leftChild; + } + else + { + Swap(rightChild, currentIndex); + currentIndex=rightChild; + } + } + else + { + if (heap[leftChild].weight >= currentWeight && heap[rightChild].weight >= currentWeight) + return returnValue; + + if (heap[leftChild].weight < heap[rightChild].weight) + { + Swap(leftChild, currentIndex); + currentIndex=leftChild; + } + else + { + Swap(rightChild, currentIndex); + currentIndex=rightChild; + } + } + } + } + } + + template + data_type Heap::Peek(const unsigned startingIndex) const + { + return heap[startingIndex].data; + } + + template + weight_type Heap::PeekWeight(const unsigned startingIndex) const + { + return heap[startingIndex].weight; + } + + template + void Heap::Clear(void) + { + heap.Clear(); + } + + template + data_type& Heap::operator[] ( const unsigned int position ) const + { + return heap[position].data; + } + template + unsigned Heap::Size(void) const + { + return heap.Size(); + } + + template + unsigned Heap::LeftChild(const unsigned i) const + { + return i*2+1; + } + + template + unsigned Heap::RightChild(const unsigned i) const + { + return i*2+2; + } + + template + unsigned Heap::Parent(const unsigned i) const + { +#ifdef _DEBUG + assert(i!=0); +#endif + return (i-1)/2; + } + + template + void Heap::Swap(const unsigned i, const unsigned j) + { + HeapNode temp; + temp=heap[i]; + heap[i]=heap[j]; + heap[j]=temp; + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/raknet/DS_HuffmanEncodingTree.cpp b/raknet/DS_HuffmanEncodingTree.cpp new file mode 100644 index 0000000..a2632dd --- /dev/null +++ b/raknet/DS_HuffmanEncodingTree.cpp @@ -0,0 +1,306 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "DS_HuffmanEncodingTree.h" +#include "DS_Queue.h" +#include "BitStream.h" +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +HuffmanEncodingTree::HuffmanEncodingTree() +{ + root = 0; +} + +HuffmanEncodingTree::~HuffmanEncodingTree() +{ + FreeMemory(); +} + +void HuffmanEncodingTree::FreeMemory( void ) +{ + if ( root == 0 ) + return ; + + // Use an in-order traversal to delete the tree + DataStructures::Queue nodeQueue; + + HuffmanEncodingTreeNode *node; + + nodeQueue.Push( root ); + + while ( nodeQueue.Size() > 0 ) + { + node = nodeQueue.Pop(); + + if ( node->left ) + nodeQueue.Push( node->left ); + + if ( node->right ) + nodeQueue.Push( node->right ); + + delete node; + } + + // Delete the encoding table + for ( int i = 0; i < 256; i++ ) + delete [] encodingTable[ i ].encoding; + + root = 0; +} + + +////#include + +// Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree +void HuffmanEncodingTree::GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] ) +{ + int counter; + HuffmanEncodingTreeNode * node; + HuffmanEncodingTreeNode *leafList[ 256 ]; // Keep a copy of the pointers to all the leaves so we can generate the encryption table bottom-up, which is easier + // 1. Make 256 trees each with a weight equal to the frequency of the corresponding character + DataStructures::LinkedList huffmanEncodingTreeNodeList; + + FreeMemory(); + + for ( counter = 0; counter < 256; counter++ ) + { + node = new HuffmanEncodingTreeNode; + node->left = 0; + node->right = 0; + node->value = (unsigned char) counter; + node->weight = frequencyTable[ counter ]; + + if ( node->weight == 0 ) + node->weight = 1; // 0 weights are illegal + + leafList[ counter ] = node; // Used later to generate the encryption table + + InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList ); // Insert and maintain sort order. + } + + + // 2. While there is more than one tree, take the two smallest trees and merge them so that the two trees are the left and right + // children of a new node, where the new node has the weight the sum of the weight of the left and right child nodes. +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( 1 ) + { + huffmanEncodingTreeNodeList.Beginning(); + HuffmanEncodingTreeNode *lesser, *greater; + lesser = huffmanEncodingTreeNodeList.Pop(); + greater = huffmanEncodingTreeNodeList.Pop(); + node = new HuffmanEncodingTreeNode; + node->left = lesser; + node->right = greater; + node->weight = lesser->weight + greater->weight; + lesser->parent = node; // This is done to make generating the encryption table easier + greater->parent = node; // This is done to make generating the encryption table easier + + if ( huffmanEncodingTreeNodeList.Size() == 0 ) + { + // 3. Assign the one remaining node in the list to the root node. + root = node; + root->parent = 0; + break; + } + + // Put the new node back into the list at the correct spot to maintain the sort. Linear search time + InsertNodeIntoSortedList( node, &huffmanEncodingTreeNodeList ); + } + + bool tempPath[ 256 ]; // Maximum path length is 256 + unsigned short tempPathLength; + HuffmanEncodingTreeNode *currentNode; + RakNet::BitStream bitStream; + + // Generate the encryption table. From before, we have an array of pointers to all the leaves which contain pointers to their parents. + // This can be done more efficiently but this isn't bad and it's way easier to program and debug + + for ( counter = 0; counter < 256; counter++ ) + { + // Already done at the end of the loop and before it! + tempPathLength = 0; + + // Set the current node at the leaf + currentNode = leafList[ counter ]; + + do + { + if ( currentNode->parent->left == currentNode ) // We're storing the paths in reverse order.since we are going from the leaf to the root + tempPath[ tempPathLength++ ] = false; + else + tempPath[ tempPathLength++ ] = true; + + currentNode = currentNode->parent; + } + + while ( currentNode != root ); + + // Write to the bitstream in the reverse order that we stored the path, which gives us the correct order from the root to the leaf + while ( tempPathLength-- > 0 ) + { + if ( tempPath[ tempPathLength ] ) // Write 1's and 0's because writing a bool will write the BitStream TYPE_CHECKING validation bits if that is defined along with the actual data bit, which is not what we want + bitStream.Write1(); + else + bitStream.Write0(); + } + + // Read data from the bitstream, which is written to the encoding table in bits and bitlength. Note this function allocates the encodingTable[counter].encoding pointer + encodingTable[ counter ].bitLength = ( unsigned char ) bitStream.CopyData( &encodingTable[ counter ].encoding ); + + // Reset the bitstream for the next iteration + bitStream.Reset(); + } +} + +// Pass an array of bytes to array and a preallocated BitStream to receive the output +void HuffmanEncodingTree::EncodeArray( unsigned char *input, unsigned sizeInBytes, RakNet::BitStream * output ) +{ + unsigned counter; + + // For each input byte, Write out the corresponding series of 1's and 0's that give the encoded representation + for ( counter = 0; counter < sizeInBytes; counter++ ) + { + output->WriteBits( encodingTable[ input[ counter ] ].encoding, encodingTable[ input[ counter ] ].bitLength, false ); // Data is left aligned + } + + // Byte align the output so the unassigned remaining bits don't equate to some actual value + if ( output->GetNumberOfBitsUsed() % 8 != 0 ) + { + // Find an input that is longer than the remaining bits. Write out part of it to pad the output to be byte aligned. + unsigned char remainingBits = (unsigned char) ( 8 - ( output->GetNumberOfBitsUsed() % 8 ) ); + + for ( counter = 0; counter < 256; counter++ ) + if ( encodingTable[ counter ].bitLength > remainingBits ) + { + output->WriteBits( encodingTable[ counter ].encoding, remainingBits, false ); // Data is left aligned + break; + } + +#ifdef _DEBUG + assert( counter != 256 ); // Given 256 elements, we should always be able to find an input that would be >= 7 bits + +#endif + + } +} + +unsigned HuffmanEncodingTree::DecodeArray( RakNet::BitStream * input, unsigned sizeInBits, unsigned maxCharsToWrite, unsigned char *output ) +{ + HuffmanEncodingTreeNode * currentNode; + + unsigned outputWriteIndex; + outputWriteIndex = 0; + currentNode = root; + + // For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root + + for ( unsigned counter = 0; counter < sizeInBits; counter++ ) + { + if ( input->ReadBit() == false ) // left! + currentNode = currentNode->left; + else + currentNode = currentNode->right; + + if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf + { + + if ( outputWriteIndex < maxCharsToWrite ) + output[ outputWriteIndex ] = currentNode->value; + + outputWriteIndex++; + + currentNode = root; + } + } + + return outputWriteIndex; +} + +// Pass an array of encoded bytes to array and a preallocated BitStream to receive the output +void HuffmanEncodingTree::DecodeArray( unsigned char *input, unsigned sizeInBits, RakNet::BitStream * output ) +{ + HuffmanEncodingTreeNode * currentNode; + + if ( sizeInBits <= 0 ) + return ; + + RakNet::BitStream bitStream( input, BITS_TO_BYTES(sizeInBits), false ); + + currentNode = root; + + // For each bit, go left if it is a 0 and right if it is a 1. When we reach a leaf, that gives us the desired value and we restart from the root + for ( unsigned counter = 0; counter < sizeInBits; counter++ ) + { + if ( bitStream.ReadBit() == false ) // left! + currentNode = currentNode->left; + else + currentNode = currentNode->right; + + if ( currentNode->left == 0 && currentNode->right == 0 ) // Leaf + { + output->WriteBits( &( currentNode->value ), sizeof( char ) * 8, true ); // Use WriteBits instead of Write(char) because we want to avoid TYPE_CHECKING + currentNode = root; + } + } +} + +// Insertion sort. Slow but easy to write in this case +void HuffmanEncodingTree::InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList *huffmanEncodingTreeNodeList ) const +{ + if ( huffmanEncodingTreeNodeList->Size() == 0 ) + { + huffmanEncodingTreeNodeList->Insert( node ); + return ; + } + + huffmanEncodingTreeNodeList->Beginning(); + + unsigned counter = 0; +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( 1 ) + { + if ( huffmanEncodingTreeNodeList->Peek()->weight < node->weight ) + ++( *huffmanEncodingTreeNodeList ); + else + { + huffmanEncodingTreeNodeList->Insert( node ); + break; + } + + // Didn't find a spot in the middle - add to the end + if ( ++counter == huffmanEncodingTreeNodeList->Size() ) + { + huffmanEncodingTreeNodeList->End(); + + huffmanEncodingTreeNodeList->Add( node ) + + ; // Add to the end + break; + } + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/DS_HuffmanEncodingTree.h b/raknet/DS_HuffmanEncodingTree.h new file mode 100644 index 0000000..c146754 --- /dev/null +++ b/raknet/DS_HuffmanEncodingTree.h @@ -0,0 +1,70 @@ +/// \file +/// \brief \b [Internal] Generates a huffman encoding tree, used for string and global compression. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __HUFFMAN_ENCODING_TREE +#define __HUFFMAN_ENCODING_TREE + +#include "DS_HuffmanEncodingTreeNode.h" +#include "BitStream.h" +#include "Export.h" +#include "DS_LinkedList.h" + +/// This generates special cases of the huffman encoding tree using 8 bit keys with the additional condition that unused combinations of 8 bits are treated as a frequency of 1 +class RAK_DLL_EXPORT HuffmanEncodingTree +{ + +public: + HuffmanEncodingTree(); + ~HuffmanEncodingTree(); + + /// Pass an array of bytes to array and a preallocated BitStream to receive the output + /// \param [in] input Array of bytes to encode + /// \param [in] sizeInBytes size of \a input + /// \param [out] output The bitstream to write to + void EncodeArray( unsigned char *input, unsigned sizeInBytes, RakNet::BitStream * output ); + + // Decodes an array encoded by EncodeArray() + unsigned DecodeArray( RakNet::BitStream * input, unsigned sizeInBits, unsigned maxCharsToWrite, unsigned char *output ); + void DecodeArray( unsigned char *input, unsigned sizeInBits, RakNet::BitStream * output ); + + /// Given a frequency table of 256 elements, all with a frequency of 1 or more, generate the tree + void GenerateFromFrequencyTable( unsigned int frequencyTable[ 256 ] ); + + /// Free the memory used by the tree + void FreeMemory( void ); + +private: + + /// The root node of the tree + + HuffmanEncodingTreeNode *root; + + /// Used to hold bit encoding for one character + + + struct CharacterEncoding + { + unsigned char* encoding; + unsigned short bitLength; + }; + + CharacterEncoding encodingTable[ 256 ]; + + void InsertNodeIntoSortedList( HuffmanEncodingTreeNode * node, DataStructures::LinkedList *huffmanEncodingTreeNodeList ) const; +}; + +#endif diff --git a/raknet/DS_HuffmanEncodingTreeFactory.h b/raknet/DS_HuffmanEncodingTreeFactory.h new file mode 100644 index 0000000..d8996a6 --- /dev/null +++ b/raknet/DS_HuffmanEncodingTreeFactory.h @@ -0,0 +1,60 @@ +/// \file +/// \brief \b [Internal] Creates instances of the class HuffmanEncodingTree +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __HUFFMAN_ENCODING_TREE_FACTORY +#define __HUFFMAN_ENCODING_TREE_FACTORY + +class HuffmanEncodingTree; + +/// \brief Creates instances of the class HuffmanEncodingTree +/// +/// This class takes a frequency table and given that frequence table, will generate an instance of HuffmanEncodingTree +class HuffmanEncodingTreeFactory +{ +public: + /// Default constructor + HuffmanEncodingTreeFactory(); + + /// Reset the frequency table. You don't need to call this unless you want to reuse the class for a new tree + void Reset( void ); + + /// Pass an array of bytes to this to add those elements to the frequency table + /// \param[in] array the data to insert into the frequency table + /// \param[in] size the size of the data to insert + void AddToFrequencyTable( unsigned char *array, int size ); + + /// Copies the frequency table to the array passed + /// Retrieve the frequency table + /// \param[in] _frequency The frequency table used currently + void GetFrequencyTable( unsigned int _frequency[ 256 ] ); + + /// Returns the frequency table as a pointer + /// \return the address of the frenquency table + unsigned int * GetFrequencyTable( void ); + + /// Generate a HuffmanEncodingTree. + /// You can also use GetFrequencyTable and GenerateFromFrequencyTable in the tree itself + /// \return The generated instance of HuffmanEncodingTree + HuffmanEncodingTree * GenerateTree( void ); + +private: + + /// Frequency table + unsigned int frequency[ 256 ]; +}; + +#endif diff --git a/raknet/DS_HuffmanEncodingTreeNode.h b/raknet/DS_HuffmanEncodingTreeNode.h new file mode 100644 index 0000000..349ba51 --- /dev/null +++ b/raknet/DS_HuffmanEncodingTreeNode.h @@ -0,0 +1,30 @@ +/// \file +/// \brief \b [Internal] A single node in the Huffman Encoding Tree. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __HUFFMAN_ENCODING_TREE_NODE +#define __HUFFMAN_ENCODING_TREE_NODE + +struct HuffmanEncodingTreeNode +{ + unsigned char value; + unsigned weight; + HuffmanEncodingTreeNode *left; + HuffmanEncodingTreeNode *right; + HuffmanEncodingTreeNode *parent; +}; + +#endif diff --git a/raknet/DS_LinkedList.h b/raknet/DS_LinkedList.h new file mode 100644 index 0000000..cfe6a03 --- /dev/null +++ b/raknet/DS_LinkedList.h @@ -0,0 +1,1258 @@ +/// \file +/// \brief \b [Internal] Straightforward linked list data structure. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __LINKED_LIST_H +#define __LINKED_LIST_H + +#include "Export.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + // Prototype to prevent error in CircularLinkedList class when a reference is made to a LinkedList class + template + class RAK_DLL_EXPORT LinkedList; + + /** + * \brief (Circular) Linked List ADT (Doubly Linked Pointer to Node Style) - + * + * By Kevin Jenkins (http://www.rakkar.org) + * Initilize with the following command + * LinkedList + * OR + * CircularLinkedList + * + * Has the following member functions + * - size: returns number of elements in the linked list + * - insert(item): inserts @em item at the current position in + * the LinkedList. + * - add(item): inserts @em item after the current position in + * the LinkedList. Does not increment the position + * - replace(item): replaces the element at the current position @em item. + * - peek: returns the element at the current position + * - pop: returns the element at the current position and deletes it + * - del: deletes the current element. Does nothing for an empty list. + * - clear: empties the LinkedList and returns storage + * - bool IsInitem): Does a linear search for @em item. Does not set + * the position to it, only returns true on item found, false otherwise + * - bool find(item): Does a linear search for @em item and sets the current + * position to point to it if and only if the item is found. Returns true + * on item found, false otherwise + * - sort: Sorts the elements of the list with a mergesort and sets the + * current pointer to the first element + * - concatenate(list L): This appends L to the current list + * - ++(prefix): moves the pointer one element up in the list and returns the + * appropriate copy of the element in the list + * - --(prefix): moves the pointer one element back in the list and returns + * the appropriate copy of the element in the list + * - beginning - moves the pointer to the start of the list. For circular + * linked lists this is first 'position' created. You should call this + * after the sort function to read the first value. + * - end - moves the pointer to the end of the list. For circular linked + * lists this is one less than the first 'position' created + * The assignment and copy constructor operators are defined + * + * \note + * 1. LinkedList and CircularLinkedList are exactly the same except LinkedList + * won't let you wrap around the root and lets you jump to two positions + * relative to the root/ + * 2. Postfix ++ and -- can be used but simply call the prefix versions. + * + * + * EXAMPLE: + * @code + * LinkedList A; // Creates a Linked List of integers called A + * CircularLinkedList B; // Creates a Circular Linked List of + * // integers called B + * + * A.Insert(20); // Adds 20 to A. A: 20 - current is 20 + * A.Insert(5); // Adds 5 to A. A: 5 20 - current is 5 + * A.Insert(1); // Adds 1 to A. A: 1 5 20 - current is 1 + * + * A.IsIn1); // returns true + * A.IsIn200); // returns false + * A.Find(5); // returns true and sets current to 5 + * A.Peek(); // returns 5 + * A.Find(1); // returns true and sets current to 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 5 + * + * A.Replace(10); // Replaces 5 with 10. + * A.Peek(); // Returns 10 + * + * A.Beginning(); // Current points to the beginning of the list at 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 10 + * + * A.Del(); // Deletes 10. Current points to the next element, which is 20 + * A.Peek(); // Returns 20 + * + * A.Beginning(); // Current points to the beginning of the list at 1 + * + * (++A).Peek(); // Returns 5 + * A.Peek(); // Returns 20 + * + * A.Clear(); // Deletes all nodes in A + * + * A.Insert(5); // A: 5 - current is 5 + * A.Insert(6); // A: 6 5 - current is 6 + * A.Insert(7); // A: 7 6 5 - current is 7 + * + * A.Clear(); + * B.Clear(); + * + * B.Add(10); + * B.Add(20); + * B.Add(30); + * B.Add(5); + * B.Add(2); + * B.Add(25); + * // Sorts the numbers in the list and sets the current pointer to the + * // first element + * B.sort(); + * + * // Postfix ++ just calls the prefix version and has no functional + * // difference. + * B.Peek(); // Returns 2 + * B++; + * B.Peek(); // Returns 5 + * B++; + * B.Peek(); // Returns 10 + * B++; + * B.Peek(); // Returns 20 + * B++; + * B.Peek(); // Returns 25 + * B++; + * B.Peek(); // Returns 30 + * @endcode + */ + template + + class CircularLinkedList + { + + public: + + struct node + { + CircularLinkedListType item; + + node* previous; + node* next; + }; + + CircularLinkedList(); + ~CircularLinkedList(); + CircularLinkedList( const CircularLinkedList& original_copy ); + // CircularLinkedList(LinkedList original_copy) {CircularLinkedList(original_copy);} // Converts linked list to circular type + bool operator= ( const CircularLinkedList& original_copy ); + CircularLinkedList& operator++(); // CircularLinkedList A; ++A; + CircularLinkedList& operator++( int ); // Circular_Linked List A; A++; + CircularLinkedList& operator--(); // CircularLinkedList A; --A; + CircularLinkedList& operator--( int ); // Circular_Linked List A; A--; + bool IsIn( const CircularLinkedListType& input ); + bool Find( const CircularLinkedListType& input ); + void Insert( const CircularLinkedListType& input ); + + CircularLinkedListType& Add ( const CircularLinkedListType& input ) + + ; // Adds after the current position + void Replace( const CircularLinkedListType& input ); + + void Del( void ); + + unsigned int Size( void ); + + CircularLinkedListType& Peek( void ); + + CircularLinkedListType Pop( void ); + + void Clear( void ); + + void Sort( void ); + + void Beginning( void ); + + void End( void ); + + void Concatenate( const CircularLinkedList& L ); + + protected: + unsigned int list_size; + + node *root; + + node *position; + + node* FindPointer( const CircularLinkedListType& input ); + + private: + CircularLinkedList Merge( CircularLinkedList L1, CircularLinkedList L2 ); + + CircularLinkedList Mergesort( const CircularLinkedList& L ); + }; + + template + + class LinkedList : public CircularLinkedList + { + + public: + LinkedList() + {} + + LinkedList( const LinkedList& original_copy ); + ~LinkedList(); + bool operator= ( const LinkedList& original_copy ); + LinkedList& operator++(); // LinkedList A; ++A; + LinkedList& operator++( int ); // Linked List A; A++; + LinkedList& operator--(); // LinkedList A; --A; + LinkedList& operator--( int ); // Linked List A; A--; + + private: + LinkedList Merge( LinkedList L1, LinkedList L2 ); + LinkedList Mergesort( const LinkedList& L ); + + }; + + + template + inline void CircularLinkedList::Beginning( void ) + { + if ( this->root ) + this->position = this->root; + } + + template + inline void CircularLinkedList::End( void ) + { + if ( this->root ) + this->position = this->root->previous; + } + + template + bool LinkedList::operator= ( const LinkedList& original_copy ) + { + typename LinkedList::node * original_copy_pointer, *last, *save_position; + + if ( ( &original_copy ) != this ) + { + + this->Clear(); + + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = new typename LinkedList::node; + // root->item = new LinkedListType; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = new typename LinkedList::node; + // root->item = new LinkedListType; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + + + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = new typename LinkedList::node; + // position->item = new LinkedListType; + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + + return true; + } + + + template + CircularLinkedList::CircularLinkedList() + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + template + CircularLinkedList::~CircularLinkedList() + { + this->Clear(); + } + + template + LinkedList::~LinkedList() + { + this->Clear(); + } + + template + LinkedList::LinkedList( const LinkedList& original_copy ) + { + typename LinkedList::node * original_copy_pointer, *last, *save_position; + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + return ; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = new typename LinkedList::node; + // root->item = new CircularLinkedListType; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item) = *((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = new typename LinkedList::node; + // root->item = new CircularLinkedListType; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = new typename LinkedList::node; + // position->item = new CircularLinkedListType; + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif + template + CircularLinkedList::CircularLinkedList( const CircularLinkedList& original_copy ) + { + node * original_copy_pointer; + node *last; + node *save_position; + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + return ; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item) = *((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + + + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = new typename CircularLinkedList::node; + // position->item = new CircularLinkedListType; + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif + template + bool CircularLinkedList::operator= ( const CircularLinkedList& original_copy ) + { + node * original_copy_pointer; + node *last; + node *save_position; + + if ( ( &original_copy ) != this ) + { + + this->Clear(); + + + if ( original_copy.list_size == 0 ) + { + this->root = 0; + this->position = 0; + this->list_size = 0; + } + + else + if ( original_copy.list_size == 1 ) + { + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + } + + else + { + // Setup the first part of the root node + original_copy_pointer = original_copy.root; + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + this->position = this->root; + // *(root->item)=*((original_copy.root)->item); + this->root->item = original_copy.root->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + do + { + // Save the current element + last = this->position; + + // Point to the next node in the source list + original_copy_pointer = original_copy_pointer->next; + + // Create a new node and point position to it + this->position = new typename CircularLinkedList::node; + // position->item = new CircularLinkedListType; + + // Copy the item to the new node + // *(position->item)=*(original_copy_pointer->item); + this->position->item = original_copy_pointer->item; + + if ( original_copy_pointer == original_copy.position ) + save_position = this->position; + + // Set the previous pointer for the new node + ( this->position->previous ) = last; + + // Set the next pointer for the old node to the new node + ( last->next ) = this->position; + + } + + while ( ( original_copy_pointer->next ) != ( original_copy.root ) ); + + // Complete the circle. Set the next pointer of the newest node to the root and the previous pointer of the root to the newest node + this->position->next = this->root; + + this->root->previous = this->position; + + this->list_size = original_copy.list_size; + + this->position = save_position; + } + } + + return true; + } + + template + void CircularLinkedList::Insert( const CircularLinkedListType& input ) + { + node * new_node; + + if ( list_size == 0 ) + { + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + //*(root->item)=input; + this->root->item = input; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + } + + else + if ( list_size == 1 ) + { + this->position = new typename CircularLinkedList::node; + // position->item = new CircularLinkedListType; + this->root->next = this->position; + this->root->previous = this->position; + this->position->previous = this->root; + this->position->next = this->root; + // *(position->item)=input; + this->position->item = input; + this->root = this->position; // Since we're inserting into a 1 element list the old root is now the second item + this->list_size = 2; + } + + else + { + /* + + B + | + A --- C + + position->previous=A + new_node=B + position=C + + Note that the order of the following statements is important */ + + new_node = new typename CircularLinkedList::node; + // new_node->item = new CircularLinkedListType; + + // *(new_node->item)=input; + new_node->item = input; + + // Point next of A to B + ( this->position->previous ) ->next = new_node; + + // Point last of B to A + new_node->previous = this->position->previous; + + // Point last of C to B + this->position->previous = new_node; + + // Point next of B to C + new_node->next = this->position; + + // Since the root pointer is bound to a node rather than an index this moves it back if you insert an element at the root + + if ( this->position == this->root ) + { + this->root = new_node; + this->position = this->root; + } + + // Increase the recorded size of the list by one + this->list_size++; + } + } + + template + CircularLinkedListType& CircularLinkedList::Add ( const CircularLinkedListType& input ) + { + node * new_node; + + if ( this->list_size == 0 ) + { + this->root = new typename CircularLinkedList::node; + // root->item = new CircularLinkedListType; + // *(root->item)=input; + this->root->item = input; + this->root->next = this->root; + this->root->previous = this->root; + this->list_size = 1; + this->position = this->root; + // return *(position->item); + return this->position->item; + } + + else + if ( list_size == 1 ) + { + this->position = new typename CircularLinkedList::node; + // position->item = new CircularLinkedListType; + this->root->next = this->position; + this->root->previous = this->position; + this->position->previous = this->root; + this->position->next = this->root; + // *(position->item)=input; + this->position->item = input; + this->list_size = 2; + this->position = this->root; // Don't move the position from the root + // return *(position->item); + return this->position->item; + } + + else + { + /* + + B + | + A --- C + + new_node=B + position=A + position->next=C + + Note that the order of the following statements is important */ + + new_node = new typename CircularLinkedList::node; + // new_node->item = new CircularLinkedListType; + + // *(new_node->item)=input; + new_node->item = input; + + // Point last of B to A + new_node->previous = this->position; + + // Point next of B to C + new_node->next = ( this->position->next ); + + // Point last of C to B + ( this->position->next ) ->previous = new_node; + + // Point next of A to B + ( this->position->next ) = new_node; + + // Increase the recorded size of the list by one + this->list_size++; + + // return *(new_node->item); + return new_node->item; + } + } + + template + inline void CircularLinkedList::Replace( const CircularLinkedListType& input ) + { + if ( this->list_size > 0 ) + // *(position->item)=input; + this->position->item = input; + } + + template + void CircularLinkedList::Del() + { + node * new_position; + + if ( this->list_size == 0 ) + return ; + + else + if ( this->list_size == 1 ) + { + // delete root->item; + delete this->root; + this->root = this->position = 0; + this->list_size = 0; + } + + else + { + ( this->position->previous ) ->next = this->position->next; + ( this->position->next ) ->previous = this->position->previous; + new_position = this->position->next; + + if ( this->position == this->root ) + this->root = new_position; + + // delete position->item; + delete this->position; + + this->position = new_position; + + this->list_size--; + } + } + + template + bool CircularLinkedList::IsIn(const CircularLinkedListType& input ) + { + node * return_value, *old_position; + + old_position = this->position; + + return_value = FindPointer( input ); + this->position = old_position; + + if ( return_value != 0 ) + return true; + else + return false; // Can't find the item don't do anything + } + + template + bool CircularLinkedList::Find( const CircularLinkedListType& input ) + { + node * return_value; + + return_value = FindPointer( input ); + + if ( return_value != 0 ) + { + this->position = return_value; + return true; + } + + else + return false; // Can't find the item don't do anything + } + + template + typename CircularLinkedList::node* CircularLinkedList::FindPointer( const CircularLinkedListType& input ) + { + node * current; + + if ( this->list_size == 0 ) + return 0; + + current = this->root; + + // Search for the item starting from the root node and incrementing the pointer after every check + // If you wind up pointing at the root again you looped around the list so didn't find the item, in which case return 0 + do + { + // if (*(current->item) == input) return current; + + if ( current->item == input ) + return current; + + current = current->next; + } + + while ( current != this->root ); + + return 0; + + } + + template + inline unsigned int CircularLinkedList::Size( void ) + { + return this->list_size; + } + + template + inline CircularLinkedListType& CircularLinkedList::Peek( void ) + { + // return *(position->item); + return this->position->item; + } + + template + CircularLinkedListType CircularLinkedList::Pop( void ) + { + CircularLinkedListType element; + element = Peek(); + Del(); + return CircularLinkedListType( element ); // return temporary + } + + // Prefix + template + CircularLinkedList& CircularLinkedList::operator++() + { + if ( this->list_size != 0 ) + position = position->next; + + return *this; + } + + /* + // Postfix + template + CircularLinkedList& CircularLinkedList::operator++(int) + { + CircularLinkedList before; + before=*this; + operator++(); + return before; + } + */ + + template + CircularLinkedList& CircularLinkedList::operator++( int ) + { + return this->operator++(); + } + + // Prefix + template + CircularLinkedList& CircularLinkedList::operator--() + { + if ( this->list_size != 0 ) + this->position = this->position->previous; + + return *this; + } + + /* + // Postfix + template + CircularLinkedList& CircularLinkedList::operator--(int) + { + CircularLinkedList before; + before=*this; + operator--(); + return before; + } + */ + + template + CircularLinkedList& CircularLinkedList::operator--( int ) + { + return this->operator--(); + } + + template + void CircularLinkedList::Clear( void ) + { + if ( this->list_size == 0 ) + return ; + else + if ( this->list_size == 1 ) // {delete root->item; delete root;} + { + delete this->root; + } + + else + { + node* current; + node* temp; + + current = this->root; + + do + { + temp = current; + current = current->next; + // delete temp->item; + delete temp; + } + + while ( current != this->root ); + } + + this->list_size = 0; + this->root = 0; + this->position = 0; + } + + template + inline void CircularLinkedList::Concatenate( const CircularLinkedList& L ) + { + unsigned int counter; + node* ptr; + + if ( L.list_size == 0 ) + return ; + + if ( this->list_size == 0 ) + * this = L; + + ptr = L.root; + + this->position = this->root->previous; + + // Cycle through each element in L and add it to the current list + for ( counter = 0; counter < L.list_size; counter++ ) + { + // Add item after the current item pointed to + // add(*(ptr->item)); + + Add ( ptr->item ); + + // Update pointers. Moving ptr keeps the current pointer at the end of the list since the add function does not move the pointer + ptr = ptr->next; + + this->position = this->position->next; + } + } + + template + inline void CircularLinkedList::Sort( void ) + { + if ( this->list_size <= 1 ) + return ; + + // Call equal operator to assign result of mergesort to current object + *this = Mergesort( *this ); + + this->position = this->root; + } + + template + CircularLinkedList CircularLinkedList::Mergesort( const CircularLinkedList& L ) + { + unsigned int counter; + node* location; + CircularLinkedList L1; + CircularLinkedList L2; + + location = L.root; + + // Split the list into two equal size sublists, L1 and L2 + + for ( counter = 0; counter < L.list_size / 2; counter++ ) + { + // L1.add (*(location->item)); + L1.Add ( location->item ); + location = location->next; + } + + for ( ;counter < L.list_size; counter++ ) + { + // L2.Add(*(location->item)); + L2.Add ( location->item ); + location = location->next; + } + + // Recursively sort the sublists + if ( L1.list_size > 1 ) + L1 = Mergesort( L1 ); + + if ( L2.list_size > 1 ) + L2 = Mergesort( L2 ); + + // Merge the two sublists + return Merge( L1, L2 ); + } + + template + CircularLinkedList CircularLinkedList::Merge( CircularLinkedList L1, CircularLinkedList L2 ) + { + CircularLinkedList X; + CircularLinkedListType element; + L1.position = L1.root; + L2.position = L2.root; + + // While neither list is empty + + while ( ( L1.list_size != 0 ) && ( L2.list_size != 0 ) ) + { + // Compare the first items of L1 and L2 + // Remove the smaller of the two items from the list + + if ( ( ( L1.root ) ->item ) < ( ( L2.root ) ->item ) ) + // if ((*((L1.root)->item)) < (*((L2.root)->item))) + { + // element = *((L1.root)->item); + element = ( L1.root ) ->item; + L1.Del(); + } + else + { + // element = *((L2.root)->item); + element = ( L2.root ) ->item; + L2.Del(); + } + + // Add this item to the end of X + X.Add( element ); + + X++; + } + + // Add the remaining list to X + if ( L1.list_size != 0 ) + X.Concatenate( L1 ); + else + X.Concatenate( L2 ); + + return X; + } + + template + LinkedList LinkedList::Mergesort( const LinkedList& L ) + { + unsigned int counter; + typename LinkedList::node* location; + LinkedList L1; + LinkedList L2; + + location = L.root; + + // Split the list into two equal size sublists, L1 and L2 + + for ( counter = 0; counter < L.LinkedList_size / 2; counter++ ) + { + // L1.add (*(location->item)); + L1.Add ( location->item ); + location = location->next; + } + + for ( ;counter < L.LinkedList_size; counter++ ) + { + // L2.Add(*(location->item)); + L2.Add ( location->item ); + location = location->next; + } + + // Recursively sort the sublists + if ( L1.list_size > 1 ) + L1 = Mergesort( L1 ); + + if ( L2.list_size > 1 ) + L2 = Mergesort( L2 ); + + // Merge the two sublists + return Merge( L1, L2 ); + } + + template + LinkedList LinkedList::Merge( LinkedList L1, LinkedList L2 ) + { + LinkedList X; + LinkedListType element; + L1.position = L1.root; + L2.position = L2.root; + + // While neither list is empty + + while ( ( L1.LinkedList_size != 0 ) && ( L2.LinkedList_size != 0 ) ) + { + // Compare the first items of L1 and L2 + // Remove the smaller of the two items from the list + + if ( ( ( L1.root ) ->item ) < ( ( L2.root ) ->item ) ) + // if ((*((L1.root)->item)) < (*((L2.root)->item))) + { + element = ( L1.root ) ->item; + // element = *((L1.root)->item); + L1.Del(); + } + else + { + element = ( L2.root ) ->item; + // element = *((L2.root)->item); + L2.Del(); + } + + // Add this item to the end of X + X.Add( element ); + } + + // Add the remaining list to X + if ( L1.LinkedList_size != 0 ) + X.concatenate( L1 ); + else + X.concatenate( L2 ); + + return X; + } + + + // Prefix + template + LinkedList& LinkedList::operator++() + { + if ( ( this->list_size != 0 ) && ( this->position->next != this->root ) ) + this->position = this->position->next; + + return *this; + } + + /* + // Postfix + template + LinkedList& LinkedList::operator++(int) + { + LinkedList before; + before=*this; + operator++(); + return before; + } + */ + // Postfix + template + LinkedList& LinkedList::operator++( int ) + { + return this->operator++(); + } + + // Prefix + template + LinkedList& LinkedList::operator--() + { + if ( ( this->list_size != 0 ) && ( this->position != this->root ) ) + this->position = this->position->previous; + + return *this; + } + + /* + // Postfix + template + LinkedList& LinkedList::operator--(int) + { + LinkedList before; + before=*this; + operator--(); + return before; + } + */ + + // Postfix + template + LinkedList& LinkedList::operator--( int ) + { + return this->operator--(); + } + +} // End namespace + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/raknet/DS_List.h b/raknet/DS_List.h new file mode 100644 index 0000000..6b50b86 --- /dev/null +++ b/raknet/DS_List.h @@ -0,0 +1,417 @@ +/// \file +/// \brief \b [Internal] Array based list. Usually the Queue class is used instead, since it has all the same functionality and is only worse at random access. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __LIST_H +#define __LIST_H + +#include +#include // memmove +#include "Export.h" + +/// Maximum unsigned long +static const unsigned int MAX_UNSIGNED_LONG = 4294967295U; + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + /// \brief Array based implementation of a list. + template + class RAK_DLL_EXPORT List + { + public: + /// Default constructor + List(); + + /// Destructor + ~List(); + + /// Copy constructor + /// \param[in] original_copy The list to duplicate + List( const List& original_copy ); + + /// Assign one list to another + List& operator= ( const List& original_copy ); + + /// Access an element by its index in the array + /// \param[in] position The index into the array. + /// \return The element at position \a position. + list_type& operator[] ( const unsigned int position ) const; + + /// Insert an element at position \a position in the list + /// \param[in] input The new element. + /// \param[in] position The position of the new element. + void Insert( const list_type input, const unsigned int position ); + + /// Insert at the end of the list. + /// \param[in] input The new element. + void Insert( const list_type input ); + + /// Replace the value at \a position by \a input. If the size of + /// the list is less than @em position, it increase the capacity of + /// the list and fill slot with @em filler. + /// \param[in] input The element to replace at position @em position. + /// \param[in] filler The element use to fill new allocated capacity. + /// \param[in] position The position of input in the list. + void Replace( const list_type input, const list_type filler, const unsigned int position ); + + /// Replace the last element of the list by \a input . + /// \param[in] input The element used to replace the last element. + void Replace( const list_type input ); + + /// Delete the element at position \a position. + /// \param[in] position The index of the element to delete + void RemoveAtIndex( const unsigned int position ); + + /// Delete the element at the end of the list + void Del(const unsigned num=1); + + /// Returns the index of the specified item or MAX_UNSIGNED_LONG if not found + /// \param[in] input The element to check for + /// \return The index or position of @em input in the list. + /// \retval MAX_UNSIGNED_LONG The object is not in the list + /// \retval [Integer] The index of the element in the list + unsigned int GetIndexOf( const list_type input ); + + /// \return The number of elements in the list + unsigned int Size( void ) const; + + /// Clear the list + void Clear( bool doNotDeallocate=false ); + + /// Frees overallocated members, to use the minimum memory necessary + /// \attention + /// This is a slow operation + void Compress( void ); + + private: + /// An array of user values + list_type* listArray; + + /// Number of elements in the list + unsigned int list_size; + + /// Size of \a array + unsigned int allocation_size; + }; + template + List::List() + { + allocation_size = 0; + listArray = 0; + list_size = 0; + } + + template + List::~List() + { + if (allocation_size>0) + delete [] listArray; + } + + + template + List::List( const List& original_copy ) + { + // Allocate memory for copy + + if ( original_copy.list_size == 0 ) + { + list_size = 0; + allocation_size = 0; + } + else + { + listArray = new list_type [ original_copy.list_size ]; + + //for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter ) + // listArray[ counter ] = original_copy.listArray[ counter ]; + + // Don't call constructors, assignment operators, etc. + memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type)); + + list_size = allocation_size = original_copy.list_size; + } + } + + template + List& List::operator= ( const List& original_copy ) + { + if ( ( &original_copy ) != this ) + { + Clear(); + + // Allocate memory for copy + + if ( original_copy.list_size == 0 ) + { + list_size = 0; + allocation_size = 0; + } + + else + { + listArray = new list_type [ original_copy.list_size ]; + + //for ( unsigned int counter = 0; counter < original_copy.list_size; ++counter ) + // listArray[ counter ] = original_copy.listArray[ counter ]; + // Don't call constructors, assignment operators, etc. + memcpy(listArray, original_copy.listArray, original_copy.list_size*sizeof(list_type)); + + list_size = allocation_size = original_copy.list_size; + } + } + + return *this; + } + + + template + inline list_type& List::operator[] ( const unsigned int position ) const + { +#ifdef _DEBUG + assert ( position < list_size ); +#endif + return listArray[ position ]; + } + + template + void List::Insert( const list_type input, const unsigned int position ) + { +#ifdef _DEBUG + assert( position <= list_size ); +#endif + + // Reallocate list if necessary + if ( list_size == allocation_size ) + { + // allocate twice the currently allocated memory + list_type * new_array; + + if ( allocation_size == 0 ) + allocation_size = 16; + else + allocation_size *= 2; + + new_array = new list_type [ allocation_size ]; + + // copy old array over + //for ( unsigned int counter = 0; counter < list_size; ++counter ) + // new_array[ counter ] = listArray[ counter ]; + + // Don't call constructors, assignment operators, etc. + memcpy(new_array, listArray, list_size*sizeof(list_type)); + + // set old array to point to the newly allocated and twice as large array + delete[] listArray; + + listArray = new_array; + } + + // Move the elements in the list to make room + //for ( unsigned int counter = list_size; counter != position; counter-- ) + // listArray[ counter ] = listArray[ counter - 1 ]; + + // Don't call constructors, assignment operators, etc. + memmove(listArray+position+1, listArray+position, (list_size-position)*sizeof(list_type)); + + // Insert the new item at the correct spot + listArray[ position ] = input; + + ++list_size; + + } + + + template + void List::Insert( const list_type input ) + { + // Reallocate list if necessary + + if ( list_size == allocation_size ) + { + // allocate twice the currently allocated memory + list_type * new_array; + + if ( allocation_size == 0 ) + allocation_size = 16; + else + allocation_size *= 2; + + new_array = new list_type [ allocation_size ]; + + // copy old array over + // for ( unsigned int counter = 0; counter < list_size; ++counter ) + // new_array[ counter ] = listArray[ counter ]; + + // Don't call constructors, assignment operators, etc. + memcpy(new_array, listArray, list_size*sizeof(list_type)); + + // set old array to point to the newly allocated and twice as large array + delete[] listArray; + + listArray = new_array; + } + + // Insert the new item at the correct spot + listArray[ list_size ] = input; + + ++list_size; + } + + template + inline void List::Replace( const list_type input, const list_type filler, const unsigned int position ) + { + if ( ( list_size > 0 ) && ( position < list_size ) ) + { + // Direct replacement + listArray[ position ] = input; + } + else + { + if ( position >= allocation_size ) + { + // Reallocate the list to size position and fill in blanks with filler + list_type * new_array; + allocation_size = position + 1; + + new_array = new list_type [ allocation_size ]; + + // copy old array over + + //for ( unsigned int counter = 0; counter < list_size; ++counter ) + // new_array[ counter ] = listArray[ counter ]; + + // Don't call constructors, assignment operators, etc. + memcpy(new_array, listArray, list_size*sizeof(list_type)); + + // set old array to point to the newly allocated array + delete[] listArray; + + listArray = new_array; + } + + // Fill in holes with filler + while ( list_size < position ) + listArray[ list_size++ ] = filler; + + // Fill in the last element with the new item + listArray[ list_size++ ] = input; + +#ifdef _DEBUG + + assert( list_size == position + 1 ); + +#endif + + } + } + + template + inline void List::Replace( const list_type input ) + { + if ( list_size > 0 ) + listArray[ list_size - 1 ] = input; + } + + template + void List::RemoveAtIndex( const unsigned int position ) + { +#ifdef _DEBUG + assert( position < list_size ); +#endif + + if ( position < list_size ) + { + // Compress the array + /* + for ( unsigned int counter = position; counter < list_size - 1 ; ++counter ) + listArray[ counter ] = listArray[ counter + 1 ]; + */ + memmove(listArray+position, listArray+position+1, (list_size-1-position) * sizeof(list_type)); + + Del(); + } + } + + template + inline void List::Del( const unsigned num ) + { + // Delete the last elements on the list. No compression needed +#ifdef _DEBUG + assert(list_size>=num); +#endif + list_size-=num; + } + + template + unsigned int List::GetIndexOf( const list_type input ) + { + for ( unsigned int i = 0; i < list_size; ++i ) + if ( listArray[ i ] == input ) + return i; + + return MAX_UNSIGNED_LONG; + } + + template + inline unsigned int List::Size( void ) const + { + return list_size; + } + + template + void List::Clear( bool doNotDeallocate ) + { + if ( allocation_size == 0 ) + return; + + if (allocation_size>512 && doNotDeallocate==false) + { + delete [] listArray; + allocation_size = 0; + listArray = 0; + } + list_size = 0; + } + + template + void List::Compress( void ) + { + list_type * new_array; + + if ( allocation_size == 0 ) + return ; + + new_array = new list_type [ allocation_size ]; + + // copy old array over + //for ( unsigned int counter = 0; counter < list_size; ++counter ) + // new_array[ counter ] = listArray[ counter ]; + + // Don't call constructors, assignment operators, etc. + memcpy(new_array, listArray, list_size*sizeof(list_type)); + + // set old array to point to the newly allocated array + delete[] listArray; + + listArray = new_array; + } + +} // End namespace + +#endif diff --git a/raknet/DS_Map.h b/raknet/DS_Map.h new file mode 100644 index 0000000..d52a940 --- /dev/null +++ b/raknet/DS_Map.h @@ -0,0 +1,314 @@ +/// \file +/// \brief \b [Internal] Map +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __RAKNET_MAP_H +#define __RAKNET_MAP_H + +#include "DS_OrderedList.h" +#include "Export.h" + +// If I want to change this to a red-black tree, this is a good site: http://www.cs.auckland.ac.nz/software/AlgAnim/red_black.html +// This makes insertions and deletions faster. But then traversals are slow, while they are currently fast. + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + /// The default comparison has to be first so it can be called as a default parameter. + /// It then is followed by MapNode, followed by NodeComparisonFunc + template + int defaultMapKeyComparison(const key_type &a, const key_type &b) + { + if (a > + class RAK_DLL_EXPORT Map + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(key_type(),key_type());} + + struct MapNode + { + MapNode() {} + MapNode(key_type _key, data_type _data) : mapNodeKey(_key), mapNodeData(_data) {} + key_type mapNodeKey; + data_type mapNodeData; + }; + + // Has to be a static because the comparison callback for DataStructures::OrderedList is a C function + static int NodeComparisonFunc(const key_type &a, const MapNode &b) + { +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + return key_comparison_func(a, b.mapNodeKey); + } + + Map(); + ~Map(); + Map( const Map& original_copy ); + Map& operator= ( const Map& original_copy ); + + data_type& Get(const key_type &key); + data_type Pop(const key_type &key); + // Add if needed + void Set(const key_type &key, const data_type &data); + // Must already exist + void SetExisting(const key_type &key, const data_type &data); + // Must add + void SetNew(const key_type &key, const data_type &data); + bool Has(const key_type &key); + bool Delete(const key_type &key); + data_type& operator[] ( const unsigned int position ) const; + key_type GetKeyAtIndex( const unsigned int position ) const; + unsigned GetIndexAtKey( const key_type &key ); + void RemoveAtIndex(const unsigned index); + void Clear(void); + unsigned Size(void) const; + + protected: + DataStructures::OrderedList< key_type,MapNode,Map::NodeComparisonFunc > mapNodeList; + + void SaveLastSearch(const key_type &key, unsigned index); + bool HasSavedSearchResult(const key_type &key) const; + + unsigned lastSearchIndex; + key_type lastSearchKey; + bool lastSearchIndexValid; + }; + + template + Map::Map() + { + lastSearchIndexValid=false; + } + + template + Map::~Map() + { + Clear(); + } + + template + Map::Map( const Map& original_copy ) + { + mapNodeList=original_copy.mapNodeList; + lastSearchIndex=original_copy.lastSearchIndex; + lastSearchKey=original_copy.lastSearchKey; + lastSearchIndexValid=original_copy.lastSearchIndexValid; + } + + template + Map& Map::operator= ( const Map& original_copy ) + { + mapNodeList=original_copy.mapNodeList; + lastSearchIndex=original_copy.lastSearchIndex; + lastSearchKey=original_copy.lastSearchKey; + lastSearchIndexValid=original_copy.lastSearchIndexValid; + return *this; + } + + template + data_type& Map::Get(const key_type &key) + { + if (HasSavedSearchResult(key)) + return mapNodeList[lastSearchIndex].mapNodeData; + + bool objectExists; + unsigned index; + index=mapNodeList.GetIndexFromKey(key, &objectExists); + assert(objectExists); + SaveLastSearch(key,index); + return mapNodeList[index].mapNodeData; + } + + template + unsigned Map::GetIndexAtKey( const key_type &key ) + { + if (HasSavedSearchResult(key)) + return lastSearchIndex; + + bool objectExists; + unsigned index; + index=mapNodeList.GetIndexFromKey(key, &objectExists); + assert(objectExists); + SaveLastSearch(key,index); + return index; + } + + template + void Map::RemoveAtIndex(const unsigned index) + { + mapNodeList.RemoveAtIndex(index); + lastSearchIndexValid=false; + } + + template + data_type Map::Pop(const key_type &key) + { + bool objectExists; + unsigned index; + if (HasSavedSearchResult(key)) + index=lastSearchIndex; + else + { + index=mapNodeList.GetIndexFromKey(key, &objectExists); + assert(objectExists); + } + data_type tmp = mapNodeList[index].mapNodeData; + mapNodeList.RemoveAtIndex(index); + lastSearchIndexValid=false; + return tmp; + } + + template + void Map::Set(const key_type &key, const data_type &data) + { + bool objectExists; + unsigned index; + + if (HasSavedSearchResult(key)) + { + mapNodeList[lastSearchIndex].mapNodeData=data; + return; + } + + index=mapNodeList.GetIndexFromKey(key, &objectExists); + + if (objectExists) + { + SaveLastSearch(key,index); + mapNodeList[index].mapNodeData=data; + } + else + { + SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data))); + } + } + + template + void Map::SetExisting(const key_type &key, const data_type &data) + { + bool objectExists; + unsigned index; + + if (HasSavedSearchResult(key)) + { + index=lastSearchIndex; + } + else + { + index=mapNodeList.GetIndexFromKey(key, &objectExists); + assert(objectExists); + SaveLastSearch(key,index); + } + + mapNodeList[index].mapNodeData=data; + } + + template + void Map::SetNew(const key_type &key, const data_type &data) + { +#ifdef _DEBUG + unsigned index; + bool objectExists; + index=mapNodeList.GetIndexFromKey(key, &objectExists); + assert(objectExists==false); +#endif + SaveLastSearch(key,mapNodeList.Insert(key,MapNode(key,data))); + } + + template + bool Map::Has(const key_type &key) + { + if (HasSavedSearchResult(key)) + return true; + + bool objectExists; + unsigned index; + index=mapNodeList.GetIndexFromKey(key, &objectExists); + if (objectExists) + SaveLastSearch(key,index); + return objectExists; + } + + template + bool Map::Delete(const key_type &key) + { + if (HasSavedSearchResult(key)) + { + lastSearchIndexValid=false; + mapNodeList.RemoveAtIndex(lastSearchIndex); + return true; + } + + bool objectExists; + unsigned index; + index=mapNodeList.GetIndexFromKey(key, &objectExists); + if (objectExists) + { + lastSearchIndexValid=false; + mapNodeList.RemoveAtIndex(index); + return true; + } + else + return false; + } + + template + void Map::Clear(void) + { + lastSearchIndexValid=false; + mapNodeList.Clear(); + } + + template + data_type& Map::operator[]( const unsigned int position ) const + { + return mapNodeList[position].mapNodeData; + } + + template + key_type Map::GetKeyAtIndex( const unsigned int position ) const + { + return mapNodeList[position].mapNodeKey; + } + + template + unsigned Map::Size(void) const + { + return mapNodeList.Size(); + } + + template + void Map::SaveLastSearch(const key_type &key, const unsigned index) + { + lastSearchIndex=index; + lastSearchKey=key; + lastSearchIndexValid=true; + } + + template + bool Map::HasSavedSearchResult(const key_type &key) const + { + return lastSearchIndexValid && key_comparison_func(key,lastSearchKey)==0; + } +} + +#endif diff --git a/raknet/DS_MemoryPool.h b/raknet/DS_MemoryPool.h new file mode 100644 index 0000000..3a50548 --- /dev/null +++ b/raknet/DS_MemoryPool.h @@ -0,0 +1,90 @@ +#ifndef __MEMORY_POOL_H +#define __MEMORY_POOL_H + +#include "DS_List.h" +#include + +namespace DataStructures +{ + template + class MemoryPool + { + public: + MemoryPool(); + ~MemoryPool(); + void Preallocate(unsigned numElements); + MemoryBlockType *Allocate(void); + void Release(MemoryBlockType *m); + void Clear(void); + protected: + int blocksOut; + DataStructures::List pool; + }; + + template + MemoryPool::MemoryPool() + { +#ifdef _DEBUG + blocksOut=0; +#endif + } + template + MemoryPool::~MemoryPool() + { +#ifdef _DEBUG + assert(blocksOut==0); +#endif + unsigned i; + for (i=0; i < pool.Size(); i++) + delete pool[i]; + } + + template + void MemoryPool::Preallocate(unsigned numElements) + { + unsigned i; + for (i=pool.Size(); i < numElements; i++) + { + pool.Insert(new MemoryBlockType); + } + } + + template + MemoryBlockType* MemoryPool::Allocate(void) + { +#ifdef _DEBUG + blocksOut++; +#endif + if (pool.Size()==0) + return new MemoryBlockType; + else + { + MemoryBlockType* out; + out=pool[pool.Size()-1]; + pool.Del(); + return out; + } + } + template + void MemoryPool::Release(MemoryBlockType *m) + { + pool.Insert(m); +#ifdef _DEBUG + assert(blocksOut>0); + blocksOut--; +#endif + } + template + void MemoryPool::Clear(void) + { +#ifdef _DEBUG + assert(blocksOut==0); +#endif + unsigned i; + for (i=0; i < pool.Size(); i++) + delete pool[i]; + pool.Clear(); + } +} + +#endif diff --git a/raknet/DS_OrderedChannelHeap.h b/raknet/DS_OrderedChannelHeap.h new file mode 100644 index 0000000..7e87f58 --- /dev/null +++ b/raknet/DS_OrderedChannelHeap.h @@ -0,0 +1,252 @@ +/// \file +/// \brief \b [Internal] Ordered Channel Heap . This is a heap where you add to it on multiple ordered channels, with each channel having a different weight. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __RAKNET_ORDERED_CHANNEL_HEAP_H +#define __RAKNET_ORDERED_CHANNEL_HEAP_H + +#include "DS_Heap.h" +#include "DS_Map.h" +#include "DS_Queue.h" +#include "Export.h" +#include +#include "Rand.h" + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + template > + class RAK_DLL_EXPORT OrderedChannelHeap + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(channel_key_type(),channel_key_type());} + + OrderedChannelHeap(); + ~OrderedChannelHeap(); + void Push(const channel_key_type &channelID, const heap_data_type &data); + void PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data); + heap_data_type Pop(const unsigned startingIndex=0); + heap_data_type Peek(const unsigned startingIndex) const; + void AddChannel(const channel_key_type &channelID, const double weight); + void RemoveChannel(channel_key_type channelID); + void Clear(void); + heap_data_type& operator[] ( const unsigned int position ) const; + unsigned ChannelSize(const channel_key_type &channelID); + unsigned Size(void) const; + + struct QueueAndWeight + { + DataStructures::Queue randResultQueue; + double weight; + bool signalDeletion; + }; + + struct HeapChannelAndData + { + HeapChannelAndData() {} + HeapChannelAndData(const channel_key_type &_channel, const heap_data_type &_data) : data(_data), channel(_channel) {} + heap_data_type data; + channel_key_type channel; + }; + + protected: + DataStructures::Map map; + DataStructures::Heap heap; + void GreatestRandResult(void); + }; + + template + OrderedChannelHeap::OrderedChannelHeap() + { + } + + template + OrderedChannelHeap::~OrderedChannelHeap() + { + Clear(); + } + + template + void OrderedChannelHeap::Push(const channel_key_type &channelID, const heap_data_type &data) + { + PushAtHead(MAX_UNSIGNED_LONG, channelID, data); + } + + template + void OrderedChannelHeap::GreatestRandResult(void) + { + double greatest; + unsigned i; + greatest=0.0; + for (i=0; i < map.Size(); i++) + { + if (map[i]->randResultQueue.Size() && map[i]->randResultQueue[0]>greatest) + greatest=map[i]->randResultQueue[0]; + } + return greatest; + } + + template + void OrderedChannelHeap::PushAtHead(const unsigned index, const channel_key_type &channelID, const heap_data_type &data) + { + // If an assert hits here then this is an unknown channel. Call AddChannel first. + QueueAndWeight *queueAndWeight=map.Get(channelID); + double maxRange, minRange, rnd; + if (queueAndWeight->randResultQueue.Size()==0) + { + // Set maxRange to the greatest random number waiting to be returned, rather than 1.0 necessarily + // This is so weights are scaled similarly among channels. For example, if the head weight for a used channel was .25 + // and then we added another channel, the new channel would need to choose between .25 and 0 + // If we chose between 1.0 and 0, it would be 1/.25 (4x) more likely to be at the head of the heap than it should be + maxRange=GreatestRandResult(); + if (maxRange==0.0) + maxRange=1.0; + minRange=0.0; + } + else if (index >= queueAndWeight->randResultQueue.Size()) + { + maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999; + minRange=0.0; + } + else + { + if (index==0) + { + maxRange=GreatestRandResult(); + if (maxRange==queueAndWeight->randResultQueue[0]) + maxRange=1.0; + } + else if (index >= queueAndWeight->randResultQueue.Size()) + maxRange=queueAndWeight->randResultQueue[queueAndWeight->randResultQueue.Size()-1]*.99999999; + else + maxRange=queueAndWeight->randResultQueue[index-1]*.99999999; + + minRange=maxRange=queueAndWeight->randResultQueue[index]*1.00000001; + } + +#ifdef _DEBUG + assert(maxRange!=0.0); +#endif + rnd=frandomMT() * (maxRange - minRange); + if (rnd==0.0) + rnd=maxRange/2.0; + + if (index >= queueAndWeight->randResultQueue.Size()) + queueAndWeight->randResultQueue.Push(rnd); + else + queueAndWeight->randResultQueue.PushAtHead(rnd, index); + + heap.Push(rnd*queueAndWeight->weight, HeapChannelAndData(channelID, data)); + } + + template + heap_data_type OrderedChannelHeap::Pop(const unsigned startingIndex) + { + assert(startingIndex < heap.Size()); + + QueueAndWeight *queueAndWeight=map.Get(heap[startingIndex].channel); + if (startingIndex!=0) + { + // Ugly - have to count in the heap how many nodes have the same channel, so we know where to delete from in the queue + unsigned indiceCount=0; + unsigned i; + for (i=0; i < startingIndex; i++) + if (channel_key_comparison_func(heap[i].channel,heap[startingIndex].channel)==0) + indiceCount++; + queueAndWeight->randResultQueue.Del(indiceCount); + } + else + { + // TODO - ordered channel heap uses progressively lower values as items are inserted. But this won't give relative ordering among channels. I have to renormalize after every pop. + queueAndWeight->randResultQueue.Pop(); + } + + // Try to remove the channel after every pop, because doing so is not valid while there are elements in the list. + if (queueAndWeight->signalDeletion) + RemoveChannel(heap[startingIndex].channel); + + return heap.Pop(startingIndex).data; + } + + template + heap_data_type OrderedChannelHeap::Peek(const unsigned startingIndex) const + { + HeapChannelAndData heapChannelAndData = heap.Peek(startingIndex); + return heapChannelAndData.data; + } + + template + void OrderedChannelHeap::AddChannel(const channel_key_type &channelID, const double weight) + { + QueueAndWeight *qaw = new QueueAndWeight; + qaw->weight=weight; + qaw->signalDeletion=false; + map.SetNew(channelID, qaw); + } + + template + void OrderedChannelHeap::RemoveChannel(channel_key_type channelID) + { + if (map.Has(channelID)) + { + unsigned i; + i=map.GetIndexAtKey(channelID); + if (map[i]->randResultQueue.Size()==0) + { + delete map[i]; + map.RemoveAtIndex(i); + } + else + { + // Signal this channel for deletion later, because the heap has nodes with this channel right now + map[i]->signalDeletion=true; + } + } + } + + template + unsigned OrderedChannelHeap::Size(void) const + { + return heap.Size(); + } + + template + heap_data_type& OrderedChannelHeap::operator[]( const unsigned int position ) const + { + return heap[position].data; + } + + + template + unsigned OrderedChannelHeap::ChannelSize(const channel_key_type &channelID) + { + QueueAndWeight *queueAndWeight=map.Get(channelID); + return queueAndWeight->randResultQueue.Size(); + } + + template + void OrderedChannelHeap::Clear(void) + { + unsigned i; + for (i=0; i < map.Size(); i++) + delete map[i]; + map.Clear(); + heap.Clear(); + } +} + +#endif diff --git a/raknet/DS_OrderedList.h b/raknet/DS_OrderedList.h new file mode 100644 index 0000000..ddb047b --- /dev/null +++ b/raknet/DS_OrderedList.h @@ -0,0 +1,236 @@ +/// \file +/// \brief \b [Internal] Quicksort ordered list. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "DS_List.h" +#include "Export.h" + +#ifndef __ORDERED_LIST_H +#define __ORDERED_LIST_H + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + template + int defaultOrderedListComparison(const key_type &a, const data_type &b) + { + if (a > + class RAK_DLL_EXPORT OrderedList + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultOrderedListComparison(key_type(),data_type());} + + OrderedList(); + ~OrderedList(); + OrderedList( const OrderedList& original_copy ); + OrderedList& operator= ( const OrderedList& original_copy ); + + /// comparisonFunction must take a key_type and a data_type and return <0, ==0, or >0 + /// If the data type has comparison operators already defined then you can just use defaultComparison + bool HasData(const key_type &key) const; + unsigned GetIndexFromKey(const key_type &key, bool *objectExists) const; + data_type GetElementFromKey(const key_type &key); + unsigned Insert(const key_type &key, const data_type &data); + unsigned Remove(const key_type &key); + data_type& operator[] ( const unsigned int position ) const; + void RemoveAtIndex(const unsigned index); + void InsertAtIndex(const data_type &data, const unsigned index); + void InsertAtEnd(const data_type &data); + void Del(const unsigned num=1); + void Clear(void); + unsigned Size(void) const; + + protected: + DataStructures::List orderedList; + }; + + template + OrderedList::OrderedList() + { + } + + template + OrderedList::~OrderedList() + { + Clear(); + } + + template + OrderedList::OrderedList( const OrderedList& original_copy ) + { + orderedList=original_copy.orderedList; + } + + template + OrderedList& OrderedList::operator= ( const OrderedList& original_copy ) + { + orderedList=original_copy.orderedList; + return *this; + } + + template + bool OrderedList::HasData(const key_type &key) const + { + bool objectExists; + unsigned index; + index = GetIndexFromKey(key, &objectExists); + return objectExists; + } + + template + data_type OrderedList::GetElementFromKey(const key_type &key) + { + bool objectExists; + unsigned index; + index = GetIndexFromKey(key, &objectExists); + assert(objectExists); + return orderedList[index]; + } + + template + unsigned OrderedList::GetIndexFromKey(const key_type &key, bool *objectExists) const + { + int index, upperBound, lowerBound; + int res; + + if (orderedList.Size()==0) + { + *objectExists=false; + return 0; + } + + upperBound=(int)orderedList.Size()-1; + lowerBound=0; + index = (int)orderedList.Size()/2; + +#ifdef _MSC_VER + #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + res = comparison_function(key,orderedList[index]); + if (res==0) + { + *objectExists=true; + return index; + } + else if (res<0) + { + upperBound=index-1; + } + else// if (res>0) + { + lowerBound=index+1; + } + + index=lowerBound+(upperBound-lowerBound)/2; + + if (lowerBound>upperBound) + { + *objectExists=false; + return lowerBound; // No match + } + } + } + + template + unsigned OrderedList::Insert(const key_type &key, const data_type &data) + { + bool objectExists; + unsigned index; + index = GetIndexFromKey(key, &objectExists); + + // Don't allow duplicate insertion. + if (objectExists) + return (unsigned)-1; + + if (index>=orderedList.Size()) + { + orderedList.Insert(data); + return orderedList.Size()-1; + } + else + { + orderedList.Insert(data,index); + return index; + } + } + + template + unsigned OrderedList::Remove(const key_type &key) + { + bool objectExists; + unsigned index; + index = GetIndexFromKey(key, &objectExists); + + // Can't find the element to remove if this assert hits + assert(objectExists==true); + if (objectExists==false) + return 0; + + orderedList.RemoveAtIndex(index); + return index; + } + + template + void OrderedList::RemoveAtIndex(const unsigned index) + { + orderedList.RemoveAtIndex(index); + } + + template + void OrderedList::InsertAtIndex(const data_type &data, const unsigned index) + { + orderedList.Insert(data, index); + } + + template + void OrderedList::InsertAtEnd(const data_type &data) + { + orderedList.Insert(data); + } + + template + void OrderedList::Del(const unsigned num) + { + orderedList.Del(num); + } + + template + void OrderedList::Clear(void) + { + orderedList.Clear(); + } + + template + data_type& OrderedList::operator[]( const unsigned int position ) const + { + return orderedList[position]; + } + + template + unsigned OrderedList::Size(void) const + { + return orderedList.Size(); + } +} + +#endif diff --git a/raknet/DS_Queue.h b/raknet/DS_Queue.h new file mode 100644 index 0000000..4dece54 --- /dev/null +++ b/raknet/DS_Queue.h @@ -0,0 +1,415 @@ +/// \file +/// \brief \b [Internal] A queue used by RakNet. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __QUEUE_H +#define __QUEUE_H + +// Template classes have to have all the code in the header file +#include +#include "Export.h" + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + /// \brief A queue implemented as an array with a read and write index. + template + class RAK_DLL_EXPORT Queue + { + public: + Queue(); + ~Queue(); + Queue( Queue& original_copy ); + bool operator= ( const Queue& original_copy ); + void Push( const queue_type& input ); + void PushAtHead( const queue_type& input, unsigned index=0 ); + queue_type& operator[] ( unsigned int position ) const; // Not a normal thing you do with a queue but can be used for efficiency + void Del( unsigned int position ); // Not a normal thing you do with a queue but can be used for efficiency + inline queue_type Peek( void ) const; + inline queue_type Pop( void ); + inline unsigned int Size( void ) const; + inline bool IsEmpty(void) const; + inline unsigned int AllocationSize( void ) const; + inline void Clear( void ); + void Compress( void ); + bool Find ( queue_type q ); + void ClearAndForceAllocation( int size ); // Force a memory allocation to a certain larger size + + private: + queue_type* array; + unsigned int head; // Array index for the head of the queue + unsigned int tail; // Array index for the tail of the queue + unsigned int allocation_size; + }; + + + template + inline unsigned int Queue::Size( void ) const + { + if ( head <= tail ) + return tail -head; + else + return allocation_size -head + tail; + } + + template + inline bool Queue::IsEmpty(void) const + { + return head==tail; + } + + template + inline unsigned int Queue::AllocationSize( void ) const + { + return allocation_size; + } + + template + Queue::Queue() + { + allocation_size = 16; + array = new queue_type[ allocation_size ]; + head = 0; + tail = 0; + } + + template + Queue::~Queue() + { + if (allocation_size>0) + delete [] array; + } + + template + inline queue_type Queue::Pop( void ) + { +#ifdef _DEBUG + assert( allocation_size > 0 && Size() >= 0 && head != tail); +#endif + //head=(head+1) % allocation_size; + + if ( ++head == allocation_size ) + head = 0; + + if ( head == 0 ) + return ( queue_type ) array[ allocation_size -1 ]; + + return ( queue_type ) array[ head -1 ]; + } + + template + void Queue::PushAtHead( const queue_type& input, unsigned index ) + { + if ( allocation_size == 0 ) + { + array = new queue_type[ 16 ]; + head = 0; + tail = 1; + array[ 0 ] = input; + allocation_size = 16; + return ; + } + + if ( head == 0 ) + head = allocation_size - 1; + else + --head; + + unsigned count=0; + while (count < index) + { + array[head+count]=array[head+count+1]; + count++; + } + array[ head+count ] = input; + + if ( tail == head ) + { + // unsigned int index=tail; + + // Need to allocate more memory. + queue_type * new_array; + new_array = new queue_type[ allocation_size * 2 ]; +#ifdef _DEBUG + + assert( new_array ); +#endif + + for ( unsigned int counter = 0; counter < allocation_size; ++counter ) + new_array[ counter ] = array[ ( head + counter ) % ( allocation_size ) ]; + + head = 0; + + tail = allocation_size; + + allocation_size *= 2; + + // Delete the old array and move the pointer to the new array + delete [] array; + + array = new_array; + } + } + + + template + inline queue_type Queue::Peek( void ) const + { +#ifdef _DEBUG + assert( head != tail ); + assert( allocation_size > 0 && Size() >= 0 ); +#endif + + return ( queue_type ) array[ head ]; + } + + template + void Queue::Push( const queue_type& input ) + { + if ( allocation_size == 0 ) + { + array = new queue_type[ 16 ]; + head = 0; + tail = 1; + array[ 0 ] = input; + allocation_size = 16; + return ; + } + + array[ tail++ ] = input; + + if ( tail == allocation_size ) + tail = 0; + + if ( tail == head ) + { + // unsigned int index=tail; + + // Need to allocate more memory. + queue_type * new_array; + new_array = new queue_type[ allocation_size * 2 ]; +#ifdef _DEBUG + + assert( new_array ); +#endif + + for ( unsigned int counter = 0; counter < allocation_size; ++counter ) + new_array[ counter ] = array[ ( head + counter ) % ( allocation_size ) ]; + + head = 0; + + tail = allocation_size; + + allocation_size *= 2; + + // Delete the old array and move the pointer to the new array + delete [] array; + + array = new_array; + } + + } + + template + Queue::Queue( Queue& original_copy ) + { + // Allocate memory for copy + + if ( original_copy.Size() == 0 ) + { + allocation_size = 0; + } + + else + { + array = new queue_type [ original_copy.Size() + 1 ]; + + for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter ) + array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ]; + + head = 0; + + tail = original_copy.Size(); + + allocation_size = original_copy.Size() + 1; + } + } + + template + bool Queue::operator= ( const Queue& original_copy ) + { + if ( ( &original_copy ) == this ) + return false; + + Clear(); + + // Allocate memory for copy + if ( original_copy.Size() == 0 ) + { + allocation_size = 0; + } + + else + { + array = new queue_type [ original_copy.Size() + 1 ]; + + for ( unsigned int counter = 0; counter < original_copy.Size(); ++counter ) + array[ counter ] = original_copy.array[ ( original_copy.head + counter ) % ( original_copy.allocation_size ) ]; + + head = 0; + + tail = original_copy.Size(); + + allocation_size = original_copy.Size() + 1; + } + + return true; + } + + template + inline void Queue::Clear ( void ) + { + if ( allocation_size == 0 ) + return ; + + if (allocation_size > 32) + { + delete[] array; + allocation_size = 0; + } + + head = 0; + tail = 0; + } + + template + void Queue::Compress ( void ) + { + queue_type* new_array; + unsigned int newAllocationSize; + if (allocation_size==0) + return; + + newAllocationSize=1; + while (newAllocationSize <= Size()) + newAllocationSize<<=1; // Must be a better way to do this but I'm too dumb to figure it out quickly :) + + new_array = new queue_type [newAllocationSize]; + + for (unsigned int counter=0; counter < Size(); ++counter) + new_array[counter] = array[(head + counter)%(allocation_size)]; + + tail=Size(); + allocation_size=newAllocationSize; + head=0; + + // Delete the old array and move the pointer to the new array + delete [] array; + array=new_array; + } + + template + bool Queue::Find ( queue_type q ) + { + if ( allocation_size == 0 ) + return false; + + unsigned int counter = head; + + while ( counter != tail ) + { + if ( array[ counter ] == q ) + return true; + + counter = ( counter + 1 ) % allocation_size; + } + + return false; + } + + template + void Queue::ClearAndForceAllocation( int size ) + { + delete [] array; + array = new queue_type[ size ]; + allocation_size = size; + head = 0; + tail = 0; + } + + template + inline queue_type& Queue::operator[] ( unsigned int position ) const + { +#ifdef _DEBUG + assert( position < Size() ); +#endif + //return array[(head + position) % allocation_size]; + + if ( head + position >= allocation_size ) + return array[ head + position - allocation_size ]; + else + return array[ head + position ]; + } + + template + void Queue::Del( unsigned int position ) + { +#ifdef _DEBUG + assert( position < Size() ); + assert( head != tail ); +#endif + + if ( head == tail || position >= Size() ) + return ; + + unsigned int index; + + unsigned int next; + + //index = (head + position) % allocation_size; + if ( head + position >= allocation_size ) + index = head + position - allocation_size; + else + index = head + position; + + //next = (index + 1) % allocation_size; + next = index + 1; + + if ( next == allocation_size ) + next = 0; + + while ( next != tail ) + { + // Overwrite the previous element + array[ index ] = array[ next ]; + index = next; + //next = (next + 1) % allocation_size; + + if ( ++next == allocation_size ) + next = 0; + } + + // Move the tail back + if ( tail == 0 ) + tail = allocation_size - 1; + else + --tail; + } +} // End namespace + +#endif + diff --git a/raknet/DS_QueueLinkedList.h b/raknet/DS_QueueLinkedList.h new file mode 100644 index 0000000..fbdfad9 --- /dev/null +++ b/raknet/DS_QueueLinkedList.h @@ -0,0 +1,110 @@ +/// \file +/// \brief \b [Internal] A queue implemented as a linked list. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __QUEUE_LINKED_LIST_H +#define __QUEUE_LINKED_LIST_H + +#include "DS_LinkedList.h" +#include "Export.h" + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + /// \brief A queue implemented using a linked list. Rarely used. + template + class RAK_DLL_EXPORT QueueLinkedList + { + + public: + QueueLinkedList(); + QueueLinkedList( const QueueLinkedList& original_copy ); + bool operator= ( const QueueLinkedList& original_copy ); + QueueType Pop( void ); + QueueType& Peek( void ); + QueueType& EndPeek( void ); + void Push( const QueueType& input ); + unsigned int Size( void ); + void Clear( void ); + void Compress( void ); + + private: + LinkedList data; + }; + + template + QueueLinkedList::QueueLinkedList() + { + } + + template + inline unsigned int QueueLinkedList::Size() + { + return data.Size(); + } + + template + inline QueueType QueueLinkedList::Pop( void ) + { + data.Beginning(); + return ( QueueType ) data.Pop(); + } + + template + inline QueueType& QueueLinkedList::Peek( void ) + { + data.Beginning(); + return ( QueueType ) data.Peek(); + } + + template + inline QueueType& QueueLinkedList::EndPeek( void ) + { + data.End(); + return ( QueueType ) data.Peek(); + } + + template + void QueueLinkedList::Push( const QueueType& input ) + { + data.End(); + data.Add( input ); + } + + template + QueueLinkedList::QueueLinkedList( const QueueLinkedList& original_copy ) + { + data = original_copy.data; + } + + template + bool QueueLinkedList::operator= ( const QueueLinkedList& original_copy ) + { + if ( ( &original_copy ) == this ) + return false; + + data = original_copy.data; + } + + template + void QueueLinkedList::Clear ( void ) + { + data.Clear(); + } +} // End namespace + +#endif diff --git a/raknet/DS_RangeList.h b/raknet/DS_RangeList.h new file mode 100644 index 0000000..383eb16 --- /dev/null +++ b/raknet/DS_RangeList.h @@ -0,0 +1,218 @@ +#ifndef __RANGE_LIST_H +#define __RANGE_LIST_H + +#include "DS_OrderedList.h" +#include "BitStream.h" +#include + +namespace DataStructures +{ + template + struct RangeNode + { + RangeNode() {} + ~RangeNode() {} + RangeNode(range_type min, range_type max) {minIndex=min; maxIndex=max;} + range_type minIndex; + range_type maxIndex; + }; + + + template + int RangeNodeComp(const range_type &a, const RangeNode &b) + { + if (a + class RAK_DLL_EXPORT RangeList + { + public: + RangeList(); + ~RangeList(); + void Insert(range_type index); + void Clear(void); + unsigned Size(void); + unsigned RangeSum(void); + unsigned Serialize(RakNet::BitStream *in, int maxBits, bool clearSerialized); + bool Deserialize(RakNet::BitStream *out); + + DataStructures::OrderedList , RangeNodeComp > ranges; + }; + + template + unsigned RangeList::Serialize(RakNet::BitStream *in, int maxBits, bool clearSerialized) + { + assert(ranges.Size() < (unsigned short)-1); + RakNet::BitStream tempBS; + int bitsWritten; + unsigned short countWritten; + unsigned i; + countWritten=0; + bitsWritten=0; + for (i=0; i < ranges.Size(); i++) + { + if ((int)sizeof(unsigned short)*8+bitsWritten+(int)sizeof(range_type)*8*2+1>maxBits) + break; + tempBS.Write(ranges[i].minIndex==ranges[i].maxIndex); + tempBS.Write(ranges[i].minIndex); + bitsWritten+=sizeof(range_type)*8+1; + if (ranges[i].minIndex!=ranges[i].maxIndex) + { + tempBS.Write(ranges[i].maxIndex); + bitsWritten+=sizeof(range_type)*8; + } + countWritten++; + } + + int before=in->GetWriteOffset(); + in->WriteCompressed(countWritten); + bitsWritten+=in->GetWriteOffset()-before; + // printf("%i ", in->GetNumberOfBitsUsed()); + in->Write(&tempBS, tempBS.GetNumberOfBitsUsed()); + // printf("%i %i \n", tempBS.GetNumberOfBitsUsed(),in->GetNumberOfBitsUsed()); + + if (clearSerialized && countWritten) + { + unsigned rangeSize=ranges.Size(); + for (i=0; i < rangeSize-countWritten; i++) + { + ranges[i]=ranges[i+countWritten]; + } + ranges.Del(countWritten); + } + + return bitsWritten; + } + template + bool RangeList::Deserialize(RakNet::BitStream *out) + { + ranges.Clear(); + unsigned short count; + out->ReadCompressed(count); + unsigned short i; + range_type min,max; + bool maxEqualToMin; + + for (i=0; i < count; i++) + { + out->Read(maxEqualToMin); + if (out->Read(min)==false) + return false; + if (maxEqualToMin==false) + { + if (out->Read(max)==false) + return false; + if (max(min,max)); + } + return true; + } + + template + RangeList::RangeList() + { + RangeNodeComp(0, RangeNode()); + } + + template + RangeList::~RangeList() + { + Clear(); + } + + template + void RangeList::Insert(range_type index) + { + if (ranges.Size()==0) + { + ranges.Insert(index, RangeNode(index, index)); + return; + } + + bool objectExists; + unsigned insertionIndex=ranges.GetIndexFromKey(index, &objectExists); + if (insertionIndex==ranges.Size()) + { + if (index == ranges[insertionIndex-1].maxIndex+1) + ranges[insertionIndex-1].maxIndex++; + else if (index > ranges[insertionIndex-1].maxIndex+1) + { + // Insert at end + ranges.Insert(index, RangeNode(index, index)); + } + + return; + } + + if (index < ranges[insertionIndex].minIndex-1) + { + // Insert here + ranges.InsertAtIndex(RangeNode(index, index), insertionIndex); + + return; + } + else if (index == ranges[insertionIndex].minIndex-1) + { + // Decrease minIndex and join left + ranges[insertionIndex].minIndex--; + if (insertionIndex>0 && ranges[insertionIndex-1].maxIndex+1==ranges[insertionIndex].minIndex) + { + ranges[insertionIndex-1].maxIndex=ranges[insertionIndex].maxIndex; + ranges.RemoveAtIndex(insertionIndex); + } + + return; + } + else if (index >= ranges[insertionIndex].minIndex && index <= ranges[insertionIndex].maxIndex) + { + // Already exists + return; + } + else if (index == ranges[insertionIndex].maxIndex+1) + { + // Increase maxIndex and join right + ranges[insertionIndex].maxIndex++; + if (insertionIndex + void RangeList::Clear(void) + { + ranges.Clear(); + } + + template + unsigned RangeList::Size(void) + { + return ranges.Size(); + } + + template + unsigned RangeList::RangeSum(void) + { + unsigned sum,i; + for (i=0; i < ranges.Size(); i++) + sum+=ranges[i].maxIndex-ranges[i].minIndex+1; + return sum; + } + +} + +#endif diff --git a/raknet/DS_Table.cpp b/raknet/DS_Table.cpp new file mode 100644 index 0000000..cc47d06 --- /dev/null +++ b/raknet/DS_Table.cpp @@ -0,0 +1,772 @@ +#include "DS_Table.h" +#include "DS_OrderedList.h" +#include +#include + +using namespace DataStructures; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void ExtendRows(Table::Row* input, int index) +{ + input->cells.Insert(new Table::Cell ); +} + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void FreeRow(Table::Row* input, int index) +{ + unsigned i; + for (i=0; i < input->cells.Size(); i++) + { + delete input->cells[i]; + } + delete input; +} +Table::Cell::Cell() +{ + isEmpty=true; +} +Table::Cell::~Cell() +{ + Clear(); +} +Table::Cell& Table::Cell::operator = ( const Table::Cell& input ) +{ + isEmpty=input.isEmpty; + i=input.i; + if (input.c) + { + if (c) + delete [] c; + c = new char [i]; + memcpy(c, input.c, i); + } + return *this; +} +Table::Cell::Cell( const Table::Cell & input) +{ + isEmpty=input.isEmpty; + i=input.i; + if (input.c) + { + if (c) + delete [] c; + c = new char [i]; + memcpy(c, input.c, i); + } +} +void Table::Cell::Set(int input) +{ + assert(isEmpty); + i=input; + c=0; + isEmpty=false; +} +void Table::Cell::Set(char *input) +{ + assert(isEmpty); + if (input && input[0]) + { + i=(int)strlen(input)+1; + c = new char [i]; + strcpy(c, input); + } + else + c=0; + i=0; + isEmpty=false; +} +void Table::Cell::Set(char *input, int inputLength) +{ + assert(isEmpty); + if (input) + { + c = new char [inputLength]; + i=inputLength; + memcpy(c, input, inputLength); + } + else + { + c=0; + i=0; + } + isEmpty=false; +} +void Table::Cell::Get(int *output) +{ + assert(isEmpty==false); + *output=i; +} +void Table::Cell::Get(char *output) +{ + assert(isEmpty==false); + strcpy(output, c); +} +void Table::Cell::Get(char *output, int *outputLength) +{ + assert(isEmpty==false); + memcpy(output, c, i); + if (outputLength) + *outputLength=i; +} +Table::Cell::Cell(int intValue, char *charValue, ColumnType type) +{ + if (type==NUMERIC) + { + Set(intValue); + } + else if (type==STRING) + { + Set(charValue); + } + else + { + Set(charValue, intValue); + } + + isEmpty=false; +} +void Table::Cell::Clear(void) +{ + if (isEmpty==false) + delete [] c; + isEmpty=true; +} +Table::ColumnDescriptor::ColumnDescriptor() +{ + +} +Table::ColumnDescriptor::~ColumnDescriptor() +{ + +} +Table::ColumnDescriptor::ColumnDescriptor(char cn[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType ct) +{ + columnType=ct; + strcpy(columnName, cn); +} +void Table::Row::UpdateCell(unsigned columnIndex, int value) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(value); + +// cells[columnIndex]->i=value; +// cells[columnIndex]->c=0; +// cells[columnIndex]->isEmpty=false; +} +void Table::Row::UpdateCell(unsigned columnIndex, char *str) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(str); +} +void Table::Row::UpdateCell(unsigned columnIndex, int byteLength, char *data) +{ + cells[columnIndex]->Clear(); + cells[columnIndex]->Set(data,byteLength); +} +Table::Table() +{ +} +Table::~Table() +{ + Clear(); +} +unsigned Table::AddColumn(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType columnType) +{ + if (columnName[0]==0) + return (unsigned) -1; + + // Add this column. + columns.Insert(Table::ColumnDescriptor(columnName, columnType)); + + // Extend the rows by one + rows.ForEachData(ExtendRows); + + return columns.Size()-1; +} +void Table::RemoveColumn(unsigned columnIndex) +{ + if (columnIndex >= columns.Size()) + return; + + columns.RemoveAtIndex(columnIndex); + + // Remove this index from each row. + int i; + DataStructures::Page *cur = rows.GetListHead(); + while (cur) + { + for (i=0; i < cur->size; i++) + { + delete cur->data[i]->cells[columnIndex]; + cur->data[i]->cells.RemoveAtIndex(columnIndex); + } + + cur=cur->next; + } +} +unsigned Table::ColumnIndex(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]) +{ + unsigned columnIndex; + for (columnIndex=0; columnIndex= columns.Size()) + return 0; + else + return (char*)columns[index].columnName; +} +Table::ColumnType Table::GetColumnType(unsigned index) +{ + if (index >= columns.Size()) + return (Table::ColumnType) 0; + else + return columns[index].columnType; +} +unsigned Table::GetColumnCount(void) const +{ + return columns.Size(); +} +unsigned Table::GetRowCount(void) const +{ + return rows.Size(); +} +Table::Row* Table::AddRow(unsigned rowId) +{ + Row *newRow; + newRow = new Row; + if (rows.Insert(rowId, newRow)==false) + { + delete newRow; + return 0; // Already exists + } + unsigned rowIndex; + for (rowIndex=0; rowIndex < columns.Size(); rowIndex++) + newRow->cells.Insert( new Table::Cell() ); + return newRow; +} +Table::Row* Table::AddRow(unsigned rowId, DataStructures::List &initialCellValues) +{ + Row *newRow = new Row; + unsigned rowIndex; + for (rowIndex=0; rowIndex < columns.Size(); rowIndex++) + { + if (rowIndex < initialCellValues.Size() && initialCellValues[rowIndex].isEmpty==false) + newRow->cells.Insert(new Table::Cell(initialCellValues[rowIndex].i, initialCellValues[rowIndex].c, columns[rowIndex].columnType)); + else + newRow->cells.Insert(new Table::Cell()); + } + rows.Insert(rowId, newRow); + return newRow; +} +Table::Row* Table::AddRowColumns(unsigned rowId, Row *row, DataStructures::List columnIndices) +{ + Row *newRow = new Row; + unsigned columnIndex; + for (columnIndex=0; columnIndex < columnIndices.Size(); columnIndex++) + { + if (row->cells[columnIndices[columnIndex]]->isEmpty==false) + { + newRow->cells.Insert(new Table::Cell( + row->cells[columnIndices[columnIndex]]->i, + row->cells[columnIndices[columnIndex]]->c, + columns[columnIndex].columnType + )); + } + else + { + newRow->cells.Insert(new Cell()); + } + } + rows.Insert(rowId, newRow); + return newRow; +} +void Table::RemoveRow(unsigned rowId) +{ + Row *out; + if (rows.Delete(rowId, out)) + DeleteRow(out); +} +void Table::RemoveRows(Table *tableContainingRowIDs) +{ + unsigned i; + DataStructures::Page *cur = tableContainingRowIDs->GetRows().GetListHead(); + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + rows.Delete(cur->keys[i]); + } + cur=cur->next; + } + return; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, int value) +{ + assert(columns[columnIndex].columnType==NUMERIC); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, value); + return true; + } + return false; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, char *str) +{ + assert(columns[columnIndex].columnType==STRING); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, str); + return true; + } + return false; +} +bool Table::UpdateCell(unsigned rowId, unsigned columnIndex, int byteLength, char *data) +{ + assert(columns[columnIndex].columnType==BINARY); + + Row *row = GetRowByID(rowId); + if (row) + { + row->UpdateCell(columnIndex, byteLength, data); + return true; + } + return false; +} +Table::FilterQuery::FilterQuery() +{ + +} +Table::FilterQuery::~FilterQuery() +{ + +} +Table::FilterQuery::FilterQuery(unsigned column, Cell *cell, FilterQueryType op) +{ + columnIndex=column; + cellValue=cell; + operation=op; +} +Table::Row* Table::GetRowByID(unsigned rowId) +{ + Row *row; + if (rows.Get(rowId, row)) + return row; + return 0; +} + +Table::Row* Table::GetRowByIndex(unsigned rowIndex) +{ + DataStructures::Page *cur = rows.GetListHead(); + while (cur) + { + if (rowIndex < (unsigned)cur->size) + return cur->data[rowIndex]; + if (rowIndex <= (unsigned)cur->size) + rowIndex-=cur->size; + else + return 0; + cur=cur->next; + } + return 0; +} + +void Table::QueryTable(unsigned *columnSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result) +{ + unsigned i; + DataStructures::List columnIndicesToReturn; + + // Clear the result table. + result->Clear(); + + if (columnSubset && numColumnSubset>0) + { + for (i=0; i < numColumnSubset; i++) + { + if (columnSubset[i]>=0 && columnSubset[i]AddColumn(columns[columnIndicesToReturn[i]].columnName,columns[columnIndicesToReturn[i]].columnType); + } + + // Get the column indices of the filter queries. + DataStructures::List inclusionFilterColumnIndices; + if (inclusionFilters && numInclusionFilters>0) + { + for (i=0; i < numInclusionFilters; i++) + { + if (inclusionFilters[i].columnIndex>=0 && inclusionFilters[i].columnIndex *cur = rows.GetListHead(); + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + QueryRow(inclusionFilterColumnIndices, columnIndicesToReturn, cur->keys[i], cur->data[i], inclusionFilters, result); + } + cur=cur->next; + } + } + else + { + // Specific rows + Row *row; + for (i=0; i < numRowIDs; i++) + { + if (rows.Get(rowIds[i], row)) + { + QueryRow(inclusionFilterColumnIndices, columnIndicesToReturn, rowIds[i], row, inclusionFilters, result); + } + } + } +} + +void Table::QueryRow(DataStructures::List &inclusionFilterColumnIndices, DataStructures::List &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result) +{ + bool pass; + unsigned columnIndex; + unsigned j; + + // If no inclusion filters, just add the row + if (inclusionFilterColumnIndices.Size()==0) + { + result->AddRowColumns(key, row, columnIndicesToReturn); + } + else + { + // Go through all inclusion filters. Only add this row if all filters pass. + for (j=0; jcells[columnIndex]->isEmpty==false && columnIndex!=(unsigned)-1) + { + switch (inclusionFilters[j].operation) + { + case QF_EQUAL: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)==0; + break; + case BINARY: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i && + memcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c, row->cells[columnIndex]->i)==0; + break; + } + break; + case QF_NOT_EQUAL: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i!=inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)!=0; + break; + case BINARY: + pass=row->cells[columnIndex]->i==inclusionFilters[j].cellValue->i && + memcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c, row->cells[columnIndex]->i)==0; + break; + } + break; + case QF_GREATER_THAN: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->i>inclusionFilters[j].cellValue->i; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)>0; + break; + } + break; + case QF_LESS_THAN: + switch(columns[inclusionFilterColumnIndices[j]].columnType) + { + case NUMERIC: + pass=row->cells[columnIndex]->ii; + break; + case STRING: + pass=strcmp(row->cells[columnIndex]->c,inclusionFilters[j].cellValue->c)<0; + break; + } + break; + case QF_IS_EMPTY: + pass=false; + break; + case QF_NOT_EMPTY: + pass=true; + break; + } + } + else + { + if (inclusionFilters[j].operation==QF_IS_EMPTY) + pass=true; + else + pass=false; // No value for this cell + } + + if (pass==false) + break; + } + + if (pass) + { + result->AddRowColumns(key, row, columnIndicesToReturn); + } + } +} + +static Table::SortQuery *_sortQueries; +static unsigned _numSortQueries; +static DataStructures::List *_columnIndices; +static DataStructures::List *_columns; +int RowSort(Table::Row* const &first, Table::Row* const &second) // first is the one inserting, second is the one already there. +{ + unsigned i, columnIndex; + for (i=0; i<_numSortQueries; i++) + { + columnIndex=(*_columnIndices)[i]; + if (columnIndex==(unsigned)-1) + continue; + + if (first->cells[columnIndex]->isEmpty==true && second->cells[columnIndex]->isEmpty==false) + return 1; // Empty cells always go at the end + + if (first->cells[columnIndex]->isEmpty==false && second->cells[columnIndex]->isEmpty==true) + return -1; // Empty cells always go at the end + + if (_sortQueries[i].operation==Table::QS_INCREASING_ORDER) + { + if ((*_columns)[columnIndex].columnType==Table::NUMERIC) + { + if (first->cells[columnIndex]->i>second->cells[columnIndex]->i) + return 1; + if (first->cells[columnIndex]->icells[columnIndex]->i) + return -1; + } + else + { + // String + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)>0) + return 1; + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)<0) + return -1; + } + } + else + { + if ((*_columns)[columnIndex].columnType==Table::NUMERIC) + { + if (first->cells[columnIndex]->icells[columnIndex]->i) + return 1; + if (first->cells[columnIndex]->i>second->cells[columnIndex]->i) + return -1; + } + else + { + // String + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)<0) + return 1; + if (strcmp(first->cells[columnIndex]->c,second->cells[columnIndex]->c)>0) + return -1; + } + } + } + + return 0; +} +void Table::SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Table::Row** out) +{ + unsigned i; + unsigned outLength; + DataStructures::List columnIndices; + _sortQueries=sortQueries; + _numSortQueries=numSortQueries; + _columnIndices=&columnIndices; + _columns=&columns; + bool anyValid=false; + + for (i=0; i < numSortQueries; i++) + { + if (sortQueries[i].columnIndex>=0 && sortQueries[i].columnIndex *cur; + cur = rows.GetListHead(); + if (anyValid==false) + { + outLength=0; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + out[(outLength)++]=cur->data[i]; + } + cur=cur->next; + } + return; + } + + // Start adding to ordered list. + DataStructures::OrderedList orderedList; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + orderedList.Insert(cur->data[i],cur->data[i]); + } + cur=cur->next; + } + + outLength=0; + for (i=0; i < orderedList.Size(); i++) + out[(outLength)++]=orderedList[i]; +} +void Table::PrintRow(char *out, int outLength, char columnDelineator, bool printDelineatorForBinary, Table::Row* inputRow) +{ + if (outLength<=0) + return; + if (outLength==1) + { + *out=0; + return; + } + + if (inputRow->cells.Size()!=columns.Size()) + { + strncpy(out, "Cell width does not match column width.\n", outLength); + out[outLength-1]=0; + return; + } + + char buff[512]; + unsigned i; + int len; + out[0]=0; + for (i=0; i < columns.Size(); i++) + { + if (columns[i].columnType==NUMERIC) + { + if (inputRow->cells[i]->isEmpty==false) + { + sprintf(buff, "%i", inputRow->cells[i]->i); + len=(int)strlen(buff); + } + else + len=0; + if (i+1!=columns.Size()) + buff[len++]=columnDelineator; + buff[len]=0; + } + else if (columns[i].columnType==STRING) + { + if (inputRow->cells[i]->isEmpty==false && inputRow->cells[i]->c) + { + strncpy(buff, inputRow->cells[i]->c, 512-2); + buff[512-2]=0; + len=(int)strlen(buff); + } + else + len=0; + if (i+1!=columns.Size()) + buff[len++]=columnDelineator; + buff[len]=0; + } + else + { + if (printDelineatorForBinary) + { + if (i+1!=columns.Size()) + buff[0]=columnDelineator; + buff[1]=0; + } + else + buff[0]=0; + + } + + len=(int)strlen(out); + if (outLength==len+1) + break; + strncpy(out+len, buff, outLength-len); + out[outLength-1]=0; + } +} + +void Table::Clear(void) +{ + rows.ForEachData(FreeRow); + rows.Clear(); + columns.Clear(true); +} +List& Table::GetColumns(void) +{ + return columns; +} +DataStructures::BPlusTree& Table::GetRows(void) +{ + return rows; +} +DataStructures::Page * Table::GetListHead(void) +{ + return rows.GetListHead(); +} +void Table::DeleteRow(Table::Row *row) +{ + unsigned rowIndex; + for (rowIndex=0; rowIndex < row->cells.Size(); rowIndex++) + { + delete row->cells[rowIndex]; + } + delete row; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/DS_Table.h b/raknet/DS_Table.h new file mode 100644 index 0000000..9ddd2a8 --- /dev/null +++ b/raknet/DS_Table.h @@ -0,0 +1,285 @@ +#ifndef __TABLE_H +#define __TABLE_H + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#include "DS_List.h" +#include "DS_BPlusTree.h" + +#define _TABLE_BPLUS_TREE_ORDER 16 +#define _TABLE_MAX_COLUMN_NAME_LENGTH 32 + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + + /// \brief Holds a set of columns, a set of rows, and rows times columns cells. + /// The table data structure is useful if you want to store a set of structures and perform queries on those structures + /// This is a relatively simple and fast implementation of the types of tables commonly used in databases + /// See TableSerializer to serialize data members of the table + /// See LightweightDatabaseClient and LightweightDatabaseServer to transmit the table over the network. + class Table + { + public: + + enum ColumnType + { + // Cell::i used + NUMERIC, + + // Cell::c used to hold a null terminated string. + STRING, + + // Cell::c holds data. Cell::i holds data length of c in bits. + BINARY + }; + + + /// Holds the actual data in the table + struct Cell + { + Cell(); + ~Cell(); + Cell(int intValue, char *charValue, ColumnType type); + void Clear(void); + + /// Numeric + void Set(int input); + + /// String + void Set(char *input); + + /// Binary + void Set(char *input, int inputLength); + + /// Numeric + void Get(int *output); + + /// String + void Get(char *output); + + /// Binary + void Get(char *output, int *outputLength); + + // assignment operator and copy constructor + Cell& operator = ( const Cell& input ); + Cell( const Cell & input); + + bool isEmpty; + int i; + char *c; + }; + + /// Stores the name and type of the column + /// \internal + struct ColumnDescriptor + { + ColumnDescriptor(); + ~ColumnDescriptor(); + ColumnDescriptor(char cn[_TABLE_MAX_COLUMN_NAME_LENGTH],ColumnType ct); + + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; + ColumnType columnType; + }; + + /// Stores the list of cells for this row, and a special flag used for internal sorting + struct Row + { + // list of cells + DataStructures::List cells; + + /// Numeric + void UpdateCell(unsigned columnIndex, int value); + + /// String + void UpdateCell(unsigned columnIndex, char *str); + + /// Binary + void UpdateCell(unsigned columnIndex, int byteLength, char *data); + }; + + // Operations to perform for cell comparison + enum FilterQueryType + { + QF_EQUAL, + QF_NOT_EQUAL, + QF_GREATER_THAN, + QF_LESS_THAN, + QF_IS_EMPTY, + QF_NOT_EMPTY, + }; + + // Compare the cell value for a row at columnName to the cellValue using operation. + struct FilterQuery + { + FilterQuery(); + ~FilterQuery(); + FilterQuery(unsigned column, Cell *cell, FilterQueryType op); + + unsigned columnIndex; + Cell *cellValue; + FilterQueryType operation; + }; + + /// Increasing or decreasing sort order + enum SortQueryType + { + QS_INCREASING_ORDER, + QS_DECREASING_ORDER, + }; + + // Sort on increasing or decreasing order for a particular column + struct SortQuery + { + /// The index of the table column we are sorting on + unsigned columnIndex; + + /// See SortQueryType + SortQueryType operation; + }; + + /// Constructor + Table(); + + /// Destructor + ~Table(); + + /// \brief Adds a column to the table + /// \param[in] columnName The name of the column + /// \param[in] columnType What type of data this column will hold + /// \return The index of the new column + unsigned AddColumn(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH], ColumnType columnType); + + /// \brief Removes a column by index + /// \param[in] columnIndex The index of the column to remove + void RemoveColumn(unsigned columnIndex); + + /// \brief Gets the index of a column by name + /// Column indices are stored in the order they are added. + /// \param[in] columnName The name of the column + /// \return The index of the column, or (unsigned)-1 if no such column + unsigned ColumnIndex(char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]); + + /// \brief Gives the string name of the column at a certain index + /// \param[in] index The index of the column + /// \return The name of the column, or 0 if an invalid index + char* ColumnName(unsigned index); + + /// \brief Returns the type of a column, referenced by index + /// \param[in] index The index of the column + /// \return The type of the column + ColumnType GetColumnType(unsigned index); + + /// Returns the number of columns + /// \return The number of columns in the table + unsigned GetColumnCount(void) const; + + /// Returns the number of rows + /// \return The number of rows in the table + unsigned GetRowCount(void) const; + + /// \brief Adds a row to the table + /// New rows are added with empty values for all cells. However, if you specify initialCelLValues you can specify initial values + /// It's up to you to ensure that the values in the specific cells match the type of data used by that row + /// rowId can be considered the primary key for the row. It is much faster to lookup a row by its rowId than by searching keys. + /// rowId must be unique + /// Rows are stored in sorted order in the table, using rowId as the sort key + /// \param[in] rowId The UNIQUE primary key for the row. This can never be changed. + /// \param[in] initialCellValues Initial values to give the row (optional) + /// \return The newly added row + Table::Row* AddRow(unsigned rowId); + Table::Row* AddRow(unsigned rowId, DataStructures::List &initialCellValues); + + /// Removes a row specified by rowId + /// \param[in] rowId The ID of the row + void RemoveRow(unsigned rowId); + + /// Removes all the rows with IDs that the specified table also has + /// \param[in] tableContainingRowIDs The IDs of the rows + void RemoveRows(Table *tableContainingRowIDs); + + /// Updates a particular cell in the table + /// \note If you are going to update many cells of a particular row, it is more efficient to call GetRow and perform the operations on the row directly. + /// \note Row pointers do not change, so you can also write directly to the rows for more efficiency. + /// \param[in] rowId The ID of the row + /// \param[in] columnIndex The column of the cell + /// \param[in] value The data to set + bool UpdateCell(unsigned rowId, unsigned columnIndex, int value); + bool UpdateCell(unsigned rowId, unsigned columnIndex, char *str); + bool UpdateCell(unsigned rowId, unsigned columnIndex, int byteLength, char *data); + + /// Gets a row. More efficient to do this and access Row::cells than to repeatedly call GetCell. + /// You can also update cells in rows from this function. + /// \param[in] rowId The ID of the row + /// \return The desired row, or 0 if no such row. + Row* GetRowByID(unsigned rowId); + + /// Gets a row at a specific index + /// rowIndex should be less than GetRowCount() + /// \param[in] rowIndex The index of the row + /// \return The desired row, or 0 if no such row. + Row* GetRowByIndex(unsigned rowIndex); + + /// \brief Queries the table, optionally returning only a subset of columns and rows. + /// \param[in] columnSubset An array of column indices. Only columns in this array are returned. Pass 0 for all columns + /// \param[in] numColumnSubset The number of elements in \a columnSubset + /// \param[in] inclusionFilters An array of FilterQuery. All filters must pass for the row to be returned. + /// \param[in] numInclusionFilters The number of elements in \a inclusionFilters + /// \param[in] rowIds An arrow of row IDs. Only these rows with these IDs are returned. Pass 0 for all rows. + /// \param[in] numRowIDs The number of elements in \a rowIds + /// \param[out] result The result of the query. If no rows are returned, the table will only have columns. + void QueryTable(unsigned *columnSubset, unsigned numColumnSubset, FilterQuery *inclusionFilters, unsigned numInclusionFilters, unsigned *rowIds, unsigned numRowIDs, Table *result); + + /// \brief Sorts the table by rows + /// You can sort the table in ascending or descending order on one or more columns + /// Columns have precedence in the order they appear in the \a sortQueries array + /// If a row cell on column n has the same value as a a different row on column n, then the row will be compared on column n+1 + /// \param[in] sortQueries A list of SortQuery structures, defining the sorts to perform on the table + /// \param[in] numColumnSubset The number of elements in \a numSortQueries + /// \param[out] out The address of an array of Rows, which will receive the sorted output. The array must be long enough to contain all returned rows, up to GetRowCount() + void SortTable(Table::SortQuery *sortQueries, unsigned numSortQueries, Table::Row** out); + + /// Frees all memory in the table. + void Clear(void); + + /// Writes a text representation of the row to \a out + /// \param[out] out A pointer to an array of bytes which will hold the output. + /// \param[in] outLength The size of the \a out array + /// \param[in] columnDelineator What character to print to delineate columns + /// \param[in] printDelineatorForBinary Binary output is not printed. True to still print the delineator. + /// \param[in] inputRow The row to print + void PrintRow(char *out, int outLength, char columnDelineator, bool printDelineatorForBinary, Table::Row* inputRow); + + /// Direct access to make things easier + DataStructures::List& GetColumns(void); + + /// Direct access to make things easier + DataStructures::BPlusTree& GetRows(void); + + // Get the head of a linked list containing all the row data + DataStructures::Page * GetListHead(void); + + protected: + Table::Row* AddRowColumns(unsigned rowId, Row *row, DataStructures::List columnIndices); + + void DeleteRow(Row *row); + + void QueryRow(DataStructures::List &inclusionFilterColumnIndices, DataStructures::List &columnIndicesToReturn, unsigned key, Table::Row* row, FilterQuery *inclusionFilters, Table *result); + + // 16 is arbitrary and is the order of the BPlus tree. Higher orders are better for searching while lower orders are better for + // Insertions and deletions. + DataStructures::BPlusTree rows; + + // Columns in the table. + DataStructures::List columns; + }; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/raknet/DS_Tree.h b/raknet/DS_Tree.h new file mode 100644 index 0000000..480e285 --- /dev/null +++ b/raknet/DS_Tree.h @@ -0,0 +1,97 @@ +/// \file +/// \brief \b [Internal] Just a regular tree +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __DS_TREE_H +#define __DS_TREE_H + +#include "Export.h" +#include "DS_List.h" +#include "DS_Queue.h" + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + template + class RAK_DLL_EXPORT Tree + { + public: + Tree(); + Tree(TreeType &inputData); + ~Tree(); + void LevelOrderTraversal(DataStructures::List &output); + void AddChild(TreeType &newData); + void DeleteDecendants(void); + + TreeType data; + DataStructures::List children; + }; + + template + Tree::Tree() + { + + } + + template + Tree::Tree(TreeType &inputData) + { + data=inputData; + } + + template + Tree::~Tree() + { + } + + template + void Tree::LevelOrderTraversal(DataStructures::List &output) + { + unsigned i; + Tree *node; + DataStructures::Queue*> queue; + + for (i=0; i < children.Size(); i++) + queue.Push(children[i]); + + while (queue.Size()) + { + node=queue.Pop(); + output.Insert(node); + for (i=0; i < node->children.Size(); i++) + queue.Push(node->children[i]); + } + } + + template + void Tree::AddChild(TreeType &newData) + { + children.Insert(new Tree(newData)); + } + + template + void Tree::DeleteDecendants(void) + { + DataStructures::List output; + LevelOrderTraversal(output); + unsigned i; + for (i=0; i < output.Size(); i++) + delete output[i]; + } +} + +#endif diff --git a/raknet/DS_WeightedGraph.h b/raknet/DS_WeightedGraph.h new file mode 100644 index 0000000..53564d0 --- /dev/null +++ b/raknet/DS_WeightedGraph.h @@ -0,0 +1,537 @@ +/// \file +/// \brief \b [Internal] Weighted graph. I'm assuming the indices are complex map types, rather than sequential numbers (which could be implemented much more efficiently). +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __WEIGHTED_GRAPH_H +#define __WEIGHTED_GRAPH_H + +#include "DS_OrderedList.h" +#include "DS_Map.h" +#include "DS_Heap.h" +#include "DS_Queue.h" +#include "DS_Tree.h" +#include +#ifdef _DEBUG +#include +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +/// The namespace DataStructures was only added to avoid compiler errors for commonly named data structures +/// As these data structures are stand-alone, you can use them outside of RakNet for your own projects if you wish. +namespace DataStructures +{ + template + class RAK_DLL_EXPORT WeightedGraph + { + public: + static void IMPLEMENT_DEFAULT_COMPARISON(void) {DataStructures::defaultMapKeyComparison(node_type(),node_type());} + + WeightedGraph(); + ~WeightedGraph(); + WeightedGraph( const WeightedGraph& original_copy ); + WeightedGraph& operator= ( const WeightedGraph& original_copy ); + void AddNode(const node_type &node); + void RemoveNode(const node_type &node); + void AddConnection(const node_type &node1, const node_type &node2, weight_type weight); + void RemoveConnection(const node_type &node1, const node_type &node2); + bool HasConnection(const node_type &node1, const node_type &node2); + void Print(void); + void Clear(void); + bool GetShortestPath(DataStructures::List &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT); + bool GetSpanningTree(DataStructures::Tree &outTree, DataStructures::List *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT ); + unsigned GetNodeCount(void) const; + unsigned GetConnectionCount(unsigned nodeIndex) const; + void GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const; + node_type GetNodeAtIndex(unsigned nodeIndex) const; + + protected: + void ClearDijkstra(void); + void GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT); + + DataStructures::Map *> adjacencyLists; + + // All these variables are for path finding with Dijkstra + // 08/23/06 Won't compile as a DLL inside this struct + // struct + // { + bool isValid; + node_type rootNode; + DataStructures::OrderedList costMatrixIndices; + weight_type *costMatrix; + node_type *leastNodeArray; + // } dijkstra; + + struct NodeAndParent + { + DataStructures::Tree*node; + DataStructures::Tree*parent; + }; + }; + + template + WeightedGraph::WeightedGraph() + { + isValid=false; + costMatrix=0; + } + + template + WeightedGraph::~WeightedGraph() + { + Clear(); + } + + template + WeightedGraph::WeightedGraph( const WeightedGraph& original_copy ) + { + adjacencyLists=original_copy.adjacencyLists; + + isValid=original_copy.isValid; + if (isValid) + { + rootNode=original_copy.rootNode; + costMatrixIndices=original_copy.costMatrixIndices; + costMatrix = new weight_type[costMatrixIndices.Size() * costMatrixIndices.Size()]; + leastNodeArray = new node_type[costMatrixIndices.Size()]; + memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type)); + memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type)); + } + } + + template + WeightedGraph& WeightedGraph::operator=( const WeightedGraph& original_copy ) + { + adjacencyLists=original_copy.adjacencyLists; + + isValid=original_copy.isValid; + if (isValid) + { + rootNode=original_copy.rootNode; + costMatrixIndices=original_copy.costMatrixIndices; + costMatrix = new weight_type[costMatrixIndices.Size() * costMatrixIndices.Size()]; + leastNodeArray = new node_type[costMatrixIndices.Size()]; + memcpy(costMatrix, original_copy.costMatrix, costMatrixIndices.Size() * costMatrixIndices.Size() * sizeof(weight_type)); + memcpy(leastNodeArray, original_copy.leastNodeArray, costMatrixIndices.Size() * sizeof(weight_type)); + } + + return *this; + } + + template + void WeightedGraph::AddNode(const node_type &node) + { + adjacencyLists.SetNew(node, new DataStructures::Map); + } + + template + void WeightedGraph::RemoveNode(const node_type &node) + { + unsigned i; + DataStructures::Queue removeNodeQueue; + + removeNodeQueue.Push(node); + while (removeNodeQueue.Size()) + { + delete adjacencyLists.Pop(removeNodeQueue.Pop()); + + // Remove this node from all of the other lists as well + for (i=0; i < adjacencyLists.Size(); i++) + { + adjacencyLists[i]->Delete(node); + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (allow_unlinkedNodes==false && adjacencyLists[i]->Size()==0) + removeNodeQueue.Push(adjacencyLists.GetKeyAtIndex(i)); + } + } + + ClearDijkstra(); + } + + template + bool WeightedGraph::HasConnection(const node_type &node1, const node_type &node2) + { + if (node1==node2) + return false; + if (adjacencyLists.Has(node1)==false) + return false; + return adjacencyLists.Get(node1)->Has(node2); + } + + template + void WeightedGraph::AddConnection(const node_type &node1, const node_type &node2, weight_type weight) + { + if (node1==node2) + return; + + if (adjacencyLists.Has(node1)==false) + AddNode(node1); + adjacencyLists.Get(node1)->Set(node2, weight); + if (adjacencyLists.Has(node2)==false) + AddNode(node2); + adjacencyLists.Get(node2)->Set(node1, weight); + } + + template + void WeightedGraph::RemoveConnection(const node_type &node1, const node_type &node2) + { + adjacencyLists.Get(node2)->Delete(node1); + adjacencyLists.Get(node1)->Delete(node2); + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (allow_unlinkedNodes==false) // If we do not allow _unlinked nodes, then if there are no connections, remove the node + { + if (adjacencyLists.Get(node1)->Size()==0) + RemoveNode(node1); // Will also remove node1 from the adjacency list of node2 + if (adjacencyLists.Has(node2) && adjacencyLists.Get(node2)->Size()==0) + RemoveNode(node2); + } + + ClearDijkstra(); + } + + template + void WeightedGraph::Clear(void) + { + unsigned i; + for (i=0; i < adjacencyLists.Size(); i++) + delete adjacencyLists[i]; + adjacencyLists.Clear(); + + ClearDijkstra(); + } + + template + bool WeightedGraph::GetShortestPath(DataStructures::List &path, node_type startNode, node_type endNode, weight_type INFINITE_WEIGHT) + { + path.Clear(); + if (startNode==endNode) + { + path.Insert(startNode); + path.Insert(endNode); + return true; + } + + if (isValid==false || rootNode!=startNode) + { + ClearDijkstra(); + GenerateDisjktraMatrix(startNode, INFINITE_WEIGHT); + } + + // return the results + bool objectExists; + unsigned col,row; + weight_type currentWeight; + DataStructures::Queue outputQueue; + col=costMatrixIndices.GetIndexFromKey(endNode, &objectExists); + if (costMatrixIndices.Size()<2) + { + return false; + } + if (objectExists==false) + { + return false; + } + node_type vertex; + row=costMatrixIndices.Size()-2; + if (row==0) + { + path.Insert(startNode); + path.Insert(endNode); + return true; + } + currentWeight=costMatrix[row*adjacencyLists.Size() + col]; + if (currentWeight==INFINITE_WEIGHT) + { + // No path + return true; + } + vertex=endNode; + outputQueue.PushAtHead(vertex); + row--; +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + while (costMatrix[row*adjacencyLists.Size() + col] == currentWeight) + { + if (row==0) + { + path.Insert(startNode); + for (col=0; outputQueue.Size(); col++) + path.Insert(outputQueue.Pop()); + return true; + } + --row; + } + + vertex=leastNodeArray[row]; + outputQueue.PushAtHead(vertex); + if (row==0) + break; + col=costMatrixIndices.GetIndexFromKey(vertex, &objectExists); + currentWeight=costMatrix[row*adjacencyLists.Size() + col]; + } + + path.Insert(startNode); + for (col=0; outputQueue.Size(); col++) + path.Insert(outputQueue.Pop()); + return true; + } + + template + node_type WeightedGraph::GetNodeAtIndex(unsigned nodeIndex) const + { + return adjacencyLists.GetKeyAtIndex(nodeIndex); + } + + template + unsigned WeightedGraph::GetNodeCount(void) const + { + return adjacencyLists.Size(); + } + + template + unsigned WeightedGraph::GetConnectionCount(unsigned nodeIndex) const + { + return adjacencyLists[nodeIndex]->Size(); + } + + template + void WeightedGraph::GetConnectionAtIndex(unsigned nodeIndex, unsigned connectionIndex, node_type &outNode, weight_type &outWeight) const + { + outWeight=adjacencyLists[nodeIndex]->operator[](connectionIndex); + outNode=adjacencyLists[nodeIndex]->GetKeyAtIndex(connectionIndex); + } + + template + bool WeightedGraph::GetSpanningTree(DataStructures::Tree &outTree, DataStructures::List *inputNodes, node_type startNode, weight_type INFINITE_WEIGHT ) + { + // Find the shortest path from the start node to each of the input nodes. Add this path to a new WeightedGraph if the result is reachable + DataStructures::List path; + DataStructures::WeightedGraph outGraph; + bool res; + unsigned i,j; + for (i=0; i < inputNodes->Size(); i++) + { + res=GetShortestPath(path, startNode, (*inputNodes)[i], INFINITE_WEIGHT); + if (res) + { + for (j=0; j < path.Size()-1; j++) + { + // Don't bother looking up the weight + outGraph.AddConnection(path[j], path[j+1], INFINITE_WEIGHT); + } + } + } + + // Copy the graph to a tree. + DataStructures::Queue nodesToProcess; + DataStructures::Tree *current; + DataStructures::Map *adjacencyList; + node_type key; + NodeAndParent nap, nap2; + outTree.DeleteDecendants(); + outTree.data=startNode; + current=&outTree; + if (outGraph.adjacencyLists.Has(startNode)==false) + return false; + adjacencyList = outGraph.adjacencyLists.Get(startNode); + + for (i=0; i < adjacencyList->Size(); i++) + { + nap2.node=new DataStructures::Tree; + nap2.node->data=adjacencyList->GetKeyAtIndex(i); + nap2.parent=current; + nodesToProcess.Push(nap2); + current->children.Insert(nap2.node); + } + + while (nodesToProcess.Size()) + { + nap=nodesToProcess.Pop(); + current=nap.node; + adjacencyList = outGraph.adjacencyLists.Get(nap.node->data); + + for (i=0; i < adjacencyList->Size(); i++) + { + key=adjacencyList->GetKeyAtIndex(i); + if (key!=nap.parent->data) + { + nap2.node=new DataStructures::Tree; + nap2.node->data=key; + nap2.parent=current; + nodesToProcess.Push(nap2); + current->children.Insert(nap2.node); + } + } + } + + return true; + } + + template + void WeightedGraph::GenerateDisjktraMatrix(node_type startNode, weight_type INFINITE_WEIGHT) + { + costMatrix = new weight_type[adjacencyLists.Size() * adjacencyLists.Size()]; + leastNodeArray = new node_type[adjacencyLists.Size()]; + + node_type currentNode; + unsigned col, row, row2, openSetIndex; + node_type adjacentKey; + unsigned adjacentIndex; + weight_type edgeWeight, currentNodeWeight, adjacentNodeWeight; + DataStructures::Map *adjacencyList; + DataStructures::Heap minHeap; + DataStructures::Map openSet; + + for (col=0; col < adjacencyLists.Size(); col++) + { + // This should be already sorted, so it's a bit inefficient to do an insertion sort, but what the heck + costMatrixIndices.Insert(adjacencyLists.GetKeyAtIndex(col),adjacencyLists.GetKeyAtIndex(col)); + } + for (col=0; col < adjacencyLists.Size() * adjacencyLists.Size(); col++) + costMatrix[col]=INFINITE_WEIGHT; + currentNode=startNode; + row=0; + currentNodeWeight=0; + rootNode=startNode; + + // Clear the starting node column + adjacentIndex=adjacencyLists.GetIndexAtKey(startNode); + for (row2=0; row2 < adjacencyLists.Size(); row2++) + costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=0; + + while (row < adjacencyLists.Size()-1) + { + adjacencyList = adjacencyLists.Get(currentNode); + // Go through all connections from the current node. If the new weight is less than the current weight, then update that weight. + for (col=0; col < adjacencyList->Size(); col++) + { + edgeWeight=(*adjacencyList)[col]; + adjacentKey=adjacencyList->GetKeyAtIndex(col); + adjacentIndex=adjacencyLists.GetIndexAtKey(adjacentKey); + adjacentNodeWeight=costMatrix[row*adjacencyLists.Size() + adjacentIndex]; + + if (currentNodeWeight + edgeWeight < adjacentNodeWeight) + { + // Update the weight for the adjacent node + for (row2=row; row2 < adjacencyLists.Size(); row2++) + costMatrix[row2*adjacencyLists.Size() + adjacentIndex]=currentNodeWeight + edgeWeight; + openSet.Set(adjacentKey, currentNodeWeight + edgeWeight); + } + } + + // Find the lowest in the open set + minHeap.Clear(); + for (openSetIndex=0; openSetIndex < openSet.Size(); openSetIndex++) + minHeap.Push(openSet[openSetIndex], openSet.GetKeyAtIndex(openSetIndex)); + + /* + unsigned i,j; + for (i=0; i < adjacencyLists.Size()-1; i++) + { + for (j=0; j < adjacencyLists.Size(); j++) + { + printf("%2i ", costMatrix[i*adjacencyLists.Size() + j]); + } + printf("Node=%i", leastNodeArray[i]); + printf("\n"); + } + */ + + if (minHeap.Size()==0) + { + // Unreachable nodes + isValid=true; + return; + } + + currentNodeWeight=minHeap.PeekWeight(0); + leastNodeArray[row]=minHeap.Pop(0); + currentNode=leastNodeArray[row]; + openSet.Delete(currentNode); + row++; + } + + /* +#ifdef _DEBUG + unsigned i,j; + for (i=0; i < adjacencyLists.Size()-1; i++) + { + for (j=0; j < adjacencyLists.Size(); j++) + { + printf("%2i ", costMatrix[i*adjacencyLists.Size() + j]); + } + printf("Node=%i", leastNodeArray[i]); + printf("\n"); + } +#endif + */ + + isValid=true; + } + + template + void WeightedGraph::ClearDijkstra(void) + { + if (isValid) + { + isValid=false; + delete [] costMatrix; + delete [] leastNodeArray; + costMatrixIndices.Clear(); + } + } + + template + void WeightedGraph::Print(void) + { +#ifdef _DEBUG + unsigned i,j; + for (i=0; i < adjacencyLists.Size(); i++) + { + //printf("%i connected to ", i); + printf("%s connected to ", adjacencyLists.GetKeyAtIndex(i).playerId.ToString()); + + if (adjacencyLists[i]->Size()==0) + printf(""); + else + { + for (j=0; j < adjacencyLists[i]->Size(); j++) + // printf("%i (%.2f) ", adjacencyLists.GetIndexAtKey(adjacencyLists[i]->GetKeyAtIndex(j)), (float) adjacencyLists[i]->operator[](j) ); + printf("%s (%.2f) ", adjacencyLists[i]->GetKeyAtIndex(j).playerId.ToString(), (float) adjacencyLists[i]->operator[](j) ); + } + + printf("\n"); + } +#endif + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/raknet/DataBlockEncryptor.cpp b/raknet/DataBlockEncryptor.cpp index 0d437d9..9c961ad 100644 --- a/raknet/DataBlockEncryptor.cpp +++ b/raknet/DataBlockEncryptor.cpp @@ -1,8 +1,207 @@ -// TODO: Implement DataBlockEncryptor.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "DataBlockEncryptor.h" +#include "CheckSum.h" +#include "GetTime.h" +#include "Rand.h" +#include +#include +#include "rijndael.h" +#include "Types.h" DataBlockEncryptor::DataBlockEncryptor() { keySet = false; } + +DataBlockEncryptor::~DataBlockEncryptor() +{} + +bool DataBlockEncryptor::IsKeySet( void ) const +{ + return keySet; +} + +void DataBlockEncryptor::SetKey( const unsigned char key[ 16 ] ) +{ + keySet = true; + //secretKeyAES128.set_key( key ); + makeKey(&keyEncrypt, DIR_ENCRYPT, 16, (char*)key); + makeKey(&keyDecrypt, DIR_DECRYPT, 16, (char*)key); + cipherInit(&cipherInst, MODE_ECB, 0); // ECB is not secure except that I chain manually farther down. +} + +void DataBlockEncryptor::UnsetKey( void ) +{ + keySet = false; +} + +void DataBlockEncryptor::Encrypt( unsigned char *input, int inputLength, unsigned char *output, int *outputLength ) +{ + unsigned index, byteIndex, lastBlock; + unsigned int checkSum; + unsigned char paddingBytes; + unsigned char encodedPad; + unsigned char randomChar; + CheckSum checkSumCalculator; + +#ifdef _DEBUG + + assert( keySet ); +#endif + + assert( input && inputLength ); + + + // randomChar will randomize the data so the same data sent twice will not look the same + randomChar = (unsigned char) randomMT(); + + // 16-(((x-1) % 16)+1) + + // # of padding bytes is 16 -(((input_length + extra_data -1) % 16)+1) + paddingBytes = (unsigned char) ( 16 - ( ( ( inputLength + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) - 1 ) % 16 ) + 1 ) ); + + // Randomize the pad size variable + encodedPad = (unsigned char) randomMT(); + encodedPad <<= 4; + encodedPad |= paddingBytes; + + *outputLength = inputLength + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes; + + // Write the data first, in case we are overwriting ourselves + + if ( input == output ) + memmove( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes, input, inputLength ); + else + memcpy( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes, input, inputLength ); + + // Write the random char + memcpy( output + sizeof( checkSum ), ( char* ) & randomChar, sizeof( randomChar ) ); + + // Write the pad size variable + memcpy( output + sizeof( checkSum ) + sizeof( randomChar ), ( char* ) & encodedPad, sizeof( encodedPad ) ); + + // Write the padding + for ( index = 0; index < paddingBytes; index++ ) + *( output + sizeof( checkSum ) + sizeof( randomChar ) + sizeof( encodedPad ) + index ) = (unsigned char) randomMT(); + + // Calculate the checksum on the data + checkSumCalculator.Add( output + sizeof( checkSum ), inputLength + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes ); + + checkSum = checkSumCalculator.Get(); + + // Write checksum +#ifdef HOST_ENDIAN_IS_BIG + output[0] = checkSum&0xFF; + output[1] = (checkSum>>8)&0xFF; + output[2] = (checkSum>>16)&0xFF; + output[3] = (checkSum>>24)&0xFF; +#else + memcpy( output, ( char* ) & checkSum, sizeof( checkSum ) ); +#endif + + // AES on the first block +// secretKeyAES128.encrypt16( output ); + blockEncrypt(&cipherInst, &keyEncrypt, output, 16, output); + + lastBlock = 0; + + // Now do AES on every other block from back to front + for ( index = *outputLength - 16; index >= 16; index -= 16 ) + { + for ( byteIndex = 0; byteIndex < 16; byteIndex++ ) + output[ index + byteIndex ] ^= output[ lastBlock + byteIndex ]; + + //secretKeyAES128.encrypt16( output + index ); + blockEncrypt(&cipherInst, &keyEncrypt, output+index, 16, output+index); + + lastBlock = index; + } +} + +bool DataBlockEncryptor::Decrypt( unsigned char *input, int inputLength, unsigned char *output, int *outputLength ) +{ + unsigned index, byteIndex, lastBlock; + unsigned int checkSum; + unsigned char paddingBytes; + unsigned char encodedPad; + unsigned char randomChar; + CheckSum checkSumCalculator; +#ifdef _DEBUG + + assert( keySet ); +#endif + + if ( input == 0 || inputLength < 16 || ( inputLength % 16 ) != 0 ) + { + return false; + } + + // Unchain in reverse order + for ( index = 16; ( int ) index <= inputLength - 16;index += 16 ) + { + // secretKeyAES128.decrypt16( input + index ); + blockDecrypt(&cipherInst, &keyDecrypt, input + index, 16, input + index); + + for ( byteIndex = 0; byteIndex < 16; byteIndex++ ) + { + if ( index + 16 == ( unsigned ) inputLength ) + input[ index + byteIndex ] ^= input[ byteIndex ]; + else + input[ index + byteIndex ] ^= input[ index + 16 + byteIndex ]; + } + + lastBlock = index; + }; + + // Decrypt the first block + //secretKeyAES128.decrypt16( input ); + blockDecrypt(&cipherInst, &keyDecrypt, input, 16, input); + + // Read checksum +#ifdef HOST_ENDIAN_IS_BIG + checkSum = (unsigned int)input[0] | (unsigned int)(input[1]<<8) | + (unsigned int)(input[2]<<16)|(unsigned int)(input[3]<<24); +#else + memcpy( ( char* ) & checkSum, input, sizeof( checkSum ) ); +#endif + + // Read the pad size variable + memcpy( ( char* ) & encodedPad, input + sizeof( randomChar ) + sizeof( checkSum ), sizeof( encodedPad ) ); + + // Ignore the high 4 bytes + paddingBytes = encodedPad & 0x0F; + + + // Get the data length + *outputLength = inputLength - sizeof( randomChar ) - sizeof( checkSum ) - sizeof( encodedPad ) - paddingBytes; + + // Calculate the checksum on the data. + checkSumCalculator.Add( input + sizeof( checkSum ), *outputLength + sizeof( randomChar ) + sizeof( encodedPad ) + paddingBytes ); + + if ( checkSum != checkSumCalculator.Get() ) + return false; + + // Read the data + if ( input == output ) + memmove( output, input + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes, *outputLength ); + else + memcpy( output, input + sizeof( randomChar ) + sizeof( checkSum ) + sizeof( encodedPad ) + paddingBytes, *outputLength ); + + + return true; +} diff --git a/raknet/DataBlockEncryptor.h b/raknet/DataBlockEncryptor.h index c15e438..a1e48e7 100644 --- a/raknet/DataBlockEncryptor.h +++ b/raknet/DataBlockEncryptor.h @@ -1,18 +1,69 @@ -// TODO: Implement DataBlockEncryptor.h +/// \file +/// \brief \b [Internal] Encrypts and decrypts data blocks. Used as part of secure connections. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __DATA_BLOCK_ENCRYPTOR_H #define __DATA_BLOCK_ENCRYPTOR_H +#include "rijndael.h" + /// Encrypts and decrypts data blocks. class DataBlockEncryptor { public: - + /// Constructor DataBlockEncryptor(); - + + /// Destructor + ~DataBlockEncryptor(); + + /// \return true if SetKey has been called previously + bool IsKeySet( void ) const; + + /// Set the encryption key + /// \param[in] key The new encryption key + void SetKey( const unsigned char key[ 16 ] ); + + /// Unset the encryption key + void UnsetKey( void ); + + /// Encryption adds up to 15 bytes. Output should be large enough to hold this. + /// Output can be the same memory block as input + /// \param[in] input the input buffer to encrypt + /// \param[in] inputLength the size of the @em input buffer + /// \param[in] output the output buffer to store encrypted data + /// \param[in] outputLength the size of the output buffer + void Encrypt( unsigned char *input, int inputLength, unsigned char *output, int *outputLength ); + + /// Decryption removes bytes, as few as 6. Output should be large enough to hold this. + /// Output can be the same memory block as input + /// \param[in] input the input buffer to decrypt + /// \param[in] inputLength the size of the @em input buffer + /// \param[in] output the output buffer to store decrypted data + /// \param[in] outputLength the size of the @em output buffer + /// \return False on bad checksum or input, true on success + bool Decrypt( unsigned char *input, int inputLength, unsigned char *output, int *outputLength ); + protected: + + keyInstance keyEncrypt; + keyInstance keyDecrypt; + cipherInstance cipherInst; bool keySet; }; diff --git a/raknet/DataCompressor.cpp b/raknet/DataCompressor.cpp new file mode 100644 index 0000000..308ea11 --- /dev/null +++ b/raknet/DataCompressor.cpp @@ -0,0 +1,58 @@ +#include "DataCompressor.h" +#include "DS_HuffmanEncodingTree.h" +#include +#include // Use string.h rather than memory.h for the PS3 + +void DataCompressor::Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output ) +{ + // Don't use this for small files as you will just make them bigger! + assert(sizeInBytes > 2048); + + unsigned int frequencyTable[ 256 ]; + unsigned int i; + memset(frequencyTable,0,256*sizeof(unsigned int)); + for (i=0; i < sizeInBytes; i++) + ++frequencyTable[userData[i]]; + HuffmanEncodingTree tree; + unsigned int writeOffset1, writeOffset2, bitsUsed1, bitsUsed2; + tree.GenerateFromFrequencyTable(frequencyTable); + output->WriteCompressed(sizeInBytes); + for (i=0; i < 256; i++) + output->WriteCompressed(frequencyTable[i]); + output->AlignWriteToByteBoundary(); + writeOffset1=output->GetWriteOffset(); + output->Write((unsigned int)0); // Dummy value + bitsUsed1=output->GetNumberOfBitsUsed(); + tree.EncodeArray(userData, sizeInBytes, output); + bitsUsed2=output->GetNumberOfBitsUsed(); + writeOffset2=output->GetWriteOffset(); + output->SetWriteOffset(writeOffset1); + output->Write(bitsUsed2-bitsUsed1); // Go back and write how many bits were used for the encoding + output->SetWriteOffset(writeOffset2); +} + +unsigned DataCompressor::DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output ) +{ + HuffmanEncodingTree tree; + unsigned int bitsUsed, destinationSizeInBytes, decompressedBytes; + unsigned int frequencyTable[ 256 ]; + unsigned i; + + input->ReadCompressed(destinationSizeInBytes); + for (i=0; i < 256; i++) + input->ReadCompressed(frequencyTable[i]); + input->AlignReadToByteBoundary(); + if (input->Read(bitsUsed)==false) + { + // Read error +#ifdef _DEBUG + assert(0); +#endif + return 0; + } + *output = new unsigned char [destinationSizeInBytes]; + tree.GenerateFromFrequencyTable(frequencyTable); + decompressedBytes=tree.DecodeArray(input, bitsUsed, destinationSizeInBytes, *output ); + assert(decompressedBytes==destinationSizeInBytes); + return destinationSizeInBytes; +} diff --git a/raknet/DataCompressor.h b/raknet/DataCompressor.h new file mode 100644 index 0000000..ae2cac5 --- /dev/null +++ b/raknet/DataCompressor.h @@ -0,0 +1,33 @@ +/// \file +/// \brief DataCompressor does compression on a block of data. Not very good compression, but it's small and fast so is something you can use per-message at runtime. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + + +#ifndef __DATA_COMPRESSOR_H +#define __DATA_COMPRESSOR_H + +#include "DS_HuffmanEncodingTree.h" +#include "Export.h" + +/// \brief Does compression on a block of data. Not very good compression, but it's small and fast so is something you can compute at runtime. +class RAK_DLL_EXPORT DataCompressor +{ +public: + static void Compress( unsigned char *userData, unsigned sizeInBytes, RakNet::BitStream * output ); + static unsigned DecompressAndAllocate( RakNet::BitStream * input, unsigned char **output ); +}; + +#endif diff --git a/raknet/DirectoryDeltaTransfer.cpp b/raknet/DirectoryDeltaTransfer.cpp new file mode 100644 index 0000000..1ba86b6 --- /dev/null +++ b/raknet/DirectoryDeltaTransfer.cpp @@ -0,0 +1,227 @@ +#include "DirectoryDeltaTransfer.h" +#include "FileList.h" +#include "StringCompressor.h" +#include "RakPeerInterface.h" +#include "FileListTransfer.h" +#include "FileListTransferCBInterface.h" +#include "BitStream.h" +#include "PacketEnumerations.h" +#include "FileOperations.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +class DDTCallback : public FileListTransferCBInterface +{ +public: + unsigned subdirLen; + char outputSubdir[512]; + FileListTransferCBInterface *onFileCallback; + + virtual void OnFile( + unsigned fileIndex, + char *filename, + char *fileData, + unsigned compressedTransmissionLength, + unsigned finalDataLength, + unsigned short setID, + unsigned setCount, + unsigned setTotalCompressedTransmissionLength, + unsigned setTotalFinalLength, + unsigned char context) + { + char fullPathToDir[1024]; + + if (filename && fileData && subdirLen < strlen(filename)) + { + strcpy(fullPathToDir, outputSubdir); + strcat(fullPathToDir, filename+subdirLen); + WriteFileWithDirectories(fullPathToDir, (char*)fileData, finalDataLength); + } + else + fullPathToDir[0]=0; + + onFileCallback->OnFile(fileIndex, fullPathToDir, fileData, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, context); + } + + virtual void OnFileProgress(unsigned fileIndex, + char *filename, + unsigned compressedTransmissionLength, + unsigned finalDataLength, + unsigned short setID, + unsigned setCount, + unsigned setTotalCompressedTransmissionLength, + unsigned setTotalFinalLength, + unsigned char context, + unsigned int partCount, + unsigned int partTotal, + unsigned int partLength) + { + char fullPathToDir[1024]; + + if (filename && subdirLen < strlen(filename)) + { + strcpy(fullPathToDir, outputSubdir); + strcat(fullPathToDir, filename+subdirLen); + } + else + fullPathToDir[0]=0; + + onFileCallback->OnFileProgress(fileIndex, fullPathToDir, compressedTransmissionLength, finalDataLength, setID, setCount, setTotalCompressedTransmissionLength, setTotalFinalLength, context, partCount, partTotal, partLength); + } +}; + +DirectoryDeltaTransfer::DirectoryDeltaTransfer() +{ + applicationDirectory[0]=0; + fileListTransfer=0; + availableUploads = new FileList; + rakPeer=0; + priority=HIGH_PRIORITY; + orderingChannel=0; +} +DirectoryDeltaTransfer::~DirectoryDeltaTransfer() +{ + delete availableUploads; +} +void DirectoryDeltaTransfer::SetFileListTransferPlugin(FileListTransfer *flt) +{ + fileListTransfer=flt; +} +void DirectoryDeltaTransfer::SetApplicationDirectory(const char *pathToApplication) +{ + if (pathToApplication==0 || pathToApplication[0]==0) + applicationDirectory[0]=0; + else + { + strncpy(applicationDirectory, pathToApplication, 510); + if (applicationDirectory[strlen(applicationDirectory)-1]!='/' && applicationDirectory[strlen(applicationDirectory)-1]!='\\') + strcat(applicationDirectory, "/"); + applicationDirectory[511]=0; + } +} +void DirectoryDeltaTransfer::SetUploadSendParameters(PacketPriority _priority, char _orderingChannel) +{ + priority=_priority; + orderingChannel=_orderingChannel; +} +void DirectoryDeltaTransfer::AddUploadsFromSubdirectory(const char *subdir) +{ + availableUploads->AddFilesFromDirectory(applicationDirectory, subdir, true, false, true, 0); +} +unsigned short DirectoryDeltaTransfer::DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, PlayerID host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel) +{ + if (rakPeer->GetIndexFromPlayerID(host)==-1) + return (unsigned short) -1; + + DDTCallback *transferCallback; + FileList localFiles; + // Get a hash of all the files that we already have (if any) + localFiles.AddFilesFromDirectory(prependAppDirToOutputSubdir ? applicationDirectory : 0, outputSubdir, true, false, true, 0); + + // Prepare the callback data + transferCallback = new DDTCallback; + if (subdir && subdir[0]) + { + transferCallback->subdirLen=(unsigned int)strlen(subdir); + if (subdir[transferCallback->subdirLen-1]!='/' && subdir[transferCallback->subdirLen-1]!='\\') + transferCallback->subdirLen++; + } + else + transferCallback->subdirLen=0; + if (prependAppDirToOutputSubdir) + strcpy(transferCallback->outputSubdir, applicationDirectory); + else + transferCallback->outputSubdir[0]=0; + if (outputSubdir) + strcat(transferCallback->outputSubdir, outputSubdir); + if (transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='/' && transferCallback->outputSubdir[strlen(transferCallback->outputSubdir)-1]!='\\') + strcat(transferCallback->outputSubdir, "/"); + transferCallback->onFileCallback=onFileCallback; + + // Setup the transfer plugin to get the response to this download request + unsigned short setId = fileListTransfer->SetupReceive(transferCallback, true, host); + + // Send to the host, telling it to process this request + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_DDT_DOWNLOAD_REQUEST); + outBitstream.Write(setId); + stringCompressor->EncodeString(subdir, 256, &outBitstream); + stringCompressor->EncodeString(outputSubdir, 256, &outBitstream); + localFiles.Serialize(&outBitstream); + rakPeer->Send(&outBitstream, _priority, RELIABLE_ORDERED, _orderingChannel, host, false); + + return setId; +} +void DirectoryDeltaTransfer::ClearUploads(void) +{ + availableUploads->Clear(); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void DirectoryDeltaTransfer::OnDownloadRequest(RakPeerInterface *peer, Packet *packet) +{ + char subdir[256]; + char remoteSubdir[256]; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + FileList remoteFileHash; + FileList delta; + unsigned short setId; + inBitstream.IgnoreBits(8); + inBitstream.Read(setId); + stringCompressor->DecodeString(subdir, 256, &inBitstream); + stringCompressor->DecodeString(remoteSubdir, 256, &inBitstream); + if (remoteFileHash.Deserialize(&inBitstream)==false) + { +#ifdef _DEBUG + assert(0); +#endif + return; + } + + availableUploads->GetDeltaToCurrent(&remoteFileHash, &delta, subdir, remoteSubdir); + delta.PopulateDataFromDisk(applicationDirectory, true, false, true); + + // This will call the ddtCallback interface that was passed to FileListTransfer::SetupReceive on the remote system + fileListTransfer->Send(&delta, rakPeer, packet->playerId, setId, priority, orderingChannel, true); +} +void DirectoryDeltaTransfer::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void DirectoryDeltaTransfer::Update(RakPeerInterface *peer) +{ + +} +PluginReceiveResult DirectoryDeltaTransfer::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + switch (packet->data[0]) + { + case ID_DDT_DOWNLOAD_REQUEST: + OnDownloadRequest(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void DirectoryDeltaTransfer::OnDisconnect(RakPeerInterface *peer) +{ + +} + +unsigned DirectoryDeltaTransfer::GetNumberOfFilesForUpload(void) const +{ + return availableUploads->fileList.Size(); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/DirectoryDeltaTransfer.h b/raknet/DirectoryDeltaTransfer.h new file mode 100644 index 0000000..ae659f0 --- /dev/null +++ b/raknet/DirectoryDeltaTransfer.h @@ -0,0 +1,124 @@ +/// \file +/// \brief Simple class to send changes between directories. In essense, a simple autopatcher that can be used for transmitting levels, skins, etc. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __DIRECTORY_DELTA_TRANSFER_H +#define __DIRECTORY_DELTA_TRANSFER_H + +#include "NetworkTypes.h" +#include "Export.h" +#include "PluginInterface.h" +#include "DS_Map.h" +#include "PacketPriority.h" + +class RakPeerInterface; +class FileList; +struct Packet; +struct InternalPacket; +struct DownloadRequest; +class FileListTransfer; +class FileListTransferCBInterface; + +/// \defgroup DIRECTORY_DELTA_TRANSFER_GROUP DirectoryDeltaTransfer +/// \ingroup PLUGINS_GROUP + +/// \brief Simple class to send changes between directories. In essense, a simple autopatcher that can be used for transmitting levels, skins, etc. +/// \sa Autopatcher class for database driven patching, including binary deltas and search by date. +/// +/// To use, first set the path to your application. For example "C:/Games/MyRPG/" +/// To allow other systems to download files, call AddUploadsFromSubdirectory, where the parameter is a path relative +/// to the path to your application. This includes subdirectories. +/// For example: +/// SetApplicationDirectory("C:/Games/MyRPG/"); +/// AddUploadsFromSubdirectory("Mods/Skins/"); +/// would allow downloads from +/// "C:/Games/MyRPG/Mods/Skins/*.*" as well as "C:/Games/MyRPG/Mods/Skins/Level1/*.*" +/// It would NOT allow downloads from C:/Games/MyRPG/Levels, nor would it allow downloads from C:/Windows +/// While pathToApplication can be anything you want, applicationSubdirectory must match either partially or fully between systems. +/// \ingroup DIRECTORY_DELTA_TRANSFER_GROUP +class RAK_DLL_EXPORT DirectoryDeltaTransfer : public PluginInterface +{ +public: + /// Constructor + DirectoryDeltaTransfer(); + + /// Destructor + ~DirectoryDeltaTransfer(); + + /// This plugin has a dependency on the FileListTransfer plugin, which it uses to actually send the files. + /// So you need an instance of that plugin registered with RakPeerInterface, and a pointer to that interface should be passed here. + /// \param[in] flt A pointer to a registered instance of FileListTransfer + void SetFileListTransferPlugin(FileListTransfer *flt); + + /// Set the local root directory to base all file uploads and downloads off of. + /// \param[in] pathToApplication This path will be prepended to \a applicationSubdirectory in AddUploadsFromSubdirectory to find the actual path on disk. + void SetApplicationDirectory(const char *pathToApplication); + + /// What parameters to use for the RakPeerInterface::Send() call when uploading files. + /// \param[in] _priority See RakPeerInterface::Send() + /// \param[in] _orderingChannel See RakPeerInterface::Send() + void SetUploadSendParameters(PacketPriority _priority, char _orderingChannel); + + /// Add all files in the specified subdirectory recursively + /// \a subdir is appended to \a pathToApplication in SetApplicationDirectory(). + /// All files in the resultant directory and subdirectories are then hashed so that users can download them. + /// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin + /// \param[in] subdir Concatenated with pathToApplication to form the final path from which to allow uploads. + void AddUploadsFromSubdirectory(const char *subdir); + + /// Downloads files from the matching parameter \a subdir in AddUploadsFromSubdirectory. + /// \a subdir must contain all starting characters in \a subdir in AddUploadsFromSubdirectory + /// Therefore, + /// AddUploadsFromSubdirectory("Levels/Level1/"); would allow you to download using DownloadFromSubdirectory("Levels/Level1/Textures/"... + /// but it would NOT allow you to download from DownloadFromSubdirectory("Levels/"... or DownloadFromSubdirectory("Levels/Level2/"... + /// \pre You must call SetFileListTransferPlugin with a valid FileListTransfer plugin + /// \param[in] subdir A directory passed to AddUploadsFromSubdirectory on the remote system. The passed dir can be more specific than the remote dir. + /// \param[in] outputSubdir The directory to write the output to. Usually this will match \a subdir but it can be different if you want. + /// \param[in] prependAppDirToOutputSubdir True to prepend outputSubdir with pathToApplication when determining the final output path. Usually you want this to be true. + /// \param[in] host The address of the remote system to send the message to. + /// \param[in] onFileCallback Callback to call per-file (optional). When fileIndex+1==setCount in the callback then the download is done + /// \param[in] _priority See RakPeerInterface::Send() + /// \param[in] _orderingChannel See RakPeerInterface::Send() + /// \return A set ID, identifying this download set. Returns 65535 on host unreachable. + unsigned short DownloadFromSubdirectory(const char *subdir, const char *outputSubdir, bool prependAppDirToOutputSubdir, PlayerID host, FileListTransferCBInterface *onFileCallback, PacketPriority _priority, char _orderingChannel); + + /// Clear all allowed uploads previously set with AddUploadsFromSubdirectory + void ClearUploads(void); + + /// Returns how many files are available for upload + /// \return How many files are available for upload + unsigned GetNumberOfFilesForUpload(void) const; + + /// \internal For plugin handling + virtual void OnAttach(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void Update(RakPeerInterface *peer); + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + /// \internal For plugin handling + virtual void OnDisconnect(RakPeerInterface *peer); +protected: + void OnDownloadRequest(RakPeerInterface *peer, Packet *packet); + + char applicationDirectory[512]; + FileListTransfer *fileListTransfer; + FileList *availableUploads; + RakPeerInterface *rakPeer; + PacketPriority priority; + char orderingChannel; +}; + +#endif diff --git a/raknet/EmailSender.cpp b/raknet/EmailSender.cpp index 9c67707..b3c836e 100644 --- a/raknet/EmailSender.cpp +++ b/raknet/EmailSender.cpp @@ -1,13 +1,250 @@ -// TODO: Implement EmailSender.cpp +// Useful sites +// http://www.faqs.org\rfcs\rfc2821.html +// http://en.wikipedia.org/wiki/Base64 +// http://www2.rad.com\networks/1995/mime/examples.htm #include "EmailSender.h" #include "TCPInterface.h" #include "GetTime.h" +#include "Rand.h" +#include "FileList.h" #include -#include // strstr + +#if defined(_COMPATIBILITY_1) +#include "Compatibility1Includes.h" +#endif #include "RakSleep.h" +char *EmailSender::Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf) +{ + Packet *packet; + char query[1024]; + char *response; + TCPInterface tcpInterface; + PlayerID emailServer; + if (tcpInterface.Start(0, 0)==false) + return "Unknown error starting TCP"; + emailServer=tcpInterface.Connect(hostAddress, hostPort); + if (emailServer==UNASSIGNED_PLAYER_ID) + return "Failed to connect to host"; + RakNetTime timeoutTime = RakNet::GetTime()+3000; + packet=0; + while (RakNet::GetTime() < timeoutTime) + { + packet = tcpInterface.Receive(); + if (packet) + { + if (doPrintf) + printf("%s", packet->data); + break; + } + RakSleep(250); + } + + if (packet==0) + return "Timeout while waiting for initial data from server."; + + tcpInterface.Send("HELO\r\n", 6, emailServer); + + response=GetResponse(&tcpInterface, emailServer, doPrintf); + if (response!=0) + return response; + + if (sender) + sprintf(query, "MAIL From: <%s>\r\n", sender); + else + sprintf(query, "MAIL From: <>\r\n"); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + response=GetResponse(&tcpInterface, emailServer, doPrintf); + if (response!=0) + return response; + + if (recipient) + sprintf(query, "RCPT TO: <%s>\r\n", recipient); + else + sprintf(query, "RCPT TO: <>\r\n"); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + response=GetResponse(&tcpInterface, emailServer, doPrintf); + if (response!=0) + return response; + + tcpInterface.Send("DATA\r\n", (unsigned int)strlen("DATA\r\n"), emailServer); + + response=GetResponse(&tcpInterface, emailServer, doPrintf); + if (response!=0) + return response; + + if (subject) + { + sprintf(query, "Subject: %s\r\n", subject); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + } + if (senderName) + { + sprintf(query, "From: %s\r\n", senderName); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + } + if (recipientName) + { + sprintf(query, "To: %s\r\n", recipientName); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + } + + const char base64Map[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const int boundarySize=60; + char boundary[boundarySize+1]; + int i,j; + if (attachedFiles && attachedFiles->fileList.Size()) + { + seedMT(RakNet::GetTime()); + // Random multipart message boundary + for (i=0; i < boundarySize; i++) + boundary[i]=base64Map[randomMT()%64]; + boundary[boundarySize]=0; + } + + sprintf(query, "MIME-version: 1.0\r\n"); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + + if (attachedFiles && attachedFiles->fileList.Size()) + { + sprintf(query, "Content-type: multipart/mixed; BOUNDARY=\"%s\"\r\n\r\n", boundary); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + + sprintf(query, "This is a multi-part message in MIME format.\r\n\r\n--%s\r\n", boundary); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + } + + sprintf(query, "Content-Type: text/plain; charset=\"US-ASCII\"\r\n\r\n"); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + + // Write the body of the email, doing some lame shitty shit where I have to make periods at the start of a newline have a second period. + char *newBody; + int bodyLength; + bodyLength=(int)strlen(body); + newBody = new char [bodyLength*3]; + if (bodyLength>0) + newBody[0]=body[0]; + for (i=1, j=1; i < bodyLength; i++) + { + // Transform \n . \r \n into \n . . \r \n + if (i < bodyLength-2 && + body[i-1]=='\n' && + body[i+0]=='.' && + body[i+1]=='\r' && + body[i+2]=='\n') + { + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='\r'; + newBody[j++]='\n'; + i+=2; + } + // Transform \n . . \r \n into \n . . . \r \n + // Having to process .. is a bug in the mail server - the spec says ONLY \r\n.\r\n should be transformed + else if (i <= bodyLength-3 && + body[i-1]=='\n' && + body[i+0]=='.' && + body[i+1]=='.' && + body[i+2]=='\r' && + body[i+3]=='\n') + { + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='\r'; + newBody[j++]='\n'; + i+=3; + } + // Transform \n . \n into \n . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does) + else if (i < bodyLength-1 && + body[i-1]=='\n' && + body[i+0]=='.' && + body[i+1]=='\n') + { + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='\r'; + newBody[j++]='\n'; + i+=1; + } + // Transform \n . . \n into \n . . . \r \n (this is a bug in the mail server - the spec says do not count \n alone but it does) + // In fact having to process .. is a bug too - because the spec says ONLY \r\n.\r\n should be transformed + else if (i <= bodyLength-2 && + body[i-1]=='\n' && + body[i+0]=='.' && + body[i+1]=='.' && + body[i+2]=='\n') + { + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='.'; + newBody[j++]='\r'; + newBody[j++]='\n'; + i+=2; + } + else + newBody[j++]=body[i]; + } + + newBody[j++]='\r'; + newBody[j++]='\n'; + tcpInterface.Send(newBody, j, emailServer); + + delete [] newBody; + int outputOffset; + + // What a pain in the rear. I have to map the binary to printable characters using 6 bits per character. + if (attachedFiles && attachedFiles->fileList.Size()) + { + for (i=0; i < (int) attachedFiles->fileList.Size(); i++) + { + // Write boundary + sprintf(query, "\r\n--%s\r\n", boundary); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + + sprintf(query, "Content-Type: APPLICATION/Octet-Stream; SizeOnDisk=%i; name=\"%s\"\r\nContent-Transfer-Encoding: BASE64\r\nContent-Description: %s\r\n\r\n", attachedFiles->fileList[i].dataLength, attachedFiles->fileList[i].filename, attachedFiles->fileList[i].filename); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + + newBody = new char[(attachedFiles->fileList[i].dataLength*3)/2]; + + outputOffset=Base64Encoding(attachedFiles->fileList[i].data, attachedFiles->fileList[i].dataLength, newBody, base64Map); + + // Send the base64 mapped file. + tcpInterface.Send(newBody, outputOffset, emailServer); + delete [] newBody; + + } + + // Write last boundary + sprintf(query, "\r\n--%s--\r\n", boundary); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + } + + + sprintf(query, "\r\n.\r\n"); + tcpInterface.Send(query, (unsigned int)strlen(query), emailServer); + response=GetResponse(&tcpInterface, emailServer, doPrintf); + if (response!=0) + return response; + + tcpInterface.Send("QUIT\r\n", (unsigned int)strlen("QUIT\r\n"), emailServer); + + RakSleep(30); + if (doPrintf) + { + packet = tcpInterface.Receive(); + while (packet) + { + printf("%s", packet->data); + packet = tcpInterface.Receive(); + } + } + tcpInterface.Stop(); + return 0; // Success +} + char *EmailSender::GetResponse(TCPInterface *tcpInterface, const PlayerID &emailServer, bool doPrintf) { Packet *packet; @@ -36,3 +273,70 @@ char *EmailSender::GetResponse(TCPInterface *tcpInterface, const PlayerID &email RakSleep(100); } } + +int EmailSender::Base64Encoding(const char *inputData, int dataLength, char *outputData, const char *base64Map) +{ + int outputOffset, charCount; + int write3Count; + outputOffset=0; + charCount=0; + int j; + + write3Count=dataLength/3; + for (j=0; j < write3Count; j++) + { + // 6 leftmost bits from first byte, shifted to bits 7,8 are 0 + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // 4 low bits from the second byte and the two high bits from the third byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+1] << 2) | (inputData[j*3+2] >> 6)) & 63]; // Third 6 bits + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Last 6 bits from the third byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[inputData[j*3+2] & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + } + + if (dataLength % 3==1) + { + // One input byte remaining + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Pad with two equals + outputData[outputOffset++]='='; + outputData[outputOffset++]='='; + } + else if (dataLength % 3==2) + { + // Two input bytes remaining + + // 6 leftmost bits from first byte, shifted to bits 7,8 are 0 + outputData[outputOffset++]=base64Map[inputData[j*3+0] >> 2]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Remaining 2 bits from first byte, placed in position, and 4 high bits from the second byte, masked to ignore bits 7,8 + outputData[outputOffset++]=base64Map[((inputData[j*3+0] << 4) | (inputData[j*3+1] >> 4)) & 63]; + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // 4 low bits from the second byte, followed by 00 + outputData[outputOffset++]=base64Map[(inputData[j*3+1] << 2) & 63]; // Third 6 bits + if ((++charCount % 76)==0) {outputData[outputOffset++]='\r'; outputData[outputOffset++]='\n'; charCount=0;} + + // Pad with one equal + outputData[outputOffset++]='='; + //outputData[outputOffset++]='='; + } + + // Append \r\n + outputData[outputOffset++]='\r'; + outputData[outputOffset++]='\n'; + outputData[outputOffset]=0; + + return outputOffset; +} diff --git a/raknet/EmailSender.h b/raknet/EmailSender.h index 880f57f..f3020cf 100644 --- a/raknet/EmailSender.h +++ b/raknet/EmailSender.h @@ -1,14 +1,47 @@ -// TODO: Implement EmailSender.h +/// \file +/// \brief Rudimentary class to send email from code. Don't expect anything fancy. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __EMAIL_SENDER_H #define __EMAIL_SENDER_H +class FileList; class TCPInterface; #include "NetworkTypes.h" +/// \brief Rudimentary class to send email from code. class EmailSender { public: + /// Sends an email + /// \param[in] hostAddress The address of the email server. + /// \param[in] hostPort The port of the email server (usually 25) + /// \param[in] sender The email address you are sending from. + /// \param[in] recipient The email address you are sending to. + /// \param[in] senderName The email address you claim to be sending from + /// \param[in] recipientName The email address you claim to be sending to + /// \param[in] subject Email subject + /// \param[in] body Email body + /// \param[in] attachedFiles List of files to attach to the email. (Can be 0 to send none). + /// \param[in] doPrintf true to output SMTP info to console(for debugging?) + /// \return 0 on success, otherwise a string indicating the error message + char *Send(const char *hostAddress, unsigned short hostPort, const char *sender, const char *recipient, const char *senderName, const char *recipientName, const char *subject, const char *body, FileList *attachedFiles, bool doPrintf); + + // Returns how many bytes were written + int Base64Encoding(const char *inputData, int dataLength, char *outputData, const char *base64Map); protected: char *GetResponse(TCPInterface *tcpInterface, const PlayerID &emailServer, bool doPrintf); }; diff --git a/raknet/EncodeClassName.cpp b/raknet/EncodeClassName.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/raknet/EncodeClassName.cpp @@ -0,0 +1 @@ + diff --git a/raknet/ExtendedOverlappedPool.cpp b/raknet/ExtendedOverlappedPool.cpp new file mode 100644 index 0000000..dedeb75 --- /dev/null +++ b/raknet/ExtendedOverlappedPool.cpp @@ -0,0 +1,67 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS +#include "ExtendedOverlappedPool.h" + +ExtendedOverlappedPool ExtendedOverlappedPool::I; + +ExtendedOverlappedPool::ExtendedOverlappedPool() +{} + +ExtendedOverlappedPool::~ExtendedOverlappedPool() +{ + // The caller better have returned all the packets! + ExtendedOverlappedStruct * p; + poolMutex.Lock(); + + while ( pool.Size() ) + { + p = pool.Pop(); + delete p; + } + + poolMutex.Unlock(); +} + +ExtendedOverlappedStruct* ExtendedOverlappedPool::GetPointer( void ) +{ + ExtendedOverlappedStruct * p = 0; + poolMutex.Lock(); + + if ( pool.Size() ) + p = pool.Pop(); + + poolMutex.Unlock(); + + if ( p ) + return p; + + return new ExtendedOverlappedStruct; +} + +void ExtendedOverlappedPool::ReleasePointer( ExtendedOverlappedStruct *p ) +{ + poolMutex.Lock(); + pool.Push( p ); + poolMutex.Unlock(); +} + +#endif + +*/ diff --git a/raknet/ExtendedOverlappedPool.h b/raknet/ExtendedOverlappedPool.h new file mode 100644 index 0000000..7e0c8eb --- /dev/null +++ b/raknet/ExtendedOverlappedPool.h @@ -0,0 +1,50 @@ +/// \file +/// \brief \b [Depreciated] This was used for IO completion ports. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +// No longer used as I no longer support IO Completion ports +/* +#ifdef __USE_IO_COMPLETION_PORTS +#ifndef __EXTENDED_OVERLAPPED_POOL +#define __EXTENDED_OVERLAPPED_POOL +#include "SimpleMutex.h" +#include "ClientContextStruct.h" +#include "DS_Queue.h" + +/// Depreciated - for IO completion ports +class ExtendedOverlappedPool +{ + +public: + ExtendedOverlappedPool(); + ~ExtendedOverlappedPool(); + ExtendedOverlappedStruct* GetPointer( void ); + void ReleasePointer( ExtendedOverlappedStruct *p ); + static inline ExtendedOverlappedPool* Instance() + { + return & I; + } + +private: + DataStructures::Queue pool; + SimpleMutex poolMutex; + static ExtendedOverlappedPool I; +}; + +#endif +#endif + +*/ diff --git a/raknet/FileList.cpp b/raknet/FileList.cpp new file mode 100644 index 0000000..3e8639a --- /dev/null +++ b/raknet/FileList.cpp @@ -0,0 +1,592 @@ +#include "FileList.h" +#include +#ifndef _COMPATIBILITY_2 + #if defined(_WIN32) || defined(__CYGWIN__) + #include + #elif !defined ( __APPLE__ ) && !defined ( __APPLE_CC__ ) + #include + #endif +#endif +#include +#include "DS_Queue.h" +#ifdef _WIN32 +// For mkdir +#include +#else +#include +#include "LinuxStrings.h" +#endif +#include "SHA1.h" +#include "StringCompressor.h" +#include "BitStream.h" +#include "FileOperations.h" + +#if !defined(_WIN32) +#define _unlink unlink +#define _mkdir mkdir +#endif + +// alloca +#ifdef _COMPATIBILITY_1 +#elif defined(_WIN32) +#include +#elif defined(_COMPATIBILITY_2) +#include "Compatibility2Includes.h" +#else +#include +#include +#include +#include "LinuxStrings.h" +#include "_findfirst.h" +#include //defines intptr_t +#endif + +//int RAK_DLL_EXPORT FileListNodeComp( char * const &key, const FileListNode &data ) +//{ +// return strcmp(key, data.filename); +//} + +FileList::FileList() +{ +} +FileList::~FileList() +{ + Clear(); +} +void FileList::AddFile(const char *filepath, const char *filename, unsigned char context) +{ + if (filepath==0 || filename==0) + return; + + char *data; + FILE *fp = fopen(filepath, "rb"); + if (fp==0) + return; + fseek(fp, 0, SEEK_END); + int length = ftell(fp); + fseek(fp, 0, SEEK_SET); +#if !defined(_COMPATIBILITY_1) + bool usedAlloca=false; + if (length < MAX_ALLOCA_STACK_ALLOCATION) + { + data = ( char* ) alloca( length ); + usedAlloca=true; + } + else +#endif + { + data = new char [length]; + } + + AddFile(filename, data, length, length, context); + fclose(fp); + +#if !defined(_COMPATIBILITY_1) + if (usedAlloca==false) +#endif + delete [] data; + +} +void FileList::AddFile(const char *filename, const char *data, const unsigned dataLength, const unsigned fileLength, unsigned char context) +{ + if (filename==0) + return; + + // Avoid duplicate insertions unless the data is different, in which case overwrite the old data + unsigned i; + for (i=0; i dirList; + char root[260]; + char fullPath[520]; + _finddata_t fileInfo; + intptr_t dir; + int file; + FILE *fp; + CSHA1 sha1; + char *dirSoFar, *fileData; + dirSoFar=new char[520]; + + if (applicationDirectory) + strcpy(root, applicationDirectory); + else + root[0]=0; + + int rootLen=(int)strlen(root); + if (rootLen) + { + strcpy(dirSoFar, root); + if (dirSoFar[strlen(dirSoFar)-1]!='/' && dirSoFar[strlen(dirSoFar)-1]!='\\') + { + strcat(dirSoFar, "/"); + rootLen++; + } + } + else + dirSoFar[0]=0; + + if (subDirectory) + { + strcat(dirSoFar, subDirectory); + if (dirSoFar[strlen(dirSoFar)-1]!='/' && dirSoFar[strlen(dirSoFar)-1]!='\\') + { + strcat(dirSoFar, "/"); + } + } + dirList.Push(dirSoFar); + while (dirList.Size()) + { + dirSoFar=dirList.Pop(); + strcpy(fullPath, dirSoFar); + strcat(fullPath, "*.*"); + dir=_findfirst(fullPath, &fileInfo ); // Read . + if (dir==-1) + { + _findclose(dir); + delete [] dirSoFar; + unsigned i; + for (i=0; i < dirList.Size(); i++) + delete [] dirList[i]; + return; + } + file=_findnext(dir, &fileInfo ); // Read .. + file=_findnext(dir, &fileInfo ); // Skip .. + + while (file!=-1) + { + if ((fileInfo.attrib & (_A_HIDDEN | _A_SUBDIR | _A_SYSTEM))==0) + { + strcpy(fullPath, dirSoFar); + strcat(fullPath, fileInfo.name); + if (writeData && writeHash) + fileData= new char [fileInfo.size+SHA1_LENGTH]; + else + fileData= new char [fileInfo.size]; + fp = fopen(fullPath, "rb"); + if (writeData && writeHash) + fread(fileData+SHA1_LENGTH, fileInfo.size, 1, fp); + else + fread(fileData, fileInfo.size, 1, fp); + fclose(fp); + + if (writeData && writeHash) + { + sha1.Reset(); + sha1.Update( ( unsigned char* ) fileData+SHA1_LENGTH, fileInfo.size ); + sha1.Final(); + memcpy(fileData, sha1.GetHash(), SHA1_LENGTH); + AddFile((const char*)fullPath+rootLen, fileData, fileInfo.size+SHA1_LENGTH, fileInfo.size, context); + } + else if (writeHash) + { + sha1.Reset(); + sha1.Update( ( unsigned char* ) fileData, fileInfo.size ); + sha1.Final(); + AddFile((const char*)fullPath+rootLen, (const char*)sha1.GetHash(), SHA1_LENGTH, fileInfo.size, context); + } + else if (writeData) + { + AddFile(fullPath+rootLen, fileData, fileInfo.size, fileInfo.size, context); + } + else + AddFile(fullPath+rootLen, 0, 0, fileInfo.size, context); + + delete [] fileData; + } + else if ((fileInfo.attrib & _A_SUBDIR) && (fileInfo.attrib & (_A_HIDDEN | _A_SYSTEM))==0 && recursive) + { + char *newDir=new char[520]; + strcpy(newDir, dirSoFar); + strcat(newDir, fileInfo.name); + strcat(newDir, "/"); + dirList.Push(newDir); + } + file=_findnext(dir, &fileInfo ); + } + + _findclose(dir); + delete [] dirSoFar; + } +#endif +} +void FileList::Clear(void) +{ + unsigned i; + for (i=0; iWriteCompressed(fileList.Size()); + unsigned i; + for (i=0; i < fileList.Size(); i++) + { + outBitStream->WriteCompressed(fileList[i].context); + stringCompressor->EncodeString(fileList[i].filename, 512, outBitStream); + outBitStream->Write((bool)(fileList[i].dataLength>0==true)); + if (fileList[i].dataLength>0) + { + outBitStream->WriteCompressed(fileList[i].dataLength); + outBitStream->Write(fileList[i].data, fileList[i].dataLength); + } + + outBitStream->Write((bool)(fileList[i].fileLength==fileList[i].dataLength)); + if (fileList[i].fileLength!=fileList[i].dataLength) + outBitStream->WriteCompressed(fileList[i].fileLength); + } +} +bool FileList::Deserialize(RakNet::BitStream *inBitStream) +{ + bool b, dataLenNonZero, fileLenMatchesDataLen; + char filename[512]; + unsigned int fileListSize; + FileListNode n; + b=inBitStream->ReadCompressed(fileListSize); +#ifdef _DEBUG + assert(b); + assert(fileListSize < 10000); +#endif + if (b==false || fileListSize > 10000) + return false; // Sanity check + Clear(); + unsigned i; + for (i=0; i < fileListSize; i++) + { + inBitStream->ReadCompressed(n.context); + stringCompressor->DecodeString((char*)filename, 512, inBitStream); + inBitStream->Read(dataLenNonZero); + if (dataLenNonZero) + { + inBitStream->ReadCompressed(n.dataLength); + // sanity check + if (n.dataLength>200000000) + { +#ifdef _DEBUG + assert(n.dataLength<=200000000); +#endif + return false; + } + n.data=new char [n.dataLength]; + inBitStream->Read(n.data, n.dataLength); + } + else + { + n.dataLength=0; + n.data=0; + } + + b=inBitStream->Read(fileLenMatchesDataLen); + if (fileLenMatchesDataLen) + n.fileLength=n.dataLength; + else + b=inBitStream->ReadCompressed(n.fileLength); +#ifdef _DEBUG + assert(b); +#endif + if (b==0) + { + Clear(); + return false; + } + n.filename=new char [strlen(filename)+1]; + strcpy(n.filename, filename); + fileList.Insert(n); + } + + return true; +} +void FileList::GetDeltaToCurrent(FileList *input, FileList *output, const char *dirSubset, const char *remoteSubdir) +{ + // For all files in this list that do not match the input list, write them to the output list. + // dirSubset allows checking only a portion of the files in this list. + unsigned thisIndex, inputIndex; + unsigned dirSubsetLen, localPathLen, remoteSubdirLen; + bool match; + if (dirSubset) + dirSubsetLen = (unsigned int) strlen(dirSubset); + else + dirSubsetLen = 0; + if (remoteSubdir && remoteSubdir[0]) + { + remoteSubdirLen=(unsigned int) strlen(remoteSubdir); + if (IsSlash(remoteSubdir[remoteSubdirLen-1])) + remoteSubdirLen--; + } + else + remoteSubdirLen=0; + + for (thisIndex=0; thisIndex < fileList.Size(); thisIndex++) + { + localPathLen = (unsigned int) strlen(fileList[thisIndex].filename); + while (localPathLen>0) + { + if (IsSlash(fileList[thisIndex].filename[localPathLen-1])) + { + localPathLen--; + break; + } + localPathLen--; + } + + // fileList[thisIndex].filename has to match dirSubset and be shorter or equal to it in length. + if (dirSubsetLen>0 && + (localPathLendirSubsetLen && IsSlash(fileList[thisIndex].filename[dirSubsetLen])==false))) + continue; + + match=false; + for (inputIndex=0; inputIndex < input->fileList.Size(); inputIndex++) + { + // If the filenames, hashes, and lengths match then skip this element in fileList. Otherwise write it to output + if (_stricmp(input->fileList[inputIndex].filename+remoteSubdirLen,fileList[thisIndex].filename+dirSubsetLen)==0) + { + match=true; + if (input->fileList[inputIndex].fileLength==fileList[thisIndex].fileLength && + input->fileList[inputIndex].dataLength==fileList[thisIndex].dataLength && + memcmp(input->fileList[inputIndex].data,fileList[thisIndex].data,fileList[thisIndex].dataLength)==0) + { + // File exists on both machines and is the same. + break; + } + else + { + // File exists on both machines and is not the same. + output->AddFile(fileList[inputIndex].filename, 0,0, fileList[inputIndex].fileLength, 0); + break; + } + } + } + if (match==false) + { + // Other system does not have the file at all + output->AddFile(fileList[thisIndex].filename, 0,0, fileList[thisIndex].fileLength, 0); + } + } +} +void FileList::ListMissingOrChangedFiles(const char *applicationDirectory, FileList *missingOrChangedFiles, bool alwaysWriteHash, bool neverWriteHash) +{ + unsigned fileLength; + CSHA1 sha1; + FILE *fp; + char fullPath[512]; + unsigned i; + char *fileData; + + for (i=0; i < fileList.Size(); i++) + { + strcpy(fullPath, applicationDirectory); + if (fullPath[strlen(fullPath)-1]!='/' && fullPath[strlen(fullPath)-1]!='\\') + strcat(fullPath, "/"); + strcat(fullPath,fileList[i].filename); + fp=fopen(fullPath, "rb"); + if (fp==0) + { + missingOrChangedFiles->AddFile(fileList[i].filename, 0, 0, 0, 0); + } + else + { + fseek(fp, 0, SEEK_END); + fileLength = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (fileLength != fileList[i].fileLength && alwaysWriteHash==false) + { + missingOrChangedFiles->AddFile(fileList[i].filename, 0, 0, fileLength, 0); + } + else + { + + fileData= new char [fileLength]; + fread(fileData, fileLength, 1, fp); + fclose(fp); + + sha1.Reset(); + sha1.Update( ( unsigned char* ) fileData, fileLength ); + sha1.Final(); + + delete [] fileData; + + if (fileLength != fileList[i].fileLength || memcmp( sha1.GetHash(), fileList[i].data, 20)!=0) + { + if (neverWriteHash==false) + missingOrChangedFiles->AddFile((const char*)fileList[i].filename, (const char*)sha1.GetHash(), 20, fileLength, 0); + else + missingOrChangedFiles->AddFile((const char*)fileList[i].filename, 0, 0, fileLength, 0); + } + } + } + } +} +void FileList::PopulateDataFromDisk(const char *applicationDirectory, bool writeFileData, bool writeFileHash, bool removeUnknownFiles) +{ + FILE *fp; + char fullPath[512]; + unsigned i; + CSHA1 sha1; + + i=0; + while (i < fileList.Size()) + { + delete [] fileList[i].data; + strcpy(fullPath, applicationDirectory); + if (fullPath[strlen(fullPath)-1]!='/' && fullPath[strlen(fullPath)-1]!='\\') + strcat(fullPath, "/"); + strcat(fullPath,fileList[i].filename); + fp=fopen(fullPath, "rb"); + if (fp) + { + if (writeFileHash || writeFileData) + { + fseek(fp, 0, SEEK_END); + fileList[i].fileLength = ftell(fp); + fseek(fp, 0, SEEK_SET); + if (writeFileHash) + { + if (writeFileData) + { + // Hash + data so offset the data by SHA1_LENGTH + fileList[i].data=new char[fileList[i].fileLength+SHA1_LENGTH]; + fread(fileList[i].data+SHA1_LENGTH, fileList[i].fileLength, 1, fp); + sha1.Reset(); + sha1.Update((unsigned char*)fileList[i].data+SHA1_LENGTH, fileList[i].fileLength); + sha1.Final(); + memcpy(fileList[i].data, sha1.GetHash(), SHA1_LENGTH); + } + else + { + // Hash only + fileList[i].dataLength=SHA1_LENGTH; + if (fileList[i].fileLength < SHA1_LENGTH) + fileList[i].data=new char[SHA1_LENGTH]; + else + fileList[i].data=new char[fileList[i].fileLength]; + fread(fileList[i].data, fileList[i].fileLength, 1, fp); + sha1.Reset(); + sha1.Update((unsigned char*)fileList[i].data, fileList[i].fileLength); + sha1.Final(); + memcpy(fileList[i].data, sha1.GetHash(), SHA1_LENGTH); + } + } + else + { + // Data only + fileList[i].dataLength=fileList[i].fileLength; + fileList[i].data=new char[fileList[i].fileLength]; + fread(fileList[i].data, fileList[i].fileLength, 1, fp); + } + + fclose(fp); + i++; + } + else + { + fileList[i].data=0; + fileList[i].dataLength=0; + } + } + else + { + if (removeUnknownFiles) + { + delete [] fileList[i].filename; + fileList.RemoveAtIndex(i); + } + else + i++; + } + } +} +void FileList::WriteDataToDisk(const char *applicationDirectory) +{ + char fullPath[512]; + unsigned i,j; + + for (i=0; i < fileList.Size(); i++) + { + strcpy(fullPath, applicationDirectory); + if (fullPath[strlen(fullPath)-1]!='/' && fullPath[strlen(fullPath)-1]!='\\') + strcat(fullPath, "/"); + strcat(fullPath,fileList[i].filename); + + // Security - Don't allow .. in the filename anywhere so you can't write outside of the root directory + for (j=1; j < strlen(fileList[i].filename); j++) + { + if (fileList[i].filename[j]=='.' && fileList[i].filename[j-1]=='.') + { +#ifdef _DEBUG + assert(0); +#endif + // Just cancel the write entirely + return; + } + } + + WriteFileWithDirectories(fullPath, fileList[i].data, fileList[i].dataLength); + } +} + + +void FileList::DeleteFiles(const char *applicationDirectory) +{ + char fullPath[512]; + unsigned i,j; + + for (i=0; i < fileList.Size(); i++) + { + // The filename should not have .. in the path - if it does ignore it + for (j=1; j < strlen(fileList[i].filename); j++) + { + if (fileList[i].filename[j]=='.' && fileList[i].filename[j-1]=='.') + { +#ifdef _DEBUG + assert(0); +#endif + // Just cancel the deletion entirely + return; + } + } + + strcpy(fullPath, applicationDirectory); + strcat(fullPath, fileList[i].filename); + _unlink(fullPath); + } +} diff --git a/raknet/FileList.h b/raknet/FileList.h new file mode 100644 index 0000000..e7188b0 --- /dev/null +++ b/raknet/FileList.h @@ -0,0 +1,51 @@ +#ifndef __FILE_LIST +#define __FILE_LIST + +#include "Export.h" +#include "DS_List.h" + +namespace RakNet +{ + class BitStream; +} + +struct FileListNode +{ + char *filename; + char *data; + unsigned dataLength; + unsigned fileLength; + unsigned char context; // User specific data for whatever, describing this file. +}; + +//int RAK_DLL_EXPORT FileListNodeComp( char * const &key, const FileListNode &data ); + +class RakPeerInterface; + +class RAK_DLL_EXPORT FileList +{ +public: + FileList(); + ~FileList(); + void AddFilesFromDirectory(const char *applicationDirectory, const char *subDirectory, bool writeHash, bool writeData, bool recursive, unsigned char context); + void Clear(void); + void Serialize(RakNet::BitStream *outBitStream); + bool Deserialize(RakNet::BitStream *inBitStream); + void ListMissingOrChangedFiles(const char *applicationDirectory, FileList *missingOrChangedFiles, bool alwaysWriteHash, bool neverWriteHash); + // Return the files that need to be written to make \a input match this FileList. + // Specify dirSubset to only consider files in fileList that start with this path + // specify remoteSubdir to assume that all filenames in input start with this path, so strip it off when comparing filenames. + void GetDeltaToCurrent(FileList *input, FileList *output, const char *dirSubset, const char *remoteSubdir); + void PopulateDataFromDisk(const char *applicationDirectory, bool writeFileData, bool writeFileHash, bool removeUnknownFiles); + void WriteDataToDisk(const char *applicationDirectory); + void AddFile(const char *filename, const char *data, const unsigned dataLength, const unsigned fileLength, unsigned char context); + void AddFile(const char *filepath, const char *filename, unsigned char context); + void DeleteFiles(const char *applicationDirectory); + + + // Here so you can read it, but don't modify it + DataStructures::List fileList; +protected: +}; + +#endif diff --git a/raknet/FileListTransfer.cpp b/raknet/FileListTransfer.cpp new file mode 100644 index 0000000..96cecb2 --- /dev/null +++ b/raknet/FileListTransfer.cpp @@ -0,0 +1,408 @@ +#include "FileListTransfer.h" +#include "DS_HuffmanEncodingTree.h" +#include "FileListTransferCBInterface.h" +#include "StringCompressor.h" +#include "FileList.h" +#include "DS_Queue.h" +#include "PacketEnumerations.h" +#include "NetworkTypes.h" +#include "RakPeerInterface.h" +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +struct FileListTransfer::FileListReceiver +{ + FileListTransferCBInterface *downloadHandler; + PlayerID allowedSender; + HuffmanEncodingTree tree; + unsigned short setID; + unsigned setCount; + unsigned setTotalCompressedTransmissionLength; + unsigned setTotalFinalLength; + bool gotSetHeader; + bool deleteDownloadHandler; + bool isCompressed; +}; + +FileListTransfer::FileListTransfer() +{ + rakPeer=0; + setId=0; + DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON(); +} +FileListTransfer::~FileListTransfer() +{ + Clear(); +} +unsigned short FileListTransfer::SetupReceive(FileListTransferCBInterface *handler, bool deleteHandler, PlayerID allowedSender) +{ + if (rakPeer->GetIndexFromPlayerID(allowedSender)==-1) + return (unsigned short)-1; + FileListReceiver *receiver; + + if (fileListReceivers.Has(setId)) + { + receiver=fileListReceivers.Get(setId); + if (receiver->deleteDownloadHandler) + delete receiver->downloadHandler; + delete receiver; + fileListReceivers.Delete(setId); + } + + unsigned short oldId; + receiver = new FileListReceiver; + receiver->downloadHandler=handler; + receiver->allowedSender=allowedSender; + receiver->gotSetHeader=false; + receiver->deleteDownloadHandler=deleteHandler; + fileListReceivers.Set(setId, receiver); + oldId=setId; + if (++setId==(unsigned short)-1) + setId=0; + return oldId; +} + +void FileListTransfer::Send(FileList *fileList, RakPeerInterface *rakPeer, PlayerID recipient, unsigned short setID, PacketPriority priority, char orderingChannel, bool compressData) +{ + RakNet::BitStream outBitstream, encodedData; + HuffmanEncodingTree tree; + unsigned int frequencyTable[ 256 ]; + unsigned int i,j; + unsigned totalCompressedLength, totalLength; + DataStructures::Queue compressedFiles; + FileListNode n; + + if (compressData) + { + memset(frequencyTable,0,256*sizeof(unsigned int)); + + for (i=0; i < fileList->fileList.Size(); i++) + { + for (j=0; j < fileList->fileList[i].dataLength; j++) + { + ++frequencyTable[(unsigned char)(fileList->fileList[i].data[j])]; + } + } + + tree.GenerateFromFrequencyTable(frequencyTable); + + // Compress all the files, so we know the total compressed size to be sent + totalCompressedLength=totalLength=0; + for (i=0; i < fileList->fileList.Size(); i++) + { + encodedData.Reset(); + // Why send compressed chunks if we are not sending the whole file? + assert(fileList->fileList[i].fileLength==fileList->fileList[i].fileLength); + tree.EncodeArray((unsigned char*)fileList->fileList[i].data, fileList->fileList[i].dataLength, &encodedData); + n.dataLength=encodedData.GetNumberOfBitsUsed(); + totalCompressedLength+=BITS_TO_BYTES(n.dataLength); + totalLength+=fileList->fileList[i].fileLength; + n.data = new char[BITS_TO_BYTES(n.dataLength)]; + memcpy(n.data, encodedData.GetData(), BITS_TO_BYTES(n.dataLength)); + compressedFiles.Push(n); + } + } + + // Write the chunk header, which contains the frequency table, the total number of files, and the total number of bytes + bool anythingToWrite; + outBitstream.Write((unsigned char)ID_FILE_LIST_TRANSFER_HEADER); + outBitstream.Write(setID); + anythingToWrite=fileList->fileList.Size()>0; + outBitstream.Write(anythingToWrite); + if (anythingToWrite) + { + if (compressData) + { + outBitstream.Write(true); + for (i=0; i < 256; i++) + outBitstream.WriteCompressed(frequencyTable[i]); + outBitstream.WriteCompressed(fileList->fileList.Size()); + outBitstream.WriteCompressed(totalLength); + outBitstream.WriteCompressed(totalCompressedLength); + } + else + { + outBitstream.Write(false); + outBitstream.WriteCompressed(fileList->fileList.Size()); + outBitstream.WriteCompressed(totalLength); + } + + rakPeer->Send(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + + // Send each possibly compressed file + for (i=0; i < compressedFiles.Size(); i++) + { + outBitstream.Reset(); + outBitstream.Write((unsigned char)ID_FILE_LIST_TRANSFER_FILE); + outBitstream.Write(fileList->fileList[i].context); + outBitstream.Write(setID); + outBitstream.WriteCompressed(i); + outBitstream.WriteCompressed(fileList->fileList[i].dataLength); // Original length + if (compressData) + outBitstream.WriteCompressed(compressedFiles[i].dataLength); // Compressed bitlength } + stringCompressor->EncodeString(fileList->fileList[i].filename, 512, &outBitstream); + if (compressData) + { + outBitstream.WriteBits((const unsigned char*)compressedFiles[i].data, compressedFiles[i].dataLength); + delete [] compressedFiles[i].data; + } + else + outBitstream.WriteBits((const unsigned char*)fileList->fileList[i].data, fileList->fileList[i].dataLength); + + rakPeer->Send(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); + } + } + else + rakPeer->Send(&outBitstream, priority, RELIABLE_ORDERED, orderingChannel, recipient, false); +} + +bool FileListTransfer::DecodeSetHeader(Packet *packet) +{ + unsigned i; + unsigned int frequencyTable[ 256 ]; + bool anythingToWrite; + unsigned short setID; + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); + inBitStream.Read(setID); + FileListReceiver *fileListReceiver; + if (fileListReceivers.Has(setID)==false) + { +#ifdef _DEBUG + assert(0); +#endif + return false; + } + fileListReceiver=fileListReceivers.Get(setID); + if (fileListReceiver->allowedSender!=packet->playerId) + { +#ifdef _DEBUG + assert(0); +#endif + return false; + } + +#ifdef _DEBUG + assert(fileListReceiver->gotSetHeader==false); +#endif + + inBitStream.Read(anythingToWrite); + if (anythingToWrite) + { + inBitStream.Read(fileListReceiver->isCompressed); + if (fileListReceiver->isCompressed) + { + for (i=0; i < 256; i++) + inBitStream.ReadCompressed(frequencyTable[i]); + fileListReceiver->tree.GenerateFromFrequencyTable(frequencyTable); + inBitStream.ReadCompressed(fileListReceiver->setCount); + inBitStream.ReadCompressed(fileListReceiver->setTotalFinalLength); + if (inBitStream.ReadCompressed(fileListReceiver->setTotalCompressedTransmissionLength)) + { + fileListReceiver->gotSetHeader=true; + return true; + } + } + else + { + inBitStream.ReadCompressed(fileListReceiver->setCount); + inBitStream.ReadCompressed(fileListReceiver->setTotalFinalLength); + fileListReceiver->setTotalCompressedTransmissionLength=fileListReceiver->setTotalFinalLength; + } + } + else + { + fileListReceiver->downloadHandler->OnFile(0, 0, 0, 0, 0, setID, 0, 0, 0, 0); + return true; + } + + return false; +} + +bool FileListTransfer::DecodeFile(Packet *packet, bool fullFile) +{ + unsigned char *decompressedFileData; + char fileName[512]; + unsigned char context; + unsigned fileIndex, fileLength, compressedLength; + unsigned bitLength; + unsigned len; + unsigned short setID; + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); + + unsigned int partCount; + unsigned int partTotal; + unsigned int partLength; + if (fullFile==false) + { + inBitStream.Read(partCount); + inBitStream.Read(partTotal); + inBitStream.Read(partLength); + inBitStream.IgnoreBits(8); + } + inBitStream.Read(context); + inBitStream.Read(setID); + FileListReceiver *fileListReceiver; + if (fileListReceivers.Has(setID)==false) + { +#ifdef _DEBUG + assert(0); +#endif + return false; + } + fileListReceiver=fileListReceivers.Get(setID); + if (fileListReceiver->allowedSender!=packet->playerId) + { +#ifdef _DEBUG + assert(0); +#endif + return false; + } + +#ifdef _DEBUG + assert(fileListReceiver->gotSetHeader==true); +#endif + + inBitStream.ReadCompressed(fileIndex); + inBitStream.ReadCompressed(fileLength); + if (fileListReceiver->isCompressed) + { + inBitStream.ReadCompressed(bitLength); + compressedLength=BITS_TO_BYTES(bitLength); + } + + if (stringCompressor->DecodeString(fileName, 512, &inBitStream)==false) + { +#ifdef _DEBUG + assert(0); +#endif + return false; + } + + if (fullFile) + { + decompressedFileData = new unsigned char [fileLength]; + + if (fileListReceiver->isCompressed) + { + len=fileListReceiver->tree.DecodeArray(&inBitStream, bitLength, fileLength, decompressedFileData); + if (len!=fileLength) + { +#ifdef _DEBUG + assert(0); +#endif + delete [] decompressedFileData; + return false; + } + } + else + inBitStream.Read((char*)decompressedFileData, fileLength); + } + + + // User callback for this file. + if (fullFile) + { + fileListReceiver->downloadHandler->OnFile(fileIndex, fileName, (char*)decompressedFileData, compressedLength, fileLength, setID, fileListReceiver->setCount, fileListReceiver->setTotalCompressedTransmissionLength, fileListReceiver->setTotalFinalLength, context); + delete [] decompressedFileData; + + // If this set is done, free the memory for it. + if (fileListReceiver->setCount==fileIndex+1) + { + if (fileListReceiver->deleteDownloadHandler) + delete fileListReceiver->downloadHandler; + delete fileListReceiver; + fileListReceivers.Delete(setID); + } + + } + else + fileListReceiver->downloadHandler->OnFileProgress(fileIndex, fileName, compressedLength, fileLength, setID, fileListReceiver->setCount, fileListReceiver->setTotalCompressedTransmissionLength, fileListReceiver->setTotalFinalLength, context, partCount, partTotal, partLength); + + return true; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +PluginReceiveResult FileListTransfer::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + switch (packet->data[0]) + { + case ID_CONNECTION_LOST: + case ID_DISCONNECTION_NOTIFICATION: + RemoveReceiver(packet->playerId); + break; + case ID_FILE_LIST_TRANSFER_HEADER: + DecodeSetHeader(packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_FILE_LIST_TRANSFER_FILE: + DecodeFile(packet, true); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DOWNLOAD_PROGRESS: + if (packet->length>sizeof(MessageID)+sizeof(unsigned int)*3 && + packet->data[sizeof(MessageID)+sizeof(unsigned int)*3]==ID_FILE_LIST_TRANSFER_FILE) + { + DecodeFile(packet, false); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + break; + } + + return RR_CONTINUE_PROCESSING; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void FileListTransfer::OnDisconnect(RakPeerInterface *peer) +{ + Clear(); +} +void FileListTransfer::Clear(void) +{ + unsigned i; + for (i=0; i < fileListReceivers.Size(); i++) + { + if (fileListReceivers[i]->deleteDownloadHandler) + delete fileListReceivers[i]->downloadHandler; + delete fileListReceivers[i]; + } + fileListReceivers.Clear(); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void FileListTransfer::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + RemoveReceiver(playerId); +} + +void FileListTransfer::RemoveReceiver(PlayerID playerId) +{ + unsigned i; + i=0; + while (i < fileListReceivers.Size()) + { + if (fileListReceivers[i]->allowedSender==playerId) + { + if (fileListReceivers[i]->deleteDownloadHandler) + delete fileListReceivers[i]->downloadHandler; + delete fileListReceivers[i]; + fileListReceivers.RemoveAtIndex(i); + } + else + i++; + } +} +void FileListTransfer::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; + +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/FileListTransfer.h b/raknet/FileListTransfer.h new file mode 100644 index 0000000..f945bb2 --- /dev/null +++ b/raknet/FileListTransfer.h @@ -0,0 +1,89 @@ +/// \file +/// \brief A plugin to provide a simple way to compress and incrementally send the files in the FileList structure. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __FILE_LIST_TRANFER_H +#define __FILE_LIST_TRANFER_H + +#include "NetworkTypes.h" +#include "Export.h" +#include "PluginInterface.h" +#include "DS_Map.h" +#include "NetworkTypes.h" +#include "PacketPriority.h" + +class FileListTransferCBInterface; +class FileList; + +/// \defgroup FILE_LIST_TRANSFER_GROUP FileListTransfer +/// \ingroup PLUGINS_GROUP + +/// \brief A plugin to provide a simple way to compress and incrementally send the files in the FileList structure. +/// Similar to the DirectoryDeltaTransfer plugin, except that it doesn't send deltas based on pre-existing files or actually write the files to disk. +/// +/// Usage: +/// Call SetupReceive to allow one file set to arrive. The value returned by FileListTransfer::SetupReceive() +/// is the setID that is allowed. +/// It's up to you to transmit this value to the other system, along with information indicating what kind of files you want to get. +/// The other system should then prepare a FileList and call send, passing the return value of FileListTransfer::SetupReceive() +/// as the \a setID parameter to FileListTransfer::Send() +/// \ingroup FILE_LIST_TRANSFER_GROUP +class RAK_DLL_EXPORT FileListTransfer : public PluginInterface +{ +public: + FileListTransfer(); + ~FileListTransfer(); + + /// Allows one corresponding Send() call from another system to arrive. + /// \param[in] handler The class to call on each file + /// \param[in] deleteHandler True to delete the handler when it is no longer needed. False to not do so. + /// \param[in] allowedSender Which system to allow files from + /// \return A set ID value, which should be passed as the \a setID value to the Send() call on the other system. This value will be returned in the callback and is unique per file set. Returns 65535 on failure (not connected to sender) + unsigned short SetupReceive(FileListTransferCBInterface *handler, bool deleteHandler, PlayerID allowedSender); + + /// Send the FileList structure to another system, which must have previously called SetupReceive() + /// \param[in] fileList A list of files. The data contained in FileList::data will be sent incrementally and compressed among all files in the set + /// \param[in] rakPeer The instance of RakNet to use to send the message + /// \param[in] recipient The address of the system to send to + /// \param[in] setID The return value of SetupReceive() which was previously called on \a recipient + /// \param[in] priority Passed to RakPeerInterface::Send() + /// \param[in] orderingChannel Passed to RakPeerInterface::Send() + /// \param[in] compressData Use a poor but fast compression algorithm. This makes your data larger if it is already compressed or if the amount of data to send is small so don't use it blindly. + void Send(FileList *fileList, RakPeerInterface *rakPeer, PlayerID recipient, unsigned short setID, PacketPriority priority, char orderingChannel, bool compressData); + + void RemoveReceiver(PlayerID playerId); + + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + /// \internal For plugin handling + virtual void OnDisconnect(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + /// \internal For plugin handling + virtual void OnAttach(RakPeerInterface *peer); +protected: + bool DecodeSetHeader(Packet *packet); + bool DecodeFile(Packet *packet, bool fullFile); + + void Clear(void); + + struct FileListReceiver; + DataStructures::Map fileListReceivers; + unsigned short setId; + RakPeerInterface *rakPeer; +}; + +#endif diff --git a/raknet/FileListTransferCBInterface.h b/raknet/FileListTransferCBInterface.h new file mode 100644 index 0000000..ace3004 --- /dev/null +++ b/raknet/FileListTransferCBInterface.h @@ -0,0 +1,38 @@ +#ifndef __FILE_LIST_TRANSFER_CALLBACK_INTERFACE_H +#define __FILE_LIST_TRANSFER_CALLBACK_INTERFACE_H + +/// \brief Used by FileListTransfer plugin as a callback for when we get a file. +/// \sa FileListTransfer +class FileListTransferCBInterface +{ +public: + // Got a file + virtual void OnFile( + unsigned fileIndex, + char *filename, + char *fileData, + unsigned compressedTransmissionLength, + unsigned finalDataLength, + unsigned short setID, + unsigned setCount, + unsigned setTotalCompressedTransmissionLength, + unsigned setTotalFinalLength, + unsigned char context)=0; + + // Got part of a big file. Activate with RakPeer::SetSplitMessageProgressInterval + virtual void OnFileProgress(unsigned fileIndex, + char *filename, + unsigned compressedTransmissionLength, + unsigned finalDataLength, + unsigned short setID, + unsigned setCount, + unsigned setTotalCompressedTransmissionLength, + unsigned setTotalFinalLength, + unsigned char context, + unsigned int partCount, + unsigned int partTotal, + unsigned int partLength) {} +}; + +#endif + diff --git a/raknet/FileOperations.cpp b/raknet/FileOperations.cpp new file mode 100644 index 0000000..56f92ef --- /dev/null +++ b/raknet/FileOperations.cpp @@ -0,0 +1,147 @@ +#include "_findfirst.h" // For linux +#include "FileOperations.h" +#include +#include +#ifdef _WIN32 +// For mkdir +#include +#include +#else +#include +#include +#include "LinuxStrings.h" +#include "_findfirst.h" +#endif + + +bool WriteFileWithDirectories( const char *path, char *data, unsigned dataLength ) +{ + int index; + FILE *fp; + char *pathCopy; +#ifndef _WIN32 + + char *systemCommand; +#endif + + if ( path == 0 || path[ 0 ] == 0 ) + return false; + +#ifndef _WIN32 + + systemCommand = new char [ strlen( path ) + 1 + 6 ]; + +#endif + + pathCopy = new char [ strlen( path ) + 1 ]; + + strcpy( pathCopy, path ); + + index = 0; + + while ( pathCopy[ index ] ) + { + if ( pathCopy[ index ] == '/' || pathCopy[ index ] == '\\') + { + pathCopy[ index ] = 0; +#ifdef _WIN32 + _mkdir( pathCopy ); +#else + + mkdir( pathCopy, 0744 ); +#endif + + pathCopy[ index ] = '/'; + } + + index++; + } + + if (data && dataLength) + { + fp = fopen( path, "wb" ); + + if ( fp == 0 ) + { + delete [] pathCopy; +#ifndef _WIN32 + delete [] systemCommand; +#endif + return false; + } + + fwrite( data, 1, dataLength, fp ); + + fclose( fp ); + } + else + { +#ifdef _WIN32 + _mkdir( pathCopy ); +#else + mkdir( pathCopy, 0744 ); +#endif + } + + delete [] pathCopy; +#ifndef _WIN32 + delete [] systemCommand; +#endif + + + return true; +} +bool IsSlash(unsigned char c) +{ + return c=='/' || c=='\\'; +} + +void AddSlash( char *input ) +{ + if (input==0 || input[0]==0) + return; + + int lastCharIndex=(int) strlen(input)-1; + if (input[lastCharIndex]=='\\') + input[lastCharIndex]='/'; + else if (input[lastCharIndex]!='/') + { + input[lastCharIndex+1]='/'; + input[lastCharIndex+2]=0; + } +} +bool DirectoryExists(const char *directory) +{ + _finddata_t fileInfo; + intptr_t dir; + char baseDirWithStars[560]; + strcpy(baseDirWithStars, directory); + AddSlash(baseDirWithStars); + strcat(baseDirWithStars, "*.*"); + dir=_findfirst(baseDirWithStars, &fileInfo ); + if (dir==-1) + return false; + _findclose(dir); + return true; +} +void QuoteIfSpaces(char *str) +{ + unsigned i; + bool hasSpace=false; + for (i=0; str[i]; i++) + { + if (str[i]==' ') + { + hasSpace=true; + break; + } + } + if (hasSpace) + { + int len=(int)strlen(str); + memmove(str+1, str, len); + str[0]='\"'; + str[len]='\"'; + str[len+1]=0; + } +} diff --git a/raknet/FileOperations.h b/raknet/FileOperations.h new file mode 100644 index 0000000..2d6f4c2 --- /dev/null +++ b/raknet/FileOperations.h @@ -0,0 +1,12 @@ +#ifndef __FILE_OPERATIONS_H +#define __FILE_OPERATIONS_H + +#include "Export.h" + +bool RAK_DLL_EXPORT WriteFileWithDirectories( const char *path, char *data, unsigned dataLength ); +bool RAK_DLL_EXPORT IsSlash(unsigned char c); +void RAK_DLL_EXPORT AddSlash( char *input ); +void RAK_DLL_EXPORT QuoteIfSpaces(char *str); +bool RAK_DLL_EXPORT DirectoryExists(const char *directory); + +#endif diff --git a/raknet/FullyConnectedMesh.cpp b/raknet/FullyConnectedMesh.cpp new file mode 100644 index 0000000..2426eda --- /dev/null +++ b/raknet/FullyConnectedMesh.cpp @@ -0,0 +1,98 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "FullyConnectedMesh.h" +#include "RakPeerInterface.h" +#include "PacketEnumerations.h" +#include "BitStream.h" +#include "ConnectionGraph.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +FullyConnectedMesh::FullyConnectedMesh() +{ + pw=0; +} + +FullyConnectedMesh::~FullyConnectedMesh() +{ + if (pw) + delete [] pw; +} + +void FullyConnectedMesh::Initialize(const char *password) +{ + if (pw) + delete [] pw; + if (password && password[0]) + { + pw = new char [strlen(password)+1]; + strcpy(pw, password); + } + else + pw=0; + +} + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void FullyConnectedMesh::OnDisconnect(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void FullyConnectedMesh::Update(RakPeerInterface *peer) +{ +} + +PluginReceiveResult FullyConnectedMesh::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + assert(packet); + assert(peer); + + switch (packet->data[0]) + { + case ID_REMOTE_NEW_INCOMING_CONNECTION: // This comes from the connection graph plugin + { + RakNet::BitStream b(packet->data, packet->length, false); + b.IgnoreBits(8); + ConnectionGraphGroupID group1, group2; + PlayerID node1, node2; + b.Read(node1); + b.Read(group1); + if (peer->GetIndexFromPlayerID(node1)==-1) + peer->Connect(peer->PlayerIDToDottedIP(node1), node1.port, pw, pw ? (int)strlen(pw) : 0); + b.Read(node2); + b.Read(group2); + if (peer->GetIndexFromPlayerID(node2)==-1) + peer->Connect(peer->PlayerIDToDottedIP(node2), node2.port, pw, pw ? (int)strlen(pw) : 0); + break; + } + } + + return RR_CONTINUE_PROCESSING; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/FullyConnectedMesh.h b/raknet/FullyConnectedMesh.h new file mode 100644 index 0000000..55a0367 --- /dev/null +++ b/raknet/FullyConnectedMesh.h @@ -0,0 +1,54 @@ +/// \file +/// \brief Fully connected mesh plugin. This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __FULLY_CONNECTED_MESH_H +#define __FULLY_CONNECTED_MESH_H + +class RakPeerInterface; +#include "PluginInterface.h" + +/// \defgroup FULLY_CONNECTED_MESH_GROUP FullyConnectedMesh +/// \ingroup PLUGINS_GROUP + +/// Fully connected mesh plugin. This will connect RakPeer to all connecting peers, and all peers the connecting peer knows about. +/// \pre You must also install the ConnectionGraph plugin. If you want a password, set it there. +/// \ingroup FULLY_CONNECTED_MESH_GROUP +class FullyConnectedMesh : public PluginInterface +{ +public: + FullyConnectedMesh(); + virtual ~FullyConnectedMesh(); + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + // Set the password to use to connect to the other systems + void Initialize(const char *password); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual void OnDisconnect(RakPeerInterface *peer); + virtual void Update(RakPeerInterface *peer); + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + + +protected: + char *pw; +}; + +#endif diff --git a/raknet/GetTime.cpp b/raknet/GetTime.cpp index baf3d30..17856f0 100644 --- a/raknet/GetTime.cpp +++ b/raknet/GetTime.cpp @@ -48,22 +48,22 @@ RakNetTime RakNet::GetTime( void ) #else gettimeofday( &initialTime, 0 ); #endif - + initialized = true; } - + #ifdef _WIN32 LARGE_INTEGER PerfVal; - + QueryPerformanceCounter( &PerfVal ); - + return (RakNetTime)(PerfVal.QuadPart*1000 / yo.QuadPart); #else gettimeofday( &tp, 0 ); - + // Seconds to ms and microseconds to ms return ( tp.tv_sec - initialTime.tv_sec ) * 1000 + ( tp.tv_usec - initialTime.tv_usec ) / 1000; - + #endif } diff --git a/raknet/InternalPacket.h b/raknet/InternalPacket.h new file mode 100644 index 0000000..a8bd53e --- /dev/null +++ b/raknet/InternalPacket.h @@ -0,0 +1,81 @@ +/// \file +/// \brief \b [Internal] A class which stores a user message, and all information associated with sending and receiving that message. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __INTERNAL_PACKET_H +#define __INTERNAL_PACKET_H + +#include "PacketPriority.h" +#include "NetworkTypes.h" + + +/// This is the counter used for holding ordered packet numbers, so we can detect out-of-order packets. It should be large enough that if the variables +/// were to wrap, the newly wrapped values would no longer be in use. Warning: Too large of a value wastes bandwidth! +typedef unsigned short OrderingIndexType; + +typedef unsigned short SplitPacketIdType; +typedef unsigned int SplitPacketIndexType; + + +/// This is the counter used for holding packet numbers, so we can detect duplicate packets. It should be large enough that if the variables +/// were to wrap, the newly wrapped values would no longer be in use. Warning: Too large of a value wastes bandwidth! +/// Use the smallest possible value, such that you send no more than rangeof(MessageNumberType) / GetTimeoutTime() packets per second +/// For the default value of 10 seconds, this is +/// unsigned char - 25.5 packets per second +/// unsigned short - 6553.5 packets per second +/// unsigned int - You'll run out of memory first. +typedef unsigned short MessageNumberType; + +/// Holds a user message, and related information +struct InternalPacket +{ + ///True if this is an acknowledgment packet + //bool isAcknowledgement; + + ///A unique numerical identifier given to this user message + MessageNumberType messageNumber; + /// Used only for tracking packetloss and windowing internally, this is the aggreggate packet number that a message was last sent in + unsigned packetNumber; + /// Was this packet number used this update to track windowing drops or increases? Each packet number is only used once per update. +// bool allowWindowUpdate; + ///The priority level of this packet + PacketPriority priority; + ///What type of reliability algorithm to use with this packet + PacketReliability reliability; + ///What ordering channel this packet is on, if the reliability type uses ordering channels + unsigned char orderingChannel; + ///The ID used as identification for ordering channels + OrderingIndexType orderingIndex; + ///The ID of the split packet, if we have split packets. This is the maximum number of split messages we can send simultaneously per connection. + SplitPacketIdType splitPacketId; + ///If this is a split packet, the index into the array of subsplit packets + SplitPacketIndexType splitPacketIndex; + ///The size of the array of subsplit packets + SplitPacketIndexType splitPacketCount; + ///When this packet was created + RakNetTimeNS creationTime; + ///The next time to take action on this packet + RakNetTimeNS nextActionTime; + ///How many bits the data is + unsigned int dataBitLength; + ///Buffer is a pointer to the actual data, assuming this packet has data at all + unsigned char *data; + /// For checking packetloss at a particular send rate + unsigned histogramMarker; +}; + +#endif + diff --git a/raknet/InternalPacketPool.cpp b/raknet/InternalPacketPool.cpp new file mode 100644 index 0000000..1dfda06 --- /dev/null +++ b/raknet/InternalPacketPool.cpp @@ -0,0 +1,67 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "InternalPacketPool.h" +#include + +InternalPacketPool::InternalPacketPool() +{ + // Speed things up by not reallocating at runtime when a mutex is locked. + pool.ClearAndForceAllocation( 64 ); + unsigned i; + for (i=0; i < 64; i++) + pool.Push(new InternalPacket); +} + +InternalPacketPool::~InternalPacketPool() +{ + ClearPool(); +} + +void InternalPacketPool::ClearPool( void ) +{ + while ( pool.Size() ) + delete pool.Pop(); +} +/* +InternalPacket* InternalPacketPool::GetPointer( void ) +{ + if ( pool.Size() ) + return pool.Pop(); + return new InternalPacket; + +} +*/ + +void InternalPacketPool::ReleasePointer( InternalPacket *p ) +{ + if ( p == 0 ) + { + // Releasing a null pointer? +#ifdef _DEBUG + assert( 0 ); +#endif + return ; + } + +#ifdef _DEBUG + p->data=0; +#endif + //poolMutex.Lock(); + pool.Push( p ); + //poolMutex.Unlock(); +} + diff --git a/raknet/InternalPacketPool.h b/raknet/InternalPacketPool.h new file mode 100644 index 0000000..73b892b --- /dev/null +++ b/raknet/InternalPacketPool.h @@ -0,0 +1,55 @@ +/// \file +/// \brief \b [Internal] Memory pool for InternalPacket* +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __INTERNAL_PACKET_POOL +#define __INTERNAL_PACKET_POOL +#include "DS_Queue.h" +#include "InternalPacket.h" + +/// Handles of a pool of InternalPacket pointers. This is only here for efficiency. +/// \sa InternalPacket.h +class InternalPacketPool +{ +public: + + /// Constructor + InternalPacketPool(); + + /// Destructor + ~InternalPacketPool(); + + /// Get an InternalPacket pointer. Will either allocate a new one or return one from the pool + /// \return An InternalPacket pointer. + InternalPacket* GetPointer( void ) + {if ( pool.Size() ) + return pool.Pop(); + return new InternalPacket;} + + /// Return an InternalPacket pointer to the pool. + /// \param[in] p A pointer to an InternalPacket you no longer need. + void ReleasePointer( InternalPacket *p ); + + // Delete all InternalPacket pointers in the pool. + void ClearPool( void ); + +private: + /// Queue of internal packets + DataStructures::Queue pool; +}; + +#endif + diff --git a/raknet/LightweightDatabaseClient.cpp b/raknet/LightweightDatabaseClient.cpp new file mode 100644 index 0000000..952a25d --- /dev/null +++ b/raknet/LightweightDatabaseClient.cpp @@ -0,0 +1,131 @@ +#include "LightweightDatabaseClient.h" +#include "StringCompressor.h" +#include "PacketEnumerations.h" +#include "RakPeerInterface.h" +#include "TableSerializer.h" +#include "BitStream.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +LightweightDatabaseClient::LightweightDatabaseClient() +{ + rakPeer=0; +} +LightweightDatabaseClient::~LightweightDatabaseClient() +{ + +} +void LightweightDatabaseClient::QueryTable(const char *tableName, const char *queryPassword, const char **columnSubset, unsigned char numColumnSubset, DatabaseFilter *filter, unsigned char numFilters, unsigned *rowIds, unsigned char numRowIDs, PlayerID playerId, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeer==0) + return; + + RakNet::BitStream out; + out.Write((unsigned char) ID_DATABASE_QUERY_REQUEST); + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (queryPassword && queryPassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(queryPassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write(numColumnSubset); + unsigned i; + for (i=0; i < numColumnSubset; i++) + out.Write(columnSubset[i]); + + out.Write(numFilters); + for (i=0; i < numFilters; i++) + filter[i].Serialize(&out); + + out.Write(numRowIDs); + for (i=0; i < numRowIDs; i++) + out.Write(rowIds[i]); + + rakPeer->Send(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,playerId, broadcast); +} +void LightweightDatabaseClient::RemoveRow(const char *tableName, const char *removePassword, unsigned rowId, PlayerID playerId, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeer==0) + return; + + RakNet::BitStream out; + out.Write((unsigned char) ID_DATABASE_REMOVE_ROW); + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (removePassword && removePassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(removePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write(rowId); + + rakPeer->Send(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,playerId, broadcast); +} +void LightweightDatabaseClient::UpdateRow(const char *tableName, const char *updatePassword, RowUpdateMode updateMode, bool hasRowId, unsigned rowId, DatabaseCellUpdate *cellUpdates, unsigned char numCellUpdates, PlayerID playerId, bool broadcast) +{ + if (tableName==0 || tableName[0]==0) + return; + if (rakPeer==0) + return; + if (cellUpdates==0 || numCellUpdates==0) + return; + + RakNet::BitStream out; + out.Write((unsigned char) ID_DATABASE_UPDATE_ROW); + stringCompressor->EncodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, &out); + if (updatePassword && updatePassword[0]) + { + out.Write(true); + // This is sent in plain text. I can do this securely but it's not worth the trouble. + // Use secure connections if you want security. + stringCompressor->EncodeString(updatePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH, &out); + } + else + out.Write(false); + + out.Write((unsigned char) updateMode); + out.Write(hasRowId); + if (hasRowId) + out.Write(rowId); + out.Write(numCellUpdates); + unsigned i; + for (i=0; i < numCellUpdates; i++) + cellUpdates[i].Serialize(&out); + + rakPeer->Send(&out, HIGH_PRIORITY, RELIABLE_ORDERED,0,playerId, broadcast); +} +PluginReceiveResult LightweightDatabaseClient::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + // All these messages are just returned to the user. +// switch (packet->data[0]) +// { +// case ID_DATABASE_QUERY_REPLY: +// case ID_DATABASE_UNKNOWN_TABLE: +// case ID_DATABASE_INCORRECT_PASSWORD: +// } + + return RR_CONTINUE_PROCESSING; +} +void LightweightDatabaseClient::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/LightweightDatabaseClient.h b/raknet/LightweightDatabaseClient.h new file mode 100644 index 0000000..1795a02 --- /dev/null +++ b/raknet/LightweightDatabaseClient.h @@ -0,0 +1,88 @@ +/// \file +/// \brief Contains the client interface to the simple database included with RakNet, useful for a server browser or a lobby server. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __LIGHTWEIGHT_DATABASE_CLIENT_H +#define __LIGHTWEIGHT_DATABASE_CLIENT_H + +#include "Export.h" +#include "PluginInterface.h" +#include "LightweightDatabaseCommon.h" + +class RakPeerInterface; +struct Packet; + +/// \defgroup SIMPLE_DATABSE_GROUP LightweightDatabase +/// \ingroup PLUGINS_GROUP + +/// \brief The client interface to the simple database included with RakNet, useful for a server browser or a lobby server. +/// \ingroup SIMPLE_DATABSE_GROUP +class RAK_DLL_EXPORT LightweightDatabaseClient : public PluginInterface +{ +public: + + /// Constructor + LightweightDatabaseClient(); + + /// Destructor + ~LightweightDatabaseClient(); + + /// Sends a query to a remote table. + /// The remote system should return ID_DATABASE_QUERY_REPLY, ID_DATABASE_UNKNOWN_TABLE, or ID_DATABASE_INCORRECT_PASSWORD + /// \note If the remote table uses a password and you send the wrong one you will be silently disconnected and banned for one second. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] queryPassword Password to query the remote table, if any. + /// \param[in] columnSubset An array of string names of the columns to query. The resulting table will return the columns in this same order. Pass 0 for all columns. + /// \param[in] numColumnSubset The number of elements in the columnSubset array + /// \param[in] filter Filters to apply to the query. For each row the filters are applied. All filters must be passed for the row to be returned. Pass 0 for no filters. + /// \param[in] numFilters The number of elements in the filter array + /// \param[in] rowIds Which particular rows to return. Pass 0 for all rows. + /// \param[in] numRowIDs The number of elements in the rowIds array + /// \param[in] playerId Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void QueryTable(const char *tableName, const char *queryPassword, const char **columnSubset, unsigned char numColumnSubset, DatabaseFilter *filter, unsigned char numFilters, unsigned *rowIds, unsigned char numRowIDs, PlayerID playerId, bool broadcast); + + /// Sets one or more values in a new or existing row, assuming the server allows row creation and updates. + /// No response is returned by the server. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] updatePassword Password to update the remote table, if any. + /// \param[in] updateMode See RowUpdateMode in LightweightDatabaseCommon.h . This determines if to update an existing or new row. + /// \param[in] hasRowId True if a valid value was passed for \a rowId, false otherwise. Required to be true for updating an existing row. Required if adding a new row and the remote system does not automatically create rowIDs. + /// \param[in] rowId The rowID of the new or existing row. + /// \param[in] cellUpdates An array of DatabaseCellUpdate structures containing the values to write to the remote row. + /// \param[in] numCellUpdates The number of elements in the cellUpdates array + /// \param[in] playerId Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void UpdateRow(const char *tableName, const char *updatePassword, RowUpdateMode updateMode, bool hasRowId, unsigned rowId, DatabaseCellUpdate *cellUpdates, unsigned char numCellUpdates, PlayerID playerId, bool broadcast); + + /// Removes a remote row, assuming the server allows row removal. + /// No response is returned by the server. + /// \param[in] tableName String name of the remote table. Case sensitive. + /// \param[in] removePassword Password to remove rows from the remote table, if any. + /// \param[in] rowId The rowID of the existing row. + /// \param[in] playerId Which system to send to. + /// \param[in] broadcast Broadcast or not. Same as the parameter in RakPeer::Send + void RemoveRow(const char *tableName, const char *removePassword, unsigned rowId, PlayerID playerId, bool broadcast); + + /// \internal For plugin handling + void OnAttach(RakPeerInterface *peer); + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); +protected: + RakPeerInterface *rakPeer; +}; + +#endif diff --git a/raknet/LightweightDatabaseCommon.cpp b/raknet/LightweightDatabaseCommon.cpp new file mode 100644 index 0000000..6d12518 --- /dev/null +++ b/raknet/LightweightDatabaseCommon.cpp @@ -0,0 +1,45 @@ +#include "TableSerializer.h" +#include "LightweightDatabaseCommon.h" +#include "BitStream.h" +#include "StringCompressor.h" + +void DatabaseFilter::Serialize(RakNet::BitStream *out) +{ + stringCompressor->EncodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columnType); + out->Write((unsigned char)operation); + if (operation!=DataStructures::Table::QF_IS_EMPTY && operation!=DataStructures::Table::QF_NOT_EMPTY) + { + assert(cellValue.isEmpty==false); + TableSerializer::SerializeCell(out, &cellValue, columnType); + } +} +bool DatabaseFilter::Deserialize(RakNet::BitStream *in) +{ + unsigned char temp; + stringCompressor->DecodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, in); + in->Read(temp); + columnType=(DataStructures::Table::ColumnType)temp; + if (in->Read(temp)==false) + return false; + operation=(DataStructures::Table::FilterQueryType)temp; + if (operation!=DataStructures::Table::QF_IS_EMPTY && operation!=DataStructures::Table::QF_NOT_EMPTY) + { + return TableSerializer::DeserializeCell(in, &cellValue, columnType); + } + return true; +} +void DatabaseCellUpdate::Serialize(RakNet::BitStream *out) +{ + stringCompressor->EncodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columnType); + TableSerializer::SerializeCell(out, &cellValue, columnType); +} +bool DatabaseCellUpdate::Deserialize(RakNet::BitStream *in) +{ + unsigned char temp; + stringCompressor->DecodeString(columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, in); + in->Read(temp); + columnType=(DataStructures::Table::ColumnType)temp; + return TableSerializer::DeserializeCell(in, &cellValue, columnType); +} diff --git a/raknet/LightweightDatabaseCommon.h b/raknet/LightweightDatabaseCommon.h new file mode 100644 index 0000000..5e4ecbb --- /dev/null +++ b/raknet/LightweightDatabaseCommon.h @@ -0,0 +1,49 @@ +#ifndef __SIMPLE_DATABASE_COMMON_H +#define __SIMPLE_DATABASE_COMMON_H + +#include "DS_Table.h" + +namespace RakNet +{ + class BitStream; +}; + +#define _SIMPLE_DATABASE_PASSWORD_LENGTH 32 +#define _SIMPLE_DATABASE_TABLE_NAME_LENGTH 32 + +struct DatabaseFilter +{ + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + + DataStructures::Table::Cell cellValue; + DataStructures::Table::FilterQueryType operation; + DataStructures::Table::ColumnType columnType; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; +}; + +/// The value to write to a cell in a remote database. +struct DatabaseCellUpdate +{ + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + + DataStructures::Table::Cell cellValue; + DataStructures::Table::ColumnType columnType; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; +}; + +enum RowUpdateMode +{ + // Only update an existing row. rowId is required. + RUM_UPDATE_EXISTING_ROW, + + // Update an existing row if present - otherwise add a new row. rowId is required. + RUM_UPDATE_OR_ADD_ROW, + + // Add a new row. If rowId is passed then the row will only be added if this id doesn't already exist. + // If rowId is not passed and the table requires a rowId the creation fails. + RUM_ADD_NEW_ROW, +}; + +#endif diff --git a/raknet/LightweightDatabaseServer.h b/raknet/LightweightDatabaseServer.h new file mode 100644 index 0000000..f1d97d2 --- /dev/null +++ b/raknet/LightweightDatabaseServer.h @@ -0,0 +1,147 @@ +/// \file +/// \brief A simple flat database included with RakNet, useful for a server browser or a lobby server. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __LIGHTWEIGHT_DATABASE_SERVER_H +#define __LIGHTWEIGHT_DATABASE_SERVER_H + +#include "Export.h" +#include "PluginInterface.h" +#include "LightweightDatabaseCommon.h" +#include "DS_Map.h" + +class RakPeerInterface; +struct Packet; + +/// \brief A simple flat database included with RakNet, useful for a server browser or a lobby server. +/// A flat database interface. Adds the ability to track IPs of row updaters and passwords for table read and write operations, +/// Best used for data in which queries which do not need to be updated in real-time +/// \ingroup SIMPLE_DATABSE_GROUP +class RAK_DLL_EXPORT LightweightDatabaseServer : public PluginInterface +{ +public: + /// Constructor + LightweightDatabaseServer(); + + /// Destructor + ~LightweightDatabaseServer(); + + /// Returns a table by name. + /// It is valid to read and write to the returned pointer. + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return The requested table, or 0 if \a tableName cannot be found. + DataStructures::Table *GetTable(char *tableName); + + /// Adds a new table + /// It is valid to read and write to the returned pointer. + /// \param[in] tableName Name of the new table to create. Remote systems will refer to this table by this name. + /// \param[in] allowRemoteQuery true to allow remote systems to query the table. false to not allow this. + /// \param[in] allowRemoteUpdate true to allow remote systems to update rows in the table. false to not allow this. + /// \param[in] allowRemoteRemove true to allow remote systems to remove rows from the table. false to not allow this. + /// \param[in] queryPassword The password required to query the table. Pass an empty string for no password. + /// \param[in] updatePassword The password required to update rows in the table. Pass an empty string for no password. + /// \param[in] removePassword The password required to remove rows from the table. Pass an empty string for no password. + /// \param[in] oneRowPerSystemId Only used if allowRemoteUpdate==true. This limits remote systems to one row. + /// \param[in] onlyUpdateOwnRows Only used if allowRemoteUpdate==true. This limits remote systems to only updating rows they created. + /// \param[in] removeRowOnPingFailure Only used if allowRemoteUpdate==true and removeRowOnDisconnect==false. If true, this will periodically ping disconnected systems and remove rows created by that system if that system does not respond to pings. + /// \param[in] removeRowOnDisconnect Only used if allowRemoteUpdate==true. This removes rows created by a system when that system disconnects. + /// \param[in] autogenerateRowIDs true to automatically generate row IDs. Rows are stored in order by row ID. If false, the clients must specify a unique row ID when adding rows. If they specify a row that already exists the addition is ignored. + /// \return The newly created table, or 0 on failure. + DataStructures::Table* AddTable(char *tableName, + bool allowRemoteQuery, + bool allowRemoteUpdate, + bool allowRemoteRemove, + char queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + char updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + char removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + bool oneRowPerSystemId, + bool onlyUpdateOwnRows, + bool removeRowOnPingFailure, + bool removeRowOnDisconnect, + bool autogenerateRowIDs); + + /// Removes a table by name. + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return true on success, false on failure. + bool RemoveTable(char *tableName); + + /// Clears all memory. + void Clear(void); + + // Gets the next valid auto-generated rowId for a table and increments it. + unsigned GetAndIncrementRowID(char *tableName); + + /// Returns a linked list of ordered lists containing the rows of a table, by name. + /// The returned structure is internal to the BPlus tree. See DS_BPlusTree + /// This is a convenience accessor, as you can also get this from the table returned from GetTable() + /// \param[in] tableName The name of the table that was passed to AddTable + /// \return The requested rows, or 0 if \a tableName cannot be found. + DataStructures::Page *GetTableRows(char *tableName); + + /// \internal For plugin handling + virtual void OnAttach(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void Update(RakPeerInterface *peer); + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + /// \internal For plugin handling + virtual void OnDisconnect(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + + struct DatabaseTable + { + bool allowRemoteUpdate; + bool allowRemoteQuery; + bool allowRemoteRemove; + + char updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + char removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + bool oneRowPerSystemId; + bool onlyUpdateOwnRows; + bool removeRowOnPingFailure; + bool removeRowOnDisconnect; + bool autogenerateRowIDs; + char tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH]; + + // Used if autogenerateRowIDs==true + unsigned nextRowId; + + unsigned systemIdColumnIndex; + unsigned lastPingResponseColumnIndex; + unsigned nextPingSendColumnIndex; + RakNetTime nextRowPingCheck; + DataStructures::Table table; + }; + + static int DatabaseTableComp( char* const &key1, char* const &key2 ); + +protected: + DataStructures::Map database; + void OnQueryRequest(RakPeerInterface *peer, Packet *packet); + void OnUpdateRow(RakPeerInterface *peer, Packet *packet); + void OnRemoveRow(RakPeerInterface *peer, Packet *packet); + void OnPong(RakPeerInterface *peer, Packet *packet); + // mode 0 = query, mode 1 = update, mode 2 = remove + DatabaseTable * DeserializeClientHeader(RakNet::BitStream *inBitstream, RakPeerInterface *peer, Packet *packet, int mode); + DataStructures::Table::Row * GetRowFromIP(DatabaseTable *databaseTable, PlayerID playerId, unsigned *rowId); + bool RowHasIP(DataStructures::Table::Row *row, PlayerID playerId, unsigned systemIdColumnIndex); + DataStructures::Table::Row * AddRow(LightweightDatabaseServer::DatabaseTable *databaseTable, PlayerID playerId, bool hasRowId, unsigned rowId); + void RemoveRowsFromIP(PlayerID playerId); +}; + +#endif diff --git a/raknet/LinuxStrings.cpp b/raknet/LinuxStrings.cpp new file mode 100644 index 0000000..1c578c4 --- /dev/null +++ b/raknet/LinuxStrings.cpp @@ -0,0 +1,19 @@ + +#if (defined(__GNUC__) || defined(__GCCXML__)) +#include +int _stricmp(const char* s1, const char* s2) +{ + return strcasecmp(s1,s2); +} +int _strnicmp(const char* s1, const char* s2, size_t n) +{ + return strncasecmp(s1,s2,n); +} +//#ifndef __CYGWIN__ +//int _unlink(const char* filename) +//{ +// return remove(filename); +//} +//#endif + +#endif diff --git a/raknet/LinuxStrings.h b/raknet/LinuxStrings.h new file mode 100644 index 0000000..7dfbd32 --- /dev/null +++ b/raknet/LinuxStrings.h @@ -0,0 +1,12 @@ +#ifndef _GCC_WIN_STRINGS +#define _GCC_WIN_STRINGS + +#if (defined(__GNUC__) || defined(__GCCXML__)) +int _stricmp(const char* s1, const char* s2); +int _strnicmp(const char* s1, const char* s2, size_t n); +//#ifndef __CYGWIN__ +//int _unlink(const char* filename); +//#endif + +#endif +#endif diff --git a/raknet/LogCommandParser.cpp b/raknet/LogCommandParser.cpp new file mode 100644 index 0000000..9df37e9 --- /dev/null +++ b/raknet/LogCommandParser.cpp @@ -0,0 +1,276 @@ +#include "LogCommandParser.h" +#include "TransportInterface.h" +#include +#include +#include +#include + +#if (defined(__GNUC__) || defined(__GCCXML__)) +#define _vsnprintf vsnprintf +#define _stricmp strcasecmp +#endif + + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +LogCommandParser::LogCommandParser() +{ + RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Subscribe","[] - Subscribes to a named channel, or all channels"); + RegisterCommand(CommandParserInterface::VARIABLE_NUMBER_OF_PARAMETERS,"Unsubscribe","[] - Unsubscribes from a named channel, or all channels"); + memset(channelNames,0,sizeof(channelNames)); +} +LogCommandParser::~LogCommandParser() +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +bool LogCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString) +{ + if (strcmp(command, "Subscribe")==0) + { + unsigned channelIndex; + if (numParameters==0) + { + Subscribe(playerId, 0); + transport->Send(playerId, "Subscribed to all channels.\r\n"); + } + else if (numParameters==1) + { + if ((channelIndex=Subscribe(playerId, parameterList[0]))!=(unsigned)-1) + { + transport->Send(playerId, "You are now subscribed to channel %s.\r\n", channelNames[channelIndex]); + } + else + { + transport->Send(playerId, "Cannot find channel %s.\r\n", parameterList[0]); + PrintChannels(playerId, transport); + } + } + else + { + transport->Send(playerId, "Subscribe takes either 0 or 1 parameters.\r\n"); + } + } + else if (strcmp(command, "Unsubscribe")==0) + { + unsigned channelIndex; + if (numParameters==0) + { + Unsubscribe(playerId, 0); + transport->Send(playerId, "Unsubscribed from all channels.\r\n"); + } + else if (numParameters==1) + { + if ((channelIndex=Unsubscribe(playerId, parameterList[0]))!=(unsigned)-1) + { + transport->Send(playerId, "You are now unsubscribed from channel %s.\r\n", channelNames[channelIndex]); + } + else + { + transport->Send(playerId, "Cannot find channel %s.\r\n", parameterList[0]); + PrintChannels(playerId, transport); + } + } + else + { + transport->Send(playerId, "Unsubscribe takes either 0 or 1 parameters.\r\n"); + } + } + + return true; +} +char *LogCommandParser::GetName(void) const +{ + return "Logger"; +} +void LogCommandParser::SendHelp(TransportInterface *transport, PlayerID playerId) +{ + transport->Send(playerId, "The logger will accept user log data via the Log(...) function.\r\n"); + transport->Send(playerId, "Each log is associated with a named channel.\r\n"); + transport->Send(playerId, "You can subscribe to or unsubscribe from named channels.\r\n"); + PrintChannels(playerId, transport); +} +void LogCommandParser::AddChannel(const char *channelName) +{ + unsigned channelIndex; + channelIndex = GetChannelIndexFromName(channelName); + // Each channel can only be added once. + assert(channelIndex==(unsigned)-1); + + unsigned i; + for (i=0; i < 32; i++) + { + if (channelNames[i]==0) + { + // Assuming a persistent static string. + channelNames[i]=channelName; + return; + } + } + + // No more available channels - max 32 with this implementation where I save subscribed channels with bit operations + assert(0); +} +void LogCommandParser::WriteLog(const char *channelName, const char *format, ...) +{ + if (channelName==0 || format==0) + return; + + unsigned channelIndex; + channelIndex = GetChannelIndexFromName(channelName); + if (channelIndex==(unsigned)-1) + { + AddChannel(channelName); + } + + char text[REMOTE_MAX_TEXT_INPUT]; + va_list ap; + va_start(ap, format); + _vsnprintf(text, REMOTE_MAX_TEXT_INPUT, format, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + // Make sure that text ends in \r\n + int textLen; + textLen=(int)strlen(text); + if (textLen==0) + return; + if (text[textLen-1]=='\n') + { + text[textLen-1]=0; + } + if (textLen < REMOTE_MAX_TEXT_INPUT-4) + strcat(text, "\r\n"); + else + { + text[textLen-3]='\r'; + text[textLen-2]='\n'; + text[textLen-1]=0; + } + + // For each user that subscribes to this channel, send to them. + unsigned i; + for (i=0; i < remoteUsers.Size(); i++) + { + if (remoteUsers[i].channels & (1 << channelIndex)) + { + trans->Send(remoteUsers[i].playerId, text); + } + } +} +void LogCommandParser::PrintChannels(PlayerID playerId, TransportInterface *transport) const +{ + unsigned i; + bool anyChannels=false; + transport->Send(playerId, "CHANNELS:\r\n"); + for (i=0; i < 32; i++) + { + if (channelNames[i]) + { + transport->Send(playerId, "%i. %s\r\n", i+1,channelNames[i]); + anyChannels=true; + } + } + if (anyChannels==false) + transport->Send(playerId, "None.\r\n"); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void LogCommandParser::OnNewIncomingConnection(PlayerID playerId, TransportInterface *transport) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void LogCommandParser::OnConnectionLost(PlayerID playerId, TransportInterface *transport) +{ + Unsubscribe(playerId, 0); +} +unsigned LogCommandParser::Unsubscribe(PlayerID playerId, const char *channelName) +{ + unsigned i; + for (i=0; i < remoteUsers.Size(); i++) + { + if (remoteUsers[i].playerId==playerId) + { + if (channelName==0) + { + // Unsubscribe from all and delete this user. + remoteUsers[i]=remoteUsers[remoteUsers.Size()-1]; + remoteUsers.Del(); + return 0; + } + else + { + unsigned channelIndex; + channelIndex = GetChannelIndexFromName(channelName); + if (channelIndex!=(unsigned)-1) + { + remoteUsers[i].channels&=0xFFFF ^ (1< remoteUsers; + + /// Names of the channels at each bit, or 0 for an unused channel + const char *channelNames[32]; + + /// This is so I can save the current transport provider, solely so I can use it without having the user pass it to Log + TransportInterface *trans; +}; + +#endif diff --git a/raknet/MTUSize.h b/raknet/MTUSize.h index 2cf5ec7..9af6b0d 100644 --- a/raknet/MTUSize.h +++ b/raknet/MTUSize.h @@ -33,16 +33,12 @@ #ifdef _COMPATIBILITY_1 #define DEFAULT_MTU_SIZE 1264 #else -#define DEFAULT_MTU_SIZE 576 +#define DEFAULT_MTU_SIZE 1500 #endif /// The largest value for an UDP datagram /// \sa RakPeer::SetMTUSize() -#ifdef SAMPCLI -#define MAXIMUM_MTU_SIZE 1492 -#else -#define MAXIMUM_MTU_SIZE 576 -#endif +#define MAXIMUM_MTU_SIZE 1500 #endif diff --git a/raknet/Makefile b/raknet/Makefile new file mode 100644 index 0000000..d6033fb --- /dev/null +++ b/raknet/Makefile @@ -0,0 +1,19 @@ +include $(BASE_DIR)/makefile.defs + +all: shared static + +shared: + mkdir -p $(BASE_DIR)/Lib/linux/ + $(CC) $(DEBUG) -I$(INCLUDE) -w -c *.cpp + $(CC) $(DEBUG) -shared -Wl-soname,libraknet.so.2 -o $(BASE_DIR)/Lib/linux/libraknet.so.$(VERSION) *.o $(LIBS) + +static: + mkdir -p $(BASE_DIR)/Lib/linux/ + $(CC) $(DEBUG) -I$(INCLUDE) -w -c *.cpp + $(AR) rcs $(BASE_DIR)/Lib/linux/libraknet.a *.o + +clean: + rm -f core + rm -f *.o + rm -f $(BASE_DIR)/Lib/linux/libraknet.so.$(VERSION) + rm -f $(BASE_DIR)/Lib/linux/linuxraknet.a diff --git a/raknet/Makefile.am b/raknet/Makefile.am new file mode 100644 index 0000000..0e54313 --- /dev/null +++ b/raknet/Makefile.am @@ -0,0 +1,106 @@ +# Makefile.am +# RakNet configure script +SUBDIRS=Autopatcher RakVoice . + +lib_LTLIBRARIES=libRakNet.la + + +#RakNet headers +libRakNet_ladir=$(includedir)/RakNet +libRakNet_la_HEADERS=\ + AES128.h\ + ArrayList.h\ + AsynchronousFileIO.h\ + BigTypes.h\ + BinarySearchTree.h\ + BitStream.h\ + CheckSum.h\ + ClientContextStruct.h\ + DataBlockEncryptor.h\ + DistributedNetworkObject.h\ + DistributedNetworkObjectHeader.h\ + DistributedNetworkObjectManager.h\ + DistributedNetworkObjectStub.h\ + EncodeClassName.h\ + ExtendedOverlappedPool.h\ + GetTime.h\ + HuffmanEncodingTree.h\ + HuffmanEncodingTreeFactory.h\ + HuffmanEncodingTreeNode.h\ + InternalPacket.h\ + InternalPacketPool.h\ + LinkedList.h\ + MTUSize.h\ + Multiplayer.h\ + NetworkObject.h\ + NetworkTypes.h\ + PacketEnumerations.h\ + PacketPool.h\ + PacketPriority.h\ + Queue.h\ + QueueLinkedList.h\ + RPCNode.h\ + RSACrypt.h\ + RakClient.h\ + RakClientInterface.h\ + RakNetStatistics.h\ + RakNetworkFactory.h\ + RakPeer.h\ + RakPeerInterface.h\ + RakServer.h\ + RakServerInterface.h\ + Rand.h\ + ReliabilityLayer.h\ + SHA1.h\ + SimpleMutex.h\ + SocketLayer.h\ + StringCompressor.h\ + Types.h + +#RakNet sources files +libRakNet_la_SOURCES=\ + AES128.cpp\ + AsynchronousFileIO.cpp\ + BitStream.cpp\ + CheckSum.cpp\ + DataBlockEncryptor.cpp\ + DistributedNetworkObject.cpp\ + DistributedNetworkObjectManager.cpp\ + DistributedNetworkObjectStub.cpp\ + EncodeClassName.cpp\ + ExtendedOverlappedPool.cpp\ + GetTime.cpp\ + HuffmanEncodingTree.cpp\ + HuffmanEncodingTreeFactory.cpp\ + InternalPacketPool.cpp\ + NetworkObject.cpp\ + NetworkTypes.cpp\ + PacketPool.cpp\ + RPCNode.cpp\ + RakClient.cpp\ + RakNetStatistics.cpp\ + RakNetworkFactory.cpp\ + RakPeer.cpp\ + RakServer.cpp\ + Rand.cpp\ + ReliabilityLayer.cpp\ + SHA1.cpp\ + SimpleMutex.cpp\ + SocketLayer.cpp\ + StringCompressor.cpp + +if BUILD_RAKVOICE + +if BUILD_MONOLITHIC +libRakNet_la_LIBADD=\ + Autopatcher/libRakNet_autopatcher.la\ + RakVoice/libRakNet_voice.la +else +libRakNet_la_LIBADD=\ + Autopatcher/libRakNet_autopatcher.la +endif + +else +libRakNet_la_LIBADD=\ + Autopatcher/libRakNet_autopatcher.la +endif diff --git a/raknet/NatPunchthrough.cpp b/raknet/NatPunchthrough.cpp new file mode 100644 index 0000000..edc3032 --- /dev/null +++ b/raknet/NatPunchthrough.cpp @@ -0,0 +1,381 @@ +#include "NatPunchthrough.h" +#include "GetTime.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "PacketEnumerations.h" +#include "RakAssert.h" + +//#include + +// Number is arbitrary, but you want a long enough interval to get a good sample, but short enough not to annoy the user. +static const int PING_INTERVAL=100; +// Fill up the ping buffer +static const int PING_COUNT=PING_TIMES_ARRAY_SIZE; +// What multiple to multiply the ping by to figure out how long to wait before sending the start time. +// Should be slightly larger than the ping so the last ping arrives +static const float SEND_TIMESTAMP_DELAY_PING_MULTIPLE=1.5f; +// What multiple to multiply the ping by to figure out when to send the NAT punchthrough request +// Should be large enough to make sure both systems get the message in the future, even if it needs to be resent due to packetloss. +// Too large is bad because the user has to wait and it makes it more likely our estimated ping is wrong +static const float SEND_PUNCHTHROUGH_DELAY_PING_MULTIPLE=5.0f; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + + +NatPunchthrough::NatPunchthrough() +{ + allowFacilitation=true; + rakPeer=0; +} +NatPunchthrough::~NatPunchthrough() +{ + Clear(); +} +void NatPunchthrough::FacilitateConnections(bool allow) +{ + allowFacilitation=allow; +} +bool NatPunchthrough::Connect(const char* host, unsigned short remotePort, char* passwordData, int passwordDataLength, PlayerID facilitator) +{ + PlayerID playerId; + rakPeer->IPToPlayerID(host, remotePort, &playerId); + return Connect(playerId, passwordData, passwordDataLength, facilitator); +} +bool NatPunchthrough::Connect(PlayerID receiver, char* passwordData, int passwordDataLength, PlayerID facilitator) +{ + if (rakPeer->GetIndexFromPlayerID(facilitator)==-1) + return false; + + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char) ID_NAT_PUNCHTHROUGH_REQUEST); + outBitstream.Write(receiver); + + // Remember this connection request + NatPunchthrough::ConnectionRequest *connectionRequest = new NatPunchthrough::ConnectionRequest; + connectionRequest->receiver=receiver; + connectionRequest->facilitator=facilitator; + if (passwordDataLength) + { + connectionRequest->passwordData = new char [passwordDataLength]; + memcpy(connectionRequest->passwordData, passwordData, passwordDataLength); + } + else + connectionRequest->passwordData = 0; + connectionRequest->sender=UNASSIGNED_PLAYER_ID; + connectionRequest->passwordDataLength=passwordDataLength; + connectionRequest->facilitator=facilitator; + connectionRequest->nextActionTime=0; + connectionRequest->facilitatingConnection=false; + connectionRequest->timeoutTime=RakNet::GetTime()+30000; + connectionRequest->pingCount=0; + connectionRequestList.Insert(connectionRequest); + + rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE, 0, facilitator, false); + + return true; +} +void NatPunchthrough::Clear(void) +{ + unsigned i; + for (i=0; i < connectionRequestList.Size(); i++) + { + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + } + connectionRequestList.Clear(); +} +void NatPunchthrough::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::Update(RakPeerInterface *peer) +{ + if (connectionRequestList.Size()) + { + RakNetTime time = RakNet::GetTime(); + RakNet::BitStream outBitstream; + unsigned i; + i=0; + while (i < connectionRequestList.Size()) + { + // Remove old connection requests that get no answer + if (connectionRequestList[i]->timeoutTime < time) + { + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + connectionRequestList.RemoveAtIndex(i); + continue; + } + + // If we are a facilitator, do pings + if (connectionRequestList[i]->facilitatingConnection) + { + if (time >= connectionRequestList[i]->nextActionTime ) + { + if (connectionRequestList[i]->pingCount < PING_COUNT) + { + // Ping + connectionRequestList[i]->pingCount++; + rakPeer->Ping(connectionRequestList[i]->receiver); + rakPeer->Ping(connectionRequestList[i]->sender); + connectionRequestList[i]->nextActionTime=time+PING_INTERVAL; + } + else if (connectionRequestList[i]->pingCount == PING_COUNT) + { + // Done pinging. Wait till the next stage + int receiverPing, senderPing; + receiverPing=rakPeer->GetAveragePing(connectionRequestList[i]->receiver); + senderPing=rakPeer->GetAveragePing(connectionRequestList[i]->sender); + if (receiverPing > senderPing) + connectionRequestList[i]->nextActionTime=(RakNetTime)(receiverPing*SEND_TIMESTAMP_DELAY_PING_MULTIPLE); + else + connectionRequestList[i]->nextActionTime=(RakNetTime)(senderPing*SEND_TIMESTAMP_DELAY_PING_MULTIPLE); + connectionRequestList[i]->pingCount++; + } + else + { + // Send the timestamped message to both systems so they send datagrams to each other simultaneously + int receiverPing, senderPing; + RakNetTime delayTime; + receiverPing=rakPeer->GetAveragePing(connectionRequestList[i]->receiver); + senderPing=rakPeer->GetAveragePing(connectionRequestList[i]->sender); + if (receiverPing > senderPing) + delayTime=(RakNetTime)(receiverPing*SEND_PUNCHTHROUGH_DELAY_PING_MULTIPLE); + else + delayTime=(RakNetTime)(senderPing*SEND_PUNCHTHROUGH_DELAY_PING_MULTIPLE); + + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(time+delayTime); + outBitstream.Write((MessageID)ID_NAT_CONNECT_AT_TIME); + rakPeer->Send(&outBitstream, SYSTEM_PRIORITY, RELIABLE, 0, connectionRequestList[i]->sender, false); + + outBitstream.Reset(); + outBitstream.Write((MessageID)ID_TIMESTAMP); + outBitstream.Write(time+delayTime); + outBitstream.Write((MessageID)ID_NAT_SEND_OFFLINE_MESSAGE_AT_TIME); + outBitstream.Write(connectionRequestList[i]->sender); + rakPeer->Send(&outBitstream, SYSTEM_PRIORITY, RELIABLE, 0, connectionRequestList[i]->receiver, false); + + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + connectionRequestList.RemoveAtIndex(i); + continue; + } + } + } + // Else not a facilitator. If nextActionTime is non-zero, connect or send the offline message + else if (connectionRequestList[i]->nextActionTime && time >= connectionRequestList[i]->nextActionTime) + { + if (connectionRequestList[i]->receiver!=UNASSIGNED_PLAYER_ID) + { + // Connect to this system. + rakPeer->Connect(rakPeer->PlayerIDToDottedIP(connectionRequestList[i]->receiver), connectionRequestList[i]->receiver.port, connectionRequestList[i]->passwordData, connectionRequestList[i]->passwordDataLength); + } + else + { + RakAssert(connectionRequestList[i]->sender!=UNASSIGNED_PLAYER_ID); + + // Send offline message to this system, hopefully at the exact same time that system tries to connect to us. + rakPeer->Ping(rakPeer->PlayerIDToDottedIP(connectionRequestList[i]->sender), connectionRequestList[i]->sender.port, false); + } + + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + connectionRequestList.RemoveAtIndex(i); + continue; + } + + i++; + } + } +} +PluginReceiveResult NatPunchthrough::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + unsigned char packetId; + if (packet->length>sizeof(MessageID)+sizeof(RakNetTime) && packet->data[0]==ID_TIMESTAMP) + packetId=packet->data[sizeof(MessageID)+sizeof(RakNetTime)]; + else + packetId=packet->data[0]; + + switch (packetId) + { + case ID_NAT_PUNCHTHROUGH_REQUEST: + OnPunchthroughRequest(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb + case ID_NAT_TARGET_CONNECTION_LOST: + case ID_NAT_TARGET_NOT_CONNECTED: + // Sender only + return RemoveRequestByFacilitator(packet->playerId); + case ID_NAT_CONNECT_AT_TIME: + OnConnectAtTime(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb + case ID_NAT_SEND_OFFLINE_MESSAGE_AT_TIME: + OnSendOfflineMessageAtTime(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; // Absorb + case ID_DISCONNECTION_NOTIFICATION: + case ID_CONNECTION_LOST: + OnCloseConnection(peer, packet->playerId); + return RR_CONTINUE_PROCESSING; + } + + return RR_CONTINUE_PROCESSING; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + if (allowFacilitation==false) + return; + + // If in ping mode, send ID_NAT_TARGET_CONNECTION_LOST + if (connectionRequestList.Size()) + { + unsigned i; + i=0; + while (i < connectionRequestList.Size()) + { + if (connectionRequestList[i]->facilitatingConnection && + (connectionRequestList[i]->receiver==playerId || + connectionRequestList[i]->sender==playerId)) + { + // This field is not used by the facilitator. + RakAssert(connectionRequestList[i]->passwordData==0); + + if (connectionRequestList[i]->sender==playerId) + { + RakNet::BitStream outBitstream; + outBitstream.Write((unsigned char)ID_NAT_TARGET_CONNECTION_LOST); + outBitstream.Write(connectionRequestList[i]->receiver); + rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE, 0, connectionRequestList[i]->sender, false); + } + + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + connectionRequestList.RemoveAtIndex(i); + } + else + i++; + } + } +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::OnDisconnect(RakPeerInterface *peer) +{ + +} +PluginReceiveResult NatPunchthrough::RemoveRequestByFacilitator(PlayerID playerId) +{ + unsigned i; + i=0; + while (i < connectionRequestList.Size()) + { + if (connectionRequestList[i]->facilitator==playerId) + { + delete [] connectionRequestList[i]->passwordData; + delete connectionRequestList[i]; + connectionRequestList.RemoveAtIndex(i); + return RR_CONTINUE_PROCESSING; + } + else + i++; + } + return RR_STOP_PROCESSING_AND_DEALLOCATE; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::OnPunchthroughRequest(RakPeerInterface *peer, Packet *packet) +{ + if (allowFacilitation==false) + return; + + PlayerID target; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + RakNet::BitStream outBitstream; + inBitstream.IgnoreBits(8); + if (inBitstream.Read(target)==false) + return; + if (rakPeer->GetIndexFromPlayerID(target)==-1) + { + outBitstream.Write((unsigned char)ID_NAT_TARGET_NOT_CONNECTED); + outBitstream.Write(target); + rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE, 0, packet->playerId, false); + } + else + { + // Remember this connection request + NatPunchthrough::ConnectionRequest *connectionRequest = new NatPunchthrough::ConnectionRequest; + connectionRequest->receiver=target; + connectionRequest->facilitator=UNASSIGNED_PLAYER_ID; + connectionRequest->passwordData = 0; + connectionRequest->sender=packet->playerId; + connectionRequest->passwordDataLength=0; + connectionRequest->facilitatingConnection=true; + connectionRequest->nextActionTime=RakNet::GetTime()+PING_INTERVAL; + connectionRequest->pingCount=1; + connectionRequest->timeoutTime=RakNet::GetTime()+30000; + connectionRequestList.Insert(connectionRequest); + + rakPeer->Ping(connectionRequest->receiver); + rakPeer->Ping(connectionRequest->sender); + } +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::OnConnectAtTime(RakPeerInterface *peer, Packet *packet) +{ + // Find this connection request and set the connection timer. + unsigned i; + for (i=0; i < connectionRequestList.Size(); i++) + { + if (connectionRequestList[i]->nextActionTime==0 && + connectionRequestList[i]->facilitatingConnection==false && + connectionRequestList[i]->facilitator==packet->playerId) + { + RakNet::BitStream inBitstream(packet->data, packet->length, false); + inBitstream.IgnoreBits(8); + inBitstream.Read(connectionRequestList[i]->nextActionTime); + return; + } + } +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void NatPunchthrough::OnSendOfflineMessageAtTime(RakPeerInterface *peer, Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + RakNetTime nextActionTime; + inBitstream.IgnoreBits(8); + PlayerID sender; + inBitstream.Read(nextActionTime); + inBitstream.IgnoreBits(8); + if (inBitstream.Read(sender)==false) + return; + + NatPunchthrough::ConnectionRequest *connectionRequest = new NatPunchthrough::ConnectionRequest; + connectionRequest->receiver=UNASSIGNED_PLAYER_ID; + connectionRequest->facilitator=packet->playerId; + connectionRequest->sender=sender; + connectionRequest->passwordData = 0; + connectionRequest->passwordDataLength=0; + connectionRequest->facilitatingConnection=false; + connectionRequest->nextActionTime=nextActionTime; + connectionRequest->pingCount=0; + connectionRequest->timeoutTime=RakNet::GetTime()+30000; + connectionRequestList.Insert(connectionRequest); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/NatPunchthrough.h b/raknet/NatPunchthrough.h new file mode 100644 index 0000000..8092a91 --- /dev/null +++ b/raknet/NatPunchthrough.h @@ -0,0 +1,143 @@ +/// \file +/// \brief Contains the NAT-punchthrough plugin +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __NAT_PUNCHTHROUGH_H +#define __NAT_PUNCHTHROUGH_H + +#include "NetworkTypes.h" +#include "Export.h" +#include "PluginInterface.h" +#include "PacketPriority.h" +#include "DS_List.h" + +class RakPeerInterface; +struct Packet; + +/// \defgroup NAT_PUNCHTHROUGH_GROUP NatPunchthrough +/// \ingroup PLUGINS_GROUP + +/// \brief The NatPunchthrough class implements the NAT punch through technique, allowing two systems to connect to each other that are both behind NATs. +/// +/// A NAT (Network Address Translator) is a system that will makes it so your system's IP address is different from the IP address exposed to the internet. +/// This provides some security and allows multiple computers, each with a different IP address, to share one IP address as seen by the internet. +/// +/// The problem is that NATs also ignore packets sent to them unless they sent a packet to the sender first. +/// If two systems are both behind NATs, then neither system can connect to each other. +/// Furthermore, some NATs will impose a temporary ban on an IP address that send unsolicited packets to them. +/// +/// This can be solved by using a third system, a facilitator, that is not behind a NAT and that both systems are already connected to. +/// It will synchronize a send between both NAT systems such that the routers will both consider themselves as handling a reply to a message, when in +/// fact they are handing an initial message. As replies are allowed, both systems get their corresponding messages and the connection takes place. +/// S = system that wants to connect +/// F = facilitator +/// R = system to get the connection request. +/// +/// S knows IP of R in advance. +/// 1. S->F facilitate connection request to R +/// 2. if (R is not is connected) F->S ID_NAT_TARGET_NOT_CONNECTED. Exit. +/// 3. F -> (Ping S, Ping R), every X ms Y times. Wait Max(Ping(s), Ping(r) * multiple ms more, then go to step 4. +/// 4. F picks time highest(ave of Ping R,S) * N from now and sends this time RELIABLE and timestamped to R,S. +/// 5. At time picked in (4), S attempts to connect to R. R sends offline ping to S. +/// 6. If R disconnects before or at step 4, tell this to S via ID_NAT_TARGET_CONNECTION_LOST +/// +/// \note Timing is important with this plugin. You need to call RakPeer::Receive frequently. +/// \ingroup NAT_PUNCHTHROUGH_GROUP +class RAK_DLL_EXPORT NatPunchthrough : public PluginInterface +{ +public: + /// Constructor + NatPunchthrough(); + + /// Destructor + ~NatPunchthrough(); + + /// Call with true to allow other systems to use this system as a NAT punch through facilitator. This takes a little bandwidth but + /// otherwise there is no reason to disallow it. + /// Defaults to true + /// \param[in] allow True to allow, false to disallow. + void FacilitateConnections(bool allow); + + /// Call this to start to connect to the specified host (ip or domain name) and server port using \a facilitator to punch through a NAT + /// This is a non-blocking operation + /// You know the connection is successful when you get the message ID_CONNECTION_ACCEPTED. + /// You know the connection failed when you get the message ID_CONNECTION_ATTEMPT_FAILED, ID_CONNECTION_BANNED, or ID_NAT_TARGET_NOT_CONNECTED + /// Both you and the host must be connected to the facilitator. + /// \pre Requires that you first call Initialize + /// \pre Both \a host and this system must already be connected to the system at the address \a facilitator and facilitator must be running NatPunchthrough with FacilitateConnections(true) previously called. + /// \param[in] receiver Either a dotted IP address or a domain name of the system you ultimately want to connect to. + /// \param[in] remotePort Which port to connect to of the system you ultimately want to connect to. + /// \param[in] passwordData A data block that must match the data block on the \a host. This can be just a password, or can be a stream of data + /// \param[in] passwordDataLength The length in bytes of passwordData + /// \return If you are not connected to the facilitator this function returns false. Otherwise it returns true. + bool Connect(const char* receiver, unsigned short remotePort, char* passwordData, int passwordDataLength, PlayerID facilitator); + + /// Same as above, but takes a PlayerID for a host + /// \param[in] receiver The address of the host to connect to. + /// \param[in] remotePort Which port to connect to of the system you ultimately want to connect to. + /// \param[in] passwordData A data block that must match the data block on the \a host. This can be just a password, or can be a stream of data + /// \param[in] passwordDataLength The length in bytes of passwordData + /// \return If you are not connected to the facilitator this function returns false. Otherwise it returns true. + bool Connect(PlayerID receiver, char* passwordData, int passwordDataLength, PlayerID facilitator); + + /// Free internal memory. + void Clear(void); + + /// \internal For plugin handling + virtual void OnAttach(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void Update(RakPeerInterface *peer); + /// \internal For plugin handling + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + /// \internal For plugin handling + virtual void OnDisconnect(RakPeerInterface *peer); + /// \internal For plugin handling + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + + struct ConnectionRequest + { + // Used by all + // 0 means unset. non-zero means send a connection request or offline message at that time. + // If the sender is set, then we send an offline message. If the reciever is set, we send a connection request. + RakNetTime nextActionTime; + + // Used by sender and facilitator + bool facilitatingConnection; + PlayerID receiver; + // Used to remove old connection Requests + RakNetTime timeoutTime; + + // Used only by sender + PlayerID facilitator; + char* passwordData; + int passwordDataLength; + + // Used only by facilitator + PlayerID sender; + unsigned char pingCount; + }; +protected: + void OnPunchthroughRequest(RakPeerInterface *peer, Packet *packet); + void OnConnectAtTime(RakPeerInterface *peer, Packet *packet); + void OnSendOfflineMessageAtTime(RakPeerInterface *peer, Packet *packet); + PluginReceiveResult RemoveRequestByFacilitator(PlayerID playerId); + + bool allowFacilitation; + RakPeerInterface *rakPeer; + DataStructures::List connectionRequestList; +}; + +#endif diff --git a/raknet/NetworkIDGenerator.cpp b/raknet/NetworkIDGenerator.cpp new file mode 100644 index 0000000..70984db --- /dev/null +++ b/raknet/NetworkIDGenerator.cpp @@ -0,0 +1,291 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "NetworkIDGenerator.h" + +#ifdef _COMPATIBILITY_1 +#elif defined(_WIN32) +#include // alloca +#elif defined(_COMPATIBILITY_2) +#include "Compatibility2Includes.h" +#else +#include // alloca +#endif + +#include + +// Note you will need to save and load this if your game supports saving and loading so you start at the same index you left off. +// If you don't do this you can overwrite indices +unsigned short NetworkIDGenerator::staticItemID = 0; +DataStructures::AVLBalancedBinarySearchTree NetworkIDGenerator::IDTree; +PlayerID NetworkIDGenerator::externalPlayerId=UNASSIGNED_PLAYER_ID; + +bool NetworkIDNode::operator==( const NetworkIDNode& right ) const +{ + if ( networkID == right.networkID ) + return !0; + + return 0; +} + +bool NetworkIDNode::operator > ( const NetworkIDNode& right ) const +{ + if ( networkID > right.networkID ) + return !0; + + return 0; +} + +bool NetworkIDNode::operator < ( const NetworkIDNode& right ) const +{ + if ( networkID < right.networkID ) + return !0; + + return 0; +} + +NetworkIDNode::NetworkIDNode() +{ + object = 0; +} + +NetworkIDNode::NetworkIDNode( NetworkID _networkID, NetworkIDGenerator *_object ) +{ + networkID = _networkID; + object = _object; +} + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +NetworkIDGenerator::NetworkIDGenerator() +{ + callGenerationCode=true; + networkID=UNASSIGNED_NETWORK_ID; + parent=0; +} + +//------------------------------------------------------------------------------------- + + +NetworkIDGenerator::~NetworkIDGenerator() +{ + if (networkID!=UNASSIGNED_NETWORK_ID) + { + NetworkIDNode * object = NetworkIDGenerator::IDTree.GetPointerToNode( NetworkIDNode( ( networkID ), 0 ) ); + if ( object->object == this ) + IDTree.Del( NetworkIDNode( object->networkID, 0 ) ); + } +} + +////////////////////////////////////////////////////////////////////// +// Public Methods +////////////////////////////////////////////////////////////////////// + +NetworkID NetworkIDGenerator::GetNetworkID( void ) +{ + if (callGenerationCode && IsNetworkIDAuthority()) + { + GenerateID(); + callGenerationCode=false; + } + + return networkID; +}; + +//------------------------------------------------------------------------------------- + +unsigned short NetworkIDGenerator::GetStaticNetworkID( void ) +{ + return staticItemID; +} + +//------------------------------------------------------------------------------------- + +void NetworkIDGenerator::SetStaticNetworkID( unsigned short i ) +{ + staticItemID = i; +} + +//------------------------------------------------------------------------------------- +void NetworkIDGenerator::SetExternalPlayerID(PlayerID playerId) +{ + assert(playerId!=UNASSIGNED_PLAYER_ID); + externalPlayerId=playerId; +} +//------------------------------------------------------------------------------------- +PlayerID NetworkIDGenerator::GetExternalPlayerID(void) +{ + return externalPlayerId; +} +//------------------------------------------------------------------------------------- + +bool NetworkIDGenerator::RequiresSetParent(void) const +{ + return false; +} + +//------------------------------------------------------------------------------------- + +void NetworkIDGenerator::SetNetworkID( NetworkID id ) +{ + callGenerationCode=false; + + if ( id == UNASSIGNED_NETWORK_ID ) + { + // puts("Warning: NetworkIDGenerator passed UNASSIGNED_NETWORK_ID. SetID ignored"); + return ; + } + + if ( networkID == id ) + { + // printf("NetworkIDGenerator passed %i which already exists in the tree. SetID ignored", id); + return ; + } + + NetworkIDNode* collision = NetworkIDGenerator::IDTree.GetPointerToNode( NetworkIDNode( ( id ), 0 ) ); + + if ( collision ) // Tree should have only unique values. The new value is already in use. + { + //printf("Warning: NetworkIDGenerator::SetID passed %i, which has an existing node in the tree. Old node removed, which will cause the item pointed to to be inaccessible to the network", id); + IDTree.Del( NetworkIDNode( collision->networkID, collision->object ) ); + } + + if ( networkID == UNASSIGNED_NETWORK_ID ) // Object has not had an ID assigned so does not already exist in the tree + { + networkID = id; + IDTree.Add( NetworkIDNode( networkID, this ) ); + } + else // Object already exists in the tree and has an assigned ID + { + IDTree.Del( NetworkIDNode( networkID, this ) ); // Delete the node with whatever ID the existing object is using + networkID = id; + IDTree.Add( NetworkIDNode( networkID, this ) ); + } +} + +//------------------------------------------------------------------------------------- +void NetworkIDGenerator::SetParent( void *_parent ) +{ + parent=_parent; + +#ifdef _DEBUG + // Avoid duplicate parents in the tree + unsigned size = IDTree.Size(); + NetworkIDNode *nodeArray; + + bool usedAlloca=false; + #if !defined(_COMPATIBILITY_1) + if (sizeof(NetworkIDNode) * size < MAX_ALLOCA_STACK_ALLOCATION) + { + nodeArray = (NetworkIDNode*) alloca(sizeof(NetworkIDNode) * size); + usedAlloca=true; + } + else + #endif + nodeArray = new NetworkIDNode[size]; + + + IDTree.DisplayBreadthFirstSearch( nodeArray ); + unsigned i; + for (i=0; i < size; i++) + { + // If this assert hits then this _parent is already in the tree. Classes instance should never contain more than one NetworkIDGenerator + assert(nodeArray->object->GetParent()!=parent); + } + + if (usedAlloca==false) + delete [] nodeArray; + +#endif +} + +//------------------------------------------------------------------------------------- +void* NetworkIDGenerator::GetParent( void ) const +{ + return parent; +} +//------------------------------------------------------------------------------------- +void NetworkIDGenerator::GenerateID(void) +{ + assert(IsNetworkIDAuthority()); + + NetworkIDNode* collision; + do + { + networkID.localSystemId=staticItemID++; + if (NetworkID::peerToPeerMode) + { + // If this assert hits you forgot to call SetExternalPlayerID + assert(externalPlayerId!=UNASSIGNED_PLAYER_ID); + networkID.playerId=externalPlayerId; + } + collision = NetworkIDGenerator::IDTree.GetPointerToNode( NetworkIDNode( ( networkID ), 0 ) ); + } + while ( collision ); + + IDTree.Add( NetworkIDNode( networkID, this ) ); +} + +//------------------------------------------------------------------------------------- +void* NetworkIDGenerator::GET_BASE_OBJECT_FROM_ID( NetworkID x ) +{ + if ( x == UNASSIGNED_NETWORK_ID ) + return 0; + + NetworkIDNode *n = NetworkIDGenerator::IDTree.GetPointerToNode( NetworkIDNode( ( x ), 0 ) ); + + if ( n ) + { + return n->object; + } + + return 0; +} +//------------------------------------------------------------------------------------- +void* NetworkIDGenerator::GET_OBJECT_FROM_ID( NetworkID x ) +{ + NetworkIDGenerator *object = (NetworkIDGenerator *) GET_BASE_OBJECT_FROM_ID( x ); + if (object) + { + if (object->GetParent()) + { + return object->GetParent(); + } + else + { +#ifdef _DEBUG + // If this assert hit then this object requires a call to SetParent and it never got one. + assert(object->RequiresSetParent()==false); +#endif + return object; + } + } + + return 0; +} +//------------------------------------------------------------------------------------- +void* GET_OBJECT_FROM_ID( NetworkID x ) +{ + return NetworkIDGenerator::GET_OBJECT_FROM_ID( x ); +} + + +////////////////////////////////////////////////////////////////////// +// EOF +////////////////////////////////////////////////////////////////////// diff --git a/raknet/NetworkIDGenerator.h b/raknet/NetworkIDGenerator.h new file mode 100644 index 0000000..b9074d7 --- /dev/null +++ b/raknet/NetworkIDGenerator.h @@ -0,0 +1,134 @@ +/// \file +/// \brief A class you can derive from to make it easier to represent every networked object with an integer. This way you can refer to objects over the network. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#if !defined(__NETWORK_ID_GENERATOR) +#define __NETWORK_ID_GENERATOR + +#include "DS_BinarySearchTree.h" +#include "NetworkTypes.h" +#include "Export.h" + +class NetworkIDGenerator; + +/// \internal +/// \brief A node in the AVL tree that holds the mapping between NetworkID and pointers. +struct RAK_DLL_EXPORT NetworkIDNode +{ + NetworkID networkID; + NetworkIDGenerator *object; + NetworkIDNode(); + NetworkIDNode( NetworkID _networkID, NetworkIDGenerator *_object ); + bool operator==( const NetworkIDNode& right ) const; + bool operator > ( const NetworkIDNode& right ) const; + bool operator < ( const NetworkIDNode& right ) const; +}; + +/// \brief Unique shared ids for each object instance +/// +/// A class you can derive from to make it easier to represent every networked object with an integer. This way you can refer to objects over the network. +/// One system should return true for IsNetworkIDAuthority() and the rest should return false. When an object needs to be created, have the the one system create the object. +/// Then have that system send a message to all other systems, and include the value returned from GetNetworkID() in that packet. All other systems should then create the same +/// class of object, and call SetNetworkID() on that class with the NetworkID in the packet. +/// \see the manual for more information on this. +class RAK_DLL_EXPORT NetworkIDGenerator +{ +public: + + /// Constructor. NetworkIDs, if IsNetworkIDAuthority() is true, are created here. + NetworkIDGenerator(); + + /// Destructor. Used NetworkIDs, if any, are freed here. + virtual ~NetworkIDGenerator(); + + /// Returns the NetworkID that you can use to refer to this object over the network. + /// \retval UNASSIGNED_NETWORK_ID UNASSIGNED_NETWORK_ID is returned IsNetworkIDAuthority() is false and SetNetworkID() was not previously called. This is also returned if you call this function in the constructor. + /// \retval 0-65534 Any other value is a valid NetworkID. NetworkIDs start at 0 and go to 65534, wrapping at that point. + virtual NetworkID GetNetworkID( void ); + + /// Sets the NetworkID for this instance. Usually this is called by the clients and determined from the servers. However, if you save multiplayer games you would likely use + /// This on load as well. + virtual void SetNetworkID( NetworkID id ); + + /// Your class does not have to derive from NetworkIDGenerator, although that is the easiest way to implement this. + /// If you want this to be a member object of another class, rather than inherit, then call SetParent() with a pointer to the parent class instance. + /// GET_OBJECT_FROM_ID will then return the parent rather than this instance. + virtual void SetParent( void *_parent ); + + /// Return what was passed to SetParent + /// \return The value passed to SetParent, or 0 if it was never called. + virtual void* GetParent( void ) const; + + /// This AVL tree holds the pointer to NetworkID mappings + static DataStructures::AVLBalancedBinarySearchTree IDTree; + + /// These function is only meant to be used when saving games as you + /// should save the HIGHEST value staticItemID has achieved upon save + /// and reload it upon load. Save AFTER you've created all the items + /// derived from this class you are going to create. + /// \return the HIGHEST Object Id currently used + static unsigned short GetStaticNetworkID( void ); + + /// These function is only meant to be used when loading games. Load + /// BEFORE you create any new objects that are not SetIDed based on + /// the save data. + /// \param[in] i the highest number of NetworkIDGenerator reached. + static void SetStaticNetworkID( unsigned short i ); + + /// For every group of systems, one system needs to be responsible for creating unique IDs for all objects created on all systems. + /// This way, systems can send that id in packets to refer to objects (you can't send pointers because the memory allocations may be different). + /// In a client/server enviroment, the system that creates unique IDs would be the server. + /// return true if this system is responsible for creating unique IDs (probably the server), return true. Otherwise return false. + virtual bool IsNetworkIDAuthority(void) const=0; + + /// Necessary for peer to peer, as NetworkIDs are then composed of your external player Id (doesn't matter which, as long as unique) + /// plus the usual object ID number. + /// Get this from RakPeer::GetExternalPlayerID) one time, the first time you make a connection. + /// \param[in] playerId Your external playerID + static void SetExternalPlayerID(PlayerID playerId); + static PlayerID GetExternalPlayerID(void); + + /// Overload this function and return true if you require that SetParent is called before this object is used. + /// This is a safety check you should do this if you want this to be + /// a member object of another class rather than derive from this class. + virtual bool RequiresSetParent(void) const; + + /// If you use a parent, returns this instance rather than the parent object. + static void* GET_BASE_OBJECT_FROM_ID( NetworkID x ); + + /// Returns the parent object, or this instance if you don't use a parent. + static void* GET_OBJECT_FROM_ID( NetworkID x ); + +protected: + + /// The network ID of this object + NetworkID networkID; + + /// The parent set by SetParent() + void *parent; + + /// Internal function to generate an ID when needed. This is deferred until needed and is not called from the constructor. + void GenerateID(void); + + /// This is crap but is necessary because virtual functions don't work in the constructor + bool callGenerationCode; + +private: + static PlayerID externalPlayerId; + static unsigned short staticItemID; +}; + +#endif diff --git a/raknet/NetworkTypes.cpp b/raknet/NetworkTypes.cpp index 83e700e..60f9b3a 100644 --- a/raknet/NetworkTypes.cpp +++ b/raknet/NetworkTypes.cpp @@ -1,8 +1,181 @@ -// TODO: Implement NetworkTypes.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "NetworkTypes.h" +#include +#include + +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib +// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly +#include +#include // itoa +#else +#include +#include +#include +#endif + +// Fast itoa from http://www.jb.man.ac.uk/~slowe/cpp/itoa.html for Linux since it seems like Linux doesn't support this function. +// I modified it to remove the std dependencies. +char* my_itoa( int value, char* result, int base ) { + // check that the base if valid + if (base < 2 || base > 16) { *result = 0; return result; } + char* out = result; + int quotient = value; + + int absQModB; + + do { + // KevinJ - get rid of this dependency + //*out = "0123456789abcdef"[ std::abs( quotient % base ) ]; + absQModB=quotient % base; + if (absQModB < 0) + absQModB=-absQModB; + *out = "0123456789abcdef"[ absQModB ]; + ++out; + quotient /= base; + } while ( quotient ); + + // Only apply negative sign for base 10 + if ( value < 0 && base == 10) *out++ = '-'; + + // KevinJ - get rid of this dependency + // std::reverse( result, out ); + *out = 0; + + // KevinJ - My own reverse code + char *start = result; + char temp; + out--; + while (start < out) + { + temp=*start; + *start=*out; + *out=temp; + start++; + out--; + } + + return result; +} + +// Defaults to not in peer to peer mode for NetworkIDs. This only sends the localSystemId portion in the BitStream class +// This is what you want for client/server, where the server assigns all NetworkIDs and it is unnecessary to transmit the full structure. +// For peer to peer, this will transmit the playerId of the system that created the object in addition to localSystemId. This allows +// Any system to create unique ids locally. +// All systems must use the same value for this variable. +bool RAK_DLL_EXPORT NetworkID::peerToPeerMode=false; bool PlayerID::operator==( const PlayerID& right ) const { return binaryAddress == right.binaryAddress && port == right.port; } + +bool PlayerID::operator!=( const PlayerID& right ) const +{ + return binaryAddress != right.binaryAddress || port != right.port; +} + +bool PlayerID::operator>( const PlayerID& right ) const +{ + return ( ( binaryAddress > right.binaryAddress ) || ( ( binaryAddress == right.binaryAddress ) && ( port > right.port ) ) ); +} + +bool PlayerID::operator<( const PlayerID& right ) const +{ + return ( ( binaryAddress < right.binaryAddress ) || ( ( binaryAddress == right.binaryAddress ) && ( port < right.port ) ) ); +} +char *PlayerID::ToString(bool writePort) const +{ +#ifdef _COMPATIBILITY_1 + return ""; +#else + static char str[22]; + in_addr in; + in.s_addr = binaryAddress; + strcpy(str, inet_ntoa( in )); + if (writePort) + { + strcat(str, ":"); +#if (defined(__GNUC__) || defined(__GCCXML__)) + my_itoa(port, str+strlen(str), 10); +#else + _itoa(port, str+strlen(str), 10); +#endif + } + + return (char*) str; +#endif +} +void PlayerID::SetBinaryAddress(const char *str) +{ +#ifdef _COMPATIBILITY_1 + binaryAddress=UNASSIGNED_PLAYER_ID.binaryAddress; +#else + binaryAddress=inet_addr(str); +#endif +} + +NetworkID& NetworkID::operator = ( const NetworkID& input ) +{ + playerId = input.playerId; + localSystemId = input.localSystemId; + return *this; +} + +bool NetworkID::operator==( const NetworkID& right ) const +{ + if (NetworkID::peerToPeerMode) + return playerId == right.playerId && localSystemId == right.localSystemId; + else + return localSystemId==right.localSystemId; +} + +bool NetworkID::operator!=( const NetworkID& right ) const +{ + if (NetworkID::peerToPeerMode) + return playerId != right.playerId || localSystemId != right.localSystemId; + else + return localSystemId!=right.localSystemId; +} + +bool NetworkID::operator>( const NetworkID& right ) const +{ + if (NetworkID::peerToPeerMode) + return ( ( playerId > right.playerId ) || ( ( playerId == right.playerId ) && ( localSystemId > right.localSystemId ) ) ); + else + return localSystemId>right.localSystemId; +} + +bool NetworkID::operator<( const NetworkID& right ) const +{ + if (NetworkID::peerToPeerMode) + return ( ( playerId < right.playerId ) || ( ( playerId == right.playerId ) && ( localSystemId < right.localSystemId ) ) ); + else + return localSystemId>3) +#define BYTES_TO_BITS(x) ((x)<<3) + +/// \sa NetworkIDGenerator.h +typedef unsigned char UniqueIDType; +typedef unsigned short PlayerIndex; +typedef unsigned char RPCIndex; +const int MAX_RPC_MAP_SIZE=((RPCIndex)-1)-1; +const int UNDEFINED_RPC_INDEX=((RPCIndex)-1); + +/// First byte of a network message +typedef unsigned char MessageID; // Define __GET_TIME_64BIT if you want to use large types for GetTime (takes more bandwidth when you transmit time though!) // You would want to do this if your system is going to run long enough to overflow the millisecond counter (over a month) @@ -27,19 +60,67 @@ struct RAK_DLL_EXPORT PlayerID ///The port number unsigned short port; + // Return the playerID as a string in the format : + // Note - returns a static string. Not thread-safe or safe for multiple calls per line. + char *ToString(bool writePort=true) const; + + // Sets the binary address part from a string. Doesn't set the port + void SetBinaryAddress(const char *str); + + PlayerID& operator = ( const PlayerID& input ) + { + binaryAddress = input.binaryAddress; + port = input.port; + return *this; + } + bool operator==( const PlayerID& right ) const; + bool operator!=( const PlayerID& right ) const; + bool operator > ( const PlayerID& right ) const; + bool operator < ( const PlayerID& right ) const; }; +struct RAK_DLL_EXPORT NetworkID +{ + // Set this to true to use peer to peer mode for NetworkIDs. + // Obviously the value of this must match on all systems. + // True, and this will write the playerId portion with network sends. Takes more bandwidth, but NetworkIDs can be locally generated + // False, and only localSystemId is used. + static bool peerToPeerMode; + + // In peer to peer, we use both playerId and localSystemId + // In client / server, we only use localSystemId + PlayerID playerId; + unsigned short localSystemId; + + NetworkID& operator = ( const NetworkID& input ); + + static bool IsPeerToPeerMode(void); + static void SetPeerToPeerMode(bool isPeerToPeer); + bool operator==( const NetworkID& right ) const; + bool operator!=( const NetworkID& right ) const; + bool operator > ( const NetworkID& right ) const; + bool operator < ( const NetworkID& right ) const; +}; + +/// Size of PlayerID data +#define PlayerID_Size 6 + /// This represents a user message from another system. struct Packet { - char _gap0[8]; + /// Server only - this is the index into the player array that this playerId maps to + PlayerIndex playerIndex; + + /// The system that send this packet. + PlayerID playerId; /// The length of the data in bytes /// \deprecated You should use bitSize. unsigned int length; - char _gapC[4]; + /// The length of the data in bits + unsigned int bitSize; /// The data from the sender unsigned char* data; @@ -49,17 +130,81 @@ struct Packet bool deleteData; }; +class RakPeerInterface; + +/// All RPC functions have the same parameter list - this structure. struct RPCParameters { - char _gap0; // TODO: RPCParameters + /// The data from the remote system + unsigned char *input; + + /// How many bits long \a input is + unsigned int numberOfBitsOfData; + + /// Which system called this RPC + PlayerID sender; + + /// Which instance of RakPeer (or a derived RakServer or RakClient) got this call + RakPeerInterface *recipient; + + // TODO - RakNet 3.0. Be consistent and have user put ID_TIMESTAMP, rather than do this + // Bug: hasTimestamp was unused. + // bool hasTimestamp; + + /// You can return values from RPC calls by writing them to this BitStream. + /// This is only sent back if the RPC call originally passed a BitStream to receive the reply. + /// If you do so and your send is reliable, it will block until you get a reply or you get disconnected from the system you are sending to, whichever is first. + /// If your send is not reliable, it will block for triple the ping time, or until you are disconnected, or you get a reply, whichever is first. + RakNet::BitStream *replyToSender; }; +/// Index of an unassigned player +const PlayerIndex UNASSIGNED_PLAYER_INDEX = 65535; + /// Index of an invalid PlayerID const PlayerID UNASSIGNED_PLAYER_ID = { 0xFFFFFFFF, 0xFFFF }; +/// Unassigned object ID +const NetworkID UNASSIGNED_NETWORK_ID = +{ + {0xFFFFFFFF, 0xFFFF}, 65535 +}; + +const int PING_TIMES_ARRAY_SIZE = 5; + +/// \brief RPC Function Implementation +/// +/// The Remote Procedure Call Subsystem provide the RPC paradigm to +/// RakNet user. It consists in providing remote function call over the +/// network. A call to a remote function require you to prepare the +/// data for each parameter (using BitStream) for example. +/// +/// Use the following C function prototype for your callbacks +/// @code +/// void functionName(RPCParameters *rpcParms); +/// @endcode +/// If you pass input data, you can parse the input data in two ways. +/// 1. +/// Cast input to a struct (such as if you sent a struct) +/// i.e. MyStruct *s = (MyStruct*) input; +/// Make sure that the sizeof(MyStruct) is equal to the number of bytes passed! +/// 2. +/// Create a BitStream instance with input as data and the number of bytes +/// i.e. BitStream myBitStream(input, (numberOfBitsOfData-1)/8+1) +/// (numberOfBitsOfData-1)/8+1 is how convert from bits to bytes +/// Full example: +/// @code +/// void MyFunc(RPCParameters *rpcParms) {} +/// RakClient *rakClient; +/// REGISTER_AS_REMOTE_PROCEDURE_CALL(rakClient, MyFunc); +/// This would allow MyFunc to be called from the server using (for example) +/// rakServer->RPC("MyFunc", 0, clientID, false); +/// @endcode + + /// \def REGISTER_STATIC_RPC /// \ingroup RAKNET_RPC /// Register a C function as a Remote procedure. @@ -68,7 +213,28 @@ const PlayerID UNASSIGNED_PLAYER_ID = /// \attention 12/01/05 REGISTER_AS_REMOTE_PROCEDURE_CALL renamed to REGISTER_STATIC_RPC. Delete the old name sometime in the future //#pragma deprecated(REGISTER_AS_REMOTE_PROCEDURE_CALL) //#define REGISTER_AS_REMOTE_PROCEDURE_CALL(networkObject, functionName) REGISTER_STATIC_RPC(networkObject, functionName) -#define REGISTER_STATIC_RPC(networkObject, functionName) (networkObject)->RegisterAsRemoteProcedureCall((RPC_##functionName),(functionName)) +#define REGISTER_STATIC_RPC(networkObject, functionName) (networkObject)->RegisterAsRemoteProcedureCall((#functionName),(functionName)) + +/// \def CLASS_MEMBER_ID +/// \ingroup RAKNET_RPC +/// \brief Concatenate two strings + +/// \def REGISTER_CLASS_MEMBER_RPC +/// \ingroup RAKNET_RPC +/// \brief Register a member function of an instantiated object as a Remote procedure call. +/// RPC member Functions MUST be marked __cdecl! +/// \sa ObjectMemberRPC.cpp +/// \b CLASS_MEMBER_ID is a utility macro to generate a unique signature for a class and function pair and can be used for the Raknet functions RegisterClassMemberRPC(...) and RPC(...) +/// \b REGISTER_CLASS_MEMBER_RPC is a utility macro to more easily call RegisterClassMemberRPC +/// \param[in] networkObject Your instance of RakPeer, RakServer, or RakClient +/// \param[in] className The class containing the function +/// \param[in] functionName The name of the function (not in quotes, just the name) +#define CLASS_MEMBER_ID(className, functionName) #className "_" #functionName +#define REGISTER_CLASS_MEMBER_RPC(networkObject, className, functionName) {union {void (__cdecl className::*cFunc)( RPCParameters *rpcParms ); void* voidFunc;}; cFunc=&className::functionName; networkObject->RegisterClassMemberRPC(CLASS_MEMBER_ID(className, functionName),voidFunc);} + +/// \def UNREGISTER_AS_REMOTE_PROCEDURE_CALL +/// \depreciated +/// \brief Only calls UNREGISTER_STATIC_RPC /// \def UNREGISTER_STATIC_RPC /// \ingroup RAKNET_RPC @@ -79,6 +245,15 @@ const PlayerID UNASSIGNED_PLAYER_ID = // 12/01/05 UNREGISTER_AS_REMOTE_PROCEDURE_CALL Renamed to UNREGISTER_STATIC_RPC. Delete the old name sometime in the future //#pragma deprecated(UNREGISTER_AS_REMOTE_PROCEDURE_CALL) //#define UNREGISTER_AS_REMOTE_PROCEDURE_CALL(networkObject,functionName) UNREGISTER_STATIC_RPC(networkObject,functionName) -#define UNREGISTER_STATIC_RPC(networkObject,functionName) (networkObject)->UnregisterAsRemoteProcedureCall((RPC_##functionName)) +#define UNREGISTER_STATIC_RPC(networkObject,functionName) (networkObject)->UnregisterAsRemoteProcedureCall((#functionName)) + +/// \def UNREGISTER_CLASS_INST_RPC +/// \ingroup RAKNET_RPC +/// \brief Unregisters a member function of an instantiated object as a Remote procedure call. +/// \param[in] networkObject The object that manages the function +/// \param[in] className The className that was originally passed to REGISTER_AS_REMOTE_PROCEDURE_CALL +/// \param[in] functionName The function name +#define UNREGISTER_CLASS_MEMBER_RPC(networkObject, className, functionName) (networkObject)->UnregisterAsRemoteProcedureCall((#className "_" #functionName)) #endif + diff --git a/raknet/PacketConsoleLogger.cpp b/raknet/PacketConsoleLogger.cpp new file mode 100644 index 0000000..2ab84a4 --- /dev/null +++ b/raknet/PacketConsoleLogger.cpp @@ -0,0 +1,19 @@ +#include "PacketConsoleLogger.h" +#include "LogCommandParser.h" + +PacketConsoleLogger::PacketConsoleLogger() +{ + logCommandParser=0; +} + +void PacketConsoleLogger::SetLogCommandParser(LogCommandParser *lcp) +{ + logCommandParser=lcp; + if (logCommandParser) + logCommandParser->AddChannel("PacketConsoleLogger"); +} +void PacketConsoleLogger::WriteLog(const char *str) +{ + if (logCommandParser) + logCommandParser->WriteLog("PacketConsoleLogger", str); +} diff --git a/raknet/PacketConsoleLogger.h b/raknet/PacketConsoleLogger.h new file mode 100644 index 0000000..abe34c0 --- /dev/null +++ b/raknet/PacketConsoleLogger.h @@ -0,0 +1,37 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to the log command parser, which can be accessed through Telnet +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __PACKET_CONSOLE_LOGGER_H_ +#define __PACKET_CONSOLE_LOGGER_H_ + +#include "PacketLogger.h" +class LogCommandParser; + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Packetlogger that logs to a remote command console +class PacketConsoleLogger : public PacketLogger +{ +public: + PacketConsoleLogger(); + // Writes to the command parser used for logging, which is accessed through a secondary communication layer (such as Telnet or RakNet) - See ConsoleServer.h + virtual void SetLogCommandParser(LogCommandParser *lcp); + virtual void WriteLog(const char *str); +protected: + LogCommandParser *logCommandParser; +}; + +#endif diff --git a/raknet/PacketEnumerations.h b/raknet/PacketEnumerations.h index 6c15448..6fb0019 100644 --- a/raknet/PacketEnumerations.h +++ b/raknet/PacketEnumerations.h @@ -32,128 +32,185 @@ /// \note All these enumerations should be casted to (unsigned char) before writing them to RakNet::BitStream enum { - // TODO: PacketEnumerations.h is not complete yet - - /// 6: Ping from a connected system. Update timestamps (internal use only) - ID_INTERNAL_PING = 6, - /// 7: Ping from an unconnected system. Reply but do not update timestamps. (internal use only) + // + // RESERVED TYPES - DO NOT CHANGE THESE + // + /// These types are never returned to the user. + /// 0: Ping from a connected system. Update timestamps (internal use only) + ID_INTERNAL_PING, + /// 1: Ping from an unconnected system. Reply but do not update timestamps. (internal use only) ID_PING, - /// 8: Ping from an unconnected system. Only reply if we have open connections. Do not update timestamps. (internal use only) + /// 2: Ping from an unconnected system. Only reply if we have open connections. Do not update timestamps. (internal use only) ID_PING_OPEN_CONNECTIONS, - /// 9: Pong from a connected system. Update timestamps (internal use only) + /// 3: Pong from a connected system. Update timestamps (internal use only) ID_CONNECTED_PONG, - - /// 15: Connecting to a secured server/peer - ID_SECURED_CONNECTION_RESPONSE = 15, - /// 16: Connecting to a secured server/peer + /// 4: Someone asked for our static data (internal use only) + ID_REQUEST_STATIC_DATA, + /// 5: Asking for a new connection (internal use only) + ID_CONNECTION_REQUEST, + /// 6: Connecting to a secured server/peer + ID_SECURED_CONNECTION_RESPONSE, + /// 7: Connecting to a secured server/peer ID_SECURED_CONNECTION_CONFIRMATION, - - /// 19: Server / Client only - The server is broadcasting a random number seed (internal use only) - ID_SET_RANDOM_NUMBER_SEED = 19, - /// 20: Remote procedure call (internal use only) + /// 8: Packet that tells us the packet contains an integer ID to name mapping for the remote system + ID_RPC_MAPPING, + /// 9: A reliable packet to detect lost connections + ID_DETECT_LOST_CONNECTIONS, + /// 10: Offline message so we know when to reset and start a new connection + ID_OPEN_CONNECTION_REQUEST, + /// 11: Offline message response so we know when to reset and start a new connection + ID_OPEN_CONNECTION_REPLY, + /// 12: Remote procedure call (internal use only) ID_RPC, + /// 13: Remote procedure call reply, for RPCs that return data (internal use only) + ID_RPC_REPLY, + /// 14: Server / Client only - The server is broadcasting the pings of all players in the game (internal use only) + ID_BROADCAST_PINGS, + /// 15: Server / Client only - The server is broadcasting a random number seed (internal use only) + ID_SET_RANDOM_NUMBER_SEED, - /// 22: ??? - ID_NEW_INCOMING_CONNECTION_2, + // + // USER TYPES - DO NOT CHANGE THESE + // + // Ordered from most useful to least useful - /// [PEER|SERVER|CLIENT] 29: Sent to the player when a connection request cannot be completed due to inability to connect. - /// Never transmitted. - ID_CONNECTION_ATTEMPT_FAILED = 29, - - /// [PEER|SERVER] 30: A remote system has successfully connected. - ID_NEW_INCOMING_CONNECTION, - - /// [PEER|CLIENT] 31: The system we attempted to connect to is not accepting new connections. - ID_NO_FREE_INCOMING_CONNECTIONS, - - /// [PEER|SERVER|CLIENT] 32: The system specified in Packet::playerID has disconnected from us. For the client, this would mean the server has shutdown. - ID_DISCONNECTION_NOTIFICATION, - - /// [PEER|SERVER|CLIENT] 33: Reliable packets cannot be delivered to the system specifed in Packet::playerID. The connection to that system has been closed. - ID_CONNECTION_LOST, - - /// [PEER|CLIENT] 34: In a client/server environment, our connection request to the server has been accepted. + /// [PEER|CLIENT] 16: In a client/server environment, our connection request to the server has been accepted. ID_CONNECTION_REQUEST_ACCEPTED, - /// [CLIENT|PEER] 35: We preset an RSA public key which does not match what the system we connected to is using. + /// [PEER|SERVER|CLIENT] 17: Sent to the player when a connection request cannot be completed due to inability to connect. + /// Never transmitted. + ID_CONNECTION_ATTEMPT_FAILED, + + /// [PEER|SERVER] 18: A remote system has successfully connected. + ID_NEW_INCOMING_CONNECTION, + + /// [PEER|CLIENT] 19: The system we attempted to connect to is not accepting new connections. + ID_NO_FREE_INCOMING_CONNECTIONS, + + /// [PEER|SERVER|CLIENT] 20: The system specified in Packet::playerID has disconnected from us. For the client, this would mean the server has shutdown. + ID_DISCONNECTION_NOTIFICATION, + + /// [PEER|SERVER|CLIENT] 21: Reliable packets cannot be delivered to the system specifed in Packet::playerID. The connection to that system has been closed. + ID_CONNECTION_LOST, + + /// [CLIENT|PEER] 22: We preset an RSA public key which does not match what the system we connected to is using. ID_RSA_PUBLIC_KEY_MISMATCH, - /// [PEER|CLIENT] 36: We are banned from the system we attempted to connect to. + /// [PEER|CLIENT] 23: We are banned from the system we attempted to connect to. ID_CONNECTION_BANNED, - /// [PEER|CLIENT] 37: The remote system is using a password and has refused our connection because we did not set the correct password. + /// [PEER|CLIENT] 24: The remote system is using a password and has refused our connection because we did not set the correct password. ID_INVALID_PASSWORD, - /// [PEER|SERVER|CLIENT] 38: A packet has been tampered with in transit. The sender is contained in Packet::playerID. + /// [PEER|SERVER|CLIENT] 25: A packet has been tampered with in transit. The sender is contained in Packet::playerID. /// Never transmitted. ID_MODIFIED_PACKET, - /// [PEER] 39: Pong from an unconnected system. First byte is ID_PONG, second sizeof(RakNetTime) bytes is the ping, following bytes is system specific enumeration data. - ID_PONG, - - /// [PEER|SERVER|CLIENT] 40: The four bytes following this byte represent an unsigned int which is automatically modified by the difference in system times between the sender and the recipient. Requires that you call StartOccasionalPing. + /// [PEER|SERVER|CLIENT] 26: The four bytes following this byte represent an unsigned int which is automatically modified by the difference in system times between the sender and the recipient. Requires that you call StartOccasionalPing. ID_TIMESTAMP, - /// [PEER|SERVER|CLIENT] 41: We got a bitstream containing static data. You can now read this data. This packet is transmitted automatically on connections, and can also be manually sent. + /// [PEER] 27: Pong from an unconnected system. First byte is ID_PONG, second sizeof(RakNetTime) bytes is the ping, following bytes is system specific enumeration data. + ID_PONG, + + /// [PEER|SERVER|CLIENT] 28: We got a bitstream containing static data. You can now read this data. This packet is transmitted automatically on connections, and can also be manually sent. ID_RECEIVED_STATIC_DATA, - /// [CLIENT] 42: In a client/server environment, a client other than ourselves has disconnected gracefully. Packet::playerID is modified to reflect the playerID of this client. + /// [CLIENT] 29: In a client/server environment, a client other than ourselves has disconnected gracefully. Packet::playerID is modified to reflect the playerID of this client. ID_REMOTE_DISCONNECTION_NOTIFICATION, - /// [CLIENT] 43: In a client/server environment, a client other than ourselves has been forcefully dropped. Packet::playerID is modified to reflect the playerID of this client. + /// [CLIENT] 30: In a client/server environment, a client other than ourselves has been forcefully dropped. Packet::playerID is modified to reflect the playerID of this client. ID_REMOTE_CONNECTION_LOST, - /// [FILELIST] 47: - ID_FILE_LIST_TRANSFER_HEADER = 47, + /// [CLIENT] 31: In a client/server environment, a client other than ourselves has connected. Packet::playerID is modified to reflect the playerID of this client. + ID_REMOTE_NEW_INCOMING_CONNECTION, - /// [FILELIST] 48: + /// [CLIENT] 32: On our initial connection to the server, we are told of every other client in the game. Packet::playerID is modified to reflect the playerID of this client. + ID_REMOTE_EXISTING_CONNECTION, + + /// [CLIENT] - 33: Got the data for another client + ID_REMOTE_STATIC_DATA, + + /// [FILELIST] 34: + ID_FILE_LIST_TRANSFER_HEADER, + + /// [FILELIST] 35: ID_FILE_LIST_TRANSFER_FILE, - /// [Delta Directory transfer] 49: + /// [Delta Directory transfer] 36: ID_DDT_DOWNLOAD_REQUEST, - /// [PEER|SERVER|CLIENT] 55: Inform a remote system of our IP/Port. - ID_ADVERTISE_SYSTEM = 55, + /// [MASTERSERVER] 37: Request to the master server for the list of servers that contain at least one of the specified keys + ID_QUERY_MASTER_SERVER, + /// [MASTERSERVER] 38: Remove a game server from the master server. + ID_MASTER_SERVER_DELIST_SERVER, + /// [MASTERSERVER|MASTERCLIENT] 39: Add or update the information for a server. + ID_MASTER_SERVER_UPDATE_SERVER, + /// [MASTERSERVER|MASTERCLIENT] 40: Add or set the information for a server. + ID_MASTER_SERVER_SET_SERVER, + /// [MASTERSERVER|MASTERCLIENT] 41: This message indicates a game client is connecting to a game server, and is relayed through the master server. + ID_RELAYED_CONNECTION_NOTIFICATION, - /// [RakNetTransport] 56 + /// [PEER|SERVER|CLIENT] 42: Inform a remote system of our IP/Port. + ID_ADVERTISE_SYSTEM, + + /// [RakNetTransport] 43 ID_TRANSPORT_STRING, - /// [ConnectionGraph] 57 - ID_CONNECTION_GRAPH_REQUEST, - /// [ConnectionGraph] 58 - ID_CONNECTION_GRAPH_REPLY, - /// [ConnectionGraph] 59 - ID_CONNECTION_GRAPH_UPDATE, - /// [ConnectionGraph] 60 - ID_CONNECTION_GRAPH_NEW_CONNECTION, - /// [ConnectionGraph] 61 - ID_CONNECTION_GRAPH_CONNECTION_LOST, - /// [ConnectionGraph] 62 - ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION, - - /// [ReplicaManager] 63 + /// [ReplicaManager] 44 ID_REPLICA_MANAGER_CONSTRUCTION, - /// [ReplicaManager] 64 + /// [ReplicaManager] 45 ID_REPLICA_MANAGER_DESTRUCTION, - /// [ReplicaManager] 65 + /// [ReplicaManager] 46 ID_REPLICA_MANAGER_SCOPE_CHANGE, - /// [ReplicaManager] 66 + /// [ReplicaManager] 47 ID_REPLICA_MANAGER_SERIALIZE, - /// [ReplicaManager] 67 + /// [ReplicaManager] 48 ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE, - /// [Router] 68 + /// [ConnectionGraph] 49 + ID_CONNECTION_GRAPH_REQUEST, + /// [ConnectionGraph] 50 + ID_CONNECTION_GRAPH_REPLY, + /// [ConnectionGraph] 51 + ID_CONNECTION_GRAPH_UPDATE, + /// [ConnectionGraph] 52 + ID_CONNECTION_GRAPH_NEW_CONNECTION, + /// [ConnectionGraph] 53 + ID_CONNECTION_GRAPH_CONNECTION_LOST, + /// [ConnectionGraph] 54 + ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION, + + /// [Router] 55 ID_ROUTE_AND_MULTICAST, - /// [NAT Punchthrough] - ID_NAT_PUNCHTHROUGH_REQUEST = 81, + /// [RakVoice] 56 + ID_RAKVOICE_OPEN_CHANNEL_REQUEST, + /// [RakVoice] 57 + ID_RAKVOICE_OPEN_CHANNEL_REPLY, + /// [RakVoice] 58 + ID_RAKVOICE_CLOSE_CHANNEL, + /// [RakVoice] 59 + ID_RAKVOICE_DATA, - /// [NAT Punchthrough] Returned to user. PlayerID binary address / port is written to the stream - ID_NAT_TARGET_CONNECTION_LOST, + /// [Autopatcher] 60 + ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE, + ID_AUTOPATCHER_CREATION_LIST, + ID_AUTOPATCHER_DELETION_LIST, + ID_AUTOPATCHER_GET_PATCH, + ID_AUTOPATCHER_PATCH_LIST, + ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR, // Returned to user + ID_AUTOPATCHER_FINISHED, + ID_AUTOPATCHER_RESTART_APPLICATION, // Returned to user + + /// [NAT Punchthrough] + ID_NAT_PUNCHTHROUGH_REQUEST, /// [NAT Punchthrough] Returned to user. PlayerID binary address / port is written to the stream ID_NAT_TARGET_NOT_CONNECTED, + /// [NAT Punchthrough] Returned to user. PlayerID binary address / port is written to the stream + ID_NAT_TARGET_CONNECTION_LOST, + // [NAT Punchthrough] ID_NAT_CONNECT_AT_TIME, @@ -180,24 +237,13 @@ enum // RakPeer - Downloading a large message. Format is ID_DOWNLOAD_PROGRESS (MessageID), partCount (unsigned int), partTotal (unsigned int), partLength (unsigned int), first part data (length <= MAX_MTU_SIZE) ID_DOWNLOAD_PROGRESS, - - /// - /// SA-MP 0.3.7 Packet IDs - /// - - ID_VEHICLE_SYNC = 200, - ID_RCON_COMMAND, - ID_RCON_RESPONCE, - ID_AIM_SYNC, - ID_WEAPONS_UPDATE, - ID_STATS_UPDATE, - ID_WEAPON_SHOT_SYNC, - ID_PLAYER_SYNC, - // 208 - ID_UNOCCUPIED_SYNC = 209, - ID_TRAILER_SYNC, - ID_PASSENGER_SYNC, - ID_SPECTATOR_SYNC, + + /// Depreciated + ID_RESERVED9, + // For the user to use. Start your first enumeration at this value. + ID_USER_PACKET_ENUM, + //------------------------------------------------------------------------------------------------------------- + }; #endif diff --git a/raknet/PacketFileLogger.cpp b/raknet/PacketFileLogger.cpp new file mode 100644 index 0000000..6692c20 --- /dev/null +++ b/raknet/PacketFileLogger.cpp @@ -0,0 +1,22 @@ +#include "PacketFileLogger.h" +#include "GetTime.h" + +PacketFileLogger::~PacketFileLogger() +{ + fclose(packetLogFile); +} +void PacketFileLogger::OnAttach(RakPeerInterface *peer) +{ + PacketLogger::OnAttach(peer); + + // Open file for writing + char filename[256]; + sprintf(filename, "PacketLog%i.csv", RakNet::GetTime()); + packetLogFile = fopen(filename, "wt"); + LogHeader(); +} + +void PacketFileLogger::WriteLog(const char *str) +{ + fputs(str, packetLogFile); +} diff --git a/raknet/PacketFileLogger.h b/raknet/PacketFileLogger.h new file mode 100644 index 0000000..0bbbe04 --- /dev/null +++ b/raknet/PacketFileLogger.h @@ -0,0 +1,37 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to a file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __PACKET_FILE_LOGGER_H_ +#define __PACKET_FILE_LOGGER_H_ + +#include "PacketLogger.h" + +#include + +/// \ingroup PACKETLOGGER_GROUP +/// \brief Packetlogger that outputs to a file +class PacketFileLogger : public PacketLogger +{ +public: + virtual ~PacketFileLogger(); + virtual void OnAttach(RakPeerInterface *peer); + virtual void WriteLog(const char *str); +protected: + FILE *packetLogFile; +}; + +#endif diff --git a/raknet/PacketLogger.cpp b/raknet/PacketLogger.cpp new file mode 100644 index 0000000..979ec10 --- /dev/null +++ b/raknet/PacketLogger.cpp @@ -0,0 +1,319 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "PacketLogger.h" +#include "BitStream.h" +#include "DS_List.h" +#include "InternalPacket.h" +#include "RakPeerInterface.h" +#include "PacketEnumerations.h" +#include "StringCompressor.h" +#include "RPCMap.h" +#include "GetTime.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +PacketLogger::PacketLogger() +{ + rakPeer=0; + printId=true; + printAcks=true; +} +PacketLogger::~PacketLogger() +{ +} +void PacketLogger::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; +} +void PacketLogger::OnDirectSocketSend(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID) +{ + char str[256]; + PlayerID localPlayerId; + localPlayerId = rakPeer->GetInternalID(); + + if (printId==false) + { + sprintf(str, "Snd,Raw, NIL, NIL,%5i,%5i,%i,%u:%i,%u:%i\n", data[0], bitsUsed,RakNet::GetTime(), + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + { + sprintf(str, "Snd,Raw,NIL,NIL,%s,%i,%i,%u:%i,%u:%i\n",IDTOString(data[0]), bitsUsed,RakNet::GetTime(), + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + WriteLog(str); +} + +void PacketLogger::LogHeader(void) +{ + WriteLog("S|R,Typ,Pckt#,Frm #,PktID,BitLn,Time ,Local IP:Port ,RemoteIP:Port\n"); +} +void PacketLogger::OnDirectSocketReceive(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID) +{ + char str[256]; + PlayerID localPlayerId; + localPlayerId = rakPeer->GetInternalID(); + if (printId==false) + { + sprintf(str, "Rcv,Raw, NIL, NIL,%5i,%5i,%i,%u:%i,%u:%i\n", data[0], bitsUsed,RakNet::GetTime(), + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + { + sprintf(str, "Rcv,Raw,NIL,NIL,%s,%i,%i,%u:%i,%u:%i\n", IDTOString(data[0]), bitsUsed,RakNet::GetTime(), + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + + WriteLog(str); +} +void PacketLogger::OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, PlayerID remoteSystemID, RakNetTime time, bool isSend) +{ + char str[256]; + char sendType[4]; + PlayerID localPlayerId; + localPlayerId = rakPeer->GetInternalID(); + + if (isSend) + strcpy(sendType, "Snd"); + else + strcpy(sendType, "Rcv"); + + // TODO - put this back in a different form + /* + if (internalPacket->isAcknowledgement) + { + if (printAcks) + { + if (printId==false) + sprintf(str, "%s,Ack,%5i,%5i, NIL, 1,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + else + sprintf(str, "%s,Ack,%i,%i,NIL,1,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + str[0]=0; + } + else + */ + { + if (internalPacket->data[0]==ID_TIMESTAMP && internalPacket->data[sizeof(unsigned char)+sizeof(RakNetTime)]!=ID_RPC) + { + if (printId==false) + { + sprintf(str, "%s,Tms,%5i,%5i,%5i,%5i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + internalPacket->data[1+sizeof(int)], internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + { + sprintf(str, "%s,Tms,%i,%i,%s,%i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + IDTOString(internalPacket->data[1+sizeof(int)]), internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + + } + else if (internalPacket->data[0]==ID_RPC || (internalPacket->dataBitLength>(sizeof(unsigned char)+sizeof(RakNetTime))*8 && internalPacket->data[0]==ID_TIMESTAMP && internalPacket->data[sizeof(unsigned char)+sizeof(RakNetTime)]==ID_RPC)) + { + bool hasTimestamp; + unsigned int bitsOfData; + bool nameIsEncoded; + unsigned char uniqueIdentifier[256]; + RPCIndex rpcIndex; + RPCMap *rpcMap; + RakNet::BitStream rpcDecode(internalPacket->data, BITS_TO_BYTES(internalPacket->dataBitLength), false); + rpcDecode.IgnoreBits(8); + if (internalPacket->data[0]==ID_TIMESTAMP) + rpcDecode.IgnoreBits(sizeof(unsigned char)+sizeof(RakNetTime)); + rpcDecode.Read(nameIsEncoded); + if (nameIsEncoded) + { + stringCompressor->DecodeString((char*)uniqueIdentifier, 256, &rpcDecode); + } + else + { + rpcDecode.ReadCompressed( rpcIndex ); + RPCNode *rpcNode; + rpcMap = rakPeer->GetRPCMap(isSend==true ? remoteSystemID : UNASSIGNED_PLAYER_ID); + if (rpcMap) + rpcNode = rpcMap->GetNodeFromIndex(rpcIndex); + else + rpcNode=0; + + if (rpcMap && rpcNode) + strcpy((char*)uniqueIdentifier, rpcNode->uniqueIdentifier); + else + strcpy((char*)uniqueIdentifier, "[UNKNOWN]"); + } + rpcDecode.Read(hasTimestamp); + rpcDecode.ReadCompressed(bitsOfData); + + if (hasTimestamp) + sprintf(str, "%s,RpT,%5i,%5i,%s,%5i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + uniqueIdentifier, internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + else + sprintf(str, "%s,Rpc,%5i,%5i,%s,%5i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + uniqueIdentifier, internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + { + if (printId==false) + { + sprintf(str, "%s,Nrm,%5i,%5i,%5i,%5i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + internalPacket->data[0], internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + else + { + sprintf(str, "%s,Nrm,%i,%i,%s,%i,%i,%u:%i,%u:%i\n",sendType, internalPacket->messageNumber,frameNumber, + IDTOString(internalPacket->data[0]), internalPacket->dataBitLength,time, + localPlayerId.binaryAddress, localPlayerId.port, remoteSystemID.binaryAddress, remoteSystemID.port); + } + } + } + + WriteLog(str); +} +void PacketLogger::WriteLog(const char *str) +{ + printf("%s", str); +} +void PacketLogger::SetPrintID(bool print) +{ + printId=print; +} +void PacketLogger::SetPrintAcks(bool print) +{ + printAcks=print; +} +char* PacketLogger::BaseIDTOString(unsigned char Id) +{ + if (Id >= ID_RESERVED9) + return 0; + + const char *IDTable[(int)ID_RESERVED9]= + { + "ID_INTERNAL_PING", + "ID_PING", + "ID_PING_OPEN_CONNECTIONS", + "ID_CONNECTED_PONG", + "ID_REQUEST_STATIC_DATA", + "ID_CONNECTION_REQUEST", + "ID_SECURED_CONNECTION_RESPONSE", + "ID_SECURED_CONNECTION_CONFIRMATION", + "ID_RPC_MAPPING", + "ID_DETECT_LOST_CONNECTIONS", + "ID_OPEN_CONNECTION_REQUEST", + "ID_OPEN_CONNECTION_REPLY", + "ID_RPC", + "ID_RPC_REPLY", + "ID_BROADCAST_PINGS", + "ID_SET_RANDOM_NUMBER_SEED", + "ID_CONNECTION_REQUEST_ACCEPTED", + "ID_CONNECTION_ATTEMPT_FAILED", + "ID_NEW_INCOMING_CONNECTION", + "ID_NO_FREE_INCOMING_CONNECTIONS", + "ID_DISCONNECTION_NOTIFICATION", + "ID_CONNECTION_LOST", + "ID_RSA_PUBLIC_KEY_MISMATCH", + "ID_CONNECTION_BANNED", + "ID_INVALID_PASSWORD", + "ID_MODIFIED_PACKET", + "ID_TIMESTAMP", + "ID_PONG", + "ID_RECEIVED_STATIC_DATA", + "ID_REMOTE_DISCONNECTION_NOTIFICATION", + "ID_REMOTE_CONNECTION_LOST", + "ID_REMOTE_NEW_INCOMING_CONNECTION", + "ID_REMOTE_EXISTING_CONNECTION", + "ID_REMOTE_STATIC_DATA", + "ID_FILE_LIST_TRANSFER_HEADER", + "ID_FILE_LIST_TRANSFER_FILE", + "ID_DDT_DOWNLOAD_REQUEST", + "ID_QUERY_MASTER_SERVER", + "ID_MASTER_SERVER_DELIST_SERVER", + "ID_MASTER_SERVER_UPDATE_SERVER", + "ID_MASTER_SERVER_SET_SERVER", + "ID_RELAYED_CONNECTION_NOTIFICATION", + "ID_ADVERTISE_SYSTEM", + "ID_TRANSPORT_STRING", + "ID_REPLICA_MANAGER_CONSTRUCTION", + "ID_REPLICA_MANAGER_DESTRUCTION", + "ID_REPLICA_MANAGER_SCOPE_CHANGE", + "ID_REPLICA_MANAGER_SERIALIZE", + "ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE", + "ID_CONNECTION_GRAPH_REQUEST", + "ID_CONNECTION_GRAPH_REPLY", + "ID_CONNECTION_GRAPH_UPDATE", + "ID_CONNECTION_GRAPH_NEW_CONNECTION", + "ID_CONNECTION_GRAPH_CONNECTION_LOST", + "ID_CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION", + "ID_ROUTE_AND_MULTICAST", + "ID_RAKVOICE_OPEN_CHANNEL_REQUEST", + "ID_RAKVOICE_OPEN_CHANNEL_REPLY", + "ID_RAKVOICE_CLOSE_CHANNEL", + "ID_RAKVOICE_DATA", + "ID_AUTOPATCHER_GET_CHANGELIST_SINCE_DATE", + "ID_AUTOPATCHER_CREATION_LIST", + "ID_AUTOPATCHER_DELETION_LIST", + "ID_AUTOPATCHER_GET_PATCH", + "ID_AUTOPATCHER_PATCH_LIST", + "ID_AUTOPATCHER_REPOSITORY_FATAL_ERROR", + "ID_AUTOPATCHER_FINISHED", + "ID_AUTOPATCHER_RESTART_APPLICATION", + "ID_NAT_PUNCHTHROUGH_REQUEST", + "ID_NAT_TARGET_NOT_CONNECTED", + "ID_NAT_TARGET_CONNECTION_LOST", + "ID_NAT_CONNECT_AT_TIME", + "ID_NAT_SEND_OFFLINE_MESSAGE_AT_TIME", + "ID_DATABASE_QUERY_REQUEST", + "ID_DATABASE_UPDATE_ROW", + "ID_DATABASE_REMOVE_ROW", + "ID_DATABASE_QUERY_REPLY", + "ID_DATABASE_UNKNOWN_TABLE", + "ID_DATABASE_INCORRECT_PASSWORD", + + }; + + return (char*)IDTable[Id]; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +char* PacketLogger::UserIDTOString(unsigned char Id) +{ + // Users should override this + return "Unknown"; +} +char* PacketLogger::IDTOString(unsigned char Id) +{ + char *out; + out=BaseIDTOString(Id); + if (out) + return (char*)out; + return UserIDTOString(Id); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/PacketLogger.h b/raknet/PacketLogger.h new file mode 100644 index 0000000..1d01146 --- /dev/null +++ b/raknet/PacketLogger.h @@ -0,0 +1,65 @@ +/// \file +/// \brief This will write all incoming and outgoing network messages to the local console screen. See derived functions for other outputs +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __PACKET_LOGGER_H +#define __PACKET_LOGGER_H + +class RakPeerInterface; +#include "NetworkTypes.h" +#include "PluginInterface.h" +#include "Export.h" + +/// \defgroup PACKETLOGGER_GROUP PacketLogger +/// \ingroup PLUGINS_GROUP + +/// \brief Writes incoming and outgoing messages to the screen. +/// This will write all incoming and outgoing messages to the console window, or to a file if you override it and give it this functionality. +/// \ingroup PACKETLOGGER_GROUP +class RAK_DLL_EXPORT PacketLogger : public PluginInterface +{ +public: + PacketLogger(); + virtual ~PacketLogger(); + + virtual void OnAttach(RakPeerInterface *peer); + + /// Events on low level sends and receives. These functions may be called from different threads at the same time. + virtual void OnDirectSocketSend(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID); + virtual void OnDirectSocketReceive(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID); + virtual void OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, PlayerID remoteSystemID, RakNetTime time, bool isSend); + + /// Logs out a header for all the data + virtual void LogHeader(void); + + /// Override this to log strings to wherever. Log should be threadsafe + virtual void WriteLog(const char *str); + + // Set to true to print ID_* instead of numbers + virtual void SetPrintID(bool print); + // Print or hide acks (clears up the screen not to print them but is worse for debugging) + virtual void SetPrintAcks(bool print); +protected: + char* IDTOString(unsigned char Id); + char* BaseIDTOString(unsigned char Id); + // Users should override this + virtual char* UserIDTOString(unsigned char Id); + + RakPeerInterface *rakPeer; + bool printId, printAcks; +}; + +#endif diff --git a/raknet/PacketPool.h b/raknet/PacketPool.h new file mode 100644 index 0000000..d57e091 --- /dev/null +++ b/raknet/PacketPool.h @@ -0,0 +1,56 @@ +/// \file +/// \brief \b [Internal] A pool for the Packet class. I don't believe this is used any longer. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +// TODO - Remove this file from the project +/* +#ifndef __PACKET_POOL +#define __PACKET_POOL +#include "SimpleMutex.h" +#include "DS_Queue.h" + +struct Packet; + +class PacketPool +{ + +public: + PacketPool(); + ~PacketPool(); + + static void AddReference(void); + static void RemoveReference(void); + Packet* GetPointer( void ); + void ReleasePointer( Packet *p ); + void ClearPool( void ); + static inline PacketPool* Instance() + { + return I; + } + +private: + DataStructures::Queue pool; + SimpleMutex poolMutex; + static PacketPool *I; + static int referenceCount; +#ifdef _DEBUG + int packetsReleased; +#endif +}; + +#endif + +*/ \ No newline at end of file diff --git a/raknet/PacketPriority.h b/raknet/PacketPriority.h index 47ae624..e3ee020 100644 --- a/raknet/PacketPriority.h +++ b/raknet/PacketPriority.h @@ -16,7 +16,7 @@ /// option) any later version. #ifndef __PACKET_PRIORITY_H -#define __PACKET_PRIORITY_H +#define __PACKET_PRIORITY_H /// These enumerations are used to describe when packets are delivered. enum PacketPriority @@ -32,7 +32,7 @@ enum PacketPriority /// \note Note to self: I write this with 3 bits in the stream. If I add more remember to change that enum PacketReliability { - UNRELIABLE = 6, /// Same as regular UDP, except that it will also discard duplicate datagrams. RakNet adds (6 to 17) + 21 bits of overhead, 16 of which is used to detect duplicate packets and 6 to 17 of which is used for message length. + UNRELIABLE, /// Same as regular UDP, except that it will also discard duplicate datagrams. RakNet adds (6 to 17) + 21 bits of overhead, 16 of which is used to detect duplicate packets and 6 to 17 of which is used for message length. UNRELIABLE_SEQUENCED, /// Regular UDP with a sequence counter. Out of order messages will be discarded. This adds an additional 13 bits on top what is used for UNRELIABLE. RELIABLE, /// The message is sent reliably, but not necessarily in any order. Same overhead as UNRELIABLE. RELIABLE_ORDERED, /// This message is reliable and will arrive in the order you sent it. Messages will be delayed while waiting for out of order messages. Same overhead as UNRELIABLE_SEQUENCED. diff --git a/raknet/PluginInterface.cpp b/raknet/PluginInterface.cpp new file mode 100644 index 0000000..6707983 --- /dev/null +++ b/raknet/PluginInterface.cpp @@ -0,0 +1,87 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "PluginInterface.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnAttach(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnDetach(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnInitialize(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::Update(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +PluginReceiveResult PluginInterface::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + return RR_CONTINUE_PROCESSING; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnDisconnect(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnDirectSocketSend(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnDirectSocketReceive(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void PluginInterface::OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, PlayerID remoteSystemID, RakNetTime time, bool isSend) +{ +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/PluginInterface.h b/raknet/PluginInterface.h new file mode 100644 index 0000000..17cccd5 --- /dev/null +++ b/raknet/PluginInterface.h @@ -0,0 +1,107 @@ +/// \file +/// \brief \b RakNet's plugin functionality system. You can derive from this to create your own plugins. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __PLUGIN_INTERFACE_H +#define __PLUGIN_INTERFACE_H + +class RakPeerInterface; +struct Packet; +struct InternalPacket; + +enum PluginReceiveResult +{ + // The plugin used this message and it shouldn't be given to the user. + RR_STOP_PROCESSING_AND_DEALLOCATE=0, + + // This message will be processed by other plugins, and at last by the user. + RR_CONTINUE_PROCESSING, + + // The plugin is going to hold on to this message. Do not deallocate it but do not pass it to other plugins either. + RR_STOP_PROCESSING, +}; + +#include "NetworkTypes.h" +#include "Export.h" + +/// \defgroup PLUGINS_GROUP PluginInterface + +/// \brief PluginInterface provides a mechanism to add functionality in a modular way. +/// MessageHandlers should derive from PluginInterface and be attached to RakPeer using the function AttachPlugin +/// On a user call to Receive, OnReceive is called for every PluginInterface, which can then take action based on the message +/// passed to it. This is used to transparently add game-independent functional modules, similar to browser plugins +/// +/// \sa ReplicaManager +/// \sa FullyConnectedMesh +/// \sa PacketLogger +/// \ingroup PLUGINS_GROUP +class RAK_DLL_EXPORT PluginInterface +{ +public: + /// Called when the interface is attached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnAttach(RakPeerInterface *peer); + + /// Called when the interface is detached + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnDetach(RakPeerInterface *peer); + + /// Called when RakPeer is initialized + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnInitialize(RakPeerInterface *peer); + + /// Update is called every time a packet is checked for . + /// \param[in] peer - the instance of RakPeer that is calling Receive + virtual void Update(RakPeerInterface *peer); + + /// OnReceive is called for every packet. + /// \param[in] peer the instance of RakPeer that is calling Receive + /// \param[in] packet the packet that is being returned to the user + /// \return True to allow the game and other plugins to get this message, false to absorb it + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + + /// Called when RakPeer is shutdown + /// \param[in] peer the instance of RakPeer that is calling Receive + virtual void OnDisconnect(RakPeerInterface *peer); + + /// Called when a connection is dropped because the user called RakPeer::CloseConnection() for a particular system + /// \param[in] peer the instance of RakPeer that is calling Receive + /// \param[in] playerId The system whose connection was closed + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + + /// Called on a send to the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemID Which system this message is being sent to + virtual void OnDirectSocketSend(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID); + + /// Called on a receive from the socket, per datagram, that does not go through the reliability layer + /// \param[in] data The data being sent + /// \param[in] bitsUsed How many bits long \a data is + /// \param[in] remoteSystemID Which system this message is being sent to + virtual void OnDirectSocketReceive(const char *data, const unsigned bitsUsed, PlayerID remoteSystemID); + + /// Called on a send or recieve within the reliability layer + /// \param[in] internalPacket The user message, along with all send data. + /// \param[in] frameNumber The number of frames sent or received so far for this player depending on \a isSend . Indicates the frame of this user message. + /// \param[in] remoteSystemID The player we sent or got this packet from + /// \param[in] time The current time as returned by RakNet::GetTime() + /// \param[in] isSend Is this callback representing a send event or receive event? + virtual void OnInternalPacket(InternalPacket *internalPacket, unsigned frameNumber, PlayerID remoteSystemID, RakNetTime time, bool isSend); +}; + +#endif + diff --git a/raknet/RPCMap.cpp b/raknet/RPCMap.cpp index 82afb37..689c5ff 100644 --- a/raknet/RPCMap.cpp +++ b/raknet/RPCMap.cpp @@ -1,16 +1,167 @@ -// TODO: Implement RPCMap.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "RPCMap.h" +#include + +RPCMap::RPCMap() +{ +} +RPCMap::~RPCMap() +{ + Clear(); +} +void RPCMap::Clear(void) +{ + unsigned i; + RPCNode *node; + for (i=0; i < rpcSet.Size(); i++) + { + node=rpcSet[i]; + if (node) + { + delete [] node->uniqueIdentifier; + delete node; + } + } + rpcSet.Clear(); +} +RPCNode *RPCMap::GetNodeFromIndex(RPCIndex index) +{ + if ((unsigned)index < rpcSet.Size()) + return rpcSet[(unsigned)index]; + return 0; +} +RPCNode *RPCMap::GetNodeFromFunctionName(char *uniqueIdentifier) +{ + unsigned index; + index=(unsigned)GetIndexFromFunctionName(uniqueIdentifier); + if ((RPCIndex)index!=UNDEFINED_RPC_INDEX) + return rpcSet[index]; + return 0; +} +RPCIndex RPCMap::GetIndexFromFunctionName(char *uniqueIdentifier) +{ + unsigned index; + for (index=0; index < rpcSet.Size(); index++) + if (rpcSet[index] && strcmp(rpcSet[index]->uniqueIdentifier, uniqueIdentifier)==0) + return (RPCIndex) index; + return UNDEFINED_RPC_INDEX; +} // Called from the user thread for the local system -void RPCMap::AddIdentifierWithFunction(unsigned char uniqueIdentifier, void *functionPointer, bool isPointerToMember) +void RPCMap::AddIdentifierWithFunction(char *uniqueIdentifier, void *functionPointer, bool isPointerToMember) { +#ifdef _DEBUG + assert(rpcSet.Size()+1 < MAX_RPC_MAP_SIZE); // If this hits change the typedef of RPCIndex to use an unsigned short + assert(uniqueIdentifier && uniqueIdentifier[0]); + assert(functionPointer); +#endif + + unsigned index, existingNodeIndex; RPCNode *node; + existingNodeIndex=GetIndexFromFunctionName(uniqueIdentifier); + if ((RPCIndex)existingNodeIndex!=UNDEFINED_RPC_INDEX) // Insert at any free spot. + { + // Trying to insert an identifier at any free slot and that identifier already exists + // The user should not insert nodes that already exist in the list +#ifdef _DEBUG +// assert(0); +#endif + return; + } + node = new RPCNode; - node->uniqueIdentifier = uniqueIdentifier; + node->uniqueIdentifier = new char [strlen(uniqueIdentifier)+1]; + strcpy(node->uniqueIdentifier, uniqueIdentifier); node->functionPointer=functionPointer; node->isPointerToMember=isPointerToMember; - rpcSet[uniqueIdentifier] = node; + // Insert into an empty spot if possible + for (index=0; index < rpcSet.Size(); index++) + { + if (rpcSet[index]==0) + { + rpcSet.Replace(node, 0, index); + return; + } + } + + rpcSet.Insert(node); // No empty spots available so just add to the end of the list + } +void RPCMap::AddIdentifierAtIndex(char *uniqueIdentifier, RPCIndex insertionIndex) +{ +#ifdef _DEBUG + assert(uniqueIdentifier && uniqueIdentifier[0]); +#endif + + unsigned existingNodeIndex; + RPCNode *node, *oldNode; + + existingNodeIndex=GetIndexFromFunctionName(uniqueIdentifier); + + if (existingNodeIndex==insertionIndex) + return; // Already there + + if ((RPCIndex)existingNodeIndex!=UNDEFINED_RPC_INDEX) + { + // Delete the existing one + oldNode=rpcSet[existingNodeIndex]; + rpcSet[existingNodeIndex]=0; + delete [] oldNode->uniqueIdentifier; + delete oldNode; + } + + node = new RPCNode; + node->uniqueIdentifier = new char [strlen(uniqueIdentifier)+1]; + strcpy(node->uniqueIdentifier, uniqueIdentifier); + node->functionPointer=0; + + // Insert at a user specified spot + if (insertionIndex < rpcSet.Size()) + { + // Overwrite what is there already + oldNode=rpcSet[insertionIndex]; + if (oldNode) + { + delete [] oldNode->uniqueIdentifier; + delete oldNode; + } + rpcSet[insertionIndex]=node; + } + else + { + // Insert after the end of the list and use 0 as a filler for the empty spots + rpcSet.Replace(node, 0, insertionIndex); + } +} + +void RPCMap::RemoveNode(char *uniqueIdentifier) +{ + unsigned index; + index=GetIndexFromFunctionName(uniqueIdentifier); + #ifdef _DEBUG + assert(index!=UNDEFINED_RPC_INDEX); // If this hits then the user was removing an RPC call that wasn't currently registered + #endif + RPCNode *node; + node = rpcSet[index]; + delete [] node->uniqueIdentifier; + delete node; + rpcSet[index]=0; +} + diff --git a/raknet/RPCMap.h b/raknet/RPCMap.h index deaeb02..978c40e 100644 --- a/raknet/RPCMap.h +++ b/raknet/RPCMap.h @@ -1,19 +1,48 @@ -// TODO: Implement RPCMap.h +/// \file +/// \brief \b [Internal] A container class for a list of RPCNodes +/// +/// \ingroup RAKNET_RPC +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RPC_MAP #define __RPC_MAP #include "RPCNode.h" +#include "DS_List.h" +#include "NetworkTypes.h" #include "Export.h" -#define RPC_MAP_SIZE 256 - +/// \ingroup RAKNET_RPC +/// \internal +/// \brief A container class for a list of RPCNodes struct RAK_DLL_EXPORT RPCMap { public: - void AddIdentifierWithFunction(unsigned char uniqueIdentifier, void *functionPointer, bool isPointerToMember); + RPCMap(); + ~RPCMap(); + void Clear(void); + RPCNode *GetNodeFromIndex(RPCIndex index); + RPCNode *GetNodeFromFunctionName(char *uniqueIdentifier); + RPCIndex GetIndexFromFunctionName(char *uniqueIdentifier); + void AddIdentifierWithFunction(char *uniqueIdentifier, void *functionPointer, bool isPointerToMember); + void AddIdentifierAtIndex(char *uniqueIdentifier, RPCIndex insertionIndex); + void RemoveNode(char *uniqueIdentifier); protected: - RPCNode *rpcSet[RPC_MAP_SIZE]; + DataStructures::List rpcSet; }; #endif + diff --git a/raknet/RPCNode.h b/raknet/RPCNode.h index 29ecae6..be9385f 100644 --- a/raknet/RPCNode.h +++ b/raknet/RPCNode.h @@ -1,4 +1,21 @@ -// TODO: Implement RPCNode.h +/// \file +/// \brief \b [Internal] Holds information related to a RPC +/// +/// \ingroup RAKNET_RPC +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RPC_NODE #define __RPC_NODE @@ -6,20 +23,24 @@ #include "NetworkTypes.h" #include "Export.h" +class RakPeerInterface; + + /// \defgroup RAKNET_RPC Remote Procedure Call Subsystem /// \brief A system to call C or object member procudures on other systems, and even to return return values. -/// \ingroup RAKNET_RPC +/// \ingroup RAKNET_RPC /// \internal -/// -/// \brief Map registered procedure inside of a peer. -/// +/// +/// \brief Map registered procedure inside of a peer. +/// struct RAK_DLL_EXPORT RPCNode { - /// String identifier of the RPC - unsigned char uniqueIdentifier; - - /// Force casting of member functions to void * + + /// String identifier of the RPC + char *uniqueIdentifier; + + /// Force casting of member functions to void * union { void ( *staticFunctionPointer ) ( RPCParameters *rpcParms ); @@ -31,9 +52,10 @@ struct RAK_DLL_EXPORT RPCNode void *functionPointer; }; - + /// Is this a member function pointer? True if so. If false it's a regular C function. bool isPointerToMember; }; #endif + diff --git a/raknet/RSACrypt.h b/raknet/RSACrypt.h new file mode 100644 index 0000000..90f6470 --- /dev/null +++ b/raknet/RSACrypt.h @@ -0,0 +1,1255 @@ +/// +/// \brief \b [Internal] SHA-1 computation class +/// +/// Performant RSA en/decryption with 256-bit to 16384-bit modulus +/// +/// catid(cat02e@fsu.edu) +/// +/// 7/30/2004 Fixed VS6 compat +/// 7/26/2004 Now internally generates private keys +/// simpleModExp() is faster for encryption than MontyModExp +/// CRT-MontyModExp is faster for decryption than CRT-SimpleModExp +/// 7/25/2004 Implemented Montgomery modular exponentation +/// Implemented CRT modular exponentation optimization +/// 7/21/2004 Did some pre-lim coding +/// +/// Best performance on my 1.8 GHz P4 (mobile): +/// 1024-bit generate key : 30 seconds +/// 1024-bit set private key : 100 ms (pre-compute this step) +/// 1024-bit encryption : 200 usec +/// 1024-bit decryption : 400 ms +/// +/// \todo There's a bug in MonModExp() that restricts us to k-1 bits +/// +/// Tabs: 4 spaces +/// Dist: public + +#ifndef RSACRYPT_H +#define RSACRYPT_H + +#if !defined(_COMPATIBILITY_1) + +#define RSASUPPORTGENPRIME +#include "Export.h" +/// Can't go under 256 or you'll need to disable the USEASSEMBLY macro in bigtypes.h +/// That's because the assembly assumes at least 128-bit data to work on +/// #define RSA_BIT_SIZE big::u512 +#define RSA_BIT_SIZE big::u256 + +#include "BigTypes.h" +#include "Rand.h" //Giblet - added missing include for randomMT() + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +namespace big +{ + + using namespace cat; + + // r = x^y Mod n (fast for small y) + BIGONETYPE void simpleModExp( T &x0, T &y0, T &n0, T &r0 ) + { + BIGDOUBLESIZE( T, x ); + BIGDOUBLESIZE( T, y ); + BIGDOUBLESIZE( T, n ); + BIGDOUBLESIZE( T, r ); + + usetlow( x, x0 ); + usetlow( y, y0 ); + usetlow( n, n0 ); + usetw( r, 1 ); + + umodulo( x, n, x ); + + u32 squares = 0; + + for ( u32 ii = 0; ii < BIGWORDCOUNT( T ); ++ii ) + { + word y_i = y[ ii ]; + + u32 ctr = WORDBITS; + + while ( y_i ) + { + if ( y_i & 1 ) + { + if ( squares ) + do + { + usquare( x ); + umodulo( x, n, x ); + } + + while ( --squares ); + + umultiply( r, x, r ); + + umodulo( r, n, r ); + } + + y_i >>= 1; + ++squares; + --ctr; + } + + squares += ctr; + } + + takelow( r0, r ); + } + + // computes Rn = 2^k (mod n), n < 2^k + BIGONETYPE void rModn( T &n, T &Rn ) + { + BIGDOUBLESIZE( T, dR ); + BIGDOUBLESIZE( T, dn ); + BIGDOUBLESIZE( T, dRn ); + T one; + + // dR = 2^k + usetw( one, 1 ); + sethigh( dR, one ); + + // Rn = 2^k (mod n) + usetlow( dn, n ); + umodulo( dR, dn, dRn ); + takelow( Rn, dRn ); + } + + // computes c = GCD(a, b) + BIGONETYPE void GCD( T &a0, T &b0, T &c ) + { + T a; + + umodulo( a0, b0, c ); + + if ( isZero( c ) ) + { + set ( c, b0 ) + + ; + return ; + } + + umodulo( b0, c, a ); + + if ( isZero( a ) ) + return ; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + umodulo( c, a, c ); + + if ( isZero( c ) ) + { + set ( c, a ) + + ; + return ; + } + + umodulo( a, c, a ); + + if ( isZero( a ) ) + return ; + } + } + + // directly computes x = c - a * b (mod n) > 0, c < n + BIGONETYPE void SubMulMod( T &a, T &b, T &c, T &n, T &x ) + { + BIGDOUBLESIZE( T, da ); + BIGDOUBLESIZE( T, dn ); + T y; + + // y = a b (mod n) + usetlow( da, a ); + umultiply( da, b ); + usetlow( dn, n ); + umodulo( da, dn, da ); + takelow( y, da ); + + // x = (c - y) (mod n) > 0 + + set ( x, c ) + + ; + if ( ugreater( c, y ) ) + { + subtract( x, y ); + } + + else + { + subtract( x, y ); + + add ( x, n ) + + ; + } + } + + /* + directly compute a' s.t. a' a - b' b = 1 + + b = b0 = n0 + rp = a' + a = 2^k + a > b > 0 + GCD(a, b) = 1 (b odd) + + Trying to keep everything positive + */ + BIGONETYPE void computeRinverse( T &n0, T &rp ) + { + T x0, x1, x2, a, b, q; + + //x[0] = 1 + usetw( x0, 1 ); + + // a = 2^k (mod b0) + rModn( n0, a ); + + // {q, b} = b0 / a + udivide( n0, a, q, b ); + + // if b = 0, return x[0] + + if ( isZero( b ) ) + { + set ( rp, x0 ) + + ; + return ; + } + + // x[1] = -q (mod b0) = b0 - q, q <= b0 + set ( x1, n0 ) + + ; + subtract( x1, q ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[1] + if ( isZero( a ) ) + { + set ( rp, x1 ) + + ; + return ; + } +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod( q, x1, x0, n0, x2 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[2] + + if ( isZero( b ) ) + { + set ( rp, x2 ) + + ; + return ; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod( q, x2, x1, n0, x0 ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[0] + if ( isZero( a ) ) + { + set ( rp, x0 ) + + ; + return ; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod( q, x0, x2, n0, x1 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[1] + if ( isZero( b ) ) + { + set ( rp, x1 ) + + ; + return ; + } + + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod( q, x1, x0, n0, x2 ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[2] + if ( isZero( a ) ) + { + set ( rp, x2 ) + + ; + return ; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod( q, x2, x1, n0, x0 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[0] + if ( isZero( b ) ) + { + set ( rp, x0 ) + + ; + return ; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod( q, x0, x2, n0, x1 ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[1] + if ( isZero( a ) ) + { + set ( rp, x1 ) + + ; + return ; + } + } + } + + /* BIGONETYPE void computeRinverse2(T &_n0, T &_rp) + { + //T x0, x1, x2, a, b, q; + BIGDOUBLESIZE(T, x0); + BIGDOUBLESIZE(T, x1); + BIGDOUBLESIZE(T, x2); + BIGDOUBLESIZE(T, a); + BIGDOUBLESIZE(T, b); + BIGDOUBLESIZE(T, q); + BIGDOUBLESIZE(T, n0); + BIGDOUBLESIZE(T, rp); + + usetlow(n0, _n0); + usetlow(rp, _rp); + + std::string old; + //x[0] = 1 + usetw(x0, 1); + + T _a; + // a = 2^k (mod b0) + rModn(_n0, _a); + RECORD("TEST") << "a=" << toString(a, false) << " = 2^k (mod " << toString(n0, false) << ")"; + usetlow(a, _a); + + // {q, b} = b0 / a + udivide(n0, a, q, b); + RECORD("TEST") << "{q=" << toString(q, false) << ", b=" << toString(b, false) << "} = n0=" << toString(n0, false) << " / a=" << toString(a, false); + + // if b = 0, return x[0] + if (isZero(b)) + { + RECORD("TEST") << "b == 0, Returning x[0]"; + set(rp, x0); + takelow(_rp, rp); + return; + } + + // x[1] = -q (mod b0) + negate(q); + smodulo(q, n0, x1); + if (BIGHIGHBIT(x1)) + add(x1, n0); // q > 0 + RECORD("TEST") << "x1=" << toString(x1, false) << " = q=" << toString(q, false) << " (mod n0=" << toString(n0, false) << ")"; + + // {q, a} = a / b + old = toString(a, false); + udivide(a, b, q, a); + RECORD("TEST") << "{q=" << toString(q, false) << ", a=" << toString(a, false) << "} = a=" << old << " / b=" << toString(b); + + // if a = 0, return x[1] + if (isZero(a)) + { + RECORD("TEST") << "a == 0, Returning x[1]"; + set(rp, x1); + takelow(_rp, rp); + return; + } + + RECORD("TEST") << "Entering loop..."; + while (true) + { + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod(q, x1, x0, n0, x2); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, b} = b / a + old = toString(b); + udivide(b, a, q, b); + RECORD("TEST") << "{q=" << toString(q, false) << ", b=" << toString(b) << "} = b=" << old << " / a=" << toString(a, false); + + // if b = 0, return x[2] + if (isZero(b)) + { + RECORD("TEST") << "b == 0, Returning x[2]"; + set(rp, x2); + takelow(_rp, rp); + return; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod(q, x2, x1, n0, x0); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, a} = a / b + old = toString(a, false); + udivide(a, b, q, a); + RECORD("TEST") << "{q=" << toString(q, false) << ", a=" << toString(a, false) << "} = a=" << old << " / b=" << toString(b); + + // if a = 0, return x[0] + if (isZero(a)) + { + RECORD("TEST") << "a == 0, Returning x[0]"; + set(rp, x0); + takelow(_rp, rp); + return; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod(q, x0, x2, n0, x1); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, b} = b / a + old = toString(b); + udivide(b, a, q, b); + RECORD("TEST") << "{q=" << toString(q, false) << ", b=" << toString(b) << "} = b=" << old << " / a=" << toString(a, false); + + // if b = 0, return x[1] + if (isZero(b)) + { + RECORD("TEST") << "b == 0, Returning x[1]"; + set(rp, x1); + takelow(_rp, rp); + return; + } + + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod(q, x1, x0, n0, x2); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, a} = a / b + old = toString(a, false); + udivide(a, b, q, a); + RECORD("TEST") << "{q=" << toString(q, false) << ", a=" << toString(a, false) << "} = a=" << old << " / b=" << toString(b); + + // if a = 0, return x[2] + if (isZero(a)) + { + RECORD("TEST") << "a == 0, Returning x[2]"; + set(rp, x2); + takelow(_rp, rp); + return; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod(q, x2, x1, n0, x0); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, b} = b / a + old = toString(b); + udivide(b, a, q, b); + RECORD("TEST") << "{q=" << toString(q, false) << ", b=" << toString(b) << "} = b=" << old << " / a=" << toString(a, false); + + // if b = 0, return x[0] + if (isZero(b)) + { + RECORD("TEST") << "b == 0, Returning x[0]"; + set(rp, x0); + takelow(_rp, rp); + return; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod(q, x0, x2, n0, x1); + RECORD("TEST") << "x[0] = " << toString(x0, false); + RECORD("TEST") << "x[1] = " << toString(x1, false); + RECORD("TEST") << "x[2] = " << toString(x2, false); + + // {q, a} = a / b + old = toString(a, false); + udivide(a, b, q, a); + RECORD("TEST") << "{q=" << toString(q, false) << ", a=" << toString(a, false) << "} = a=" << old << " / b=" << toString(b); + + // if a = 0, return x[1] + if (isZero(a)) + { + RECORD("TEST") << "a == 0, Returning x[1]"; + set(rp, x1); + takelow(_rp, rp); + return; + } + } + } + */ + // directly compute a^-1 s.t. a^-1 a (mod b) = 1, a < b, GCD(a, b) + BIGONETYPE void computeModularInverse( T &a0, T &b0, T &ap ) + { + T x0, x1, x2; + T a, b, q; + + // x[2] = 1 + usetw( x2, 1 ); + + // {q, b} = b0 / a0 + udivide( b0, a0, q, b ); + + // x[0] = -q (mod b0) = b0 - q, q <= b0 + + set ( x0, b0 ) + + ; + subtract( x0, q ); + + set ( a, a0 ) + + ; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[0] + + if ( isZero( a ) ) + { + set ( ap, x0 ) + + ; + return ; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod( x0, q, x2, b0, x1 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[1] + if ( isZero( b ) ) + { + set ( ap, x1 ) + + ; + return ; + } + + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod( x1, q, x0, b0, x2 ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[2] + if ( isZero( a ) ) + { + set ( ap, x2 ) + + ; + return ; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod( x2, q, x1, b0, x0 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[0] + if ( isZero( b ) ) + { + set ( ap, x0 ) + + ; + return ; + } + + // x[1] = x[2] - x[0] * q (mod b0) + SubMulMod( x0, q, x2, b0, x1 ); + + // {q, a} = a / b + udivide( a, b, q, a ); + + // if a = 0, return x[1] + if ( isZero( a ) ) + { + set ( ap, x1 ) + + ; + return ; + } + + // x[2] = x[0] - x[1] * q (mod b0) + SubMulMod( x1, q, x0, b0, x2 ); + + // {q, b} = b / a + udivide( b, a, q, b ); + + // if b = 0, return x[2] + if ( isZero( b ) ) + { + set ( ap, x2 ) + + ; + return ; + } + + // x[0] = x[1] - x[2] * q (mod b0) + SubMulMod( x2, q, x1, b0, x0 ); + } + } + + // indirectly computes n' s.t. 1 = r' r - n' n = GCD(r, n) + BIGONETYPE void computeNRinverse( T &n0, T &np ) + { + BIGDOUBLESIZE( T, r ); + BIGDOUBLESIZE( T, n ); + + // r' = (1 + n' n) / r + computeRinverse( n0, np ); + + // n' = (r' r - 1) / n + sethigh( r, np ); // special case of r = 2^k + decrement( r ); + usetlow( n, n0 ); + udivide( r, n, n, r ); + takelow( np, n ); + } + + /* + // indirectly computes n' s.t. 1 = r' r - n' n = GCD(r, n) + BIGONETYPE void computeNRinverse2(T &n0, T &np) + { + BIGDOUBLESIZE(T, r); + BIGDOUBLESIZE(T, n); + + // r' = (1 + n' n) / r + computeRinverse2(n0, np); + + // n' = (r' r - 1) / n + sethigh(r, np); // special case of r = 2^k + decrement(r); + usetlow(n, n0); + udivide(r, n, n, r); + takelow(np, n); + } + */ + // Montgomery product u = a * b (mod n) + BIGONETYPE void MonPro( T &ap, T &bp, T &n, T &np, T &u_out ) + { + BIGDOUBLESIZE( T, t ); + BIGDOUBLESIZE( T, u ); + T m; + + // t = a' b' + umultiply( ap, bp, t ); + + // m = (low half of t)*np (mod r) + takelow( m, t ); + umultiply( m, np ); + + // u = (t + m*n), u_out = u / r = high half of u + umultiply( m, n, u ); + + add ( u, t ) + + ; + takehigh( u_out, u ); + + // if u >= n, return u - n, else u + if ( ugreaterOrEqual( u_out, n ) ) + subtract( u_out, n ); + } + + // indirectly calculates x = M^e (mod n) + BIGONETYPE void MonModExp( T &x, T &M, T &e, T &n, T &np, T &xp0 ) + { + // x' = xp0 + + set ( x, xp0 ) + + ; + + // find M' = M r (mod n) + BIGDOUBLESIZE( T, dM ); + + BIGDOUBLESIZE( T, dn ); + + T Mp; + + sethigh( dM, M ); // dM = M r + + usetlow( dn, n ); + + umodulo( dM, dn, dM ); // dM = dM (mod n) + + takelow( Mp, dM ); // M' = M r (mod n) + + /* i may be wrong, but it seems to me that the squaring + results in a constant until we hit the first set bit + this could save a lot of time, but it needs to be proven + */ + + s32 ii, bc; + + word e_i; + + // for i = k - 1 down to 0 do + for ( ii = BIGWORDCOUNT( T ) - 1; ii >= 0; --ii ) + { + e_i = e[ ii ]; + bc = WORDBITS; + + while ( bc-- ) + { + // if e_i = 1, x = MonPro(M', x') + + if ( e_i & WORDHIGHBIT ) + goto start_squaring; + + e_i <<= 1; + } + } + + for ( ; ii >= 0; --ii ) + { + e_i = e[ ii ]; + bc = WORDBITS; + + while ( bc-- ) + { + // x' = MonPro(x', x') + MonPro( x, x, n, np, x ); + + // if e_i = 1, x = MonPro(M', x') + + if ( e_i & WORDHIGHBIT ) + { + +start_squaring: + MonPro( Mp, x, n, np, x ); + } + + e_i <<= 1; + } + } + + // x = MonPro(x', 1) + T one; + + usetw( one, 1 ); + + MonPro( x, one, n, np, x ); + } + + // indirectly calculates x = C ^ d (mod n) using the Chinese Remainder Thm +#ifdef _MSC_VER + #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif + BIGTWOTYPES void CRTModExp( Bigger &x, Bigger &C, Bigger &d, T &p, T &q, T &pInverse, T &pnp, T &pxp, T &qnp, T &qxp ) + { + // d1 = d mod (p - 1) + Bigger dd1; + T d1; + usetlow( dd1, p ); + decrement( dd1 ); + umodulo( d, dd1, dd1 ); + takelow( d1, dd1 ); + + // M1 = C1^d1 (mod p) + Bigger dp, dC1; + T M1, C1; + usetlow( dp, p ); + umodulo( C, dp, dC1 ); + takelow( C1, dC1 ); + simpleModExp( C1, d1, p, M1 ); + //MonModExp(M1, C1, d1, p, pnp, pxp); + + // d2 = d mod (q - 1) + Bigger dd2; + T d2; + usetlow( dd2, q ); + decrement( dd2 ); + umodulo( d, dd2, dd2 ); + takelow( d2, dd2 ); + + // M2 = C2^d2 (mod q) + Bigger dq, dC2; + T M2, C2; + usetlow( dq, q ); + umodulo( C, dq, dC2 ); + takelow( C2, dC2 ); + simpleModExp( C2, d2, q, M2 ); + //MonModExp(M2, C2, d2, q, qnp, qxp); + + // x = M1 + p * ((M2 - M1)(p^-1 mod q) mod q) + + if ( ugreater( M2, M1 ) ) + { + subtract( M2, M1 ); + } + + else + { + subtract( M2, M1 ); + + add ( M2, q ) + + ; + } + + // x = M1 + p * (( M2 )(p^-1 mod q) mod q) + + umultiply( M2, pInverse, x ); + + // x = M1 + p * (( x ) mod q) + + umodulo( x, dq, x ); + + // x = M1 + p * ( x ) + + umultiply( x, dp ); + + // x = M1 + ( x ) + + Bigger dM1; + + usetlow( dM1, M1 ); + + // x = ( dM1 ) + ( x ) + + add ( x, dM1 ) + + ; + } + + // generates a suitable public exponent s.t. 4 < e << phi, GCD(e, phi) = 1 + BIGONETYPE void computePublicExponent( T &phi, T &e ) + { + T r, one, two; + usetw( one, 1 ); + usetw( two, 2 ); + usetw( e, 65537 - 2 ); + + if ( ugreater( e, phi ) ) + usetw( e, 5 - 2 ); + + do + { + add ( e, two ) + + ; + + GCD( phi, e, r ); + } + + while ( !equal( r, one ) ); + } + + // directly computes private exponent + BIGONETYPE void computePrivateExponent( T &e, T &phi, T &d ) + { + // d = e^-1 (mod phi), 1 < e << phi + computeModularInverse( e, phi, d ); + } + +#ifdef RSASUPPORTGENPRIME + + static const u16 PRIME_TABLE[ 256 ] = + { + 3, 5, 7, 11, 13, 17, 19, 23, + 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, + 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, + 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, + 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, + 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, + 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, + 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, + 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, + 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997, 1009, + 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, + 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, + 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, + 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, + 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, + 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, + 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, + 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, + 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, + 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, + 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621 + }; + + /*modified Rabin-Miller primality test (added small primes) + + When picking a value for insurance, note that the probability of failure + of the test to detect a composite number is at most 4^(-insurance), so: + insurance max. probability of failure + 3 1.56% + 4 0.39% + 5 0.098% <-- default + 6 0.024% + ... + */ + BIGONETYPE bool RabinMillerPrimalityTest( T &n, u32 insurance ) + { + // check divisibility by small primes <= 1621 (speeds up computation) + T temp; + + for ( u32 ii = 0; ii < 256; ++ii ) + { + usetw( temp, PRIME_TABLE[ ii++ ] ); + + umodulo( n, temp, temp ); + + if ( isZero( temp ) ) + return false; + } + + // n1 = n - 1 + T n1; + + set ( n1, n ) + + ; + decrement( n1 ); + + // write r 2^s = n - 1, r is odd + T r; + + u32 s = 0; + + set ( r, n1 ) + + ; + while ( !( r[ 0 ] & 1 ) ) + { + ushiftRight1( r ); + ++s; + } + + // one = 1 + T one; + + usetw( one, 1 ); + + // cache n -> dn + BIGDOUBLESIZE( T, dy ); + + BIGDOUBLESIZE( T, dn ); + + usetlow( dn, n ); + + while ( insurance-- ) + { + // choose random integer a s.t. 1 < a < n - 1 + T a; + int index; + + for ( index = 0; index < (int) sizeof( a ) / (int) sizeof( a[ 0 ] ); index++ ) + a[ index ] = randomMT(); + + umodulo( a, n1, a ); + + // compute y = a ^ r (mod n) + T y; + + simpleModExp( a, r, n, y ); + + if ( !equal( y, one ) && !equal( y, n1 ) ) + { + u32 j = s; + + while ( ( j-- > 1 ) && !equal( y, n1 ) ) + { + umultiply( y, y, dy ); + umodulo( dy, dn, dy ); + takelow( y, dy ); + + if ( equal( y, one ) ) + return false; + } + + if ( !equal( y, n1 ) ) + return false; + } + } + + return true; + } + + // generates a strong pseudo-prime + BIGONETYPE void generateStrongPseudoPrime( T &n ) + { + do + { + int index; + + for ( index = 0; index < (int) sizeof( n ) / (int) sizeof( n[ 0 ] ); index++ ) + n[ index ] = randomMT(); + + n[ BIGWORDCOUNT( T ) - 1 ] |= WORDHIGHBIT; + + //n[BIGWORDCOUNT(T) - 1] &= ~WORDHIGHBIT; n[BIGWORDCOUNT(T) - 1] |= WORDHIGHBIT >> 1; + n[ 0 ] |= 1; + } + + while ( !RabinMillerPrimalityTest( n, 5 ) ); + } + +#endif // RSASUPPORTGENPRIME + + + //////// RSACrypt class //////// + + BIGONETYPE class RAK_DLL_EXPORT RSACrypt + { + // public key + T e, n; + T np, xp; + + // private key + bool factorsAvailable; + T d, phi; + BIGHALFSIZE( T, p ); + BIGHALFSIZE( T, pnp ); + BIGHALFSIZE( T, pxp ); + BIGHALFSIZE( T, q ); + BIGHALFSIZE( T, qnp ); + BIGHALFSIZE( T, qxp ); + BIGHALFSIZE( T, pInverse ); + + public: + RSACrypt() + { + reset(); + } + + ~RSACrypt() + { + reset(); + } + + public: + void reset() + { + zero( d ); + zero( p ); + zero( q ); + zero( pInverse ); + factorsAvailable = false; + } + +#ifdef RSASUPPORTGENPRIME + + void generateKeys() + { + BIGHALFSIZE( T, p0 ); + BIGHALFSIZE( T, q0 ); + + generateStrongPseudoPrime( p0 ); + generateStrongPseudoPrime( q0 ); + + setPrivateKey( p0, q0 ); + } + +#endif // RSASUPPORTGENPRIME + + BIGSMALLTYPE void setPrivateKey( Smaller &c_p, Smaller &c_q ) + { + factorsAvailable = true; + + // re-order factors s.t. q > p + + if ( ugreater( c_p, c_q ) ) + { + set ( q, c_p ) + + ; + set ( p, c_q ) + + ; + } + + else + { + set ( p, c_p ) + + ; + set ( q, c_q ) + + ; + } + + // phi = (p - 1)(q - 1) + BIGHALFSIZE( T, p1 ); + + BIGHALFSIZE( T, q1 ); + + set ( p1, p ) + + ; + decrement( p1 ); + + set ( q1, q ) + + ; + decrement( q1 ); + + umultiply( p1, q1, phi ); + + // compute e + computePublicExponent( phi, e ); + + // compute d + computePrivateExponent( e, phi, d ); + + // compute p^-1 mod q + computeModularInverse( p, q, pInverse ); + + // compute n = pq + umultiply( p, q, n ); + + // find n' + computeNRinverse( n, np ); + + // x' = 1*r (mod n) + rModn( n, xp ); + + // find pn' + computeNRinverse( p, pnp ); + + // computeNRinverse2(p, pnp); + + // px' = 1*r (mod p) + rModn( p, pxp ); + + // find qn' + computeNRinverse( q, qnp ); + + // qx' = 1*r (mod q) + rModn( q, qxp ); + } + + void setPublicKey( u32 c_e, T &c_n ) + { + reset(); // in case we knew a private key + + usetw( e, c_e ); + + set ( n, c_n ) + + ; + + // find n' + computeNRinverse( n, np ); + + // x' = 1*r (mod n) + rModn( n, xp ); + } + + public: + void getPublicKey( u32 &c_e, T &c_n ) + { + c_e = e[ 0 ]; + + set ( c_n, n ) + + ; + } + + BIGSMALLTYPE void getPrivateKey( Smaller &c_p, Smaller &c_q ) + { + set ( c_p, p ) + + ; + set ( c_q, q ) + + ; + } + + public: + void encrypt( T &M, T &x ) + { + if ( factorsAvailable ) + CRTModExp( x, M, e, p, q, pInverse, pnp, pxp, qnp, qxp ); + else + simpleModExp( M, e, n, x ); + } + + void decrypt( T &C, T &x ) + { + if ( factorsAvailable ) + CRTModExp( x, C, d, p, q, pInverse, pnp, pxp, qnp, qxp ); + } + }; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif // #if !defined(_COMPATIBILITY_1) + +#endif // RSACRYPT_H + diff --git a/raknet/RakAssert.h b/raknet/RakAssert.h new file mode 100644 index 0000000..80b8a77 --- /dev/null +++ b/raknet/RakAssert.h @@ -0,0 +1,8 @@ +#include + +// So stupid Linux doesn't assert in release +#ifdef _DEBUG +#define RakAssert(x) assert(x); +#else +#define RakAssert(x) +#endif diff --git a/raknet/RakClient.cpp b/raknet/RakClient.cpp index 79db692..c0f25b4 100644 --- a/raknet/RakClient.cpp +++ b/raknet/RakClient.cpp @@ -1,122 +1,353 @@ -// TODO: Implement RakClient.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "RakClient.h" +#include "PacketEnumerations.h" +#include "GetTime.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif // Constructor RakClient::RakClient() { - // TODO: RakClient::RakClient saco .text:10034BA0 server L .text:08067B60 bot W .text:004033E0 L .text:0806B130 + unsigned i; + + for ( i = 0; i < 32; i++ ) + otherClients[ i ].isActive = false; + + nextSeedUpdate = 0; } -void RakClient::vftable_0() -{ - // TODO: RakClient::vftable_0() (saco 10034A30) (server L: 80691F0) (bot W: 403270 L: 806CBF2) -} +// Destructor +RakClient::~RakClient() +{} -void RakClient::vftable_4() +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: 'depreciated' : unreferenced formal parameter +#endif +bool RakClient::Connect( const char* host, unsigned short serverPort, unsigned short clientPort, unsigned int depreciated, int threadSleepTimer ) { - // TODO: RakClient::vftable_4() (saco 10034130) (server L: 8069200) (bot W: 402970 L: 806CC00) + RakPeer::Disconnect( 100 ); + + RakPeer::Initialize( 1, clientPort, threadSleepTimer ); + + if ( host[ 0 ] < '0' || host[ 0 ] > '2' ) + { +#if !defined(_COMPATIBILITY_1) + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); +#else + return false; +#endif + } + + unsigned i; + + for ( i = 0; i < 32; i++ ) + { + otherClients[ i ].isActive = false; + otherClients[ i ].playerId = UNASSIGNED_PLAYER_ID; + otherClients[ i ].staticData.Reset(); + } + + // ignore depreciated. A pointless variable + return RakPeer::Connect( host, serverPort, ( char* ) password.GetData(), password.GetNumberOfBytesUsed() ); } void RakClient::Disconnect( unsigned int blockDuration, unsigned char orderingChannel ) { - // TODO: RakClient::vftable_8() (saco 10034A40) (server L: 8069210) (bot W: 403280 L: 806CC0E) + RakPeer::Disconnect( blockDuration, orderingChannel ); } -void RakClient::vftable_C() +void RakClient::InitializeSecurity( const char *privKeyP, const char *privKeyQ ) { - // TODO: RakClient::vftable_C() (saco 10034200) (server L: 8069220) (bot W: 402A40 L: 806CC36) + RakPeer::InitializeSecurity( privKeyP, privKeyQ, 0, 0 ); } -void RakClient::vftable_10() +void RakClient::SetPassword( const char *_password ) { - // TODO: RakClient::vftable_10() (saco 10034220) (server L: 8069230) (bot W: 402A60 L: 806CC44) + if ( _password == 0 || _password[ 0 ] == 0 ) + password.Reset(); + else + { + password.Reset(); + password.Write( _password, ( int ) strlen( _password ) + 1 ); + } } -void RakClient::vftable_14() +bool RakClient::HasPassword( void ) const { - // TODO: RakClient::vftable_14() (saco 10034270) (server L: 8069240) (bot W: 402AB0 L: 806CC52) + return password.GetNumberOfBytesUsed() > 0; } -void RakClient::vftable_18() +bool RakClient::Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel ) { - // TODO: RakClient::vftable_18() (saco 100342E0) (server L: 8069250) (bot W: 402B20 L: 806CC60) + if ( remoteSystemList == 0 ) + return false; + + return RakPeer::Send( data, length, priority, reliability, orderingChannel, remoteSystemList[ 0 ].playerId, false ); } -void RakClient::vftable_1C() +bool RakClient::Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel ) { - // TODO: RakClient::vftable_1C() (saco 10034290) (server L: 8069260) (bot W: 402AD0 L: 806CC6E) + if ( remoteSystemList == 0 ) + return false; + + return RakPeer::Send( bitStream, priority, reliability, orderingChannel, remoteSystemList[ 0 ].playerId, false ); } -void RakClient::vftable_20() +Packet* RakClient::Receive( void ) { - // TODO: RakClient::vftable_20() (saco 10035200) (server L: 8069270) (bot W: 403A40 L: 806CC7C) + Packet * packet = RakPeer::Receive(); + + // Intercept specific client / server feature packets + + if ( packet ) + { + RakNet::BitStream bitStream( packet->data, packet->length, false ); + int i; + + if ( packet->data[ 0 ] == ID_CONNECTION_REQUEST_ACCEPTED ) + { +// ConnectionAcceptStruct cas; +// cas.Deserialize(bitStream); + // unsigned short remotePort; + // PlayerID externalID; + PlayerIndex playerIndex; + + RakNet::BitStream inBitStream(packet->data, packet->length, false); + inBitStream.IgnoreBits(8); // ID_CONNECTION_REQUEST_ACCEPTED + inBitStream.IgnoreBits(8 * sizeof(unsigned short)); //inBitStream.Read(remotePort); + inBitStream.IgnoreBits(8 * sizeof(unsigned int)); //inBitStream.Read(externalID.binaryAddress); + inBitStream.IgnoreBits(8 * sizeof(unsigned short)); //inBitStream.Read(externalID.port); + inBitStream.Read(playerIndex); + + localPlayerIndex = playerIndex; + packet->playerIndex = playerIndex; + } + else if ( + packet->data[ 0 ] == ID_REMOTE_NEW_INCOMING_CONNECTION || + packet->data[ 0 ] == ID_REMOTE_EXISTING_CONNECTION || + packet->data[ 0 ] == ID_REMOTE_DISCONNECTION_NOTIFICATION || + packet->data[ 0 ] == ID_REMOTE_CONNECTION_LOST ) + { + bitStream.IgnoreBits( 8 ); // Ignore identifier + bitStream.Read( packet->playerId.binaryAddress ); + bitStream.Read( packet->playerId.port ); + + if ( bitStream.Read( ( unsigned short& ) packet->playerIndex ) == false ) + { + DeallocatePacket( packet ); + return 0; + } + + + if ( packet->data[ 0 ] == ID_REMOTE_DISCONNECTION_NOTIFICATION || + packet->data[ 0 ] == ID_REMOTE_CONNECTION_LOST ) + { + i = GetOtherClientIndexByPlayerID( packet->playerId ); + + if ( i >= 0 ) + otherClients[ i ].isActive = false; + } + } + else if ( packet->data[ 0 ] == ID_REMOTE_STATIC_DATA ) + { + bitStream.IgnoreBits( 8 ); // Ignore identifier + bitStream.Read( packet->playerId.binaryAddress ); + bitStream.Read( packet->playerId.port ); + bitStream.Read( packet->playerIndex ); // ADDED BY KURI + + i = GetOtherClientIndexByPlayerID( packet->playerId ); + + if ( i < 0 ) + i = GetFreeOtherClientIndex(); + + if ( i >= 0 ) + { + otherClients[ i ].playerId = packet->playerId; + otherClients[ i ].isActive = true; + otherClients[ i ].staticData.Reset(); + // The static data is what is left over in the stream + otherClients[ i ].staticData.Write( ( char* ) bitStream.GetData() + BITS_TO_BYTES( bitStream.GetReadOffset() ), bitStream.GetNumberOfBytesUsed() - BITS_TO_BYTES( bitStream.GetReadOffset() ) ); + } + } + else if ( packet->data[ 0 ] == ID_BROADCAST_PINGS ) + { + PlayerID playerId; + int index; + + bitStream.IgnoreBits( 8 ); // Ignore identifier + + for ( i = 0; i < 32; i++ ) + { + if ( bitStream.Read( playerId.binaryAddress ) == false ) + break; // No remaining data! + + bitStream.Read( playerId.port ); + + index = GetOtherClientIndexByPlayerID( playerId ); + + if ( index >= 0 ) + bitStream.Read( otherClients[ index ].ping ); + else + { + index = GetFreeOtherClientIndex(); + + if ( index >= 0 ) + { + otherClients[ index ].isActive = true; + bitStream.Read( otherClients[ index ].ping ); + otherClients[ index ].playerId = playerId; + otherClients[ index ].staticData.Reset(); + } + + else + bitStream.IgnoreBits( sizeof( short ) * 8 ); + } + } + + DeallocatePacket( packet ); + return 0; + } + else + if ( packet->data[ 0 ] == ID_TIMESTAMP && + packet->length == sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned int) ) + { + + + RakNet::BitStream inBitStream(packet->data, packet->length, false); + + RakNetTime timeStamp; + unsigned char typeId; + unsigned int in_seed; + unsigned int in_nextSeed; + inBitStream.IgnoreBits(8); // ID_TIMESTAMP + inBitStream.Read(timeStamp); + inBitStream.Read(typeId); // ID_SET_RANDOM_NUMBER_SEED ? + + // Check to see if this is a user TIMESTAMP message which + // accidentally has length SetRandomNumberSeedStruct_Size + if ( typeId != ID_SET_RANDOM_NUMBER_SEED ) + return packet; + + inBitStream.Read(in_seed); + inBitStream.Read(in_nextSeed); + + seed = in_seed; + nextSeed = in_nextSeed; + nextSeedUpdate = timeStamp + 9000; // Seeds are updated every 9 seconds + + DeallocatePacket( packet ); + return 0; + } + } + + return packet; } -void RakClient::vftable_24() +void RakClient::DeallocatePacket( Packet *packet ) { - // TODO: RakClient::vftable_24() (saco 10034A50) (server L: 8069280) (bot W: 403290 L: 806CC8A) + RakPeer::DeallocatePacket( packet ); } -void RakClient::vftable_28() +void RakClient::PingServer( void ) { - // TODO: RakClient::vftable_28() (saco 10034370) (server L: 8069290) (bot W: 402BB0 L: 806CCA4) + if ( remoteSystemList == 0 ) + return ; + + RakPeer::Ping( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_2C() +void RakClient::PingServer( const char* host, unsigned short serverPort, unsigned short clientPort, bool onlyReplyOnAcceptingConnections ) { - // TODO: RakClient::vftable_2C() (saco 10034340) (server L: 80692A0) (bot W: 402B80 L: 806CCB2) + RakPeer::Initialize( 1, clientPort, 0 ); + RakPeer::Ping( host, serverPort, onlyReplyOnAcceptingConnections ); } -void RakClient::vftable_30() +int RakClient::GetAveragePing( void ) { - // TODO: RakClient::vftable_30() (saco 100343B0) (server L: 80692B0) (bot W: 402BF0 L: 806CCC0) + if ( remoteSystemList == 0 ) + return -1; + + return RakPeer::GetAveragePing( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_34() +int RakClient::GetLastPing( void ) const { - // TODO: RakClient::vftable_34() (saco 100343E0) (server L: 80692C0) (bot W: 402C20 L: 806CCCE) + if ( remoteSystemList == 0 ) + return -1; + + return RakPeer::GetLastPing( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_38() +int RakClient::GetLowestPing( void ) const { - // TODO: RakClient::vftable_38() (saco 10034410) (server L: 80692D0) (bot W: 402C50 L: 806CCDC) + if ( remoteSystemList == 0 ) + return -1; + + return RakPeer::GetLowestPing( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_3C() +int RakClient::GetPlayerPing( const PlayerID playerId ) { - // TODO: RakClient::vftable_3C() (saco 10034440) (server L: 80692E0) (bot W: 402C80 L: 806CCEA) + int i; + + for ( i = 0; i < 32; i++ ) + if ( otherClients[ i ].playerId == playerId ) + return otherClients[ i ].ping; + + return -1; } -void RakClient::vftable_40() +void RakClient::StartOccasionalPing( void ) { - // TODO: RakClient::vftable_40() (saco 10034490) (server L: 80692F0) (bot W: 402CD0 L: 806CCF8) + RakPeer::SetOccasionalPing( true ); } -void RakClient::vftable_44() +void RakClient::StopOccasionalPing( void ) { - // TODO: RakClient::vftable_44() (saco 100344A0) (server L: 8069300) (bot W: 402CE0 L: 806CD06) + RakPeer::SetOccasionalPing( false ); } bool RakClient::IsConnected( void ) const { - // TODO: RakClient::vftable_48() (saco 100344B0) (server L: 8069310) (bot W: 402CF0 L: 806CD14) - return false; + unsigned short numberOfSystems; + + RakPeer::GetConnectionList( 0, &numberOfSystems ); + return numberOfSystems == 1; } -void RakClient::vftable_4C() +unsigned int RakClient::GetSynchronizedRandomInteger( void ) const { - // TODO: RakClient::vftable_4C() (saco 100344D0) (server L: 8069320) (bot W: 402D10 L: 806CD22) + if ( RakNet::GetTime() > nextSeedUpdate ) + return nextSeed; + else + return seed; } -void RakClient::vftable_50() +bool RakClient::GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ) { - // TODO: RakClient::vftable_50() (saco 10034A60) (server L: 8069330) (bot W: 4032A0 L: 806CD30) + return RakPeer::GenerateCompressionLayer( inputFrequencyTable, inputLayer ); } -void RakClient::vftable_54() +bool RakClient::DeleteCompressionLayer( bool inputLayer ) { - // TODO: RakClient::vftable_54() (saco 10034A70) (server L: 8069340) (bot W: 4032B0 L: 806CD3E) + return RakPeer::DeleteCompressionLayer( inputLayer ); } void RakClient::RegisterAsRemoteProcedureCall( char* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) ) @@ -134,158 +365,243 @@ void RakClient::UnregisterAsRemoteProcedureCall( char* uniqueID ) RakPeer::UnregisterAsRemoteProcedureCall( uniqueID ); } -void RakClient::vftable_64() +bool RakClient::RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakClient::vftable_64() (saco 10034620) (server L: 8069380) (bot W: 402E60 L: 806CD76) + if ( remoteSystemList == 0 ) + return false; + + return RakPeer::RPC( uniqueID, data, bitLength, priority, reliability, orderingChannel, remoteSystemList[ 0 ].playerId, false, shiftTimestamp, networkID, replyFromTarget ); } -void RakClient::vftable_68() +bool RakClient::RPC( char* uniqueID, RakNet::BitStream *parameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakClient::vftable_68() (saco 100345B0) (server L: 8069390) (bot W: 402DF0 L: 806CD84) + if ( remoteSystemList == 0 ) + return false; + + return RakPeer::RPC( uniqueID, parameters, priority, reliability, orderingChannel, remoteSystemList[ 0 ].playerId, false, shiftTimestamp, networkID, replyFromTarget ); } -void RakClient::vftable_6C() +void RakClient::SetTrackFrequencyTable( bool b ) { - // TODO: RakClient::vftable_6C() (saco 10034540) (server L: 80693A0) (bot W: 402D80 L: 806CD92) + RakPeer::SetCompileFrequencyTable( b ); } -void RakClient::vftable_70() +bool RakClient::GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ) { - // TODO: RakClient::vftable_70() (saco 10034690) (server L: 80693B0) (bot W: 402ED0 L: 806CDA0) + return RakPeer::GetOutgoingFrequencyTable( outputFrequencyTable ); } -void RakClient::vftable_74() +float RakClient::GetCompressionRatio( void ) const { - // TODO: RakClient::vftable_74() (saco 100346A0) (server L: 80693C0) (bot W: 402EE0 L: 806CDAE) + return RakPeer::GetCompressionRatio(); } -void RakClient::vftable_78() +float RakClient::GetDecompressionRatio( void ) const { - // TODO: RakClient::vftable_78() (saco 10034AB0) (server L: 80693D0) (bot W: 4032F0 L: 806CDBC) + return RakPeer::GetDecompressionRatio(); } -void RakClient::vftable_7C() +void RakClient::AttachPlugin( PluginInterface *messageHandler ) { - // TODO: RakClient::vftable_7C() (saco 10034AC0) (server L: 80693E0) (bot W: 403300 L: 806CDCA) + RakPeer::AttachPlugin(messageHandler); } -void RakClient::vftable_80() +void RakClient::DetachPlugin( PluginInterface *messageHandler ) { - // TODO: RakClient::vftable_80() (saco 10034AD0) (server L: 80693F0) (bot W: 403310 L: 806CDD8) + RakPeer::DetachPlugin(messageHandler); } -void RakClient::vftable_84() +RakNet::BitStream * RakClient::GetStaticServerData( void ) { - // TODO: RakClient::vftable_84() (saco 10034AE0) (server L: 8069400) (bot W: 403320 L: 806CDE6) + if ( remoteSystemList == 0 ) + return 0; + + return RakPeer::GetRemoteStaticData( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_88() +void RakClient::SetStaticServerData( const char *data, const int length ) { - // TODO: RakClient::vftable_88() (saco 100346F0) (server L: 8069410) (bot W: 402F30 L: 806CDF4) + if ( remoteSystemList == 0 ) + return ; + + RakPeer::SetRemoteStaticData( remoteSystemList[ 0 ].playerId, data, length ); } -void RakClient::vftable_8C() +RakNet::BitStream * RakClient::GetStaticClientData( const PlayerID playerId ) { - // TODO: RakClient::vftable_8C() (saco 10034720) (server L: 8069420) (bot W: 402F60 L: 806CE02) + int i; + + if ( playerId == UNASSIGNED_PLAYER_ID ) + { + return & localStaticData; + } + + else + { + i = GetOtherClientIndexByPlayerID( playerId ); + + if ( i >= 0 ) + { + return & ( otherClients[ i ].staticData ); + } + + } + + return 0; } -void RakClient::vftable_90() +void RakClient::SetStaticClientData( const PlayerID playerId, const char *data, const int length ) { - // TODO: RakClient::vftable_90() (saco 100350E0) (server L: 8069430) (bot W: 403920 L: 806CE10) + int i; + + if ( playerId == UNASSIGNED_PLAYER_ID ) + { + localStaticData.Reset(); + localStaticData.Write( data, length ); + } + + else + { + i = GetOtherClientIndexByPlayerID( playerId ); + + if ( i >= 0 ) + { + otherClients[ i ].staticData.Reset(); + otherClients[ i ].staticData.Write( data, length ); + } + + else + RakPeer::SetRemoteStaticData( playerId, data, length ); + } + } -void RakClient::vftable_94() +void RakClient::SendStaticClientDataToServer( void ) { - // TODO: RakClient::vftable_94() (saco 10035140) (server L: 8069440) (bot W: 403980 L: 806CE1E) + if ( remoteSystemList == 0 ) + return ; + + RakPeer::SendStaticData( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_98() +PlayerID RakClient::GetServerID( void ) const { - // TODO: RakClient::vftable_98() (saco 10034760) (server L: 8069450) (bot W: 402FA0 L: 806CE2C) + if ( remoteSystemList == 0 ) + return UNASSIGNED_PLAYER_ID; + + return remoteSystemList[ 0 ].playerId; } -void RakClient::vftable_9C() +PlayerID RakClient::GetPlayerID( void ) const { - // TODO: RakClient::vftable_9C() (saco 10034790) (server L: 8069460) (bot W: 402FD0 L: 806CE3A) + if ( remoteSystemList == 0 ) + return UNASSIGNED_PLAYER_ID; + + // GetExternalID is more accurate because it reflects our external IP and port to the server. + // GetInternalID only matches the parameters we passed + PlayerID myID = RakPeer::GetExternalID( remoteSystemList[ 0 ].playerId ); + + if ( myID == UNASSIGNED_PLAYER_ID ) + return RakPeer::GetInternalID(); + else + return myID; } -void RakClient::vftable_A0() +PlayerID RakClient::GetInternalID( void ) const { - // TODO: RakClient::vftable_A0() (saco 100347D0) (server L: 8069470) (bot W: 403010 L: 806CE48) + return RakPeer::GetInternalID(); } -void RakClient::vftable_A4() +const char* RakClient::PlayerIDToDottedIP( const PlayerID playerId ) const { - // TODO: RakClient::vftable_A4() (saco 10034AF0) (server L: 8069480) (bot W: 403330 L: 806CE56) + return RakPeer::PlayerIDToDottedIP( playerId ); } -void RakClient::vftable_A8() +void RakClient::PushBackPacket( Packet *packet, bool pushAtHead ) { - // TODO: RakClient::vftable_A8() (saco 10034B00) (server L: 8069490) (bot W: 403340 L: 806CE64) + RakPeer::PushBackPacket(packet, pushAtHead); } -void RakClient::vftable_AC() +void RakClient::SetRouterInterface( RouterInterface *routerInterface ) { - // TODO: RakClient::vftable_AC() (saco 10034B10) (server L: 80694A0) (bot W: 403350 L: 806CE72) + RakPeer::SetRouterInterface(routerInterface); +} +void RakClient::RemoveRouterInterface( RouterInterface *routerInterface ) +{ + RakPeer::RemoveRouterInterface(routerInterface); } -void RakClient::vftable_B0() +void RakClient::SetTimeoutTime( RakNetTime timeMS ) { - // TODO: RakClient::vftable_B0() (saco 10034B20) (server L: 80694B0) (bot W: 403360 L: 806CE80) + RakPeer::SetTimeoutTime( timeMS, GetServerID() ); } -void RakClient::vftable_B4() +bool RakClient::SetMTUSize( int size ) { - // TODO: RakClient::vftable_B4() (saco 10034B30) (server L: 80694C0) (bot W: 403370 L: 806CE8E) + return RakPeer::SetMTUSize( size ); } -void RakClient::vftable_B8() +int RakClient::GetMTUSize( void ) const { - // TODO: RakClient::vftable_B8() (saco 100348E0) (server L: 80694D0) (bot W: 403120 L: 806CE9C) + return RakPeer::GetMTUSize(); } -void RakClient::vftable_BC() +void RakClient::AllowConnectionResponseIPMigration( bool allow ) { - // TODO: RakClient::vftable_BC() (saco 10034B40) (server L: 80694E0) (bot W: 403380 L: 806CEAA) + RakPeer::AllowConnectionResponseIPMigration( allow ); } -void RakClient::vftable_C0() +void RakClient::AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ) { - // TODO: RakClient::vftable_C0() (saco 10034B50) (server L: 80694F0) (bot W: 403390 L: 806CEB8) + RakPeer::AdvertiseSystem( host, remotePort, data, dataLength ); } -void RakClient::vftable_C4() +RakNetStatisticsStruct* const RakClient::GetStatistics( void ) { - // TODO: RakClient::vftable_C4() (saco 10034B60) (server L: 8069500) (bot W: 4033A0 L: 806CEC6) + return RakPeer::GetStatistics( remoteSystemList[ 0 ].playerId ); } -void RakClient::vftable_C8() +void RakClient::ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance) { - // TODO: RakClient::vftable_C8() (saco 10034B70) (server L: 8069510) (bot W: 4033B0 L: 806CED4) + RakPeer::ApplyNetworkSimulator( maxSendBPS, minExtraPing, extraPingVariance ); } -void RakClient::vftable_CC() +bool RakClient::IsNetworkSimulatorActive( void ) { - // TODO: RakClient::vftable_CC() (saco 10034960) (server L: 8069520) (bot W: 4031A0 L: 806CEE2) + return RakPeer::IsNetworkSimulatorActive(); } -void RakClient::vftable_D0() +int RakClient::GetOtherClientIndexByPlayerID( const PlayerID playerId ) { - // TODO: RakClient::vftable_D0() (saco 10034B80) (server L: 8069530) (bot W: 4033C0 L: 806CEF0) + unsigned i; + + for ( i = 0; i < 32; i++ ) + { + if ( otherClients[ i ].playerId == playerId ) + return i; + } + + return -1; } -void RakClient::vftable_D4() +int RakClient::GetFreeOtherClientIndex( void ) { - // TODO: RakClient::vftable_D4() (saco 10034B90) (server L: 8069540) (bot W: 4033D0 L: 806CEFE) + unsigned i; + + for ( i = 0; i < 32; i++ ) + { + if ( otherClients[ i ].isActive == false ) + return i; + } + + return -1; } -void RakClient::vftable_D8() +PlayerIndex RakClient::GetPlayerIndex( void ) { - // TODO: RakClient::vftable_D8() (saco 10034A20) (server L: 8069550) (bot W: 403260 L: 806CF0C) -} - -void RakClient::vftable_DC() -{ - // TODO: RakClient::vftable_DC() (bot L: 0806CF1A) + return localPlayerIndex; } +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/RakClient.h b/raknet/RakClient.h index 56f73e0..b6a79a1 100644 --- a/raknet/RakClient.h +++ b/raknet/RakClient.h @@ -1,51 +1,187 @@ -// TODO: Implement RakClient.h +/// \file +/// \brief Specializes RakPeer to act as a client. +/// +/// \remarks This file originated from my first game, where I only had a server and a client. Now that I have RakPeer, it's depreciated although in such wide use I can't delete it. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_CLIENT_H #define __RAK_CLIENT_H #include "RakPeer.h" -#include "RakClientInterface.h" +#include "RakClientInterface.h" +// #pragma deprecated(RakClient) + +/// This is a user-interface class to act as a game client. All it does is implement some functionality on top of RakPeer. +/// See the individual functions for what the class can do. +/// \brief Defines the functions used by a game client class RakClient : public RakPeer, public RakClientInterface { public: ///Constructor RakClient(); + + /// Destructor + virtual ~RakClient(); - void vftable_0(); - void vftable_4(); + ///Call this to connect the client to the specified host (ip or domain name) and server port. + /// This is a non-blocking connection. You know the connection is successful when IsConnected() returns true + /// or receive gets a packet with the type identifier ID_CONNECTION_REQUEST_ACCEPTED. + /// \param[in] host Dotted IP or a domain name + /// \param[in] serverPort The port on which to connect to \a host + /// \param[in] clientPort The port to use localy + /// \param[in] depreciated Ignore this + /// \param[in] threadSleepTimer How many ms to Sleep each internal update cycle (30 to give the game priority, 0 for regular (recommended), -1 to not Sleep() (may be slower)) + /// \return true on successful initiation, false otherwise + bool Connect( const char* host, unsigned short serverPort, unsigned short clientPort, unsigned int depreciated, int threadSleepTimer ); /// Stops the client, stops synchronized data, and resets all internal data. /// Does nothing if the client is not connected to begin wit /// \param[in] blockDuration how long you should wait for all remaining packets to go outIf you set it to 0 then the disconnection notification probably won't arrive /// \param[in] orderingChannel If blockDuration > 0, the disconnect packet will be sent on this channel void Disconnect( unsigned int blockDuration, unsigned char orderingChannel=0 ); - - void vftable_C(); - void vftable_10(); - void vftable_14(); - void vftable_18(); - void vftable_1C(); - void vftable_20(); - void vftable_24(); - void vftable_28(); - void vftable_2C(); - void vftable_30(); - void vftable_34(); - void vftable_38(); - void vftable_3C(); - void vftable_40(); - void vftable_44(); - + + /// Can be called to use specific public RSA keys. (e and n) + /// In order to prevent altered keys. + /// Will return ID_RSA_PUBLIC_KEY_MISMATCH in a packet if a key has been altered. + /// \param[in] privKeyP Private keys generated from the RSACrypt class. Can be 0 + /// \param[in] privKeyQ Private keys generated from the RSACrypt class. Can be 0 + /// \sa Encryption.cpp + void InitializeSecurity( const char *privKeyP, const char *privKeyQ ); + + /// Set the password to use when connecting to a server. The password persists between connections. + /// \param[in] _password The password to use to connect to a server, or 0 for none. + void SetPassword( const char *_password ); + + /// Returns true if a password was set, false otherwise + /// \return true if a password has previously been set using SetPassword + bool HasPassword( void ) const; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected (Use the Connect function). + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \return False if we are not connected to the specified recipient. True otherwise + bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel ); + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected (Use the Connect function). + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \return False if we are not connected to the specified recipient. True otherwise + bool Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel ); + + /// Gets a packet from the incoming packet queue. + /// Use DeallocatePacket() to deallocate the packet after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + // sa CoreNetworkStructures.h contains struct Packet + Packet* Receive( void ); + + /// Call this to deallocate a packet returned by Receive when you are done handling it. + /// \param[in] packet The packet to deallocate. + void DeallocatePacket( Packet *packet ); + + + /// Send a ping to the server + void PingServer( void ); + + /// Sends a ping request to a server we are not connected to. This will also initialize the + /// networking system if it is not already initialized. You can stop the networking system + /// by calling Disconnect() + /// The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in NetworkTypes.h + /// You can specify if the server should only reply if it has an open connection or not + /// This must be true for LAN broadcast server discovery on "255.255.255.255" + /// or you will get replies from clients as well. + /// \param[in] host The host to contact + /// \param[in] ServerPort the port used by the server + /// \param[in] clientPort the port used to receive the answer + /// \param[in] onlyReplyOnAcceptingConnections if true the server must be ready to accept incomming connection. + void PingServer( const char* host, unsigned short serverPort, unsigned short clientPort, bool onlyReplyOnAcceptingConnections ); + + /// Returns the average of all ping times read. + int GetAveragePing( void ); + + /// Returns the last ping time read + /// \return last ping value + int GetLastPing( void ) const; + + /// Returns the lowest ping time read or -1 if none read yet + /// \return lowest ping value + int GetLowestPing( void ) const; + + /// Returns the last ping for the specified player. This information + /// is broadcast by the server automatically In order to save + /// bandwidth this information is updated only infrequently and only + /// for the first 32 players + /// \param[in] playerId The id of the player you want to have the ping (it might be your id) + /// \return the last ping for this player + /// \note You can read your own ping with + /// this method by passing your own playerId, however for more + /// up-to-date readings you should use one of the three functions + /// above + int GetPlayerPing( const PlayerID playerId ); + + /// Ping the server every so often. This is on by default. + /// In games where you don't care about ping you can callStopOccasionalPing to save the bandwidth. + /// This can be called anytime. + void StartOccasionalPing( void ); + + /// Stop pinging the server every so often. The server is pinged by + /// default. In games where you don't care about ping you can call + /// this to save the bandwidth This will work anytime + void StopOccasionalPing( void ); + /// Returns true if the client is connected to a responsive server /// \return true if connected to a server bool IsConnected( void ) const; - - void vftable_4C(); - void vftable_50(); - void vftable_54(); - + + /// Returns a number automatically synchronized between the server and client which randomly changes every 9 seconds. + /// The time it changes is accurate to within a few ms and is best used to seed random number generators that you want to usually + /// return the same output on all systems. + /// Keep in mind thisisn't perfectly accurate as there is always a very small chance the numbers will by out of synch. + /// You should should confine its use to visual effects or functionality that has a backup method to maintain synchronization. + /// If you don't need this functionality and want to save the bandwidth callStopSynchronizedRandomInteger after starting the server + /// \return A number, which is probably synchronized among all clients and the server. + unsigned int GetSynchronizedRandomInteger( void ) const; + + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ); + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + bool DeleteCompressionLayer( bool inputLayer ); + /// \ingroup RAKNET_RPC /// Register a C or static member function as available for calling as a remote procedure call /// \param[in] uniqueID: A null-terminated unique string to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions @@ -58,46 +194,243 @@ public: /// \param[in] functionPointer: The name of the function to be used as a function pointer. This can be called whether active or not, and registered functions stay registered unless unregistered with UnregisterAsRemoteProcedureCall /// \sa ObjectMemberRPC.cpp void RegisterClassMemberRPC( char* uniqueID, void *functionPointer ); - + /// \ingroup RAKNET_RPC /// Unregisters a C function as available for calling as a remote procedure call that was formerly registeredwith RegisterAsRemoteProcedureCallOnly call offline /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameterpassed to RegisterAsRemoteProcedureCall void UnregisterAsRemoteProcedureCall( char* uniqueID ); + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure\note This is part of the Remote Procedure Call Subsystem + bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure\note This is part of the Remote Procedure Call Subsystem + bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); - void vftable_64(); - void vftable_68(); - void vftable_6C(); - void vftable_70(); - void vftable_74(); - void vftable_78(); - void vftable_7C(); - void vftable_80(); - void vftable_84(); - void vftable_88(); - void vftable_8C(); - void vftable_90(); - void vftable_94(); - void vftable_98(); - void vftable_9C(); - void vftable_A0(); - void vftable_A4(); - void vftable_A8(); - void vftable_AC(); - void vftable_B0(); - void vftable_B4(); - void vftable_B8(); - void vftable_BC(); - void vftable_C0(); - void vftable_C4(); - void vftable_C8(); - void vftable_CC(); - void vftable_D0(); - void vftable_D4(); - void vftable_D8(); - void vftable_DC(); + /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// This value persists between connect calls and defaults to false (no frequency tracking) + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] b True to enable tracking + void SetTrackFrequencyTable( bool b ); + + /// Returns the frequency of outgoing bytes into outputFrequencyTable. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// The purpose is to save to file as either a master frequency table from a sample game session. + /// \pre You should only call this when disconnected + /// \pre Requires that you first enable data frequency tracking by calling SetTrackFrequencyTable(true) + /// \param[out] outputFrequencyTable The Frequency Table used in the compression layer + /// \return false (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + bool GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ); + + /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data + /// \return The compression ratio + float GetCompressionRatio( void ) const; + ///Returns the decompression ratio. A high decompression ratio is good. Decompression is for incoming data + /// \return The decompression ratio + float GetDecompressionRatio( void ) const; + + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a message handler to attach + void AttachPlugin( PluginInterface *messageHandler ); + + ///Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a message handler to detach + void DetachPlugin( PluginInterface *messageHandler ); + + /// The server internally maintains a data struct that is + /// automatically sent to clients when the connect. This is useful + /// to contain data such as the server name or message of the day. + /// Access that struct with this function. The data is entered as an + /// array and stored and returned as a BitStream. Everytime you call + /// GetStaticServerData it resets the read pointer to the start of + /// the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in RakNet::BitStream *copy = ...->GetStaticServerData(...); + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + /// Note that the server may change at any time the + /// data contents and/or its length! + /// \return a bitstream containing static server data + RakNet::BitStream * GetStaticServerData( void ); + + /// The server internally maintains a data struct that is + /// automatically sent to clients when the connect. This is useful + /// to contain data such as the server name or message of the day. + /// Access that struct with this function. The data is entered as an + /// array and stored and returned as a BitStream. Everytime you call + /// GetStaticServerData it resets the read pointer to the start of + /// the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in RakNet::BitStream *copy = ...->GetStaticServerData(...); + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + /// Note that the server may change at any time the + /// data contents and/or its length! + void SetStaticServerData( const char *data, const int length ); + + /// The client internally maintains a data struct that is automatically sent to the server on connection + /// This is useful to contain data such as the player name. Access that struct with this + /// function. Pass UNASSIGNED_PLAYER_ID for playerId to reference your internal data. A playerId value to access the data of another player. + /// *** NOTE ** * If you change any data in the struct the server won't reflect this change unless you manually update it + /// Do so by calling SendStaticClientDataToServer + /// The data is entered as an array and stored and returned as a BitStream. + /// Everytime you call GetStaticServerData it resets the read pointer to the start of the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in + /// RakNet::BitStream *copy = ...->GetStaticServerData(...); + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + RakNet::BitStream * GetStaticClientData( const PlayerID playerId ); + + /// Set Local statistical information for playId. Call this + /// function when you receive statistical information from a + /// client. + /// \param[in] playerId the player ID + /// \param[in] data the packet data + /// \param[in] length the size of the data + void SetStaticClientData( const PlayerID playerId, const char *data, const int length ); + + /// Send the static server data to the server The only time you need + /// to call this function is to update clients that are already + /// connected when you change the static server data by calling + /// GetStaticServerData and directly modifying the object pointed to. + /// Obviously if the connected clients don't need to know the new + /// data you don't need to update them, so it's up to you The server + /// must be active for this to have meaning + void SendStaticClientDataToServer( void ); + + /// Return the player number of the server. + /// \return the server playerID + PlayerID GetServerID( void ) const; + + /// Return the player number the server has assigned to you. + /// \return our player ID + /// \note that unlike in previous versions, this is a struct and is not sequential + PlayerID GetPlayerID( void ) const; + + ///Return the unique address identifier that represents you on the the network and is based on your local IP / portNote that unlike in previous versions, this is a struct and is not sequential + PlayerID GetInternalID( void ) const; + + /// Returns the dotted IP address for the specified playerId + /// \param[in] playerId Any player ID other than UNASSIGNED_PLAYER_ID, even if that player is not currently connected + /// \return a dotted notation string representation of the address of playerId. + const char* PlayerIDToDottedIP( const PlayerID playerId ) const; + + /// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately + /// \param[in] packet the packet to delayed + /// \pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + void PushBackPacket( Packet *packet, bool pushAtHead ); + + /// \Internal + void SetRouterInterface( RouterInterface *routerInterface ); + + /// \Internal + void RemoveRouterInterface( RouterInterface *routerInterface ); + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + void SetTimeoutTime( RakNetTime timeMS ); + + /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + bool SetMTUSize( int size ); + + /// Returns the current MTU size + /// \return The current MTU size + int GetMTUSize( void ) const; + + ///Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary + /// when connection to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections + void AllowConnectionResponseIPMigration( bool allow ); + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength length of data in bytes. Use 0 if no data. + void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ); + + /// Returns a structure containing a large set of network statistics for server + /// You can map this data to a string using the C style StatisticsToString() function + /// \sa RakNetStatistics.h + RakNetStatisticsStruct * const GetStatistics( void ); + + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS The maximum window size for the windowing algorithm before 100% packetloss. The chance of each packet being lost is RakNetStatisticsStruct::windowSize / maxSendBPS . Recommended you set this between 100 and 1000 with lower numbers corresponding to greater packetloss. This approximates the real condition where the more data you send the more likely you are to lose data. Call with 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance); + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + bool IsNetworkSimulatorActive( void ); + + /// @internal + /// Retrieve the player index corresponding to this client. + PlayerIndex GetPlayerIndex( void ); + private: + + int GetOtherClientIndexByPlayerID( const PlayerID playerId ); + int GetFreeOtherClientIndex( void ); + RakNet::BitStream password; + + struct OtherClientsStruct + { + ///The id of the other player + + PlayerID playerId; + ///The average ping time + + short ping; + ///Other client's Static Client Data + + RakNet::BitStream staticData; + ///Tel whether the remote client is active or not + + bool isActive; + } + + otherClients[ 32 ]; + unsigned int seed; + unsigned int nextSeed; + RakNetTime nextSeedUpdate; + PlayerIndex localPlayerIndex; + PlayerID externalPlayerID; }; #endif diff --git a/raknet/RakClientInterface.h b/raknet/RakClientInterface.h index 14dbfd0..16ea395 100644 --- a/raknet/RakClientInterface.h +++ b/raknet/RakClientInterface.h @@ -1,18 +1,49 @@ -// TODO: Implement RakClientInterface.h +/// \file +/// \brief An interface for RakClient. Simply contains all user functions as pure virtuals. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_CLIENT_INTERFACE_H #define __RAK_CLIENT_INTERFACE_H #include "NetworkTypes.h" +#include "PacketPriority.h" +#include "RakPeerInterface.h" +#include "BitStream.h" #include "RakNetStatistics.h" +/// This is a user-interface class to act as a game client. All it does is implement some functionality on top of RakPeer. +/// See the individual functions for what the class can do. +/// \brief Defines the functions used by a game client class RakClientInterface { public: + /// Destructor + virtual ~RakClientInterface() {} - virtual void vftable_0()=0; - virtual void vftable_4()=0; + ///Call this to connect the client to the specified host (ip or domain name) and server port. + /// This is a non-blocking connection. You know the connection is successful when IsConnected() returns true + /// or receive gets a packet with the type identifier ID_CONNECTION_REQUEST_ACCEPTED. + /// \param[in] host Dotted IP or a domain name + /// \param[in] serverPort The port on which to connect to \a host + /// \param[in] clientPort The port to use localy + /// \param[in] depreciated Ignore this + /// \param[in] threadSleepTimer How many ms to Sleep each internal update cycle (30 to give the game priority, 0 for regular (recommended), -1 to not Sleep() (may be slower)) + /// \return true on successful initiation, false otherwise + virtual bool Connect( const char* host, unsigned short serverPort, unsigned short clientPort, unsigned int depreciated, int threadSleepTimer )=0; /// Stops the client, stops synchronized data, and resets all internal data. /// Does nothing if the client is not connected to begin wit @@ -20,29 +51,131 @@ public: /// \param[in] orderingChannel If blockDuration > 0, the disconnect packet will be sent on this channel virtual void Disconnect( unsigned int blockDuration, unsigned char orderingChannel=0 )=0; - virtual void vftable_C()=0; - virtual void vftable_10()=0; - virtual void vftable_14()=0; - virtual void vftable_18()=0; - virtual void vftable_1C()=0; - virtual void vftable_20()=0; - virtual void vftable_24()=0; - virtual void vftable_28()=0; - virtual void vftable_2C()=0; - virtual void vftable_30()=0; - virtual void vftable_34()=0; - virtual void vftable_38()=0; - virtual void vftable_3C()=0; - virtual void vftable_40()=0; - virtual void vftable_44()=0; + /// Can be called to use specific public RSA keys. (e and n) + /// In order to prevent altered keys. + /// Will return ID_RSA_PUBLIC_KEY_MISMATCH in a packet if a key has been altered. + /// \param[in] privKeyP Private keys generated from the RSACrypt class. Can be 0 + /// \param[in] privKeyQ Private keys generated from the RSACrypt class. Can be 0 + /// \sa Encryption.cpp + virtual void InitializeSecurity( const char *privKeyP, const char *privKeyQ )=0; + + /// Set the password to use when connecting to a server. The password persists between connections. + /// \param[in] _password The password to use to connect to a server, or 0 for none. + virtual void SetPassword( const char *_password )=0; + + /// Returns true if a password was set, false otherwise + /// \return true if a password has previously been set using SetPassword + virtual bool HasPassword( void ) const=0; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected (Use the Connect function). + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \return False if we are not connected to the specified recipient. True otherwise + virtual bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel )=0; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected (Use the Connect function). + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \return False if we are not connected to the specified recipient. True otherwise + virtual bool Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel )=0; + + /// Gets a packet from the incoming packet queue. + /// Use DeallocatePacket() to deallocate the packet after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + // sa CoreNetworkStructures.h contains struct Packet + virtual Packet* Receive( void )=0; + + /// Call this to deallocate a packet returned by Receive when you are done handling it. + /// \param[in] packet The packet to deallocate. + virtual void DeallocatePacket( Packet *packet )=0; + + /// Send a ping to the server + virtual void PingServer( void )=0; + + /// Sends a ping request to a server we are not connected to. This will also initialize the + /// networking system if it is not already initialized. You can stop the networking system + /// by calling Disconnect() + /// The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in NetworkTypes.h + /// You can specify if the server should only reply if it has an open connection or not + /// This must be true for LAN broadcast server discovery on "255.255.255.255" + /// or you will get replies from clients as well. + /// \param[in] host The host to contact + /// \param[in] ServerPort the port used by the server + /// \param[in] clientPort the port used to receive the answer + /// \param[in] onlyReplyOnAcceptingConnections if true the server must be ready to accept incomming connection. + virtual void PingServer( const char* host, unsigned short serverPort, unsigned short clientPort, bool onlyReplyOnAcceptingConnections )=0; + + /// Returns the average of all ping times read. + virtual int GetAveragePing( void )=0; + + /// Returns the last ping time read + /// \return last ping value + virtual int GetLastPing( void ) const=0; + + /// Returns the lowest ping time read or -1 if none read yet + /// \return lowest ping value + virtual int GetLowestPing( void ) const=0; + + /// Returns the last ping for the specified player. This information + /// is broadcast by the server automatically In order to save + /// bandwidth this information is updated only infrequently and only + /// for the first 32 players + /// \param[in] playerId The id of the player you want to have the ping (it might be your id) + /// \return the last ping for this player + /// \note You can read your own ping with + /// this method by passing your own playerId, however for more + /// up-to-date readings you should use one of the three functions + /// above + virtual int GetPlayerPing( const PlayerID playerId )=0; + + /// Ping the server every so often. This is on by default. + /// In games where you don't care about ping you can callStopOccasionalPing to save the bandwidth. + /// This can be called anytime. + virtual void StartOccasionalPing( void )=0; + + /// Stop pinging the server every so often. The server is pinged by + /// default. In games where you don't care about ping you can call + /// this to save the bandwidth This will work anytime + virtual void StopOccasionalPing( void )=0; /// Returns true if the client is connected to a responsive server /// \return true if connected to a server virtual bool IsConnected( void ) const=0; - virtual void vftable_4C()=0; - virtual void vftable_50()=0; - virtual void vftable_54()=0; + /// Returns a number automatically synchronized between the server and client which randomly changes every 9 seconds. + /// The time it changes is accurate to within a few ms and is best used to seed random number generators that you want to usually + /// return the same output on all systems. + /// Keep in mind thisisn't perfectly accurate as there is always a very small chance the numbers will by out of synch. + /// You should should confine its use to visual effects or functionality that has a backup method to maintain synchronization. + /// If you don't need this functionality and want to save the bandwidth callStopSynchronizedRandomInteger after starting the server + /// \return A number, which is probably synchronized among all clients and the server. + virtual unsigned int GetSynchronizedRandomInteger( void ) const=0; + + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + virtual bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer )=0; + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + virtual bool DeleteCompressionLayer( bool inputLayer )=0; /// \ingroup RAKNET_RPC /// Register a C or static member function as available for calling as a remote procedure call @@ -62,38 +195,207 @@ public: /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameterpassed to RegisterAsRemoteProcedureCall virtual void UnregisterAsRemoteProcedureCall( char* uniqueID )=0; - virtual void vftable_64()=0; - virtual void vftable_68()=0; - virtual void vftable_6C()=0; - virtual void vftable_70()=0; - virtual void vftable_74()=0; - virtual void vftable_78()=0; - virtual void vftable_7C()=0; - virtual void vftable_80()=0; - virtual void vftable_84()=0; - virtual void vftable_88()=0; - virtual void vftable_8C()=0; - virtual void vftable_90()=0; - virtual void vftable_94()=0; - virtual void vftable_98()=0; - virtual void vftable_9C()=0; - virtual void vftable_A0()=0; - virtual void vftable_A4()=0; - virtual void vftable_A8()=0; - virtual void vftable_AC()=0; - virtual void vftable_B0()=0; - virtual void vftable_B4()=0; - virtual void vftable_B8()=0; - virtual void vftable_BC()=0; - virtual void vftable_C0()=0; - virtual void vftable_C4()=0; - virtual void vftable_C8()=0; - virtual void vftable_CC()=0; - virtual void vftable_D0()=0; - virtual void vftable_D4()=0; - virtual void vftable_D8()=0; - virtual void vftable_DC()=0; + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + virtual bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + virtual bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// This value persists between connect calls and defaults to false (no frequency tracking) + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] b True to enable tracking + virtual void SetTrackFrequencyTable( bool b )=0; + + /// Returns the frequency of outgoing bytes into outputFrequencyTable. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// The purpose is to save to file as either a master frequency table from a sample game session. + /// \pre You should only call this when disconnected + /// \pre Requires that you first enable data frequency tracking by calling SetTrackFrequencyTable(true) + /// \param[out] outputFrequencyTable The Frequency Table used in the compression layer + /// \return false (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + virtual bool GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] )=0; + + /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data + /// \return The compression ratio + virtual float GetCompressionRatio( void ) const=0; + + ///Returns the decompression ratio. A high decompression ratio is good. Decompression is for incoming data + /// \return The decompression ratio + virtual float GetDecompressionRatio( void ) const=0; + + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a message handler to attach + virtual void AttachPlugin( PluginInterface *messageHandler )=0; + + ///Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a message handler to detach + virtual void DetachPlugin( PluginInterface *messageHandler )=0; + + /// The server internally maintains a data struct that is + /// automatically sent to clients when the connect. This is useful + /// to contain data such as the server name or message of the day. + /// Access that struct with this function. The data is entered as an + /// array and stored and returned as a BitStream. Everytime you call + /// GetStaticServerData it resets the read pointer to the start of + /// the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in RakNet::BitStream *copy = ...->GetStaticServerData(...)=0; + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + /// Note that the server may change at any time the + /// data contents and/or its length! + /// \return a bitstream containing static server data + virtual RakNet::BitStream * GetStaticServerData( void )=0; + + /// The server internally maintains a data struct that is + /// automatically sent to clients when the connect. This is useful + /// to contain data such as the server name or message of the day. + /// Access that struct with this function. The data is entered as an + /// array and stored and returned as a BitStream. Everytime you call + /// GetStaticServerData it resets the read pointer to the start of + /// the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in RakNet::BitStream *copy = ...->GetStaticServerData(...)=0; + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + /// Note that the server may change at any time the + /// data contents and/or its length! + virtual void SetStaticServerData( const char *data, const int length )=0; + + /// The client internally maintains a data struct that is automatically sent to the server on connection + /// This is useful to contain data such as the player name. Access that struct with this + /// function. Pass UNASSIGNED_PLAYER_ID for playerId to reference your internal data. A playerId value to access the data of another player. + /// *** NOTE ** * If you change any data in the struct the server won't reflect this change unless you manually update it + /// Do so by calling SendStaticClientDataToServer + /// The data is entered as an array and stored and returned as a BitStream. + /// Everytime you call GetStaticServerData it resets the read pointer to the start of the bitstream. To do multiple reads without reseting the pointer + /// Maintain a pointer copy to the bitstream as in + /// RakNet::BitStream *copy = ...->GetStaticServerData(...)=0; + /// To store a bitstream, use the GetData() and GetNumberOfBytesUsed() methods + /// of the bitstream for the 2nd and 3rd parameters + virtual RakNet::BitStream * GetStaticClientData( const PlayerID playerId )=0; + + /// Set Local statistical information for playId. Call this + /// function when you receive statistical information from a + /// client. + /// \param[in] playerId the player ID + /// \param[in] data the packet data + /// \param[in] length the size of the data + virtual void SetStaticClientData( const PlayerID playerId, const char *data, const int length )=0; + + /// Send the static server data to the server The only time you need + /// to call this function is to update clients that are already + /// connected when you change the static server data by calling + /// GetStaticServerData and directly modifying the object pointed to. + /// Obviously if the connected clients don't need to know the new + /// data you don't need to update them, so it's up to you The server + /// must be active for this to have meaning + virtual void SendStaticClientDataToServer( void )=0; + + /// Return the player number of the server. + /// \return the server playerID + virtual PlayerID GetServerID( void ) const=0; + + /// Return the player number the server has assigned to you. + /// \return our player ID + /// \note that unlike in previous versions, this is a struct and is not sequential + virtual PlayerID GetPlayerID( void ) const=0; + + ///Return the unique address identifier that represents you on the the network and is based on your local IP / portNote that unlike in previous versions, this is a struct and is not sequential + virtual PlayerID GetInternalID( void ) const=0; + + /// Returns the dotted IP address for the specified playerId + /// \param[in] playerId Any player ID other than UNASSIGNED_PLAYER_ID, even if that player is not currently connected + /// \return a dotted notation string representation of the address of playerId. + virtual const char* PlayerIDToDottedIP( const PlayerID playerId ) const=0; + + /// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately + /// \param[in] packet the packet to delayed + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + virtual void PushBackPacket( Packet *packet, bool pushAtHead )=0; + + /// \Internal + virtual void SetRouterInterface( RouterInterface *routerInterface )=0; + + /// \Internal + virtual void RemoveRouterInterface( RouterInterface *routerInterface )=0; + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + virtual void SetTimeoutTime( RakNetTime timeMS )=0; + + /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + virtual bool SetMTUSize( int size )=0; + + /// Returns the current MTU size + /// \return The current MTU size + virtual int GetMTUSize( void ) const=0; + + ///Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary + /// when connection to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections + virtual void AllowConnectionResponseIPMigration( bool allow )=0; + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength length of data in bytes. Use 0 if no data. + virtual void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength )=0; + + /// Returns a structure containing a large set of network statistics for server + /// You can map this data to a string using the C style StatisticsToString() function + /// \sa RakNetStatistics.h + virtual RakNetStatisticsStruct * const GetStatistics( void )=0; + + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS Maximum bits per second to send. Packetloss grows linearly. 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + virtual void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance)=0; + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + virtual bool IsNetworkSimulatorActive( void )=0; + + /// @internal + /// Retrieve the player index corresponding to this client. + virtual PlayerIndex GetPlayerIndex( void )=0; }; #endif diff --git a/raknet/RakNetCommandParser.cpp b/raknet/RakNetCommandParser.cpp new file mode 100644 index 0000000..25aabdd --- /dev/null +++ b/raknet/RakNetCommandParser.cpp @@ -0,0 +1,294 @@ +#include "RakNetCommandParser.h" +#include "TransportInterface.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +RakNetCommandParser::RakNetCommandParser() +{ + RegisterCommand(4, "Initialize","( unsigned short maxConnections, unsigned short localPort, int _threadSleepTimer, const char *forceHostAddress );"); + RegisterCommand(0,"InitializeSecurity","();"); + RegisterCommand(0,"DisableSecurity","( void );"); + RegisterCommand(1,"SetMaximumIncomingConnections","( unsigned short numberAllowed );"); + RegisterCommand(0,"GetMaximumIncomingConnections","( void ) const;"); + RegisterCommand(4,"Connect","( const char* host, unsigned short remotePort, char* passwordData, int passwordDataLength );"); + RegisterCommand(2,"Disconnect","( unsigned int blockDuration, unsigned char orderingChannel=0 );"); + RegisterCommand(0,"IsActive","( void ) const;"); + RegisterCommand(0,"GetConnectionList","() const;"); + RegisterCommand(4,"CloseConnection","( const PlayerID target, bool sendDisconnectionNotification, unsigned char orderingChannel=0 );"); + RegisterCommand(2,"GetIndexFromPlayerID","( const PlayerID playerId );"); + RegisterCommand(1,"GetPlayerIDFromIndex","( int index );"); + RegisterCommand(2,"AddToBanList","( const char *IP, RakNetTime milliseconds=0 );"); + RegisterCommand(1,"RemoveFromBanList","( const char *IP );"); + RegisterCommand(0,"ClearBanList","( void );"); + RegisterCommand(1,"IsBanned","( const char *IP );"); + RegisterCommand(2,"Ping1","( const PlayerID target );"); + RegisterCommand(3,"Ping2","( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections );"); + RegisterCommand(2,"GetAveragePing","( const PlayerID playerId );"); + RegisterCommand(2,"GetLastPing","( const PlayerID playerId ) const;"); + RegisterCommand(2,"GetLowestPing","( const PlayerID playerId ) const;"); + RegisterCommand(1,"SetOccasionalPing","( bool doPing );"); + RegisterCommand(2,"SetOfflinePingResponse","( const char *data, const unsigned int length );"); + RegisterCommand(0,"GetInternalID","( void ) const;"); + RegisterCommand(2,"GetExternalID","( const PlayerID target ) const;"); + RegisterCommand(3,"SetTimeoutTime","( RakNetTime timeMS, const PlayerID target );"); + RegisterCommand(1,"SetMTUSize","( int size );"); + RegisterCommand(0,"GetMTUSize","( void ) const;"); + RegisterCommand(0,"GetNumberOfAddresses","( void );"); + RegisterCommand(2,"PlayerIDToDottedIP","( const PlayerID playerId ) const;"); + RegisterCommand(2,"IPToPlayerID","( const char* host, unsigned short remotePort );"); + RegisterCommand(1,"GetLocalIP","( unsigned int index );"); + RegisterCommand(1,"AllowConnectionResponseIPMigration","( bool allow );"); + RegisterCommand(4,"AdvertiseSystem","( const char *host, unsigned short remotePort, const char *data, int dataLength );"); + RegisterCommand(2,"SetIncomingPassword","( const char* passwordData, int passwordDataLength );"); + RegisterCommand(0,"GetIncomingPassword","( void );"); + RegisterCommand(3,"ApplyNetworkSimulator","( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance);"); + RegisterCommand(0,"IsNetworkSimulatorActive","( void );"); +} +RakNetCommandParser::~RakNetCommandParser() +{ +} +void RakNetCommandParser::SetRakPeerInterface(RakPeerInterface *rakPeer) +{ + peer=rakPeer; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +bool RakNetCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString) +{ + if (peer==0) + return false; + + if (strcmp(command, "Initialize")==0) + { + ReturnResult(peer->Initialize((unsigned short)atoi(parameterList[0]), (unsigned short)atoi(parameterList[1]),atoi(parameterList[2]),parameterList[3]), command, transport, playerId); + } + else if (strcmp(command, "InitializeSecurity")==0) + { + peer->InitializeSecurity(0,0,0,0); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "DisableSecurity")==0) + { + peer->DisableSecurity(); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "SetMaximumIncomingConnections")==0) + { + peer->SetMaximumIncomingConnections((unsigned short)atoi(parameterList[0])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "GetMaximumIncomingConnections")==0) + { + ReturnResult(peer->GetMaximumIncomingConnections(), command, transport, playerId); + } + else if (strcmp(command, "Connect")==0) + { + ReturnResult(peer->Connect(parameterList[0], (unsigned short)atoi(parameterList[1]),parameterList[2],atoi(parameterList[3])), command, transport, playerId); + } + else if (strcmp(command, "Disconnect")==0) + { + peer->Disconnect(atoi(parameterList[0]), (unsigned char)atoi(parameterList[1])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "IsActive")==0) + { + ReturnResult(peer->IsActive(), command, transport, playerId); + } + else if (strcmp(command, "GetConnectionList")==0) + { + PlayerID remoteSystems[32]; + unsigned short count=32; + unsigned i; + if (peer->GetConnectionList(remoteSystems, &count)) + { + if (count==0) + { + transport->Send(playerId, "GetConnectionList() returned no systems connected.\r\n"); + } + else + { + transport->Send(playerId, "GetConnectionList() returned:\r\n"); + for (i=0; i < count; i++) + transport->Send(playerId, "%i %s %i:%i\r\n", i, peer->PlayerIDToDottedIP(remoteSystems[i]), remoteSystems[i].binaryAddress, remoteSystems[i].port); + } + } + else + transport->Send(playerId, "GetConnectionList() returned false.\r\n"); + } + else if (strcmp(command, "CloseConnection")==0) + { + peer->CloseConnection(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1])),atoi(parameterList[2])!=0,(unsigned char)atoi(parameterList[3])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "GetIndexFromPlayerID")==0) + { + ReturnResult(peer->GetIndexFromPlayerID(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "GetPlayerIDFromIndex")==0) + { + ReturnResult(peer->GetPlayerIDFromIndex(atoi(parameterList[0])), command, transport, playerId); + } + else if (strcmp(command, "AddToBanList")==0) + { + peer->AddToBanList(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "RemoveFromBanList")==0) + { + peer->RemoveFromBanList(parameterList[0]); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "ClearBanList")==0) + { + peer->ClearBanList(); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "IsBanned")==0) + { + ReturnResult(peer->IsBanned(parameterList[0]), command, transport, playerId); + } + else if (strcmp(command, "Ping1")==0) + { + peer->Ping(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "Ping2")==0) + { + peer->Ping(parameterList[0], (unsigned short) atoi(parameterList[1]), atoi(parameterList[2])!=0); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "GetAveragePing")==0) + { + ReturnResult(peer->GetAveragePing(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "GetLastPing")==0) + { + ReturnResult(peer->GetLastPing(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "GetLowestPing")==0) + { + ReturnResult(peer->GetLowestPing(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "SetOccasionalPing")==0) + { + peer->SetOccasionalPing(atoi(parameterList[0])!=0); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "SetOfflinePingResponse")==0) + { + peer->SetOfflinePingResponse(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "GetInternalID")==0) + { + ReturnResult(peer->GetInternalID(), command, transport, playerId); + } + else if (strcmp(command, "GetExternalID")==0) + { + ReturnResult(peer->GetExternalID(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "SetTimeoutTime")==0) + { + peer->SetTimeoutTime(atoi(parameterList[0]), IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "SetMTUSize")==0) + { + ReturnResult(peer->SetMTUSize(atoi(parameterList[0])), command, transport, playerId); + } + else if (strcmp(command, "GetMTUSize")==0) + { + ReturnResult(peer->GetMTUSize(), command, transport, playerId); + } + else if (strcmp(command, "GetNumberOfAddresses")==0) + { + ReturnResult((int)peer->GetNumberOfAddresses(), command, transport, playerId); + } + else if (strcmp(command, "PlayerIDToDottedIP")==0) + { + ReturnResult((char*)peer->PlayerIDToDottedIP(IntegersToPlayerID(atoi(parameterList[0]), atoi(parameterList[1]))), command, transport, playerId); + } + else if (strcmp(command, "IPToPlayerID")==0) + { + PlayerID output; + peer->IPToPlayerID(parameterList[0],(unsigned short) atoi(parameterList[1]),&output); + if (output==UNASSIGNED_PLAYER_ID) + { + transport->Send(playerId, "IPToPlayerID(): UNASSIGNED_PLAYER_ID.\r\n"); + } + else + { + transport->Send(playerId, "IPToPlayerID(): %s %i:%i\r\n", peer->PlayerIDToDottedIP(output), output.binaryAddress, output.port); + } + } + else if (strcmp(command, "GetLocalIP")==0) + { + ReturnResult((char*) peer->GetLocalIP(atoi(parameterList[0])), command, transport, playerId); + } + else if (strcmp(command, "AllowConnectionResponseIPMigration")==0) + { + peer->AllowConnectionResponseIPMigration(atoi(parameterList[0])!=0); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "AdvertiseSystem")==0) + { + peer->AdvertiseSystem(parameterList[0], (unsigned short) atoi(parameterList[1]),parameterList[2],atoi(parameterList[3])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "ApplyNetworkSimulator")==0) + { + peer->ApplyNetworkSimulator(atof(parameterList[0]), (unsigned short) atoi(parameterList[1]),(unsigned short) atoi(parameterList[2])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "IsNetworkSimulatorActive")==0) + { + ReturnResult(peer->IsNetworkSimulatorActive(), command, transport, playerId); + } + else if (strcmp(command, "SetIncomingPassword")==0) + { + peer->SetIncomingPassword(parameterList[0], atoi(parameterList[1])); + ReturnResult(command, transport, playerId); + } + else if (strcmp(command, "GetIncomingPassword")==0) + { + char password[256]; + int passwordLength; + peer->GetIncomingPassword(password, &passwordLength); + if (passwordLength) + ReturnResult((char*)password, command, transport, playerId); + else + ReturnResult(0, command, transport, playerId); + } + + return true; +} +char *RakNetCommandParser::GetName(void) const +{ + return "RakNet"; +} +void RakNetCommandParser::SendHelp(TransportInterface *transport, PlayerID playerId) +{ + if (peer) + { + transport->Send(playerId, "The RakNet parser provides mirror functions to RakPeer\r\n"); + transport->Send(playerId, "PlayerIDs take two parameters: send .\r\n"); + transport->Send(playerId, "For bool, send 1 or 0.\r\n"); + } + else + { + transport->Send(playerId, "Parser not active. Call SetRakPeerInterface.\r\n"); + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/RakNetCommandParser.h b/raknet/RakNetCommandParser.h new file mode 100644 index 0000000..d392f56 --- /dev/null +++ b/raknet/RakNetCommandParser.h @@ -0,0 +1,60 @@ +/// \file +/// \brief Contains RakNetCommandParser , used to send commands to an instance of RakPeer +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __RAKNET_COMMAND_PARSER +#define __RAKNET_COMMAND_PARSER + +#include "CommandParserInterface.h" +#include "Export.h" +class RakPeerInterface; + +/// \brief This allows a console client to call most of the functions in RakPeer +class RAK_DLL_EXPORT RakNetCommandParser : public CommandParserInterface +{ +public: + RakNetCommandParser(); + ~RakNetCommandParser(); + + /// Given \a command with parameters \a parameterList , do whatever processing you wish. + /// \param[in] command The command to process + /// \param[in] numParameters How many parameters were passed along with the command + /// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on. + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that sent this command. + /// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing + bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString); + + /// You are responsible for overriding this function and returning a static string, which will identifier your parser. + /// This should return a static string + /// \return The name that you return. + char *GetName(void) const; + + /// A callback for when you are expected to send a brief description of your parser to \a playerId + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that requested help. + void SendHelp(TransportInterface *transport, PlayerID playerId); + + /// Records the instance of RakPeer to perform the desired commands on + /// \param[in] rakPeer The RakPeer instance, or a derived class (e.g. RakServer or RakClient) + void SetRakPeerInterface(RakPeerInterface *rakPeer); +protected: + + /// Which instance of RakPeer we are working on. Set from SetRakPeerInterface() + RakPeerInterface *peer; +}; + +#endif diff --git a/raknet/RakNetDefines.h b/raknet/RakNetDefines.h new file mode 100644 index 0000000..c467222 --- /dev/null +++ b/raknet/RakNetDefines.h @@ -0,0 +1,21 @@ +/// Define __GET_TIME_64BIT to have RakNetTime use a 64, rather than 32 bit value. A 32 bit value will overflow after about 5 weeks. +/// However, this doubles the bandwidth use for sending times, so don't do it unless you have a reason to. +/// Disabled by default. +// #define __GET_TIME_64BIT + +/// Makes RakNet threadsafe +/// Define this if you use the same instance of RakPeer from multiple threads +/// Otherwise leave it undefined, since it makes things an order of magnitude slower. +/// Disabled by default +// #define _RAKNET_THREADSAFE + +/// Can interrupt a Sleep() if a message is incoming. Useful to define if you pass a large sleep value to RakPeer::Initialize +// #define USE_WAIT_FOR_MULTIPLE_EVENTS + +/// Define __BITSTREAM_NATIVE_END to NOT support endian swapping in the BitStream class. This is faster and is what you should use +/// unless you actually plan to have different endianness systems connect to each other +/// Enabled by default. +#define __BITSTREAM_NATIVE_END + +/// Maximum (stack) size to use with _alloca before using new and delete instead. +#define MAX_ALLOCA_STACK_ALLOCATION 1048576 diff --git a/raknet/RakNetStatistics.cpp b/raknet/RakNetStatistics.cpp index 8bd005a..679db45 100644 --- a/raknet/RakNetStatistics.cpp +++ b/raknet/RakNetStatistics.cpp @@ -19,10 +19,6 @@ #include "BitStream.h" // BITS_TO_BYTES #include "GetTime.h" -RakNetTime connectionStartTimeSaved=0; -uint64_t totalBitsSentSaved=0; -uint64_t bitsReceivedSaved=0; - // Verbosity level currently supports 0 (low), 1 (medium), 2 (high) // Buffer must be hold enough to hold the output string. See the source to get an idea of how many bytes will be output void StatisticsToString( RakNetStatisticsStruct *s, char *buffer, int verbosityLevel ) @@ -33,184 +29,147 @@ void StatisticsToString( RakNetStatisticsStruct *s, char *buffer, int verbosityL return ; } - switch ( verbosityLevel ) + if ( verbosityLevel == 0 ) { - case 1: - { - RakNetTime time = RakNet::GetTime(); - double elapsedTime; - double bpsSent; - double bpsReceived; + // Verbosity level 0 + sprintf( buffer, + "Total bytes sent: %u\n" + "Total bytes received: %u\n" + "Packetloss: %.1f%%\n", + BITS_TO_BYTES( s->totalBitsSent ), + BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), + 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent ); + } - if ( connectionStartTimeSaved == 0 ) - connectionStartTimeSaved = s->connectionStartTime; + else if ( verbosityLevel == 1 ) + { + RakNetTime time = RakNet::GetTime(); + double elapsedTime; + double bpsSent; + double bpsReceived; + elapsedTime = (time-s->connectionStartTime) / 1000.0f; + bpsSent = (double) s->totalBitsSent / elapsedTime; + bpsReceived= (double) s->bitsReceived / elapsedTime; + // Verbosity level 1 - elapsedTime = (time-connectionStartTimeSaved) / 1000.0f; - bpsSent = (double) ((unsigned)s->totalBitsSent-totalBitsSentSaved) / elapsedTime; - bpsReceived = (double) ((unsigned)s->bitsReceived-bitsReceivedSaved) / elapsedTime; + sprintf( buffer, + "Messages in Send buffer: %u\n" + "Messages sent: %u\n" + "Bytes sent: %u\n" + "Acks sent: %u\n" + "Acks in send buffer: %u\n" + "Messages waiting for ack: %u\n" + "Messages resent: %u\n" + "Bytes resent: %u\n" + "Packetloss: %.1f%%\n" + "Messages received: %u\n" + "Bytes received: %u\n" + "Acks received: %u\n" + "Duplicate acks received: %u\n" + "Inst. KBits per second: %.1f\n" + "KBits per second sent:\t\t\t%.1f\n" + "KBits per second received:\t\t%.1f\n", + s->messageSendBuffer[ SYSTEM_PRIORITY ] + s->messageSendBuffer[ HIGH_PRIORITY ] + s->messageSendBuffer[ MEDIUM_PRIORITY ] + s->messageSendBuffer[ LOW_PRIORITY ], + s->messagesSent[ SYSTEM_PRIORITY ] + s->messagesSent[ HIGH_PRIORITY ] + s->messagesSent[ MEDIUM_PRIORITY ] + s->messagesSent[ LOW_PRIORITY ], + BITS_TO_BYTES( s->totalBitsSent ), + s->acknowlegementsSent, + s->acknowlegementsPending, + s->messagesOnResendQueue, + s->messageResends, + BITS_TO_BYTES( s->messagesTotalBitsResent ), + 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent, + s->duplicateMessagesReceived + s->invalidMessagesReceived + s->messagesReceived, + BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), + s->acknowlegementsReceived, + s->duplicateAcknowlegementsReceived, + s->bitsPerSecond / 1000.0, + bpsSent / 1000.0, + bpsReceived / 1000.0); + } + else + { + RakNetTime time = RakNet::GetTime(); + double elapsedTime; + double bpsSent; + double bpsReceived; + elapsedTime = (time-s->connectionStartTime) / 1000.0f; + bpsSent = (double) s->totalBitsSent / elapsedTime; + bpsReceived= (double) s->bitsReceived / elapsedTime; - totalBitsSentSaved = s->totalBitsSent; - bitsReceivedSaved = s->bitsReceived; - connectionStartTimeSaved = RakNet::GetTime(); - - // Verbosity level 1 - sprintf( buffer, - "Messages in Send buffer: %u\n" - "Messages sent: %u\n" - "Bytes sent: %u\n" - "Acks sent: %u\n" - "Acks in send buffer: %u\n" - "Messages waiting for ack: %u\n" - "Messages resent: %u\n" - "Bytes resent: %u\n" - "Packetloss: %.1f%%\n" - "Messages received: %u\n" - "Bytes received: %u\n" - "Acks received: %u\n" - "Duplicate acks received: %u\n" - "Inst. KBits per second: %.1f\n" - "KBits per second sent: %.1f\n" - "KBits per second received: %.1f\n", - s->messageSendBuffer[ SYSTEM_PRIORITY ] + s->messageSendBuffer[ HIGH_PRIORITY ] + s->messageSendBuffer[ MEDIUM_PRIORITY ] + s->messageSendBuffer[ LOW_PRIORITY ], - s->messagesSent[ SYSTEM_PRIORITY ] + s->messagesSent[ HIGH_PRIORITY ] + s->messagesSent[ MEDIUM_PRIORITY ] + s->messagesSent[ LOW_PRIORITY ], - ( unsigned ) BITS_TO_BYTES( s->totalBitsSent ), - s->acknowlegementsSent, - s->acknowlegementsPending, - s->messagesOnResendQueue, - s->messageResends, - ( unsigned ) BITS_TO_BYTES( s->messagesTotalBitsResent ), - 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent, - s->duplicateMessagesReceived + s->invalidMessagesReceived + s->messagesReceived, - ( unsigned ) BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), - s->acknowlegementsReceived, - s->duplicateAcknowlegementsReceived, - s->bitsPerSecond / 1000.0, - bpsSent / 1000.0, - bpsReceived / 1000.0); - - break; - } - - case 2: - { - RakNetTime time = RakNet::GetTime(); - double elapsedTime; - double bpsSent; - double bpsReceived; - elapsedTime = (time-s->connectionStartTime) / 1000.0f; - bpsSent = (double) s->totalBitsSent / elapsedTime; - bpsReceived= (double) s->bitsReceived / elapsedTime; - - // Verbosity level 2. - sprintf( buffer, - "Bytes sent:\t\t\t\t%u\n" - "Messages in send buffer:\t\tSP:%u HP:%u MP:%u LP:%u\n" - "Messages sent:\t\t\t\tSP:%u HP:%u MP:%u LP:%u\n" - "Message data bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" - "Message header bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" - "Message total bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" - "Bytes received:\t\t\t\tTtl:%u Good:%u Bad:%u\n" - "Packets received:\t\t\tTtl:%u Good:%u Bad:%u\n" - "Acks received:\t\t\t\tTtl:%u Good:%u Dup:%u\n" - "Messages received:\t\t\tTotal:%u Valid:%u Invalid:%u Dup:%u\n" - "Packetloss:\t\t\t\t%.1f%%\n" - "Packets sent:\t\t\t\t%u\n" - "Acks sent:\t\t\t\t%u\n" - "Acks in send buffer:\t\t\t%u\n" - "Messages waiting for ack:\t\t%u\n" - "Ack bytes sent:\t\t\t\t%u\n" - "Sent packets containing only acks:\t%u\n" - "Sent packets w/only acks and resends:\t%u\n" - "Reliable messages resent:\t\t%u\n" - "Reliable message data bytes resent:\t%u\n" - "Reliable message header bytes resent:\t%u\n" - "Reliable message total bytes resent:\t%u\n" - "Number of messages split:\t\t%u\n" - "Number of messages unsplit:\t\t%u\n" - "Message splits performed:\t\t%u\n" - "Additional encryption bytes:\t\t%u\n" - "Sequenced messages out of order:\t%u\n" - "Sequenced messages in order:\t\t%u\n" - "Ordered messages out of order:\t\t%u\n" - "Ordered messages in of order:\t\t%u\n" - "Split messages waiting for reassembly:\t%u\n" - "Messages in internal output queue:\t%u\n" - "Inst KBits per second:\t\t\t%.1f\n" - "Elapsed time (sec):\t\t\t%.1f\n" - "KBits per second sent:\t\t\t%.1f\n" - "KBits per second received:\t\t%.1f\n", - BITS_TO_BYTES( s->totalBitsSent ), - s->messageSendBuffer[ SYSTEM_PRIORITY ], s->messageSendBuffer[ HIGH_PRIORITY ], s->messageSendBuffer[ MEDIUM_PRIORITY ], s->messageSendBuffer[ LOW_PRIORITY ], - s->messagesSent[ SYSTEM_PRIORITY ], s->messagesSent[ HIGH_PRIORITY ], s->messagesSent[ MEDIUM_PRIORITY ], s->messagesSent[ LOW_PRIORITY ], - BITS_TO_BYTES( s->messageDataBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ LOW_PRIORITY ] ), - BITS_TO_BYTES( s->messageTotalBitsSent[ SYSTEM_PRIORITY ] - s->messageDataBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ HIGH_PRIORITY ] - s->messageDataBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ MEDIUM_PRIORITY ] - s->messageDataBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ LOW_PRIORITY ] - s->messageDataBitsSent[ LOW_PRIORITY ] ), - BITS_TO_BYTES( s->messageTotalBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ LOW_PRIORITY ] ), - BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), BITS_TO_BYTES( s->bitsReceived ), BITS_TO_BYTES( s->bitsWithBadCRCReceived ), - s->packetsReceived + s->packetsWithBadCRCReceived, s->packetsReceived, s->packetsWithBadCRCReceived, - s->acknowlegementsReceived + s->duplicateAcknowlegementsReceived, s->acknowlegementsReceived, s->duplicateAcknowlegementsReceived, - s->messagesReceived + s->invalidMessagesReceived + s->duplicateMessagesReceived, s->messagesReceived, s->invalidMessagesReceived, s->duplicateMessagesReceived, - 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent, - s->packetsSent, - s->acknowlegementsSent, - s->acknowlegementsPending, - s->messagesOnResendQueue, - BITS_TO_BYTES( s->acknowlegementBitsSent ), - s->packetsContainingOnlyAcknowlegements, - s->packetsContainingOnlyAcknowlegementsAndResends, - s->messageResends, - BITS_TO_BYTES( s->messageDataBitsResent ), - BITS_TO_BYTES( s->messagesTotalBitsResent - s->messageDataBitsResent ), - BITS_TO_BYTES( s->messagesTotalBitsResent ), - s->numberOfSplitMessages, - s->numberOfUnsplitMessages, - s->totalSplits, - BITS_TO_BYTES( s->encryptionBitsSent ), - s->sequencedMessagesOutOfOrder, - s->sequencedMessagesInOrder, - s->orderedMessagesOutOfOrder, - s->orderedMessagesInOrder, - s->messagesWaitingForReassembly, - s->internalOutputQueueSize, - s->bitsPerSecond/1000.0, - elapsedTime, - bpsSent / 1000.0, - bpsReceived / 1000.0 - ); - - break; - } - - case 4: - { - sprintf( buffer, - "Messages in Send buffer: %u\n" - "Messages sent: %u\n" - "Bytes sent: %u\n" - "Acks sent: %u\n" - "Acks in send buffer: %u\n" - "Messages waiting for ack: %u\n" - "Messages resent: %u\n" - "Bytes resent: %u\n" - "Packetloss: %.1f%%\n" - "Messages received: %u\n" - "Bytes received: %u\n" - "Acks received: %u\n" - "Duplicate acks received: %u\n", - s->messageSendBuffer[ SYSTEM_PRIORITY ] + s->messageSendBuffer[ HIGH_PRIORITY ] + s->messageSendBuffer[ MEDIUM_PRIORITY ] + s->messageSendBuffer[ LOW_PRIORITY ], - s->messagesSent[ SYSTEM_PRIORITY ] + s->messagesSent[ HIGH_PRIORITY ] + s->messagesSent[ MEDIUM_PRIORITY ] + s->messagesSent[ LOW_PRIORITY ], - ( unsigned ) BITS_TO_BYTES( s->totalBitsSent ), - s->acknowlegementsSent, - s->acknowlegementsPending, - s->messagesOnResendQueue, - s->messageResends, - ( unsigned ) BITS_TO_BYTES( s->messagesTotalBitsResent ), - 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent, - s->duplicateMessagesReceived + s->invalidMessagesReceived + s->messagesReceived, - ( unsigned ) BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), - s->acknowlegementsReceived, - s->duplicateAcknowlegementsReceived); - - break; - } + // Verbosity level 2. + sprintf( buffer, + "Bytes sent:\t\t\t\t%u\n" + "Messages in send buffer:\t\tSP:%u HP:%u MP:%u LP:%u\n" + "Messages sent:\t\t\t\tSP:%u HP:%u MP:%u LP:%u\n" + "Message data bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" + "Message header bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" + "Message total bytes sent:\t\tSP:%u HP:%u MP:%u LP:%u\n" + "Bytes received:\t\t\t\tTtl:%u Good:%u Bad:%u\n" + "Packets received:\t\t\tTtl:%u Good:%u Bad:%u\n" + "Acks received:\t\t\t\tTtl:%u Good:%u Dup:%u\n" + "Messages received:\t\t\tTotal:%u Valid:%u Invalid:%u Dup:%u\n" + "Packetloss:\t\t\t\t%.1f%%\n" + "Packets sent:\t\t\t\t%u\n" + "Acks sent:\t\t\t\t%u\n" + "Acks in send buffer:\t\t\t%u\n" + "Messages waiting for ack:\t\t%u\n" + "Ack bytes sent:\t\t\t\t%u\n" + "Sent packets containing only acks:\t%u\n" + "Sent packets w/only acks and resends:\t%u\n" + "Reliable messages resent:\t\t%u\n" + "Reliable message data bytes resent:\t%u\n" + "Reliable message header bytes resent:\t%u\n" + "Reliable message total bytes resent:\t%u\n" + "Number of messages split:\t\t%u\n" + "Number of messages unsplit:\t\t%u\n" + "Message splits performed:\t\t%u\n" + "Additional encryption bytes:\t\t%u\n" + "Sequenced messages out of order:\t%u\n" + "Sequenced messages in order:\t\t%u\n" + "Ordered messages out of order:\t\t%u\n" + "Ordered messages in of order:\t\t%u\n" + "Split messages waiting for reassembly:\t%u\n" + "Messages in internal output queue:\t%u\n" + "Inst KBits per second:\t\t\t%.1f\n" + "Elapsed time (sec):\t\t\t%.1f\n" + "KBits per second sent:\t\t\t%.1f\n" + "KBits per second received:\t\t%.1f\n", + BITS_TO_BYTES( s->totalBitsSent ), + s->messageSendBuffer[ SYSTEM_PRIORITY ], s->messageSendBuffer[ HIGH_PRIORITY ], s->messageSendBuffer[ MEDIUM_PRIORITY ], s->messageSendBuffer[ LOW_PRIORITY ], + s->messagesSent[ SYSTEM_PRIORITY ], s->messagesSent[ HIGH_PRIORITY ], s->messagesSent[ MEDIUM_PRIORITY ], s->messagesSent[ LOW_PRIORITY ], + BITS_TO_BYTES( s->messageDataBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageDataBitsSent[ LOW_PRIORITY ] ), + BITS_TO_BYTES( s->messageTotalBitsSent[ SYSTEM_PRIORITY ] - s->messageDataBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ HIGH_PRIORITY ] - s->messageDataBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ MEDIUM_PRIORITY ] - s->messageDataBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ LOW_PRIORITY ] - s->messageDataBitsSent[ LOW_PRIORITY ] ), + BITS_TO_BYTES( s->messageTotalBitsSent[ SYSTEM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ HIGH_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ MEDIUM_PRIORITY ] ), BITS_TO_BYTES( s->messageTotalBitsSent[ LOW_PRIORITY ] ), + BITS_TO_BYTES( s->bitsReceived + s->bitsWithBadCRCReceived ), BITS_TO_BYTES( s->bitsReceived ), BITS_TO_BYTES( s->bitsWithBadCRCReceived ), + s->packetsReceived + s->packetsWithBadCRCReceived, s->packetsReceived, s->packetsWithBadCRCReceived, + s->acknowlegementsReceived + s->duplicateAcknowlegementsReceived, s->acknowlegementsReceived, s->duplicateAcknowlegementsReceived, + s->messagesReceived + s->invalidMessagesReceived + s->duplicateMessagesReceived, s->messagesReceived, s->invalidMessagesReceived, s->duplicateMessagesReceived, + 100.0f * ( float ) s->messagesTotalBitsResent / ( float ) s->totalBitsSent, + s->packetsSent, + s->acknowlegementsSent, + s->acknowlegementsPending, + s->messagesOnResendQueue, + BITS_TO_BYTES( s->acknowlegementBitsSent ), + s->packetsContainingOnlyAcknowlegements, + s->packetsContainingOnlyAcknowlegementsAndResends, + s->messageResends, + BITS_TO_BYTES( s->messageDataBitsResent ), + BITS_TO_BYTES( s->messagesTotalBitsResent - s->messageDataBitsResent ), + BITS_TO_BYTES( s->messagesTotalBitsResent ), + s->numberOfSplitMessages, + s->numberOfUnsplitMessages, + s->totalSplits, + BITS_TO_BYTES( s->encryptionBitsSent ), + s->sequencedMessagesOutOfOrder, + s->sequencedMessagesInOrder, + s->orderedMessagesOutOfOrder, + s->orderedMessagesInOrder, + s->messagesWaitingForReassembly, + s->internalOutputQueueSize, + s->bitsPerSecond/1000.0, + elapsedTime, + bpsSent / 1000.0, + bpsReceived / 1000.0 + ); } } diff --git a/raknet/RakNetStatistics.h b/raknet/RakNetStatistics.h index 96bfc2d..e24fd2a 100644 --- a/raknet/RakNetStatistics.h +++ b/raknet/RakNetStatistics.h @@ -23,27 +23,20 @@ #include "Export.h" #include "NetworkTypes.h" -typedef unsigned long long uint64_t; - -/// \brief Network Statisics Usage +/// \brief Network Statisics Usage /// -/// Store Statistics information related to network usage +/// Store Statistics information related to network usage struct RAK_DLL_EXPORT RakNetStatisticsStruct { - // TODO: RakNetStatisticsStruct - /// Number of Messages in the send Buffer (high, medium, low priority) unsigned messageSendBuffer[ NUMBER_OF_PRIORITIES ]; /// Number of messages sent (high, medium, low priority) unsigned messagesSent[ NUMBER_OF_PRIORITIES ]; - - char _gap20[16]; - /// Number of data bits used for user messages - uint64_t messageDataBitsSent[ NUMBER_OF_PRIORITIES ]; + unsigned messageDataBitsSent[ NUMBER_OF_PRIORITIES ]; /// Number of total bits used for user messages, including headers - uint64_t messageTotalBitsSent[ NUMBER_OF_PRIORITIES ]; - + unsigned messageTotalBitsSent[ NUMBER_OF_PRIORITIES ]; + /// Number of packets sent containing only acknowledgements unsigned packetsContainingOnlyAcknowlegements; /// Number of acknowledgements sent @@ -51,53 +44,53 @@ struct RAK_DLL_EXPORT RakNetStatisticsStruct /// Number of acknowledgements waiting to be sent unsigned acknowlegementsPending; /// Number of acknowledgements bits sent - uint64_t acknowlegementBitsSent; - + unsigned acknowlegementBitsSent; + /// Number of packets containing only acknowledgements and resends unsigned packetsContainingOnlyAcknowlegementsAndResends; - + /// Number of messages resent unsigned messageResends; /// Number of bits resent of actual data - uint64_t messageDataBitsResent; + unsigned messageDataBitsResent; /// Total number of bits resent, including headers - uint64_t messagesTotalBitsResent; + unsigned messagesTotalBitsResent; /// Number of messages waiting for ack (// TODO - rename this) unsigned messagesOnResendQueue; - + /// Number of messages not split for sending unsigned numberOfUnsplitMessages; /// Number of messages split for sending unsigned numberOfSplitMessages; /// Total number of splits done for sending unsigned totalSplits; - + /// Total packets sent unsigned packetsSent; - + /// Number of bits added by encryption - uint64_t encryptionBitsSent; + unsigned encryptionBitsSent; /// total bits sent - uint64_t totalBitsSent; - + unsigned totalBitsSent; + /// Number of sequenced messages arrived out of order unsigned sequencedMessagesOutOfOrder; /// Number of sequenced messages arrived in order unsigned sequencedMessagesInOrder; - + /// Number of ordered messages arrived out of order unsigned orderedMessagesOutOfOrder; /// Number of ordered messages arrived in order unsigned orderedMessagesInOrder; - + /// Packets with a good CRC received unsigned packetsReceived; /// Packets with a bad CRC received unsigned packetsWithBadCRCReceived; /// Bits with a good CRC received - uint64_t bitsReceived; + unsigned bitsReceived; /// Bits with a bad CRC received - uint64_t bitsWithBadCRCReceived; + unsigned bitsWithBadCRCReceived; /// Number of acknowledgement messages received for packets we are resending unsigned acknowlegementsReceived; /// Number of acknowledgement messages received for packets we are not resending @@ -117,15 +110,59 @@ struct RAK_DLL_EXPORT RakNetStatisticsStruct /// connection start time RakNetTime connectionStartTime; - RakNetTime field_110; - unsigned field_114; - unsigned field_118; - RakNetTime field_11C; - unsigned field_120; - unsigned field_124; + RakNetStatisticsStruct operator +=(const RakNetStatisticsStruct& other) + { + unsigned i; + for (i=0; i < NUMBER_OF_PRIORITIES; i++) + { + messageSendBuffer[i]+=other.messageSendBuffer[i]; + messagesSent[i]+=other.messagesSent[i]; + messageDataBitsSent[i]+=other.messageDataBitsSent[i]; + messageTotalBitsSent[i]+=other.messageTotalBitsSent[i]; + } + packetsContainingOnlyAcknowlegements+=other.packetsContainingOnlyAcknowlegements; + acknowlegementsSent+=other.packetsContainingOnlyAcknowlegements; + acknowlegementsPending+=other.acknowlegementsPending; + acknowlegementBitsSent+=other.acknowlegementBitsSent; + packetsContainingOnlyAcknowlegementsAndResends+=other.packetsContainingOnlyAcknowlegementsAndResends; + messageResends+=other.messageResends; + messageDataBitsResent+=other.messageDataBitsResent; + messagesTotalBitsResent+=other.messagesTotalBitsResent; + messagesOnResendQueue+=other.messagesOnResendQueue; + numberOfUnsplitMessages+=other.numberOfUnsplitMessages; + numberOfSplitMessages+=other.numberOfSplitMessages; + totalSplits+=other.totalSplits; + packetsSent+=other.packetsSent; + encryptionBitsSent+=other.encryptionBitsSent; + totalBitsSent+=other.totalBitsSent; + sequencedMessagesOutOfOrder+=other.sequencedMessagesOutOfOrder; + sequencedMessagesInOrder+=other.sequencedMessagesInOrder; + orderedMessagesOutOfOrder+=other.orderedMessagesOutOfOrder; + orderedMessagesInOrder+=other.orderedMessagesInOrder; + packetsReceived+=other.packetsReceived; + packetsWithBadCRCReceived+=other.packetsWithBadCRCReceived; + bitsReceived+=other.bitsReceived; + bitsWithBadCRCReceived+=other.bitsWithBadCRCReceived; + acknowlegementsReceived+=other.acknowlegementsReceived; + duplicateAcknowlegementsReceived+=other.duplicateAcknowlegementsReceived; + messagesReceived+=other.messagesReceived; + invalidMessagesReceived+=other.invalidMessagesReceived; + duplicateMessagesReceived+=other.duplicateMessagesReceived; + messagesWaitingForReassembly+=other.messagesWaitingForReassembly; + internalOutputQueueSize+=other.internalOutputQueueSize; + + return *this; + } }; +/// Verbosity level currently supports 0 (low), 1 (medium), 2 (high) +/// \param[in] s The Statistical information to format out +/// \param[in] buffer The buffer containing a formated report +/// \param[in] verbosityLevel +/// 0 low +/// 1 medium +/// 2 high void StatisticsToString( RakNetStatisticsStruct *s, char *buffer, int verbosityLevel ); #endif diff --git a/raknet/RakNetTransport.cpp b/raknet/RakNetTransport.cpp new file mode 100644 index 0000000..585cf87 --- /dev/null +++ b/raknet/RakNetTransport.cpp @@ -0,0 +1,183 @@ +#include "RakNetTransport.h" +#include "RakNetworkFactory.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "PacketEnumerations.h" +#include +#include +#include + +#if (defined(__GNUC__) || defined(__GCCXML__)) +#define _vsnprintf vsnprintf +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +RakNetTransportCommandParser::RakNetTransportCommandParser() +{ + RegisterCommand(1, "SetPassword","Changes the console password to whatever."); + RegisterCommand(0, "ClearPassword","Removes the console passwords."); + RegisterCommand(0, "GetPassword","Gets the console password."); +} +RakNetTransportCommandParser::~RakNetTransportCommandParser() +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +bool RakNetTransportCommandParser::OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString) +{ + RakNetTransport *rnt = (RakNetTransport*) transport; + if (strcmp(command, "SetPassword")==0) + { + rnt->SetIncomingPassword(parameterList[0]); + rnt->Send(playerId, "Password changed to %s\r\n", parameterList[0]); + } + else if (strcmp(command, "ClearPassword")==0) + { + rnt->SetIncomingPassword(0); + rnt->Send(playerId, "Password cleared\r\n"); + } + else if (strcmp(command, "GetPassword")==0) + { + char *password; + password=rnt->GetIncomingPassword(); + if (password[0]) + rnt->Send(playerId, "Password is %s\r\n",password); + else + rnt->Send(playerId, "No password is set.\r\n"); + } + return true; +} +char *RakNetTransportCommandParser::GetName(void) const +{ + return "RakNetTransport"; +} +void RakNetTransportCommandParser::SendHelp(TransportInterface *transport, PlayerID playerId) +{ + transport->Send(playerId, "Provides a secure connection between your console\r\n"); + transport->Send(playerId, "and the console server. Used to modify the console password.\r\n"); +} +RakNetTransport::RakNetTransport() +{ + rakPeer=0; +} +RakNetTransport::~RakNetTransport() +{ + if (rakPeer) + RakNetworkFactory::DestroyRakPeerInterface(rakPeer); +} +bool RakNetTransport::Start(unsigned short port, bool serverMode) +{ + AutoAllocate(); + rakPeer->InitializeSecurity(0,0,0,0); + + if (serverMode) + { + // Allow up to 8 remote systems to login + rakPeer->SetMaximumIncomingConnections(8); + } + + return rakPeer->Initialize(8, port, 250, 0); +} +void RakNetTransport::Stop(void) +{ + if (rakPeer==0) return; + rakPeer->Disconnect(1000, 0); + newConnections.Clear(); + lostConnections.Clear(); +} +void RakNetTransport::Send( PlayerID playerId, const char *data, ... ) +{ + if (rakPeer==0) return; + if (data==0 || data[0]==0) return; + + char text[REMOTE_MAX_TEXT_INPUT]; + va_list ap; + va_start(ap, data); + _vsnprintf(text, REMOTE_MAX_TEXT_INPUT, data, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + RakNet::BitStream str; + str.Write((unsigned char) ID_TRANSPORT_STRING); + str.Write(text, (int) strlen(text)); + str.Write((unsigned char) 0); // Null terminate the string + rakPeer->Send(&str, MEDIUM_PRIORITY, RELIABLE_ORDERED, 0, playerId, (playerId==UNASSIGNED_PLAYER_ID)!=0); +} +void RakNetTransport::CloseConnection( PlayerID playerId ) +{ + rakPeer->CloseConnection(playerId, true, 0); +} +Packet* RakNetTransport::Receive( void ) +{ + if (rakPeer==0) return 0; + Packet *p; + p=rakPeer->Receive(); + if (p==0) + return 0; + if (p->data[0]==ID_TRANSPORT_STRING) + { + p->data++; // Go past ID_TRANSPORT_STRING, since the transport protocol is only supposed to send strings. + return p; + } + if (p->data[0]==ID_NEW_INCOMING_CONNECTION) + { + newConnections.Push(p->playerId); + } + else if (p->data[0]==ID_DISCONNECTION_NOTIFICATION || p->data[0]==ID_CONNECTION_LOST) + { + lostConnections.Push(p->playerId); + } + rakPeer->DeallocatePacket(p); + + return 0; +} +PlayerID RakNetTransport::HasNewConnection(void) +{ + if (newConnections.Size()) + return newConnections.Pop(); + return UNASSIGNED_PLAYER_ID; +} +PlayerID RakNetTransport::HasLostConnection(void) +{ + if (lostConnections.Size()) + return lostConnections.Pop(); + return UNASSIGNED_PLAYER_ID; +} +void RakNetTransport::SetIncomingPassword(const char *password) +{ + if (password) + rakPeer->SetIncomingPassword(password, (int) strlen(password)+1); + else + rakPeer->SetIncomingPassword(0, 0); +} +char * RakNetTransport::GetIncomingPassword(void) +{ + static char password[256]; + int passwordLength=255; + rakPeer->GetIncomingPassword((char*)password, &passwordLength); + password[passwordLength]=0; + return (char*) password; +} +void RakNetTransport::DeallocatePacket( Packet *packet ) +{ + if (rakPeer==0) return; + packet->data--; // Go back to ID_TRANSPORT_STRING, which we passed up in Receive() + rakPeer->DeallocatePacket(packet); +} +void RakNetTransport::AutoAllocate(void) +{ + if (rakPeer==0) + rakPeer=RakNetworkFactory::GetRakPeerInterface(); +} +CommandParserInterface* RakNetTransport::GetCommandParser(void) +{ + return &rakNetTransportCommandParser; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/RakNetTransport.h b/raknet/RakNetTransport.h new file mode 100644 index 0000000..c0a00c4 --- /dev/null +++ b/raknet/RakNetTransport.h @@ -0,0 +1,130 @@ +/// \file +/// \brief Contains RakNetTransportCommandParser and RakNetTransport used to provide a secure console connection. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __RAKNET_TRANSPORT +#define __RAKNET_TRANSPORT + +#include "TransportInterface.h" +#include "DS_Queue.h" +#include "CommandParserInterface.h" +#include "Export.h" + +class RakPeerInterface; +class RakNetTransport; +namespace RakNet +{ + class BitStream; +} + +/// \brief RakNetTransport has its own command parser to enable remote users to change the command console's password. +class RAK_DLL_EXPORT RakNetTransportCommandParser : public CommandParserInterface +{ +public: + RakNetTransportCommandParser(); + ~RakNetTransportCommandParser(); + + /// Given \a command with parameters \a parameterList , do whatever processing you wish. + /// \param[in] command The command to process + /// \param[in] numParameters How many parameters were passed along with the command + /// \param[in] parameterList The list of parameters. parameterList[0] is the first parameter and so on. + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that sent this command. + /// \param[in] originalString The string that was actually sent over the network, in case you want to do your own parsing + bool OnCommand(const char *command, unsigned numParameters, char **parameterList, TransportInterface *transport, PlayerID playerId, const char *originalString); + + /// You are responsible for overriding this function and returning a static string, which will identifier your parser. + /// This should return a static string + /// \return The name that you return. + char* GetName(void) const; + + /// A callback for when you are expected to send a brief description of your parser to \a playerId + /// \param[in] transport The transport interface we can use to write to + /// \param[in] playerId The player that requested help. + void SendHelp(TransportInterface *transport, PlayerID playerId); +protected: +}; + +/// \brief Use RakNetTransport if you need a secure connection between the client and the console server. +/// RakNetTransport automatically initializes security for the system. Use the project CommandConsoleClient to connect +/// To the ConsoleServer if you use RakNetTransport +class RAK_DLL_EXPORT RakNetTransport : public TransportInterface +{ +public: + RakNetTransport(); + ~RakNetTransport(); + + /// Start the transport provider on the indicated port. + /// \param[in] port The port to start the transport provider on + /// \param[in] serverMode If true, you should allow incoming connections (I don't actually use this anywhere) + /// \return Return true on success, false on failure. + bool Start(unsigned short port, bool serverMode); + + /// Stop the transport provider. You can clear memory and shutdown threads here. + void Stop(void); + + /// Send a null-terminated string to \a playerId + /// If your transport method requires particular formatting of the outgoing data (e.g. you don't just send strings) you can do it here + /// and parse it out in Receive(). + /// \param[in] playerId The player to send the string to + /// \param[in] data format specifier - same as printf + /// \param[in] ... format specification arguments - same as printf + void Send( PlayerID playerId, const char *data, ... ); + + /// Return a string. The string should be allocated and written to Packet::data . + /// The byte length should be written to Packet::length . The player/address should be written to Packet::playerid + /// If your transport protocol adds special formatting to the data stream you should parse it out before returning it in the packet + /// and thus only return a string in Packet::data + /// \return The packet structure containing the result of Receive, or 0 if no data is available + Packet* Receive( void ); + + /// Deallocate the Packet structure returned by Receive + /// \param[in] The packet to deallocate + void DeallocatePacket( Packet *packet ); + + /// Disconnect \a playerId . The binary address and port defines the PlayerID structure. + /// \param[in] playerId The player/address to disconnect + void CloseConnection( PlayerID playerId ); + + /// If a new system connects to you, you should queue that event and return the playerId/address of that player in this function. + /// \return The PlayerID/address of the system + PlayerID HasNewConnection(void); + + /// If a system loses the connection, you should queue that event and return the playerId/address of that player in this function. + /// \return The PlayerID/address of the system + PlayerID HasLostConnection(void); + + /// Sets the password which incoming connections must match. + /// While not required, it is highly recommended you set this in a real game environment or anyone can login and control your server. + /// Don't set it to a fixed value, but instead require that the server admin sets it when you start the application server + /// \param[in] password Null-terminated string to use as a password. + void SetIncomingPassword(const char *password); + + /// Returns the password set by SetIncomingPassword(). + /// \return The password set by SetIncomingPassword() + char * GetIncomingPassword(void); + + /// Returns RakNetTransportCommandParser so the console admin can change the password + CommandParserInterface* GetCommandParser(void); + +protected: + RakPeerInterface *rakPeer; + void AutoAllocate(void); + DataStructures::Queue newConnections, lostConnections; + RakNetTransportCommandParser rakNetTransportCommandParser; +}; + +#endif diff --git a/raknet/RakNetworkFactory.cpp b/raknet/RakNetworkFactory.cpp index 115798b..e2b76ec 100644 --- a/raknet/RakNetworkFactory.cpp +++ b/raknet/RakNetworkFactory.cpp @@ -1,4 +1,20 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. +#include "LogCommandParser.h" #include "RakNetworkFactory.h" #include "RakServerInterface.h" #include "RakClientInterface.h" @@ -6,6 +22,16 @@ #include "RakClient.h" #include "RakPeerInterface.h" #include "RakPeer.h" +#include "ConsoleServer.h" +#include "PacketLogger.h" +#include "RakNetCommandParser.h" +#include "ReplicaManager.h" +#include "RakNetTransport.h" +#include "TelnetTransport.h" +#include "PacketConsoleLogger.h" +#include "PacketFileLogger.h" +#include "Router.h" +#include "ConnectionGraph.h" RakClientInterface* RakNetworkFactory::GetRakClientInterface( void ) { @@ -19,10 +45,50 @@ RakPeerInterface* RakNetworkFactory::GetRakPeerInterface( void ) { return new RakPeer; } - - - - +ConsoleServer* RakNetworkFactory::GetConsoleServer( void ) +{ + return new ConsoleServer; +} +ReplicaManager* RakNetworkFactory::GetReplicaManager( void ) +{ + return new ReplicaManager; +} +LogCommandParser* RakNetworkFactory::GetLogCommandParser( void ) +{ + return new LogCommandParser; +} +PacketLogger* RakNetworkFactory::GetPacketLogger( void ) +{ + return new PacketLogger; +} +RakNetCommandParser* RakNetworkFactory::GetRakNetCommandParser( void ) +{ + return new RakNetCommandParser; +} +RakNetTransport* RakNetworkFactory::GetRakNetTransport( void ) +{ + return new RakNetTransport; +} +TelnetTransport* RakNetworkFactory::GetTelnetTransport( void ) +{ + return new TelnetTransport; +} +PacketConsoleLogger* RakNetworkFactory::GetPacketConsoleLogger( void ) +{ + return new PacketConsoleLogger; +} +PacketFileLogger* RakNetworkFactory::GetPacketFileLogger( void ) +{ + return new PacketFileLogger; +} +Router* RakNetworkFactory::GetRouter( void ) +{ + return new Router; +} +ConnectionGraph* RakNetworkFactory::GetConnectionGraph( void ) +{ + return new ConnectionGraph; +} void RakNetworkFactory::DestroyRakClientInterface( RakClientInterface* i ) { delete ( RakClient* ) i; @@ -35,3 +101,47 @@ void RakNetworkFactory::DestroyRakPeerInterface( RakPeerInterface* i ) { delete ( RakPeer* ) i; } +void RakNetworkFactory::DestroyConsoleServer( ConsoleServer* i) +{ + delete ( ConsoleServer* ) i; +} +void RakNetworkFactory::DestroyReplicaManager( ReplicaManager* i) +{ + delete ( ReplicaManager* ) i; +} +void RakNetworkFactory::DestroyLogCommandParser( LogCommandParser* i) +{ + delete ( LogCommandParser* ) i; +} +void RakNetworkFactory::DestroyPacketLogger( PacketLogger* i) +{ + delete ( PacketLogger* ) i; +} +void RakNetworkFactory::DestroyRakNetCommandParser( RakNetCommandParser* i ) +{ + delete ( RakNetCommandParser* ) i; +} +void RakNetworkFactory::DestroyRakNetTransport( RakNetTransport* i ) +{ + delete ( RakNetTransport* ) i; +} +void RakNetworkFactory::DestroyTelnetTransport( TelnetTransport* i ) +{ + delete ( TelnetTransport* ) i; +} +void RakNetworkFactory::DestroyPacketConsoleLogger( PacketConsoleLogger* i ) +{ + delete ( PacketConsoleLogger* ) i; +} +void RakNetworkFactory::DestroyPacketFileLogger( PacketFileLogger* i ) +{ + delete ( PacketFileLogger* ) i; +} +void RakNetworkFactory::DestroyRouter( Router* i ) +{ + delete ( Router* ) i; +} +void RakNetworkFactory::DestroyConnectionGraph( ConnectionGraph* i ) +{ + delete ( ConnectionGraph* ) i; +} diff --git a/raknet/RakNetworkFactory.h b/raknet/RakNetworkFactory.h index 3b68df7..315ae62 100644 --- a/raknet/RakNetworkFactory.h +++ b/raknet/RakNetworkFactory.h @@ -1,3 +1,19 @@ +/// \file +/// \brief Factory class for RakServerInterface, RakClientInterface, and RakPeerInterface +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_NETWORK_FACTORY_H #define __RAK_NETWORK_FACTORY_H @@ -7,18 +23,55 @@ class RakClientInterface; class RakServerInterface; class RakPeerInterface; +class ConsoleServer; +class ReplicaManager; +class LogCommandParser; +class PacketLogger; +class RakNetCommandParser; +class RakNetTransport; +class TelnetTransport; +class PacketConsoleLogger; +class PacketFileLogger; +class Router; +class ConnectionGraph; class RAK_DLL_EXPORT RakNetworkFactory { public: - + // For DLL's, these are user classes that you might want to new and delete. + // You can't instantiate exported classes directly in your program. The instantiation + // has to take place inside the DLL. So these functions will do the news and deletes for you. + // if you're using the source or static library you don't need these functions, but can use them if you want. static RakClientInterface* GetRakClientInterface( void ); static RakServerInterface* GetRakServerInterface( void ); static RakPeerInterface* GetRakPeerInterface( void ); + static ConsoleServer* GetConsoleServer( void ); + static ReplicaManager* GetReplicaManager( void ); + static LogCommandParser* GetLogCommandParser( void ); + static PacketLogger* GetPacketLogger( void ); + static RakNetCommandParser* GetRakNetCommandParser( void ); + static RakNetTransport* GetRakNetTransport( void ); + static TelnetTransport* GetTelnetTransport( void ); + static PacketConsoleLogger* GetPacketConsoleLogger( void ); + static PacketFileLogger* GetPacketFileLogger( void ); + static Router* GetRouter( void ); + static ConnectionGraph* GetConnectionGraph( void ); + // To delete the object returned by the Get functions above. static void DestroyRakClientInterface( RakClientInterface* i ); static void DestroyRakServerInterface( RakServerInterface* i ); static void DestroyRakPeerInterface( RakPeerInterface* i ); + static void DestroyConsoleServer( ConsoleServer* i); + static void DestroyReplicaManager( ReplicaManager* i); + static void DestroyLogCommandParser( LogCommandParser* i); + static void DestroyPacketLogger( PacketLogger* i); + static void DestroyRakNetCommandParser( RakNetCommandParser* i ); + static void DestroyRakNetTransport( RakNetTransport* i ); + static void DestroyTelnetTransport( TelnetTransport* i ); + static void DestroyPacketConsoleLogger( PacketConsoleLogger* i ); + static void DestroyPacketFileLogger( PacketFileLogger* i ); + static void DestroyRouter( Router* i ); + static void DestroyConnectionGraph( ConnectionGraph* i ); }; #endif diff --git a/raknet/RakPeer.cpp b/raknet/RakPeer.cpp index 56e8d47..fa45630 100644 --- a/raknet/RakPeer.cpp +++ b/raknet/RakPeer.cpp @@ -1,43 +1,488 @@ -// TODO: Implement RakPeer.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. +#include "RakNetDefines.h" #include "RakPeer.h" +#include "NetworkTypes.h" +#ifdef __USE_IO_COMPLETION_PORTS +#include "AsynchronousFileIO.h" +#endif + +#ifdef _WIN32 +//#include +#include +#else +#define closesocket close +#include +#include +#endif +#include // toupper #include -#include +#include "GetTime.h" +#include "PacketEnumerations.h" +#include "DS_HuffmanEncodingTree.h" +#include "Rand.h" +#include "PluginInterface.h" +#include "StringCompressor.h" +#include "StringTable.h" +#include "NetworkIDGenerator.h" +#include "NetworkTypes.h" +#include "SHA1.h" +#include "RakSleep.h" +#include "RouterInterface.h" +#include "RakAssert.h" +#if !defined ( __APPLE__ ) && !defined ( __APPLE_CC__ ) +#include +#endif + +#ifdef _COMPATIBILITY_1 +// +#elif defined(_WIN32) +// +#elif defined(_COMPATIBILITY_2) +#include "Compatibility2Includes.h" +#include +#else +#include +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#ifdef _WIN32 +unsigned __stdcall UpdateNetworkLoop( LPVOID arguments ); +#else +void* UpdateNetworkLoop( void* arguments ); +#endif + +int PlayerIDAndIndexComp( const PlayerID &key, const PlayerIDAndIndex &data ) +{ + if (key < data.playerId) + return -1; + if (key==data.playerId) + return 0; + return 1; +} + +// On a Little-endian machine the RSA key and message are mangled, but we're +// trying to be friendly to the little endians, so we do byte order +// mangling on Big-Endian machines. Note that this mangling is independent +// of the byte order used on the network (which also defaults to little-end). +#ifdef HOST_ENDIAN_IS_BIG + void __inline BSWAPCPY(unsigned char *dest, unsigned char *source, int bytesize) + { + #ifdef _DEBUG + assert( (bytesize % 4 == 0)&&(bytesize)&& "Something is wrong with your exponent or modulus size."); + #endif + int i; + for (i=0; idata=(unsigned char*)p+sizeof(Packet); + p->length=dataSize; + p->deleteData=false; + return p; +} + +Packet *AllocPacket(unsigned dataSize, unsigned char *data) +{ + Packet *p = (Packet *)malloc(sizeof(Packet)); + p->data=data; + p->length=dataSize; + p->deleteData=true; + return p; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Constructor +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- RakPeer::RakPeer() { + StringCompressor::AddReference(); + StringTable::AddReference(); + +#if !defined(_COMPATIBILITY_1) + usingSecurity = false; +#endif memset( frequencyTable, 0, sizeof( unsigned int ) * 256 ); rawBytesSent = rawBytesReceived = compressedBytesSent = compressedBytesReceived = 0; + outputTree = inputTree = 0; + connectionSocket = INVALID_SOCKET; MTUSize = DEFAULT_MTU_SIZE; trackFrequencyTable = false; maximumIncomingConnections = 0; maximumNumberOfPeers = 0; + //remoteSystemListSize=0; + remoteSystemList = 0; + bytesSentPerSecond = bytesReceivedPerSecond = 0; endThreads = true; + isMainLoopThreadActive = false; + // isRecvfromThreadActive=false; + occasionalPing = false; + connectionSocket = INVALID_SOCKET; myPlayerId = UNASSIGNED_PLAYER_ID; + allowConnectionResponseIPMigration = false; + blockOnRPCReply=false; + //incomingPasswordLength=outgoingPasswordLength=0; incomingPasswordLength=0; + router=0; + splitMessageProgressInterval=0; + unreliableTimeout=0; - // TODO: RakPeer ctor saco .text:1003DE50 server W .text:00455140 L .text:08072970 bot W .text:00408DF0 L .text:08071AB0 +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + recvEvent = INVALID_HANDLE_VALUE; +#endif + +#ifndef _RELEASE + _maxSendBPS=0.0; + _minExtraPing=0; + _extraPingVariance=0; +#endif } -void RakPeer::vftable_0() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Destructor +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::~RakPeer() { - // TODO: RakPeer::vftable_0() (saco W: 10042780) (server W: 459CC0 L: 8072430) (bot W: 40D490 L: 807229E) +// unsigned i; + + // Free the ban list. + ClearBanList(); + + Disconnect( 0, 0); + + + StringCompressor::RemoveReference(); + StringTable::RemoveReference(); } -void RakPeer::vftable_4() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Starts the network threads, opens the listen port +// You must call this before calling SetMaximumIncomingConnections or Connect +// Multiple calls while already active are ignored. To call this function again with different settings, you must first call Disconnect() +// +// Parameters: +// maxConnections: Required so the network can preallocate and for thread safety. +// - A pure client would set this to 1. A pure server would set it to the number of allowed clients. +// - A hybrid would set it to the sum of both types of connections +// localPort: The port to listen for connections on. +// _threadSleepTimer: How many ms to Sleep each internal update cycle (30 to give the game priority, 0 for regular (recommended), -1 to not Sleep() (may be slower)) + // forceHostAddress Can force RakNet to use a particular IP to host on. Pass 0 to automatically pick an IP +// Returns: +// False on failure (can't create socket or thread), true on success. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::Initialize( unsigned short maxConnections, unsigned short localPort, int _threadSleepTimer, const char *forceHostAddress ) { - // TODO: RakPeer::vftable_4() (saco W: 100427A0) (server W: 459CE0 L: 8072190) (bot W: 40D4B0 L: 80725E2) + if (IsActive()) + return false; + + unsigned i; + + assert( maxConnections > 0 ); + + if ( maxConnections <= 0 ) + return false; + + if ( connectionSocket == INVALID_SOCKET ) + { + connectionSocket = SocketLayer::Instance()->CreateBoundSocket( localPort, true, forceHostAddress ); + + if ( connectionSocket == INVALID_SOCKET ) + return false; + + unsigned short localPort2 = SocketLayer::Instance()->GetLocalPort(connectionSocket); + if (localPort2!=0) + localPort=localPort2; + } + +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (_threadSleepTimer>0) + { + recvEvent=CreateEvent(0,FALSE,FALSE,0); + WSAEventSelect(connectionSocket,recvEvent,FD_READ); + } +#endif + + if ( maximumNumberOfPeers == 0 ) + { + // Don't allow more incoming connections than we have peers. + if ( maximumIncomingConnections > maxConnections ) + maximumIncomingConnections = maxConnections; + + maximumNumberOfPeers = maxConnections; + // 04/19/2006 - Don't overallocate because I'm not longer allowing connected pings. + // The disconnects are not consistently processed and the process was sloppy and complicated. + // Allocate 10% extra to handle new connections from players trying to connect when the server is full + //remoteSystemListSize = maxConnections;// * 11 / 10 + 1; + + // remoteSystemList in Single thread + //remoteSystemList = new RemoteSystemStruct[ remoteSystemListSize ]; + remoteSystemList = new RemoteSystemStruct[ maximumNumberOfPeers ]; + + + for ( i = 0; i < maximumNumberOfPeers; i++ ) + //for ( i = 0; i < remoteSystemListSize; i++ ) + { + // remoteSystemList in Single thread + remoteSystemList[ i ].isActive = false; + #ifndef _RELEASE + remoteSystemList[ i ].reliabilityLayer.ApplyNetworkSimulator(_maxSendBPS, _minExtraPing, _extraPingVariance); + #endif + } + + // Clear the lookup table. Safe to call from the user thread since the network thread is now stopped + remoteSystemLookup.Clear(); + } + + // For histogram statistics + // nextReadBytesTime=0; + // lastSentBytes=lastReceivedBytes=0; + + if ( endThreads ) + { + // lastUserUpdateCycle = 0; + + // Reset the frequency table that we use to save outgoing data + memset( frequencyTable, 0, sizeof( unsigned int ) * 256 ); + + // Reset the statistical data + rawBytesSent = rawBytesReceived = compressedBytesSent = compressedBytesReceived = 0; + + updateCycleIsRunning = false; + endThreads = false; + // Create the threads + threadSleepTimer = _threadSleepTimer; + + ClearBufferedCommands(); + +#if !defined(_COMPATIBILITY_1) + char ipList[ 10 ][ 16 ]; + SocketLayer::Instance()->GetMyIP( ipList ); + myPlayerId.port = localPort; + if (forceHostAddress==0 || forceHostAddress[0]==0) + myPlayerId.binaryAddress = inet_addr( ipList[ 0 ] ); + else + myPlayerId.binaryAddress = inet_addr( forceHostAddress ); +#else + myPlayerId=UNASSIGNED_PLAYER_ID; +#endif + { +#ifdef _WIN32 + + if ( isMainLoopThreadActive == false ) + { + unsigned ProcessPacketsThreadID = 0; +#ifdef _COMPATIBILITY_1 + processPacketsThreadHandle = ( HANDLE ) _beginthreadex( NULL, 0, UpdateNetworkLoop, this, 0, &ProcessPacketsThreadID ); +#else + processPacketsThreadHandle = ( HANDLE ) _beginthreadex( NULL, MAX_ALLOCA_STACK_ALLOCATION*2, UpdateNetworkLoop, this, 0, &ProcessPacketsThreadID ); +#endif + + //BOOL b = SetThreadPriority( + // processPacketsThreadHandle, + // THREAD_PRIORITY_HIGHEST + // ); + + if ( processPacketsThreadHandle == 0 ) + { + Disconnect( 0, 0 ); + return false; + } + + // SetThreadPriority(processPacketsThreadHandle, THREAD_PRIORITY_HIGHEST); + + CloseHandle( processPacketsThreadHandle ); + + processPacketsThreadHandle = 0; + + } + +#else + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + + // sched_param sp; + // sp.sched_priority = sched_get_priority_max(SCHED_OTHER); + // pthread_attr_setschedparam(&attr, &sp); + + int error; + + if ( isMainLoopThreadActive == false ) + { + error = pthread_create( &processPacketsThreadHandle, &attr, &UpdateNetworkLoop, this ); + + if ( error ) + { + Disconnect( 0 ); + return false; + } + } + + processPacketsThreadHandle = 0; +#endif + + + // Wait for the threads to activate. When they are active they will set these variables to true + + while ( /*isRecvfromThreadActive==false || */isMainLoopThreadActive == false ) + RakSleep(10); + + } + } + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->OnInitialize(this); + } + + return true; } -void RakPeer::vftable_8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Must be called while offline +// Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent +// connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. +// There is a significant amount of processing and a slight amount of bandwidth +// overhead for this feature. +// +// If you accept connections, you must call this or else secure connections will not be enabled +// for incoming connections. +// If you are connecting to another system, you can call this with values for the +// (e and p,q) public keys before connecting to prevent MitM +// +// Parameters: +// pubKeyE, pubKeyN - A pointer to the public keys from the RSACrypt class. See the Encryption sample +// privKeyP, privKeyQ - Private keys generated from the RSACrypt class. See the Encryption sample +// If the private keys are 0, then a new key will be generated when this function is called +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ ) { - // TODO: RakPeer::vftable_8() (saco W: 10042A40) (server W: 459F80 L: 806EE80) (bot W: 40D750 L: 8072926) +#if !defined(_COMPATIBILITY_1) + if ( endThreads == false ) + return ; + + // Setting the client key is e,n, + // Setting the server key is p,q + if ( //( privKeyP && privKeyQ && ( pubKeyE || pubKeyN ) ) || + //( pubKeyE && pubKeyN && ( privKeyP || privKeyQ ) ) || + ( privKeyP && privKeyQ == 0 ) || + ( privKeyQ && privKeyP == 0 ) || + ( pubKeyE && pubKeyN == 0 ) || + ( pubKeyN && pubKeyE == 0 ) ) + { + // Invalid parameters + assert( 0 ); + } + + seedMT( (unsigned int) RakNet::GetTime() ); + + GenerateSYNCookieRandomNumber(); + + usingSecurity = true; + + if ( privKeyP == 0 && privKeyQ == 0 && pubKeyE == 0 && pubKeyN == 0 ) + { + keysLocallyGenerated = true; + rsacrypt.generateKeys(); + } + + else + { + if ( pubKeyE && pubKeyN ) + { + // Save public keys + memcpy( ( char* ) & publicKeyE, pubKeyE, sizeof( publicKeyE ) ); + memcpy( publicKeyN, pubKeyN, sizeof( publicKeyN ) ); + } + + if ( privKeyP && privKeyQ ) + { + BIGHALFSIZE( RSA_BIT_SIZE, p ); + BIGHALFSIZE( RSA_BIT_SIZE, q ); + memcpy( p, privKeyP, sizeof( p ) ); + memcpy( q, privKeyQ, sizeof( q ) ); + // Save private keys + rsacrypt.setPrivateKey( p, q ); + } + + keysLocallyGenerated = false; + } +#endif } -void RakPeer::vftable_C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description +// Must be called while offline +// Disables all security. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DisableSecurity( void ) { - // TODO: RakPeer::vftable_C() (saco W: 10038400) (server W: 44FF00 L: 8076430) (bot W: 403EA0 L: 8072E54) +#if !defined(_COMPATIBILITY_1) + if ( endThreads == false ) + return ; + + usingSecurity = false; +#endif } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -127,8 +572,45 @@ void RakPeer::GetIncomingPassword( char* passwordData, int *passwordDataLength // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- bool RakPeer::Connect( const char* host, unsigned short remotePort, char* passwordData, int passwordDataLength ) { - // TODO: RakPeer::vftable_20() (saco W: 10040550) (server W: 457B00 L: 806D230) (bot W: 40B2C0 L: 807306A) - return false; + // If endThreads is true here you didn't call Initialize() first. + if ( host == 0 || endThreads || connectionSocket == INVALID_SOCKET ) + return false; + + unsigned numberOfFreeSlots; + + numberOfFreeSlots = 0; + + //if (passwordDataLength>MAX_OFFLINE_DATA_LENGTH) + // passwordDataLength=MAX_OFFLINE_DATA_LENGTH; + if (passwordDataLength>255) + passwordDataLength=255; + + if (passwordData==0) + passwordDataLength=0; + + // Not threadsafe but it's not important enough to lock. Who is going to change the password a lot during runtime? + // It won't overflow at least because outgoingPasswordLength is an unsigned char +// if (passwordDataLength>0) +// memcpy(outgoingPassword, passwordData, passwordDataLength); +// outgoingPasswordLength=(unsigned char) passwordDataLength; + + // If the host starts with something other than 0, 1, or 2 it's (probably) a domain name. + if ( host[ 0 ] < '0' || host[ 0 ] > '2' ) + { +#if !defined(_COMPATIBILITY_1) + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); +#else + return false; +#endif + if (host==0) + return false; + } + + // Connecting to ourselves in the same instance of the program? + if ( ( strcmp( host, "127.0.0.1" ) == 0 || strcmp( host, "0.0.0.0" ) == 0 ) && remotePort == myPlayerId.port ) + return false; + + return SendConnectionRequest( host, remotePort, passwordData, passwordDataLength ); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -137,7 +619,139 @@ bool RakPeer::Connect( const char* host, unsigned short remotePort, char* passwo // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::Disconnect( unsigned int blockDuration, unsigned char orderingChannel ) { - // TODO: RakPeer::vftable_24() (saco W: 10040620) (server W: 457BD0 L: 806FB70) (bot W: 40B390 L: 80730D2) + unsigned i,j; + bool anyActive; + RakNetTime startWaitingTime; +// PlayerID playerId; + RakNetTime time; + //unsigned short systemListSize = remoteSystemListSize; // This is done for threading reasons + unsigned short systemListSize = maximumNumberOfPeers; + + if ( blockDuration > 0 ) + { + for ( i = 0; i < systemListSize; i++ ) + { + // remoteSystemList in user thread + NotifyAndFlagForDisconnect(remoteSystemList[i].playerId, false, orderingChannel); + } + + time = RakNet::GetTime(); + startWaitingTime = time; + while ( time - startWaitingTime < blockDuration ) + { + anyActive=false; + for (j=0; j < systemListSize; j++) + { + // remoteSystemList in user thread + if (remoteSystemList[j].isActive) + { + anyActive=true; + break; + } + } + + // If this system is out of packets to send, then stop waiting + if ( anyActive==false ) + break; + + // This will probably cause the update thread to run which will probably + // send the disconnection notification + + RakSleep(15); + time = RakNet::GetTime(); + } + } + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->OnDisconnect(this); + } + + if ( endThreads == false ) + { + // Stop the threads + endThreads = true; + + // Normally the thread will call DecreaseUserCount on termination but if we aren't using threads just do it + // manually +#ifdef __USE_IO_COMPLETION_PORTS + AsynchronousFileIO::Instance()->DecreaseUserCount(); +#endif + } + + while ( isMainLoopThreadActive ) + RakSleep(15); + + // remoteSystemList in Single thread + for ( i = 0; i < systemListSize; i++ ) + { + // Reserve this reliability layer for ourselves + remoteSystemList[ i ].isActive = false; + + // Remove any remaining packets + remoteSystemList[ i ].reliabilityLayer.Reset(false); + } + + // Clear the lookup table. Safe to call from the user thread since the network thread is now stopped + remoteSystemLookup.Clear(); + + // Setting maximumNumberOfPeers to 0 allows remoteSystemList to be reallocated in Initialize. + // Setting remoteSystemListSize prevents threads from accessing the reliability layer + maximumNumberOfPeers = 0; + //remoteSystemListSize = 0; + + // Free any packets the user didn't deallocate + Packet **packet; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[transferToPacketQueue_Mutex].Lock(); +#endif + packet=packetSingleProducerConsumer.ReadLock(); + while (packet) + { + DeallocatePacket(*packet); + packetSingleProducerConsumer.ReadUnlock(); + packet=packetSingleProducerConsumer.ReadLock(); + } + packetSingleProducerConsumer.Clear(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[transferToPacketQueue_Mutex].Unlock(); +#endif + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Lock(); +#endif + for (i=0; i < packetPool.Size(); i++) + DeallocatePacket(packetPool[i]); + packetPool.Clear(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Unlock(); +#endif + + blockOnRPCReply=false; + + if ( connectionSocket != INVALID_SOCKET ) + { + closesocket( connectionSocket ); + connectionSocket = INVALID_SOCKET; + } + + ClearBufferedCommands(); + bytesSentPerSecond = bytesReceivedPerSecond = 0; + + ClearRequestedConnectionList(); + +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (recvEvent!=INVALID_HANDLE_VALUE) + { + CloseHandle( recvEvent ); + recvEvent = INVALID_HANDLE_VALUE; + } +#endif + + // Clear out the reliability layer list in case we want to reallocate it in a successive call to Init. + RemoteSystemStruct * temp = remoteSystemList; + remoteSystemList = 0; + delete [] temp; } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -149,24 +763,252 @@ inline bool RakPeer::IsActive( void ) const return endThreads == false; } -void RakPeer::vftable_2C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Fills the array remoteSystems with the playerID of all the systems we are connected to +// +// Parameters: +// remoteSystems (out): An array of PlayerID structures to be filled with the PlayerIDs of the systems we are connected to +// - pass 0 to remoteSystems to only get the number of systems we are connected to +// numberOfSystems (int, out): As input, the size of remoteSystems array. As output, the number of elements put into the array +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::GetConnectionList( PlayerID *remoteSystems, unsigned short *numberOfSystems ) const { - // TODO: RakPeer::vftable_2C() (saco W: 100384D0) (server W: 44FFD0 L: 8076E70) (bot W: 403F70 L: 8080FB0) + int count, index; + count=0; + + if ( remoteSystemList == 0 || endThreads == true ) + { + *numberOfSystems = 0; + return false; + } + + // This is called a lot so I unrolled the loop + if ( remoteSystems ) + { + // remoteSystemList in user thread + //for ( count = 0, index = 0; index < remoteSystemListSize; ++index ) + for ( count = 0, index = 0; index < maximumNumberOfPeers; ++index ) + if ( remoteSystemList[ index ].isActive && remoteSystemList[ index ].connectMode==RemoteSystemStruct::CONNECTED) + { + if ( count < *numberOfSystems ) + remoteSystems[ count ] = remoteSystemList[ index ].playerId; + + ++count; + } + } + else + { + // remoteSystemList in user thread + //for ( count = 0, index = 0; index < remoteSystemListSize; ++index ) + for ( count = 0, index = 0; index < maximumNumberOfPeers; ++index ) + if ( remoteSystemList[ index ].isActive && remoteSystemList[ index ].connectMode==RemoteSystemStruct::CONNECTED) + ++count; + } + + *numberOfSystems = ( unsigned short ) count; + + return 0; } -void RakPeer::vftable_30() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sends a block of data to the specified system that you are connected to. +// This function only works while the client is connected (Use the Connect function). +// +// Parameters: +// data: The block of data to send +// length: The size in bytes of the data to send +// bitStream: The bitstream to send +// priority: What priority level to send on. +// reliability: How reliability to send this data +// orderingChannel: When using ordered or sequenced packets, what channel to order these on. +// - Packets are only ordered relative to other packets on the same stream +// playerId: Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none +// broadcast: True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. +// Returns: +// False if we are not connected to the specified recipient. True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ) { - // TODO: RakPeer::vftable_30() (saco W: 1003C3D0) (server W: 4536E0 L: 806D2B0) (bot W: 407380 L: 8073546) +#ifdef _DEBUG + assert( data && length > 0 ); +#endif + + if ( data == 0 || length < 0 ) + return false; + + if ( remoteSystemList == 0 || endThreads == true ) + return false; + + if ( broadcast == false && playerId == UNASSIGNED_PLAYER_ID ) + return false; + + if (broadcast==false && router && GetIndexFromPlayerID(playerId)==-1) + { + return router->Send(data, BYTES_TO_BITS(length), priority, reliability, orderingChannel, playerId); + } + else + { + SendBuffered(data, length*8, priority, reliability, orderingChannel, playerId, broadcast, RemoteSystemStruct::NO_ACTION); + } + + return true; } -void RakPeer::vftable_34() +bool RakPeer::Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ) { - // TODO: RakPeer::vftable_34() (saco W: 1003C2C0) (server W: 4535D0 L: 806EBA0) (bot W: 407270 L: 8073696) +#ifdef _DEBUG + assert( bitStream->GetNumberOfBytesUsed() > 0 ); +#endif + + if ( bitStream->GetNumberOfBytesUsed() == 0 ) + return false; + + if ( remoteSystemList == 0 || endThreads == true ) + return false; + + if ( broadcast == false && playerId == UNASSIGNED_PLAYER_ID ) + return false; + + if (broadcast==false && router && GetIndexFromPlayerID(playerId)==-1) + { + return router->Send((const char*)bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, playerId); + } + else + { + // Sends need to be buffered and processed in the update thread because the playerID associated with the reliability layer can change, + // from that thread, resulting in a send to the wrong player! While I could mutex the playerID, that is much slower than doing this + SendBuffered((const char*)bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, playerId, broadcast, RemoteSystemStruct::NO_ACTION); + } + + return true; } -void RakPeer::vftable_38() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Gets a packet from the incoming packet queue. Use DeallocatePacket to deallocate the packet after you are done with it. Packets must be deallocated in the same order they are received. +// Check the Packet struct at the top of CoreNetworkStructures.h for the format of the struct +// +// Returns: +// 0 if no packets are waiting to be handled, otherwise an allocated packet +// If the client is not active this will also return 0, as all waiting packets are flushed when the client is Disconnected +// This also updates all memory blocks associated with synchronized memory and distributed objects +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +Packet* RakPeer::Receive( void ) { - // TODO: RakPeer::vftable_38() (saco W: 10040FD0) (server W: 458510 L: 806EA30) (bot W: 40BCE0 L: 8073806) + Packet *packet = ReceiveIgnoreRPC(); + while (packet && (packet->data[ 0 ] == ID_RPC || (packet->length>sizeof(unsigned char)+sizeof(RakNetTime) && packet->data[0]==ID_TIMESTAMP && packet->data[sizeof(unsigned char)+sizeof(RakNetTime)]==ID_RPC))) + { + // Do RPC calls from the user thread, not the network update thread + // If we are currently blocking on an RPC reply, send ID_RPC to the blocker to handle rather than handling RPCs automatically + HandleRPCPacket( ( char* ) packet->data, packet->length, packet->playerId ); + DeallocatePacket( packet ); + + packet = ReceiveIgnoreRPC(); + } + + return packet; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Internal - Gets a packet without checking for RPCs +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +Packet* RakPeer::ReceiveIgnoreRPC( void ) +{ + if ( !( IsActive() ) ) + return 0; + + Packet *packet; + Packet **threadPacket; + PluginReceiveResult pluginResult; + + int offset; + unsigned int i; + + for (i=0; i < messageHandlerList.Size(); i++) + { + messageHandlerList[i]->Update(this); + } + + do + { +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[transferToPacketQueue_Mutex].Lock(); +#endif + // Take all the messages off the queue so if the user pushes them back they are really pushed back, and not just at the end of the immediate write + threadPacket=packetSingleProducerConsumer.ReadLock(); + while (threadPacket) + { + packet=*threadPacket; + packetSingleProducerConsumer.ReadUnlock(); + threadPacket=packetSingleProducerConsumer.ReadLock(); + packetPool.Push(packet); + } +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[transferToPacketQueue_Mutex].Unlock(); +#endif + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Lock(); +#endif + if (packetPool.Size()==0) + { +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Unlock(); +#endif + return 0; + } + + packet = packetPool.Pop(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Unlock(); +#endif + if ( ( packet->length >= sizeof(unsigned char) + sizeof( RakNetTime ) ) && + ( (unsigned char) packet->data[ 0 ] == ID_TIMESTAMP ) ) + { + offset = sizeof(unsigned char); + ShiftIncomingTimestamp( packet->data + offset, packet->playerId ); + } + if ( (unsigned char) packet->data[ 0 ] == ID_RPC_REPLY ) + { + HandleRPCReplyPacket( ( char* ) packet->data, packet->length, packet->playerId ); + DeallocatePacket( packet ); + packet=0; // Will do the loop again and get another packet + } + else + { + for (i=0; i < messageHandlerList.Size(); i++) + { + pluginResult=messageHandlerList[i]->OnReceive(this, packet); + if (pluginResult==RR_STOP_PROCESSING_AND_DEALLOCATE) + { + DeallocatePacket( packet ); + packet=0; // Will do the loop again and get another packet + break; // break out of the enclosing for + } + else if (pluginResult==RR_STOP_PROCESSING) + { + packet=0; + break; + } + } + } + + } while(packet==0); + +#ifdef _DEBUG + assert( packet->data ); +#endif + + return packet; } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -179,9 +1021,7 @@ void RakPeer::DeallocatePacket( Packet *packet ) return; if (packet->deleteData) - if (packet->data) - delete packet->data; - + delete packet->data; free(packet); } @@ -209,7 +1049,31 @@ void RakPeer::RegisterAsRemoteProcedureCall( char* uniqueID, void ( *functionPoi if ( uniqueID == 0 || uniqueID[ 0 ] == 0 || functionPointer == 0 ) return; - rpcMap.AddIdentifierWithFunction((unsigned char)*uniqueID, (void*)functionPointer, false); + rpcMap.AddIdentifierWithFunction(uniqueID, (void*)functionPointer, false); + + /* + char uppercaseUniqueID[ 256 ]; + + int counter = 0; + + while ( uniqueID[ counter ] ) + { + uppercaseUniqueID[ counter ] = ( char ) toupper( uniqueID[ counter ] ); + counter++; + } + + uppercaseUniqueID[ counter ] = 0; + + // Each id must be unique +//#ifdef _DEBUG +// assert( rpcTree.IsIn( RPCNode( uppercaseUniqueID, functionName ) ) == false ); +//#endif + + if (rpcTree.IsIn( RPCNode( uppercaseUniqueID, functionName ) )) + return; + + rpcTree.Add( RPCNode( uppercaseUniqueID, functionName ) ); + */ } void RakPeer::RegisterClassMemberRPC( char* uniqueID, void *functionPointer ) @@ -217,7 +1081,7 @@ void RakPeer::RegisterClassMemberRPC( char* uniqueID, void *functionPointer ) if ( uniqueID == 0 || uniqueID[ 0 ] == 0 || functionPointer == 0 ) return; - rpcMap.AddIdentifierWithFunction((unsigned char)*uniqueID, functionPointer, true); + rpcMap.AddIdentifierWithFunction(uniqueID, functionPointer, true); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -231,102 +1095,807 @@ void RakPeer::RegisterClassMemberRPC( char* uniqueID, void *functionPointer ) // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- void RakPeer::UnregisterAsRemoteProcedureCall( char* uniqueID ) { - // nothing + if ( uniqueID == 0 || uniqueID[ 0 ] == 0 ) + return; + +// Don't call this while running because if you remove RPCs and add them they will not match the indices on the other systems anymore +#ifdef _DEBUG + assert(IsActive()==false); + //assert( strlen( uniqueID ) < 256 ); +#endif + + rpcMap.RemoveNode(uniqueID); + + /* + char uppercaseUniqueID[ 256 ]; + + strcpy( uppercaseUniqueID, uniqueID ); + + int counter = 0; + + while ( uniqueID[ counter ] ) + { + uppercaseUniqueID[ counter ] = ( char ) toupper( uniqueID[ counter ] ); + counter++; + } + + uppercaseUniqueID[ counter ] = 0; + + // Unique ID must exist +#ifdef _DEBUG + assert( rpcTree.IsIn( RPCNode( uppercaseUniqueID, 0 ) ) == true ); +#endif + + rpcTree.Del( RPCNode( uppercaseUniqueID, 0 ) ); + */ } -void RakPeer::vftable_50() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Calls a C function on the server that the server already registered using RegisterAsRemoteProcedureCall +// If you want that function to return data you should call RPC from that system in the same way +// Returns true on a successful packet send (this does not indicate the recipient performed the call), false on failure +// +// Parameters: +// uniqueID: A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter +// data: The block of data to send +// length: The size in BITS of the data to send +// bitStream: The bitstream to send +// priority: What priority level to send on. +// reliability: How reliability to send this data +// orderingChannel: When using ordered or sequenced packets, what channel to order these on. +// broadcast - Send this packet to everyone. +// playerId: Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none +// broadcast: True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. +// networkID: For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. +// replyFromTarget: If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakPeer::vftable_50() (saco W: 1003A720) (server W: 451A60 L: 806D580) (bot W: 405750 L: 8073DAA) +#ifdef _DEBUG + assert( uniqueID && uniqueID[ 0 ] ); + assert(orderingChannel >=0 && orderingChannel < 32); +#endif + + if ( uniqueID == 0 ) + return false; + + if ( strlen( uniqueID ) > 256 ) + { +#ifdef _DEBUG + assert( 0 ); +#endif + return false; // Unique ID is too long + } + if (replyFromTarget && blockOnRPCReply==true) + { + // TODO - this should be fixed eventually + // Prevent a bug where function A calls B (blocking) which calls C back on the sender, which calls D, and C is blocking. + // blockOnRPCReply is a shared variable so making it unset would unset both blocks, rather than the lowest on the callstack + // Fix by tracking which function the reply is for. + return false; + } + + unsigned *sendList; +// bool callerAllocationDataUsed; + unsigned sendListSize; + + // All this code modifies bcs->data and bcs->numberOfBitsToSend in order to transform an RPC request into an actual packet for SendImmediate + RPCIndex rpcIndex; // Index into the list of RPC calls so we know what number to encode in the packet +// char *userData; // RPC ID (the name of it) and a pointer to the data sent by the user +// int extraBuffer; // How many data bytes were allocated to hold the RPC header + unsigned remoteSystemIndex, sendListIndex; // Iterates into the list of remote systems +// int dataBlockAllocationLength; // Total number of bytes to allocate for the packet +// char *writeTarget; // Used to hold either a block of allocated data or the externally allocated data + + sendListSize=0; + bool routeSend; + routeSend=false; + + if (broadcast==false) + { +#if !defined(_COMPATIBILITY_1) + sendList=(unsigned *)alloca(sizeof(unsigned)); +#else + sendList = new unsigned[1]; +#endif + remoteSystemIndex=GetIndexFromPlayerID( playerId, false ); + if (remoteSystemIndex!=(unsigned)-1 && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + sendList[0]=remoteSystemIndex; + sendListSize=1; + } + else if (router) + routeSend=true; + } + else + { +#if !defined(_COMPATIBILITY_1) + sendList=(unsigned *)alloca(sizeof(unsigned)*maximumNumberOfPeers); +#else + sendList = new unsigned[maximumNumberOfPeers]; +#endif + + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].isActive && remoteSystemList[ remoteSystemIndex ].playerId != playerId ) + sendList[sendListSize++]=remoteSystemIndex; + } + } + + if (sendListSize==0 && routeSend==false) + { +#if defined(_COMPATIBILITY_1) + delete [] sendList; +#endif + + return false; + } + if (routeSend) + sendListSize=1; + + RakNet::BitStream outgoingBitStream; + // remoteSystemList in network thread + for (sendListIndex=0; sendListIndex < (unsigned)sendListSize; sendListIndex++) + { + outgoingBitStream.ResetWritePointer(); // Let us write at the start of the data block, rather than at the end + + if (shiftTimestamp) + { + outgoingBitStream.Write((unsigned char) ID_TIMESTAMP); + outgoingBitStream.Write(RakNet::GetTime()); + } + outgoingBitStream.Write((unsigned char) ID_RPC); + if (routeSend) + rpcIndex=UNDEFINED_RPC_INDEX; + else + rpcIndex=remoteSystemList[sendList[sendListIndex]].rpcMap.GetIndexFromFunctionName(uniqueID); // Lots of trouble but we can only use remoteSystem->[whatever] in this thread so that is why this command was buffered + if (rpcIndex!=UNDEFINED_RPC_INDEX) + { + // We have an RPC name to an index mapping, so write the index + outgoingBitStream.Write(false); + outgoingBitStream.WriteCompressed(rpcIndex); + } + else + { + // No mapping, so write the encoded RPC name + outgoingBitStream.Write(true); + stringCompressor->EncodeString(uniqueID, 256, &outgoingBitStream); + } + outgoingBitStream.Write((bool) ((replyFromTarget!=0)==true)); + outgoingBitStream.WriteCompressed( bitLength ); + if (networkID==UNASSIGNED_NETWORK_ID) + { + // No object ID + outgoingBitStream.Write(false); + } + else + { + // Encode an object ID. This will use pointer to class member RPC + outgoingBitStream.Write(true); + outgoingBitStream.Write(networkID); + } + + + if ( bitLength > 0 ) + outgoingBitStream.WriteBits( (const unsigned char *) data, bitLength, false ); // Last param is false to write the raw data originally from another bitstream, rather than shifting from user data + else + outgoingBitStream.WriteCompressed( ( unsigned int ) 0 ); + + if (routeSend) + router->Send((const char*)outgoingBitStream.GetData(), outgoingBitStream.GetNumberOfBitsUsed(), priority,reliability,orderingChannel,playerId); + else + Send(&outgoingBitStream, priority, reliability, orderingChannel, remoteSystemList[sendList[sendListIndex]].playerId, false); + } + +#if defined(_COMPATIBILITY_1) + delete [] sendList; +#endif + + if (replyFromTarget) + { + blockOnRPCReply=true; + // 04/20/06 Just do this transparently. + // We have to be able to read blocking packets out of order. Otherwise, if two systems were to send blocking RPC calls to each other at the same time, + // and they also had ordered packets waiting before the block, it would be impossible to unblock. + // assert(reliability==RELIABLE || reliability==UNRELIABLE); + replyFromTargetBS=replyFromTarget; + replyFromTargetPlayer=playerId; + replyFromTargetBroadcast=broadcast; + } + + // Do not enter this loop on blockOnRPCReply because it is a global which could be set to true by an RPC higher on the callstack, where one RPC was called while waiting for another RPC + if (replyFromTarget) +// if (blockOnRPCReply) + { +// Packet *p; + RakNetTime stopWaitingTime; +// RPCIndex arrivedRPCIndex; +// char uniqueIdentifier[256]; + if (reliability==UNRELIABLE) + if (playerId==UNASSIGNED_PLAYER_ID) + stopWaitingTime=RakNet::GetTime()+1500; // Lets guess the ave. ping is 500. Not important to be very accurate + else + stopWaitingTime=RakNet::GetTime()+GetAveragePing(playerId)*3; + + // For reliable messages, block until we get a reply or the connection is lost + // For unreliable messages, block until we get a reply, the connection is lost, or 3X the ping passes + while (blockOnRPCReply && + ((reliability==RELIABLE || reliability==RELIABLE_ORDERED || reliability==RELIABLE_SEQUENCED) || + RakNet::GetTime() < stopWaitingTime)) + { + + RakSleep(30); + + if (routeSend==false && ValidSendTarget(playerId, broadcast)==false) + return false; + + // I might not support processing other RPCs while blocking on one due to complexities I can't control + // Problem is FuncA calls FuncB which calls back to the sender FuncC. Sometimes it is desirable to call FuncC before returning a return value + // from FuncB - sometimes not. There is also a problem with recursion where FuncA calls FuncB which calls FuncA - sometimes valid if + // a different control path is taken in FuncA. (This can take many different forms) + /* + // Same as Receive, but doesn't automatically do RPCs + p = ReceiveIgnoreRPC(); + if (p) + { + // Process all RPC calls except for those calling the function we are currently blocking in (to prevent recursion). + if ( p->data[ 0 ] == ID_RPC ) + { + RakNet::BitStream temp((unsigned char *) p->data, p->length, false); + RPCNode *rpcNode; + temp.IgnoreBits(8); + bool nameIsEncoded; + temp.Read(nameIsEncoded); + if (nameIsEncoded) + { + stringCompressor->DecodeString((char*)uniqueIdentifier, 256, &temp); + } + else + { + temp.ReadCompressed( arrivedRPCIndex ); + rpcNode=rpcMap.GetNodeFromIndex( arrivedRPCIndex ); + if (rpcNode==0) + { + // Invalid RPC format +#ifdef _DEBUG + assert(0); +#endif + DeallocatePacket(p); + continue; + } + else + strcpy(uniqueIdentifier, rpcNode->uniqueIdentifier); + } + + if (strcmp(uniqueIdentifier, uniqueID)!=0) + { + HandleRPCPacket( ( char* ) p->data, p->length, p->playerId ); + DeallocatePacket(p); + } + else + { + PushBackPacket(p, false); + } + } + else + { + PushBackPacket(p, false); + } + } + */ + } + + blockOnRPCReply=false; + } + + return true; } -void RakPeer::vftable_54() + +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +bool RakPeer::RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakPeer::vftable_54() (saco W: 1003E050) (server W: 455340 L: 8071710) (bot W: 408FF0 L: 8073DB6) + if (bitStream) + return RPC(uniqueID, (const char*) bitStream->GetData(), bitStream->GetNumberOfBitsUsed(), priority, reliability, orderingChannel, playerId, broadcast, shiftTimestamp, networkID, replyFromTarget); + else + return RPC(uniqueID, 0,0, priority, reliability, orderingChannel, playerId, broadcast, shiftTimestamp, networkID, replyFromTarget); } -void RakPeer::vftable_58() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). +// +// Parameters: +// target: Which connection to close +// sendDisconnectionNotification: True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. +// channel: If blockDuration > 0, the disconnect packet will be sent on this channel +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::CloseConnection( const PlayerID target, bool sendDisconnectionNotification, unsigned char orderingChannel ) { - // TODO: RakPeer::vftable_58() (saco W: 10040A00) (server W: 457FB0 L: 806D590) (bot W: 40B770 L: 80741A0) + CloseConnectionInternal(target, sendDisconnectionNotification, false, orderingChannel); } -void RakPeer::vftable_5C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Given a playerID, returns an index from 0 to the maximum number of players allowed - 1. +// +// Parameters +// playerId - The playerID to search for +// +// Returns +// An integer from 0 to the maximum number of peers -1, or -1 if that player is not found +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetIndexFromPlayerID( const PlayerID playerId ) { - // TODO: RakPeer::vftable_5C() (saco W: 1003E330) (server W: 455620 L: 8074290) (bot W: 4092D0 L: 80742D2) + return GetIndexFromPlayerID(playerId, false); } -void RakPeer::vftable_60() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// This function is only useful for looping through all players. +// +// Parameters +// index - an integer between 0 and the maximum number of players allowed - 1. +// +// Returns +// A valid playerID or UNASSIGNED_PLAYER_ID if no such player at that index +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +PlayerID RakPeer::GetPlayerIDFromIndex( int index ) { - // TODO: RakPeer::vftable_60() (saco W: 10038660) (server W: 450160 L: 80716E0) (bot W: 404100 L: 8074318) + // remoteSystemList in user thread + //if ( index >= 0 && index < remoteSystemListSize ) + if ( index >= 0 && index < maximumNumberOfPeers ) + if (remoteSystemList[ index ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED) // Don't give the user players that aren't fully connected, since sends will fail + return remoteSystemList[ index ].playerId; + + return UNASSIGNED_PLAYER_ID; } -void RakPeer::vftable_64() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Bans an IP from connecting. Banned IPs persist between connections. +// +// Parameters +// IP - Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban +// All IP addresses starting with 128.0.0 +// milliseconds - how many ms for a temporary ban. Use 0 for a permanent ban +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AddToBanList( const char *IP, RakNetTime milliseconds ) { - // TODO: RakPeer::vftable_64() (saco W: 1003A7D0) (server W: 451B10 L: 806D6B0) (bot W: 405800 L: 8074342) + unsigned index; + RakNetTime time = RakNet::GetTime(); + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return ; + + // If this guy is already in the ban list, do nothing + index = 0; + + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + if ( strcmp( IP, banList[ index ]->IP ) == 0 ) + { + // Already in the ban list. Just update the time + if (milliseconds==0) + banList[ index ]->timeout=0; // Infinite + else + banList[ index ]->timeout=time+milliseconds; + banListMutex.Unlock(); + return; + } + } + + banListMutex.Unlock(); + + BanStruct *banStruct = new BanStruct; + banStruct->IP = new char [ 16 ]; + if (milliseconds==0) + banStruct->timeout=0; // Infinite + else + banStruct->timeout=time+milliseconds; + strcpy( banStruct->IP, IP ); + banListMutex.Lock(); + banList.Insert( banStruct ); + banListMutex.Unlock(); } -void RakPeer::vftable_68() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allows a previously banned IP to connect. +// +// Parameters +// IP - Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban +// All IP addresses starting with 128.0.0 +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RemoveFromBanList( const char *IP ) { - // TODO: RakPeer::vftable_68() (saco W: 1003C4D0) (server W: 4537E0 L: 806D770) (bot W: 407480 L: 807441A) + unsigned index; + BanStruct *temp; + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return ; + + index = 0; + temp=0; + + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + if ( strcmp( IP, banList[ index ]->IP ) == 0 ) + { + temp = banList[ index ]; + banList[ index ] = banList[ banList.Size() - 1 ]; + banList.RemoveAtIndex( banList.Size() - 1 ); + break; + } + } + + banListMutex.Unlock(); + + if (temp) + { + delete [] temp->IP; + delete temp; + } + } -void RakPeer::vftable_6C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allows all previously banned IPs to connect. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearBanList( void ) { - // TODO: RakPeer::vftable_6C() (saco W: 1003A960) (server W: 451CA0 L: 806F440) (bot W: 405990 L: 80745D6) + unsigned index; + index = 0; + banListMutex.Lock(); + + for ( ; index < banList.Size(); index++ ) + { + delete [] banList[ index ]->IP; + delete [] banList[ index ]; + } + + banList.Clear(); + + banListMutex.Unlock(); } -void RakPeer::vftable_70() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Determines if a particular IP is banned. +// +// Parameters +// IP - Complete dotted IP address +// +// Returns +// True if IP matches any IPs in the ban list, accounting for any wildcards. +// False otherwise. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsBanned( const char *IP ) { - // TODO: RakPeer::vftable_70() (saco W: 1003C5E0) (server W: 4538F0 L: 806D8E0) (bot W: 407590 L: 807473C) + unsigned banListIndex, characterIndex; + RakNetTime time; + BanStruct *temp; + + if ( IP == 0 || IP[ 0 ] == 0 || strlen( IP ) > 15 ) + return false; + + banListIndex = 0; + + if ( banList.Size() == 0 ) + return false; // Skip the mutex if possible + + time = RakNet::GetTime(); + + banListMutex.Lock(); + + while ( banListIndex < banList.Size() ) + { + if (banList[ banListIndex ]->timeout>0 && banList[ banListIndex ]->timeoutIP; + delete temp; + } + else + { + characterIndex = 0; + +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while ( true ) + { + if ( banList[ banListIndex ]->IP[ characterIndex ] == IP[ characterIndex ] ) + { + // Equal characters + + if ( IP[ characterIndex ] == 0 ) + { + banListMutex.Unlock(); + // End of the string and the strings match + + return true; + } + + characterIndex++; + } + + else + { + if ( banList[ banListIndex ]->IP[ characterIndex ] == 0 || IP[ characterIndex ] == 0 ) + { + // End of one of the strings + break; + } + + // Characters do not match + if ( banList[ banListIndex ]->IP[ characterIndex ] == '*' ) + { + banListMutex.Unlock(); + + // Domain is banned. + return true; + } + + // Characters do not match and it is not a * + break; + } + } + + banListIndex++; + } + } + + banListMutex.Unlock(); + + // No match found. + return false; } -void RakPeer::vftable_74() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Send a ping to the specified connected system. +// +// Parameters: +// target - who to ping +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::Ping( const PlayerID target ) { - // TODO: RakPeer::vftable_74() (saco W: 1003A9D0) (server W: 451D10 L: 806F300) (bot W: 405A00 L: 80747FC) + PingInternal(target, false); } -void RakPeer::vftable_78() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Send a ping to the specified unconnected system. +// The remote system, if it is Initialized, will respond with ID_PONG. +// The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in NetworkTypes.h +// +// Parameters: +// host: Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. +// remotePort: Which port to connect to on the remote machine. +// onlyReplyOnAcceptingConnections: Only request a reply if the remote system has open connections +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections ) { - // TODO: RakPeer::vftable_78() (saco W: 10040A30) (server W: 457FE0 L: 8070970) (bot W: 40B7A0 L: 8074A70) + if ( host == 0 ) + return; + +// if ( IsActive() == false ) +// return; + + // If the host starts with something other than 0, 1, or 2 it's (probably) a domain name. + if ( host[ 0 ] < '0' || host[ 0 ] > '2' ) + { +#if !defined(_COMPATIBILITY_1) + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); +#else + return; +#endif + } + + PlayerID playerId; + IPToPlayerID( host, remotePort, &playerId ); + + RakNet::BitStream bitStream( sizeof(unsigned char) + sizeof(RakNetTime) ); + if ( onlyReplyOnAcceptingConnections ) + bitStream.Write((unsigned char)ID_PING_OPEN_CONNECTIONS); + else + bitStream.Write((unsigned char)ID_PING); + + bitStream.Write(RakNet::GetTime()); + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((const char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), playerId); + // No timestamp for 255.255.255.255 + SocketLayer::Instance()->SendTo( connectionSocket, (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed(), ( char* ) host, remotePort ); + + + + } -void RakPeer::vftable_7C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the average of all ping times read for a specified target +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetAveragePing( const PlayerID playerId ) { - // TODO: RakPeer::vftable_7C() (saco W: 1003E350) (server W: 455640 L: 806D9A0) (bot W: 4092F0 L: 8074A9A) + int sum, quantity; + RemoteSystemStruct *remoteSystem = GetRemoteSystemFromPlayerID( playerId, false, false ); + + if ( remoteSystem == 0 ) + return -1; + + for ( sum = 0, quantity = 0; quantity < PING_TIMES_ARRAY_SIZE; quantity++ ) + { + if ( remoteSystem->pingAndClockDifferential[ quantity ].pingTime == 65535 ) + break; + else + sum += remoteSystem->pingAndClockDifferential[ quantity ].pingTime; + } + + if ( quantity > 0 ) + return sum / quantity; + else + return -1; } -void RakPeer::vftable_80() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the last ping time read for the specific player or -1 if none read yet +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetLastPing( const PlayerID playerId ) const { - // TODO: RakPeer::vftable_80() (saco W: 1003E3B0) (server W: 4556A0 L: 8070270) (bot W: 409350 L: 8074CB4) + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromPlayerID( playerId, false, false ); + + if ( remoteSystem == 0 ) + return -1; + + if ( remoteSystem->pingAndClockDifferentialWriteIndex == 0 ) + return remoteSystem->pingAndClockDifferential[ PING_TIMES_ARRAY_SIZE - 1 ].pingTime; + else + return remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex - 1 ].pingTime; } -void RakPeer::vftable_84() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the lowest ping time read or -1 if none read yet +// +// Parameters: +// target - whose time to read +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetLowestPing( const PlayerID playerId ) const { - // TODO: RakPeer::vftable_84() (saco W: 1003E400) (server W: 4556F0 L: 8070210) (bot W: 4093A0 L: 8074D7A) + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromPlayerID( playerId, false, false ); + + if ( remoteSystem == 0 ) + return -1; + + return remoteSystem->lowestPing; } -void RakPeer::vftable_88() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Ping the remote systems every so often. This is off by default +// This will work anytime +// +// Parameters: +// doPing - True to start occasional pings. False to stop them. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetOccasionalPing( bool doPing ) { - // TODO: RakPeer::vftable_88() (saco W: 10038720) (server W: 450220 L: 80701D0) (bot W: 404160 L: 8074DFE) + occasionalPing = doPing; } -void RakPeer::vftable_8C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// All systems have a block of data associated with them, for user use. This block of data can be used to easily +// specify typical system data that you want to know on connection, such as the player's name. +// +// Parameters: +// playerId: Which system you are referring to. Pass the value returned by GetInternalID to refer to yourself +// +// Returns: +// The data passed to SetRemoteStaticData stored as a bitstream +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakNet::BitStream * RakPeer::GetRemoteStaticData( const PlayerID playerId ) { - // TODO: RakPeer::vftable_8C() (saco W: 1003E440) (server W: 455730 L: 806DBB0) (bot W: 4093E0 L: 8074E52) + if ( playerId == myPlayerId ) + return & localStaticData; + + RemoteSystemStruct *remoteSystem = GetRemoteSystemFromPlayerID( playerId, false, false ); + + if ( remoteSystem ) + return &(remoteSystem->staticData); + else + return 0; } -void RakPeer::vftable_90() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// All systems have a block of data associated with them, for user use. This block of data can be used to easily +// specify typical system data that you want to know on connection, such as the player's name. +// +// Parameters: +// playerId: Whose static data to change. Use your own playerId to change your own static data +// data: a block of data to store +// length: The length of data in bytes +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetRemoteStaticData( const PlayerID playerId, const char *data, const int length ) { - // TODO: RakPeer::vftable_90() (saco W: 1003E490) (server W: 455780 L: 8070160) (bot W: 409430 L: 8074E6A) + if ( playerId == myPlayerId ) + { + localStaticData.Reset(); + + if ( data && length > 0 ) + localStaticData.Write( data, length ); + } + else + { + RemoteSystemStruct *remoteSystem = GetRemoteSystemFromPlayerID( playerId, false, true ); + + if ( remoteSystem == 0 ) + return; + + remoteSystem->staticData.Reset(); + remoteSystem->staticData.Write( data, length ); + } } -void RakPeer::vftable_94() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sends your static data to the specified system. This is automatically done on connection. +// You should call this when you change your static data. +// To send the static data of another system (such as relaying their data) you should do this normally with Send +// +// Parameters: +// target: Who to send your static data to. Specify UNASSIGNED_PLAYER_ID to broadcast to all +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendStaticData( const PlayerID target ) { - // TODO: RakPeer::vftable_94() (saco W: 10040A50) (server W: 458000 L: 80700B0) (bot W: 40B7C0 L: 8074EE2) + SendStaticDataInternal(target, false); } -void RakPeer::vftable_98() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Length should be under 400 bytes, as a security measure against flood attacks +// Sets the data to send with an (LAN server discovery) /(offline ping) response +// See the Ping sample project for how this is used. +// data: a block of data to store, or 0 for none +// length: The length of data in bytes, or 0 for none +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetOfflinePingResponse( const char *data, const unsigned int length ) { - // TODO: RakPeer::vftable_98() (saco W: 10038730) (server W: 450230 L: 8070B60) (bot W: 404170 L: 8074FA8) + assert(length < 400); + + rakPeerMutexes[ offlinePingResponse_Mutex ].Lock(); + offlinePingResponse.Reset(); + + if ( data && length > 0 ) + offlinePingResponse.Write( data, length ); + + rakPeerMutexes[ offlinePingResponse_Mutex ].Unlock(); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -339,14 +1908,47 @@ PlayerID RakPeer::GetInternalID( void ) const return myPlayerId; } -void RakPeer::vftable_A0() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Return the unique address identifier that represents you on the the network and is based on your external +// IP / port (the IP / port the specified player uses to communicate with you) +// Note that unlike in previous versions, this is a struct and is not sequential +// +// Parameters: +// target: Which remote system you are referring to for your external ID +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +PlayerID RakPeer::GetExternalID( const PlayerID target ) const { - // TODO: RakPeer::vftable_A0() (saco W: 100387A0) (server W: 4502A0 L: 806DC50) (bot W: 4041E0 L: 8075034) + unsigned i; + PlayerID inactiveExternalId; + + inactiveExternalId=UNASSIGNED_PLAYER_ID; + + // First check for active connection with this playerId + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].playerId == target || target==UNASSIGNED_PLAYER_ID ) + { + if ( remoteSystemList[ i ].isActive ) + return remoteSystemList[ i ].myExternalPlayerId; + else + inactiveExternalId=remoteSystemList[ i ].myExternalPlayerId; + } + } + + return inactiveExternalId; } -void RakPeer::vftable_A4() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet +// \param[in] time Time, in MS +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetTimeoutTime( RakNetTime timeMS, const PlayerID target ) { - // TODO: RakPeer::vftable_A4() (saco W: 1003E520) (server W: 455810 L: 806DC70) (bot W: 4094C0 L: 8075056) + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromPlayerID( target, false, true ); + + if ( remoteSystem != 0 ) + remoteSystem->reliabilityLayer.SetTimeoutTime(timeMS); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -398,44 +2000,201 @@ int RakPeer::GetMTUSize( void ) const return MTUSize; } -void RakPeer::vftable_B0() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns the number of IP addresses we have +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned int RakPeer::GetNumberOfAddresses( void ) { - // TODO: RakPeer::vftable_B0() (saco W: 100388C0) (server W: 4503C0 L: 806DDB0) (bot W: 404300 L: 80751FE) +#if !defined(_COMPATIBILITY_1) + char ipList[ 10 ][ 16 ]; + memset( ipList, 0, sizeof( char ) * 16 * 10 ); + SocketLayer::Instance()->GetMyIP( ipList ); + + int i = 0; + + while ( ipList[ i ][ 0 ] ) + i++; + + return i; +#else + assert(0); + return 0; +#endif } -void RakPeer::vftable_B4() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Given a PlayerID struct, returns the dotted IP address string this binaryAddress field represents +// +// Returns: +// Null terminated dotted IP address string. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +const char* RakPeer::PlayerIDToDottedIP( const PlayerID playerId ) const { - // TODO: RakPeer::vftable_B4() (saco W: 10038920) (server W: 450420 L: 806DDC0) (bot W: 404360 L: 807520C) +#if !defined(_COMPATIBILITY_1) + in_addr in; + in.s_addr = playerId.binaryAddress; + return inet_ntoa( in ); +#else + assert(0); // Not supported + return 0; +#endif } -void RakPeer::vftable_B8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Returns an IP address at index 0 to GetNumberOfAddresses-1 +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +const char* RakPeer::GetLocalIP( unsigned int index ) { - // TODO: RakPeer::vftable_B8() (saco W: 10038910) (server W: 450410 L: 806DE40) (bot W: 404350 L: 807529A) +#if !defined(_COMPATIBILITY_1) + static char ipList[ 10 ][ 16 ]; + + if ( index >= 10 ) + index = 9; + + memset( ipList, 0, sizeof( char ) * 16 * 10 ); + + SocketLayer::Instance()->GetMyIP( ipList ); + + return ipList[ index ]; +#else + assert(0); + return 0; +#endif } -void RakPeer::vftable_BC() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary +// when connection to servers with multiple IP addresses +// +// Parameters: +// allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AllowConnectionResponseIPMigration( bool allow ) { - // TODO: RakPeer::vftable_BC() (saco W: 10038CB0) (server W: 450790 L: 806DE30) (bot W: 4046D0 L: 8075280) + allowConnectionResponseIPMigration = allow; } -void RakPeer::vftable_C0() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. +// This will tell the remote system our external IP outside the LAN, and can be used for NAT punch through +// +// Requires: +// The sender and recipient must already be started via a successful call to Initialize +// +// host: Either a dotted IP address or a domain name +// remotePort: Which port to connect to on the remote machine. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ) { - // TODO: RakPeer::vftable_C0() (saco W: 10038960) (server W: 450460 L: 806E690) (bot W: 4043A0 L: 8075F86) + if ( IsActive() == false ) + return ; + + if (host==0) + return; + + // This is a security measure. Don't send data longer than this value + assert(dataLength <= MAX_OFFLINE_DATA_LENGTH); + assert(dataLength>=0); + + // If the host starts with something other than 0, 1, or 2 it's (probably) a domain name. + if ( host[ 0 ] < '0' || host[ 0 ] > '2' ) + { +#if !defined(_COMPATIBILITY_1) + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); +#else + return; +#endif + } + + PlayerID playerId; + IPToPlayerID( host, remotePort, &playerId ); + + RakNet::BitStream bitStream; + bitStream.Write((unsigned char)ID_ADVERTISE_SYSTEM); + if (dataLength>0) + bitStream.Write(data, dataLength); + else + bitStream.Write((unsigned char)0); // Pad + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((const char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), playerId); + SocketLayer::Instance()->SendTo( connectionSocket, (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed(), ( char* ) host, remotePort ); + + + + /* + // If the host starts with something other than 0, 1, or 2 it's (probably) a domain name. + if ( host[ 0 ] < '0' || host[ 0 ] > '2' ) + { +#if !defined(_COMPATIBILITY_1) + host = ( char* ) SocketLayer::Instance()->DomainNameToIP( host ); +#else + return; +#endif + } + + PlayerID playerId; + IPToPlayerID( host, remotePort, &playerId ); + + RequestedConnectionStruct *rcs; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Lock(); +#endif + rcs = requestedConnectionList.WriteLock(); + rcs->playerId=playerId; + rcs->nextRequestTime=RakNet::GetTime(); + rcs->requestsMade=0; + if (data && dataLength>0) + { + rcs->data=new char [dataLength]; + rcs->dataLength=(unsigned short)dataLength; + memcpy(rcs->data, data, dataLength); + } + else + { + rcs->data=0; + rcs->dataLength=0; + } + rcs->actionToTake=RequestedConnectionStruct::ADVERTISE_SYSTEM; + requestedConnectionList.WriteUnlock(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Unlock(); +#endif + */ } -void RakPeer::vftable_C4() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. +// ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived +// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. +// Defaults to 0 (never return this notification) +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetSplitMessageProgressInterval(int interval) { - // TODO: RakPeer::vftable_C4() (saco W: 1003AB30) (server W: 451E70 L: 806DEA0) (bot W: 405B60 L: 80752EC) + RakAssert(interval>=0); + splitMessageProgressInterval=interval; + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + remoteSystemList[ i ].reliabilityLayer.SetSplitMessageProgressInterval(splitMessageProgressInterval); } -void RakPeer::vftable_C8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Set how long to wait before giving up on sending an unreliable message +// Useful if the network is clogged up. +// Set to 0 or less to never timeout. Defaults to 0. +// timeoutMS How many ms to wait before simply not sending an unreliable message. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetUnreliableTimeout(RakNetTime timeoutMS) { - // TODO: RakPeer::vftable_C8() (saco W: 10038970) (server W: 450470 L: 806DEC0) (bot W: 4043B0 L: 8075308) -} - -void RakPeer::vftable_CC() -{ - // TODO: RakPeer::vftable_CC() (saco W: 100389C0) (server W: 4504C0 L: 806E110) (bot W: 404400 L: 807558A) + RakAssert(timeoutMS>=0); + unreliableTimeout=timeoutMS; + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + remoteSystemList[ i ].reliabilityLayer.SetUnreliableTimeout(unreliableTimeout); } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -481,14 +2240,79 @@ bool RakPeer::GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 return true; } -void RakPeer::vftable_D8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Generates the compression layer from the input frequency table. +// You should call this twice - once with inputLayer as true and once as false. +// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. +// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true +// Calling this function when there is an existing layer will overwrite the old layer +// You should only call this when disconnected +// +// Parameters: +// inputFrequencyTable: The frequency table returned from GetSendFrequencyTable(...) +// inputLayer - Whether inputFrequencyTable represents incoming data from other systems (true) or outgoing data from this system (false) +// +// Returns: +// False on failure (we are connected). True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ) { - // TODO: RakPeer::vftable_D8() (saco W: 10038A60) (server W: 450560 L: 806E1F0) (bot W: 4044A0 L: 8075666) + if ( IsActive() ) + return false; + + DeleteCompressionLayer( inputLayer ); + + if ( inputLayer ) + { + inputTree = new HuffmanEncodingTree; + inputTree->GenerateFromFrequencyTable( inputFrequencyTable ); + } + + else + { + outputTree = new HuffmanEncodingTree; + outputTree->GenerateFromFrequencyTable( inputFrequencyTable ); + } + + return true; } -void RakPeer::vftable_DC() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Deletes the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory +// You should only call this when disconnected +// +// Parameters: +// inputLayer - Specifies the corresponding compression layer generated by GenerateCompressionLayer. +// +// Returns: +// False on failure (we are connected). True otherwise +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::DeleteCompressionLayer( bool inputLayer ) { - // TODO: RakPeer::vftable_DC() (saco W: 1003ACA0) (server W: 451FE0 L: 806E250) (bot W: 405CD0 L: 80756CC) + if ( IsActive() ) + return false; + + if ( inputLayer ) + { + if ( inputTree ) + { + delete inputTree; + inputTree = 0; + } + } + + else + { + if ( outputTree ) + { + delete outputTree; + outputTree = 0; + } + } + + return true; } // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -521,57 +2345,2673 @@ float RakPeer::GetDecompressionRatio( void ) const return 0.0f; } -void RakPeer::vftable_E8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Attatches a Plugin interface to run code automatically on message receipt in the Receive call +// +// \param messageHandler Pointer to a plugin to attach +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::AttachPlugin( PluginInterface *plugin ) { - // TODO: RakPeer::vftable_E8() (saco W: 1003AD20) (server W: 452060 L: 806E400) (bot W: 405D50 L: 8075910) + if (messageHandlerList.GetIndexOf(plugin)==MAX_UNSIGNED_LONG) + { + messageHandlerList.Insert(plugin); + plugin->OnAttach(this); + } } -void RakPeer::vftable_EC() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Detaches a Plugin interface to run code automatically on message receipt +// +// \param messageHandler Pointer to a plugin to detach +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::DetachPlugin( PluginInterface *plugin ) { - // TODO: RakPeer::vftable_EC() (saco W: 1003AD60) (server W: 4520A0 L: 806E440) (bot W: 405D90 L: 8075962) + if (plugin==0) + return; + + unsigned int index; + index = messageHandlerList.GetIndexOf(plugin); + if (index!=MAX_UNSIGNED_LONG) + { + messageHandlerList[index]->OnDetach(this); + // Unordered list so delete from end for speed + messageHandlerList[index]=messageHandlerList[messageHandlerList.Size()-1]; + messageHandlerList.Del(); + } } -void RakPeer::vftable_F0() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately +// +// packet The packet you want to push back. +// pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::PushBackPacket( Packet *packet, bool pushAtHead) { - // TODO: RakPeer::vftable_F0() (saco W: 1003ADB0) (server W: 4520F0 L: 806E4A0) (bot W: 405DE0 L: 80759B4) +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Lock(); +#endif + RakAssert(packet); + if (pushAtHead) + packetPool.PushAtHead(packet); + else + packetPool.Push(packet); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[packetPool_Mutex].Unlock(); +#endif } -void RakPeer::vftable_F4() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SetRouterInterface( RouterInterface *routerInterface ) { - // TODO: RakPeer::vftable_F4() (saco W: 10038BD0) (server W: 4506B0 L: 806E510) (bot W: 4045F0 L: 8075A76) + router=routerInterface; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::RemoveRouterInterface( RouterInterface *routerInterface ) +{ + if (router==routerInterface) + router=0; } -void RakPeer::vftable_F8() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Adds simulated ping and packet loss to the outgoing data flow. +// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance) { - // TODO: RakPeer::vftable_F8() (saco W: 10038BE0) (server W: 4506C0 L: 806E560) (bot W: 404600 L: 8075AC2) +#ifndef _RELEASE + if (remoteSystemList) + { + unsigned short i; + for (i=0; i < maximumNumberOfPeers; i++) + //for (i=0; i < remoteSystemListSize; i++) + remoteSystemList[i].reliabilityLayer.ApplyNetworkSimulator(maxSendBPS, minExtraPing, extraPingVariance); + } + + _maxSendBPS=maxSendBPS; + _minExtraPing=minExtraPing; + _extraPingVariance=extraPingVariance; +#endif } -void RakPeer::vftable_FC() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Returns if you previously called ApplyNetworkSimulator +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::IsNetworkSimulatorActive( void ) { - // TODO: RakPeer::vftable_FC() (saco W: 10038C00) (server W: 4506E0 L: 806E580) (bot W: 404620 L: 8075AD4) + return _maxSendBPS>0 || _minExtraPing>0 || _extraPingVariance>0; } -void RakPeer::vftable_100() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// For internal use +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RPCMap* RakPeer::GetRPCMap( const PlayerID playerId) { - // TODO: RakPeer::vftable_100() (saco W: 10038C80) (server W: 450760 L: 806E5A0) (bot W: 4046A0 L: 8075AF4) + if (playerId==UNASSIGNED_PLAYER_ID) + return &rpcMap; + else + { + RemoteSystemStruct *rss=GetRemoteSystemFromPlayerID(playerId, false, true); + if (rss) + return &(rss->rpcMap); + else + return 0; + } } -void RakPeer::vftable_104() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakNetStatisticsStruct * const RakPeer::GetStatistics( const PlayerID playerId ) { - // TODO: RakPeer::vftable_104() (saco W: 1003E5C0) (server W: 4558B0 L: 806E650) (bot W: 409560 L: 8075B92) + if (playerId==UNASSIGNED_PLAYER_ID) + { + bool firstWrite=false; + static RakNetStatisticsStruct sum; + RakNetStatisticsStruct *systemStats; + // Return a crude sum + for ( unsigned short i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].isActive) + { + systemStats=remoteSystemList[ i ].reliabilityLayer.GetStatistics(); + + if (firstWrite==false) + memcpy(&sum, systemStats, sizeof(RakNetStatisticsStruct)); + else + sum+=*systemStats; + } + } + return ∑ + } + else + { + RemoteSystemStruct * rss; + rss = GetRemoteSystemFromPlayerID( playerId, false, false ); + if ( rss && endThreads==false ) + return rss->reliabilityLayer.GetStatistics(); + } + + return 0; } -void RakPeer::vftable_108() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/* +void RakPeer::RemoveFromRequestedConnectionsList( const PlayerID playerId ) { - // TODO: RakPeer::vftable_108() (saco W: 1003EC80) (server W: 456030 L: 806FC90) (bot W: 409C20 L: 8075C52) + int i; + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Lock(); + + for ( i = 0; i < ( int ) requestedConnectionsList.Size(); ) + { + if ( requestedConnectionsList[ i ]->playerId == playerId ) + { + delete requestedConnectionsList[ i ]; + requestedConnectionsList.Del( i ); + break; + } + } + + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Unlock(); +} +*/ + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +int RakPeer::GetIndexFromPlayerID( const PlayerID playerId, bool calledFromNetworkThread ) +{ + unsigned i; + + if ( playerId == UNASSIGNED_PLAYER_ID ) + return -1; + + if (calledFromNetworkThread) + { + bool objectExists; + unsigned index; + index = remoteSystemLookup.GetIndexFromKey(playerId, &objectExists); + if (objectExists) + { + assert(remoteSystemList[remoteSystemLookup[index].index].playerId==playerId); + return remoteSystemLookup[index].index; + } + else + return -1; + } + else + { + // remoteSystemList in user and network thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].playerId == playerId ) + return i; + } + + return -1; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::SendConnectionRequest( const char* host, unsigned short remotePort, char* passwordData, int passwordDataLength ) +{ + PlayerID playerId; + IPToPlayerID( host, remotePort, &playerId ); + + // Already connected? + if (GetRemoteSystemFromPlayerID(playerId, false, true)) + return false; + + assert(passwordDataLength <= 256); + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Lock(); +#endif + RequestedConnectionStruct *rcs = requestedConnectionList.WriteLock(); + rcs->playerId=playerId; + rcs->nextRequestTime=RakNet::GetTime(); + rcs->requestsMade=0; + rcs->data=0; + rcs->actionToTake=RequestedConnectionStruct::CONNECT; + memcpy(rcs->outgoingPassword, passwordData, passwordDataLength); + rcs->outgoingPasswordLength=(unsigned char) passwordDataLength; + requestedConnectionList.WriteUnlock(); + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Unlock(); +#endif + + return true; } -void RakPeer::vftable_10C() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::IPToPlayerID( const char* host, unsigned short remotePort, PlayerID *playerId ) { - // TODO: RakPeer::vftable_10C() (saco W: 1003E560) (server W: 455850 L: 806F710) (bot W: 409500 L: 8076B8C) + if ( host == 0 ) + return ; + + playerId->binaryAddress = inet_addr( host ); + + playerId->port = remotePort; } -void RakPeer::vftable_110() +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct *RakPeer::GetRemoteSystemFromPlayerID( const PlayerID playerID, bool calledFromNetworkThread, bool onlyActive ) const { - // TODO: RakPeer::vftable_110() (server L: 806FFE0) (bot L: 8075BDC) + unsigned i; + + if ( playerID == UNASSIGNED_PLAYER_ID ) + return 0; + + if (calledFromNetworkThread) + { + bool objectExists; + unsigned index; + index = remoteSystemLookup.GetIndexFromKey(playerID, &objectExists); + if (objectExists) + { +#ifdef _DEBUG + assert(remoteSystemList[ remoteSystemLookup[index].index ].playerId==playerID); +#endif + return remoteSystemList + remoteSystemLookup[index].index; + } + } + else + { + int deadConnectionIndex=-1; + + // Active connections take priority. But if there are no active connections, return the first systemAddress match found + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if (remoteSystemList[ i ].playerId == playerID ) + { + if ( remoteSystemList[ i ].isActive ) + return remoteSystemList + i; + else if (deadConnectionIndex==-1) + deadConnectionIndex=i; + } + } + + if (deadConnectionIndex!=-1 && onlyActive==false) + return remoteSystemList + deadConnectionIndex; + } + + return 0; } +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, PlayerID playerId, const char *data, int byteSize ) +{ + // If we are full tell the sender. + if ( !AllowIncomingConnections() ) + { + unsigned char c = ID_NO_FREE_INCOMING_CONNECTIONS; + // SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, ( char* ) & c, sizeof( char ), systemAddress.binaryAddress, systemAddress.port ); + SendImmediate(( char* ) & c, sizeof( char )*8, SYSTEM_PRIORITY, RELIABLE, 0, playerId, false, false, RakNet::GetTime()); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + else + { + const char *password = data + sizeof(unsigned char); + int passwordLength = byteSize - sizeof(unsigned char); + + if ( incomingPasswordLength == passwordLength && + memcmp( password, incomingPassword, incomingPasswordLength ) == 0 ) + { + remoteSystem->connectMode=RemoteSystemStruct::HANDLING_CONNECTION_REQUEST; + +#if !defined(_COMPATIBILITY_1) + if ( usingSecurity == false ) +#endif + { +#ifdef _TEST_AES + unsigned char AESKey[ 16 ]; + // Save the AES key + for ( i = 0; i < 16; i++ ) + AESKey[ i ] = i; + + OnConnectionRequest( remoteSystem, AESKey, true ); +#else + // Connect this player assuming we have open slots + OnConnectionRequest( remoteSystem, 0, false ); +#endif + } +#if !defined(_COMPATIBILITY_1) + else + SecuredConnectionResponse( playerId ); +#endif + } + else + { + // This one we only send once since we don't care if it arrives. + unsigned char c = ID_INVALID_PASSWORD; + // SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, ( char* ) & c, sizeof( char ), systemAddress.binaryAddress, systemAddress.port ); + SendImmediate(( char* ) & c, sizeof( char )*8, SYSTEM_PRIORITY, RELIABLE, 0, playerId, false, false, RakNet::GetTime()); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::OnConnectionRequest( RakPeer::RemoteSystemStruct *remoteSystem, unsigned char *AESKey, bool setAESKey ) +{ + // Already handled by caller + //if ( AllowIncomingConnections() ) + { +#ifdef __USE_IO_COMPLETION_PORTS + unsigned index; + + // remoteSystemList in network thread + for ( index = 0; index < maximumNumberOfPeers; index++ ) + //for ( index = 0; index < remoteSystemListSize; index++ ) + if ( remoteSystemList + index == remoteSystem ) + break; + + if ( SetupIOCompletionPortSocket( index ) == false ) + { + // Socket error + assert( 0 ); + return ; + } +#endif + + RakNet::BitStream bitStream(sizeof(unsigned char)+sizeof(unsigned short)+sizeof(unsigned int)+sizeof(unsigned short)+sizeof(PlayerIndex)); + bitStream.Write((unsigned char)ID_CONNECTION_REQUEST_ACCEPTED); +//#ifdef __USE_IO_COMPLETION_PORTS +// bitStream.Write((unsigned short)myPlayerId.port + ( unsigned short ) index + ( unsigned short ) 1); +//#else +// bitStream.Write((unsigned short)myPlayerId.port); +//#endif + bitStream.Write(remoteSystem->playerId.binaryAddress); + bitStream.Write(remoteSystem->playerId.port); + bitStream.Write(( PlayerIndex ) GetIndexFromPlayerID( remoteSystem->playerId, true )); + + + SendImmediate((char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, remoteSystem->playerId, false, false, RakNet::GetTime()); + + // Don't set secure connections immediately because we need the ack from the remote system to know ID_CONNECTION_REQUEST_ACCEPTED + // As soon as a 16 byte packet arrives, we will turn on AES. This works because all encrypted packets are multiples of 16 and the + // packets I happen to be sending are less than 16 bytes + remoteSystem->setAESKey=setAESKey; + if ( setAESKey ) + { + memcpy(remoteSystem->AESKey, AESKey, 16); + remoteSystem->connectMode=RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET; + } + } + /* + else + { + unsigned char c = ID_NO_FREE_INCOMING_CONNECTIONS; + //SocketLayer::Instance()->SendTo( connectionSocket, ( char* ) & c, sizeof( char ), playerId.binaryAddress, playerId.port ); + + SendImmediate((char*)&c, sizeof(c)*8, SYSTEM_PRIORITY, RELIABLE, 0, remoteSystem->systemAddress, false, false, RakNet::GetTime()); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + } + */ +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::NotifyAndFlagForDisconnect( const PlayerID playerId, bool performImmediate, unsigned char orderingChannel ) +{ + RakNet::BitStream temp( sizeof(unsigned char) ); + temp.Write( (unsigned char) ID_DISCONNECTION_NOTIFICATION ); + if (performImmediate) + { + SendImmediate((char*)temp.GetData(), temp.GetNumberOfBitsUsed(), LOW_PRIORITY, RELIABLE_ORDERED, orderingChannel, playerId, false, false, RakNet::GetTime()); + RemoteSystemStruct *rss=GetRemoteSystemFromPlayerID(playerId, true, true); + rss->connectMode=RemoteSystemStruct::DISCONNECT_ASAP; + } + else + { + SendBuffered((const char*)temp.GetData(), temp.GetNumberOfBitsUsed(), LOW_PRIORITY, RELIABLE_ORDERED, orderingChannel, playerId, false, RemoteSystemStruct::DISCONNECT_ASAP); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +unsigned short RakPeer::GetNumberOfRemoteInitiatedConnections( void ) const +{ + unsigned short i, numberOfIncomingConnections; + + if ( remoteSystemList == 0 || endThreads == true ) + return 0; + + numberOfIncomingConnections = 0; + + // remoteSystemList in network thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + //for ( i = 0; i < remoteSystemListSize; i++ ) + { + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].weInitiatedTheConnection == false && remoteSystemList[i].connectMode==RemoteSystemStruct::CONNECTED) + numberOfIncomingConnections++; + } + + return numberOfIncomingConnections; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +RakPeer::RemoteSystemStruct * RakPeer::AssignPlayerIDToRemoteSystemList( const PlayerID playerId, RemoteSystemStruct::ConnectMode connectionMode ) +{ + RemoteSystemStruct * remoteSystem; + unsigned i,j; + RakNetTime time = RakNet::GetTime(); +#ifdef _DEBUG + assert(playerId!=UNASSIGNED_PLAYER_ID); +#endif + + // remoteSystemList in user thread + for ( i = 0; i < maximumNumberOfPeers; i++ ) + //for ( i = 0; i < remoteSystemListSize; i++ ) + { + if ( remoteSystemList[ i ].isActive==false ) + { + remoteSystem=remoteSystemList+i; + remoteSystem->rpcMap.Clear(); + remoteSystem->playerId = playerId; + remoteSystem->isActive=true; // This one line causes future incoming packets to go through the reliability layer + remoteSystem->reliabilityLayer.SetSplitMessageProgressInterval(splitMessageProgressInterval); + remoteSystem->reliabilityLayer.SetUnreliableTimeout(unreliableTimeout); + remoteSystem->reliabilityLayer.SetEncryptionKey( 0 ); + + for ( j = 0; j < (unsigned) PING_TIMES_ARRAY_SIZE; j++ ) + { + remoteSystem->pingAndClockDifferential[ j ].pingTime = 65535; + remoteSystem->pingAndClockDifferential[ j ].clockDifferential = 0; + } + + remoteSystem->connectMode=connectionMode; + remoteSystem->pingAndClockDifferentialWriteIndex = 0; + remoteSystem->lowestPing = 65535; + remoteSystem->nextPingTime = 0; // Ping immediately + remoteSystem->weInitiatedTheConnection = false; + remoteSystem->staticData.Reset(); + remoteSystem->connectionTime = time; + remoteSystem->myExternalPlayerId = UNASSIGNED_PLAYER_ID; + remoteSystem->setAESKey=false; + remoteSystem->lastReliableSend=time; + + // Reserve this reliability layer for ourselves. + remoteSystem->reliabilityLayer.Reset(true); + + /// Add this player to the lookup tree + PlayerIDAndIndex playerIDAndIndex; + playerIDAndIndex.playerId=playerId; + playerIDAndIndex.index=i; + remoteSystemLookup.Insert(playerId,playerIDAndIndex); + + return remoteSystem; + } + } + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Adjust the first four bytes (treated as unsigned int) of the pointer +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ShiftIncomingTimestamp( unsigned char *data, PlayerID playerId ) const +{ +#ifdef _DEBUG + assert( IsActive() ); + assert( data ); +#endif + + RakNet::BitStream timeBS( data, sizeof(RakNetTime), false); + RakNetTime encodedTimestamp; + timeBS.Read(encodedTimestamp); + + encodedTimestamp = encodedTimestamp - GetBestClockDifferential( playerId ); + timeBS.SetWriteOffset(0); + timeBS.Write(encodedTimestamp); +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Thanks to Chris Taylor (cat02e@fsu.edu) for the improved timestamping algorithm +RakNetTime RakPeer::GetBestClockDifferential( const PlayerID playerId ) const +{ + int counter, lowestPingSoFar; + RakNetTime clockDifferential; + RemoteSystemStruct *remoteSystem = GetRemoteSystemFromPlayerID( playerId, true, true ); + + if ( remoteSystem == 0 ) + return 0; + + lowestPingSoFar = 65535; + + clockDifferential = 0; + + for ( counter = 0; counter < PING_TIMES_ARRAY_SIZE; counter++ ) + { + if ( remoteSystem->pingAndClockDifferential[ counter ].pingTime == 65535 ) + break; + + if ( remoteSystem->pingAndClockDifferential[ counter ].pingTime < lowestPingSoFar ) + { + clockDifferential = remoteSystem->pingAndClockDifferential[ counter ].clockDifferential; + lowestPingSoFar = remoteSystem->pingAndClockDifferential[ counter ].pingTime; + } + } + + return clockDifferential; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Description: +// Handles an RPC packet. If you get a packet with the ID ID_RPC you should pass it to this function +// This is already done in Multiplayer.cpp, so if you use the Multiplayer class it is handled for you. +// +// Parameters: +// packet - A packet returned from Receive with the ID ID_RPC +// +// Returns: +// true on success, false on a bad packet or an unregistered function +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +bool RakPeer::HandleRPCPacket( const char *data, int length, PlayerID playerId ) +{ + // RPC BitStream format is + // ID_RPC - unsigned char + // Unique identifier string length - unsigned char + // The unique ID - string with each letter in upper case, subtracted by 'A' and written in 5 bits. + // Number of bits of the data (int) + // The data + + RakNet::BitStream incomingBitStream( (unsigned char *) data, length, false ); + char uniqueIdentifier[ 256 ]; +// unsigned int bitLength; + unsigned char *userData; + //bool hasTimestamp; + bool nameIsEncoded, networkIDIsEncoded; + RPCIndex rpcIndex; + RPCNode *node; + RPCParameters rpcParms; + NetworkID networkID; + bool blockingCommand; + RakNet::BitStream replyToSender; + rpcParms.replyToSender=&replyToSender; + + rpcParms.recipient=this; + rpcParms.sender=playerId; + + // Note to self - if I change this format then I have to change the PacketLogger class too + incomingBitStream.IgnoreBits(8); + if (data[0]==ID_TIMESTAMP) + incomingBitStream.IgnoreBits(8*(sizeof(RakNetTime)+sizeof(unsigned char))); + if ( incomingBitStream.Read( nameIsEncoded ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + + if (nameIsEncoded) + { + if ( stringCompressor->DecodeString(uniqueIdentifier, 256, &incomingBitStream) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + + rpcIndex = rpcMap.GetIndexFromFunctionName(uniqueIdentifier); + } + else + { + if ( incomingBitStream.ReadCompressed( rpcIndex ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + } + if ( incomingBitStream.Read( blockingCommand ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + + /* + if ( incomingBitStream.Read( rpcParms.hasTimestamp ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + */ + + if ( incomingBitStream.ReadCompressed( rpcParms.numberOfBitsOfData ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + + if ( incomingBitStream.Read( networkIDIsEncoded ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + + if (networkIDIsEncoded) + { + if ( incomingBitStream.Read( networkID ) == false ) + { +#ifdef _DEBUG + assert( 0 ); // bitstream was not long enough. Some kind of internal error +#endif + return false; + } + } + + if (rpcIndex==UNDEFINED_RPC_INDEX) + { + // Unregistered function + RakAssert(0); + return false; + } + + node = rpcMap.GetNodeFromIndex(rpcIndex); + if (node==0) + { +#ifdef _DEBUG + assert( 0 ); // Should never happen except perhaps from threading errors? No harm in checking anyway +#endif + return false; + } + + // Make sure the call type matches - if this is a pointer to a class member then networkID must be defined. Otherwise it must not be defined + if (node->isPointerToMember==true && networkIDIsEncoded==false) + { + // If this hits then this pointer was registered as a class member function but the packet does not have an NetworkID. + // Most likely this means this system registered a function with REGISTER_CLASS_MEMBER_RPC and the remote system called it + // using the unique ID for a function registered with REGISTER_STATIC_RPC. + assert(0); + return false; + } + + if (node->isPointerToMember==false && networkIDIsEncoded==true) + { + // If this hits then this pointer was not registered as a class member function but the packet does have an NetworkID. + // Most likely this means this system registered a function with REGISTER_STATIC_RPC and the remote system called it + // using the unique ID for a function registered with REGISTER_CLASS_MEMBER_RPC. + assert(0); + return false; + } + + if (nameIsEncoded && GetRemoteSystemFromPlayerID(playerId, false, true)) + { + // Send ID_RPC_MAPPING to the sender so they know what index to use next time + RakNet::BitStream rpcMapBitStream; + rpcMapBitStream.Write((unsigned char)ID_RPC_MAPPING); + stringCompressor->EncodeString(node->uniqueIdentifier, 256, &rpcMapBitStream); + rpcMapBitStream.WriteCompressed(rpcIndex); + SendBuffered( (const char*)rpcMapBitStream.GetData(), rpcMapBitStream.GetNumberOfBitsUsed(), HIGH_PRIORITY, UNRELIABLE, 0, playerId, false, RemoteSystemStruct::NO_ACTION ); + } + + // Call the function + if ( rpcParms.numberOfBitsOfData == 0 ) + { + rpcParms.input=0; + if (networkIDIsEncoded) + { + void *object = NetworkIDGenerator::GET_OBJECT_FROM_ID(networkID); + if (object) + (node->memberFunctionPointer(object, &rpcParms)); + } + else + { + node->staticFunctionPointer( &rpcParms ); + } + } + else + { + if ( incomingBitStream.GetNumberOfUnreadBits() == 0 ) + { +#ifdef _DEBUG + assert( 0 ); +#endif + return false; // No data was appended! + } + + // We have to copy into a new data chunk because the user data might not be byte aligned. + bool usedAlloca=false; +#if !defined(_COMPATIBILITY_1) + if (BITS_TO_BYTES( incomingBitStream.GetNumberOfUnreadBits() ) < MAX_ALLOCA_STACK_ALLOCATION) + { + userData = ( unsigned char* ) alloca( BITS_TO_BYTES( incomingBitStream.GetNumberOfUnreadBits() ) ); + usedAlloca=true; + } + else +#endif + userData = new unsigned char[BITS_TO_BYTES(incomingBitStream.GetNumberOfUnreadBits())]; + + + // The false means read out the internal representation of the bitstream data rather than + // aligning it as we normally would with user data. This is so the end user can cast the data received + // into a bitstream for reading + if ( incomingBitStream.ReadBits( ( unsigned char* ) userData, rpcParms.numberOfBitsOfData, false ) == false ) + { +#ifdef _DEBUG + assert( 0 ); +#endif + #if defined(_COMPATIBILITY_1) + delete [] userData; + #endif + + return false; // Not enough data to read + } + +// if ( rpcParms.hasTimestamp ) +// ShiftIncomingTimestamp( userData, playerId ); + + // Call the function callback + rpcParms.input=userData; + if (networkIDIsEncoded) + { + void *object = NetworkIDGenerator::GET_OBJECT_FROM_ID(networkID); + if (object) + (node->memberFunctionPointer(object, &rpcParms)); + } + else + { + node->staticFunctionPointer( &rpcParms ); + } + + + if (usedAlloca==false) + delete [] userData; + } + + if (blockingCommand) + { + RakNet::BitStream reply; + reply.Write((unsigned char) ID_RPC_REPLY); + reply.Write((char*)replyToSender.GetData(), replyToSender.GetNumberOfBytesUsed()); + Send(&reply, HIGH_PRIORITY, RELIABLE, 0, playerId, false); + } + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/** +* Handles an RPC reply packet. This is data returned from an RPC call +* +* \param data A packet returned from Receive with the ID ID_RPC +* \param length The size of the packet data +* \param playerId The sender of the packet +* +* \return true on success, false on a bad packet or an unregistered function +*/ +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::HandleRPCReplyPacket( const char *data, int length, PlayerID playerId ) +{ + if (blockOnRPCReply) + { + if ((playerId==replyFromTargetPlayer && replyFromTargetBroadcast==false) || + (playerId!=replyFromTargetPlayer && replyFromTargetBroadcast==true)) + { + replyFromTargetBS->Write(data+1, length-1); + blockOnRPCReply=false; + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef __USE_IO_COMPLETION_PORTS +bool RakPeer::SetupIOCompletionPortSocket( int index ) +{ + SOCKET newSocket; + + if ( remoteSystemList[ index ].reliabilityLayer.GetSocket() != INVALID_SOCKET ) + closesocket( remoteSystemList[ index ].reliabilityLayer.GetSocket() ); + + newSocket = SocketLayer::Instance()->CreateBoundSocket( myPlayerId.port + index + 1, false ); + + SocketLayer::Instance()->Connect( newSocket, remoteSystemList[ index ].playerId.binaryAddress, remoteSystemList[ index ].playerId.port ); // port is the port of the client + + remoteSystemList[ index ].reliabilityLayer.SetSocket( newSocket ); + + // Associate our new socket with a completion port and do the first read + return SocketLayer::Instance()->AssociateSocketWithCompletionPortAndRead( newSocket, remoteSystemList[ index ].playerId.binaryAddress, remoteSystemList[ index ].playerId.port, this ); +} + +#endif + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::GenerateSYNCookieRandomNumber( void ) +{ +#if !defined(_COMPATIBILITY_1) + unsigned int number; + int i; + memcpy( oldRandomNumber, newRandomNumber, sizeof( newRandomNumber ) ); + + for ( i = 0; i < (int) sizeof( newRandomNumber ); i += (int) sizeof( number ) ) + { + number = randomMT(); + memcpy( newRandomNumber + i, ( char* ) & number, sizeof( number ) ); + } + + randomNumberExpirationTime = RakNet::GetTime() + SYN_COOKIE_OLD_RANDOM_NUMBER_DURATION; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SecuredConnectionResponse( const PlayerID playerId ) +{ +#if !defined(_COMPATIBILITY_1) + CSHA1 sha1; + RSA_BIT_SIZE n; + big::u32 e; + unsigned char connectionRequestResponse[ 1 + sizeof( big::u32 ) + sizeof( RSA_BIT_SIZE ) + 20 ]; + connectionRequestResponse[ 0 ] = ID_SECURED_CONNECTION_RESPONSE; + + if ( randomNumberExpirationTime < RakNet::GetTime() ) + GenerateSYNCookieRandomNumber(); + + // Hash the SYN-Cookie + // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) + sha1.Reset(); + sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); + sha1.Update( ( unsigned char* ) & ( newRandomNumber ), 20 ); + sha1.Final(); + + // Write the cookie + memcpy( connectionRequestResponse + 1, sha1.GetHash(), 20 ); + + // Write the public keys + rsacrypt.getPublicKey( e, n ); +#ifdef HOST_ENDIAN_IS_BIG + // Mangle the keys on a Big-endian machine before sending + BSWAPCPY( (unsigned char *)(connectionRequestResponse + 1 + 20), + (unsigned char *)&e, sizeof( big::u32 ) ); + BSWAPCPY( (unsigned char *)(connectionRequestResponse + 1 + 20 + sizeof( big::u32 ) ), + (unsigned char *)n, sizeof( RSA_BIT_SIZE ) ); +#else + memcpy( connectionRequestResponse + 1 + 20, ( char* ) & e, sizeof( big::u32 ) ); + memcpy( connectionRequestResponse + 1 + 20 + sizeof( big::u32 ), n, sizeof( RSA_BIT_SIZE ) ); +#endif + + // s2c public key, syn-cookie + //SocketLayer::Instance()->SendTo( connectionSocket, ( char* ) connectionRequestResponse, 1 + sizeof( big::u32 ) + sizeof( RSA_BIT_SIZE ) + 20, playerId.binaryAddress, playerId.port ); + // All secure connection requests are unreliable because the entire process needs to be restarted if any part fails. + // Connection requests are resent periodically + SendImmediate(( char* ) connectionRequestResponse, (1 + sizeof( big::u32 ) + sizeof( RSA_BIT_SIZE ) + 20) *8, SYSTEM_PRIORITY, UNRELIABLE, 0, playerId, false, false, RakNet::GetTime()); +#endif +} + +void RakPeer::SecuredConnectionConfirmation( RakPeer::RemoteSystemStruct * remoteSystem, char* data ) +{ +#if !defined(_COMPATIBILITY_1) + int i, j; + unsigned char randomNumber[ 20 ]; + unsigned int number; + //bool doSend; + Packet *packet; + big::u32 e; + RSA_BIT_SIZE n, message, encryptedMessage; + big::RSACrypt privKeyPncrypt; + + // Make sure that we still want to connect + if (remoteSystem->connectMode!=RemoteSystemStruct::REQUESTED_CONNECTION) + return; + + // Copy out e and n +#ifdef HOST_ENDIAN_IS_BIG + BSWAPCPY( (unsigned char *)&e, (unsigned char *)(data + 1 + 20), sizeof( big::u32 ) ); + BSWAPCPY( (unsigned char *)n, (unsigned char *)(data + 1 + 20 + sizeof( big::u32 )), sizeof( RSA_BIT_SIZE ) ); +#else + memcpy( ( char* ) & e, data + 1 + 20, sizeof( big::u32 ) ); + memcpy( n, data + 1 + 20 + sizeof( big::u32 ), sizeof( RSA_BIT_SIZE ) ); +#endif + + // If we preset a size and it doesn't match, or the keys do not match, then tell the user + if ( usingSecurity == true && keysLocallyGenerated == false ) + { + if ( memcmp( ( char* ) & e, ( char* ) & publicKeyE, sizeof( big::u32 ) ) != 0 || + memcmp( n, publicKeyN, sizeof( RSA_BIT_SIZE ) ) != 0 ) + { + packet=AllocPacket(1); + packet->data[ 0 ] = ID_RSA_PUBLIC_KEY_MISMATCH; + packet->bitSize = sizeof( char ) * 8; + packet->playerId = remoteSystem->playerId; + packet->playerIndex = ( PlayerIndex ) GetIndexFromPlayerID( packet->playerId, true ); + AddPacketToProducer(packet); + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY; + return; + } + } + + // Create a random number + for ( i = 0; i < (int) sizeof( randomNumber ); i += (int) sizeof( number ) ) + { + number = randomMT(); + memcpy( randomNumber + i, ( char* ) & number, sizeof( number ) ); + } + + memset( message, 0, sizeof( message ) ); + assert( sizeof( message ) >= sizeof( randomNumber ) ); + +#ifdef HOST_ENDIAN_IS_BIG + // Scramble the plaintext message + BSWAPCPY( (unsigned char *)message, randomNumber, sizeof(randomNumber) ); +#else + memcpy( message, randomNumber, sizeof( randomNumber ) ); +#endif + privKeyPncrypt.setPublicKey( e, n ); + privKeyPncrypt.encrypt( message, encryptedMessage ); +#ifdef HOST_ENDIAN_IS_BIG + // A big-endian machine needs to scramble the byte order of an outgoing (encrypted) message + BSWAPSELF( (unsigned char *)encryptedMessage, sizeof( RSA_BIT_SIZE ) ); +#endif + + /* + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Lock(); + for ( i = 0; i < ( int ) requestedConnectionsList.Size(); i++ ) + { + if ( requestedConnectionsList[ i ]->playerId == playerId ) + { + doSend = true; + // Generate the AES key + + for ( j = 0; j < 16; j++ ) + requestedConnectionsList[ i ]->AESKey[ j ] = data[ 1 + j ] ^ randomNumber[ j ]; + + requestedConnectionsList[ i ]->setAESKey = true; + + break; + } + } + rakPeerMutexes[ RakPeer::requestedConnections_MUTEX ].Unlock(); + */ + + // Take the remote system's AESKey and XOR with our random number. + for ( j = 0; j < 16; j++ ) + remoteSystem->AESKey[ j ] = data[ 1 + j ] ^ randomNumber[ j ]; + remoteSystem->setAESKey = true; + +// if ( doSend ) +// { + char reply[ 1 + 20 + sizeof( RSA_BIT_SIZE ) ]; + // c2s RSA(random number), same syn-cookie + reply[ 0 ] = ID_SECURED_CONNECTION_CONFIRMATION; + memcpy( reply + 1, data + 1, 20 ); // Copy the syn-cookie + memcpy( reply + 1 + 20, encryptedMessage, sizeof( RSA_BIT_SIZE ) ); // Copy the encoded random number + + //SocketLayer::Instance()->SendTo( connectionSocket, reply, 1 + 20 + sizeof( RSA_BIT_SIZE ), playerId.binaryAddress, playerId.port ); + // All secure connection requests are unreliable because the entire process needs to be restarted if any part fails. + // Connection requests are resent periodically + SendImmediate((char*)reply, (1 + 20 + sizeof( RSA_BIT_SIZE )) * 8, SYSTEM_PRIORITY, UNRELIABLE, 0, remoteSystem->playerId, false, false, RakNet::GetTime()); +// } + +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::AllowIncomingConnections(void) const +{ + return GetNumberOfRemoteInitiatedConnections() < GetMaximumIncomingConnections(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendStaticDataInternal( const PlayerID target, bool performImmediate ) +{ + RakNet::BitStream reply( sizeof(unsigned char) + localStaticData.GetNumberOfBytesUsed() ); + reply.Write( (unsigned char) ID_RECEIVED_STATIC_DATA ); + reply.Write( (char*)localStaticData.GetData(), localStaticData.GetNumberOfBytesUsed() ); + + if (performImmediate) + { + if ( target == UNASSIGNED_PLAYER_ID ) + SendImmediate( (char*)reply.GetData(), reply.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, target, true, false, RakNet::GetTime() ); + else + SendImmediate( (char*)reply.GetData(), reply.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, target, false, false, RakNet::GetTime() ); + } + else + { + if ( target == UNASSIGNED_PLAYER_ID ) + Send( &reply, SYSTEM_PRIORITY, RELIABLE, 0, target, true ); + else + Send( &reply, SYSTEM_PRIORITY, RELIABLE, 0, target, false ); + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::PingInternal( const PlayerID target, bool performImmediate ) +{ + if ( IsActive() == false ) + return ; + + RakNet::BitStream bitStream(sizeof(unsigned char)+sizeof(RakNetTime)); + bitStream.Write((unsigned char)ID_INTERNAL_PING); + RakNetTimeNS currentTimeNS = RakNet::GetTimeNS(); + RakNetTime currentTime = RakNet::GetTime(); + bitStream.Write(currentTime); + if (performImmediate) + SendImmediate( (char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, UNRELIABLE, 0, target, false, false, currentTimeNS ); + else + Send( &bitStream, SYSTEM_PRIORITY, UNRELIABLE, 0, target, false ); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::CloseConnectionInternal( const PlayerID target, bool sendDisconnectionNotification, bool performImmediate, unsigned char orderingChannel ) +{ + unsigned i,j; + +#ifdef _DEBUG + assert(orderingChannel >=0 && orderingChannel < 32); +#endif + + if (target==UNASSIGNED_PLAYER_ID) + return; + + if ( remoteSystemList == 0 || endThreads == true ) + return; + + if (sendDisconnectionNotification) + { + NotifyAndFlagForDisconnect(target, performImmediate, orderingChannel); + } + else + { + if (performImmediate) + { + i = 0; + // remoteSystemList in user thread + for ( ; i < maximumNumberOfPeers; i++ ) + //for ( ; i < remoteSystemListSize; i++ ) + { + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].playerId == target ) + { + // Found the index to stop + remoteSystemList[ i ].isActive=false; + + // Reserve this reliability layer for ourselves + //remoteSystemList[ i ].playerId = UNASSIGNED_PLAYER_ID; + + for (j=0; j < messageHandlerList.Size(); j++) + { + messageHandlerList[j]->OnCloseConnection(this, target); + } + + // Clear any remaining messages + remoteSystemList[ i ].reliabilityLayer.Reset(false); + + // Remove from the lookup list + remoteSystemLookup.Remove(target); + + break; + } + } + } + else + { + BufferedCommandStruct *bcs; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Lock(); +#endif + bcs=bufferedCommands.WriteLock(); + bcs->command=BufferedCommandStruct::BCS_CLOSE_CONNECTION; + bcs->playerId=target; + bcs->data=0; + bcs->orderingChannel=orderingChannel; + bufferedCommands.WriteUnlock(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Unlock(); +#endif + } + } +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::ValidSendTarget(PlayerID playerId, bool broadcast) +{ + unsigned remoteSystemIndex; + + // remoteSystemList in user thread. This is slow so only do it in debug + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + //for ( remoteSystemIndex = 0; remoteSystemIndex < remoteSystemListSize; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].isActive && + remoteSystemList[ remoteSystemIndex ].connectMode==RakPeer::RemoteSystemStruct::CONNECTED && // Not fully connected players are not valid user-send targets because the reliability layer wasn't reset yet + ( ( broadcast == false && remoteSystemList[ remoteSystemIndex ].playerId == playerId ) || + ( broadcast == true && remoteSystemList[ remoteSystemIndex ].playerId != playerId ) ) + ) + return true; + } + + return false; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::SendBuffered( const char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode ) +{ +#ifdef _DEBUG + assert(orderingChannel >=0 && orderingChannel < 32); +#endif + + + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Lock(); +#endif + bcs=bufferedCommands.WriteLock(); + bcs->data = new char[BITS_TO_BYTES(numberOfBitsToSend)]; // Making a copy doesn't lose efficiency because I tell the reliability layer to use this allocation for its own copy +#ifdef _DEBUG + assert(bcs->data); +#endif + memcpy(bcs->data, data, BITS_TO_BYTES(numberOfBitsToSend)); + bcs->numberOfBitsToSend=numberOfBitsToSend; + bcs->priority=priority; + bcs->reliability=reliability; + bcs->orderingChannel=orderingChannel; + bcs->playerId=playerId; + bcs->broadcast=broadcast; + bcs->connectionMode=connectionMode; + bcs->command=BufferedCommandStruct::BCS_SEND; + bufferedCommands.WriteUnlock(); + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Unlock(); +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::SendImmediate( char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool useCallerDataAllocation, RakNetTimeNS currentTime ) +{ + unsigned *sendList; + unsigned sendListSize; + bool callerDataAllocationUsed; + unsigned remoteSystemIndex, sendListIndex; // Iterates into the list of remote systems + unsigned numberOfBytesUsed = BITS_TO_BYTES(numberOfBitsToSend); + callerDataAllocationUsed=false; + + sendListSize=0; + + // 03/06/06 - If broadcast is false, use the optimized version of GetIndexFromPlayerID + if (broadcast==false) + { +#if !defined(_COMPATIBILITY_1) + sendList=(unsigned *)alloca(sizeof(unsigned)); +#else + sendList = new unsigned[1]; +#endif + remoteSystemIndex=GetIndexFromPlayerID( playerId, true ); + if (remoteSystemIndex!=(unsigned)-1 && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY && + remoteSystemList[remoteSystemIndex].connectMode!=RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + sendList[0]=remoteSystemIndex; + sendListSize=1; + } + } + else + { +#if !defined(_COMPATIBILITY_1) + //sendList=(unsigned *)alloca(sizeof(unsigned)*remoteSystemListSize); + sendList=(unsigned *)alloca(sizeof(unsigned)*maximumNumberOfPeers); +#else + //sendList = new unsigned[remoteSystemListSize]; + sendList = new unsigned[maximumNumberOfPeers]; +#endif + + // remoteSystemList in network thread + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + //for ( remoteSystemIndex = 0; remoteSystemIndex < remoteSystemListSize; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].isActive && remoteSystemList[ remoteSystemIndex ].playerId != playerId && remoteSystemList[ remoteSystemIndex ].playerId != UNASSIGNED_PLAYER_ID ) + sendList[sendListSize++]=remoteSystemIndex; + } + } + + if (sendListSize==0) + { +#if defined(_COMPATIBILITY_1) + delete [] sendList; +#endif + return false; + } + + for (sendListIndex=0; sendListIndex < sendListSize; sendListIndex++) + { + if ( trackFrequencyTable ) + { + unsigned i; + // Store output frequency + for (i=0 ; i < numberOfBytesUsed; i++ ) + frequencyTable[ (unsigned char)(data[i]) ]++; + rawBytesSent += numberOfBytesUsed; + } + + if ( outputTree ) + { + RakNet::BitStream bitStreamCopy( numberOfBytesUsed ); + outputTree->EncodeArray( (unsigned char*) data, numberOfBytesUsed, &bitStreamCopy ); + compressedBytesSent += bitStreamCopy.GetNumberOfBytesUsed(); + remoteSystemList[sendList[sendListIndex]].reliabilityLayer.Send( (char*) bitStreamCopy.GetData(), bitStreamCopy.GetNumberOfBitsUsed(), priority, reliability, orderingChannel, true, MTUSize, currentTime ); + } + else + { + // Send may split the packet and thus deallocate data. Don't assume data is valid if we use the callerAllocationData + bool useData = useCallerDataAllocation && callerDataAllocationUsed==false && sendListIndex+1==sendListSize; + remoteSystemList[sendList[sendListIndex]].reliabilityLayer.Send( data, numberOfBitsToSend, priority, reliability, orderingChannel, useData==false, MTUSize, currentTime ); + if (useData) + callerDataAllocationUsed=true; + } + + if (reliability==RELIABLE || reliability==RELIABLE_ORDERED || reliability==RELIABLE_SEQUENCED) + remoteSystemList[sendList[sendListIndex]].lastReliableSend=(RakNetTime)(currentTime/(RakNetTimeNS)1000); + } + +#if defined(_COMPATIBILITY_1) + delete [] sendList; +#endif + + // Return value only meaningful if true was passed for useCallerDataAllocation. Means the reliability layer used that data copy, so the caller should not deallocate it + return callerDataAllocationUsed; +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/* +#ifdef _MSC_VER +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +bool RakPeer::HandleBufferedRPC(BufferedCommandStruct *bcs, RakNetTime time) +{ + unsigned *sendList; + bool callerAllocationDataUsed; + unsigned sendListSize; + + // All this code modifies bcs->data and bcs->numberOfBitsToSend in order to transform an RPC request into an actual packet for 4 + RPCIndex rpcIndex; // Index into the list of RPC calls so we know what number to encode in the packet + char uniqueID[256], *userData; // RPC ID (the name of it) and a pointer to the data sent by the user + int extraBuffer; // How many data bytes were allocated to hold the RPC header + unsigned remoteSystemIndex, sendListIndex; // Iterates into the list of remote systems + int dataBlockAllocationLength; // Total number of bytes to allocate for the packet + char *writeTarget; // Used to hold either a block of allocated data or the externally allocated data + + strcpy(uniqueID, bcs->data); // Copy out the string because it is at the front of the data block and will be overwritten + extraBuffer=2+(int)strlen(uniqueID)*2+3; // Exact code copied earlier in this file. Keep these two in synch! + userData=bcs->data+extraBuffer; + dataBlockAllocationLength=BITS_TO_BYTES(bcs->numberOfBitsToSend)+extraBuffer; + + sendListSize=0; + + // 03/06/06 - If broadcast is false, use the optimized version of GetIndexFromPlayerID + if (bcs->broadcast==false) + { +#if !defined(_COMPATIBILITY_1) + sendList=(unsigned *)alloca(sizeof(unsigned)); +#else + sendList = new unsigned[1]; +#endif + remoteSystemIndex=GetIndexFromPlayerID( bcs->playerId, true ); + if (remoteSystemIndex!=(unsigned)-1) + { + sendList[0]=remoteSystemIndex; + sendListSize=1; + } + } + else + { +#if !defined(_COMPATIBILITY_1) + sendList=(unsigned *)alloca(sizeof(unsigned)*maximumNumberOfPeers); +#else + sendList = new unsigned[maximumNumberOfPeers]; +#endif + + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; remoteSystemIndex++ ) + { + if ( remoteSystemList[ remoteSystemIndex ].playerId != UNASSIGNED_PLAYER_ID && remoteSystemList[ remoteSystemIndex ].playerId != bcs->playerId ) + sendList[sendListSize++]=remoteSystemIndex; + } + } + + if (sendListSize==0) + { + #if defined(_COMPATIBILITY_1) + delete [] sendList; + #endif + + return false; + } + + // remoteSystemList in network thread + for (sendListIndex=0; sendListIndex < (unsigned)sendListSize; sendListIndex++) + { + if (sendListIndex+1==sendListSize) + writeTarget=bcs->data; // Write to the externally allocated buffer. This destroys the buffer format so we do it only once for the last call + else + writeTarget=new char [dataBlockAllocationLength]; // Create a new buffer + + // Last send so use the buffer that was allocated externally + RakNet::BitStream outgoingBitStream((unsigned char *) writeTarget, dataBlockAllocationLength, false ); + outgoingBitStream.ResetWritePointer(); // Let us write at the start of the data block, rather than at the end + + outgoingBitStream.Write( (unsigned char) ID_RPC ); + rpcIndex=remoteSystemList[sendList[sendListIndex]].rpcMap.GetIndexFromFunctionName(uniqueID); // Lots of trouble but we can only use remoteSystem->[whatever] in this thread so that is why this command was buffered + if (rpcIndex!=UNDEFINED_RPC_INDEX) + { + // We have an RPC name to an index mapping, so write the index + outgoingBitStream.Write(false); + outgoingBitStream.WriteCompressed(rpcIndex); + } + else + { + // No mapping, so write the encoded RPC name + outgoingBitStream.Write(true); + stringCompressor->EncodeString(uniqueID, 256, &outgoingBitStream); + } + outgoingBitStream.Write(bcs->blockingCommand); + outgoingBitStream.Write((bool)(bcs->command==BufferedCommandStruct::BCS_RPC_SHIFT)); // True or false to shift the timestamp + outgoingBitStream.WriteCompressed( bcs->numberOfBitsToSend ); + if (bcs->networkID==UNASSIGNED_NETWORK_ID) + { + // No object ID + outgoingBitStream.Write(false); + } + else + { + // Encode an object ID. This will use pointer to class member RPC + outgoingBitStream.Write(true); + outgoingBitStream.Write(bcs->networkID); + } + + + if ( bcs->numberOfBitsToSend > 0 ) + outgoingBitStream.WriteBits( (const unsigned char*) userData, bcs->numberOfBitsToSend, false ); // Last param is false to write the raw data originally from another bitstream, rather than shifting from user data + else + outgoingBitStream.WriteCompressed( ( int ) 0 ); + + callerAllocationDataUsed=SendImmediate((char*)outgoingBitStream.GetData(), outgoingBitStream.GetNumberOfBitsUsed(), bcs->priority, bcs->reliability, bcs->orderingChannel, remoteSystemList[sendList[sendListIndex]].playerId, false, true, time); + } + +#if defined(_COMPATIBILITY_1) + delete [] sendList; +#endif + + return callerAllocationDataUsed; +} +*/ +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearBufferedCommands(void) +{ + BufferedCommandStruct *bcs; + +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Lock(); +#endif + while ((bcs=bufferedCommands.ReadLock())!=0) + { + if (bcs->data) + delete [] bcs->data; + + bufferedCommands.ReadUnlock(); + } + bufferedCommands.Clear(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[bufferedCommands_Mutex].Unlock(); +#endif +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +void RakPeer::ClearRequestedConnectionList(void) +{ + RequestedConnectionStruct *bcs; +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Lock(); +#endif + while ((bcs=requestedConnectionList.ReadLock())!=0) + { + if (bcs->data) + delete [] bcs->data; + + requestedConnectionList.ReadUnlock(); + } + requestedConnectionList.Clear(); +#ifdef _RAKNET_THREADSAFE + rakPeerMutexes[requestedConnectionList_Mutex].Unlock(); +#endif +} +inline void RakPeer::AddPacketToProducer(Packet *p) +{ + Packet **packetPtr=packetSingleProducerConsumer.WriteLock(); + *packetPtr=p; + packetSingleProducerConsumer.WriteUnlock(); +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +/* +#ifdef _WIN32 +unsigned __stdcall RecvFromNetworkLoop(LPVOID arguments) +#else +void* RecvFromNetworkLoop( void* arguments ) +#endif +{ +RakPeer *peer = (RakPeer *)arguments; +unsigned int errorCode; + +#ifdef __USE_IO_COMPLETION_PORTS +AsynchronousFileIO::Instance()->IncreaseUserCount(); +#endif + +peer->isRecvfromThreadActive=true; + +while(peer->endThreads==false) +{ +peer->isSocketLayerBlocking=true; +errorCode=SocketLayer::Instance()->RecvFrom(peer->connectionSocket, peer); +peer->isSocketLayerBlocking=false; + +#ifdef _WIN32 +if (errorCode==WSAECONNRESET) +{ +peer->PushPortRefused(UNASSIGNED_PLAYER_ID); +//closesocket(peer->connectionSocket); +//peer->connectionSocket = SocketLayer::Instance()->CreateBoundSocket(peer->myPlayerId.port, true); +} +else if (errorCode!=0 && peer->endThreads==false) +{ +#ifdef _DEBUG +printf("Server RecvFrom critical failure!\n"); +#endif +// Some kind of critical error +peer->isRecvfromThreadActive=false; +peer->endThreads=true; +peer->Disconnect(); +break; +} +#else +if (errorCode==-1) +{ +peer->isRecvfromThreadActive=false; +peer->endThreads=true; +peer->Disconnect(); +break; +} +#endif +} + +#ifdef __USE_IO_COMPLETION_PORTS +AsynchronousFileIO::Instance()->DecreaseUserCount(); +#endif + +peer->isRecvfromThreadActive=false; + +#ifdef _WIN32 +//_endthreadex( 0 ); +#endif +return 0; +} +*/ +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +#ifdef _WIN32 +void __stdcall ProcessPortUnreachable( unsigned int binaryAddress, unsigned short port, RakPeer *rakPeer ) +#else +void ProcessPortUnreachable( unsigned int binaryAddress, unsigned short port, RakPeer *rakPeer ) +#endif +{ + +} +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _WIN32 +void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ) +#else +void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ) +#endif +{ + Packet *packet; + PlayerID playerId; + unsigned i; + RakPeer::RemoteSystemStruct *remoteSystem; + playerId.binaryAddress = binaryAddress; + playerId.port = port; + +#if !defined(_COMPATIBILITY_1) + if (rakPeer->IsBanned( rakPeer->PlayerIDToDottedIP( playerId ) )) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + char c[2]; + c[0] = ID_CONNECTION_BANNED; + c[1] = 0; // Pad, some routers apparently block 1 byte packets + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, playerId); + SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, (char*)&c, 2, playerId.binaryAddress, playerId.port ); + + return; + } +#endif + + // We didn't check this datagram to see if it came from a connected system or not yet. + // Therefore, this datagram must be under 17 bits - otherwise it may be normal network traffic as the min size for a raknet send is 17 bits + if ((unsigned char)(data)[0] == (unsigned char) ID_OPEN_CONNECTION_REPLY && length <= sizeof(unsigned char)*2) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + // Verify that we were waiting for this + // bool acceptOpenConnection; +// int actionToTake=0; +// char data[MAX_OFFLINE_DATA_LENGTH]; + RakPeer::RequestedConnectionStruct *rcsFirst, *rcs; + rcsFirst = rakPeer->requestedConnectionList.ReadLock(); + rcs=rcsFirst; + // acceptOpenConnection=false; + while (rcs) + { + // Scan through the requested connection queue and process any elements whose playerId matches the player we just got this packet from. + // If it is the first element in the queue, remove it from the queue. Otherwise, set the playerId to UNASSIGNED_PLAYER_ID to cancel it out of order. + if (rcs->playerId==playerId) + { + // Go ahead and process this request + // acceptOpenConnection=true; + + // Store the action (may be multiple actions to take at once) + // actionToTake|=(int)rcs->actionToTake; + assert(rcs->actionToTake==RakPeer::RequestedConnectionStruct::CONNECT); + + // You might get this when already connected because of cross-connections + remoteSystem=rakPeer->GetRemoteSystemFromPlayerID( playerId, true, true ); + if (remoteSystem==0) + remoteSystem=rakPeer->AssignPlayerIDToRemoteSystemList(playerId, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER); + + if (remoteSystem) + { + RakNetTimeNS time = RakNet::GetTimeNS(); + remoteSystem->connectMode=RakPeer::RemoteSystemStruct::REQUESTED_CONNECTION; + remoteSystem->weInitiatedTheConnection=true; + + RakNet::BitStream temp; + + temp.Write( (unsigned char) ID_CONNECTION_REQUEST ); + if ( rcs->outgoingPasswordLength > 0 ) + temp.Write( ( char* ) rcs->outgoingPassword, rcs->outgoingPasswordLength ); + rakPeer->SendImmediate((char*)temp.GetData(), temp.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, playerId, false, false, time ); + } + + if (rcs==rcsFirst) + { + // Delete the head of the queue + rakPeer->requestedConnectionList.ReadUnlock(); + rcsFirst=rakPeer->requestedConnectionList.ReadLock(); + rcs=rcsFirst; + continue; + } + else + { + // Cancel this out of order element of the queue - we are handling it now. + rcs->playerId=UNASSIGNED_PLAYER_ID; + } + } + + rcs=rakPeer->requestedConnectionList.ReadLock(); + } + + // Go back to the current head of the queue + if (rcsFirst) + rakPeer->requestedConnectionList.CancelReadLock(rcsFirst); + + /* + if (acceptOpenConnection) + { + // You might get this when already connected because of cross-connections + remoteSystem=rakPeer->GetRemoteSystemFromPlayerID( playerId, true ); + if (remoteSystem==0) + { + remoteSystem=rakPeer->AssignPlayerIDToRemoteSystemList(playerId, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER); + } + if (remoteSystem) + { + RakNetTime time = RakNet::GetTime(); + if (actionToTake & RakPeer::RequestedConnectionStruct::CONNECT) + { + remoteSystem->connectMode=RakPeer::RemoteSystemStruct::REQUESTED_CONNECTION; + remoteSystem->weInitiatedTheConnection=true; + + RakNet::BitStream temp; + + temp.Write( (unsigned char) ID_CONNECTION_REQUEST ); + if ( rakPeer->outgoingPasswordLength > 0 ) + temp.Write( ( char* ) rakPeer->outgoingPassword, rakPeer->outgoingPasswordLength ); + rakPeer->SendImmediate((char*)temp.GetData(), temp.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, playerId, false, false, time ); + } + } + } + */ + + return; + } + // Connecting to a system we are already connected to. + else if ((unsigned char)(data)[0] == (unsigned char) ID_CONNECTION_ATTEMPT_FAILED && length <= sizeof(unsigned char)*2) + { + // Remove the connection attempt from the buffered commands + RakPeer::RequestedConnectionStruct *rcsFirst, *rcs; + rcsFirst = rakPeer->requestedConnectionList.ReadLock(); + rcs=rcsFirst; + bool connectionAttemptCancelled=false; + while (rcs) + { + if (rcs->actionToTake==RakPeer::RequestedConnectionStruct::CONNECT && rcs->playerId==playerId) + { + connectionAttemptCancelled=true; + if (rcs==rcsFirst) + { + rakPeer->requestedConnectionList.ReadUnlock(); + rcsFirst=rakPeer->requestedConnectionList.ReadLock(); + rcs=rcsFirst; + } + else + { + // Hole in the middle + rcs->playerId=UNASSIGNED_PLAYER_ID; + rcs=rakPeer->requestedConnectionList.ReadLock(); + } + + continue; + } + + rcs=rakPeer->requestedConnectionList.ReadLock(); + } + + if (rcsFirst) + rakPeer->requestedConnectionList.CancelReadLock(rcsFirst); + + if (connectionAttemptCancelled) + { + // Tell user of connection attempt failed + packet=AllocPacket(sizeof( char )); + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + packet->bitSize = ( sizeof( char ) * 8); + packet->playerId = playerId; + packet->playerIndex = 65535; + rakPeer->AddPacketToProducer(packet); + } + } + // We didn't check this datagram to see if it came from a connected system or not yet. + // Therefore, this datagram must be under 17 bits - otherwise it may be normal network traffic as the min size for a raknet send is 17 bits + else if ((unsigned char)(data)[0] == ID_OPEN_CONNECTION_REQUEST && length == sizeof(unsigned char)*2) + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + // If this guy is already connected and they initiated the connection, ignore the connection request + RakPeer::RemoteSystemStruct *rss = rakPeer->GetRemoteSystemFromPlayerID( playerId, true, true ); + if (rss==0 || rss->weInitiatedTheConnection==true) + { + // Assign new remote system + if (rss==0) + rss=rakPeer->AssignPlayerIDToRemoteSystemList(playerId, RakPeer::RemoteSystemStruct::UNVERIFIED_SENDER); + + unsigned char c[2]; + if (rss) // If this guy is already connected remote system will be 0 + c[0] = ID_OPEN_CONNECTION_REPLY; + else + c[0] = ID_NO_FREE_INCOMING_CONNECTIONS; + c[1] = 0; // Pad, some routers apparently block 1 byte packets + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, playerId); + SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, (char*)&c, 2, playerId.binaryAddress, playerId.port ); + + return; + } + else if (rss!=0) + { + // If this is an existing connection, and they are already fully connected (not in progress), reply with connection attempt failed + if (rss->connectMode==RakPeer::RemoteSystemStruct::CONNECTED || + rss->connectMode==RakPeer::RemoteSystemStruct::DISCONNECT_ASAP || + rss->connectMode==RakPeer::RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY) + { + char c[2]; + c[0] = ID_CONNECTION_ATTEMPT_FAILED; + c[1] = 0; // Pad, some routers apparently block 1 byte packets + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, playerId); + SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, (char*)&c, 2, playerId.binaryAddress, playerId.port ); + } + } + + } + + // See if this datagram came from a connected system + remoteSystem = rakPeer->GetRemoteSystemFromPlayerID( playerId, true, true ); + if ( remoteSystem ) + { + if (remoteSystem->connectMode==RakPeer::RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET && (length%16)==0) + remoteSystem->reliabilityLayer.SetEncryptionKey( remoteSystem->AESKey ); + + // Handle regular incoming data + // HandleSocketReceiveFromConnectedPlayer is only safe to be called from the same thread as Update, which is this thread + if ( remoteSystem->reliabilityLayer.HandleSocketReceiveFromConnectedPlayer( data, length, playerId, rakPeer->messageHandlerList, rakPeer->MTUSize ) == false ) + { + // These kinds of packets may have been duplicated and incorrectly determined to be + // cheat packets. Anything else really is a cheat packet + if ( !( + ( (unsigned char)data[0] == ID_OPEN_CONNECTION_REQUEST && length <= 2 ) || + ( (unsigned char)data[0] == ID_OPEN_CONNECTION_REPLY && length <= 2 ) || + ( (unsigned char)data[0] == ID_CONNECTION_ATTEMPT_FAILED && length <= 2 ) || + ( ((unsigned char)data[0] == ID_PING_OPEN_CONNECTIONS || (unsigned char)data[0] == ID_PING || (unsigned char)data[0] == ID_PONG) && length >= sizeof(unsigned char)+sizeof(RakNetTime) ) || + ( (unsigned char)data[0] == ID_ADVERTISE_SYSTEM && lengthdata[ 0 ] = ID_MODIFIED_PACKET; + packet->bitSize = sizeof( char ) * 8; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) rakPeer->GetIndexFromPlayerID( playerId, true ); + rakPeer->AddPacketToProducer(packet); + } + } + } + else + { + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketReceive(data, length*8, playerId); + + if (length > 512) + { +#if !defined(_COMPATIBILITY_1) + // Flood attack? Unknown systems should never send more than a small amount of data. Do a short ban + rakPeer->AddToBanList(rakPeer->PlayerIDToDottedIP(playerId), 10000); +#endif + return; + } + + // These are all messages from unconnected systems. Messages here can be any size, but are never processed from connected systems. + if ( ( (unsigned char) data[ 0 ] == ID_PING_OPEN_CONNECTIONS + || (unsigned char)(data)[0] == ID_PING) && length == sizeof(unsigned char)+sizeof(RakNetTime) ) + { + if ( (unsigned char)(data)[0] == ID_PING || + rakPeer->AllowIncomingConnections() ) // Open connections with players + { +#if !defined(_COMPATIBILITY_1) + RakNet::BitStream inBitStream( (unsigned char *) data, length, false ); + inBitStream.IgnoreBits(8); + RakNetTime sendPingTime; + inBitStream.Read(sendPingTime); + + RakNet::BitStream outBitStream; + outBitStream.Write((unsigned char)ID_PONG); // Should be named ID_UNCONNECTED_PONG eventually + outBitStream.Write(sendPingTime); + //tempBitStream.Write( data, UnconnectedPingStruct_Size ); + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Lock(); + // They are connected, so append offline ping data + outBitStream.Write( (char*)rakPeer->offlinePingResponse.GetData(), rakPeer->offlinePingResponse.GetNumberOfBytesUsed() ); + rakPeer->rakPeerMutexes[ RakPeer::offlinePingResponse_Mutex ].Unlock(); + //SocketLayer::Instance()->SendTo( connectionSocket, ( char* ) outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), playerId.binaryAddress, playerId.port ); + + unsigned i; + for (i=0; i < rakPeer->messageHandlerList.Size(); i++) + rakPeer->messageHandlerList[i]->OnDirectSocketSend((const char*)outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), playerId); + + SocketLayer::Instance()->SendTo( rakPeer->connectionSocket, (const char*)outBitStream.GetData(), outBitStream.GetNumberOfBytesUsed(), (char*)rakPeer->PlayerIDToDottedIP(playerId) , playerId.port ); +#endif + } + } + // UNCONNECTED MESSAGE Pong with no data. TODO - Problem - this matches a reliable send of other random data. + else if ((unsigned char) data[ 0 ] == ID_PONG && length >= sizeof(unsigned char)+sizeof(RakNetTime) && length < sizeof(unsigned char)+sizeof(RakNetTime)+MAX_OFFLINE_DATA_LENGTH) + { + packet=AllocPacket(length); + memcpy(packet->data, data, length); + packet->bitSize = length * 8; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) rakPeer->GetIndexFromPlayerID( playerId, true ); + rakPeer->AddPacketToProducer(packet); + } + else if ((unsigned char) data[ 0 ] == ID_ADVERTISE_SYSTEM && length >= 2 && length < MAX_OFFLINE_DATA_LENGTH+2) + { + packet=AllocPacket(length); + memcpy(packet->data, data, length); + packet->bitSize = length * 8; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) rakPeer->GetIndexFromPlayerID( playerId, true ); + rakPeer->AddPacketToProducer(packet); + } + } +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +bool RakPeer::RunUpdateCycle( void ) +{ + RakPeer::RemoteSystemStruct * remoteSystem; + unsigned remoteSystemIndex; + Packet *packet; + RakNetTime ping, lastPing; + // int currentSentBytes,currentReceivedBytes; +// unsigned numberOfBytesUsed; + unsigned numberOfBitsUsed; + //PlayerID authoritativeClientPlayerId; + int bitSize, byteSize; + unsigned char *data; + int errorCode; + int gotData; + RakNetTimeNS timeNS; + RakNetTime timeMS; + PlayerID playerId; + BufferedCommandStruct *bcs; + bool callerDataAllocationUsed; + RakNetStatisticsStruct *rnss; + + do + { + // Read a packet + gotData = SocketLayer::Instance()->RecvFrom( connectionSocket, this, &errorCode ); + + if ( gotData == SOCKET_ERROR ) + { +#ifdef _WIN32 + if ( errorCode == WSAECONNRESET ) + { + gotData=false; + // 11/14/05 - RecvFrom now calls HandlePortUnreachable rather than PushPortRefused + //PushPortRefused( UNASSIGNED_PLAYER_ID ); + //closesocket(peer->connectionSocket); + + //peer->connectionSocket = SocketLayer::Instance()->CreateBoundSocket(peer->myPlayerId.port, true); + } + else + if ( errorCode != 0 && endThreads == false ) + { +#ifdef _DO_PRINTF + printf( "Server RecvFrom critical failure!\n" ); +#endif + // Some kind of critical error + // peer->isRecvfromThreadActive=false; + endThreads = true; + Disconnect( 0, 0 ); + return false; + } + +#else + if ( errorCode == -1 ) + { + // isRecvfromThreadActive=false; + endThreads = true; + Disconnect( 0 ); + return false; + } +#endif + } + + if ( endThreads ) + return false; + } + while ( gotData>0 ); // Read until there is nothing left + + timeNS=0; + timeMS=0; + + // Process all the deferred user thread Send and connect calls + while ((bcs=bufferedCommands.ReadLock())!=0) + { + if (bcs->command==BufferedCommandStruct::BCS_SEND) + { + // GetTime is a very slow call so do it once and as late as possible + if (timeNS==0) + timeNS = RakNet::GetTimeNS(); + + callerDataAllocationUsed=SendImmediate((char*)bcs->data, bcs->numberOfBitsToSend, bcs->priority, bcs->reliability, bcs->orderingChannel, bcs->playerId, bcs->broadcast, true, timeNS); + if ( callerDataAllocationUsed==false ) + delete bcs->data; + + // Set the new connection state AFTER we call sendImmediate in case we are setting it to a disconnection state, which does not allow further sends + if (bcs->connectionMode!=RemoteSystemStruct::NO_ACTION && bcs->playerId!=UNASSIGNED_PLAYER_ID) + { + remoteSystem=GetRemoteSystemFromPlayerID( bcs->playerId, true, true ); + // if (remoteSystem==0) + // remoteSystem=AssignSystemAddressToRemoteSystemList(bcs->systemAddress, bcs->connectionMode); + if (remoteSystem) + remoteSystem->connectMode=bcs->connectionMode; + } + } + else + { +#ifdef _DEBUG + assert(bcs->command==BufferedCommandStruct::BCS_CLOSE_CONNECTION); +#endif + CloseConnectionInternal(bcs->playerId, false, true, bcs->orderingChannel); + } + +#ifdef _DEBUG + bcs->data=0; +#endif + + bufferedCommands.ReadUnlock(); + } + + // Process connection attempts + RequestedConnectionStruct *rcsFirst, *rcs; + bool condition1, condition2; + rcsFirst = requestedConnectionList.ReadLock(); + rcs=rcsFirst; + while (rcs) + { + if (timeNS==0) + { + timeNS = RakNet::GetTimeNS(); + timeMS = (RakNetTime)(timeNS/(RakNetTimeNS)1000); + } + + if (rcs->nextRequestTime < timeMS) + { + condition1=rcs->requestsMade==6; + condition2=(bool)((rcs->playerId==UNASSIGNED_PLAYER_ID)==1); + // If too many requests made or a hole then remove this if possible, otherwise invalidate it + if (condition1 || condition2) + { + if (rcs->data) + { + delete [] rcs->data; + rcs->data=0; + } + + if (condition1 && !condition2 && rcs->actionToTake==RequestedConnectionStruct::CONNECT) + { + // Tell user of connection attempt failed + packet=AllocPacket(sizeof( char )); + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + packet->bitSize = ( sizeof( char ) * 8); + packet->playerId = rcs->playerId; + packet->playerIndex = 65535; + AddPacketToProducer(packet); + } + + // Remove this if possible + if (rcs==rcsFirst) + { + requestedConnectionList.ReadUnlock(); + rcsFirst = requestedConnectionList.ReadLock(); + rcs=rcsFirst; + } + else + { + // Hole in the middle + rcs->playerId=UNASSIGNED_PLAYER_ID; + rcs=requestedConnectionList.ReadLock(); + } + + continue; + } + + rcs->requestsMade++; + rcs->nextRequestTime=timeMS+1000; + char c[2]; + c[0] = ID_OPEN_CONNECTION_REQUEST; + c[1] = 0; // Pad - apparently some routers block 1 byte packets + + unsigned i; + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnDirectSocketSend((char*)&c, 16, rcs->playerId); + SocketLayer::Instance()->SendTo( connectionSocket, (char*)&c, 2, rcs->playerId.binaryAddress, rcs->playerId.port ); + } + + rcs=requestedConnectionList.ReadLock(); + } + + if (rcsFirst) + requestedConnectionList.CancelReadLock(rcsFirst); + + + // remoteSystemList in network thread + for ( remoteSystemIndex = 0; remoteSystemIndex < maximumNumberOfPeers; ++remoteSystemIndex ) + //for ( remoteSystemIndex = 0; remoteSystemIndex < remoteSystemListSize; ++remoteSystemIndex ) + { + // I'm using playerId from remoteSystemList but am not locking it because this loop is called very frequently and it doesn't + // matter if we miss or do an extra update. The reliability layers themselves never care which player they are associated with + //playerId = remoteSystemList[ remoteSystemIndex ].playerId; + // Allow the playerID for this remote system list to change. We don't care if it changes now. + // remoteSystemList[ remoteSystemIndex ].allowPlayerIdAssigment=true; + if ( remoteSystemList[ remoteSystemIndex ].isActive ) + { + playerId = remoteSystemList[ remoteSystemIndex ].playerId; + RakAssert(playerId!=UNASSIGNED_PLAYER_ID); + + // Found an active remote system + remoteSystem = remoteSystemList + remoteSystemIndex; + // Update is only safe to call from the same thread that calls HandleSocketReceiveFromConnectedPlayer, + // which is this thread + + if (timeNS==0) + { + timeNS = RakNet::GetTimeNS(); + timeMS = (RakNetTime)(timeNS/(RakNetTimeNS)1000); + //printf("timeNS = %I64i timeMS=%i\n", timeNS, timeMS); + } + + + if (timeMS > remoteSystem->lastReliableSend && timeMS-remoteSystem->lastReliableSend > 5000 && remoteSystem->connectMode==RemoteSystemStruct::CONNECTED) + { + // If no reliable packets are waiting for an ack, do a one byte reliable send so that disconnections are noticed + rnss=remoteSystem->reliabilityLayer.GetStatistics(); + if (rnss->messagesOnResendQueue==0) + { + unsigned char keepAlive=ID_DETECT_LOST_CONNECTIONS; + SendImmediate((char*)&keepAlive,8,LOW_PRIORITY, RELIABLE, 0, remoteSystem->playerId, false, false, timeNS); + remoteSystem->lastReliableSend=timeMS+remoteSystem->reliabilityLayer.GetTimeoutTime(); + } + } + + remoteSystem->reliabilityLayer.Update( connectionSocket, playerId, MTUSize, timeNS, messageHandlerList ); // playerId only used for the internet simulator test + + // Check for failure conditions + if ( remoteSystem->reliabilityLayer.IsDeadConnection() || + ((remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP_SILENTLY) && remoteSystem->reliabilityLayer.IsDataWaiting()==false) || + (remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK && remoteSystem->reliabilityLayer.AreAcksWaiting()==false) || + (( + (remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || + remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystem->connectMode==RemoteSystemStruct::UNVERIFIED_SENDER || + remoteSystem->connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET) + && timeMS > remoteSystem->connectionTime && timeMS - remoteSystem->connectionTime > 10000)) + ) + { + // printf("timeMS=%i remoteSystem->connectionTime=%i\n", timeMS, remoteSystem->connectionTime ); + + // Failed. Inform the user? + if (remoteSystem->connectMode==RemoteSystemStruct::CONNECTED || remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION + || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ASAP || remoteSystem->connectMode==RemoteSystemStruct::DISCONNECT_ON_NO_ACK) + { + // Inform the user of the connection failure. + // unsigned staticDataBytes; + + // staticDataBytes=remoteSystem->staticData.GetNumberOfBytesUsed(); + // packet=AllocPacket(sizeof( char ) + staticDataBytes); + packet=AllocPacket(sizeof( char ) ); + if (remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION) + packet->data[ 0 ] = ID_CONNECTION_ATTEMPT_FAILED; // Attempted a connection and couldn't + else if (remoteSystem->connectMode==RemoteSystemStruct::CONNECTED) + packet->data[ 0 ] = ID_CONNECTION_LOST; // DeadConnection + else + packet->data[ 0 ] = ID_DISCONNECTION_NOTIFICATION; // DeadConnection + + //if (staticDataBytes) + // memcpy( packet->data + sizeof( char ), remoteSystem->staticData.GetData(), staticDataBytes ); + packet->bitSize = ( sizeof( char ) ) * 8; + //packet->bitSize = ( sizeof( char ) + staticDataBytes ) * 8; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + + AddPacketToProducer(packet); + } + // else connection shutting down, don't bother telling the user + +#ifdef _DO_PRINTF + printf("Connection dropped for player %i:%i\n", playerId.binaryAddress, playerId.port); +#endif + CloseConnectionInternal( playerId, false, true, 0 ); + continue; + } + + // Did the reliability layer detect a modified packet? + if ( remoteSystem->reliabilityLayer.IsCheater() ) + { + packet=AllocPacket(sizeof(char)); + packet->bitSize=8; + packet->data[ 0 ] = (unsigned char) ID_MODIFIED_PACKET; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + AddPacketToProducer(packet); + continue; + } + + // Ping this guy if it is time to do so + if ( remoteSystem->connectMode==RemoteSystemStruct::CONNECTED && timeMS > remoteSystem->nextPingTime && ( occasionalPing || remoteSystem->lowestPing == (unsigned short)-1 ) ) + { + remoteSystem->nextPingTime = timeMS + 5000; + PingInternal( playerId, true ); + } + + // Find whoever has the lowest player ID + //if (playerId < authoritativeClientPlayerId) + // authoritativeClientPlayerId=playerId; + + // Does the reliability layer have any packets waiting for us? + // To be thread safe, this has to be called in the same thread as HandleSocketReceiveFromConnectedPlayer + bitSize = remoteSystem->reliabilityLayer.Receive( &data ); + + while ( bitSize > 0 ) + { + // These types are for internal use and should never arrive from a network packet + if (data[0]==ID_CONNECTION_ATTEMPT_FAILED && data[0]==ID_MODIFIED_PACKET) + { + RakAssert(0); + continue; + } + + // Put the input through compression if necessary + if ( inputTree ) + { + RakNet::BitStream dataBitStream( MAXIMUM_MTU_SIZE ); + // Since we are decompressing input, we need to copy to a bitstream, decompress, then copy back to a probably + // larger data block. It's slow, but the user should have known that anyway + dataBitStream.Reset(); + dataBitStream.WriteAlignedBytes( ( unsigned char* ) data, BITS_TO_BYTES( bitSize ) ); + rawBytesReceived += dataBitStream.GetNumberOfBytesUsed(); + +// numberOfBytesUsed = dataBitStream.GetNumberOfBytesUsed(); + numberOfBitsUsed = dataBitStream.GetNumberOfBitsUsed(); + //rawBytesReceived += numberOfBytesUsed; + // Decompress the input data. + + if (numberOfBitsUsed>0) + { + unsigned char *dataCopy = new unsigned char[ dataBitStream.GetNumberOfBytesUsed() ]; + memcpy( dataCopy, dataBitStream.GetData(), dataBitStream.GetNumberOfBytesUsed() ); + dataBitStream.Reset(); + inputTree->DecodeArray( dataCopy, numberOfBitsUsed, &dataBitStream ); + compressedBytesReceived += dataBitStream.GetNumberOfBytesUsed(); + delete [] dataCopy; + + byteSize = dataBitStream.GetNumberOfBytesUsed(); + + if ( byteSize > BITS_TO_BYTES( bitSize ) ) // Probably the case - otherwise why decompress? + { + delete [] data; + data = new unsigned char [ byteSize ]; + } + memcpy( data, dataBitStream.GetData(), byteSize ); + } + else + byteSize=0; + } + else + // Fast and easy - just use the data that was returned + byteSize = BITS_TO_BYTES( bitSize ); + + // For unknown senders we only accept a few specific packets + if (remoteSystem->connectMode==RemoteSystemStruct::UNVERIFIED_SENDER) + { + if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST ) + { + ParseConnectionRequestPacket(remoteSystem, playerId, (const char*)data, byteSize); + delete [] data; + } + else + { + CloseConnectionInternal( playerId, false, true, 0 ); +#ifdef _DO_PRINTF + printf("Temporarily banning %i:%i for sending nonsense data\n", playerId.binaryAddress, playerId.port); +#endif + +#if !defined(_COMPATIBILITY_1) + AddToBanList(PlayerIDToDottedIP(playerId), remoteSystem->reliabilityLayer.GetTimeoutTime()); +#endif + delete [] data; + } + } + else + { + // However, if we are connected we still take a connection request in case both systems are trying to connect to each other + // at the same time + if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST ) + { + // 04/27/06 This is wrong. With cross connections, we can both have initiated the connection are in state REQUESTED_CONNECTION + // 04/28/06 Downgrading connections from connected will close the connection due to security at ((remoteSystem->connectMode!=RemoteSystemStruct::CONNECTED && time > remoteSystem->connectionTime && time - remoteSystem->connectionTime > 10000)) + if (remoteSystem->connectMode!=RemoteSystemStruct::CONNECTED) + ParseConnectionRequestPacket(remoteSystem, playerId, (const char*)data, byteSize); + delete [] data; + } + else if ( (unsigned char) data[ 0 ] == ID_NEW_INCOMING_CONNECTION && byteSize == sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned short) ) + { +#ifdef _DEBUG + // This assert can be ignored since it could hit from duplicate packets. + // It's just here for internal testing since it should only happen rarely and will mostly be from bugs +// assert(remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST); +#endif + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || + remoteSystem->connectMode==RemoteSystemStruct::SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET || + playerId==myPlayerId) // local system connect + { + remoteSystem->connectMode=RemoteSystemStruct::CONNECTED; + PingInternal( playerId, true ); + SendStaticDataInternal( playerId, true ); + + RakNet::BitStream inBitStream((unsigned char *) data, byteSize, false); + PlayerID bsPlayerId; + + inBitStream.IgnoreBits(8); + inBitStream.Read(bsPlayerId.binaryAddress); + inBitStream.Read(bsPlayerId.port); + + // Overwrite the data in the packet + // NewIncomingConnectionStruct newIncomingConnectionStruct; + // RakNet::BitStream nICS_BS( data, NewIncomingConnectionStruct_Size, false ); + // newIncomingConnectionStruct.Deserialize( nICS_BS ); + remoteSystem->myExternalPlayerId = bsPlayerId; + + // Send this info down to the game + + packet=AllocPacket(byteSize, data); + packet->bitSize = bitSize; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + AddPacketToProducer(packet); + } + else + delete [] data; + } + else if ( (unsigned char) data[ 0 ] == ID_CONNECTED_PONG && byteSize == sizeof(unsigned char)+sizeof(RakNetTime)*2 ) + { + RakNetTime sendPingTime, sendPongTime; + + // Copy into the ping times array the current time - the value returned + // First extract the sent ping + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + //PingStruct ps; + //ps.Deserialize(psBS); + inBitStream.IgnoreBits(8); + inBitStream.Read(sendPingTime); + inBitStream.Read(sendPongTime); + + timeNS = RakNet::GetTimeNS(); // Update the time value to be accurate + timeMS = (RakNetTime)(timeNS/(RakNetTimeNS)1000); + if (timeMS > sendPingTime) + ping = timeMS - sendPingTime; + else + ping=0; + lastPing = remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].pingTime; + + // Ignore super high spikes in the average + if ( lastPing <= 0 || ( ( ping < ( lastPing * 3 ) ) && ping < 1200 ) ) + { + remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].pingTime = ( unsigned short ) ping; + // Thanks to Chris Taylor (cat02e@fsu.edu) for the improved timestamping algorithm + remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].clockDifferential = sendPongTime - ( timeMS + sendPingTime ) / 2; + + if ( remoteSystem->lowestPing == (unsigned short)-1 || remoteSystem->lowestPing > (int) ping ) + remoteSystem->lowestPing = (unsigned short) ping; + + // Most packets should arrive by the ping time. + assert(ping < 10000); // Sanity check - could hit due to negative pings causing the var to overflow + remoteSystem->reliabilityLayer.SetPing( (unsigned short) ping ); + + if ( ++( remoteSystem->pingAndClockDifferentialWriteIndex ) == PING_TIMES_ARRAY_SIZE ) + remoteSystem->pingAndClockDifferentialWriteIndex = 0; + } + + delete [] data; + } + else if ( (unsigned char)data[0] == ID_INTERNAL_PING && byteSize == sizeof(unsigned char)+sizeof(RakNetTime) ) + { + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + inBitStream.IgnoreBits(8); + RakNetTime sendPingTime; + inBitStream.Read(sendPingTime); + + RakNet::BitStream outBitStream; + outBitStream.Write((unsigned char)ID_CONNECTED_PONG); + outBitStream.Write(sendPingTime); + timeMS = RakNet::GetTime(); + timeNS = RakNet::GetTimeNS(); + outBitStream.Write(timeMS); + SendImmediate( (char*)outBitStream.GetData(), outBitStream.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, UNRELIABLE, 0, playerId, false, false, timeMS ); + + delete [] data; + } + else if ( (unsigned char) data[ 0 ] == ID_DISCONNECTION_NOTIFICATION ) + { + /* + unsigned staticDataBytes=remoteSystem->staticData.GetNumberOfBytesUsed(); + + if ( staticDataBytes > 0 ) + { + packet=AllocPacket(sizeof( char ) + staticDataBytes); + packet->data[ 0 ] = ID_DISCONNECTION_NOTIFICATION; + memcpy( packet->data + sizeof( char ), remoteSystem->staticData.GetData(), staticDataBytes ); + packet->bitSize = sizeof( char ) * 8 + remoteSystem->staticData.GetNumberOfBitsUsed(); + delete [] data; + } + else + { + packet=AllocPacket(1, data); + packet->bitSize = 8; + } + */ + + +// packet->playerId = playerId; +// packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + + // We shouldn't close the connection immediately because we need to ack the ID_DISCONNECTION_NOTIFICATION + remoteSystem->connectMode=RemoteSystemStruct::DISCONNECT_ON_NO_ACK; + delete [] data; + + // AddPacketToProducer(packet); + } + else if ( (unsigned char) data[ 0 ] == ID_RPC_MAPPING ) + { + RakNet::BitStream inBitStream( (unsigned char *) data, byteSize, false ); + RPCIndex index; + char output[256]; + inBitStream.IgnoreBits(8); + stringCompressor->DecodeString(output, 255, &inBitStream); + inBitStream.ReadCompressed(index); + remoteSystem->rpcMap.AddIdentifierAtIndex((char*)output,index); + delete [] data; + } + else if ( (unsigned char) data[ 0 ] == ID_REQUEST_STATIC_DATA ) + { + SendStaticDataInternal( playerId, true ); + delete [] data; + } + else if ( (unsigned char) data[ 0 ] == ID_RECEIVED_STATIC_DATA ) + { + remoteSystem->staticData.Reset(); + remoteSystem->staticData.Write( ( char* ) data + sizeof(unsigned char), byteSize - 1 ); + + // Inform game server code that we got static data + packet=AllocPacket(byteSize, data); + packet->bitSize = bitSize; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + AddPacketToProducer(packet); + } +#if !defined(_COMPATIBILITY_1) + else if ( (unsigned char)(data)[0] == ID_SECURED_CONNECTION_RESPONSE && + byteSize == 1 + sizeof( big::u32 ) + sizeof( RSA_BIT_SIZE ) + 20 ) + { + SecuredConnectionConfirmation( remoteSystem, (char*)data ); + delete [] data; + } + else if ( (unsigned char)(data)[0] == ID_SECURED_CONNECTION_CONFIRMATION && + byteSize == 1 + 20 + sizeof( RSA_BIT_SIZE ) ) + { + CSHA1 sha1; + bool confirmedHash, newRandNumber; + + confirmedHash = false; + + // Hash the SYN-Cookie + // s2c syn-cookie = SHA1_HASH(source ip address + source port + random number) + sha1.Reset(); + sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); + sha1.Update( ( unsigned char* ) & ( newRandomNumber ), 20 ); + sha1.Final(); + + newRandNumber = false; + + // Confirm if + //syn-cookie ?= HASH(source ip address + source port + last random number) + //syn-cookie ?= HASH(source ip address + source port + current random number) + if ( memcmp( sha1.GetHash(), data + 1, 20 ) == 0 ) + { + confirmedHash = true; + } + else + { + sha1.Reset(); + sha1.Update( ( unsigned char* ) & playerId.binaryAddress, sizeof( playerId.binaryAddress ) ); + sha1.Update( ( unsigned char* ) & playerId.port, sizeof( playerId.port ) ); + sha1.Update( ( unsigned char* ) & ( oldRandomNumber ), 20 ); + sha1.Final(); + + if ( memcmp( sha1.GetHash(), data + 1, 20 ) == 0 ) + confirmedHash = true; + } + if ( confirmedHash ) + { + int i; + unsigned char AESKey[ 16 ]; + RSA_BIT_SIZE message, encryptedMessage; + + // On connection accept, AES key is c2s RSA_Decrypt(random number) XOR s2c syn-cookie + // Get the random number first + #ifdef HOST_ENDIAN_IS_BIG + BSWAPCPY( (unsigned char *) encryptedMessage, (unsigned char *)(data + 1 + 20), sizeof( RSA_BIT_SIZE ) ); + #else + memcpy( encryptedMessage, data + 1 + 20, sizeof( RSA_BIT_SIZE ) ); + #endif + rsacrypt.decrypt( encryptedMessage, message ); + #ifdef HOST_ENDIAN_IS_BIG + BSWAPSELF( (unsigned char *) message, sizeof( RSA_BIT_SIZE ) ); + #endif + + // Save the AES key + for ( i = 0; i < 16; i++ ) + AESKey[ i ] = data[ 1 + i ] ^ ( ( unsigned char* ) ( message ) ) [ i ]; + + // Connect this player assuming we have open slots + OnConnectionRequest( remoteSystem, AESKey, true ); + } + delete [] data; + } +#endif // #if !defined(_COMPATIBILITY_1) + else if ( (unsigned char)(data)[0] == ID_DETECT_LOST_CONNECTIONS && byteSize == sizeof(unsigned char) ) + { + // Do nothing + delete [] data; + } + else if ( (unsigned char)(data)[0] == ID_CONNECTION_REQUEST_ACCEPTED && byteSize == sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned short)+sizeof(PlayerIndex) ) + { + // Make sure this connection accept is from someone we wanted to connect to + bool allowConnection, alreadyConnected; + + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST || remoteSystem->connectMode==RemoteSystemStruct::REQUESTED_CONNECTION || allowConnectionResponseIPMigration) + allowConnection=true; + else + allowConnection=false; + if (remoteSystem->connectMode==RemoteSystemStruct::HANDLING_CONNECTION_REQUEST) + alreadyConnected=true; + else + alreadyConnected=false; + + if ( allowConnection ) + { + PlayerID externalID; + PlayerIndex playerIndex; + + RakNet::BitStream inBitStream((unsigned char *) data, byteSize, false); + inBitStream.IgnoreBits(8); // ID_CONNECTION_REQUEST_ACCEPTED + // inBitStream.Read(remotePort); + inBitStream.Read(externalID.binaryAddress); + inBitStream.Read(externalID.port); + inBitStream.Read(playerIndex); + + // Find a free remote system struct to use + // RakNet::BitStream casBitS(data, byteSize, false); + // ConnectionAcceptStruct cas; + // cas.Deserialize(casBitS); + // playerId.port = remotePort; + + // The remote system told us our external IP, so save it + remoteSystem->myExternalPlayerId = externalID; + remoteSystem->connectMode=RemoteSystemStruct::CONNECTED; + + + if (alreadyConnected==false) + { +#ifdef __USE_IO_COMPLETION_PORTS + bool b; + // Create a new nonblocking socket + remoteSystem->reliabilityLayer.SetSocket( SocketLayer::Instance()->CreateBoundSocket( myPlayerId.port, false ) ); + + SocketLayer::Instance()->Connect( remoteSystem->reliabilityLayer.GetSocket(), playerId.binaryAddress, playerId.port ); + // Associate our new socket with a completion port and do the first read + b = SocketLayer::Instance()->AssociateSocketWithCompletionPortAndRead( remoteSystem->reliabilityLayer.GetSocket(), playerId.binaryAddress, playerId.port, rakPeer ); + //client->//reliabilityLayerMutex.Unlock(); + + if ( b == false ) // Some damn completion port error... windows is so unreliable + { +#ifdef _DO_PRINTF + printf( "RakClient - AssociateSocketWithCompletionPortAndRead failed" ); +#endif + return ; + } +#endif + + // Use the stored encryption key + if (remoteSystem->setAESKey) + remoteSystem->reliabilityLayer.SetEncryptionKey( remoteSystem->AESKey ); + else + remoteSystem->reliabilityLayer.SetEncryptionKey( 0 ); + } + + // Send the connection request complete to the game + packet=AllocPacket(byteSize, data); + packet->bitSize = byteSize * 8; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) GetIndexFromPlayerID( playerId, true ); + AddPacketToProducer(packet); + + RakNet::BitStream outBitStream(sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned short)); + outBitStream.Write((unsigned char)ID_NEW_INCOMING_CONNECTION); + outBitStream.Write(playerId.binaryAddress); + outBitStream.Write(playerId.port); + // We turned on encryption with SetEncryptionKey. This pads packets to up to 16 bytes. + // As soon as a 16 byte packet arrives on the remote system, we will turn on AES. This works because all encrypted packets are multiples of 16 and the + // packets I happen to be sending before this are less than 16 bytes. Otherwise there is no way to know if a packet that arrived is + // encrypted or not so the other side won't know to turn on encryption or not. + SendImmediate( (char*)outBitStream.GetData(), outBitStream.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, RELIABLE, 0, playerId, false, false, RakNet::GetTime() ); + + if (alreadyConnected==false) + { + PingInternal( playerId, true ); + SendStaticDataInternal( playerId, true ); + } + } + else + { + // Tell the remote system the connection failed + NotifyAndFlagForDisconnect(playerId, true, 0); +#ifdef _DO_PRINTF + printf( "Error: Got a connection accept when we didn't request the connection.\n" ); +#endif + delete [] data; + } + } + else + { + if (data[0]>=(unsigned char)ID_RPC) + { + packet=AllocPacket(byteSize, data); + packet->bitSize = bitSize; + packet->playerId = playerId; + packet->playerIndex = ( PlayerIndex ) remoteSystemIndex; + AddPacketToProducer(packet); + } + //else + // Some internal type got returned to the user? + //RakAssert(0); + } + } + + // Does the reliability layer have any more packets waiting for us? + // To be thread safe, this has to be called in the same thread as HandleSocketReceiveFromConnectedPlayer + bitSize = remoteSystem->reliabilityLayer.Receive( &data ); + } + } + } + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef _WIN32 +unsigned __stdcall UpdateNetworkLoop( LPVOID arguments ) +#else +void* UpdateNetworkLoop( void* arguments ) +#endif +{ + RakPeer * rakPeer = ( RakPeer * ) arguments; + // RakNetTime time; + +#ifdef __USE_IO_COMPLETION_PORTS + + AsynchronousFileIO::Instance()->IncreaseUserCount(); +#endif + + // 11/15/05 - this is slower than Sleep() +#ifdef _WIN32 +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + // Lets see if these timers give better performance than Sleep + HANDLE timerHandle; + LARGE_INTEGER dueTime; + + if ( rakPeer->threadSleepTimer <= 0 ) + rakPeer->threadSleepTimer = 1; + + // 2nd parameter of false means synchronization timer instead of manual-reset timer + timerHandle = CreateWaitableTimer( NULL, FALSE, 0 ); + + assert( timerHandle ); + + dueTime.QuadPart = -10000 * rakPeer->threadSleepTimer; // 10000 is 1 ms? + + BOOL success = SetWaitableTimer( timerHandle, &dueTime, rakPeer->threadSleepTimer, NULL, NULL, FALSE ); + + assert( success ); + +#endif +#endif + +#ifdef _RAKNET_THREADSAFE + #pragma message("-- RakNet: _RAKNET_THREADSAFE defined. Safe to use multiple threads on the same instance of RakPeer (Slow!). --") +#else + #pragma message("-- RakNet: _RAKNET_THREADSAFE not defined. Do NOT use multiple threads on the same instance of RakPeer (Fast!). --") +#endif + + rakPeer->isMainLoopThreadActive = true; + + while ( rakPeer->endThreads == false ) + { + rakPeer->RunUpdateCycle(); + /* +#ifdef _WIN32 +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + #pragma message("-- RakNet: Using WaitForSingleObject. Comment out USE_WAIT_FOR_MULTIPLE_EVENTS in RakNetDefines.h if you want to use Sleep instead. --") + + if ( WaitForSingleObject( timerHandle, INFINITE ) != WAIT_OBJECT_0 ) + { +#ifdef _DEBUG + + assert( 0 ); + #ifdef _DO_PRINTF + printf( "WaitForSingleObject failed (%d)\n", GetLastError() ); + #endif +#endif + } + +#else + #pragma message("-- RakNet: Using Sleep(). Uncomment USE_WAIT_FOR_MULTIPLE_EVENTS in RakNetDefines.h if you want to use WaitForSingleObject instead. --") +*/ + if (rakPeer->threadSleepTimer>=0) + { +#if defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + if (rakPeer->threadSleepTimer>0) + WSAWaitForMultipleEvents(1,&rakPeer->recvEvent,TRUE,rakPeer->threadSleepTimer,FALSE); + else + RakSleep(0); +#else // _WIN32 + RakSleep( rakPeer->threadSleepTimer ); +#endif + } + } + + +#ifdef __USE_IO_COMPLETION_PORTS + AsynchronousFileIO::Instance()->DecreaseUserCount(); +#endif + + + /* +#ifdef _WIN32 +#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400) + CloseHandle( timerHandle ); +#endif +#endif +*/ + + rakPeer->isMainLoopThreadActive = false; + + return 0; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/RakPeer.h b/raknet/RakPeer.h index dac00cb..05b6054 100644 --- a/raknet/RakPeer.h +++ b/raknet/RakPeer.h @@ -1,4 +1,19 @@ -// TODO: Implement RakPeer.h +/// \file +/// \brief The main class used for data transmission and most of RakNet's functionality. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_PEER_H #define __RAK_PEER_H @@ -7,18 +22,59 @@ #include "RakPeerInterface.h" #include "ReliabilityLayer.h" #include "RPCNode.h" +#include "RSACrypt.h" +#include "BitStream.h" +#include "SingleProducerConsumer.h" #include "RPCMap.h" +#include "SimpleMutex.h" +#include "DS_OrderedList.h" +class HuffmanEncodingTree; +class PluginInterface; + +// Sucks but this struct has to be outside the class. Inside and DevCPP won't let you refer to the struct as RakPeer::PlayerIDAndIndex while GCC +// forces you to do RakPeer::PlayerIDAndIndex +struct PlayerIDAndIndex{PlayerID playerId;unsigned index;}; +int RAK_DLL_EXPORT PlayerIDAndIndexComp( const PlayerID &key, const PlayerIDAndIndex &data ); // GCC requires RakPeer::PlayerIDAndIndex or it won't compile + +/// The primary interface for RakNet, RakPeer contains all major functions for the library. +/// See the individual functions for what the class can do. +/// \brief The main interface for network communications class RAK_DLL_EXPORT RakPeer : public RakPeerInterface { public: ///Constructor RakPeer(); - void vftable_0(); - void vftable_4(); - void vftable_8(); - void vftable_C(); + ///Destructor + virtual ~RakPeer(); + + // --------------------------------------------------------------------------------------------Major Low Level Functions - Functions needed by most users-------------------------------------------------------------------------------------------- + /// \brief Starts the network threads, opens the listen port. + /// You must call this before calling Connect(). + /// Multiple calls while already active are ignored. To call this function again with different settings, you must first call Disconnect(). + /// \note Call SetMaximumIncomingConnections if you want to accept incoming connections + /// \param[in] maxConnections The maximum number of connections between this instance of RakPeer and another instance of RakPeer. Required so the network can preallocate and for thread safety. A pure client would set this to 1. A pure server would set it to the number of allowed clients.- A hybrid would set it to the sum of both types of connections + /// \param[in] localPort The port to listen for connections on. + /// \param[in] _threadSleepTimer How many ms to Sleep each internal update cycle (30 to give the game priority, 0 for regular (recommended), -1 to not Sleep() (may be slower)) + /// \param[in] forceHostAddress Can force RakNet to use a particular IP to host on. Pass 0 to automatically pick an IP + /// \return False on failure (can't create socket or thread), true on success. + bool Initialize( unsigned short maxConnections, unsigned short localPort, int _threadSleepTimer, const char *forceHostAddress=0 ); + + /// Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. + /// There is a significant amount of processing and a slight amount of bandwidth overhead for this feature. + /// If you accept connections, you must call this or else secure connections will not be enabled for incoming connections. + /// If you are connecting to another system, you can call this with values for the (e and p,q) public keys before connecting to prevent MitM + /// \pre Must be called before Initialize + /// \param[in] pubKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] pubKeyN A pointer to the public keys from the RSACrypt class. + /// \param[in] privKeyP Public key generated from the RSACrypt class. + /// \param[in] privKeyQ Public key generated from the RSACrypt class. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + void InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ ); + + /// Disables all security. + /// \note Must be called while offline + void DisableSecurity( void ); /// Sets how many incoming connections are allowed. If this is less than the number of players currently connected, /// no more players will be allowed to connect. If this is greater than the maximum number of peers allowed, @@ -41,7 +97,7 @@ public: /// \param[in,out] passwordDataLength Maximum size of the array passwordData. Modified to hold the number of bytes actually written void GetIncomingPassword( char* passwordData, int *passwordDataLength ); - /// \brief Connect to the specified host (ip or domain name) and server port. + /// \brief Connect to the specified host (ip or domain name) and server port. /// Calling Connect and not calling SetMaximumIncomingConnections acts as a dedicated client. /// Calling both acts as a true peer. This is a non-blocking connection. /// You know the connection is successful when IsConnected() returns true or Receive() gets a message with the type identifier ID_CONNECTION_ACCEPTED. @@ -64,10 +120,39 @@ public: /// \return true if the network thread is running, false otherwise bool IsActive( void ) const; - void vftable_2C(); - void vftable_30(); - void vftable_34(); - void vftable_38(); + /// Fills the array remoteSystems with the SystemID of all the systems we are connected to + /// \param[out] remoteSystems An array of PlayerID structures to be filled with the PlayerIDs of the systems we are connected to. Pass 0 to remoteSystems to only get the number of systems we are connected to + /// \param[in, out] numberOfSystems As input, the size of remoteSystems array. As output, the number of elements put into the array + bool GetConnectionList( PlayerID *remoteSystems, unsigned short *numberOfSystems ) const; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \return False if we are not connected to the specified recipient. True otherwise + bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ); + + /// Sends a block of data to the specified system that you are connected to. Same as the above version, but takes a BitStream as input. + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \return False if we are not connected to the specified recipient. True otherwise + bool Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ); + + /// Gets a message from the incoming message queue. + /// Use DeallocatePacket() to deallocate the message after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + /// sa NetworkTypes.h contains struct Packet + Packet* Receive( void ); /// Call this to deallocate a message returned by Receive() when you are done handling it. /// \param[in] packet The message to deallocate. @@ -95,33 +180,148 @@ public: /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. void UnregisterAsRemoteProcedureCall( char* uniqueID ); - void vftable_50(); - void vftable_54(); - void vftable_58(); - void vftable_5C(); - void vftable_60(); - void vftable_64(); - void vftable_68(); - void vftable_6C(); - void vftable_70(); - void vftable_74(); - void vftable_78(); - void vftable_7C(); - void vftable_80(); - void vftable_84(); - void vftable_88(); - void vftable_8C(); - void vftable_90(); - void vftable_94(); - void vftable_98(); + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall(). + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] playerId Who to send this message to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] playerId Who to send this message to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + + // -------------------------------------------------------------------------------------------- Connection Management Functions-------------------------------------------------------------------------------------------- + /// Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). + /// \param[in] target Which system to close the connection to. + /// \param[in] sendDisconnectionNotification True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. + /// \param[in] channel Which ordering channel to send the disconnection notification on, if any + void CloseConnection( const PlayerID target, bool sendDisconnectionNotification, unsigned char orderingChannel=0 ); + + /// Given a playerID, returns an index from 0 to the maximum number of players allowed - 1. + /// \param[in] playerId The PlayerID we are referring to + /// \return The index of this PlayerID or -1 on system not found. + int GetIndexFromPlayerID( const PlayerID playerId ); + + /// This function is only useful for looping through all systems + /// Given an index, will return a PlayerID. + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The PlayerID + PlayerID GetPlayerIDFromIndex( int index ); + + /// Bans an IP from connecting. Banned IPs persist between connections but are not saved on shutdown nor loaded on startup. + /// param[in] IP Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban all IP addresses starting with 128.0.0 + /// \param[in] milliseconds how many ms for a temporary ban. Use 0 for a permanent ban + void AddToBanList( const char *IP, RakNetTime milliseconds=0 ); + + /// Allows a previously banned IP to connect. + /// param[in] Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + void RemoveFromBanList( const char *IP ); + + /// Allows all previously banned IPs to connect. + void ClearBanList( void ); + + /// Returns true or false indicating if a particular IP is banned. + /// \param[in] IP - Dotted IP address. + /// \return true if IP matches any IPs in the ban list, accounting for any wildcards. False otherwise. + bool IsBanned( const char *IP ); + + // --------------------------------------------------------------------------------------------Pinging Functions - Functions dealing with the automatic ping mechanism-------------------------------------------------------------------------------------------- + /// Send a ping to the specified connected system. + /// \pre The sender and recipient must already be started via a successful call to Initialize() + /// \param[in] target Which system to ping + void Ping( const PlayerID target ); + + /// Send a ping to the specified unconnected system. The remote system, if it is Initialized, will respond with ID_PONG. The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in NetworkTypes.h + /// \param[in] host Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] onlyReplyOnAcceptingConnections Only request a reply if the remote system is accepting connections + void Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections ); + + /// Returns the average of all ping times read for the specific system or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The ping time for this system, or -1 + int GetAveragePing( const PlayerID playerId ); + + /// Returns the last ping time read for the specific system or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The last ping time for this system, or -1 + int GetLastPing( const PlayerID playerId ) const; + + /// Returns the lowest ping time read or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The lowest ping time for this system, or -1 + int GetLowestPing( const PlayerID playerId ) const; + + /// Ping the remote systems every so often, or not. This is off by default. Can be called anytime. + /// \param[in] doPing True to start occasional pings. False to stop them. + void SetOccasionalPing( bool doPing ); + + // --------------------------------------------------------------------------------------------Static Data Functions - Functions dealing with API defined synchronized memory-------------------------------------------------------------------------------------------- + /// All systems have a block of data associated with them, for user use. This block of data can be used to easily + /// specify typical system data that you want to know on connection, such as the player's name. + /// \param[in] playerId Which system you are referring to. Pass the value returned by GetInternalID to refer to yourself + /// \return The data passed to SetRemoteStaticData stored as a bitstream + RakNet::BitStream * GetRemoteStaticData( const PlayerID playerId ); + + /// All systems have a block of data associated with them, for user use. This block of data can be used to easily + /// specify typical system data that you want to know on connection, such as the player's name. + /// \param[in] playerId Whose static data to change. Use your own playerId to change your own static data + /// \param[in] data a block of data to store + /// \param[in] length The length of data in bytes + void SetRemoteStaticData( const PlayerID playerId, const char *data, const int length ); + + /// Sends your static data to the specified system. This is automatically done on connection. + /// You should call this when you change your static data.To send the static data of another system (such as relaying their data) you should do this + /// normally with Send) + /// \param[in] target Who to send your static data to. Specify UNASSIGNED_PLAYER_ID to broadcast to all + void SendStaticData( const PlayerID target ); + + /// Sets the data to send along with a LAN server discovery or offline ping reply. + /// \a length should be under 400 bytes, as a security measure against flood attacks + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + /// \sa Ping.cpp + void SetOfflinePingResponse( const char *data, const unsigned int length ); + //--------------------------------------------------------------------------------------------Network Functions - Functions dealing with the network in general-------------------------------------------------------------------------------------------- /// Return the unique address identifier that represents you on the the network and is based on your local IP / port. /// \return the identifier of your system internally, which may not be how other systems see if you if you are behind a NAT or proxy PlayerID GetInternalID( void ) const; - void vftable_A0(); - void vftable_A4(); + /// Return the unique address identifier that represents you on the the network and is based on your externalIP / port + /// (the IP / port the specified player uses to communicate with you) + /// \param[in] target Which remote system you are referring to for your external ID. Usually the same for all systems, unless you have two or more network cards. + PlayerID GetExternalID( const PlayerID target ) const; + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable message. + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target Which system to do this for + void SetTimeoutTime( RakNetTime timeMS, const PlayerID target ); /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. /// Maximum allowed size is MAXIMUM_MTU_SIZE. @@ -137,14 +337,50 @@ public: /// \return The current MTU size int GetMTUSize( void ) const; - void vftable_B0(); - void vftable_B4(); - void vftable_B8(); - void vftable_BC(); - void vftable_C0(); - void vftable_C4(); - void vftable_C8(); - void vftable_CC(); + /// Returns the number of IP addresses this system has internally. Get the actual addresses from GetLocalIP() + unsigned GetNumberOfAddresses( void ); + + /// Returns an IP address at index 0 to GetNumberOfAddresses-1 + const char* GetLocalIP( unsigned int index ); + + /// TODO - depreciate this + /// Returns the dotted IP address for the specified playerId + /// \param[in] playerId Any player ID other than UNASSIGNED_PLAYER_ID, even if that player is not currently connected + const char* PlayerIDToDottedIP( const PlayerID playerId ) const; + + // TODO - depreciate this + /// Converts a dotted IP to a playerId + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[out] playerId The result of this operation + void IPToPlayerID( const char* host, unsigned short remotePort, PlayerID *playerId ); + + /// Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary + /// when connecting to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections + void AllowConnectionResponseIPMigration( bool allow ); + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength length of data in bytes. Use 0 if no data. + void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ); + + /// Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. + /// ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived + /// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. + /// Defaults to 0 (never return this notification) + /// \param[in] interval How many messages to use as an interval + void SetSplitMessageProgressInterval(int interval); + + /// Set how long to wait before giving up on sending an unreliable message + /// Useful if the network is clogged up. + /// Set to 0 or less to never timeout. Defaults to 0. + /// \param[in] timeoutMS How many ms to wait before simply not sending an unreliable message. + void SetUnreliableTimeout(RakNetTime timeoutMS); // --------------------------------------------------------------------------------------------Compression Functions - Functions related to the compression layer-------------------------------------------------------------------------------------------- /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() @@ -161,8 +397,23 @@ public: /// \return False (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) bool GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ); - void vftable_D8(); - void vftable_DC(); + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ); + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + bool DeleteCompressionLayer( bool inputLayer ); /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data /// \return The compression ratio @@ -172,52 +423,356 @@ public: /// \return The decompression ratio float GetDecompressionRatio( void ) const; - void vftable_E8(); - void vftable_EC(); - void vftable_F0(); - void vftable_F4(); - void vftable_F8(); - void vftable_FC(); - void vftable_100(); - void vftable_104(); - void vftable_108(); - void vftable_10C(); - void vftable_110(); + // -------------------------------------------------------------------------------------------- Plugin Functions-------------------------------------------------------------------------------------------- + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a plugin to attach + void AttachPlugin( PluginInterface *plugin ); + + /// Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a plugin to detach + void DetachPlugin( PluginInterface *messageHandler ); + + // --------------------------------------------------------------------------------------------Miscellaneous Functions-------------------------------------------------------------------------------------------- + /// Put a message back at the end of the receive queue in case you don't want to deal with it immediately + /// \param[in] packet The packet you want to push back. + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + void PushBackPacket( Packet *packet, bool pushAtHead ); + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + void SetRouterInterface( RouterInterface *routerInterface ); + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + void RemoveRouterInterface( RouterInterface *routerInterface ); + + // --------------------------------------------------------------------------------------------Network Simulator Functions-------------------------------------------------------------------------------------------- + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS Maximum bits per second to send. Packetloss grows linearly. 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance); + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + bool IsNetworkSimulatorActive( void ); + + // --------------------------------------------------------------------------------------------Statistical Functions - Functions dealing with API performance-------------------------------------------------------------------------------------------- + + /// Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] playerId: Which connected system to get statistics for + /// \return 0 on can't find the specified system. A pointer to a set of data otherwise. + /// \sa RakNetStatistics.h + RakNetStatisticsStruct * const GetStatistics( const PlayerID playerId ); + + // --------------------------------------------------------------------------------------------EVERYTHING AFTER THIS COMMENT IS FOR INTERNAL USE ONLY-------------------------------------------------------------------------------------------- + /// \internal + RPCMap *GetRPCMap( const PlayerID playerId); + + /// \internal + /// \brief Holds the clock differences between systems, along with the ping + struct PingAndClockDifferential + { + unsigned short pingTime; + RakNetTime clockDifferential; + }; + + /// \internal + /// \brief All the information representing a connected system system + struct RemoteSystemStruct + { + bool isActive; // Is this structure in use? + PlayerID playerId; /// The remote system associated with this reliability layer + PlayerID myExternalPlayerId; /// Your own IP, as reported by the remote system + ReliabilityLayer reliabilityLayer; /// The reliability layer associated with this player + bool weInitiatedTheConnection; /// True if we started this connection via Connect. False if someone else connected to us. + PingAndClockDifferential pingAndClockDifferential[ PING_TIMES_ARRAY_SIZE ]; /// last x ping times and calculated clock differentials with it + int pingAndClockDifferentialWriteIndex; /// The index we are writing into the pingAndClockDifferential circular buffer + unsigned short lowestPing; ///The lowest ping value encountered + RakNetTime nextPingTime; /// When to next ping this player + RakNetTime lastReliableSend; /// When did the last reliable send occur. Reliable sends must occur at least once every timeoutTime/2 units to notice disconnects + RakNet::BitStream staticData; /// static data. This cannot be a pointer because it might be accessed in another thread. + RakNetTime connectionTime; /// connection time, if active. + unsigned char AESKey[ 16 ]; /// Security key. + bool setAESKey; /// true if security is enabled. + RPCMap rpcMap; /// Mapping of RPC calls to single byte integers to save transmission bandwidth. + enum ConnectMode {NO_ACTION, DISCONNECT_ASAP, DISCONNECT_ASAP_SILENTLY, DISCONNECT_ON_NO_ACK, REQUESTED_CONNECTION, HANDLING_CONNECTION_REQUEST, UNVERIFIED_SENDER, SET_ENCRYPTION_ON_MULTIPLE_16_BYTE_PACKET, CONNECTED} connectMode; + }; protected: +#ifdef _WIN32 + // friend unsigned __stdcall RecvFromNetworkLoop(LPVOID arguments); + friend void __stdcall ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + friend void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + friend unsigned __stdcall UpdateNetworkLoop( LPVOID arguments ); +#else + // friend void* RecvFromNetworkLoop( void* arguments ); + friend void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); + friend void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); + friend void* UpdateNetworkLoop( void* arguments ); +#endif + + // This is done to provide custom RPC handling when in a blocking RPC + Packet* ReceiveIgnoreRPC( void ); + + int GetIndexFromPlayerID( const PlayerID playerId, bool calledFromNetworkThread ); + + //void RemoveFromRequestedConnectionsList( const PlayerID playerId ); + bool SendConnectionRequest( const char* host, unsigned short remotePort, char* passwordData, int passwordDataLength ); + ///Get the reliability layer associated with a playerID. + /// \param[in] playerID The player identifier + /// \return 0 if none + RemoteSystemStruct *GetRemoteSystemFromPlayerID( const PlayerID playerID, bool calledFromNetworkThread, bool onlyActive) const; + ///Parse out a connection request packet + void ParseConnectionRequestPacket( RakPeer::RemoteSystemStruct *remoteSystem, PlayerID playerId, const char *data, int byteSize); + ///When we get a connection request from an ip / port, accept it unless full + void OnConnectionRequest( RakPeer::RemoteSystemStruct *remoteSystem, unsigned char *AESKey, bool setAESKey ); + ///Send a reliable disconnect packet to this player and disconnect them when it is delivered + void NotifyAndFlagForDisconnect( const PlayerID playerId, bool performImmediate, unsigned char orderingChannel ); + ///Returns how many remote systems initiated a connection to us + unsigned short GetNumberOfRemoteInitiatedConnections( void ) const; + ///Get a free remote system from the list and assign our playerID to it. Should only be called from the update thread - not the user thread + RemoteSystemStruct * AssignPlayerIDToRemoteSystemList( const PlayerID playerId, RemoteSystemStruct::ConnectMode connectionMode ); + ///An incoming packet has a timestamp, so adjust it to be relative to this system + void ShiftIncomingTimestamp( unsigned char *data, PlayerID playerId ) const; + ///Get the most probably accurate clock differential for a certain player + RakNetTime GetBestClockDifferential( const PlayerID playerId ) const; + + //void PushPortRefused( const PlayerID target ); + ///Handles an RPC packet. This is sending an RPC request + /// \param[in] data A packet returned from Receive with the ID ID_RPC + /// \param[in] length The size of the packet data + /// \param[in] playerId The sender of the packet + /// \return true on success, false on a bad packet or an unregistered function + bool HandleRPCPacket( const char *data, int length, PlayerID playerId ); + + ///Handles an RPC reply packet. This is data returned from an RPC call + /// \param[in] data A packet returned from Receive with the ID ID_RPC + /// \param[in] length The size of the packet data + /// \param[in] playerId The sender of the packet + void HandleRPCReplyPacket( const char *data, int length, PlayerID playerId ); + +#ifdef __USE_IO_COMPLETION_PORTS + + bool SetupIOCompletionPortSocket( int index ); +#endif ///Set this to true to terminate the Peer thread execution volatile bool endThreads; - - char _gap6[2]; - + ///true if the peer thread is active. + volatile bool isMainLoopThreadActive; + bool occasionalPing; /// Do we occasionally ping the other systems?*/ ///Store the maximum number of peers allowed to connect unsigned short maximumNumberOfPeers; + //05/02/06 Just using maximumNumberOfPeers instead + ///Store the maximum number of peers able to connect, including reserved connection slots for pings, etc. + //unsigned short remoteSystemListSize; ///Store the maximum incoming connection allowed unsigned short maximumIncomingConnections; - - RakNet::BitStream field_B; - RakNet::BitStream field_11C; - + ///localStaticData necessary because we may not have a RemoteSystemStruct representing ourselves in the list + RakNet::BitStream localStaticData, offlinePingResponse; ///Local Player ID PlayerID myPlayerId; char incomingPassword[256]; unsigned char incomingPasswordLength; - char _gap334[158]; + /// This is an array of pointers to RemoteSystemStruct + /// This allows us to preallocate the list when starting, so we don't have to allocate or delete at runtime. + /// Another benefit is that is lets us add and remove active players simply by setting playerId + /// and moving elements in the list by copying pointers variables without affecting running threads, even if they are in the reliability layer + RemoteSystemStruct* remoteSystemList; + + // remoteSystemLookup is only used in the network thread or when single threaded. Not safe to use this in the user thread + // A list of PlayerIDAndIndex sorted by playerId. This way, given a PlayerID, we can quickly get the index into remoteSystemList to find this player. + // This is an optimization to avoid scanning the remoteSystemList::playerId member when doing targeted sends + // Improves speed of a targeted send to every player from O(n^2) to O(n * log2(n)). + // For an MMOG with 1000 players: + // The original method takes 1000^2=1,000,000 calls + // The new method takes: + // logbx = (logax) / (logab) + // log2(x) = log10(x) / log10(2) + // log2(1000) = log10(1000) / log10(2) + // log2(1000) = 9.965 + // 9.965 * 1000 = 9965 + // For 1000 players this optimization improves the speed by over 1000 times. + DataStructures::OrderedList remoteSystemLookup; + enum + { + // Only put these mutexes in user thread functions! +#ifdef _RAKNET_THREADSAFE + transferToPacketQueue_Mutex, + packetPool_Mutex, + bufferedCommands_Mutex, + requestedConnectionList_Mutex, +#endif + offlinePingResponse_Mutex, + NUMBER_OF_RAKPEER_MUTEXES + }; + SimpleMutex rakPeerMutexes[ NUMBER_OF_RAKPEER_MUTEXES ]; + ///RunUpdateCycle is not thread safe but we don't need to mutex calls. Just skip calls if it is running already + + bool updateCycleIsRunning; + ///The list of people we have tried to connect to recently + + //DataStructures::Queue requestedConnectionsList; + ///Data that both the client and the server needs + + unsigned int bytesSentPerSecond, bytesReceivedPerSecond; + // bool isSocketLayerBlocking; + // bool continualPing,isRecvfromThreadActive,isMainLoopThreadActive, endThreads, isSocketLayerBlocking; + unsigned int validationInteger; +#ifdef _WIN32 + HANDLE +#else + pthread_t +#endif + processPacketsThreadHandle, recvfromThreadHandle; + SimpleMutex incomingQueueMutex, banListMutex; //,synchronizedMemoryQueueMutex, automaticVariableSynchronizationMutex; + //DataStructures::Queue incomingpacketSingleProducerConsumer; //, synchronizedMemorypacketSingleProducerConsumer; + // BitStream enumerationData; + + /// @brief Automatic Variable Synchronization Mechanism + /// automatic variable synchronization takes a primary and secondary identifier + /// The unique primary identifier is the index into the automaticVariableSynchronizationList + /// The unique secondary identifier (UNASSIGNED_NETWORK_ID for none) is in an unsorted list of memory blocks + struct MemoryBlock + { + char *original, *copy; + unsigned short size; + NetworkID secondaryID; + bool isAuthority; + bool ( *synchronizationRules ) ( char*, char* ); + }; + + struct BanStruct + { + char *IP; + RakNetTime timeout; // 0 for none + }; + + struct RequestedConnectionStruct + { + PlayerID playerId; + RakNetTime nextRequestTime; + unsigned char requestsMade; + char *data; + unsigned short dataLength; + char outgoingPassword[256]; + unsigned char outgoingPasswordLength; + enum {CONNECT=1, /*PING=2, PING_OPEN_CONNECTIONS=4,*/ /*ADVERTISE_SYSTEM=2*/} actionToTake; + }; + + //DataStructures::List* > automaticVariableSynchronizationList; + DataStructures::List banList; + DataStructures::List messageHandlerList; + DataStructures::SingleProducerConsumer requestedConnectionList; + /// Compression stuff unsigned int frequencyTable[ 256 ]; - - char _gap7D1[8]; - + HuffmanEncodingTree *inputTree, *outputTree; unsigned int rawBytesSent, rawBytesReceived, compressedBytesSent, compressedBytesReceived; + // void DecompressInput(RakNet::BitStream *bitStream); + // void UpdateOutgoingFrequencyTable(RakNet::BitStream * bitStream); + void GenerateSYNCookieRandomNumber( void ); + void SecuredConnectionResponse( const PlayerID playerId ); + void SecuredConnectionConfirmation( RakPeer::RemoteSystemStruct * remoteSystem, char* data ); + bool RunUpdateCycle( void ); + // void RunMutexedUpdateCycle(void); - char _gap7E9[24]; + struct BufferedCommandStruct + { + int numberOfBitsToSend; + PacketPriority priority; + PacketReliability reliability; + char orderingChannel; + PlayerID playerId; + bool broadcast; + RemoteSystemStruct::ConnectMode connectionMode; + NetworkID networkID; + bool blockingCommand; // Only used for RPC + char *data; + enum {BCS_SEND, BCS_CLOSE_CONNECTION, /*BCS_RPC, BCS_RPC_SHIFT,*/ BCS_DO_NOTHING} command; + }; + // Single producer single consumer queue using a linked list + //BufferedCommandStruct* bufferedCommandReadIndex, bufferedCommandWriteIndex; + DataStructures::SingleProducerConsumer bufferedCommands; + + bool AllowIncomingConnections(void) const; + + // Sends static data using immediate send mode or not (if called from user thread, put false for performImmediate. If called from update thread, put true). + // This is done for efficiency, so we don't buffer send calls that are from the network thread anyway + void SendStaticDataInternal( const PlayerID target, bool performImmediate ); + void PingInternal( const PlayerID target, bool performImmediate ); + bool ValidSendTarget(PlayerID playerId, bool broadcast); + // This stores the user send calls to be handled by the update thread. This way we don't have thread contention over playerIDs + void CloseConnectionInternal( const PlayerID target, bool sendDisconnectionNotification, bool performImmediate, unsigned char orderingChannel ); + void SendBuffered( const char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, RemoteSystemStruct::ConnectMode connectionMode ); + bool SendImmediate( char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool useCallerDataAllocation, RakNetTimeNS currentTime ); + //bool HandleBufferedRPC(BufferedCommandStruct *bcs, RakNetTime time); + void ClearBufferedCommands(void); + void ClearRequestedConnectionList(void); + void AddPacketToProducer(Packet *p); + + //DataStructures::AVLBalancedBinarySearchTree rpcTree; RPCMap rpcMap; // Can't use StrPtrHash because runtime insertions will screw up the indices int MTUSize; bool trackFrequencyTable; + int threadSleepTimer; + + SOCKET connectionSocket; +#if defined (_WIN32) && defined(USE_WAIT_FOR_MULTIPLE_EVENTS) + WSAEVENT recvEvent; +#endif + + // Used for RPC replies + RakNet::BitStream *replyFromTargetBS; + PlayerID replyFromTargetPlayer; + bool replyFromTargetBroadcast; + + // Problem: + // Waiting in function A: + // Wait function gets RPC B: + // + bool blockOnRPCReply; + + // For redirecting sends through the router plugin. Unfortunate I have to use this architecture. + RouterInterface *router; + + // Nobody would use the internet simulator in a final build. +#ifndef _RELEASE + double _maxSendBPS; + unsigned short _minExtraPing, _extraPingVariance; +#endif + +#if !defined(_COMPATIBILITY_1) + /// Encryption and security + big::RSACrypt rsacrypt; + big::u32 publicKeyE; + RSA_BIT_SIZE publicKeyN; + bool keysLocallyGenerated, usingSecurity; + RakNetTime randomNumberExpirationTime; + unsigned char newRandomNumber[ 20 ], oldRandomNumber[ 20 ]; +#endif + + ///How long it has been since things were updated by a call to receiveUpdate thread uses this to determine how long to sleep for + //unsigned int lastUserUpdateCycle; + /// True to allow connection accepted packets from anyone. False to only allow these packets from servers we requested a connection to. + bool allowConnectionResponseIPMigration; + + int splitMessageProgressInterval; + RakNetTime unreliableTimeout; + + // The packetSingleProducerConsumer transfers the packets from the network thread to the user thread. The pushedBackPacket holds packets that couldn't be processed + // immediately while waiting on blocked RPCs + DataStructures::SingleProducerConsumer packetSingleProducerConsumer; + //DataStructures::Queue pushedBackPacket, outOfOrderDeallocatedPacket; + DataStructures::Queue packetPool; }; #endif diff --git a/raknet/RakPeerInterface.h b/raknet/RakPeerInterface.h index b4eec32..79b154d 100644 --- a/raknet/RakPeerInterface.h +++ b/raknet/RakPeerInterface.h @@ -1,19 +1,72 @@ -// TODO: Implement RakPeerInterface.h +/// \file +/// \brief An interface for RakPeer. Simply contains all user functions as pure virtuals. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_PEER_INTERFACE_H #define __RAK_PEER_INTERFACE_H +#include "PacketPriority.h" #include "NetworkTypes.h" #include "Export.h" +// Forward declarations +namespace RakNet +{ + class BitStream; +} +class PluginInterface; +struct RPCMap; +struct RakNetStatisticsStruct; +class RouterInterface; + +/// The primary interface for RakNet, RakPeer contains all major functions for the library. +/// See the individual functions for what the class can do. +/// \brief The main interface for network communications class RAK_DLL_EXPORT RakPeerInterface { public: + ///Destructor + virtual ~RakPeerInterface() {} - virtual void vftable_0()=0; - virtual void vftable_4()=0; - virtual void vftable_8()=0; - virtual void vftable_C()=0; + // --------------------------------------------------------------------------------------------Major Low Level Functions - Functions needed by most users-------------------------------------------------------------------------------------------- + /// \brief Starts the network threads, opens the listen port. + /// You must call this before calling Connect(). + /// Multiple calls while already active are ignored. To call this function again with different settings, you must first call Disconnect(). + /// \note Call SetMaximumIncomingConnections if you want to accept incoming connections + /// \param[in] maxConnections The maximum number of connections between this instance of RakPeer and another instance of RakPeer. Required so the network can preallocate and for thread safety. A pure client would set this to 1. A pure server would set it to the number of allowed clients.- A hybrid would set it to the sum of both types of connections + /// \param[in] localPort The port to listen for connections on. + /// \param[in] _threadSleepTimer How many ms to Sleep each internal update cycle (30 to give the game priority, 0 for regular (recommended), -1 to not Sleep() (may be slower)) + /// \param[in] forceHostAddress Can force RakNet to use a particular IP to host on. Pass 0 to automatically pick an IP + /// \return False on failure (can't create socket or thread), true on success. + virtual bool Initialize( unsigned short maxConnections, unsigned short localPort, int _threadSleepTimer, const char *forceHostAddress=0 )=0; + + /// Secures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to prevent connection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks. + /// There is a significant amount of processing and a slight amount of bandwidth overhead for this feature. + /// If you accept connections, you must call this or else secure connections will not be enabled for incoming connections. + /// If you are connecting to another system, you can call this with values for the (e and p,q) public keys before connecting to prevent MitM + /// \pre Must be called while offline + /// \param[in] pubKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] pubKeyN A pointer to the public keys from the RSACrypt class. + /// \param[in] privKeyP Public key generated from the RSACrypt class. + /// \param[in] privKeyQ Public key generated from the RSACrypt class. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + virtual void InitializeSecurity(const char *pubKeyE, const char *pubKeyN, const char *privKeyP, const char *privKeyQ )=0; + + /// Disables all security. + /// \note Must be called while offline + virtual void DisableSecurity( void )=0; /// Sets how many incoming connections are allowed. If this is less than the number of players currently connected, /// no more players will be allowed to connect. If this is greater than the maximum number of peers allowed, @@ -59,10 +112,39 @@ public: /// \return true if the network thread is running, false otherwise virtual bool IsActive( void ) const=0; - virtual void vftable_2C()=0; - virtual void vftable_30()=0; - virtual void vftable_34()=0; - virtual void vftable_38()=0; + /// Fills the array remoteSystems with the SystemID of all the systems we are connected to + /// \param[out] remoteSystems An array of PlayerID structures to be filled with the PlayerIDs of the systems we are connected to. Pass 0 to remoteSystems to only get the number of systems we are connected to + /// \param[in, out] numberOfSystems As input, the size of remoteSystems array. As output, the number of elements put into the array + virtual bool GetConnectionList( PlayerID *remoteSystems, unsigned short *numberOfSystems ) const=0; + + /// Sends a block of data to the specified system that you are connected to. + /// This function only works while the connected + /// \param[in] data The block of data to send + /// \param[in] length The size in bytes of the data to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \return False if we are not connected to the specified recipient. True otherwise + virtual bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast )=0; + + /// Sends a block of data to the specified system that you are connected to. Same as the above version, but takes a BitStream as input. + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. See PacketPriority.h + /// \param[in] reliability How reliability to send this data. See PacketPriority.h + /// \param[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \return False if we are not connected to the specified recipient. True otherwise + virtual bool Send( RakNet::BitStream * bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast )=0; + + /// Gets a message from the incoming message queue. + /// Use DeallocatePacket() to deallocate the message after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + /// sa NetworkTypes.h contains struct Packet + virtual Packet* Receive( void )=0; /// Call this to deallocate a message returned by Receive() when you are done handling it. /// \param[in] packet The message to deallocate. @@ -91,33 +173,148 @@ public: /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. virtual void UnregisterAsRemoteProcedureCall( char* uniqueID )=0; - virtual void vftable_50()=0; - virtual void vftable_54()=0; - virtual void vftable_58()=0; - virtual void vftable_5C()=0; - virtual void vftable_60()=0; - virtual void vftable_64()=0; - virtual void vftable_68()=0; - virtual void vftable_6C()=0; - virtual void vftable_70()=0; - virtual void vftable_74()=0; - virtual void vftable_78()=0; - virtual void vftable_7C()=0; - virtual void vftable_80()=0; - virtual void vftable_84()=0; - virtual void vftable_88()=0; - virtual void vftable_8C()=0; - virtual void vftable_90()=0; - virtual void vftable_94()=0; - virtual void vftable_98()=0; + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall(). + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] playerId Who to send this message to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + virtual bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terminated string identifying the function to call. Recommended you use the macro CLASS_MEMBER_ID for class member functions. + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. See PacketPriority.h. + /// \param[in] reliability How reliability to send this data. See PacketPriority.h. + /// \param[in] orderingChannel When using ordered or sequenced message, what channel to order these on. + /// \param[in] playerId Who to send this message to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which should be remotely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + virtual bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + // -------------------------------------------------------------------------------------------- Connection Management Functions-------------------------------------------------------------------------------------------- + /// Close the connection to another host (if we initiated the connection it will disconnect, if they did it will kick them out). + /// \param[in] target Which system to close the connection to. + /// \param[in] sendDisconnectionNotification True to send ID_DISCONNECTION_NOTIFICATION to the recipient. False to close it silently. + /// \param[in] channel Which ordering channel to send the disconnection notification on, if any + virtual void CloseConnection( const PlayerID target, bool sendDisconnectionNotification, unsigned char orderingChannel=0 )=0; + + /// Given a playerID, returns an index from 0 to the maximum number of players allowed - 1. + /// \param[in] playerId The PlayerID we are referring to + /// \return The index of this PlayerID or -1 on system not found. + virtual int GetIndexFromPlayerID( const PlayerID playerId )=0; + + /// This function is only useful for looping through all systems + /// Given an index, will return a PlayerID. + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + /// \return The PlayerID + virtual PlayerID GetPlayerIDFromIndex( int index )=0; + + /// Bans an IP from connecting. Banned IPs persist between connections but are not saved on shutdown nor loaded on startup. + /// param[in] IP Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will ban all IP addresses starting with 128.0.0 + /// \param[in] milliseconds how many ms for a temporary ban. Use 0 for a permanent ban + virtual void AddToBanList( const char *IP, RakNetTime milliseconds=0 )=0; + + /// Allows a previously banned IP to connect. + /// param[in] Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + virtual void RemoveFromBanList( const char *IP )=0; + + /// Allows all previously banned IPs to connect. + virtual void ClearBanList( void )=0; + + /// Returns true or false indicating if a particular IP is banned. + /// \param[in] IP - Dotted IP address. + /// \return true if IP matches any IPs in the ban list, accounting for any wildcards. False otherwise. + virtual bool IsBanned( const char *IP )=0; + + // --------------------------------------------------------------------------------------------Pinging Functions - Functions dealing with the automatic ping mechanism-------------------------------------------------------------------------------------------- + /// Send a ping to the specified connected system. + /// \pre The sender and recipient must already be started via a successful call to Initialize() + /// \param[in] target Which system to ping + virtual void Ping( const PlayerID target )=0; + + /// Send a ping to the specified unconnected system. The remote system, if it is Initialized, will respond with ID_PONG. The final ping time will be encoded in the following sizeof(RakNetTime) bytes. (Default is 4 bytes - See __GET_TIME_64BIT in NetworkTypes.h + /// \param[in] host Either a dotted IP address or a domain name. Can be 255.255.255.255 for LAN broadcast. + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] onlyReplyOnAcceptingConnections Only request a reply if the remote system is accepting connections + virtual void Ping( const char* host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections )=0; + + /// Returns the average of all ping times read for the specific system or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The ping time for this system, or -1 + virtual int GetAveragePing( const PlayerID playerId )=0; + + /// Returns the last ping time read for the specific system or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The last ping time for this system, or -1 + virtual int GetLastPing( const PlayerID playerId ) const=0; + + /// Returns the lowest ping time read or -1 if none read yet + /// \param[in] playerId Which system we are referring to + /// \return The lowest ping time for this system, or -1 + virtual int GetLowestPing( const PlayerID playerId ) const=0; + + /// Ping the remote systems every so often, or not. This is off by default. Can be called anytime. + /// \param[in] doPing True to start occasional pings. False to stop them. + virtual void SetOccasionalPing( bool doPing )=0; + + // --------------------------------------------------------------------------------------------Static Data Functions - Functions dealing with API defined synchronized memory-------------------------------------------------------------------------------------------- + /// All systems have a block of data associated with them, for user use. This block of data can be used to easily + /// specify typical system data that you want to know on connection, such as the player's name. + /// \param[in] playerId Which system you are referring to. Pass the value returned by GetInternalID to refer to yourself + /// \return The data passed to SetRemoteStaticData stored as a bitstream + virtual RakNet::BitStream * GetRemoteStaticData( const PlayerID playerId )=0; + + /// All systems have a block of data associated with them, for user use. This block of data can be used to easily + /// specify typical system data that you want to know on connection, such as the player's name. + /// \param[in] playerId Whose static data to change. Use your own playerId to change your own static data + /// \param[in] data a block of data to store + /// \param[in] length The length of data in bytes + virtual void SetRemoteStaticData( const PlayerID playerId, const char *data, const int length )=0; + + /// Sends your static data to the specified system. This is automatically done on connection. + /// You should call this when you change your static data.To send the static data of another system (such as relaying their data) you should do this + /// normally with Send) + /// \param[in] target Who to send your static data to. Specify UNASSIGNED_PLAYER_ID to broadcast to all + virtual void SendStaticData( const PlayerID target )=0; + + /// Sets the data to send along with a LAN server discovery or offline ping reply. + /// \a length should be under 400 bytes, as a security measure against flood attacks + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + /// \sa Ping.cpp + virtual void SetOfflinePingResponse( const char *data, const unsigned int length )=0; //--------------------------------------------------------------------------------------------Network Functions - Functions dealing with the network in general-------------------------------------------------------------------------------------------- /// Return the unique address identifier that represents you on the the network and is based on your local IP / port. /// \return the identifier of your system internally, which may not be how other systems see if you if you are behind a NAT or proxy virtual PlayerID GetInternalID( void ) const=0; - virtual void vftable_A0()=0; - virtual void vftable_A4()=0; + /// Return the unique address identifier that represents you on the the network and is based on your externalIP / port + /// (the IP / port the specified player uses to communicate with you) + /// \param[in] target Which remote system you are referring to for your external ID. Usually the same for all systems, unless you have two or more network cards. + virtual PlayerID GetExternalID( const PlayerID target ) const=0; + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable message. + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target Which system to do this for + virtual void SetTimeoutTime( RakNetTime timeMS, const PlayerID target )=0; /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. /// Maximum allowed size is MAXIMUM_MTU_SIZE. @@ -128,19 +325,55 @@ public: /// \pre Can only be called when not connected. /// \return false on failure (we are connected), else true virtual bool SetMTUSize( int size )=0; - + /// Returns the current MTU size /// \return The current MTU size virtual int GetMTUSize( void ) const=0; - virtual void vftable_B0()=0; - virtual void vftable_B4()=0; - virtual void vftable_B8()=0; - virtual void vftable_BC()=0; - virtual void vftable_C0()=0; - virtual void vftable_C4()=0; - virtual void vftable_C8()=0; - virtual void vftable_CC()=0; + /// Returns the number of IP addresses this system has internally. Get the actual addresses from GetLocalIP() + virtual unsigned GetNumberOfAddresses( void )=0; + + /// Returns an IP address at index 0 to GetNumberOfAddresses-1 + virtual const char* GetLocalIP( unsigned int index )=0; + + /// TODO - depreciate this + /// Returns the dotted IP address for the specified playerId + /// \param[in] playerId Any player ID other than UNASSIGNED_PLAYER_ID, even if that player is not currently connected + virtual const char* PlayerIDToDottedIP( const PlayerID playerId ) const=0; + + // TODO - depreciate this + /// Converts a dotted IP to a playerId + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[out] playerId The result of this operation + virtual void IPToPlayerID( const char* host, unsigned short remotePort, PlayerID *playerId )=0; + + /// Allow or disallow connection responses from any IP. Normally this should be false, but may be necessary + /// when connecting to servers with multiple IP addresses. + /// \param[in] allow - True to allow this behavior, false to not allow. Defaults to false. Value persists between connections + virtual void AllowConnectionResponseIPMigration( bool allow )=0; + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data. + /// \pre The sender and recipient must already be started via a successful call to Initialize + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data Optional data to append to the packet. + /// \param[in] dataLength length of data in bytes. Use 0 if no data. + virtual void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength )=0; + + /// Controls how often to return ID_DOWNLOAD_PROGRESS for large message downloads. + /// ID_DOWNLOAD_PROGRESS is returned to indicate a new partial message chunk, roughly the MTU size, has arrived + /// As it can be slow or cumbersome to get this notification for every chunk, you can set the interval at which it is returned. + /// Defaults to 0 (never return this notification) + /// \param[in] interval How many messages to use as an interval + virtual void SetSplitMessageProgressInterval(int interval)=0; + + /// Set how long to wait before giving up on sending an unreliable message + /// Useful if the network is clogged up. + /// Set to 0 or less to never timeout. Defaults to 0. + /// \param[in] timeoutMS How many ms to wait before simply not sending an unreliable message. + virtual void SetUnreliableTimeout(RakNetTime timeoutMS)=0; // --------------------------------------------------------------------------------------------Compression Functions - Functions related to the compression layer-------------------------------------------------------------------------------------------- /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() @@ -157,8 +390,23 @@ public: /// \return False (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) virtual bool GetOutgoingFrequencyTable( unsigned int outputFrequencyTable[ 256 ] )=0; - virtual void vftable_D8()=0; - virtual void vftable_DC()=0; + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + virtual bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer )=0; + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + virtual bool DeleteCompressionLayer( bool inputLayer )=0; /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data /// \return The compression ratio @@ -168,17 +416,56 @@ public: /// \return The decompression ratio virtual float GetDecompressionRatio( void ) const=0; - virtual void vftable_E8()=0; - virtual void vftable_EC()=0; - virtual void vftable_F0()=0; - virtual void vftable_F4()=0; - virtual void vftable_F8()=0; - virtual void vftable_FC()=0; - virtual void vftable_100()=0; - virtual void vftable_104()=0; - virtual void vftable_108()=0; - virtual void vftable_10C()=0; - virtual void vftable_110()=0; + // -------------------------------------------------------------------------------------------- Plugin Functions-------------------------------------------------------------------------------------------- + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a plugin to attach + virtual void AttachPlugin( PluginInterface *plugin )=0; + + /// Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a plugin to detach + virtual void DetachPlugin( PluginInterface *messageHandler )=0; + + // --------------------------------------------------------------------------------------------Miscellaneous Functions-------------------------------------------------------------------------------------------- + /// Put a message back at the end of the receive queue in case you don't want to deal with it immediately + /// \param[in] packet The packet you want to push back. + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + virtual void PushBackPacket( Packet *packet, bool pushAtHead )=0; + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + virtual void SetRouterInterface( RouterInterface *routerInterface )=0; + + /// \Internal + // \param[in] routerInterface The router to use to route messages to systems not directly connected to this system. + virtual void RemoveRouterInterface( RouterInterface *routerInterface )=0; + + // --------------------------------------------------------------------------------------------Network Simulator Functions-------------------------------------------------------------------------------------------- + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS Maximum bits per second to send. Packetloss grows linearly. 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + virtual void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance)=0; + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + virtual bool IsNetworkSimulatorActive( void )=0; + + // --------------------------------------------------------------------------------------------Statistical Functions - Functions dealing with API performance-------------------------------------------------------------------------------------------- + + /// Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] playerId: Which connected system to get statistics for + /// \return 0 on can't find the specified system. A pointer to a set of data otherwise. + /// \sa RakNetStatistics.h + virtual RakNetStatisticsStruct * const GetStatistics( const PlayerID playerId )=0; + + // --------------------------------------------------------------------------------------------EVERYTHING AFTER THIS COMMENT IS FOR INTERNAL USE ONLY-------------------------------------------------------------------------------------------- + /// \internal + virtual RPCMap *GetRPCMap( const PlayerID playerId)=0; + }; diff --git a/raknet/RakServer.cpp b/raknet/RakServer.cpp index a73523c..5155dbb 100644 --- a/raknet/RakServer.cpp +++ b/raknet/RakServer.cpp @@ -1,82 +1,291 @@ -// TODO: Implement RakServer.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "RakServer.h" +#include "PacketEnumerations.h" +#include "GetTime.h" +#include "Rand.h" + +#ifdef _MSC_VER +#pragma warning( push ) +#endif RakServer::RakServer() { - // TODO: RakServer ctor saco .text:100436F0 server W .text:0045AC20 L .text:0807B820 bot L .text:08081062 + nextSeedUpdate = 0; + synchronizedRandomInteger = false; + relayStaticClientData = false; + broadcastPingsTime = 0; } -void RakServer::vftable_0() +RakServer::~RakServer() +{} + +void RakServer::InitializeSecurity( const char *pubKeyE, const char *pubKeyN ) { - // TODO: RakServer::vftable_0() (server W: 45AC90 L: 807B8C0) + RakPeer::InitializeSecurity( 0, 0, pubKeyE, pubKeyN ); } +void RakServer::DisableSecurity( void ) +{ + RakPeer::DisableSecurity(); +} + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: 'depreciated' : unreferenced formal parameter +#endif bool RakServer::Start( unsigned short AllowedPlayers, unsigned int depreciated, int threadSleepTimer, unsigned short port, const char *forceHostAddress ) { bool init; + RakPeer::Disconnect( 30 ); + + init = RakPeer::Initialize( AllowedPlayers, port, threadSleepTimer, forceHostAddress ); + RakPeer::SetMaximumIncomingConnections( AllowedPlayers ); + + // Random number seed + RakNetTime time = RakNet::GetTime(); + seedMT( (unsigned int) time ); + seed = randomMT(); + + if ( seed % 2 == 0 ) // Even + seed--; // make odd + + nextSeed = randomMT(); + + if ( nextSeed % 2 == 0 ) // Even + nextSeed--; // make odd - init=false; - - // TODO: RakServer::vftable_4() (server W: 45A130 L: 807B900) - return init; } -void RakServer::vftable_8() -{ - // TODO: RakServer::vftable_8() (server W: 45A100 L: 807B990) -} - -void RakServer::vftable_C() -{ - // TODO: RakServer::vftable_C() (server W: 45A120 L: 807B950) -} - void RakServer::SetPassword( const char *_password ) { - // TODO: RakServer::vftable_10() (server W: 45A1B0 L: 807B980) + if ( _password ) + { + RakPeer::SetIncomingPassword( _password, ( int ) strlen( _password ) + 1 ); + } + + else + { + RakPeer::SetIncomingPassword( 0, 0 ); + } } -void RakServer::vftable_14() +bool RakServer::HasPassword( void ) { - // TODO: RakServer::vftable_14() (server W: 45A1F0 L: 807BA40) + int passwordLength; + GetIncomingPassword(0, &passwordLength); + return passwordLength > 0; } void RakServer::Disconnect( unsigned int blockDuration, unsigned char orderingChannel ) { - // TODO: RakServer::vftable_18() (server W: 45A210 L: 807BAB0) + RakPeer::Disconnect( blockDuration, orderingChannel ); } -void RakServer::vftable_1C() +bool RakServer::Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ) { - // TODO: RakServer::vftable_1C() (server W: 45A260 L: 807BAE0) + return RakPeer::Send( data, length, priority, reliability, orderingChannel, playerId, broadcast ); } -void RakServer::vftable_20() +bool RakServer::Send( RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ) { - // TODO: RakServer::vftable_20() (server W: 45A220 L: 807BB00) + return RakPeer::Send( bitStream, priority, reliability, orderingChannel, playerId, broadcast ); } -void RakServer::vftable_24() +Packet* RakServer::Receive( void ) { - // TODO: RakServer::vftable_24() (server W: 45ACB0 L: 807BB60) + Packet * packet = RakPeer::Receive(); + + // This is just a regular time based update. Nowhere else good to put it + + if ( RakPeer::IsActive() && occasionalPing ) + { + RakNetTime time = RakNet::GetTime(); + + if ( time > broadcastPingsTime || ( packet && packet->data[ 0 ] == ID_RECEIVED_STATIC_DATA ) ) + { + if ( time > broadcastPingsTime ) + broadcastPingsTime = time + 30000; // Broadcast pings every 30 seconds + + unsigned i, count; + + RemoteSystemStruct *remoteSystem; + RakNet::BitStream bitStream( ( PlayerID_Size + sizeof( short ) ) * 32 + sizeof(unsigned char) ); + unsigned char typeId = ID_BROADCAST_PINGS; + + bitStream.Write( typeId ); + + //for ( i = 0, count = 0; count < 32 && i < remoteSystemListSize; i++ ) + for ( i = 0, count = 0; count < 32 && i < maximumNumberOfPeers; i++ ) + { + remoteSystem = remoteSystemList + i; + + if ( remoteSystem->playerId != UNASSIGNED_PLAYER_ID && remoteSystem->isActive) + { + bitStream.Write( remoteSystem->playerId.binaryAddress ); + bitStream.Write( remoteSystem->playerId.port ); + bitStream.Write( remoteSystem->pingAndClockDifferential[ remoteSystem->pingAndClockDifferentialWriteIndex ].pingTime ); + count++; + } + } + + if ( count > 0 ) // If we wrote anything + { + + if ( packet && packet->data[ 0 ] == ID_NEW_INCOMING_CONNECTION ) // If this was a new connection + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, false ); // Send to the new connection + else + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, UNASSIGNED_PLAYER_ID, true ); // Send to everyone + } + } + } + + // This is just a regular time based update. Nowhere else good to put it + if ( RakPeer::IsActive() && synchronizedRandomInteger ) + { + RakNetTime time = RakNet::GetTime(); + + if ( time > nextSeedUpdate || ( packet && packet->data[ 0 ] == ID_NEW_INCOMING_CONNECTION ) ) + { + if ( time > nextSeedUpdate ) + nextSeedUpdate = time + 9000; // Seeds are updated every 9 seconds + + seed = nextSeed; + + nextSeed = randomMT(); + + if ( nextSeed % 2 == 0 ) // Even + nextSeed--; // make odd + + /* SetRandomNumberSeedStruct s; + + s.ts = ID_TIMESTAMP; + s.timeStamp = RakNet::GetTime(); + s.typeId = ID_SET_RANDOM_NUMBER_SEED; + s.seed = seed; + s.nextSeed = nextSeed; + RakNet::BitStream s_BitS( SetRandomNumberSeedStruct_Size ); + s.Serialize( s_BitS ); + */ + + RakNet::BitStream outBitStream(sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned char)+sizeof(unsigned int)+sizeof(unsigned int)); + outBitStream.Write((unsigned char) ID_TIMESTAMP); + outBitStream.Write((unsigned int) RakNet::GetTime()); + outBitStream.Write((unsigned char) ID_SET_RANDOM_NUMBER_SEED); + outBitStream.Write(seed); + outBitStream.Write(nextSeed); + + if ( packet && packet->data[ 0 ] == ID_NEW_INCOMING_CONNECTION ) + Send( &outBitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, false ); + else + Send( &outBitStream, SYSTEM_PRIORITY, RELIABLE, 0, UNASSIGNED_PLAYER_ID, true ); + } + } + + if ( packet ) + { + // Intercept specific client / server feature packets. This will do an extra send and still pass on the data to the user + + if ( packet->data[ 0 ] == ID_RECEIVED_STATIC_DATA ) + { + if ( relayStaticClientData ) + { + // Relay static data to the other systems but the sender + RakNet::BitStream bitStream( packet->length + PlayerID_Size ); + unsigned char typeId = ID_REMOTE_STATIC_DATA; + bitStream.Write( typeId ); + bitStream.Write( packet->playerId.binaryAddress ); + bitStream.Write( packet->playerId.port ); + bitStream.Write( packet->playerIndex ); + bitStream.Write( ( char* ) packet->data + sizeof(unsigned char), packet->length - sizeof(unsigned char) ); + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, true ); + } + } + + else + if ( packet->data[ 0 ] == ID_DISCONNECTION_NOTIFICATION || packet->data[ 0 ] == ID_CONNECTION_LOST || packet->data[ 0 ] == ID_NEW_INCOMING_CONNECTION ) + { + // Relay the disconnection + RakNet::BitStream bitStream( packet->length + PlayerID_Size ); + unsigned char typeId; + + if ( packet->data[ 0 ] == ID_DISCONNECTION_NOTIFICATION ) + typeId = ID_REMOTE_DISCONNECTION_NOTIFICATION; + else + if ( packet->data[ 0 ] == ID_CONNECTION_LOST ) + typeId = ID_REMOTE_CONNECTION_LOST; + else + typeId = ID_REMOTE_NEW_INCOMING_CONNECTION; + + bitStream.Write( typeId ); + bitStream.Write( packet->playerId.binaryAddress ); + bitStream.Write( packet->playerId.port ); + bitStream.Write( ( unsigned short& ) packet->playerIndex ); + + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, true ); + + if ( packet->data[ 0 ] == ID_NEW_INCOMING_CONNECTION ) + { + unsigned i; + + //for ( i = 0; i < remoteSystemListSize; i++ ) + for ( i = 0; i < maximumNumberOfPeers; i++ ) + { + if ( remoteSystemList[ i ].isActive && remoteSystemList[ i ].playerId != UNASSIGNED_PLAYER_ID && packet->playerId != remoteSystemList[ i ].playerId ) + { + bitStream.Reset(); + typeId = ID_REMOTE_EXISTING_CONNECTION; + bitStream.Write( typeId ); + bitStream.Write( remoteSystemList[ i ].playerId.binaryAddress ); + bitStream.Write( remoteSystemList[ i ].playerId.port ); + bitStream.Write( ( unsigned short ) i ); + // One send to tell them of the connection + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, false ); + + if ( relayStaticClientData ) + { + bitStream.Reset(); + typeId = ID_REMOTE_STATIC_DATA; + bitStream.Write( typeId ); + bitStream.Write( remoteSystemList[ i ].playerId.binaryAddress ); + bitStream.Write( remoteSystemList[ i ].playerId.port ); + bitStream.Write( (unsigned short) i ); + bitStream.Write( ( char* ) remoteSystemList[ i ].staticData.GetData(), remoteSystemList[ i ].staticData.GetNumberOfBytesUsed() ); + // Another send to tell them of the static data + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, packet->playerId, false ); + } + } + } + } + } + } + + return packet; } -void RakServer::vftable_28() +void RakServer::Kick( const PlayerID playerId ) { - // TODO: RakServer::vftable_28() (server W: 45AD00 L: 807BBB0) + RakPeer::CloseConnection(playerId, true, 0); } -void RakServer::vftable_2C() +void RakServer::DeallocatePacket( Packet *packet ) { - // TODO: RakServer::vftable_2C() (server W: 45A2A0 L: 807BC30) -} - -void RakServer::vftable_30() -{ - // TODO: RakServer::vftable_30() (server W: 45A2D0 L: 807BE80) + RakPeer::DeallocatePacket( packet ); } void RakServer::SetAllowedPlayers( unsigned short AllowedPlayers ) @@ -89,74 +298,78 @@ unsigned short RakServer::GetAllowedPlayers( void ) const return RakPeer::GetMaximumIncomingConnections(); } -void RakServer::vftable_3C() +unsigned short RakServer::GetConnectedPlayers( void ) { - // TODO: RakServer::vftable_3C() (server W: 45A300 L: 807BF00) + unsigned short numberOfSystems; + + RakPeer::GetConnectionList( 0, &numberOfSystems ); + return numberOfSystems; } -void RakServer::vftable_40() +void RakServer::GetPlayerIPFromID( const PlayerID playerId, char returnValue[ 22 ], unsigned short *port ) { - // TODO: RakServer::vftable_40() (server W: 45A320 L: 807BF20) + *port = playerId.port; + strcpy( returnValue, RakPeer::PlayerIDToDottedIP( playerId ) ); } -void RakServer::vftable_44() +void RakServer::PingPlayer( const PlayerID playerId ) { - // TODO: RakServer::vftable_44() (server W: 45A360 L: 807BF50) + RakPeer::Ping( playerId ); } -void RakServer::vftable_48() +int RakServer::GetAveragePing( const PlayerID playerId ) { - // TODO: RakServer::vftable_48() (server W: 45A380 L: 807BF90) + return RakPeer::GetAveragePing( playerId ); } -void RakServer::vftable_4C() +int RakServer::GetLastPing( const PlayerID playerId ) { - // TODO: RakServer::vftable_4C() (server W: 45A3A0 L: 807BFA0) + return RakPeer::GetLastPing( playerId ); } -void RakServer::vftable_50() +int RakServer::GetLowestPing( const PlayerID playerId ) { - // TODO: RakServer::vftable_50() (server W: 45A3C0 L: 807BFB0) + return RakPeer::GetLowestPing( playerId ); } -void RakServer::vftable_54() +void RakServer::StartOccasionalPing( void ) { - // TODO: RakServer::vftable_54() (server W: 45A3E0 L: 807BFC0) + RakPeer::SetOccasionalPing( true ); } -void RakServer::vftable_58() +void RakServer::StopOccasionalPing( void ) { - // TODO: RakServer::vftable_58() (server W: 45A3F0 L: 807BFD0) + RakPeer::SetOccasionalPing( false ); } -void RakServer::vftable_5C() +bool RakServer::IsActive( void ) const { - // TODO: RakServer::vftable_5C() (server W: 45A400 L: 807BFF0) + return RakPeer::IsActive(); } -void RakServer::vftable_60() +unsigned int RakServer::GetSynchronizedRandomInteger( void ) const { - // TODO: RakServer::vftable_60() (server W: 45A410 L: 807C010) + return seed; } -void RakServer::vftable_64() +void RakServer::StartSynchronizedRandomInteger( void ) { - // TODO: RakServer::vftable_64() (server W: 45A420 L: 807C030) + synchronizedRandomInteger = true; } -void RakServer::vftable_68() +void RakServer::StopSynchronizedRandomInteger( void ) { - // TODO: RakServer::vftable_68() (server W: 45A430 L: 807C040) + synchronizedRandomInteger = false; } -void RakServer::vftable_6C() +bool RakServer::GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ) { - // TODO: RakServer::vftable_6C() (server W: 45A440 L: 807C050) + return RakPeer::GenerateCompressionLayer( inputFrequencyTable, inputLayer ); } -void RakServer::vftable_70() +bool RakServer::DeleteCompressionLayer( bool inputLayer ) { - // TODO: RakServer::vftable_70() (server W: 45A450 L: 807C060) + return RakPeer::DeleteCompressionLayer( inputLayer ); } void RakServer::RegisterAsRemoteProcedureCall( char* uniqueID, void ( *functionPointer ) ( RPCParameters *rpcParms ) ) @@ -174,212 +387,203 @@ void RakServer::UnregisterAsRemoteProcedureCall( char* uniqueID ) RakPeer::UnregisterAsRemoteProcedureCall( uniqueID ); } -void RakServer::vftable_80() +bool RakServer::RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakServer::vftable_80() (server W: 45A5B0 L: 807C100) + return RakPeer::RPC( uniqueID, data, bitLength, priority, reliability, orderingChannel, playerId, broadcast, shiftTimestamp, networkID, replyFromTarget ); } -void RakServer::vftable_84() +bool RakServer::RPC( char* uniqueID, RakNet::BitStream *parameters, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ) { - // TODO: RakServer::vftable_84() (server W: 45A4F0 L: 807C120) + return RakPeer::RPC( uniqueID, parameters, priority, reliability, orderingChannel, playerId, broadcast, shiftTimestamp, networkID, replyFromTarget ); } -void RakServer::vftable_88() +void RakServer::SetTrackFrequencyTable( bool b ) { - // TODO: RakServer::vftable_88() (server W: 45A490 L: 807C1A0) + RakPeer::SetCompileFrequencyTable( b ); } -void RakServer::vftable_8C() +bool RakServer::GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ) { - // TODO: RakServer::vftable_8C() (server W: 45A550 L: 807C2A0) + return RakPeer::GetOutgoingFrequencyTable( outputFrequencyTable ); } -void RakServer::vftable_90() +float RakServer::GetCompressionRatio( void ) const { - // TODO: RakServer::vftable_90() (server W: 45A610 L: 807C220) + return RakPeer::GetCompressionRatio(); } -void RakServer::vftable_94() +float RakServer::GetDecompressionRatio( void ) const { - // TODO: RakServer::vftable_94() (server W: 45A640 L: 807C320) + return RakPeer::GetDecompressionRatio(); } -void RakServer::vftable_98() +void RakServer::AttachPlugin( PluginInterface *messageHandler ) { - // TODO: RakServer::vftable_98() (server W: 45A650 L: 807C360) + RakPeer::AttachPlugin(messageHandler); } -void RakServer::vftable_9C() +void RakServer::DetachPlugin( PluginInterface *messageHandler ) { - // TODO: RakServer::vftable_9C() (server W: 45A660 L: 807C380) + RakPeer::DetachPlugin(messageHandler); } -void RakServer::vftable_A0() +RakNet::BitStream * RakServer::GetStaticServerData( void ) { - // TODO: RakServer::vftable_A0() (server W: 45A670 L: 807C3A0) + return RakPeer::GetRemoteStaticData( myPlayerId ); } -void RakServer::vftable_A4() +void RakServer::SetStaticServerData( const char *data, const int length ) { - // TODO: RakServer::vftable_A4() (server W: 45A680 L: 807C3B0) + RakPeer::SetRemoteStaticData( myPlayerId, data, length ); } -void RakServer::vftable_A8() +void RakServer::SetRelayStaticClientData( bool b ) { - // TODO: RakServer::vftable_A8() (server W: 45A690 L: 807C3C0) + relayStaticClientData = b; } -void RakServer::vftable_AC() +void RakServer::SendStaticServerDataToClient( const PlayerID playerId ) { - // TODO: RakServer::vftable_AC() (server W: 45A6A0 L: 807C3E0) + RakPeer::SendStaticData( playerId ); } -void RakServer::vftable_B0() +void RakServer::SetOfflinePingResponse( const char *data, const unsigned int length ) { - // TODO: RakServer::vftable_B0() (server W: 45A6D0 L: 807C400) + RakPeer::SetOfflinePingResponse( data, length ); } -void RakServer::vftable_B4() +RakNet::BitStream * RakServer::GetStaticClientData( const PlayerID playerId ) { - // TODO: RakServer::vftable_B4() (server W: 45A700 L: 807C430) + return RakPeer::GetRemoteStaticData( playerId ); } -void RakServer::vftable_B8() +void RakServer::SetStaticClientData( const PlayerID playerId, const char *data, const int length ) { - // TODO: RakServer::vftable_B8() (server W: 45A710 L: 807C470) + RakPeer::SetRemoteStaticData( playerId, data, length ); } -void RakServer::vftable_BC() +// This will read the data from playerChangedId and send it to playerToSendToId +void RakServer::ChangeStaticClientData( const PlayerID playerChangedId, PlayerID playerToSendToId ) { - // TODO: RakServer::vftable_BC() (server W: 45A730 L: 807C490) + RemoteSystemStruct * remoteSystem = GetRemoteSystemFromPlayerID( playerChangedId, false, true ); + + if ( remoteSystem == 0 ) + return ; // No such playerChangedId + + // Relay static data to the other systems but the sender + RakNet::BitStream bitStream; + + unsigned char typeId = ID_REMOTE_STATIC_DATA; + + bitStream.Write( typeId ); + + bitStream.Write( playerChangedId.binaryAddress ); + + bitStream.Write( playerChangedId.port ); + + bitStream.Write( ( char* ) remoteSystem->staticData.GetData(), remoteSystem->staticData.GetNumberOfBytesUsed() ); + + Send( &bitStream, SYSTEM_PRIORITY, RELIABLE, 0, playerToSendToId, true ); } -void RakServer::vftable_C0() +unsigned int RakServer::GetNumberOfAddresses( void ) { - // TODO: RakServer::vftable_C0() (server W: 45A740 L: 807C4A0) + return RakPeer::GetNumberOfAddresses(); } -void RakServer::vftable_C4() +PlayerID RakServer::GetInternalID( void ) const { - // TODO: RakServer::vftable_C4() (server W: 45A760 L: 807C4C0) + return RakPeer::GetInternalID(); } -void RakServer::vftable_C8() +void RakServer::PushBackPacket( Packet *packet, bool pushAtHead ) { - // TODO: RakServer::vftable_C8() (server W: 45AEC0 L: 807C4D0) + RakPeer::PushBackPacket(packet, pushAtHead); +} +void RakServer::SetRouterInterface( RouterInterface *routerInterface ) +{ + RakPeer::SetRouterInterface(routerInterface); +} +void RakServer::RemoveRouterInterface( RouterInterface *routerInterface ) +{ + RakPeer::RemoveRouterInterface(routerInterface); } -void RakServer::vftable_CC() +const char* RakServer::GetLocalIP( unsigned int index ) { - // TODO: RakServer::vftable_CC() (server W: 45A790 L: 807C4F0) + return RakPeer::GetLocalIP( index ); } -void RakServer::vftable_D0() +int RakServer::GetIndexFromPlayerID( const PlayerID playerId ) { - // TODO: RakServer::vftable_D0() (server W: 45A7F0 L: 807C620) + return RakPeer::GetIndexFromPlayerID( playerId ); } -void RakServer::vftable_D4() +PlayerID RakServer::GetPlayerIDFromIndex( int index ) { - // TODO: RakServer::vftable_D4() (server W: 45A7A0 L: 807C6C0) + return RakPeer::GetPlayerIDFromIndex( index ); } -void RakServer::vftable_D8() +void RakServer::AddToBanList( const char *IP ) { - // TODO: RakServer::vftable_D8() (server W: 45A7C0 L: 807C630) + RakPeer::AddToBanList( IP ); } -void RakServer::vftable_DC() +void RakServer::RemoveFromBanList( const char *IP ) { - // TODO: RakServer::vftable_DC() (server W: 45A7D0 L: 807C660) + RakPeer::RemoveFromBanList( IP ); } -void RakServer::vftable_E0() +void RakServer::ClearBanList( void ) { - // TODO: RakServer::vftable_E0() (server W: 45A7E0 L: 807C680) + RakPeer::ClearBanList(); } -void RakServer::vftable_E4() +bool RakServer::IsBanned( const char *IP ) { - // TODO: RakServer::vftable_E4() (server W: 45A800 L: 807C6A0) + return RakPeer::IsBanned( IP ); } -void RakServer::vftable_E8() +bool RakServer::IsActivePlayerID( const PlayerID playerId ) { - // TODO: RakServer::vftable_E8() (server W: 45A820 L: 807C6E0) + return RakPeer::GetRemoteSystemFromPlayerID( playerId, false, true ) != 0; } -void RakServer::vftable_EC() +void RakServer::SetTimeoutTime( RakNetTime timeMS, const PlayerID target ) { - // TODO: RakServer::vftable_EC() (server W: 45A840 L: 807C6F0) + RakPeer::SetTimeoutTime(timeMS, target); } -void RakServer::vftable_F0() +bool RakServer::SetMTUSize( int size ) { - // TODO: RakServer::vftable_F0() (server W: 45A860 L: 807C720) + return RakPeer::SetMTUSize( size ); } -void RakServer::vftable_F4() +int RakServer::GetMTUSize( void ) const { - // TODO: RakServer::vftable_F4() (server W: 45A870 L: 807C750) + return RakPeer::GetMTUSize(); } -void RakServer::vftable_F8() +void RakServer::AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ) { - // TODO: RakServer::vftable_F8() (server W: 45A880 L: 807C770) + RakPeer::AdvertiseSystem( host, remotePort, data, dataLength ); } -void RakServer::vftable_FC() +RakNetStatisticsStruct * const RakServer::GetStatistics( const PlayerID playerId ) { - // TODO: RakServer::vftable_FC() (server W: 45A890 L: 807C790) + return RakPeer::GetStatistics( playerId ); } -void RakServer::vftable_100() +void RakServer::ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance) { - // TODO: RakServer::vftable_100() (server W: 45A8A0 L: 807C7A0) + RakPeer::ApplyNetworkSimulator( maxSendBPS, minExtraPing, extraPingVariance ); } -void RakServer::vftable_104() +bool RakServer::IsNetworkSimulatorActive( void ) { - // TODO: RakServer::vftable_104() (server W: 45A8D0 L: 807C7C0) + return RakPeer::IsNetworkSimulatorActive(); } -void RakServer::vftable_108() -{ - // TODO: RakServer::vftable_108() (server W: 45A900 L: 807C800) -} - -void RakServer::vftable_10C() -{ - // TODO: RakServer::vftable_10C() (server W: 45A910 L: 807C820) -} - -void RakServer::vftable_110() -{ - // TODO: RakServer::vftable_110() (server W: 45A920 L: 807C840) -} - -void RakServer::vftable_114() -{ - // TODO: RakServer::vftable_114() (server W: 45A930 L: 807C850) -} - -void RakServer::vftable_118() -{ - // TODO: RakServer::vftable_118() (server W: 45A950 L: 807C870) -} - -void RakServer::vftable_11C() -{ - // TODO: RakServer::vftable_11C() (server W: 45A980 L: 807C880) -} - -void RakServer::vftable_120() -{ - // TODO: RakServer::vftable_120() (server W: 45A9A0 L: 807C8A0) -} - -void RakServer::vftable_124() -{ - // TODO: RakServer::vftable_124() (server L: 0807C8C0) -} +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/RakServer.h b/raknet/RakServer.h index 2348920..852ec01 100644 --- a/raknet/RakServer.h +++ b/raknet/RakServer.h @@ -1,4 +1,21 @@ -// TODO: Implement RakServer.h +/// \file +/// \brief Specializes RakPeer to act as a server. +/// +/// \remarks This file originated from my first game, where I only had a server and a client. Now that I have RakPeer, it's depreciated although in such wide use I can't delete it. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_SERVER_H #define __RAK_SERVER_H @@ -7,6 +24,8 @@ #include "RakServerInterface.h" #include "Export.h" +// #pragma deprecated(RakServer) + /// This is a user-interface class to act as a game server. All it does is implement some functionality on top of RakPeer. /// See the individual functions for what the class can do. /// \brief Defines the functions used by a game server @@ -17,7 +36,8 @@ public: ///Constructor RakServer(); - void vftable_0(); + ///Destructor + virtual ~RakServer(); /// Call this to initiate the server with the number of players you want to be allowed connected at once /// \param[in] AllowedPlayers Current maximum number of allowed players is 65535 @@ -27,54 +47,156 @@ public: /// \param[in] forceHostAddress Can force RakNet to use a particular IP to host on. Pass 0 to automatically pick an IP /// \return true on successful initiation, false otherwise bool Start( unsigned short AllowedPlayers, unsigned int depreciated, int threadSleepTimer, unsigned short port, const char *forceHostAddress=0 ); + + /// Must be called while offlineSecures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to preventconnection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks.There is a significant amount of processing and a slight amount of bandwidthoverhead for this feature.If you accept connections, you must call this or else secure connections will not be enabledfor incoming connections. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + /// \param[in] privateKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] privateKeyN A pointer to the public keys from the RSACrypt class. + void InitializeSecurity( const char *privateKeyE, const char *privateKeyN ); - void vftable_8(); - void vftable_C(); - + /// Disables all security. + /// \pre Must be called while offline + void DisableSecurity( void ); + /// Set the password clients have to use to connect to this server. The password persists between connections. /// Pass 0 for no password. You can call this anytime /// \param[in] _password The password string, or 0 for none. void SetPassword( const char *_password ); - - void vftable_14(); - + + /// Returns if non-zero was passed to SetPassword() + // \return true if a password was set, false if not. + bool HasPassword( void ); + /// Stops the server, stops synchronized data, and resets all internal data. This will drop all players currently connected, howeversince the server is stopped packet reliability is not enforced so the Kick network message may not actuallyarrive. Those players will disconnect due to timeout. If you want to end the server more gracefully, youcan manually Kick each player first. Does nothing if the server is not running to begin with /// \param[in] blockDuration The maximum amount of time to wait for all remaining packets to go out, including the disconnection notification. If you set it to 0 then the disconnection notifications probably won't arrive /// \param[in] orderingChannel If blockDuration > 0, the disconnect packet will be sent on this channel void Disconnect( unsigned int blockDuration, unsigned char orderingChannel=0 ); - - void vftable_1C(); - void vftable_20(); - void vftable_24(); - void vftable_28(); - void vftable_2C(); - void vftable_30(); - + + /// /pre The server must be active. + /// Send the data stream of length \a length to whichever \a playerId you specify. + /// \param[in] data The data to send + /// \param[in] length The length, in bytes, of \a data + /// \param[in] priority See PacketPriority + /// \param[in] reliability See PacketReliabilty + /// \param[in] orderingChannel The ordering channel to use, from 0 to 31. Ordered or sequenced packets sent on the channel arrive ordered or sequence in relation to each other. See the manual for more details on this. + /// \param[in] playerId Who to send to. Specify UNASSIGNED_PLAYER_ID to designate all connected systems. + /// \param[in] broadcast Whether to send to everyone or not. If true, then the meaning of \a playerId changes to mean who NOT to send to. + /// \return Returns false on failure, true on success + bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ); + + /// /pre The server must be active. + /// Send the data stream of length \a length to whichever \a playerId you specify. + /// \param[in] bitStream The bitstream to send. + /// \param[in] priority See PacketPriority + /// \param[in] reliability See PacketReliabilty + /// \param[in] orderingChannel The ordering channel to use, from 0 to 31. Ordered or sequenced packets sent on the channel arrive ordered or sequence in relation to each other. See the manual for more details on this. + /// \param[in] playerId Who to send to. Specify UNASSIGNED_PLAYER_ID to designate all connected systems. + /// \param[in] broadcast Whether to send to everyone or not. If true, then the meaning of \a playerId changes to mean who NOT to send to. + /// \return Returns false on failure, true on success + bool Send( RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast ); + + /// Gets a packet from the incoming packet queue. + /// Use DeallocatePacket() to deallocate the packet after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + // sa CoreNetworkStructures.h contains struct Packet + Packet* Receive( void ); + + /// Kick out the specified player. + /// \param[in] playerId The playerId of the player to kcik. + void Kick( const PlayerID playerId ); + + /// Call this to deallocate a packet returned by Receive when you are done handling it. + /// \param[in] packet The packet to deallocate. + void DeallocatePacket( Packet *packet ); + /// Set how many players are allowed on the server. /// If more players are currently connected then are allowed then no more players will be allowed to join until the number of players is less than the number of allowed players. /// \pre The server must be active for this to have meaning /// \param[in] AllowedPlayers The number of players to allow void SetAllowedPlayers( unsigned short AllowedPlayers ); - + /// Return how many players are allowed to connect. This value was set either from Start or from SetAllowedPlayers. /// \pre The server must be active for this to have meaning /// \return The number of allowed players unsigned short GetAllowedPlayers( void ) const; + + ///Return how many players are currently connected to the server. + /// \pre The server must be active for this to have meaning + /// \return The number of connected players. + unsigned short GetConnectedPlayers( void ); + + /// Returns a static string pointer containing the IP of the specified connected player. + /// Also returns the client port. + /// This changes between calls so be sure to copy what is returned if you need to retain it. + /// Useful for creating and maintaining ban lists. + /// \pre The server must be active for this to have meaning + /// If the specified id does not represent an active player the results are undefined (most likely returns 0) + /// \param[in] playerId Which player we are referring to + /// \param[out] returnValue The IP of this player we are referring to + /// \param[out] port The port of the player we are referring to. + void GetPlayerIPFromID( const PlayerID playerId, char returnValue[ 22 ], unsigned short *port ); + + /// Send a ping request to the specified player + /// \param[in] playerId Which player we are referring to + void PingPlayer( const PlayerID playerId ); + + /// Returns the average of all ping times read for the specific player or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The ping time for this player, or -1 + int GetAveragePing( const PlayerID playerId ); + + /// Returns the last ping time read for the specific player or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The last ping time for this player, or -1 + int GetLastPing( const PlayerID playerId ); + + /// Returns the lowest ping time read or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The lowest ping time for this player, or -1 + int GetLowestPing( const PlayerID playerId ); + + /// Ping all players every so often. This is on by default. In games where you don't care about ping you can callStopOccasionalPing to save the bandwidth. + /// This can be called anytime. + void StartOccasionalPing( void ); + + /// Stop pinging players every so often. Players are pinged by default. In games where you don't care about ping you can call this to save the bandwidthThis will work anytime + void StopOccasionalPing( void ); + + /// Returns true if the server is currently active + bool IsActive( void ) const; + + /// Returns a number automatically synchronized between the server and client which randomly changes every 9 seconds. + /// The time it changes is accurate to within a few ms and is best used to seed random number generators that you want to usually + /// return the same output on all systems. + /// Keep in mind thisisn't perfectly accurate as there is always a very small chance the numbers will by out of synch. + /// You should should confine its use to visual effects or functionality that has a backup method to maintain synchronization. + /// If you don't need this functionality and want to save the bandwidth callStopSynchronizedRandomInteger after starting the server + /// \return A number, which is probably synchronized among all clients and the server. + unsigned int GetSynchronizedRandomInteger( void ) const; + + /// Start or restart the synchronized random integer. This is on by default. Call StopSynchronizedRandomIntegerto stop it keeping the number in synch + void StartSynchronizedRandomInteger( void ); + + /// Stop the synchronized random integer. Call StartSynchronizedRandomInteger to start it again + void StopSynchronizedRandomInteger( void ); - void vftable_3C(); - void vftable_40(); - void vftable_44(); - void vftable_48(); - void vftable_4C(); - void vftable_50(); - void vftable_54(); - void vftable_58(); - void vftable_5C(); - void vftable_60(); - void vftable_64(); - void vftable_68(); - void vftable_6C(); - void vftable_70(); + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer ); + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + bool DeleteCompressionLayer( bool inputLayer ); /// \ingroup RAKNET_RPC /// Register a C or static member function as available for calling as a remote procedure call @@ -94,49 +216,236 @@ public: /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameterpassed to RegisterAsRemoteProcedureCall void UnregisterAsRemoteProcedureCall( char* uniqueID ); - void vftable_80(); - void vftable_84(); - void vftable_88(); - void vftable_8C(); - void vftable_90(); - void vftable_94(); - void vftable_98(); - void vftable_9C(); - void vftable_A0(); - void vftable_A4(); - void vftable_A8(); - void vftable_AC(); - void vftable_B0(); - void vftable_B4(); - void vftable_B8(); - void vftable_BC(); - void vftable_C0(); - void vftable_C4(); - void vftable_C8(); - void vftable_CC(); - void vftable_D0(); - void vftable_D4(); - void vftable_D8(); - void vftable_DC(); - void vftable_E0(); - void vftable_E4(); - void vftable_E8(); - void vftable_EC(); - void vftable_F0(); - void vftable_F4(); - void vftable_F8(); - void vftable_FC(); - void vftable_100(); - void vftable_104(); - void vftable_108(); - void vftable_10C(); - void vftable_110(); - void vftable_114(); - void vftable_118(); - void vftable_11C(); - void vftable_120(); - void vftable_124(); + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget ); + + /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// This value persists between connect calls and defaults to false (no frequency tracking) + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] b True to enable tracking + void SetTrackFrequencyTable( bool b ); + + /// Returns the frequency of outgoing bytes into outputFrequencyTable. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// The purpose is to save to file as either a master frequency table from a sample game session. + /// \pre You should only call this when disconnected + /// \pre Requires that you first enable data frequency tracking by calling SetTrackFrequencyTable(true) + /// \param[out] outputFrequencyTable The Frequency Table used in the compression layer + /// \return false (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + bool GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] ); + + /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data + /// \return The compression ratio + float GetCompressionRatio( void ) const; + + ///Returns the decompression ratio. A high decompression ratio is good. Decompression is for incoming data + /// \return The decompression ratio + float GetDecompressionRatio( void ) const; + + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a message handler to attach + void AttachPlugin( PluginInterface *messageHandler ); + + ///Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a message handler to detach + void DetachPlugin( PluginInterface *messageHandler ); + + ///The server internally maintains a data struct that is automatically sent to clients when they connect. + /// This is useful to contain data such as the server name or message of the day. + /// Access that struct with thisfunction. + /// \note If you change any data in the struct remote clients won't reflect this change unless you manually update themDo so by calling SendStaticServerDataToClient(UNASSIGNED_PLAYER_ID) + /// \return The static server data, stored in a bitstream + RakNet::BitStream * GetStaticServerData( void ); + + /// The server internally maintains a data struct that is automatically sent to clients when they connect. + /// This function will set that data. + /// \param[in] data The data to use as the static server data + /// \param[in] length The length, in bytes, of \a data + void SetStaticServerData( const char *data, const int length ); + + /// This sets to true or false whether we want to support relaying of static client data to other connected clients. + /// If set to false it saves some bandwdith, however other clients won't know the static client data and attempting to read that data will return garbage. + /// Default is false. This only works for up to 32 players. Games supporting more than 32 players will have this shut off automatically upon server start and must have it forced back on with this function + /// if you do indeed want it. + /// \pre This should be called after the server is started in case you want to override when it shuts off at 32 players + /// \param[in] b true or false, as you wish to set the function + void SetRelayStaticClientData( bool b ); + + /// Send the static server data to the specified player. + /// Pass UNASSIGNED_PLAYER_ID to broadcast to all players. + /// The only time you need to call this function is to update clients that are already connected when you change the static server data by calling GetStaticServerData() and directly modifying the object pointed to. + /// Obviously if theconnected clients don't need to know the new data you don't need to update them, so it's up to you. + /// \pre The server must be active for this to have meaning + /// \param[in] playerId The playerId we are referring to + void SendStaticServerDataToClient( const PlayerID playerId ); + + /// Sets the data to send along with a LAN server discovery or offline ping reply. + /// \a length should be under 400 bytes, as a security measure against flood attacks + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + /// \sa Ping.cpp + void SetOfflinePingResponse( const char *data, const unsigned int length ); + + /// Returns a pointer to an attached client's static data specified by the playerId. + /// Returns 0 if no such player is connected. + /// \note that you can modify the client data here. Changes won't be reflected on clients unless you force them to update by calling ChangeStaticClientData() + /// \pre The server must be active for this to have meaning + /// \param[in] playerId The ID of the client + /// \return A bitstream containing the static client data + RakNet::BitStream * GetStaticClientData( const PlayerID playerId ); + + /// Set the stait client data for a particular player + /// \param[in] playerId The ID of the client + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + void SetStaticClientData( const PlayerID playerId, const char *data, const int length ); + + /// This function is used to update the information on connected clients when the server effects a change of static client data. + /// Note that a client that gets this new information for himself will update the data for his playerID but not his local data (which is the user's copy) i.e. player 5 would have the data returned by GetStaticClientData(5) changed but his local information returned byGetStaticClientData(UNASSIGNED_PLAYER_ID) would remain the same as it was previously. + /// \param[in] playerChangedId The playerId of the player whose data was changed. This is the parameter passed toGetStaticClientData to get a pointer to that player's information. + /// \param[in] playerToSendToId The player you want to update with the new information. This will almost always be everybody, in which case you should pass UNASSIGNED_PLAYER_ID. + /// \pre The server must be active for this to have meaning + void ChangeStaticClientData( const PlayerID playerChangedId, PlayerID playerToSendToId ); + + /// Internally store the IP address(es) for the server and return how many it has. + /// This can easily be more than one, for example a system on both a LAN and with a net connection. + /// The server does not have to be active for this to work + unsigned int GetNumberOfAddresses( void ); + + /// Call this function where 0 <= index < x where x is the value returned by GetNumberOfAddresses() + /// Returns a static string filled with the server IP of the specified index. + /// Strings returned in no particular order. You'll have to check every index see which string you want. + /// \return 0 on invalid input, otherwise an dotted IP + const char* GetLocalIP( unsigned int index ); + + /// Return the unique address identifier that represents you on the the network and is based on your local IP / port. + /// \note Unlike in previous versions, PlayerID is a struct and is not sequential + PlayerID GetInternalID( void ) const; + + /// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately. + /// \param[in] packet The packet to push + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + void PushBackPacket( Packet *packet, bool pushAtHead ); + + /// \Internal + void SetRouterInterface( RouterInterface *routerInterface ); + + /// \Internal + void RemoveRouterInterface( RouterInterface *routerInterface ); + + /// Given a playerID, returns an index from 0 to the maximum number of players allowed - 1. + /// \param[in] playerId The PlayerID we are referring to + /// \return The index of this PlayerID + int GetIndexFromPlayerID( const PlayerID playerId ); + + /// This function is only useful for looping through all players. + /// Given an index, will return a PlayerID. + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + PlayerID GetPlayerIDFromIndex( int index ); + + /// Bans an IP from connecting. Banned IPs persist between connections. + /// param[in] IP Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + void AddToBanList( const char *IP ); + + /// Allows a previously banned IP to connect. + /// param[in] Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + void RemoveFromBanList( const char *IP ); + + /// Allows all previously banned IPs to connect. + void ClearBanList( void ); + + /// Returns true or false indicating if a particular IP is banned. + /// \param[in] IP - Dotted IP address. + /// \return true if IP matches any IPs in the ban list, accounting for any wildcards. false otherwise. + bool IsBanned( const char *IP ); + + /// Returns true if that player ID is currently active + /// \param[in] playerId Which playerId we are referring to + /// \return true or false, as noted + bool IsActivePlayerID( const PlayerID playerId ); + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target Which system to do this for + void SetTimeoutTime( RakNetTime timeMS, const PlayerID target ); + + /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + bool SetMTUSize( int size ); + + /// Returns the current MTU size + /// \return The current MTU size + int GetMTUSize( void ) const; + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data.. + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data to send along with ID_ADVERTISE_SYSTEM + /// \param[in] dataLength The length of \a data + void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength ); + + /// Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] playerId: Which connected system to get statistics for + /// \return 0 on can't find the specified system. A pointer to a set of data otherwise. + /// \sa RakNetStatistics.h + RakNetStatisticsStruct * const GetStatistics( const PlayerID playerId ); + + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS Maximum bits per second to send. Packetloss grows linearly. 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance); + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + virtual bool IsNetworkSimulatorActive( void ); + +private: + unsigned int seed, nextSeed; + RakNetTime broadcastPingsTime, nextSeedUpdate; + bool synchronizedRandomInteger, relayStaticClientData; }; #endif diff --git a/raknet/RakServerInterface.h b/raknet/RakServerInterface.h index f5680c3..30c2946 100644 --- a/raknet/RakServerInterface.h +++ b/raknet/RakServerInterface.h @@ -1,9 +1,28 @@ -// TODO: Implement RakServerInterface.h +/// \file +/// \brief An interface for RakServer. Simply contains all user functions as pure virtuals. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RAK_SERVER_INTERFACE_H #define __RAK_SERVER_INTERFACE_H #include "NetworkTypes.h" +#include "PacketPriority.h" +#include "RakPeerInterface.h" +#include "BitStream.h" +#include "RakNetStatistics.h" #include "Export.h" /// This is a user-interface class to act as a game server. All it does is implement some functionality on top of RakPeer. @@ -13,7 +32,8 @@ class RAK_DLL_EXPORT RakServerInterface { public: - virtual void vftable_0()=0; + ///Destructor + virtual ~RakServerInterface() {} /// Call this to initiate the server with the number of players you want to be allowed connected at once /// \param[in] AllowedPlayers Current maximum number of allowed players is 65535 @@ -23,55 +43,157 @@ public: /// \param[in] forceHostAddress Can force RakNet to use a particular IP to host on. Pass 0 to automatically pick an IP /// \return true on successful initiation, false otherwise virtual bool Start( unsigned short AllowedPlayers, unsigned int depreciated, int threadSleepTimer, unsigned short port, const char *forceHostAddress=0 )=0; + + /// Must be called while offlineSecures connections though a combination of SHA1, AES128, SYN Cookies, and RSA to preventconnection spoofing, replay attacks, data eavesdropping, packet tampering, and MitM attacks.There is a significant amount of processing and a slight amount of bandwidthoverhead for this feature.If you accept connections, you must call this or else secure connections will not be enabledfor incoming connections. If the private keys are 0, then a new key will be generated when this function is called@see the Encryption sample + /// \param[in] privateKeyE A pointer to the public keys from the RSACrypt class. + /// \param[in] privateKeyN A pointer to the public keys from the RSACrypt class. + virtual void InitializeSecurity( const char *privateKeyE, const char *privateKeyN )=0; - virtual void vftable_8()=0; - virtual void vftable_C()=0; - + /// Disables all security. + /// \pre Must be called while offline + virtual void DisableSecurity( void )=0; + /// Set the password clients have to use to connect to this server. The password persists between connections. /// Pass 0 for no password. You can call this anytime /// \param[in] _password The password string, or 0 for none. virtual void SetPassword( const char *_password )=0; - - virtual void vftable_14()=0; - + + /// Returns if non-zero was passed to SetPassword() + // \return true if a password was set, false if not. + virtual bool HasPassword( void )=0; + /// Stops the server, stops synchronized data, and resets all internal data. This will drop all players currently connected, howeversince the server is stopped packet reliability is not enforced so the Kick network message may not actuallyarrive. Those players will disconnect due to timeout. If you want to end the server more gracefully, youcan manually Kick each player first. Does nothing if the server is not running to begin with /// \param[in] blockDuration The maximum amount of time to wait for all remaining packets to go out, including the disconnection notification. If you set it to 0 then the disconnection notifications probably won't arrive /// \param[in] orderingChannel If blockDuration > 0, the disconnect packet will be sent on this channel virtual void Disconnect( unsigned int blockDuration, unsigned char orderingChannel=0 )=0; - - virtual void vftable_1C()=0; - virtual void vftable_20()=0; - virtual void vftable_24()=0; - virtual void vftable_28()=0; - virtual void vftable_2C()=0; - virtual void vftable_30()=0; - + + /// /pre The server must be active. + /// Send the data stream of length \a length to whichever \a playerId you specify. + /// \param[in] data The data to send + /// \param[in] length The length, in bytes, of \a data + /// \param[in] priority See PacketPriority + /// \param[in] reliability See PacketReliabilty + /// \param[in] orderingChannel The ordering channel to use, from 0 to 31. Ordered or sequenced packets sent on the channel arrive ordered or sequence in relation to each other. See the manual for more details on this. + /// \param[in] playerId Who to send to. Specify UNASSIGNED_PLAYER_ID to designate all connected systems. + /// \param[in] broadcast Whether to send to everyone or not. If true, then the meaning of \a playerId changes to mean who NOT to send to. + /// \return Returns false on failure, true on success + virtual bool Send( const char *data, const int length, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast )=0; + + /// /pre The server must be active. + /// Send the data stream of length \a length to whichever \a playerId you specify. + /// \param[in] bitStream The bitstream to send. + /// \param[in] priority See PacketPriority + /// \param[in] reliability See PacketReliabilty + /// \param[in] orderingChannel The ordering channel to use, from 0 to 31. Ordered or sequenced packets sent on the channel arrive ordered or sequence in relation to each other. See the manual for more details on this. + /// \param[in] playerId Who to send to. Specify UNASSIGNED_PLAYER_ID to designate all connected systems. + /// \param[in] broadcast Whether to send to everyone or not. If true, then the meaning of \a playerId changes to mean who NOT to send to. + /// \return Returns false on failure, true on success + virtual bool Send( RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast )=0; + + /// Gets a packet from the incoming packet queue. + /// Use DeallocatePacket() to deallocate the packet after you are done with it. + /// User-thread functions, such as RPC calls and the plugin function PluginInterface::Update occur here. + /// \return 0 if no packets are waiting to be handled, otherwise a pointer to a packet. + // sa CoreNetworkStructures.h contains struct Packet + virtual Packet* Receive( void )=0; + + /// Kick out the specified player. + /// \param[in] playerId The playerId of the player to kcik. + virtual void Kick( const PlayerID playerId )=0; + + /// Call this to deallocate a packet returned by Receive when you are done handling it. + /// \param[in] packet The packet to deallocate. + virtual void DeallocatePacket( Packet *packet )=0; + /// Set how many players are allowed on the server. /// If more players are currently connected then are allowed then no more players will be allowed to join until the number of players is less than the number of allowed players. /// \pre The server must be active for this to have meaning /// \param[in] AllowedPlayers The number of players to allow virtual void SetAllowedPlayers( unsigned short AllowedPlayers )=0; - + /// Return how many players are allowed to connect. This value was set either from Start or from SetAllowedPlayers. /// \pre The server must be active for this to have meaning /// \return The number of allowed players virtual unsigned short GetAllowedPlayers( void ) const=0; + + ///Return how many players are currently connected to the server. + /// \pre The server must be active for this to have meaning + /// \return The number of connected players. + virtual unsigned short GetConnectedPlayers( void )=0; + + /// Returns a static string pointer containing the IP of the specified connected player. + /// Also returns the client port. + /// This changes between calls so be sure to copy what is returned if you need to retain it. + /// Useful for creating and maintaining ban lists. + /// \pre The server must be active for this to have meaning + /// If the specified id does not represent an active player the results are undefined (most likely returns 0) + /// \param[in] playerId Which player we are referring to + /// \param[out] returnValue The IP of this player we are referring to + /// \param[out] port The port of the player we are referring to. + virtual void GetPlayerIPFromID( const PlayerID playerId, char returnValue[ 22 ], unsigned short *port )=0; + + /// Send a ping request to the specified player + /// \param[in] playerId Which player we are referring to + virtual void PingPlayer( const PlayerID playerId )=0; + + ///Returns the average of all ping times read for the specific player or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The ping time for this player, or -1 + virtual int GetAveragePing( const PlayerID playerId )=0; + + /// Returns the last ping time read for the specific player or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The last ping time for this player, or -1 + virtual int GetLastPing( const PlayerID playerId )=0; + + /// Returns the lowest ping time read or -1 if none read yet + /// \param[in] playerId Which player we are referring to + /// \return The lowest ping time for this player, or -1 + virtual int GetLowestPing( const PlayerID playerId )=0; + + /// Ping all players every so often. This is on by default. In games where you don't care about ping you can callStopOccasionalPing to save the bandwidth. + /// This can be called anytime. + virtual void StartOccasionalPing( void )=0; + + /// Stop pinging players every so often. Players are pinged by default. In games where you don't care about ping you can call this to save the bandwidthThis will work anytime + virtual void StopOccasionalPing( void )=0; + + /// Returns true if the server is currently active + virtual bool IsActive( void ) const=0; + + /// Returns a number automatically synchronized between the server and client which randomly changes every 9 seconds. + /// The time it changes is accurate to within a few ms and is best used to seed random number generators that you want to usually + /// return the same output on all systems. + /// Keep in mind thisisn't perfectly accurate as there is always a very small chance the numbers will by out of synch. + /// You should should confine its use to visual effects or functionality that has a backup method to maintain synchronization. + /// If you don't need this functionality and want to save the bandwidth callStopSynchronizedRandomInteger after starting the server + /// \return A number, which is probably synchronized among all clients and the server. + virtual unsigned int GetSynchronizedRandomInteger( void ) const=0; + + /// Start or restart the synchronized random integer. This is on by default. Call StopSynchronizedRandomIntegerto stop it keeping the number in synch + virtual void StartSynchronizedRandomInteger( void )=0; + + /// Stop the synchronized random integer. Call StartSynchronizedRandomInteger to start it again + virtual void StopSynchronizedRandomInteger( void )=0; - virtual void vftable_3C()=0; - virtual void vftable_40()=0; - virtual void vftable_44()=0; - virtual void vftable_48()=0; - virtual void vftable_4C()=0; - virtual void vftable_50()=0; - virtual void vftable_54()=0; - virtual void vftable_58()=0; - virtual void vftable_5C()=0; - virtual void vftable_60()=0; - virtual void vftable_64()=0; - virtual void vftable_68()=0; - virtual void vftable_6C()=0; - virtual void vftable_70()=0; - + /// This is an optional function to generate the compression layer based on the input frequency table. + /// If you want to use it you should call this twice - once with inputLayer as true and once as false. + /// The frequency table passed here with inputLayer=true should match the frequency table on the recipient with inputLayer=false. + /// Likewise, the frequency table passed here with inputLayer=false should match the frequency table on the recipient with inputLayer=true. + /// Calling this function when there is an existing layer will overwrite the old layer. + /// \pre You should only call this when disconnected + /// \param[in] inputFrequencyTable A frequency table for your data + /// \param[in] inputLayer Is this the input layer? + /// \return false (failure) if connected. Otherwise true (success) + /// \sa Compression.cpp + virtual bool GenerateCompressionLayer( unsigned int inputFrequencyTable[ 256 ], bool inputLayer )=0; + + /// Delete the output or input layer as specified. This is not necessary to call and is only valuable for freeing memory. + /// \pre You should only call this when disconnected + /// \param[in] inputLayer True to mean the inputLayer, false to mean the output layer + /// \return false (failure) if connected. Otherwise true (success) + virtual bool DeleteCompressionLayer( bool inputLayer )=0; + /// \ingroup RAKNET_RPC /// Register a C or static member function as available for calling as a remote procedure call /// \param[in] uniqueID: A null-terminated unique string to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions @@ -90,48 +212,231 @@ public: /// \param[in] uniqueID A string of only letters to identify this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameterpassed to RegisterAsRemoteProcedureCall virtual void UnregisterAsRemoteProcedureCall( char* uniqueID )=0; - virtual void vftable_80()=0; - virtual void vftable_84()=0; - virtual void vftable_88()=0; - virtual void vftable_8C()=0; - virtual void vftable_90()=0; - virtual void vftable_94()=0; - virtual void vftable_98()=0; - virtual void vftable_9C()=0; - virtual void vftable_A0()=0; - virtual void vftable_A4()=0; - virtual void vftable_A8()=0; - virtual void vftable_AC()=0; - virtual void vftable_B0()=0; - virtual void vftable_B4()=0; - virtual void vftable_B8()=0; - virtual void vftable_BC()=0; - virtual void vftable_C0()=0; - virtual void vftable_C4()=0; - virtual void vftable_C8()=0; - virtual void vftable_CC()=0; - virtual void vftable_D0()=0; - virtual void vftable_D4()=0; - virtual void vftable_D8()=0; - virtual void vftable_DC()=0; - virtual void vftable_E0()=0; - virtual void vftable_E4()=0; - virtual void vftable_E8()=0; - virtual void vftable_EC()=0; - virtual void vftable_F0()=0; - virtual void vftable_F4()=0; - virtual void vftable_F8()=0; - virtual void vftable_FC()=0; - virtual void vftable_100()=0; - virtual void vftable_104()=0; - virtual void vftable_108()=0; - virtual void vftable_10C()=0; - virtual void vftable_110()=0; - virtual void vftable_114()=0; - virtual void vftable_118()=0; - virtual void vftable_11C()=0; - virtual void vftable_120()=0; - virtual void vftable_124()=0; + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] data The data to send + /// \param[in] bitLength The number of bits of \a data + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure\note This is part of the Remote Procedure Call Subsystem + virtual bool RPC( char* uniqueID, const char *data, unsigned int bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + /// \ingroup RAKNET_RPC + /// Calls a C function on the remote system that was already registered using RegisterAsRemoteProcedureCall. + /// If you want that function to return data you should call RPC from that system in the same wayReturns true on a successful packet + /// send (this does not indicate the recipient performed the call), false on failure + /// \param[in] uniqueID A NULL terimianted string to this procedure. Recommended you use the macro CLASS_MEMBER_ID for class member functions. Must match the parameter + /// \param[in] bitStream The bitstream to send + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on. + /// \param[in] playerId Who to send this packet to, or in the case of broadcasting who not to send it to. Use UNASSIGNED_PLAYER_ID to specify none + /// \param[in] broadcast True to send this packet to all connected systems. If true, then playerId specifies who not to send the packet to. + /// \param[in] shiftTimestamp True to add a timestamp to your data, such that the first byte is ID_TIMESTAMP and the next sizeof(RakNetTime) is the timestamp. + /// \param[in] networkID For static functions, pass UNASSIGNED_NETWORK_ID. For member functions, you must derive from NetworkIDGenerator and pass the value returned by NetworkIDGenerator::GetNetworkID for that object. + /// \param[in] replyFromTarget If 0, this function is non-blocking. Otherwise it will block while waiting for a reply from the target procedure, which is remtely written to RPCParameters::replyToSender and copied to replyFromTarget. The block will return early on disconnect or if the sent packet is unreliable and more than 3X the ping has elapsed. + /// \return True on a successful packet send (this does not indicate the recipient performed the call), false on failure + virtual bool RPC( char* uniqueID, RakNet::BitStream *bitStream, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId, bool broadcast, bool shiftTimestamp, NetworkID networkID, RakNet::BitStream *replyFromTarget )=0; + + /// Enables or disables frequency table tracking. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// This value persists between connect calls and defaults to false (no frequency tracking) + /// \pre You can call this at any time - however you SHOULD only call it when disconnected. Otherwise you will only trackpart of the values sent over the network. + /// \param[in] b True to enable tracking + virtual void SetTrackFrequencyTable( bool b )=0; + + /// Returns the frequency of outgoing bytes into outputFrequencyTable. This is required to get a frequency table, which is used in GenerateCompressionLayer() + /// The purpose is to save to file as either a master frequency table from a sample game session. + /// \pre You should only call this when disconnected + /// \pre Requires that you first enable data frequency tracking by calling SetTrackFrequencyTable(true) + /// \param[out] outputFrequencyTable The Frequency Table used in the compression layer + /// \return false (failure) if connected or if frequency table tracking is not enabled. Otherwise true (success) + virtual bool GetSendFrequencyTable( unsigned int outputFrequencyTable[ 256 ] )=0; + + /// Returns the compression ratio. A low compression ratio is good. Compression is for outgoing data + /// \return The compression ratio + virtual float GetCompressionRatio( void ) const=0; + + ///Returns the decompression ratio. A high decompression ratio is good. Decompression is for incoming data + /// \return The decompression ratio + virtual float GetDecompressionRatio( void ) const=0; + + /// Attatches a Plugin interface to run code automatically on message receipt in the Receive call + /// \note If plugins have dependencies on each other then the order does matter - for example the router plugin should go first because it might route messages for other plugins + /// \param[in] messageHandler Pointer to a message handler to attach + virtual void AttachPlugin( PluginInterface *messageHandler )=0; + + ///Detaches a Plugin interface to run code automatically on message receipt + /// \param[in] messageHandler Pointer to a message handler to detach + virtual void DetachPlugin( PluginInterface *messageHandler )=0; + + ///The server internally maintains a data struct that is automatically sent to clients when they connect. + /// This is useful to contain data such as the server name or message of the day. + /// Access that struct with thisfunction. + /// \note If you change any data in the struct remote clients won't reflect this change unless you manually update themDo so by calling SendStaticServerDataToClient(UNASSIGNED_PLAYER_ID) + /// \return The static server data, stored in a bitstream + virtual RakNet::BitStream * GetStaticServerData( void )=0; + + /// The server internally maintains a data struct that is automatically sent to clients when they connect. + /// This function will set that data. + /// \param[in] data The data to use as the static server data + /// \param[in] length The length, in bytes, of \a data + virtual void SetStaticServerData( const char *data, const int length )=0; + + /// This sets to true or false whether we want to support relaying of static client data to other connected clients. + /// If set to false it saves some bandwdith, however other clients won't know the static client data and attempting to read that data will return garbage. + /// Default is false. This only works for up to 32 players. Games supporting more than 32 players will have this shut off automatically upon server start and must have it forced back on with this function + /// if you do indeed want it. + /// \pre This should be called after the server is started in case you want to override when it shuts off at 32 players + /// \param[in] b true or false, as you wish to set the function + virtual void SetRelayStaticClientData( bool b )=0; + + /// Send the static server data to the specified player. + /// Pass UNASSIGNED_PLAYER_ID to broadcast to all players. + /// The only time you need to call this function is to update clients that are already connected when you change the static server data by calling GetStaticServerData() and directly modifying the object pointed to. + /// Obviously if theconnected clients don't need to know the new data you don't need to update them, so it's up to you. + /// \pre The server must be active for this to have meaning + /// \param[in] playerId The playerId we are referring to + virtual void SendStaticServerDataToClient( const PlayerID playerId )=0; + + /// Sets the data to send along with a LAN server discovery or offline ping reply. + /// \a length should be under 400 bytes, as a security measure against flood attacks + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + /// \sa Ping.cpp + virtual void SetOfflinePingResponse( const char *data, const unsigned int length )=0; + + /// Returns a pointer to an attached client's static data specified by the playerId. + /// Returns 0 if no such player is connected. + /// \note that you can modify the client data here. Changes won't be reflected on clients unless you force them to update by calling ChangeStaticClientData() + /// \pre The server must be active for this to have meaning + /// \param[in] playerId The ID of the client + /// \return A bitstream containing the static client data + virtual RakNet::BitStream * GetStaticClientData( const PlayerID playerId )=0; + + /// Set the stait client data for a particular player + /// \param[in] playerId The ID of the client + /// \param[in] data a block of data to store, or 0 for none + /// \param[in] length The length of data in bytes, or 0 for none + virtual void SetStaticClientData( const PlayerID playerId, const char *data, const int length )=0; + + /// This function is used to update the information on connected clients when the server effects a change of static client data. + /// Note that a client that gets this new information for himself will update the data for his playerID but not his local data (which is the user's copy) i.e. player 5 would have the data returned by GetStaticClientData(5) changed but his local information returned byGetStaticClientData(UNASSIGNED_PLAYER_ID) would remain the same as it was previously. + /// \param[in] playerChangedId The playerId of the player whose data was changed. This is the parameter passed toGetStaticClientData to get a pointer to that player's information. + /// \param[in] playerToSendToId The player you want to update with the new information. This will almost always be everybody, in which case you should pass UNASSIGNED_PLAYER_ID. + /// \pre The server must be active for this to have meaning + virtual void ChangeStaticClientData( const PlayerID playerChangedId, PlayerID playerToSendToId )=0; + + /// Internally store the IP address(es) for the server and return how many it has. + /// This can easily be more than one, for example a system on both a LAN and with a net connection. + /// The server does not have to be active for this to work + virtual unsigned int GetNumberOfAddresses( void )=0; + + /// Call this function where 0 <= index < x where x is the value returned by GetNumberOfAddresses() + /// Returns a static string filled with the server IP of the specified index. + /// Strings returned in no particular order. You'll have to check every index see which string you want. + /// \return 0 on invalid input, otherwise an dotted IP + virtual const char* GetLocalIP( unsigned int index )=0; + + /// Return the unique address identifier that represents you on the the network and is based on your local IP / port. + /// \note Unlike in previous versions, PlayerID is a struct and is not sequential + virtual PlayerID GetInternalID( void ) const=0; + + /// Put a packet back at the end of the receive queue in case you don't want to deal with it immediately. + /// \param[in] packet The packet to push + /// \param[in] pushAtHead True to push the packet so that the next receive call returns it. False to push it at the end of the queue (obviously pushing it at the end makes the packets out of order) + virtual void PushBackPacket( Packet *packet, bool pushAtHead )=0; + + /// \Internal + virtual void SetRouterInterface( RouterInterface *routerInterface )=0; + + /// \Internal + virtual void RemoveRouterInterface( RouterInterface *routerInterface )=0; + + /// Given a playerID, returns an index from 0 to the maximum number of players allowed - 1. + /// \param[in] playerId The PlayerID we are referring to + /// \return The index of this PlayerID + virtual int GetIndexFromPlayerID( const PlayerID playerId )=0; + + /// This function is only useful for looping through all players. + /// Given an index, will return a PlayerID. + /// \param[in] index Index should range between 0 and the maximum number of players allowed - 1. + virtual PlayerID GetPlayerIDFromIndex( int index )=0; + + /// Bans an IP from connecting. Banned IPs persist between connections. + /// param[in] IP Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + virtual void AddToBanList( const char *IP )=0; + + /// Allows a previously banned IP to connect. + /// param[in] Dotted IP address. Can use * as a wildcard, such as 128.0.0.* will banAll IP addresses starting with 128.0.0 + virtual void RemoveFromBanList( const char *IP )=0; + + /// Allows all previously banned IPs to connect. + virtual void ClearBanList( void )=0; + + /// Returns true or false indicating if a particular IP is banned. + /// \param[in] IP - Dotted IP address. + /// \return true if IP matches any IPs in the ban list, accounting for any wildcards. false otherwise. + virtual bool IsBanned( const char *IP )=0; + + /// Returns true if that player ID is currently active + /// \param[in] playerId Which playerId we are referring to + /// \return true or false, as noted + virtual bool IsActivePlayerID( const PlayerID playerId )=0; + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] timeMS Time, in MS + /// \param[in] target Which system to do this for + virtual void SetTimeoutTime( RakNetTime timeMS, const PlayerID target )=0; + + /// Set the MTU per datagram. It's important to set this correctly - otherwise packets will be needlessly split, decreasing performance and throughput. + /// Maximum allowed size is MAXIMUM_MTU_SIZE. + /// Too high of a value will cause packets not to arrive at worst and be fragmented at best. + /// Too low of a value will split packets unnecessarily. + /// sa MTUSize.h + /// \pre Can only be called when not connected. + /// \return false on failure (we are connected), else true + virtual bool SetMTUSize( int size )=0; + + /// Returns the current MTU size + /// \return The current MTU size + virtual int GetMTUSize( void ) const=0; + + /// Sends a one byte message ID_ADVERTISE_SYSTEM to the remote unconnected system. + /// This will tell the remote system our external IP outside the LAN along with some user data.. + /// \param[in] host Either a dotted IP address or a domain name + /// \param[in] remotePort Which port to connect to on the remote machine. + /// \param[in] data to send along with ID_ADVERTISE_SYSTEM + /// \param[in] dataLength The length of \a data + virtual void AdvertiseSystem( const char *host, unsigned short remotePort, const char *data, int dataLength )=0; + + /// Returns a structure containing a large set of network statistics for the specified system. + /// You can map this data to a string using the C style StatisticsToString() function + /// \param[in] playerId: Which connected system to get statistics for + /// \return 0 on can't find the specified system. A pointer to a set of data otherwise. + /// \sa RakNetStatistics.h + virtual RakNetStatisticsStruct * const GetStatistics( const PlayerID playerId )=0; + + /// Adds simulated ping and packet loss to the outgoing data flow. + /// To simulate bi-directional ping and packet loss, you should call this on both the sender and the recipient, with half the total ping and maxSendBPS value on each. + /// You can exclude network simulator code with the _RELEASE #define to decrease code size + /// \param[in] maxSendBPS Maximum bits per second to send. Packetloss grows linearly. 0 to disable. + /// \param[in] minExtraPing The minimum time to delay sends. + /// \param[in] extraPingVariance The additional random time to delay sends. + virtual void ApplyNetworkSimulator( double maxSendBPS, unsigned short minExtraPing, unsigned short extraPingVariance)=0; + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + virtual bool IsNetworkSimulatorActive( void )=0; }; #endif diff --git a/raknet/ReliabilityLayer.cpp b/raknet/ReliabilityLayer.cpp index 78f13a3..f22e89b 100644 --- a/raknet/ReliabilityLayer.cpp +++ b/raknet/ReliabilityLayer.cpp @@ -1,35 +1,2434 @@ -// TODO: Implement ReliabilityLayer.cpp +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "ReliabilityLayer.h" +#include "GetTime.h" +#include "SocketLayer.h" +#include "PluginInterface.h" +#include "RakAssert.h" +#include "Rand.h" +#include "PacketEnumerations.h" +// alloca +#ifdef _COMPATIBILITY_1 +#elif defined(_WIN32) +#include +#elif defined(_COMPATIBILITY_2) +#include "Compatibility2Includes.h" +#else +#include +#endif + +static const int DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE=512; +static const float PACKETLOSS_TOLERANCE=.02f; // What percentile packetloss we are willing to accept as background noise. +static const double MINIMUM_SEND_BPS=14400.0; // Won't go below this send rate +static const double STARTING_SEND_BPS=28800.0; // What send rate to start at. static const float PING_MULTIPLIER_TO_RESEND=3.0; // So internet ping variation doesn't cause needless resends static const RakNetTime MIN_PING_TO_RESEND=30; // So system timer changes and CPU lag don't send needless resends +static const RakNetTimeNS TIME_TO_NEW_SAMPLE=500000; // How many ns to wait before starting a new sample. This way buffers have time to overflow or relax at the new send rate, if they are indeed going to overflow. +static const RakNetTimeNS MAX_TIME_TO_SAMPLE=250000; // How many ns to sample the connection before deciding on a course of action(increase or decrease throughput). You must be at full send rate the whole time -ReliabilityLayer::ReliabilityLayer() +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +#ifdef _WIN32 + //#define _DEBUG_LOGGER + #ifdef _DEBUG_LOGGER + #include + #endif +#endif + +int SplitPacketChannelComp( SplitPacketIdType const &key, SplitPacketChannel* const &data ) { - // TODO: ReliabilityLayer::ReliabilityLayer + if (key < data->splitPacketList[0]->splitPacketId) + return -1; + if (key == data->splitPacketList[0]->splitPacketId) + return 0; + return 1; +} + +int SplitPacketIndexComp( SplitPacketIndexType const &key, InternalPacket* const &data ) +{ + if (key < data->splitPacketIndex) + return -1; + if (key == data->splitPacketIndex) + return 0; + return 1; +} + + +//------------------------------------------------------------------------------------------------------- +// Constructor +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::ReliabilityLayer() : updateBitStream( DEFAULT_MTU_SIZE ) // preallocate the update bitstream so we can avoid a lot of reallocs at runtime +{ +#ifdef __USE_IO_COMPLETION_PORTS + readWriteSocket = INVALID_SOCKET; +#endif + + freeThreadedMemoryOnNextUpdate = false; +#ifdef _DEBUG + // Wait longer to disconnect in debug so I don't get disconnected while tracing + timeoutTime=30000; +#else + timeoutTime=10000; +#endif + +#ifndef _RELEASE + maxSendBPS=minExtraPing=extraPingVariance=0; +#endif InitializeVariables(); } +//------------------------------------------------------------------------------------------------------- +// Destructor +//------------------------------------------------------------------------------------------------------- +ReliabilityLayer::~ReliabilityLayer() +{ + FreeMemory( true ); // Free all memory immediately +#ifdef __USE_IO_COMPLETION_PORTS + if ( readWriteSocket != INVALID_SOCKET ) + closesocket( readWriteSocket ); +#endif +} + +//------------------------------------------------------------------------------------------------------- +// Resets the layer for reuse +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::Reset( bool resetVariables ) +{ + FreeMemory( true ); // true because making a memory reset pending in the update cycle causes resets after reconnects. Instead, just call Reset from a single thread + if (resetVariables) + InitializeVariables(); +} + +//------------------------------------------------------------------------------------------------------- +// Sets up encryption +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetEncryptionKey( const unsigned char* key ) +{ + if ( key ) + encryptor.SetKey( key ); + else + encryptor.UnsetKey(); +} + +//------------------------------------------------------------------------------------------------------- +// Assign a socket for the reliability layer to use for writing +//------------------------------------------------------------------------------------------------------- +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void ReliabilityLayer::SetSocket( SOCKET s ) +{ +#ifdef __USE_IO_COMPLETION_PORTS + // If this hits I am probably using sequential ports while doing IO completion ports + assert( s != INVALID_SOCKET ); + readWriteSocket = s; +#endif +} + +//------------------------------------------------------------------------------------------------------- +// Get the socket held by the reliability layer +//------------------------------------------------------------------------------------------------------- +SOCKET ReliabilityLayer::GetSocket( void ) +{ +#ifdef __USE_IO_COMPLETION_PORTS + return readWriteSocket; +#else + + return INVALID_SOCKET; +#endif +} + +//------------------------------------------------------------------------------------------------------- +// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetTimeoutTime( RakNetTime time ) +{ + timeoutTime=time; +} + +//------------------------------------------------------------------------------------------------------- +// Returns the value passed to SetTimeoutTime. or the default if it was never called +//------------------------------------------------------------------------------------------------------- +RakNetTime ReliabilityLayer::GetTimeoutTime(void) +{ + return timeoutTime; +} + //------------------------------------------------------------------------------------------------------- // Initialize the variables //------------------------------------------------------------------------------------------------------- void ReliabilityLayer::InitializeVariables( void ) { - // TODO: ReliabilityLayer::InitializeVariables - + memset( waitingForOrderedPacketReadIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType)); + memset( waitingForSequencedPacketReadIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); + memset( waitingForOrderedPacketWriteIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); + memset( waitingForSequencedPacketWriteIndex, 0, NUMBER_OF_ORDERED_STREAMS * sizeof(OrderingIndexType) ); memset( &statistics, 0, sizeof( statistics ) ); statistics.connectionStartTime = RakNet::GetTime(); - statistics.field_110 = RakNet::GetTime(); - statistics.field_114 = 0; - statistics.field_118 = 0; - statistics.field_11C = RakNet::GetTime(); - statistics.field_120 = 0; - statistics.field_124 = 0; - field_403 = 4; + splitPacketId = 0; + messageNumber = 0; + availableBandwidth=0; + lastUpdateTime= RakNet::GetTimeNS(); + currentBandwidth=STARTING_SEND_BPS; + // lastPacketSendTime=retransmittedFrames=sentPackets=sentFrames=receivedPacketsCount=bytesSent=bytesReceived=0; + deadConnection = cheater = false; + lastAckTime = 0; + + lowBandwidth=STARTING_SEND_BPS; + histogramStartTime=lastUpdateTime+TIME_TO_NEW_SAMPLE+ping*2*1000; + histogramEndTime=histogramStartTime+MAX_TIME_TO_SAMPLE; + + highBandwidth=0; + histogramPlossCount=0; + histogramAckCount=0; + continuousSend=false; + histogramReceiveMarker=0; + noPacketlossIncreaseCount=0; + nextAckTime=statistics.connectionStartTime; + + receivedPacketsBaseIndex=0; + resetReceivedPackets=true; + sendPacketCount=receivePacketCount=0; SetPing( 1000 ); + resendList.Preallocate(RESEND_TREE_ORDER*2); +} + +//------------------------------------------------------------------------------------------------------- +// Frees all allocated memory +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::FreeMemory( bool freeAllImmediately ) +{ + if ( freeAllImmediately ) + { + FreeThreadedMemory(); + FreeThreadSafeMemory(); + } + else + { + FreeThreadSafeMemory(); + freeThreadedMemoryOnNextUpdate = true; + } +} + +void ReliabilityLayer::FreeThreadedMemory( void ) +{ +} + +void ReliabilityLayer::FreeThreadSafeMemory( void ) +{ + unsigned i,j; + InternalPacket *internalPacket; + + for (i=0; i < splitPacketChannelList.Size(); i++) + { + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + { + delete [] splitPacketChannelList[i]->splitPacketList[j]->data; + internalPacketPool.ReleasePointer( splitPacketChannelList[i]->splitPacketList[j] ); + } + delete splitPacketChannelList[i]; + } + splitPacketChannelList.Clear(); + + while ( outputQueue.Size() > 0 ) + { + internalPacket = outputQueue.Pop(); + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + + outputQueue.ClearAndForceAllocation( 32 ); + + for ( i = 0; i < orderingList.Size(); i++ ) + { + if ( orderingList[ i ] ) + { + DataStructures::LinkedList* theList = orderingList[ i ]; + + if ( theList ) + { + while ( theList->Size() ) + { + internalPacket = orderingList[ i ]->Pop(); + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + + delete theList; + } + } + } + + orderingList.Clear(); + + //resendList.ForEachData(DeleteInternalPacket); + resendList.Clear(); + while ( resendQueue.Size() ) + { + // The resend Queue can have NULL pointer holes. This is so we can deallocate blocks without having to compress the array + internalPacket = resendQueue.Pop(); + + if ( internalPacket ) + { + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + } + resendQueue.ClearAndForceAllocation( DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE ); + + + for ( i = 0; i < NUMBER_OF_PRIORITIES; i++ ) + { + j = 0; + for ( ; j < sendPacketSet[ i ].Size(); j++ ) + { + delete [] ( sendPacketSet[ i ] ) [ j ]->data; + internalPacketPool.ReleasePointer( ( sendPacketSet[ i ] ) [ j ] ); + } + + sendPacketSet[ i ].ClearAndForceAllocation( 32 ); // Preallocate the send lists so we don't do a bunch of reallocations unnecessarily + } + +#ifndef _RELEASE + for (unsigned i = 0; i < delayList.Size(); i++ ) + delete delayList[ i ]; + delayList.Clear(); +#endif + + internalPacketPool.ClearPool(); + + //messageHistogram.Clear(); + + acknowlegements.Clear(); +} + +//------------------------------------------------------------------------------------------------------- +// Packets are read directly from the socket layer and skip the reliability +//layer because unconnected players do not use the reliability layer +// This function takes packet data after a player has been confirmed as +//connected. The game should not use that data directly +// because some data is used internally, such as packet acknowledgement and +//split packets +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer( const char *buffer, int length, PlayerID playerId, DataStructures::List &messageHandlerList, int MTUSize ) +{ +#ifdef _DEBUG + assert( !( length <= 0 || buffer == 0 ) ); +#endif + + if ( length <= 1 || buffer == 0 ) // Length of 1 is a connection request resend that we just ignore + return true; + + //int numberOfAcksInFrame = 0; + RakNetTimeNS time; + bool indexFound; + int count, size; + MessageNumberType holeCount; + unsigned i; + unsigned ackedHistogramCounter; + bool hasAcks=false; + +// bool duplicatePacket; + + // bytesReceived+=length + UDP_HEADER_SIZE; + + UpdateThreadedMemory(); + + // decode this whole chunk if the decoder is defined. + if ( encryptor.IsKeySet() ) + { + if ( encryptor.Decrypt( ( unsigned char* ) buffer, length, ( unsigned char* ) buffer, &length ) == false ) + { + statistics.bitsWithBadCRCReceived += length * 8; + statistics.packetsWithBadCRCReceived++; + return false; + } + } + + statistics.bitsReceived += length * 8; + statistics.packetsReceived++; + + RakNet::BitStream socketData( (unsigned char*) buffer, length, false ); // Convert the incoming data to a bitstream for easy parsing + time = RakNet::GetTimeNS(); + + DataStructures::RangeList incomingAcks; + socketData.Read(hasAcks); + if (hasAcks) + { + MessageNumberType messageNumber; + if (incomingAcks.Deserialize(&socketData)==false) + return false; + for (i=0; iincomingAcks.ranges[i].maxIndex) + { + RakAssert(incomingAcks.ranges[i].minIndex<=incomingAcks.ranges[i].maxIndex); + return false; + } + + for (messageNumber=incomingAcks.ranges[i].minIndex; messageNumber >= incomingAcks.ranges[i].minIndex && messageNumber <= incomingAcks.ranges[i].maxIndex; messageNumber++) + { + hasAcks=true; + + // SHOW - ack received + //printf("Got Ack for %i. resendList.Size()=%i sendQueue[0].Size() = %i\n",internalPacket->messageNumber, resendList.Size(), sendQueue[0].Size()); + ackedHistogramCounter=RemovePacketFromResendListAndDeleteOlderReliableSequenced( messageNumber, time ); + +#ifdef _DEBUG_LOGGER + { + char temp[256]; + sprintf(temp, "%p: Got ack for %i. Resend queue size=%i\n", this, messageNumber, resendQueue.Size()); + OutputDebugStr(temp); + } +#endif + + if (time >= histogramStartTime && ackedHistogramCounter!=(unsigned)-1 && ackedHistogramCounter==histogramReceiveMarker) + ++histogramAckCount; + +// internalPacketPool.ReleasePointer( internalPacket ); + + + if ( resendList.IsEmpty() ) + { + lastAckTime = 0; // Not resending anything so clear this var so we don't drop the connection on not getting any more acks + } + else + { + lastAckTime = time; // Just got an ack. Record when we got it so we know the connection is alive + } + } + } + } + + + // Parse the bitstream to create an internal packet + InternalPacket* internalPacket = CreateInternalPacketFromBitStream( &socketData, time ); + + if (internalPacket==0) + return hasAcks; + + while ( internalPacket ) + { + for (i=0; i < messageHandlerList.Size(); i++) + messageHandlerList[i]->OnInternalPacket(internalPacket, receivePacketCount, playerId, (RakNetTime)(time/(RakNetTimeNS)1000), false); + + { +#ifdef _DEBUG_LOGGER + { + char temp[256]; + sprintf(temp, "%p: Got packet %i data: %i bitlen: %i\n", this, internalPacket->messageNumber, (unsigned char) internalPacket->data[0], internalPacket->dataBitLength); + OutputDebugStr(temp); + } +#endif + + // receivedPacketsCount++; + if ( internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED || internalPacket->reliability == RELIABLE ) + { + SendAcknowledgementPacket( internalPacket->messageNumber, time ); + } + + // resetReceivedPackets is set from a non-threadsafe function. + // We do the actual reset in this function so the data is not modified by multiple threads + if (resetReceivedPackets) + { + hasReceivedPacketQueue.ClearAndForceAllocation(DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE); + receivedPacketsBaseIndex=0; + resetReceivedPackets=false; + } + + // If the following conditional is true then this either a duplicate packet + // or an older out of order packet + // The subtraction unsigned overflow is intentional + holeCount = (MessageNumberType)(internalPacket->messageNumber-receivedPacketsBaseIndex); + const int typeRange = (MessageNumberType)-1; + + if (holeCount==0) + { + // Got what we were expecting + if (hasReceivedPacketQueue.Size()) + hasReceivedPacketQueue.Pop(); + ++receivedPacketsBaseIndex; + } + else if (holeCount > typeRange-typeRange/2) + { + // Underflow - got a packet we have already counted past + statistics.duplicateMessagesReceived++; + + // Duplicate packet + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + else if (holeCountdata; + internalPacketPool.ReleasePointer( internalPacket ); + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + } + else // holeCount>=receivedPackets.Size() + { + // Got a higher count out of order packet whose messageNumber is higher than we have ever got + + // Add 0 times to the queue until (messageNumber - baseIndex) < queue size. + while ((MessageNumberType)(holeCount) > hasReceivedPacketQueue.Size()) + hasReceivedPacketQueue.Push(time+(RakNetTimeNS)timeoutTime*1000); // Didn't get this packet - set the time to give up waiting + hasReceivedPacketQueue.Push(0); // Got the packet +#ifdef _DEBUG + // If this assert hits then MessageNumberType has overflowed + assert(hasReceivedPacketQueue.Size() < (unsigned int)((MessageNumberType)(-1))); +#endif + } + + // Pop all expired times. 0 means we got the packet, in which case we don't track this index either. + while ( hasReceivedPacketQueue.Size()>0 && hasReceivedPacketQueue.Peek() < time ) + { + hasReceivedPacketQueue.Pop(); + ++receivedPacketsBaseIndex; + } + + statistics.messagesReceived++; + + // If the allocated buffer is > DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE and it is 3x greater than the number of elements actually being used + if (hasReceivedPacketQueue.AllocationSize() > (unsigned int) DEFAULT_HAS_RECEIVED_PACKET_QUEUE_SIZE && hasReceivedPacketQueue.AllocationSize() > hasReceivedPacketQueue.Size() * 3) + hasReceivedPacketQueue.Compress(); + + // Keep on top of deleting old unreliable split packets so they don't clog the list. + if ( internalPacket->splitPacketCount > 0 ) + DeleteOldUnreliableSplitPackets( time ); + + if ( internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == UNRELIABLE_SEQUENCED ) + { +#ifdef _DEBUG + assert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { + // Invalid packet +#ifdef _DEBUG + printf( "Got invalid packet\n" ); +#endif + + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + if ( IsOlderOrderedPacket( internalPacket->orderingIndex, waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] ) == false ) + { + statistics.sequencedMessagesInOrder++; + + + // Is this a split packet? + if ( internalPacket->splitPacketCount > 0 ) + { + // Generate the split + // Verify some parameters to make sure we don't get junk data + + + // Check for a rebuilt packet + InsertIntoSplitPacketList( internalPacket, time ); + + // Sequenced + internalPacket = BuildPacketFromSplitPacketList( internalPacket->splitPacketId, time ); + + if ( internalPacket ) + { + // Update our index to the newest packet + waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] = internalPacket->orderingIndex + 1; + + // If there is a rebuilt packet, add it to the output queue + outputQueue.Push( internalPacket ); + internalPacket = 0; + } + + // else don't have all the parts yet + } + + else + { + // Update our index to the newest packet + waitingForSequencedPacketReadIndex[ internalPacket->orderingChannel ] = internalPacket->orderingIndex + 1; + + // Not a split packet. Add the packet to the output queue + outputQueue.Push( internalPacket ); + internalPacket = 0; + } + } + else + { + statistics.sequencedMessagesOutOfOrder++; + + // Older sequenced packet. Discard it + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + // Is this an unsequenced split packet? + if ( internalPacket->splitPacketCount > 0 ) + { + // An unsequenced split packet. May be ordered though. + + // Check for a rebuilt packet + if ( internalPacket->reliability != RELIABLE_ORDERED ) + internalPacket->orderingChannel = 255; // Use 255 to designate not sequenced and not ordered + + InsertIntoSplitPacketList( internalPacket, time ); + + internalPacket = BuildPacketFromSplitPacketList( internalPacket->splitPacketId, time ); + + if ( internalPacket == 0 ) + { + + // Don't have all the parts yet + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + // else continue down to handle RELIABLE_ORDERED + } + + if ( internalPacket->reliability == RELIABLE_ORDERED ) + { +#ifdef _DEBUG + assert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { +#ifdef _DEBUG + printf("Got invalid ordering channel %i from packet %i\n", internalPacket->orderingChannel, internalPacket->messageNumber); +#endif + // Invalid packet + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + if ( waitingForOrderedPacketReadIndex[ internalPacket->orderingChannel ] == internalPacket->orderingIndex ) + { + // Get the list to hold ordered packets for this stream + DataStructures::LinkedList *orderingListAtOrderingStream; + unsigned char orderingChannelCopy = internalPacket->orderingChannel; + + statistics.orderedMessagesInOrder++; + + // Show ordering index increment + //printf("Pushing immediate packet %i with ordering index %i\n", internalPacket->messageNumber, internalPacket->orderingIndex ); + + // Push the packet for the user to read + outputQueue.Push( internalPacket ); + internalPacket = 0; // Don't reference this any longer since other threads access it + + // Wait for the next ordered packet in sequence + waitingForOrderedPacketReadIndex[ orderingChannelCopy ] ++; // This wraps + + orderingListAtOrderingStream = GetOrderingListAtOrderingStream( orderingChannelCopy ); + + if ( orderingListAtOrderingStream != 0) + { + while ( orderingListAtOrderingStream->Size() > 0 ) + { + // Cycle through the list until nothing is found + orderingListAtOrderingStream->Beginning(); + indexFound=false; + size=orderingListAtOrderingStream->Size(); + count=0; + + while (count++ < size) + { + if ( orderingListAtOrderingStream->Peek()->orderingIndex == waitingForOrderedPacketReadIndex[ orderingChannelCopy ] ) + { + /* + RakNet::BitStream temp(orderingListAtOrderingStream->Peek()->data, BITS_TO_BYTES(orderingListAtOrderingStream->Peek()->dataBitLength), false); + temp.IgnoreBits(8); + unsigned int receivedPacketNumber=0; + temp.Read(receivedPacketNumber); + printf("Receive: receivedPacketNumber=%i orderingIndex=%i waitingFor=%i\n", receivedPacketNumber, orderingListAtOrderingStream->Peek()->orderingIndex, waitingForOrderedPacketReadIndex[ orderingChannelCopy ]); + */ + + //printf("Pushing delayed packet %i with ordering index %i. outputQueue.Size()==%i\n", orderingListAtOrderingStream->Peek()->messageNumber, orderingListAtOrderingStream->Peek()->orderingIndex, outputQueue.Size() ); + outputQueue.Push( orderingListAtOrderingStream->Pop() ); + waitingForOrderedPacketReadIndex[ orderingChannelCopy ]++; // This wraps at 255 + indexFound=true; + } + else + (*orderingListAtOrderingStream)++; + } + + if (indexFound==false) + break; + } + } + + internalPacket = 0; + } + else + { + // assert(waitingForOrderedPacketReadIndex[ internalPacket->orderingChannel ] < internalPacket->orderingIndex); + statistics.orderedMessagesOutOfOrder++; + + // This is a newer ordered packet than we are waiting for. Store it for future use + AddToOrderingList( internalPacket ); + } + + goto CONTINUE_SOCKET_DATA_PARSE_LOOP; + } + + // Nothing special about this packet. Add it to the output queue + outputQueue.Push( internalPacket ); + + internalPacket = 0; + } + + // Used for a goto to jump to the next packet immediately + + CONTINUE_SOCKET_DATA_PARSE_LOOP: + // Parse the bitstream to create an internal packet + internalPacket = CreateInternalPacketFromBitStream( &socketData, time ); + } + + /* + if (numberOfAcksInFrame > 0) +// if (time > lastWindowAdjustTime+ping) + { + // printf("Window size up\n"); + windowSize+=1 + numberOfAcksInFrame/windowSize; + if ( windowSize > MAXIMUM_WINDOW_SIZE ) + windowSize = MAXIMUM_WINDOW_SIZE; + //lastWindowAdjustTime=time; + } + //else + // printf("No acks in frame\n"); +*/ + /* + // numberOfAcksInFrame>=windowSize means that all the packets we last sent from the resendList are cleared out + // 11/17/05 - the problem with numberOfAcksInFrame >= windowSize is that if the entire frame is filled with resends but not all resends filled the frame + // then the sender is limited by how many resends can fit in one frame + if ( numberOfAcksInFrame >= windowSize && ( sendPacketSet[ SYSTEM_PRIORITY ].Size() > 0 || sendPacketSet[ HIGH_PRIORITY ].Size() > 0 || sendPacketSet[ MEDIUM_PRIORITY ].Size() > 0 ) ) + { + // reliabilityLayerMutexes[windowSize_MUTEX].Lock(); + //printf("windowSize=%i lossyWindowSize=%i\n", windowSize, lossyWindowSize); + + if ( windowSize < lossyWindowSize || (time>lastWindowIncreaseSizeTime && time-lastWindowIncreaseSizeTime>lostPacketResendDelay*2) ) // Increases the window size slowly, testing for packetloss + { + // If we get a frame which clears out the resend queue after handling one or more acks, and we have packets waiting to go out, + // and we didn't recently lose a packet then increase the window size by 1 + windowSize++; + + if ( (time>lastWindowIncreaseSizeTime && time-lastWindowIncreaseSizeTime>lostPacketResendDelay*2) ) // The increase is to test for packetloss + lastWindowIncreaseSizeTime = time; + + // If the window is so large that we couldn't possibly fit any more packets into the frame, then just leave it alone + if ( windowSize > MAXIMUM_WINDOW_SIZE ) + windowSize = MAXIMUM_WINDOW_SIZE; + + // SHOW - WINDOWING + //else + // printf("Increasing windowSize to %i. Lossy window size = %i\n", windowSize, lossyWindowSize); + + // If we are more than 5 over the lossy window size, increase the lossy window size by 1 + if ( windowSize == MAXIMUM_WINDOW_SIZE || windowSize - lossyWindowSize > 5 ) + lossyWindowSize++; + } + // reliabilityLayerMutexes[windowSize_MUTEX].Unlock(); + } + */ + + if (hasAcks) + { + UpdateWindowFromAck(time); + } + + receivePacketCount++; + + return true; +} + +//------------------------------------------------------------------------------------------------------- +// This gets an end-user packet already parsed out. Returns number of BITS put into the buffer +//------------------------------------------------------------------------------------------------------- +int ReliabilityLayer::Receive( unsigned char **data ) +{ + // Wait until the clear occurs + if (freeThreadedMemoryOnNextUpdate) + return 0; + + InternalPacket * internalPacket; + + if ( outputQueue.Size() > 0 ) + { + // #ifdef _DEBUG + // assert(bitStream->GetNumberOfBitsUsed()==0); + // #endif + internalPacket = outputQueue.Pop(); + + int bitLength; + *data = internalPacket->data; + bitLength = internalPacket->dataBitLength; + internalPacketPool.ReleasePointer( internalPacket ); + return bitLength; + } + + else + { + return 0; + } + +} + +//------------------------------------------------------------------------------------------------------- +// Puts data on the send queue +// bitStream contains the data to send +// priority is what priority to send the data at +// reliability is what reliability to use +// ordering channel is from 0 to 255 and specifies what stream to use +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::Send( char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, unsigned char orderingChannel, bool makeDataCopy, int MTUSize, RakNetTimeNS currentTime ) +{ +#ifdef _DEBUG + assert( !( reliability > RELIABLE_SEQUENCED || reliability < 0 ) ); + assert( !( priority > NUMBER_OF_PRIORITIES || priority < 0 ) ); + assert( !( orderingChannel < 0 || orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) ); + assert( numberOfBitsToSend > 0 ); +#endif + +#ifdef __USE_IO_COMPLETION_PORTS + + if ( readWriteSocket == INVALID_SOCKET ) + return false; + +#endif + + // Fix any bad parameters + if ( reliability > RELIABLE_SEQUENCED || reliability < 0 ) + reliability = RELIABLE; + + if ( priority > NUMBER_OF_PRIORITIES || priority < 0 ) + priority = HIGH_PRIORITY; + + if ( orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + orderingChannel = 0; + + int numberOfBytesToSend=BITS_TO_BYTES(numberOfBitsToSend); + if ( numberOfBitsToSend == 0 ) + { +#ifdef _DEBUG + printf( "Error!! ReliabilityLayer::Send bitStream->GetNumberOfBytesUsed()==0\n" ); +#endif + + return false; + } + InternalPacket * internalPacket = internalPacketPool.GetPointer(); + //InternalPacket * internalPacket = sendPacketSet[priority].WriteLock(); +#ifdef _DEBUG + // Remove accessing undefined memory warning + memset( internalPacket, 255, sizeof( InternalPacket ) ); +#endif + + internalPacket->creationTime = currentTime; + + if ( makeDataCopy ) + { + internalPacket->data = new unsigned char [ numberOfBytesToSend ]; + memcpy( internalPacket->data, data, numberOfBytesToSend ); +// printf("Allocated %i\n", internalPacket->data); + } + else + { + // Allocated the data elsewhere, delete it in here + internalPacket->data = ( unsigned char* ) data; +// printf("Using Pre-Allocated %i\n", internalPacket->data); + } + + internalPacket->dataBitLength = numberOfBitsToSend; + internalPacket->nextActionTime = 0; + + internalPacket->messageNumber = messageNumber; + + internalPacket->priority = priority; + internalPacket->reliability = reliability; + internalPacket->splitPacketCount = 0; + + // Calculate if I need to split the packet + int headerLength = BITS_TO_BYTES( GetBitStreamHeaderLength( internalPacket ) ); + + int maxDataSize = MTUSize - UDP_HEADER_SIZE - headerLength; + + if ( encryptor.IsKeySet() ) + maxDataSize -= 16; // Extra data for the encryptor + + bool splitPacket = numberOfBytesToSend > maxDataSize; + + // If a split packet, we might have to upgrade the reliability + if ( splitPacket ) + statistics.numberOfSplitMessages++; + else + statistics.numberOfUnsplitMessages++; + + ++messageNumber; + + + if ( internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == UNRELIABLE_SEQUENCED ) + { + // Assign the sequence stream and index + internalPacket->orderingChannel = orderingChannel; + internalPacket->orderingIndex = waitingForSequencedPacketWriteIndex[ orderingChannel ] ++; + + // This packet supersedes all other sequenced packets on the same ordering channel + // Delete all packets in all send lists that are sequenced and on the same ordering channel + // UPDATE: + // Disabled. We don't have enough info to consistently do this. Sometimes newer data does supercede + // older data such as with constantly declining health, but not in all cases. + // For example, with sequenced unreliable sound packets just because you send a newer one doesn't mean you + // don't need the older ones because the odds are they will still arrive in order + /* + for (int i=0; i < NUMBER_OF_PRIORITIES; i++) + { + DeleteSequencedPacketsInList(orderingChannel, sendQueue[i]); + } + */ + } + + else + if ( internalPacket->reliability == RELIABLE_ORDERED ) + { + // Assign the ordering channel and index + internalPacket->orderingChannel = orderingChannel; + internalPacket->orderingIndex = waitingForOrderedPacketWriteIndex[ orderingChannel ] ++; + } + + if ( splitPacket ) // If it uses a secure header it will be generated here + { + // Must split the packet. This will also generate the SHA1 if it is required. It also adds it to the send list. + //InternalPacket packetCopy; + //memcpy(&packetCopy, internalPacket, sizeof(InternalPacket)); + //sendPacketSet[priority].CancelWriteLock(internalPacket); + //SplitPacket( &packetCopy, MTUSize ); + SplitPacket( internalPacket, MTUSize ); + //delete [] packetCopy.data; + return true; + } + + sendPacketSet[ internalPacket->priority ].Push( internalPacket ); + +// sendPacketSet[priority].WriteUnlock(); + return true; +} + +//------------------------------------------------------------------------------------------------------- +// Run this once per game cycle. Handles internal lists and actually does the send +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ) +{ +#ifdef __USE_IO_COMPLETION_PORTS + + if ( readWriteSocket == INVALID_SOCKET ) + return; + + if (deadConnection) + return; +#endif + + // This line is necessary because the timer isn't accurate + if (time <= lastUpdateTime) + { + // Always set the last time in case of overflow + lastUpdateTime=time; + return; + } + + RakNetTimeNS elapsedTime = time - lastUpdateTime; + availableBandwidth+=currentBandwidth * ((double)elapsedTime/1000000.0f); + if (availableBandwidth > currentBandwidth) + availableBandwidth = currentBandwidth; + lastUpdateTime=time; + + // unsigned resendListSize; + bool reliableDataSent; + UpdateThreadedMemory(); + + // Due to thread vagarities and the way I store the time to avoid slow calls to RakNet::GetTime + // time may be less than lastAck + if ( resendList.IsEmpty()==false && time > lastAckTime && lastAckTime && time - lastAckTime > (RakNetTimeNS)timeoutTime*1000 ) + { + // SHOW - dead connection + // printf("The connection has been lost.\n"); + // We've waited a very long time for a reliable packet to get an ack and it never has + deadConnection = true; + return; + } + + // Water canister has to have enough room to put more water in :) + double requiredBuffer=(float)((MTUSize+UDP_HEADER_SIZE)*8); + if (requiredBuffer > currentBandwidth) + requiredBuffer=currentBandwidth; + + while ( availableBandwidth > requiredBuffer ) + { + updateBitStream.Reset(); + GenerateDatagram( &updateBitStream, MTUSize, &reliableDataSent, time, playerId, messageHandlerList ); + if ( updateBitStream.GetNumberOfBitsUsed() > 0 ) + { +#ifndef _RELEASE + if (minExtraPing > 0 || extraPingVariance > 0) + { + // Delay the send to simulate lag + DataAndTime *dt; + dt = new DataAndTime; + memcpy( dt->data, updateBitStream.GetData(), updateBitStream.GetNumberOfBytesUsed() ); + dt->length = updateBitStream.GetNumberOfBytesUsed(); + dt->sendTime = time + (RakNetTimeNS)minExtraPing*1000; + if (extraPingVariance > 0) + dt->sendTime += ( randomMT() % (int)extraPingVariance ); + delayList.Insert( dt ); + } + else +#endif + SendBitStream( s, playerId, &updateBitStream ); + + availableBandwidth-=updateBitStream.GetNumberOfBitsUsed()+UDP_HEADER_SIZE*8; + } + else + break; + } + + bool lastContinuousSend=continuousSend; + continuousSend=availableBandwidth < requiredBuffer; + + if (continuousSend==true && lastContinuousSend==false) + { + histogramAckCount=0; + histogramPlossCount=0; + histogramStartTime=time+ping*2*1000; + histogramEndTime=histogramStartTime+MAX_TIME_TO_SAMPLE; + if (++histogramReceiveMarker==(unsigned)-1) + histogramReceiveMarker=0; + } + + if (time >= histogramEndTime ) + { + float packetloss; + + double delta; + if (histogramAckCount+histogramPlossCount) + packetloss=(float)histogramPlossCount / ((float)histogramAckCount+(float)histogramPlossCount); + else + packetloss=0.0f; // This line can be true if we are sending only acks + + if (continuousSend==false) + { + if (packetloss > PACKETLOSS_TOLERANCE) + { + highBandwidth=currentBandwidth; + if (packetloss > .2) + { + lowBandwidth/=2; + } + else + { + lowBandwidth*=.9; + } + + if (lowBandwidth < MINIMUM_SEND_BPS) + lowBandwidth=MINIMUM_SEND_BPS; + + delta = (highBandwidth-lowBandwidth)/2; + currentBandwidth=delta+lowBandwidth; + noPacketlossIncreaseCount=0; + } + } + else + { + if (packetloss <= PACKETLOSS_TOLERANCE) + lowBandwidth=currentBandwidth; + else + highBandwidth=currentBandwidth; + + if (packetloss==0.0) + { + // If no packetloss for many increases in a row, drop the high range and go into search mode. + if (++noPacketlossIncreaseCount==10) + { + noPacketlossIncreaseCount=0; + highBandwidth=0; + } + } + else + noPacketlossIncreaseCount=0; + if (highBandwidth!=0.0) + { + // If a lot of packetloss at any time, decrease the low range by half + if (packetloss > .2) + { + lowBandwidth/=2; + if (lowBandwidth < MINIMUM_SEND_BPS) + lowBandwidth=MINIMUM_SEND_BPS; + } + + delta = (highBandwidth-lowBandwidth)/2; + if (delta < MINIMUM_SEND_BPS/4) + { + // If no packetloss and done searching, increase the high range by 50% + if (packetloss==0.0) + { + highBandwidth*=1.5; + } + else if (packetloss < PACKETLOSS_TOLERANCE) + { + // If some packetloss and done searching, increase the high range by 5% + highBandwidth*=1.05; + } + else if (packetloss < PACKETLOSS_TOLERANCE*2) + { + // If some packetloss, but not a huge amount and done searching, decrease the low range by 10% + lowBandwidth*=.9; + if (lowBandwidth < MINIMUM_SEND_BPS) + lowBandwidth=MINIMUM_SEND_BPS; + } + delta = (highBandwidth-lowBandwidth)/2; + } + currentBandwidth=delta+lowBandwidth; + } + else + { + // Don't know the maximum bandwidth, so keep doubling to find out + currentBandwidth*=2.0; + } + } + + histogramPlossCount=0; + histogramAckCount=0; + histogramStartTime=time+TIME_TO_NEW_SAMPLE+ping*2*1000; + histogramEndTime=histogramStartTime+MAX_TIME_TO_SAMPLE; + if (++histogramReceiveMarker==(unsigned)-1) + histogramReceiveMarker=0; + } + +#ifndef _RELEASE + // Do any lagged sends + unsigned i = 0; + while ( i < delayList.Size() ) + { + if ( delayList[ i ]->sendTime < time ) + { + updateBitStream.Reset(); + updateBitStream.Write( delayList[ i ]->data, delayList[ i ]->length ); + // Send it now + SendBitStream( s, playerId, &updateBitStream ); + + delete delayList[ i ]; + if (i != delayList.Size() - 1) + delayList[ i ] = delayList[ delayList.Size() - 1 ]; + delayList.Del(); + } + + else + i++; + } +#endif + +} + +//------------------------------------------------------------------------------------------------------- +// Writes a bitstream to the socket +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SendBitStream( SOCKET s, PlayerID playerId, RakNet::BitStream *bitStream ) +{ + // SHOW - showing reliable flow + // if (bitStream->GetNumberOfBytesUsed()>50) + // printf("Sending %i bytes. sendQueue[0].Size()=%i, resendList.Size()=%i\n", bitStream->GetNumberOfBytesUsed(), sendQueue[0].Size(),resendList.Size()); + + int oldLength, length; + + // sentFrames++; + +#ifndef _RELEASE + if (maxSendBPS>0) + { + double chanceToLosePacket = (double)currentBandwidth / (double)maxSendBPS; + if (frandomMT() < (float)chanceToLosePacket) + return; + + } +#endif + + // Encode the whole bitstream if the encoder is defined. + + if ( encryptor.IsKeySet() ) + { + length = bitStream->GetNumberOfBytesUsed(); + oldLength = length; + + encryptor.Encrypt( ( unsigned char* ) bitStream->GetData(), length, ( unsigned char* ) bitStream->GetData(), &length ); + statistics.encryptionBitsSent = ( length - oldLength ) * 8; + + assert( ( length % 16 ) == 0 ); + } + else + { + length = bitStream->GetNumberOfBytesUsed(); + } + +#ifdef __USE_IO_COMPLETION_PORTS + if ( readWriteSocket == INVALID_SOCKET ) + { + assert( 0 ); + return ; + } + + statistics.packetsSent++; + statistics.totalBitsSent += length * 8; + SocketLayer::Instance()->Write( readWriteSocket, ( const char* ) bitStream->GetData(), length ); +#else + + statistics.packetsSent++; + statistics.totalBitsSent += length * 8; + //printf("total bits=%i length=%i\n", BITS_TO_BYTES(statistics.totalBitsSent), length); + + SocketLayer::Instance()->SendTo( s, ( char* ) bitStream->GetData(), length, playerId.binaryAddress, playerId.port ); +#endif // __USE_IO_COMPLETION_PORTS + + // lastPacketSendTime=time; +} + +//------------------------------------------------------------------------------------------------------- +// Generates a datagram (coalesced packets) +//------------------------------------------------------------------------------------------------------- +unsigned ReliabilityLayer::GenerateDatagram( RakNet::BitStream *output, int MTUSize, bool *reliableDataSent, RakNetTimeNS time, PlayerID playerId, DataStructures::List &messageHandlerList ) +{ + InternalPacket * internalPacket; +// InternalPacket *temp; + int maxDataBitSize; + int reliableBits = 0; + int nextPacketBitLength; + unsigned i, messageHandlerIndex; + bool isReliable, onlySendUnreliable; + bool writeFalseToHeader; + unsigned messagesSent=0; + + maxDataBitSize = MTUSize - UDP_HEADER_SIZE; + + if ( encryptor.IsKeySet() ) + maxDataBitSize -= 16; // Extra data for the encryptor + + maxDataBitSize <<= 3; + + *reliableDataSent = false; + + if (time > nextAckTime) + { + if (acknowlegements.Size()>0) + { + output->Write(true); + messagesSent++; + statistics.acknowlegementBitsSent +=acknowlegements.Serialize(output, (MTUSize-UDP_HEADER_SIZE)*8-1, true); + if (acknowlegements.Size()==0) + nextAckTime=time+(RakNetTimeNS)(ping*(RakNetTime)(PING_MULTIPLIER_TO_RESEND/4.0f)); + else + { + // printf("Ack full\n"); + } + + writeFalseToHeader=false; + } + else + { + writeFalseToHeader=true; + nextAckTime=time+(RakNetTimeNS)(ping*(RakNetTime)(PING_MULTIPLIER_TO_RESEND/4.0f)); + } + } + else + writeFalseToHeader=true; + + while ( resendQueue.Size() > 0 ) + { + internalPacket = resendQueue.Peek(); + // The resend Queue can have holes. This is so we can deallocate blocks without having to compress the array + if ( internalPacket->nextActionTime == 0 ) + { + resendQueue.Pop(); + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + continue; // This was a hole + } + + if ( resendQueue.Peek()->nextActionTime < time ) + { + internalPacket = resendQueue.Pop(); + + nextPacketBitLength = GetBitStreamHeaderLength( internalPacket ) + internalPacket->dataBitLength; + + if ( output->GetNumberOfBitsUsed() + nextPacketBitLength > maxDataBitSize ) + { + resendQueue.PushAtHead( internalPacket ); // Not enough room to use this packet after all! + + goto END_OF_GENERATE_FRAME; + } + + RakAssert(internalPacket->priority >= 0); + +#ifdef _DEBUG_LOGGER + { + char temp[256]; + sprintf(temp, "%p: Resending packet %i data: %i bitlen: %i\n", this, internalPacket->messageNumber, (unsigned char) internalPacket->data[0], internalPacket->dataBitLength); + OutputDebugStr(temp); + } +#endif + + for (messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, sendPacketCount, playerId, (RakNetTime)(time/(RakNetTimeNS)1000), true); + + // Write to the output bitstream + statistics.messageResends++; + statistics.messageDataBitsResent += internalPacket->dataBitLength; + + if (writeFalseToHeader) + { + output->Write(false); + writeFalseToHeader=false; + } + statistics.messagesTotalBitsResent += WriteToBitStreamFromInternalPacket( output, internalPacket ); + internalPacket->packetNumber=sendPacketCount; + messagesSent++; + + *reliableDataSent = true; + + statistics.packetsContainingOnlyAcknowlegementsAndResends++; + + internalPacket->nextActionTime = time + ackTimeIncrement; + if (time >= histogramStartTime && internalPacket->histogramMarker==histogramReceiveMarker) + histogramPlossCount++; + + internalPacket->histogramMarker=histogramReceiveMarker; + + //printf("PACKETLOSS\n "); + + // Put the packet back into the resend list at the correct spot + // Don't make a copy since I'm reinserting an allocated struct + InsertPacketIntoResendList( internalPacket, time, false, false ); + + } + else + { + break; + } + } + + + onlySendUnreliable = false; + + + // From highest to lowest priority, fill up the output bitstream from the send lists + for ( i = 0; i < NUMBER_OF_PRIORITIES; i++ ) + { + while ( sendPacketSet[ i ].Size() ) + { + internalPacket = sendPacketSet[ i ].Pop(); + + nextPacketBitLength = GetBitStreamHeaderLength( internalPacket ) + internalPacket->dataBitLength; + + if (unreliableTimeout!=0 && + (internalPacket->reliability==UNRELIABLE || internalPacket->reliability==UNRELIABLE_SEQUENCED) && + time > internalPacket->creationTime+(RakNetTimeNS)unreliableTimeout) + { + // Unreliable packets are deleted + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + continue; + } + + + if ( output->GetNumberOfBitsUsed() + nextPacketBitLength > maxDataBitSize ) + { + // This output won't fit. + sendPacketSet[ i ].PushAtHead( internalPacket ); // Push this back at the head so it is the next thing to go out + break; + } + + if ( internalPacket->reliability == RELIABLE || internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED ) + isReliable = true; + else + isReliable = false; + + // Write to the output bitstream + statistics.messagesSent[ i ] ++; + statistics.messageDataBitsSent[ i ] += internalPacket->dataBitLength; + +#ifdef _DEBUG_LOGGER + { + char temp[256]; + sprintf(temp, "%p: Sending packet %i data: %i bitlen: %i\n", this, internalPacket->messageNumber, (unsigned char) internalPacket->data[0], internalPacket->dataBitLength); + OutputDebugStr(temp); + } +#endif + + for (messageHandlerIndex=0; messageHandlerIndex < messageHandlerList.Size(); messageHandlerIndex++) + messageHandlerList[messageHandlerIndex]->OnInternalPacket(internalPacket, sendPacketCount, playerId, (RakNetTime)(time/(RakNetTimeNS)1000), true); + + if (writeFalseToHeader) + { + output->Write(false); + writeFalseToHeader=false; + } + statistics.messageTotalBitsSent[ i ] += WriteToBitStreamFromInternalPacket( output, internalPacket ); + //output->PrintBits(); + internalPacket->packetNumber=sendPacketCount; + messagesSent++; + + if ( isReliable ) + { + // Reliable packets are saved to resend later + reliableBits += internalPacket->dataBitLength; + internalPacket->nextActionTime = time + ackTimeIncrement; + internalPacket->histogramMarker=histogramReceiveMarker; + resendList.Insert( internalPacket->messageNumber, internalPacket); + //printf("ackTimeIncrement=%i\n", ackTimeIncrement/1000); + InsertPacketIntoResendList( internalPacket, time, false, true); + *reliableDataSent = true; + } + else + { + // Unreliable packets are deleted + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + } + } + +END_OF_GENERATE_FRAME: + ; + + // if (output->GetNumberOfBitsUsed()>0) + // { + // Update the throttle with the header + // bytesSent+=output->GetNumberOfBytesUsed() + UDP_HEADER_SIZE; + //} + + if (output->GetNumberOfBitsUsed()>0) + sendPacketCount++; + + return messagesSent; +} + +//------------------------------------------------------------------------------------------------------- +// Are we waiting for any data to be sent out or be processed by the player? +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsDataWaiting(void) +{ + unsigned i; + for ( i = 0; i < NUMBER_OF_PRIORITIES; i++ ) + { + if (sendPacketSet[ i ].Size() > 0) + return true; + } + + return acknowlegements.Size() > 0 || resendList.IsEmpty()==false || outputQueue.Size() > 0 || orderingList.Size() > 0 || splitPacketChannelList.Size() > 0; +} + +bool ReliabilityLayer::AreAcksWaiting(void) +{ + return acknowlegements.Size() > 0; +} + +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::ApplyNetworkSimulator( double _maxSendBPS, RakNetTime _minExtraPing, RakNetTime _extraPingVariance ) +{ +#ifndef _RELEASE + maxSendBPS=_maxSendBPS; + minExtraPing=_minExtraPing; + extraPingVariance=_extraPingVariance; + if (ping < (unsigned int)(minExtraPing+extraPingVariance)*2) + ping=(minExtraPing+extraPingVariance)*2; +#endif +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetSplitMessageProgressInterval(int interval) +{ + splitMessageProgressInterval=interval; +} +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SetUnreliableTimeout(RakNetTime timeoutMS) +{ + unreliableTimeout=(RakNetTimeNS)timeoutMS*(RakNetTimeNS)1000; +} + +//------------------------------------------------------------------------------------------------------- +// This will return true if we should not send at this time +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsSendThrottled( int MTUSize ) +{ + return false; +// return resendList.Size() > windowSize; + + // Disabling this, because it can get stuck here forever + /* + unsigned packetsWaiting; + unsigned resendListDataSize=0; + unsigned i; + for (i=0; i < resendList.Size(); i++) + { + if (resendList[i]) + resendListDataSize+=resendList[i]->dataBitLength; + } + packetsWaiting = 1 + ((BITS_TO_BYTES(resendListDataSize)) / (MTUSize - UDP_HEADER_SIZE - 10)); // 10 to roughly estimate the raknet header + + return packetsWaiting >= windowSize; + */ +} + +//------------------------------------------------------------------------------------------------------- +// We lost a packet +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateWindowFromPacketloss( RakNetTimeNS time ) +{ + +} + +//------------------------------------------------------------------------------------------------------- +// Increase the window size +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateWindowFromAck( RakNetTimeNS time ) +{ + +} + +//------------------------------------------------------------------------------------------------------- +// Does what the function name says +//------------------------------------------------------------------------------------------------------- +unsigned ReliabilityLayer::RemovePacketFromResendListAndDeleteOlderReliableSequenced( const MessageNumberType messageNumber, RakNetTimeNS time ) +{ + InternalPacket * internalPacket; + //InternalPacket *temp; + PacketReliability reliability; // What type of reliability algorithm to use with this packet + unsigned char orderingChannel; // What ordering channel this packet is on, if the reliability type uses ordering channels + OrderingIndexType orderingIndex; // The ID used as identification for ordering channels +// unsigned j; + + bool deleted; + deleted=resendList.Delete(messageNumber, internalPacket); + if (deleted) + { + reliability = internalPacket->reliability; + orderingChannel = internalPacket->orderingChannel; + orderingIndex = internalPacket->orderingIndex; +// delete [] internalPacket->data; +// internalPacketPool.ReleasePointer( internalPacket ); + internalPacket->nextActionTime=0; // Will be freed in the update function + return internalPacket->histogramMarker; + + // Rarely used and thus disabled for speed + /* + // If the deleted packet was reliable sequenced, also delete all older reliable sequenced resends on the same ordering channel. + // This is because we no longer need to send these. + if ( reliability == RELIABLE_SEQUENCED ) + { + unsigned j = 0; + while ( j < resendList.Size() ) + { + internalPacket = resendList[ j ]; + + if ( internalPacket && internalPacket->reliability == RELIABLE_SEQUENCED && internalPacket->orderingChannel == orderingChannel && IsOlderOrderedPacket( internalPacket->orderingIndex, orderingIndex ) ) + { + // Delete the packet + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + resendList[ j ] = 0; // Generate a hole + } + + j++; + } + + } + */ + } + else + { + + statistics.duplicateAcknowlegementsReceived++; + } + + return (unsigned)-1; +} + +//------------------------------------------------------------------------------------------------------- +// Acknowledge receipt of the packet with the specified messageNumber +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SendAcknowledgementPacket( const MessageNumberType messageNumber, RakNetTimeNS time ) +{ + statistics.acknowlegementsSent++; + acknowlegements.Insert(messageNumber); +} + +//------------------------------------------------------------------------------------------------------- +// Parse an internalPacket and figure out how many header bits would be +// written. Returns that number +//------------------------------------------------------------------------------------------------------- +int ReliabilityLayer::GetBitStreamHeaderLength( const InternalPacket *const internalPacket ) +{ +#ifdef _DEBUG + assert( internalPacket ); +#endif + + int bitLength; + + bitLength=sizeof(MessageNumberType)*2*8; + + // Write the PacketReliability. This is encoded in 3 bits + //bitStream->WriteBits((unsigned char*)&(internalPacket->reliability), 3, true); + bitLength += 3; + + // If the reliability requires an ordering channel and ordering index, we Write those. + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED ) + { + // ordering channel encoded in 5 bits (from 0 to 31) + //bitStream->WriteBits((unsigned char*)&(internalPacket->orderingChannel), 5, true); + bitLength+=5; + + // ordering index is one byte + //bitStream->WriteCompressed(internalPacket->orderingIndex); + bitLength+=sizeof(OrderingIndexType)*8; + } + + // Write if this is a split packet (1 bit) + bool isSplitPacket = internalPacket->splitPacketCount > 0; + + //bitStream->Write(isSplitPacket); + bitLength += 1; + + if ( isSplitPacket ) + { + // split packet indices are two bytes (so one packet can be split up to 65535 + // times - maximum packet size would be about 500 * 65535) + //bitStream->Write(internalPacket->splitPacketId); + //bitStream->WriteCompressed(internalPacket->splitPacketIndex); + //bitStream->WriteCompressed(internalPacket->splitPacketCount); + bitLength += (sizeof(SplitPacketIdType) + sizeof(SplitPacketIndexType) * 2) * 8; + } + + // Write how many bits the packet data is. Stored in an unsigned short and + // read from 16 bits + //bitStream->WriteBits((unsigned char*)&(internalPacket->dataBitLength), 16, true); + + // Read how many bits the packet data is. Stored in 16 bits + bitLength += 16; + + // Byte alignment + //bitLength += 8 - ((bitLength -1) %8 + 1); + + return bitLength; +} + +//------------------------------------------------------------------------------------------------------- +// Parse an internalPacket and create a bitstream to represent this data +//------------------------------------------------------------------------------------------------------- +int ReliabilityLayer::WriteToBitStreamFromInternalPacket( RakNet::BitStream *bitStream, const InternalPacket *const internalPacket ) +{ +#ifdef _DEBUG + assert( bitStream && internalPacket ); +#endif + + int start = bitStream->GetNumberOfBitsUsed(); + const unsigned char c = (unsigned char) internalPacket->reliability; + + // testing + //if (internalPacket->reliability==UNRELIABLE) + // printf("Sending unreliable packet %i\n", internalPacket->messageNumber); + //else if (internalPacket->reliability==RELIABLE_SEQUENCED || internalPacket->reliability==RELIABLE_ORDERED || internalPacket->reliability==RELIABLE) + // printf("Sending reliable packet number %i\n", internalPacket->messageNumber); + + //bitStream->AlignWriteToByteBoundary(); + + // Write the message number (2 bytes) + bitStream->Write( internalPacket->messageNumber ); + + // Acknowledgment packets have no more data than the messageNumber and whether it is anacknowledgment + + +#ifdef _DEBUG + assert( internalPacket->dataBitLength > 0 ); +#endif + + // Write the PacketReliability. This is encoded in 3 bits + bitStream->WriteBits( (const unsigned char *)&c, 3, true ); + + // If the reliability requires an ordering channel and ordering index, we Write those. + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED ) + { + // ordering channel encoded in 5 bits (from 0 to 31) + bitStream->WriteBits( ( unsigned char* ) & ( internalPacket->orderingChannel ), 5, true ); + + // One or two bytes + bitStream->Write( internalPacket->orderingIndex ); + } + + // Write if this is a split packet (1 bit) + bool isSplitPacket = internalPacket->splitPacketCount > 0; + + bitStream->Write( isSplitPacket ); + + if ( isSplitPacket ) + { + bitStream->Write( internalPacket->splitPacketId ); + bitStream->WriteCompressed( internalPacket->splitPacketIndex ); + bitStream->WriteCompressed( internalPacket->splitPacketCount ); + } + + // Write how many bits the packet data is. Stored in 13 bits +#ifdef _DEBUG + assert( BITS_TO_BYTES( internalPacket->dataBitLength ) < MAXIMUM_MTU_SIZE ); // I never send more than MTU_SIZE bytes + +#endif + + unsigned short length = ( unsigned short ) internalPacket->dataBitLength; // Ignore the 2 high bytes for WriteBits + + bitStream->WriteCompressed( length ); + + // Write the actual data. + bitStream->WriteAlignedBytes( ( unsigned char* ) internalPacket->data, BITS_TO_BYTES( internalPacket->dataBitLength ) ); + + //bitStream->WriteBits((unsigned char*)internalPacket->data, internalPacket->dataBitLength); + + return bitStream->GetNumberOfBitsUsed() - start; +} + +//------------------------------------------------------------------------------------------------------- +// Parse a bitstream and create an internal packet to represent this data +//------------------------------------------------------------------------------------------------------- +InternalPacket* ReliabilityLayer::CreateInternalPacketFromBitStream( RakNet::BitStream *bitStream, RakNetTimeNS time ) +{ + bool bitStreamSucceeded; + InternalPacket* internalPacket; + + if ( bitStream->GetNumberOfUnreadBits() < (int) sizeof( internalPacket->messageNumber ) * 8 ) + return 0; // leftover bits + + internalPacket = internalPacketPool.GetPointer(); + +#ifdef _DEBUG + // Remove accessing undefined memory error + memset( internalPacket, 255, sizeof( InternalPacket ) ); +#endif + + internalPacket->creationTime = time; + + //bitStream->AlignReadToByteBoundary(); + + // Read the packet number (2 bytes) + bitStreamSucceeded = bitStream->Read( internalPacket->messageNumber ); + +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + // Read the PacketReliability. This is encoded in 3 bits + unsigned char reliability; + + bitStreamSucceeded = bitStream->ReadBits( ( unsigned char* ) ( &( reliability ) ), 3 ); + + internalPacket->reliability = ( const PacketReliability ) reliability; + +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + // assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + // If the reliability requires an ordering channel and ordering index, we read those. + if ( internalPacket->reliability == UNRELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_SEQUENCED || internalPacket->reliability == RELIABLE_ORDERED ) + { + // ordering channel encoded in 5 bits (from 0 to 31) + bitStreamSucceeded = bitStream->ReadBits( ( unsigned char* ) & ( internalPacket->orderingChannel ), 5 ); +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + bitStreamSucceeded = bitStream->Read( internalPacket->orderingIndex ); + +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + } + + // Read if this is a split packet (1 bit) + bool isSplitPacket; + + bitStreamSucceeded = bitStream->Read( isSplitPacket ); + +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + if ( isSplitPacket ) + { + bitStreamSucceeded = bitStream->Read( internalPacket->splitPacketId ); +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + // assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + bitStreamSucceeded = bitStream->ReadCompressed( internalPacket->splitPacketIndex ); +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + bitStreamSucceeded = bitStream->ReadCompressed( internalPacket->splitPacketCount ); +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + } + + else + internalPacket->splitPacketIndex = internalPacket->splitPacketCount = 0; + + // Optimization - do byte alignment here + //unsigned char zero; + //bitStream->ReadBits(&zero, 8 - (bitStream->GetNumberOfBitsUsed() %8)); + //assert(zero==0); + + + unsigned short length; + + bitStreamSucceeded = bitStream->ReadCompressed( length ); + + // Read into an unsigned short. Otherwise the data would be offset too high by two bytes +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + internalPacket->dataBitLength = length; +#ifdef _DEBUG + // 10/08/05 - Disabled assert since this hits from offline packets arriving when the sender does not know we just connected, which is an unavoidable condition sometimes + // assert( internalPacket->dataBitLength > 0 && BITS_TO_BYTES( internalPacket->dataBitLength ) < MAXIMUM_MTU_SIZE ); +#endif + if ( ! ( internalPacket->dataBitLength > 0 && BITS_TO_BYTES( internalPacket->dataBitLength ) < MAXIMUM_MTU_SIZE ) ) + { + // 10/08/05 - internalPacket->data wasn't allocated yet + // delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + // Allocate memory to hold our data + internalPacket->data = new unsigned char [ BITS_TO_BYTES( internalPacket->dataBitLength ) ]; + //printf("Allocating %i\n", internalPacket->data); + + // Set the last byte to 0 so if ReadBits does not read a multiple of 8 the last bits are 0'ed out + internalPacket->data[ BITS_TO_BYTES( internalPacket->dataBitLength ) - 1 ] = 0; + + // Read the data the packet holds + bitStreamSucceeded = bitStream->ReadAlignedBytes( ( unsigned char* ) internalPacket->data, BITS_TO_BYTES( internalPacket->dataBitLength ) ); + + //bitStreamSucceeded = bitStream->ReadBits((unsigned char*)internalPacket->data, internalPacket->dataBitLength); +#ifdef _DEBUG + + // 10/08/05 - Disabled assert since this hits from offline packets + //assert( bitStreamSucceeded ); +#endif + + if ( bitStreamSucceeded == false ) + { + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + return 0; + } + + // PRINTING UNRELIABLE STRINGS + // if (internalPacket->data && internalPacket->dataBitLength>5*8) + // printf("Received %s\n",internalPacket->data); + + return internalPacket; +} + +//------------------------------------------------------------------------------------------------------- +// Get the SHA1 code +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::GetSHA1( unsigned char * const buffer, unsigned int + nbytes, char code[ SHA1_LENGTH ] ) +{ + CSHA1 sha1; + + sha1.Reset(); + sha1.Update( ( unsigned char* ) buffer, nbytes ); + sha1.Final(); + memcpy( code, sha1.GetHash(), SHA1_LENGTH ); +} + +//------------------------------------------------------------------------------------------------------- +// Check the SHA1 code +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::CheckSHA1( char code[ SHA1_LENGTH ], unsigned char * + const buffer, unsigned int nbytes ) +{ + char code2[ SHA1_LENGTH ]; + GetSHA1( buffer, nbytes, code2 ); + + for ( int i = 0; i < SHA1_LENGTH; i++ ) + if ( code[ i ] != code2[ i ] ) + return false; + + return true; +} + +//------------------------------------------------------------------------------------------------------- +// Search the specified list for sequenced packets on the specified ordering +// stream, optionally skipping those with splitPacketId, and delete them +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::List&theList, int splitPacketId ) +{ + unsigned i = 0; + + while ( i < theList.Size() ) + { + if ( ( theList[ i ]->reliability == RELIABLE_SEQUENCED || theList[ i ]->reliability == UNRELIABLE_SEQUENCED ) && + theList[ i ]->orderingChannel == orderingChannel && ( splitPacketId == -1 || theList[ i ]->splitPacketId != (unsigned int) splitPacketId ) ) + { + InternalPacket * internalPacket = theList[ i ]; + theList.RemoveAtIndex( i ); + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + } + + else + i++; + } +} + +//------------------------------------------------------------------------------------------------------- +// Search the specified list for sequenced packets with a value less than orderingIndex and delete them +// Note - I added functionality so you can use the Queue as a list (in this case for searching) but it is less efficient to do so than a regular list +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::Queue&theList ) +{ + InternalPacket * internalPacket; + int listSize = theList.Size(); + int i = 0; + + while ( i < listSize ) + { + if ( ( theList[ i ]->reliability == RELIABLE_SEQUENCED || theList[ i ]->reliability == UNRELIABLE_SEQUENCED ) && theList[ i ]->orderingChannel == orderingChannel ) + { + internalPacket = theList[ i ]; + theList.Del( i ); + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + listSize--; + } + + else + i++; + } +} + +//------------------------------------------------------------------------------------------------------- +// Returns true if newPacketOrderingIndex is older than the waitingForPacketOrderingIndex +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsOlderOrderedPacket( OrderingIndexType newPacketOrderingIndex, OrderingIndexType waitingForPacketOrderingIndex ) +{ + // This should give me 255 or 65535 + OrderingIndexType maxRange = (OrderingIndexType) -1; + + if ( waitingForPacketOrderingIndex > maxRange/2 ) + { + if ( newPacketOrderingIndex >= waitingForPacketOrderingIndex - maxRange/2+1 && newPacketOrderingIndex < waitingForPacketOrderingIndex ) + { + return true; + } + } + + else + if ( newPacketOrderingIndex >= ( OrderingIndexType ) ( waitingForPacketOrderingIndex - (( OrderingIndexType ) maxRange/2+1) ) || + newPacketOrderingIndex < waitingForPacketOrderingIndex ) + { + return true; + } + + // Old packet + return false; +} + +//------------------------------------------------------------------------------------------------------- +// Split the passed packet into chunks under MTU_SIZEbytes (including headers) and save those new chunks +// Optimized version +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::SplitPacket( InternalPacket *internalPacket, int MTUSize ) +{ + // Doing all sizes in bytes in this function so I don't write partial bytes with split packets + internalPacket->splitPacketCount = 1; // This causes GetBitStreamHeaderLength to account for the split packet header + int headerLength = BITS_TO_BYTES( GetBitStreamHeaderLength( internalPacket ) ); + int dataByteLength = BITS_TO_BYTES( internalPacket->dataBitLength ); + int maxDataSize; + int maximumSendBlock, byteOffset, bytesToSend; + SplitPacketIndexType splitPacketIndex; + int i; + InternalPacket **internalPacketArray; + + maxDataSize = MTUSize - UDP_HEADER_SIZE; + + if ( encryptor.IsKeySet() ) + maxDataSize -= 16; // Extra data for the encryptor + +#ifdef _DEBUG + // Make sure we need to split the packet to begin with + assert( dataByteLength > maxDataSize - headerLength ); +#endif + + // How much to send in the largest block + maximumSendBlock = maxDataSize - headerLength; + + // Calculate how many packets we need to create + internalPacket->splitPacketCount = ( ( dataByteLength - 1 ) / ( maximumSendBlock ) + 1 ); + + statistics.totalSplits += internalPacket->splitPacketCount; + + // Optimization + // internalPacketArray = new InternalPacket*[internalPacket->splitPacketCount]; + bool usedAlloca=false; + #if !defined(_COMPATIBILITY_1) + if (sizeof( InternalPacket* ) * internalPacket->splitPacketCount < MAX_ALLOCA_STACK_ALLOCATION) + { + internalPacketArray = ( InternalPacket** ) alloca( sizeof( InternalPacket* ) * internalPacket->splitPacketCount ); + usedAlloca=true; + } + else + #endif + internalPacketArray = new InternalPacket*[internalPacket->splitPacketCount]; + + for ( i = 0; i < ( int ) internalPacket->splitPacketCount; i++ ) + { + internalPacketArray[ i ] = internalPacketPool.GetPointer(); + //internalPacketArray[ i ] = (InternalPacket*) alloca( sizeof( InternalPacket ) ); +// internalPacketArray[ i ] = sendPacketSet[internalPacket->priority].WriteLock(); + memcpy( internalPacketArray[ i ], internalPacket, sizeof( InternalPacket ) ); + } + + // This identifies which packet this is in the set + splitPacketIndex = 0; + + // Do a loop to send out all the packets + do + { + byteOffset = splitPacketIndex * maximumSendBlock; + bytesToSend = dataByteLength - byteOffset; + + if ( bytesToSend > maximumSendBlock ) + bytesToSend = maximumSendBlock; + + // Copy over our chunk of data + internalPacketArray[ splitPacketIndex ]->data = new unsigned char[ bytesToSend ]; + + memcpy( internalPacketArray[ splitPacketIndex ]->data, internalPacket->data + byteOffset, bytesToSend ); + + if ( bytesToSend != maximumSendBlock ) + internalPacketArray[ splitPacketIndex ]->dataBitLength = internalPacket->dataBitLength - splitPacketIndex * ( maximumSendBlock << 3 ); + else + internalPacketArray[ splitPacketIndex ]->dataBitLength = bytesToSend << 3; + + internalPacketArray[ splitPacketIndex ]->splitPacketIndex = splitPacketIndex; + internalPacketArray[ splitPacketIndex ]->splitPacketId = splitPacketId; + internalPacketArray[ splitPacketIndex ]->splitPacketCount = internalPacket->splitPacketCount; + + if ( splitPacketIndex > 0 ) // For the first split packet index we keep the messageNumber already assigned + { + // For every further packet we use a new messageNumber. + // Note that all split packets are reliable + internalPacketArray[ splitPacketIndex ]->messageNumber = messageNumber; + + //if ( ++messageNumber == RECEIVED_PACKET_LOG_LENGTH ) + // messageNumber = 0; + ++messageNumber; + + } + + // Add the new packet to send list at the correct priority + // sendQueue[internalPacket->priority].Insert(newInternalPacket); + // SHOW SPLIT PACKET GENERATION + // if (splitPacketIndex % 100 == 0) + // printf("splitPacketIndex=%i\n",splitPacketIndex); + //} while(++splitPacketIndex < internalPacket->splitPacketCount); + } + + while ( ++splitPacketIndex < internalPacket->splitPacketCount ); + + splitPacketId++; // It's ok if this wraps to 0 + +// InternalPacket *workingPacket; + + // Copy all the new packets into the split packet list + for ( i = 0; i < ( int ) internalPacket->splitPacketCount; i++ ) + { + sendPacketSet[ internalPacket->priority ].Push( internalPacketArray[ i ] ); +// workingPacket=sendPacketSet[internalPacket->priority].WriteLock(); +// memcpy(workingPacket, internalPacketArray[ i ], sizeof(InternalPacket)); +// sendPacketSet[internalPacket->priority].WriteUnlock(); + } + + // Delete the original + delete [] internalPacket->data; + internalPacketPool.ReleasePointer( internalPacket ); + + if (usedAlloca==false) + delete [] internalPacketArray; +} + +//------------------------------------------------------------------------------------------------------- +// Insert a packet into the split packet list +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InsertIntoSplitPacketList( InternalPacket * internalPacket, RakNetTimeNS time ) +{ + bool objectExists; + unsigned index; + index=splitPacketChannelList.GetIndexFromKey(internalPacket->splitPacketId, &objectExists); + if (objectExists==false) + { + SplitPacketChannel *newChannel = new SplitPacketChannel; + index=splitPacketChannelList.Insert(internalPacket->splitPacketId, newChannel); + } + splitPacketChannelList[index]->splitPacketList.Insert(internalPacket->splitPacketIndex, internalPacket); + splitPacketChannelList[index]->lastUpdateTime=time; + + if (splitMessageProgressInterval && + splitPacketChannelList[index]->splitPacketList[0]->splitPacketIndex==0 && + splitPacketChannelList[index]->splitPacketList.Size()!=splitPacketChannelList[index]->splitPacketList[0]->splitPacketCount && + (splitPacketChannelList[index]->splitPacketList.Size()%splitMessageProgressInterval)==0) + { +// printf("msgID=%i Progress %i/%i Partsize=%i\n", +// splitPacketChannelList[index]->splitPacketList[0]->data[0], +// splitPacketChannelList[index]->splitPacketList.Size(), +// internalPacket->splitPacketCount, +// BITS_TO_BYTES(splitPacketChannelList[index]->splitPacketList[0]->dataBitLength)); + + // Return ID_DOWNLOAD_PROGRESS + // Write splitPacketIndex (SplitPacketIndexType) + // Write splitPacketCount (SplitPacketIndexType) + // Write byteLength (4) + // Write data, splitPacketChannelList[index]->splitPacketList[0]->data + InternalPacket *progressIndicator = internalPacketPool.GetPointer(); + unsigned int length = sizeof(MessageID) + sizeof(unsigned int)*2 + sizeof(unsigned int) + BITS_TO_BYTES(splitPacketChannelList[index]->splitPacketList[0]->dataBitLength); + progressIndicator->data = new unsigned char [length]; + progressIndicator->dataBitLength=BYTES_TO_BITS(length); + progressIndicator->data[0]=(MessageID)ID_DOWNLOAD_PROGRESS; + unsigned int temp; + temp=splitPacketChannelList[index]->splitPacketList.Size(); + memcpy(progressIndicator->data+sizeof(MessageID), &temp, sizeof(unsigned int)); + temp=(unsigned int)internalPacket->splitPacketCount; + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*1, &temp, sizeof(unsigned int)); + temp=BITS_TO_BYTES(splitPacketChannelList[index]->splitPacketList[0]->dataBitLength); + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*2, &temp, sizeof(unsigned int)); + memcpy(progressIndicator->data+sizeof(MessageID)+sizeof(unsigned int)*3, splitPacketChannelList[index]->splitPacketList[0]->data, BITS_TO_BYTES(splitPacketChannelList[index]->splitPacketList[0]->dataBitLength)); + outputQueue.Push(progressIndicator); + } +} + +//------------------------------------------------------------------------------------------------------- +// Take all split chunks with the specified splitPacketId and try to +//reconstruct a packet. If we can, allocate and return it. Otherwise return 0 +// Optimized version +//------------------------------------------------------------------------------------------------------- +InternalPacket * ReliabilityLayer::BuildPacketFromSplitPacketList( SplitPacketIdType splitPacketId, RakNetTimeNS time ) +{ + unsigned i, j; + unsigned byteProgress; + InternalPacket * internalPacket; + bool objectExists; + + i=splitPacketChannelList.GetIndexFromKey(splitPacketId, &objectExists); +#ifdef _DEBUG + assert(objectExists); +#endif + + if (splitPacketChannelList[i]->splitPacketList.Size()==splitPacketChannelList[i]->splitPacketList[0]->splitPacketCount) + { + // Reconstruct + internalPacket = CreateInternalPacketCopy( splitPacketChannelList[i]->splitPacketList[0], 0, 0, time ); + internalPacket->dataBitLength=0; + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + internalPacket->dataBitLength+=splitPacketChannelList[i]->splitPacketList[j]->dataBitLength; + + internalPacket->data = new unsigned char[ BITS_TO_BYTES( internalPacket->dataBitLength ) ]; + + byteProgress=0; + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + { + memcpy(internalPacket->data+byteProgress, splitPacketChannelList[i]->splitPacketList[j]->data, BITS_TO_BYTES(splitPacketChannelList[i]->splitPacketList[j]->dataBitLength)); + byteProgress+=BITS_TO_BYTES(splitPacketChannelList[i]->splitPacketList[j]->dataBitLength); + } + + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + { + delete [] splitPacketChannelList[i]->splitPacketList[j]->data; + internalPacketPool.ReleasePointer(splitPacketChannelList[i]->splitPacketList[j]); + } + delete splitPacketChannelList[i]; + splitPacketChannelList.RemoveAtIndex(i); + + return internalPacket; + } + + return 0; +} + +// Delete any unreliable split packets that have long since expired +void ReliabilityLayer::DeleteOldUnreliableSplitPackets( RakNetTimeNS time ) +{ + unsigned i,j; + i=0; + while (i < splitPacketChannelList.Size()) + { + if (time > splitPacketChannelList[i]->lastUpdateTime + 10000000 && + (splitPacketChannelList[i]->splitPacketList[0]->reliability==UNRELIABLE || splitPacketChannelList[i]->splitPacketList[0]->reliability==UNRELIABLE_SEQUENCED)) + { + for (j=0; j < splitPacketChannelList[i]->splitPacketList.Size(); j++) + { + delete [] splitPacketChannelList[i]->splitPacketList[j]->data; + internalPacketPool.ReleasePointer(splitPacketChannelList[i]->splitPacketList[j]); + } + delete splitPacketChannelList[i]; + splitPacketChannelList.RemoveAtIndex(i); + } + else + i++; + } +} + +//------------------------------------------------------------------------------------------------------- +// Creates a copy of the specified internal packet with data copied from the original starting at dataByteOffset for dataByteLength bytes. +// Does not copy any split data parameters as that information is always generated does not have any reason to be copied +//------------------------------------------------------------------------------------------------------- +InternalPacket * ReliabilityLayer::CreateInternalPacketCopy( InternalPacket *original, int dataByteOffset, int dataByteLength, RakNetTimeNS time ) +{ + InternalPacket * copy = internalPacketPool.GetPointer(); +#ifdef _DEBUG + // Remove accessing undefined memory error + memset( copy, 255, sizeof( InternalPacket ) ); +#endif + // Copy over our chunk of data + + if ( dataByteLength > 0 ) + { + copy->data = new unsigned char[ dataByteLength ]; + memcpy( copy->data, original->data + dataByteOffset, dataByteLength ); + } + else + copy->data = 0; + + copy->dataBitLength = dataByteLength << 3; + copy->creationTime = time; + copy->nextActionTime = 0; + copy->orderingIndex = original->orderingIndex; + copy->orderingChannel = original->orderingChannel; + copy->messageNumber = original->messageNumber; + copy->priority = original->priority; + copy->reliability = original->reliability; + + return copy; +} + +//------------------------------------------------------------------------------------------------------- +// Get the specified ordering list +//------------------------------------------------------------------------------------------------------- +DataStructures::LinkedList *ReliabilityLayer::GetOrderingListAtOrderingStream( unsigned char orderingChannel ) +{ + if ( orderingChannel >= orderingList.Size() ) + return 0; + + return orderingList[ orderingChannel ]; +} + +//------------------------------------------------------------------------------------------------------- +// Add the internal packet to the ordering list in order based on order index +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::AddToOrderingList( InternalPacket * internalPacket ) +{ +#ifdef _DEBUG + assert( internalPacket->orderingChannel < NUMBER_OF_ORDERED_STREAMS ); +#endif + + if ( internalPacket->orderingChannel >= NUMBER_OF_ORDERED_STREAMS ) + { + return; + } + + DataStructures::LinkedList *theList; + + if ( internalPacket->orderingChannel >= orderingList.Size() || orderingList[ internalPacket->orderingChannel ] == 0 ) + { + // Need a linked list in this index + orderingList.Replace( new DataStructures::LinkedList, 0, internalPacket->orderingChannel ); + theList=orderingList[ internalPacket->orderingChannel ]; + } + else + { + // Have a linked list in this index + if ( orderingList[ internalPacket->orderingChannel ]->Size() == 0 ) + { + theList=orderingList[ internalPacket->orderingChannel ]; + } + else + { + theList = GetOrderingListAtOrderingStream( internalPacket->orderingChannel ); + } + } + + theList->End(); + theList->Add(internalPacket); +} + +//------------------------------------------------------------------------------------------------------- +// Inserts a packet into the resend list in order +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::InsertPacketIntoResendList( InternalPacket *internalPacket, RakNetTimeNS time, bool makeCopyOfInternalPacket, bool firstResend ) +{ + // lastAckTime is the time we last got an acknowledgment - however we also initialize the value if this is the first resend and + // either we never got an ack before or we are inserting into an empty resend queue + if ( firstResend && (lastAckTime == 0 || resendList.IsEmpty())) + { + lastAckTime = time; // Start the timer for the ack of this packet if we aren't already waiting for an ack + } + + if (makeCopyOfInternalPacket) + { + InternalPacket *pool=internalPacketPool.GetPointer(); + //printf("Adding %i\n", internalPacket->data); + memcpy(pool, internalPacket, sizeof(InternalPacket)); + resendQueue.Push( pool ); + } + else + { + RakAssert(internalPacket->nextActionTime!=0); + + resendQueue.Push( internalPacket ); + } + +} + +//------------------------------------------------------------------------------------------------------- +// If Read returns -1 and this returns true then a modified packet was detected +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsCheater( void ) const +{ + return cheater; +} + +//------------------------------------------------------------------------------------------------------- +// Were you ever unable to deliver a packet despite retries? +//------------------------------------------------------------------------------------------------------- +bool ReliabilityLayer::IsDeadConnection( void ) const +{ + return deadConnection; +} + +//------------------------------------------------------------------------------------------------------- +// Causes IsDeadConnection to return true +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::KillConnection( void ) +{ + deadConnection=true; } //------------------------------------------------------------------------------------------------------- @@ -61,3 +2460,62 @@ void ReliabilityLayer::UpdateNextActionTime(void) else ackTimeIncrement=(RakNetTimeNS)(ping*(RakNetTime)PING_MULTIPLIER_TO_RESEND)*1000; } + +//------------------------------------------------------------------------------------------------------- +// Statistics +//------------------------------------------------------------------------------------------------------- +RakNetStatisticsStruct * const ReliabilityLayer::GetStatistics( void ) +{ + unsigned i; + + for ( i = 0; i < NUMBER_OF_PRIORITIES; i++ ) + { + statistics.messageSendBuffer[i] = sendPacketSet[i].Size(); + // statistics.messageSendBuffer[i] = sendPacketSet[i].Size(); + } + + statistics.acknowlegementsPending = acknowlegements.Size(); + statistics.messagesWaitingForReassembly = 0; + for (i=0; i < splitPacketChannelList.Size(); i++) + statistics.messagesWaitingForReassembly+=splitPacketChannelList[i]->splitPacketList.Size(); + statistics.internalOutputQueueSize = outputQueue.Size(); + statistics.bitsPerSecond = currentBandwidth; + //statistics.lossySize = lossyWindowSize == MAXIMUM_WINDOW_SIZE + 1 ? 0 : lossyWindowSize; +// statistics.lossySize=0; + statistics.messagesOnResendQueue = GetResendListDataSize(); + + return &statistics; +} + +//------------------------------------------------------------------------------------------------------- +// Returns the number of packets in the resend queue, not counting holes +//------------------------------------------------------------------------------------------------------- +unsigned int ReliabilityLayer::GetResendListDataSize(void) const +{ + /* + unsigned int i, count; + for (count=0, i=0; i < resendList.Size(); i++) + if (resendList[i]!=0) + count++; + return count; + */ + + // Not accurate but thread-safe. The commented version might crash if the queue is cleared while we loop through it + return resendList.Size(); +} + +//------------------------------------------------------------------------------------------------------- +// Process threaded commands +//------------------------------------------------------------------------------------------------------- +void ReliabilityLayer::UpdateThreadedMemory(void) +{ + if ( freeThreadedMemoryOnNextUpdate ) + { + freeThreadedMemoryOnNextUpdate = false; + FreeThreadedMemory(); + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/ReliabilityLayer.h b/raknet/ReliabilityLayer.h index 12dc9e5..e847e24 100644 --- a/raknet/ReliabilityLayer.h +++ b/raknet/ReliabilityLayer.h @@ -1,12 +1,60 @@ -// TODO: Implement ReliabilityLayer.h +/// \file +/// \brief \b [Internal] Datagram reliable, ordered, unordered and sequenced sends. Flow control. Message splitting, reassembly, and coalescence. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __RELIABILITY_LAYER_H #define __RELIABILITY_LAYER_H #include "SocketLayer.h" #include "MTUSize.h" +#include "DS_LinkedList.h" +#include "DS_List.h" +#include "PacketPriority.h" +#include "DS_Queue.h" +#include "BitStream.h" +#include "InternalPacket.h" +#include "InternalPacketPool.h" +#include "DataBlockEncryptor.h" #include "RakNetStatistics.h" +#include "SHA1.h" +#include "DS_OrderedList.h" +#include "DS_RangeList.h" +#include "DS_BPlusTree.h" +class PluginInterface; + +/// Sizeof an UDP header in byte +#define UDP_HEADER_SIZE 28 + +/// Number of ordered streams available. You can use up to 32 ordered streams +#define NUMBER_OF_ORDERED_STREAMS 32 // 2^5 + +#define RESEND_TREE_ORDER 32 + +#include "BitStream.h" + +int SplitPacketIndexComp( SplitPacketIndexType const &key, InternalPacket* const &data ); +struct SplitPacketChannel +{ + RakNetTimeNS lastUpdateTime; + DataStructures::OrderedList splitPacketList; +}; +int RAK_DLL_EXPORT SplitPacketChannelComp( SplitPacketIdType const &key, SplitPacketChannel* const &data ); + +/// Datagram reliable, ordered, unordered and sequenced sends. Flow control. Message splitting, reassembly, and coalescence. class ReliabilityLayer { public: @@ -14,41 +62,299 @@ public: /// Constructor ReliabilityLayer(); + /// Destructor + ~ReliabilityLayer(); + + /// Resets the layer for reuse + void Reset( bool resetVariables ); + + ///Sets the encryption key. Doing so will activate secure connections + /// \param[in] key Byte stream for the encryption key + void SetEncryptionKey( const unsigned char *key ); + + /// Depreciated, from IO Completion ports + /// \param[in] s The socket + void SetSocket( SOCKET s ); + + /// Returns what was passed to SetSocket + /// \return The socket + SOCKET GetSocket( void ); + + /// Set the time, in MS, to use before considering ourselves disconnected after not being able to deliver a reliable packet + /// Default time is 10,000 or 10 seconds in release and 30,000 or 30 seconds in debug. + /// \param[in] time Time, in MS + void SetTimeoutTime( RakNetTime time ); + + /// Returns the value passed to SetTimeoutTime. or the default if it was never called + /// \param[out] the value passed to SetTimeoutTime + RakNetTime GetTimeoutTime(void); + + /// Packets are read directly from the socket layer and skip the reliability layer because unconnected players do not use the reliability layer + /// This function takes packet data after a player has been confirmed as connected. + /// \param[in] buffer The socket data + /// \param[in] length The length of the socket data + /// \param[in] playerId The player that this data is from + /// \param[in] messageHandlerList A list of registered plugins + /// \param[in] MTUSize maximum datagram size + /// \retval true Success + /// \retval false Modified packet + bool HandleSocketReceiveFromConnectedPlayer( const char *buffer, int length, PlayerID playerId, DataStructures::List &messageHandlerList, int MTUSize ); + + /// This allocates bytes and writes a user-level message to those bytes. + /// \param[out] data The message + /// \return Returns number of BITS put into the buffer + int Receive( unsigned char**data ); + + /// Puts data on the send queue + /// \param[in] data The data to send + /// \param[in] numberOfBitsToSend The length of \a data in bits + /// \param[in] priority The priority level for the send + /// \param[in] reliability The reliability type for the send + /// \param[in] orderingChannel 0 to 31. Specifies what channel to use, for relational ordering and sequencing of packets. + /// \param[in] makeDataCopy If true \a data will be copied. Otherwise, only a pointer will be stored. + /// \param[in] MTUSize maximum datagram size + /// \param[in] currentTime Current time, as per RakNet::GetTime() + /// \return True or false for success or failure. + bool Send( char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, unsigned char orderingChannel, bool makeDataCopy, int MTUSize, RakNetTimeNS currentTime ); + + /// Call once per game cycle. Handles internal lists and actually does the send. + /// \param[in] s the communication end point + /// \param[in] playerId The Unique Player Identifier who shouldhave sent some packets + /// \param[in] MTUSize maximum datagram size + /// \param[in] time current system time + /// \param[in] messageHandlerList A list of registered plugins + void Update( SOCKET s, PlayerID playerId, int MTUSize, RakNetTimeNS time, DataStructures::List &messageHandlerList ); + + /// If Read returns -1 and this returns true then a modified packetwas detected + /// \return true when a modified packet is detected + bool IsCheater( void ) const; + + /// Were you ever unable to deliver a packet despite retries? + /// \return true means the connection has been lost. Otherwise not. + bool IsDeadConnection( void ) const; + + /// Causes IsDeadConnection to return true + void KillConnection(void); + /// Sets the ping, which is used by the reliability layer to determine how long to wait for resends. Mostly for flow control. /// \param[in] The ping time. void SetPing( RakNetTime i ); + /// Get Statistics + /// \return A pointer to a static struct, filled out with current statistical information. + RakNetStatisticsStruct * const GetStatistics( void ); + + ///Are we waiting for any data to be sent out or be processed by the player? + bool IsDataWaiting(void); + bool AreAcksWaiting(void); + + // Set outgoing lag and packet loss properties + void ApplyNetworkSimulator( double _maxSendBPS, RakNetTime _minExtraPing, RakNetTime _extraPingVariance ); + + /// Returns if you previously called ApplyNetworkSimulator + /// \return If you previously called ApplyNetworkSimulator + bool IsNetworkSimulatorActive( void ); + + void SetSplitMessageProgressInterval(int interval); + void SetUnreliableTimeout(RakNetTime timeoutMS); + private: + /// Generates a datagram (coalesced packets) + /// \param[out] output The resulting BitStream + /// \param[in] Current MTU size + /// \param[out] reliableDataSent Set to true or false as a return value as to if reliable data was sent. + /// \param[in] time Current time + /// \param[in] playerId Who we are sending to + /// \param[in] messageHandlerList A list of registered plugins + /// \return The number of messages sent + unsigned GenerateDatagram( RakNet::BitStream *output, int MTUSize, bool *reliableDataSent, RakNetTimeNS time, PlayerID playerId, DataStructures::List &messageHandlerList ); + + /// Send the contents of a bitstream to the socket + /// \param[in] s The socket used for sending data + /// \param[in] playerId The address and port to send to + /// \param[in] bitStream The data to send. + void SendBitStream( SOCKET s, PlayerID playerId, RakNet::BitStream *bitStream ); + + ///Parse an internalPacket and create a bitstream to represent this dataReturns number of bits used + int WriteToBitStreamFromInternalPacket( RakNet::BitStream *bitStream, const InternalPacket *const internalPacket ); + + /// Parse a bitstream and create an internal packet to represent this data + InternalPacket* CreateInternalPacketFromBitStream( RakNet::BitStream *bitStream, RakNetTimeNS time ); + + /// Does what the function name says + unsigned RemovePacketFromResendListAndDeleteOlderReliableSequenced( const MessageNumberType messageNumber, RakNetTimeNS time ); + + /// Acknowledge receipt of the packet with the specified messageNumber + void SendAcknowledgementPacket( const MessageNumberType messageNumber, RakNetTimeNS time ); + + /// This will return true if we should not send at this time + bool IsSendThrottled( int MTUSize ); + + /// We lost a packet + void UpdateWindowFromPacketloss( RakNetTimeNS time ); + + /// Increase the window size + void UpdateWindowFromAck( RakNetTimeNS time ); + + /// Parse an internalPacket and figure out how many header bits would be written. Returns that number + int GetBitStreamHeaderLength( const InternalPacket *const internalPacket ); + + /// Get the SHA1 code + void GetSHA1( unsigned char * const buffer, unsigned int nbytes, char code[ SHA1_LENGTH ] ); + + /// Check the SHA1 code + bool CheckSHA1( char code[ SHA1_LENGTH ], unsigned char * const buffer, unsigned int nbytes ); + + /// Search the specified list for sequenced packets on the specified ordering channel, optionally skipping those with splitPacketId, and delete them + void DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::List&theList, int splitPacketId = -1 ); + + /// Search the specified list for sequenced packets with a value less than orderingIndex and delete them + void DeleteSequencedPacketsInList( unsigned char orderingChannel, DataStructures::Queue&theList ); + + /// Returns true if newPacketOrderingIndex is older than the waitingForPacketOrderingIndex + bool IsOlderOrderedPacket( OrderingIndexType newPacketOrderingIndex, OrderingIndexType waitingForPacketOrderingIndex ); + + /// Split the passed packet into chunks under MTU_SIZE bytes (including headers) and save those new chunks + void SplitPacket( InternalPacket *internalPacket, int MTUSize ); + + /// Insert a packet into the split packet list + void InsertIntoSplitPacketList( InternalPacket * internalPacket, RakNetTimeNS time ); + + /// Take all split chunks with the specified splitPacketId and try to reconstruct a packet. If we can, allocate and return it. Otherwise return 0 + InternalPacket * BuildPacketFromSplitPacketList( SplitPacketIdType splitPacketId, RakNetTimeNS time ); + + /// Delete any unreliable split packets that have long since expired + void DeleteOldUnreliableSplitPackets( RakNetTimeNS time ); + + /// Creates a copy of the specified internal packet with data copied from the original starting at dataByteOffset for dataByteLength bytes. + /// Does not copy any split data parameters as that information is always generated does not have any reason to be copied + InternalPacket * CreateInternalPacketCopy( InternalPacket *original, int dataByteOffset, int dataByteLength, RakNetTimeNS time ); + + /// Get the specified ordering list + DataStructures::LinkedList *GetOrderingListAtOrderingStream( unsigned char orderingChannel ); + + /// Add the internal packet to the ordering list in order based on order index + void AddToOrderingList( InternalPacket * internalPacket ); + + /// Inserts a packet into the resend list in order + void InsertPacketIntoResendList( InternalPacket *internalPacket, RakNetTimeNS time, bool makeCopyOfInternalPacket, bool firstResend ); + + /// Memory handling + void FreeMemory( bool freeAllImmediately ); + + /// Memory handling + void FreeThreadedMemory( void ); + + /// Memory handling + void FreeThreadSafeMemory( void ); + // Initialize the variables void InitializeVariables( void ); + /// Given the current time, is this time so old that we should consider it a timeout? + bool IsExpiredTime(unsigned int input, RakNetTimeNS currentTime) const; + // Make it so we don't do resends within a minimum threshold of time void UpdateNextActionTime(void); - char _gap0[713]; + /// Does this packet number represent a packet that was skipped (out of order?) + //unsigned int IsReceivedPacketHole(unsigned int input, RakNetTime currentTime) const; + /// Skip an element in the received packets list + //unsigned int MakeReceivedPacketHole(unsigned int input) const; + + /// How many elements are waiting to be resent? + unsigned int GetResendListDataSize(void) const; + + /// Update all memory which is not threadsafe + void UpdateThreadedMemory(void); + + void CalculateHistogramAckSize(void); + + DataStructures::List*> orderingList; + DataStructures::Queue outputQueue; + DataStructures::RangeList acknowlegements; + RakNetTimeNS nextAckTime; + int splitMessageProgressInterval; + RakNetTimeNS unreliableTimeout; + + DataStructures::BPlusTree resendList; + DataStructures::Queue resendQueue; + + DataStructures::Queue sendPacketSet[ NUMBER_OF_PRIORITIES ]; + DataStructures::OrderedList splitPacketChannelList; + MessageNumberType messageNumber; + //unsigned int windowSize; + RakNetTimeNS lastAckTime; + RakNet::BitStream updateBitStream; + OrderingIndexType waitingForOrderedPacketWriteIndex[ NUMBER_OF_ORDERED_STREAMS ], waitingForSequencedPacketWriteIndex[ NUMBER_OF_ORDERED_STREAMS ]; + // Used for flow control (changed to regular TCP sliding window) + // unsigned int maximumWindowSize, bytesSentSinceAck; + // unsigned int outputWindowFullTime; // under linux if this last variable is on the line above it the delete operator crashes deleting this class! + + // STUFF TO NOT MUTEX HERE (called from non-conflicting threads, or value is not important) + OrderingIndexType waitingForOrderedPacketReadIndex[ NUMBER_OF_ORDERED_STREAMS ], waitingForSequencedPacketReadIndex[ NUMBER_OF_ORDERED_STREAMS ]; + bool deadConnection, cheater; + // unsigned int lastPacketSendTime,retransmittedFrames, sentPackets, sentFrames, receivedPacketsCount, bytesSent, bytesReceived,lastPacketReceivedTime; RakNetTime ping; - - char _gap2D1[2]; - + SplitPacketIdType splitPacketId; RakNetTime timeoutTime; // How long to wait in MS before timing someone out - - char _gap2D7[4]; - + //int MAX_AVERAGE_PACKETS_PER_SECOND; // Name says it all +// int RECEIVED_PACKET_LOG_LENGTH, requestedReceivedPacketLogLength; // How big the receivedPackets array is +// unsigned int *receivedPackets; + unsigned int blockWindowIncreaseUntilTime; RakNetStatisticsStruct statistics; - unsigned int field_403; + /// Memory-efficient receivedPackets algorithm: + /// receivedPacketsBaseIndex is the packet number we are expecting + /// Everything under receivedPacketsBaseIndex is a packet we already got + /// Everything over receivedPacketsBaseIndex is stored in hasReceivedPacketQueue + /// It stores the time to stop waiting for a particular packet number, where the packet number is receivedPacketsBaseIndex + the index into the queue + /// If 0, we got got that packet. Otherwise, the time to give up waiting for that packet. + /// If we get a packet number where (receivedPacketsBaseIndex-packetNumber) is less than half the range of receivedPacketsBaseIndex then it is a duplicate + /// Otherwise, it is a duplicate packet (and ignore it). + DataStructures::Queue hasReceivedPacketQueue; + MessageNumberType receivedPacketsBaseIndex; + bool resetReceivedPackets; - char _gap407[710]; + RakNetTimeNS lastUpdateTime; + RakNetTimeNS histogramEndTime, histogramStartTime; + unsigned histogramReceiveMarker; + int noPacketlossIncreaseCount; + unsigned histogramPlossCount, histogramAckCount; + double lowBandwidth, highBandwidth, currentBandwidth; // In bits per second + double availableBandwidth; + bool continuousSend; + + DataBlockEncryptor encryptor; + unsigned sendPacketCount, receivePacketCount; RakNetTimeNS ackTimeIncrement; - char _gap6D1[25]; +#ifdef __USE_IO_COMPLETION_PORTS + ///\note Windows Port only + SOCKET readWriteSocket; +#endif + ///This variable is so that free memory can be called by only the update thread so we don't have to mutex things so much + bool freeThreadedMemoryOnNextUpdate; + +#ifndef _RELEASE + struct DataAndTime + { + char data[ MAXIMUM_MTU_SIZE ]; + int length; + RakNetTimeNS sendTime; + }; + DataStructures::List delayList; // Internet simulator + double maxSendBPS; RakNetTime minExtraPing, extraPingVariance; +#endif + // This has to be a member because it's not threadsafe when I removed the mutexes + InternalPacketPool internalPacketPool; }; #endif diff --git a/raknet/Replica.h b/raknet/Replica.h new file mode 100644 index 0000000..3ebb434 --- /dev/null +++ b/raknet/Replica.h @@ -0,0 +1,98 @@ +/// \file +/// \brief Contains interface Replica used by the ReplicaManager. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __REPLICA_H +#define __REPLICA_H + +#include "NetworkIDGenerator.h" +#include "PacketPriority.h" +#include "ReplicaEnums.h" + +/// This is an interface of a replicated object for use in the framework of ReplicaManager +/// You should derive from this class, implementing the functions to provide the behavior you want. +/// If your architecture doesn't allow you to derive from this class, you can store an instance of a derived instance of this class in your base game object. +/// In that case, use GetParent() and SetParent() and propagate the function calls up to your real classes. For an example where I do this, see Monster.h in the ReplicaManagerCS sample. +/// \brief The interface to derive your game's networked classes from +/// \ingroup REPLICA_MANAGER_GROUP +class Replica : public NetworkIDGenerator +{ +public: + /// This function is called in the first update tick after this object is first passed to ReplicaManager::Replicate for each player, and also when a new participant joins + /// The intent of this function is to tell another system to create an instance of this class. + /// You can do this however you want - I recommend having each class having a string identifier which is registered with StringTable, and then using EncodeString to write the name of the class. In the callback passed to SetReceiveConstructionCB create an instance of the object based on that string. + /// \note If you return true from IsNetworkIDAuthority, which you should do for a server or peer, I recommend encoding the value returned by GetNetworkID() into your bitstream and calling SetNetworkID with that value in your SetReceiveConstructionCB callback. + /// \note Dereplicate, SetScope, and SignalSerializeNeeded all rely on being able to call GET_OBJECT_FROM_ID which requires that SetNetworkID be called on that object. + /// \note SendConstruction is called once for every new player that connects and every existing player when an object is passed to Replicate. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] playerId The participant to send to. + /// \param[out] outBitStream The data you want to write in the message. If you do not write to outBitStream and return true, then no send call will occur and the system will consider this object as not created on that remote system. + /// \param[out] includeTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter of the callback. Defaults to false. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendConstruction( RakNetTime currentTime, PlayerID playerId, RakNet::BitStream *outBitStream, bool *includeTimestamp )=0; + + /// The purpose of the function is to send a packet containing the data in \a outBitStream to \a playerId telling that system that Dereplicate was called. + /// In the code, this is called in the update cycle after you call ReplicaManager::Destruct(). Then, if you write to outBitStream, a message is sent to that participant. + /// This is the one send you cannot delay because objects may be deleted and we can't read into them past this. + /// \param[out] outBitStream The data to send. If you do not write to outBitStream, then no send call will occur + /// \param[in] playerId The participant to send to. + virtual void SendDestruction(RakNet::BitStream *outBitStream, PlayerID playerId)=0; + + /// This function is called when SendDestruction is sent from another system. Delete your object if you want. + /// \param[in] inBitStream What was sent in SendDestruction::outBitStream + /// \param[in] playerId The participant that sent this message to us. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult ReceiveDestruction(RakNet::BitStream *inBitStream, PlayerID playerId)=0; + + /// Called when ReplicaManager::SetScope is called with a different value than what it currently has. + /// It is up to you to write \a inScope to \a outBitStream. Not doing so, and returning true, means you want to cancel the scope change call. + /// If \a inScope is true, you return true, and data is written to outBitStream, then Serialize will be called automatically + /// This is a convenience feature, since there's almost no case where an object would go in scope but not be serialized + /// \param[in] inScope The new scope that will be sent to ReceiveScopeChange that originally came from SetScope + /// \param[out] outBitStream The data to send. If you do not write to outBitStream and return true, then no send will occur and the object will keep its existing scope + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] playerId The participant to send to. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult SendScopeChange(bool inScope, RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID playerId)=0; + + /// Called when when we get the SendScopeChange message. The new scope should have been encoded (by you) into \a inBitStream + /// \param[in] inBitStream What was sent in SendScopeChange::outBitStream + /// \param[in] playerId The participant that sent this message to us. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult ReceiveScopeChange(RakNet::BitStream *inBitStream,PlayerID playerId)=0; + + /// Called when ReplicaManager::SignalSerializeNeeded is called with this object as the parameter. + /// The system will ensure that Serialize only occurs for participants that have this object constructed and in scope + /// The intent of this function is to serialize all your class member variables for remote transmission. + /// \param[out] sendTimestamp Set to true to include a timestamp with the message. This will be reflected in the timestamp parameter Deserialize. Defaults to false. + /// \param[out] outBitStream The data you want to write in the message. If you do not write to outBitStream and return true, then no send will occur for this participant. + /// \param[in] lastSendTime The last time Serialize returned true and outBitStream was written to. 0 if this is the first time the function has ever been called for this \a playerId + /// \param[in] priority Passed to RakPeer::Send for the send call. + /// \param[in] reliability Passed to RakPeer::Send for the send call. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] playerId The participant we are sending to. + /// \return See ReplicaReturnResult + virtual ReplicaReturnResult Serialize(bool *sendTimestamp, RakNet::BitStream *outBitStream, RakNetTime lastSendTime, PacketPriority *priority, PacketReliability *reliability, RakNetTime currentTime, PlayerID playerId)=0; + + /// Called when another participant called Serialize with our system as the target + /// \param[in] inBitStream What was written to Serialize::outBitStream + /// \param[in] timestamp if Serialize::sendTimestamp was set to true, the time the packet was sent. + /// \param[in] lastDeserializeTime Last time you returned true from this function for this object, or 0 if never, regardless of \a playerId. + /// \param[in] playerId The participant that sent this message to us. + virtual ReplicaReturnResult Deserialize(RakNet::BitStream *inBitStream, RakNetTime timestamp, RakNetTime lastDeserializeTime, PlayerID playerId )=0; +}; + +#endif diff --git a/raknet/ReplicaEnums.h b/raknet/ReplicaEnums.h new file mode 100644 index 0000000..675ae73 --- /dev/null +++ b/raknet/ReplicaEnums.h @@ -0,0 +1,49 @@ +/// \file +/// \brief Contains enumerations used by the ReplicaManager system. This file is a lightweight header, so you can include it without worrying about linking in lots of other crap +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __REPLICA_ENUMS_H +#define __REPLICA_ENUMS_H + +/// Replica interface flags, used to enable and disable function calls on the Replica object +/// Passed to ReplicaManager::EnableReplicaInterfaces and ReplicaManager::DisableReplicaInterfaces +enum +{ + REPLICA_RECEIVE_DESTRUCTION=1<<0, + REPLICA_RECEIVE_SERIALIZE=1<<1, + REPLICA_RECEIVE_SCOPE_CHANGE=1<<2, + REPLICA_SEND_CONSTRUCTION=1<<3, + REPLICA_SEND_DESTRUCTION=1<<4, + REPLICA_SEND_SCOPE_CHANGE=1<<5, + REPLICA_SEND_SERIALIZE=1<<6, + REPLICA_SET_ALL = 0xFF // Allow all of the above +}; + +enum ReplicaReturnResult +{ + /// This means call the function again later, with the same parameters + REPLICA_PROCESS_LATER, + /// This means we are done processing (the normal result to return) + REPLICA_PROCESSING_DONE, + /// This means cancel the processing - don't send any network messages and don't change the current state. + REPLICA_CANCEL_PROCESS, + /// Same as REPLICA_PROCESSING_DONE, where a message is sent, but does not clear the send bit. + /// Useful for multi-part sends with different reliability levels. + /// Only currently used by Replica::Serialize + REPLICA_PROCESS_AGAIN, +}; + +#endif diff --git a/raknet/ReplicaManager.h b/raknet/ReplicaManager.h new file mode 100644 index 0000000..43cc594 --- /dev/null +++ b/raknet/ReplicaManager.h @@ -0,0 +1,361 @@ +/// \file +/// \brief Contains class ReplicaManager. This system provides management for your game objects and players to make serialization, scoping, and object creation and destruction easier. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __REPLICA_MANAGER_H +#define __REPLICA_MANAGER_H + +#include "Export.h" +#include "NetworkTypes.h" +#include "DS_OrderedList.h" +#include "PluginInterface.h" +#include "NetworkIDGenerator.h" +#include "DS_Queue.h" +#include "ReplicaEnums.h" + +/// Forward declarations +namespace RakNet +{ + class BitStream; +}; +class Replica; + +/// \defgroup REPLICA_MANAGER_GROUP ReplicaManager +/// \ingroup PLUGINS_GROUP + +/// ReplicaManager is a system manager for your game objects that performs the following tasks: +/// 1. Tracks all locally registered game objects and players and only performs operations to and for those objects and players +/// 2. Allows you to automatically turn off unneeded local and remote functions for your game objects, thus providing convenience and security against unauthorized sends. +/// 3. Sends notifications of existing game objects to new connections, including a download complete message. +/// 4. Sends notifications of new game objects to existing players. +/// A. Serialize and scoping calls are not sent to objects that were not notified of that object. +/// B. Notification calls can be cancelled on a per-object basis. Object notification sends are tracked on a per-system per-object basis. +/// 5. Configurable per-system per-object scoping. +/// A. Scoping provides a mechanism to hide and unhide remote objects without destroying the whole object, used when when entities should not be destroyed but are currently not visible to systems. +/// B. Serialize calls are not sent to hidden objects. +/// C. Scoping calls can be cancelled on a per-object basis. Scope is tracked on a per-system per-object basis. +/// 6. Replicate, SetScope, SignalSerializeNeeded, and the corresponding Replica interfaces are processed in RakPeer::Receive, rather than immediately. +/// A. This allows the ReplicaManager to reorganize function calls in order by dependency. This allows out of order calls, per-object call cancellation (which also cancels dependencies), and per-object call delays (which also delays dependencies) +/// B. For example, although SetScope and SignalSerializeNeeded have a dependency on SetNetworkID(), you can still call them in the constructor and call SetNetworkID() later, as long as it happens before calling RakPeer::Receive() +/// 7. The system is fast, uses little memory, and is intentionally hands off such that it can work with any game architecture and network topology +/// +/// What the ReplicaManager system does NOT do for you +/// 1. Actually create or destroy your game objects +/// 2. Associate object destruction events with remote system disconnects. +/// 3. Set networkIDs via SetNetworkID() on newly created objects. +/// 4. Object sub-serialization. Serialize only granular on the level of entire objects. If you want to serialize part of the object, you need to set your own flags and indicate in the BitStream which parts were sent and which not. +/// \brief A management system for your game objects and players to make serialization, scoping, and object creation and destruction easier. +/// \ingroup REPLICA_MANAGER_GROUP +class RAK_DLL_EXPORT ReplicaManager : public PluginInterface +{ +public: + /// Constructor + ReplicaManager(); + + /// Destructor + ~ReplicaManager(); + + /// Do or don't automatically call AddParticipant when new systems connect to us. + /// Won't add automatically add connections that already exist before this was called + /// Defaults to false + /// \param[in] autoAdd True or false, to add or not + void SetAutoParticipateNewConnections(bool autoAdd); + + /// Adds a participant to the ReplicaManager system. Only these participants get packets and we only accept ReplicaManager packets from these participants. + /// This way you can have connections that have nothing to do with your game - for example remote console logins + /// \param[in] playerId Which player you are referring to + void AddParticipant(PlayerID playerId); + + /// Removes a participant from the data replicator system + /// This is called automatically on ID_DISCONNECTION_NOTIFICATION and ID_CONNECTION_LOST messages, as well as CloseConnection() calls. + /// \param[in] playerId Which player you are referring to + void RemoveParticipant(PlayerID playerId); + + /// Construct the specified object on the specified system + /// Replica::SendConstruction will be called on the next update cycle for the player you specify + /// Nothing is actually created - this just signals that another system wants you to do so. + /// The other system will get Replica::ReceiveConstruction + /// If your system assigns NetworkIDs, do so before calling Replicate as the NetworkID is automatically included in the packet. + /// Replicate packets that are sent to systems that already have this NetworkID are ignored. + /// \note Objects which are replicated get exactly one call to SendConstruction for every player / object permutation. + /// \note To perform scoping and serialize updates on an object already created by another system, call Construct with \a isCopy true. + /// \note Setting \a isCopy true will consider the object created on that system without actually trying to create it. + /// \note If you don't need to send updates to other systems for this object, it is more efficient to use ReferencePointer instead. + /// \param[in] replica A pointer to your object + /// \param[in] isCopy True means that this is a copy of an object that already exists on the systems specified by \a playerId and \a broadcast. If true, we will consider these systems as having the object without sending a datagram to them. SendConstruction will NOT be called for objects which \a isCopy is true. + /// \param[in] playerId The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If playerId!=UNASSIGNED_PLAYER_ID then this means send to all but that participant + void Construct(Replica *replica, bool isCopy, PlayerID playerId, bool broadcast); + + /// Call this with your game objects to have them send Replica::SendDestruction. + /// This will be sent immediately to all participants that have this object. Those participants will get Replica::ReceiveDestruction + /// All pending calls for this object, for this player, are canceled. + /// Nothing is actually deleted - this just signals that the other system called this function. It is up to you to actually delete your object. + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, SetNetworkID on that object must have been called with the same value as GetNetworkID for this object. + /// \note Call Destruct before DereferencePointer if you plan on calling both, since Destruct will fail with no pointer reference. + /// \note Calling Destruct with playerId==UNASSIGNED_PLAYER_ID and broadcast true is equivalent to calling DereferencePointer except that Destruct also sends the destruct packet. + /// \note It is important to call this before deleting your object. Otherwise this system will crash the next Update call. + /// \param[in] replica A pointer to your object + /// \param[in] playerId The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If playerId!=UNASSIGNED_PLAYER_ID then this means send to all but that participant + void Destruct(Replica *replica, PlayerID playerId, bool broadcast); + + /// This makes sure the object is tracked, so you can get calls on it. + /// This will automatically happen if you call Construct, SetScope, or SignalSerializeNeeded with \a replica + /// Otherwise you need to call this, or for security the system will ignore calls that reference this object, even if given a valid NetworkID + /// Duplicate calls are safe and are simply ignored. + /// Best place to put this is in the SetReceiveConstructionCB callback so that all your objects are registered. + /// \param[in] replica A pointer to your object + void ReferencePointer(Replica *replica); + + /// Call this before you delete \a replica. This locally removes all references to this pointer. + /// No messages are sent. + /// Best place to put this is in the destructor of \a replica + /// \param[in] replica A pointer to your object + void DereferencePointer(Replica *replica); + + /// Sets the scope of your object in relation to another participant. + /// Objects that are in-scope for that participant will send out Replica::Serialize calls. Otherwise Serialize calls are not sent. + /// Scoping is useful when you want to disable sends to an object temporarily, without deleting that object. + /// Calling this results in Replica::SendScopeChange being called on the local object and Replica::ReceiveScopeChange on the remote object if that object has been created on that remote system. + /// Your game should ensure that objects not in scope are hidden, but not deallocated, on the remote system. + /// Replica::SendScopeChange with \a inScope as true will automatically perform Replica::Serialize + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, that object must have an NetworkID (set by SetNetworkID()) the same as our object's NetworkID (returned from GetNetworkID()). + /// \note You can set the default scope with SetDefaultScope() + /// \note Individual objects can refuse to perform the SendScopeChange call by not writing to the output bitstream while returning true. + /// \param[in] replica An object previously registered with Replicate + /// \param[in] inScope in scope or not. + /// \param[in] playerId The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If playerId!=UNASSIGNED_PLAYER_ID then this means send to all but that participant + void SetScope(Replica *replica, bool inScope, PlayerID playerId, bool broadcast); + + /// Signal that data has changed and we need to call Serialize() on the \a replica object. + /// This will happen if the object has been registered, Replica::SendConstruction wrote to outBitStream and returned true, and the object is in scope for this player. + /// \pre Call Replicate with this object first. + /// \pre For the other system to get the network message, that object must have an NetworkID (set by SetNetworkID()) the same as our object's NetworkID (returned from GetNetworkID()). + /// \param[in] replica An object previously registered with Replicate + /// \param[in] playerId The participant to send the command to, or the one to exclude if broadcast is true. + /// \param[in] broadcast True to send to all. If playerId!=UNASSIGNED_PLAYER_ID then this means send to all but that participant + void SignalSerializeNeeded(Replica *replica, PlayerID playerId, bool broadcast); + + /// Required callback + /// Set your callback to parse requests to create new objects. Specifically, when Replica::SendConstruction is called and the networkID of the object is either unset or can't be found, this callback will get that call. + /// How do you know what object to create? It's up to you, but I suggest in Replica::SendConstruction you encode the class name. The best way to do this is with the StringTable class. + /// \note If you return true from IsNetworkIDAuthority, which you should do for a server or peer, I recommend also encoding the value returned by GetNetworkID() within Replica::SendConstruction into that bitstream and reading it here. Then set that value in a call to SetNetworkID. Dereplicate, SetScope, and SignalSerializeNeeded all rely on being able to call GET_OBJECT_FROM_ID which requires that SetNetworkID be called on that object. + /// \param[in] constructionCB The callback function pointer + /// \param[in] inBitStream The bitstream that was written to in Replica::SendConstruction + /// \param[in] timestamp If in Replica::SendConstruction you set sendTimestamp to true, this is the time the packet was sent. Otherwise it is 0. + /// \param[in] networkID If the remote object had an NetworkID set by the time Replica::SendConstruction was called it is here. + /// \param[in] senderId Which PlayerID sent this packet. + /// \return See ReplicaReturnResult + void SetReceiveConstructionCB(ReplicaReturnResult (* constructionCB)(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller)); + + /// Optional callback + /// Set your callbacks to be called when, after connecting to another system, you get all objects that system is going to send to you when it is done with the first iteration through the object list. + /// \param[in] sendDownloadCompleteCB The callback function pointer or 0, to call when we send a download complete packet. Used to append data to the download complete packet - you don't have to set this. The download complete message will still be sent. + /// \param[out] outBitStream Write whatever you want to this bitstream. It will arrive in the receiveDownloadCompleteCB callback. + /// \param[in] currentTime The current time that would be returned by RakNet::GetTime(). That's a slow call I do already, so you can use the parameter instead of having to call it yourself. + /// \param[in] senderId Who we are sending to + /// \param[in] receiveDownloadCompleteCB The callback function pointer or 0, to call when we get a download complete packet. You do need to set this if you want to be notified of the download complete packet. + /// \param[in] inBitStream The bitstream that was written to in the sendDownloadCompleteCB callback + /// \return See ReplicaReturnResult + void SetDownloadCompleteCB(ReplicaReturnResult (* sendDownloadCompleteCB)(RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID senderId, ReplicaManager *caller), ReplicaReturnResult (* receiveDownloadCompleteCB)(RakNet::BitStream *inBitStream, PlayerID senderId, ReplicaManager *caller)); + + /// This channel will be used for all RakPeer::Send calls + /// \param[in] channel The channel to use for internal RakPeer::Send calls from this system. Defaults to 0. + void SetSendChannel(unsigned char channel); + + /// This means automatically construct all known objects to all new participants + /// Has no effect on existing participants + /// Useful if your architecture always has all objects constructed on all systems all the time anyway, or if you want them to normally start constructed + /// Defaults to false. + /// \param[in] autoConstruct true or false, as desired. + void SetAutoConstructToNewParticipants(bool autoConstruct); + + /// Set the default scope for new objects to all players. Defaults to false, which means Serialize will not be called for new objects automatically. + /// If you set this to true, then new players will get existing objects, and new objects will be sent to existing players + /// This only applies to players that connect and objects that are replicated after this call. Existing object scopes are not affected. + /// Useful to set to true if you don't use scope, or if all objects normally start in scope + /// \param[in] scope The default scope to use. + void SetDefaultScope(bool scope); + + /// Lets you enable calling any or all of the interface functions in an instance of Replica + /// This setting is the same for all participants for this object, so if you want per-participant permissions you will need to handle that inside your implementation + /// All functions enabled by default. + /// \param[in] replica The object you are referring to + /// \param[in] interfaceFlags A bitwise-OR of REPLICA_SEND_CONSTRUCTION ... REPLICA_SET_ALL corresponding to the function of the same name + void EnableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags); + + /// Lets you disable calling any or all of the interface functions in an instance of Replica + /// This setting is the same for all participants for this object, so if you want per-participant permissions you will need to handle that inside your implementation + /// All functions enabled by default. + /// \note Disabling functions is very useful for security. + /// \note For example, on the server you may wish to disable all receive functions so clients cannot change server objects. + /// \param[in] replica The object you are referring to + /// \param[in] interfaceFlags A bitwise-OR of REPLICA_SEND_CONSTRUCTION ... REPLICA_SET_ALL corresponding to the function of the same name + void DisableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags); + + /// Tells us if a particular system got a SendConstruction() message from this object. e.g. does this object exist on this remote system? + /// This is set by the user when calling Replicate and sending (any) data to outBitStream in Replica::SendConstruction + /// \param[in] replica The object we are checking + /// \param[in] playerId The system we are checking + bool IsConstructed(Replica *replica, PlayerID playerId); + + /// Tells us if a particular object is in scope for a particular system + /// This is set by the user when calling SetScope and sending (any) data to outBitstream in Replica::SendScopeChange + /// \param[in] replica The object we are checking + /// \param[in] playerId The system we are checking + bool IsInScope(Replica *replica, PlayerID playerId); + + /// Returns how many Replica instances are registered. + /// This number goes up with each non-duplicate call to Replicate and down with each non-duplicate call to Dereplicate + /// Used for GetReplicaAtIndex if you want to perform some object on all registered Replica objects. + /// \return How many replica objects are in the list of replica objects + unsigned GetReplicaCount(void) const; + + /// Returns a previously registered Replica *, from index 0 to GetReplicaCount()-1. + /// The order that Replica * objects are returned in is arbitrary (it currently happens to be ordered by pointer address). + /// Calling Dereplicate immediately deletes the Replica * passed to it, so if you call Dereplicate while using this function + /// the array will be shifted over and the current index will now reference the next object in the array, if there was one. + /// \param[in] index An index, from 0 to GetReplicaCount()-1. + /// \return A Replica * previously passed to Construct() + Replica *GetReplicaAtIndex(unsigned index); + + enum + { + REPLICA_EXPLICIT_CONSTRUCTION=1<<0, + REPLICA_IMPLICIT_CONSTRUCTION=1<<1, // Overridden by REPLICA_EXPLICIT_CONSTRUCTION. IMPLICIT assumes the object exists on the remote system. + REPLICA_SCOPE_TRUE=1<<2, // Mutually exclusive REPLICA_SCOPE_FALSE + REPLICA_SCOPE_FALSE=1<<3, // Mutually exclusive REPLICA_SCOPE_TRUE + REPLICA_SERIALIZE=1<<4, + }; + + /// \internal + /// One pointer and a command to act on that pointer + struct CommandStruct + { + Replica *replica; // Pointer to an external object - not allocated here. + unsigned char command; // This is one of the enums immediately above. + }; + + struct RegisteredReplica + { + Replica *replica; // Pointer to an external object - not allocated here. + RakNetTime lastDeserializeTrue; // For replicatedObjects it's the last time deserialize returned true. + unsigned char allowedInterfaces; // Replica interface flags + }; + + struct RemoteObject + { + Replica *replica; // Pointer to an external object - not allocated here. + bool inScope; // Is replica in scope or not? + RakNetTime lastSendTime; + }; + + struct ReceivedCommand + { + PlayerID playerId; + NetworkID networkID; + unsigned command; // A packetID + unsigned u1; + RakNet::BitStream *userData; + }; + + + static int RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ); + static int RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ); + static int CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ); + + /// \internal + /// One remote system + struct ParticipantStruct + { + ~ParticipantStruct(); + + // The player this participant struct represents. + PlayerID playerId; + + // Call sendDownloadCompleteCB when REPLICA_SEND_CONSTRUCTION is done for all objects in commandList + // This variable tracks if we did it yet or not. + bool callDownloadCompleteCB; + + // Sorted list of Replica*, sorted by pointer, along with a command to perform on that pointer. + // Ordering is just for fast lookup. + // Nothing is allocated inside this list + DataStructures::OrderedList commandList; + + // Sorted list of Replica*, sorted by pointer, along with if that object is inScope or not for this system + // Only objects that exist on the remote system are in this list, so not all objects are necessarily in this list + DataStructures::OrderedList remoteObjectList; + + // List of pending ReceivedCommand to process + DataStructures::Queue pendingCommands; + }; + + static int ParticipantStructComp( const PlayerID &key, ReplicaManager::ParticipantStruct * const &data ); + +protected: + /// Frees all memory + void Clear(void); + // Processes a struct representing a received command + ReplicaReturnResult ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand); + + // Plugin interface functions + void Update(RakPeerInterface *peer); + void OnAttach(RakPeerInterface *peer); + PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); + void OnDisconnect(RakPeerInterface *peer); + + /// List of objects replicated in the Replicate function. + /// Used to make sure queued actions happen on valid pointers, since objects are removed from the list in Dereplicate + /// Sorted by raw pointer address using the default sort + DataStructures::OrderedList replicatedObjects; + + /// List of participants + /// Each participant has several queues of pending commands + /// Sorted by playerID + /// The only complexity is that each participant also needs a list of objects that mirror the variable replicatedObjects so we know per-player if that object is in scope + DataStructures::OrderedList participantList; + + // Internal functions + ParticipantStruct* GetParticipantByPlayerID(const PlayerID playerId) const; + + // Callback pointers. + + // Required callback to handle construction calls + ReplicaReturnResult (* _constructionCB)(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller); + + // Optional callbacks to send and receive download complete. + ReplicaReturnResult (* _sendDownloadCompleteCB)(RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID senderId, ReplicaManager *caller); + ReplicaReturnResult (* _receiveDownloadCompleteCB)(RakNet::BitStream *inBitStream, PlayerID senderId, ReplicaManager *caller); + + // Channel to do send calls on. All calls are reliable ordered except for Replica::Serialize + unsigned char sendChannel; + + // Stores what you pass to SetAutoParticipateNewConnections + bool autoParticipateNewConnections; + + bool defaultScope; + bool autoConstructToNewParticipants; + + RakPeerInterface *rakPeer; +}; + + +#endif diff --git a/raknet/Router.cpp b/raknet/Router.cpp new file mode 100644 index 0000000..eb67b94 --- /dev/null +++ b/raknet/Router.cpp @@ -0,0 +1,335 @@ +#include "Router.h" +#include "BitStream.h" +#include "RakPeerInterface.h" +#include "PacketEnumerations.h" +#include "RakAssert.h" + +//#define _DO_PRINTF + +#ifdef _DO_PRINTF +#include +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +Router::Router() +{ + graph=0; + restrictByType=false; + rakPeer=0; + DataStructures::OrderedList::IMPLEMENT_DEFAULT_COMPARISON(); +} +Router::~Router() +{ +} +void Router::SetRestrictRoutingByType(bool restrict) +{ + restrictByType=restrict; +} +void Router::AddAllowedType(unsigned char messageId) +{ + if (allowedTypes.HasData(messageId)==false) + allowedTypes.Insert(messageId,messageId); +} +void Router::RemoveAllowedType(unsigned char messageId) +{ + if (allowedTypes.HasData(messageId)==true) + allowedTypes.Remove(messageId); +} +void Router::SetConnectionGraph(DataStructures::WeightedGraph *connectionGraph) +{ + graph=connectionGraph; +} +bool Router::Send( const char *data, unsigned bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId ) +{ + if (playerId!=UNASSIGNED_PLAYER_ID) + { + RakAssert(data); + RakAssert(bitLength); + // Prevent recursion in case a routing call itself calls the router + if (bitLength>=8 && data[0]==ID_ROUTE_AND_MULTICAST) + return false; + + SystemAddressList systemAddressList; + systemAddressList.AddSystem(playerId); + return Send((char*)data, bitLength, priority, reliability, orderingChannel, &systemAddressList); + } + return false; +} +bool Router::Send( char *data, unsigned bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddressList *recipients ) +{ + RakAssert(data); + RakAssert(bitLength); + if (recipients->GetList()->Size()==0) + return false; + if (bitLength==0) + return false; + DataStructures::Tree tree; + PlayerID root; + root = rakPeer->GetExternalID(rakPeer->GetPlayerIDFromIndex(0)); + if (root==UNASSIGNED_PLAYER_ID) + return false; + DataStructures::List recipientList; + unsigned i; + for (i=0; i < recipients->Size(); i++) + recipientList.Insert(ConnectionGraph::PlayerIdAndGroupId(recipients->GetList()->operator [](i),0)); + if (graph->GetSpanningTree(tree, &recipientList, ConnectionGraph::PlayerIdAndGroupId(root,0), 65535)==false) + return false; + + RakNet::BitStream out; + + // Write timestamp first, if the user had a timestamp + if (data[0]==ID_TIMESTAMP && bitLength >= BYTES_TO_BITS(sizeof(MessageID)+sizeof(RakNetTime))) + { + out.Write(data, sizeof(MessageID)+sizeof(RakNetTime)); + data+=sizeof(MessageID)+sizeof(RakNetTime); + bitLength-=BYTES_TO_BITS(sizeof(MessageID)+sizeof(RakNetTime)); + } + + SendTree(priority, reliability, orderingChannel, &tree, data, bitLength, &out, recipients); + return true; +} +void Router::SendTree(PacketPriority priority, PacketReliability reliability, char orderingChannel, DataStructures::Tree *tree, const char *data, unsigned bitLength, RakNet::BitStream *out, SystemAddressList *recipients) +{ + unsigned outputOffset; + + // Write routing identifer + out->Write((unsigned char)ID_ROUTE_AND_MULTICAST); + + // Write the send parameters + out->WriteCompressed((unsigned char)priority); + out->WriteCompressed((unsigned char)reliability); + out->WriteCompressed((unsigned char)orderingChannel); + + // Write the user payload length + out->Write((unsigned int)bitLength); +// out->PrintBits(); +// payload->PrintBits(); + + out->AlignWriteToByteBoundary(); +// payload->AlignReadToByteBoundary(); +// out->Write(payload, payload->GetNumberOfUnreadBits()); +// out->PrintBits(); + if ((bitLength % 8)==0) + out->Write(data, BITS_TO_BYTES(bitLength)); + else + out->WriteBits((const unsigned char*)data, bitLength, false); + + // Save where to start writing per-system data + outputOffset=out->GetWriteOffset(); + + // Write every child of the root of the tree (PlayerID, isRecipient, branch) + unsigned i; + for (i=0; i < tree->children.Size(); i++) + { + // Start writing at the per-system data byte + out->SetWriteOffset(outputOffset); + + // Write our external IP to designate the sender + out->Write(rakPeer->GetExternalID(tree->children[i]->data.playerId)); + + // Serialize the tree + SerializePreorder(tree->children[i], out, recipients); + + // Send to the first hop +#ifdef _DO_PRINTF + printf("%i sending to %i\n", rakPeer->GetExternalID(tree->children[i]->data).port, tree->children[i]->data.port); +#endif + rakPeer->Send(out, priority, reliability, orderingChannel, tree->children[i]->data.playerId, false); + } +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +PluginReceiveResult Router::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + if (packet->data[0]==ID_ROUTE_AND_MULTICAST || + (packet->length>5 && packet->data[0]==ID_TIMESTAMP && packet->data[5]==ID_ROUTE_AND_MULTICAST)) + { +#ifdef _DO_PRINTF + printf("%i got routed message from %i\n", peer->GetExternalID(packet->playerId).port, packet->playerId.port); +#endif + RakNetTime timestamp; + PacketPriority priority; + PacketReliability reliability; + unsigned char orderingChannel; + PlayerID originalSender; + RakNet::BitStream out; + unsigned outStartingOffset; + unsigned int payloadBitLength; + unsigned payloadWriteByteOffset; + RakNet::BitStream incomingBitstream(packet->data, packet->length, false); + incomingBitstream.IgnoreBits(8); + + if (packet->data[0]==ID_TIMESTAMP) + { + incomingBitstream.Read(timestamp); + out.Write((unsigned char)ID_TIMESTAMP); + out.Write(timestamp); + } + + // Read the send parameters + unsigned char c; + incomingBitstream.ReadCompressed(c); + priority=(PacketPriority)c; + incomingBitstream.ReadCompressed(c); + reliability=(PacketReliability)c; + incomingBitstream.ReadCompressed(orderingChannel); + incomingBitstream.Read(payloadBitLength); + + out.Write((unsigned char) ID_ROUTE_AND_MULTICAST); + out.WriteCompressed((unsigned char)priority); + out.WriteCompressed((unsigned char)reliability); + out.WriteCompressed((unsigned char)orderingChannel); + out.Write(payloadBitLength); + out.AlignWriteToByteBoundary(); + incomingBitstream.AlignReadToByteBoundary(); + payloadWriteByteOffset=BITS_TO_BYTES(out.GetWriteOffset()); + out.Write(&incomingBitstream, payloadBitLength); // This write also does a read on incomingBitStream + + if (restrictByType) + { + RakNet::BitStream t(out.GetData()+payloadWriteByteOffset, sizeof(unsigned char), false); + unsigned char messageID; + t.Read(messageID); + if (allowedTypes.HasData(messageID)==false) + return RR_STOP_PROCESSING_AND_DEALLOCATE; // Don't route restricted types + } + + incomingBitstream.Read(originalSender); + out.Write(originalSender); + outStartingOffset=out.GetWriteOffset(); + + // Deserialize the root + bool hasData; + PlayerID recipient; + unsigned short numberOfChildren; + incomingBitstream.Read(hasData); + incomingBitstream.Read(recipient); // This should be our own address + if (incomingBitstream.ReadCompressed(numberOfChildren)==false) + { +#ifdef _DEBUG + assert(0); +#endif + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + unsigned childIndex; + bool childHasData; + PlayerID childRecipient; + unsigned short childNumberOfChildren; + PlayerID immediateRecipient; + immediateRecipient=UNASSIGNED_PLAYER_ID; + int pendingNodeCount=0; + + for (childIndex=0; childIndex < numberOfChildren; childIndex++) + { + while (pendingNodeCount!=-1) + { + // Copy out the serialized subtree for this child + incomingBitstream.Read(childHasData); + incomingBitstream.Read(childRecipient); + if (!incomingBitstream.ReadCompressed(childNumberOfChildren)) + return RR_STOP_PROCESSING_AND_DEALLOCATE; + if (immediateRecipient==UNASSIGNED_PLAYER_ID) + { + immediateRecipient=childRecipient; + } + + pendingNodeCount+=childNumberOfChildren-1; + + out.Write(childHasData); + out.Write(childRecipient); + out.WriteCompressed(childNumberOfChildren); + } + +#ifdef _DO_PRINTF + printf("%i routing to %i\n", peer->GetExternalID(packet->playerId).port, immediateRecipient.port); +#endif + + // Send what we got so far + rakPeer->Send(&out, priority, reliability, orderingChannel, immediateRecipient, false); + + // Restart writing the per recipient data + out.SetWriteOffset(outStartingOffset); + + // Reread the top level node + immediateRecipient=UNASSIGNED_PLAYER_ID; + + pendingNodeCount=0; + + } + + // Write the user payload to the packet struct if this is a destination and change the sender and return true + if (hasData) + { +#ifdef _DO_PRINTF + printf("%i returning payload to user\n", peer->GetExternalID(packet->playerId).port); +#endif + + if (packet->data[0]==ID_TIMESTAMP ) + { + memcpy( packet->data + sizeof(RakNetTime)+sizeof(unsigned char), out.GetData()+payloadWriteByteOffset, BITS_TO_BYTES(payloadBitLength) ); + packet->bitSize=sizeof(RakNetTime)+sizeof(unsigned char)+payloadBitLength; + } + else + { + memcpy( packet->data, out.GetData()+payloadWriteByteOffset, BITS_TO_BYTES(payloadBitLength) ); + packet->bitSize=payloadBitLength; + } + packet->length=BITS_TO_BYTES(packet->bitSize); + packet->playerIndex=65535; + packet->playerId=originalSender; + + return RR_CONTINUE_PROCESSING; + } + + // Absorb + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + + return RR_CONTINUE_PROCESSING; +} +void Router::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; + peer->SetRouterInterface(this); +} +void Router::OnDetach(RakPeerInterface *peer) +{ + peer->RemoveRouterInterface(this); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void Router::OnDisconnect(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void Router::Update(RakPeerInterface *peer) +{ +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void Router::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + +} +void Router::SerializePreorder(DataStructures::Tree *tree, RakNet::BitStream *out, SystemAddressList *recipients) const +{ + unsigned i; + out->Write((bool) (recipients->GetList()->GetIndexOf(tree->data.playerId)!=MAX_UNSIGNED_LONG)); + out->Write(tree->data.playerId); + out->WriteCompressed((unsigned short) tree->children.Size()); + for (i=0; i < tree->children.Size(); i++) + SerializePreorder(tree->children[i], out, recipients); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/Router.h b/raknet/Router.h new file mode 100644 index 0000000..b31f046 --- /dev/null +++ b/raknet/Router.h @@ -0,0 +1,97 @@ +/// \file +/// \brief Router plugin. Allows you to send to systems you are not directly connected to, and to route those messages +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __ROUTER_PLUGIN_H +#define __ROUTER_PLUGIN_H + +class RakPeerInterface; +#include "NetworkTypes.h" +#include "PluginInterface.h" +#include "DS_OrderedList.h" +#include "DS_WeightedGraph.h" +#include "PacketPriority.h" +#include "SystemAddressList.h" +#include "RouterInterface.h" +#include "Export.h" +#include "ConnectionGraph.h" + +/// \defgroup ROUTER_GROUP Router +/// \ingroup PLUGINS_GROUP + +/// \ingroup ROUTER_GROUP +/// \brief Used to route messages between peers +class RAK_DLL_EXPORT Router : public PluginInterface , public RouterInterface +{ +public: + Router(); + ~Router(); + + // -------------------------------------------------------------------------------------------- + // User functions + // -------------------------------------------------------------------------------------------- + /// We can restrict what kind of messages are routed by this plugin. + /// This is useful for security, since you usually want to restrict what kind of messages you have to worry about from (as an example) other + /// clients in a client / server system + /// \param[in] restrict True to restrict what messages will be routed. False to not do so (default). + void SetRestrictRoutingByType(bool restrict); + + /// If types are restricted, this adds an allowed message type to be routed + /// \param[in] messageId The type to not allow routing of. + void AddAllowedType(unsigned char messageId); + + /// Removes a restricted type previously added with AddRestrictedType + /// \param[in] messageId The type to no longer restrict routing of. + void RemoveAllowedType(unsigned char messageId); + + /// Set the connection graph, which is a weighted graph of the topology of the network. You can easily get this from the + /// ConnectionGraph plugin. See the 'router' sample for usage. + /// This is necessary if you want to send (not necessary just to route). + /// \param[in] connectionGraph A weighted graph representing the topology of the network. + void SetConnectionGraph(DataStructures::WeightedGraph *connectionGraph); + + /// Sends a bitstream to one or more systems. If multiple systems are specified, the message will be multicasted using a minimum spanning tree + /// \pre You just have called SetConnectionGraph with a valid graph representing the network topology + /// \note Single target sends from RakPeer with this plugin installed will also be routed. Sends from other plugins will also be routed as long as this plugin is attached first. + /// \param[in] data The data to send + /// \param[in] bitLength How many bits long data is + /// \param[in] priority What priority level to send on. + /// \param[in] reliability How reliability to send this data + /// \param[in] orderingChannel When using ordered or sequenced packets, what channel to order these on.- Packets are only ordered relative to other packets on the same stream + /// \param[in] recipients A list of recipients to send to. To send to one recipient, just pass a PlayerID + /// \return True on success, false mostly if the connection graph cannot find the destination. + bool Send( char *data, unsigned bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, SystemAddressList *recipients ); + bool Send( const char *data, unsigned bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId ); + + // -------------------------------------------------------------------------------------------- + // Packet handling functions + // -------------------------------------------------------------------------------------------- + virtual void OnAttach(RakPeerInterface *peer); + virtual void OnDetach(RakPeerInterface *peer); + virtual void OnDisconnect(RakPeerInterface *peer); + virtual void Update(RakPeerInterface *peer); + virtual PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet); + virtual void OnCloseConnection(RakPeerInterface *peer, PlayerID playerId); +protected: + void SendTree(PacketPriority priority, PacketReliability reliability, char orderingChannel, DataStructures::Tree *tree, const char *data, unsigned bitLength, RakNet::BitStream *out, SystemAddressList *recipients); + void SerializePreorder(DataStructures::Tree *tree, RakNet::BitStream *out, SystemAddressList *recipients) const; + DataStructures::WeightedGraph *graph; + bool restrictByType; + DataStructures::OrderedList allowedTypes; + RakPeerInterface *rakPeer; +}; + +#endif diff --git a/raknet/RouterInterface.h b/raknet/RouterInterface.h new file mode 100644 index 0000000..6cb7502 --- /dev/null +++ b/raknet/RouterInterface.h @@ -0,0 +1,13 @@ +#ifndef __ROUTER_INTERFACE_H +#define __ROUTER_INTERFACE_H + +#include "Export.h" + +/// On failed directed sends, RakNet can call an alternative send function to use. +class RAK_DLL_EXPORT RouterInterface +{ +public: + virtual bool Send( const char *data, unsigned bitLength, PacketPriority priority, PacketReliability reliability, char orderingChannel, PlayerID playerId )=0; +}; + +#endif diff --git a/raknet/SAMPRPC.h b/raknet/SAMPRPC.h deleted file mode 100644 index 16c3785..0000000 --- a/raknet/SAMPRPC.h +++ /dev/null @@ -1,177 +0,0 @@ - -/* - Unused RPC IDs: - \x00 \x01 \x02 \x03 \x04 \x05 \x06 \x07 \x08 \x09 - \x0A \x33 \x64 \x6D \x6E \x72 \x8D \x8E \x8F \xB3 - \xB4 \xB5 \xB6 \xB7 \xB8 \xB9 \xBA \xBB \xBC \xBD - \xBE \xBF \xC0 \xC1 \xC2 \xC3 \xC4 \xC5 \xC6 \xC7 - \xC8 \xC9 \xCA \xCB \xCC \xCD \xCE \xCF \xD0 \xD1 - \xD2 \xD3 \xD4 \xD5 \xD6 \xD7 \xD8 \xD9 \xDA \xDB - \xDC \xDD \xDE \xDF \xE0 \xE1 \xE2 \xE3 \xE4 \xE5 - \xE6 \xE7 \xE8 \xE9 \xEA \xEB \xEC \xED \xEE \xEF - \xF0 \xF1 \xF2 \xF3 \xF4 \xF5 \xF6 \xF7 \xF8 \xF9 - \xFA \xFB \xFC \xFD \xFE \xFF -*/ - -#define RPC_Unk22 "\x22" -#define RPC_Unk24 "\x24" -#define RPC_Unk3A "\x3A" -#define RPC_Unk3B "\x3B" -#define RPC_Unk3D "\x3D" -#define RPC_SetCheckpoint "\x6B" -#define RPC_DisableCheckpoint "\x25" -#define RPC_SetRaceCheckpoint "\x26" -#define RPC_DisableRaceCheckpoint "\x27" -#define RPC_UpdateScoresPingsIPs "\x9B" -#define RPC_SvrStats "\x66" -#define RPC_GameModeRestart "\x28" -#define RPC_ConnectionRejected "\x82" -#define RPC_ClientMessage "\x5D" -#define RPC_WorldTime "\x5E" -#define RPC_Unk5F "\x5F" -#define RPC_Unk3F "\x3F" -#define RPC_Unk97 "\x97" -#define RPC_ScmEvent "\x60" -#define RPC_Weather "\x98" -#define RPC_Unk1D "\x1D" -#define RPC_Unk1E "\x1E" -#define RPC_Unk3C "\x3C" -#define RPC_WorldPlayerAdd "\x20" -#define RPC_WorldPlayerDeath "\xA6" -#define RPC_WorldPlayerRemove "\xA3" -#define RPC_WorldVehicleAdd "\xA4" -#define RPC_WorldVehicleRemove "\xA5" -#define RPC_DamageVehicle "\x6A" -#define RPC_Unk18 "\x18" -#define RPC_EnterVehicle "\x1A" -#define RPC_ExitVehicle "\x9A" -#define RPC_ServerJoin "\x89" -#define RPC_ServerQuit "\x8A" -#define RPC_InitGame "\x8B" -#define RPC_Chat "\x65" -#define RPC_RequestClass "\x80" -#define RPC_RequestSpawn "\x81" -#define RPC_EditAttachedObject "\x74" -#define RPC_EditObject "\x75" -#define RPC_SelectObject "\x1B" -#define RPC_Unk1C "\x1C" -#define RPC_UnkAA "\xAA" -#define RPC_ClientCheck "\x67" -#define RPC_UnkAB "\xAB" -#define RPC_UnkAC "\xAC" -#define RPC_PlayerDamage "\x73" -#define RPC_SetInteriorId "\x76" -#define RPC_ScriptCash "\x1F" -#define RPC_ClickMap "\x77" -#define RPC_VehicleDestroyed "\x88" -#define RPC_PickedUpWeapon "\x61" -#define RPC_PickedUpPickup "\x83" -#define RPC_MenuSelect "\x84" -#define RPC_MenuQuit "\x8C" -#define RPC_DialogResponse "\x3E" -#define RPC_ClickPlayer "\x17" -#define RPC_ClientJoin "\x19" -#define RPC_NPCJoin "\x36" -#define RPC_Spawn "\x34" -#define RPC_Death "\x35" -#define RPC_ServerCommand "\x32" -#define RPC_ClickTextDraw "\x53" -#define RPC_CameraTarget "\xA8" -#define RPC_ActorDamage "\xB1" - -#define RPC_ScrUnkA7 "\xA7" -#define RPC_ScrUnk38 "\x38" -#define RPC_ScrUnk90 "\x90" -#define RPC_ScrUnk91 "\x91" -#define RPC_ScrSetGravity "\x92" -#define RPC_ScrUnk93 "\x93" -#define RPC_ScrUnk94 "\x94" -#define RPC_ScrUnk95 "\x95" -#define RPC_ScrUnk2C "\x2C" -#define RPC_ScrUnk2D "\x2D" -#define RPC_ScrUnk2E "\x2E" -#define RPC_ScrUnk2F "\x2F" -#define RPC_ScrUnk4F "\x4F" -#define RPC_ScrUnk50 "\x50" -#define RPC_ScrUnk63 "\x63" -#define RPC_ScrUnk7A "\x7A" -#define RPC_ScrUnk7B "\x7B" -#define RPC_ScrUnk7C "\x7C" -#define RPC_ScrUnk7D "\x7D" -#define RPC_ScrUnk7E "\x7E" -#define RPC_ScrUnk7F "\x7F" -#define RPC_ScrUnk39 "\x39" -#define RPC_ScrUnk4A "\x4A" -#define RPC_ScrUnk4B "\x4B" -#define RPC_ScrUnk85 "\x85" -#define RPC_ScrUnk86 "\x86" -#define RPC_ScrUnk87 "\x87" -#define RPC_ScrUnk69 "\x69" -#define RPC_ScrAddGangZone "\x6C" -#define RPC_ScrRemoveGangZone "\x78" -#define RPC_ScrFlashGangZone "\x79" -#define RPC_ScrStopFlashGangZone "\x55" -#define RPC_ScrUnk56 "\x56" -#define RPC_ScrUnk57 "\x57" -#define RPC_ScrUnk58 "\x58" -#define RPC_ScrUnk68 "\x68" -#define RPC_ScrUnk59 "\x59" -#define RPC_ScrUnk5A "\x5A" -#define RPC_ScrUnk5B "\x5B" -#define RPC_ScrUnk6F "\x6F" -#define RPC_ScrUnk62 "\x62" -#define RPC_ScrUnk5C "\x5C" -#define RPC_ScrUnk96 "\x96" -#define RPC_ScrUnk70 "\x70" -#define RPC_ScrSetSpawnInfo "\x44" -#define RPC_ScrUnk45 "\x45" -#define RPC_ScrUnk99 "\x99" -#define RPC_ScrUnk0B "\x0B" -#define RPC_ScrSetPlayerPos "\x0C" -#define RPC_ScrUnk0D "\x0D" -#define RPC_ScrUnk0E "\x0E" -#define RPC_ScrPutPlayerInVehicle "\x46" -#define RPC_ScrRemovePlayerFromVehicle "\x47" -#define RPC_ScrUnk48 "\x48" -#define RPC_ScrDisplayGameText "\x49" -#define RPC_ScrUnk9C "\x9C" -#define RPC_ScrUnk9D "\x9D" -#define RPC_ScrUnk9E "\x9E" -#define RPC_ScrUnk9F "\x9F" -#define RPC_ScrUnkA0 "\xA0" -#define RPC_ScrUnkA1 "\xA1" -#define RPC_ScrUnkA2 "\xA2" -#define RPC_ScrUnk0F "\x0F" -#define RPC_ScrUnk10 "\x10" -#define RPC_ScrUnk11 "\x11" -#define RPC_ScrUnk12 "\x12" -#define RPC_ScrSetPlayerFacingAngle "\x13" -#define RPC_ScrUnk14 "\x14" -#define RPC_ScrUnk15 "\x15" -#define RPC_ScrUnk16 "\x16" -#define RPC_ScrUnk40 "\x40" -#define RPC_ScrUnk41 "\x41" -#define RPC_ScrUnk42 "\x42" -#define RPC_ScrUnk37 "\x37" -#define RPC_ScrUnk21 "\x21" -#define RPC_ScrUnk23 "\x23" -#define RPC_ScrUnk43 "\x43" -#define RPC_ScrUnk71 "\x71" -#define RPC_ScrUnk29 "\x29" -#define RPC_ScrUnk2A "\x2A" -#define RPC_ScrUnk2B "\x2B" -#define RPC_ScrUnk51 "\x51" -#define RPC_ScrUnk52 "\x52" -#define RPC_ScrUnk53 "\x53" -#define RPC_ScrUnk54 "\x54" -#define RPC_ScrUnkA9 "\xA9" -#define RPC_ScrUnkAD "\xAD" -#define RPC_ScrUnkAE "\xAE" -#define RPC_ScrUnkAF "\xAF" -#define RPC_ScrUnkB0 "\xB0" -#define RPC_ScrUnkB2 "\xB2" -#define RPC_ScrUnk30 "\x30" -#define RPC_ScrInitMenu "\x4C" -#define RPC_ScrShowMenu "\x4D" -#define RPC_ScrHideMenu "\x4E" -#define RPC_ScrUnk31 "\x31" diff --git a/raknet/SimpleMutex.cpp b/raknet/SimpleMutex.cpp index d3d3fc8..316295b 100644 --- a/raknet/SimpleMutex.cpp +++ b/raknet/SimpleMutex.cpp @@ -95,3 +95,4 @@ void SimpleMutex::Unlock(void) assert(error==0); #endif } + diff --git a/raknet/SimpleMutex.h b/raknet/SimpleMutex.h index 2bf2891..501b587 100644 --- a/raknet/SimpleMutex.h +++ b/raknet/SimpleMutex.h @@ -37,13 +37,13 @@ public: /// Constructor SimpleMutex(); - + // Destructor ~SimpleMutex(); - + // Locks the mutex. Slow! void Lock(void); - + // Unlocks the mutex. void Unlock(void); private: @@ -55,3 +55,4 @@ private: }; #endif + diff --git a/raknet/SimpleTCPServer.h b/raknet/SimpleTCPServer.h new file mode 100644 index 0000000..f428359 --- /dev/null +++ b/raknet/SimpleTCPServer.h @@ -0,0 +1,104 @@ +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected by any TCP client, including telnet. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __SIMPLE_TCP_SERVER +#define __SIMPLE_TCP_SERVER + +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +#include +#include +#else +#include +#include +#include +#include +#include +#include +/// Unix/Linux uses ints for sockets +typedef int SOCKET; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#endif + +#include "DS_List.h" +#include "NetworkTypes.h" +#include "SingleProducerConsumer.h" +#include "Export.h" + +struct RemoteClient; + +/// \internal +/// \brief As the name says, a simple multithreaded TCP server. Used by TelnetTransport +class RAK_DLL_EXPORT SimpleTCPServer +{ +public: + SimpleTCPServer(); + ~SimpleTCPServer(); + + /// Starts the TCP server on the indicated port + bool Start(unsigned short port); + + /// Stops the TCP server + void Stop(void); + + /// Sends a byte stream + void Send( const char *data, unsigned length, PlayerID playerId ); + + /// Returns data received + Packet* Receive( void ); + + /// Disconnects a player/address + void CloseConnection( PlayerID playerId ); + + /// Deallocates a packet returned by Receive + void DeallocatePacket( Packet *packet ); + + /// Queued events of new connections + PlayerID HasNewConnection(void); + + /// Queued events of lost connections + PlayerID HasLostConnection(void); +protected: + + bool isStarted, threadRunning; + SOCKET listenSocket; + + DataStructures::List remoteClients; + DataStructures::SingleProducerConsumer outgoingMessages, incomingMessages; + DataStructures::SingleProducerConsumer newConnections, lostConnections, requestedCloseConnections; + +#ifdef _WIN32 + HANDLE threadHandle; + friend unsigned __stdcall UpdateTCPServerLoop( LPVOID arguments ); +#else + pthread_t threadHandle; + friend void* UpdateTCPServerLoop( void* arguments ); +#endif + + void DeleteRemoteClient(RemoteClient *remoteClient, fd_set *exceptionFD); +}; + +/// Stores information about a remote client. In this case, only the socket used by that client. +struct RemoteClient +{ + SOCKET socket; + PlayerID playerId; +}; + +#endif diff --git a/raknet/SingleProducerConsumer.h b/raknet/SingleProducerConsumer.h index e1e81ea..41a8057 100644 --- a/raknet/SingleProducerConsumer.h +++ b/raknet/SingleProducerConsumer.h @@ -1,4 +1,19 @@ -// TODO: Implement SingleProducerConsumer.h +/// \file +/// \brief \b [Internal] Passes queued data between threads using a circular buffer with read and write pointers +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __SINGLE_PRODUCER_CONSUMER_H #define __SINGLE_PRODUCER_CONSUMER_H @@ -21,15 +36,49 @@ namespace DataStructures /// Constructor SingleProducerConsumer(); + /// Destructor + ~SingleProducerConsumer(); + + /// WriteLock must be immediately followed by WriteUnlock. These two functions must be called in the same thread. + /// \return A pointer to a block of data you can write to. + SingleProducerConsumerType* WriteLock(void); + + /// Call if you don't want to write to a block of data from WriteLock() after all. + /// Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored + /// \param[in] cancelToLocation Which WriteLock() to cancel. + void CancelWriteLock(SingleProducerConsumerType* cancelToLocation); + + /// Call when you are done writing to a block of memory returned by WriteLock() + void WriteUnlock(void); + /// ReadLock must be immediately followed by ReadUnlock. These two functions must be called in the same thread. /// \retval 0 No data is availble to read /// \retval Non-zero The data previously written to, in another thread, by WriteLock followed by WriteUnlock. SingleProducerConsumerType* ReadLock(void); + // Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored + /// param[in] Which ReadLock() to cancel. + void CancelReadLock(SingleProducerConsumerType* cancelToLocation); + /// Signals that we are done reading the the data from the least recent call of ReadLock. /// At this point that pointer is no longer valid, and should no longer be read. void ReadUnlock(void); + /// Clear is not thread-safe and none of the lock or unlock functions should be called while it is running. + void Clear(void); + + /// This function will estimate how many elements are waiting to be read. It's threadsafe enough that the value returned is stable, but not threadsafe enough to give accurate results. + /// \return An ESTIMATE of how many data elements are waiting to be read + int Size(void) const; + + /// Make sure that the pointer we done reading for the call to ReadUnlock is the right pointer. + /// param[in] A previous pointer returned by ReadLock() + bool CheckReadUnlockOrder(const SingleProducerConsumerType* data) const; + + /// Returns if ReadUnlock was called before ReadLock + /// \return If the read is locked + bool ReadIsLocked(void) const; + private: struct DataPlusPtr { @@ -70,18 +119,82 @@ namespace DataStructures } template - SingleProducerConsumerType* SingleProducerConsumer::ReadLock( void ) + SingleProducerConsumer::~SingleProducerConsumer() { - if (readAheadPointer==writePointer || - readAheadPointer->readyToRead==false) + volatile DataPlusPtr *next; + readPointer=writeAheadPointer->next; + while (readPointer!=writeAheadPointer) { - return 0; + next=readPointer->next; + delete (char*) readPointer; + readPointer=next; + } + delete (char*) readPointer; + } + + template + SingleProducerConsumerType* SingleProducerConsumer::WriteLock( void ) + { + if (writeAheadPointer->next==readPointer || + writeAheadPointer->next->readyToRead==true) + { + volatile DataPlusPtr *originalNext=writeAheadPointer->next; + writeAheadPointer->next=new DataPlusPtr; + assert(writeAheadPointer->next); + writeAheadPointer->next->next=originalNext; } volatile DataPlusPtr *last; - last=readAheadPointer; - readAheadPointer=readAheadPointer->next; - return (SingleProducerConsumerType*)last; + last=writeAheadPointer; + writeAheadPointer=writeAheadPointer->next; + + return (SingleProducerConsumerType*) last; + } + + template + void SingleProducerConsumer::CancelWriteLock( SingleProducerConsumerType* cancelToLocation ) + { + writeAheadPointer=(DataPlusPtr *)cancelToLocation; + } + + template + void SingleProducerConsumer::WriteUnlock( void ) + { + // DataPlusPtr *dataContainer = (DataPlusPtr *)structure; + +#ifdef _DEBUG + assert(writePointer->next!=readPointer); + assert(writePointer!=writeAheadPointer); +#endif + + writeCount++; + // User is done with the data, allow send by updating the write pointer + writePointer->readyToRead=true; + writePointer=writePointer->next; + } + + template + SingleProducerConsumerType* SingleProducerConsumer::ReadLock( void ) + { + if (readAheadPointer==writePointer || + readAheadPointer->readyToRead==false) + { + return 0; + } + + volatile DataPlusPtr *last; + last=readAheadPointer; + readAheadPointer=readAheadPointer->next; + return (SingleProducerConsumerType*)last; + } + + template + void SingleProducerConsumer::CancelReadLock( SingleProducerConsumerType* cancelToLocation ) + { +#ifdef _DEBUG + assert(readPointer!=writePointer); +#endif + readAheadPointer=(DataPlusPtr *)cancelToLocation; } template @@ -98,8 +211,136 @@ namespace DataStructures readPointer=readPointer->next; } + template + void SingleProducerConsumer::Clear( void ) + { + // Shrink the list down to MINIMUM_LIST_SIZE elements + volatile DataPlusPtr *next; + writePointer=readPointer->next; + + int listSize=1; + next=readPointer->next; + while (next!=readPointer) + { + listSize++; + next=next->next; + } + + while (listSize-- > MINIMUM_LIST_SIZE) + { + next=writePointer->next; +#ifdef _DEBUG + assert(writePointer!=readPointer); +#endif + delete (char*) writePointer; + writePointer=next; + } + + readPointer->next=writePointer; + writePointer=readPointer; + readAheadPointer=readPointer; + writeAheadPointer=writePointer; + readCount=writeCount=0; + } + + template + int SingleProducerConsumer::Size( void ) const + { + return writeCount-readCount; + } + + template + bool SingleProducerConsumer::CheckReadUnlockOrder(const SingleProducerConsumerType* data) const + { + return const_cast(&readPointer->object) == data; + } + template + bool SingleProducerConsumer::ReadIsLocked(void) const + { + return readAheadPointer!=readPointer; + } } #endif + +/* +#include "SingleProducerConsumer.h" +#include +#include +#include +#include +#include +#include + +#define READ_COUNT_ITERATIONS 10000000 + +DataStructures::SingleProducerConsumer spc; +unsigned long readCount; + +unsigned __stdcall ProducerThread( LPVOID arguments ) +{ +unsigned long producerCount; +unsigned long *writeBlock; +producerCount=0; +while (readCount < READ_COUNT_ITERATIONS) +{ +writeBlock=spc.WriteLock(); +*writeBlock=producerCount; +spc.WriteUnlock(); +producerCount++; +if ((producerCount%1000000)==0) +{ +printf("WriteCount: %i. BufferSize=%i\n", producerCount, spc.Size()); +} +} +printf("PRODUCER THREAD ENDED!\n"); +return 0; +} + +unsigned __stdcall ConsumerThread( LPVOID arguments ) +{ +unsigned long *readBlock; +while (readCount < READ_COUNT_ITERATIONS) +{ +if ((readBlock=spc.ReadLock())!=0) +{ +if (*readBlock!=readCount) +{ +printf("Test failed! Expected %i got %i!\n", readCount, *readBlock); +readCount = READ_COUNT_ITERATIONS; +assert(0); +} +spc.ReadUnlock(); +readCount++; +if ((readCount%1000000)==0) +{ +printf("ReadCount: %i. BufferSize=%i\n", readCount, spc.Size()); +} +} +} +printf("CONSUMER THREAD ENDED!\n"); +return 0; +} + +void main(void) +{ +readCount=0; +unsigned threadId1 = 0; +unsigned threadId2 = 0; +HANDLE thread1Handle, thread2Handle; +unsigned long startTime = timeGetTime(); + +thread1Handle=(HANDLE)_beginthreadex( NULL, 0, ProducerThread, 0, 0, &threadId1 ); +thread2Handle=(HANDLE)_beginthreadex( NULL, 0, ConsumerThread, 0, 0, &threadId1 ); + +while (readCount < READ_COUNT_ITERATIONS) +{ +Sleep(0); +} +char str[256]; +printf("Elapsed time = %i milliseconds. Press Enter to continue\n", timeGetTime() - startTime); +gets(str); +} +*/ diff --git a/raknet/SocketDataEncryptor.cpp b/raknet/SocketDataEncryptor.cpp deleted file mode 100644 index 1f8d42d..0000000 --- a/raknet/SocketDataEncryptor.cpp +++ /dev/null @@ -1,130 +0,0 @@ - -#include -#include "SocketDataEncryptor.h" - -unsigned short SocketDataEncryptor::session_key = 0; - -static const unsigned char S[256] = { -#ifndef SAMPSRV - 0x27,0x69,0xFD,0x87,0x60,0x7D,0x83,0x02,0xF2,0x3F,0x71,0x99,0xA3,0x7C,0x1B,0x9D, - 0x76,0x30,0x23,0x25,0xC5,0x82,0x9B,0xEB,0x1E,0xFA,0x46,0x4F,0x98,0xC9,0x37,0x88, - 0x18,0xA2,0x68,0xD6,0xD7,0x22,0xD1,0x74,0x7A,0x79,0x2E,0xD2,0x6D,0x48,0x0F,0xB1, - 0x62,0x97,0xBC,0x8B,0x59,0x7F,0x29,0xB6,0xB9,0x61,0xBE,0xC8,0xC1,0xC6,0x40,0xEF, - 0x11,0x6A,0xA5,0xC7,0x3A,0xF4,0x4C,0x13,0x6C,0x2B,0x1C,0x54,0x56,0x55,0x53,0xA8, - 0xDC,0x9C,0x9A,0x16,0xDD,0xB0,0xF5,0x2D,0xFF,0xDE,0x8A,0x90,0xFC,0x95,0xEC,0x31, - 0x85,0xC2,0x01,0x06,0xDB,0x28,0xD8,0xEA,0xA0,0xDA,0x10,0x0E,0xF0,0x2A,0x6B,0x21, - 0xF1,0x86,0xFB,0x65,0xE1,0x6F,0xF6,0x26,0x33,0x39,0xAE,0xBF,0xD4,0xE4,0xE9,0x44, - 0x75,0x3D,0x63,0xBD,0xC0,0x7B,0x9E,0xA6,0x5C,0x1F,0xB2,0xA4,0xC4,0x8D,0xB3,0xFE, - 0x8F,0x19,0x8C,0x4D,0x5E,0x34,0xCC,0xF9,0xB5,0xF3,0xF8,0xA1,0x50,0x04,0x93,0x73, - 0xE0,0xBA,0xCB,0x45,0x35,0x1A,0x49,0x47,0x6E,0x2F,0x51,0x12,0xE2,0x4A,0x72,0x05, - 0x66,0x70,0xB8,0xCD,0x00,0xE5,0xBB,0x24,0x58,0xEE,0xB4,0x80,0x81,0x36,0xA9,0x67, - 0x5A,0x4B,0xE8,0xCA,0xCF,0x9F,0xE3,0xAC,0xAA,0x14,0x5B,0x5F,0x0A,0x3B,0x77,0x92, - 0x09,0x15,0x4E,0x94,0xAD,0x17,0x64,0x52,0xD3,0x38,0x43,0x0D,0x0C,0x07,0x3C,0x1D, - 0xAF,0xED,0xE7,0x08,0xB7,0x03,0xE6,0x8E,0xAB,0x91,0x89,0x3E,0x2C,0x96,0x42,0xD9, - 0x78,0xDF,0xD0,0x57,0x5D,0x84,0x41,0x7E,0xCE,0xF7,0x32,0xC3,0xD5,0x20,0x0B,0xA7, -#else - 0xB4,0x62,0x07,0xE5,0x9D,0xAF,0x63,0xDD,0xE3,0xD0,0xCC,0xFE,0xDC,0xDB,0x6B,0x2E, - 0x6A,0x40,0xAB,0x47,0xC9,0xD1,0x53,0xD5,0x20,0x91,0xA5,0x0E,0x4A,0xDF,0x18,0x89, - 0xFD,0x6F,0x25,0x12,0xB7,0x13,0x77,0x00,0x65,0x36,0x6D,0x49,0xEC,0x57,0x2A,0xA9, - 0x11,0x5F,0xFA,0x78,0x95,0xA4,0xBD,0x1E,0xD9,0x79,0x44,0xCD,0xDE,0x81,0xEB,0x09, - 0x3E,0xF6,0xEE,0xDA,0x7F,0xA3,0x1A,0xA7,0x2D,0xA6,0xAD,0xC1,0x46,0x93,0xD2,0x1B, - 0x9C,0xAA,0xD7,0x4E,0x4B,0x4D,0x4C,0xF3,0xB8,0x34,0xC0,0xCA,0x88,0xF4,0x94,0xCB, - 0x04,0x39,0x30,0x82,0xD6,0x73,0xB0,0xBF,0x22,0x01,0x41,0x6E,0x48,0x2C,0xA8,0x75, - 0xB1,0x0A,0xAE,0x9F,0x27,0x80,0x10,0xCE,0xF0,0x29,0x28,0x85,0x0D,0x05,0xF7,0x35, - 0xBB,0xBC,0x15,0x06,0xF5,0x60,0x71,0x03,0x1F,0xEA,0x5A,0x33,0x92,0x8D,0xE7,0x90, - 0x5B,0xE9,0xCF,0x9E,0xD3,0x5D,0xED,0x31,0x1C,0x0B,0x52,0x16,0x51,0x0F,0x86,0xC5, - 0x68,0x9B,0x21,0x0C,0x8B,0x42,0x87,0xFF,0x4F,0xBE,0xC8,0xE8,0xC7,0xD4,0x7A,0xE0, - 0x55,0x2F,0x8A,0x8E,0xBA,0x98,0x37,0xE4,0xB2,0x38,0xA1,0xB6,0x32,0x83,0x3A,0x7B, - 0x84,0x3C,0x61,0xFB,0x8C,0x14,0x3D,0x43,0x3B,0x1D,0xC3,0xA2,0x96,0xB3,0xF8,0xC4, - 0xF2,0x26,0x2B,0xD8,0x7C,0xFC,0x23,0x24,0x66,0xEF,0x69,0x64,0x50,0x54,0x59,0xF1, - 0xA0,0x74,0xAC,0xC6,0x7D,0xB5,0xE6,0xE2,0xC2,0x7E,0x67,0x17,0x5E,0xE1,0xB9,0x3F, - 0x6C,0x70,0x08,0x99,0x45,0x56,0x76,0xF9,0x9A,0x97,0x19,0x72,0x5C,0x02,0x8F,0x58, -#endif -}; - -//---------------------------------------------------- - -unsigned char SocketDataEncryptor::GetChecksum(unsigned char *data, int length) -{ - unsigned char sum = 0; - - for(int i = 0; i != length; i++) - { - sum ^= data[i] & 0xAA; - } - return sum; -} - -//---------------------------------------------------- - -void SocketDataEncryptor::Process(unsigned char key1, - unsigned char key2, - unsigned char *data, - int length) -{ - int counter = 0; - - for(int i = 0; i != length; i++) - { -#ifndef SAMPSRV - data[i] = S[data[i]]; -#endif - if(counter == 0) - { - data[i] ^= key1; - counter = 1; - } - else - { - data[i] ^= key2; - counter--; - } -#ifdef SAMPSRV - data[i] = S[data[i]]; -#endif - } -} - -//---------------------------------------------------- - -#ifndef SAMPSRV - -void SocketDataEncryptor::Encrypt(unsigned char *destination, - unsigned char *source, - int *length) -{ - unsigned char key = session_key; - - *destination = GetChecksum(source, *length); - memcpy(destination + 1, source, *length); - Process(0, key, destination + 1, *length); - ++*length; -} - -#else - -int SocketDataEncryptor::Decrypt(unsigned char *destination, - unsigned char *source, - int *length) -{ - unsigned char key = session_key; - unsigned char sum = source[0]; - - *length--; - memcpy(destination, source + 1, *length); - Process(0, key, destination, *length); - - return GetChecksum(destination, *length) == sum; -} - -#endif - -//---------------------------------------------------- - -void SocketDataEncryptor::SetKey(int key) -{ - session_key = key ^ 0xCCCC; -} - -//---------------------------------------------------- -// EOF diff --git a/raknet/SocketDataEncryptor.h b/raknet/SocketDataEncryptor.h deleted file mode 100644 index 499996b..0000000 --- a/raknet/SocketDataEncryptor.h +++ /dev/null @@ -1,21 +0,0 @@ - -#ifndef __SOCKET_DATA_ENCRYPTOR_H -#define __SOCKET_DATA_ENCRYPTOR_H - -class SocketDataEncryptor -{ -private: - static unsigned char GetChecksum(unsigned char *data, int length); - static void Process(unsigned char key1, unsigned char key2, unsigned char *data, int length); - - static unsigned short session_key; - -public: - static void Encrypt(unsigned char *destination, unsigned char *source, int *length); - static int Decrypt(unsigned char *destination, unsigned char *source, int *length); - - static void SetKey(int key); - -}; - -#endif diff --git a/raknet/SocketLayer.cpp b/raknet/SocketLayer.cpp index e69de29..a215ac4 100644 --- a/raknet/SocketLayer.cpp +++ b/raknet/SocketLayer.cpp @@ -0,0 +1,600 @@ +/** +* @file +* @brief SocketLayer class implementation +* + * This file is part of RakNet Copyright 2003 Rakkarsoft LLC and Kevin Jenkins. + * + * Usage of Raknet is subject to the appropriate licence agreement. + * "Shareware" Licensees with Rakkarsoft LLC are subject to the + * shareware license found at + * http://www.rakkarsoft.com/shareWareLicense.html which you agreed to + * upon purchase of a "Shareware license" "Commercial" Licensees with + * Rakkarsoft LLC are subject to the commercial license found at + * http://www.rakkarsoft.com/sourceCodeLicense.html which you agreed + * to upon purchase of a "Commercial license" + * Custom license users are subject to the terms therein. + * All other users are + * subject to the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Refer to the appropriate license agreement for distribution, + * modification, and warranty rights. +*/ +#include "SocketLayer.h" +#include +#include "MTUSize.h" + +#ifdef _WIN32 +#include +#define COMPATIBILITY_2_RECV_FROM_FLAGS 0 +typedef int socklen_t; +#elif defined(_COMPATIBILITY_2) +#include "Compatibility2Includes.h" +#else +#define COMPATIBILITY_2_RECV_FROM_FLAGS 0 +#include // memcpy +#include +#include +#endif + +#include "ExtendedOverlappedPool.h" +#ifdef __USE_IO_COMPLETION_PORTS +#include "AsynchronousFileIO.h" +#endif + + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +bool SocketLayer::socketLayerStarted = false; +#ifdef _WIN32 +WSADATA SocketLayer::winsockInfo; +#endif +SocketLayer SocketLayer::I; + +#ifdef _WIN32 +extern void __stdcall ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); +#else +extern void ProcessNetworkPacket( const unsigned int binaryAddress, const unsigned short port, const char *data, const int length, RakPeer *rakPeer ); +#endif + +#ifdef _WIN32 +extern void __stdcall ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); +#else +extern void ProcessPortUnreachable( const unsigned int binaryAddress, const unsigned short port, RakPeer *rakPeer ); +#endif + +#ifdef _DEBUG +#include +#endif + +SocketLayer::SocketLayer() +{ + if ( socketLayerStarted == false ) + { +#ifdef _WIN32 + + if ( WSAStartup( MAKEWORD( 2, 2 ), &winsockInfo ) != 0 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "WSAStartup failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + +#endif + socketLayerStarted = true; + } +} + +SocketLayer::~SocketLayer() +{ + if ( socketLayerStarted == true ) + { +#ifdef _WIN32 + WSACleanup(); +#endif + + socketLayerStarted = false; + } +} + +SOCKET SocketLayer::Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ) +{ + assert( writeSocket != INVALID_SOCKET ); + sockaddr_in connectSocketAddress; + + connectSocketAddress.sin_family = AF_INET; + connectSocketAddress.sin_port = htons( port ); + connectSocketAddress.sin_addr.s_addr = binaryAddress; + + if ( connect( writeSocket, ( struct sockaddr * ) & connectSocketAddress, sizeof( struct sockaddr ) ) != 0 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) &messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "WSAConnect failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + + return writeSocket; +} + +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +SOCKET SocketLayer::CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress ) +{ + SOCKET listenSocket; + sockaddr_in listenerSocketAddress; + int ret; + +#ifdef __USE_IO_COMPLETION_PORTS + + if ( blockingSocket == false ) + listenSocket = WSASocket( AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED ); + else +#endif + + listenSocket = socket( AF_INET, SOCK_DGRAM, 0 ); + + if ( listenSocket == INVALID_SOCKET ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "socket(...) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return INVALID_SOCKET; + } + + int sock_opt = 1; + + if ( setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, ( char * ) & sock_opt, sizeof ( sock_opt ) ) == -1 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "setsockopt(SO_REUSEADDR) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + + // This doubles the max throughput rate + sock_opt=1024*256; + setsockopt(listenSocket, SOL_SOCKET, SO_RCVBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + + // This doesn't make much difference: 10% maybe + sock_opt=1024*16; + setsockopt(listenSocket, SOL_SOCKET, SO_SNDBUF, ( char * ) & sock_opt, sizeof ( sock_opt ) ); + + #if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + // If this assert hit you improperly linked against WSock32.h + assert(IP_DONTFRAGMENT==14); + #endif + + // TODO - I need someone on dialup to test this with :( + // Path MTU Detection + /* + if ( setsockopt( listenSocket, IPPROTO_IP, IP_DONTFRAGMENT, ( char * ) & sock_opt, sizeof ( sock_opt ) ) == -1 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "setsockopt(IP_DONTFRAGMENT) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + */ + +#ifndef _COMPATIBILITY_2 + //Set non-blocking +#ifdef _WIN32 + unsigned long nonblocking = 1; +// http://www.die.net/doc/linux/man/man7/ip.7.html + if ( ioctlsocket( listenSocket, FIONBIO, &nonblocking ) != 0 ) + { + assert( 0 ); + return INVALID_SOCKET; + } +#else + if ( fcntl( listenSocket, F_SETFL, O_NONBLOCK ) != 0 ) + { + assert( 0 ); + return INVALID_SOCKET; + } +#endif +#endif + + // Set broadcast capable + if ( setsockopt( listenSocket, SOL_SOCKET, SO_BROADCAST, ( char * ) & sock_opt, sizeof( sock_opt ) ) == -1 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "setsockopt(SO_BROADCAST) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + + // Listen on our designated Port# + listenerSocketAddress.sin_port = htons( port ); + + // Fill in the rest of the address structure + listenerSocketAddress.sin_family = AF_INET; + + if (forceHostAddress && forceHostAddress[0]) + { + listenerSocketAddress.sin_addr.s_addr = inet_addr( forceHostAddress ); + } + else + { + listenerSocketAddress.sin_addr.s_addr = INADDR_ANY; + } + + // bind our name to the socket + ret = bind( listenSocket, ( struct sockaddr * ) & listenerSocketAddress, sizeof( struct sockaddr ) ); + + if ( ret == SOCKET_ERROR ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "bind(...) failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return INVALID_SOCKET; + } + + return listenSocket; +} + +#if !defined(_COMPATIBILITY_1) && !defined(_COMPATIBILITY_2) +const char* SocketLayer::DomainNameToIP( const char *domainName ) +{ + struct hostent * phe = gethostbyname( domainName ); + + if ( phe == 0 || phe->h_addr_list[ 0 ] == 0 ) + { + //cerr << "Yow! Bad host lookup." << endl; + return 0; + } + + struct in_addr addr; + + memcpy( &addr, phe->h_addr_list[ 0 ], sizeof( struct in_addr ) ); + + return inet_ntoa( addr ); +} +#endif + +void SocketLayer::Write( const SOCKET writeSocket, const char* data, const int length ) +{ +#ifdef _DEBUG + assert( writeSocket != INVALID_SOCKET ); +#endif +#ifdef __USE_IO_COMPLETION_PORTS + + ExtendedOverlappedStruct* eos = ExtendedOverlappedPool::Instance()->GetPointer(); + memset( &( eos->overlapped ), 0, sizeof( OVERLAPPED ) ); + memcpy( eos->data, data, length ); + eos->length = length; + + //AsynchronousFileIO::Instance()->PostWriteCompletion(ccs); + WriteAsynch( ( HANDLE ) writeSocket, eos ); +#else + + send( writeSocket, data, length, 0 ); +#endif +} + +// Start an asynchronous read using the specified socket. +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +bool SocketLayer::AssociateSocketWithCompletionPortAndRead( SOCKET readSocket, unsigned int binaryAddress, unsigned short port, RakPeer *rakPeer ) +{ +#ifdef __USE_IO_COMPLETION_PORTS + assert( readSocket != INVALID_SOCKET ); + + ClientContextStruct* ccs = new ClientContextStruct; + ccs->handle = ( HANDLE ) readSocket; + + ExtendedOverlappedStruct* eos = ExtendedOverlappedPool::Instance()->GetPointer(); + memset( &( eos->overlapped ), 0, sizeof( OVERLAPPED ) ); + eos->binaryAddress = binaryAddress; + eos->port = port; + eos->rakPeer = rakPeer; + eos->length = MAXIMUM_MTU_SIZE; + + bool b = AsynchronousFileIO::Instance()->AssociateSocketWithCompletionPort( readSocket, ( DWORD ) ccs ); + + if ( !b ) + { + ExtendedOverlappedPool::Instance()->ReleasePointer( eos ); + delete ccs; + return false; + } + + BOOL success = ReadAsynch( ( HANDLE ) readSocket, eos ); + + if ( success == FALSE ) + return false; + +#endif + + return true; +} + +int SocketLayer::RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ) +{ + int len; + char data[ MAXIMUM_MTU_SIZE ]; + sockaddr_in sa; + + const socklen_t len2 = sizeof( struct sockaddr_in ); + sa.sin_family = AF_INET; + +#ifdef _DEBUG + data[ 0 ] = 0; + len = 0; + sa.sin_addr.s_addr = 0; +#endif + + if ( s == INVALID_SOCKET ) + { + *errorCode = SOCKET_ERROR; + return SOCKET_ERROR; + } + + len = recvfrom( s, data, MAXIMUM_MTU_SIZE, COMPATIBILITY_2_RECV_FROM_FLAGS, ( sockaddr* ) & sa, ( socklen_t* ) & len2 ); + + // if (len>0) + // printf("Got packet on port %i\n",ntohs(sa.sin_port)); + + if ( len == 0 ) + { +#ifdef _DEBUG + printf( "Error: recvfrom returned 0 on a connectionless blocking call\non port %i. This is a bug with Zone Alarm. Please turn off Zone Alarm.\n", ntohs( sa.sin_port ) ); + assert( 0 ); +#endif + + *errorCode = SOCKET_ERROR; + return SOCKET_ERROR; + } + + if ( len != SOCKET_ERROR ) + { + unsigned short portnum; + portnum = ntohs( sa.sin_port ); + //strcpy(ip, inet_ntoa(sa.sin_addr)); + //if (strcmp(ip, "0.0.0.0")==0) + // strcpy(ip, "127.0.0.1"); + ProcessNetworkPacket( sa.sin_addr.s_addr, portnum, data, len, rakPeer ); + + return 1; + } + else + { + *errorCode = 0; + +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + + DWORD dwIOError = WSAGetLastError(); + + if ( dwIOError == WSAEWOULDBLOCK ) + { + return SOCKET_ERROR; + } + if ( dwIOError == WSAECONNRESET ) + { +#if defined(_DEBUG) +// printf( "A previous send operation resulted in an ICMP Port Unreachable message.\n" ); +#endif + + + unsigned short portnum=0; + ProcessPortUnreachable(sa.sin_addr.s_addr, portnum, rakPeer); + // *errorCode = dwIOError; + return SOCKET_ERROR; + } + else + { +#if defined(_DEBUG) + if ( dwIOError != WSAEINTR ) + { + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "recvfrom failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); + } +#endif + } +#endif + } + + return 0; // no data +} + +#ifdef _MSC_VER +#pragma warning( disable : 4702 ) // warning C4702: unreachable code +#endif +int SocketLayer::SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ) +{ + if ( s == INVALID_SOCKET ) + { + return -1; + } + + int len; + sockaddr_in sa; + sa.sin_port = htons( port ); + sa.sin_addr.s_addr = binaryAddress; + sa.sin_family = AF_INET; + + do + { + // TODO - use WSASendTo which is faster. + len = sendto( s, data, length, 0, ( const sockaddr* ) & sa, sizeof( struct sockaddr_in ) ); + } + while ( len == 0 ); + + if ( len != SOCKET_ERROR ) + return 0; + +#if defined(_WIN32) + + DWORD dwIOError = WSAGetLastError(); + + if ( dwIOError == WSAECONNRESET ) + { +#if defined(_DEBUG) + printf( "A previous send operation resulted in an ICMP Port Unreachable message.\n" ); +#endif + + } + else if ( dwIOError != WSAEWOULDBLOCK ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "sendto failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + } + + return dwIOError; +#endif + + return 1; // error +} + +int SocketLayer::SendTo( SOCKET s, const char *data, int length, char ip[ 16 ], unsigned short port ) +{ + unsigned int binaryAddress; + binaryAddress = inet_addr( ip ); + return SendTo( s, data, length, binaryAddress, port ); +} + +#if !defined(_COMPATIBILITY_1) && !defined(_COMPATIBILITY_2) +void SocketLayer::GetMyIP( char ipList[ 10 ][ 16 ] ) +{ + char ac[ 80 ]; + + if ( gethostname( ac, sizeof( ac ) ) == SOCKET_ERROR ) + { + #if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "gethostname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); + #endif + + return ; + } + + struct hostent *phe = gethostbyname( ac ); + + if ( phe == 0 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "gethostbyname failed:Error code - %d\n%s", dwIOError, messageBuffer ); + + //Free the buffer. + LocalFree( messageBuffer ); +#endif + + return ; + } + + for ( int i = 0; phe->h_addr_list[ i ] != 0 && i < 10; ++i ) + { + + struct in_addr addr; + + memcpy( &addr, phe->h_addr_list[ i ], sizeof( struct in_addr ) ); + //cout << "Address " << i << ": " << inet_ntoa(addr) << endl; + strcpy( ipList[ i ], inet_ntoa( addr ) ); + } +} +#endif + +unsigned short SocketLayer::GetLocalPort ( SOCKET s ) +{ + sockaddr_in sa; + socklen_t len = sizeof(sa); + if (getsockname(s, (sockaddr*)&sa, &len)!=0) + return 0; + return ntohs(sa.sin_port); +} + + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/SocketLayer.h b/raknet/SocketLayer.h index 0601d21..58337d1 100644 --- a/raknet/SocketLayer.h +++ b/raknet/SocketLayer.h @@ -1,11 +1,140 @@ +/// \file +/// \brief \b [Internal] Encapsulates Berkely sockets +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + #ifndef __SOCKET_LAYER_H #define __SOCKET_LAYER_H +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +// IP_DONTFRAGMENT is different between winsock 1 and winsock 2. Therefore, Winsock2.h must be linked againt Ws2_32.lib +// winsock.h must be linked against WSock32.lib. If these two are mixed up the flag won't work correctly +#include +#include +#else +#include +#include +#include +#include +#include +#include +/// Unix/Linux uses ints for sockets +typedef int SOCKET; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#endif +#include "ClientContextStruct.h" + +class RakPeer; + +// A platform independent implementation of Berkeley sockets, with settings used by RakNet class SocketLayer { public: + + /// Default Constructor + SocketLayer(); + + /// Destructor + ~SocketLayer(); + + // Get the singleton instance of the Socket Layer. + /// \return unique instance + static inline SocketLayer* Instance() + { + return & I; + } + + // Connect a socket to a remote host. + /// \param[in] writeSocket The local socket. + /// \param[in] binaryAddress The address of the remote host. + /// \param[in] port the remote port. + /// \return A new socket used for communication. + SOCKET Connect( SOCKET writeSocket, unsigned int binaryAddress, unsigned short port ); + + // Creates a bound socket to listen for incoming connections on the specified port + /// \param[in] port the port number + /// \param[in] blockingSocket + /// \return A new socket used for accepting clients + SOCKET CreateBoundSocket( unsigned short port, bool blockingSocket, const char *forceHostAddress ); + + #if !defined(_COMPATIBILITY_1) + const char* DomainNameToIP( const char *domainName ); + #endif + + #ifdef __USE_IO_COMPLETION_PORTS + void AssociateSocketWithCompletionPort( SOCKET socket, ClientContextStruct* completionKey ); + #endif + + /// Start an asynchronous read using the specified socket. The callback will use the specified PlayerID (associated with this socket) and call either the client or the server callback (one or + /// the other should be 0) + /// \note Was used for depreciated IO completion ports. + bool AssociateSocketWithCompletionPortAndRead( SOCKET readSocket, unsigned int binaryAddress, unsigned short port, RakPeer* rakPeer ); + + /// Write \a data of length \a length to \a writeSocket + /// \param[in] writeSocket The socket to write to + /// \param[in] data The data to write + /// \param[in] length The length of \a data + void Write( const SOCKET writeSocket, const char* data, const int length ); + + /// Read data from a socket + /// \param[in] s the socket + /// \param[in] rakPeer The instance of rakPeer containing the recvFrom C callback + /// \param[in] errorCode An error code if an error occured . + /// \return Returns true if you successfully read data, false on error. + int RecvFrom( const SOCKET s, RakPeer *rakPeer, int *errorCode ); + +#if !defined(_COMPATIBILITY_1) + /// Retrieve all local IP address in a string format. + /// \param[in] ipList An array of ip address in dotted notation. + void GetMyIP( char ipList[ 10 ][ 16 ] ); +#endif + + /// Call sendto (UDP obviously) + /// \param[in] s the socket + /// \param[in] data The byte buffer to send + /// \param[in] length The length of the \a data in bytes + /// \param[in] ip The address of the remote host in dotted notation. + /// \param[in] port The port number to send to. + /// \return 0 on success, nonzero on failure. + int SendTo( SOCKET s, const char *data, int length, char ip[ 16 ], unsigned short port ); + + /// Call sendto (UDP obviously) + /// \param[in] s the socket + /// \param[in] data The byte buffer to send + /// \param[in] length The length of the \a data in bytes + /// \param[in] binaryAddress The address of the remote host in binary format. + /// \param[in] port The port number to send to. + /// \return 0 on success, nonzero on failure. + int SendTo( SOCKET s, const char *data, int length, unsigned int binaryAddress, unsigned short port ); + + /// Returns the local port, useful when passing 0 as the startup port. + /// \param[in] s The socket whose port we are referring to + /// \return The local port + unsigned short GetLocalPort ( SOCKET s ); +private: + + static bool socketLayerStarted; +#ifdef _WIN32 + static WSADATA winsockInfo; +#endif + static SocketLayer I; }; #endif diff --git a/raknet/StringCompressor.cpp b/raknet/StringCompressor.cpp new file mode 100644 index 0000000..54ce955 --- /dev/null +++ b/raknet/StringCompressor.cpp @@ -0,0 +1,415 @@ +/// \file +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#include "StringCompressor.h" +#include "DS_HuffmanEncodingTree.h" +#include "BitStream.h" +#include +#include +#include + +StringCompressor* StringCompressor::instance=0; +int StringCompressor::referenceCount=0; + +void StringCompressor::AddReference(void) +{ + if (++referenceCount==1) + { + instance = new StringCompressor; + } +} +void StringCompressor::RemoveReference(void) +{ + assert(referenceCount > 0); + + if (referenceCount > 0) + { + if (--referenceCount==0) + { + delete instance; + instance=0; + } + } +} + +StringCompressor* StringCompressor::Instance(void) +{ + return instance; +} + +unsigned int englishCharacterFrequencies[ 256 ] = +{ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 722, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 11084, + 58, + 63, + 1, + 0, + 31, + 0, + 317, + 64, + 64, + 44, + 0, + 695, + 62, + 980, + 266, + 69, + 67, + 56, + 7, + 73, + 3, + 14, + 2, + 69, + 1, + 167, + 9, + 1, + 2, + 25, + 94, + 0, + 195, + 139, + 34, + 96, + 48, + 103, + 56, + 125, + 653, + 21, + 5, + 23, + 64, + 85, + 44, + 34, + 7, + 92, + 76, + 147, + 12, + 14, + 57, + 15, + 39, + 15, + 1, + 1, + 1, + 2, + 3, + 0, + 3611, + 845, + 1077, + 1884, + 5870, + 841, + 1057, + 2501, + 3212, + 164, + 531, + 2019, + 1330, + 3056, + 4037, + 848, + 47, + 2586, + 2919, + 4771, + 1707, + 535, + 1106, + 152, + 1243, + 100, + 0, + 2, + 0, + 10, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +StringCompressor::StringCompressor() +{ + DataStructures::Map::IMPLEMENT_DEFAULT_COMPARISON(); + + // Make a default tree immediately, since this is used for RPC possibly from multiple threads at the same time + HuffmanEncodingTree *huffmanEncodingTree = new HuffmanEncodingTree; + huffmanEncodingTree->GenerateFromFrequencyTable( englishCharacterFrequencies ); + huffmanEncodingTrees.Set(0, huffmanEncodingTree); +} +void StringCompressor::GenerateTreeFromStrings( unsigned char *input, unsigned inputLength, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)) + { + huffmanEncodingTree = huffmanEncodingTrees.Get(languageID); + delete huffmanEncodingTree; + } + + unsigned index; + unsigned int frequencyTable[ 256 ]; + + if ( inputLength == 0 ) + return ; + + // Zero out the frequency table + memset( frequencyTable, 0, sizeof( frequencyTable ) ); + + // Generate the frequency table from the strings + for ( index = 0; index < inputLength; index++ ) + frequencyTable[ input[ index ] ] ++; + + // Build the tree + huffmanEncodingTree = new HuffmanEncodingTree; + huffmanEncodingTree->GenerateFromFrequencyTable( frequencyTable ); + huffmanEncodingTrees.Set(languageID, huffmanEncodingTree); +} + +StringCompressor::~StringCompressor() +{ + for (unsigned i=0; i < huffmanEncodingTrees.Size(); i++) + delete huffmanEncodingTrees[i]; +} + +void StringCompressor::EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)==false) + return; + huffmanEncodingTree=huffmanEncodingTrees.Get(languageID); + + if ( input == 0 ) + { + output->WriteCompressed( (unsigned short) 0 ); + return ; + } + + RakNet::BitStream encodedBitStream; + + unsigned short stringBitLength; + + int charsToWrite; + + if ( maxCharsToWrite<=0 || ( int ) strlen( input ) < maxCharsToWrite ) + charsToWrite = ( int ) strlen( input ); + else + charsToWrite = maxCharsToWrite - 1; + + huffmanEncodingTree->EncodeArray( ( unsigned char* ) input, charsToWrite, &encodedBitStream ); + + stringBitLength = ( unsigned short ) encodedBitStream.GetNumberOfBitsUsed(); + + output->WriteCompressed( stringBitLength ); + + output->WriteBits( encodedBitStream.GetData(), stringBitLength ); +} + +bool StringCompressor::DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID ) +{ + HuffmanEncodingTree *huffmanEncodingTree; + if (huffmanEncodingTrees.Has(languageID)==false) + return false; + huffmanEncodingTree=huffmanEncodingTrees.Get(languageID); + + unsigned short stringBitLength; + int bytesInStream; + + output[ 0 ] = 0; + + if ( input->ReadCompressed( stringBitLength ) == false ) + return false; + + if ( input->GetNumberOfUnreadBits() < stringBitLength ) + return false; + + bytesInStream = huffmanEncodingTree->DecodeArray( input, stringBitLength, maxCharsToWrite, ( unsigned char* ) output ); + + if ( bytesInStream < maxCharsToWrite ) + output[ bytesInStream ] = 0; + else + output[ maxCharsToWrite - 1 ] = 0; + + return true; +} diff --git a/raknet/StringCompressor.h b/raknet/StringCompressor.h new file mode 100644 index 0000000..1251576 --- /dev/null +++ b/raknet/StringCompressor.h @@ -0,0 +1,93 @@ +/// \file +/// \brief \b Compresses/Decompresses ASCII strings and writes/reads them to BitStream class instances. You can use this to easily serialize and deserialize your own strings. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __STRING_COMPRESSOR_H +#define __STRING_COMPRESSOR_H + +#include "Export.h" +#include "DS_Map.h" + +/// Forward declaration +namespace RakNet +{ + class BitStream; +}; + +class HuffmanEncodingTree; + +/// \brief Writes and reads strings to and from bitstreams. +/// +/// Only works with ASCII strings. The default compression is for English. +/// You can call GenerateTreeFromStrings to compress and decompress other languages efficiently as well. +class RAK_DLL_EXPORT StringCompressor +{ +public: + + /// Destructor + ~StringCompressor(); + + /// static function because only static functions can access static members + /// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. + /// \return the unique instance of the StringCompressor + static StringCompressor* Instance(void); + + /// Given an array of strings, such as a chat log, generate the optimal encoding tree for it. + /// This function is optional and if it is not called a default tree will be used instead. + /// \param[in] input An array of bytes which should point to text. + /// \param[in] inputLength Length of \a input + /// \param[in] languageID An identifier for the language / string table to generate the tree for. English is automatically created with ID 0 in the constructor. + void GenerateTreeFromStrings( unsigned char *input, unsigned inputLength, int languageID ); + + /// Writes input to output, compressed. Takes care of the null terminator for you. + /// \param[in] input Pointer to an ASCII string + /// \param[in] maxCharsToWrite The max number of bytes to write of \a input. Use 0 to mean no limit. + /// \param[out] output The bitstream to write the compressed string to + /// \param[in] languageID Which language to use + void EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output, int languageID=0 ); + + /// Writes input to output, uncompressed. Takes care of the null terminator for you. + /// \param[out] output A block of bytes to receive the output + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + /// \param[in] input The bitstream containing the compressed string + /// \param[in] languageID Which language to use + bool DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input, int languageID=0 ); + + /// Used so I can allocate and deallocate this singleton at runtime + static void AddReference(void); + + /// Used so I can allocate and deallocate this singleton at runtime + static void RemoveReference(void); + +private: + + /// Private Constructor + StringCompressor(); + + /// Singleton instance + static StringCompressor *instance; + + /// Pointer to the huffman encoding trees. + DataStructures::Map huffmanEncodingTrees; + + static int referenceCount; +}; + +/// Define to more easily reference the string compressor instance. +/// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. +#define stringCompressor StringCompressor::Instance() + +#endif diff --git a/raknet/StringTable.cpp b/raknet/StringTable.cpp new file mode 100644 index 0000000..a09ff31 --- /dev/null +++ b/raknet/StringTable.cpp @@ -0,0 +1,143 @@ +#include "StringTable.h" +#include +#include +#include +#include "BitStream.h" +#include "StringCompressor.h" + +StringTable* StringTable::instance=0; +int StringTable::referenceCount=0; + +int StrAndBoolComp( char *const &key, const StrAndBool &data ) +{ + return strcmp(key,(const char*)data.str); +} + +StringTable::StringTable() +{ + +} + +StringTable::~StringTable() +{ + unsigned i; + for (i=0; i < orderedStringList.Size(); i++) + { + if (orderedStringList[i].b) + delete [] orderedStringList[i].str; + } +} + +void StringTable::AddReference(void) +{ + if (++referenceCount==1) + { + instance = new StringTable; + } +} +void StringTable::RemoveReference(void) +{ + assert(referenceCount > 0); + + if (referenceCount > 0) + { + if (--referenceCount==0) + { + delete instance; + instance=0; + } + } +} + +StringTable* StringTable::Instance(void) +{ + return instance; +} + +void StringTable::AddString(const char *str, bool copyString) +{ + StrAndBool sab; + sab.b=copyString; + if (copyString) + { + sab.str = new char [strlen(str)+1]; + strcpy(sab.str, str); + } + else + { + sab.str=(char*)str; + } + + // If it asserts inside here you are adding duplicate strings. + if (!orderedStringList.Insert(sab.str,sab)) + { + if (copyString) + delete sab.str; + } + + // If this assert hits you need to increase the range of StringTableType + assert(orderedStringList.Size() < (StringTableType)-1); + +} +void StringTable::EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output ) +{ + unsigned index; + bool objectExists; + // This is fast because the list is kept ordered. + index=orderedStringList.GetIndexFromKey((char*)input, &objectExists); + if (objectExists) + { + output->Write(true); + output->Write((StringTableType)index); + } + else + { + LogStringNotFound(input); + output->Write(false); + stringCompressor->EncodeString(input, maxCharsToWrite, output); + } +} + +bool StringTable::DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input ) +{ + bool hasIndex; + assert(maxCharsToWrite>0); + + if (maxCharsToWrite==0) + return false; + if (!input->Read(hasIndex)) + return false; + if (hasIndex==false) + { + stringCompressor->DecodeString(output, maxCharsToWrite, input); + } + else + { + StringTableType index; + if (!input->Read(index)) + return false; + if (index >= orderedStringList.Size()) + { +#ifdef _DEBUG + // Critical error - got a string index out of range, which means AddString was called more times on the remote system than on this system. + // All systems must call AddString the same number of types, with the same strings in the same order. + assert(0); +#endif + return false; + } + + strncpy(output, orderedStringList[index].str, maxCharsToWrite); + output[maxCharsToWrite-1]=0; + } + + return true; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void StringTable::LogStringNotFound(const char *strName) +{ +#ifdef _DEBUG + printf("Efficiency Warning! Unregistered String %s sent to StringTable.\n", strName); +#endif +} diff --git a/raknet/StringTable.h b/raknet/StringTable.h new file mode 100644 index 0000000..3166730 --- /dev/null +++ b/raknet/StringTable.h @@ -0,0 +1,103 @@ +/// \file +/// \brief A simple class to encode and decode known strings based on a lookup table. Similar to the StringCompressor class. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __STRING_TABLE_H +#define __STRING_TABLE_H + +#include "DS_OrderedList.h" +#include "Export.h" + +/// Forward declaration +namespace RakNet +{ + class BitStream; +}; + +/// StringTableType should be the smallest type possible, or else it defeats the purpose of the StringTable class, which is to save bandwidth. +typedef unsigned char StringTableType; + +/// The string plus a bool telling us if this string was copied or not. +struct StrAndBool +{ + char *str; + bool b; +}; +int RAK_DLL_EXPORT StrAndBoolComp( char *const &key, const StrAndBool &data ); + +/// This is an even more efficient alternative to StringCompressor in that it writes a single byte from a lookup table and only does compression +/// if the string does not already exist in the table. +/// All string tables must match on all systems - hence you must add all the strings in the same order on all systems. +/// Furthermore, this must be done before sending packets that use this class, since the strings are ordered for fast lookup. Adding after that time would mess up all the indices so don't do it. +/// Don't use this class to write strings which were not previously registered with AddString, since you just waste bandwidth then. Use StringCompressor instead. +/// \brief Writes a string index, instead of the whole string +class RAK_DLL_EXPORT StringTable +{ +public: + + /// Destructor + ~StringTable(); + + /// static function because only static functions can access static members + /// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. + /// \return the unique instance of the StringTable + static StringTable* Instance(void); + + /// Add a string to the string table. + /// \param[in] str The string to add to the string table + /// \param[in] copyString true to make a copy of the passed string (takes more memory), false to not do so (if your string is in static memory). + void AddString(const char *str, bool copyString); + + /// Writes input to output, compressed. Takes care of the null terminator for you. + /// Relies on the StringCompressor class, which is automatically reference counted in the constructor and destructor in RakPeer. You can call the reference counting functions yourself if you wish too. + /// \param[in] input Pointer to an ASCII string + /// \param[in] maxCharsToWrite The size of \a input + /// \param[out] output The bitstream to write the compressed string to + void EncodeString( const char *input, int maxCharsToWrite, RakNet::BitStream *output ); + + /// Writes input to output, uncompressed. Takes care of the null terminator for you. + /// Relies on the StringCompressor class, which is automatically reference counted in the constructor and destructor in RakPeer. You can call the reference counting functions yourself if you wish too. + /// \param[out] output A block of bytes to receive the output + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + /// \param[in] input The bitstream containing the compressed string + bool DecodeString( char *output, int maxCharsToWrite, RakNet::BitStream *input ); + + /// Used so I can allocate and deallocate this singleton at runtime + static void AddReference(void); + + /// Used so I can allocate and deallocate this singleton at runtime + static void RemoveReference(void); + +protected: + /// Called when you mess up and send a string using this class that was not registered with AddString + /// \param[in] maxCharsToWrite Size, in bytes, of \a output . A NULL terminator will always be appended to the output string. If the maxCharsToWrite is not large enough, the string will be truncated. + void LogStringNotFound(const char *strName); + + /// Private Constructor + StringTable(); + + /// Singleton instance + static StringTable *instance; + static int referenceCount; + + DataStructures::OrderedList orderedStringList; +}; + +/// Define to more easily reference the string table instance. +/// The RakPeer constructor adds a reference to this class, so don't call this until an instance of RakPeer exists, or unless you call AddReference yourself. +#define stringTable StringTable::Instance() + +#endif diff --git a/raknet/SystemAddressList.cpp b/raknet/SystemAddressList.cpp new file mode 100644 index 0000000..195afb4 --- /dev/null +++ b/raknet/SystemAddressList.cpp @@ -0,0 +1,131 @@ +#include "SystemAddressList.h" +#include "Rand.h" +#include "RakAssert.h" +#include "BitStream.h" +#include + +SystemAddressList::SystemAddressList() +{ + +} +SystemAddressList::SystemAddressList(PlayerID system) +{ + systemList.Insert(system); +} +void SystemAddressList::AddSystem(PlayerID system) +{ + systemList.Insert(system); +} +void SystemAddressList::RandomizeOrder(void) +{ + unsigned index, size, randIndex; + PlayerID temp; + size = systemList.Size(); + for (index=0; index < size; index++) + { + randIndex=index + (randomMT() % (size-index)); + if (randIndex!=index) + { + temp=systemList[index]; + systemList[index]=systemList[randIndex]; + systemList[randIndex]=temp; + } + } +} +void SystemAddressList::Serialize(RakNet::BitStream *out) +{ + out->Write((unsigned short) systemList.Size()); + unsigned index; + for (index=0; index < systemList.Size(); index++) + out->Write(systemList[index]); +} +bool SystemAddressList::Deserialize(RakNet::BitStream *in) +{ + unsigned short systemListSize; + PlayerID systemAddress; + unsigned index; + if (in->Read(systemListSize)==false) + { + RakAssert(0); + return false; + } + systemList.Clear(); + for (index=0; index < systemListSize; index++) + { + if (in->Read(systemAddress)==false) + { + RakAssert(0); + systemList.Clear(); + return false; + } + systemList.Insert(systemAddress); + + } + return true; +} +void SystemAddressList::RemoveSystem(PlayerID system) +{ + unsigned i; + for (i=0; i < systemList.Size(); i++) + { + if (systemList[i]==system) + { + systemList.Del(i); + return; + } + } +} +DataStructures::List * SystemAddressList::GetList(void) +{ + return &systemList; +} +bool SystemAddressList::Save(const char *filename) +{ + RakNet::BitStream temp; + Serialize(&temp); + FILE *fp = fopen(filename, "wb"); + if (fp) + { + fwrite(temp.GetData(), temp.GetNumberOfBytesUsed(), 1, fp); + fclose(fp); + return true; + } + return false; +} +bool SystemAddressList::Load(const char *filename) +{ + FILE *fp = NULL; + unsigned long fileSize; + + if ( ( fp = fopen( filename, "rb" ) ) == 0 ) + return false; + + fseek( fp, 0, SEEK_END ); + fileSize = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + if (fileSize==0) + { + fclose(fp); + return false; + } + unsigned char *filedata = new unsigned char [fileSize]; + fread(filedata, fileSize, 1, fp); + fclose(fp); + + RakNet::BitStream bs(filedata, fileSize, false); + Deserialize(&bs); + delete [] filedata; + return true; +} +unsigned SystemAddressList::Size(void) const +{ + return systemList.Size(); +} +PlayerID& SystemAddressList::operator[] ( const unsigned int position ) const +{ + return systemList[position]; +} +void SystemAddressList::Clear(void) +{ + systemList.Clear(); +} diff --git a/raknet/SystemDatabaseClient.cpp b/raknet/SystemDatabaseClient.cpp new file mode 100644 index 0000000..d8299c9 --- /dev/null +++ b/raknet/SystemDatabaseClient.cpp @@ -0,0 +1,3 @@ +#include "SystemDatabaseClient.h" + +// TODO diff --git a/raknet/SystemDatabaseClient.h b/raknet/SystemDatabaseClient.h new file mode 100644 index 0000000..8fc8fa6 --- /dev/null +++ b/raknet/SystemDatabaseClient.h @@ -0,0 +1,6 @@ +#ifndef __SYSTEM_DATABASE_CLIENT_H +#define __SYSTEM_DATABASE_CLIENT_H + +// TODO + +#endif diff --git a/raknet/SystemDatabaseServer.cpp b/raknet/SystemDatabaseServer.cpp new file mode 100644 index 0000000..8566525 --- /dev/null +++ b/raknet/SystemDatabaseServer.cpp @@ -0,0 +1,3 @@ +#include "SystemDatabaseServer.h" + +// TODO diff --git a/raknet/SystemDatabaseServer.h b/raknet/SystemDatabaseServer.h new file mode 100644 index 0000000..9c36623 --- /dev/null +++ b/raknet/SystemDatabaseServer.h @@ -0,0 +1,6 @@ +#ifndef __SYSTEM_DATABASE_SERVER_H +#define __SYSTEM_DATABASE_SERVER_H + +// TODO + +#endif diff --git a/raknet/TCPInterface.cpp b/raknet/TCPInterface.cpp index 2576005..c86f3ff 100644 --- a/raknet/TCPInterface.cpp +++ b/raknet/TCPInterface.cpp @@ -1,17 +1,267 @@ -// TODO: Implement TCPInterface.cpp +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected to by a telnet client. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #include "TCPInterface.h" +#ifdef _WIN32 +//#include +#include +#else +#ifdef _COMPATIBILITY_2 +#include "Compatibility2Includes.h" +#include +#endif +#define closesocket close +#include +#include +#endif +#include +#include + +#ifdef _WIN32 +unsigned __stdcall UpdateTCPInterfaceLoop( LPVOID arguments ); +#else +#define closesocket close +void* UpdateTCPInterfaceLoop( void* arguments ); +#endif + +#include "RakSleep.h" + +#ifdef _DO_PRINTF +#endif + + +#ifdef _MSC_VER +#pragma warning( push ) +#endif TCPInterface::TCPInterface() { -} + isStarted=false; + threadRunning=false; + listenSocket=(SOCKET)-1; +#ifdef _WIN32 + + WSADATA winsockInfo; + if ( WSAStartup( MAKEWORD( 2, 2 ), &winsockInfo ) != 0 ) + { +#if defined(_WIN32) && !defined(_COMPATIBILITY_1) && defined(_DEBUG) + DWORD dwIOError = GetLastError(); + LPVOID messageBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwIOError, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // Default language + ( LPTSTR ) & messageBuffer, 0, NULL ); + // something has gone wrong here... + printf( "WSAStartup failed:Error code - %d\n%s", dwIOError, messageBuffer ); + //Free the buffer. + LocalFree( messageBuffer ); +#endif + } + +#endif +} +TCPInterface::~TCPInterface() +{ + Stop(); +} +bool TCPInterface::Start(unsigned short port, unsigned short maxIncomingConnections) +{ + if (isStarted) + return false; + + isStarted=true; + + if (maxIncomingConnections>0) + { + listenSocket = socket(AF_INET, SOCK_STREAM, 0); + if ((int)listenSocket <0) + return false; + + struct sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(port); + + if (bind(listenSocket,(struct sockaddr *) &serverAddress,sizeof(serverAddress)) < 0) + return false; + + listen(listenSocket, maxIncomingConnections); + } + + // Start the update thread +#ifdef _WIN32 + unsigned threadId = 0; + threadHandle = ( HANDLE ) _beginthreadex( NULL, 0, UpdateTCPInterfaceLoop, this, 0, &threadId ); + + //SetThreadPriority(threadHandle, THREAD_PRIORITY_BELOW_NORMAL); + + if ( threadHandle == 0 ) + return false; + CloseHandle( threadHandle ); + threadHandle = 0; +#else + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + +// sched_param sp; +// sp.sched_priority = sched_get_priority_min(SCHED_OTHER); +// pthread_attr_setschedparam(&attr, &sp); + + int error; + error = pthread_create( &threadHandle, &attr, &UpdateTCPInterfaceLoop, this ); + if ( error ) + return false; +#endif + + return true; +} +void TCPInterface::Stop(void) +{ + if (isStarted==false) + return; + + isStarted=false; + + if (listenSocket!=(SOCKET)-1) + { + closesocket(listenSocket); + listenSocket=(SOCKET)-1; + } + + // Wait for the thread to stop + while ( threadRunning ) + RakSleep(15); + + // Stuff from here on to the end of the function is not threadsafe + unsigned i; + for (i=0; i < remoteClients.Size(); i++) + { + closesocket(remoteClients[i]->socket); + delete remoteClients[i]; + } + remoteClients.Clear(); + + outgoingMessages.Clear(); + incomingMessages.Clear(); + newConnections.Clear(); + newRemoteClients.Clear(); + lostConnections.Clear(); + requestedCloseConnections.Clear(); +} +PlayerID TCPInterface::Connect(const char* host, unsigned short remotePort) +{ + sockaddr_in serverAddress; + +#if !defined(_COMPATIBILITY_1) + struct hostent * server; + server = gethostbyname(host); + if (server == NULL) + return UNASSIGNED_PLAYER_ID; +#endif + + SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + return UNASSIGNED_PLAYER_ID; + + memset(&serverAddress, 0, sizeof(serverAddress)); + serverAddress.sin_family = AF_INET; + serverAddress.sin_port = htons( remotePort ); + + +#if !defined(_COMPATIBILITY_1) + memcpy((char *)&serverAddress.sin_addr.s_addr, (char *)server->h_addr, server->h_length); +#else + serverAddress.sin_addr.s_addr = inet_addr( host ); +#endif + + // This is blocking but whatever. If I need it non-blocking I will make it so later. + if ( connect( sockfd, ( struct sockaddr * ) &serverAddress, sizeof( struct sockaddr ) ) != 0 ) + { + closesocket(sockfd); + return UNASSIGNED_PLAYER_ID; + } + + waitForClient=true; + RemoteClient *remoteClient; + remoteClient = new RemoteClient; + remoteClient->socket=sockfd; + remoteClient->playerId.binaryAddress=inet_addr(host); + remoteClient->playerId.port=remotePort; + RemoteClient **temp = newRemoteClients.WriteLock(); + *temp=remoteClient; + newRemoteClients.WriteUnlock(); + while (waitForClient); + { + RakSleep(30); + } + return remoteClient->playerId; +} +void TCPInterface::Send( const char *data, unsigned length, PlayerID playerId ) +{ + if (isStarted==false) + return; + if (remoteClients.Size()==0) + return; + if (data==0) + return; + Packet *p=outgoingMessages.WriteLock(); + p->length=length; + p->data = new unsigned char [p->length]; + memcpy(p->data, data, p->length); + p->playerId=playerId; + outgoingMessages.WriteUnlock(); +} Packet* TCPInterface::Receive( void ) { if (isStarted==false) return 0; return incomingMessages.ReadLock(); } +void TCPInterface::CloseConnection( PlayerID playerId ) +{ + if (isStarted==false) + return; + if (playerId==UNASSIGNED_PLAYER_ID) + return; + PlayerID *id = requestedCloseConnections.WriteLock(); + *id=playerId; + requestedCloseConnections.WriteUnlock(); +} +void TCPInterface::DeallocatePacket( Packet *packet ) +{ + assert(incomingMessages.CheckReadUnlockOrder(packet)); + delete [] packet->data; + incomingMessages.ReadUnlock(); +} +PlayerID TCPInterface::HasNewConnection(void) +{ + PlayerID *out; + out = newConnections.ReadLock(); + if (out) + { + newConnections.ReadUnlock(); + return *out; + } + else + { + return UNASSIGNED_PLAYER_ID; + } +} PlayerID TCPInterface::HasLostConnection(void) { PlayerID *out; @@ -26,3 +276,240 @@ PlayerID TCPInterface::HasLostConnection(void) return UNASSIGNED_PLAYER_ID; } } +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void TCPInterface::DeleteRemoteClient(RemoteClient *remoteClient, fd_set *exceptionFD) +{ +// FD_CLR(remoteClient->socket, exceptionFD); + closesocket(remoteClient->socket); + //shutdown(remoteClient->socket, SD_SEND); + delete remoteClient; +} + +#ifdef _WIN32 +unsigned __stdcall UpdateTCPInterfaceLoop( LPVOID arguments ) +#else +void* UpdateTCPInterfaceLoop( void* arguments ) +#endif +{ + TCPInterface * sts = ( TCPInterface * ) arguments; + RemoteClient *remoteClient; + RemoteClient **otherThreadClient; + const int BUFF_SIZE=8096; + char data[ BUFF_SIZE ]; + Packet *p; + PlayerID *playerId; + fd_set readFD, exceptionFD; + sts->threadRunning=true; + + sockaddr_in sockAddr; + int sockAddrSize = sizeof(sockAddr); + + unsigned i; + int len; + SOCKET newSock; + timeval tv; + int selectResult; + tv.tv_sec=0; + tv.tv_usec=25000; + + while (sts->isStarted) + { + p=sts->outgoingMessages.ReadLock(); + while (p) + { + if (p->playerId==UNASSIGNED_PLAYER_ID) + { + // Send to all + for (i=0; i < sts->remoteClients.Size(); i++) + send(sts->remoteClients[i]->socket, (const char*)p->data, p->length, 0); + } + else + { + // Send to this player + for (i=0; i < sts->remoteClients.Size(); i++) + if (sts->remoteClients[i]->playerId==p->playerId ) + send(sts->remoteClients[i]->socket, (const char*)p->data, p->length, 0); + } + sts->outgoingMessages.ReadUnlock(); + p=sts->outgoingMessages.ReadLock(); + } + + otherThreadClient=sts->newRemoteClients.ReadLock(); + if (otherThreadClient) + { + sts->remoteClients.Insert(*otherThreadClient); + sts->newRemoteClients.ReadUnlock(); + sts->waitForClient=false; + } + + playerId=sts->requestedCloseConnections.ReadLock(); + if (playerId) + { + for (i=0; i < sts->remoteClients.Size(); i++) + { + if (sts->remoteClients[i]->playerId==*playerId ) + { + playerId=sts->lostConnections.WriteLock(); + *playerId=sts->remoteClients[i]->playerId; + sts->lostConnections.WriteUnlock(); + + sts->DeleteRemoteClient(sts->remoteClients[i], &exceptionFD); + sts->remoteClients.RemoveAtIndex(i); + + + /* + playerId=sts->lostConnections.WriteLock(); + *playerId=sts->remoteClients[i]->playerId; + sts->lostConnections.WriteUnlock(); + sts->remoteClients.Del(i); + */ + break; + } + } + + sts->requestedCloseConnections.ReadUnlock(); + } + + // Reset readFD and exceptionFD since select seems to clear it + FD_ZERO(&readFD); + FD_ZERO(&exceptionFD); +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + if (sts->listenSocket!=(SOCKET)-1) + { + FD_SET(sts->listenSocket, &readFD); + FD_SET(sts->listenSocket, &exceptionFD); + } + + for (i=0; i < sts->remoteClients.Size(); i++) + { + FD_SET(sts->remoteClients[i]->socket, &readFD); + FD_SET(sts->remoteClients[i]->socket, &exceptionFD); + } + + selectResult=select(0, &readFD, 0, &exceptionFD, &tv); + + if (selectResult > 0) + { + if (sts->listenSocket!=(SOCKET)-1 && FD_ISSET(sts->listenSocket, &readFD)) + { + newSock = accept(sts->listenSocket, (sockaddr*)&sockAddr, (socklen_t*)&sockAddrSize); + + if (newSock != INVALID_SOCKET) + { + remoteClient = new RemoteClient; + remoteClient->socket=newSock; + remoteClient->playerId.binaryAddress=sockAddr.sin_addr.s_addr; + remoteClient->playerId.port=ntohs( sockAddr.sin_port); + sts->remoteClients.Insert(remoteClient); + playerId=sts->newConnections.WriteLock(); + *playerId=remoteClient->playerId; + sts->newConnections.WriteUnlock(); + + FD_SET(newSock, &readFD); + FD_SET(newSock, &exceptionFD); + } + else + { +#ifdef _DO_PRINTF + printf("Error: connection failed\n"); +#endif + } + } + else if (sts->listenSocket!=(SOCKET)-1 && FD_ISSET(sts->listenSocket, &exceptionFD)) + { +#ifdef _DO_PRINTF + int err; + int errlen = sizeof(err); + getsockopt(sts->listenSocket, SOL_SOCKET, SO_ERROR,(char*)&err, &errlen); + printf("Socket error %s on listening socket\n", err); +#endif + } + else + { + i=0; + while (i < sts->remoteClients.Size()) + { + if (FD_ISSET(sts->remoteClients[i]->socket, &exceptionFD)) + { +#ifdef _DO_PRINTF + if (sts->listenSocket!=(SOCKET)-1) + { + int err; + int errlen = sizeof(err); + getsockopt(sts->listenSocket, SOL_SOCKET, SO_ERROR,(char*)&err, &errlen); + in_addr in; + in.s_addr = sts->remoteClients[i]->playerId.binaryAddress; + printf("Socket error %i on %s:%i\n", err,inet_ntoa( in ), sts->remoteClients[i]->playerId.port ); + } + +#endif + // Connection lost abruptly + playerId=sts->lostConnections.WriteLock(); + *playerId=sts->remoteClients[i]->playerId; + sts->lostConnections.WriteUnlock(); + sts->DeleteRemoteClient(sts->remoteClients[i], &exceptionFD); + sts->remoteClients.RemoveAtIndex(i); + } + else + { + if (FD_ISSET(sts->remoteClients[i]->socket, &readFD)) + { + // if recv returns 0 this was a graceful close + len=recv(sts->remoteClients[i]->socket, data, BUFF_SIZE, 0); + if (len>0) + { + p=sts->incomingMessages.WriteLock(); + p->data = new unsigned char[len+1]; + memcpy(p->data, data, len); + p->data[len]=0; // Null terminate this so we can print it out as regular strings. This is different from RakNet which does not do this. + p->length=len; + p->playerId=sts->remoteClients[i]->playerId; + sts->incomingMessages.WriteUnlock(); + i++; // Nothing deleted so increment the index + } + else + { + // Connection lost gracefully + playerId=sts->lostConnections.WriteLock(); + *playerId=sts->remoteClients[i]->playerId; + sts->lostConnections.WriteUnlock(); + sts->DeleteRemoteClient(sts->remoteClients[i], &exceptionFD); + sts->remoteClients.RemoveAtIndex(i); + } + } + else + i++; // Nothing deleted so increment the index + } + } + } + } + else if (selectResult==0) + { + // No input - sleep for a while + RakSleep(50); + } + else + { + // FD_CLOSE? closesocket(remoteClient->socket); + +#if defined(_DO_PRINTF) && defined(_WIN32) + DWORD dwIOError = WSAGetLastError(); + printf("Socket error %i\n", dwIOError); +#endif + } + + + + } + sts->threadRunning=false; + + return 0; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/TCPInterface.h b/raknet/TCPInterface.h index 000bb86..84977da 100644 --- a/raknet/TCPInterface.h +++ b/raknet/TCPInterface.h @@ -1,31 +1,112 @@ -// TODO: Implement TCPInterface.h +/// \file +/// \brief A simple TCP based server allowing sends and receives. Can be connected by any TCP client, including telnet. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. #ifndef __SIMPLE_TCP_SERVER #define __SIMPLE_TCP_SERVER +#ifdef _COMPATIBILITY_1 +#include "Compatibility1Includes.h" +#elif defined(_WIN32) +#include +#include +#else +#include +#include +#include // fd_set +#include +#include +#include +#include +#include +/// Unix/Linux uses ints for sockets +typedef int SOCKET; +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#endif + +#include "DS_List.h" #include "NetworkTypes.h" #include "SingleProducerConsumer.h" #include "Export.h" +struct RemoteClient; + /// \internal /// \brief As the name says, a simple multithreaded TCP server. Used by TelnetTransport class RAK_DLL_EXPORT TCPInterface { public: TCPInterface(); + ~TCPInterface(); + + /// Starts the TCP server on the indicated port + bool Start(unsigned short port, unsigned short maxIncomingConnections); + + /// Stops the TCP server + void Stop(void); + + /// Connect to the specified host on the specified port + PlayerID Connect(const char* host, unsigned short remotePort); + + /// Sends a byte stream + void Send( const char *data, unsigned length, PlayerID playerId ); /// Returns data received Packet* Receive( void ); + /// Disconnects a player/address + void CloseConnection( PlayerID playerId ); + + /// Deallocates a packet returned by Receive + void DeallocatePacket( Packet *packet ); + + /// Queued events of new connections + PlayerID HasNewConnection(void); + /// Queued events of lost connections PlayerID HasLostConnection(void); - protected: - bool isStarted; - DataStructures::SingleProducerConsumer incomingMessages; + bool waitForClient; + bool isStarted, threadRunning; + SOCKET listenSocket; - DataStructures::SingleProducerConsumer lostConnections; + // Assuming remoteClients is only used by one thread! + DataStructures::List remoteClients; + DataStructures::SingleProducerConsumer outgoingMessages, incomingMessages; + DataStructures::SingleProducerConsumer newConnections, lostConnections, requestedCloseConnections; + DataStructures::SingleProducerConsumer newRemoteClients; + +#ifdef _WIN32 + HANDLE threadHandle; + friend unsigned __stdcall UpdateTCPInterfaceLoop( LPVOID arguments ); +#else + pthread_t threadHandle; + friend void* UpdateTCPInterfaceLoop( void* arguments ); +#endif + + void DeleteRemoteClient(RemoteClient *remoteClient, fd_set *exceptionFD); +}; + +/// Stores information about a remote client. In this case, only the socket used by that client. +struct RemoteClient +{ + SOCKET socket; + PlayerID playerId; }; #endif diff --git a/raknet/TEABlockEncryptor.cpp b/raknet/TEABlockEncryptor.cpp deleted file mode 100644 index bafe558..0000000 --- a/raknet/TEABlockEncryptor.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// TODO: Implement TEABlockEncryptor.cpp - -#include "TEABlockEncryptor.h" - -#define TEA_ROUNDS 32 -#define TEA_XOR_MASK 0x5E94A3CF - -unsigned int TEABlockEncryptor::initObsDelta = (0x9E3779B9 ^ TEA_XOR_MASK); - -TEABlockEncryptor::TEABlockEncryptor() -{ - initDelta = initObsDelta ^ TEA_XOR_MASK; - initSum = initDelta * TEA_ROUNDS; - - keySet = false; -} diff --git a/raknet/TEABlockEncryptor.h b/raknet/TEABlockEncryptor.h deleted file mode 100644 index 75dc341..0000000 --- a/raknet/TEABlockEncryptor.h +++ /dev/null @@ -1,25 +0,0 @@ -// TODO: Implement TEABlockEncryptor.h - -#ifndef __TEA_BLOCK_ENCRYPTOR_H -#define __TEA_BLOCK_ENCRYPTOR_H - -#include "DataBlockEncryptor.h" - -class TEABlockEncryptor : - public DataBlockEncryptor -{ - -public: - - TEABlockEncryptor(); - -protected: - - unsigned int initSum; - unsigned int initDelta; - - static unsigned int initObsDelta; - -}; - -#endif diff --git a/raknet/TableSerializer.cpp b/raknet/TableSerializer.cpp new file mode 100644 index 0000000..26a9f77 --- /dev/null +++ b/raknet/TableSerializer.cpp @@ -0,0 +1,146 @@ +#include "TableSerializer.h" +#include "DS_Table.h" +#include "BitStream.h" +#include "StringCompressor.h" + +void TableSerializer::SerializeTable(DataStructures::Table *in, RakNet::BitStream *out) +{ + DataStructures::List &columns=in->GetColumns(); + DataStructures::Page *cur = in->GetRows().GetListHead(); + out->Write((unsigned)columns.Size()); + unsigned i; + for (i=0; iEncodeString(columns[i].columnName, _TABLE_MAX_COLUMN_NAME_LENGTH, out); + out->Write((unsigned char)columns[i].columnType); + } + out->Write((unsigned)in->GetRows().Size()); + unsigned rowIndex; + while (cur) + { + for (rowIndex=0; rowIndex < (unsigned)cur->size; rowIndex++) + { + SerializeRow(cur->data[rowIndex], cur->keys[rowIndex], columns, out); + } + cur=cur->next; + } +} +bool TableSerializer::DeserializeTable(unsigned char *serializedTable, unsigned int tableLength, DataStructures::Table *out) +{ + RakNet::BitStream in((unsigned char*) serializedTable, tableLength, false); + + unsigned columnSize; + unsigned char columnType; + unsigned rowSize; + char columnName[_TABLE_MAX_COLUMN_NAME_LENGTH]; + if (in.Read(columnSize)==false || columnSize > 10000) + return false; // Hacker crash prevention + + out->Clear(); + unsigned i; + for (i=0; iDecodeString(columnName, 32, &in); + in.Read(columnType); + out->AddColumn(columnName, (DataStructures::Table::ColumnType)columnType); + } + + if (in.Read(rowSize)==false || rowSize>100000) + return false; // Hacker crash prevention + + unsigned rowIndex; + for (rowIndex=0; rowIndex < rowSize; rowIndex++) + { + if (DeserializeRow(&in, out)==false) + return false; + } + return true; +} +void TableSerializer::SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, DataStructures::List &columns, RakNet::BitStream *out) +{ + unsigned cellIndex; + out->Write(keyIn); + for (cellIndex=0; cellIndexcells[cellIndex], columns[cellIndex].columnType); + } +} +bool TableSerializer::DeserializeRow(RakNet::BitStream *in, DataStructures::Table *out) +{ + DataStructures::List &columns=out->GetColumns(); + unsigned cellIndex; + DataStructures::Table::Row *row; + unsigned key; + if (in->Read(key)==false) + return false; + row=out->AddRow(key); + for (cellIndex=0; cellIndexcells[cellIndex], columns[cellIndex].columnType)==false) + { + out->RemoveRow(key); + return false; + } + } + return true; +} +void TableSerializer::SerializeCell(RakNet::BitStream *out, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType) +{ + out->Write(cell->isEmpty); + if (cell->isEmpty==false) + { + if (columnType==DataStructures::Table::NUMERIC) + { + out->Write(cell->i); + } + else if (columnType==DataStructures::Table::STRING) + { + stringCompressor->EncodeString(cell->c, 65535, out); + } + else + { + // Binary + assert(columnType==DataStructures::Table::BINARY); + out->Write((unsigned)cell->i); + out->WriteAlignedBytes((const unsigned char *) cell->c, cell->i); + } + } +} +bool TableSerializer::DeserializeCell(RakNet::BitStream *in, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType) +{ + bool isEmpty; + int value; + char tempString[65535]; + cell->Clear(); + + if (in->Read(isEmpty)==false) + return false; + if (isEmpty==false) + { + if (columnType==DataStructures::Table::NUMERIC) + { + if (in->Read(value)==false) + return false; + cell->Set(value); + } + else if (columnType==DataStructures::Table::STRING) + { + if (stringCompressor->DecodeString(tempString, 65535, in)==false) + return false; + cell->Set(tempString); + } + else + { + // Binary + assert(columnType==DataStructures::Table::BINARY); + if (in->Read(value)==false || value > 10000000) + return false; // Sanity check to max binary cell of 10 megabytes + in->AlignReadToByteBoundary(); + if (BITS_TO_BYTES(in->GetNumberOfUnreadBits())Set((char*) in->GetData()+BITS_TO_BYTES(in->GetReadOffset()), value); + in->IgnoreBits(BYTES_TO_BITS(value)); + } + } + return true; +} diff --git a/raknet/TableSerializer.h b/raknet/TableSerializer.h new file mode 100644 index 0000000..627e50c --- /dev/null +++ b/raknet/TableSerializer.h @@ -0,0 +1,189 @@ +#ifndef __TABLE_SERIALIZER_H +#define __TABLE_SERIALIZER_H + +#include "DS_Table.h" + +namespace RakNet +{ + class BitStream; +} + +class TableSerializer +{ +public: + static void SerializeTable(DataStructures::Table *in, RakNet::BitStream *out); + static bool DeserializeTable(unsigned char *serializedTable, unsigned int tableLength, DataStructures::Table *out); + static void SerializeRow(DataStructures::Table::Row *in, unsigned keyIn, DataStructures::List &columns, RakNet::BitStream *out); + static bool DeserializeRow(RakNet::BitStream *in, DataStructures::Table *out); + static void SerializeCell(RakNet::BitStream *out, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType); + static bool DeserializeCell(RakNet::BitStream *in, DataStructures::Table::Cell *cell, DataStructures::Table::ColumnType columnType); +}; + +#endif + +// Test code for the table +/* +#include "LightweightDatabaseServer.h" +#include "LightweightDatabaseClient.h" +#include "TableSerializer.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "DS_Table.h" +void main(void) +{ + DataStructures::Table table; + DataStructures::Table::Row *row; + unsigned int dummydata=12345; + + // Add columns Name (string), IP (binary), score (int), and players (int). + table.AddColumn("Name", DataStructures::Table::STRING); + table.AddColumn("IP", DataStructures::Table::BINARY); + table.AddColumn("Score", DataStructures::Table::NUMERIC); + table.AddColumn("Players", DataStructures::Table::NUMERIC); + table.AddColumn("Empty Test Column", DataStructures::Table::STRING); + assert(table.GetColumnCount()==5); + row=table.AddRow(0); + assert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,10); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(1); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,15); + + row=table.AddRow(2); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,5); + row->UpdateCell(3,20); + + row=table.AddRow(3); + assert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,15); + row->UpdateCell(3,5); + row->UpdateCell(4,"col index 4"); + + row=table.AddRow(4); + assert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + //row->UpdateCell(2,25); + row->UpdateCell(3,30); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(5); + assert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + //row->UpdateCell(2,25); + row->UpdateCell(3,5); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(6); + assert(row); + row->UpdateCell(0,"Kevin Jenkins"); + row->UpdateCell(1,sizeof(dummydata), (char*)&dummydata); + row->UpdateCell(2,35); + //row->UpdateCell(3,40); + //row->UpdateCell(4,"should be unique"); + + row=table.AddRow(7); + assert(row); + row->UpdateCell(0,"Bob Jenkins"); + + row=table.AddRow(8); + assert(row); + row->UpdateCell(0,"Zack Jenkins"); + + // Test multi-column sorting + DataStructures::Table::Row *rows[30]; + DataStructures::Table::SortQuery queries[4]; + queries[0].columnIndex=0; + queries[0].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[1].columnIndex=1; + queries[1].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[2].columnIndex=2; + queries[2].operation=DataStructures::Table::QS_INCREASING_ORDER; + queries[3].columnIndex=3; + queries[3].operation=DataStructures::Table::QS_DECREASING_ORDER; + table.SortTable(queries, 4, rows); + unsigned i; + char out[256]; + printf("Sort: Ascending except for column index 3\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, rows[i]); + printf("%s\n", out); + } + + // Test query: + // Don't return column 3, and swap columns 0 and 2 + unsigned columnsToReturn[4]; + columnsToReturn[0]=2; + columnsToReturn[1]=1; + columnsToReturn[2]=0; + columnsToReturn[3]=4; + DataStructures::Table resultsTable; + table.QueryTable(columnsToReturn,4,0,0,&resultsTable); + printf("Query: Don't return column 3, and swap columns 0 and 2:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + printf("%s\n", out); + } + + // Test filter: + // Only return rows with column index 4 empty + DataStructures::Table::FilterQuery inclusionFilters[3]; + inclusionFilters[0].columnIndex=4; + inclusionFilters[0].operation=DataStructures::Table::QF_IS_EMPTY; + // inclusionFilters[0].cellValue; // Unused for IS_EMPTY + table.QueryTable(0,0,inclusionFilters,1,&resultsTable); + printf("Filter: Only return rows with column index 4 empty:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + printf("%s\n", out); + } + + // Column 5 empty and column 0 == Kevin Jenkins + inclusionFilters[0].columnIndex=4; + inclusionFilters[0].operation=DataStructures::Table::QF_IS_EMPTY; + inclusionFilters[1].columnIndex=0; + inclusionFilters[1].operation=DataStructures::Table::QF_EQUAL; + inclusionFilters[1].cellValue.Set("Kevin Jenkins"); + table.QueryTable(0,0,inclusionFilters,2,&resultsTable); + printf("Filter: Column 5 empty and column 0 == Kevin Jenkins:\n"); + for (i=0; i < resultsTable.GetRowCount(); i++) + { + resultsTable.PrintRow(out,256,',',true, resultsTable.GetRowByIndex(i)); + printf("%s\n", out); + } + + RakNet::BitStream bs; + printf("PreSerialize:\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, table.GetRowByIndex(i)); + printf("%s\n", out); + } + StringCompressor::AddReference(); + TableSerializer::Serialize(&table, &bs); + TableSerializer::Deserialize(&bs, &table); + StringCompressor::RemoveReference(); + printf("PostDeserialize:\n"); + for (i=0; i < table.GetRowCount(); i++) + { + table.PrintRow(out,256,',',true, table.GetRowByIndex(i)); + printf("%s\n", out); + } + int a=5; +} +*/ diff --git a/raknet/TelnetTransport.cpp b/raknet/TelnetTransport.cpp new file mode 100644 index 0000000..cbd105d --- /dev/null +++ b/raknet/TelnetTransport.cpp @@ -0,0 +1,283 @@ +#include "TelnetTransport.h" +#include "TCPInterface.h" +#include +#include +#include + +// #define _PRINTF_DEBUG + +#define ECHO_INPUT + +#if (defined(__GNUC__) || defined(__GCCXML__)) +#define _vsnprintf vsnprintf +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +TelnetTransport::TelnetTransport() +{ + tcpInterface=0; +} +TelnetTransport::~TelnetTransport() +{ + Stop(); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +bool TelnetTransport::Start(unsigned short port, bool serverMode) +{ + AutoAllocate(); + assert(serverMode); + return tcpInterface->Start(port, 64); +} +void TelnetTransport::Stop(void) +{ + if (tcpInterface==0) return; + tcpInterface->Stop(); + unsigned i; + for (i=0; i < remoteClients.Size(); i++) + delete remoteClients[i]; + remoteClients.Clear(); +} +void TelnetTransport::Send( PlayerID playerId, const char *data,... ) +{ + if (tcpInterface==0) return; + + char text[REMOTE_MAX_TEXT_INPUT]; + va_list ap; + va_start(ap, data); + _vsnprintf(text, REMOTE_MAX_TEXT_INPUT, data, ap); + va_end(ap); + text[REMOTE_MAX_TEXT_INPUT-1]=0; + + tcpInterface->Send(text, (unsigned int) strlen(text), playerId); +} +void TelnetTransport::CloseConnection( PlayerID playerId ) +{ + tcpInterface->CloseConnection(playerId); +} +Packet* TelnetTransport::Receive( void ) +{ + if (tcpInterface==0) return 0; + Packet *p = tcpInterface->Receive(); + if (p==0) + return 0; + + /* + if (p->data[0]==255) + { + unsigned i; + for (i=0; i < p->length; i++) + { + printf("%i ", p->data[i]); + } + printf("\n"); + tcpInterface->DeallocatePacket(p); + return 0; + } + */ + + // 127 is delete - ignore that + // 9 is tab + // 27 is escape + if (p->data[0]>=127 || p->data[0]==9 || p->data[0]==27) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + // Hack - I don't know what the hell this is about but cursor keys send 3 characters at a time. I can block these + //Up=27,91,65 + //Down=27,91,66 + //Right=27,91,67 + //Left=27,91,68 + if (p->length==3 && p->data[0]==27 && p->data[1]==91 && p->data[2]>=65 && p->data[2]<=68) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + // Get this guy's cursor buffer. This is real bullcrap that I have to do this. + unsigned i; + TelnetClient *remoteClient=0; + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->playerId==p->playerId) + remoteClient=remoteClients[i]; + } + assert(remoteClient); + if (remoteClient==0) + { + tcpInterface->DeallocatePacket(p); + return 0; + } + + + // Echo +#ifdef ECHO_INPUT + tcpInterface->Send((const char *)p->data, p->length, p->playerId); +#endif + + bool gotLine; + // Process each character in turn + for (i=0; i < p->length; i++) + { + +#ifdef ECHO_INPUT + if (p->data[i]==8) + { + char spaceThenBack[2]; + spaceThenBack[0]=' '; + spaceThenBack[1]=8; + tcpInterface->Send((const char *)spaceThenBack, 2, p->playerId); + } +#endif + + gotLine=ReassembleLine(remoteClient, p->data[i]); + if (gotLine && remoteClient->textInput[0]) + { + Packet *reassembledLine = new Packet; + reassembledLine->length=(unsigned int) strlen(remoteClient->textInput); + assert(reassembledLine->length < REMOTE_MAX_TEXT_INPUT); + reassembledLine->data= new unsigned char [reassembledLine->length+1]; + memcpy(reassembledLine->data, remoteClient->textInput, reassembledLine->length); +#ifdef _PRINTF_DEBUG + memset(remoteClient->textInput, 0, REMOTE_MAX_TEXT_INPUT); +#endif + reassembledLine->data[reassembledLine->length]=0; + reassembledLine->playerId=p->playerId; + tcpInterface->DeallocatePacket(p); + return reassembledLine; + } + } + + tcpInterface->DeallocatePacket(p); + return 0; +} +void TelnetTransport::DeallocatePacket( Packet *packet ) +{ + if (tcpInterface==0) return; + delete [] packet->data; + delete packet; +} +PlayerID TelnetTransport::HasNewConnection(void) +{ + unsigned i; + PlayerID newConnection; + newConnection = tcpInterface->HasNewConnection(); + // 03/16/06 Can't force the stupid windows telnet to use line mode or local echo so now I have to track all the remote players and their + // input buffer + if (newConnection != UNASSIGNED_PLAYER_ID) + { + unsigned char command[10]; + // http://www.pcmicro.com/netfoss/RFC857.html + // IAC WON'T ECHO + command[0]=255; // IAC + //command[1]=253; // WON'T + command[1]=251; // WILL + command[2]=1; // ECHO + tcpInterface->Send((const char*)command, 3, newConnection); + + /* + // Tell the other side to use line mode + // http://www.faqs.org/rfcs/rfc1184.html + // IAC DO LINEMODE + // command[0]=255; // IAC + // command[1]=252; // DO + // command[2]=34; // LINEMODE + // tcpInterface->Send((const char*)command, 3, newConnection); + + */ + + TelnetClient *remoteClient=0; + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->playerId==newConnection) + { + remoteClient=remoteClients[i]; + remoteClient->cursorPosition=0; + } + } + + if (remoteClient==0) + { + remoteClient=new TelnetClient; + remoteClient->cursorPosition=0; + remoteClient->playerId=newConnection; +#ifdef _PRINTF_DEBUG + memset(remoteClient->textInput, 0, REMOTE_MAX_TEXT_INPUT); +#endif + } + + remoteClients.Insert(remoteClient); + } + return newConnection; +} +PlayerID TelnetTransport::HasLostConnection(void) +{ + PlayerID playerId; + unsigned i; + playerId=tcpInterface->HasLostConnection(); + if (playerId!=UNASSIGNED_PLAYER_ID) + { + for (i=0; i < remoteClients.Size(); i++) + { + if (remoteClients[i]->playerId==playerId) + { + delete remoteClients[i]; + remoteClients[i]=remoteClients[remoteClients.Size()-1]; + remoteClients.Del(); + } + } + } + return playerId; +} +CommandParserInterface* TelnetTransport::GetCommandParser(void) +{ + return 0; +} +void TelnetTransport::AutoAllocate(void) +{ + if (tcpInterface==0) + tcpInterface=new TCPInterface; +} +bool TelnetTransport::ReassembleLine(TelnetTransport::TelnetClient* remoteClient, unsigned char c) +{ + if (c=='\n') + { + remoteClient->textInput[remoteClient->cursorPosition]=0; + remoteClient->cursorPosition=0; +#ifdef _PRINTF_DEBUG + printf("[Done] %s\n", remoteClient->textInput); +#endif + return true; + } + else if (c==8) // backspace + { + if (remoteClient->cursorPosition>0) + { + remoteClient->textInput[--remoteClient->cursorPosition]=0; +#ifdef _PRINTF_DEBUG + printf("[Back] %s\n", remoteClient->textInput); +#endif + } + } + else if (c>=32 && c <127) + { + if (remoteClient->cursorPosition < REMOTE_MAX_TEXT_INPUT) + { + remoteClient->textInput[remoteClient->cursorPosition++]=c; +#ifdef _PRINTF_DEBUG + printf("[Norm] %s\n", remoteClient->textInput); +#endif + } + } + return false; +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/TelnetTransport.h b/raknet/TelnetTransport.h new file mode 100644 index 0000000..348dcdb --- /dev/null +++ b/raknet/TelnetTransport.h @@ -0,0 +1,62 @@ +/// \file +/// \brief Contains TelnetTransport , used to supports the telnet transport protocol. Insecure +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __TELNET_TRANSPORT +#define __TELNET_TRANSPORT + +#include "TransportInterface.h" +#include "DS_List.h" +#include "Export.h" +class TCPInterface; +struct TelnetClient; + +/// \brief Use TelnetTransport to easily allow windows telnet to connect to your ConsoleServer +/// To run Windows telnet, go to your start menu, click run, and in the edit box type "telnet " where is the ip address +/// of your ConsoleServer (most likely the same IP as your game). +/// This implementation always echos commands. +class RAK_DLL_EXPORT TelnetTransport : public TransportInterface +{ +public: + TelnetTransport(); + ~TelnetTransport(); + bool Start(unsigned short port, bool serverMode); + void Stop(void); + void Send( PlayerID playerId, const char *data, ... ); + void CloseConnection( PlayerID playerId ); + Packet* Receive( void ); + void DeallocatePacket( Packet *packet ); + PlayerID HasNewConnection(void); + PlayerID HasLostConnection(void); + CommandParserInterface* GetCommandParser(void); +protected: + + struct TelnetClient + { + PlayerID playerId; + char textInput[REMOTE_MAX_TEXT_INPUT]; + unsigned cursorPosition; + }; + + TCPInterface *tcpInterface; + void AutoAllocate(void); + bool ReassembleLine(TelnetTransport::TelnetClient* telnetClient, unsigned char c); + + // Crap this sucks but because windows telnet won't send line at a time, I have to reconstruct the lines at the server per player + DataStructures::List remoteClients; +}; + +#endif diff --git a/raknet/ThreadPool.h b/raknet/ThreadPool.h new file mode 100644 index 0000000..43674a5 --- /dev/null +++ b/raknet/ThreadPool.h @@ -0,0 +1,432 @@ +#ifndef __THREAD_POOL_H +#define __THREAD_POOL_H + +#include "DS_Queue.h" +#include "SimpleMutex.h" + +/// A simple class to create work threads that processes a queue of functions with data. +/// This class does not allocate or deallocate memory. It is up to the user to handle memory management. +/// InputType and OutputType are stored directly in a queue. For large structures, if you plan to delete from the middle of the queue, +/// you might wish to store pointers rather than the structures themselves so the array can shift efficiently. +template +struct ThreadPool +{ + ThreadPool(); + ~ThreadPool(); + + /// Start the specified number of threads. + /// \param[in] numThreads The number of threads to start + /// \param[in] stackSize 0 for default (except on consoles). + /// \return True on success, false on failure. + bool StartThreads(int numThreads, int stackSize); + + /// Stops all threads + void StopThreads(void); + + /// Adds a function to a queue with data to pass to that function. This function will be called from the thread + /// Memory management is your responsibility! This class does not allocate or deallocate memory. + /// The best way to deallocate \a inputData is in userCallback. If you call EndThreads such that callbacks were not called, you + /// can iterate through the inputDataQueue and deallocate all pending input data there + /// The best way to deallocate output is as it is returned to you from GetOutput. Similarly, if you end the threads such that + /// not all output was returned, you can iterate through outputQueue and deallocate it there. + /// \param[in] workerThreadCallback The function to call from the thread + /// \param[in] inputData The parameter to pass to \a userCallback + void AddInput(OutputType (*workerThreadCallback)(InputType, bool *returnOutput), InputType inputData); + + /// Returns true if output from GetOutput is waiting. + /// \return true if output is waiting, false otherwise + bool HasOutput(void); + + /// Inaccurate but fast version of HasOutput. If this returns true, you should still check HasOutput for the real value. + /// \return true if output is probably waiting, false otherwise + bool HasOutputFast(void); + + /// Gets the output of a call to \a userCallback + /// HasOutput must return true before you call this function. Otherwise it will assert. + /// \return The output of \a userCallback. If you have different output signatures, it is up to you to encode the data to indicate this + OutputType GetOutput(void); + + /// Clears internal buffers + void Clear(void); + + /// Lock the input buffer before calling the functions InputSize, InputAtIndex, and RemoveInputAtIndex + /// It is only necessary to lock the input or output while the threads are running + void LockInput(void); + + /// Unlock the input buffer after you are done with the functions InputSize, GetInputAtIndex, and RemoveInputAtIndex + void UnlockInput(void); + + /// Length of the input queue + unsigned InputSize(void); + + /// Get the input at a specified index + InputType GetInputAtIndex(unsigned index); + + /// Remove input from a specific index. This does NOT do memory deallocation - it only removes the item from the queue + void RemoveInputAtIndex(unsigned index); + + /// Lock the output buffer before calling the functions OutputSize, OutputAtIndex, and RemoveOutputAtIndex + /// It is only necessary to lock the input or output while the threads are running + void LockOutput(void); + + /// Unlock the output buffer after you are done with the functions OutputSize, GetOutputAtIndex, and RemoveOutputAtIndex + void UnlockOutput(void); + + /// Length of the output queue + unsigned OutputSize(void); + + /// Get the output at a specified index + OutputType GetOutputAtIndex(unsigned index); + + /// Remove output from a specific index. This does NOT do memory deallocation - it only removes the item from the queue + void RemoveOutputAtIndex(unsigned index); + + /// Removes all items from the input queue + void ClearInput(void); + + /// Removes all items from the output queue + void ClearOutput(void); + +protected: + // It is valid to cancel input before it is processed. To do so, lock the inputQueue with inputQueueMutex, + // Scan the list, and remove the item you don't want. + SimpleMutex inputQueueMutex, outputQueueMutex; + + // inputFunctionQueue & inputDataQueue are paired arrays so if you delete from one at a particular index you must delete from the other + // at the same index + DataStructures::Queue inputFunctionQueue; + DataStructures::Queue inputDataQueue; + + DataStructures::Queue outputQueue; + + template +#ifdef _WIN32 + friend unsigned __stdcall WorkerThread( LPVOID arguments ); +#else + friend void* WorkerThread( void* arguments ); +#endif + + /// \internal + bool threadsRunning; + /// \internal + int numThreadsRunning; + /// \internal + SimpleMutex numThreadsRunningMutex; +#ifdef _WIN32 + /// \internal + HANDLE quitAndIncomingDataEvents[2]; +#endif +}; + +#include "ThreadPool.h" +#include "RakSleep.h" +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +template +#ifdef _WIN32 +unsigned __stdcall WorkerThread( LPVOID arguments ) +#else +void* WorkerThread( void* arguments ) +#endif +{ + bool returnOutput; + ThreadPool *threadPool = (ThreadPool*) arguments; + OutputType (*userCallback)(InputType, bool *returnOutput); + InputType inputData; + OutputType callbackOutput; + + userCallback=0; + + // Increase numThreadsRunning + threadPool->numThreadsRunningMutex.Lock(); + ++threadPool->numThreadsRunning; + threadPool->numThreadsRunningMutex.Unlock(); + + while (1) + { +#ifdef _WIN32 + if (userCallback==0) + { + // Wait for signaled event + WaitForMultipleObjects( + 2, + threadPool->quitAndIncomingDataEvents, + false, + INFINITE); + } +#endif + + if (threadPool->threadsRunning==false) + break; + + // Read input data + userCallback=0; + threadPool->inputQueueMutex.Lock(); + if (threadPool->inputFunctionQueue.Size()) + { + userCallback=threadPool->inputFunctionQueue.Pop(); + inputData=threadPool->inputDataQueue.Pop(); + } + threadPool->inputQueueMutex.Unlock(); + + if (userCallback) + { + callbackOutput=userCallback(inputData, &returnOutput); + if (returnOutput) + { + threadPool->outputQueueMutex.Lock(); + threadPool->outputQueue.Push(callbackOutput); + threadPool->outputQueueMutex.Unlock(); + } + } + +#ifndef _WIN32 + // If no input data available, and GCC, then sleep. + if (userCallback==0) + RakSleep(1000); +#endif + } + + // Decrease numThreadsRunning + threadPool->numThreadsRunningMutex.Lock(); + --threadPool->numThreadsRunning; + threadPool->numThreadsRunningMutex.Unlock(); + + return 0; +} +template +ThreadPool::ThreadPool() +{ + threadsRunning=false; + numThreadsRunning=0; +} +template +ThreadPool::~ThreadPool() +{ + StopThreads(); + Clear(); +} + +template +bool ThreadPool::StartThreads(int numThreads, int stackSize) +{ + if (threadsRunning==true) + return false; + +#ifdef _WIN32 + quitAndIncomingDataEvents[0]=CreateEvent(0, true, false, 0); + quitAndIncomingDataEvents[1]=CreateEvent(0, false, false, 0); +#endif + + unsigned threadId = 0; + int i; + for (i=0; i < numThreads; i++) + { +#ifdef _WIN32 + HANDLE threadHandle; +#ifdef _COMPATIBILITY_1 + threadHandle = ( HANDLE ) _beginthreadex( NULL, 0, WorkerThread, this, 0, &threadId ); +#else + threadHandle = ( HANDLE ) _beginthreadex( NULL, stackSize, WorkerThread, this, 0, &threadId ); +#endif + + if (threadHandle==0) + { + StopThreads(); + return false; + } + CloseHandle(threadHandle); +#else + pthread_t threadHandle; + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED ); + + // sched_param sp; + // sp.sched_priority = sched_get_priority_max(SCHED_OTHER); + // pthread_attr_setschedparam(&attr, &sp); + + int error; + error = pthread_create( &threadHandle, &attr, &WorkerThread, this ); + + if ( error ) + { + StopThreads(); + return false; + } +#endif + } + + threadsRunning=true; + + // Wait for number of threads running to increase to numThreads + bool done=false; + while (done==false) + { + RakSleep(50); + numThreadsRunningMutex.Lock(); + if (numThreadsRunning==numThreads) + done=true; + numThreadsRunningMutex.Unlock(); + } + + return true; +} +template +void ThreadPool::StopThreads(void) +{ + if (threadsRunning==false) + return; + + threadsRunning=false; + +#ifdef _WIN32 + // Quit event + SetEvent(quitAndIncomingDataEvents[0]); +#endif + + // Wait for number of threads running to decrease to 0 + bool done=false; + while (done==false) + { + RakSleep(50); + numThreadsRunningMutex.Lock(); + if (numThreadsRunning==0) + done=true; + numThreadsRunningMutex.Unlock(); + } + +#ifdef _WIN32 + CloseHandle(quitAndIncomingDataEvents[0]); + CloseHandle(quitAndIncomingDataEvents[1]); +#endif +} +template +void ThreadPool::AddInput(OutputType (*workerThreadCallback)(InputType, bool *returnOutput), InputType inputData) +{ + inputQueueMutex.Lock(); + inputDataQueue.Push(inputData); + inputFunctionQueue.Push(workerThreadCallback); + inputQueueMutex.Unlock(); + +#ifdef _WIN32 + // Input data event + SetEvent(quitAndIncomingDataEvents[1]); +#endif +} +template +bool ThreadPool::HasOutputFast(void) +{ + return outputQueue.IsEmpty()==false; +} +template +bool ThreadPool::HasOutput(void) +{ + bool res; + outputQueueMutex.Lock(); + res=outputQueue.IsEmpty()==false; + outputQueueMutex.Unlock(); + return res; +} +template +OutputType ThreadPool::GetOutput(void) +{ + // Real output check + OutputType output; + outputQueueMutex.Lock(); + output=outputQueue.Pop(); + outputQueueMutex.Unlock(); + return output; +} +template +void ThreadPool::Clear(void) +{ + if (threadsRunning) + { + inputQueueMutex.Lock(); + inputFunctionQueue.Clear(); + inputDataQueue.Clear(); + inputQueueMutex.Unlock(); + + outputQueueMutex.Lock(); + outputQueue.Clear(); + outputQueueMutex.Unlock(); + } + else + { + inputFunctionQueue.Clear(); + inputDataQueue.Clear(); + outputQueue.Clear(); + } +} +template +void ThreadPool::LockInput(void) +{ + inputQueueMutex.Lock(); +} +template +void ThreadPool::UnlockInput(void) +{ + inputQueueMutex.Unlock(); +} +template +unsigned ThreadPool::InputSize(void) +{ + return inputDataQueue.Size(); +} +template +InputType ThreadPool::GetInputAtIndex(unsigned index) +{ + return inputDataQueue[index]; +} +template +void ThreadPool::RemoveInputAtIndex(unsigned index) +{ + inputDataQueue.Del(index); + inputFunctionQueue.Del(index); +} +template +void ThreadPool::LockOutput(void) +{ + outputQueueMutex.Lock(); +} +template +void ThreadPool::UnlockOutput(void) +{ + outputQueueMutex.Unlock(); +} +template +unsigned ThreadPool::OutputSize(void) +{ + return outputQueue.Size(); +} +template +OutputType ThreadPool::GetOutputAtIndex(unsigned index) +{ + return outputQueue[index]; +} +template +void ThreadPool::RemoveOutputAtIndex(unsigned index) +{ + outputQueue.Del(index); +} +template +void ThreadPool::ClearInput(void) +{ + inputDataQueue.Clear(); + inputFunctionQueue.Clear(); +} + +template +void ThreadPool::ClearOutput(void) +{ + outputQueue.Clear(); +} + + +#endif diff --git a/raknet/TransportInterface.h b/raknet/TransportInterface.h new file mode 100644 index 0000000..dc9c031 --- /dev/null +++ b/raknet/TransportInterface.h @@ -0,0 +1,82 @@ +/// \file +/// \brief Contains TransportInterface from which you can derive custom transport providers for ConsoleServer. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __TRANSPORT_INTERFACE_H +#define __TRANSPORT_INTERFACE_H + +#include "NetworkTypes.h" +#include "Export.h" + +#define REMOTE_MAX_TEXT_INPUT 512 + +class CommandParserInterface; + +/// \brief Defines an interface that is used to send and receive null-terminated strings. +/// In practice this is only used by the CommandParser system for for servers. +class RAK_DLL_EXPORT TransportInterface +{ +public: + /// Start the transport provider on the indicated port. + /// \param[in] port The port to start the transport provider on + /// \param[in] serverMode If true, you should allow incoming connections (I don't actually use this anywhere) + /// \return Return true on success, false on failure. + virtual bool Start(unsigned short port, bool serverMode)=0; + + /// Stop the transport provider. You can clear memory and shutdown threads here. + virtual void Stop(void)=0; + + /// Send a null-terminated string to \a playerId + /// If your transport method requires particular formatting of the outgoing data (e.g. you don't just send strings) you can do it here + /// and parse it out in Receive(). + /// \param[in] playerId The player to send the string to + /// \param[in] data format specifier - same as printf + /// \param[in] ... format specification arguments - same as printf + virtual void Send( PlayerID playerId, const char *data, ... )=0; + + /// Disconnect \a playerId . The binary address and port defines the PlayerID structure. + /// \param[in] playerId The player/address to disconnect + virtual void CloseConnection( PlayerID playerId )=0; + + /// Return a string. The string should be allocated and written to Packet::data . + /// The byte length should be written to Packet::length . The player/address should be written to Packet::playerid + /// If your transport protocol adds special formatting to the data stream you should parse it out before returning it in the packet + /// and thus only return a string in Packet::data + /// \return The packet structure containing the result of Receive, or 0 if no data is available + virtual Packet* Receive( void )=0; + + /// Deallocate the Packet structure returned by Receive + /// \param[in] The packet to deallocate + virtual void DeallocatePacket( Packet *packet )=0; + + /// If a new system connects to you, you should queue that event and return the playerId/address of that player in this function. + /// \return The PlayerID/address of the system + virtual PlayerID HasNewConnection(void)=0; + + /// If a system loses the connection, you should queue that event and return the playerId/address of that player in this function. + /// \return The PlayerID/address of the system + virtual PlayerID HasLostConnection(void)=0; + + /// Your transport provider can itself have command parsers if the transport layer has user-modifiable features + /// For example, your transport layer may have a password which you want remote users to be able to set or you may want + /// to allow remote users to turn on or off command echo + /// \return 0 if you do not need a command parser - otherwise the desired derivation of CommandParserInterface + virtual CommandParserInterface* GetCommandParser(void)=0; +protected: +}; + +#endif + diff --git a/raknet/Types.h b/raknet/Types.h index 73a4e8f..dac0758 100644 --- a/raknet/Types.h +++ b/raknet/Types.h @@ -1,8 +1,703 @@ +/// \file Types.h +/// \brief Used for types used by RSA +/// +/// @verbatim +/// Fundamental tools & types +/// +/// Catid(cat02e@fsu.edu) +/// +/// 8/9/2004 Added SINGLE/ARRAY_RELEASE +/// 8/5/2004 Added COMPILER_ preprocessors +/// class NoCopies +/// 8/1/2004 Removed mask stuff +/// 7/29/2004 Added swapLE, swapBE, getLE, getBE +/// 7/28/2004 Automatic and AutoArray now compile in dev-c++ +/// Added pre-processor conditions to support +/// other compilers +/// Removed GETWORD and GETDWORD +/// 7/15/2004 Now using COM_RELEASE throughout CatGL3 +/// 6/22/2004 Removed triple and pair +/// 6/12/2004 AutoDeallocate -> Automatic, AutoArray +/// 6/9/2004 OBJCLR +/// 5/2/2004 class AutoDeallocate +/// 5/1/2004 IS_POWER_OF_2, next_highest_power_of_2 +/// 4/30/2004 Merged character manip macros +/// 2/23/2004 CEIL* +/// Removed MEMCOPY32 and MEMCLEAR32, +/// memcpy and memset are now faster +/// MAKE_MASK +/// 2/10/2004 LITTLE_ENDIAN +/// COUNT1BITS32 +/// AT_LEAST_2_BITS +/// LEAST_SIGNIFICANT_BIT +/// X-mas/2003 [u/s]int?? -> [u/s]?? +/// 7/3/2003 Added template triple, point->pair +/// 6/15/2003 Added template rect, point +/// 3/30/2003 Added RO?8, RO?16 and ?int64 +/// Added MEMCOPY32 and MEMCLEAR32 +/// 3/12/2003 Added GETWORD and GETDWORD +/// 1/16/2003 Formalized this library. +/// +/// Tabs: 4 spaces +/// Dist: public +/// @endverbatim +/// #ifndef TYPES_H #define TYPES_H +/// @brief Compatibility and Fundamental Tools and Types. This +/// namespace contains compatibility tools and types and also +/// fundamental class. +/// @section sec_compiler Compiler detection +/// Things to consider for each compiler: +/// +/// - BIG_ENDIAN / LITTLE_ENDIAN +/// - MULTITHREADED +/// - DEBUG +/// - HASINT64 +/// - Basic types {u8-u64, s8-s64, f32, f64} +/// - WIN32 +/// - ASSEMBLY_INTEL_SYNTAX / ASSEMBLY_ATT_SYNTAX, ASSEMBLY_BLOCK +/// - INLINE +/// - NO_TEMPLATE_INLINE_ASSEMBLY +/// - Fixes +/// +/// Set depending which compiler is being used: +/// +/// - COMPILER_MSVC +/// - COMPILER_GCC +/// - COMPILER_BORLANDC + +// CB: Something strange happens with the system includes on +// Linux and BIG_ENDIAN ends up defined at some point, so +// we need to use HOST_ENDIAN_IS_BIG instead. This code is +// a copy of code further down, but keeps the def outside +// the cat namespace + +#if !defined(HOST_ENDIAN_IS_BIG) && !defined(HOST_ENDIAN_IS_LITTLE) +#if defined(__sparc) || defined(__sparc__) || defined(__powerpc__) || \ + defined(__ppc__) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || \ + defined(_M_PPC) || defined(_M_MPPC) || defined(_M_MRX000) || \ + defined(__POWERPC) || defined(m68k) || defined(powerpc) || \ + defined(sel) || defined(pyr) || defined(mc68000) || defined(is68k) || \ + defined(tahoe) || defined(ibm032) || defined(ibm370) || defined(MIPSEB) || \ + defined(__convex__) || defined(DGUX) || defined(hppa) || defined(apollo) || \ + defined(_CRAY) || defined(__hp9000) || defined(__hp9000s300) || defined(_AIX) || \ + defined(__AIX) || defined(__pyr__) || defined(hp9000s700) || defined(_IBMR2) + +# define HOST_ENDIAN_IS_BIG + +#elif defined(__i386__) || defined(i386) || defined(intel) || defined(_M_IX86) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(__alpha__) || defined(__alpha) || defined(__ia64) || defined(__ia64__) || \ + defined(_M_ALPHA) || defined(ns32000) || defined(__ns32000__) || defined(sequent) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(sun386) || defined(__sun386__) + +# define HOST_ENDIAN_IS_LITTLE + +#else + +# error "I can't tell what endian-ness to use for your architecture." + +#endif +#endif + + +namespace cat +{ + + //// endian-ness (ignoring NUXI) //// + // Prevent an error message with gcc which define preprocessor symbol + // LITTLE_ENDIAN or BIG_ENDIAN (endian.h) + // dalfy +#if !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) +#if defined(__sparc) || defined(__sparc__) || defined(__powerpc__) || \ + defined(__ppc__) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || \ + defined(_M_PPC) || defined(_M_MPPC) || defined(_M_MRX000) || \ + defined(__POWERPC) || defined(m68k) || defined(powerpc) || \ + defined(sel) || defined(pyr) || defined(mc68000) || defined(is68k) || \ + defined(tahoe) || defined(ibm032) || defined(ibm370) || defined(MIPSEB) || \ + defined(__convex__) || defined(DGUX) || defined(hppa) || defined(apollo) || \ + defined(_CRAY) || defined(__hp9000) || defined(__hp9000s300) || defined(_AIX) || \ + defined(__AIX) || defined(__pyr__) || defined(hp9000s700) || defined(_IBMR2) + +# define BIG_ENDIAN + +#elif defined(__i386__) || defined(i386) || defined(intel) || defined(_M_IX86) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(__alpha__) || defined(__alpha) || defined(__ia64) || defined(__ia64__) || \ + defined(_M_ALPHA) || defined(ns32000) || defined(__ns32000__) || defined(sequent) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(sun386) || defined(__sun386__) + +# define LITTLE_ENDIAN + +#else + +# error "I can't tell what endian-ness to use for your architecture." + +#endif +#endif + +#ifndef FALSE + /** + * Define an alias between FALSE and false + */ +#define FALSE false +#endif +#ifndef TRUE + /** + * Define an alias between TRUE and true + */ +#define TRUE true +#endif + //// compiler-specific //// + +#if defined(__COMO__) // Comeau C++ + +# error "Comeau C++ : I don't know your compiler" + +#elif defined(__DMC__) // Digital Mars C++ + +# error "Digital Mars C++ : I don't know your compiler" + +#elif defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC) // Intel + +# error "Intel : I don't know your compiler" + +#elif (defined(__GNUC__) || defined(__GCCXML__)) // GNU C++ + +#define COMPILER_GCC + +# if defined(_MT) +# define MULTITHREADED +# endif + +# if defined(_DEBUG) +# define DEBUG +# endif + +# define INLINE inline +# define HASINT64 + +# define ASSEMBLY_ATT_SYNTAX +# define ASSEMBLY_BLOCK asm + +#elif defined(__DJGPP__) // DJGPP + +# error "DJGPP : I don't know your compiler" + +# define ASSEMBLY_ATT_SYNTAX + +#elif defined(__WATCOMC__) // WatcomC + +# error "WatcomC : I don't know your compiler" + +#elif defined(__KCC) // Kai C++ + +# error "Kai C++ : I don't know your compiler" + +#elif defined(__sgi) // SGI MIPSpro C++ + +# error "SGI MIPSpro C++ : I don't know your compiler" + +#elif defined(__SUNPRO_CC) // Sun Workshop Compiler C++ + +# error "Sun Workshop Compiler C++ : I don't know your compiler" + +#elif defined(__HP_aCC) // HP aCC + +# error "HP aCC : I don't know your compiler" + +#elif defined(__DECCXX) // Compaq Tru64 Unix cxx + +# error "Compaq Tru64 Unix cxx : I don't know your compiler" + +#elif defined(__ghs) // Greenhills C++ + +# error "Greenhills C++ : I don't know your compiler" + +#elif defined(__BORLANDC__) // Borland + +# if (__BORLANDC__ >= 0x561) +# define HASINT64 +# endif + +#define COMPILER_BORLANDC + +# if defined(__MT__) +# define MULTITHREADED +# endif + +# if defined(__DEBUG) +# define DEBUG +# endif + + +# define INLINE inline +# define ASSEMBLY_INTEL_SYNTAX +# define ASSEMBLY_BLOCK _asm +# define NO_TEMPLATE_INLINE_ASSEMBLY + +#elif defined(__MWERKS__) // Metrowerks CodeWarrior + +# error "Metrowerks CodeWarrior : I don't know your compiler" + +#elif defined(__MRC__) || defined(__SC__) // MPW MrCpp or SCpp + +# error "MPW MrCpp or SCpp : I don't know your compiler" + +#elif defined(__IBMCPP__) // IBM Visual Age + +# error "IBM Visual Age : I don't know your compiler" + +#elif defined(_MSC_VER) // Microsoft VC++ + // must be last because many other compilers define this also + +#define COMPILER_MSVC + +# if (_MSC_VER >= 1200) /* 1200 == VC++ 6.0 */ +# define HASINT64 +# define INLINE __forceinline +# endif + +# if defined(_MT) +# define MULTITHREADED +# endif + +# if defined(__DEBUG) +# define DEBUG +# endif + +# define BASIC_TYPES_ALREADY_DEFINED + /** + * Typename for 8 bits unsigned integer + */ + typedef unsigned __int8 u8; + /** + * Typename for 8 bits signed integer + */ + typedef signed __int8 s8; + /** + * Typename for 16 bits unsigned integer + */ + typedef unsigned __int16 u16; + /** + * Typename for 16 bits signed integer + */ + typedef signed __int16 s16; + /** + * Typename for 32 bits unsigned integer + */ + typedef unsigned __int32 u32; + /** + * Typename for 32 bits floatting point number + */ + typedef signed __int32 s32; + /** + * Typename for 32 bits floatting point number + */ + typedef float f32; + /** + * Typename for 64 bits floatting point number + */ + typedef double f64; +# if defined(HASINT64) + /** + * Typename for 64 bits unsigned integer + */ + typedef unsigned __int64 u64; + /** + * Typename for 64 bits signed integer + */ + typedef signed __int64 s64; +# endif + +# define ASSEMBLY_INTEL_SYNTAX +# define ASSEMBLY_BLOCK __asm + +# if (_MSC_VER <= 1200) +# pragma warning(disable : 4786) // truncation to 255 chars +# endif + +#else + +# error "Unknown : I don't know your compiler" + +#endif // compilers + + + // Generic basic types +#if !defined(BASIC_TYPES_ALREADY_DEFINED) + /** + * Typename for 8 bits unsigned integer + */ + typedef unsigned char u8; + /** + * Typename for 8 bits signed integer + */ + typedef signed char s8; + /** + * Typename for 16 bits unsigned integer + */ + typedef unsigned short u16; + /** + * Typename for 16 bits signed integer + */ + typedef signed short s16; + /** + * Typename for 32 bits unsigned integer + */ + typedef unsigned int u32; + /** + * Typename for 32 bits signed integer + */ + typedef signed int s32; + /** + * Typename for 32 bits floatting point number + */ + typedef float f32; + /** + * Typename for 64 bits floatting point number + */ + typedef double f64; +# if defined(HASINT64) + /** + * Typename for 64 bits unsigned integer + */ + typedef unsigned int long u64; + /** + * Typename for 64 bits signed integer + */ + typedef signed long long s64; +# endif +#endif + + + /** + * Fixed-point types + * hi-Siiiiiiiiiiiiiiiiiiiii-lo | hi-ffffffffff-lo + */ + typedef s32 sfp22_10; + /** + * Fixed-point types + * hi-iiiiiiiiiiiiiiiiiiiiii-lo | hi-ffffffffff-lo + */ + typedef u32 ufp22_10; + /** + * Fixed point types + * hi-Siiiiiiiiiiiiiii-lo | hi-ffffffffffffffff-lo + */ + typedef s32 sfp16_16; + /** + * Fixed point types + * hi-iiiiiiiiiiiiiiii-lo | hi-ffffffffffffffff-lo + */ + typedef u32 ufp16_16; + + /* + COMmy macros + */ + +#define COM_RELEASE(ref) if (ref) { (ref)->Release(); (ref) = 0; } +#define SINGLE_RELEASE(ref) if (ref) { delete (ref); (ref) = 0; } +#define ARRAY_RELEASE(ref) if (ref) { delete [](ref); (ref) = 0; } + + template + + class rect + { + + public: + rect() : x( 0 ), y( 0 ), w( 0 ), h( 0 ) + {} + + rect( T xx, T yy, T ww, T hh ) : x( xx ), y( yy ), w( ww ), h( hh ) + {} + + T x, y, w, h; + }; + + template + + class AutoArray + { + T *ptr; + + public: + AutoArray( T *c_ptr ) + { + ptr = c_ptr; + } + + ~AutoArray() + { + ARRAY_RELEASE( ptr ); + } + + INLINE void cancel() + { + ptr = 0; + } + }; + + template + + class Automatic + { + T *ptr; + + public: + Automatic( T *c_ptr ) + { + ptr = c_ptr; + } + + ~Automatic() + { + SINGLE_RELEASE( ptr ); + } + + INLINE void cancel() + { + ptr = 0; + } + }; + + // Derive from NoCopies to disallow copies of derived class + + class NoCopies + { + + protected: + NoCopies() + {} + + ~NoCopies() + {} + + private: + NoCopies( const NoCopies &cp ); + NoCopies &operator=( const NoCopies &cp ); + }; + + + // Byte-order swapping +#define BOSWAP32(n) ( ((n) << 24) | (((n) & 0x00ff0000) >> 8) | (((n) & 0x0000ff00) << 8) | ((n) >> 24) ) /* only works for u32 */ +#define BOSWAP16(n) ( ((n) << 8) | ((n) >> 8) ) /* only works for u16 */ + +#ifdef LITTLE_ENDIAN +# define swapLE(n) +# define getLE(n) (n) + + INLINE void swapBE( u32 &n ) + { + n = BOSWAP32( n ); + } + + INLINE void swapBE( u16 &n ) + { + n = BOSWAP16( n ); + } + + INLINE u32 getBE( u32 n ) + { + return BOSWAP32( n ); + } + + INLINE u16 getBE( u16 n ) + { + return BOSWAP16( n ); + } + + INLINE void swapBE( s32 &n ) + { + n = BOSWAP32( ( u32 ) n ); + } + + INLINE void swapBE( s16 &n ) + { + n = BOSWAP16( ( u16 ) n ); + } + + INLINE s32 getBE( s32 n ) + { + return BOSWAP32( ( u32 ) n ); + } + + INLINE s16 getBE( s16 n ) + { + return BOSWAP16( ( u16 ) n ); + } + +#else +# define swapBE(n) +# define getBE(n) (n) + INLINE void swapLE( u32 &n ) + { + n = BOSWAP32( n ); + } + + INLINE void swapLE( u16 &n ) + { + n = BOSWAP16( n ); + } + + INLINE u32 getLE( u32 n ) + { + return BOSWAP32( n ); + } + + INLINE u16 getLE( u16 n ) + { + return BOSWAP16( n ); + } + + INLINE void swapLE( s32 &n ) + { + n = BOSWAP32( ( u32 ) n ); + } + + INLINE void swapLE( s16 &n ) + { + n = BOSWAP16( ( u16 ) n ); + } + + INLINE s32 getLE( s32 n ) + { + return BOSWAP32( ( u32 ) n ); + } + + INLINE s16 getLE( s16 n ) + { + return BOSWAP16( ( u16 ) n ); + } + +#endif + +} // namespace cat + + // Rotation +#define ROL8(n, r) ( ((n) << (r)) | ((n) >> ( 8 - (r))) ) /* only works for u8 */ +#define ROR8(n, r) ( ((n) >> (r)) | ((n) << ( 8 - (r))) ) /* only works for u8 */ +#define ROL16(n, r) ( ((n) << (r)) | ((n) >> (16 - (r))) ) /* only works for u16 */ +#define ROR16(n, r) ( ((n) >> (r)) | ((n) << (16 - (r))) ) /* only works for u16 */ #define ROL32(n, r) ( ((n) << (r)) | ((n) >> (32 - (r))) ) /* only works for u32 */ +#define ROR32(n, r) ( ((n) >> (r)) | ((n) << (32 - (r))) ) /* only works for u32 */ + +/* +Add memory that is allocated on a 32-bit boundary +and has at least one block. +*/ +#define MEMADD32(ptr, len, val) { \ + register u32 *__data = (u32*)(ptr); /* pointer to data to clear */ \ + register s32 __length = (len); /* number of 32-bit blocks */ \ + \ + switch (__length % 8) \ +{ \ + case 0: do { *__data++ += (val); \ + case 7: *__data++ += (val); \ + case 6: *__data++ += (val); \ + case 5: *__data++ += (val); \ + case 4: *__data++ += (val); \ + case 3: *__data++ += (val); \ + case 2: *__data++ += (val); \ + case 1: *__data++ += (val); \ + __length -= 8; \ + } while(__length > 0); \ +} \ +} + +/** +* Safe null-terminated string -> char buffer copy +* \param dest the resulting string +* \param src the string to copy +* \param size the number of char to copy +*/ +#define STRNCPY(dest, src, size) { \ + strncpy(dest, src, size); \ + dest[size-1] = 0; \ +} + +/* +Because memory clearing is a frequent operation +*/ +#define MEMCLR(dest, size) memset(dest, 0, size) + +// Works for arrays, also +#define OBJCLR(object) memset(&(object), 0, sizeof(object)) + +/* +Fast binary method of counting bits + +MIT Hackmem from X11 + +Only works for 32-bit integers +*/ +#define _C1B_INTERMED(n) ( (n) - (((n) >> 1) & 033333333333) - (((n) >> 2) & 011111111111) ) +#define COUNT1BITS32(n) ( ((_C1B_INTERMED(n) + (_C1B_INTERMED(n) >> 3)) & 030707070707) % 63 ) + +/* +Consider N an ordered set of 1/0 values. + +LESSTHAN2BITS(n) := there are less than 2 bits in set N + +Proof: + +(N - 1) will clear the least significant of the bits in N. + +Three cases exist concerning (N-1): + +N contains 0 bits +An intersection with N would be trivially null. +N contains 1 bit +The least only existing bit was cleared, +thus an intersection with the original +set WOULD be null. +N contains more than 1 bit +A more significant bit remains in the set, +thus an intersection with the original +set WOULD NOT be null. +*/ +#define AT_LEAST_2_BITS(n) ( (n) & ((n) - 1) ) + +#define LEAST_SIGNIFICANT_BIT(n) ( (n) & -(n) ) + +#define IS_POWER_OF_2(n) ( n && !AT_LEAST_2_BITS(n) ) + +INLINE cat::u32 next_highest_power_of_2( cat::u32 n ) +{ + if ( IS_POWER_OF_2( n ) ) + return n; + + cat::u16 b = 2; + + while ( n >>= 1 ) + b <<= 1; + + return b; +} + +/* +CEIL* + +Bump 'n' to the next unit of 'width'. +*/ +#define CEIL_UNIT(n, width) ( ( (n) + (width) - 1 ) / (width) ) +#define CEIL(n, width) ( CEIL_UNIT(n, width) * (width) ) + +/* +Quick character manipulation +*/ +//#define IS_ALPHA(ch) ( (((u8)(ch) & 0xc0) == 0x40) && ((((u8)(ch) - 1) & 0x1f) <= 0x19) ) + +//#define IS_NUM(ch) ( ((u8)(ch) - 0x30) < 10 ) + +//#define IS_ALPHANUM(ch) ( IS_ALPHA(ch) || IS_NUM(ch) ) + +//#define TO_LOWER(ch) (char)( (ch) | 0x20 ) /* must be upper/lower-case alpha */ +//#define TO_UPPER(ch) (char)( (ch) & (~0x20) ) /* must be upper/lower-case alpha */ + #endif // TYPES_H diff --git a/raknet/_findfirst.cpp b/raknet/_findfirst.cpp new file mode 100644 index 0000000..b17e337 --- /dev/null +++ b/raknet/_findfirst.cpp @@ -0,0 +1,129 @@ +/** +* Original file by the_viking, fixed by R√¥mulo Fernandes, fixed by Emmanuel Nars +* Should emulate windows finddata structure +*/ +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(__WIN32) +#include "_findfirst.h" +#include "DS_List.h" + +static DataStructures::List< _findinfo_t* > fileInfo; + +/** +* _findfirst - equivalent +*/ +long _findfirst(const char *name, _finddata_t *f) +{ + + // char* nameCopy = new char[sizeof(name)]; + // memset(nameCopy, '\0', sizeof(nameCopy)); + // + // strcpy(nameCopy, name); + // + // char* filter = new char[sizeof(nameCopy)]; + // memset(filter, '\0', sizeof(filter)); + + int length = strlen(name)+1; + char* nameCopy = new char[length]; + memset(nameCopy, '\0', length); + + strcpy(nameCopy, name); + + char* filter = new char[length]; + memset(filter, '\0', length); + + char* lastSep = strrchr(nameCopy,'/'); + if(!lastSep) + { + strcpy(filter, nameCopy); + strcpy(nameCopy, "."); + } + else + { + strcpy(filter, lastSep+1); + *lastSep = 0; + } + + DIR* dir = opendir(nameCopy); + + if(!dir) + { + return -1; + } + + _findinfo_t* fi = new _findinfo_t; + strcpy(fi->filter,filter); + fi->openedDir = dir; + + while(true) + { + dirent* entry = readdir(dir); + if(entry == 0) + break; + + if(fnmatch(fi->filter,entry->d_name, 200) == 0) + { + strcpy(f->name, entry->d_name); + break; + } + } + + + fileInfo.Insert(fi); + return fileInfo.Size()-1; + + // return 0; +} + +/** +* _findnext - equivalent +*/ +int _findnext(long h, _finddata_t *f) +{ + + _findinfo_t* fi = fileInfo[h]; + + while(true) + { + dirent* entry = readdir(fi->openedDir); + if(entry == 0) + return -1; + + if(fnmatch(fi->filter,entry->d_name, 200) == 0) + { + strcpy(f->name, entry->d_name); + if (entry->d_type == DT_REG) + f->attrib = _A_NORMAL; + f->size = entry->d_reclen; + return 0; + } + if (entry->d_type == DT_DIR) + { + f->attrib = _A_SUBDIR; + strcpy(f->name, entry->d_name); + return 0; + } + } + + return -1; +} + +/** +* _findclose - equivalent +*/ +int _findclose(long h) +{ + if (fileInfo.Size()>h) + { + _findinfo_t* fi = fileInfo[h]; + fileInfo.RemoveAtIndex(h); + delete fi; + return 0; + } + else + { + printf("Error _findclose\n"); + return -1; + } + +} +#endif diff --git a/raknet/_findfirst.h b/raknet/_findfirst.h new file mode 100644 index 0000000..0d83fed --- /dev/null +++ b/raknet/_findfirst.h @@ -0,0 +1,52 @@ +/** + * Original file by the_viking, fixed by Rômulo Fernandes + * Should emulate windows finddata structure + */ + +#ifndef GCC_FINDFIRST_H +#define GCC_FINDFIRST_H + +#if (defined(__GNUC__) || defined(__GCCXML__)) && !defined(__WIN32) + +#include +#include +#include +#include +#include + +#define _A_NORMAL 0x00 // Normal file +#define _A_RDONLY 0x01 // Read-only file +#define _A_HIDDEN 0x02 // Hidden file +#define _A_SYSTEM 0x04 // System file +#define _A_VOLID 0x08 // Volume ID +#define _A_SUBDIR 0x10 // Subdirectory +#define _A_ARCH 0x20 // File changed since last archive +#define FA_NORMAL 0x00 // Synonym of _A_NORMAL +#define FA_RDONLY 0x01 // Synonym of _A_RDONLY +#define FA_HIDDEN 0x02 // Synonym of _A_HIDDEN +#define FA_SYSTEM 0x04 // Synonym of _A_SYSTEM +#define FA_LABEL 0x08 // Synonym of _A_VOLID +#define FA_DIREC 0x10 // Synonym of _A_SUBDIR +#define FA_ARCH 0x20 // Synonym of _A_ARCH + +typedef struct _finddata_t +{ + char name[260]; + int attrib; + unsigned long size; +} _finddata; + +/** Hold information about the current search +*/ +typedef struct _findinfo_t +{ + DIR* openedDir; + char filter[260]; +} _findinfo; + +long _findfirst(const char *name, _finddata_t *f); +int _findnext(long h, _finddata_t *f); +int _findclose(long h); + +#endif +#endif diff --git a/raknet/lightweightdatabaseserver.cpp b/raknet/lightweightdatabaseserver.cpp new file mode 100644 index 0000000..1feb7ac --- /dev/null +++ b/raknet/lightweightdatabaseserver.cpp @@ -0,0 +1,617 @@ +#include "LightweightDatabaseServer.h" +#include "PacketEnumerations.h" +#include "BitStream.h" +#include "StringCompressor.h" +#include "RakPeerInterface.h" +#include "TableSerializer.h" +#include "RakAssert.h" +#include "GetTime.h" +#include "Rand.h" + +#define SYSTEM_ID_COLUMN_NAME "__systemId" +#define LAST_PING_RESPONSE_COLUMN_NAME "__lastPingResponseTime" +#define NEXT_PING_SEND_COLUMN_NAME "__nextPingSendTime" +static const int SEND_PING_INTERVAL=15000; +static const int DROP_SERVER_INTERVAL=75000; + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int LightweightDatabaseServer::DatabaseTableComp( char* const &key1, char* const &key2 ) +{ + return strcmp(key1, key2); +} + +LightweightDatabaseServer::LightweightDatabaseServer() +{ + +} +LightweightDatabaseServer::~LightweightDatabaseServer() +{ + Clear(); +} +DataStructures::Table *LightweightDatabaseServer::GetTable(char *tableName) +{ + if (database.Has(tableName)) + return &(database.Get(tableName)->table); + return 0; +} +DataStructures::Page *LightweightDatabaseServer::GetTableRows(char *tableName) +{ + if (database.Has(tableName)) + database.Get(tableName)->table.GetRows().GetListHead(); + return 0; +} +DataStructures::Table* LightweightDatabaseServer::AddTable(char *tableName, + bool allowRemoteQuery, + bool allowRemoteUpdate, + bool allowRemoteRemove, + char queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + char updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + char removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH], + bool oneRowPerSystemId, + bool onlyUpdateOwnRows, + bool removeRowOnPingFailure, + bool removeRowOnDisconnect, + bool autogenerateRowIDs) +{ + if (tableName==0 || tableName[0]==0) + return 0; + if (database.Has(tableName)) + return 0; + DatabaseTable *databaseTable = new DatabaseTable; + + strncpy(databaseTable->tableName, tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH); + databaseTable->tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH]=0; + + if (allowRemoteUpdate) + { + strncpy(databaseTable->updatePassword, updatePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH); + databaseTable->updatePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]=0; + } + else + databaseTable->updatePassword[0]=0; + + if (allowRemoteQuery) + { + strncpy(databaseTable->queryPassword, queryPassword, _SIMPLE_DATABASE_PASSWORD_LENGTH); + databaseTable->queryPassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]=0; + } + else + databaseTable->queryPassword[0]=0; + + if (allowRemoteRemove) + { + strncpy(databaseTable->removePassword, removePassword, _SIMPLE_DATABASE_PASSWORD_LENGTH); + databaseTable->removePassword[_SIMPLE_DATABASE_PASSWORD_LENGTH]=0; + } + else + databaseTable->removePassword[0]=0; + + if (allowRemoteUpdate) + { + databaseTable->allowRemoteUpdate=true; + databaseTable->oneRowPerSystemId=oneRowPerSystemId; + databaseTable->onlyUpdateOwnRows=onlyUpdateOwnRows; + databaseTable->removeRowOnPingFailure=removeRowOnPingFailure; + databaseTable->removeRowOnDisconnect=removeRowOnDisconnect; + } + else + { + // All these parameters are related to IP tracking, which is not done if remote updates are not allowed + databaseTable->allowRemoteUpdate=true; + databaseTable->oneRowPerSystemId=false; + databaseTable->onlyUpdateOwnRows=false; + databaseTable->removeRowOnPingFailure=false; + databaseTable->removeRowOnDisconnect=false; + } + + databaseTable->nextRowId=0; + databaseTable->nextRowPingCheck=0; + + databaseTable->autogenerateRowIDs=autogenerateRowIDs; + databaseTable->allowRemoteRemove=allowRemoteRemove; + databaseTable->allowRemoteQuery=allowRemoteQuery; + + database.SetNew(databaseTable->tableName, databaseTable); + + if ( oneRowPerSystemId || onlyUpdateOwnRows || removeRowOnPingFailure || removeRowOnDisconnect) + databaseTable->systemIdColumnIndex=databaseTable->table.AddColumn(SYSTEM_ID_COLUMN_NAME, DataStructures::Table::BINARY); + else + databaseTable->systemIdColumnIndex=(unsigned) -1; + if (databaseTable->removeRowOnPingFailure) + { + databaseTable->lastPingResponseColumnIndex=databaseTable->table.AddColumn(LAST_PING_RESPONSE_COLUMN_NAME, DataStructures::Table::NUMERIC); + databaseTable->nextPingSendColumnIndex=databaseTable->table.AddColumn(NEXT_PING_SEND_COLUMN_NAME, DataStructures::Table::NUMERIC); + } + else + { + databaseTable->lastPingResponseColumnIndex=(unsigned) -1; + databaseTable->nextPingSendColumnIndex=(unsigned) -1; + } + + return &(databaseTable->table); +} +bool LightweightDatabaseServer::RemoveTable(char *tableName) +{ + LightweightDatabaseServer::DatabaseTable *databaseTable; + databaseTable = database.Get(tableName); + if (databaseTable==0) + return false; + // Be sure to call Delete on database before I do the actual pointer deletion since the key won't be valid after that time. + database.Delete(tableName); + databaseTable->table.Clear(); + delete databaseTable; + return true; +} +void LightweightDatabaseServer::Clear(void) +{ + unsigned i; + + for (i=0; i < database.Size(); i++) + { + database[i]->table.Clear(); + delete database[i]; + } + + database.Clear(); +} +unsigned LightweightDatabaseServer::GetAndIncrementRowID(char *tableName) +{ + LightweightDatabaseServer::DatabaseTable *databaseTable; + databaseTable = database.Get(tableName); + RakAssert(databaseTable); + RakAssert(databaseTable->autogenerateRowIDs==true); + return ++(databaseTable->nextRowId) - 1; +} +void LightweightDatabaseServer::OnAttach(RakPeerInterface *peer) +{ + +} +void LightweightDatabaseServer::Update(RakPeerInterface *peer) +{ + RakNetTime time=0; + DatabaseTable *databaseTable; + DataStructures::Page *cur; + unsigned i,j; + DataStructures::Table::Row* row; + DataStructures::List removeList; + PlayerID playerId; + + // periodic ping if removing system that do not respond to pings. + for (i=0; i < database.Size(); i++) + { + databaseTable=database[i]; + + if (databaseTable->removeRowOnPingFailure) + { + // Reading the time is slow - only do it once if necessary. + if (time==0) + time = RakNet::GetTime(); + + if (databaseTable->nextRowPingCheck < time) + { + databaseTable->nextRowPingCheck=time+1000+(randomMT()%1000); + DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + cur = rows.GetListHead(); + while (cur) + { + // Mark dropped entities + for (j=0; j < (unsigned)cur->size; j++) + { + row = cur->data[j]; + if (time - row->cells[databaseTable->lastPingResponseColumnIndex]->i > DROP_SERVER_INTERVAL) + removeList.Insert(cur->keys[j]); + } + cur=cur->next; + } + + // Remove dropped entities + for (j=0; j < removeList.Size(); j++) + databaseTable->table.RemoveRow(removeList[i]); + removeList.Clear(true); + + cur = rows.GetListHead(); + // Ping remaining entities if they are not connected. If they are connected just increase the ping interval. + while (cur) + { + for (j=0; j < (unsigned)cur->size; j++) + { + row = cur->data[j]; + if (row->cells[databaseTable->nextPingSendColumnIndex]->i < (int) time) + { + row->cells[databaseTable->systemIdColumnIndex]->Get((char*)&playerId, 0); + if (peer->GetIndexFromPlayerID(playerId)==-1) + { + peer->Ping(playerId.ToString(false), playerId.port, false); + } + else + { + // Consider the fact that they are connected to be a ping response + row->cells[databaseTable->lastPingResponseColumnIndex]->i=time; + } + + row->cells[databaseTable->nextPingSendColumnIndex]->i=time+SEND_PING_INTERVAL+(randomMT()%1000); + } + } + cur=cur->next; + } + } + } + } +} +PluginReceiveResult LightweightDatabaseServer::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + switch (packet->data[0]) + { + case ID_DATABASE_QUERY_REQUEST: + OnQueryRequest(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DATABASE_UPDATE_ROW: + OnUpdateRow(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DATABASE_REMOVE_ROW: + OnRemoveRow(peer, packet); + return RR_STOP_PROCESSING_AND_DEALLOCATE; + case ID_DISCONNECTION_NOTIFICATION: + case ID_CONNECTION_LOST: + RemoveRowsFromIP(packet->playerId); + return RR_CONTINUE_PROCESSING; + case ID_PONG: + OnPong(peer, packet); + return RR_CONTINUE_PROCESSING; + } + return RR_CONTINUE_PROCESSING; +} +void LightweightDatabaseServer::OnDisconnect(RakPeerInterface *peer) +{ + +} +void LightweightDatabaseServer::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + RemoveRowsFromIP(playerId); +} +void LightweightDatabaseServer::OnQueryRequest(RakPeerInterface *peer, Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, peer, packet, 0); + if (databaseTable==0) + return; + if (databaseTable->allowRemoteQuery==false) + return; + unsigned char numColumnSubset; + RakNet::BitStream outBitstream; + unsigned i; + if (inBitstream.Read(numColumnSubset)==false) + return; + unsigned columnSubset[256]; + for (i=0; i < numColumnSubset; i++) + inBitstream.Read(columnSubset[i]); + unsigned char numNetworkedFilters; + if (inBitstream.Read(numNetworkedFilters)==false) + return; + DatabaseFilter networkedFilters[256]; + for (i=0; i < numNetworkedFilters; i++) + { + if (networkedFilters[i].Deserialize(&inBitstream)==false) + return; + } + + unsigned rowIds[256]; + unsigned char numRowIDs; + if (inBitstream.Read(numRowIDs)==false) + return; + for (i=0; i < numRowIDs; i++) + inBitstream.Read(rowIds[i]); + + // Convert the safer and more robust networked database filter to the more efficient form the table actually uses. + DataStructures::Table::FilterQuery tableFilters[256]; + unsigned numTableFilters=0; + for (i=0; i < numNetworkedFilters; i++) + { + tableFilters[numTableFilters].columnIndex=databaseTable->table.ColumnIndex(networkedFilters[i].columnName); + if (tableFilters[numTableFilters].columnIndex==(unsigned)-1) + continue; + if (networkedFilters[i].columnType!=databaseTable->table.GetColumns()[tableFilters[numTableFilters].columnIndex].columnType) + continue; + tableFilters[numTableFilters].operation=networkedFilters[i].operation; + // It's important that I store a pointer to the class here or the destructor of the class will deallocate the cell twice + tableFilters[numTableFilters++].cellValue=&(networkedFilters[i].cellValue); + } + + DataStructures::Table queryResult; + databaseTable->table.QueryTable(columnSubset, numColumnSubset, tableFilters, numTableFilters, rowIds, numRowIDs, &queryResult); + outBitstream.Write((unsigned char)ID_DATABASE_QUERY_REPLY); + TableSerializer::SerializeTable(&queryResult, &outBitstream); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->playerId, false); + +} +void LightweightDatabaseServer::OnUpdateRow(RakPeerInterface *peer, Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, peer, packet, 1); + if (databaseTable==0) + return; + if (databaseTable->allowRemoteUpdate==false) + return; + unsigned char updateMode; + bool hasRowId; + unsigned rowId; + unsigned i; + DataStructures::Table::Row *row; + inBitstream.Read(updateMode); + inBitstream.Read(hasRowId); + if (hasRowId) + inBitstream.Read(rowId); + else + rowId=(unsigned) -1; // Not used here but remove the debugging check + unsigned char numCellUpdates; + if (inBitstream.Read(numCellUpdates)==false) + return; + // Read the updates for the row + DatabaseCellUpdate cellUpdates[256]; + for (i=0; i < numCellUpdates; i++) + { + if (cellUpdates[i].Deserialize(&inBitstream)==false) + return; + } + + if ((RowUpdateMode)updateMode==RUM_UPDATE_EXISTING_ROW) + { + if (hasRowId==false) + return; + + row = databaseTable->table.GetRowByID(rowId); + if (row==0 || databaseTable->onlyUpdateOwnRows && RowHasIP(row, packet->playerId, databaseTable->systemIdColumnIndex)==false) + return; // You can't update some other system's row + } + else if ((RowUpdateMode)updateMode==RUM_UPDATE_OR_ADD_ROW) + { + if (hasRowId) + row = databaseTable->table.GetRowByID(rowId); + else + row=0; + + if (row==0) + { + row=AddRow(databaseTable, packet->playerId, hasRowId, rowId); + if (row==0) + return; + } + else + { + // Existing row + if (databaseTable->onlyUpdateOwnRows && RowHasIP(row, packet->playerId, databaseTable->systemIdColumnIndex)==false) + return; // You can't update some other system's row + } + } + else + { + RakAssert((RowUpdateMode)updateMode==RUM_ADD_NEW_ROW); + + row=AddRow(databaseTable, packet->playerId, hasRowId, rowId); + if (row==0) + return; + } + + unsigned columnIndex; + for (i=0; i < numCellUpdates; i++) + { + columnIndex=databaseTable->table.ColumnIndex(cellUpdates[i].columnName); + if (columnIndex!=(unsigned)-1 && columnIndex!=databaseTable->lastPingResponseColumnIndex && columnIndex!=databaseTable->nextPingSendColumnIndex && columnIndex!=databaseTable->systemIdColumnIndex) + { + if (cellUpdates[i].cellValue.isEmpty) + row->cells[columnIndex]->Clear(); + else if (cellUpdates[i].columnType==databaseTable->table.GetColumnType(columnIndex)) + { + if (cellUpdates[i].columnType==DataStructures::Table::NUMERIC) + { + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.i); + } + else if (cellUpdates[i].columnType==DataStructures::Table::BINARY) + { + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.i, cellUpdates[i].cellValue.c); + } + else + { + RakAssert(cellUpdates[i].columnType==DataStructures::Table::STRING); + row->UpdateCell(columnIndex, cellUpdates[i].cellValue.c); + } + } + } + } +} +void LightweightDatabaseServer::OnRemoveRow(RakPeerInterface *peer, Packet *packet) +{ + RakNet::BitStream inBitstream(packet->data, packet->length, false); + LightweightDatabaseServer::DatabaseTable *databaseTable = DeserializeClientHeader(&inBitstream, peer, packet, 0); + if (databaseTable==0) + return; + if (databaseTable->allowRemoteRemove==false) + return; + unsigned rowId; + inBitstream.Read(rowId); + databaseTable->table.RemoveRow(rowId); +} + +void LightweightDatabaseServer::OnPong(RakPeerInterface *peer, Packet *packet) +{ + unsigned databaseIndex; + DatabaseTable *databaseTable; + unsigned curIndex; + PlayerID playerId; + RakNetTime time=0; + for (databaseIndex=0; databaseIndex < database.Size(); databaseIndex++) + { + databaseTable=database[databaseIndex]; + if (databaseTable->removeRowOnPingFailure) + { + if (time==0) + time=RakNet::GetTime(); + + DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + DataStructures::Page *cur = rows.GetListHead(); + + while (cur) + { + for (curIndex=0; curIndex < (unsigned) cur->size; curIndex++) + { + cur->data[curIndex]->cells[databaseTable->systemIdColumnIndex]->Get((char*)&playerId,0); + if (playerId==packet->playerId) + { + cur->data[curIndex]->cells[databaseTable->lastPingResponseColumnIndex]->i=time; + } + } + cur=cur->next; + } + } + } +} + +LightweightDatabaseServer::DatabaseTable * LightweightDatabaseServer::DeserializeClientHeader(RakNet::BitStream *inBitstream, RakPeerInterface *peer, Packet *packet, int mode) +{ + RakNet::BitStream outBitstream; + bool hasPassword; + char password[_SIMPLE_DATABASE_PASSWORD_LENGTH]; + inBitstream->IgnoreBits(8); + char tableName[_SIMPLE_DATABASE_TABLE_NAME_LENGTH]; + stringCompressor->DecodeString(tableName, _SIMPLE_DATABASE_TABLE_NAME_LENGTH, inBitstream); + DatabaseTable *databaseTable = database.Get(tableName); + if (databaseTable==0) + { + outBitstream.Write((unsigned char)ID_DATABASE_UNKNOWN_TABLE); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->playerId, false); + return 0; + } + const char *dbPass; + if (mode==0) + dbPass=databaseTable->queryPassword; + else if (mode==1) + dbPass=databaseTable->updatePassword; + else + dbPass=databaseTable->removePassword; + + inBitstream->Read(hasPassword); + if (hasPassword) + { + if (stringCompressor->DecodeString(password, _SIMPLE_DATABASE_PASSWORD_LENGTH, inBitstream)==false) + return 0; + if (databaseTable->queryPassword[0] && strcmp(password, dbPass)!=0) + { + outBitstream.Write((unsigned char)ID_DATABASE_INCORRECT_PASSWORD); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->playerId, false); + // Short ban to prevent brute force password attempts + peer->AddToBanList(packet->playerId.ToString(false), 1000); + // Don't send a disconnection notification so it closes the connection right away. + peer->CloseConnection(packet->playerId, false, 0); + return 0; + } + } + else if (dbPass[0]) + { + outBitstream.Write((unsigned char)ID_DATABASE_INCORRECT_PASSWORD); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, packet->playerId, false); + return 0; + } + + return databaseTable; +} + +DataStructures::Table::Row * LightweightDatabaseServer::GetRowFromIP(DatabaseTable *databaseTable, PlayerID playerId, unsigned *rowKey) +{ + DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + DataStructures::Page *cur = rows.GetListHead(); + DataStructures::Table::Row* row; + unsigned i; + while (cur) + { + for (i=0; i < (unsigned)cur->size; i++) + { + row = cur->data[i]; + if (RowHasIP(row, playerId, databaseTable->systemIdColumnIndex )) + { + if (rowKey) + *rowKey=cur->keys[i]; + return row; + } + } + cur=cur->next; + } + return 0; +} +bool LightweightDatabaseServer::RowHasIP(DataStructures::Table::Row *row, PlayerID playerId, unsigned systemIdColumnIndex) +{ + RakAssert(row->cells[systemIdColumnIndex]->isEmpty==false); + if (memcmp(row->cells[systemIdColumnIndex]->c, &playerId, sizeof(PlayerID))==0) + return true; + return false; +} +DataStructures::Table::Row * LightweightDatabaseServer::AddRow(LightweightDatabaseServer::DatabaseTable *databaseTable, PlayerID playerId, bool hasRowId, unsigned rowId) +{ + DataStructures::Table::Row *row; + if (databaseTable->oneRowPerSystemId && GetRowFromIP(databaseTable, playerId, 0)) + return 0; // This system already has a row. + + if (databaseTable->autogenerateRowIDs==false) + { + // For a new row: + // rowID required but not specified OR + // rowId specified but already in the table + // Then exit + if (hasRowId==false || databaseTable->table.GetRowByID(rowId)) + return 0; + } + else + rowId=databaseTable->nextRowId++; + + // Add new row + row = databaseTable->table.AddRow(rowId); + + // Set IP and last update time + if ( databaseTable->oneRowPerSystemId || databaseTable->onlyUpdateOwnRows || databaseTable->removeRowOnPingFailure || databaseTable->removeRowOnDisconnect) + row->cells[databaseTable->systemIdColumnIndex]->Set((char*)&playerId, sizeof(PlayerID)); + if (databaseTable->removeRowOnPingFailure) + { + RakNetTime time = RakNet::GetTime(); + row->cells[databaseTable->lastPingResponseColumnIndex]->Set(time); + row->cells[databaseTable->nextPingSendColumnIndex]->Set(time+SEND_PING_INTERVAL); + } + + return row; +} +void LightweightDatabaseServer::RemoveRowsFromIP(PlayerID playerId) +{ + // Remove rows for tables that do so on a system disconnect + DatabaseTable *databaseTable; + DataStructures::List removeList; + DataStructures::Page *cur; + unsigned i,j; + for (i=0; i < database.Size(); i++) + { + databaseTable=database[i]; + if (databaseTable->removeRowOnDisconnect) + { + DataStructures::BPlusTree &rows = databaseTable->table.GetRows(); + cur = rows.GetListHead(); + while (cur) + { + // Mark dropped entities + for (j=0; j < (unsigned)cur->size; j++) + { + if (RowHasIP(cur->data[j], playerId, databaseTable->systemIdColumnIndex)) + removeList.Insert(cur->keys[j]); + } + cur=cur->next; + } + + for (j=0; j < removeList.Size(); j++) + databaseTable->table.RemoveRow(removeList[i]); + removeList.Clear(true); + } + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/multiplayer.h b/raknet/multiplayer.h new file mode 100644 index 0000000..c950a93 --- /dev/null +++ b/raknet/multiplayer.h @@ -0,0 +1,438 @@ +/// \file +/// \brief \b [Depreciated] Maps packet IDs to functions. I use this in the samples, but you shouldn't use it yourself. +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __MULTIPLAYER_H +#define __MULTIPLAYER_H + +struct Packet; +#define _DO_PRINTF +#include "Export.h" + +// #pragma deprecated(Multiplayer) + +/// \brief Maps packet IDs to functions +/// +/// \depreciated This will eventually be replaced with the plugin-system. +/// \sa PacketEnumerations.h +template +class RAK_DLL_EXPORT Multiplayer +{ +public: + + /// Default Constructor + + Multiplayer(); + + /// Destructor + + virtual ~Multiplayer(); + + + /// Call every update cycle to read packets from RakNet and parse them down to the appropriate function. + virtual void ProcessPackets( InterfaceType *interfaceType ); + +protected: + + virtual void ReceiveRemoteDisconnectionNotification( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveRemoteConnectionLost( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveRemoteNewIncomingConnection( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveRemoteExistingConnection( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveRemoteStaticData( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveConnectionBanned( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveConnectionRequestAccepted( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveNewIncomingConnection( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveConnectionAttemptFailed( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveConnectionResumption( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveNoFreeIncomingConnections( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveDisconnectionNotification( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveConnectionLost( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceivedStaticData( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveInvalidPassword( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveModifiedPacket( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveVoicePacket( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceivePong( Packet *packet, InterfaceType *interfaceType ); + virtual void ReceiveAdvertisedSystem( Packet *packet, InterfaceType *interfaceType ); + virtual void ProcessUnhandledPacket( Packet *packet, unsigned char packetIdentifier, InterfaceType *interfaceType ); + // static unsigned char GetPacketIdentifier( Packet *packet ); + +private: +}; + +// If you don't want to use distributed network objects, delete this include +//#include "DistributedNetworkObjectManager.h" +#include "PacketEnumerations.h" +#include "NetworkTypes.h" +#include +#include +#include "GetTime.h" + +#ifdef _DO_PRINTF +#include +#endif + +template +Multiplayer::Multiplayer() +{ +} + +template +Multiplayer::~Multiplayer() +{} + +template +void Multiplayer::ProcessPackets( InterfaceType *interfaceType ) +{ + Packet * p; + unsigned char packetIdentifier; + + p = interfaceType->Receive(); + + while ( p ) + { + if ( ( unsigned char ) p->data[ 0 ] == ID_TIMESTAMP ) + { + if ( p->length > sizeof( unsigned char ) + sizeof( unsigned int ) ) + packetIdentifier = ( unsigned char ) p->data[ sizeof( unsigned char ) + sizeof( unsigned int ) ]; + else + break; + } + else + packetIdentifier = ( unsigned char ) p->data[ 0 ]; + + // Check if this is a native packet + switch ( packetIdentifier ) + { + + case ID_REMOTE_DISCONNECTION_NOTIFICATION: + ReceiveRemoteDisconnectionNotification( p, interfaceType ); + break; + + case ID_REMOTE_CONNECTION_LOST: + ReceiveRemoteConnectionLost( p, interfaceType ); + break; + + case ID_REMOTE_NEW_INCOMING_CONNECTION: + ReceiveRemoteNewIncomingConnection( p, interfaceType ); + break; + + case ID_REMOTE_EXISTING_CONNECTION: + ReceiveRemoteExistingConnection( p, interfaceType ); + break; + + case ID_REMOTE_STATIC_DATA: + ReceiveRemoteStaticData( p, interfaceType ); + break; + + case ID_CONNECTION_BANNED: + ReceiveConnectionBanned( p, interfaceType ); + break; + + case ID_CONNECTION_REQUEST_ACCEPTED: + ReceiveConnectionRequestAccepted( p, interfaceType ); + break; + + case ID_NEW_INCOMING_CONNECTION: + ReceiveNewIncomingConnection( p, interfaceType ); + break; + + case ID_CONNECTION_ATTEMPT_FAILED: + ReceiveConnectionAttemptFailed( p, interfaceType ); + break; + + case ID_NO_FREE_INCOMING_CONNECTIONS: + ReceiveNoFreeIncomingConnections( p, interfaceType ); + break; + + case ID_DISCONNECTION_NOTIFICATION: + ReceiveDisconnectionNotification( p, interfaceType ); + break; + + case ID_CONNECTION_LOST: + ReceiveConnectionLost( p, interfaceType ); + break; + + case ID_RECEIVED_STATIC_DATA: + ReceivedStaticData( p, interfaceType ); + break; + + case ID_INVALID_PASSWORD: + ReceiveInvalidPassword( p, interfaceType ); + break; + + case ID_MODIFIED_PACKET: + ReceiveModifiedPacket( p, interfaceType ); + break; + + case ID_PONG: + ReceivePong( p, interfaceType ); + break; + + case ID_ADVERTISE_SYSTEM: + ReceiveAdvertisedSystem( p, interfaceType ); + break; + + default: + // If not a native packet send it to ProcessUnhandledPacket which should have been written by the user + ProcessUnhandledPacket( p, packetIdentifier, interfaceType ); + break; + } + + interfaceType->DeallocatePacket( p ); + + p = interfaceType->Receive(); + } +} + +template +void Multiplayer::ProcessUnhandledPacket( Packet *p, unsigned char packetIdentifier, InterfaceType *interfaceType ) +{ + // This class should have been overrided to handle user defined packets +#ifdef _DO_PRINTF + // Uncomment this to show output as integers + /* int i; + static unsigned messageNumber=0; + // Raw output (nonstring) + printf("Multiplayer::ProcessUnhandledPacket (%i) (%i): ", messageNumber++, p->length); + for (i=0; i < p->length; i++) + printf("%i ",p->data[i]); + printf("\n"); + */ + + // Uncomment this to show output as a string + + // Raw output (string) + printf( "(%i) %s\n", p->data[0], p->data ); + +#endif +} + +template +void Multiplayer::ReceiveRemoteDisconnectionNotification( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has disconnected. Client only. +#ifdef _DO_PRINTF + printf( "ID_REMOTE_DISCONNECTION_NOTIFICATION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_DISCONNECTION_NOTIFICATION,interfaceType); +} + +template +void Multiplayer::ReceiveRemoteConnectionLost( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has been dropped by the server. Client only. +#ifdef _DO_PRINTF + printf( "ID_REMOTE_CONNECTION_LOST from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_CONNECTION_LOST,interfaceType); +} + +template +void Multiplayer::ReceiveRemoteNewIncomingConnection( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has connected. Client only. +#ifdef _DO_PRINTF + printf( "ID_REMOTE_NEW_INCOMING_CONNECTION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_NEW_INCOMING_CONNECTION,interfaceType); +} + +template +void Multiplayer::ReceiveRemoteExistingConnection( Packet *packet, InterfaceType *interfaceType ) +{ + // We just connected to the server and are getting a list of players already connected + // Note due to thread timing you might get both this and ID_REMOTE_NEW_INCOMING_CONNECTION when first connecting. + // Client only. +#ifdef _DO_PRINTF + printf( "ID_REMOTE_EXISTING_CONNECTION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_EXISTING_CONNECTION,interfaceType); +} + +template +void Multiplayer::ReceiveRemoteStaticData( Packet *packet, InterfaceType *interfaceType ) +{ + // A client got the remote static data for another system + // Client only. +#ifdef _DO_PRINTF + printf( "ID_REMOTE_STATIC_DATA from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_EXISTING_CONNECTION,interfaceType); +} + +template +void Multiplayer::ReceiveConnectionBanned( Packet *packet, InterfaceType *interfaceType ) +{ + // We are banned from connecting to the system specified in packet->playerId + // Peer or client +#ifdef _DO_PRINTF + printf( "ID_CONNECTION_BANNED from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_REMOTE_EXISTING_CONNECTION,interfaceType); +} + + +template +void Multiplayer::ReceiveConnectionRequestAccepted( Packet *packet, InterfaceType *interfaceType ) +{ + // Our request to connect to another system has been accepted. Client only. +#ifdef _DO_PRINTF + printf( "ID_CONNECTION_REQUEST_ACCEPTED from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_CONNECTION_REQUEST_ACCEPTED,interfaceType); +} + +template +void Multiplayer::ReceiveNewIncomingConnection( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has requested to connect to us, which we have accepted. Server or peer only. +#ifdef _DO_PRINTF + printf( "ID_NEW_INCOMING_CONNECTION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_NEW_INCOMING_CONNECTION,interfaceType); + + // This will send all existing distributed objects to the new player +#ifdef __DISTRIBUTED_NETWORK_OBJECT_MANAGER_H + + DistributedNetworkObjectManager::Instance() ->SendAllDistributedObjects( packet->playerId ); +#endif +} + +template +void Multiplayer::ReceiveConnectionAttemptFailed( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has requested to connect to us, which we have accepted. Server or peer only. +#ifdef _DO_PRINTF + printf( "ID_CONNECTION_ATTEMPT_FAILED from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_NEW_INCOMING_CONNECTION,interfaceType); +} + +template +void Multiplayer::ReceiveConnectionResumption( Packet *packet, InterfaceType *interfaceType ) +{ + // Someone who was already connected to us connected again. Server or peer only. +#ifdef _DO_PRINTF + printf( "ID_CONNECTION_RESUMPTION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_CONNECTION_RESUMPTION,interfaceType); +} + +template +void Multiplayer::ReceiveNoFreeIncomingConnections( Packet *packet, InterfaceType *interfaceType ) +{ + // The system we connected to has no free slots to connect to + // Set free slots by calling SetMaximumIncomingConnections + // Client or peer only. +#ifdef _DO_PRINTF + printf( "ID_NO_FREE_INCOMING_CONNECTIONS from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_NO_FREE_INCOMING_CONNECTIONS,interfaceType); +} + +template +void Multiplayer::ReceiveDisconnectionNotification( Packet *packet, InterfaceType *interfaceType ) +{ + // A remote system terminated the connection. packet->playerId specifies which remote system +#ifdef _DO_PRINTF + printf( "ID_DISCONNECTION_NOTIFICATION from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_DISCONNECTION_NOTIFICATION,interfaceType); +} + +template +void Multiplayer::ReceiveConnectionLost( Packet *packet, InterfaceType *interfaceType ) +{ + // The network cannot send reliable packets so has terminated the connection. packet->playerId specifies which remote system +#ifdef _DO_PRINTF + printf( "ID_CONNECTION_LOST from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_CONNECTION_LOST,interfaceType); +} + +template +void Multiplayer::ReceivedStaticData( Packet *packet, InterfaceType *interfaceType ) +{ + // Another system has just sent their static data to us (which we recorded automatically) +#ifdef _DO_PRINTF + printf( "ID_RECEIVED_STATIC_DATA from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_RECEIVED_STATIC_DATA,interfaceType); +} + +template +void Multiplayer::ReceiveInvalidPassword( Packet *packet, InterfaceType *interfaceType ) +{ + // Our connection to another system was refused because the passwords do not match +#ifdef _DO_PRINTF + printf( "ID_INVALID_PASSWORD from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_INVALID_PASSWORD,interfaceType); +} + +template +void Multiplayer::ReceiveModifiedPacket( Packet *packet, InterfaceType *interfaceType ) +{ + // The network layer has detected packet tampering + // This does NOT automatically close the connection +#ifdef _DO_PRINTF + printf( "ID_MODIFIED_PACKET from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_MODIFIED_PACKET,interfaceType); +} + +template +void Multiplayer::ReceiveVoicePacket( Packet *packet, InterfaceType *interfaceType ) +{ + // We got a voice packet +#ifdef _DO_PRINTF + printf( "ID_VOICE_PACKET from PlayerID:%u:%u on %p.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + // ProcessUnhandledPacket(packet, ID_VOICE_PACKET,interfaceType); +} + +template +void Multiplayer::ReceivePong( Packet *packet, InterfaceType *interfaceType ) +{ + // Peer or client. Response from a ping for an unconnected system. +#ifdef _DO_PRINTF + RakNetTime time, dataLength; + memcpy( ( char* ) & time, packet->data + sizeof( unsigned char ), sizeof( unsigned int ) ); + dataLength = packet->length - sizeof( unsigned char ) - sizeof( unsigned int ); + printf( "ID_PONG from PlayerID:%u:%u on %p.\nPing is %i\nData is %i bytes long.\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType, time, dataLength ); + + if ( dataLength > 0 ) + printf( "Data is %s\n", packet->data + sizeof( unsigned char ) + sizeof( unsigned int ) ); + +#endif + // ProcessUnhandledPacket(packet, ID_PONG,interfaceType); +} + +template +void Multiplayer::ReceiveAdvertisedSystem( Packet *packet, InterfaceType *interfaceType ) +{ + // Got a packet from another RakNet system indicating that it exists. + // Currently this is used for the master server for the server to indicate its external + // IP to a client as well as open the NAT +#ifdef _DO_PRINTF + printf( "ID_ADVERTISED_SYSTEM from PlayerID:%u:%u on %p.\nIf you are running a client connecting to a server behind a NAT, you should\ncall Disconnect and connect to the to the IP specified by packet->playerId instead\n", packet->playerId.binaryAddress, packet->playerId.port, interfaceType ); +#endif + + // When you get this packet, you should disconnect if you are not already connected. Then connect + // To the IP / port given by packet->playerID. You can translate a PlayerID to an IP with PlayerIDToDottedIP +} + +#endif diff --git a/raknet/replicamanager.cpp b/raknet/replicamanager.cpp new file mode 100644 index 0000000..41ea7af --- /dev/null +++ b/raknet/replicamanager.cpp @@ -0,0 +1,1004 @@ +#include "ReplicaManager.h" +#include "RakPeerInterface.h" +#include "GetTime.h" +#include "PacketEnumerations.h" +#include "BitStream.h" +#include "Replica.h" +#include +#include +#include // For my debug printfs + +#ifdef _MSC_VER +#pragma warning( push ) +#endif + +int ReplicaManager::CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ) +{ + if (key < data.replica) + return -1; + if (key==data.replica) + return 0; + return 1; +} + +int ReplicaManager::RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ) +{ + if (key < data.replica) + return -1; + if (key==data.replica) + return 0; + return 1; +} + +int ReplicaManager::RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ) +{ + if (key < data.replica) + return -1; + if (key==data.replica) + return 0; + return 1; +} + +int ReplicaManager::ParticipantStructComp( const PlayerID &key, ReplicaManager::ParticipantStruct * const &data ) +{ + if (key < data->playerId) + return -1; + if (key==data->playerId) + return 0; + return 1; +} + +ReplicaManager::ReplicaManager() +{ + _constructionCB=0; + _sendDownloadCompleteCB=0; + _receiveDownloadCompleteCB=0; + rakPeer=0; + sendChannel=0; + autoParticipateNewConnections=false; + defaultScope=false; + autoConstructToNewParticipants=false; +} +ReplicaManager::~ReplicaManager() +{ + Clear(); +} +void ReplicaManager::SetAutoParticipateNewConnections(bool autoAdd) +{ + autoParticipateNewConnections=autoAdd; +} +void ReplicaManager::AddParticipant(PlayerID playerId) +{ + assert(playerId!=UNASSIGNED_PLAYER_ID); + + // If this player is already in the list of participants, just return. + ParticipantStruct *participantStruct; + participantStruct=GetParticipantByPlayerID(playerId); + if (participantStruct) + return; + + // Create a new participant with this playerID + participantStruct = new ParticipantStruct; + participantStruct->playerId=playerId; + + // Signal that when done sending SendConstruction for each existing object, we call sendDownloadCompleteCB + participantStruct->callDownloadCompleteCB=true; + + // Add the new participant to the list of participants + participantList.Insert(playerId,participantStruct); + + if (autoConstructToNewParticipants) + { + // Signal that we need to call SendConstruction for each existing object to this participant + unsigned i; + CommandStruct replicaAndCommand; + replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; + for (i=0; i < replicatedObjects.Size(); i++) + { + replicaAndCommand.replica=replicatedObjects[i].replica; + participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); + } + } +} +void ReplicaManager::RemoveParticipant(PlayerID playerId) +{ + assert(playerId!=UNASSIGNED_PLAYER_ID); + + // Find this participant by playerId + ParticipantStruct *participantStruct; + participantStruct=GetParticipantByPlayerID(playerId); + + // If found, remove and free this participant structure + if (participantStruct) + { + participantList.Remove(playerId); + delete participantStruct; + } +} + +void ReplicaManager::Construct(Replica *replica, bool isCopy, PlayerID playerId, bool broadcast) +{ + assert(replica); + + unsigned i; + ParticipantStruct *participantStruct; + CommandStruct replicaAndCommand; + unsigned index; + bool objectExists; + replicaAndCommand.replica=replica; + + ReferencePointer(replica); + + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + if ((broadcast==true && playerId!=participantStruct->playerId) || + (broadcast==false && playerId==participantStruct->playerId)) + { + if (participantStruct->remoteObjectList.HasData(replica)==false) + { + index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { +#ifdef _DEBUG + // Implicit is only used for objects that were not already registered. + assert(isCopy==false); +#endif + participantStruct->commandList[index].command|=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit + participantStruct->commandList[index].command&=0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; // Unset this bit + } + else + { + if (isCopy) + replicaAndCommand.command=REPLICA_IMPLICIT_CONSTRUCTION; // Set this bit + else + replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit + + participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); + } + } + } + } +} +void ReplicaManager::Destruct(Replica *replica, PlayerID playerId, bool broadcast) +{ + assert(replica); + + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + return; + + // For each existing participant, send a packet telling them of this object destruction + RakNet::BitStream outBitstream; + unsigned i,tempIndex; + bool replicaReferenced; + ParticipantStruct *participantStruct; + replicaReferenced=false; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && playerId!=participantStruct->playerId) || + (broadcast==false && playerId==participantStruct->playerId)) + { + // Send the destruction packet immediately + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_DESTRUCTION)) + { + outBitstream.Reset(); + outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_DESTRUCTION); + outBitstream.Write(replica->GetNetworkID()); + replica->SendDestruction(&outBitstream, participantStruct->playerId); + if (outBitstream.GetNumberOfBitsUsed()>0) + { + rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); + } + } + + // Remove any pending commands that reference this object, for this player + tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->commandList.RemoveAtIndex(tempIndex); + + // Remove any remote object state tracking for this object, for this player + tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); + } + else if (replicaReferenced==false) + { + // See if any commands or objects reference replica + if (participantStruct->commandList.HasData(replica)) + replicaReferenced=true; + else if (participantStruct->remoteObjectList.HasData(replica)) + replicaReferenced=true; + } + } + + // Remove replica from the list if no commands and no remote objects reference it + if (replicaReferenced==false) + replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); +} +void ReplicaManager::ReferencePointer(Replica *replica) +{ + // Start tracking this object, if we are not already + if (replicatedObjects.HasData(replica)==false) + { + RegisteredReplica replicaAndTime; + replicaAndTime.replica=replica; + replicaAndTime.lastDeserializeTrue=0; + replicaAndTime.allowedInterfaces=REPLICA_SET_ALL; + replicatedObjects.Insert(replica,replicaAndTime); + } +} +void ReplicaManager::DereferencePointer(Replica *replica) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + unsigned tempIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + return; + replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); + + ParticipantStruct *participantStruct; + unsigned i; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + // Remove any pending commands that reference this object for any player + tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->commandList.RemoveAtIndex(tempIndex); + + // Remove any remote object state tracking for this object for any player + tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); + } +} +void ReplicaManager::SetScope(Replica *replica, bool inScope, PlayerID playerId, bool broadcast) +{ + assert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + if (replicatedObjects.HasData(replica)==false) + ReferencePointer(replica); + + // For each player that we want, flag to call SendScopeChange if inScope is different from what they already have + unsigned i; + ParticipantStruct *participantStruct; + bool objectExists; + unsigned index; + CommandStruct replicaAndCommand; + if (inScope) + replicaAndCommand.command=REPLICA_SCOPE_TRUE; + else + replicaAndCommand.command=REPLICA_SCOPE_FALSE; + replicaAndCommand.replica=replica; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && playerId!=participantStruct->playerId) || + (broadcast==false && playerId==participantStruct->playerId)) + { + // If there is already a pending command for this object, add to it. Otherwise, add a new pending command + index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + // Update a pending command + if (inScope) + { + participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_FALSE; // Unset this bit + participantStruct->commandList[index].command|=REPLICA_SCOPE_TRUE; // Set this bit + } + else + { + participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_TRUE; // Unset this bit + participantStruct->commandList[index].command|=REPLICA_SCOPE_FALSE; // Set this bit + } + } + else + { + // Add a new command, since there are no pending commands for this object + participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); + } + } + } +} +void ReplicaManager::SignalSerializeNeeded(Replica *replica, PlayerID playerId, bool broadcast) +{ + assert(replica); + + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + if (replicatedObjects.HasData(replica)==false) + ReferencePointer(replica); + + // For each player that we want, if this object exists on that system, flag to call Serialize + // (this may not necessarily happen - it depends on if the object is inScope when Update actually processes it.) + unsigned i; + ParticipantStruct *participantStruct; + bool objectExists; + unsigned index; + CommandStruct replicaAndCommand; + replicaAndCommand.command=REPLICA_SERIALIZE; + replicaAndCommand.replica=replica; + for (i=0; i < participantList.Size(); i++) + { + participantStruct=participantList[i]; + + if ((broadcast==true && playerId!=participantStruct->playerId) || + (broadcast==false && playerId==participantStruct->playerId)) + { + // If there is already a pending command for this object, add to it. Otherwise, add a new pending command + index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + participantStruct->commandList[index].command|=REPLICA_SERIALIZE; // Set this bit + } + else + { + // Add a new command, since there are no pending commands for this object + participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); + } + } + } +} +void ReplicaManager::SetReceiveConstructionCB(ReplicaReturnResult (* constructionCB)(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller)) +{ + // Just overwrite the construction callback pointer + _constructionCB=constructionCB; +} +void ReplicaManager::SetDownloadCompleteCB(ReplicaReturnResult (* sendDownloadCompleteCB)(RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID senderId, ReplicaManager *caller), ReplicaReturnResult (* receiveDownloadCompleteCB)(RakNet::BitStream *inBitStream, PlayerID senderId, ReplicaManager *caller)) +{ + // Just overwrite the send and receive download complete pointers. + _sendDownloadCompleteCB=sendDownloadCompleteCB; + _receiveDownloadCompleteCB=receiveDownloadCompleteCB; +} +void ReplicaManager::SetSendChannel(unsigned char channel) +{ + // Change the send channel from the default of 0 + sendChannel=channel; +} +void ReplicaManager::SetAutoConstructToNewParticipants(bool autoConstruct) +{ + autoConstructToNewParticipants=autoConstruct; +} +void ReplicaManager::SetDefaultScope(bool scope) +{ + defaultScope=scope; +} +void ReplicaManager::EnableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + } + replicatedObjects[replicatedObjectsIndex].allowedInterfaces|=interfaceFlags; +} +void ReplicaManager::DisableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) +{ + bool objectExists; + unsigned replicatedObjectsIndex; + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry + // About the order of operations. + ReferencePointer(replica); + replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); + } + replicatedObjects[replicatedObjectsIndex].allowedInterfaces&= 0xFF ^ interfaceFlags; +} +bool ReplicaManager::IsConstructed(Replica *replica, PlayerID playerId) +{ + ParticipantStruct *participantStruct = GetParticipantByPlayerID(playerId); + if (participantStruct) + { + bool objectExists; + participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + return objectExists; + } + return false; +} +bool ReplicaManager::IsInScope(Replica *replica, PlayerID playerId) +{ + ParticipantStruct *participantStruct = GetParticipantByPlayerID(playerId); + if (participantStruct) + { + bool objectExists; + unsigned remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + return participantStruct->remoteObjectList[remoteObjectListIndex].inScope; + } + return false; +} +unsigned ReplicaManager::GetReplicaCount(void) const +{ + return replicatedObjects.Size(); +} +Replica *ReplicaManager::GetReplicaAtIndex(unsigned index) +{ + return replicatedObjects[index].replica; +} +void ReplicaManager::Clear(void) +{ + // Free all memory + unsigned i; + for (i=0; i < participantList.Size(); i++) + delete participantList[i]; + participantList.Clear(); + replicatedObjects.Clear(); +} +void ReplicaManager::OnAttach(RakPeerInterface *peer) +{ + rakPeer=peer; +} +void ReplicaManager::Update(RakPeerInterface *peer) +{ + if (participantList.Size()==0) + return; + + unsigned participantIndex, remoteObjectListIndex, replicatedObjectsIndex; + ReplicaReturnResult res; + bool sendTimestamp; + ParticipantStruct *participantStruct; + unsigned commandListIndex; + RakNet::BitStream outBitstream, userDataBitstream; + RakNetTime currentTime; + bool objectExists; + PacketPriority priority; + PacketReliability reliability; + ReceivedCommand *receivedCommand; + Replica *replica; + unsigned char command; + currentTime=0; + + // For each participant + for (participantIndex=0; participantIndex < participantList.Size(); participantIndex++) + { + participantStruct = participantList[participantIndex]; + + // For each CommandStruct to send + for (commandListIndex=0; commandListIndex < participantStruct->commandList.Size(); commandListIndex++) + { + // Only call RakNet::GetTime() once because it's slow + if (currentTime==0) + currentTime=RakNet::GetTime(); + + replica=participantStruct->commandList[commandListIndex].replica; + command=participantStruct->commandList[commandListIndex].command; + replicatedObjectsIndex=replicatedObjects.GetIndexFromKey(replica, &objectExists); +#ifdef _DEBUG + assert(objectExists); +#endif + + // If construction is set, call SendConstruction. The only precondition is that the object was not already created, + // which was checked in ReplicaManager::Replicate + if (command & REPLICA_EXPLICIT_CONSTRUCTION) + { + if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_CONSTRUCTION) + { + userDataBitstream.Reset(); + sendTimestamp=false; + res=replica->SendConstruction(currentTime, participantStruct->playerId, &userDataBitstream, &sendTimestamp); + + if (res==REPLICA_PROCESSING_DONE) + { + outBitstream.Reset(); + // If SendConstruction returns true and writes to outBitStream, do this send. Clear the construction command. Then process the next command for this CommandStruct, if any. + if (sendTimestamp) + { + outBitstream.Write((unsigned char)ID_TIMESTAMP); + outBitstream.Write(currentTime); + } + outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_CONSTRUCTION); + // It's required to send an NetworkID if available. + // Problem: + // A->B->C + // | | + // D->E + // + // A creates. + // B->C->E->D->B will cycle forever. + // Fix is to always include an networkID. Objects are not created if that object id already is present. + if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) + { + outBitstream.Write(true); + outBitstream.Write(replica->GetNetworkID()); + } + else + outBitstream.Write(false); + + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); + + // Turn off this bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_EXPLICIT_CONSTRUCTION; + + // Add the object to the participant's object list, indicating this object has been remotely created + RemoteObject remoteObject; + remoteObject.replica=replica; + remoteObject.inScope=defaultScope; + remoteObject.lastSendTime=0; + // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, + // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. + participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject); + } + else if (res==REPLICA_PROCESS_LATER) + { + continue; + } + else // REPLICA_CANCEL_PROCESS + { + assert(res==REPLICA_CANCEL_PROCESS); + participantStruct->commandList[commandListIndex].command=0; + } + } + else + { + // Don't allow construction, or anything else for this object, as the construction send call is disallowed + participantStruct->commandList[commandListIndex].command=0; + } + } + else if (command & REPLICA_IMPLICIT_CONSTRUCTION) + { + // Turn off this bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; + + // Add the object to the participant's object list, indicating this object is assumed to be remotely created + RemoteObject remoteObject; + remoteObject.replica=replica; + remoteObject.inScope=defaultScope; + remoteObject.lastSendTime=0; + // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, + // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. + participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject); + } + + // Sends the download complete packet + // If callDownloadCompleteCB is true then check all the remaining objects starting at commandListIndex + // I scan every frame in case the callback returns false to delay, and after that time a new object is Replicated + if (participantStruct->callDownloadCompleteCB) + { + bool anyHasConstruction; + unsigned j; + anyHasConstruction=false; + for (j=0; j < participantStruct->commandList.Size(); j++) + { + if (participantStruct->commandList[j].command & REPLICA_EXPLICIT_CONSTRUCTION) + { + anyHasConstruction=true; + break; + } + } + // If none have REPLICA_EXPLICIT_CONSTRUCTION, send ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE and set callDownloadCompleteCB false + if (anyHasConstruction==false) + { + ReplicaReturnResult sendDLComplete; + userDataBitstream.Reset(); + if (_sendDownloadCompleteCB) + sendDLComplete=_sendDownloadCompleteCB(&userDataBitstream, currentTime, participantStruct->playerId, this); // If you return false, this will be called again next update + else + sendDLComplete=REPLICA_CANCEL_PROCESS; + if (sendDLComplete==REPLICA_PROCESSING_DONE) + { + outBitstream.Reset(); + outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); + participantStruct->callDownloadCompleteCB=false; + } + else if (sendDLComplete==REPLICA_CANCEL_PROCESS) + { + participantStruct->callDownloadCompleteCB=false; + } + else + { + assert(sendDLComplete==REPLICA_PROCESS_LATER); + // else REPLICA_PROCESS_LATER + } + + } + } + + // The remaining commands, SendScopeChange and Serialize, require the object the command references exists on the remote system, so check that + remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); + if (objectExists) + { + command = participantStruct->commandList[commandListIndex].command; + + // Process SendScopeChange. + if ((command & (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE))) + { + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + continue; // Not set yet so call this later. + + if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SCOPE_CHANGE) + { + bool scopeTrue = (command & REPLICA_SCOPE_TRUE)!=0; + + // Only send scope changes if the requested change is different from what they already have + if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope!=scopeTrue) + { + userDataBitstream.Reset(); + res=replica->SendScopeChange(scopeTrue, &userDataBitstream, currentTime, participantStruct->playerId); + + if (res==REPLICA_PROCESSING_DONE) + { + // If the user returns true and does write to outBitstream, do this send. Clear the scope change command. Then process the next command for this CommandStruct, if any. + outBitstream.Reset(); + outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_SCOPE_CHANGE); + outBitstream.Write(replica->GetNetworkID()); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); + + // Set the scope for this object and system + participantStruct->remoteObjectList[remoteObjectListIndex].inScope=scopeTrue; + + // If scope is true, turn on serialize, since you virtually always want to serialize when an object goes in scope + participantStruct->commandList[commandListIndex].command |= REPLICA_SERIALIZE; + + // Turn off these bits - Call is processed + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + } + else if (res==REPLICA_CANCEL_PROCESS) + { + // Turn off these bits - Call is canceled + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + } + else + { + // If the user returns false and the scope is currently set to false, just continue with another CommandStruct. Don't process serialization until scoping is resolved first. + if (scopeTrue==false) + continue; + + // If the user returns false and the scope is currently set to false, process the next command for this CommandStruct, if any. + } + } + } + else + { + // Turn off these bits - Call is disallowed + participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); + + // Set the scope - even if the actual send is disabled we might still be able to serialize. + participantStruct->remoteObjectList[remoteObjectListIndex].inScope=(command & REPLICA_SCOPE_TRUE)!=0; + } + } + + command = participantStruct->commandList[commandListIndex].command; + // Process Serialize + if ((participantStruct->commandList[commandListIndex].command & REPLICA_SERIALIZE)) + { + if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) + continue; // Not set yet so call this later. + + // If scope is currently false for this object in the RemoteObject list, cancel this serialize as the scope changed before the serialization went out + if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SERIALIZE)) + { + do + { + userDataBitstream.Reset(); + priority=HIGH_PRIORITY; + reliability=RELIABLE_ORDERED; + sendTimestamp=false; + res=replica->Serialize(&sendTimestamp, &userDataBitstream, participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime, &priority, &reliability, currentTime, participantStruct->playerId); + + if (res==REPLICA_PROCESSING_DONE || res==REPLICA_PROCESS_AGAIN) + { + participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime=currentTime; + + outBitstream.Reset(); + if (sendTimestamp) + { + outBitstream.Write((unsigned char)ID_TIMESTAMP); + outBitstream.Write(currentTime); + } + outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_SERIALIZE); + outBitstream.Write(replica->GetNetworkID()); + outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); + peer->Send(&outBitstream, priority, reliability, sendChannel, participantStruct->playerId, false); + + // Clear the serialize bit when done + if (res==REPLICA_PROCESSING_DONE) + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + // else res==REPLICA_PROCESS_AGAIN so it will repeat the enclosing do {} while(); loop + } + else if (res==REPLICA_CANCEL_PROCESS) + { + // Clear the serialize bit + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + } + else + { + // if the user returns REPLICA_PROCESS_LATER, just continue with another CommandStruct. + assert(res==REPLICA_PROCESS_LATER); + } + } while(res==REPLICA_PROCESS_AGAIN); + } + else + { + // Cancel this serialize + participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; + } + } + } + } + + // Go through the command list and delete all cleared commands, from back to front. It is more efficient to do this than to delete them as we process + commandListIndex=participantStruct->commandList.Size(); + if (commandListIndex>0) + { +#ifdef _MSC_VER +#pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant +#endif + while (1) + { + if (participantStruct->commandList[commandListIndex-1].command==0) + { + // If this is the last item in the list, and it probably is, then it just changes a number rather than shifts the entire array + participantStruct->commandList.RemoveAtIndex(commandListIndex-1); + } + + if (--commandListIndex==0) + break; + } + } + + // Now process queued receives + while (participantStruct->pendingCommands.Size()) + { + receivedCommand=participantStruct->pendingCommands.Pop(); + participantStruct=GetParticipantByPlayerID(receivedCommand->playerId); + if (participantStruct) + { + res=ProcessReceivedCommand(participantStruct, receivedCommand); + // Returning false means process this command again later + if (res==REPLICA_PROCESS_LATER) + { + // Push the command back in the queue + participantStruct->pendingCommands.PushAtHead(receivedCommand); + + // Stop processing, because all processing is in order + break; + } + else + { + assert(res==REPLICA_CANCEL_PROCESS); + } + } + + // Done with this command, so delete it + delete receivedCommand->userData; + delete receivedCommand; + } + } +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void ReplicaManager::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) +{ + RemoveParticipant(playerId); +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif +void ReplicaManager::OnDisconnect(RakPeerInterface *peer) +{ + Clear(); +} +PluginReceiveResult ReplicaManager::OnReceive(RakPeerInterface *peer, Packet *packet) +{ + unsigned char packetIdentifier; + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + if ( packet->length > sizeof( unsigned char ) + sizeof( unsigned int ) ) + packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( unsigned int ) ]; + else + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + else + packetIdentifier = ( unsigned char ) packet->data[ 0 ]; + + switch (packetIdentifier) + { + case ID_NEW_INCOMING_CONNECTION: + case ID_CONNECTION_REQUEST_ACCEPTED: + if (autoParticipateNewConnections) + AddParticipant(packet->playerId); + return RR_CONTINUE_PROCESSING; + case ID_DISCONNECTION_NOTIFICATION: + case ID_CONNECTION_LOST: + OnCloseConnection(peer, packet->playerId); + return RR_CONTINUE_PROCESSING; + case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE: + if (_receiveDownloadCompleteCB==0) + { + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + case ID_REPLICA_MANAGER_CONSTRUCTION: + case ID_REPLICA_MANAGER_DESTRUCTION: + case ID_REPLICA_MANAGER_SCOPE_CHANGE: + case ID_REPLICA_MANAGER_SERIALIZE: + { + ParticipantStruct *participantStruct; + bool hasNetworkId; + ReceivedCommand receivedCommand; + bool b=true; + RakNet::BitStream inBitstream(packet->data, packet->length, false); + // SetWriteOffset is used here to get around a design flaw, where I should have had the bitstream constructor take bits, rather than bytes + // It sets the actual number of bits in the packet + inBitstream.SetWriteOffset(packet->bitSize); + receivedCommand.playerId=packet->playerId; + receivedCommand.command=packetIdentifier; + + if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) + { + inBitstream.IgnoreBits(8); + b=inBitstream.Read(receivedCommand.u1); + } + else + receivedCommand.u1=0; + inBitstream.IgnoreBits(8); // Ignore the packet id + receivedCommand.networkID=UNASSIGNED_NETWORK_ID; + if (packetIdentifier==ID_REPLICA_MANAGER_CONSTRUCTION) // ID_REPLICA_MANAGER_CONSTRUCTION has an optional networkID + { + b=inBitstream.Read(hasNetworkId); + if (hasNetworkId) + b=inBitstream.Read(receivedCommand.networkID); + } + else if (packetIdentifier!=ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) + { + b=inBitstream.Read(receivedCommand.networkID); // Other packets always have an networkID + } + + if (b==false) + { + // Invalid packet +#ifdef _DEBUG + assert(0); +#endif + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + receivedCommand.userData=&inBitstream; + participantStruct=GetParticipantByPlayerID(receivedCommand.playerId); + if (participantStruct) + { + // .Size()>0 is because commands are always processed in order. If a command is delayed, no further commands are processed. + // ProcessReceivedCommand(...)==false means that the use signaled to delay a command + if (participantStruct->pendingCommands.Size()>0 || ProcessReceivedCommand(participantStruct, &receivedCommand)==REPLICA_PROCESS_LATER) + { + // Copy the data and add this to a queue that will call ProcessReceivedCommand again in Update. + + // Allocate and copy structure + ReceivedCommand *rc = new ReceivedCommand; + memcpy(rc, &receivedCommand, sizeof(ReceivedCommand)); + + // Allocate and copy inBitstream remaining data + rc->userData = new RakNet::BitStream; + rc->userData->Write(&inBitstream, inBitstream.GetNumberOfBitsUsed()); + + participantStruct->pendingCommands.Push(rc); + } + } + + return RR_STOP_PROCESSING_AND_DEALLOCATE; + } + } + + return RR_CONTINUE_PROCESSING; +} + +ReplicaManager::ParticipantStruct* ReplicaManager::GetParticipantByPlayerID(const PlayerID playerId) const +{ + bool objectExists; + unsigned index; + index = participantList.GetIndexFromKey(playerId, &objectExists); + if (objectExists==false) + return 0; + return participantList[index]; +} +#ifdef _MSC_VER +#pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized +#endif +ReplicaReturnResult ReplicaManager::ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand) +{ + Replica *replica = (Replica*) NetworkIDGenerator::GET_BASE_OBJECT_FROM_ID(receivedCommand->networkID); + + bool objectExists; + unsigned index; + ReplicaReturnResult b; + if (replica) + { + index = replicatedObjects.GetIndexFromKey(replica, &objectExists); + if (objectExists==false) + { + // Two ways to handle this. One is to autoregister the object - but this is dangerous in that if you have a network object that is not + // using the replica system it would be of the wrong type, read read invalid memory, and crash. + // Construct(replica, playerId, broadcast, true); + + // The other way is to assert and do nothing. The disadvantage is now the user has to register the object in order to receive calls on it. +#ifdef _DEBUG + // If this assert hits, call ReferencePointer on your replica pointer after it is created. + assert(0); +#endif + return REPLICA_CANCEL_PROCESS; + } + } + + if (receivedCommand->command==ID_REPLICA_MANAGER_SERIALIZE) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SERIALIZE)) + { + b=replica->Deserialize(receivedCommand->userData, receivedCommand->u1, replicatedObjects[index].lastDeserializeTrue, receivedCommand->playerId); + if (b==REPLICA_PROCESSING_DONE) + replicatedObjects[index].lastDeserializeTrue=RakNet::GetTime(); + return b; + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_CONSTRUCTION) + { + // If networkID already exists on this system, ignore the packet + if (replica==0) + { +#ifdef _DEBUG + assert(_constructionCB); +#endif + // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB + return _constructionCB(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, receivedCommand->playerId, this); + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_SCOPE_CHANGE) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SCOPE_CHANGE)) + { + return replica->ReceiveScopeChange(receivedCommand->userData, receivedCommand->playerId); + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_DESTRUCTION) + { + if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_DESTRUCTION)) + { + return replica->ReceiveDestruction(receivedCommand->userData, receivedCommand->playerId); + } + } + else if (receivedCommand->command==ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) + { + if (_receiveDownloadCompleteCB) + { + return _receiveDownloadCompleteCB(receivedCommand->userData, receivedCommand->playerId, this); + } + } + + return REPLICA_CANCEL_PROCESS; +} + +ReplicaManager::ParticipantStruct::~ParticipantStruct() +{ + ReceivedCommand *receivedCommand; + while ( pendingCommands.Size() ) + { + receivedCommand=pendingCommands.Pop(); + delete receivedCommand->userData; + delete receivedCommand; + } +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif diff --git a/raknet/rijndael-boxes.h b/raknet/rijndael-boxes.h new file mode 100644 index 0000000..c49c2eb --- /dev/null +++ b/raknet/rijndael-boxes.h @@ -0,0 +1,949 @@ +word8 Logtable[256] = { + 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, +100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, +125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, +101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, +150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, +102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, +126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, + 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, +175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, + 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, +127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, +204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, +151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, + 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, + 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, +103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7 +}; + +word8 Alogtable[256] = { + 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, + 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, +229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, + 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, + 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, +131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, +181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, +254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, +251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, +195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, +159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, +155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, +252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, + 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, + 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, + 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1 +}; + +word8 S[256] = { + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, +202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, +183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, + 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, + 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, + 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, +208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, + 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, +205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, + 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, +224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, +231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, +186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, +112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, +225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, +140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 +}; + +word8 Si[256] = { + 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, 251, +124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, 233, 203, + 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, 250, 195, 78, + 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, 109, 139, 209, 37, +114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, 204, 93, 101, 182, 146, +108, 112, 72, 80, 253, 237, 185, 218, 94, 21, 70, 87, 167, 141, 157, 132, +144, 216, 171, 0, 140, 188, 211, 10, 247, 228, 88, 5, 184, 179, 69, 6, +208, 44, 30, 143, 202, 63, 15, 2, 193, 175, 189, 3, 1, 19, 138, 107, + 58, 145, 17, 65, 79, 103, 220, 234, 151, 242, 207, 206, 240, 180, 230, 115, +150, 172, 116, 34, 231, 173, 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, + 71, 241, 26, 113, 29, 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, +252, 86, 62, 75, 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, + 31, 221, 168, 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, + 96, 81, 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, +160, 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, + 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12, 125 +}; + +word8 T1[256][4] = { +{0xc6,0x63,0x63,0xa5}, {0xf8,0x7c,0x7c,0x84}, {0xee,0x77,0x77,0x99}, {0xf6,0x7b,0x7b,0x8d}, +{0xff,0xf2,0xf2,0x0d}, {0xd6,0x6b,0x6b,0xbd}, {0xde,0x6f,0x6f,0xb1}, {0x91,0xc5,0xc5,0x54}, +{0x60,0x30,0x30,0x50}, {0x02,0x01,0x01,0x03}, {0xce,0x67,0x67,0xa9}, {0x56,0x2b,0x2b,0x7d}, +{0xe7,0xfe,0xfe,0x19}, {0xb5,0xd7,0xd7,0x62}, {0x4d,0xab,0xab,0xe6}, {0xec,0x76,0x76,0x9a}, +{0x8f,0xca,0xca,0x45}, {0x1f,0x82,0x82,0x9d}, {0x89,0xc9,0xc9,0x40}, {0xfa,0x7d,0x7d,0x87}, +{0xef,0xfa,0xfa,0x15}, {0xb2,0x59,0x59,0xeb}, {0x8e,0x47,0x47,0xc9}, {0xfb,0xf0,0xf0,0x0b}, +{0x41,0xad,0xad,0xec}, {0xb3,0xd4,0xd4,0x67}, {0x5f,0xa2,0xa2,0xfd}, {0x45,0xaf,0xaf,0xea}, +{0x23,0x9c,0x9c,0xbf}, {0x53,0xa4,0xa4,0xf7}, {0xe4,0x72,0x72,0x96}, {0x9b,0xc0,0xc0,0x5b}, +{0x75,0xb7,0xb7,0xc2}, {0xe1,0xfd,0xfd,0x1c}, {0x3d,0x93,0x93,0xae}, {0x4c,0x26,0x26,0x6a}, +{0x6c,0x36,0x36,0x5a}, {0x7e,0x3f,0x3f,0x41}, {0xf5,0xf7,0xf7,0x02}, {0x83,0xcc,0xcc,0x4f}, +{0x68,0x34,0x34,0x5c}, {0x51,0xa5,0xa5,0xf4}, {0xd1,0xe5,0xe5,0x34}, {0xf9,0xf1,0xf1,0x08}, +{0xe2,0x71,0x71,0x93}, {0xab,0xd8,0xd8,0x73}, {0x62,0x31,0x31,0x53}, {0x2a,0x15,0x15,0x3f}, +{0x08,0x04,0x04,0x0c}, {0x95,0xc7,0xc7,0x52}, {0x46,0x23,0x23,0x65}, {0x9d,0xc3,0xc3,0x5e}, +{0x30,0x18,0x18,0x28}, {0x37,0x96,0x96,0xa1}, {0x0a,0x05,0x05,0x0f}, {0x2f,0x9a,0x9a,0xb5}, +{0x0e,0x07,0x07,0x09}, {0x24,0x12,0x12,0x36}, {0x1b,0x80,0x80,0x9b}, {0xdf,0xe2,0xe2,0x3d}, +{0xcd,0xeb,0xeb,0x26}, {0x4e,0x27,0x27,0x69}, {0x7f,0xb2,0xb2,0xcd}, {0xea,0x75,0x75,0x9f}, +{0x12,0x09,0x09,0x1b}, {0x1d,0x83,0x83,0x9e}, {0x58,0x2c,0x2c,0x74}, {0x34,0x1a,0x1a,0x2e}, +{0x36,0x1b,0x1b,0x2d}, {0xdc,0x6e,0x6e,0xb2}, {0xb4,0x5a,0x5a,0xee}, {0x5b,0xa0,0xa0,0xfb}, +{0xa4,0x52,0x52,0xf6}, {0x76,0x3b,0x3b,0x4d}, {0xb7,0xd6,0xd6,0x61}, {0x7d,0xb3,0xb3,0xce}, +{0x52,0x29,0x29,0x7b}, {0xdd,0xe3,0xe3,0x3e}, {0x5e,0x2f,0x2f,0x71}, {0x13,0x84,0x84,0x97}, +{0xa6,0x53,0x53,0xf5}, {0xb9,0xd1,0xd1,0x68}, {0x00,0x00,0x00,0x00}, {0xc1,0xed,0xed,0x2c}, +{0x40,0x20,0x20,0x60}, {0xe3,0xfc,0xfc,0x1f}, {0x79,0xb1,0xb1,0xc8}, {0xb6,0x5b,0x5b,0xed}, +{0xd4,0x6a,0x6a,0xbe}, {0x8d,0xcb,0xcb,0x46}, {0x67,0xbe,0xbe,0xd9}, {0x72,0x39,0x39,0x4b}, +{0x94,0x4a,0x4a,0xde}, {0x98,0x4c,0x4c,0xd4}, {0xb0,0x58,0x58,0xe8}, {0x85,0xcf,0xcf,0x4a}, +{0xbb,0xd0,0xd0,0x6b}, {0xc5,0xef,0xef,0x2a}, {0x4f,0xaa,0xaa,0xe5}, {0xed,0xfb,0xfb,0x16}, +{0x86,0x43,0x43,0xc5}, {0x9a,0x4d,0x4d,0xd7}, {0x66,0x33,0x33,0x55}, {0x11,0x85,0x85,0x94}, +{0x8a,0x45,0x45,0xcf}, {0xe9,0xf9,0xf9,0x10}, {0x04,0x02,0x02,0x06}, {0xfe,0x7f,0x7f,0x81}, +{0xa0,0x50,0x50,0xf0}, {0x78,0x3c,0x3c,0x44}, {0x25,0x9f,0x9f,0xba}, {0x4b,0xa8,0xa8,0xe3}, +{0xa2,0x51,0x51,0xf3}, {0x5d,0xa3,0xa3,0xfe}, {0x80,0x40,0x40,0xc0}, {0x05,0x8f,0x8f,0x8a}, +{0x3f,0x92,0x92,0xad}, {0x21,0x9d,0x9d,0xbc}, {0x70,0x38,0x38,0x48}, {0xf1,0xf5,0xf5,0x04}, +{0x63,0xbc,0xbc,0xdf}, {0x77,0xb6,0xb6,0xc1}, {0xaf,0xda,0xda,0x75}, {0x42,0x21,0x21,0x63}, +{0x20,0x10,0x10,0x30}, {0xe5,0xff,0xff,0x1a}, {0xfd,0xf3,0xf3,0x0e}, {0xbf,0xd2,0xd2,0x6d}, +{0x81,0xcd,0xcd,0x4c}, {0x18,0x0c,0x0c,0x14}, {0x26,0x13,0x13,0x35}, {0xc3,0xec,0xec,0x2f}, +{0xbe,0x5f,0x5f,0xe1}, {0x35,0x97,0x97,0xa2}, {0x88,0x44,0x44,0xcc}, {0x2e,0x17,0x17,0x39}, +{0x93,0xc4,0xc4,0x57}, {0x55,0xa7,0xa7,0xf2}, {0xfc,0x7e,0x7e,0x82}, {0x7a,0x3d,0x3d,0x47}, +{0xc8,0x64,0x64,0xac}, {0xba,0x5d,0x5d,0xe7}, {0x32,0x19,0x19,0x2b}, {0xe6,0x73,0x73,0x95}, +{0xc0,0x60,0x60,0xa0}, {0x19,0x81,0x81,0x98}, {0x9e,0x4f,0x4f,0xd1}, {0xa3,0xdc,0xdc,0x7f}, +{0x44,0x22,0x22,0x66}, {0x54,0x2a,0x2a,0x7e}, {0x3b,0x90,0x90,0xab}, {0x0b,0x88,0x88,0x83}, +{0x8c,0x46,0x46,0xca}, {0xc7,0xee,0xee,0x29}, {0x6b,0xb8,0xb8,0xd3}, {0x28,0x14,0x14,0x3c}, +{0xa7,0xde,0xde,0x79}, {0xbc,0x5e,0x5e,0xe2}, {0x16,0x0b,0x0b,0x1d}, {0xad,0xdb,0xdb,0x76}, +{0xdb,0xe0,0xe0,0x3b}, {0x64,0x32,0x32,0x56}, {0x74,0x3a,0x3a,0x4e}, {0x14,0x0a,0x0a,0x1e}, +{0x92,0x49,0x49,0xdb}, {0x0c,0x06,0x06,0x0a}, {0x48,0x24,0x24,0x6c}, {0xb8,0x5c,0x5c,0xe4}, +{0x9f,0xc2,0xc2,0x5d}, {0xbd,0xd3,0xd3,0x6e}, {0x43,0xac,0xac,0xef}, {0xc4,0x62,0x62,0xa6}, +{0x39,0x91,0x91,0xa8}, {0x31,0x95,0x95,0xa4}, {0xd3,0xe4,0xe4,0x37}, {0xf2,0x79,0x79,0x8b}, +{0xd5,0xe7,0xe7,0x32}, {0x8b,0xc8,0xc8,0x43}, {0x6e,0x37,0x37,0x59}, {0xda,0x6d,0x6d,0xb7}, +{0x01,0x8d,0x8d,0x8c}, {0xb1,0xd5,0xd5,0x64}, {0x9c,0x4e,0x4e,0xd2}, {0x49,0xa9,0xa9,0xe0}, +{0xd8,0x6c,0x6c,0xb4}, {0xac,0x56,0x56,0xfa}, {0xf3,0xf4,0xf4,0x07}, {0xcf,0xea,0xea,0x25}, +{0xca,0x65,0x65,0xaf}, {0xf4,0x7a,0x7a,0x8e}, {0x47,0xae,0xae,0xe9}, {0x10,0x08,0x08,0x18}, +{0x6f,0xba,0xba,0xd5}, {0xf0,0x78,0x78,0x88}, {0x4a,0x25,0x25,0x6f}, {0x5c,0x2e,0x2e,0x72}, +{0x38,0x1c,0x1c,0x24}, {0x57,0xa6,0xa6,0xf1}, {0x73,0xb4,0xb4,0xc7}, {0x97,0xc6,0xc6,0x51}, +{0xcb,0xe8,0xe8,0x23}, {0xa1,0xdd,0xdd,0x7c}, {0xe8,0x74,0x74,0x9c}, {0x3e,0x1f,0x1f,0x21}, +{0x96,0x4b,0x4b,0xdd}, {0x61,0xbd,0xbd,0xdc}, {0x0d,0x8b,0x8b,0x86}, {0x0f,0x8a,0x8a,0x85}, +{0xe0,0x70,0x70,0x90}, {0x7c,0x3e,0x3e,0x42}, {0x71,0xb5,0xb5,0xc4}, {0xcc,0x66,0x66,0xaa}, +{0x90,0x48,0x48,0xd8}, {0x06,0x03,0x03,0x05}, {0xf7,0xf6,0xf6,0x01}, {0x1c,0x0e,0x0e,0x12}, +{0xc2,0x61,0x61,0xa3}, {0x6a,0x35,0x35,0x5f}, {0xae,0x57,0x57,0xf9}, {0x69,0xb9,0xb9,0xd0}, +{0x17,0x86,0x86,0x91}, {0x99,0xc1,0xc1,0x58}, {0x3a,0x1d,0x1d,0x27}, {0x27,0x9e,0x9e,0xb9}, +{0xd9,0xe1,0xe1,0x38}, {0xeb,0xf8,0xf8,0x13}, {0x2b,0x98,0x98,0xb3}, {0x22,0x11,0x11,0x33}, +{0xd2,0x69,0x69,0xbb}, {0xa9,0xd9,0xd9,0x70}, {0x07,0x8e,0x8e,0x89}, {0x33,0x94,0x94,0xa7}, +{0x2d,0x9b,0x9b,0xb6}, {0x3c,0x1e,0x1e,0x22}, {0x15,0x87,0x87,0x92}, {0xc9,0xe9,0xe9,0x20}, +{0x87,0xce,0xce,0x49}, {0xaa,0x55,0x55,0xff}, {0x50,0x28,0x28,0x78}, {0xa5,0xdf,0xdf,0x7a}, +{0x03,0x8c,0x8c,0x8f}, {0x59,0xa1,0xa1,0xf8}, {0x09,0x89,0x89,0x80}, {0x1a,0x0d,0x0d,0x17}, +{0x65,0xbf,0xbf,0xda}, {0xd7,0xe6,0xe6,0x31}, {0x84,0x42,0x42,0xc6}, {0xd0,0x68,0x68,0xb8}, +{0x82,0x41,0x41,0xc3}, {0x29,0x99,0x99,0xb0}, {0x5a,0x2d,0x2d,0x77}, {0x1e,0x0f,0x0f,0x11}, +{0x7b,0xb0,0xb0,0xcb}, {0xa8,0x54,0x54,0xfc}, {0x6d,0xbb,0xbb,0xd6}, {0x2c,0x16,0x16,0x3a} +}; + +word8 T2[256][4] = { +{0xa5,0xc6,0x63,0x63}, {0x84,0xf8,0x7c,0x7c}, {0x99,0xee,0x77,0x77}, {0x8d,0xf6,0x7b,0x7b}, +{0x0d,0xff,0xf2,0xf2}, {0xbd,0xd6,0x6b,0x6b}, {0xb1,0xde,0x6f,0x6f}, {0x54,0x91,0xc5,0xc5}, +{0x50,0x60,0x30,0x30}, {0x03,0x02,0x01,0x01}, {0xa9,0xce,0x67,0x67}, {0x7d,0x56,0x2b,0x2b}, +{0x19,0xe7,0xfe,0xfe}, {0x62,0xb5,0xd7,0xd7}, {0xe6,0x4d,0xab,0xab}, {0x9a,0xec,0x76,0x76}, +{0x45,0x8f,0xca,0xca}, {0x9d,0x1f,0x82,0x82}, {0x40,0x89,0xc9,0xc9}, {0x87,0xfa,0x7d,0x7d}, +{0x15,0xef,0xfa,0xfa}, {0xeb,0xb2,0x59,0x59}, {0xc9,0x8e,0x47,0x47}, {0x0b,0xfb,0xf0,0xf0}, +{0xec,0x41,0xad,0xad}, {0x67,0xb3,0xd4,0xd4}, {0xfd,0x5f,0xa2,0xa2}, {0xea,0x45,0xaf,0xaf}, +{0xbf,0x23,0x9c,0x9c}, {0xf7,0x53,0xa4,0xa4}, {0x96,0xe4,0x72,0x72}, {0x5b,0x9b,0xc0,0xc0}, +{0xc2,0x75,0xb7,0xb7}, {0x1c,0xe1,0xfd,0xfd}, {0xae,0x3d,0x93,0x93}, {0x6a,0x4c,0x26,0x26}, +{0x5a,0x6c,0x36,0x36}, {0x41,0x7e,0x3f,0x3f}, {0x02,0xf5,0xf7,0xf7}, {0x4f,0x83,0xcc,0xcc}, +{0x5c,0x68,0x34,0x34}, {0xf4,0x51,0xa5,0xa5}, {0x34,0xd1,0xe5,0xe5}, {0x08,0xf9,0xf1,0xf1}, +{0x93,0xe2,0x71,0x71}, {0x73,0xab,0xd8,0xd8}, {0x53,0x62,0x31,0x31}, {0x3f,0x2a,0x15,0x15}, +{0x0c,0x08,0x04,0x04}, {0x52,0x95,0xc7,0xc7}, {0x65,0x46,0x23,0x23}, {0x5e,0x9d,0xc3,0xc3}, +{0x28,0x30,0x18,0x18}, {0xa1,0x37,0x96,0x96}, {0x0f,0x0a,0x05,0x05}, {0xb5,0x2f,0x9a,0x9a}, +{0x09,0x0e,0x07,0x07}, {0x36,0x24,0x12,0x12}, {0x9b,0x1b,0x80,0x80}, {0x3d,0xdf,0xe2,0xe2}, +{0x26,0xcd,0xeb,0xeb}, {0x69,0x4e,0x27,0x27}, {0xcd,0x7f,0xb2,0xb2}, {0x9f,0xea,0x75,0x75}, +{0x1b,0x12,0x09,0x09}, {0x9e,0x1d,0x83,0x83}, {0x74,0x58,0x2c,0x2c}, {0x2e,0x34,0x1a,0x1a}, +{0x2d,0x36,0x1b,0x1b}, {0xb2,0xdc,0x6e,0x6e}, {0xee,0xb4,0x5a,0x5a}, {0xfb,0x5b,0xa0,0xa0}, +{0xf6,0xa4,0x52,0x52}, {0x4d,0x76,0x3b,0x3b}, {0x61,0xb7,0xd6,0xd6}, {0xce,0x7d,0xb3,0xb3}, +{0x7b,0x52,0x29,0x29}, {0x3e,0xdd,0xe3,0xe3}, {0x71,0x5e,0x2f,0x2f}, {0x97,0x13,0x84,0x84}, +{0xf5,0xa6,0x53,0x53}, {0x68,0xb9,0xd1,0xd1}, {0x00,0x00,0x00,0x00}, {0x2c,0xc1,0xed,0xed}, +{0x60,0x40,0x20,0x20}, {0x1f,0xe3,0xfc,0xfc}, {0xc8,0x79,0xb1,0xb1}, {0xed,0xb6,0x5b,0x5b}, +{0xbe,0xd4,0x6a,0x6a}, {0x46,0x8d,0xcb,0xcb}, {0xd9,0x67,0xbe,0xbe}, {0x4b,0x72,0x39,0x39}, +{0xde,0x94,0x4a,0x4a}, {0xd4,0x98,0x4c,0x4c}, {0xe8,0xb0,0x58,0x58}, {0x4a,0x85,0xcf,0xcf}, +{0x6b,0xbb,0xd0,0xd0}, {0x2a,0xc5,0xef,0xef}, {0xe5,0x4f,0xaa,0xaa}, {0x16,0xed,0xfb,0xfb}, +{0xc5,0x86,0x43,0x43}, {0xd7,0x9a,0x4d,0x4d}, {0x55,0x66,0x33,0x33}, {0x94,0x11,0x85,0x85}, +{0xcf,0x8a,0x45,0x45}, {0x10,0xe9,0xf9,0xf9}, {0x06,0x04,0x02,0x02}, {0x81,0xfe,0x7f,0x7f}, +{0xf0,0xa0,0x50,0x50}, {0x44,0x78,0x3c,0x3c}, {0xba,0x25,0x9f,0x9f}, {0xe3,0x4b,0xa8,0xa8}, +{0xf3,0xa2,0x51,0x51}, {0xfe,0x5d,0xa3,0xa3}, {0xc0,0x80,0x40,0x40}, {0x8a,0x05,0x8f,0x8f}, +{0xad,0x3f,0x92,0x92}, {0xbc,0x21,0x9d,0x9d}, {0x48,0x70,0x38,0x38}, {0x04,0xf1,0xf5,0xf5}, +{0xdf,0x63,0xbc,0xbc}, {0xc1,0x77,0xb6,0xb6}, {0x75,0xaf,0xda,0xda}, {0x63,0x42,0x21,0x21}, +{0x30,0x20,0x10,0x10}, {0x1a,0xe5,0xff,0xff}, {0x0e,0xfd,0xf3,0xf3}, {0x6d,0xbf,0xd2,0xd2}, +{0x4c,0x81,0xcd,0xcd}, {0x14,0x18,0x0c,0x0c}, {0x35,0x26,0x13,0x13}, {0x2f,0xc3,0xec,0xec}, +{0xe1,0xbe,0x5f,0x5f}, {0xa2,0x35,0x97,0x97}, {0xcc,0x88,0x44,0x44}, {0x39,0x2e,0x17,0x17}, +{0x57,0x93,0xc4,0xc4}, {0xf2,0x55,0xa7,0xa7}, {0x82,0xfc,0x7e,0x7e}, {0x47,0x7a,0x3d,0x3d}, +{0xac,0xc8,0x64,0x64}, {0xe7,0xba,0x5d,0x5d}, {0x2b,0x32,0x19,0x19}, {0x95,0xe6,0x73,0x73}, +{0xa0,0xc0,0x60,0x60}, {0x98,0x19,0x81,0x81}, {0xd1,0x9e,0x4f,0x4f}, {0x7f,0xa3,0xdc,0xdc}, +{0x66,0x44,0x22,0x22}, {0x7e,0x54,0x2a,0x2a}, {0xab,0x3b,0x90,0x90}, {0x83,0x0b,0x88,0x88}, +{0xca,0x8c,0x46,0x46}, {0x29,0xc7,0xee,0xee}, {0xd3,0x6b,0xb8,0xb8}, {0x3c,0x28,0x14,0x14}, +{0x79,0xa7,0xde,0xde}, {0xe2,0xbc,0x5e,0x5e}, {0x1d,0x16,0x0b,0x0b}, {0x76,0xad,0xdb,0xdb}, +{0x3b,0xdb,0xe0,0xe0}, {0x56,0x64,0x32,0x32}, {0x4e,0x74,0x3a,0x3a}, {0x1e,0x14,0x0a,0x0a}, +{0xdb,0x92,0x49,0x49}, {0x0a,0x0c,0x06,0x06}, {0x6c,0x48,0x24,0x24}, {0xe4,0xb8,0x5c,0x5c}, +{0x5d,0x9f,0xc2,0xc2}, {0x6e,0xbd,0xd3,0xd3}, {0xef,0x43,0xac,0xac}, {0xa6,0xc4,0x62,0x62}, +{0xa8,0x39,0x91,0x91}, {0xa4,0x31,0x95,0x95}, {0x37,0xd3,0xe4,0xe4}, {0x8b,0xf2,0x79,0x79}, +{0x32,0xd5,0xe7,0xe7}, {0x43,0x8b,0xc8,0xc8}, {0x59,0x6e,0x37,0x37}, {0xb7,0xda,0x6d,0x6d}, +{0x8c,0x01,0x8d,0x8d}, {0x64,0xb1,0xd5,0xd5}, {0xd2,0x9c,0x4e,0x4e}, {0xe0,0x49,0xa9,0xa9}, +{0xb4,0xd8,0x6c,0x6c}, {0xfa,0xac,0x56,0x56}, {0x07,0xf3,0xf4,0xf4}, {0x25,0xcf,0xea,0xea}, +{0xaf,0xca,0x65,0x65}, {0x8e,0xf4,0x7a,0x7a}, {0xe9,0x47,0xae,0xae}, {0x18,0x10,0x08,0x08}, +{0xd5,0x6f,0xba,0xba}, {0x88,0xf0,0x78,0x78}, {0x6f,0x4a,0x25,0x25}, {0x72,0x5c,0x2e,0x2e}, +{0x24,0x38,0x1c,0x1c}, {0xf1,0x57,0xa6,0xa6}, {0xc7,0x73,0xb4,0xb4}, {0x51,0x97,0xc6,0xc6}, +{0x23,0xcb,0xe8,0xe8}, {0x7c,0xa1,0xdd,0xdd}, {0x9c,0xe8,0x74,0x74}, {0x21,0x3e,0x1f,0x1f}, +{0xdd,0x96,0x4b,0x4b}, {0xdc,0x61,0xbd,0xbd}, {0x86,0x0d,0x8b,0x8b}, {0x85,0x0f,0x8a,0x8a}, +{0x90,0xe0,0x70,0x70}, {0x42,0x7c,0x3e,0x3e}, {0xc4,0x71,0xb5,0xb5}, {0xaa,0xcc,0x66,0x66}, +{0xd8,0x90,0x48,0x48}, {0x05,0x06,0x03,0x03}, {0x01,0xf7,0xf6,0xf6}, {0x12,0x1c,0x0e,0x0e}, +{0xa3,0xc2,0x61,0x61}, {0x5f,0x6a,0x35,0x35}, {0xf9,0xae,0x57,0x57}, {0xd0,0x69,0xb9,0xb9}, +{0x91,0x17,0x86,0x86}, {0x58,0x99,0xc1,0xc1}, {0x27,0x3a,0x1d,0x1d}, {0xb9,0x27,0x9e,0x9e}, +{0x38,0xd9,0xe1,0xe1}, {0x13,0xeb,0xf8,0xf8}, {0xb3,0x2b,0x98,0x98}, {0x33,0x22,0x11,0x11}, +{0xbb,0xd2,0x69,0x69}, {0x70,0xa9,0xd9,0xd9}, {0x89,0x07,0x8e,0x8e}, {0xa7,0x33,0x94,0x94}, +{0xb6,0x2d,0x9b,0x9b}, {0x22,0x3c,0x1e,0x1e}, {0x92,0x15,0x87,0x87}, {0x20,0xc9,0xe9,0xe9}, +{0x49,0x87,0xce,0xce}, {0xff,0xaa,0x55,0x55}, {0x78,0x50,0x28,0x28}, {0x7a,0xa5,0xdf,0xdf}, +{0x8f,0x03,0x8c,0x8c}, {0xf8,0x59,0xa1,0xa1}, {0x80,0x09,0x89,0x89}, {0x17,0x1a,0x0d,0x0d}, +{0xda,0x65,0xbf,0xbf}, {0x31,0xd7,0xe6,0xe6}, {0xc6,0x84,0x42,0x42}, {0xb8,0xd0,0x68,0x68}, +{0xc3,0x82,0x41,0x41}, {0xb0,0x29,0x99,0x99}, {0x77,0x5a,0x2d,0x2d}, {0x11,0x1e,0x0f,0x0f}, +{0xcb,0x7b,0xb0,0xb0}, {0xfc,0xa8,0x54,0x54}, {0xd6,0x6d,0xbb,0xbb}, {0x3a,0x2c,0x16,0x16} +}; + +word8 T3[256][4] = { +{0x63,0xa5,0xc6,0x63}, {0x7c,0x84,0xf8,0x7c}, {0x77,0x99,0xee,0x77}, {0x7b,0x8d,0xf6,0x7b}, +{0xf2,0x0d,0xff,0xf2}, {0x6b,0xbd,0xd6,0x6b}, {0x6f,0xb1,0xde,0x6f}, {0xc5,0x54,0x91,0xc5}, +{0x30,0x50,0x60,0x30}, {0x01,0x03,0x02,0x01}, {0x67,0xa9,0xce,0x67}, {0x2b,0x7d,0x56,0x2b}, +{0xfe,0x19,0xe7,0xfe}, {0xd7,0x62,0xb5,0xd7}, {0xab,0xe6,0x4d,0xab}, {0x76,0x9a,0xec,0x76}, +{0xca,0x45,0x8f,0xca}, {0x82,0x9d,0x1f,0x82}, {0xc9,0x40,0x89,0xc9}, {0x7d,0x87,0xfa,0x7d}, +{0xfa,0x15,0xef,0xfa}, {0x59,0xeb,0xb2,0x59}, {0x47,0xc9,0x8e,0x47}, {0xf0,0x0b,0xfb,0xf0}, +{0xad,0xec,0x41,0xad}, {0xd4,0x67,0xb3,0xd4}, {0xa2,0xfd,0x5f,0xa2}, {0xaf,0xea,0x45,0xaf}, +{0x9c,0xbf,0x23,0x9c}, {0xa4,0xf7,0x53,0xa4}, {0x72,0x96,0xe4,0x72}, {0xc0,0x5b,0x9b,0xc0}, +{0xb7,0xc2,0x75,0xb7}, {0xfd,0x1c,0xe1,0xfd}, {0x93,0xae,0x3d,0x93}, {0x26,0x6a,0x4c,0x26}, +{0x36,0x5a,0x6c,0x36}, {0x3f,0x41,0x7e,0x3f}, {0xf7,0x02,0xf5,0xf7}, {0xcc,0x4f,0x83,0xcc}, +{0x34,0x5c,0x68,0x34}, {0xa5,0xf4,0x51,0xa5}, {0xe5,0x34,0xd1,0xe5}, {0xf1,0x08,0xf9,0xf1}, +{0x71,0x93,0xe2,0x71}, {0xd8,0x73,0xab,0xd8}, {0x31,0x53,0x62,0x31}, {0x15,0x3f,0x2a,0x15}, +{0x04,0x0c,0x08,0x04}, {0xc7,0x52,0x95,0xc7}, {0x23,0x65,0x46,0x23}, {0xc3,0x5e,0x9d,0xc3}, +{0x18,0x28,0x30,0x18}, {0x96,0xa1,0x37,0x96}, {0x05,0x0f,0x0a,0x05}, {0x9a,0xb5,0x2f,0x9a}, +{0x07,0x09,0x0e,0x07}, {0x12,0x36,0x24,0x12}, {0x80,0x9b,0x1b,0x80}, {0xe2,0x3d,0xdf,0xe2}, +{0xeb,0x26,0xcd,0xeb}, {0x27,0x69,0x4e,0x27}, {0xb2,0xcd,0x7f,0xb2}, {0x75,0x9f,0xea,0x75}, +{0x09,0x1b,0x12,0x09}, {0x83,0x9e,0x1d,0x83}, {0x2c,0x74,0x58,0x2c}, {0x1a,0x2e,0x34,0x1a}, +{0x1b,0x2d,0x36,0x1b}, {0x6e,0xb2,0xdc,0x6e}, {0x5a,0xee,0xb4,0x5a}, {0xa0,0xfb,0x5b,0xa0}, +{0x52,0xf6,0xa4,0x52}, {0x3b,0x4d,0x76,0x3b}, {0xd6,0x61,0xb7,0xd6}, {0xb3,0xce,0x7d,0xb3}, +{0x29,0x7b,0x52,0x29}, {0xe3,0x3e,0xdd,0xe3}, {0x2f,0x71,0x5e,0x2f}, {0x84,0x97,0x13,0x84}, +{0x53,0xf5,0xa6,0x53}, {0xd1,0x68,0xb9,0xd1}, {0x00,0x00,0x00,0x00}, {0xed,0x2c,0xc1,0xed}, +{0x20,0x60,0x40,0x20}, {0xfc,0x1f,0xe3,0xfc}, {0xb1,0xc8,0x79,0xb1}, {0x5b,0xed,0xb6,0x5b}, +{0x6a,0xbe,0xd4,0x6a}, {0xcb,0x46,0x8d,0xcb}, {0xbe,0xd9,0x67,0xbe}, {0x39,0x4b,0x72,0x39}, +{0x4a,0xde,0x94,0x4a}, {0x4c,0xd4,0x98,0x4c}, {0x58,0xe8,0xb0,0x58}, {0xcf,0x4a,0x85,0xcf}, +{0xd0,0x6b,0xbb,0xd0}, {0xef,0x2a,0xc5,0xef}, {0xaa,0xe5,0x4f,0xaa}, {0xfb,0x16,0xed,0xfb}, +{0x43,0xc5,0x86,0x43}, {0x4d,0xd7,0x9a,0x4d}, {0x33,0x55,0x66,0x33}, {0x85,0x94,0x11,0x85}, +{0x45,0xcf,0x8a,0x45}, {0xf9,0x10,0xe9,0xf9}, {0x02,0x06,0x04,0x02}, {0x7f,0x81,0xfe,0x7f}, +{0x50,0xf0,0xa0,0x50}, {0x3c,0x44,0x78,0x3c}, {0x9f,0xba,0x25,0x9f}, {0xa8,0xe3,0x4b,0xa8}, +{0x51,0xf3,0xa2,0x51}, {0xa3,0xfe,0x5d,0xa3}, {0x40,0xc0,0x80,0x40}, {0x8f,0x8a,0x05,0x8f}, +{0x92,0xad,0x3f,0x92}, {0x9d,0xbc,0x21,0x9d}, {0x38,0x48,0x70,0x38}, {0xf5,0x04,0xf1,0xf5}, +{0xbc,0xdf,0x63,0xbc}, {0xb6,0xc1,0x77,0xb6}, {0xda,0x75,0xaf,0xda}, {0x21,0x63,0x42,0x21}, +{0x10,0x30,0x20,0x10}, {0xff,0x1a,0xe5,0xff}, {0xf3,0x0e,0xfd,0xf3}, {0xd2,0x6d,0xbf,0xd2}, +{0xcd,0x4c,0x81,0xcd}, {0x0c,0x14,0x18,0x0c}, {0x13,0x35,0x26,0x13}, {0xec,0x2f,0xc3,0xec}, +{0x5f,0xe1,0xbe,0x5f}, {0x97,0xa2,0x35,0x97}, {0x44,0xcc,0x88,0x44}, {0x17,0x39,0x2e,0x17}, +{0xc4,0x57,0x93,0xc4}, {0xa7,0xf2,0x55,0xa7}, {0x7e,0x82,0xfc,0x7e}, {0x3d,0x47,0x7a,0x3d}, +{0x64,0xac,0xc8,0x64}, {0x5d,0xe7,0xba,0x5d}, {0x19,0x2b,0x32,0x19}, {0x73,0x95,0xe6,0x73}, +{0x60,0xa0,0xc0,0x60}, {0x81,0x98,0x19,0x81}, {0x4f,0xd1,0x9e,0x4f}, {0xdc,0x7f,0xa3,0xdc}, +{0x22,0x66,0x44,0x22}, {0x2a,0x7e,0x54,0x2a}, {0x90,0xab,0x3b,0x90}, {0x88,0x83,0x0b,0x88}, +{0x46,0xca,0x8c,0x46}, {0xee,0x29,0xc7,0xee}, {0xb8,0xd3,0x6b,0xb8}, {0x14,0x3c,0x28,0x14}, +{0xde,0x79,0xa7,0xde}, {0x5e,0xe2,0xbc,0x5e}, {0x0b,0x1d,0x16,0x0b}, {0xdb,0x76,0xad,0xdb}, +{0xe0,0x3b,0xdb,0xe0}, {0x32,0x56,0x64,0x32}, {0x3a,0x4e,0x74,0x3a}, {0x0a,0x1e,0x14,0x0a}, +{0x49,0xdb,0x92,0x49}, {0x06,0x0a,0x0c,0x06}, {0x24,0x6c,0x48,0x24}, {0x5c,0xe4,0xb8,0x5c}, +{0xc2,0x5d,0x9f,0xc2}, {0xd3,0x6e,0xbd,0xd3}, {0xac,0xef,0x43,0xac}, {0x62,0xa6,0xc4,0x62}, +{0x91,0xa8,0x39,0x91}, {0x95,0xa4,0x31,0x95}, {0xe4,0x37,0xd3,0xe4}, {0x79,0x8b,0xf2,0x79}, +{0xe7,0x32,0xd5,0xe7}, {0xc8,0x43,0x8b,0xc8}, {0x37,0x59,0x6e,0x37}, {0x6d,0xb7,0xda,0x6d}, +{0x8d,0x8c,0x01,0x8d}, {0xd5,0x64,0xb1,0xd5}, {0x4e,0xd2,0x9c,0x4e}, {0xa9,0xe0,0x49,0xa9}, +{0x6c,0xb4,0xd8,0x6c}, {0x56,0xfa,0xac,0x56}, {0xf4,0x07,0xf3,0xf4}, {0xea,0x25,0xcf,0xea}, +{0x65,0xaf,0xca,0x65}, {0x7a,0x8e,0xf4,0x7a}, {0xae,0xe9,0x47,0xae}, {0x08,0x18,0x10,0x08}, +{0xba,0xd5,0x6f,0xba}, {0x78,0x88,0xf0,0x78}, {0x25,0x6f,0x4a,0x25}, {0x2e,0x72,0x5c,0x2e}, +{0x1c,0x24,0x38,0x1c}, {0xa6,0xf1,0x57,0xa6}, {0xb4,0xc7,0x73,0xb4}, {0xc6,0x51,0x97,0xc6}, +{0xe8,0x23,0xcb,0xe8}, {0xdd,0x7c,0xa1,0xdd}, {0x74,0x9c,0xe8,0x74}, {0x1f,0x21,0x3e,0x1f}, +{0x4b,0xdd,0x96,0x4b}, {0xbd,0xdc,0x61,0xbd}, {0x8b,0x86,0x0d,0x8b}, {0x8a,0x85,0x0f,0x8a}, +{0x70,0x90,0xe0,0x70}, {0x3e,0x42,0x7c,0x3e}, {0xb5,0xc4,0x71,0xb5}, {0x66,0xaa,0xcc,0x66}, +{0x48,0xd8,0x90,0x48}, {0x03,0x05,0x06,0x03}, {0xf6,0x01,0xf7,0xf6}, {0x0e,0x12,0x1c,0x0e}, +{0x61,0xa3,0xc2,0x61}, {0x35,0x5f,0x6a,0x35}, {0x57,0xf9,0xae,0x57}, {0xb9,0xd0,0x69,0xb9}, +{0x86,0x91,0x17,0x86}, {0xc1,0x58,0x99,0xc1}, {0x1d,0x27,0x3a,0x1d}, {0x9e,0xb9,0x27,0x9e}, +{0xe1,0x38,0xd9,0xe1}, {0xf8,0x13,0xeb,0xf8}, {0x98,0xb3,0x2b,0x98}, {0x11,0x33,0x22,0x11}, +{0x69,0xbb,0xd2,0x69}, {0xd9,0x70,0xa9,0xd9}, {0x8e,0x89,0x07,0x8e}, {0x94,0xa7,0x33,0x94}, +{0x9b,0xb6,0x2d,0x9b}, {0x1e,0x22,0x3c,0x1e}, {0x87,0x92,0x15,0x87}, {0xe9,0x20,0xc9,0xe9}, +{0xce,0x49,0x87,0xce}, {0x55,0xff,0xaa,0x55}, {0x28,0x78,0x50,0x28}, {0xdf,0x7a,0xa5,0xdf}, +{0x8c,0x8f,0x03,0x8c}, {0xa1,0xf8,0x59,0xa1}, {0x89,0x80,0x09,0x89}, {0x0d,0x17,0x1a,0x0d}, +{0xbf,0xda,0x65,0xbf}, {0xe6,0x31,0xd7,0xe6}, {0x42,0xc6,0x84,0x42}, {0x68,0xb8,0xd0,0x68}, +{0x41,0xc3,0x82,0x41}, {0x99,0xb0,0x29,0x99}, {0x2d,0x77,0x5a,0x2d}, {0x0f,0x11,0x1e,0x0f}, +{0xb0,0xcb,0x7b,0xb0}, {0x54,0xfc,0xa8,0x54}, {0xbb,0xd6,0x6d,0xbb}, {0x16,0x3a,0x2c,0x16} +}; + +word8 T4[256][4] = { +{0x63,0x63,0xa5,0xc6}, {0x7c,0x7c,0x84,0xf8}, {0x77,0x77,0x99,0xee}, {0x7b,0x7b,0x8d,0xf6}, +{0xf2,0xf2,0x0d,0xff}, {0x6b,0x6b,0xbd,0xd6}, {0x6f,0x6f,0xb1,0xde}, {0xc5,0xc5,0x54,0x91}, +{0x30,0x30,0x50,0x60}, {0x01,0x01,0x03,0x02}, {0x67,0x67,0xa9,0xce}, {0x2b,0x2b,0x7d,0x56}, +{0xfe,0xfe,0x19,0xe7}, {0xd7,0xd7,0x62,0xb5}, {0xab,0xab,0xe6,0x4d}, {0x76,0x76,0x9a,0xec}, +{0xca,0xca,0x45,0x8f}, {0x82,0x82,0x9d,0x1f}, {0xc9,0xc9,0x40,0x89}, {0x7d,0x7d,0x87,0xfa}, +{0xfa,0xfa,0x15,0xef}, {0x59,0x59,0xeb,0xb2}, {0x47,0x47,0xc9,0x8e}, {0xf0,0xf0,0x0b,0xfb}, +{0xad,0xad,0xec,0x41}, {0xd4,0xd4,0x67,0xb3}, {0xa2,0xa2,0xfd,0x5f}, {0xaf,0xaf,0xea,0x45}, +{0x9c,0x9c,0xbf,0x23}, {0xa4,0xa4,0xf7,0x53}, {0x72,0x72,0x96,0xe4}, {0xc0,0xc0,0x5b,0x9b}, +{0xb7,0xb7,0xc2,0x75}, {0xfd,0xfd,0x1c,0xe1}, {0x93,0x93,0xae,0x3d}, {0x26,0x26,0x6a,0x4c}, +{0x36,0x36,0x5a,0x6c}, {0x3f,0x3f,0x41,0x7e}, {0xf7,0xf7,0x02,0xf5}, {0xcc,0xcc,0x4f,0x83}, +{0x34,0x34,0x5c,0x68}, {0xa5,0xa5,0xf4,0x51}, {0xe5,0xe5,0x34,0xd1}, {0xf1,0xf1,0x08,0xf9}, +{0x71,0x71,0x93,0xe2}, {0xd8,0xd8,0x73,0xab}, {0x31,0x31,0x53,0x62}, {0x15,0x15,0x3f,0x2a}, +{0x04,0x04,0x0c,0x08}, {0xc7,0xc7,0x52,0x95}, {0x23,0x23,0x65,0x46}, {0xc3,0xc3,0x5e,0x9d}, +{0x18,0x18,0x28,0x30}, {0x96,0x96,0xa1,0x37}, {0x05,0x05,0x0f,0x0a}, {0x9a,0x9a,0xb5,0x2f}, +{0x07,0x07,0x09,0x0e}, {0x12,0x12,0x36,0x24}, {0x80,0x80,0x9b,0x1b}, {0xe2,0xe2,0x3d,0xdf}, +{0xeb,0xeb,0x26,0xcd}, {0x27,0x27,0x69,0x4e}, {0xb2,0xb2,0xcd,0x7f}, {0x75,0x75,0x9f,0xea}, +{0x09,0x09,0x1b,0x12}, {0x83,0x83,0x9e,0x1d}, {0x2c,0x2c,0x74,0x58}, {0x1a,0x1a,0x2e,0x34}, +{0x1b,0x1b,0x2d,0x36}, {0x6e,0x6e,0xb2,0xdc}, {0x5a,0x5a,0xee,0xb4}, {0xa0,0xa0,0xfb,0x5b}, +{0x52,0x52,0xf6,0xa4}, {0x3b,0x3b,0x4d,0x76}, {0xd6,0xd6,0x61,0xb7}, {0xb3,0xb3,0xce,0x7d}, +{0x29,0x29,0x7b,0x52}, {0xe3,0xe3,0x3e,0xdd}, {0x2f,0x2f,0x71,0x5e}, {0x84,0x84,0x97,0x13}, +{0x53,0x53,0xf5,0xa6}, {0xd1,0xd1,0x68,0xb9}, {0x00,0x00,0x00,0x00}, {0xed,0xed,0x2c,0xc1}, +{0x20,0x20,0x60,0x40}, {0xfc,0xfc,0x1f,0xe3}, {0xb1,0xb1,0xc8,0x79}, {0x5b,0x5b,0xed,0xb6}, +{0x6a,0x6a,0xbe,0xd4}, {0xcb,0xcb,0x46,0x8d}, {0xbe,0xbe,0xd9,0x67}, {0x39,0x39,0x4b,0x72}, +{0x4a,0x4a,0xde,0x94}, {0x4c,0x4c,0xd4,0x98}, {0x58,0x58,0xe8,0xb0}, {0xcf,0xcf,0x4a,0x85}, +{0xd0,0xd0,0x6b,0xbb}, {0xef,0xef,0x2a,0xc5}, {0xaa,0xaa,0xe5,0x4f}, {0xfb,0xfb,0x16,0xed}, +{0x43,0x43,0xc5,0x86}, {0x4d,0x4d,0xd7,0x9a}, {0x33,0x33,0x55,0x66}, {0x85,0x85,0x94,0x11}, +{0x45,0x45,0xcf,0x8a}, {0xf9,0xf9,0x10,0xe9}, {0x02,0x02,0x06,0x04}, {0x7f,0x7f,0x81,0xfe}, +{0x50,0x50,0xf0,0xa0}, {0x3c,0x3c,0x44,0x78}, {0x9f,0x9f,0xba,0x25}, {0xa8,0xa8,0xe3,0x4b}, +{0x51,0x51,0xf3,0xa2}, {0xa3,0xa3,0xfe,0x5d}, {0x40,0x40,0xc0,0x80}, {0x8f,0x8f,0x8a,0x05}, +{0x92,0x92,0xad,0x3f}, {0x9d,0x9d,0xbc,0x21}, {0x38,0x38,0x48,0x70}, {0xf5,0xf5,0x04,0xf1}, +{0xbc,0xbc,0xdf,0x63}, {0xb6,0xb6,0xc1,0x77}, {0xda,0xda,0x75,0xaf}, {0x21,0x21,0x63,0x42}, +{0x10,0x10,0x30,0x20}, {0xff,0xff,0x1a,0xe5}, {0xf3,0xf3,0x0e,0xfd}, {0xd2,0xd2,0x6d,0xbf}, +{0xcd,0xcd,0x4c,0x81}, {0x0c,0x0c,0x14,0x18}, {0x13,0x13,0x35,0x26}, {0xec,0xec,0x2f,0xc3}, +{0x5f,0x5f,0xe1,0xbe}, {0x97,0x97,0xa2,0x35}, {0x44,0x44,0xcc,0x88}, {0x17,0x17,0x39,0x2e}, +{0xc4,0xc4,0x57,0x93}, {0xa7,0xa7,0xf2,0x55}, {0x7e,0x7e,0x82,0xfc}, {0x3d,0x3d,0x47,0x7a}, +{0x64,0x64,0xac,0xc8}, {0x5d,0x5d,0xe7,0xba}, {0x19,0x19,0x2b,0x32}, {0x73,0x73,0x95,0xe6}, +{0x60,0x60,0xa0,0xc0}, {0x81,0x81,0x98,0x19}, {0x4f,0x4f,0xd1,0x9e}, {0xdc,0xdc,0x7f,0xa3}, +{0x22,0x22,0x66,0x44}, {0x2a,0x2a,0x7e,0x54}, {0x90,0x90,0xab,0x3b}, {0x88,0x88,0x83,0x0b}, +{0x46,0x46,0xca,0x8c}, {0xee,0xee,0x29,0xc7}, {0xb8,0xb8,0xd3,0x6b}, {0x14,0x14,0x3c,0x28}, +{0xde,0xde,0x79,0xa7}, {0x5e,0x5e,0xe2,0xbc}, {0x0b,0x0b,0x1d,0x16}, {0xdb,0xdb,0x76,0xad}, +{0xe0,0xe0,0x3b,0xdb}, {0x32,0x32,0x56,0x64}, {0x3a,0x3a,0x4e,0x74}, {0x0a,0x0a,0x1e,0x14}, +{0x49,0x49,0xdb,0x92}, {0x06,0x06,0x0a,0x0c}, {0x24,0x24,0x6c,0x48}, {0x5c,0x5c,0xe4,0xb8}, +{0xc2,0xc2,0x5d,0x9f}, {0xd3,0xd3,0x6e,0xbd}, {0xac,0xac,0xef,0x43}, {0x62,0x62,0xa6,0xc4}, +{0x91,0x91,0xa8,0x39}, {0x95,0x95,0xa4,0x31}, {0xe4,0xe4,0x37,0xd3}, {0x79,0x79,0x8b,0xf2}, +{0xe7,0xe7,0x32,0xd5}, {0xc8,0xc8,0x43,0x8b}, {0x37,0x37,0x59,0x6e}, {0x6d,0x6d,0xb7,0xda}, +{0x8d,0x8d,0x8c,0x01}, {0xd5,0xd5,0x64,0xb1}, {0x4e,0x4e,0xd2,0x9c}, {0xa9,0xa9,0xe0,0x49}, +{0x6c,0x6c,0xb4,0xd8}, {0x56,0x56,0xfa,0xac}, {0xf4,0xf4,0x07,0xf3}, {0xea,0xea,0x25,0xcf}, +{0x65,0x65,0xaf,0xca}, {0x7a,0x7a,0x8e,0xf4}, {0xae,0xae,0xe9,0x47}, {0x08,0x08,0x18,0x10}, +{0xba,0xba,0xd5,0x6f}, {0x78,0x78,0x88,0xf0}, {0x25,0x25,0x6f,0x4a}, {0x2e,0x2e,0x72,0x5c}, +{0x1c,0x1c,0x24,0x38}, {0xa6,0xa6,0xf1,0x57}, {0xb4,0xb4,0xc7,0x73}, {0xc6,0xc6,0x51,0x97}, +{0xe8,0xe8,0x23,0xcb}, {0xdd,0xdd,0x7c,0xa1}, {0x74,0x74,0x9c,0xe8}, {0x1f,0x1f,0x21,0x3e}, +{0x4b,0x4b,0xdd,0x96}, {0xbd,0xbd,0xdc,0x61}, {0x8b,0x8b,0x86,0x0d}, {0x8a,0x8a,0x85,0x0f}, +{0x70,0x70,0x90,0xe0}, {0x3e,0x3e,0x42,0x7c}, {0xb5,0xb5,0xc4,0x71}, {0x66,0x66,0xaa,0xcc}, +{0x48,0x48,0xd8,0x90}, {0x03,0x03,0x05,0x06}, {0xf6,0xf6,0x01,0xf7}, {0x0e,0x0e,0x12,0x1c}, +{0x61,0x61,0xa3,0xc2}, {0x35,0x35,0x5f,0x6a}, {0x57,0x57,0xf9,0xae}, {0xb9,0xb9,0xd0,0x69}, +{0x86,0x86,0x91,0x17}, {0xc1,0xc1,0x58,0x99}, {0x1d,0x1d,0x27,0x3a}, {0x9e,0x9e,0xb9,0x27}, +{0xe1,0xe1,0x38,0xd9}, {0xf8,0xf8,0x13,0xeb}, {0x98,0x98,0xb3,0x2b}, {0x11,0x11,0x33,0x22}, +{0x69,0x69,0xbb,0xd2}, {0xd9,0xd9,0x70,0xa9}, {0x8e,0x8e,0x89,0x07}, {0x94,0x94,0xa7,0x33}, +{0x9b,0x9b,0xb6,0x2d}, {0x1e,0x1e,0x22,0x3c}, {0x87,0x87,0x92,0x15}, {0xe9,0xe9,0x20,0xc9}, +{0xce,0xce,0x49,0x87}, {0x55,0x55,0xff,0xaa}, {0x28,0x28,0x78,0x50}, {0xdf,0xdf,0x7a,0xa5}, +{0x8c,0x8c,0x8f,0x03}, {0xa1,0xa1,0xf8,0x59}, {0x89,0x89,0x80,0x09}, {0x0d,0x0d,0x17,0x1a}, +{0xbf,0xbf,0xda,0x65}, {0xe6,0xe6,0x31,0xd7}, {0x42,0x42,0xc6,0x84}, {0x68,0x68,0xb8,0xd0}, +{0x41,0x41,0xc3,0x82}, {0x99,0x99,0xb0,0x29}, {0x2d,0x2d,0x77,0x5a}, {0x0f,0x0f,0x11,0x1e}, +{0xb0,0xb0,0xcb,0x7b}, {0x54,0x54,0xfc,0xa8}, {0xbb,0xbb,0xd6,0x6d}, {0x16,0x16,0x3a,0x2c} +}; + +word8 T5[256][4] = { +{0x51,0xf4,0xa7,0x50}, {0x7e,0x41,0x65,0x53}, {0x1a,0x17,0xa4,0xc3}, {0x3a,0x27,0x5e,0x96}, +{0x3b,0xab,0x6b,0xcb}, {0x1f,0x9d,0x45,0xf1}, {0xac,0xfa,0x58,0xab}, {0x4b,0xe3,0x03,0x93}, +{0x20,0x30,0xfa,0x55}, {0xad,0x76,0x6d,0xf6}, {0x88,0xcc,0x76,0x91}, {0xf5,0x02,0x4c,0x25}, +{0x4f,0xe5,0xd7,0xfc}, {0xc5,0x2a,0xcb,0xd7}, {0x26,0x35,0x44,0x80}, {0xb5,0x62,0xa3,0x8f}, +{0xde,0xb1,0x5a,0x49}, {0x25,0xba,0x1b,0x67}, {0x45,0xea,0x0e,0x98}, {0x5d,0xfe,0xc0,0xe1}, +{0xc3,0x2f,0x75,0x02}, {0x81,0x4c,0xf0,0x12}, {0x8d,0x46,0x97,0xa3}, {0x6b,0xd3,0xf9,0xc6}, +{0x03,0x8f,0x5f,0xe7}, {0x15,0x92,0x9c,0x95}, {0xbf,0x6d,0x7a,0xeb}, {0x95,0x52,0x59,0xda}, +{0xd4,0xbe,0x83,0x2d}, {0x58,0x74,0x21,0xd3}, {0x49,0xe0,0x69,0x29}, {0x8e,0xc9,0xc8,0x44}, +{0x75,0xc2,0x89,0x6a}, {0xf4,0x8e,0x79,0x78}, {0x99,0x58,0x3e,0x6b}, {0x27,0xb9,0x71,0xdd}, +{0xbe,0xe1,0x4f,0xb6}, {0xf0,0x88,0xad,0x17}, {0xc9,0x20,0xac,0x66}, {0x7d,0xce,0x3a,0xb4}, +{0x63,0xdf,0x4a,0x18}, {0xe5,0x1a,0x31,0x82}, {0x97,0x51,0x33,0x60}, {0x62,0x53,0x7f,0x45}, +{0xb1,0x64,0x77,0xe0}, {0xbb,0x6b,0xae,0x84}, {0xfe,0x81,0xa0,0x1c}, {0xf9,0x08,0x2b,0x94}, +{0x70,0x48,0x68,0x58}, {0x8f,0x45,0xfd,0x19}, {0x94,0xde,0x6c,0x87}, {0x52,0x7b,0xf8,0xb7}, +{0xab,0x73,0xd3,0x23}, {0x72,0x4b,0x02,0xe2}, {0xe3,0x1f,0x8f,0x57}, {0x66,0x55,0xab,0x2a}, +{0xb2,0xeb,0x28,0x07}, {0x2f,0xb5,0xc2,0x03}, {0x86,0xc5,0x7b,0x9a}, {0xd3,0x37,0x08,0xa5}, +{0x30,0x28,0x87,0xf2}, {0x23,0xbf,0xa5,0xb2}, {0x02,0x03,0x6a,0xba}, {0xed,0x16,0x82,0x5c}, +{0x8a,0xcf,0x1c,0x2b}, {0xa7,0x79,0xb4,0x92}, {0xf3,0x07,0xf2,0xf0}, {0x4e,0x69,0xe2,0xa1}, +{0x65,0xda,0xf4,0xcd}, {0x06,0x05,0xbe,0xd5}, {0xd1,0x34,0x62,0x1f}, {0xc4,0xa6,0xfe,0x8a}, +{0x34,0x2e,0x53,0x9d}, {0xa2,0xf3,0x55,0xa0}, {0x05,0x8a,0xe1,0x32}, {0xa4,0xf6,0xeb,0x75}, +{0x0b,0x83,0xec,0x39}, {0x40,0x60,0xef,0xaa}, {0x5e,0x71,0x9f,0x06}, {0xbd,0x6e,0x10,0x51}, +{0x3e,0x21,0x8a,0xf9}, {0x96,0xdd,0x06,0x3d}, {0xdd,0x3e,0x05,0xae}, {0x4d,0xe6,0xbd,0x46}, +{0x91,0x54,0x8d,0xb5}, {0x71,0xc4,0x5d,0x05}, {0x04,0x06,0xd4,0x6f}, {0x60,0x50,0x15,0xff}, +{0x19,0x98,0xfb,0x24}, {0xd6,0xbd,0xe9,0x97}, {0x89,0x40,0x43,0xcc}, {0x67,0xd9,0x9e,0x77}, +{0xb0,0xe8,0x42,0xbd}, {0x07,0x89,0x8b,0x88}, {0xe7,0x19,0x5b,0x38}, {0x79,0xc8,0xee,0xdb}, +{0xa1,0x7c,0x0a,0x47}, {0x7c,0x42,0x0f,0xe9}, {0xf8,0x84,0x1e,0xc9}, {0x00,0x00,0x00,0x00}, +{0x09,0x80,0x86,0x83}, {0x32,0x2b,0xed,0x48}, {0x1e,0x11,0x70,0xac}, {0x6c,0x5a,0x72,0x4e}, +{0xfd,0x0e,0xff,0xfb}, {0x0f,0x85,0x38,0x56}, {0x3d,0xae,0xd5,0x1e}, {0x36,0x2d,0x39,0x27}, +{0x0a,0x0f,0xd9,0x64}, {0x68,0x5c,0xa6,0x21}, {0x9b,0x5b,0x54,0xd1}, {0x24,0x36,0x2e,0x3a}, +{0x0c,0x0a,0x67,0xb1}, {0x93,0x57,0xe7,0x0f}, {0xb4,0xee,0x96,0xd2}, {0x1b,0x9b,0x91,0x9e}, +{0x80,0xc0,0xc5,0x4f}, {0x61,0xdc,0x20,0xa2}, {0x5a,0x77,0x4b,0x69}, {0x1c,0x12,0x1a,0x16}, +{0xe2,0x93,0xba,0x0a}, {0xc0,0xa0,0x2a,0xe5}, {0x3c,0x22,0xe0,0x43}, {0x12,0x1b,0x17,0x1d}, +{0x0e,0x09,0x0d,0x0b}, {0xf2,0x8b,0xc7,0xad}, {0x2d,0xb6,0xa8,0xb9}, {0x14,0x1e,0xa9,0xc8}, +{0x57,0xf1,0x19,0x85}, {0xaf,0x75,0x07,0x4c}, {0xee,0x99,0xdd,0xbb}, {0xa3,0x7f,0x60,0xfd}, +{0xf7,0x01,0x26,0x9f}, {0x5c,0x72,0xf5,0xbc}, {0x44,0x66,0x3b,0xc5}, {0x5b,0xfb,0x7e,0x34}, +{0x8b,0x43,0x29,0x76}, {0xcb,0x23,0xc6,0xdc}, {0xb6,0xed,0xfc,0x68}, {0xb8,0xe4,0xf1,0x63}, +{0xd7,0x31,0xdc,0xca}, {0x42,0x63,0x85,0x10}, {0x13,0x97,0x22,0x40}, {0x84,0xc6,0x11,0x20}, +{0x85,0x4a,0x24,0x7d}, {0xd2,0xbb,0x3d,0xf8}, {0xae,0xf9,0x32,0x11}, {0xc7,0x29,0xa1,0x6d}, +{0x1d,0x9e,0x2f,0x4b}, {0xdc,0xb2,0x30,0xf3}, {0x0d,0x86,0x52,0xec}, {0x77,0xc1,0xe3,0xd0}, +{0x2b,0xb3,0x16,0x6c}, {0xa9,0x70,0xb9,0x99}, {0x11,0x94,0x48,0xfa}, {0x47,0xe9,0x64,0x22}, +{0xa8,0xfc,0x8c,0xc4}, {0xa0,0xf0,0x3f,0x1a}, {0x56,0x7d,0x2c,0xd8}, {0x22,0x33,0x90,0xef}, +{0x87,0x49,0x4e,0xc7}, {0xd9,0x38,0xd1,0xc1}, {0x8c,0xca,0xa2,0xfe}, {0x98,0xd4,0x0b,0x36}, +{0xa6,0xf5,0x81,0xcf}, {0xa5,0x7a,0xde,0x28}, {0xda,0xb7,0x8e,0x26}, {0x3f,0xad,0xbf,0xa4}, +{0x2c,0x3a,0x9d,0xe4}, {0x50,0x78,0x92,0x0d}, {0x6a,0x5f,0xcc,0x9b}, {0x54,0x7e,0x46,0x62}, +{0xf6,0x8d,0x13,0xc2}, {0x90,0xd8,0xb8,0xe8}, {0x2e,0x39,0xf7,0x5e}, {0x82,0xc3,0xaf,0xf5}, +{0x9f,0x5d,0x80,0xbe}, {0x69,0xd0,0x93,0x7c}, {0x6f,0xd5,0x2d,0xa9}, {0xcf,0x25,0x12,0xb3}, +{0xc8,0xac,0x99,0x3b}, {0x10,0x18,0x7d,0xa7}, {0xe8,0x9c,0x63,0x6e}, {0xdb,0x3b,0xbb,0x7b}, +{0xcd,0x26,0x78,0x09}, {0x6e,0x59,0x18,0xf4}, {0xec,0x9a,0xb7,0x01}, {0x83,0x4f,0x9a,0xa8}, +{0xe6,0x95,0x6e,0x65}, {0xaa,0xff,0xe6,0x7e}, {0x21,0xbc,0xcf,0x08}, {0xef,0x15,0xe8,0xe6}, +{0xba,0xe7,0x9b,0xd9}, {0x4a,0x6f,0x36,0xce}, {0xea,0x9f,0x09,0xd4}, {0x29,0xb0,0x7c,0xd6}, +{0x31,0xa4,0xb2,0xaf}, {0x2a,0x3f,0x23,0x31}, {0xc6,0xa5,0x94,0x30}, {0x35,0xa2,0x66,0xc0}, +{0x74,0x4e,0xbc,0x37}, {0xfc,0x82,0xca,0xa6}, {0xe0,0x90,0xd0,0xb0}, {0x33,0xa7,0xd8,0x15}, +{0xf1,0x04,0x98,0x4a}, {0x41,0xec,0xda,0xf7}, {0x7f,0xcd,0x50,0x0e}, {0x17,0x91,0xf6,0x2f}, +{0x76,0x4d,0xd6,0x8d}, {0x43,0xef,0xb0,0x4d}, {0xcc,0xaa,0x4d,0x54}, {0xe4,0x96,0x04,0xdf}, +{0x9e,0xd1,0xb5,0xe3}, {0x4c,0x6a,0x88,0x1b}, {0xc1,0x2c,0x1f,0xb8}, {0x46,0x65,0x51,0x7f}, +{0x9d,0x5e,0xea,0x04}, {0x01,0x8c,0x35,0x5d}, {0xfa,0x87,0x74,0x73}, {0xfb,0x0b,0x41,0x2e}, +{0xb3,0x67,0x1d,0x5a}, {0x92,0xdb,0xd2,0x52}, {0xe9,0x10,0x56,0x33}, {0x6d,0xd6,0x47,0x13}, +{0x9a,0xd7,0x61,0x8c}, {0x37,0xa1,0x0c,0x7a}, {0x59,0xf8,0x14,0x8e}, {0xeb,0x13,0x3c,0x89}, +{0xce,0xa9,0x27,0xee}, {0xb7,0x61,0xc9,0x35}, {0xe1,0x1c,0xe5,0xed}, {0x7a,0x47,0xb1,0x3c}, +{0x9c,0xd2,0xdf,0x59}, {0x55,0xf2,0x73,0x3f}, {0x18,0x14,0xce,0x79}, {0x73,0xc7,0x37,0xbf}, +{0x53,0xf7,0xcd,0xea}, {0x5f,0xfd,0xaa,0x5b}, {0xdf,0x3d,0x6f,0x14}, {0x78,0x44,0xdb,0x86}, +{0xca,0xaf,0xf3,0x81}, {0xb9,0x68,0xc4,0x3e}, {0x38,0x24,0x34,0x2c}, {0xc2,0xa3,0x40,0x5f}, +{0x16,0x1d,0xc3,0x72}, {0xbc,0xe2,0x25,0x0c}, {0x28,0x3c,0x49,0x8b}, {0xff,0x0d,0x95,0x41}, +{0x39,0xa8,0x01,0x71}, {0x08,0x0c,0xb3,0xde}, {0xd8,0xb4,0xe4,0x9c}, {0x64,0x56,0xc1,0x90}, +{0x7b,0xcb,0x84,0x61}, {0xd5,0x32,0xb6,0x70}, {0x48,0x6c,0x5c,0x74}, {0xd0,0xb8,0x57,0x42} +}; + +word8 T6[256][4] = { +{0x50,0x51,0xf4,0xa7}, {0x53,0x7e,0x41,0x65}, {0xc3,0x1a,0x17,0xa4}, {0x96,0x3a,0x27,0x5e}, +{0xcb,0x3b,0xab,0x6b}, {0xf1,0x1f,0x9d,0x45}, {0xab,0xac,0xfa,0x58}, {0x93,0x4b,0xe3,0x03}, +{0x55,0x20,0x30,0xfa}, {0xf6,0xad,0x76,0x6d}, {0x91,0x88,0xcc,0x76}, {0x25,0xf5,0x02,0x4c}, +{0xfc,0x4f,0xe5,0xd7}, {0xd7,0xc5,0x2a,0xcb}, {0x80,0x26,0x35,0x44}, {0x8f,0xb5,0x62,0xa3}, +{0x49,0xde,0xb1,0x5a}, {0x67,0x25,0xba,0x1b}, {0x98,0x45,0xea,0x0e}, {0xe1,0x5d,0xfe,0xc0}, +{0x02,0xc3,0x2f,0x75}, {0x12,0x81,0x4c,0xf0}, {0xa3,0x8d,0x46,0x97}, {0xc6,0x6b,0xd3,0xf9}, +{0xe7,0x03,0x8f,0x5f}, {0x95,0x15,0x92,0x9c}, {0xeb,0xbf,0x6d,0x7a}, {0xda,0x95,0x52,0x59}, +{0x2d,0xd4,0xbe,0x83}, {0xd3,0x58,0x74,0x21}, {0x29,0x49,0xe0,0x69}, {0x44,0x8e,0xc9,0xc8}, +{0x6a,0x75,0xc2,0x89}, {0x78,0xf4,0x8e,0x79}, {0x6b,0x99,0x58,0x3e}, {0xdd,0x27,0xb9,0x71}, +{0xb6,0xbe,0xe1,0x4f}, {0x17,0xf0,0x88,0xad}, {0x66,0xc9,0x20,0xac}, {0xb4,0x7d,0xce,0x3a}, +{0x18,0x63,0xdf,0x4a}, {0x82,0xe5,0x1a,0x31}, {0x60,0x97,0x51,0x33}, {0x45,0x62,0x53,0x7f}, +{0xe0,0xb1,0x64,0x77}, {0x84,0xbb,0x6b,0xae}, {0x1c,0xfe,0x81,0xa0}, {0x94,0xf9,0x08,0x2b}, +{0x58,0x70,0x48,0x68}, {0x19,0x8f,0x45,0xfd}, {0x87,0x94,0xde,0x6c}, {0xb7,0x52,0x7b,0xf8}, +{0x23,0xab,0x73,0xd3}, {0xe2,0x72,0x4b,0x02}, {0x57,0xe3,0x1f,0x8f}, {0x2a,0x66,0x55,0xab}, +{0x07,0xb2,0xeb,0x28}, {0x03,0x2f,0xb5,0xc2}, {0x9a,0x86,0xc5,0x7b}, {0xa5,0xd3,0x37,0x08}, +{0xf2,0x30,0x28,0x87}, {0xb2,0x23,0xbf,0xa5}, {0xba,0x02,0x03,0x6a}, {0x5c,0xed,0x16,0x82}, +{0x2b,0x8a,0xcf,0x1c}, {0x92,0xa7,0x79,0xb4}, {0xf0,0xf3,0x07,0xf2}, {0xa1,0x4e,0x69,0xe2}, +{0xcd,0x65,0xda,0xf4}, {0xd5,0x06,0x05,0xbe}, {0x1f,0xd1,0x34,0x62}, {0x8a,0xc4,0xa6,0xfe}, +{0x9d,0x34,0x2e,0x53}, {0xa0,0xa2,0xf3,0x55}, {0x32,0x05,0x8a,0xe1}, {0x75,0xa4,0xf6,0xeb}, +{0x39,0x0b,0x83,0xec}, {0xaa,0x40,0x60,0xef}, {0x06,0x5e,0x71,0x9f}, {0x51,0xbd,0x6e,0x10}, +{0xf9,0x3e,0x21,0x8a}, {0x3d,0x96,0xdd,0x06}, {0xae,0xdd,0x3e,0x05}, {0x46,0x4d,0xe6,0xbd}, +{0xb5,0x91,0x54,0x8d}, {0x05,0x71,0xc4,0x5d}, {0x6f,0x04,0x06,0xd4}, {0xff,0x60,0x50,0x15}, +{0x24,0x19,0x98,0xfb}, {0x97,0xd6,0xbd,0xe9}, {0xcc,0x89,0x40,0x43}, {0x77,0x67,0xd9,0x9e}, +{0xbd,0xb0,0xe8,0x42}, {0x88,0x07,0x89,0x8b}, {0x38,0xe7,0x19,0x5b}, {0xdb,0x79,0xc8,0xee}, +{0x47,0xa1,0x7c,0x0a}, {0xe9,0x7c,0x42,0x0f}, {0xc9,0xf8,0x84,0x1e}, {0x00,0x00,0x00,0x00}, +{0x83,0x09,0x80,0x86}, {0x48,0x32,0x2b,0xed}, {0xac,0x1e,0x11,0x70}, {0x4e,0x6c,0x5a,0x72}, +{0xfb,0xfd,0x0e,0xff}, {0x56,0x0f,0x85,0x38}, {0x1e,0x3d,0xae,0xd5}, {0x27,0x36,0x2d,0x39}, +{0x64,0x0a,0x0f,0xd9}, {0x21,0x68,0x5c,0xa6}, {0xd1,0x9b,0x5b,0x54}, {0x3a,0x24,0x36,0x2e}, +{0xb1,0x0c,0x0a,0x67}, {0x0f,0x93,0x57,0xe7}, {0xd2,0xb4,0xee,0x96}, {0x9e,0x1b,0x9b,0x91}, +{0x4f,0x80,0xc0,0xc5}, {0xa2,0x61,0xdc,0x20}, {0x69,0x5a,0x77,0x4b}, {0x16,0x1c,0x12,0x1a}, +{0x0a,0xe2,0x93,0xba}, {0xe5,0xc0,0xa0,0x2a}, {0x43,0x3c,0x22,0xe0}, {0x1d,0x12,0x1b,0x17}, +{0x0b,0x0e,0x09,0x0d}, {0xad,0xf2,0x8b,0xc7}, {0xb9,0x2d,0xb6,0xa8}, {0xc8,0x14,0x1e,0xa9}, +{0x85,0x57,0xf1,0x19}, {0x4c,0xaf,0x75,0x07}, {0xbb,0xee,0x99,0xdd}, {0xfd,0xa3,0x7f,0x60}, +{0x9f,0xf7,0x01,0x26}, {0xbc,0x5c,0x72,0xf5}, {0xc5,0x44,0x66,0x3b}, {0x34,0x5b,0xfb,0x7e}, +{0x76,0x8b,0x43,0x29}, {0xdc,0xcb,0x23,0xc6}, {0x68,0xb6,0xed,0xfc}, {0x63,0xb8,0xe4,0xf1}, +{0xca,0xd7,0x31,0xdc}, {0x10,0x42,0x63,0x85}, {0x40,0x13,0x97,0x22}, {0x20,0x84,0xc6,0x11}, +{0x7d,0x85,0x4a,0x24}, {0xf8,0xd2,0xbb,0x3d}, {0x11,0xae,0xf9,0x32}, {0x6d,0xc7,0x29,0xa1}, +{0x4b,0x1d,0x9e,0x2f}, {0xf3,0xdc,0xb2,0x30}, {0xec,0x0d,0x86,0x52}, {0xd0,0x77,0xc1,0xe3}, +{0x6c,0x2b,0xb3,0x16}, {0x99,0xa9,0x70,0xb9}, {0xfa,0x11,0x94,0x48}, {0x22,0x47,0xe9,0x64}, +{0xc4,0xa8,0xfc,0x8c}, {0x1a,0xa0,0xf0,0x3f}, {0xd8,0x56,0x7d,0x2c}, {0xef,0x22,0x33,0x90}, +{0xc7,0x87,0x49,0x4e}, {0xc1,0xd9,0x38,0xd1}, {0xfe,0x8c,0xca,0xa2}, {0x36,0x98,0xd4,0x0b}, +{0xcf,0xa6,0xf5,0x81}, {0x28,0xa5,0x7a,0xde}, {0x26,0xda,0xb7,0x8e}, {0xa4,0x3f,0xad,0xbf}, +{0xe4,0x2c,0x3a,0x9d}, {0x0d,0x50,0x78,0x92}, {0x9b,0x6a,0x5f,0xcc}, {0x62,0x54,0x7e,0x46}, +{0xc2,0xf6,0x8d,0x13}, {0xe8,0x90,0xd8,0xb8}, {0x5e,0x2e,0x39,0xf7}, {0xf5,0x82,0xc3,0xaf}, +{0xbe,0x9f,0x5d,0x80}, {0x7c,0x69,0xd0,0x93}, {0xa9,0x6f,0xd5,0x2d}, {0xb3,0xcf,0x25,0x12}, +{0x3b,0xc8,0xac,0x99}, {0xa7,0x10,0x18,0x7d}, {0x6e,0xe8,0x9c,0x63}, {0x7b,0xdb,0x3b,0xbb}, +{0x09,0xcd,0x26,0x78}, {0xf4,0x6e,0x59,0x18}, {0x01,0xec,0x9a,0xb7}, {0xa8,0x83,0x4f,0x9a}, +{0x65,0xe6,0x95,0x6e}, {0x7e,0xaa,0xff,0xe6}, {0x08,0x21,0xbc,0xcf}, {0xe6,0xef,0x15,0xe8}, +{0xd9,0xba,0xe7,0x9b}, {0xce,0x4a,0x6f,0x36}, {0xd4,0xea,0x9f,0x09}, {0xd6,0x29,0xb0,0x7c}, +{0xaf,0x31,0xa4,0xb2}, {0x31,0x2a,0x3f,0x23}, {0x30,0xc6,0xa5,0x94}, {0xc0,0x35,0xa2,0x66}, +{0x37,0x74,0x4e,0xbc}, {0xa6,0xfc,0x82,0xca}, {0xb0,0xe0,0x90,0xd0}, {0x15,0x33,0xa7,0xd8}, +{0x4a,0xf1,0x04,0x98}, {0xf7,0x41,0xec,0xda}, {0x0e,0x7f,0xcd,0x50}, {0x2f,0x17,0x91,0xf6}, +{0x8d,0x76,0x4d,0xd6}, {0x4d,0x43,0xef,0xb0}, {0x54,0xcc,0xaa,0x4d}, {0xdf,0xe4,0x96,0x04}, +{0xe3,0x9e,0xd1,0xb5}, {0x1b,0x4c,0x6a,0x88}, {0xb8,0xc1,0x2c,0x1f}, {0x7f,0x46,0x65,0x51}, +{0x04,0x9d,0x5e,0xea}, {0x5d,0x01,0x8c,0x35}, {0x73,0xfa,0x87,0x74}, {0x2e,0xfb,0x0b,0x41}, +{0x5a,0xb3,0x67,0x1d}, {0x52,0x92,0xdb,0xd2}, {0x33,0xe9,0x10,0x56}, {0x13,0x6d,0xd6,0x47}, +{0x8c,0x9a,0xd7,0x61}, {0x7a,0x37,0xa1,0x0c}, {0x8e,0x59,0xf8,0x14}, {0x89,0xeb,0x13,0x3c}, +{0xee,0xce,0xa9,0x27}, {0x35,0xb7,0x61,0xc9}, {0xed,0xe1,0x1c,0xe5}, {0x3c,0x7a,0x47,0xb1}, +{0x59,0x9c,0xd2,0xdf}, {0x3f,0x55,0xf2,0x73}, {0x79,0x18,0x14,0xce}, {0xbf,0x73,0xc7,0x37}, +{0xea,0x53,0xf7,0xcd}, {0x5b,0x5f,0xfd,0xaa}, {0x14,0xdf,0x3d,0x6f}, {0x86,0x78,0x44,0xdb}, +{0x81,0xca,0xaf,0xf3}, {0x3e,0xb9,0x68,0xc4}, {0x2c,0x38,0x24,0x34}, {0x5f,0xc2,0xa3,0x40}, +{0x72,0x16,0x1d,0xc3}, {0x0c,0xbc,0xe2,0x25}, {0x8b,0x28,0x3c,0x49}, {0x41,0xff,0x0d,0x95}, +{0x71,0x39,0xa8,0x01}, {0xde,0x08,0x0c,0xb3}, {0x9c,0xd8,0xb4,0xe4}, {0x90,0x64,0x56,0xc1}, +{0x61,0x7b,0xcb,0x84}, {0x70,0xd5,0x32,0xb6}, {0x74,0x48,0x6c,0x5c}, {0x42,0xd0,0xb8,0x57} +}; + +word8 T7[256][4] = { +{0xa7,0x50,0x51,0xf4}, {0x65,0x53,0x7e,0x41}, {0xa4,0xc3,0x1a,0x17}, {0x5e,0x96,0x3a,0x27}, +{0x6b,0xcb,0x3b,0xab}, {0x45,0xf1,0x1f,0x9d}, {0x58,0xab,0xac,0xfa}, {0x03,0x93,0x4b,0xe3}, +{0xfa,0x55,0x20,0x30}, {0x6d,0xf6,0xad,0x76}, {0x76,0x91,0x88,0xcc}, {0x4c,0x25,0xf5,0x02}, +{0xd7,0xfc,0x4f,0xe5}, {0xcb,0xd7,0xc5,0x2a}, {0x44,0x80,0x26,0x35}, {0xa3,0x8f,0xb5,0x62}, +{0x5a,0x49,0xde,0xb1}, {0x1b,0x67,0x25,0xba}, {0x0e,0x98,0x45,0xea}, {0xc0,0xe1,0x5d,0xfe}, +{0x75,0x02,0xc3,0x2f}, {0xf0,0x12,0x81,0x4c}, {0x97,0xa3,0x8d,0x46}, {0xf9,0xc6,0x6b,0xd3}, +{0x5f,0xe7,0x03,0x8f}, {0x9c,0x95,0x15,0x92}, {0x7a,0xeb,0xbf,0x6d}, {0x59,0xda,0x95,0x52}, +{0x83,0x2d,0xd4,0xbe}, {0x21,0xd3,0x58,0x74}, {0x69,0x29,0x49,0xe0}, {0xc8,0x44,0x8e,0xc9}, +{0x89,0x6a,0x75,0xc2}, {0x79,0x78,0xf4,0x8e}, {0x3e,0x6b,0x99,0x58}, {0x71,0xdd,0x27,0xb9}, +{0x4f,0xb6,0xbe,0xe1}, {0xad,0x17,0xf0,0x88}, {0xac,0x66,0xc9,0x20}, {0x3a,0xb4,0x7d,0xce}, +{0x4a,0x18,0x63,0xdf}, {0x31,0x82,0xe5,0x1a}, {0x33,0x60,0x97,0x51}, {0x7f,0x45,0x62,0x53}, +{0x77,0xe0,0xb1,0x64}, {0xae,0x84,0xbb,0x6b}, {0xa0,0x1c,0xfe,0x81}, {0x2b,0x94,0xf9,0x08}, +{0x68,0x58,0x70,0x48}, {0xfd,0x19,0x8f,0x45}, {0x6c,0x87,0x94,0xde}, {0xf8,0xb7,0x52,0x7b}, +{0xd3,0x23,0xab,0x73}, {0x02,0xe2,0x72,0x4b}, {0x8f,0x57,0xe3,0x1f}, {0xab,0x2a,0x66,0x55}, +{0x28,0x07,0xb2,0xeb}, {0xc2,0x03,0x2f,0xb5}, {0x7b,0x9a,0x86,0xc5}, {0x08,0xa5,0xd3,0x37}, +{0x87,0xf2,0x30,0x28}, {0xa5,0xb2,0x23,0xbf}, {0x6a,0xba,0x02,0x03}, {0x82,0x5c,0xed,0x16}, +{0x1c,0x2b,0x8a,0xcf}, {0xb4,0x92,0xa7,0x79}, {0xf2,0xf0,0xf3,0x07}, {0xe2,0xa1,0x4e,0x69}, +{0xf4,0xcd,0x65,0xda}, {0xbe,0xd5,0x06,0x05}, {0x62,0x1f,0xd1,0x34}, {0xfe,0x8a,0xc4,0xa6}, +{0x53,0x9d,0x34,0x2e}, {0x55,0xa0,0xa2,0xf3}, {0xe1,0x32,0x05,0x8a}, {0xeb,0x75,0xa4,0xf6}, +{0xec,0x39,0x0b,0x83}, {0xef,0xaa,0x40,0x60}, {0x9f,0x06,0x5e,0x71}, {0x10,0x51,0xbd,0x6e}, +{0x8a,0xf9,0x3e,0x21}, {0x06,0x3d,0x96,0xdd}, {0x05,0xae,0xdd,0x3e}, {0xbd,0x46,0x4d,0xe6}, +{0x8d,0xb5,0x91,0x54}, {0x5d,0x05,0x71,0xc4}, {0xd4,0x6f,0x04,0x06}, {0x15,0xff,0x60,0x50}, +{0xfb,0x24,0x19,0x98}, {0xe9,0x97,0xd6,0xbd}, {0x43,0xcc,0x89,0x40}, {0x9e,0x77,0x67,0xd9}, +{0x42,0xbd,0xb0,0xe8}, {0x8b,0x88,0x07,0x89}, {0x5b,0x38,0xe7,0x19}, {0xee,0xdb,0x79,0xc8}, +{0x0a,0x47,0xa1,0x7c}, {0x0f,0xe9,0x7c,0x42}, {0x1e,0xc9,0xf8,0x84}, {0x00,0x00,0x00,0x00}, +{0x86,0x83,0x09,0x80}, {0xed,0x48,0x32,0x2b}, {0x70,0xac,0x1e,0x11}, {0x72,0x4e,0x6c,0x5a}, +{0xff,0xfb,0xfd,0x0e}, {0x38,0x56,0x0f,0x85}, {0xd5,0x1e,0x3d,0xae}, {0x39,0x27,0x36,0x2d}, +{0xd9,0x64,0x0a,0x0f}, {0xa6,0x21,0x68,0x5c}, {0x54,0xd1,0x9b,0x5b}, {0x2e,0x3a,0x24,0x36}, +{0x67,0xb1,0x0c,0x0a}, {0xe7,0x0f,0x93,0x57}, {0x96,0xd2,0xb4,0xee}, {0x91,0x9e,0x1b,0x9b}, +{0xc5,0x4f,0x80,0xc0}, {0x20,0xa2,0x61,0xdc}, {0x4b,0x69,0x5a,0x77}, {0x1a,0x16,0x1c,0x12}, +{0xba,0x0a,0xe2,0x93}, {0x2a,0xe5,0xc0,0xa0}, {0xe0,0x43,0x3c,0x22}, {0x17,0x1d,0x12,0x1b}, +{0x0d,0x0b,0x0e,0x09}, {0xc7,0xad,0xf2,0x8b}, {0xa8,0xb9,0x2d,0xb6}, {0xa9,0xc8,0x14,0x1e}, +{0x19,0x85,0x57,0xf1}, {0x07,0x4c,0xaf,0x75}, {0xdd,0xbb,0xee,0x99}, {0x60,0xfd,0xa3,0x7f}, +{0x26,0x9f,0xf7,0x01}, {0xf5,0xbc,0x5c,0x72}, {0x3b,0xc5,0x44,0x66}, {0x7e,0x34,0x5b,0xfb}, +{0x29,0x76,0x8b,0x43}, {0xc6,0xdc,0xcb,0x23}, {0xfc,0x68,0xb6,0xed}, {0xf1,0x63,0xb8,0xe4}, +{0xdc,0xca,0xd7,0x31}, {0x85,0x10,0x42,0x63}, {0x22,0x40,0x13,0x97}, {0x11,0x20,0x84,0xc6}, +{0x24,0x7d,0x85,0x4a}, {0x3d,0xf8,0xd2,0xbb}, {0x32,0x11,0xae,0xf9}, {0xa1,0x6d,0xc7,0x29}, +{0x2f,0x4b,0x1d,0x9e}, {0x30,0xf3,0xdc,0xb2}, {0x52,0xec,0x0d,0x86}, {0xe3,0xd0,0x77,0xc1}, +{0x16,0x6c,0x2b,0xb3}, {0xb9,0x99,0xa9,0x70}, {0x48,0xfa,0x11,0x94}, {0x64,0x22,0x47,0xe9}, +{0x8c,0xc4,0xa8,0xfc}, {0x3f,0x1a,0xa0,0xf0}, {0x2c,0xd8,0x56,0x7d}, {0x90,0xef,0x22,0x33}, +{0x4e,0xc7,0x87,0x49}, {0xd1,0xc1,0xd9,0x38}, {0xa2,0xfe,0x8c,0xca}, {0x0b,0x36,0x98,0xd4}, +{0x81,0xcf,0xa6,0xf5}, {0xde,0x28,0xa5,0x7a}, {0x8e,0x26,0xda,0xb7}, {0xbf,0xa4,0x3f,0xad}, +{0x9d,0xe4,0x2c,0x3a}, {0x92,0x0d,0x50,0x78}, {0xcc,0x9b,0x6a,0x5f}, {0x46,0x62,0x54,0x7e}, +{0x13,0xc2,0xf6,0x8d}, {0xb8,0xe8,0x90,0xd8}, {0xf7,0x5e,0x2e,0x39}, {0xaf,0xf5,0x82,0xc3}, +{0x80,0xbe,0x9f,0x5d}, {0x93,0x7c,0x69,0xd0}, {0x2d,0xa9,0x6f,0xd5}, {0x12,0xb3,0xcf,0x25}, +{0x99,0x3b,0xc8,0xac}, {0x7d,0xa7,0x10,0x18}, {0x63,0x6e,0xe8,0x9c}, {0xbb,0x7b,0xdb,0x3b}, +{0x78,0x09,0xcd,0x26}, {0x18,0xf4,0x6e,0x59}, {0xb7,0x01,0xec,0x9a}, {0x9a,0xa8,0x83,0x4f}, +{0x6e,0x65,0xe6,0x95}, {0xe6,0x7e,0xaa,0xff}, {0xcf,0x08,0x21,0xbc}, {0xe8,0xe6,0xef,0x15}, +{0x9b,0xd9,0xba,0xe7}, {0x36,0xce,0x4a,0x6f}, {0x09,0xd4,0xea,0x9f}, {0x7c,0xd6,0x29,0xb0}, +{0xb2,0xaf,0x31,0xa4}, {0x23,0x31,0x2a,0x3f}, {0x94,0x30,0xc6,0xa5}, {0x66,0xc0,0x35,0xa2}, +{0xbc,0x37,0x74,0x4e}, {0xca,0xa6,0xfc,0x82}, {0xd0,0xb0,0xe0,0x90}, {0xd8,0x15,0x33,0xa7}, +{0x98,0x4a,0xf1,0x04}, {0xda,0xf7,0x41,0xec}, {0x50,0x0e,0x7f,0xcd}, {0xf6,0x2f,0x17,0x91}, +{0xd6,0x8d,0x76,0x4d}, {0xb0,0x4d,0x43,0xef}, {0x4d,0x54,0xcc,0xaa}, {0x04,0xdf,0xe4,0x96}, +{0xb5,0xe3,0x9e,0xd1}, {0x88,0x1b,0x4c,0x6a}, {0x1f,0xb8,0xc1,0x2c}, {0x51,0x7f,0x46,0x65}, +{0xea,0x04,0x9d,0x5e}, {0x35,0x5d,0x01,0x8c}, {0x74,0x73,0xfa,0x87}, {0x41,0x2e,0xfb,0x0b}, +{0x1d,0x5a,0xb3,0x67}, {0xd2,0x52,0x92,0xdb}, {0x56,0x33,0xe9,0x10}, {0x47,0x13,0x6d,0xd6}, +{0x61,0x8c,0x9a,0xd7}, {0x0c,0x7a,0x37,0xa1}, {0x14,0x8e,0x59,0xf8}, {0x3c,0x89,0xeb,0x13}, +{0x27,0xee,0xce,0xa9}, {0xc9,0x35,0xb7,0x61}, {0xe5,0xed,0xe1,0x1c}, {0xb1,0x3c,0x7a,0x47}, +{0xdf,0x59,0x9c,0xd2}, {0x73,0x3f,0x55,0xf2}, {0xce,0x79,0x18,0x14}, {0x37,0xbf,0x73,0xc7}, +{0xcd,0xea,0x53,0xf7}, {0xaa,0x5b,0x5f,0xfd}, {0x6f,0x14,0xdf,0x3d}, {0xdb,0x86,0x78,0x44}, +{0xf3,0x81,0xca,0xaf}, {0xc4,0x3e,0xb9,0x68}, {0x34,0x2c,0x38,0x24}, {0x40,0x5f,0xc2,0xa3}, +{0xc3,0x72,0x16,0x1d}, {0x25,0x0c,0xbc,0xe2}, {0x49,0x8b,0x28,0x3c}, {0x95,0x41,0xff,0x0d}, +{0x01,0x71,0x39,0xa8}, {0xb3,0xde,0x08,0x0c}, {0xe4,0x9c,0xd8,0xb4}, {0xc1,0x90,0x64,0x56}, +{0x84,0x61,0x7b,0xcb}, {0xb6,0x70,0xd5,0x32}, {0x5c,0x74,0x48,0x6c}, {0x57,0x42,0xd0,0xb8} +}; + +word8 T8[256][4] = { +{0xf4,0xa7,0x50,0x51}, {0x41,0x65,0x53,0x7e}, {0x17,0xa4,0xc3,0x1a}, {0x27,0x5e,0x96,0x3a}, +{0xab,0x6b,0xcb,0x3b}, {0x9d,0x45,0xf1,0x1f}, {0xfa,0x58,0xab,0xac}, {0xe3,0x03,0x93,0x4b}, +{0x30,0xfa,0x55,0x20}, {0x76,0x6d,0xf6,0xad}, {0xcc,0x76,0x91,0x88}, {0x02,0x4c,0x25,0xf5}, +{0xe5,0xd7,0xfc,0x4f}, {0x2a,0xcb,0xd7,0xc5}, {0x35,0x44,0x80,0x26}, {0x62,0xa3,0x8f,0xb5}, +{0xb1,0x5a,0x49,0xde}, {0xba,0x1b,0x67,0x25}, {0xea,0x0e,0x98,0x45}, {0xfe,0xc0,0xe1,0x5d}, +{0x2f,0x75,0x02,0xc3}, {0x4c,0xf0,0x12,0x81}, {0x46,0x97,0xa3,0x8d}, {0xd3,0xf9,0xc6,0x6b}, +{0x8f,0x5f,0xe7,0x03}, {0x92,0x9c,0x95,0x15}, {0x6d,0x7a,0xeb,0xbf}, {0x52,0x59,0xda,0x95}, +{0xbe,0x83,0x2d,0xd4}, {0x74,0x21,0xd3,0x58}, {0xe0,0x69,0x29,0x49}, {0xc9,0xc8,0x44,0x8e}, +{0xc2,0x89,0x6a,0x75}, {0x8e,0x79,0x78,0xf4}, {0x58,0x3e,0x6b,0x99}, {0xb9,0x71,0xdd,0x27}, +{0xe1,0x4f,0xb6,0xbe}, {0x88,0xad,0x17,0xf0}, {0x20,0xac,0x66,0xc9}, {0xce,0x3a,0xb4,0x7d}, +{0xdf,0x4a,0x18,0x63}, {0x1a,0x31,0x82,0xe5}, {0x51,0x33,0x60,0x97}, {0x53,0x7f,0x45,0x62}, +{0x64,0x77,0xe0,0xb1}, {0x6b,0xae,0x84,0xbb}, {0x81,0xa0,0x1c,0xfe}, {0x08,0x2b,0x94,0xf9}, +{0x48,0x68,0x58,0x70}, {0x45,0xfd,0x19,0x8f}, {0xde,0x6c,0x87,0x94}, {0x7b,0xf8,0xb7,0x52}, +{0x73,0xd3,0x23,0xab}, {0x4b,0x02,0xe2,0x72}, {0x1f,0x8f,0x57,0xe3}, {0x55,0xab,0x2a,0x66}, +{0xeb,0x28,0x07,0xb2}, {0xb5,0xc2,0x03,0x2f}, {0xc5,0x7b,0x9a,0x86}, {0x37,0x08,0xa5,0xd3}, +{0x28,0x87,0xf2,0x30}, {0xbf,0xa5,0xb2,0x23}, {0x03,0x6a,0xba,0x02}, {0x16,0x82,0x5c,0xed}, +{0xcf,0x1c,0x2b,0x8a}, {0x79,0xb4,0x92,0xa7}, {0x07,0xf2,0xf0,0xf3}, {0x69,0xe2,0xa1,0x4e}, +{0xda,0xf4,0xcd,0x65}, {0x05,0xbe,0xd5,0x06}, {0x34,0x62,0x1f,0xd1}, {0xa6,0xfe,0x8a,0xc4}, +{0x2e,0x53,0x9d,0x34}, {0xf3,0x55,0xa0,0xa2}, {0x8a,0xe1,0x32,0x05}, {0xf6,0xeb,0x75,0xa4}, +{0x83,0xec,0x39,0x0b}, {0x60,0xef,0xaa,0x40}, {0x71,0x9f,0x06,0x5e}, {0x6e,0x10,0x51,0xbd}, +{0x21,0x8a,0xf9,0x3e}, {0xdd,0x06,0x3d,0x96}, {0x3e,0x05,0xae,0xdd}, {0xe6,0xbd,0x46,0x4d}, +{0x54,0x8d,0xb5,0x91}, {0xc4,0x5d,0x05,0x71}, {0x06,0xd4,0x6f,0x04}, {0x50,0x15,0xff,0x60}, +{0x98,0xfb,0x24,0x19}, {0xbd,0xe9,0x97,0xd6}, {0x40,0x43,0xcc,0x89}, {0xd9,0x9e,0x77,0x67}, +{0xe8,0x42,0xbd,0xb0}, {0x89,0x8b,0x88,0x07}, {0x19,0x5b,0x38,0xe7}, {0xc8,0xee,0xdb,0x79}, +{0x7c,0x0a,0x47,0xa1}, {0x42,0x0f,0xe9,0x7c}, {0x84,0x1e,0xc9,0xf8}, {0x00,0x00,0x00,0x00}, +{0x80,0x86,0x83,0x09}, {0x2b,0xed,0x48,0x32}, {0x11,0x70,0xac,0x1e}, {0x5a,0x72,0x4e,0x6c}, +{0x0e,0xff,0xfb,0xfd}, {0x85,0x38,0x56,0x0f}, {0xae,0xd5,0x1e,0x3d}, {0x2d,0x39,0x27,0x36}, +{0x0f,0xd9,0x64,0x0a}, {0x5c,0xa6,0x21,0x68}, {0x5b,0x54,0xd1,0x9b}, {0x36,0x2e,0x3a,0x24}, +{0x0a,0x67,0xb1,0x0c}, {0x57,0xe7,0x0f,0x93}, {0xee,0x96,0xd2,0xb4}, {0x9b,0x91,0x9e,0x1b}, +{0xc0,0xc5,0x4f,0x80}, {0xdc,0x20,0xa2,0x61}, {0x77,0x4b,0x69,0x5a}, {0x12,0x1a,0x16,0x1c}, +{0x93,0xba,0x0a,0xe2}, {0xa0,0x2a,0xe5,0xc0}, {0x22,0xe0,0x43,0x3c}, {0x1b,0x17,0x1d,0x12}, +{0x09,0x0d,0x0b,0x0e}, {0x8b,0xc7,0xad,0xf2}, {0xb6,0xa8,0xb9,0x2d}, {0x1e,0xa9,0xc8,0x14}, +{0xf1,0x19,0x85,0x57}, {0x75,0x07,0x4c,0xaf}, {0x99,0xdd,0xbb,0xee}, {0x7f,0x60,0xfd,0xa3}, +{0x01,0x26,0x9f,0xf7}, {0x72,0xf5,0xbc,0x5c}, {0x66,0x3b,0xc5,0x44}, {0xfb,0x7e,0x34,0x5b}, +{0x43,0x29,0x76,0x8b}, {0x23,0xc6,0xdc,0xcb}, {0xed,0xfc,0x68,0xb6}, {0xe4,0xf1,0x63,0xb8}, +{0x31,0xdc,0xca,0xd7}, {0x63,0x85,0x10,0x42}, {0x97,0x22,0x40,0x13}, {0xc6,0x11,0x20,0x84}, +{0x4a,0x24,0x7d,0x85}, {0xbb,0x3d,0xf8,0xd2}, {0xf9,0x32,0x11,0xae}, {0x29,0xa1,0x6d,0xc7}, +{0x9e,0x2f,0x4b,0x1d}, {0xb2,0x30,0xf3,0xdc}, {0x86,0x52,0xec,0x0d}, {0xc1,0xe3,0xd0,0x77}, +{0xb3,0x16,0x6c,0x2b}, {0x70,0xb9,0x99,0xa9}, {0x94,0x48,0xfa,0x11}, {0xe9,0x64,0x22,0x47}, +{0xfc,0x8c,0xc4,0xa8}, {0xf0,0x3f,0x1a,0xa0}, {0x7d,0x2c,0xd8,0x56}, {0x33,0x90,0xef,0x22}, +{0x49,0x4e,0xc7,0x87}, {0x38,0xd1,0xc1,0xd9}, {0xca,0xa2,0xfe,0x8c}, {0xd4,0x0b,0x36,0x98}, +{0xf5,0x81,0xcf,0xa6}, {0x7a,0xde,0x28,0xa5}, {0xb7,0x8e,0x26,0xda}, {0xad,0xbf,0xa4,0x3f}, +{0x3a,0x9d,0xe4,0x2c}, {0x78,0x92,0x0d,0x50}, {0x5f,0xcc,0x9b,0x6a}, {0x7e,0x46,0x62,0x54}, +{0x8d,0x13,0xc2,0xf6}, {0xd8,0xb8,0xe8,0x90}, {0x39,0xf7,0x5e,0x2e}, {0xc3,0xaf,0xf5,0x82}, +{0x5d,0x80,0xbe,0x9f}, {0xd0,0x93,0x7c,0x69}, {0xd5,0x2d,0xa9,0x6f}, {0x25,0x12,0xb3,0xcf}, +{0xac,0x99,0x3b,0xc8}, {0x18,0x7d,0xa7,0x10}, {0x9c,0x63,0x6e,0xe8}, {0x3b,0xbb,0x7b,0xdb}, +{0x26,0x78,0x09,0xcd}, {0x59,0x18,0xf4,0x6e}, {0x9a,0xb7,0x01,0xec}, {0x4f,0x9a,0xa8,0x83}, +{0x95,0x6e,0x65,0xe6}, {0xff,0xe6,0x7e,0xaa}, {0xbc,0xcf,0x08,0x21}, {0x15,0xe8,0xe6,0xef}, +{0xe7,0x9b,0xd9,0xba}, {0x6f,0x36,0xce,0x4a}, {0x9f,0x09,0xd4,0xea}, {0xb0,0x7c,0xd6,0x29}, +{0xa4,0xb2,0xaf,0x31}, {0x3f,0x23,0x31,0x2a}, {0xa5,0x94,0x30,0xc6}, {0xa2,0x66,0xc0,0x35}, +{0x4e,0xbc,0x37,0x74}, {0x82,0xca,0xa6,0xfc}, {0x90,0xd0,0xb0,0xe0}, {0xa7,0xd8,0x15,0x33}, +{0x04,0x98,0x4a,0xf1}, {0xec,0xda,0xf7,0x41}, {0xcd,0x50,0x0e,0x7f}, {0x91,0xf6,0x2f,0x17}, +{0x4d,0xd6,0x8d,0x76}, {0xef,0xb0,0x4d,0x43}, {0xaa,0x4d,0x54,0xcc}, {0x96,0x04,0xdf,0xe4}, +{0xd1,0xb5,0xe3,0x9e}, {0x6a,0x88,0x1b,0x4c}, {0x2c,0x1f,0xb8,0xc1}, {0x65,0x51,0x7f,0x46}, +{0x5e,0xea,0x04,0x9d}, {0x8c,0x35,0x5d,0x01}, {0x87,0x74,0x73,0xfa}, {0x0b,0x41,0x2e,0xfb}, +{0x67,0x1d,0x5a,0xb3}, {0xdb,0xd2,0x52,0x92}, {0x10,0x56,0x33,0xe9}, {0xd6,0x47,0x13,0x6d}, +{0xd7,0x61,0x8c,0x9a}, {0xa1,0x0c,0x7a,0x37}, {0xf8,0x14,0x8e,0x59}, {0x13,0x3c,0x89,0xeb}, +{0xa9,0x27,0xee,0xce}, {0x61,0xc9,0x35,0xb7}, {0x1c,0xe5,0xed,0xe1}, {0x47,0xb1,0x3c,0x7a}, +{0xd2,0xdf,0x59,0x9c}, {0xf2,0x73,0x3f,0x55}, {0x14,0xce,0x79,0x18}, {0xc7,0x37,0xbf,0x73}, +{0xf7,0xcd,0xea,0x53}, {0xfd,0xaa,0x5b,0x5f}, {0x3d,0x6f,0x14,0xdf}, {0x44,0xdb,0x86,0x78}, +{0xaf,0xf3,0x81,0xca}, {0x68,0xc4,0x3e,0xb9}, {0x24,0x34,0x2c,0x38}, {0xa3,0x40,0x5f,0xc2}, +{0x1d,0xc3,0x72,0x16}, {0xe2,0x25,0x0c,0xbc}, {0x3c,0x49,0x8b,0x28}, {0x0d,0x95,0x41,0xff}, +{0xa8,0x01,0x71,0x39}, {0x0c,0xb3,0xde,0x08}, {0xb4,0xe4,0x9c,0xd8}, {0x56,0xc1,0x90,0x64}, +{0xcb,0x84,0x61,0x7b}, {0x32,0xb6,0x70,0xd5}, {0x6c,0x5c,0x74,0x48}, {0xb8,0x57,0x42,0xd0} +}; + +word8 S5[256] = { +0x52,0x09,0x6a,0xd5, +0x30,0x36,0xa5,0x38, +0xbf,0x40,0xa3,0x9e, +0x81,0xf3,0xd7,0xfb, +0x7c,0xe3,0x39,0x82, +0x9b,0x2f,0xff,0x87, +0x34,0x8e,0x43,0x44, +0xc4,0xde,0xe9,0xcb, +0x54,0x7b,0x94,0x32, +0xa6,0xc2,0x23,0x3d, +0xee,0x4c,0x95,0x0b, +0x42,0xfa,0xc3,0x4e, +0x08,0x2e,0xa1,0x66, +0x28,0xd9,0x24,0xb2, +0x76,0x5b,0xa2,0x49, +0x6d,0x8b,0xd1,0x25, +0x72,0xf8,0xf6,0x64, +0x86,0x68,0x98,0x16, +0xd4,0xa4,0x5c,0xcc, +0x5d,0x65,0xb6,0x92, +0x6c,0x70,0x48,0x50, +0xfd,0xed,0xb9,0xda, +0x5e,0x15,0x46,0x57, +0xa7,0x8d,0x9d,0x84, +0x90,0xd8,0xab,0x00, +0x8c,0xbc,0xd3,0x0a, +0xf7,0xe4,0x58,0x05, +0xb8,0xb3,0x45,0x06, +0xd0,0x2c,0x1e,0x8f, +0xca,0x3f,0x0f,0x02, +0xc1,0xaf,0xbd,0x03, +0x01,0x13,0x8a,0x6b, +0x3a,0x91,0x11,0x41, +0x4f,0x67,0xdc,0xea, +0x97,0xf2,0xcf,0xce, +0xf0,0xb4,0xe6,0x73, +0x96,0xac,0x74,0x22, +0xe7,0xad,0x35,0x85, +0xe2,0xf9,0x37,0xe8, +0x1c,0x75,0xdf,0x6e, +0x47,0xf1,0x1a,0x71, +0x1d,0x29,0xc5,0x89, +0x6f,0xb7,0x62,0x0e, +0xaa,0x18,0xbe,0x1b, +0xfc,0x56,0x3e,0x4b, +0xc6,0xd2,0x79,0x20, +0x9a,0xdb,0xc0,0xfe, +0x78,0xcd,0x5a,0xf4, +0x1f,0xdd,0xa8,0x33, +0x88,0x07,0xc7,0x31, +0xb1,0x12,0x10,0x59, +0x27,0x80,0xec,0x5f, +0x60,0x51,0x7f,0xa9, +0x19,0xb5,0x4a,0x0d, +0x2d,0xe5,0x7a,0x9f, +0x93,0xc9,0x9c,0xef, +0xa0,0xe0,0x3b,0x4d, +0xae,0x2a,0xf5,0xb0, +0xc8,0xeb,0xbb,0x3c, +0x83,0x53,0x99,0x61, +0x17,0x2b,0x04,0x7e, +0xba,0x77,0xd6,0x26, +0xe1,0x69,0x14,0x63, +0x55,0x21,0x0c,0x7d +}; + +word8 U1[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0e,0x09,0x0d,0x0b}, {0x1c,0x12,0x1a,0x16}, {0x12,0x1b,0x17,0x1d}, +{0x38,0x24,0x34,0x2c}, {0x36,0x2d,0x39,0x27}, {0x24,0x36,0x2e,0x3a}, {0x2a,0x3f,0x23,0x31}, +{0x70,0x48,0x68,0x58}, {0x7e,0x41,0x65,0x53}, {0x6c,0x5a,0x72,0x4e}, {0x62,0x53,0x7f,0x45}, +{0x48,0x6c,0x5c,0x74}, {0x46,0x65,0x51,0x7f}, {0x54,0x7e,0x46,0x62}, {0x5a,0x77,0x4b,0x69}, +{0xe0,0x90,0xd0,0xb0}, {0xee,0x99,0xdd,0xbb}, {0xfc,0x82,0xca,0xa6}, {0xf2,0x8b,0xc7,0xad}, +{0xd8,0xb4,0xe4,0x9c}, {0xd6,0xbd,0xe9,0x97}, {0xc4,0xa6,0xfe,0x8a}, {0xca,0xaf,0xf3,0x81}, +{0x90,0xd8,0xb8,0xe8}, {0x9e,0xd1,0xb5,0xe3}, {0x8c,0xca,0xa2,0xfe}, {0x82,0xc3,0xaf,0xf5}, +{0xa8,0xfc,0x8c,0xc4}, {0xa6,0xf5,0x81,0xcf}, {0xb4,0xee,0x96,0xd2}, {0xba,0xe7,0x9b,0xd9}, +{0xdb,0x3b,0xbb,0x7b}, {0xd5,0x32,0xb6,0x70}, {0xc7,0x29,0xa1,0x6d}, {0xc9,0x20,0xac,0x66}, +{0xe3,0x1f,0x8f,0x57}, {0xed,0x16,0x82,0x5c}, {0xff,0x0d,0x95,0x41}, {0xf1,0x04,0x98,0x4a}, +{0xab,0x73,0xd3,0x23}, {0xa5,0x7a,0xde,0x28}, {0xb7,0x61,0xc9,0x35}, {0xb9,0x68,0xc4,0x3e}, +{0x93,0x57,0xe7,0x0f}, {0x9d,0x5e,0xea,0x04}, {0x8f,0x45,0xfd,0x19}, {0x81,0x4c,0xf0,0x12}, +{0x3b,0xab,0x6b,0xcb}, {0x35,0xa2,0x66,0xc0}, {0x27,0xb9,0x71,0xdd}, {0x29,0xb0,0x7c,0xd6}, +{0x03,0x8f,0x5f,0xe7}, {0x0d,0x86,0x52,0xec}, {0x1f,0x9d,0x45,0xf1}, {0x11,0x94,0x48,0xfa}, +{0x4b,0xe3,0x03,0x93}, {0x45,0xea,0x0e,0x98}, {0x57,0xf1,0x19,0x85}, {0x59,0xf8,0x14,0x8e}, +{0x73,0xc7,0x37,0xbf}, {0x7d,0xce,0x3a,0xb4}, {0x6f,0xd5,0x2d,0xa9}, {0x61,0xdc,0x20,0xa2}, +{0xad,0x76,0x6d,0xf6}, {0xa3,0x7f,0x60,0xfd}, {0xb1,0x64,0x77,0xe0}, {0xbf,0x6d,0x7a,0xeb}, +{0x95,0x52,0x59,0xda}, {0x9b,0x5b,0x54,0xd1}, {0x89,0x40,0x43,0xcc}, {0x87,0x49,0x4e,0xc7}, +{0xdd,0x3e,0x05,0xae}, {0xd3,0x37,0x08,0xa5}, {0xc1,0x2c,0x1f,0xb8}, {0xcf,0x25,0x12,0xb3}, +{0xe5,0x1a,0x31,0x82}, {0xeb,0x13,0x3c,0x89}, {0xf9,0x08,0x2b,0x94}, {0xf7,0x01,0x26,0x9f}, +{0x4d,0xe6,0xbd,0x46}, {0x43,0xef,0xb0,0x4d}, {0x51,0xf4,0xa7,0x50}, {0x5f,0xfd,0xaa,0x5b}, +{0x75,0xc2,0x89,0x6a}, {0x7b,0xcb,0x84,0x61}, {0x69,0xd0,0x93,0x7c}, {0x67,0xd9,0x9e,0x77}, +{0x3d,0xae,0xd5,0x1e}, {0x33,0xa7,0xd8,0x15}, {0x21,0xbc,0xcf,0x08}, {0x2f,0xb5,0xc2,0x03}, +{0x05,0x8a,0xe1,0x32}, {0x0b,0x83,0xec,0x39}, {0x19,0x98,0xfb,0x24}, {0x17,0x91,0xf6,0x2f}, +{0x76,0x4d,0xd6,0x8d}, {0x78,0x44,0xdb,0x86}, {0x6a,0x5f,0xcc,0x9b}, {0x64,0x56,0xc1,0x90}, +{0x4e,0x69,0xe2,0xa1}, {0x40,0x60,0xef,0xaa}, {0x52,0x7b,0xf8,0xb7}, {0x5c,0x72,0xf5,0xbc}, +{0x06,0x05,0xbe,0xd5}, {0x08,0x0c,0xb3,0xde}, {0x1a,0x17,0xa4,0xc3}, {0x14,0x1e,0xa9,0xc8}, +{0x3e,0x21,0x8a,0xf9}, {0x30,0x28,0x87,0xf2}, {0x22,0x33,0x90,0xef}, {0x2c,0x3a,0x9d,0xe4}, +{0x96,0xdd,0x06,0x3d}, {0x98,0xd4,0x0b,0x36}, {0x8a,0xcf,0x1c,0x2b}, {0x84,0xc6,0x11,0x20}, +{0xae,0xf9,0x32,0x11}, {0xa0,0xf0,0x3f,0x1a}, {0xb2,0xeb,0x28,0x07}, {0xbc,0xe2,0x25,0x0c}, +{0xe6,0x95,0x6e,0x65}, {0xe8,0x9c,0x63,0x6e}, {0xfa,0x87,0x74,0x73}, {0xf4,0x8e,0x79,0x78}, +{0xde,0xb1,0x5a,0x49}, {0xd0,0xb8,0x57,0x42}, {0xc2,0xa3,0x40,0x5f}, {0xcc,0xaa,0x4d,0x54}, +{0x41,0xec,0xda,0xf7}, {0x4f,0xe5,0xd7,0xfc}, {0x5d,0xfe,0xc0,0xe1}, {0x53,0xf7,0xcd,0xea}, +{0x79,0xc8,0xee,0xdb}, {0x77,0xc1,0xe3,0xd0}, {0x65,0xda,0xf4,0xcd}, {0x6b,0xd3,0xf9,0xc6}, +{0x31,0xa4,0xb2,0xaf}, {0x3f,0xad,0xbf,0xa4}, {0x2d,0xb6,0xa8,0xb9}, {0x23,0xbf,0xa5,0xb2}, +{0x09,0x80,0x86,0x83}, {0x07,0x89,0x8b,0x88}, {0x15,0x92,0x9c,0x95}, {0x1b,0x9b,0x91,0x9e}, +{0xa1,0x7c,0x0a,0x47}, {0xaf,0x75,0x07,0x4c}, {0xbd,0x6e,0x10,0x51}, {0xb3,0x67,0x1d,0x5a}, +{0x99,0x58,0x3e,0x6b}, {0x97,0x51,0x33,0x60}, {0x85,0x4a,0x24,0x7d}, {0x8b,0x43,0x29,0x76}, +{0xd1,0x34,0x62,0x1f}, {0xdf,0x3d,0x6f,0x14}, {0xcd,0x26,0x78,0x09}, {0xc3,0x2f,0x75,0x02}, +{0xe9,0x10,0x56,0x33}, {0xe7,0x19,0x5b,0x38}, {0xf5,0x02,0x4c,0x25}, {0xfb,0x0b,0x41,0x2e}, +{0x9a,0xd7,0x61,0x8c}, {0x94,0xde,0x6c,0x87}, {0x86,0xc5,0x7b,0x9a}, {0x88,0xcc,0x76,0x91}, +{0xa2,0xf3,0x55,0xa0}, {0xac,0xfa,0x58,0xab}, {0xbe,0xe1,0x4f,0xb6}, {0xb0,0xe8,0x42,0xbd}, +{0xea,0x9f,0x09,0xd4}, {0xe4,0x96,0x04,0xdf}, {0xf6,0x8d,0x13,0xc2}, {0xf8,0x84,0x1e,0xc9}, +{0xd2,0xbb,0x3d,0xf8}, {0xdc,0xb2,0x30,0xf3}, {0xce,0xa9,0x27,0xee}, {0xc0,0xa0,0x2a,0xe5}, +{0x7a,0x47,0xb1,0x3c}, {0x74,0x4e,0xbc,0x37}, {0x66,0x55,0xab,0x2a}, {0x68,0x5c,0xa6,0x21}, +{0x42,0x63,0x85,0x10}, {0x4c,0x6a,0x88,0x1b}, {0x5e,0x71,0x9f,0x06}, {0x50,0x78,0x92,0x0d}, +{0x0a,0x0f,0xd9,0x64}, {0x04,0x06,0xd4,0x6f}, {0x16,0x1d,0xc3,0x72}, {0x18,0x14,0xce,0x79}, +{0x32,0x2b,0xed,0x48}, {0x3c,0x22,0xe0,0x43}, {0x2e,0x39,0xf7,0x5e}, {0x20,0x30,0xfa,0x55}, +{0xec,0x9a,0xb7,0x01}, {0xe2,0x93,0xba,0x0a}, {0xf0,0x88,0xad,0x17}, {0xfe,0x81,0xa0,0x1c}, +{0xd4,0xbe,0x83,0x2d}, {0xda,0xb7,0x8e,0x26}, {0xc8,0xac,0x99,0x3b}, {0xc6,0xa5,0x94,0x30}, +{0x9c,0xd2,0xdf,0x59}, {0x92,0xdb,0xd2,0x52}, {0x80,0xc0,0xc5,0x4f}, {0x8e,0xc9,0xc8,0x44}, +{0xa4,0xf6,0xeb,0x75}, {0xaa,0xff,0xe6,0x7e}, {0xb8,0xe4,0xf1,0x63}, {0xb6,0xed,0xfc,0x68}, +{0x0c,0x0a,0x67,0xb1}, {0x02,0x03,0x6a,0xba}, {0x10,0x18,0x7d,0xa7}, {0x1e,0x11,0x70,0xac}, +{0x34,0x2e,0x53,0x9d}, {0x3a,0x27,0x5e,0x96}, {0x28,0x3c,0x49,0x8b}, {0x26,0x35,0x44,0x80}, +{0x7c,0x42,0x0f,0xe9}, {0x72,0x4b,0x02,0xe2}, {0x60,0x50,0x15,0xff}, {0x6e,0x59,0x18,0xf4}, +{0x44,0x66,0x3b,0xc5}, {0x4a,0x6f,0x36,0xce}, {0x58,0x74,0x21,0xd3}, {0x56,0x7d,0x2c,0xd8}, +{0x37,0xa1,0x0c,0x7a}, {0x39,0xa8,0x01,0x71}, {0x2b,0xb3,0x16,0x6c}, {0x25,0xba,0x1b,0x67}, +{0x0f,0x85,0x38,0x56}, {0x01,0x8c,0x35,0x5d}, {0x13,0x97,0x22,0x40}, {0x1d,0x9e,0x2f,0x4b}, +{0x47,0xe9,0x64,0x22}, {0x49,0xe0,0x69,0x29}, {0x5b,0xfb,0x7e,0x34}, {0x55,0xf2,0x73,0x3f}, +{0x7f,0xcd,0x50,0x0e}, {0x71,0xc4,0x5d,0x05}, {0x63,0xdf,0x4a,0x18}, {0x6d,0xd6,0x47,0x13}, +{0xd7,0x31,0xdc,0xca}, {0xd9,0x38,0xd1,0xc1}, {0xcb,0x23,0xc6,0xdc}, {0xc5,0x2a,0xcb,0xd7}, +{0xef,0x15,0xe8,0xe6}, {0xe1,0x1c,0xe5,0xed}, {0xf3,0x07,0xf2,0xf0}, {0xfd,0x0e,0xff,0xfb}, +{0xa7,0x79,0xb4,0x92}, {0xa9,0x70,0xb9,0x99}, {0xbb,0x6b,0xae,0x84}, {0xb5,0x62,0xa3,0x8f}, +{0x9f,0x5d,0x80,0xbe}, {0x91,0x54,0x8d,0xb5}, {0x83,0x4f,0x9a,0xa8}, {0x8d,0x46,0x97,0xa3} +}; + +word8 U2[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0b,0x0e,0x09,0x0d}, {0x16,0x1c,0x12,0x1a}, {0x1d,0x12,0x1b,0x17}, +{0x2c,0x38,0x24,0x34}, {0x27,0x36,0x2d,0x39}, {0x3a,0x24,0x36,0x2e}, {0x31,0x2a,0x3f,0x23}, +{0x58,0x70,0x48,0x68}, {0x53,0x7e,0x41,0x65}, {0x4e,0x6c,0x5a,0x72}, {0x45,0x62,0x53,0x7f}, +{0x74,0x48,0x6c,0x5c}, {0x7f,0x46,0x65,0x51}, {0x62,0x54,0x7e,0x46}, {0x69,0x5a,0x77,0x4b}, +{0xb0,0xe0,0x90,0xd0}, {0xbb,0xee,0x99,0xdd}, {0xa6,0xfc,0x82,0xca}, {0xad,0xf2,0x8b,0xc7}, +{0x9c,0xd8,0xb4,0xe4}, {0x97,0xd6,0xbd,0xe9}, {0x8a,0xc4,0xa6,0xfe}, {0x81,0xca,0xaf,0xf3}, +{0xe8,0x90,0xd8,0xb8}, {0xe3,0x9e,0xd1,0xb5}, {0xfe,0x8c,0xca,0xa2}, {0xf5,0x82,0xc3,0xaf}, +{0xc4,0xa8,0xfc,0x8c}, {0xcf,0xa6,0xf5,0x81}, {0xd2,0xb4,0xee,0x96}, {0xd9,0xba,0xe7,0x9b}, +{0x7b,0xdb,0x3b,0xbb}, {0x70,0xd5,0x32,0xb6}, {0x6d,0xc7,0x29,0xa1}, {0x66,0xc9,0x20,0xac}, +{0x57,0xe3,0x1f,0x8f}, {0x5c,0xed,0x16,0x82}, {0x41,0xff,0x0d,0x95}, {0x4a,0xf1,0x04,0x98}, +{0x23,0xab,0x73,0xd3}, {0x28,0xa5,0x7a,0xde}, {0x35,0xb7,0x61,0xc9}, {0x3e,0xb9,0x68,0xc4}, +{0x0f,0x93,0x57,0xe7}, {0x04,0x9d,0x5e,0xea}, {0x19,0x8f,0x45,0xfd}, {0x12,0x81,0x4c,0xf0}, +{0xcb,0x3b,0xab,0x6b}, {0xc0,0x35,0xa2,0x66}, {0xdd,0x27,0xb9,0x71}, {0xd6,0x29,0xb0,0x7c}, +{0xe7,0x03,0x8f,0x5f}, {0xec,0x0d,0x86,0x52}, {0xf1,0x1f,0x9d,0x45}, {0xfa,0x11,0x94,0x48}, +{0x93,0x4b,0xe3,0x03}, {0x98,0x45,0xea,0x0e}, {0x85,0x57,0xf1,0x19}, {0x8e,0x59,0xf8,0x14}, +{0xbf,0x73,0xc7,0x37}, {0xb4,0x7d,0xce,0x3a}, {0xa9,0x6f,0xd5,0x2d}, {0xa2,0x61,0xdc,0x20}, +{0xf6,0xad,0x76,0x6d}, {0xfd,0xa3,0x7f,0x60}, {0xe0,0xb1,0x64,0x77}, {0xeb,0xbf,0x6d,0x7a}, +{0xda,0x95,0x52,0x59}, {0xd1,0x9b,0x5b,0x54}, {0xcc,0x89,0x40,0x43}, {0xc7,0x87,0x49,0x4e}, +{0xae,0xdd,0x3e,0x05}, {0xa5,0xd3,0x37,0x08}, {0xb8,0xc1,0x2c,0x1f}, {0xb3,0xcf,0x25,0x12}, +{0x82,0xe5,0x1a,0x31}, {0x89,0xeb,0x13,0x3c}, {0x94,0xf9,0x08,0x2b}, {0x9f,0xf7,0x01,0x26}, +{0x46,0x4d,0xe6,0xbd}, {0x4d,0x43,0xef,0xb0}, {0x50,0x51,0xf4,0xa7}, {0x5b,0x5f,0xfd,0xaa}, +{0x6a,0x75,0xc2,0x89}, {0x61,0x7b,0xcb,0x84}, {0x7c,0x69,0xd0,0x93}, {0x77,0x67,0xd9,0x9e}, +{0x1e,0x3d,0xae,0xd5}, {0x15,0x33,0xa7,0xd8}, {0x08,0x21,0xbc,0xcf}, {0x03,0x2f,0xb5,0xc2}, +{0x32,0x05,0x8a,0xe1}, {0x39,0x0b,0x83,0xec}, {0x24,0x19,0x98,0xfb}, {0x2f,0x17,0x91,0xf6}, +{0x8d,0x76,0x4d,0xd6}, {0x86,0x78,0x44,0xdb}, {0x9b,0x6a,0x5f,0xcc}, {0x90,0x64,0x56,0xc1}, +{0xa1,0x4e,0x69,0xe2}, {0xaa,0x40,0x60,0xef}, {0xb7,0x52,0x7b,0xf8}, {0xbc,0x5c,0x72,0xf5}, +{0xd5,0x06,0x05,0xbe}, {0xde,0x08,0x0c,0xb3}, {0xc3,0x1a,0x17,0xa4}, {0xc8,0x14,0x1e,0xa9}, +{0xf9,0x3e,0x21,0x8a}, {0xf2,0x30,0x28,0x87}, {0xef,0x22,0x33,0x90}, {0xe4,0x2c,0x3a,0x9d}, +{0x3d,0x96,0xdd,0x06}, {0x36,0x98,0xd4,0x0b}, {0x2b,0x8a,0xcf,0x1c}, {0x20,0x84,0xc6,0x11}, +{0x11,0xae,0xf9,0x32}, {0x1a,0xa0,0xf0,0x3f}, {0x07,0xb2,0xeb,0x28}, {0x0c,0xbc,0xe2,0x25}, +{0x65,0xe6,0x95,0x6e}, {0x6e,0xe8,0x9c,0x63}, {0x73,0xfa,0x87,0x74}, {0x78,0xf4,0x8e,0x79}, +{0x49,0xde,0xb1,0x5a}, {0x42,0xd0,0xb8,0x57}, {0x5f,0xc2,0xa3,0x40}, {0x54,0xcc,0xaa,0x4d}, +{0xf7,0x41,0xec,0xda}, {0xfc,0x4f,0xe5,0xd7}, {0xe1,0x5d,0xfe,0xc0}, {0xea,0x53,0xf7,0xcd}, +{0xdb,0x79,0xc8,0xee}, {0xd0,0x77,0xc1,0xe3}, {0xcd,0x65,0xda,0xf4}, {0xc6,0x6b,0xd3,0xf9}, +{0xaf,0x31,0xa4,0xb2}, {0xa4,0x3f,0xad,0xbf}, {0xb9,0x2d,0xb6,0xa8}, {0xb2,0x23,0xbf,0xa5}, +{0x83,0x09,0x80,0x86}, {0x88,0x07,0x89,0x8b}, {0x95,0x15,0x92,0x9c}, {0x9e,0x1b,0x9b,0x91}, +{0x47,0xa1,0x7c,0x0a}, {0x4c,0xaf,0x75,0x07}, {0x51,0xbd,0x6e,0x10}, {0x5a,0xb3,0x67,0x1d}, +{0x6b,0x99,0x58,0x3e}, {0x60,0x97,0x51,0x33}, {0x7d,0x85,0x4a,0x24}, {0x76,0x8b,0x43,0x29}, +{0x1f,0xd1,0x34,0x62}, {0x14,0xdf,0x3d,0x6f}, {0x09,0xcd,0x26,0x78}, {0x02,0xc3,0x2f,0x75}, +{0x33,0xe9,0x10,0x56}, {0x38,0xe7,0x19,0x5b}, {0x25,0xf5,0x02,0x4c}, {0x2e,0xfb,0x0b,0x41}, +{0x8c,0x9a,0xd7,0x61}, {0x87,0x94,0xde,0x6c}, {0x9a,0x86,0xc5,0x7b}, {0x91,0x88,0xcc,0x76}, +{0xa0,0xa2,0xf3,0x55}, {0xab,0xac,0xfa,0x58}, {0xb6,0xbe,0xe1,0x4f}, {0xbd,0xb0,0xe8,0x42}, +{0xd4,0xea,0x9f,0x09}, {0xdf,0xe4,0x96,0x04}, {0xc2,0xf6,0x8d,0x13}, {0xc9,0xf8,0x84,0x1e}, +{0xf8,0xd2,0xbb,0x3d}, {0xf3,0xdc,0xb2,0x30}, {0xee,0xce,0xa9,0x27}, {0xe5,0xc0,0xa0,0x2a}, +{0x3c,0x7a,0x47,0xb1}, {0x37,0x74,0x4e,0xbc}, {0x2a,0x66,0x55,0xab}, {0x21,0x68,0x5c,0xa6}, +{0x10,0x42,0x63,0x85}, {0x1b,0x4c,0x6a,0x88}, {0x06,0x5e,0x71,0x9f}, {0x0d,0x50,0x78,0x92}, +{0x64,0x0a,0x0f,0xd9}, {0x6f,0x04,0x06,0xd4}, {0x72,0x16,0x1d,0xc3}, {0x79,0x18,0x14,0xce}, +{0x48,0x32,0x2b,0xed}, {0x43,0x3c,0x22,0xe0}, {0x5e,0x2e,0x39,0xf7}, {0x55,0x20,0x30,0xfa}, +{0x01,0xec,0x9a,0xb7}, {0x0a,0xe2,0x93,0xba}, {0x17,0xf0,0x88,0xad}, {0x1c,0xfe,0x81,0xa0}, +{0x2d,0xd4,0xbe,0x83}, {0x26,0xda,0xb7,0x8e}, {0x3b,0xc8,0xac,0x99}, {0x30,0xc6,0xa5,0x94}, +{0x59,0x9c,0xd2,0xdf}, {0x52,0x92,0xdb,0xd2}, {0x4f,0x80,0xc0,0xc5}, {0x44,0x8e,0xc9,0xc8}, +{0x75,0xa4,0xf6,0xeb}, {0x7e,0xaa,0xff,0xe6}, {0x63,0xb8,0xe4,0xf1}, {0x68,0xb6,0xed,0xfc}, +{0xb1,0x0c,0x0a,0x67}, {0xba,0x02,0x03,0x6a}, {0xa7,0x10,0x18,0x7d}, {0xac,0x1e,0x11,0x70}, +{0x9d,0x34,0x2e,0x53}, {0x96,0x3a,0x27,0x5e}, {0x8b,0x28,0x3c,0x49}, {0x80,0x26,0x35,0x44}, +{0xe9,0x7c,0x42,0x0f}, {0xe2,0x72,0x4b,0x02}, {0xff,0x60,0x50,0x15}, {0xf4,0x6e,0x59,0x18}, +{0xc5,0x44,0x66,0x3b}, {0xce,0x4a,0x6f,0x36}, {0xd3,0x58,0x74,0x21}, {0xd8,0x56,0x7d,0x2c}, +{0x7a,0x37,0xa1,0x0c}, {0x71,0x39,0xa8,0x01}, {0x6c,0x2b,0xb3,0x16}, {0x67,0x25,0xba,0x1b}, +{0x56,0x0f,0x85,0x38}, {0x5d,0x01,0x8c,0x35}, {0x40,0x13,0x97,0x22}, {0x4b,0x1d,0x9e,0x2f}, +{0x22,0x47,0xe9,0x64}, {0x29,0x49,0xe0,0x69}, {0x34,0x5b,0xfb,0x7e}, {0x3f,0x55,0xf2,0x73}, +{0x0e,0x7f,0xcd,0x50}, {0x05,0x71,0xc4,0x5d}, {0x18,0x63,0xdf,0x4a}, {0x13,0x6d,0xd6,0x47}, +{0xca,0xd7,0x31,0xdc}, {0xc1,0xd9,0x38,0xd1}, {0xdc,0xcb,0x23,0xc6}, {0xd7,0xc5,0x2a,0xcb}, +{0xe6,0xef,0x15,0xe8}, {0xed,0xe1,0x1c,0xe5}, {0xf0,0xf3,0x07,0xf2}, {0xfb,0xfd,0x0e,0xff}, +{0x92,0xa7,0x79,0xb4}, {0x99,0xa9,0x70,0xb9}, {0x84,0xbb,0x6b,0xae}, {0x8f,0xb5,0x62,0xa3}, +{0xbe,0x9f,0x5d,0x80}, {0xb5,0x91,0x54,0x8d}, {0xa8,0x83,0x4f,0x9a}, {0xa3,0x8d,0x46,0x97} +}; + +word8 U3[256][4] = { +{0x00,0x00,0x00,0x00}, {0x0d,0x0b,0x0e,0x09}, {0x1a,0x16,0x1c,0x12}, {0x17,0x1d,0x12,0x1b}, +{0x34,0x2c,0x38,0x24}, {0x39,0x27,0x36,0x2d}, {0x2e,0x3a,0x24,0x36}, {0x23,0x31,0x2a,0x3f}, +{0x68,0x58,0x70,0x48}, {0x65,0x53,0x7e,0x41}, {0x72,0x4e,0x6c,0x5a}, {0x7f,0x45,0x62,0x53}, +{0x5c,0x74,0x48,0x6c}, {0x51,0x7f,0x46,0x65}, {0x46,0x62,0x54,0x7e}, {0x4b,0x69,0x5a,0x77}, +{0xd0,0xb0,0xe0,0x90}, {0xdd,0xbb,0xee,0x99}, {0xca,0xa6,0xfc,0x82}, {0xc7,0xad,0xf2,0x8b}, +{0xe4,0x9c,0xd8,0xb4}, {0xe9,0x97,0xd6,0xbd}, {0xfe,0x8a,0xc4,0xa6}, {0xf3,0x81,0xca,0xaf}, +{0xb8,0xe8,0x90,0xd8}, {0xb5,0xe3,0x9e,0xd1}, {0xa2,0xfe,0x8c,0xca}, {0xaf,0xf5,0x82,0xc3}, +{0x8c,0xc4,0xa8,0xfc}, {0x81,0xcf,0xa6,0xf5}, {0x96,0xd2,0xb4,0xee}, {0x9b,0xd9,0xba,0xe7}, +{0xbb,0x7b,0xdb,0x3b}, {0xb6,0x70,0xd5,0x32}, {0xa1,0x6d,0xc7,0x29}, {0xac,0x66,0xc9,0x20}, +{0x8f,0x57,0xe3,0x1f}, {0x82,0x5c,0xed,0x16}, {0x95,0x41,0xff,0x0d}, {0x98,0x4a,0xf1,0x04}, +{0xd3,0x23,0xab,0x73}, {0xde,0x28,0xa5,0x7a}, {0xc9,0x35,0xb7,0x61}, {0xc4,0x3e,0xb9,0x68}, +{0xe7,0x0f,0x93,0x57}, {0xea,0x04,0x9d,0x5e}, {0xfd,0x19,0x8f,0x45}, {0xf0,0x12,0x81,0x4c}, +{0x6b,0xcb,0x3b,0xab}, {0x66,0xc0,0x35,0xa2}, {0x71,0xdd,0x27,0xb9}, {0x7c,0xd6,0x29,0xb0}, +{0x5f,0xe7,0x03,0x8f}, {0x52,0xec,0x0d,0x86}, {0x45,0xf1,0x1f,0x9d}, {0x48,0xfa,0x11,0x94}, +{0x03,0x93,0x4b,0xe3}, {0x0e,0x98,0x45,0xea}, {0x19,0x85,0x57,0xf1}, {0x14,0x8e,0x59,0xf8}, +{0x37,0xbf,0x73,0xc7}, {0x3a,0xb4,0x7d,0xce}, {0x2d,0xa9,0x6f,0xd5}, {0x20,0xa2,0x61,0xdc}, +{0x6d,0xf6,0xad,0x76}, {0x60,0xfd,0xa3,0x7f}, {0x77,0xe0,0xb1,0x64}, {0x7a,0xeb,0xbf,0x6d}, +{0x59,0xda,0x95,0x52}, {0x54,0xd1,0x9b,0x5b}, {0x43,0xcc,0x89,0x40}, {0x4e,0xc7,0x87,0x49}, +{0x05,0xae,0xdd,0x3e}, {0x08,0xa5,0xd3,0x37}, {0x1f,0xb8,0xc1,0x2c}, {0x12,0xb3,0xcf,0x25}, +{0x31,0x82,0xe5,0x1a}, {0x3c,0x89,0xeb,0x13}, {0x2b,0x94,0xf9,0x08}, {0x26,0x9f,0xf7,0x01}, +{0xbd,0x46,0x4d,0xe6}, {0xb0,0x4d,0x43,0xef}, {0xa7,0x50,0x51,0xf4}, {0xaa,0x5b,0x5f,0xfd}, +{0x89,0x6a,0x75,0xc2}, {0x84,0x61,0x7b,0xcb}, {0x93,0x7c,0x69,0xd0}, {0x9e,0x77,0x67,0xd9}, +{0xd5,0x1e,0x3d,0xae}, {0xd8,0x15,0x33,0xa7}, {0xcf,0x08,0x21,0xbc}, {0xc2,0x03,0x2f,0xb5}, +{0xe1,0x32,0x05,0x8a}, {0xec,0x39,0x0b,0x83}, {0xfb,0x24,0x19,0x98}, {0xf6,0x2f,0x17,0x91}, +{0xd6,0x8d,0x76,0x4d}, {0xdb,0x86,0x78,0x44}, {0xcc,0x9b,0x6a,0x5f}, {0xc1,0x90,0x64,0x56}, +{0xe2,0xa1,0x4e,0x69}, {0xef,0xaa,0x40,0x60}, {0xf8,0xb7,0x52,0x7b}, {0xf5,0xbc,0x5c,0x72}, +{0xbe,0xd5,0x06,0x05}, {0xb3,0xde,0x08,0x0c}, {0xa4,0xc3,0x1a,0x17}, {0xa9,0xc8,0x14,0x1e}, +{0x8a,0xf9,0x3e,0x21}, {0x87,0xf2,0x30,0x28}, {0x90,0xef,0x22,0x33}, {0x9d,0xe4,0x2c,0x3a}, +{0x06,0x3d,0x96,0xdd}, {0x0b,0x36,0x98,0xd4}, {0x1c,0x2b,0x8a,0xcf}, {0x11,0x20,0x84,0xc6}, +{0x32,0x11,0xae,0xf9}, {0x3f,0x1a,0xa0,0xf0}, {0x28,0x07,0xb2,0xeb}, {0x25,0x0c,0xbc,0xe2}, +{0x6e,0x65,0xe6,0x95}, {0x63,0x6e,0xe8,0x9c}, {0x74,0x73,0xfa,0x87}, {0x79,0x78,0xf4,0x8e}, +{0x5a,0x49,0xde,0xb1}, {0x57,0x42,0xd0,0xb8}, {0x40,0x5f,0xc2,0xa3}, {0x4d,0x54,0xcc,0xaa}, +{0xda,0xf7,0x41,0xec}, {0xd7,0xfc,0x4f,0xe5}, {0xc0,0xe1,0x5d,0xfe}, {0xcd,0xea,0x53,0xf7}, +{0xee,0xdb,0x79,0xc8}, {0xe3,0xd0,0x77,0xc1}, {0xf4,0xcd,0x65,0xda}, {0xf9,0xc6,0x6b,0xd3}, +{0xb2,0xaf,0x31,0xa4}, {0xbf,0xa4,0x3f,0xad}, {0xa8,0xb9,0x2d,0xb6}, {0xa5,0xb2,0x23,0xbf}, +{0x86,0x83,0x09,0x80}, {0x8b,0x88,0x07,0x89}, {0x9c,0x95,0x15,0x92}, {0x91,0x9e,0x1b,0x9b}, +{0x0a,0x47,0xa1,0x7c}, {0x07,0x4c,0xaf,0x75}, {0x10,0x51,0xbd,0x6e}, {0x1d,0x5a,0xb3,0x67}, +{0x3e,0x6b,0x99,0x58}, {0x33,0x60,0x97,0x51}, {0x24,0x7d,0x85,0x4a}, {0x29,0x76,0x8b,0x43}, +{0x62,0x1f,0xd1,0x34}, {0x6f,0x14,0xdf,0x3d}, {0x78,0x09,0xcd,0x26}, {0x75,0x02,0xc3,0x2f}, +{0x56,0x33,0xe9,0x10}, {0x5b,0x38,0xe7,0x19}, {0x4c,0x25,0xf5,0x02}, {0x41,0x2e,0xfb,0x0b}, +{0x61,0x8c,0x9a,0xd7}, {0x6c,0x87,0x94,0xde}, {0x7b,0x9a,0x86,0xc5}, {0x76,0x91,0x88,0xcc}, +{0x55,0xa0,0xa2,0xf3}, {0x58,0xab,0xac,0xfa}, {0x4f,0xb6,0xbe,0xe1}, {0x42,0xbd,0xb0,0xe8}, +{0x09,0xd4,0xea,0x9f}, {0x04,0xdf,0xe4,0x96}, {0x13,0xc2,0xf6,0x8d}, {0x1e,0xc9,0xf8,0x84}, +{0x3d,0xf8,0xd2,0xbb}, {0x30,0xf3,0xdc,0xb2}, {0x27,0xee,0xce,0xa9}, {0x2a,0xe5,0xc0,0xa0}, +{0xb1,0x3c,0x7a,0x47}, {0xbc,0x37,0x74,0x4e}, {0xab,0x2a,0x66,0x55}, {0xa6,0x21,0x68,0x5c}, +{0x85,0x10,0x42,0x63}, {0x88,0x1b,0x4c,0x6a}, {0x9f,0x06,0x5e,0x71}, {0x92,0x0d,0x50,0x78}, +{0xd9,0x64,0x0a,0x0f}, {0xd4,0x6f,0x04,0x06}, {0xc3,0x72,0x16,0x1d}, {0xce,0x79,0x18,0x14}, +{0xed,0x48,0x32,0x2b}, {0xe0,0x43,0x3c,0x22}, {0xf7,0x5e,0x2e,0x39}, {0xfa,0x55,0x20,0x30}, +{0xb7,0x01,0xec,0x9a}, {0xba,0x0a,0xe2,0x93}, {0xad,0x17,0xf0,0x88}, {0xa0,0x1c,0xfe,0x81}, +{0x83,0x2d,0xd4,0xbe}, {0x8e,0x26,0xda,0xb7}, {0x99,0x3b,0xc8,0xac}, {0x94,0x30,0xc6,0xa5}, +{0xdf,0x59,0x9c,0xd2}, {0xd2,0x52,0x92,0xdb}, {0xc5,0x4f,0x80,0xc0}, {0xc8,0x44,0x8e,0xc9}, +{0xeb,0x75,0xa4,0xf6}, {0xe6,0x7e,0xaa,0xff}, {0xf1,0x63,0xb8,0xe4}, {0xfc,0x68,0xb6,0xed}, +{0x67,0xb1,0x0c,0x0a}, {0x6a,0xba,0x02,0x03}, {0x7d,0xa7,0x10,0x18}, {0x70,0xac,0x1e,0x11}, +{0x53,0x9d,0x34,0x2e}, {0x5e,0x96,0x3a,0x27}, {0x49,0x8b,0x28,0x3c}, {0x44,0x80,0x26,0x35}, +{0x0f,0xe9,0x7c,0x42}, {0x02,0xe2,0x72,0x4b}, {0x15,0xff,0x60,0x50}, {0x18,0xf4,0x6e,0x59}, +{0x3b,0xc5,0x44,0x66}, {0x36,0xce,0x4a,0x6f}, {0x21,0xd3,0x58,0x74}, {0x2c,0xd8,0x56,0x7d}, +{0x0c,0x7a,0x37,0xa1}, {0x01,0x71,0x39,0xa8}, {0x16,0x6c,0x2b,0xb3}, {0x1b,0x67,0x25,0xba}, +{0x38,0x56,0x0f,0x85}, {0x35,0x5d,0x01,0x8c}, {0x22,0x40,0x13,0x97}, {0x2f,0x4b,0x1d,0x9e}, +{0x64,0x22,0x47,0xe9}, {0x69,0x29,0x49,0xe0}, {0x7e,0x34,0x5b,0xfb}, {0x73,0x3f,0x55,0xf2}, +{0x50,0x0e,0x7f,0xcd}, {0x5d,0x05,0x71,0xc4}, {0x4a,0x18,0x63,0xdf}, {0x47,0x13,0x6d,0xd6}, +{0xdc,0xca,0xd7,0x31}, {0xd1,0xc1,0xd9,0x38}, {0xc6,0xdc,0xcb,0x23}, {0xcb,0xd7,0xc5,0x2a}, +{0xe8,0xe6,0xef,0x15}, {0xe5,0xed,0xe1,0x1c}, {0xf2,0xf0,0xf3,0x07}, {0xff,0xfb,0xfd,0x0e}, +{0xb4,0x92,0xa7,0x79}, {0xb9,0x99,0xa9,0x70}, {0xae,0x84,0xbb,0x6b}, {0xa3,0x8f,0xb5,0x62}, +{0x80,0xbe,0x9f,0x5d}, {0x8d,0xb5,0x91,0x54}, {0x9a,0xa8,0x83,0x4f}, {0x97,0xa3,0x8d,0x46} +}; + +word8 U4[256][4] = { +{0x00,0x00,0x00,0x00}, {0x09,0x0d,0x0b,0x0e}, {0x12,0x1a,0x16,0x1c}, {0x1b,0x17,0x1d,0x12}, +{0x24,0x34,0x2c,0x38}, {0x2d,0x39,0x27,0x36}, {0x36,0x2e,0x3a,0x24}, {0x3f,0x23,0x31,0x2a}, +{0x48,0x68,0x58,0x70}, {0x41,0x65,0x53,0x7e}, {0x5a,0x72,0x4e,0x6c}, {0x53,0x7f,0x45,0x62}, +{0x6c,0x5c,0x74,0x48}, {0x65,0x51,0x7f,0x46}, {0x7e,0x46,0x62,0x54}, {0x77,0x4b,0x69,0x5a}, +{0x90,0xd0,0xb0,0xe0}, {0x99,0xdd,0xbb,0xee}, {0x82,0xca,0xa6,0xfc}, {0x8b,0xc7,0xad,0xf2}, +{0xb4,0xe4,0x9c,0xd8}, {0xbd,0xe9,0x97,0xd6}, {0xa6,0xfe,0x8a,0xc4}, {0xaf,0xf3,0x81,0xca}, +{0xd8,0xb8,0xe8,0x90}, {0xd1,0xb5,0xe3,0x9e}, {0xca,0xa2,0xfe,0x8c}, {0xc3,0xaf,0xf5,0x82}, +{0xfc,0x8c,0xc4,0xa8}, {0xf5,0x81,0xcf,0xa6}, {0xee,0x96,0xd2,0xb4}, {0xe7,0x9b,0xd9,0xba}, +{0x3b,0xbb,0x7b,0xdb}, {0x32,0xb6,0x70,0xd5}, {0x29,0xa1,0x6d,0xc7}, {0x20,0xac,0x66,0xc9}, +{0x1f,0x8f,0x57,0xe3}, {0x16,0x82,0x5c,0xed}, {0x0d,0x95,0x41,0xff}, {0x04,0x98,0x4a,0xf1}, +{0x73,0xd3,0x23,0xab}, {0x7a,0xde,0x28,0xa5}, {0x61,0xc9,0x35,0xb7}, {0x68,0xc4,0x3e,0xb9}, +{0x57,0xe7,0x0f,0x93}, {0x5e,0xea,0x04,0x9d}, {0x45,0xfd,0x19,0x8f}, {0x4c,0xf0,0x12,0x81}, +{0xab,0x6b,0xcb,0x3b}, {0xa2,0x66,0xc0,0x35}, {0xb9,0x71,0xdd,0x27}, {0xb0,0x7c,0xd6,0x29}, +{0x8f,0x5f,0xe7,0x03}, {0x86,0x52,0xec,0x0d}, {0x9d,0x45,0xf1,0x1f}, {0x94,0x48,0xfa,0x11}, +{0xe3,0x03,0x93,0x4b}, {0xea,0x0e,0x98,0x45}, {0xf1,0x19,0x85,0x57}, {0xf8,0x14,0x8e,0x59}, +{0xc7,0x37,0xbf,0x73}, {0xce,0x3a,0xb4,0x7d}, {0xd5,0x2d,0xa9,0x6f}, {0xdc,0x20,0xa2,0x61}, +{0x76,0x6d,0xf6,0xad}, {0x7f,0x60,0xfd,0xa3}, {0x64,0x77,0xe0,0xb1}, {0x6d,0x7a,0xeb,0xbf}, +{0x52,0x59,0xda,0x95}, {0x5b,0x54,0xd1,0x9b}, {0x40,0x43,0xcc,0x89}, {0x49,0x4e,0xc7,0x87}, +{0x3e,0x05,0xae,0xdd}, {0x37,0x08,0xa5,0xd3}, {0x2c,0x1f,0xb8,0xc1}, {0x25,0x12,0xb3,0xcf}, +{0x1a,0x31,0x82,0xe5}, {0x13,0x3c,0x89,0xeb}, {0x08,0x2b,0x94,0xf9}, {0x01,0x26,0x9f,0xf7}, +{0xe6,0xbd,0x46,0x4d}, {0xef,0xb0,0x4d,0x43}, {0xf4,0xa7,0x50,0x51}, {0xfd,0xaa,0x5b,0x5f}, +{0xc2,0x89,0x6a,0x75}, {0xcb,0x84,0x61,0x7b}, {0xd0,0x93,0x7c,0x69}, {0xd9,0x9e,0x77,0x67}, +{0xae,0xd5,0x1e,0x3d}, {0xa7,0xd8,0x15,0x33}, {0xbc,0xcf,0x08,0x21}, {0xb5,0xc2,0x03,0x2f}, +{0x8a,0xe1,0x32,0x05}, {0x83,0xec,0x39,0x0b}, {0x98,0xfb,0x24,0x19}, {0x91,0xf6,0x2f,0x17}, +{0x4d,0xd6,0x8d,0x76}, {0x44,0xdb,0x86,0x78}, {0x5f,0xcc,0x9b,0x6a}, {0x56,0xc1,0x90,0x64}, +{0x69,0xe2,0xa1,0x4e}, {0x60,0xef,0xaa,0x40}, {0x7b,0xf8,0xb7,0x52}, {0x72,0xf5,0xbc,0x5c}, +{0x05,0xbe,0xd5,0x06}, {0x0c,0xb3,0xde,0x08}, {0x17,0xa4,0xc3,0x1a}, {0x1e,0xa9,0xc8,0x14}, +{0x21,0x8a,0xf9,0x3e}, {0x28,0x87,0xf2,0x30}, {0x33,0x90,0xef,0x22}, {0x3a,0x9d,0xe4,0x2c}, +{0xdd,0x06,0x3d,0x96}, {0xd4,0x0b,0x36,0x98}, {0xcf,0x1c,0x2b,0x8a}, {0xc6,0x11,0x20,0x84}, +{0xf9,0x32,0x11,0xae}, {0xf0,0x3f,0x1a,0xa0}, {0xeb,0x28,0x07,0xb2}, {0xe2,0x25,0x0c,0xbc}, +{0x95,0x6e,0x65,0xe6}, {0x9c,0x63,0x6e,0xe8}, {0x87,0x74,0x73,0xfa}, {0x8e,0x79,0x78,0xf4}, +{0xb1,0x5a,0x49,0xde}, {0xb8,0x57,0x42,0xd0}, {0xa3,0x40,0x5f,0xc2}, {0xaa,0x4d,0x54,0xcc}, +{0xec,0xda,0xf7,0x41}, {0xe5,0xd7,0xfc,0x4f}, {0xfe,0xc0,0xe1,0x5d}, {0xf7,0xcd,0xea,0x53}, +{0xc8,0xee,0xdb,0x79}, {0xc1,0xe3,0xd0,0x77}, {0xda,0xf4,0xcd,0x65}, {0xd3,0xf9,0xc6,0x6b}, +{0xa4,0xb2,0xaf,0x31}, {0xad,0xbf,0xa4,0x3f}, {0xb6,0xa8,0xb9,0x2d}, {0xbf,0xa5,0xb2,0x23}, +{0x80,0x86,0x83,0x09}, {0x89,0x8b,0x88,0x07}, {0x92,0x9c,0x95,0x15}, {0x9b,0x91,0x9e,0x1b}, +{0x7c,0x0a,0x47,0xa1}, {0x75,0x07,0x4c,0xaf}, {0x6e,0x10,0x51,0xbd}, {0x67,0x1d,0x5a,0xb3}, +{0x58,0x3e,0x6b,0x99}, {0x51,0x33,0x60,0x97}, {0x4a,0x24,0x7d,0x85}, {0x43,0x29,0x76,0x8b}, +{0x34,0x62,0x1f,0xd1}, {0x3d,0x6f,0x14,0xdf}, {0x26,0x78,0x09,0xcd}, {0x2f,0x75,0x02,0xc3}, +{0x10,0x56,0x33,0xe9}, {0x19,0x5b,0x38,0xe7}, {0x02,0x4c,0x25,0xf5}, {0x0b,0x41,0x2e,0xfb}, +{0xd7,0x61,0x8c,0x9a}, {0xde,0x6c,0x87,0x94}, {0xc5,0x7b,0x9a,0x86}, {0xcc,0x76,0x91,0x88}, +{0xf3,0x55,0xa0,0xa2}, {0xfa,0x58,0xab,0xac}, {0xe1,0x4f,0xb6,0xbe}, {0xe8,0x42,0xbd,0xb0}, +{0x9f,0x09,0xd4,0xea}, {0x96,0x04,0xdf,0xe4}, {0x8d,0x13,0xc2,0xf6}, {0x84,0x1e,0xc9,0xf8}, +{0xbb,0x3d,0xf8,0xd2}, {0xb2,0x30,0xf3,0xdc}, {0xa9,0x27,0xee,0xce}, {0xa0,0x2a,0xe5,0xc0}, +{0x47,0xb1,0x3c,0x7a}, {0x4e,0xbc,0x37,0x74}, {0x55,0xab,0x2a,0x66}, {0x5c,0xa6,0x21,0x68}, +{0x63,0x85,0x10,0x42}, {0x6a,0x88,0x1b,0x4c}, {0x71,0x9f,0x06,0x5e}, {0x78,0x92,0x0d,0x50}, +{0x0f,0xd9,0x64,0x0a}, {0x06,0xd4,0x6f,0x04}, {0x1d,0xc3,0x72,0x16}, {0x14,0xce,0x79,0x18}, +{0x2b,0xed,0x48,0x32}, {0x22,0xe0,0x43,0x3c}, {0x39,0xf7,0x5e,0x2e}, {0x30,0xfa,0x55,0x20}, +{0x9a,0xb7,0x01,0xec}, {0x93,0xba,0x0a,0xe2}, {0x88,0xad,0x17,0xf0}, {0x81,0xa0,0x1c,0xfe}, +{0xbe,0x83,0x2d,0xd4}, {0xb7,0x8e,0x26,0xda}, {0xac,0x99,0x3b,0xc8}, {0xa5,0x94,0x30,0xc6}, +{0xd2,0xdf,0x59,0x9c}, {0xdb,0xd2,0x52,0x92}, {0xc0,0xc5,0x4f,0x80}, {0xc9,0xc8,0x44,0x8e}, +{0xf6,0xeb,0x75,0xa4}, {0xff,0xe6,0x7e,0xaa}, {0xe4,0xf1,0x63,0xb8}, {0xed,0xfc,0x68,0xb6}, +{0x0a,0x67,0xb1,0x0c}, {0x03,0x6a,0xba,0x02}, {0x18,0x7d,0xa7,0x10}, {0x11,0x70,0xac,0x1e}, +{0x2e,0x53,0x9d,0x34}, {0x27,0x5e,0x96,0x3a}, {0x3c,0x49,0x8b,0x28}, {0x35,0x44,0x80,0x26}, +{0x42,0x0f,0xe9,0x7c}, {0x4b,0x02,0xe2,0x72}, {0x50,0x15,0xff,0x60}, {0x59,0x18,0xf4,0x6e}, +{0x66,0x3b,0xc5,0x44}, {0x6f,0x36,0xce,0x4a}, {0x74,0x21,0xd3,0x58}, {0x7d,0x2c,0xd8,0x56}, +{0xa1,0x0c,0x7a,0x37}, {0xa8,0x01,0x71,0x39}, {0xb3,0x16,0x6c,0x2b}, {0xba,0x1b,0x67,0x25}, +{0x85,0x38,0x56,0x0f}, {0x8c,0x35,0x5d,0x01}, {0x97,0x22,0x40,0x13}, {0x9e,0x2f,0x4b,0x1d}, +{0xe9,0x64,0x22,0x47}, {0xe0,0x69,0x29,0x49}, {0xfb,0x7e,0x34,0x5b}, {0xf2,0x73,0x3f,0x55}, +{0xcd,0x50,0x0e,0x7f}, {0xc4,0x5d,0x05,0x71}, {0xdf,0x4a,0x18,0x63}, {0xd6,0x47,0x13,0x6d}, +{0x31,0xdc,0xca,0xd7}, {0x38,0xd1,0xc1,0xd9}, {0x23,0xc6,0xdc,0xcb}, {0x2a,0xcb,0xd7,0xc5}, +{0x15,0xe8,0xe6,0xef}, {0x1c,0xe5,0xed,0xe1}, {0x07,0xf2,0xf0,0xf3}, {0x0e,0xff,0xfb,0xfd}, +{0x79,0xb4,0x92,0xa7}, {0x70,0xb9,0x99,0xa9}, {0x6b,0xae,0x84,0xbb}, {0x62,0xa3,0x8f,0xb5}, +{0x5d,0x80,0xbe,0x9f}, {0x54,0x8d,0xb5,0x91}, {0x4f,0x9a,0xa8,0x83}, {0x46,0x97,0xa3,0x8d} +}; + +word32 rcon[30] = { + 0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 }; diff --git a/raknet/rijndael.cpp b/raknet/rijndael.cpp new file mode 100644 index 0000000..7dce214 --- /dev/null +++ b/raknet/rijndael.cpp @@ -0,0 +1,801 @@ +// rijndael-alg-fst.c v2.0 August '99 +// Optimised ANSI C code +// authors: v1.0: Antoon Bosselaers +// v2.0: Vincent Rijmen + + +/* + * taken from the 'aescrypt' project: www.sf.net/projects/aescrypt + * See LICENSE-EST for the license applicable to this file + */ + + +// 14.Dec.2005 Cirilo: Removed silly hex keys; keys are now effectively unsigned char. + +// KevinJ - TODO - What the hell is __UNUS? It causes DevCPP not to compile. I don't know what this is for so I'm taking it out entirely +/* +#if (defined(__GNUC__) || defined(__GCCXML__)) +#define __UNUS __attribute__((unused)) +#else +*/ +#define __UNUS +//#endif + +#include +#include +#include +#include "rijndael.h" + +// KevinJ - Added this to just generate a random initialization vector +#include "Rand.h" + +#define SC ((BC - 4) >> 1) + +#include "rijndael-boxes.h" + +int ROUNDS; + +static word8 shifts[3][4][2] = { + { + {0, 0}, + {1, 3}, + {2, 2}, + {3, 1} + }, + + { + {0, 0}, + {1, 5}, + {2, 4}, + {3, 3} + }, + + { + {0, 0}, + {1, 7}, + {3, 5}, + {4, 4} + } +}; + +word8 mul(word8 a, word8 b) { + // multiply two elements of GF(2^m) + // needed for MixColumn and InvMixColumn + + if (a && b) + return Alogtable[(Logtable[a] + Logtable[b])%255]; + else + return 0; +} + +void KeyAddition(word8 a[4][4], word8 rk[4][4], word8 BC) { + // XOR corresponding text input and round key input bytes + int i, j; + + for(i = 0; i < BC; i++) + for(j = 0; j < 4; j++) + a[i][j] ^= rk[i][j]; +} + +void ShiftRow(word8 a[4][4], word8 d, word8 BC) { + // Row 0 remains unchanged + // The other three rows are shifted a variable amount + + word8 tmp[4]; + int i, j; + + for(i = 1; i < 4; i++) { + for(j = 0; j < BC; j++) + tmp[j] = a[(j + shifts[SC][i][d]) % BC][i]; + for(j = 0; j < BC; j++) + a[j][i] = tmp[j]; + } +} + +void Substitution(word8 a[4][4], word8 box[256], word8 BC) { + // Replace every byte of the input by the byte at that place + // in the nonlinear S-box + + int i, j; + + for(i = 0; i < BC; i++) + for(j = 0; j < 4; j++) + a[i][j] = box[a[i][j]] ; +} + +void MixColumn(word8 a[4][4], word8 BC) { + // Mix the four bytes of every column in a linear way + + word8 b[4][4]; + int i, j; + + for(j = 0; j < BC; j++) + for(i = 0; i < 4; i++) + b[j][i] = mul(2,a[j][i]) + ^ mul(3,a[j][(i + 1) % 4]) + ^ a[j][(i + 2) % 4] + ^ a[j][(i + 3) % 4]; + for(i = 0; i < 4; i++) + for(j = 0; j < BC; j++) + a[j][i] = b[j][i]; +} + +void InvMixColumn(word8 a[4][4], word8 BC) { + // Mix the four bytes of every column in a linear way + // This is the opposite operation of Mixcolumn + + int j; + + for(j = 0; j < BC; j++) + *((word32*)a[j]) = *((word32*)U1[a[j][0]]) + ^ *((word32*)U2[a[j][1]]) + ^ *((word32*)U3[a[j][2]]) + ^ *((word32*)U4[a[j][3]]); + + +} + +int rijndaelKeySched (word8 k[MAXKC][4], int keyBits __UNUS, word8 W[MAXROUNDS+1][4][4]) +{ +#ifdef _MSC_VER + #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif + + // Calculate the necessary round keys + // The number of calculations depends on keyBits and blockBits + + int j, r, t, rconpointer = 0; + word8 tk[MAXKC][4]; + int KC = ROUNDS - 6; + + for(j = KC-1; j >= 0; j--) + *((word32*)tk[j]) = *((word32*)k[j]); + r = 0; + t = 0; + // copy values into round key array + for(j = 0; (j < KC) && (r < (ROUNDS+1)); ) { + for (; (j < KC) && (t < 4); j++, t++) + *((word32*)W[r][t]) = *((word32*)tk[j]); + if (t == 4) { + r++; + t = 0; + } + } + + while (r < (ROUNDS+1)) { // while not enough round key material calculated + // calculate new values + tk[0][0] ^= S[tk[KC-1][1]]; + tk[0][1] ^= S[tk[KC-1][2]]; + tk[0][2] ^= S[tk[KC-1][3]]; + tk[0][3] ^= S[tk[KC-1][0]]; + tk[0][0] ^= rcon[rconpointer++]; + + if (KC != 8) + for(j = 1; j < KC; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + else { + for(j = 1; j < KC/2; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + tk[KC/2][0] ^= S[tk[KC/2 - 1][0]]; + tk[KC/2][1] ^= S[tk[KC/2 - 1][1]]; + tk[KC/2][2] ^= S[tk[KC/2 - 1][2]]; + tk[KC/2][3] ^= S[tk[KC/2 - 1][3]]; + for(j = KC/2 + 1; j < KC; j++) + *((word32*)tk[j]) ^= *((word32*)tk[j-1]); + } + // copy values into round key array + for(j = 0; (j < KC) && (r < (ROUNDS+1)); ) { + for (; (j < KC) && (t < 4); j++, t++) + *((word32*)W[r][t]) = *((word32*)tk[j]); + if (t == 4) { + r++; + t = 0; + } + } + } + + return 0; +} + +int rijndaelKeyEnctoDec (int keyBits __UNUS, word8 W[MAXROUNDS+1][4][4]) +{ +#ifdef _MSC_VER + #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif + int r; + + for (r = 1; r < ROUNDS; r++) { + InvMixColumn(W[r], 4); + } + return 0; +} + +int rijndaelEncrypt (word8 a[16], word8 b[16], word8 rk[MAXROUNDS+1][4][4]) +{ + // Encryption of one block. + + int r; + word8 temp[4][4]; + + *((word32*)temp[0]) = *((word32*)a) ^ *((word32*)rk[0][0]); + *((word32*)temp[1]) = *((word32*)(a+4)) ^ *((word32*)rk[0][1]); + *((word32*)temp[2]) = *((word32*)(a+8)) ^ *((word32*)rk[0][2]); + *((word32*)temp[3]) = *((word32*)(a+12)) ^ *((word32*)rk[0][3]); + *((word32*)b) = *((word32*)T1[temp[0][0]]) + ^ *((word32*)T2[temp[1][1]]) + ^ *((word32*)T3[temp[2][2]]) + ^ *((word32*)T4[temp[3][3]]); + *((word32*)(b+4)) = *((word32*)T1[temp[1][0]]) + ^ *((word32*)T2[temp[2][1]]) + ^ *((word32*)T3[temp[3][2]]) + ^ *((word32*)T4[temp[0][3]]); + *((word32*)(b+8)) = *((word32*)T1[temp[2][0]]) + ^ *((word32*)T2[temp[3][1]]) + ^ *((word32*)T3[temp[0][2]]) + ^ *((word32*)T4[temp[1][3]]); + *((word32*)(b+12)) = *((word32*)T1[temp[3][0]]) + ^ *((word32*)T2[temp[0][1]]) + ^ *((word32*)T3[temp[1][2]]) + ^ *((word32*)T4[temp[2][3]]); + for(r = 1; r < ROUNDS-1; r++) { + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[r][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[r][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[r][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[r][3]); + *((word32*)b) = *((word32*)T1[temp[0][0]]) + ^ *((word32*)T2[temp[1][1]]) + ^ *((word32*)T3[temp[2][2]]) + ^ *((word32*)T4[temp[3][3]]); + *((word32*)(b+4)) = *((word32*)T1[temp[1][0]]) + ^ *((word32*)T2[temp[2][1]]) + ^ *((word32*)T3[temp[3][2]]) + ^ *((word32*)T4[temp[0][3]]); + *((word32*)(b+8)) = *((word32*)T1[temp[2][0]]) + ^ *((word32*)T2[temp[3][1]]) + ^ *((word32*)T3[temp[0][2]]) + ^ *((word32*)T4[temp[1][3]]); + *((word32*)(b+12)) = *((word32*)T1[temp[3][0]]) + ^ *((word32*)T2[temp[0][1]]) + ^ *((word32*)T3[temp[1][2]]) + ^ *((word32*)T4[temp[2][3]]); + } + // last round is special + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[ROUNDS-1][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[ROUNDS-1][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[ROUNDS-1][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[ROUNDS-1][3]); + b[0] = T1[temp[0][0]][1]; + b[1] = T1[temp[1][1]][1]; + b[2] = T1[temp[2][2]][1]; + b[3] = T1[temp[3][3]][1]; + b[4] = T1[temp[1][0]][1]; + b[5] = T1[temp[2][1]][1]; + b[6] = T1[temp[3][2]][1]; + b[7] = T1[temp[0][3]][1]; + b[8] = T1[temp[2][0]][1]; + b[9] = T1[temp[3][1]][1]; + b[10] = T1[temp[0][2]][1]; + b[11] = T1[temp[1][3]][1]; + b[12] = T1[temp[3][0]][1]; + b[13] = T1[temp[0][1]][1]; + b[14] = T1[temp[1][2]][1]; + b[15] = T1[temp[2][3]][1]; + *((word32*)b) ^= *((word32*)rk[ROUNDS][0]); + *((word32*)(b+4)) ^= *((word32*)rk[ROUNDS][1]); + *((word32*)(b+8)) ^= *((word32*)rk[ROUNDS][2]); + *((word32*)(b+12)) ^= *((word32*)rk[ROUNDS][3]); + + return 0; +} + +int rijndaelEncryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds) +// Encrypt only a certain number of rounds. +// Only used in the Intermediate Value Known Answer Test. + +{ + int r; + word8 temp[4][4]; + + + // make number of rounds sane + if (rounds > ROUNDS) rounds = ROUNDS; + + *((word32*)a[0]) = *((word32*)a[0]) ^ *((word32*)rk[0][0]); + *((word32*)a[1]) = *((word32*)a[1]) ^ *((word32*)rk[0][1]); + *((word32*)a[2]) = *((word32*)a[2]) ^ *((word32*)rk[0][2]); + *((word32*)a[3]) = *((word32*)a[3]) ^ *((word32*)rk[0][3]); + + for(r = 1; (r <= rounds) && (r < ROUNDS); r++) { + *((word32*)temp[0]) = *((word32*)T1[a[0][0]]) + ^ *((word32*)T2[a[1][1]]) + ^ *((word32*)T3[a[2][2]]) + ^ *((word32*)T4[a[3][3]]); + *((word32*)temp[1]) = *((word32*)T1[a[1][0]]) + ^ *((word32*)T2[a[2][1]]) + ^ *((word32*)T3[a[3][2]]) + ^ *((word32*)T4[a[0][3]]); + *((word32*)temp[2]) = *((word32*)T1[a[2][0]]) + ^ *((word32*)T2[a[3][1]]) + ^ *((word32*)T3[a[0][2]]) + ^ *((word32*)T4[a[1][3]]); + *((word32*)temp[3]) = *((word32*)T1[a[3][0]]) + ^ *((word32*)T2[a[0][1]]) + ^ *((word32*)T3[a[1][2]]) + ^ *((word32*)T4[a[2][3]]); + *((word32*)a[0]) = *((word32*)temp[0]) ^ *((word32*)rk[r][0]); + *((word32*)a[1]) = *((word32*)temp[1]) ^ *((word32*)rk[r][1]); + *((word32*)a[2]) = *((word32*)temp[2]) ^ *((word32*)rk[r][2]); + *((word32*)a[3]) = *((word32*)temp[3]) ^ *((word32*)rk[r][3]); + } + if (rounds == ROUNDS) { + // last round is special + temp[0][0] = T1[a[0][0]][1]; + temp[0][1] = T1[a[1][1]][1]; + temp[0][2] = T1[a[2][2]][1]; + temp[0][3] = T1[a[3][3]][1]; + temp[1][0] = T1[a[1][0]][1]; + temp[1][1] = T1[a[2][1]][1]; + temp[1][2] = T1[a[3][2]][1]; + temp[1][3] = T1[a[0][3]][1]; + temp[2][0] = T1[a[2][0]][1]; + temp[2][1] = T1[a[3][1]][1]; + temp[2][2] = T1[a[0][2]][1]; + temp[2][3] = T1[a[1][3]][1]; + temp[3][0] = T1[a[3][0]][1]; + temp[3][1] = T1[a[0][1]][1]; + temp[3][2] = T1[a[1][2]][1]; + temp[3][3] = T1[a[2][3]][1]; + *((word32*)a[0]) = *((word32*)temp[0]) ^ *((word32*)rk[ROUNDS][0]); + *((word32*)a[1]) = *((word32*)temp[1]) ^ *((word32*)rk[ROUNDS][1]); + *((word32*)a[2]) = *((word32*)temp[2]) ^ *((word32*)rk[ROUNDS][2]); + *((word32*)a[3]) = *((word32*)temp[3]) ^ *((word32*)rk[ROUNDS][3]); + } + + return 0; +} + + +int rijndaelDecrypt (word8 a[16], word8 b[16], word8 rk[MAXROUNDS+1][4][4]) +{ + int r; + word8 temp[4][4]; + + + *((word32*)temp[0]) = *((word32*)a) ^ *((word32*)rk[ROUNDS][0]); + *((word32*)temp[1]) = *((word32*)(a+4)) ^ *((word32*)rk[ROUNDS][1]); + *((word32*)temp[2]) = *((word32*)(a+8)) ^ *((word32*)rk[ROUNDS][2]); + *((word32*)temp[3]) = *((word32*)(a+12)) ^ *((word32*)rk[ROUNDS][3]); + *((word32*)b) = *((word32*)T5[temp[0][0]]) + ^ *((word32*)T6[temp[3][1]]) + ^ *((word32*)T7[temp[2][2]]) + ^ *((word32*)T8[temp[1][3]]); + *((word32*)(b+4)) = *((word32*)T5[temp[1][0]]) + ^ *((word32*)T6[temp[0][1]]) + ^ *((word32*)T7[temp[3][2]]) + ^ *((word32*)T8[temp[2][3]]); + *((word32*)(b+8)) = *((word32*)T5[temp[2][0]]) + ^ *((word32*)T6[temp[1][1]]) + ^ *((word32*)T7[temp[0][2]]) + ^ *((word32*)T8[temp[3][3]]); + *((word32*)(b+12)) = *((word32*)T5[temp[3][0]]) + ^ *((word32*)T6[temp[2][1]]) + ^ *((word32*)T7[temp[1][2]]) + ^ *((word32*)T8[temp[0][3]]); + for(r = ROUNDS-1; r > 1; r--) { + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[r][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[r][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[r][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[r][3]); + *((word32*)b) = *((word32*)T5[temp[0][0]]) + ^ *((word32*)T6[temp[3][1]]) + ^ *((word32*)T7[temp[2][2]]) + ^ *((word32*)T8[temp[1][3]]); + *((word32*)(b+4)) = *((word32*)T5[temp[1][0]]) + ^ *((word32*)T6[temp[0][1]]) + ^ *((word32*)T7[temp[3][2]]) + ^ *((word32*)T8[temp[2][3]]); + *((word32*)(b+8)) = *((word32*)T5[temp[2][0]]) + ^ *((word32*)T6[temp[1][1]]) + ^ *((word32*)T7[temp[0][2]]) + ^ *((word32*)T8[temp[3][3]]); + *((word32*)(b+12)) = *((word32*)T5[temp[3][0]]) + ^ *((word32*)T6[temp[2][1]]) + ^ *((word32*)T7[temp[1][2]]) + ^ *((word32*)T8[temp[0][3]]); + } + // last round is special + *((word32*)temp[0]) = *((word32*)b) ^ *((word32*)rk[1][0]); + *((word32*)temp[1]) = *((word32*)(b+4)) ^ *((word32*)rk[1][1]); + *((word32*)temp[2]) = *((word32*)(b+8)) ^ *((word32*)rk[1][2]); + *((word32*)temp[3]) = *((word32*)(b+12)) ^ *((word32*)rk[1][3]); + b[0] = S5[temp[0][0]]; + b[1] = S5[temp[3][1]]; + b[2] = S5[temp[2][2]]; + b[3] = S5[temp[1][3]]; + b[4] = S5[temp[1][0]]; + b[5] = S5[temp[0][1]]; + b[6] = S5[temp[3][2]]; + b[7] = S5[temp[2][3]]; + b[8] = S5[temp[2][0]]; + b[9] = S5[temp[1][1]]; + b[10] = S5[temp[0][2]]; + b[11] = S5[temp[3][3]]; + b[12] = S5[temp[3][0]]; + b[13] = S5[temp[2][1]]; + b[14] = S5[temp[1][2]]; + b[15] = S5[temp[0][3]]; + *((word32*)b) ^= *((word32*)rk[0][0]); + *((word32*)(b+4)) ^= *((word32*)rk[0][1]); + *((word32*)(b+8)) ^= *((word32*)rk[0][2]); + *((word32*)(b+12)) ^= *((word32*)rk[0][3]); + + return 0; +} + + +int rijndaelDecryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds) +// Decrypt only a certain number of rounds. +// Only used in the Intermediate Value Known Answer Test. +// Operations rearranged such that the intermediate values +// of decryption correspond with the intermediate values +// of encryption. + +{ + int r; + + + // make number of rounds sane + if (rounds > ROUNDS) rounds = ROUNDS; + + // First the special round: + // without InvMixColumn + // with extra KeyAddition + + KeyAddition(a,rk[ROUNDS],4); + Substitution(a,Si,4); + ShiftRow(a,1,4); + + // ROUNDS-1 ordinary rounds + + for(r = ROUNDS-1; r > rounds; r--) { + KeyAddition(a,rk[r],4); + InvMixColumn(a,4); + Substitution(a,Si,4); + ShiftRow(a,1,4); + } + + if (rounds == 0) { + // End with the extra key addition + + KeyAddition(a,rk[0],4); + } + + return 0; +} + +/*** End Rijndael algorithm, Begin the AES Interface ***/ + + +int makeKey(keyInstance *key, BYTE direction, int keyByteLen, char *keyMaterial) +{ + word8 k[MAXKC][4]; + int i; + int keyLen = keyByteLen*8; + + if (key == NULL) { + return BAD_KEY_INSTANCE; + } + + if ((direction == DIR_ENCRYPT) || (direction == DIR_DECRYPT)) { + key->direction = direction; + } else { + return BAD_KEY_DIR; + } + + if ((keyLen == 128) || (keyLen == 192) || (keyLen == 256)) { + key->keyLen = keyLen; + } else { + return BAD_KEY_MAT; + } + + if ( keyMaterial ) { + strncpy(key->keyMaterial, keyMaterial, keyByteLen); + } else { + return BAD_KEY_MAT; + } + + ROUNDS = keyLen/32 + 6; + + // initialize key schedule: + for(i = 0; i < key->keyLen/8; i++) { + k[i / 4][i % 4] = (word8) key->keyMaterial[i]; + } + rijndaelKeySched (k, key->keyLen, key->keySched); + if (direction == DIR_DECRYPT) + rijndaelKeyEnctoDec (key->keyLen, key->keySched); + + return TRUE; +} + +int cipherInit(cipherInstance *cipher, BYTE mode, char *IV) +{ + int i; + + if ((mode == MODE_ECB) || (mode == MODE_CBC) || (mode == MODE_CFB1)) { + cipher->mode = mode; + } else { + return BAD_CIPHER_MODE; + } + + + if (IV != NULL) { + for(i = 0; i < 16; i++) cipher->IV[i] = IV[i]; + } + else + { + // KevinJ - Added this to just generate a random initialization vector + for(i = 0; i < 16; i++) + cipher->IV[i]=(BYTE)randomMT(); + } + + return TRUE; +} + + +int blockEncrypt(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputByteLen, BYTE *outBuffer) +{ + int i, k, numBlocks; + word8 block[16], iv[4][4]; + int inputLen = inputByteLen*8; + + if (cipher == NULL || + key == NULL || + key->direction == DIR_DECRYPT) { + return BAD_CIPHER_STATE; + } + + + numBlocks = inputLen/128; + + switch (cipher->mode) { + case MODE_ECB: + for (i = numBlocks; i > 0; i--) { + + rijndaelEncrypt (input, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CBC: +#if STRICT_ALIGN + memcpy(block,cipher->IV,16); +#else + *((word32*)block) = *((word32*)(cipher->IV)); + *((word32*)(block+4)) = *((word32*)(cipher->IV+4)); + *((word32*)(block+8)) = *((word32*)(cipher->IV+8)); + *((word32*)(block+12)) = *((word32*)(cipher->IV+12)); +#endif + + for (i = numBlocks; i > 0; i--) { + *((word32*)block) ^= *((word32*)(input)); + *((word32*)(block+4)) ^= *((word32*)(input+4)); + *((word32*)(block+8)) ^= *((word32*)(input+8)); + *((word32*)(block+12)) ^= *((word32*)(input+12)); + + rijndaelEncrypt (block, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CFB1: +#if STRICT_ALIGN + memcpy(iv,cipher->IV,16); +#else + *((word32*)iv[0]) = *((word32*)(cipher->IV)); + *((word32*)iv[1]) = *((word32*)(cipher->IV+4)); + *((word32*)iv[2]) = *((word32*)(cipher->IV+8)); + *((word32*)iv[3]) = *((word32*)(cipher->IV+12)); +#endif + for (i = numBlocks; i > 0; i--) { + for (k = 0; k < 128; k++) { + *((word32*)block) = *((word32*)iv[0]); + *((word32*)(block+4)) = *((word32*)iv[1]); + *((word32*)(block+8)) = *((word32*)iv[2]); + *((word32*)(block+12)) = *((word32*)iv[3]); + + rijndaelEncrypt (block, block, key->keySched); + outBuffer[k/8] ^= (block[0] & 0x80) >> (k & 7); + iv[0][0] = (iv[0][0] << 1) | (iv[0][1] >> 7); + iv[0][1] = (iv[0][1] << 1) | (iv[0][2] >> 7); + iv[0][2] = (iv[0][2] << 1) | (iv[0][3] >> 7); + iv[0][3] = (iv[0][3] << 1) | (iv[1][0] >> 7); + iv[1][0] = (iv[1][0] << 1) | (iv[1][1] >> 7); + iv[1][1] = (iv[1][1] << 1) | (iv[1][2] >> 7); + iv[1][2] = (iv[1][2] << 1) | (iv[1][3] >> 7); + iv[1][3] = (iv[1][3] << 1) | (iv[2][0] >> 7); + iv[2][0] = (iv[2][0] << 1) | (iv[2][1] >> 7); + iv[2][1] = (iv[2][1] << 1) | (iv[2][2] >> 7); + iv[2][2] = (iv[2][2] << 1) | (iv[2][3] >> 7); + iv[2][3] = (iv[2][3] << 1) | (iv[3][0] >> 7); + iv[3][0] = (iv[3][0] << 1) | (iv[3][1] >> 7); + iv[3][1] = (iv[3][1] << 1) | (iv[3][2] >> 7); + iv[3][2] = (iv[3][2] << 1) | (iv[3][3] >> 7); + iv[3][3] = (word8)((iv[3][3] << 1) | (outBuffer[k/8] >> (7-(k&7))) & 1); + } + } + break; + + default: + return BAD_CIPHER_STATE; + } + + return numBlocks*128; +} + +int blockDecrypt(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputByteLen, BYTE *outBuffer) +{ + int i, k, numBlocks; + word8 block[16], iv[4][4]; + int inputLen = inputByteLen*8; + + if (cipher == NULL || + key == NULL || + cipher->mode != MODE_CFB1 && key->direction == DIR_ENCRYPT) { + return BAD_CIPHER_STATE; + } + + + numBlocks = inputLen/128; + + switch (cipher->mode) { + case MODE_ECB: + for (i = numBlocks; i > 0; i--) { + + rijndaelDecrypt (input, outBuffer, key->keySched); + + input += 16; + outBuffer += 16; + + } + break; + + case MODE_CBC: + // first block + + rijndaelDecrypt (input, block, key->keySched); +#if STRICT_ALIGN + memcpy(outBuffer,cipher->IV,16); + *((word32*)(outBuffer)) ^= *((word32*)block); + *((word32*)(outBuffer+4)) ^= *((word32*)(block+4)); + *((word32*)(outBuffer+8)) ^= *((word32*)(block+8)); + *((word32*)(outBuffer+12)) ^= *((word32*)(block+12)); +#else + *((word32*)(outBuffer)) = *((word32*)block) ^ *((word32*)(cipher->IV)); + *((word32*)(outBuffer+4)) = *((word32*)(block+4)) ^ *((word32*)(cipher->IV+4)); + *((word32*)(outBuffer+8)) = *((word32*)(block+8)) ^ *((word32*)(cipher->IV+8)); + *((word32*)(outBuffer+12)) = *((word32*)(block+12)) ^ *((word32*)(cipher->IV+12)); +#endif + + // next blocks + for (i = numBlocks-1; i > 0; i--) { + + rijndaelDecrypt (input, block, key->keySched); + + *((word32*)(outBuffer+16)) = *((word32*)block) ^ + *((word32*)(input-16)); + *((word32*)(outBuffer+20)) = *((word32*)(block+4)) ^ + *((word32*)(input-12)); + *((word32*)(outBuffer+24)) = *((word32*)(block+8)) ^ + *((word32*)(input-8)); + *((word32*)(outBuffer+28)) = *((word32*)(block+12)) ^ + *((word32*)(input-4)); + + input += 16; + outBuffer += 16; + } + break; + + case MODE_CFB1: +#if STRICT_ALIGN + memcpy(iv,cipher->IV,16); +#else + *((word32*)iv[0]) = *((word32*)(cipher->IV)); + *((word32*)iv[1]) = *((word32*)(cipher->IV+4)); + *((word32*)iv[2]) = *((word32*)(cipher->IV+8)); + *((word32*)iv[3]) = *((word32*)(cipher->IV+12)); +#endif + for (i = numBlocks; i > 0; i--) { + for (k = 0; k < 128; k++) { + *((word32*)block) = *((word32*)iv[0]); + *((word32*)(block+4)) = *((word32*)iv[1]); + *((word32*)(block+8)) = *((word32*)iv[2]); + *((word32*)(block+12)) = *((word32*)iv[3]); + + rijndaelEncrypt (block, block, key->keySched); + iv[0][0] = (iv[0][0] << 1) | (iv[0][1] >> 7); + iv[0][1] = (iv[0][1] << 1) | (iv[0][2] >> 7); + iv[0][2] = (iv[0][2] << 1) | (iv[0][3] >> 7); + iv[0][3] = (iv[0][3] << 1) | (iv[1][0] >> 7); + iv[1][0] = (iv[1][0] << 1) | (iv[1][1] >> 7); + iv[1][1] = (iv[1][1] << 1) | (iv[1][2] >> 7); + iv[1][2] = (iv[1][2] << 1) | (iv[1][3] >> 7); + iv[1][3] = (iv[1][3] << 1) | (iv[2][0] >> 7); + iv[2][0] = (iv[2][0] << 1) | (iv[2][1] >> 7); + iv[2][1] = (iv[2][1] << 1) | (iv[2][2] >> 7); + iv[2][2] = (iv[2][2] << 1) | (iv[2][3] >> 7); + iv[2][3] = (iv[2][3] << 1) | (iv[3][0] >> 7); + iv[3][0] = (iv[3][0] << 1) | (iv[3][1] >> 7); + iv[3][1] = (iv[3][1] << 1) | (iv[3][2] >> 7); + iv[3][2] = (iv[3][2] << 1) | (iv[3][3] >> 7); + iv[3][3] = (word8)((iv[3][3] << 1) | (input[k/8] >> (7-(k&7))) & 1); + outBuffer[k/8] ^= (block[0] & 0x80) >> (k & 7); + } + } + break; + + default: + return BAD_CIPHER_STATE; + } + + return numBlocks*128; +} + + +/** + * cipherUpdateRounds: + * + * Encrypts/Decrypts exactly one full block a specified number of rounds. + * Only used in the Intermediate Value Known Answer Test. + * + * Returns: + * TRUE - on success + * BAD_CIPHER_STATE - cipher in bad state (e.g., not initialized) + */ + +int cipherUpdateRounds(cipherInstance *cipher, + keyInstance *key, BYTE *input, int inputLen __UNUS, BYTE *outBuffer, int rounds) +{ +#ifdef _MSC_VER + #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter +#endif + + int j; + word8 block[4][4]; + + if (cipher == NULL || + key == NULL) { + return BAD_CIPHER_STATE; + } + + for (j = 3; j >= 0; j--) { + // parse input stream into rectangular array + *((word32*)block[j]) = *((word32*)(input+4*j)); + } + + switch (key->direction) { + case DIR_ENCRYPT: + rijndaelEncryptRound (block, key->keySched, rounds); + break; + + case DIR_DECRYPT: + rijndaelDecryptRound (block, key->keySched, rounds); + break; + + default: return BAD_KEY_DIR; + } + + for (j = 3; j >= 0; j--) { + // parse rectangular array into output ciphertext bytes + *((word32*)(outBuffer+4*j)) = *((word32*)block[j]); + } + + return TRUE; +} diff --git a/raknet/rijndael.h b/raknet/rijndael.h new file mode 100644 index 0000000..b984d64 --- /dev/null +++ b/raknet/rijndael.h @@ -0,0 +1,125 @@ +/// \file +/// \brief \b [Internal] AES encoding / decoding +/// rijndael-alg-fst.h v2.0 August '99 +/// Optimised ANSI C code +/// taken from the 'aescrypt' project: www.sf.net/projects/aescrypt +/// See LICENSE-EST for the license applicable to this file + + +/// \note Although the routines claim to support 192 and 256 bit blocks, +/// don't take your chances - stick to the 128 bit (16 byte) blocks unless +/// you've run tests to prove that 192 and 256 are correctly supported. +/// - Cirilo + + +#include + +#ifndef __RIJNDAEL_ALG_H +#define __RIJNDAEL_ALG_H + +#define MAXKC (256/32) +#define MAXROUNDS 14 + +typedef unsigned char word8; +typedef unsigned short word16; +typedef unsigned int word32; + +int rijndaelKeySched (word8 k[MAXKC][4], int keyBits, + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelKeyEnctoDec (int keyBits, word8 W[MAXROUNDS+1][4][4]); +int rijndaelEncrypt (word8 a[16], word8 b[16], + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelEncryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds); +int rijndaelDecrypt (word8 a[16], word8 b[16], + word8 rk[MAXROUNDS+1][4][4]); +int rijndaelDecryptRound (word8 a[4][4], + word8 rk[MAXROUNDS+1][4][4], int rounds); + +#endif + +// End of algorithm headers. begin the AES API header defs + + +#ifndef __RIJNDAEL_API_H +#define __RIJNDAEL_API_H + +// rijndael-api-fst.h v2.0 August '99 +// Optimised ANSI C code + + +// Defines: +// Add any additional defines you need + + +#define DIR_ENCRYPT 0 // Are we encrpyting? +#define DIR_DECRYPT 1 // Are we decrpyting? +#define MODE_ECB 1 // Are we ciphering in ECB mode? +#define MODE_CBC 2 // Are we ciphering in CBC mode? +#define MODE_CFB1 3 // Are we ciphering in 1-bit CFB mode? +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#define BITSPERBLOCK 128 // Default number of bits in a cipher block + +// Error Codes - CHANGE POSSIBLE: inclusion of additional error codes +#define BAD_KEY_DIR -1 // Key direction is invalid, e.g., unknown value +#define BAD_KEY_MAT -2 // Key material not of correct length +#define BAD_KEY_INSTANCE -3 // Key passed is not valid +#define BAD_CIPHER_MODE -4 // Params struct passed to cipherInit invalid +#define BAD_CIPHER_STATE -5 // Cipher in wrong state (e.g., not initialized) +#define BAD_BLOCK_LENGTH -6 +#define BAD_CIPHER_INSTANCE -7 + + +// CHANGE POSSIBLE: inclusion of algorithm specific defines +// 14.Dec.2005 Cirilo: keys are now unsigned char rather than hex (ASCII) +#define MAX_KEY_SIZE 32 // # of unsigned char's needed to represent a key +#define MAX_IV_SIZE 16 // # bytes needed to represent an IV + +// Typedefs: +// Typedef'ed data storage elements. Add any algorithm specific parameters at the bottom of the structs as appropriate. + +typedef unsigned char BYTE; + +// The structure for key information +typedef struct { + BYTE direction; /// Key used for encrypting or decrypting? + int keyLen; /// Length of the key + char keyMaterial[MAX_KEY_SIZE+1]; /// Raw key data in ASCII, e.g., user input or KAT values + /// The following parameters are algorithm dependent, replace or add as necessary + int blockLen; /// block length + word8 keySched[MAXROUNDS+1][4][4]; /// key schedule + } keyInstance; + +// The structure for cipher information +typedef struct { // changed order of the components + BYTE mode; /// MODE_ECB, MODE_CBC, or MODE_CFB1 + BYTE IV[MAX_IV_SIZE]; /// A possible Initialization Vector for ciphering + // Add any algorithm specific parameters needed here + int blockLen; /// Sample: Handles non-128 bit block sizes (if available) + } cipherInstance; + + +// Function protoypes +// CHANGED: makeKey(): parameter blockLen added this parameter is absolutely necessary if you want to +// setup the round keys in a variable block length setting +// cipherInit(): parameter blockLen added (for obvious reasons) + +int makeKey(keyInstance *key, BYTE direction, int keyLen, char *keyMaterial); + +int cipherInit(cipherInstance *cipher, BYTE mode, char *IV); + +int blockEncrypt(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer); + +int blockDecrypt(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer); +int cipherUpdateRounds(cipherInstance *cipher, keyInstance *key, BYTE *input, + int inputLen, BYTE *outBuffer, int Rounds); + + +#endif // __RIJNDAEL_API_H diff --git a/raknet/systemaddresslist.h b/raknet/systemaddresslist.h new file mode 100644 index 0000000..acaf8c2 --- /dev/null +++ b/raknet/systemaddresslist.h @@ -0,0 +1,46 @@ +/// \file +/// \brief Just a class to hold a list of systems +/// +/// This file is part of RakNet Copyright 2003 Kevin Jenkins. +/// +/// Usage of RakNet is subject to the appropriate license agreement. +/// Creative Commons Licensees are subject to the +/// license found at +/// http://creativecommons.org/licenses/by-nc/2.5/ +/// Single application licensees are subject to the license found at +/// http://www.rakkarsoft.com/SingleApplicationLicense.html +/// Custom license users are subject to the terms therein. +/// GPL license users are subject to the GNU General Public +/// License as published by the Free +/// Software Foundation; either version 2 of the License, or (at your +/// option) any later version. + +#ifndef __SYSTEM_ID_LIST_H +#define __SYSTEM_ID_LIST_H + +#include "NetworkTypes.h" +#include "DS_OrderedList.h" + +class SystemAddressList +{ +public: + SystemAddressList(); + SystemAddressList(PlayerID system); + void AddSystem(PlayerID system); + void RandomizeOrder(void); + void Serialize(RakNet::BitStream *out); + bool Deserialize(RakNet::BitStream *in); + bool Save(const char *filename); + bool Load(const char *filename); + void RemoveSystem(PlayerID system); + unsigned Size(void) const; + PlayerID& operator[] ( const unsigned int position ) const; + void Clear(void); + + DataStructures::List * GetList(void); + +protected: + DataStructures::List systemList; +}; + +#endif diff --git a/saco/game/game.cpp b/saco/game/game.cpp index 43d75c0..068ef0d 100644 --- a/saco/game/game.cpp +++ b/saco/game/game.cpp @@ -587,6 +587,19 @@ int CGame::GetWorldWeather() //----------------------------------------------------------- +void CGame::DisplayHud(BOOL bDisp) +{ + if(bDisp) { + *(BYTE*)ADDR_ENABLE_HUD = 1; + ToggleRadar(1); + } else { + *(BYTE*)ADDR_ENABLE_HUD = 0; + ToggleRadar(0); + } +} + +//----------------------------------------------------------- + BYTE CGame::IsHudEnabled() { return *(BYTE*)ADDR_ENABLE_HUD; diff --git a/saco/saco.vcproj b/saco/saco.vcproj index ce40b3a..aa17650 100644 --- a/saco/saco.vcproj +++ b/saco/saco.vcproj @@ -373,45 +373,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -421,12 +682,27 @@ + + + + + + + + + + @@ -469,6 +745,36 @@ + + + + + + + + + + + + + + + + + + + + @@ -478,6 +784,9 @@ + + @@ -493,6 +802,9 @@ + + @@ -508,6 +820,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -515,10 +863,16 @@ RelativePath="..\raknet\TCPInterface.h"> + RelativePath="..\raknet\TelnetTransport.cpp"> + RelativePath="..\raknet\TelnetTransport.h"> + + + + diff --git a/server/server.vcproj b/server/server.vcproj index d52bb88..993ae51 100644 --- a/server/server.vcproj +++ b/server/server.vcproj @@ -166,45 +166,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,12 +469,27 @@ + + + + + + + + + + @@ -262,6 +532,36 @@ + + + + + + + + + + + + + + + + + + + + @@ -271,6 +571,9 @@ + + @@ -286,6 +589,9 @@ + + @@ -301,6 +607,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -313,6 +655,18 @@ + + + + + + + +