Packet Serialization

To serialise packets gives us the option to split preparation code and packet creation. It comes handy for our approach to support different versions.

class ManagedPackets

The class has two member variables initialized with the constructor. The first variable is “m_opcode” (uint16_t) and holds the value of the opcode. The second variable is m_minimum_size (size_t) which is used to check the packet size before it gets deserialised. We check the received packets because we have to make sure, that it fits into the prepared read structure. Beside deserialisation, we have to serialise all packets we create and send to the clients program. To define the size of serialised packets, we use a virtual function called “expectedSize()”.

We should mention, that all class functions are in fact virtual functions and should be overwritten by packet classes.

Packet classes

There should be a class for every single packet which includes content. Be aware that some packets have no content, therefore without anything to de-/sereialise. The packet class inherits publicly from the ManagedPacket class, e.g. for MSG_GUILD_BANK_LOG_QUERY:

class MsgGuildBankLogQuery : public ManagedPacket

Packet classes - class constructor

The class constructor holds the initialiser for the inheritanced class ManagedPacket as first parameter. As already stated the first value is the packet, the second is the minimum size. All other packet specific member fields gets initialized after it. e.g.

MsgGuildBankLogQuery(uint8_t slotId, std::vector<GuildBankMoneyLog> moneyLog) :
            ManagedPacket(MSG_GUILD_BANK_LOG_QUERY, 1),
            slotId(slotId),
            moneyLog(moneyLog)
        {
        }

Packet classes - member fields

The member fields represent the structure of the send/received packet. All member fields represents the corresponding part of the packet with its name and type. To keep the initiale parameters amount as low as possible, we suggest to create a struct e.g.

struct GuildBankMoneyLog
{
    uint8_t action;
    uint64_t memberGuid;
    uint32_t entry;
    uint32_t stackCount;
    uint32_t timestamp;
};

namespace AscEmu::Packets
{
    class MsgGuildBankLogQuery : public ManagedPacket
    {
    public:
        uint8_t slotId;
        std::vector<GuildBankMoneyLog> moneyLog;

        MsgGuildBankLogQuery() : MsgGuildBankLogQuery(0, {})
        {
        }

        MsgGuildBankLogQuery(uint8_t slotId, std::vector<GuildBankMoneyLog> moneyLog) :
            ManagedPacket(MSG_GUILD_BANK_LOG_QUERY, 1),
            slotId(slotId),
            moneyLog(moneyLog)
        {
        }

Packet classes - override virtual functions

We can use the following functions:

Most packets will either read (deserialise) or write (serialise) packet content. To decide what to do with a packet, you could have a look to the packet enum name in enum Opcodes. We have three different packet types:

example code from MSG_GUILD_BANK_LOG_QUERY:

        size_t expectedSize() const override
        {
            return slotId != 6 ? 21 : 17 * moneyLog.size() + 2;
        }

        bool internalSerialise(WorldPacket& packet) override
        {
            packet << slotId;
            packet << uint8_t(moneyLog.size() > 25 ? 25 : moneyLog.size());

            for (const auto& log : moneyLog)
            {
                packet << log.action << log.memberGuid << log.entry;

                if (slotId < 6)
                    packet << log.stackCount;

                const uint32_t currentTime = static_cast<uint32_t>(UNIXTIME);
                packet << uint32_t(currentTime - log.timestamp);
            }
            return true;
        }

        bool internalDeserialise(WorldPacket& packet) override
        {
            packet >> slotId;
            return true;
        }