Binary Packet
From GPWiki
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello.
[edit] What Is a Binary PacketSimply put a binary packet is a contiguous ordering of bytes to minimize bandwidth usage in games. What does this mean exactly? Well it's a way to save data tightly together. So an 8 bytes binary packet might contain two 4 byte floats. There's no way to know how the data is arranged when a packet gets to the other side so a method has to be set up on both the sending and receiving end in order to ensure the data is read correctly. This article will not cover a precise language just how to understand the idea and how to implement it. [edit] How To Implement ItIn programming languages a programmer has the ability to move around bytes of information. The easiest way to create a byte packet is to declare an array of bytes (or a data type always equal to 1 byte). Using Object Oriented Programming (OOP) is one of the best ways to manage packet manipulation. [edit] What the packet needs to accomplishA binary packet class needs to be able to write and read data. However it's not read from anywhere, it's normally read in order of when it was put in. First in first out. So the ability to write and read different data types off the packet is key. The idea is to create an object with the ability to write and read. so in psuedo-code: BinaryPacket packet(); packet.writeShort(25); packet.writeFloat(3.56); packet.byteIndex = 0; //reset the index that the packet was writing at to zero to begin reading from the beginning show the value packet.readShort(true); //the true represents to increment the byteIndex. show the value packet.readFloat(false); //reads a float and DOESN'T increment the byteIndex show the value packet.readFloat(true); //reads the same float as before, but increments the byteIndex [edit] How to effectively organize the data packetThere's tons of methods for arranging data in a packet. For simple to complex games the method I'm going to describe works well. It involves the use of an event ID variable beginning every event. It's very simple. An action ID for a game is only 1 or 2 bytes, this allows for many event IDs. Or to save more space branch off from events as with 1 byte. To organize client to server events an enumeration list is used. So 1 might be a connection event sent by a client represented by PACKET_CLIENT_EVENT.CONNECTION.LOGIN and 2 might be disconnection event represented by PACKET_CLIENT_EVENT.CONNECTION.LOGOUT Then the bytes following would describe the event better. I will get into more detail later. But first an example. Say the client sends a packet that looks like this. Note though how the enum is for the client. This is because the events for a client sending data and a server sending data are very different. In psuedo code the class you may design will look like this. It is only declared once and is used to handle all the packets. class BinaryPacket { integer byteIndex; byte packet[1024]; constructor byteIndex = 0 function writeUbyte(unsigned byte value){ packet[byteIndex] = value; byteIndex += 1; //increment the byteIndex } function writeShort(unsigned byte value){ //loop through the two bytes of value (in C++ use bit operators) and set the value packet[byteIndex] = value; byteIndex += 2; //increment the byteIndex } //code the other write functions for the data types the same way function writeString(string value){ writeUbyte(value.length()); //write the size, strings have to be less than 255 characters, any more is usually pointless, write a string function to use a short instead of a byte if it's necessary //loop through the string and set the bytes in the string equal to the bytes in packet //for strings that are 2 bytes long per character loop through both bytes, though for efficiency don't use 2 byte characters for(int writeTemp = 0; writeTemp < value.length(); ++writeTemp){ packet[byteIndex+writeTemp] = value[writeTemp]; } byteIndex += value.length(); //increment the byteIndex } function readByte(boolean increment){ return packet[byteIndex]; if(increment) byteIndex += 1; } function readShort(boolean increment){ short value; //put the two bytes, packet[byteIndex] and packet[byteIndex+1] together into value return value; if(increment) byteIndex += 2; } //code the other read functions for the data types the same way function readString(boolean increment){ string value; int length = readByte(true); //loop through the packet to put together the string using the length return value; if(increment) byteIndex += length; else byteIndex -= 1; } function resetByteIndex(){ ByteIndex = 0; } } [edit] 1 byte Using the IDs from the least common to the most uncommonThis way is different than from the below methods in the way it relies on a chain of unsigned bytes (0-255). Think of it this way, you read in a byte and if it's value is 254 or less you use it and go to the event. However, if the value is 255, it reads in another byte and its value is added onto 255. So the value 255 can be used however another byte has to read in with the value 0. What if a number higher than 511 is needed? Well the process keeps going if the second byte is 255 go onto the third until an event ID is found. This whole concept works off of the idea that simple events that are called a lot will be put at the beginning IDs of 0-254 and rarer events are put farther back. Very effective for a low number of simple events that happen a lot, which is common for all games. A special packet function needs to be written to handle this method though. packet.writeEvent() and packet.readEvent(true). [edit] 2 bytes All Events Given an IDfrom the beginning: 2 bytes //the event ID is 1 which in this example represents a login event 1 byte // the length of the following string. In this example the length of the username part of the string ? bytes // the string, username 1 byte // the length of the following string In this example the length of the password part of the string ? bytes // the string, password the code to create it might look like this where PACKET_EVENT_LOGIN is an enumerated value for 1. packet.writeShort(PACKET_CLIENT_EVENT.CONNECTION.LOGIN); // or write a packet.writeEvent(PACKET_CLIENT_EVENT.CONNECTION.LOGIN) function for clarification packet.writeString("gpwiki"); packet.writeString("password"); the server would get this packet and put write the data to a packet and then read the data. in pseudo code: packet.byteIndex = 0; while(packet.byteIndex < packet.length()){ switch(packet.readShort(true)) //checks the event ID and goes to the corresponding case case PACKET_CLIENT_EVENT.CONNECTION.LOGIN: //login switch(Login(packet.readString(true), packet.readString(true)){ case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.SUCCESS: //login is successful on the server //respond to the player case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.INFO_NOT_FOUND: //the password or username was rejected case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.FAILURE_TO_CONNECT: //the login server is down at the moment } } } As you can see the code can run through a whole packet access the information in it and perform the events with the bytes following. [edit] 1 byte Using the IDs as an Event tree1 byte //the event ID is 1 which in this example represents a login event 1 byte // the length of the following string. In this example the length of the username part of the string ? bytes // the string, username 1 byte // the length of the following string In this example the length of the password part of the string ? bytes // the string, password 1 byte //the event ID is 1 which in this example represents a character movement event 1 byte // a boolean, but special 8 bits can work like flags to have 8 input flags like 4 bits on or off for up,down,left,right, the other 4 for other things 1 byte //event for telling player action 1 byte //nested event for telling the type of selection more specifically a spell usage 1 short //the spell number, for instance if a spell ID is set in a quickbar the player clicked the quickbar and this was called packet.writeByte(PACKET_CLIENT_EVENT.CONNECTION.LOGIN);//a byte // or write a packet.writeEvent(PACKET_CLIENT_EVENT.LOGIN) function for clarification packet.writeString("gpwiki"); packet.writeString("password"); packet.writeByte(PACKET_CLIENT_EVENT.CONNECTION.MOVEMENT); packet.writeByte(Get the byte representing movement); packet.writeByte(PACKET_CLIENT_EVENT.PLAYER_ACTION); packet.writeByte(PACKET_CLIENT_EVENT.PLAYER_ACTION.USE_SPELL); packet.writeShort(spell index); then to read it: packet.byteIndex = 0; while(packet.byteIndex < packet.length()){ switch(packet.readByte(true)) //checks the event ID and goes to the corresponding case case PACKET_CLIENT_EVENT.CONNECTION.LOGIN: //login switch(Login(packet.readString(true), packet.readString(true)){ case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.SUCCESS: //login is successful on the server //respond to the player case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.INFO_NOT_FOUND: //the password or username was rejected case PACKET_CLIENT_EVENT.CONNECTION.LOGIN.FAILURE_TO_CONNECT: //the login server is down at the moment } case PACKET_CLIENT_EVENT.MOVEMENT: packet.readByte(true);//interpret the byte and set the players movement accordingly case PACKET_CLIENT_EVENT.PLAYER_ACTION: switch(packet.readByte(true){ case PACKET_CLIENT_EVENT.PLAYER_ACTION.USE_SPELL: //spell event use packet.readShort(true) to find out what spell ID it was and check if the player can cast it and such } } } [edit] Further InformationWhen a packet has to be extremely efficient then a bit packet is used. It allows data types that don't exceed a certain limit to use only the required bits they need. So a set of 5 bits could hold an unsigned value up to 32 or a signed value between -16 and 16. Something to research or implement if you need to. Most of the time it's overkill to use bit packets though. Also look into std::map and othere methods to use function pointers to relate to event numbers. Othere dynamic array systems can be used too. The only reason I used a switch is because of it's speed. [edit] Examples[edit] C++A C++ bitBuffer written by Sirisian, though it works as a byte buffer too. #ifndef BIT_BUFFER_HPP #define BIT_BUFFER_HPP /* --BitBuffer-- Description - a buffer class for storing lists of data in the most efficient way. --Credits-- -Created By Brandon Andrews -Special Thanks to: Pfeilspitze and Mr_Awesome */ #include <iostream> #include <string> typedef unsigned char byte; class bitBuffer { private: std::vector<char> buffer; long bitIndex; public: union DoubleType { struct { int first; int second; }; double Value; }; union LongDoubleType { struct { int first; int second; short third; }; long double Value; }; bitBuffer::bitBuffer(){ bitIndex = 0; } bitBuffer::bitBuffer(long packetSize){ bitIndex = 0; buffer.resize(packetSize, 0); } explicit bitBuffer::bitBuffer(char * pCharBuffer, long Length){ bitIndex = 0; //Load in the buffer to be used buffer.resize(Length); memcpy(&buffer[0], pCharBuffer, Length); } //WRITE FUNCTIONS template <class T> void write(T Value, bool Resize){ if(Resize) buffer.resize((bitIndex+(sizeof(T)*8)+7)/8); for(char copyBits = 0; copyBits < sizeof(T)*8; ++copyBits){ buffer[(bitIndex+copyBits)/8] |= ((Value >> ((sizeof(T)*8-1)-copyBits)) & 0x1)<<(7-(bitIndex+copyBits)%8); } bitIndex += sizeof(T)*8; } template <class T> void writeArray(std::vector<T> & Value, bool Resize){ char size = char(Value.size()); write<char>(size); if(Resize) buffer.resize((bitIndex+(sizeof(T)*8*size)+7)/8); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ for(char copyBits = 0; copyBits < sizeof(T)*8; ++copyBits){ buffer[(bitIndex+copyBits)/8] |= ((Value[cycleArray] >> ((sizeof(T)*8-1)-copyBits)) & 0x1)<<(7-(bitIndex+copyBits)%8); } bitIndex += sizeof(T)*8; } } template <> void write<bool>(bool Value, bool Resize) { if(Resize) buffer.resize((bitIndex+8)/8); buffer[(bitIndex)/8] |= (Value)<<(7-(bitIndex)%8); ++bitIndex; } template <> void write<float>(float Value, bool Resize) { int Temp; memcpy(&Temp,&Value,sizeof(float)); write<int>(Temp, Resize); } template <> void write<double>(double Value, bool Resize) { DoubleType ValueTemp = {0}; ValueTemp.Value = Value; write<int>(ValueTemp.first, Resize); write<int>(ValueTemp.second, Resize); } template <> void write<long double>(long double Value, bool Resize) { LongDoubleType ValueTemp = {0}; ValueTemp.Value = Value; write<int>(ValueTemp.first, Resize); write<int>(ValueTemp.second, Resize); write<short>(ValueTemp.third, Resize); } template <> void writeArray<float>(std::vector<float> & Value, bool Resize){ int Temp; char size = char(Value.size()); write<char>(size, Resize); if(Resize) buffer.resize((bitIndex+(sizeof(float)*8*size)+7)/8); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ memcpy(&Temp,&Value[cycleArray],sizeof(float)); write<int>(Temp, Resize); } } template <> void writeArray<double>(std::vector<double> & Value, bool Resize){ DoubleType ValueTemp = {0}; char size = char(Value.size()); write<char>(size, Resize); if(Resize) buffer.resize((bitIndex+(sizeof(double)*8*size)+7)/8); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ ValueTemp.Value = Value[cycleArray]; write<int>(ValueTemp.first, Resize); write<int>(ValueTemp.second, Resize); } } template <> void writeArray<long double>(std::vector<long double> & Value, bool Resize){ LongDoubleType ValueTemp = {0}; char size = char(Value.size()); write<char>(size, Resize); if(Resize) buffer.resize((bitIndex+(sizeof(long double)*8*size)+7)/8); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ ValueTemp.Value = Value[cycleArray]; write<int>(ValueTemp.first, Resize); write<int>(ValueTemp.second, Resize); write<short>(ValueTemp.third, Resize); } } template <> void write<std::string>(std::string Value, bool Resize) { write<char>(char(Value.size()), Resize); if(Resize) buffer.resize((bitIndex+((Value.size())*8)+7)/8); for(std::string::iterator stringItr = Value.begin(); stringItr != Value.end(); ++stringItr){ for(char copyBits = 0; copyBits < 8; ++copyBits){ buffer[(bitIndex+copyBits)/8] |= (((*stringItr) >> (7-copyBits)) & 0x1)<<(7-(bitIndex+copyBits)%8); } bitIndex += 8; } } //READ FUNCTIONS template <class T> T read(bool Increment){ T Value = 0; for(char copyBits = 0; copyBits < sizeof(T)*8; ++copyBits){ Value <<= 1; Value |= (buffer[(bitIndex+copyBits)/8]>>(7-(bitIndex+copyBits)%8)) & 0x1; } if(Increment) bitIndex += sizeof(T)*8; return Value; } template <class T> std::vector<T> readArray(bool Increment){ char size = read<char>(true); std::vector<T> Value; Value.resize(size); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ for(char copyBits = 0; copyBits < sizeof(T)*8; ++copyBits){ Value[cycleArray] <<= 1; Value[cycleArray] |= (buffer[(bitIndex+copyBits)/8]>>(7-(bitIndex+copyBits)%8)) & 0x1; } bitIndex += sizeof(T)*8; } if(!Increment) bitIndex -= sizeof(T)*8*size+8; return Value; } template <> bool read<bool>(bool Increment){ bool Value = (buffer[bitIndex/8]>>(7-bitIndex%8)) & 0x1; if(Increment) ++bitIndex; return Value; } template <> float read<float>(bool Increment){ int Temp = read<int>(Increment); float Value; memcpy(&Value, &Temp, sizeof(float)); return Value; } template <> double read<double>(bool Increment){ DoubleType Value; Value.first = read<int>(true); Value.second = read<int>(true); if(!Increment) bitIndex -= 64; return Value.Value; } template <> long double read<long double>(bool Increment){ LongDoubleType Value; Value.first = read<int>(true); Value.second = read<int>(true); Value.third = read<short>(true); if(!Increment) bitIndex -= 80; return Value.Value; } template <> std::vector<float> readArray<float>(bool Increment){ int Temp; char size = read<char>(true); std::vector<float> Value; Value.resize(size); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ Temp = read<int>(true); memcpy(&Value[cycleArray], &Temp, sizeof(float)); } if(!Increment) bitIndex -= sizeof(float)*8*size+8; return Value; } template <> std::vector<double> readArray<double>(bool Increment){ DoubleType Temp; char size = read<char>(true); std::vector<double> Value; Value.resize(size); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ Temp.first = read<int>(true); Temp.second = read<int>(true); Value[cycleArray] = Temp.Value; } if(!Increment) bitIndex -= sizeof(double)*8*size+8; return Value; } template <> std::vector<long double> readArray<long double>(bool Increment){ LongDoubleType Temp; char size = read<char>(true); std::vector<long double> Value; Value.resize(size); for(char cycleArray = 0; cycleArray < size; ++cycleArray){ Temp.first = read<int>(true); Temp.second = read<int>(true); Temp.third = read<short>(true); Value[cycleArray] = Temp.Value; } if(!Increment) bitIndex -= sizeof(long double)*8*size+8; return Value; } template <> std::string read<std::string>(bool Increment){ std::string Value; long length = read<char>(true); Value.resize(length); for(std::string::iterator stringItr = Value.begin(); stringItr != Value.end(); ++stringItr){ for(char copyBits = 0; copyBits < 8; ++copyBits){ (*stringItr) <<= 1; (*stringItr) |= (buffer[(bitIndex+copyBits)/8]>>(7-(bitIndex+copyBits)%8)) & 0x1; } bitIndex+=8; } if(!Increment) bitIndex -= length*8 + 8; return Value; } //RESET AND SET BITINDEX void ResetBitIndex(){bitIndex = 0;} void SetBitIndex(long Index){bitIndex = Index;} long Length(){ return long(buffer.size()); } char * Retrieve(){ return &buffer[0]; } char * Set(){ return &buffer[0]; } void ResetBuffer(){ for(std::vector<char>::iterator bufferItr = buffer.begin(); bufferItr != buffer.end(); ++bufferItr){ (*bufferItr) = 0; } } }; #endif /* BIT_BUFFER_HPP */ Need an explanation feel free to ask by Private Message [edit] VB.NETAn extremely simplified VB.NET version written by Sirisian. VB.NET contains a System.IO.BinaryReader and BinaryWriter. They do the same thing as the code below, probably more efficiently. The code below is just an example. Examples on how to use the BinaryReader and BinaryWrite can be found on MSDN at the following links: [1] [2]
Public Class class_buffer Private byteBuffer() As Byte Public Sub New() ReDim byteBuffer(0) End Sub Public Sub New(ByRef varByteBuffer() As Byte) ReDim byteBuffer(UBound(varByteBuffer)) byteBuffer = varByteBuffer End Sub Public Sub addShort(ByVal varShort As Short, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 2) Dim byteArray() As Byte byteArray = System.BitConverter.GetBytes(CShort(varShort)) For cycleShort As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleShort) = byteArray(cycleShort) Next varByteIndex += 2 End Sub Public Sub addInteger(ByVal varInteger As Integer, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 4) Dim byteArray() As Byte byteArray = System.BitConverter.GetBytes(CInt(varInteger)) For cycleInteger As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleInteger) = byteArray(cycleInteger) Next varByteIndex += 4 End Sub Public Sub addLong(ByVal varLong As Long, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 8) Dim byteArray() As Byte byteArray = System.BitConverter.GetBytes(CLng(varLong)) For cycleLong As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleLong) = byteArray(cycleLong) Next varByteIndex += 8 End Sub Public Sub addSingle(ByVal varSingle As Single, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 4) Dim byteArray() As Byte byteArray = System.BitConverter.GetBytes(CSng(varSingle)) For cycleSingle As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleSingle) = byteArray(cycleSingle) Next varByteIndex += 4 End Sub Public Sub addDouble(ByVal varDouble As Double, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 8) Dim byteArray() As Byte byteArray = System.BitConverter.GetBytes(CDbl(varDouble)) For cycleDouble As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleDouble) = byteArray(cycleDouble) Next varByteIndex += 8 End Sub Public Sub addBoolean(ByVal varBoolean As Short, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 1) byteBuffer(varByteIndex) = CByte(varBoolean) varByteIndex += 1 End Sub Public Sub addByte(ByVal varByte As Byte, ByRef varByteIndex As Integer) ReDim Preserve byteBuffer(UBound(byteBuffer) + 1) byteBuffer(varByteIndex) = CByte(varByte) varByteIndex += 1 End Sub Public Sub addString(ByVal varString As String, ByRef varByteIndex As Integer) Dim byteArrayString() As Byte = System.Text.Encoding.ASCII.GetBytes(varString) ReDim Preserve byteBuffer(UBound(byteBuffer) + 2 + UBound(byteArrayString) + 1) Dim byteArray() As Byte = System.BitConverter.GetBytes(CShort(Len(varString))) For cycleShort As Short = 0 To UBound(byteArray) byteBuffer(varByteIndex + cycleShort) = byteArray(cycleShort) Next varByteIndex += 2 For cycleShort As Short = 0 To UBound(byteArrayString) byteBuffer(varByteIndex + cycleShort) = byteArrayString(cycleShort) Next varByteIndex += System.Text.Encoding.ASCII.GetByteCount(varString) End Sub Public Function getShort(ByRef varbyteindex) As Short varbyteindex += 2 Return System.BitConverter.ToInt16(byteBuffer, varbyteindex - 2) End Function Public Function getInteger(ByRef varbyteindex) As Integer varbyteindex += 4 Return System.BitConverter.ToInt32(byteBuffer, varbyteindex - 4) End Function Public Function getLong(ByRef varbyteindex) As Long varbyteindex += 8 Return System.BitConverter.ToInt64(byteBuffer, varbyteindex - 8) End Function Public Function getSingle(ByRef varbyteindex) As Single varbyteindex += 4 Return System.BitConverter.ToSingle(byteBuffer, varbyteindex - 4) End Function Public Function getDouble(ByRef varbyteindex) As Single varbyteindex += 8 Return System.BitConverter.ToDouble(byteBuffer, varbyteindex - 8) End Function Public Function getBoolean(ByRef varbyteindex) As Short varbyteindex += 1 Return CShort(byteBuffer(varbyteindex - 1)) End Function Public Function getByte(ByRef varbyteindex) As Byte varbyteindex += 1 Return byteBuffer(varbyteindex - 1) End Function Public Function getString(ByRef varbyteindex) As String Dim varString As String = "" Dim Length As Short = System.BitConverter.ToUInt16(byteBuffer, varbyteindex) varbyteindex += 2 varbyteindex += Length Return System.Text.Encoding.ASCII.GetString(byteBuffer, varbyteindex - Length, Length) End Function Public Function getBuffer() As Byte() Return byteBuffer End Function Public Function size() As Integer Return UBound(byteBuffer) End Function End Class [edit] Visual Basic 6Written by Spodi. Just copy and paste the code into a new class module. Use Put_ operations to put values into the buffer, and Get_ operations to get the values out. Does not support floating point integers. Private PutBytePos As Long Private GetBytePos As Long Private ByteBuffer() As Byte Private ByteBufferUbound As Long Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long) Public Sub Overflow() 'Force the buffer to overflow to break the packet reading loop GetBytePos = ByteBufferUbound + 10 End Sub Public Function HasBuffer() As Byte 'If there is a buffer or not If PutBytePos > 0 Then HasBuffer = 1 End Function Public Sub Clear() 'Clear all the values so we can use the buffer from the start again PutBytePos = 0 GetBytePos = 0 ByteBufferUbound = -1 Erase ByteBuffer End Sub Public Function Get_Buffer_Remainder() As Byte() Dim b() As Byte 'Return the remainder of the byte array buffer 'Check if we already hit the end of the buffer If UBound(ByteBuffer) - GetBytePos + 1 = 0 Then Get_Buffer_Remainder = b Else ReDim b(0 To (UBound(ByteBuffer) - GetBytePos + 1)) CopyMemory b(0), ByteBuffer(GetBytePos), UBound(ByteBuffer) - GetBytePos + 1 Get_Buffer_Remainder = ByteBuffer End If End Function Public Function Get_Buffer() As Byte() 'Return the byte array buffer Get_Buffer = ByteBuffer End Function Public Function Get_Byte() As Byte 'Retrieve a byte from the buffer (1 byte) If GetBytePos > ByteBufferUbound Then Exit Function CopyMemory Get_Byte, ByteBuffer(GetBytePos), 1 GetBytePos = GetBytePos + 1 End Function Public Function Get_Integer() As Integer 'Retrieve an integer from the buffer (2 bytes) If GetBytePos + 1 > ByteBufferUbound Then Exit Function CopyMemory Get_Integer, ByteBuffer(GetBytePos), 2 GetBytePos = GetBytePos + 2 End Function Public Function Get_Long() As Long 'Retrieve a long from the buffer (4 bytes) If GetBytePos + 3 > ByteBufferUbound Then Exit Function CopyMemory Get_Long, ByteBuffer(GetBytePos), 4 GetBytePos = GetBytePos + 4 End Function Private Function Get_PutPos() As Long 'Return the put byte position Get_PutPos = PutBytePos End Function Public Function Get_ReadPos() As Long 'Return the read byte position Get_ReadPos = GetBytePos End Function Public Function Get_String() As String Dim tempB() As Byte Dim ArraySize As Byte On Error GoTo ErrOut 'Retrieve a string from the buffer ArraySize = Get_Byte 'Get the size of the string 'Check for a valid size before sizing the array If ArraySize = 0 Then Exit Function 'Resize the temp byte array to fit the size of the string ReDim tempB(ArraySize - 1) 'Copy the bytes for the string in the buffer to the temp byte array CopyMemory tempB(0), ByteBuffer(GetBytePos), ArraySize 'Convert the byte array to Unicode Get_String = StrConv(tempB, vbUnicode) GetBytePos = GetBytePos + ArraySize ErrOut: End Function Public Function Get_StringEX() As String Dim tempB() As Byte Dim ArraySize As Integer On Error GoTo ErrOut 'Retrieve a very long string from the buffer ArraySize = Get_Integer 'Get the size of the string 'Check for a valid size before sizing the array If ArraySize = 0 Then Exit Function 'Resize the temp byte array to fit the size of the string ReDim tempB(ArraySize - 1) 'Copy the bytes for the string in the buffer to the temp byte array CopyMemory tempB(0), ByteBuffer(GetBytePos), ArraySize 'Convert the byte array to Unicode Get_StringEX = StrConv(tempB, vbUnicode) GetBytePos = GetBytePos + ArraySize ErrOut: End Function Public Sub Put_Byte(ByVal Value As Byte) 'Store a byte (1 byte) If ByteBufferUbound < PutBytePos Then ReDim Preserve ByteBuffer(0 To PutBytePos) ByteBufferUbound = PutBytePos End If CopyMemory ByteBuffer(PutBytePos), Value, 1 PutBytePos = PutBytePos + 1 End Sub Public Sub Put_Integer(ByVal Value As Integer) 'Store an integer (2 bytes) If ByteBufferUbound < PutBytePos + 1 Then ReDim Preserve ByteBuffer(0 To PutBytePos + 1) ByteBufferUbound = PutBytePos + 1 End If CopyMemory ByteBuffer(PutBytePos), Value, 2 PutBytePos = PutBytePos + 2 End Sub Public Sub Put_Long(ByVal Value As Long) 'Store a long (4 bytes) If ByteBufferUbound < PutBytePos + 3 Then ReDim Preserve ByteBuffer(0 To PutBytePos + 3) ByteBufferUbound = PutBytePos + 3 End If CopyMemory ByteBuffer(PutBytePos), Value, 4 PutBytePos = PutBytePos + 4 End Sub Public Sub Put_String(ByRef Value As String) Dim tempB() As Byte Dim i As Long 'Store a string 'Check for invalid value If Value = vbNullString Then Exit Sub 'Cache the UBound i = Len(Value) - 1 'Convert the string to a byte array tempB = StrConv(Value, vbFromUnicode) 'Store a byte-long value that represents the size of the string If i > 254 Then Exit Sub Put_Byte i + 1 'Resize the array to fit the string If ByteBufferUbound < PutBytePos + i Then ReDim Preserve ByteBuffer(0 To PutBytePos + i) ByteBufferUbound = PutBytePos + i End If 'Store the byte array of the string into the buffer byte array CopyMemory ByteBuffer(PutBytePos), tempB(0), i + 1 PutBytePos = PutBytePos + i + 1 End Sub Public Sub Put_StringEX(ByRef Value As String |