Files
DarkflameServer/dNet/PacketDispatcher.h
Aaron Kimbrell 796fd1b941 refactor: chat packets as structs
Every packet of the chat service is now an LUBitStream struct in dNet/ChatPackets.h
(docs/PacketArchitecture.md, PR 13), and nothing in the chat server or the chat side of
the world server reads or writes a packet by hand any more.

- World <-> chat: LoginSessionNotify, UnexpectedDisconnect, GMLevelUpdate, GMMute,
  Announcement (GM announce), CreateTeam, TeamUpdate (TEAM_GET_STATUS to worlds),
  AchievementNotify, ShowAllRequest, FindPlayerRequest.
- Client -> world -> chat (friends, ignore list, teams, general and private chat):
  GetFriendsList, AddFriendRequest, AddFriendResponse, RemoveFriend, GetIgnoreList,
  AddIgnore, RemoveIgnore, GeneralChatMessage, PrivateChatMessage, TeamInvite,
  TeamInviteResponse, TeamLeave, TeamKick, TeamSetLeader, TeamSetLoot, TeamGetStatus.
  The 77 bytes the handlers skipped are named fields now (the sender block the client
  fills in); the 4 unused bytes after the player ID are kept as `unknown`.
- What the client receives. Chat service (ChatPackets::Client): GeneralChatMessage
  (replaces SendChatMessage; SendSystemMessage stays as a helper built on it),
  PrivateChatMessage. Client service (ClientPackets, as structs go in the file of the
  ServiceType in their header): SendCannedText (replaces SendMessageFail), GetFriendsListResponse, AddFriendRequest,
  AddFriendResponse, RemoveFriendResponse, UpdateFriendNotify, WhoResponse,
  ShowAllResponse, Get/Add/RemoveIgnoreResponse, TeamInvite, TeamInviteInitialResponse,
  and the team game messages chat writes (TeamInviteConfirm, TeamGetStatusResponse,
  TeamSetLeader, TeamAddPlayer, TeamRemovePlayer, TeamSetOffWorldFlag) as TeamGameMsg
  structs, since chat doesn't link dGame's NetGameMsg.
- WORLD_ROUTE_PACKET is ChatPackets::WorldRoutePacket (its own file, dNet/WorldRoutePacket.h,
  since it carries packets of other services): the target and the inner packet.
  ChatPacketHandler::SendRouted replaces the per-function route headers and
  SendRoutedMsg.

Dispatch: dNet/PacketDispatcher.h is a dispatch map from packet ID to (struct, handler
function); packets that fail to Deserialize are logged and dropped. The chat server's
switch and the world's HandlePacketChat switch are now maps. The handlers take the
structs; their logic is unchanged. World code sends to chat with ChatServerLink::Send
(dGame) instead of writing to Game::chatServer by hand. eChatChannel,
eChatMessageResponseCode and eAddIgnoreResponse moved to dCommon/dEnums so the structs
can use them.

Verified: tests/dGameTests/dNetTests/Legacy/ChatPacketsLegacy.h is a verbatim copy of
the old senders and readers; ChatPacketsTests (25 tests) sends a grid of inputs through
both and requires identical bits, bytes and destination, checks the old readers read
what the structs write, round trips every struct, checks truncated packets are refused,
and has hand written golden packets for general chat, canned text, GM mute and a routed
team game message. Breaking one field width (UpdateFriendNotify) and the TeamAddPlayer
zone flag made the tests fail. No wire bytes changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:50 -05:00

78 lines
2.9 KiB
C++

#ifndef PACKETDISPATCHER_H
#define PACKETDISPATCHER_H
#include <functional>
#include <map>
#include <memory>
#include <type_traits>
#include "BitStream.h"
#include "BitStreamUtils.h"
#include "Logger.h"
#include "RakNetTypes.h"
#include "StringifiedEnum.h"
/**
* One dispatch map per service a server receives (see docs/PacketArchitecture.md, section 2.2).
*
* Each entry names the struct a packet ID is read into and the function that handles it, so the same struct can be
* handled differently by each server that receives it (WORLD_READY means one thing to master and another to the
* dashboard). Dispatch reads the packet into a fresh struct; a packet that fails to Deserialize is logged and
* dropped before its handler runs.
*
* PacketDispatcher<MessageType::Master> handlers;
* handlers.On<MasterPackets::PlayerAdded>(MessageType::Master::PLAYER_ADDED, &OnPlayerAdded);
* ...
* handlers.Dispatch(packet); // reads the header and routes by packet ID
*/
template<typename IdT>
class PacketDispatcher {
public:
template<typename Msg>
using Handler = std::function<void(Msg& msg, const SystemAddress& sysAddr)>;
// Registers the struct packets with this ID are read into, and what to do with them
template<typename Msg>
void On(const IdT id, Handler<Msg> handler) {
static_assert(std::is_base_of_v<LUBitStream, Msg>, "packets are LUBitStream structs");
m_Handlers[id] = [handler = std::move(handler)](RakNet::BitStream& bitStream, const SystemAddress& sysAddr) {
Msg msg;
if (!msg.Deserialize(bitStream)) return false;
handler(msg, sysAddr);
return true;
};
}
bool Has(const IdT id) const { return m_Handlers.contains(id); }
/**
* Routes a payload (the stream positioned right after the packet header) to its handler.
* Returns false if the ID has no handler. Packets that fail to Deserialize are logged and dropped.
*/
bool Dispatch(const IdT id, RakNet::BitStream& bitStream, const SystemAddress& sysAddr) const {
const auto it = m_Handlers.find(id);
if (it == m_Handlers.end()) return false;
if (!it->second(bitStream, sysAddr)) {
LOG("Dropped a %s packet (%u) from %s that failed to read", StringifiedEnum::ToString(id).data(), static_cast<uint32_t>(id), sysAddr.ToString());
}
return true;
}
/**
* Reads the packet's header and routes the rest to the handler for its ID. Returns false if the packet is not an
* LU packet of this dispatcher's service or has no handler.
*/
bool Dispatch(const Packet* packet, const ServiceType service) const {
if (!packet || packet->length < 1) return false;
RakNet::BitStream bitStream(packet->data, packet->length, false);
LUBitStream header;
if (!header.ReadHeader(bitStream) || header.connectionType != service) return false;
return Dispatch(static_cast<IdT>(header.internalPacketID), bitStream, packet->systemAddress);
}
private:
std::map<IdT, std::function<bool(RakNet::BitStream&, const SystemAddress&)>> m_Handlers;
};
#endif // PACKETDISPATCHER_H