Valhalla Legends Forums Archive | Battle.net Bot Development | Updated C# PacketBuffer Class

AuthorMessageTime
Myndfyr
This is the packet buffer that I use (more or less) in ArmaBot Alpha 4. It's infinitely better than the one CURRENTLY in BotDev reference, so you guys might want to replace that one with this one. ;-)

Some notes:

Due to common use, the Packet's "InsertNonNTString" function takes a string, reverses it, and puts it in, encoded in ASCII, padded with NULLs to ensure four-byte length.

InsertTerminatedString is for use with the strings that are terminated by a byte value other than 0x00 (such as 0x0d).

This is more efficient because it creates a typed linked-list specifically for byte values. In the .NET FCL, the ArrayList uses object instances. It boxes and unboxes the value-typed bytes, which costs time.

The InsertByte() function of Packet would be best to put the breakpoint during debugging.

This code is untested. I recreated it this afternoon. If it doesn't work, I'll post the *actual* code from my bot. ;)

ByteArrayList.cs
[code]
using System;

namespace ArmaBot
{
   /// <summary>
   /// Implements a singly-linked list for more efficient (than <see cref="System.Collections.ArrayList">ArrayList</see>) storage of packet data.
   /// </summary>
   public class ByteArrayList : System.Collections.IEnumerable, System.Collections.IEnumerator
   {
      #region ByteNode class
      /// <summary>
      /// Helper class for the byte array list.
      /// </summary>
      private class ByteNode
      {
         public byte Value;
         public ByteNode Next;

         public ByteNode(byte value)
         {
            Value = value;
         }

         public ByteNode(byte value, ByteNode previous) : this(value)
         {
            if (previous.Next != null)
               Next = previous.Next;

            previous.Next = this;
         }
      }
      #endregion

      private            ByteNode               m_bnHead, m_bnCursor, m_bnTail;
      private            int                     m_nCount;

      /// <summary>
      /// Instantiates a new list.
      /// </summary>
      public ByteArrayList()
      {
         m_nCount         = 0;
      }

      /// <summary>
      /// Instantiates a new list with the specifed data.
      /// </summary>
      /// <param name="buffer">The data to add to the buffer.</param>
      public ByteArrayList(byte[] buffer)
      {
         m_nCount         = buffer.Length;
         ByteNode      cursor            = null;
         for (int i = 0; i < m_nCount; i++)
         {
            if (i == 0)
            {
               cursor      = m_bnHead      = new ByteNode( buffer[i] );
            }
            else
            {
               cursor      = new ByteNode( buffer[i], cursor );
            }
         }
      }

      /// <summary>
      /// Gets the number of data items in the buffer.
      /// </summary>
      public int Count
      {
         get
         {
            return m_nCount;
         }
      }

      /// <summary>
      /// Adds a value to the buffer.
      /// </summary>
      /// <param name="b">The value to add to the buffer.</param>
      public void Add(byte b)
      {
         if (this.m_bnHead == null)
         {
            this.m_bnHead      = this.m_bnCursor      = this.m_bnTail         = new ByteNode( b );
         }
         else
         {
            this.m_bnCursor      = this.m_bnTail         = new ByteNode( b, this.m_bnTail );
         }

         this.m_nCount++;
      }

      /// <summary>
      /// Adds an array of values to the buffer.
      /// </summary>
      /// <param name="bytes">The array of values to add to the buffer.</param>
      public void AddRange(byte[] bytes)
      {
         int len = bytes.Length;
         for (int i = 0; i < len; i++)
            Add(bytes[i]);
      }

      /// <summary>
      /// Gets a buffer contained by the list.
      /// </summary>
      /// <returns>An array containing the raw data in the list.</returns>
      public byte[] GetBytes()
      {
         byte[] buffer         = new byte[m_nCount];
         ByteNode bnCur         = this.m_bnHead;

         int nPos            = 0;
         while (bnCur != null)
         {
            buffer[ nPos ]      = bnCur.Value;
            bnCur            = bnCur.Next;
         }

         return buffer;
      }


