network subsystem: Difference between revisions

From OpenKore Wiki
Jump to navigation Jump to search
Line 142: Line 142:
But how do we know whether a message is complete? To know that we have to know every message's exact length. That's what recvpackets.txt is for: it specifies which messages have what length. For example, recvpackets.txt has this line:
But how do we know whether a message is complete? To know that we have to know every message's exact length. That's what recvpackets.txt is for: it specifies which messages have what length. For example, recvpackets.txt has this line:


00C0 3
00C0 3


This means that the message with identifier "00C0" is a static length message, and has length 3. But sometimes you also see a line like this:
This means that the message with identifier "00C0" is a static length message, and has length 3. But sometimes you also see a line like this:


00D4 0
00D4 0
00D4 -1
00D4 -1


The 0/-1 means variable length, so in this case it means message 00D4 is a variable length message. As mentioned in appendix A, variable length messages have a message length field which tell us how long that message is.
The 0/-1 means variable length, so in this case it means message 00D4 is a variable length message. As mentioned in appendix A, variable length messages have a message length field which tell us how long that message is.

Revision as of 15:27, 18 May 2023

Overview

The OpenKore network subsystem roughly consists of the following classes:

The Network class

This is the connection manager. It manages the TCP/IP socket connection with the server, and handles things like connection, disconnection, sending data to the server, receiving data from the server, etc. But it doesn't do much else.

Schematically, it looks like this:

File:network-subsystem.jpg

Upon connection to the server, it creates two objects:

  • A message parser object, which is of class Network::Receive::ServerTypeX. Whenever a message is received from the server, that message is passed to the message parser.
  • A message sender object (not seen in the above diagram), which is of class Network::Send::ServerTypeX. This is used for sending messages to the server.

There are several implementations of Network class: Network::DirectConnection, Network::XKore and Network::XKoreProxy.

The Network::MessageTokenizer class

This is a tokenizer class. It extracts discrete server or client messages from a byte stream passed by the connection manager. But it doesn't do much else.

The Network::PacketParser class

This is a base message parser class. It parses messages passed by the connection manager into hashes with message data, as well as does reverse operation - generates messages from hashes with message data. But it doesn't do much else.

Afterwards, parsed messages are handled by built-in handlers in following classes and, with hooks, by plugins or other modules.

Additionally, there is API for modifying or dropping messages to alter any further processing.

The Network::Receive, Network::Send and Network::ClientReceive classes

There classes are descendants of Network::PacketParser class.

Network::Receive and Network::Send are parser helper classes. They contain parser helpers which serve as a workaround in the absense of the capable parser subsystem.

Network::Receive and Network::ClientReceive are message handling classes. They contain built-in handlers for messages coming from the server (Network::Receive) or from the client (Network::ClientReceive), which store information from network messages to be used later in other modules (like the AI).

Network::Send is the message sender class. It encapsulates network messages into simple, easy-to-use functions to be used outside of network subsystem.

Any descendant ServerType class may customize message handlers and parser helpers, but they should refrain from that other than for servertype-specific features and debugging.

The Network::Receive::ServerTypeX and Network::Send::ServerTypeX class

These are serverType description classes. They describe network message identifiers and structures for different servers. But they shouldn't do much else.

Note that none of these classes, from the connection manager to the serverType descriptions, should contain any AI code.

How it all works together

Main initialization code creates a connection manager instance and a message tokenizer instance:

$net = new Network::DirectConnection;

$incomingMessages = new Network::MessageTokenizer(\%recvpackets);

Connection manager creates a packet parser instance:

$packetParser = Network::Receive->create($wrapper, $serverType);

Main loop passes information from connection manager $net to message tokenizer $incomingMessages:

$incomingMessages->add($net->serverRecv);

Message tokenizer with data and a message handler $packetParser are passed to a packet parser $packetParser to process all available messages:

@packets = $packetParser->process($incomingMessages, $packetParser);

# compare with outgoing packets:
# @packets = $messageSender->process($outgoingClientMessages, $clientPacketHandler);

Packets are passed back to the connection manager, which passes them to XKore clients:

$net->clientSend($_) for @packets;

Meanwhile, the packet parser calls custom parsers, hooks and built-in handlers:

my $custom_parser = $self->can("parse_$handler_name")
if ($custom_parser) {
	$self->$custom_parser(\%args);
}

Plugins::callHook("packet_pre/$handler_name", \%args);

my $handler = $messageHandler->can($handler_name);
if ($handler) {
	$messageHandler->$handler(\%args);
}

Plugins::callHook("packet/$handler_name", \%args);

Handling multiple server types (message parser part)

Implementation details

Example 1: adding a new message handler

