mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
refactor: world packets as structs
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>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
SET(DNET_TESTS
|
||||
"CommonAuthPacketsTests.cpp")
|
||||
"CommonAuthPacketsTests.cpp"
|
||||
"WorldPacketsTests.cpp")
|
||||
|
||||
# Get the folder name and prepend it to the files above
|
||||
get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||
|
||||
543
tests/dGameTests/dNetTests/Legacy/WorldPacketsLegacy.h
Normal file
543
tests/dGameTests/dNetTests/Legacy/WorldPacketsLegacy.h
Normal file
@@ -0,0 +1,543 @@
|
||||
#ifndef WORLDPACKETSLEGACY_H
|
||||
#define WORLDPACKETSLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written code that WorldPackets / the ClientPackets world responses replaced, from
|
||||
// before the conversion: every WorldPackets::Send* function and HTTPMonitorInfo (dNet/WorldPackets.cpp), the four
|
||||
// ClientPackets parse functions (dNet/ClientPackets.cpp), and, below them, the parts of UserManager.cpp and
|
||||
// WorldServer.cpp's HandlePacket that read or wrote packets inline (each copied verbatim into a function that
|
||||
// returns what was read, or the packet that was built, instead of acting on it).
|
||||
// Only the namespace changed. The byte-equality tests run the same inputs through these and the new structs.
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "eCharacterCreationResponse.h"
|
||||
#include "eFunnessTypes.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eRenameResponse.h"
|
||||
#include "Game.h"
|
||||
#include "LDFFormat.h"
|
||||
#include "Logger.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "PositionUpdate.h"
|
||||
#include "ServiceType.h"
|
||||
#include "ZCompression.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LegacyWorldPackets {
|
||||
struct HTTPMonitorInfo {
|
||||
uint16_t port = 80;
|
||||
bool openWeb = false;
|
||||
bool supportsSum = false;
|
||||
bool supportsDetail = false;
|
||||
bool supportsWho = false;
|
||||
bool supportsObjects = false;
|
||||
void Serialize(RakNet::BitStream &bitstream) const;
|
||||
};
|
||||
|
||||
inline void HTTPMonitorInfo::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(port);
|
||||
bitStream.Write<uint8_t>(openWeb);
|
||||
bitStream.Write<uint8_t>(supportsSum);
|
||||
bitStream.Write<uint8_t>(supportsDetail);
|
||||
bitStream.Write<uint8_t>(supportsWho);
|
||||
bitStream.Write<uint8_t>(supportsObjects);
|
||||
}
|
||||
|
||||
inline void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::LOAD_STATIC_ZONE);
|
||||
|
||||
bitStream.Write<uint16_t>(zone.GetMapID());
|
||||
bitStream.Write<uint16_t>(zone.GetInstanceID());
|
||||
//bitStream.Write<uint32_t>(zone.GetCloneID());
|
||||
bitStream.Write(0);
|
||||
|
||||
bitStream.Write(checksum);
|
||||
bitStream.Write<uint16_t>(0); // ??
|
||||
|
||||
bitStream.Write(x);
|
||||
bitStream.Write(y);
|
||||
bitStream.Write(z);
|
||||
|
||||
bitStream.Write<uint32_t>(0); // Change this to eventually use 4 on activity worlds
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendCharacterCreationResponse(const SystemAddress& sysAddr, eCharacterCreationResponse response) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CHARACTER_CREATE_RESPONSE);
|
||||
bitStream.Write(response);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendCharacterRenameResponse(const SystemAddress& sysAddr, eRenameResponse response) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CHARACTER_RENAME_RESPONSE);
|
||||
bitStream.Write(response);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendCharacterDeleteResponse(const SystemAddress& sysAddr, bool response) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::DELETE_CHARACTER_RESPONSE);
|
||||
bitStream.Write<uint8_t>(response);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTransferToWorld(const SystemAddress& sysAddr, const std::string& serverIP, uint32_t serverPort, bool mythranShift) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::TRANSFER_TO_WORLD);
|
||||
|
||||
bitStream.Write(LUString(serverIP));
|
||||
bitStream.Write<uint16_t>(serverPort);
|
||||
bitStream.Write<uint8_t>(mythranShift);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerState(const SystemAddress& sysAddr) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::SERVER_STATES);
|
||||
bitStream.Write<uint8_t>(1); //If the server is receiving this request, it probably is ready anyway.
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm, const LWOCLONEID cloneID) {
|
||||
using namespace std;
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CREATE_CHARACTER);
|
||||
|
||||
RakNet::BitStream data;
|
||||
|
||||
LwoNameValue ldfData;
|
||||
ldfData.Insert<LWOOBJID>(u"objid", player);
|
||||
ldfData.Insert<LOT>(u"template", 1);
|
||||
ldfData.Insert<string>(u"xmlData", xmlData);
|
||||
ldfData.Insert<u16string>(u"name", username);
|
||||
ldfData.Insert<int32_t>(u"gmlevel", static_cast<int32_t>(gm));
|
||||
ldfData.Insert<int32_t>(u"chatmode", static_cast<int32_t>(gm));
|
||||
ldfData.Insert<int64_t>(u"reputation", reputation);
|
||||
ldfData.Insert<int32_t>(u"propertycloneid", cloneID);
|
||||
|
||||
data.Write<uint32_t>(ldfData.values.size());
|
||||
for (const auto& toSerialize : ldfData | std::views::values) toSerialize->WriteToPacket(data);
|
||||
|
||||
//Compress the data before sending:
|
||||
const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed());
|
||||
auto compressedData = std::make_unique<uint8_t[]>(reservedSize);
|
||||
|
||||
size_t size = ZCompression::Compress(data.GetData(), data.GetNumberOfBytesUsed(), compressedData.get(), reservedSize);
|
||||
|
||||
assert(size <= reservedSize);
|
||||
|
||||
bitStream.Write<uint32_t>(size + 9); //size of data + header bytes (8)
|
||||
bitStream.Write<uint8_t>(1); //compressed boolean, true
|
||||
bitStream.Write<uint32_t>(data.GetNumberOfBytesUsed());
|
||||
bitStream.Write<uint32_t>(size);
|
||||
|
||||
/**
|
||||
* In practice, this warning serves no purpose for us. We allocate the max memory needed on the heap
|
||||
* and then compress the data. In the off chance that the compression actually increases the size,
|
||||
* an assertion is done to prevent bad data from being saved or sent.
|
||||
*/
|
||||
#pragma warning(disable:6385) // C6385 Reading invalid data from 'compressedData'.
|
||||
bitStream.WriteAlignedBytes(compressedData.get(), size);
|
||||
#pragma warning(default:6385)
|
||||
|
||||
SEND_PACKET;
|
||||
LOG("Sent CreateCharacter for ID: %llu", player);
|
||||
}
|
||||
|
||||
inline void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::set<std::pair<uint8_t, uint8_t>> unacceptedItems) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CHAT_MODERATION_STRING);
|
||||
|
||||
bitStream.Write<uint8_t>(unacceptedItems.empty()); // Is sentence ok?
|
||||
bitStream.Write<uint16_t>(0x16); // Source ID, unknown
|
||||
|
||||
bitStream.Write<uint8_t>(requestID); // request ID
|
||||
bitStream.Write<char>(0); // chat mode
|
||||
|
||||
bitStream.Write(LUWString(receiver, 42)); // receiver name
|
||||
|
||||
for (auto it : unacceptedItems) {
|
||||
bitStream.Write<uint8_t>(it.first); // start index
|
||||
bitStream.Write<uint8_t>(it.second); // length
|
||||
}
|
||||
|
||||
for (int i = unacceptedItems.size(); 64 > i; i++) {
|
||||
bitStream.Write<uint16_t>(0);
|
||||
}
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::MAKE_GM_RESPONSE);
|
||||
|
||||
bitStream.Write<uint8_t>(success);
|
||||
bitStream.Write(static_cast<uint16_t>(highestLevel));
|
||||
bitStream.Write(static_cast<uint16_t>(prevLevel));
|
||||
bitStream.Write(static_cast<uint16_t>(newLevel));
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::HTTP_MONITOR_INFO_RESPONSE);
|
||||
info.Serialize(bitStream);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendDebugOuput(const SystemAddress& sysAddr, const std::string& data) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::DEBUG_OUTPUT);
|
||||
bitStream.Write<uint32_t>(data.size());
|
||||
bitStream.Write(data);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
struct ChatMessage {
|
||||
uint8_t chatChannel = 0;
|
||||
uint16_t unknown = 0;
|
||||
std::u16string message;
|
||||
};
|
||||
|
||||
struct ChatModerationRequest {
|
||||
uint8_t chatLevel = 0;
|
||||
uint8_t requestID = 0;
|
||||
std::string receiver;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
inline ChatMessage 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;
|
||||
}
|
||||
|
||||
inline PositionUpdate 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;
|
||||
}
|
||||
|
||||
inline ChatModerationRequest 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;
|
||||
}
|
||||
|
||||
inline int32_t SendTop5HelpIssues(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
int32_t language = 0;
|
||||
inStream.Read(language);
|
||||
return language;
|
||||
}
|
||||
|
||||
// ---- UserManager::RequestCharacterList, the part that wrote the packet (characters[i] is any type with
|
||||
// Character's getters) ----
|
||||
template<typename CharacterT>
|
||||
inline void SendCharacterList(const SystemAddress& sysAddr, std::vector<CharacterT*> characters) {
|
||||
RakNet::BitStream bitStream;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CHARACTER_LIST_RESPONSE);
|
||||
|
||||
bitStream.Write<uint8_t>(characters.size());
|
||||
bitStream.Write<uint8_t>(0); //TODO: Pick the most recent played index. character index in front, just picking 0
|
||||
|
||||
for (uint32_t i = 0; i < characters.size(); ++i) {
|
||||
bitStream.Write(characters[i]->GetObjectID());
|
||||
bitStream.Write<uint32_t>(0);
|
||||
|
||||
bitStream.Write(LUWString(characters[i]->GetName()));
|
||||
bitStream.Write(LUWString(characters[i]->GetUnapprovedName()));
|
||||
|
||||
bitStream.Write<uint8_t>(characters[i]->GetNameRejected());
|
||||
bitStream.Write<uint8_t>(false);
|
||||
|
||||
bitStream.Write(LUString("", 10));
|
||||
|
||||
bitStream.Write(characters[i]->GetShirtColor());
|
||||
bitStream.Write(characters[i]->GetShirtStyle());
|
||||
bitStream.Write(characters[i]->GetPantsColor());
|
||||
bitStream.Write(characters[i]->GetHairStyle());
|
||||
bitStream.Write(characters[i]->GetHairColor());
|
||||
bitStream.Write(characters[i]->GetLeftHand());
|
||||
bitStream.Write(characters[i]->GetRightHand());
|
||||
bitStream.Write(characters[i]->GetEyebrows());
|
||||
bitStream.Write(characters[i]->GetEyes());
|
||||
bitStream.Write(characters[i]->GetMouth());
|
||||
bitStream.Write<uint32_t>(0);
|
||||
|
||||
bitStream.Write<uint16_t>(characters[i]->GetZoneID());
|
||||
bitStream.Write<uint16_t>(characters[i]->GetZoneInstance());
|
||||
bitStream.Write(characters[i]->GetZoneClone());
|
||||
|
||||
bitStream.Write(characters[i]->GetLastLogin());
|
||||
|
||||
const auto& equippedItems = characters[i]->GetEquippedItems();
|
||||
bitStream.Write<uint16_t>(equippedItems.size());
|
||||
|
||||
for (uint32_t j = 0; j < equippedItems.size(); ++j) {
|
||||
bitStream.Write(equippedItems[j]);
|
||||
}
|
||||
}
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// ---- UserManager::CreateCharacter, the reads ----
|
||||
struct CreateCharacterRead {
|
||||
LUWString LUWStringName;
|
||||
uint32_t firstNameIndex;
|
||||
uint32_t middleNameIndex;
|
||||
uint32_t lastNameIndex;
|
||||
uint32_t shirtColor;
|
||||
uint32_t shirtStyle;
|
||||
uint32_t pantsColor;
|
||||
uint32_t hairStyle;
|
||||
uint32_t hairColor;
|
||||
uint32_t lh;
|
||||
uint32_t rh;
|
||||
uint32_t eyebrows;
|
||||
uint32_t eyes;
|
||||
uint32_t mouth;
|
||||
};
|
||||
|
||||
inline CreateCharacterRead ReadCreateCharacter(Packet* packet) {
|
||||
CreateCharacterRead r{};
|
||||
auto& [LUWStringName, firstNameIndex, middleNameIndex, lastNameIndex, shirtColor, shirtStyle, pantsColor, hairStyle, hairColor, lh, rh, eyebrows, eyes, mouth] = r;
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
inStream.Read(LUWStringName);
|
||||
inStream.Read(firstNameIndex);
|
||||
inStream.Read(middleNameIndex);
|
||||
inStream.Read(lastNameIndex);
|
||||
inStream.IgnoreBytes(9);
|
||||
inStream.Read(shirtColor);
|
||||
inStream.Read(shirtStyle);
|
||||
inStream.Read(pantsColor);
|
||||
inStream.Read(hairStyle);
|
||||
inStream.Read(hairColor);
|
||||
inStream.Read(lh);
|
||||
inStream.Read(rh);
|
||||
inStream.Read(eyebrows);
|
||||
inStream.Read(eyes);
|
||||
inStream.Read(mouth);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---- UserManager::DeleteCharacter, the read ----
|
||||
inline LWOOBJID ReadDeleteCharacter(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID objectID;
|
||||
inStream.Read(objectID);
|
||||
return objectID;
|
||||
}
|
||||
|
||||
// ---- UserManager::RenameCharacter, the reads ----
|
||||
inline std::pair<LWOOBJID, std::string> ReadRenameCharacter(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID objectID;
|
||||
inStream.Read(objectID);
|
||||
|
||||
LUWString LUWStringName;
|
||||
inStream.Read(LUWStringName);
|
||||
auto newName = LUWStringName.GetAsString();
|
||||
return { objectID, newName };
|
||||
}
|
||||
|
||||
// ---- WorldServer HandlePacket VALIDATION, the reads ----
|
||||
struct ValidationRead {
|
||||
std::string username;
|
||||
std::string sessionKey;
|
||||
std::string checksum;
|
||||
};
|
||||
|
||||
inline ValidationRead ReadValidation(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LUWString username;
|
||||
inStream.Read(username);
|
||||
|
||||
LUWString sessionKey;
|
||||
// sometimes client puts a null terminator at the end of the checksum and sometimes doesn't, weird
|
||||
inStream.Read(sessionKey);
|
||||
LUString clientDatabaseChecksum(32);
|
||||
inStream.Read(clientDatabaseChecksum);
|
||||
return { username.GetAsString(), sessionKey.GetAsString(), clientDatabaseChecksum.string };
|
||||
}
|
||||
|
||||
// ---- WorldServer HandlePacket LOGIN_REQUEST, the read ----
|
||||
inline LWOOBJID ReadLoginRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = 0;
|
||||
inStream.Read(playerID);
|
||||
return playerID;
|
||||
}
|
||||
|
||||
// ---- WorldServer HandlePacket GAME_MSG, the reads ----
|
||||
struct GameMessageRead {
|
||||
LWOOBJID objectID;
|
||||
MessageType::Game messageID;
|
||||
std::vector<uint8_t> data;
|
||||
uint32_t dataBits;
|
||||
};
|
||||
|
||||
inline GameMessageRead ReadGameMessage(Packet* packet) {
|
||||
RakNet::BitStream bitStream(packet->data, packet->length, false);
|
||||
|
||||
uint64_t header;
|
||||
LWOOBJID objectID;
|
||||
MessageType::Game messageID;
|
||||
|
||||
bitStream.Read(header);
|
||||
bitStream.Read(objectID);
|
||||
bitStream.Read(messageID);
|
||||
|
||||
RakNet::BitStream dataStream;
|
||||
bitStream.Read(dataStream, bitStream.GetNumberOfUnreadBits());
|
||||
return { objectID, messageID, { dataStream.GetData(), dataStream.GetData() + dataStream.GetNumberOfBytesUsed() }, static_cast<uint32_t>(dataStream.GetNumberOfBitsUsed()) };
|
||||
}
|
||||
|
||||
// ---- WorldServer HandlePacket ROUTE_PACKET: the packet it built for the chat server (it then sent it with
|
||||
// Game::chatServer->Send); objectID is what it looked up for the sender. Returns false where it returned. ----
|
||||
inline bool BuildRoutePacket(Packet* packet, LWOOBJID objectID, RakNet::BitStream& bitStream) {
|
||||
//Yeet to chat
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
if (size > 20000) {
|
||||
LOG("Tried to route a packet with a read size > 20000, so likely a false packet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, packet->data[14]);
|
||||
|
||||
bitStream.Write(objectID);
|
||||
|
||||
//Now write the rest of the data:
|
||||
auto data = inStream.GetData();
|
||||
for (uint32_t i = 23; i - 23 < size && i < packet->length; ++i) {
|
||||
bitStream.Write(data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- WorldServer HandlePacket HANDLE_FUNNESS, the reads ----
|
||||
inline CaughtFunness ReadFunness(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
CaughtFunness funness;
|
||||
inStream.Read(funness.cheatInfo);
|
||||
inStream.Read(funness.cheatType);
|
||||
return funness;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // WORLDPACKETSLEGACY_H
|
||||
719
tests/dGameTests/dNetTests/WorldPacketsTests.cpp
Normal file
719
tests/dGameTests/dNetTests/WorldPacketsTests.cpp
Normal file
@@ -0,0 +1,719 @@
|
||||
#include "ChatPackets.h"
|
||||
#include "ClientPackets.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "PacketTestUtils.h"
|
||||
#include "WorldPackets.h"
|
||||
#include "Legacy/WorldPacketsLegacy.h"
|
||||
|
||||
#include "eCharacterCreationResponse.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eRenameResponse.h"
|
||||
#include "magic_enum.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace PacketTestUtils;
|
||||
|
||||
namespace {
|
||||
SystemAddress TestAddress() {
|
||||
SystemAddress address;
|
||||
address.binaryAddress = 0x0100007F;
|
||||
address.port = 1234;
|
||||
return address;
|
||||
}
|
||||
|
||||
void ExpectSameSend(const std::function<void(const SystemAddress&)>& legacy, const LUBitStream& packet) {
|
||||
const auto sysAddr = TestAddress();
|
||||
const auto expected = Capture([&] { legacy(sysAddr); });
|
||||
const auto actual = Capture([&] { packet.Send(sysAddr); });
|
||||
ASSERT_EQ(expected.size(), 1);
|
||||
ASSERT_EQ(actual.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(expected[0]), FromCapture(actual[0]));
|
||||
EXPECT_EQ(expected[0].sysAddr, actual[0].sysAddr);
|
||||
EXPECT_EQ(expected[0].broadcast, actual[0].broadcast);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T RoundTrip(const T& packet) {
|
||||
RakNet::BitStream first;
|
||||
packet.WritePacket(first);
|
||||
T copy;
|
||||
EXPECT_TRUE(copy.ReadHeader(first));
|
||||
EXPECT_TRUE(copy.Deserialize(first));
|
||||
EXPECT_EQ(first.GetNumberOfUnreadBits(), 0);
|
||||
RakNet::BitStream second;
|
||||
copy.WritePacket(second);
|
||||
EXPECT_PACKET_EQ(FromBitStream(first), FromBitStream(second));
|
||||
return copy;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ExpectTruncatedFails(const T& packet, uint32_t keepAtLeast = 8) {
|
||||
RakNet::BitStream full;
|
||||
packet.WritePacket(full);
|
||||
const auto bytes = full.GetNumberOfBytesUsed();
|
||||
for (uint32_t cut = keepAtLeast; cut < bytes; cut++) {
|
||||
RakNet::BitStream truncated(full.GetData(), cut, true);
|
||||
T copy;
|
||||
ASSERT_TRUE(copy.ReadHeader(truncated));
|
||||
EXPECT_FALSE(copy.Deserialize(truncated)) << "cut at " << cut << " of " << bytes;
|
||||
}
|
||||
}
|
||||
|
||||
// A received packet as the servers get it, from bytes written by a struct.
|
||||
struct ReceivedPacket {
|
||||
RakNet::BitStream bytes;
|
||||
Packet packet{};
|
||||
|
||||
explicit ReceivedPacket(const LUBitStream& from) {
|
||||
from.WritePacket(bytes);
|
||||
packet.systemAddress = TestAddress();
|
||||
packet.data = bytes.GetData();
|
||||
packet.length = bytes.GetNumberOfBytesUsed();
|
||||
packet.bitSize = bytes.GetNumberOfBitsUsed();
|
||||
}
|
||||
};
|
||||
|
||||
// Reads a struct from a received packet the way the dispatchers do.
|
||||
template<typename T>
|
||||
bool Read(ReceivedPacket& received, T& out) {
|
||||
RakNet::BitStream inStream(received.packet.data, received.packet.length, false);
|
||||
if (!out.ReadHeader(inStream)) return false;
|
||||
return out.Deserialize(inStream);
|
||||
}
|
||||
|
||||
const std::vector<std::string> g_Strings = { "", "a", "Hello World", "127.0.0.1", std::string(32, 'x'), std::string(33, 'y'), std::string(300, 'z') };
|
||||
const std::vector<LWOOBJID> g_ObjectIDs = { 0, 1, 1152921508901814000, -1, std::numeric_limits<LWOOBJID>::max() };
|
||||
|
||||
struct FakeCharacter {
|
||||
LWOOBJID objectID{};
|
||||
std::string name;
|
||||
std::string unapprovedName;
|
||||
bool nameRejected{};
|
||||
uint32_t shirtColor{}, shirtStyle{}, pantsColor{}, hairStyle{}, hairColor{}, leftHand{}, rightHand{}, eyebrows{}, eyes{}, mouth{};
|
||||
uint32_t zoneID{}, zoneInstance{}, zoneClone{};
|
||||
uint64_t lastLogin{};
|
||||
std::vector<LOT> equippedItems;
|
||||
|
||||
LWOOBJID GetObjectID() const { return objectID; }
|
||||
const std::string& GetName() const { return name; }
|
||||
const std::string& GetUnapprovedName() const { return unapprovedName; }
|
||||
bool GetNameRejected() const { return nameRejected; }
|
||||
uint32_t GetShirtColor() const { return shirtColor; }
|
||||
uint32_t GetShirtStyle() const { return shirtStyle; }
|
||||
uint32_t GetPantsColor() const { return pantsColor; }
|
||||
uint32_t GetHairStyle() const { return hairStyle; }
|
||||
uint32_t GetHairColor() const { return hairColor; }
|
||||
uint32_t GetLeftHand() const { return leftHand; }
|
||||
uint32_t GetRightHand() const { return rightHand; }
|
||||
uint32_t GetEyebrows() const { return eyebrows; }
|
||||
uint32_t GetEyes() const { return eyes; }
|
||||
uint32_t GetMouth() const { return mouth; }
|
||||
uint32_t GetZoneID() const { return zoneID; }
|
||||
uint32_t GetZoneInstance() const { return zoneInstance; }
|
||||
uint32_t GetZoneClone() const { return zoneClone; }
|
||||
uint64_t GetLastLogin() const { return lastLogin; }
|
||||
const std::vector<LOT>& GetEquippedItems() const { return equippedItems; }
|
||||
};
|
||||
}
|
||||
|
||||
class WorldPacketsTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
// ---- Server -> client ----
|
||||
|
||||
TEST_F(WorldPacketsTests, LoadStaticZoneMatchesLegacy) {
|
||||
for (const auto& zone : { LWOZONEID(1000, 0, 0), LWOZONEID(1100, 7, 1234), LWOZONEID(0xFFFF, 0xFFFF, 0xFFFFFFFF) }) {
|
||||
for (const auto& [x, y, z] : { std::tuple{ 0.0f, 0.0f, 0.0f }, std::tuple{ -1.5f, 200.25f, 1e30f } }) {
|
||||
for (const uint32_t checksum : { 0u, 0x20b8087cu, 0xFFFFFFFFu }) {
|
||||
ClientPackets::LoadStaticZone packet;
|
||||
packet.mapID = zone.GetMapID();
|
||||
packet.instanceID = zone.GetInstanceID();
|
||||
packet.cloneID = 0; // what the world server sends
|
||||
packet.mapChecksum = checksum;
|
||||
packet.playerPosition = NiPoint3(x, y, z);
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendLoadStaticZone(a, x, y, z, checksum, zone); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).mapChecksum, checksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientPackets::LoadStaticZone packet;
|
||||
packet.mapID = 1100;
|
||||
packet.instanceID = 2;
|
||||
packet.mapChecksum = 0x11223344;
|
||||
packet.playerPosition = NiPoint3(1.0f, 2.0f, 3.0f);
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
// 0x53 | CLIENT (5) | LOAD_STATIC_ZONE (2) | pad | map | instance | clone | checksum | editor u8 u8 | x y z | instance type
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 02 00 00 00 00 4c 04 02 00 00 00 00 00 44 33 22 11 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00 00 00"), FromBitStream(bytes));
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, SmallResponsesMatchLegacy) {
|
||||
for (const auto response : magic_enum::enum_values<eCharacterCreationResponse>()) {
|
||||
ClientPackets::CharacterCreateResponse packet;
|
||||
packet.response = response;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCharacterCreationResponse(a, response); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).response, response);
|
||||
}
|
||||
for (const auto response : magic_enum::enum_values<eRenameResponse>()) {
|
||||
ClientPackets::CharacterRenameResponse packet;
|
||||
packet.response = response;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCharacterRenameResponse(a, response); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).response, response);
|
||||
}
|
||||
for (const bool success : { false, true }) {
|
||||
ClientPackets::DeleteCharacterResponse packet;
|
||||
packet.success = success;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCharacterDeleteResponse(a, success); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).success, success);
|
||||
}
|
||||
{
|
||||
ClientPackets::ServerStates packet;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendServerState(a); }, packet);
|
||||
RoundTrip(packet);
|
||||
}
|
||||
|
||||
ClientPackets::CharacterCreateResponse created;
|
||||
created.response = eCharacterCreationResponse::CUSTOM_NAME_IN_USE;
|
||||
RakNet::BitStream bytes;
|
||||
created.WritePacket(bytes);
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 07 00 00 00 00 04"), FromBitStream(bytes));
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, TransferToWorldMatchesLegacy) {
|
||||
for (const auto& ip : g_Strings) {
|
||||
for (const uint32_t port : { 0u, 2001u, 0xFFFFu, 0x12345u }) {
|
||||
for (const bool mythranShift : { false, true }) {
|
||||
ClientPackets::TransferToWorld packet;
|
||||
packet.serverIP = LUString(ip);
|
||||
packet.serverPort = port;
|
||||
packet.mythranShift = mythranShift;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendTransferToWorld(a, ip, port, mythranShift); }, packet);
|
||||
const auto copy = RoundTrip(packet);
|
||||
EXPECT_EQ(copy.serverPort, static_cast<uint16_t>(port));
|
||||
EXPECT_EQ(copy.mythranShift, mythranShift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientPackets::TransferToWorld packet;
|
||||
packet.serverIP = LUString("1.2");
|
||||
packet.serverPort = 0x0102;
|
||||
packet.mythranShift = true;
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
std::string zeros;
|
||||
for (int i = 0; i < 30; i++) zeros += " 00";
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 0e 00 00 00 00 31 2e 32" + zeros + " 02 01 01"), FromBitStream(bytes));
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, MakeGMResponseMatchesLegacy) {
|
||||
const auto levels = magic_enum::enum_values<eGameMasterLevel>();
|
||||
for (const bool success : { false, true }) {
|
||||
for (const auto highest : levels) {
|
||||
for (const auto previous : { eGameMasterLevel::CIVILIAN, eGameMasterLevel::OPERATOR }) {
|
||||
ClientPackets::MakeGMResponse packet;
|
||||
packet.success = success;
|
||||
packet.highestLevel = highest;
|
||||
packet.previousLevel = previous;
|
||||
packet.newLevel = highest;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendGMLevelChange(a, success, highest, previous, highest); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).highestLevel, highest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientPackets::MakeGMResponse packet;
|
||||
packet.success = true;
|
||||
packet.highestLevel = eGameMasterLevel::OPERATOR;
|
||||
packet.previousLevel = eGameMasterLevel::CIVILIAN;
|
||||
packet.newLevel = eGameMasterLevel::DEVELOPER;
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 10 00 00 00 00 01 09 00 00 00 08 00"), FromBitStream(bytes));
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, HTTPMonitorInfoAndDebugOutputMatchLegacy) {
|
||||
for (int bits = 0; bits < 32; bits++) {
|
||||
LegacyWorldPackets::HTTPMonitorInfo info;
|
||||
info.port = static_cast<uint16_t>(80 + bits);
|
||||
info.openWeb = bits & 1;
|
||||
info.supportsSum = bits & 2;
|
||||
info.supportsDetail = bits & 4;
|
||||
info.supportsWho = bits & 8;
|
||||
info.supportsObjects = bits & 16;
|
||||
ClientPackets::HTTPMonitorInfoResponse packet;
|
||||
packet.port = info.port;
|
||||
packet.openWeb = info.openWeb;
|
||||
packet.supportsSum = info.supportsSum;
|
||||
packet.supportsDetail = info.supportsDetail;
|
||||
packet.supportsWho = info.supportsWho;
|
||||
packet.supportsObjects = info.supportsObjects;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendHTTPMonitorInfo(a, info); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).supportsWho, info.supportsWho);
|
||||
}
|
||||
|
||||
for (const auto& text : g_Strings) {
|
||||
ClientPackets::DebugOutput packet;
|
||||
packet.data = text;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendDebugOuput(a, text); }, packet);
|
||||
EXPECT_EQ(RoundTrip(packet).data, text);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, ChatModerationStringMatchesLegacy) {
|
||||
using Segments = std::set<std::pair<uint8_t, uint8_t>>;
|
||||
Segments many;
|
||||
for (uint8_t i = 0; i < 70; i++) many.emplace(i, static_cast<uint8_t>(i + 1));
|
||||
for (const auto& segments : { Segments{}, Segments{ { 0, 4 } }, Segments{ { 2, 3 }, { 10, 255 } }, many }) {
|
||||
for (const uint32_t requestID : { 0u, 7u, 255u, 256u }) {
|
||||
for (const auto& receiver : g_Strings) {
|
||||
for (const bool accepted : { false, true }) {
|
||||
ClientPackets::ChatModerationString packet;
|
||||
packet.requestAccepted = segments.empty(); // what the world server sends
|
||||
packet.requestID = requestID;
|
||||
packet.receiver = LUWString(receiver, 42);
|
||||
packet.rejectedSegments = segments;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendChatModerationResponse(a, accepted, requestID, receiver, segments); }, packet);
|
||||
if (segments.size() <= 64) EXPECT_EQ(RoundTrip(packet).rejectedSegments, segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, CreateCharacterMatchesLegacy) {
|
||||
for (const auto objectID : g_ObjectIDs) {
|
||||
for (const auto& xml : { std::string(), std::string("<obj v=\"1\"><char acct=\"1\"/></obj>"), std::string(20000, 'x') }) {
|
||||
for (const auto gm : { eGameMasterLevel::CIVILIAN, eGameMasterLevel::OPERATOR }) {
|
||||
for (const int64_t reputation : { int64_t{ 0 }, int64_t{ -5 }, std::numeric_limits<int64_t>::max() }) {
|
||||
const std::u16string name = u"Some Name";
|
||||
const LWOCLONEID cloneID = 1234;
|
||||
ClientPackets::CreateCharacter packet;
|
||||
packet.objectID = objectID;
|
||||
packet.xmlData = xml;
|
||||
packet.name = name;
|
||||
packet.gmLevel = gm;
|
||||
packet.chatMode = static_cast<int32_t>(gm);
|
||||
packet.reputation = reputation;
|
||||
packet.propertyCloneID = cloneID;
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCreateCharacter(a, reputation, objectID, xml, name, gm, cloneID); }, packet);
|
||||
|
||||
const auto copy = RoundTrip(packet);
|
||||
EXPECT_EQ(copy.objectID, objectID);
|
||||
EXPECT_EQ(copy.xmlData, xml);
|
||||
EXPECT_EQ(copy.name, name);
|
||||
EXPECT_EQ(copy.gmLevel, gm);
|
||||
EXPECT_EQ(copy.reputation, reputation);
|
||||
EXPECT_EQ(copy.propertyCloneID, 1234);
|
||||
EXPECT_EQ(copy.templateID, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientPackets::CreateCharacter packet;
|
||||
packet.xmlData = "<obj/>";
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, CharacterListMatchesLegacy) {
|
||||
FakeCharacter first;
|
||||
FakeCharacter second;
|
||||
second.objectID = 1152921508901814000;
|
||||
second.name = "SomeName";
|
||||
second.unapprovedName = std::string(40, 'u');
|
||||
second.nameRejected = true;
|
||||
second.shirtColor = 1; second.shirtStyle = 2; second.pantsColor = 3; second.hairStyle = 4; second.hairColor = 5;
|
||||
second.leftHand = 6; second.rightHand = 7; second.eyebrows = 8; second.eyes = 9; second.mouth = 10;
|
||||
second.zoneID = 1100; second.zoneInstance = 0x12345; second.zoneClone = 99;
|
||||
second.lastLogin = 1790000000;
|
||||
second.equippedItems = { 1, -1, 7000 };
|
||||
|
||||
const std::vector<std::vector<FakeCharacter*>> lists = { {}, { &first }, { &first, &second, &second, &second } };
|
||||
for (const auto& list : lists) {
|
||||
ClientPackets::CharacterListResponse packet;
|
||||
for (const auto* character : list) {
|
||||
auto& entry = packet.characters.emplace_back();
|
||||
entry.objectID = character->GetObjectID();
|
||||
entry.name = LUWString(character->GetName());
|
||||
entry.unapprovedName = LUWString(character->GetUnapprovedName());
|
||||
entry.nameRejected = character->GetNameRejected();
|
||||
entry.shirtColor = character->GetShirtColor();
|
||||
entry.shirtStyle = character->GetShirtStyle();
|
||||
entry.pantsColor = character->GetPantsColor();
|
||||
entry.hairStyle = character->GetHairStyle();
|
||||
entry.hairColor = character->GetHairColor();
|
||||
entry.leftHand = character->GetLeftHand();
|
||||
entry.rightHand = character->GetRightHand();
|
||||
entry.eyebrows = character->GetEyebrows();
|
||||
entry.eyes = character->GetEyes();
|
||||
entry.mouth = character->GetMouth();
|
||||
entry.zoneID = character->GetZoneID();
|
||||
entry.zoneInstance = character->GetZoneInstance();
|
||||
entry.zoneClone = character->GetZoneClone();
|
||||
entry.lastLogin = character->GetLastLogin();
|
||||
entry.equippedItems = character->GetEquippedItems();
|
||||
}
|
||||
ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCharacterList(a, list); }, packet);
|
||||
const auto copy = RoundTrip(packet);
|
||||
ASSERT_EQ(copy.characters.size(), list.size());
|
||||
if (list.size() > 1) {
|
||||
EXPECT_EQ(copy.characters[1].name.GetAsString(), "SomeName");
|
||||
EXPECT_EQ(copy.characters[1].equippedItems, second.equippedItems);
|
||||
EXPECT_TRUE(copy.characters[1].nameRejected);
|
||||
}
|
||||
if (!list.empty()) ExpectTruncatedFails(packet);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Client -> server ----
|
||||
|
||||
TEST_F(WorldPacketsTests, ValidationMatchesLegacy) {
|
||||
for (const auto& username : g_Strings) {
|
||||
for (const auto& checksum : { std::string(), std::string("0123456789abcdef0123456789abcdef") }) {
|
||||
WorldPackets::Validation packet;
|
||||
packet.username = LUWString(username);
|
||||
packet.sessionKey = LUWString(std::string("fdce6d87c07128819c3a13cec99de30a"));
|
||||
packet.fdbChecksum = LUString(checksum, 32);
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::ReadValidation(&received.packet);
|
||||
WorldPackets::Validation read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.username.GetAsString(), legacy.username);
|
||||
EXPECT_EQ(read.sessionKey.GetAsString(), legacy.sessionKey);
|
||||
EXPECT_EQ(read.fdbChecksum.string, legacy.checksum);
|
||||
RoundTrip(packet);
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
}
|
||||
|
||||
// The optional trailing byte some clients send is ignored
|
||||
WorldPackets::Validation packet;
|
||||
packet.username = LUWString(std::string("user"));
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
bytes.Write<uint8_t>(0);
|
||||
WorldPackets::Validation read;
|
||||
ASSERT_TRUE(read.ReadHeader(bytes));
|
||||
ASSERT_TRUE(read.Deserialize(bytes));
|
||||
EXPECT_EQ(read.username.GetAsString(), "user");
|
||||
|
||||
// Golden: header then the username's first characters
|
||||
RakNet::BitStream golden;
|
||||
packet.WritePacket(golden);
|
||||
const auto all = FromBitStream(golden);
|
||||
EXPECT_PACKET_EQ(FromHex("53 04 00 01 00 00 00 00 75 00 73 00 65 00 72 00 00 00"), (PacketBytes{ { all.bytes.begin(), all.bytes.begin() + 18 }, 18 * 8 }));
|
||||
EXPECT_EQ(all.bytes.size(), 8 + 66 + 66 + 32);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, CharacterRequestsMatchLegacy) {
|
||||
for (const auto objectID : g_ObjectIDs) {
|
||||
WorldPackets::CharacterLoginRequest login;
|
||||
login.playerID = objectID;
|
||||
ReceivedPacket loginReceived(login);
|
||||
WorldPackets::CharacterLoginRequest loginRead;
|
||||
ASSERT_TRUE(Read(loginReceived, loginRead));
|
||||
EXPECT_EQ(loginRead.playerID, LegacyWorldPackets::ReadLoginRequest(&loginReceived.packet));
|
||||
ExpectTruncatedFails(login);
|
||||
|
||||
WorldPackets::CharacterDeleteRequest deletion;
|
||||
deletion.objectID = objectID;
|
||||
ReceivedPacket deleteReceived(deletion);
|
||||
WorldPackets::CharacterDeleteRequest deleteRead;
|
||||
ASSERT_TRUE(Read(deleteReceived, deleteRead));
|
||||
EXPECT_EQ(deleteRead.objectID, LegacyWorldPackets::ReadDeleteCharacter(&deleteReceived.packet));
|
||||
ExpectTruncatedFails(deletion);
|
||||
|
||||
for (const auto& name : g_Strings) {
|
||||
WorldPackets::CharacterRenameRequest rename;
|
||||
rename.objectID = objectID;
|
||||
rename.name = LUWString(name);
|
||||
ReceivedPacket renameReceived(rename);
|
||||
WorldPackets::CharacterRenameRequest renameRead;
|
||||
ASSERT_TRUE(Read(renameReceived, renameRead));
|
||||
const auto [legacyID, legacyName] = LegacyWorldPackets::ReadRenameCharacter(&renameReceived.packet);
|
||||
EXPECT_EQ(renameRead.objectID, legacyID);
|
||||
EXPECT_EQ(renameRead.name.GetAsString(), legacyName);
|
||||
RoundTrip(rename);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& name : g_Strings) {
|
||||
WorldPackets::CharacterCreateRequest create;
|
||||
create.name = LUWString(name);
|
||||
create.firstNameIndex = 1; create.middleNameIndex = 2; create.lastNameIndex = 0xFFFFFFFF;
|
||||
create.unknown = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
create.shirtColor = 10; create.shirtStyle = 11; create.pantsColor = 12; create.hairStyle = 13; create.hairColor = 14;
|
||||
create.leftHand = 15; create.rightHand = 16; create.eyebrows = 17; create.eyes = 18; create.mouth = 19;
|
||||
ReceivedPacket received(create);
|
||||
const auto legacy = LegacyWorldPackets::ReadCreateCharacter(&received.packet);
|
||||
WorldPackets::CharacterCreateRequest read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.name.GetAsString(), legacy.LUWStringName.GetAsString());
|
||||
EXPECT_EQ(read.firstNameIndex, legacy.firstNameIndex);
|
||||
EXPECT_EQ(read.middleNameIndex, legacy.middleNameIndex);
|
||||
EXPECT_EQ(read.lastNameIndex, legacy.lastNameIndex);
|
||||
EXPECT_EQ(read.shirtColor, legacy.shirtColor);
|
||||
EXPECT_EQ(read.shirtStyle, legacy.shirtStyle);
|
||||
EXPECT_EQ(read.pantsColor, legacy.pantsColor);
|
||||
EXPECT_EQ(read.hairStyle, legacy.hairStyle);
|
||||
EXPECT_EQ(read.hairColor, legacy.hairColor);
|
||||
EXPECT_EQ(read.leftHand, legacy.lh);
|
||||
EXPECT_EQ(read.rightHand, legacy.rh);
|
||||
EXPECT_EQ(read.eyebrows, legacy.eyebrows);
|
||||
EXPECT_EQ(read.eyes, legacy.eyes);
|
||||
EXPECT_EQ(read.mouth, legacy.mouth);
|
||||
RoundTrip(create);
|
||||
ExpectTruncatedFails(create);
|
||||
}
|
||||
|
||||
WorldPackets::CharacterListRequest list;
|
||||
RakNet::BitStream bytes;
|
||||
list.WritePacket(bytes);
|
||||
EXPECT_PACKET_EQ(FromHex("53 04 00 02 00 00 00 00"), FromBitStream(bytes));
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, GameMessageMatchesLegacy) {
|
||||
for (const auto objectID : g_ObjectIDs) {
|
||||
for (const uint32_t payloadBits : { 0u, 1u, 8u, 13u, 800u }) {
|
||||
WorldPackets::GameMessage packet;
|
||||
packet.objectID = objectID;
|
||||
packet.messageID = MessageType::Game::REQUEST_USE;
|
||||
for (uint32_t i = 0; i < payloadBits; i++) packet.data.Write(i % 3 == 0);
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::ReadGameMessage(&received.packet);
|
||||
WorldPackets::GameMessage read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.objectID, legacy.objectID);
|
||||
EXPECT_EQ(read.messageID, legacy.messageID);
|
||||
// A packet is whole bytes, so the data read is the payload padded to a byte, as before
|
||||
EXPECT_EQ(read.data.GetNumberOfBitsUsed(), legacy.dataBits);
|
||||
EXPECT_PACKET_EQ((PacketBytes{ legacy.data, legacy.dataBits }), FromBitStream(read.data));
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::GameMessage packet;
|
||||
packet.objectID = 0x0102030405060708;
|
||||
packet.messageID = static_cast<MessageType::Game>(0x0A0B);
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
EXPECT_PACKET_EQ(FromHex("53 04 00 05 00 00 00 00 08 07 06 05 04 03 02 01 0b 0a"), FromBitStream(bytes));
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, PositionUpdateMatchesLegacy) {
|
||||
for (int flags = 0; flags < 32; flags++) {
|
||||
WorldPackets::PositionUpdate packet;
|
||||
auto& update = packet.update;
|
||||
update.position = NiPoint3(1.0f, -2.0f, 3.5f);
|
||||
update.rotation = NiQuaternion(0.5f, 0.1f, 0.2f, 0.3f);
|
||||
update.onGround = flags & 1;
|
||||
update.onRail = !(flags & 1);
|
||||
packet.hasVelocity = flags & 2;
|
||||
update.velocity = packet.hasVelocity ? NiPoint3(4.0f, 5.0f, 6.0f) : NiPoint3Constant::ZERO;
|
||||
packet.hasAngularVelocity = flags & 4;
|
||||
update.angularVelocity = packet.hasAngularVelocity ? NiPoint3(7.0f, 8.0f, 9.0f) : NiPoint3Constant::ZERO;
|
||||
packet.hasLocalSpaceInfo = flags & 8;
|
||||
packet.hasLinearVelocity = packet.hasLocalSpaceInfo && (flags & 1);
|
||||
if (packet.hasLocalSpaceInfo) {
|
||||
update.localSpaceInfo.objectId = 1234;
|
||||
update.localSpaceInfo.position = NiPoint3(10.0f, 11.0f, 12.0f);
|
||||
if (packet.hasLinearVelocity) update.localSpaceInfo.linearVelocity = NiPoint3(13.0f, 14.0f, 15.0f);
|
||||
}
|
||||
packet.hasRemoteInputInfo = flags & 16;
|
||||
if (packet.hasRemoteInputInfo) {
|
||||
update.remoteInputInfo.m_RemoteInputX = 0.25f;
|
||||
update.remoteInputInfo.m_RemoteInputY = -0.75f;
|
||||
update.remoteInputInfo.m_IsPowersliding = true;
|
||||
update.remoteInputInfo.m_IsModified = flags & 1;
|
||||
}
|
||||
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::HandleClientPositionUpdate(&received.packet);
|
||||
WorldPackets::PositionUpdate read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
const auto& got = read.update;
|
||||
EXPECT_EQ(got.position, legacy.position);
|
||||
EXPECT_EQ(got.rotation, legacy.rotation);
|
||||
EXPECT_EQ(got.onGround, legacy.onGround);
|
||||
EXPECT_EQ(got.onRail, legacy.onRail);
|
||||
EXPECT_EQ(got.velocity, legacy.velocity);
|
||||
EXPECT_EQ(got.angularVelocity, legacy.angularVelocity);
|
||||
EXPECT_EQ(got.localSpaceInfo.objectId, legacy.localSpaceInfo.objectId);
|
||||
EXPECT_EQ(got.localSpaceInfo.position, legacy.localSpaceInfo.position);
|
||||
EXPECT_EQ(got.localSpaceInfo.linearVelocity, legacy.localSpaceInfo.linearVelocity);
|
||||
auto remote = got.remoteInputInfo;
|
||||
EXPECT_TRUE(remote == legacy.remoteInputInfo);
|
||||
RoundTrip(packet);
|
||||
}
|
||||
|
||||
// Like before, the last two sections may be left out entirely
|
||||
WorldPackets::PositionUpdate packet;
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
RakNet::BitStream shortened(bytes.GetData(), bytes.GetNumberOfBytesUsed(), true);
|
||||
shortened.SetWriteOffset(bytes.GetNumberOfBitsUsed() - 2); // drop the two trailing flags
|
||||
WorldPackets::PositionUpdate read;
|
||||
ASSERT_TRUE(read.ReadHeader(shortened));
|
||||
EXPECT_TRUE(read.Deserialize(shortened));
|
||||
EXPECT_FALSE(read.hasLocalSpaceInfo);
|
||||
EXPECT_FALSE(read.hasRemoteInputInfo);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, StringCheckMatchesLegacy) {
|
||||
const std::vector<std::u16string> receivers = { u"", u"Bob", u"[GM]Mythran", u"[GM]", std::u16string(42, u'r') };
|
||||
const std::vector<std::u16string> messages = { u"", u"hi", u"hello there", std::u16string(300, u'm') };
|
||||
for (const auto& receiver : receivers) {
|
||||
for (const auto& message : messages) {
|
||||
for (const uint8_t chatLevel : { uint8_t{ 0 }, uint8_t{ 1 } }) {
|
||||
WorldPackets::StringCheck packet;
|
||||
packet.chatLevel = chatLevel;
|
||||
packet.requestID = 0xa2;
|
||||
packet.receiver = receiver;
|
||||
packet.receiver.resize(42);
|
||||
// what clients leave after the name
|
||||
if (receiver.size() < 40) packet.receiver[receiver.size() + 1] = u'\x1894';
|
||||
packet.message = message;
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::HandleChatModerationRequest(&received.packet);
|
||||
WorldPackets::StringCheck read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.chatLevel, legacy.chatLevel);
|
||||
EXPECT_EQ(read.requestID, legacy.requestID);
|
||||
EXPECT_EQ(read.GetNarrowReceiver(), legacy.receiver);
|
||||
EXPECT_EQ(read.GetNarrowMessage(), legacy.message);
|
||||
RoundTrip(packet);
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, GeneralChatMessageMatchesLegacy) {
|
||||
for (const auto& message : { std::u16string(), std::u16string(u"how long are you going to stay there?"), std::u16string(2000, u'x') }) {
|
||||
for (const uint8_t channel : { uint8_t{ 0 }, uint8_t{ 4 } }) {
|
||||
WorldPackets::GeneralChatMessage packet;
|
||||
packet.chatChannel = channel;
|
||||
packet.unknown = 0x0900;
|
||||
packet.message = message;
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::HandleChatMessage(&received.packet);
|
||||
WorldPackets::GeneralChatMessage read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.chatChannel, legacy.chatChannel);
|
||||
EXPECT_EQ(read.unknown, legacy.unknown);
|
||||
EXPECT_EQ(read.message, legacy.message);
|
||||
RoundTrip(packet);
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::GeneralChatMessage packet;
|
||||
packet.chatChannel = 4;
|
||||
packet.message = u"hi";
|
||||
RakNet::BitStream bytes;
|
||||
packet.WritePacket(bytes);
|
||||
// channel | unknown u16 | count 3 (includes the terminator) | "hi" | terminator
|
||||
EXPECT_PACKET_EQ(FromHex("53 04 00 0e 00 00 00 00 04 00 00 03 00 00 00 68 00 69 00 00 00"), FromBitStream(bytes));
|
||||
|
||||
// A negative or huge count is dropped
|
||||
for (const int32_t count : { -1, static_cast<int32_t>(MAX_MESSAGE_LENGTH) + 1 }) {
|
||||
RakNet::BitStream bad;
|
||||
LUBitStream(ServiceType::WORLD, MessageType::World::GENERAL_CHAT_MESSAGE).WriteHeader(bad);
|
||||
bad.Write<uint8_t>(0);
|
||||
bad.Write<uint16_t>(0);
|
||||
bad.Write(count);
|
||||
WorldPackets::GeneralChatMessage read;
|
||||
ASSERT_TRUE(read.ReadHeader(bad));
|
||||
EXPECT_FALSE(read.Deserialize(bad));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, RoutePacketMatchesLegacy) {
|
||||
for (const uint32_t bodySize : { 0u, 1u, 4u, 20u }) {
|
||||
for (const uint32_t declaredSize : { 0u, 2u, 11u, 100u, 20001u }) {
|
||||
for (const LWOOBJID sender : { LWOOBJID{ 0 }, LWOOBJID{ 1152921508901814000 } }) {
|
||||
WorldPackets::RoutePacket packet;
|
||||
packet.size = declaredSize;
|
||||
packet.routedService = ServiceType::CHAT;
|
||||
packet.routedMessageID = 0x10A;
|
||||
for (uint32_t i = 0; i < bodySize; i++) packet.routedData.push_back(static_cast<uint8_t>(i + 1));
|
||||
ReceivedPacket received(packet);
|
||||
|
||||
RakNet::BitStream legacy;
|
||||
const bool legacyForwarded = LegacyWorldPackets::BuildRoutePacket(&received.packet, sender, legacy);
|
||||
|
||||
WorldPackets::RoutePacket read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.routedData, packet.routedData);
|
||||
EXPECT_EQ(legacyForwarded, read.size <= 20000); // the world server's size check
|
||||
if (!legacyForwarded) continue;
|
||||
|
||||
RakNet::BitStream converted;
|
||||
read.ToChat(sender).WritePacket(converted);
|
||||
EXPECT_PACKET_EQ(FromBitStream(legacy), FromBitStream(converted));
|
||||
RoundTrip(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorldPackets::RoutePacket packet;
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
TEST_F(WorldPacketsTests, SmallRequestsMatchLegacy) {
|
||||
for (const auto type : magic_enum::enum_values<eFunnessTypes>()) {
|
||||
for (const float info : { 0.0f, 1.0f, 6.5f, -3.0f }) {
|
||||
WorldPackets::HandleFunness packet;
|
||||
packet.cheatType = type;
|
||||
packet.cheatInfo = info;
|
||||
ReceivedPacket received(packet);
|
||||
const auto legacy = LegacyWorldPackets::ReadFunness(&received.packet);
|
||||
WorldPackets::HandleFunness read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.cheatType, legacy.cheatType);
|
||||
EXPECT_EQ(read.cheatInfo, legacy.cheatInfo);
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
}
|
||||
|
||||
for (const int32_t language : { 0, 1, 2, 3, -1 }) {
|
||||
WorldPackets::UIHelpTop5 packet;
|
||||
packet.language = language;
|
||||
ReceivedPacket received(packet);
|
||||
WorldPackets::UIHelpTop5 read;
|
||||
ASSERT_TRUE(Read(received, read));
|
||||
EXPECT_EQ(read.language, LegacyWorldPackets::SendTop5HelpIssues(&received.packet));
|
||||
ExpectTruncatedFails(packet);
|
||||
}
|
||||
|
||||
WorldPackets::LevelLoadComplete level;
|
||||
level.mapID = 1101;
|
||||
level.instanceID = 46671;
|
||||
RakNet::BitStream bytes;
|
||||
level.WritePacket(bytes);
|
||||
EXPECT_PACKET_EQ(FromHex("53 04 00 13 00 00 00 00 4d 04 4f b6 00 00 00 00"), FromBitStream(bytes));
|
||||
EXPECT_EQ(RoundTrip(level).instanceID, 46671);
|
||||
ExpectTruncatedFails(level);
|
||||
|
||||
WorldPackets::MailPacket mail;
|
||||
mail.data.Write<uint32_t>(0x01020304);
|
||||
RakNet::BitStream mailBytes;
|
||||
mail.WritePacket(mailBytes);
|
||||
WorldPackets::MailPacket mailRead;
|
||||
ASSERT_TRUE(mailRead.ReadHeader(mailBytes));
|
||||
ASSERT_TRUE(mailRead.Deserialize(mailBytes));
|
||||
EXPECT_PACKET_EQ(FromBitStream(mail.data), FromBitStream(mailRead.data));
|
||||
}
|
||||
Reference in New Issue
Block a user