      #region IEnumerable Members

      /// <summary>
      /// Gets the enumerator to enumerate over the collection
      /// </summary>
      /// <returns>An IEnumerator instance that allows the collection to be enumerated. </returns>
      public System.Collections.IEnumerator GetEnumerator()
      {
         return this;
      }

      #endregion

      #region IEnumerator Members

      /// <summary>
      /// Resets the enumerator to just before the beginning of the collection.
      /// </summary>
      public void Reset()
      {
         m_bnCursor               = null;
      }

      /// <summary>
      /// Gets the next byte value in the collection.
      /// </summary>
      public object Current
      {
         get
         {
            if (m_bnCursor == null)
               return null;
            else
               return m_bnCursor.Value;
         }
      }

      /// <summary>
      /// Checks to see whether or not the enumerator can move to another item, and if so, moves.
      /// </summary>
      /// <returns>Whether or not another item exists.</returns>
      public bool MoveNext()
      {
         bool      fNext         = true;
         if (m_bnCursor == null)
         {
            m_bnCursor            = m_bnHead;
         }
         else
         {
            m_bnCursor            = m_bnCursor.Next;
            if (m_bnCursor == null)
               fNext            = false;
         }

         return fNext;
      }

      #endregion
   }
}
[/code]

Packet.cs:
[code]
using System;
using System.Text;

namespace ArmaBot
{
   /// <summary>
   /// Provides the essential functionality of a packet buffer.
   /// </summary>
   [CLSCompliant(true)]
   public abstract class Packet
   {
      private         ByteArrayList            m_lData;
      private         byte                  m_nPacketId;

      /// <summary>
      /// Instantiates a new packet with the specified packet ID.
      /// </summary>
      /// <param name="packetId">The identifier of the specific packet, to be used in derived classes.</param>
      protected Packet(byte packetId)
      {
         m_lData            = new ByteArrayList();
         m_nPacketId         = packetId;
      }

      /// <summary>
      /// Gets the length of the data.
      /// </summary>
      /// <remarks>
      /// When overridden in a derived class, gets the length of the data, including header.
      /// </remarks>
      public virtual int   Count
      {
         get
         {
            return m_lData.Count;
         }
      }

      /// <summary>
      /// Gets the raw data in the buffer.
      /// </summary>
      /// <remarks>
      /// When overridden in a derived class, gets the raw data, including header.
      /// </remarks>
      public virtual byte[] Data
      {
         get
         {
            return m_lData.GetBytes();
         }
      }

      /// <summary>
      /// Gets the function identifer of the packet.
      /// </summary>
      public virtual byte PacketId
      {
         get
         {
            return m_nPacketId;
         }
      }

      /// <summary>
      /// Inserts a byte into the buffer.
      /// </summary>
      /// <param name="b">The value to add to the buffer.</param>
      public virtual void InsertByte(byte b)
      {
         m_lData.Add(b);
      }

      /// <summary>
      /// Inserts an array of bytes into the buffer.
      /// </summary>
      /// <param name="bytes">The array of values to add to the buffer.</param>
      public virtual void InsertByteArray(byte[] bytes)
      {
         int len = bytes.Length;
         for (int i = 0; i < len; i++)
            InsertByte( bytes[len] );
      }

      /// <summary>
      /// Inserts a signed byte into the buffer.
      /// </summary>
      /// <param name="b">The value to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertSByte(sbyte b)
      {
         InsertByte(
            unchecked( (byte) b )
                  );
      }

      /// <summary>
      /// Inserts a signed byte into the buffer.
      /// </summary>
      /// <param name="bytes">The array of values to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertSByteArray(sbyte[] bytes)
      {
         int len = bytes.Length;
         for (int i = 0; i < len; i++)
            InsertSByte( bytes[len] );
      }

      /// <summary>
      /// Inserts a signed word into the buffer.
      /// </summary>
      /// <param name="w">The value to add to the buffer.</param>
      public virtual void InsertWord(short w)
      {
         InsertByteArray(
            BitConverter.GetBytes(w)
                     );
      }

