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>
WorldPackets now holds what a client sends a world server, one struct per
packet with Serialize/Deserialize: Validation, CharacterListRequest,
CharacterCreateRequest, CharacterLoginRequest, GameMessage,
CharacterDeleteRequest, CharacterRenameRequest, LevelLoadComplete,
PositionUpdate, MailPacket, RoutePacket, StringCheck, GeneralChatMessage,
HandleFunness, UIHelpTop5. WorldServer's switch became a dispatch map of
handlers (each overrides Handle in WorldServer.cpp, logic unchanged:
dashboard hooks, LoadPlayer, chat logging, migration checks, message
inspector capture all still run); a packet that does not deserialize is
logged and dropped. UserManager's create/delete/rename take the structs.
The answers are ClientPackets (the CLIENT service): LoadStaticZone,
CharacterListResponse, CharacterCreateResponse, CharacterRenameResponse,
DeleteCharacterResponse, TransferToWorld, ServerStates, CreateCharacter,
ChatModerationString, MakeGMResponse, HTTPMonitorInfoResponse and
DebugOutput, built at the call sites (world server, UserManager, slash
commands, dashboard actions, components, migration). The world -> chat
forward of a routed packet is ChatPackets::RoutedFromClient. All
WorldPackets::Send* functions, HTTPMonitorInfo and the ClientPackets parse
functions are gone. The architecture doc now states the file rule: a
packet lives in the file of the ServiceType in its header.
No wire change. Verified against frozen verbatim copies of the old code
(tests/dGameTests/dNetTests/Legacy/WorldPacketsLegacy.h): every response
is sent through the old function and the struct over grids of inputs
(all enum values, strings of every length class, IDs, >64 moderation
segments, big XML) and must match byte for byte; every request is read
by the old code and the struct and must give the same values; plus
golden bytes, round trips, truncation checks and a field width mutation
that made the tests fail. Only differences: malformed requests are
dropped instead of handled with partial values, and LevelLoadComplete now
reads the zone ID the client sends after it (lu_packets and captures show
the 1.10.64 client always sends it; DLU ignored it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First step of moving hand written bitstream code to struct based
messages (see docs/PacketArchitecture.md). No wire changes.
- GameMsg is now only a server-internal event (delivered to handlers
registered with RegisterMsg); NetGameMsg is a wire message with
Send(sysAddr) (UNASSIGNED broadcasts), SendToClient(sysAddr) (one
client, never broadcasts), WritePacket, Serialize, Deserialize and
Handle. Mixing them up is now a compile error. NetGameMsgEvent<T> /
DeliverLocally carry a wire message to local handlers; loot drops,
pickup, the object debugger, GM invisibility and model RequestUse
use them. The GM invisibility message keeps its target, so its bytes
are unchanged.
- Behaviour change: GameMessageHandler logs and drops messages whose
Deserialize fails instead of handling them with default fields (the
4 messages already registered: RequestUse, RequestServerObjectInfo,
ShootingGalleryFire, PickupItem).
- LUBitStream (kept as on main) gets a virtual destructor (Mail deleted
derived packets through the base) and WritePacket, also used by
ChatPackets::SendRoutedMsg with identical bytes.
- BitStreamUtils::WriteOptional/ReadOptional for default-flag fields
and WriteLengthPrefixed/ReadLengthPrefixed for length prefixed
strings.
- Every message ID enumerator (MessageType::*, ServiceType, Mail's
wire enums) is pinned with static_asserts, so renumbering or removing
one fails to compile.
- Tests: dServerMock copies each sent packet (it kept a pointer to the
caller's destroyed BitStream); PacketTestUtils.h compares packets bit
for bit; helper tests use hand computed golden bytes and equality
with the hand written patterns they replace; compile-time checks
keep the wire/internal split in place.
- docs/PacketArchitecture.md: survey, target architecture,
conventions, verification method and migration plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>