C# Panique général dans le protocole et la lecture des paquets Dofus (2) C#

Inscrit
12 Aout 2021
Messages
35
Reactions
6
#1
Bonsoir / Bonjour à tous, je suis un développeur non professionnel, et je m'arrache les cheveux depuis plusieurs jours sur la lecture des paquets de Dofus 2,
Je m'explique, j'ai lu à peu près 4 fois tout les tutoriels sur le sujet que ce Forum propose, même s'il date un peu j'imagine que la structure des paquets n'a pas changé drastiquement depuis mais bon.
Bref, je suis les tutos, mais malheureusement, là ou je devrais récuperer l'id du paquet par exemple je récupère toujours exactement la même chose (Soit -2911 ou 1388)
Peu importe l'action que je fais en jeu (Je précise que je sniffe simplement les paquets provenant du port "5555" ou dont le port de destination est "5555")

Voilà le code exact que j'ai
cd.PNG

J'explique :
- Je récupère un packet
- Je le "cast" en packet TCP
- Je vérifie le SourcePort et le DestinationPort du paquet
- Je mets les bytes du paquet dans un MemoryStream pour me permettre de les "traduire" (packetDatas est un byte[] qui contient tcp.Bytes (Ou les bytes du paquet))
- Je "traduis" le packet en Int16 (2 bytes)
- Je cast mon packet ID en Short en décallant le bit de 2
- Je cast le lenType

Donc..

Après ça, je ne comprends pas pourquoi ça ne fonctionne pas, alors je vais bidouiller les sources du jeu à la recherche de plus amples ressources pour me défaire de ce problème..

Et je regarde le Packet "HelloGameMessage" (com.ankamagames.dofus.network.messages.approach) car je le trouve assez simple, donc c'est lui que j'ai décidé d'étudier.

Je vois dedans qu'il y'a une fonction Pack qui ne renvoie rien (Je ne fais pas d'AS3 mais j'arrive quand même à comprendre les grandes lignes)
Je vois que cette fonction Pack prends en paramètre une "variable" ? nommée "Output" qui est de type ICustomDataOutput (Si je ne dis pas de bétise, c'est une interface de ce que j'ai cru comprendre)

Puis dans le corps de la fonction :
- Création d'un byteArray (Tableau de byte) vide nommé "data"
- Un appel à la fonction "serialize" qui ne renvoie rien, dont les corps semble être vide mais qui prends un ICustomDataOutput en paramètre, l'appel ce fait comme ceci : this.serialize(new CustomDataWrapper(data));
Alors je regarde cette fameuse classe, le constructeur super(); vers nulle part (J'avoue que je comprends pas, puis-ce que les seuls choses qu'il implement sont des interface qui ne contiennent pas de constructeur (ICustomDataInput, ICustomDataOutput)) Bref.
- Et un appel à writePacket(output, this.getMessageId(), data) //getMessageId() renvoi protocolId (5556); donc l'ID du packet

J'en deduis donc que ça va me servir, je passe quelques minutes à analyser la classe, et je finis par comprendre le pourquoi du comment je code ces lignes dans mon "bot", malheureusement, je n'arrive pas à comprendre pourquoi je ne récupère pas l'ID du paquet mais soit -2911 ou 1388, aucune idée

Voilà pourquoi je poste, si quelqu'un à une idée pour m'aider un peu je vous remercie, bonne soirée à tous
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#2
Edit :
Après plusieurs longue minute d'expérimentation un peu compliquée j'ai réussi à codé ça :
Code dont je suis très fier:
#region ServerSide

            uint ID = 123;
            uint processedID = ID << 2;

            byte[] Buffer = new byte[128];

            BeBinaryWriter Writer = new BeBinaryWriter(new MemoryStream(Buffer));

            Writer.Write((short)processedID);

            #endregion

            #region Bot(ClientSide)

            BeBinaryReader Reader = new BeBinaryReader(new MemoryStream(Buffer));

            Int16 translator = Reader.ReadInt16();
            short UnknownID = (short)(translator >> 2);
            MessageBox.Show("Recieved : " + translator + "\nByteModification : " + UnknownID);

            #endregion