      /// <summary>
      /// Inserts an array of signed words into the buffer.
      /// </summary>
      /// <param name="words">The array of values to add to the buffer.</param>
      public virtual void InsertWordArray(short[] words)
      {
         int len = words.Length;
         for (int i = 0; i < len; i++)
            InsertWord( words[i] );
      }

      /// <summary>
      /// Inserts an unsigned word into the buffer.
      /// </summary>
      /// <param name="w">The value to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertWord(ushort w)
      {
         InsertByteArray(
            BitConverter.GetBytes(w)
                     );
      }

      /// <summary>
      /// Inserts an array of unsigned words into the buffer.
      /// </summary>
      /// <param name="words">The array of values to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertWordArray(ushort[] words)
      {
         int len = words.Length;
         for (int i = 0; i < len; i++)
            InsertWord( words[i] );
      }

      /// <summary>
      /// Inserts a signed double-word into the buffer.
      /// </summary>
      /// <param name="d">The value to add to the buffer.</param>
      public virtual void InsertDword(int d)
      {
         InsertByteArray(
            BitConverter.GetBytes(d)
                     );
      }

      /// <summary>
      /// Inserts an array of signed double-words into the buffer.
      /// </summary>
      /// <param name="dwords">The array of values to add to the buffer.</param>
      public virtual void InsertDwordArray(int[] dwords)
      {
         int len = dwords.Length;
         for (int i = 0; i < len; i++)
            InsertDword( dwords[i] );
      }

      /// <summary>
      /// Inserts an unsigned double-word into the buffer.
      /// </summary>
      /// <param name="d">The value to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertDword(uint d)
      {
         InsertByteArray(
            BitConverter.GetBytes(d)
                     );
      }

      /// <summary>
      /// Inserts an array of unsigned double-words into the buffer.
      /// </summary>
      /// <param name="dwords">The array of values to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertDwordArray(uint[] dwords)
      {
         int len = dwords.Length;
         for (int i = 0; i < len; i++)
            InsertDword( dwords[i] );
      }

      /// <summary>
      /// Inserts a signed quad-word into the buffer.
      /// </summary>
      /// <param name="q">The value to add to the buffer.</param>
      public virtual void InsertQword(long q)
      {
         InsertByteArray(
            BitConverter.GetBytes( q )
                     );
      }

      /// <summary>
      /// Inserts an array of signed quad-words into the buffer.
      /// </summary>
      /// <param name="qwords">The array of values to add to the buffer.</param>
      public virtual void InsertQwordArray(long[] qwords)
      {
         int len = qwords.Length;
         for (int i = 0; i < len; i++)
            InsertQword( qwords[i] );
      }

      /// <summary>
      /// Inserts an unsigned quad-word into the buffer.
      /// </summary>
      /// <param name="q">The value to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertQword(ulong q)
      {
         InsertByteArray(
            BitConverter.GetBytes( q )
                     );
      }

      /// <summary>
      /// Inserts an array of quad-words into the buffer.
      /// </summary>
      /// <param name="qwords">The array of values to add to the buffer.</param>
      /// <remarks>
      /// This method is not CLS-compliant.
      /// </remarks>
      [CLSCompliant(false)]
      public virtual void InsertQwordArray(ulong[] qwords)
      {
         int len = qwords.Length;
         for (int i = 0; i < len; i++)
            InsertQword( qwords[i] );
      }

      /// <summary>
      /// When overridden in a derived class, inserts a string encoded into bytes.
      /// </summary>
      /// <param name="data">The value to add to the buffer.</param>
      /// <param name="terminator">The value to use as a terminator.</param>
      public abstract void InsertTerminatedString(string data, byte terminator);

      /// <summary>
      /// Inserts a null-terminated string into the buffer.
      /// </summary>
      /// <param name="data">The value to add to the buffer.</param>
      public virtual void InsertNTString(string data)
      {
         InsertTerminatedString(data, 0);
      }

