Files
DarkflameServer/dNet/ClientPackets.cpp
Aaron Kimbrell 70f1c2b19e refactor: common and auth packets as structs
Adds CommonPackets (ServiceType::COMMON): ClientVersionConfirm (the
client's handshake, with the handler that logs it and answers),
ServerVersionConfirm, DisconnectNotify and GeneralNotify (layout from the
1.10.64 client's PacketHandler_MSG_SERVER_GENERAL_NOTIFY; DLU does not send
it yet). AuthPackets::LoginRequest reads the login and keeps the old login
logic in its Handle; ClientPackets::LoginResponse (with the Stamp list)
replaces the hand written response, and AuthPackets::SendLoginResponse
fills it from the settings as before. dServer::Disconnect writes a
DisconnectNotify. AuthServer's if-chain and WorldServer's COMMON case
become CommonPackets::Handle / AuthPackets::Handle, dispatch maps that
read the struct, drop and log it if it does not deserialize, then Handle.
HandleHandshake and SendHandshake are gone.

No wire change. Verified against a frozen verbatim copy of the old
functions (tests/dGameTests/dNetTests/Legacy): the old handshake and login
handlers and the new dispatchers are fed the same packets and must send
identical bytes to the same address (same RNG seed for the session key),
over grids of versions, service types, ports, response codes, error
messages, IPs and stamp lists; plus hand computed golden bytes, round
trips, truncation checks, and a deliberate field width mutation that made
the tests fail. The only behaviour difference: truncated handshake and
login packets are now dropped instead of handled with whatever was read.

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

205 lines
6.6 KiB
C++

/*
* Darkflame Universe
* Copyright 2018
*/
#include "ClientPackets.h"
#include "dCommonVars.h"
#include "PositionUpdate.h"
#include "eLoginResponse.h"
static_assert(sizeof(Stamp) == 16, "the login response's stamp size field has always been 16 bytes per stamp");
void Stamp::Serialize(RakNet::BitStream& outBitStream) const {
outBitStream.Write(type);
outBitStream.Write(value);
outBitStream.Write(timestamp);
}
bool Stamp::Deserialize(RakNet::BitStream& inBitStream) {
VALIDATE_READ(inBitStream.Read(type));
VALIDATE_READ(inBitStream.Read(value));
VALIDATE_READ(inBitStream.Read(timestamp));
return true;
}
namespace ClientPackets {
void LoginResponse::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(responseCode);
for (const auto& event : events) bitStream.Write(event);
bitStream.Write(versionMajor);
bitStream.Write(versionCurrent);
bitStream.Write(versionMinor);
bitStream.Write(userKey);
bitStream.Write(worldServerIP);
bitStream.Write(chatServerIP);
bitStream.Write(worldServerPort);
bitStream.Write(chatServerPort);
bitStream.Write(cdnKey);
bitStream.Write(cdnTicket);
bitStream.Write(language);
bitStream.Write(localization);
bitStream.Write<uint8_t>(justUpgradedFromF2P);
bitStream.Write<uint8_t>(isFreeToPlay);
bitStream.Write(freeToPlayTimeRemaining);
bitStream.Write<uint16_t>(errorMessage.length());
bitStream.Write(LUWString(errorMessage, static_cast<uint32_t>(errorMessage.length())));
bitStream.Write<uint32_t>((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t));
for (const auto& stamp : stamps) stamp.Serialize(bitStream);
}
bool LoginResponse::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(responseCode));
for (auto& event : events) VALIDATE_READ(bitStream.Read(event));
VALIDATE_READ(bitStream.Read(versionMajor));
VALIDATE_READ(bitStream.Read(versionCurrent));
VALIDATE_READ(bitStream.Read(versionMinor));
VALIDATE_READ(bitStream.Read(userKey));
VALIDATE_READ(bitStream.Read(worldServerIP));
VALIDATE_READ(bitStream.Read(chatServerIP));
VALIDATE_READ(bitStream.Read(worldServerPort));
VALIDATE_READ(bitStream.Read(chatServerPort));
VALIDATE_READ(bitStream.Read(cdnKey));
VALIDATE_READ(bitStream.Read(cdnTicket));
VALIDATE_READ(bitStream.Read(language));
VALIDATE_READ(bitStream.Read(localization));
uint8_t flag{};
VALIDATE_READ(bitStream.Read(flag));
justUpgradedFromF2P = flag != 0;
VALIDATE_READ(bitStream.Read(flag));
isFreeToPlay = flag != 0;
VALIDATE_READ(bitStream.Read(freeToPlayTimeRemaining));
uint16_t errorLength{};
VALIDATE_READ(bitStream.Read(errorLength));
LUWString error(errorLength);
if (errorLength > 0) VALIDATE_READ(bitStream.Read(error)); // RakNet fails reads of 0 bits
errorMessage = error.GetAsString();
uint32_t stampsSize{};
VALIDATE_READ(bitStream.Read(stampsSize));
if (stampsSize < sizeof(uint32_t) || (stampsSize - sizeof(uint32_t)) % sizeof(Stamp) != 0) return false;
const uint32_t stampCount = (stampsSize - sizeof(uint32_t)) / sizeof(Stamp);
if (stampCount > BITS_TO_BYTES(bitStream.GetNumberOfUnreadBits()) / sizeof(Stamp)) return false;
stamps.resize(stampCount);
for (auto& stamp : stamps) VALIDATE_READ(stamp.Deserialize(bitStream));
return true;
}
}
ChatMessage ClientPackets::HandleChatMessage(Packet* packet) {
CINSTREAM_SKIP_HEADER;
ChatMessage message;
int32_t messageLength{};
inStream.Read(message.chatChannel);
inStream.Read(message.unknown);
inStream.Read(messageLength);
if (messageLength > MAX_MESSAGE_LENGTH || messageLength < 0) return message;
for (int32_t i = 0; i < (messageLength - 1); ++i) {
char16_t character;
inStream.Read(character);
message.message.push_back(character);
}
return message;
}
PositionUpdate ClientPackets::HandleClientPositionUpdate(Packet* packet) {
PositionUpdate update;
CINSTREAM_SKIP_HEADER;
inStream.Read(update.position.x);
inStream.Read(update.position.y);
inStream.Read(update.position.z);
inStream.Read(update.rotation.x);
inStream.Read(update.rotation.y);
inStream.Read(update.rotation.z);
inStream.Read(update.rotation.w);
inStream.Read(update.onGround);
inStream.Read(update.onRail);
bool velocityFlag = false;
inStream.Read(velocityFlag);
if (velocityFlag) {
inStream.Read(update.velocity.x);
inStream.Read(update.velocity.y);
inStream.Read(update.velocity.z);
}
bool angVelocityFlag = false;
inStream.Read(angVelocityFlag);
if (angVelocityFlag) {
inStream.Read(update.angularVelocity.x);
inStream.Read(update.angularVelocity.y);
inStream.Read(update.angularVelocity.z);
}
// TODO figure out how to use these. Ignoring for now, but reading in if they exist.
bool hasLocalSpaceInfo{};
if (inStream.Read(hasLocalSpaceInfo) && hasLocalSpaceInfo) {
inStream.Read(update.localSpaceInfo.objectId);
inStream.Read(update.localSpaceInfo.position.x);
inStream.Read(update.localSpaceInfo.position.y);
inStream.Read(update.localSpaceInfo.position.z);
bool hasLinearVelocity = false;
if (inStream.Read(hasLinearVelocity) && hasLinearVelocity) {
inStream.Read(update.localSpaceInfo.linearVelocity.x);
inStream.Read(update.localSpaceInfo.linearVelocity.y);
inStream.Read(update.localSpaceInfo.linearVelocity.z);
}
}
bool hasRemoteInputInfo{};
if (inStream.Read(hasRemoteInputInfo) && hasRemoteInputInfo) {
inStream.Read(update.remoteInputInfo.m_RemoteInputX);
inStream.Read(update.remoteInputInfo.m_RemoteInputY);
inStream.Read(update.remoteInputInfo.m_IsPowersliding);
inStream.Read(update.remoteInputInfo.m_IsModified);
}
return update;
}
ChatModerationRequest ClientPackets::HandleChatModerationRequest(Packet* packet) {
CINSTREAM_SKIP_HEADER;
ChatModerationRequest request;
inStream.Read(request.chatLevel);
inStream.Read(request.requestID);
for (uint32_t i = 0; i < 42; ++i) {
uint16_t character;
inStream.Read(character);
request.receiver.push_back(static_cast<uint8_t>(character));
}
if (!request.receiver.empty()) {
if (std::string(request.receiver.c_str(), 4) == "[GM]") { // Shift the string forward if we are speaking to a GM as the client appends "[GM]" if they are
request.receiver = std::string(request.receiver.c_str() + 4, request.receiver.size() - 4);
}
}
uint16_t messageLength;
inStream.Read(messageLength);
if (messageLength > MAX_MESSAGE_LENGTH) return request;
for (uint32_t i = 0; i < messageLength; ++i) {
uint16_t character;
inStream.Read(character);
request.message.push_back(static_cast<uint8_t>(character));
}
return request;
}
int32_t ClientPackets::SendTop5HelpIssues(Packet* packet) {
CINSTREAM_SKIP_HEADER;
int32_t language = 0;
inStream.Read(language);
return language;
}