Bon alors ça à pas l'air de grand chose pour les génies que vous êtes, mais j'vous assure que c'est une révolution pour moi d'avoir réussi à faire un truc qui marche x)
En gros j'm'entraine à faire ça pour comprendre de mieux en mieux le comportement des bytes et tout ça, pour pouvoir comprendre ce qui pêche avec le paquet que je récupère..
Mais toujours pas le début d'une explication concernant le problème, j'vais continuer à tester des trucs.
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#3
Edit :
Bon de retour de 40 minutes de souffrance intense.
J'ai associé mon code à une connexion direct en socket, et je recupère bien le packet ID = 1030 (Celui de HelloConnectMessage)
J'en déduis donc que le problème ne vient pas du parsing de paquet mais des byte[] que j'utilise en tant que Buffer qui ne correspondent pas au paquet ou à la bonne section du paquet D2.. Le problème est encore pire je crois, mais plus j'avance, plus je me rapproche de la vraie source du problème
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#4
Edit : J'ai fini par réussir.
Bon il ne sniff que le paquet 1030 pour l'instant mais si celui là est lu ça doit être correct pour les autres
Maintenant, j'ai remarqué que des fois dans mon Packet (Dans le Payload du packet) j'ai ces quelques bytes qui viennent se foutre dedans : 95 29 0f 00 0d 31 2e 30 2e 33 2d 33 32 38 36 63 37 35 et des fois c'est un packet séparré, c'est à dire que je reçois d'abord ces bytes dans un paquet, puis le paquet d'HelloConnectMessage (Qui commence par 10 1a)

En gros pour résumer j'ai 1 fois sur 2, un paquet contenant uniquement : 95 29 0f 00 0d 31 2e 30 2e 33 2d 33 32 38 36 63 37 35
Et le HelloConnectMessage (10 1a..) qui s'envoie en 2 fois

