/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "stdh.h" #include #include #include #include #include #include #include #include #include #include static struct ErrorCode ErrorCodes[] = { // message types ERRORCODE(MSG_REQ_ENUMSERVERS, "MSG_REQ_ENUMSERVERS"), ERRORCODE(MSG_SERVERINFO, "MSG_SERVERINFO"), ERRORCODE(MSG_INF_DISCONNECTED, "MSG_INF_DISCONNECTED"), ERRORCODE(MSG_REQ_CONNECTLOCALSESSIONSTATE, "MSG_REQ_CONNECTLOCALSESSIONSTATE"), ERRORCODE(MSG_REP_CONNECTLOCALSESSIONSTATE, "MSG_REP_CONNECTLOCALSESSIONSTATE"), ERRORCODE(MSG_REQ_CONNECTREMOTESESSIONSTATE, "MSG_REQ_CONNECTREMOTESESSIONSTATE"), ERRORCODE(MSG_REP_CONNECTREMOTESESSIONSTATE, "MSG_REP_CONNECTREMOTESESSIONSTATE"), ERRORCODE(MSG_REQ_STATEDELTA, "MSG_REQ_STATEDELTA"), ERRORCODE(MSG_REQ_STATEDELTA, "MSG_REP_STATEDELTA"), ERRORCODE(MSG_REQ_CONNECTPLAYER, "MSG_REQ_CONNECTPLAYER"), ERRORCODE(MSG_REP_CONNECTPLAYER, "MSG_REP_CONNECTPLAYER"), ERRORCODE(MSG_ACTION, "MSG_ACTION"), ERRORCODE(MSG_ACTIONPREDICT, "MSG_ACTIONPREDICT"), ERRORCODE(MSG_SEQ_ALLACTIONS, "MSG_SEQ_ALLACTIONS"), ERRORCODE(MSG_SEQ_ADDPLAYER, "MSG_SEQ_ADDPLAYER"), ERRORCODE(MSG_SEQ_REMPLAYER, "MSG_SEQ_REMPLAYER"), ERRORCODE(MSG_GAMESTREAMBLOCKS, "MSG_GAMESTREAMBLOCKS"), ERRORCODE(MSG_REQUESTGAMESTREAMRESEND, "MSG_REQUESTGAMESTREAMRESEND"), }; extern struct ErrorTable MessageTypes = ERRORTABLE(ErrorCodes); ///////////////////////////////////////////////////////////////////// // CNetworkMessage /* * Constructor for empty message (for receiving). */ CNetworkMessage::CNetworkMessage(void) { // allocate message buffer nm_slMaxSize = MAX_NETWORKMESSAGE_SIZE; nm_pubMessage = (UBYTE*) AllocMemory(nm_slMaxSize); // mangle pointer and size so that it could not be accidentally read/written nm_pubPointer = NULL; nm_iBit = -1; nm_slSize = -1; } // reinit a message that is to be sent (to write different contents) void CNetworkMessage::Reinit(void) { // init read/write pointer and size nm_slMaxSize = MAX_NETWORKMESSAGE_SIZE; nm_pubPointer = nm_pubMessage; nm_iBit = 0; nm_slSize = 0; // write the type UBYTE ubType = nm_mtType; Write(&ubType, sizeof(ubType)); } /* * Constructor for initializing message that is to be sent. */ CNetworkMessage::CNetworkMessage(MESSAGETYPE mtType) { // allocate message buffer nm_slMaxSize = MAX_NETWORKMESSAGE_SIZE; nm_pubMessage = (UBYTE*) AllocMemory(nm_slMaxSize); // init read/write pointer and size nm_pubPointer = nm_pubMessage; nm_iBit = 0; nm_slSize = 0; // remember the type nm_mtType = mtType; // write the type UBYTE ubType = nm_mtType; Write(&ubType, sizeof(ubType)); } /* Copying. */ CNetworkMessage::CNetworkMessage(const CNetworkMessage &nmOriginal) { // allocate message buffer nm_slMaxSize = nmOriginal.nm_slMaxSize; nm_pubMessage = (UBYTE*) AllocMemory(nm_slMaxSize); // init read/write pointer and size nm_pubPointer = nm_pubMessage + (nmOriginal.nm_pubPointer-nmOriginal.nm_pubMessage); nm_iBit = nmOriginal.nm_iBit; nm_slSize = nmOriginal.nm_slSize; // copy from original memcpy(nm_pubMessage, nmOriginal.nm_pubMessage, nm_slSize); // remember the type nm_mtType = nmOriginal.nm_mtType; } void CNetworkMessage::operator=(const CNetworkMessage &nmOriginal) { if (nm_slMaxSize != nmOriginal.nm_slMaxSize) { if (nm_pubMessage!=NULL) { FreeMemory(nm_pubMessage); } // allocate message buffer nm_slMaxSize = nmOriginal.nm_slMaxSize; nm_pubMessage = (UBYTE*) AllocMemory(nm_slMaxSize); } // init read/write pointer and size nm_pubPointer = nm_pubMessage+sizeof(UBYTE);// + (nmOriginal.nm_pubPointer-nmOriginal.nm_pubMessage); nm_iBit = 0; nm_slSize = nmOriginal.nm_slSize; // copy from original memcpy(nm_pubMessage, nmOriginal.nm_pubMessage, nm_slSize); // remember the type nm_mtType = nmOriginal.nm_mtType; } /* * Destructor. */ CNetworkMessage::~CNetworkMessage(void) { ASSERT(nm_pubMessage!=NULL); if (nm_pubMessage!=NULL) { FreeMemory(nm_pubMessage); } } /* * Ignore the contents of this message. */ void CNetworkMessage::IgnoreContents(void) { } /* Check if end of message. */ BOOL CNetworkMessage::EndOfMessage(void) { ASSERTMSG((nm_pubPointer-nm_pubMessage) <= nm_slSize, "Message over-reading!"); return (nm_pubPointer-nm_pubMessage) >= nm_slSize; } // read/write functions void CNetworkMessage::Read(void *pvBuffer, SLONG slSize) { if (nm_pubPointer+slSize > nm_pubMessage+nm_slSize) { CPrintF(TRANS("Warning: Message over-reading!\n")); ASSERT(FALSE); memset(pvBuffer, 0, slSize); return; } memcpy(pvBuffer, nm_pubPointer, slSize); nm_pubPointer += slSize; nm_iBit = 0; } void CNetworkMessage::Write(const void *pvBuffer, SLONG slSize) { if (nm_pubPointer+slSize > nm_pubMessage+nm_slMaxSize) { CPrintF(TRANS("Warning: Message over-writing!\n")); ASSERT(FALSE); return; } memcpy(nm_pubPointer, pvBuffer, slSize); nm_pubPointer += slSize; nm_iBit = 0; nm_slSize += slSize; } CNetworkMessage &CNetworkMessage::operator>>(CTString &str) { // start reading string from message str = ""; nm_iBit = 0; // repeat for(;;) { // if reached end of message (this happens when we read string-only messages) if (nm_pubPointer-nm_pubMessage>=nm_slSize) { // stop return *this; } // get next char char strChar[2]; strChar[0] = *nm_pubPointer++; strChar[1] = 0; // if end of string if (strChar[0]==0) { // stop return *this; // if normal char } else { // append to the string str+=strChar; } } } CNetworkMessage &CNetworkMessage::operator<<(const CTString &str) { // start writing string to message nm_iBit = 0; const char *pstr = (const char *)str; // repeat for(;;) { // if reached one byte before end of message if (nm_pubPointer-nm_pubMessage>=nm_slMaxSize-1) { // put the end marker *nm_pubPointer++ = 0; nm_slSize++; // report error and stop CPrintF(TRANS("Warning: Message over-writing!\n")); ASSERT(FALSE); return *this; } // get next char const char chr = *pstr++; // write it to message *nm_pubPointer++ = chr; nm_slSize++; // if end if (chr==0) { // stop return *this; } } } /* * Insert a sub-message into this message. */ void CNetworkMessage::InsertSubMessage(const CNetworkMessage &nmSubMessage) { // write sub-message size operator<<(nmSubMessage.nm_slSize); // write the contents of the sub-message Write(nmSubMessage.nm_pubMessage, nmSubMessage.nm_slSize); } /* * Extract a sub-message from this message. */ void CNetworkMessage::ExtractSubMessage(CNetworkMessage &nmSubMessage) { // read sub-message size operator>>(nmSubMessage.nm_slSize); // read the contents of the sub-message Read(nmSubMessage.nm_pubMessage, nmSubMessage.nm_slSize); // init the submessage read/write pointer nmSubMessage.nm_pubPointer = nmSubMessage.nm_pubMessage; nmSubMessage.nm_iBit = 0; // get the submessage type UBYTE ubType = 0; nmSubMessage>>ubType; nmSubMessage.nm_mtType = (MESSAGETYPE)ubType; } // rewind message to start, so that written message can be read again void CNetworkMessage::Rewind(void) { nm_pubPointer = nm_pubMessage+sizeof(UBYTE); nm_iBit = 0; } /* * Pack a message to another message (message type is left untouched). */ void CNetworkMessage::Pack(CNetworkMessage &nmPacked, CCompressor &comp) { // get size and pointers for packing, leave the message type alone SLONG slUnpackedSize = nm_slSize-sizeof(UBYTE); void *pvUnpacked = nm_pubMessage+sizeof(UBYTE); SLONG slPackedSize = nmPacked.nm_slMaxSize-sizeof(UBYTE); void *pvPacked = nmPacked.nm_pubMessage+sizeof(UBYTE); // pack it there ASSERT(comp.NeededDestinationSize(slUnpackedSize)<=slPackedSize); BOOL bSucceded = comp.Pack(pvUnpacked, slUnpackedSize, pvPacked, slPackedSize); ASSERT(bSucceded); // set up the destination message size nmPacked.nm_slSize = slPackedSize+sizeof(UBYTE); } /* * Unpack a message to another message (message type is left untouched). */ void CNetworkMessage::Unpack(CNetworkMessage &nmUnpacked, CCompressor &comp) { // get size and pointers for unpacking, leave the message type alone SLONG slPackedSize = nm_slSize-sizeof(UBYTE); void *pvPacked = nm_pubMessage+sizeof(UBYTE); SLONG slUnpackedSize = nmUnpacked.nm_slMaxSize-sizeof(UBYTE); void *pvUnpacked = nmUnpacked.nm_pubMessage+sizeof(UBYTE); // unpack it there BOOL bSucceeded = comp.Unpack(pvPacked, slPackedSize, pvUnpacked, slUnpackedSize); ASSERT(bSucceeded); // set up the destination message size nmUnpacked.nm_slSize = slUnpackedSize+sizeof(UBYTE); } // NOTE: // compression type bits in the messages are different than compression type cvar values // this is to keep backward compatibility with old demos saved with full compression void CNetworkMessage::PackDefault(CNetworkMessage &nmPacked) { extern INDEX net_iCompression; if (net_iCompression==2) { // pack with zlib only CzlibCompressor compzlib; Pack(nmPacked, compzlib); (int&)nmPacked.nm_mtType|=0<<6; } else if (net_iCompression==1) { // pack with LZ only CLZCompressor compLZ; Pack(nmPacked, compLZ); (int&)nmPacked.nm_mtType|=1<<6; } else { // no packing SLONG slUnpackedSize = nm_slSize-sizeof(UBYTE); void *pvUnpacked = nm_pubMessage+sizeof(UBYTE); void *pvPacked = nmPacked.nm_pubMessage+sizeof(UBYTE); nmPacked.nm_slSize = slUnpackedSize+sizeof(UBYTE); memcpy(pvPacked, pvUnpacked, slUnpackedSize); (int&)nmPacked.nm_mtType|=2<<6; } nmPacked.nm_pubMessage[0] = (UBYTE)nmPacked.nm_mtType; /* // pack with RLE and LZ CNetworkMessage nmPackedRLE(GetType()); CRLEBBCompressor compRLE; Pack(nmPackedRLE ,compRLE); CLZCompressor compLZ; nmPackedRLE.Pack(nmPacked, compLZ); //*/ } void CNetworkMessage::UnpackDefault(CNetworkMessage &nmUnpacked) { switch (nm_mtType>>6) { case 0: { // unpack with zlib only CzlibCompressor compzlib; Unpack(nmUnpacked,compzlib); } break; case 1: { // unpack with LZ only CLZCompressor compLZ; Unpack(nmUnpacked,compLZ); } break; default: case 2: { // no unpacking SLONG slPackedSize = nm_slSize-sizeof(UBYTE); void *pvPacked = nm_pubMessage+sizeof(UBYTE); void *pvUnpacked = nmUnpacked.nm_pubMessage+sizeof(UBYTE); nmUnpacked.nm_slSize = slPackedSize+sizeof(UBYTE); memcpy(pvUnpacked, pvPacked, slPackedSize); } break; } /* // unpack with LZ and RLE CNetworkMessage nmUnpackedLZ(GetType()); CLZCompressor compLZ; Unpack(nmUnpackedLZ,compLZ); CRLEBBCompressor compRLE; nmUnpackedLZ.Unpack(nmUnpacked, compRLE); //*/ } // dump message to console void CNetworkMessage::Dump(void) { CPrintF("Message size: %d\n", nm_slSize); CPrintF("Message contents:"); for(INDEX iByte=0; iByte=8) { nm_iBit = 0; } iDstBit++; if (iDstBit>=8) { iDstBit = 0; pubDstByte++; } } } void CNetworkMessage::WriteBits(const void *pvBuffer, INDEX ctBits) { const UBYTE *pubSrcByte = (const UBYTE *)pvBuffer; // for each bit INDEX iSrcBit = 0; for (INDEX iBit=0; iBit=8) { nm_iBit = 0; } iSrcBit++; if (iSrcBit>=8) { iSrcBit = 0; pubSrcByte++; } } } ///////////////////////////////////////////////////////////////////// // CNetworkStreamBlock /* * Constructor for receiving -- uninitialized block. */ CNetworkStreamBlock::CNetworkStreamBlock(void) : CNetworkMessage() , nsb_iSequenceNumber(-1) { } /* * Constructor for sending -- empty packet with given type and sequence. */ CNetworkStreamBlock::CNetworkStreamBlock(MESSAGETYPE mtType, INDEX iSequenceNumber) : CNetworkMessage(mtType) , nsb_iSequenceNumber(iSequenceNumber) { } /* * Read a block from a received message. */ void CNetworkStreamBlock::ReadFromMessage(CNetworkMessage &nmToRead) { // read sequence number from message nmToRead>>nsb_iSequenceNumber; ASSERT(nsb_iSequenceNumber>=0); // read the block as a submessage nmToRead.ExtractSubMessage(*this); } /* * Add a block to a message to send. */ void CNetworkStreamBlock::WriteToMessage(CNetworkMessage &nmToWrite) { // write sequence number to message ASSERT(nsb_iSequenceNumber>=0); nmToWrite<>nsb_iSequenceNumber; // read block size strm>>nm_slSize; // read block contents strm.Read_t(nm_pubMessage, nm_slSize); // init the message read/write pointer nm_pubPointer = nm_pubMessage; nm_iBit = 0; // get the message type UBYTE ubType = 0; (*this)>>ubType; nm_mtType = (MESSAGETYPE)ubType; } ///////////////////////////////////////////////////////////////////// // CNetworkStream /* * Constructor. */ CNetworkStream::CNetworkStream(void) { } /* * Destructor. */ CNetworkStream::~CNetworkStream(void) { // report number of blocks left in stream //_RPT1(_CRT_WARN, "Destructing stream, %d blocks contained.\n", ns_lhBlocks.Count()); // remove all blocks Clear(); } /* * Clear the object (remove all blocks). */ void CNetworkStream::Clear(void) { // for each block in list FORDELETELIST(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsbInList) { // remove it from list itnsbInList->nsb_lnInStream.Remove(); // delete it delete &*itnsbInList; } } /* Copy from another network stream. */ void CNetworkStream::Copy(CNetworkStream &nsOther) { // for each block in list FOREACHINLIST(CNetworkStreamBlock, nsb_lnInStream, nsOther.ns_lhBlocks, itnsb) { // add it here AddBlock(*itnsb); } } // get number of blocks used by this object INDEX CNetworkStream::GetUsedBlocks(void) { return ns_lhBlocks.Count(); } // get amount of memory used by this object SLONG CNetworkStream::GetUsedMemory(void) { SLONG slMem = 0; // for each block in list FOREACHINLIST(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsb) { // add its usage slMem+=sizeof(CNetworkStreamBlock)+itnsb->nm_slMaxSize; } return slMem; } // get index of newest sequence stored INDEX CNetworkStream::GetNewestSequence(void) { // if the stream is empty if (ns_lhBlocks.IsEmpty()) { // return dummy return -1; } // get head of list CNetworkStreamBlock *pnsb = LIST_HEAD(ns_lhBlocks, CNetworkStreamBlock, nsb_lnInStream); // return its index return pnsb->nsb_iSequenceNumber; } /* * Add a block that is already allocated to the stream. */ void CNetworkStream::AddAllocatedBlock(CNetworkStreamBlock *pnsbBlock) { // search all blocks already in list FOREACHINLISTKEEP(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsbInList) { // if the block in list has same sequence as the one to add if (itnsbInList->nsb_iSequenceNumber == pnsbBlock->nsb_iSequenceNumber) { // just discard the new block delete pnsbBlock; return; } // if the block in list has lower sequence than the one to add if (itnsbInList->nsb_iSequenceNumber < pnsbBlock->nsb_iSequenceNumber) { // stop searching break; } } // add the new block before current one itnsbInList.InsertBeforeCurrent(pnsbBlock->nsb_lnInStream); } /* * Add a block to the stream. */ void CNetworkStream::AddBlock(CNetworkStreamBlock &nsbBlock) { // create a copy of the block CNetworkStreamBlock *pnsbCopy = new CNetworkStreamBlock(nsbBlock); // shrink it pnsbCopy->Shrink(); // add it to the list AddAllocatedBlock(pnsbCopy); } /* * Read a block as a submessage from a message and add it to the stream. */ void CNetworkStream::ReadBlock(CNetworkMessage &nmMessage) { // create an empty block CNetworkStreamBlock *pnsbRead = new CNetworkStreamBlock(); // read it from message pnsbRead->ReadFromMessage(nmMessage); // shrink it pnsbRead->Shrink(); // add it to the list AddAllocatedBlock(pnsbRead); } /* * Get a block from stream by its sequence number. */ CNetworkStream::Result CNetworkStream::GetBlockBySequence( INDEX iSequenceNumber, CNetworkStreamBlock *&pnsbBlock) { BOOL bNewerFound = FALSE; // search all blocks in list FOREACHINLIST(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsbInList) { CNetworkStreamBlock &nsbInList = *itnsbInList; // if the block in list has newer sequence if (nsbInList.nsb_iSequenceNumber >= iSequenceNumber) { // remember that at least one newer block was found bNewerFound = TRUE; } // if the block in list has wanted sequence if (nsbInList.nsb_iSequenceNumber == iSequenceNumber) { // return it pnsbBlock = itnsbInList; return R_OK; } // if the block in list has older sequence if (nsbInList.nsb_iSequenceNumber < iSequenceNumber) { // stop searching break; } } // ...if none found // if some block of newer sequence number was found if (bNewerFound) { // return that the block is missing (probably should be resent) pnsbBlock = NULL; return R_BLOCKMISSING; // if no newer blocks were found } else { // we assume that the wanted block is not yet received pnsbBlock = NULL; return R_BLOCKNOTRECEIVEDYET; } } // find oldest block after given one (for batching missing sequences) INDEX CNetworkStream::GetOldestSequenceAfter(INDEX iSequenceNumber) { // block are sorted newer first, so we just remember the last block found // until we find the given one INDEX iOldest = iSequenceNumber; FOREACHINLIST(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsb) { CNetworkStreamBlock &nsb = *itnsb; if (nsb.nsb_iSequenceNumberWriteToMessage(nmMessage); iBlock++; if (iBlock>=ctBlocks) { return iBlock; } } return iBlock; } /* * Remove all blocks but the given number of newest ones. */ void CNetworkStream::RemoveOlderBlocks(INDEX ctBlocksToKeep) { // for each block in list INDEX iBlock = 0; FORDELETELIST(CNetworkStreamBlock, nsb_lnInStream, ns_lhBlocks, itnsbInList) { iBlock++; // if it is older that given count if (iBlock>ctBlocksToKeep) { // remove it from list itnsbInList->nsb_lnInStream.Remove(); // delete it delete &*itnsbInList; } } } /* Remove all blocks with sequence older than given. */ void CNetworkStream::RemoveOlderBlocksBySequence(INDEX iLastSequenceToKeep) { // while there are any blocks in the list while(!ns_lhBlocks.IsEmpty()) { // get the tail of the list CNetworkStreamBlock *pnsb = LIST_TAIL(ns_lhBlocks, CNetworkStreamBlock, nsb_lnInStream); // if it is not too old if (pnsb->nsb_iSequenceNumber >= iLastSequenceToKeep) { // stop break; } // remove the tail delete pnsb; }; } ///////////////////////////////////////////////////////////////////// // CPlayerAction CPlayerAction::CPlayerAction(void) { Clear(); } /* * Clear the object (this sets up no actions). */ void CPlayerAction::Clear(void) { pa_vTranslation = FLOAT3D(0.0f,0.0f,0.0f); pa_aRotation = ANGLE3D(0,0,0); pa_aViewRotation = ANGLE3D(0,0,0); pa_ulButtons = 0; pa_llCreated = 0; } // normalize action (remove invalid floats like -0) void CPlayerAction::Normalize(void) { volatile FLOAT *pf = (FLOAT*)&pa_vTranslation; for (INDEX i=0; i<9; i++) { if (*pf==0) { *pf=0; } pf++; } } // create a checksum value for sync-check void CPlayerAction::ChecksumForSync(ULONG &ulCRC) { CRC_AddBlock(ulCRC, (UBYTE*)this, sizeof(this)); } #define DUMPVECTOR(v) \ strm.FPrintF_t(#v ": %g,%g,%g %08x,%08x,%08x\n", \ (v)(1), (v)(2), (v)(3), (ULONG&)(v)(1), (ULONG&)(v)(2), (ULONG&)(v)(3)) #define DUMPLONG(l) \ strm.FPrintF_t(#l ": %08x\n", l) // dump sync data to text file void CPlayerAction::DumpSync_t(CTStream &strm) // throw char * { DUMPVECTOR(pa_vTranslation); DUMPVECTOR(pa_aRotation); DUMPVECTOR(pa_aViewRotation); DUMPLONG(pa_ulButtons); } void CPlayerAction::Lerp(const CPlayerAction &pa0, const CPlayerAction &pa1, FLOAT fFactor) { pa_vTranslation = ::Lerp(pa0.pa_vTranslation , pa1.pa_vTranslation , fFactor); pa_aRotation = ::Lerp(pa0.pa_aRotation , pa1.pa_aRotation , fFactor); pa_aViewRotation = ::Lerp(pa0.pa_aViewRotation, pa1.pa_aViewRotation, fFactor); pa_ulButtons = pa1.pa_ulButtons; } // player action compression algorithm: // - all axes (9 of them) are compressed as one bit telling whether the axis value is used // if that bit is ==0, then the axis value is 0.0, if it is 1, the value follows in next 32 // bits // - the flags are compressed by preceding them with a bit sequence telling how many bits // are saved after that: // (0) 1 = no bits follow, value is 0 // (1) 01 = no bits follow, value is 1 // (2-3) 001 = 1 bit follows, value is 1x where x is the given bit // (4-15) 0001 = 4 bit value follows // (16-255) 00001 = 8 bit value follows // (256-65535) 000001 = 16 bit value follows // (65536-) 000000 = 32 bit value follows // note: above bits are ordered in reverse as they come when scanning bit by bit /* Write an object into message. */ CNetworkMessage &operator<<(CNetworkMessage &nm, const CPlayerAction &pa) { nm.Write(&pa.pa_llCreated, sizeof(pa.pa_llCreated)); const ULONG *pul = (const ULONG*)&pa.pa_vTranslation; for (INDEX i=0; i<9; i++) { if (*pul==0) { UBYTE ub=0; nm.WriteBits(&ub, 1); } else { UBYTE ub=1; nm.WriteBits(&ub, 1); nm.WriteBits(pul, 32); } pul++; } ULONG ulFlags = pa.pa_ulButtons; // (0) 1 = no bits follow, value is 0 if (ulFlags==0) { UBYTE ub=1; nm.WriteBits(&ub, 1); // (1) 01 = no bits follow, value is 1 } else if (ulFlags==1) { UBYTE ub=2; nm.WriteBits(&ub, 2); // (2-3) 001 = 1 bit follows, value is 1x where x is the given bit } else if (ulFlags <= 3) { UBYTE ub=4; nm.WriteBits(&ub, 3); nm.WriteBits(&ulFlags, 1); // (4-15) 0001 = 4 bit value follows } else if (ulFlags <= 15) { UBYTE ub=8; nm.WriteBits(&ub, 4); nm.WriteBits(&ulFlags, 4); // (16-255) 00001 = 8 bit value follows } else if (ulFlags <= 255) { UBYTE ub=16; nm.WriteBits(&ub, 5); nm.WriteBits(&ulFlags, 8); // (256-65535) 000001 = 16 bit value follows } else if (ulFlags <= 65535) { UBYTE ub=32; nm.WriteBits(&ub, 6); nm.WriteBits(&ulFlags, 16); // (65536-) 000000 = 32 bit value follows } else { UBYTE ub=0; nm.WriteBits(&ub, 6); nm.WriteBits(&ulFlags, 32); } return nm; } /* Read an object from message. */ CNetworkMessage &operator>>(CNetworkMessage &nm, CPlayerAction &pa) { nm.Read(&pa.pa_llCreated, sizeof(pa.pa_llCreated)); ULONG *pul = (ULONG*)&pa.pa_vTranslation; for (INDEX i=0; i<9; i++) { UBYTE ub = 0; nm.ReadBits(&ub, 1); if (ub==0) { *pul = 0; } else { nm.ReadBits(pul, 32); } pul++; } // find number of zero bits for flags INDEX iZeros=0; for(; iZeros<6; iZeros++) { UBYTE ub=0; nm.ReadBits(&ub, 1); if (ub!=0) { break; } } ULONG ulFlags = 0; // now read flags according to the number of bits // (0) 1 = no bits follow, value is 0 if (iZeros==0) { ulFlags = 0; // (1) 01 = no bits follow, value is 1 } else if (iZeros==1) { ulFlags = 1; // (2-3) 001 = 1 bit follows, value is 1x where x is the given bit } else if (iZeros==2) { ulFlags = 0; nm.ReadBits(&ulFlags, 1); ulFlags |= 2; // (4-15) 0001 = 4 bit value follows } else if (iZeros==3) { ulFlags = 0; nm.ReadBits(&ulFlags, 4); // (16-255) 00001 = 8 bit value follows } else if (iZeros==4) { ulFlags = 0; nm.ReadBits(&ulFlags, 8); // (256-65535) 000001 = 16 bit value follows } else if (iZeros==5) { ulFlags = 0; nm.ReadBits(&ulFlags, 16); // (65536-) 000000 = 32 bit value follows } else { ulFlags = 0; nm.ReadBits(&ulFlags, 32); } pa.pa_ulButtons = ulFlags; return nm; } /* Write an object into stream. */ CTStream &operator<<(CTStream &strm, const CPlayerAction &pa) { strm.Write_t(&pa,sizeof(pa)); return strm; } /* Read an object from stream. */ CTStream &operator>>(CTStream &strm, CPlayerAction &pa) { strm.Read_t(&pa,sizeof(pa)); return strm; }