Note a Geraff : Je n'ai pas le droit de créer un nouveau topic dans la section approprié. Pourrais-tu déplacer celui-ci ?
Bonjour a tous,
J'étais connu avant sous le nom de sand-saref et je revient sous le nom de Axiiom.
J'ai tout mon été devant moi et les bots dofus, c'est toujours un truc qui me bot (haha), je souhaite donc en recréer un.
Bon en faite c'est plus le coté intelligence artificielle que j'aime mais il faut bien commencer quelque part donc je recommence un bot de zéro.
J'ai regarder un petit peu BIM et devant l'absence de tuto, d'aide le concernant et mon niveau en C#. Je crois bien que je suis actuellement incapable de le comprendre. Je m'en sert tous de même comme renfort quand il y a des choses que j'ai la flemme de programmer (a) (sous l'autorisation Bouh2 bien sur (que je n'ai pas encore :p)).
Et donc je créer ce tuto pour "m'accompagner" (ou plutôt vous accompagner) dans le développement du bot. Je retracerais ici un petit peu tout mon développement et la façon dont j'aborde la chose. J'espère que ça en inspirera certain mais rien n'est perdu, vous aurez au moins une base pour développer un de ces petits logiciel.
Si ça ce trouve, ça va faire comme d'hab, je vais lancer le truc et le laisser a l'abandon mais je tente quand même le coup x).
Bon aller trêve de bavardage.
I. L'idée
L'idée de Bouh "développer un bot complétement modulaire" me plait bien, c'est ce que j'ai toujours voulu faire. Je voudrais développer une base sur laquelle on pourra greffer des plugins, développable par (presque) tout le monde, pour traiter les paquet.
Au début je ferais ma GUI en windows form parce que je ne sais pas faire de WPF.
Il est bien évident que même si je m’efforcerais de rester le plus simple et le plus complet possible, vous devez avoir une base en C# et en visual studio 2010 (et oui 2012 a mit 5 minutes a me faire un nouveau projet donc il est parti a la poubelle).
II. Introduction
J'entend souvent parler de MITM (man in the middle). C'est a dire que notre bot est un logiciel que l'on viendra interposer entre le serveur et le client et qui modifie les paquets "a la volé". Pour voir un petit peu ce que je fais et être guidé, j'ai aussi décider de faire mon MITM. Je m'en sert surtout pour voir les paquets échanger entre le client et le serveur que j'utilise le client (avoir l'id des paquets).
Pour aller plus loin et être plus clair, je souhaiterais que l'utilisateur du bot puisse démarrer un ou plusieurs client et qu'il puisse compléter avec des bots. Quatre client qu'il peut contrôler et quatre bot par exemple. L'avantage d'un bot MITM c'est que comme le client fonctionne de manière normale, vous n'avez pas de risque de vous faire bannir tant que vous ne modifiez pas les paquets. Le problème d'un bot MITM c'est qu'il consomme autant de ressource que le client en consomme. Or avec un bot "client" nous n'avons pas ou presque pas d'affichage graphique. Nous pouvons donc en démarrer 10, 100 peut être 1000....
III. Le proxy
1) modifier config.xml
En gros pour moi proxy = MITM. Ne vous souciez pas des noms.
Il faut commencer par rediriger le client vers notre proxy. Pour cela ou ouvre le répertoire du jeu, on ouvre le config.xml et on remplace la ligne
<entry key="connection.host">213.248.126.39</entry>
par
<entry key="connection.host">127.0.0.1</entry>
Je vous conseille de garder l'adresse IP officielle dans un coin parce quand on veut repasser dans une config normale sans elle, ça devient plus compliqué.
Au cas ou certains ne le saurait pas "127.0.0.1" c'est l'adresse IP locale, boucle locale ou loopback. Quand vous souhaitez rediriger un programme vers votre ordinateur, c'est celle qu'il faut utiliser. Comme j'ai l'intention de développer mon bot sur la même machine que celle ou le client est installer, je met ça.
2) Le listener
Ensuite il faut "écouter" les connexions des clients sur cette adresse IP, c'est le listener. A chaque connexion d'un nouveau client, il va le rediriger sur un autre port et nous donner la socket correspondante (bon ça on s'en fout). Il faut juste que notre class listener écoute les nouvelles connections client et nous donne le moyen de parler avec. Créez donc cette class Listener (on n'est sous windows donc on a le droit de faire des belles choses avec des majuscules et tout).
Nous avons besoin :
- d'un socket
private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Bon je vais pas vous faire tout le détail sur cette objet mais dites vous juste que c'est un fichier sur lequel on peut écrire les données a envoyer au serveur et lire les données envoyer par le serveur.
- d'un thread d'acceptation des nouveaux clients
Thread _acceptingthread = new Thread(new ThreadStart(accept));
Pareil je ne vais pas vous faire tout le détail sur le multithread, MSDN le fait mieux que moi.
- d'un endpoint
IPEndPoint _localEP = new IPEndPoint(IPAddress.Loopback, 5555);
C'est l'adresse ip et le port que la socket va devoir écouter.
- d'un event qui nous retourne les nouveau client auquel on va pouvoir s'abonner
public delegate void onClientActionEventHandler(Socket client);
public event onClientActionEventHandler onClientConnected;
Tous ça est a mettre dans dans le constructeur. Sauf les déclarations qui iront en dehors.
Maintenant a vous de voir si vous tout initialiser dans le constructeur ou dans une méthode intermédiaire, Moi j'ai pris la méthode :
public void startListening()
{
_socket.Bind(_localEP); // on dit a la socket d'écouter les connexion sur local ep
_socket.Listen(100); // on s'en fou
_acceptingthread.Start(); // on démarre le thread d'acceptation
}
}
Il manque un truc non ? La méthode du thread :
public void accept()
{
while (true)
{
Socket socket = _socket.Accept(); // on recupère la socket du nouveau client
if(onClientConnected != null) onClientConnected(socket); // on l'envoi via l'event
}
}
Il y a plusieurs façon de récupérer la socket, l'event est très long a écrire mais il reste très simple et je mit suis mit donc je garde ça.
Le fichier entier :
Cliquez pour révéler
Cliquez pour masquer
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace PimpMyFuxForm.Network
{
class Listener
{
public delegate void onClientActionEventHandler(Client client);
public event onClientActionEventHandler onClientConnected;
private Socket _socket;
private IPEndPoint _localEP;
private Thread _acceptingthread;
public Listener()
{
_acceptingthread = new Thread(new ThreadStart(accept));
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_localEP = new IPEndPoint(IPAddress.Loopback, 5555);
}
public void startListening()
{
_socket.Bind(_localEP);
_socket.Listen(100);
_acceptingthread.Start();
}
public void accept()
{
while (true)
{
Socket socket = _socket.Accept();
if(onClientConnected != null) onClientConnected(socket);
}
}
}
}
Il y a des modif' a faire comme une méthode qui libère les ressources, des events qui signale l'état de la class et remplacer ce "while (true)" qui ne devrait pas exister mais a vous de faire un peu comme voulez, je vous donne juste le minimum.
Par contre un truc qu'il va falloir faire est le remplacement de la méthode accept() par
public void accept()
{
while (true)
{
Socket socket = _socket.Accept();
Client client = new Client(socket);
if(onClientConnected != null) onClientConnected(client);
}
}
qui va nous permettre de récupérer une classe client que nous allons maintenant créer.
3) Le client
Créez donc une nouvelle classe Client. Cette classe va être polyvalente car elle pourra être utiliser pour discuter avec le client mais aussi avec le serveur mais pour l'instant on va juste faire le client.
Dedans on y met un thread, une socket et un ipendpoint puis le constructeur
public Client(Socket socket)
{
_socket = socket;
startReceive();
}
Peut être avez vous deviné que nous allons maintenant faire la méthode "startReceive". qui n'est en faite que le démarrage d'un thread que je donne aussi.
private void startReceive()
{
lowreceivethread = new Thread(new ThreadStart(lowreceive));
lowreceivethread.Start(); // on démarre la boucle de reception
}
private void lowreceive()
{
while (_socket.Connected) // tant que la socket est connecté
{
if (_socket.Available > 0) // si le nombre de byte disponible est supérieur a 0
{
byte[] buffer = new byte[_socket.Available]; // faire un tableau de byte de la longueur du nombre de bytes disponibles
_socket.Receive(buffer); // écrire les bytes disponibles dans le tableau de bytes
if (onReception != null) onReception(this, buffer); // un event qui dit qui a reçu des données et le tableau de bytes reçu
}
}
}
Vous vous souvenez que je vous ai dit que la socket était un espèce de fichier dans lequel on pouvait lire les données reçu. Bah y'a pas 36000 façon de vérifier que des données on été inscrite dedans, il faut vérifier le plus souvent possible.
Voici l'event onReception et vous pouvez rajouter un event onSending que nous ferons juste après
public delegate void onTravellingDataEventHandler(Client sender, byte[] buffer);
public event onTravellingDataEventHandler onReception;
public event onTravellingDataEventHandler onSending;
La fonction send qui n'est pas très compliquer et qui permet d'écrire des données dans la socket
public void send(byte[] data)
{
_socket.Send(data); // on écrit les données
if(onSending != null) onSending(this, data); // un event qui indique qui envoi des données et quelles sont les données envoyées
}
}
Le Client est "en gros" finit. Pensez juste a rajouter ce petit bout de code qui va rendre la classe polyvalente comme je vous l'ai dit. Il va juste permettre a cette classe de ce connecté a une adresse ip donnée :
public Client(IPEndPoint serverip)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // on initialise la socket
_remoteEP = serverip;
}
public void connect()
{
_socket.Connect(_remoteEP); // on connecte la socket
startReceive();
}
Pareil que dans la classe Listener, elle n'est pas complète vous pouvez rajouter pas mal de condition pour la sécurité de votre code.
La classe complète :
Cliquez pour révéler
Cliquez pour masquer
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace PimpMyFuxForm.Network
{
public class Client
{
public delegate void onTravellingDataEventHandler(Client sender, byte[] buffer);
public event onTravellingDataEventHandler onReception;
public event onTravellingDataEventHandler onSending;
IPEndPoint _remoteEP;
Socket _socket;
Thread lowreceivethread;
public Client(IPEndPoint serverip)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_remoteEP = serverip;
}
public Client(Socket socket)
{
_socket = socket;
startReceive();
}
public void connect()
{
_socket.Connect(_remoteEP);
startReceive();
}
public void send(byte[] data)
{
_socket.Send(data);
if(onSending != null) onSending(this, data);
}
private void startReceive()
{
lowreceivethread = new Thread(new ThreadStart(lowreceive));
lowreceivethread.Start();
}
private void lowreceive()
{
while (_socket.Connected)
{
if (_socket.Available > 0)
{
byte[] buffer = new byte[_socket.Available];
_socket.Receive(buffer);
if (onReception != null) onReception(this, buffer);
}
}
}
}
}
4) Le proxy en lui même
On peut maintenant faire une classe Proxy qui va relier les deux classes précédentes.
Il faut juste quelle créer une classe Client qui va aller se connecter au serveur quand une nouvelle connexion est détectée et qu'elle redirige tout les paquets correspondants.
Pour cela on a besoin d'un IPEndPoint, d'une classe Listener et d'une classe Client.
Le constructeur
public Proxy(IPEndPoint server)
{
_serverEP = server; // on le garde pour le Client
_listenerSocket = new Listener(); // on initialise le Listener
}
La méthode qui a démarrer l'écoute
public void start()
{
_listenerSocket.startListening(); // on démarre l'écoute
_listenerSocket.onClientConnected += clientconnect; // on s'abonne aux nouvelles connexions
}
La méthode qui va connecter un client au serveur quand une connexion est détecté
public void clientconnect(Client client)
{
Client server = new Client(_serverEP); // on initialise le client
client.associated = server;
server.associated = client;
client.onReception += forwardtoserver; // on créer les règles de redirection
server.onReception += forwadtoclient;
server.connect(); // on connect le client
}
Je revient sur ces deux lignes
client.associated = server;
server.associated = client;
Ces deux lignes servent a associer la classe Client qui parle au serveur et la classe Client qui parle au logiciel du jeu.
Pour que le code compile, il faudra rajouter dans la classe Client
public Client associated;
Normalement vous devriez comprendre l'intérêt de cette manip dans les deux méthode manquante que je donne maintenant
private void forwadtoclient(Client sender, byte[] buffer)
{
sender.associated.send(buffer);
}
private void forwardtoserver(Client sender, byte[] buffer)
{
sender.associated.send(buffer);
}
La classe complète :
Cliquez pour révéler
Cliquez pour masquer
using System.Net;
namespace PimpMyFuxForm.Network
{
class Proxy
{
public delegate void clientActionEventHandler(Client client);
public event clientActionEventHandler ClientConnected;
Listener _listenerSocket;
IPEndPoint _serverEP;
public Proxy(IPEndPoint server)
{
_serverEP = server;
_listenerSocket = new Listener();
}
public void start()
{
_listenerSocket.startListening();
_listenerSocket.onClientConnected += clientconnect;
}
public void clientconnect(Client client)
{
Client server = new Client(_serverEP);
client.associated = server;
server.associated = client;
client.onReception += forwardtoserver;
server.onReception += forwadtoclient;
server.connect();
}
private void forwadtoclient(Client sender, byte[] buffer)
{
sender.associated.send(buffer);
}
private void forwardtoserver(Client sender, byte[] buffer)
{
sender.associated.send(buffer);
}
}
}
IV. Debrief
Voila c'est finit pour le proxy, normalement le code devrait compiler sans trop de problème, on démarre le processus de la façon suivante
_proxy = new Proxy(new IPEndPoint(IPAddress.Parse("213.248.126.39"), 5555));
_proxy.start();
Si vous avez fait la manip' dans le config.xml que je donne au début et que vous essayer de vous connecter sur le jeu après avoir démarrer le programme et bien ... Rien ne se passe et le jeu fonctionne normalement jusqu’à la sélection du serveur me semble t'il. C'est normal, lorsqu'on sélectionne un serveur on est rediriger sur une autre IP. Si vous souhaitez absolument avoir des retour sur ce que vous avez fait, vous pouvez utiliser WPE PRO sur votre programme.
On se retrouve la prochaine fois soit pour analyser les paquets reçu soit pour créer le formulaire.
Si vous avez la moindre question, si vous ne comprenez pas un truc ou si vous avez une critique ou une remarque a faire, n'hésitez pas a le faire savoir. Je suis ouvert a tous et prêt a compléter ce tuto au faire et a mesure.
Axiiom