Et les autres fois j'ai : 95 29 0f 00 0d 31 2e 30 2e 33 2d 33 32 38 36 63 37 35 10 1a..
Donc 1 fois sur 2 je peux pas read le paquet, j'vais faire des tests, voir si c'est toujours exactement les mêmes bytes peu importe le paquet envoyé ou si ils changent (Si c'est toujours les mêmes, j'aurai juste à verifier leur présence et les tejs s'ils sont là)

Edit : En fait je sniffer tout simplement la "trame" complete du message, avec le header du paquet.. 'fin bref, vous connaissez la composition d'un paquet Tcp, au lieu de récupèrer simplement le Payload du Paquet, je récupérer le tout, donc forcément..
 
Dernière édition:
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#5
Edit :
Par contre je ne comprends toujours pas comment récuperer uniquement les bytes du contenu
Par exemple je ne sais pas comment récuperer le texte dans un paquet 'ChatServerMessage'
J'arrive à récuperer l'id du paquet, la taille de la taille, la taille du contenu du paquet (Le message écrit par un joueur quoi) mais j'arrive pas à comprendre comme accèder à ces bytes pour les Read en UTF si quelqu'un peut m'aider, merci

En gros : J'arrive à tout récuperer sauf l'information un message du genre "Recrute.." je sais comment procéder pour le traduire, ce que je ne sais pas c'est comment récupérer ces bytes pour les procéder
 
Inscrit
14 Decembre 2012
Messages
48
Reactions
2
#6
Un packet TCP ne veut pas dire un Message (au sens du protocol de Dofus). Tu peux très bien avoir plusieurs fragments, et tu dois donc savoir la taille attendue (qui est la première information que tu reçois, bien fait hein). D'où l'utilité de travailler avec un stream, histoire de pouvoir le passer en argument et laisser l'héritage (le fameux "this.deserialize()") s'occuper de la lecture au fur et à mesure (à coup de readVarInt() par exemple). Après tu n'as plus qu'à lire les sources et progresser doucement. Si tu as du mal tu peux trouver des reader/writer big endian sur le forum. Donc tu récupères l'id, tu construits un mapping vers le bon message (switch case, dictionnaire, array, comme tu le sens), tu lui donnes la main et tu le laisses extraire les informations nécessaires.

Par exemple pour le HelloConnectMessage, il arrive dans le MessageReceiver (dans la fonction parse) qui détermine qui gère cet id là (en outre ici le HelloConnectMessage) et lui donne la main:
1628860834440.png

Dans le HelloConnectMessage:
1628860887624.png
1628860925174.png

Ce n'est pas très compliqué, juste long et répétitif.
Ah oui et je pense que c'est un uint16 (unsigned short) maintenant le header, plus un signed short. Du coup c'est normal que tu aies des valeurs négatives.
 
Dernière édition:
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#7
Salut, alors déjà merci d'avoir pris le temps de répondre.
Je vais expliquer exactement ce que mon code fait à l'heure actuelle :

- Sniffage du port 5555
- Recuperation des paquets TCP entrants et sortant
- Pour chaque paquet, récupere l'entete de l'entete du paquet, récupere l'id paquet, recupere la taille du paquet

Moi ce qu'il me manque ou que je n'arrive pas à utiliser c'est par exemple un readUint();
Est ce que je dois séparer l'hi-header du reste, recuperer "le reste" dans un byte array, et utiliser un BigEndianReader sur le tableau du reste ?


Par exemple si un paquet etait codé comme ça : {Hiheader}{MessageLenth}{Salut toi}

Je sais recuperer le HiHeader, le message length, mais Salut toi, c'est ce que j'essaye de recuperer comme bytes dans les data du paquet, mais je sais pas comment procéder
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#8
Merci pour les screens des sources mais pareil, dans Parse à quoi ressemble l'input ? Est-ce que c'est exactement la meme trame que je recois ou ça été modifié d'une quelconque façon avant d'arriver à la methode Parse, ou pas
Sinon :ça veut dire que je dois readUint16() sur le reader qui contient le hiheader + message ?
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#9
RE ! J'ai une bonne nouvelle (Pour moi en tout cas)

"J'arrive" à parser un message de chat, bon c'est encore très très très très très très très très très très très très très sommaire, disons que si le message est trop petit j'ai mon reader qui me fait la gueule parce qu'il ne peut pas lire plus loin que le flux de ce que l'erreur me rapporte, mais j'suis sur la bonne voie j'ai l'impression (J'ai juste recopier betement le code des sources et je l'ai réadapté, mais j'arrive au moins à lire un truc, en fait je crois avoir compris le fonctionnement d'un MemoryStream, en gros si je fais un "readByte();" il va récuperer le byte au début de mon buffer, et "supprimer" ? ou mettre son curseur plus loin, pour un ReadInt() il va aller se placer 4 bytes plus loin pour lire le prochain truc etc etc..
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#10
Hey hey ! C'est moi que v'là, bon j'ai pas chômé (Enfin peut-être un peu mais.. bon, c'est ouf ce que j'ai fais pour moi)

Bref voilà, j'ai un tout petit soucis, vraiment pas grand chose, enfin.. si c'était si peu je serais pas là à écrire ces lignes.

Donc j'vais balancer mon code (Il est commenté)

Traitement du Paquet TCP :
Traitement Paquet TCP:
var Packet = PacketDotNet.Packet.ParsePacket(e.GetPacket().LinkLayerType, e.GetPacket().Data);

            var ip = Packet.Extract<PacketDotNet.IPPacket>();
            if(ip != null)
            {
                var tcp = Packet.Extract<PacketDotNet.TcpPacket>();
                if(tcp != null)
                {

                        //Tout ce qui est au dessus est un peu useless, c'est juste la partie sniff du paquet


                        byte[] buffer = Packet.PayloadPacket.PayloadPacket.PayloadData;


                        if (buffer.Length > 0)
                        {
                            BigEndianBinaryReader reader = new BigEndianBinaryReader(new MemoryStream(buffer));    //Créer un stream des datas du paquet

                            Int16 hiheader = reader.ReadInt16(); //Récupère le Hi Header du paquet
                            short packetID = (short)(hiheader >> 2); //Récupère l'ID du paquet (Enfin, du protocol contenu dans le paquet)
                            short lenType = (short)(hiheader & 3); //Récupère le lentype
                            uint length = 0;

                            switch (lenType)
                            {
                                case 0:
                                    length = 0;
                                    break;
                                case 1:
                                    length = reader.ReadByte();
                                    break;
                                case 2:
                                    length =  reader.ReadUInt16();
                                    break;
                                case 3:
                                    length = (uint)(((reader.ReadByte() & 255) << 16) + ((reader.ReadByte() & 255) << 8) + (reader.ReadByte() & 255));
                                    break;
                                default:
                                    length = 0;
                                    break;
                            }

                            TreatPacket(reader, packetID, length, buffer); //Appel la fonction TreatPacket
                            //reader.Close();
                        }
                }
            }
Paquet Traitement :

Paquet traitement:
private void TreatPacket(BigEndianBinaryReader Reader, short packetID, uint Lenght, byte[] Buffer)
        {
            switch(packetID)
            {
                case 5855: //Protocol ID correpondant à la classe 'com.ankamagames.dofus.network.messages.game.chat.ChatServerMessage
                    new ServerMessageManager(Reader, Lenght, richTextBox1, Buffer); //Créer un nouveau ServerMessageManager en lui passant, le stream des datas, la taille du contenu du paquet, (Le log de la lecture du packet), le buffer (Dont je ne me sers que pour analyser les bytes des paquets erronés)
                    break;
   
            }
        }
Gestion du paquet 'ChatServerMessage' :

Gestion du paquet 'ChatServerMessage':
    public class ServerMessageManager
    {
        private uint _Length = 0; //La taille du contenu
        private BigEndianBinaryReader _Reader; //Le stream des datas (Sans le Hi Header)
        private RichTextBox _LogBox; //Le log des paquets
        private byte[] _Buffer; //Le buffer pour read les bytes des paquets éronés, si paquet éronés il y'a

        private double _SenderID = 0; //SenderID (Information du contenu du paquet)
        private string _SenderName = ""; //SenderName (Information du contenu du paquet (Qui represent l'emetteur du message))

        private uint _Channel = 0; //Le canal sur lequel à été envoyé le message
        private string _Content = ""; //Le contenu du message (Le message en somme)
        private uint _TimeStamp = 0; //Un truc dont je ne me sers pas
        private string _FingerPrint = ""; //Un autre truc dont je ne me sers pas

        // Dans cette classe je reproduis le plus fidelement possible les sources du jeu

        public ServerMessageManager(BigEndianBinaryReader Reader, uint Length, RichTextBox LogBox, byte[] Buffer = null)
        {
            _Reader = Reader;
            _Length = Length;
            _LogBox = LogBox;
            _Buffer = Buffer;
            DeserializeChatServerMessage();
            _Reader.Close(); //Ferme le stream une fois qu'il a disséquer le paquet
        }

        public void DeserializeChatServerMessage()
        {
            DeserializeChatAbstractServerMessage(); //Lecture du premier "paquet"
            try //Préviens en cas d'eventuel erreur
            {
                _SenderID = _Reader.ReadDouble();
                _Reader.BaseStream.Position = _Reader.BaseStream.Position + 1;
                _SenderName = _Reader.ReadString();
                LogMessage();
            }
            catch (Exception ex)
            {
                ErrorLogMessage("La fonction DeserializeChatServerMessage à retourner : " + ex.Message + " erreur");
            }
        }

        public void DeserializeChatAbstractServerMessage()
        {
            try
            {
                _Channel = _Reader.ReadByte(); //Récupère l'ID du chan sur lequel le message à été envoyé
                _Reader.BaseStream.Position = _Reader.BaseStream.Position + 1; //Se déplace de 1 dans le stream (Parce que je ne sais pas pourquoi mais entre le ChannelID et le contenu du message, il y'a un byte (Si vous avez une idée))
                _Content = _Reader.ReadString(); //Récupère le contenu du message
                _TimeStamp = _Reader.ReadUInt32(); //Récupère le timestamp (Contenu dans le contenu du paquet)
                _Reader.BaseStream.Position = _Reader.BaseStream.Position + 1; //On se redéplace de 1 pour pouvoir lire le FingerPrint
                _FingerPrint = _Reader.ReadString(); //Recupère le FingerPrint
            }
            catch (Exception ex)
            {
                ErrorLogMessage("La fonction DeserializeChatAbstractServerManager à retourner : " + ex.Message + " erreur"); //Si il y'a une erreur quelconque on le notifie dans les logs et on affiche un messageBox, qui contient entre autre le _Buffer (Ou la structure complète du paquet erronée)
            }
        }


        private void LogMessage()
        {
            string Message = "(" + Enum.GetName(typeof(Utils.Channels), (uint)_Channel) + ") " + _SenderName + " : " + _Content + "\n"; //Cast le contenu du paquet pour le rendre lisible (Format Dofus en somme)
            _LogBox.Invoke(new MethodInvoker(() => _LogBox.AppendText(Message)));
        }
        private void ErrorLogMessage(string ErrorMessage)
        {
            if (_Buffer != null) //Si j'ai passé le Buffer en argument dans le constructeur, alors il va afficher les bytes du paquet
                ErrorMessage += "\n" + Utils.GetBytes(_Buffer);
            _LogBox.Invoke(new MethodInvoker(() => _LogBox.AppendText(ErrorMessage + "\n"))); //Affichage du message d'erreur dans les logs
        }


    }
Donc, le chat Dofus correspondant :

dofus-2021-08-13_21-20-35-Mecopi.png

Et maintenant la sortie sur mon programme :

1_Help.PNG

En gros la plupart des messages sont bien lu, mais pour une raison que je ne connais pas (Peut-être que si le message est trop long, la trame change pour récuperer le contenu du message ailleurs sur la trame ou je sais pas trop)

Et donc le message de Piicola est complétement bugué, je récupère une partie de son message là où j'aurai du récuperer son Nom.

Edit : Je viens de comprendre un truc en regardant les sources. (Plus j'avance, plus je m'y retrouve, ça fait vraiment plaisir)
En fait le byte que je devais sauter dans mon code (Pour read le contenu du paquet) bah c'est l'instance ID ! C'est vrai que j'avais lu quelque part que les paquets contenait ce truc là, mais j'y avais pas prêter attention, c'est en regardant writePacket (Dans la classe Network message) que j'ai pigé..

En gros le paquet est codé comme ça :
Type len est défini à partir de la taille du contenu du paquet
On écrit dans le flux un short qui contient "MsgID" décaler de 2 bits vers la gauche, on | avec le type len, et en fait dans mon code je lis un short >> 2 pour décaller les bits de 2 pour récupérer simplement le MsgID et ensuite je lis un autre short & 3 pour dissocier le type len du tout (3 étant le BITMASK qui est utilisé dans la classe ServerConnection d'ailleurs)

Alors j'ai pas l'habitude de faire des compliments, et je suis du genre à approuver le développement d'Ankama, et vu que je suis novice dans le domaine du réseau.. je trouve que c'est du p***** de génie.
 
Dernière édition:
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#11
Un packet TCP ne veut pas dire un Message (au sens du protocol de Dofus). Tu peux très bien avoir plusieurs fragments, et tu dois donc savoir la taille attendue (qui est la première information que tu reçois, bien fait hein). D'où l'utilité de travailler avec un stream, histoire de pouvoir le passer en argument et laisser l'héritage (le fameux "this.deserialize()") s'occuper de la lecture au fur et à mesure (à coup de readVarInt() par exemple). Après tu n'as plus qu'à lire les sources et progresser doucement. Si tu as du mal tu peux trouver des reader/writer big endian sur le forum. Donc tu récupères l'id, tu construits un mapping vers le bon message (switch case, dictionnaire, array, comme tu le sens), tu lui donnes la main et tu le laisses extraire les informations nécessaires.

Par exemple pour le HelloConnectMessage, il arrive dans le MessageReceiver (dans la fonction parse) qui détermine qui gère cet id là (en outre ici le HelloConnectMessage) et lui donne la main:
Afficher la pièce jointe 652

Dans le HelloConnectMessage:
Afficher la pièce jointe 653
Afficher la pièce jointe 654

Ce n'est pas très compliqué, juste long et répétitif.
Ah oui et je pense que c'est un uint16 (unsigned short) maintenant le header, plus un signed short. Du coup c'est normal que tu aies des valeurs négatives.
Je tiens aussi à te remercier, car même si j'avais déjà penser à regarder les sources, c'est grâce à toi que j'ai tilté comment procéder pour récuperer le contenu d'un paquet, alors merci.
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#12
Bon, c'est re moi que v'là, après 2 jours de recherches intensives, de décortication de code, de compréhension de truc petit à petit, j'ai fini par réussir à déchiffrer les messages et je n'obtiens plus aucune erreur, ça venait dans un premier temps de la lecture du Header, qui comme me l'avait dit AshlanFox était bien encodé sur un short (Merci encore à toi) et de mon BinaryReader qui n'était pas "adapté" au protocol Dofus, alors j'ai choppé le Reader et tout fonctionne niquel maintenant, bonne soirée à tous.
 
Inscrit
14 Decembre 2012
Messages
48
Reactions
2
#13
Bon, c'est re moi que v'là, après 2 jours de recherches intensives, de décortication de code, de compréhension de truc petit à petit, j'ai fini par réussir à déchiffrer les messages et je n'obtiens plus aucune erreur, ça venait dans un premier temps de la lecture du Header, qui comme me l'avait dit AshlanFox était bien encodé sur un short (Merci encore à toi) et de mon BinaryReader qui n'était pas "adapté" au protocol Dofus, alors j'ai choppé le Reader et tout fonctionne niquel maintenant, bonne soirée à tous.
Tant mieux si ça a pu aider et je suis content de voir que tes recherches ont porté leur fruit (c'est toujours mieux quand on le découvre soit-même), courage pour la suite ;)
 
Inscrit
12 Aout 2021
Messages
35
Reactions
6
#14
Tant mieux si ça a pu aider et je suis content de voir que tes recherches ont porté leur fruit (c'est toujours mieux quand on le découvre soit-même), courage pour la suite ;)
Merci ! :D
 
Haut Bas