Example 2: handling a different server type

Handling multiple server types (message sender part)

Using the message sender

Compatibility notes

Hooks

The message sender class provides some hooks which allows plugins to handle messages.

"packet_pre/$HANDLER_NAME"

Here, $HANDLER_NAME is the name of the handler function, as specified in the packet_list hash. For example, "packet_pre/account_server_info".

This hook is called just before the handler function is called. But this hook is only called if there is a handler function for the current packet. The argument given to this hook is an array containing the message fields (the message arguments).

"packet/$HANDLER_NAME"

This hook is called after the handler function is called, or when there is no handler function for the current message. Its argument is the message arguments.

Appendix A: introduction to the Ragnarok Online protocol

The Ragnarok Online protocol uses TCP as its transport protocol. Every message* that the RO server sends to its clients has the following format:

packet switch (2 bytes) + message content (variable length)

A message consists of at least 1 field: the message identifier (also known as the packet switch, but I think "message identifier" is easier to understand). This message identifier tells the client what kind of message this is. How the message is to be interpreted depends on this message identifier.

There are two kinds of messages:

Static length messages

These messages are always of the same length. Examples of such messages are the "someone has sent an emoticon" message and the "a monster has appeared" message.

Variable length messages

The length of these messages depend on their contents. Because their lengths vary, they have a special message length field which tells the client exactly how long the message is. This length is the length of the entire message, including message identifier. The "you have sent a public chat message" message is an example of a variable length message. Finally, we have the message arguments. The exact contents of the arguments depends on the message. For example, the "someone has sent an emoticon" message has the following information in its message arguments:


The ID of the actor who sent the emoticon. What kind of emoticon it was.


(*) There is one exception to the rule. If the client is in-game, and the user instructs the client to switch character, then the client will disconnect from the map server and connect to the character server. The first message that we receive, in this case, is the account ID, which is exactly 4 bytes. It is not a "normal" RO message in that it has no message ID - it's just a serialized integer.

Appendix B: recvpackets.txt and handling message lengths

When we receive data through a socket from the RO server, we cannot assume that we receive exactly 1 complete message every time we read from the socket. We may receive a part of a message, or we may receive two messages, or we may receive a complete message and an incomplete part of the next message. This is why we must buffer data received from the RO server. Whenever we've determined that we've received at least one complete message, we'll process that message and remove it from the buffer. Then we keep waiting until we know we have another complete message, and so forth.

But how do we know whether a message is complete? To know that we have to know every message's exact length. That's what recvpackets.txt is for: it specifies which messages have what length. For example, recvpackets.txt has this line:

00C0 3

This means that the message with identifier "00C0" is a static length message, and has length 3. But sometimes you also see a line like this:

00D4 0
00D4 -1

The 0/-1 means variable length, so in this case it means message 00D4 is a variable length message. As mentioned in appendix A, variable length messages have a message length field which tell us how long that message is.

Appendix C: obfuscation of outgoing messages

RO has made several attempts to prevent third party clients from (correctly) accessing the server. The most important attempts involve the obfuscation of outgoing messages. That is: messages that are to be sent from an RO client to the RO server are first obfuscated using some algorithm. This appendix describes a few obfuscation techniques.

Padded packets

This is not used anymore Some RO servers, such as euRO (Europe), iRO (International) and rRO (Russia) use so-called padded packets. The RO client will insert what seems to be garbage data into parts of certain message. In reality, this "garbage" is the result of of a complex hashing algorithm. Furthermore, the size of the garbage data, and the algorithm that is used, varies every time a sync is received from the RO server. Thus, a packet may have different sizes during different times.

We refer to messages that contain such data, as "padded packets". The padded packets emulator subsystem in OpenKore is responsible for generating correct padded packets.

Padded packets only affect packets that are sent from the client to the server, not packets received from server to client. Furthermore, not all packets are padded - only some are, usually the "sit", "stand", "attack" and "use skill" packets.

See the file "src/auto/XSTools/PaddedPackets/README.TXT" in the OpenKore source code for more information.

Encrypted message IDs

In this technique, the message ID of an outgoing message (i.e. the first 2 bytes) might be encrypted. This is only applicable to messages that are sent to the map server - account server and character server messages are unaffected. Messages are unencrypted, until the map server, at some point, sends the encryption key. This encryption key is valid as long as the connection to the map server is alive. Once the client disconnects from the map server, the encryption key is invalid and should not be used.

See the file "src/Network/Send.pm" in the OpenKore source code for the exact algorithm. Look for function encryptMessageID().

Original article

http://web.archive.org/web/20090305035837/http://www.openkore.com/wiki/index.php/Network_subsystem