      /// <summary>
      /// Inserts a variable-length string list that is terminated by an additional null terminator.
      /// </summary>
      /// <param name="strings">The array of values to add to the buffer.</param>
      public virtual void InsertNTStringList(string[] strings)
      {
         InsertStringList( strings );
         InsertByte(0);
      }

      /// <summary>
      /// Inserts a string list.
      /// </summary>
      /// <param name="strings">The array of values to add to the buffer.</param>
      public virtual void InsertStringList(string[] strings)
      {
         int nLen = strings.Length;
         for (int i = 0; i < nLen; i++)
            InsertNTString( strings[i] );
      }

      /// <summary>
      /// Inserts a four-byte, reversed string with no null terminator, using null values as buffers for less-than-four-character strings.
      /// </summary>
      /// <param name="value">The value to add to the buffer.</param>
      public virtual void InsertNonNTString(string value)
      {
         if (value.Length > 4)
            throw new ArgumentException("A string entered into a non-NT string (DWORD type) must be no longer than 4 characters.", "value");

         if (value.Length < 4)
         {
            int nLen      = 4 - value.Length;
            for (int i = 0; i < nLen; i++)
            {
               InsertByte(0);
            }
         }

         byte[] bufStr      = Encoding.ASCII.GetBytes(value);
         for (int i = (bufStr.Length - 1); i >= 0; i--)
         {
            InsertByte( bufStr[i] );
         }
      }
   }
}
[/code]

BnetPacket is an example of the polymorphic functionality of the Packet class. The Packet class can be extended to any type.

BnetPacket.cs:
[code]
using System;
using System.Text;

namespace ArmaBot.Bncs
{
   /// <summary>
   /// Overrides base functionality for a Battle.net packet.
   /// </summary>
   public class BnetPacket : ArmaBot.Packet
   {

      /// <summary>
      /// Instantiates a new Battle.net packet with the specified packet ID.
      /// </summary>
      /// <param name="PacketId">The value of the packet function ID.</param>
      public BnetPacket(byte PacketId) : base(PacketId)
      {
         
      }

      /// <summary>
      /// Instantiates a new Battle.net packet with the specified packet ID.
      /// </summary>
      /// <param name="PacketId">The BNCS packet ID enumeration member for the specified packet function ID.</param>
      public BnetPacket(BncsPacketIds PacketId) : base( (byte)PacketId)
      {

      }

      /// <summary>
      /// Gets the length of the packet data, including header.
      /// </summary>
      public override int Count
      {
         get
         {
            return base.Count + 4;
         }
      }

      /// <summary>
      /// Gets the raw packet data, including header.
      /// </summary>
      public override byte[] Data
      {
         get
         {
            byte[] data = new byte[ Count ];
            data[0] = 0xff;
            data[1] = PacketId;
            byte[] len         = BitConverter.GetBytes( Count );
            data[2] = len[0];
            data[3] = len[1];

            Array.Copy(base.Data, 0, data, 4, base.Count);

            return data;
         }
      }

