Skip to content

Support new socket parsing

LucBerge edited this page Apr 27, 2021 · 8 revisions

B4D can read incoming sockets from the Dofus server. While creating you API, you might want to be able to parse sockets that are not parsed yet. I'll introduce you how I reversed engineer chat message sockets. Before going any futher, you'll need to understand the general structure of a dofus socket.

Dofus socket structure

Dofus sockets have the following structure:

ID Flags Length Payload 0x00 0x00 Checksum ?
1 1 2 - 3 * 2 4

The only field you'll need to reverse engineer is the payload, you don't have to parse the rest.

Prerequisits

  • Download and install WireShark
  • Start Dofus and connect to a server
  • Start WireShark and start listening

Reverse Engineering

Step 1 : Identify the sockets you need to parse

The objective of this step is to identify, in WireShark, the sockets you are looking for.

  • Open a terminal and paste the following command to identify the dofus server you are connected to: netstat -a -p TCP -n | findstr :5555

  • In WireShark, add an ip filter to see the incoming sockets from the server: ip.src == 172.65.232.71

  • In the dofus chat, copy a word that someone said. eg: "Kamas".
  • In WireShark, add a content filter to see the sockets containing the word. ip.src == 172.65.232.71 && frame contains "Kamas"

  • In WireShark, identify the socket id (0x03 in V2.59).

  • In WireShark, add a byte filter to see the sockets starting with this id: ip.src == 172.65.232.71 && data[0] == 03

The displayed sockets are now chat messages only. Those are the one you'll need to reverse engineer and parse.

Step 2 : Reverse Engineer the socket

This step is the most complicated and long one. You'll have to find the payload structure from scratch without any documentation. There are multiple ways of doing it. Here is a list of tips you can do:

  • Start by collecting a lot of sockets in WireShark and save the records in a local file. You can then work from this file in offline without Dofus.
  • In the recorded sockets, find the common bytes to identify the delimiters.
  • You can also force the event to happend and look for the location of the expected data in the socket.

Be patient and try hard, ask for help if needed...

The payload structure I found for chat messages is the following:

Channel ID Text length Text 0x60 Id something Length something Something Something else Pseudo length Pseudo
1 2 * 1 3 2 * 8 2 *

The relevant data are the channel id, the text and the pseudo of the player.

The correspondance for channel id is the following:

General Team Guild Allies Group Business Recruitment Private Information Fight Promotion Kolizeum Community
0x04 0x09 ? ? ? 0x05 0x06 0x09 ? ? ? ? ?

Step 3 : Create the parser

  • In the package fr.B4D.socket.event, create a new event class extending the DofusEvent class. This class will represent your dofus event once parsed. It must be a simple class containing attributs and the getters. The attributs can only be defined in the constructor.
/**
 * The {@code ChatMessageEvent} class represents a message in the chat.<br><br>
 * A message is defined by a pseudo, a channel and a text.
 * 
 * @author Lucas
 *
 */
public class ChatMessageEvent extends DofusEvent {
	
	/**
	 * Channel of the message.
	 */
	private Channel channel;
	
	/**
	 * Text of the message.
	 */
	private String text;
	
	/**
	 * Pseudo of the player which send the message.
	 */
	private String pseudo;
	
	/**
	 * Constructor of the {@code ChatMessageEvent} class with a pseudo, a channel and a text to send.
	 * @param channel - Channel of the message.
	 * @param text - Text of the message.
	 * @param pseudo - Pseudo of the player.
	 */
	public ChatMessageEvent(Channel channel, String text, String pseudo) {
		this.channel = channel;
		this.text = text;
		this.pseudo = pseudo;
	}
	
	/**
	 * Returns the text of the message.
	 * @return Text of the message.
	 */
	public String getText() {
		return text;
	}
	
	/**
	 * Returns the channel of the message.
	 * @return Channel of the message.
	 */
	public Channel getChannel() {
		return channel;
	}
	
	/**
	 * Returns the pseudo of the player.
	 * @return Pseudo of the player.
	 */
	public String getPseudo() {
		return pseudo;
	}
}
  • In the package fr.B4D.socket.parse, create a new parse class extending the SocketParser<ChatMessageEvent> class.
/**
 * The {@code ChatMessageSocketParser} class is used to parse a socket relative to chat messages.
 * 
 * @author Lucas
 *
 */
public class ChatMessageSocketParser extends SocketParser<ChatMessageEvent>{

	@Override
	public ChatMessageEvent parse(DofusSocket dofusSocket) throws B4DException {
		//TODO
	}
}
  • Implement the parse method based on the payload structure you identified in step 2. Once all the fields have been identified, you can create the event and return it.
DofusSocketIterator iterator = new DofusSocketIterator(dofusSocket);
		
Channel channel = Channel.fromByte(iterator.getNextByte());
		
Integer textLenght = iterator.getNextSocketElement(2).asSmallEndian();
String text = iterator.getNextSocketElement(textLenght).asString();
		
iterator.skip(4);
		
Integer lengthSomething = iterator.getNextSocketElement(2).asSmallEndian();
iterator.skip(lengthSomething);
iterator.skip(8);
		
Integer pseudoLenght = iterator.getNextSocketElement(2).asSmallEndian();
String pseudo = iterator.getNextSocketElement(pseudoLenght).asString();
		
return new ChatMessageEvent(channel, text, pseudo);
  • In the class fr.B4D.socket.DofusSocketType, map the socket id with the parser by adding a value.
/**
 * Socket representing a message in the chat.
 */
CHAT_MESSAGE_SOCKET((byte) 0x03, ChatMessageSocketParser.class),

At this step, all the sockets starting with the id 0x03 will be parsed using the parser ChatMessageSocketParser. The event will be created and added to the event store.

Step 4 : Wait for the event in a program

When coding an API or a program, you are now able to wait for your event using the fr.B4D.socket.store.EventStore class. The following call is blocking the main thread:

ChatMessageEvent event = EventStore.getInstance().waitForEvent(ChatMessageEvent.class, 1000);

You can also register listeners and wait for a given number of events:

EventStore.getInstance().addEventHandler(Message.class, new EventHandler<Message>() {
	@Override
	public boolean onEventReceived(Message socketEvent) {
		// TODO
		return false;
	}
			
});
EventStore.getInstance().read(Message.class, 100);