• Welcome to Valhalla Legends Archive.
 

[C#]Packet Buffer

Started by MyndFyre, December 08, 2003, 12:19 PM

Previous topic - Next topic

MyndFyre

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.

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));
      }
   }
}


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:


      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;
      }


Hope this helps someone.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

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?
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

MyndFyre

Quote from: Kp on December 08, 2003, 01:33 PM
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  
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:

     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));
     }

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 from: Kp on December 08, 2003, 01:33 PM
The gameID is also a symbolic dword constant.  
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 from: Kp on December 08, 2003, 01:33 PM
Also, I have to comment that this seems excessively complex for a basic Packetbuffer - price of progress?
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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

Kp

#3
Quote from: Myndfyre on December 08, 2003, 05:19 PM
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?

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

Quote from: Myndfyre on December 08, 2003, 05:19 PM
And note the 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));
     }

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.

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

Quote from: Myndfyre on December 08, 2003, 05:19 PM
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.

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 from: Myndfyre on December 08, 2003, 05:19 PM
Quote from: Kp on December 08, 2003, 01:33 PM
Also, I have to comment that this seems excessively complex for a basic Packetbuffer - price of progress?
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?

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]
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

MyndFyre

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:


     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));
     }


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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

Skywing

It looks like you're trying to UTF-8 encode that?  I don't think this is a good idea.

Kp

Quote from: Myndfyre on December 08, 2003, 06:34 PM
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:


     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));
     }

ah, ok

Quote from: Myndfyre on December 08, 2003, 06:34 PM
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.

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

Quote from: Myndfyre on December 08, 2003, 06:34 PM
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.

... 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...
[19:20:23] (BotNet) <[vL]Kp> Any idiot can make a bot with CSB, and many do!

Twin_One1

#7
There is BinaryWriter class, would that work just as well?

EDIT: I meant BinaryWriter.

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.

Twin_One1

Yeah, I just looked it up, and the BinaryWriter only writes a byte array. :'(

MyndFyre

Quote from: Twin_One1 on April 29, 2004, 05:36 PM
Yeah, I just looked it up, and the BinaryWriter only writes a byte array. :'(

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.
QuoteEvery generation of humans believed it had all the answers it needed, except for a few mysteries they assumed would be solved at any moment. And they all believed their ancestors were simplistic and deluded. What are the odds that you are the first generation of humans who will understand reality?

After 3 years, it's on the horizon.  The new JinxBot, and BN#, the managed Battle.net Client library.

Quote from: chyea on January 16, 2009, 05:05 PM
You've just located global warming.

Maddox

It seems more logical to make a buffer class then derive a packetbuffer class from it.
asdf.