Valhalla Legends Forums Archive | Battle.net Bot Development References | [C#]Packet Buffer

AuthorMessageTime
Myndfyr
Since I'm so nice, I'm posting the code to my packetbuffer-equivalent classes in C#. They use ArrayList, which I know isn't the most ideal collection class, but I was being lazy at the time. These are transmission classes; note also that they are instance, not static, like (IIRC) DarkMinion's is.
[code]
using System;
using System.Collections;
using System.Text;
namespace AngelsOfArmageddon.ArmaBot
{
   public enum Medium
   {
      BNLS,
      BattleNet,
      Realm
   }
   internal abstract class Packet
   {
      protected ArrayList alBuffer;
      protected byte packetId;
      protected Medium medium;

      private Packet()
      {
         this.alBuffer = new ArrayList(3);
      }
      protected Packet(byte PacketID, Medium ServerType) : this()
      {
         this.packetId = PacketID;
         this.medium = ServerType;
      }

      public Medium ServerType
      {
         get
         {
            return this.medium;
         }
      }

      public byte PacketID
      {
         get
         {
            return this.packetId;
         }
      }

      public abstract short Length { get; }

      public abstract byte[] Data { get; }

      public virtual void InsertDWORDArray(int[] DWORDList)
      {
         foreach (int i in DWORDList)
            InsertDWORD(i);
      }
      public virtual void InsertDWORD(int DWORD)
      {
         InsertBYTEArray(BitConverter.GetBytes(DWORD));
      }
      public virtual void InsertWORD(short WORD)
      {
         InsertBYTEArray(BitConverter.GetBytes(WORD));
      }
      public virtual void InsertWORDArray(short[] WORDList)
      {
         foreach (short s in WORDList)
            InsertWORD(s);
      }
      public virtual void InsertBYTE(byte BYTE)
      {
         this.alBuffer.Add(BYTE);
      }
      public virtual void InsertBYTEArray(byte[] BYTEList)
      {
         foreach (byte b in BYTEList)
            InsertBYTE(b);
      }
      public virtual void InsertNTString(string str)
      {
         InsertBYTEArray(Encoding.ASCII.GetBytes(str));
         if ((byte)this.alBuffer[this.alBuffer.Count - 1] != 0x00b)
            InsertBYTE(0);

      }
      public virtual void InsertNonNTString(string str)
      {
         char[] car = new char[str.Length];
         for (int i = 0; i < str.Length; i++)
            car[i] = str[i];
         InsertBYTEArray(Encoding.ASCII.GetBytes(car));
      }

      public void Clear()
      {
         this.alBuffer.Clear();
      }

      public override string ToString()
      {
         StringBuilder sb = new StringBuilder(String.Empty);
         foreach (byte b in this.Data)
         {
            sb.Append(b);
            sb.Append(Environment.NewLine);
         }
         return sb.ToString();
      }
   }

   internal sealed class BNLSPacket : Packet
   {
      public BNLSPacket(byte PacketID) : base(PacketID, Medium.BNLS) { }

      public override short Length
      {
         get
         {
            return ((short)(this.alBuffer.Count + 3));
         }
      }
   
      public override byte[] Data
      {
         get
         {
            return this.ToSend();
         }
      }

      private byte[] ToSend()
      {
         ArrayList al = new ArrayList(this.alBuffer);
         al.InsertRange(0, BitConverter.GetBytes(this.Length));
         al.Insert(2, this.packetId);

         byte[] data = new byte[al.Count];
         al.CopyTo(0, data, 0, al.Count);

         return data;
      }

      public override void InsertNonNTString(string str)
      {
         char[] car = new char[str.Length];
         for (int i = 0; i < str.Length; i++)
            car[i] = str[i];
         InsertBYTEArray(Encoding.ASCII.GetBytes(car));
      }

      public override void InsertNTString(string str)
      {
         InsertBYTEArray(Encoding.ASCII.GetBytes(str));
         if ((byte)this.alBuffer[this.alBuffer.Count - 1] != 0x00)
            InsertBYTE(0);
      }
   }
   internal sealed class BnetPacket : Packet
   {
      public BnetPacket(byte PacketID) : base(PacketID, Medium.BattleNet) {}

      public override byte[] Data
      {
         get
         {
            return this.ToSend();
         }
      }

      public override short Length
      {
         get
         {
            return ((short)(this.alBuffer.Count + 4));
         }
      }

      private byte[] ToSend()
      {
         ArrayList al = new ArrayList(this.alBuffer);
         al.Insert(0, (byte)0xff);
         al.Insert(1, this.packetId);
         al.InsertRange(2, BitConverter.GetBytes(this.Length));
         byte[] data = new byte[al.Count];
         al.CopyTo(0, data, 0, al.Count);

         return data;
      }

      public override void InsertNTString(string str)
      {
         InsertBYTEArray(Encoding.UTF8.GetBytes(str));
         if ((byte)this.alBuffer[this.alBuffer.Count - 1] != 0x00)
            InsertBYTE(0);

      }
      public override void InsertNonNTString(string str)
      {
         char[] car = new char[str.Length];
         for (int i = str.Length - 1, j = 0; i >= 0; i--, j++)
            car[j] = str[i];
         InsertBYTEArray(Encoding.UTF8.GetBytes(car));
      }
   }
}
[/code]

Now, I used static methods on the derived classes to get instances (although you don't have to). Here's a sample method usage from the BnetPacket class:

[code]
      public static Packet AuthInfo(int verByte, int timeZoneBias, string GameID)
      {
         Packet pck = new BnetPacket((byte)0x50);
         pck.InsertDWORD(0);
         pck.InsertNonNTString("IX86");
         pck.InsertNonNTString(GameID);
         pck.InsertDWORD(verByte);
         pck.InsertDWORD(0);
         pck.InsertDWORD(0);
         pck.InsertDWORD(-60 * timeZoneBias);
         pck.InsertDWORD(0);
         pck.InsertDWORD(0);
         pck.InsertNTString("USA");
         pck.InsertNTString("United States");
#if DEBUG
         WritePacket(pck.Data, "0x50");
#endif
         return pck;
      }
[/code]

Hope this helps someone.
December 8, 2003, 6:19 PM
Kp
It's supposed to be a dword (symbolic const 'IX86'), and if you're going to treat it as a string, you have it backwards! :P The gameID is also a symbolic dword constant. Also, I have to comment that this seems excessively complex for a basic Packetbuffer - price of progress?
December 8, 2003, 7:33 PM
Myndfyr
[quote author=Kp link=board=17;threadid=4150;start=0#msg34308 date=1070911995]
It's supposed to be a dword (symbolic const 'IX86'), and if you're going to treat it as a string, you have it backwards! :P
[/quote]
Yes, I know - however, the theory is that if the name of the method is "InsertNonNTString" for "Insert Non-Null-Terminated String", then the parameter to be accepted should be a string, shouldn't it? And note the code:
[code]
public virtual void InsertNonNTString(string str)
{
char[] car = new char[str.Length];
for (int i = 0; i < str.Length; i++)
car[i] = str[i];
InsertBYTEArray(Encoding.ASCII.GetBytes(car));
}
[/code]
automatically reverses the string, and then inserts it as an array of bytes. The postcondition is that it takes any length of string, reverses it, and inserts it without a null terminator.

[quote author=Kp link=board=17;threadid=4150;start=0#msg34308 date=1070911995]
The gameID is also a symbolic dword constant.
[/quote]
Again, this is so that some yahoo (ala CSB) can just use "STAR", "SEXP", "JSTR", "WAR3", etc. for the game ID constants, rather than figuring out the hex DWORD. I was too lazy to do it myself.

[quote author=Kp link=board=17;threadid=4150;start=0#msg34308 date=1070911995]
Also, I have to comment that this seems excessively complex for a basic Packetbuffer - price of progress?
[/quote]
Perhaps. I never played around with the source code for a PacketBuffer - I just looked at the function descriptions. The Medium enumeration is probably unnecessary, but I included it so that if you just had a Packet floating around for whatever reason, you wouldn't need to use a typeof() check.

The reason it seems really complicated is that I used an abstract base class and two sealed child classes. I could have used a single class overall, but who knows? Maybe BNLS and B.net will grow up to be different one day. I think when I put this together some months ago, I was just gunning for Starcraft, and IIRC I read in one of the docs that Starcraft/Brood War communicates to Battle.net with UTF-8, whereas the other clients use ASCII. Apparently I didn't plan for ASCII, but I haven't had a problem (I'm not sure about the differences between the two encodings anyway - just that there are static members in the System.Text.Encoding class that do what I need them to do).

Cheers.
December 8, 2003, 11:19 PM
Kp
[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34349 date=1070925579]
Yes, I know - however, the theory is that if the name of the method is "InsertNonNTString" for "Insert Non-Null-Terminated String", then the parameter to be accepted should be a string, shouldn't it?[/quote]

Sure, but you're not supposed to be treating those as strings, NT or otherwise. They're magic dwords.

[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34349 date=1070925579]
And note the code:
[code]
public virtual void InsertNonNTString(string str)
{
char[] car = new char[str.Length];
for (int i = 0; i < str.Length; i++)
car[i] = str[i];
InsertBYTEArray(Encoding.ASCII.GetBytes(car));
}
[/code]
automatically reverses the string, and then inserts it as an array of bytes. The postcondition is that it takes any length of string, reverses it, and inserts it without a null terminator.[/quote]

In looking at that code, it looks like a manual string copy - where's the reversal being done?

[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34349 date=1070925579]
Again, this is so that some yahoo (ala CSB) can just use "STAR", "SEXP", "JSTR", "WAR3", etc. for the game ID constants, rather than figuring out the hex DWORD.[/quote]

So I take it that your environment doesn't support multi-char character literals? It'd be legal to do 'STAR' in VC and you'd get the non-NT "string" "RATS" iirc.

[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34349 date=1070925579]
[quote author=Kp link=board=17;threadid=4150;start=0#msg34308 date=1070911995]
Also, I have to comment that this seems excessively complex for a basic Packetbuffer - price of progress?
[/quote]
Perhaps. I never played around with the source code for a PacketBuffer - I just looked at the function descriptions. The Medium enumeration is probably unnecessary, but I included it so that if you just had a Packet floating around for whatever reason, you wouldn't need to use a typeof() check.

The reason it seems really complicated is that I used an abstract base class and two sealed child classes. I could have used a single class overall, but who knows?[/quote]

I was getting at the amount of work done *in* each function, not at how many there were. Most of the PacketBuffers that I've seen require only a few lines of code for each insertion and don't go chaining to each other several layers deep. :P

[Edit: fix quotes]
December 9, 2003, 12:12 AM
Myndfyr
hehe oops, I copied the wrong code; you don't need a NonNT string, but since it's required implementation for the abstract class, I made one anyway. The correct code from the BnetPacket class was:

[code]
public override void InsertNonNTString(string str)
{
char[] car = new char[str.Length];
for (int i = str.Length - 1, j = 0; i >= 0; i--, j++)
car[j] = str[i];
InsertBYTEArray(Encoding.UTF8.GetBytes(car));
}
[/code]

In C#, it is illegal to do 'STAR', so we pass it as a string instance as "STAR", and the code reverses it and inserts it as four bytes in a row.

And about the several-layer chaining, each method at most calls InsertBYTE or InsertBYTEArray, which in turn calls InsertBYTE. Big deal! I could have inserted directly into the arraylist, but why do that? It's the same thing, just a quick near jump anyway.
December 9, 2003, 12:34 AM
Skywing
It looks like you're trying to UTF-8 encode that? I don't think this is a good idea.
December 9, 2003, 1:13 AM
Kp
[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34383 date=1070930056]
hehe oops, I copied the wrong code; you don't need a NonNT string, but since it's required implementation for the abstract class, I made one anyway. The correct code from the BnetPacket class was:

[code]
public override void InsertNonNTString(string str)
{
char[] car = new char[str.Length];
for (int i = str.Length - 1, j = 0; i >= 0; i--, j++)
car[j] = str[i];
InsertBYTEArray(Encoding.UTF8.GetBytes(car));
}
[/code][/quote]

ah, ok

[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34383 date=1070930056]
In C#, it is illegal to do 'STAR', so we pass it as a string instance as "STAR", and the code reverses it and inserts it as four bytes in a row.[/quote]

How unfortunate. Multi-character literals are so nice. It really should support them. ;)

[quote author=Myndfyre link=board=17;threadid=4150;start=0#msg34383 date=1070930056]
And about the several-layer chaining, each method at most calls InsertBYTE or InsertBYTEArray, which in turn calls InsertBYTE. Big deal! I could have inserted directly into the arraylist, but why do that? It's the same thing, just a quick near jump anyway.[/quote]

... and each call is a performance cost, as well as probably a space cost, unless your VM happens to figure out that these can be inlined (which I doubt it will if it thinks they might be virtual...) Also, I personally find it to be quite a nuisance to trace through many levels of function calls to figure what's really going on. Unfortunately, I'm about to go back to doing exactly that because this library implementor didn't document very well...
December 9, 2003, 5:55 PM
Twin_One1
There is BinaryWriter class, would that work just as well?

EDIT: I meant BinaryWriter.
April 29, 2004, 10:07 PM
K
You could use the BinaryReader class to read from a NetworkStream, yes, but you're still faced with the issue of parsing individual packets.
April 29, 2004, 10:24 PM
Twin_One1
Yeah, I just looked it up, and the BinaryWriter only writes a byte array. :'(
April 29, 2004, 10:36 PM
Myndfyr
[quote author=Twin_One1 link=board=17;threadid=4150;start=0#msg57638 date=1083278199]
Yeah, I just looked it up, and the BinaryWriter only writes a byte array. :'(
[/quote]

Of course, though, I'm using the raw System.Net.Sockets class, and that uses a byte[] to send and receive data. I don't use the underlying stream at all.

They're just utility classes. Do with them what you wish.
April 29, 2004, 10:44 PM
Maddox
It seems more logical to make a buffer class then derive a packetbuffer class from it.
April 30, 2004, 2:26 AM

Search