Bonjour à tous, bonjour à toute
Dans ce tutoriel, nous allons découvrir les bases pour la création d'un bot socket pour Dofus.
Il s'agit vraiment de bases, ce code pourra être améliorer sans problème.
Il a été rédigé par moi-même en étroite collaboration avec MikeDotNet.
J'ai essayer de faire le plus complet possible, alors ça donne quelque chose de très long, ne faites pas tout en une fois, prenez le temps de bien comprendre.
Je ne donnerai pas le projet final pour obliger les personnes à suivre, comprendre ce tutoriel.
En cas de problème de compréhension n'hésitez pas à poser des questions.
Les codes présent seront en c#, cependant, ils sont facile à traduire dans d'autre langage.
Voici le plan de ce tutoriel :
- I - Prérequis
- II - Création de la solution et de son contenue.
- III - MyBot.Packet
- 1) Arborescence de la library
- 2) Reader/Writer
- 3) Interfaces et class utiles
- 4) La structure des packets
- 5) Les Types
- IV - MyBot.Network
- Préambule
- 1) MyBotSocket
- 2) MyBotBuffer
- 3) MyBotPacketDeserializer
- 4) La méthode Send (MyBotSocket / Writer)
- 5) Assemblage
Attention, c'est partie.
I - Prérequis
Pour réaliser ce tutoriel vous allez avoir de besoin de quelques connaissances rapide, de fournitures (pas scolaire ;) ), d'envie et de motivation.
- Connaissances :
Avant de se lancer dans la création d'un bot socket, il faut, tout de même, avoir quelques connaissances basiques, mais qui permettent néanmoins de comprendre le code que nous allons utiliser.
Vous aurez donc besoin de :
- Connaitre un langage de programmation (mieux vaut préciser, on sait jamais :p )
- Connaitre un minimum les socket (leurs utilités, et les fonctions de base)
- Connaitre également le terme de Thread
- Et enfin avoir compris les grandes lignes du fonctionnement de Dofus 2.0 (packets)
- Fournitures :
Du point de vue matériel, développer un bot socket ne demande pas beaucoup de chose, simplement :
- Visual studio, dans le cadre de langage .net, sinon un IDE à adéquate
- Une dll dont je parlerai par la suite.
- Un cerveaux en forme, qui a la soif d'apprendre.
Attention, c'est partie.
On commence par :
II - Création de la solution et de son contenue.
Avant de foncer la tête la première dans le code, il faut se poser plusieurs questions sur l'architecture du projet (ça évitera de devoir tout recommencer à mi-chemin)
On commence par créer la solution de notre projet : MyBot
Comme vous le savez, pour communiquer dofus utilise des class, accessible dans les sources du jeux (DofusInvoker.swf) qui sont serializées et deserializées. Ces class sont nommées packet.
Dans notre solution, on va donc créer une library nommée MyBot.Packet qui contiendra toute ces class.
Ensuite, on va également créer une autre library que l'on nommera MyBot.Core, dans celle-ci, nous mettrons tout les algorithmes et la logique de notre projet. (tel que le Pathfinding, Cryptage du mot de passe, ... Ceux-ci ne seront pas expliqué dans ce tutoriel)
Encore une autre dll pour la communication: MyBot.Network
Pour finir, on va bien évidament, créer l'exe du projet que l'on nommera MyBot.Interface
Voilà, voici le projet prêt à être démarré :
PS : Libre à vous, d'ajouter d'autre partie dans celui-ci comme par exemple, la lecture des .d2i, .d2o, ...
III - MyBot.Packet
1) Arborescence de la library
Etant donné que cette partie du projet contient, les sources du jeux, nous allons garder la même arborescence que celles-ci.
Il est très important de ranger nos class comme ceci, sinon on est rapidement débordé par la masse de fichier.
On crée donc le premier noeud avec les dossiers "Messages" et "Types" (on veillera toujours à mettre des majuscules)
2) Reader/Writer
Dans cette petite partie, nous allons un peu anticiper sur la suite du tutoriel, en effet, le projet Mybot.Packet va également contenir les class Reader et Writer pour serializer et deserializer nos packet.
Si vous ne maîtrisez pas bien le protocol de Dofus 2.0, c'est le moment de s'y intéresser, même pour ce qui le connaisse bien, je vous conseil de relire ce tutoriel, un peu de révision ne fait pas de mal.
Et bien on utilise (sous .net) les classes BinaryWriter et BinaryReader (en faite non, mais j'expliquerai après) pour écrire le paquet. C'est ces classes qui se chargent d'écrire/lire les octets (comme quoi c'est plus facile hein )
[...]
Problème D. 2.0 utilise un grand endian alors qu’en .net tous les lecteurs binaires sont en petit endian (BinaryReader/Writer en autres). De base il n'y en a pas qui supporte les 2 modes.
Résultat nous devons impérativement exporter une librairie tierce pour pouvoir lire/écrire en Big Endian. Je n'ai pas de conseil spécial à ce sujet, à vous de trouver la bonne
Comme l'a très bien expliqué Bouh2, le protocol de dofus 2.0, lit/écrit les octets en BigEndian (à l'envers) il faut donc trouver un moyen de faire de même.
Je vous conseil vivement, si vous n'avez pas envie de vous prendre la tête, d'utiliser la dll MiscUtils (projet open source téléchargeable gratuitement.)
Bon maintenant équipé, commençons par le reader :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using MiscUtil.IO;
using MiscUtil.Conversion;
namespace MyBot.Packet
{
public class Reader
{
private EndianBinaryReader _reader;
public Reader(byte[] data)
{
_reader = new EndianBinaryReader(BigEndianBitConverter.Big, new MemoryStream(data));
}
Comme vous l'avez remarqué, on définie à Big le converter, comme l'a dit bouh.
Jusque là, rien de bien compliqué, ne vous inquiétez pas, le reste l'est tout autant ;)
Arrivé à ce stade, on va crée toute les méthodes, qui vont nous servir :
public int ReadInt()
{
return _reader.ReadInt32();
}
(faire de même pour uint, short (int16), ushort(uint16), byte, sbyte, bool , double)
On complète ensuite avec :
public byte[] ReadBytes(int lenght)
{
return _reader.ReadBytes(lenght);
}
public string ReadString()
{
ushort stringLenght = ReadUShort();
byte[] byteString = ReadBytes(stringLenght);
return ASCIIEncoding.UTF8.GetString(byteString);
}
Et voilà notre classe finie.
Pour le writer c'est assez semblable :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using MiscUtil.Conversion;
using MiscUtil.IO;
namespace MyBot.Packet
{
public class Writer
{
private EndianBinaryWriter _writer;
private MemoryStream _stream;
public Writer()
{
_stream = new MemoryStream();
_writer = new EndianBinaryWriter(EndianBitConverter.Big, _stream);
}
public void WriteString(string value)
{
byte[] stringByte = ASCIIEncoding.UTF8.GetBytes(value);
WriteUShort((ushort)(stringByte.Length));
WriteBytes(stringByte);
}
public void WriteInt(int value)
{
_writer.Write(value);
}
... faire de même avec les autre types
3) Interfaces et class utile
Comme vous le savez, j'en suis sûre, il y a deux types différents de packet :
[tab]- Les packets que l'on reçois, qui sont envoyé par le serveur et réceptionné par le client (visible dans le MessageReceiver.as)[/tab]
[tab]- Et les packets que l'on envoie, ils sont envoyé par le client à destination du server ( le reste)[/tab]
Ces deux types de packets, nous allons les différencier grâce à deux interfaces et deux enum :
IClientPacket et ClientPacketEnum, pour ceux qui sont envoyé par le client .
IServerPacket et ServerPacketEnum, pour les autres.
[tab]A) Les Enum[/tab]
Chaque enum contiendra la liste des packets correspondant avec le protocolId associé exemple :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet
{
public enum ServerPacketEnum
{
HelloConnectMessage = 3, //HelloConnectMessage est un packet envoyé par le server ayant pour protocolId 3.
}
}
Et
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet
{
public enum ClientPacketEnum
{
IdentificationMessage = 4, // IdentificationMessage est un packet envoyé par le client ayant pour protocolId 4.
}
}
A chaque fois que l'on ajoutera un packet à notre library, on ajoutera sont équivalent dans l'enum correspondant.
[tab]B) Les Interfaces[/tab]
Avant tout si vous ne savez pas ce qu'est une interface, il faut aller vous renseigner, ici par exemple
Les interfaces IClientPacket et IServerPacket vont être implantées sur chacun de nos packets, ils nous permettrons, entre autre, de garder toujours la même structure pour nos packets.
Voici les interfaces :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet
{
public interface IClientPacket
{
ClientPacketEnum PacketType { get; }
void Serialize(Writer writer);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet
{
public interface IServerPacket
{
ServerPacketEnum PacketType { get; }
void Deserialize(Reader reader);
}
}
Je ne vais pas développer plus cette sous-partie, j'en parle davantage dans la partie suivante.
4) La structure des packets
Comme je l'ai expliqué ci-dessus, il est très important de garder toujours la même structure pour nos packets.
Voici celle que nous allons adopter :
[tab]Notions de base :[/tab]
Avant tout, on essayera de remplacer, les variables de type _loc_... par des noms en rapport avec leur rôle.
On remplacera également les boucles while() par des boucles for()/foreach() pour plus de propreté
Il faut toujours déclarer la variable dans le type avec lequel elle va être lu/écrite, exemple : une variable est déclarée en int mais plus bas, dans la méthodes de deserialization on trouve "maVariable = param1.ReadUint();" dans ce cas maVariable devra être déclarée en uint et pas en int.
On utilise des void ( Sub en Vb) pour serializer/deserializer un packet (les fonctions ne servent pas)
[tab]A) Les packets client :[/tab]
Les packets client sont donc, je le rappelle, les packets qui sont envoyé par le client.
Voici un exemple de ceci (je n'ai pas pris IdentificationMessage car il est trop long) :
Cliquez pour révéler
Cliquez pour masquer
package com.ankamagames.dofus.network.messages.connection
{
import com.ankamagames.jerakine.network.*;
import flash.utils.*;
public class ServerSelectionMessage extends NetworkMessage implements INetworkMessage
{
private var _isInitialized:Boolean = false;
public var serverId:int = 0;
public static const protocolId:uint = 40;
public function ServerSelectionMessage()
{
return;
}// end function
override public function get isInitialized() : Boolean
{
return this._isInitialized;
}// end function
override public function getMessageId() : uint
{
return 40;
}// end function
public function initServerSelectionMessage(param1:int = 0) : ServerSelectionMessage
{
this.serverId = param1;
this._isInitialized = true;
return this;
}// end function
override public function reset() : void
{
this.serverId = 0;
this._isInitialized = false;
return;
}// end function
override public function pack(param1:IDataOutput) : void
{
var _loc_2:* = new ByteArray();
this.serialize(_loc_2);
writePacket(param1, this.getMessageId(), _loc_2);
return;
}// end function
override public function unpack(param1:IDataInput, param2:uint) : void
{
this.deserialize(param1);
return;
}// end function
public function serialize(param1:IDataOutput) : void
{
this.serializeAs_ServerSelectionMessage(param1);
return;
}// end function
public function serializeAs_ServerSelectionMessage(param1:IDataOutput) : void
{
param1.writeShort(this.serverId);
return;
}// end function
public function deserialize(param1:IDataInput) : void
{
this.deserializeAs_ServerSelectionMessage(param1);
return;
}// end function
public function deserializeAs_ServerSelectionMessage(param1:IDataInput) : void
{
this.serverId = param1.readShort();
return;
}// end function
}
}
Comme vous pouvez le voir, c'est assez conséquent, surtout que plus de la moitié est inutile, nous nous avons simplement besoin :
- de properties
- d'un constructeur
- d'une méthode de Serialisation (car on souhaite envoyer ce packet)
Commençons point par point:
- Les properties :
Comme il s'agit d'un packet envoyé par le client, nous n'avons donc pas besoin de connaitre les valeurs de ses variable, on peut donc les déclarer en private :
private short _serverId;
Vous pouvez constater que j'ai modifié le type (int en short) car dans la méthode de deserialization, on trouve "_serverId = param1.ReadShort();"
- Le constructeur :
On utilisera un constructeur classique :
public ServerSelectionMessage(short serverId)
{
_serverId = serverId;
}
- La méthode de serialisation :
public void Serialize(Writer writer)
{
writer.WriteShort(_serverId);
}
- Pour identifier le packet, on utilisera le ClientPacketEnum ( et non le protocolId)
public ClientPacketEnum PacketType
{
get { return ClientPacketEnum.ServerSelectionMessage; }
}
Voici la structure de base d'un ClientPacket
La class finale est donc :
Cliquez pour révéler
Cliquez pour masquer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet.Messages.Connection
{
public class ServerSelectionMessage : IClientPacket
{
public ClientPacketEnum PacketType
{
get { return ClientPacketEnum.ServerSelectionMessage; }
}
private short _serverId;
public ServerSelectionMessage(short serverId)
{
_serverId = serverId;
}
public void Serialize(Writer writer)
{
writer.WriteShort(_serverId);
}
}
}
(bien plus courte que la .as)
[tab]B) Les packets server[/tab]
Les packets server sont donc, eux, les packets envoyé par le server.
Voici un exemple :
Cliquez pour révéler
Cliquez pour masquer
package com.ankamagames.dofus.network.messages.connection
{
import __AS3__.vec.*;
import com.ankamagames.jerakine.network.*;
import flash.utils.*;
public class HelloConnectMessage extends NetworkMessage implements INetworkMessage
{
private var _isInitialized:Boolean = false;
public var salt:String = "";
public var key:Vector.<int>;
public static const protocolId:uint = 3;
public function HelloConnectMessage()
{
this.key = new Vector.<int>;
return;
}// end function
override public function get isInitialized() : Boolean
{
return this._isInitialized;
}// end function
override public function getMessageId() : uint
{
return 3;
}// end function
public function initHelloConnectMessage(param1:String = "", param2:Vector.<int> = null) : HelloConnectMessage
{
this.salt = param1;
this.key = param2;
this._isInitialized = true;
return this;
}// end function
override public function reset() : void
{
this.salt = "";
this.key = new Vector.<int>;
this._isInitialized = false;
return;
}// end function
override public function pack(param1:IDataOutput) : void
{
var _loc_2:* = new ByteArray();
this.serialize(_loc_2);
writePacket(param1, this.getMessageId(), _loc_2);
return;
}// end function
override public function unpack(param1:IDataInput, param2:uint) : void
{
this.deserialize(param1);
return;
}// end function
public function serialize(param1:IDataOutput) : void
{
this.serializeAs_HelloConnectMessage(param1);
return;
}// end function
public function serializeAs_HelloConnectMessage(param1:IDataOutput) : void
{
param1.writeUTF(this.salt);
param1.writeShort(this.key.length);
var _loc_2:uint = 0;
while (_loc_2 < this.key.length)
{
param1.writeByte(this.key[_loc_2]);
_loc_2 = _loc_2 + 1;
}
return;
}// end function
public function deserialize(param1:IDataInput) : void
{
this.deserializeAs_HelloConnectMessage(param1);
return;
}// end function
public function deserializeAs_HelloConnectMessage(param1:IDataInput) : void
{
var _loc_4:int = 0;
this.salt = param1.readUTF();
var _loc_2:* = param1.readUnsignedShort();
var _loc_3:uint = 0;
while (_loc_3 < _loc_2)
{
_loc_4 = param1.readByte();
this.key.push(_loc_4);
_loc_3 = _loc_3 + 1;
}
return;
}// end function
}
}
Cette fois-ci, nous avons besoin :
- De properties
- D'une methode de Deserialisation
- Les properties :
Ici, nous avons besoin de connaitre les valeurs des variables, on va donc les déclarer en private/publique (principe de la POO) :
private string _salt;
private List<int> _key = new List<int>();
public string Salt
{
get { return _salt; }
}
public List<int> Key
{
get { return _key; }
}
- Une méthode de deserialisation (comme je l'ai dit, j'utilise ici une boucle for()) :
public void Deserialize(Reader reader)
{
_salt = reader.ReadString();
ushort keyCount = reader.ReadUShort();
for (int i = 0; i < keyCount; i++)
{
_key.Add(reader.ReadByte());
}
}
- Pour identifier le packet on utilisera ServerPacketEnum:
public ServerPacketEnum PacketType
{
get { return ServerPacketEnum.HelloConnectMessage; }
}
On a donc au final :
Cliquez pour révéler
Cliquez pour masquer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyBot.Packet.Messages.Connection
{
public class HelloConnectMessage : IServerPacket
{
public ServerPacketEnum PacketType
{
get { return ServerPacketEnum.HelloConnectMessage; }
}
private string _salt;
private List<int> _key = new List<int>();
public string Salt
{
get { return _salt; }
}
public List<int> Key
{
get { return _key; }
}
public void Deserialize(Reader reader)
{
_salt = reader.ReadString();
ushort keyCount = reader.ReadUShort();
for (int i = 0; i < keyCount; i++)
{
_key.Add(reader.ReadByte());
}
}
}
}
5) Les Types
Notre lib va également contenir les typee, on va donc choisir également une structure à respecter pour ceci, elle va être très semblable à celle que nous avons vue précédemment.
On commence par crée, même principe, un enum :
namespace MyBot.Packet
{
public enum TypeEnum
{
Version = 11,
}
}
On crée ensuite une interface :
namespace MyBot.Packet
{
public interface ITypes
{
TypeEnum Type { get; }
void Serialize(Writer writer);
void Deserialize(Reader reader);
}
}
Que l'on implémentera sur tout nos types.
Je ne fait pas d'exemple complet, c'est la même chose que pour les packet sauf que l'on a les deux méthode (Serialize et Deserialize).
Voilà, je pense que vous êtes maintenant capable d'ajouter des packet à ce projet sans problème.
La base de notre library terminée, nous pouvons désormais passer à autre chose.
IV - MyBot.Network
Préambule :
Avant de commencer cette partie, je pense qu'il est nécessaire de faire plusieurs précisions.
Les méthodes que je vais expliquer ci-dessous ont été faites par Mike, le pro du réseaux !
Vous pouvez d’ailleurs consulter ses tutos sur ce sujet, qui sont très intéressant, dans la partie C#.
Cette partie de notre solution va donc gérer toute la communication bot<->Server , c'est un peu technique et pas très facile à expliqué, donc il est possible que je ne soit pas très claire. Il faudra donc que vous posiez des questions si besoin.
Cette communication va se décomposer en trois parties, trois class primordiales.
Voici un schémas explicatif:
Loading Image
1) MyBotSocket
Cette première class jouera le rôle d'une socket classique, à laquelle on va ajouter plusieurs fonctionnalités.
Donc pour ce qui est de la réception des donnés, nous allons faire ceci de façons synchrone avec un thread dans une boucle infini qui réceptionne les données envoyées par le server.
A) Déclaration des variables :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using MyBot.Packet;
namespace MyBot.Network
{
public class MyBotSocket
{
private Socket _socket;
private Thread _listeningThread;
private IPEndPoint _endPoint;
Je pense que les noms sont assez claires pour que l'on puisse comprendre.
B) Le constructeur :
public MyBotSocket(IPEndPoint endPoint)
{
_endPoint = endPoint;
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listeningThread = new Thread(new ThreadStart(Listening));
}
Rien de bien complexe non plus, la méthode Listening est expliquée plus bas
C) Le démarage :
public void ConnectEndListen()
{
_socket.Connect(_endPoint);
_listeningThread.Start();
}
On connecte la socket et on lance le thread pour écouter la connexion.
D) L'écoute :
Ça se complique légèrement mais rien d'insurmontable.
byte[] receiverBuffer;
byte[] buffer;
int bufferSize;
while (true) // <- Boucle infinie
{
receiverBuffer = new byte[500]; // On crée un nouveau tableau de byte
bufferSize = _socket.Receive(receiverBuffer); // On reçois des données dans le reiverBuffer, et on récupère le nombre de byte reçu.
if (bufferSize == 0) //On est déconnecté
Close(); //On appelle une méthode de fermeture
buffer = new byte[bufferSize]; // Un crée un nouveau tableau de byte de la taille de ce que l'on a reçu
Array.Copy(receiverBuffer, 0, buffer, 0, bufferSize); // On copie le contenue du premier buffer vers le 2eme pour avoir un tableau de la bonne taille
// ici buffer contient le tableau de byte reçu
}
Voilà, j'ai un peu commenté le code, je précise que la taille initiale de receiverBuffer (500) a été choisie arbitrairement, on peut y mettre la valeur que l'on souhaite ( entre 100 et 1000 tout de même pour des soucis de performance).
Une dernière chose :
public void Send(IClientPacket packet)
{
//Nous remplirons ceci plus-tard
}
2) MyBotBuffer
Cette class va jouer le rôle d'un buffer, c'est a dire qu'elle va contenir les bytes et va ensuite, une fois qu'elle obtient un packet entier, lancer un évènement, qui lui va être utilisé dans notre 3)
Cette fois c'est un peu plus compliqué, il faut bien avoir compris le tutoriel de bouh, en particulier à partir :
Attaquons maintenant la structure concrète d'un paquet D. 2.0 :
[HEADER:[HI-HEADER:2 bytes (PacketId 6 bits + LengthType 2 bits)][LENGTH:LengthType (1/2/3) bytes][MESSAGE:LENGTH bytes]]
jusqu'a :
Voilà, désormais vous savez, lire correctement un paquet dans D. 2.0
Il faut également connaitre l'objet Queue<> car c'est la pièce maîtresse de cette class (on peut faire sans, avec d'autre objet, mais ce serai plus long, ou bien avec quelque chose de plus poussé, plus performant cependant, pour un bot socket pour dofus, Queue<> est largement suffisant).
A) Déclaration des variables :
private Queue<byte> _byteQueue = new Queue<byte>();
public event EventHandler<PacketBufferEventArg> RecievePacketBuffer;
L'EventArg PacketBufferEventArg est très simple, elle contient simplement l'id du packet et sont contenue (byte[]) :
Cliquez pour révéler
Cliquez pour masquer
namespace MyBot.Network
{
public class PacketBufferEventArg : EventArgs
{
private byte[] _data;
private short _packetId;
public byte[] Data
{
get { return _data; }
}
public short PacketId
{
get { return _packetId; }
}
public PacketBufferEventArg(short packetId, byte[] buffer)
{
_data = buffer;
_packetId = packetId;
}
}
}
B) La méthode d'ajout de byte[] (Enqueue)
Cette méthode va permettre de remplir notre _byteQueue avec un byte array (byte[])
public void Enqueu(byte[] buffer)
{
foreach (byte b in buffer)
{
_byteQueue.Enqueue(b); // On ajoute chaque byte du buffer à _byteQueue
}
ProcessQueue(); // A voir plus bas
}
C) La méthode de traitement du buffer (ProcessQueue)
Cette méthode est un peu complexe, elle va "regarder" si dans _byteQueue il y a un packet.
Si c'est le cas elle lance l'event crée ci-dessus.
private void ProcessQueue()
{
while (_byteQueue.Count > 0) // Tant que _byteQueue n'est pas vide
{
if (_byteQueue.Count < 2) // Si le nombre de byte contenue est plus petit que 2, on ne pourra pas lire le header du packet, dans ce cas, on sort de la méthode
return;
Reader temp = new Reader(_byteQueue.ToArray()); // On crée un reader temporaire pour lire le header
short header = temp.ReadShort(); // On lit le header
short packetId = (short)(header >> 2); // On récupère l'id du packet
short lenghtSize = (short)(header & 3); // On récupère la taille de la taille du packet
if (_byteQueue.Count < 2 + lenghtSize) // Si on ne peu pas lire la taille du packet, on quitte la méthode
return;
short lenght; // On récupère la taille du packet
switch (lenghtSize)
{
case 0: lenght = 0; break;
case 1: lenght = temp.ReadByte(); break;
case 2: lenght = temp.ReadShort(); break;
case 3: lenght = (short)((((temp.ReadByte() & 255) << 16) + ((temp.ReadByte() & 255) << 8) + (temp.ReadByte() & 255))); break;
default:
throw new Exception("Unknow lenght");
}
if (_byteQueue.Count < lenght + lenghtSize + 2) // Si le nombre de byte contenue dans le buffer est plus petit que : 2 (taille du header) + lenghtSize(taille de la longueur du packet) + lenght (taille du packet) alors on sort de la méthode, on va recevoir prochainement la suite du packet.
return;
DequeueByteUtils(lenghtSize); // On supprime de la _byteQueue les bytes utilisé
byte[] data = new byte[lenght]; // On crée le data du packet
for (int i = 0; i < data.Length; i++)
{
data = _byteQueue.Dequeue(); // On le remplit
}
RecievePacketBuffer(null, new PacketBufferEventArg(packetId, data)); // On lance l'event
}
}
private void DequeueByteUtils(short lenghtSize)
{
_byteQueue.Dequeue();//
_byteQueue.Dequeue();// Header
if (lenghtSize > 0)
_byteQueue.Dequeue();
if (lenghtSize > 1)
_byteQueue.Dequeue();
if (lenghtSize > 2)
_byteQueue.Dequeue();
}
Voilà, notre buffer est pret !
3) MyBotDeserializer
C'est la dernière class de notre partie Network !
C'est elle qui va transformer "PacketBufferEventArg" en un beau packet :)
Elle contient également un event qui renverra le packet.
A) Déclaration des variables
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MyBot.Packet;
using MyBot.Packet.Messages.Connection;
namespace MyBot.Network
{
public class MyBotPacketDeserializer
{
public event EventHandler<PacketEventArg> ReceivePacket;
PacketEventArg :
Cliquez pour révéler
Cliquez pour masquer
namespace MyBot.Network
{
public class PacketEventArg : EventArgs
{
private IServerPacket _packet;
public IServerPacket Packet
{
get { return _packet; }
}
public PacketEventArg(IServerPacket packet)
{
_packet = packet;
}
}
}
B) GetPacket, son unique méthode
Ici on va réutiliser notre interface IServerMessage
public void GetPacket(object obj, PacketBufferEventArg e)
{
ServerPacketEnum packetType = (ServerPacketEnum)e.PacketId; // On convertie le PacketId en ServerPacketEnum
Reader reader = new Reader(e.Data); // On crée le reader
IServerPacket packet = null;
switch (packetType) // A chaque packet que l'on a implanté, on l'ajoute ici
{
case ServerPacketEnum.ProtocolRequired: //
packet = new ProtocolRequired(); // Comme ceci
break; //
default :
Console.WriteLine("Packet id : {0} is not implemented", e.PacketId);
break;
}
if (packet != null)
packet.Deserialize(reader); // On Deserialize le packet
ReceivePacket(null, new PacketEventArg(packet)); // On lance l'event
}
Voilà, c'est tout, la class est terminée.
4) La méthode Send (MyBotSocket/ Writer)
Dans cette partie, nous allons voir comment on pourrait send un packet.
Dans la class MyBotSocket, on a bien crée une méthode Send, c'est de la qu'on l'on va envoyer nos packet, cependant, cette class joue simplement le rôle d'une socket, ce n'est donc pas à elle de générer le byte[] à partir de notre packet.
J'ai donc choisi de faire cette transformation dans le Writer.
Avant tout, on va lui ajouter une fonction "GetByte" qui va retourner un byte[], c'est en faite le byte array sur lequel le writer a écrit.
public byte[] GetByte()
{
return _stream.GetBuffer();
}
Puis, maintenant on va créer une méthode qui va écrire un ClientPacket:
public void WritePacket(IClientPacket packet)
{
ushort packetId = (ushort)packet.PacketType; // On convertie le PacketType en packetId
Writer temp = new Writer(); //
packet.Serialize(temp); // On crée un writer temporaire, et on serialize le packet avec celui-ci
byte[] datat = temp.GetByte(); // On récupère le tableau de byte du writer temporaire
byte lenghtType = GetTypeLenght(data.Length); // On récupère le LenghtType en fonction de la taille.
ushort header = (ushort)(packetId << 2 | lenghtType); // On crée le header
WriteUShort(header); // On l'écrit
switch (lenghtType) // On écrit la longueur du packet en fonction du lenghtType
{
case 0: break;
case 1:
WriteByte((byte)(data.Length));
break;
case 2:
WriteUShort((ushort)(data.Length));
break;
case 3:
WriteUInt((uint)(data.Length));
break;
}
WriteBytes(data); // Et on écrit le bytes de notre writer temporaire.
}
private byte GetTypeLenght(int lenght)
{
if (lenght > 65535)
return 3;
if (lenght > 255)
return 2;
if (lenght > 0)
return 1;
return 0;
}
Voilà, on a plus qu'a l'envoyer à notre MyBotSocket.
5) Assemblage :
Maintenant, comme nos 3 class sont terminées, et qu'on sait comment écrire un packet, il faut "assembler" le tout pour qu'elles fonctionnent.
Je vous laisse faire tout ça tout seul, c'est le seul moyen que j'ai pour savoir si vous avez bien compris tout mon charabia.
Bonne chance, vous avez toute les clefs en main !
Bon voilà, ce tutoriel est fini, j’espère qu'il servira à plus d'un.
J'ai essayer de faire quelque chose de complet, il est donc assez long, et donc il y a surment beaucoup de fautes, il serai sympas de me les faire remarquer
Merci de m'avoir lue, et n'oubliez pas, comme c'est écrit un peu plus bas, Gardes la Pêche !