      /// <summary>
      /// Inserts a string into the buffer, with the specified byte terminator.
      /// </summary>
      /// <param name="data">The value to add into the buffer.</param>
      /// <param name="terminator">The value to use as the terminator.</param>
      public override void InsertTerminatedString(string data, byte terminator)
      {
         InsertByteArray(
            Encoding.ASCII.GetBytes(data)
                     );
         InsertByte( terminator );
      }
   }
}
[/code]
July 28, 2004, 10:25 PM
Maddox
Like I said before, I think you should make a Buffer class and then an inherited Packet class. You may find that you'll need the base class for things other than packets.
July 28, 2004, 10:57 PM
Myndfyr
[quote author=Maddox link=board=17;threadid=7937;start=0#msg73134 date=1091055422]
Like I said before, I think you should make a Buffer class and then an inherited Packet class. You may find that you'll need the base class for things other than packets.
[/quote]

Then it shouldn't be named "Packet," don't you think? Perhaps "Buffer" would be a better name.

I could just use the ByteArrayList for things other than packets. :-P

It's not like it's particularly important. If you don't like it, use something else or wrap it.
July 28, 2004, 10:58 PM
Maddox
[quote author=Myndfyre link=board=17;threadid=7937;start=0#msg73136 date=1091055495]
Then it shouldn't be named "Packet," don't you think? Perhaps "Buffer" would be a better name.
[/quote]

Huh? I meant something like this.

[code]
public abstract class Packet : Buffer
{
private ByteArrayList m_lData;
private byte m_nPacketId;
public abstract byte[] Output(); // would output the data in whatever format the header is.
}
[/code]

I don't know C# so I'm sure if that's right, but you get the idea.

Edit: Changed virtual to abstract per MyndFyre's comment.
July 28, 2004, 11:21 PM
Myndfyr
So... What functionality would the Packet inherit from Buffer that would make it any different than it is now?

And you were close, just make the output() function "abstract" instead of "virtual". ;)
July 28, 2004, 11:25 PM
Maddox
[quote author=Myndfyre link=board=17;threadid=7937;start=0#msg73138 date=1091057102]
So... What functionality would the Packet inherit from Buffer that would make it any different than it is now?

And you were close, just make the output() function "abstract" instead of "virtual". ;)
[/quote]

Nothing, and that's the point. You get the same functionality, but now you are free to use the Buffer class in other applications.
July 28, 2004, 11:34 PM
Myndfyr
[quote author=Maddox link=board=17;threadid=7937;start=0#msg73139 date=1091057659]
[quote author=Myndfyre link=board=17;threadid=7937;start=0#msg73138 date=1091057102]
So... What functionality would the Packet inherit from Buffer that would make it any different than it is now?

And you were close, just make the output() function "abstract" instead of "virtual". ;)
[/quote]

Nothing, and that's the point. You get the same functionality, but now you are free to use the Buffer class in other applications.
[/quote]

So then.... Why can't I just use the ByteArrayList in other applications, for the same effect?
July 28, 2004, 11:35 PM
Maddox
Because those InsertDWORD, etc, functions can be implemented in other places besides battle.net packets.
July 28, 2004, 11:39 PM
Myndfyr
[quote author=Maddox link=board=17;threadid=7937;start=0#msg73139 date=1091057659]
[quote author=Myndfyre link=board=17;threadid=7937;start=0#msg73138 date=1091057102]
So... What functionality would the Packet inherit from Buffer that would make it any different than it is now?
[/quote]

Nothing, and that's the point. You get the same functionality, but now you are free to use the Buffer class in other applications.
[/quote]

[quote author=Maddox link=board=17;threadid=7937;start=0#msg73141 date=1091057943]
Because those InsertDWORD, etc, functions can be implemented in other places besides battle.net packets.
[/quote]

So then you're saying that the buffer class should provide the InsertDWORD (etc) functionality as well?

Make up your mind!!! :P
July 29, 2004, 12:38 AM
Maddox
Perhaps you are getting confused here...
[quote]
What functionality would the Packet inherit from Buffer that would make it any different than it is now?
[/quote]

[quote]
Nothing, and that's the point.
[/quote]

I always meant that the Buffer class should include those InsertDWORD, etc.
July 29, 2004, 12:47 AM
tA-Kane
I have a question that's kind've off topic, but was spawned from reading this thread; What do the virtual and abstract keywords do?
July 29, 2004, 4:56 PM
Maddox
Virtual functions allow you to override a method in an inherited class. An abstract function is a virtual function with no implementation in the base class. It's the same thing as virtual void Foo() = 0; in C++. You cannot create objects that contain an abstract function.
July 29, 2004, 5:08 PM
iGotPropz
Ive been looking this over, and I think I might find myself dumb when seeing it but, could you post a little sample about how you would format the packet writer/maker, like you did in your old C#Packet Buffer post?
August 27, 2004, 5:44 PM

Search