mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
wire: guild packets and DisplayGuildCreateBox as structs
The guild packets of the 1.10.64 client (docs/Guilds.md) as Serialize/Deserialize structs, laid out the way the client's packet handlers read them: GUILD_CREATE_RESPONSE, GUILD_INVITE, GUILD_INVITE_INITIAL_RESPONSE, _FINAL_RESPONSE, _CONFIRM, GUILD_ADD_PLAYER, GUILD_REMOVE_PLAYER, GUILD_LOGIN_LOGOUT and GUILD_DATA (ClientPackets), the world packet TMP_GUILD_CREATE the create box sends, the chat packets the client sends through its world (GUILD_INVITE, GUILD_INVITE_RESPONSE, GUILD_LEAVE, GUILD_GET_ALL), DLU's world <-> chat packets (GUILD_CREATE, GUILD_KICK, GUILD_GET_STATUS as the chat server's guild update to a world, and GUILD_SET_RANK and GUILD_DISBAND appended to MessageType::Chat after CREATE_TEAM) and the game message DisplayGuildCreateBox (626). Fixed-size names always keep a NUL, since the client reads them as C strings. Enums eGuildCreateResponse, eGuildInviteResponse, eGuildInviteFinalResponse, eGuildRank and eGuildLeaveReason. Nothing sends them yet. Check: GuildPacketsTests compares every packet with the bytes at the client's offsets. Nothing to check in game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,5 +74,7 @@ namespace MessageType {
|
|||||||
UPDATE_FREE_TRIAL_STATUS,
|
UPDATE_FREE_TRIAL_STATUS,
|
||||||
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
|
// CUSTOM DLU MESSAGE ID FOR INTERNAL USE
|
||||||
CREATE_TEAM,
|
CREATE_TEAM,
|
||||||
|
GUILD_SET_RANK, // world -> chat: /gpromote, /gdemote, /gleader
|
||||||
|
GUILD_DISBAND, // world -> chat: /gdisband
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
14
dCommon/dEnums/eGuildCreateResponse.h
Normal file
14
dCommon/dEnums/eGuildCreateResponse.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef EGUILDCREATERESPONSE_H
|
||||||
|
#define EGUILDCREATERESPONSE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// MSG_CLIENT_GUILD_CREATE_RESPONSE's result (docs/Guilds.md)
|
||||||
|
enum class eGuildCreateResponse : uint8_t {
|
||||||
|
CREATED, // "MSG_GUILD_GUILD_NAME_CREATED", then the client asks for the guild's data
|
||||||
|
BAD_NAME, // "MSG_GUILD_GUILD_NAME_CANT_BE_USED"
|
||||||
|
EXISTS, // "MSG_GUILD_GUILD_NAME_ALREADY_IN_USE"
|
||||||
|
FAILED, // anything else: "MSG_GUILD_GUILD_COULD_NOT_BE_CREATED"
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EGUILDCREATERESPONSE_H
|
||||||
23
dCommon/dEnums/eGuildInviteResponse.h
Normal file
23
dCommon/dEnums/eGuildInviteResponse.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef EGUILDINVITERESPONSE_H
|
||||||
|
#define EGUILDINVITERESPONSE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// MSG_CLIENT_GUILD_INVITE_INITIAL_RESPONSE's code: what happened to the invite the player sent (docs/Guilds.md)
|
||||||
|
enum class eGuildInviteResponse : uint8_t {
|
||||||
|
SENT, // "MSG_GUILD_GUILD_INVITE_SENT_TO_NAME"
|
||||||
|
NOT_ONLINE, // "MSG_GENERIC_NAME_IS_NOT_ONLINE"
|
||||||
|
ALREADY_IN_GUILD, // "MSG_GUILD_NAME_IS_ALREADY_IN_A_GUILD"
|
||||||
|
INVITE_PENDING, // "MSG_GUILD_NAME_ALREADY_HAS_A_GUILD_INVITE_PENDING"
|
||||||
|
FAILED, // anything else: "MSG_GUILD_COULD_NOT_INVITE_NAME"
|
||||||
|
};
|
||||||
|
|
||||||
|
// MSG_CLIENT_GUILD_INVITE_FINAL_RESPONSE's code: the invited player's answer, told to the inviter
|
||||||
|
enum class eGuildInviteFinalResponse : uint8_t {
|
||||||
|
JOINED, // "MSG_GUILD_NAME_HAS_JOINED_THE_GUILD"
|
||||||
|
DECLINED, // "MSG_GUILD_NAME_DECLINED_YOUR_INVITATION"
|
||||||
|
NOT_ONLINE, // "MSG_GENERIC_NAME_IS_NOT_ONLINE"
|
||||||
|
FAILED, // anything else: "CLIENTMSG_COULD_NOT_INVITE_NAME"
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EGUILDINVITERESPONSE_H
|
||||||
21
dCommon/dEnums/eGuildRank.h
Normal file
21
dCommon/dEnums/eGuildRank.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef EGUILDRANK_H
|
||||||
|
#define EGUILDRANK_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// A guild member's rank, as the client names it in the guild list (0 and anything above 4: no name)
|
||||||
|
enum class eGuildRank : uint8_t {
|
||||||
|
NONE,
|
||||||
|
LEADER,
|
||||||
|
OFFICER,
|
||||||
|
VETERAN,
|
||||||
|
RECRUIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// MSG_CLIENT_GUILD_REMOVE_PLAYER's reason
|
||||||
|
enum class eGuildLeaveReason : uint8_t {
|
||||||
|
LEFT,
|
||||||
|
KICKED,
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // EGUILDRANK_H
|
||||||
@@ -251,4 +251,12 @@ namespace GameMessages {
|
|||||||
bool Help::Deserialize(RakNet::BitStream& bitStream) {
|
bool Help::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
return bitStream.Read(helpId);
|
return bitStream.Read(helpId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayGuildCreateBox::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(bShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DisplayGuildCreateBox::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
return bitStream.Read(bShow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,16 @@ namespace GameMessages {
|
|||||||
|
|
||||||
eHelpType helpId{ eHelpType::NONE };
|
eHelpType helpId{ eHelpType::NONE };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Server -> client, to one client. Opens the guild create box (the client ignores bShow and always opens it).
|
||||||
|
// Live sent it from the Guild Master NPC's script (LOT 3001, L_GUILD_CREATE.lua); see docs/Guilds.md.
|
||||||
|
struct DisplayGuildCreateBox : public NetGameMsg {
|
||||||
|
DisplayGuildCreateBox() : NetGameMsg(MessageType::Game::DISPLAY_GUILD_CREATE_BOX) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
|
||||||
|
bool bShow{ true };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // PLAYERMESSAGES_H
|
#endif // PLAYERMESSAGES_H
|
||||||
|
|||||||
@@ -539,3 +539,112 @@ bool ChatPackets::RoutedFromClient::Deserialize(RakNet::BitStream& bitStream) {
|
|||||||
if (!data.empty()) VALIDATE_READ(bitStream.ReadBits(data.data(), BYTES_TO_BITS(data.size()), true));
|
if (!data.empty()) VALIDATE_READ(bitStream.ReadBits(data.data(), BYTES_TO_BITS(data.size()), true));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Guilds ----
|
||||||
|
|
||||||
|
void ChatPackets::GuildInvite::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(unknown);
|
||||||
|
bitStream.Write(invitedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildInvite::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown));
|
||||||
|
VALIDATE_READ(bitStream.Read(invitedPlayer));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildInviteResponse::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(unknown);
|
||||||
|
bitStream.Write(declined);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildInviteResponse::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown));
|
||||||
|
VALIDATE_READ(bitStream.Read(declined));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildLeave::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildLeave::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildGetAll::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildGetAll::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildCreate::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(guildName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildCreate::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(guildName));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildKick::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(unknown);
|
||||||
|
bitStream.Write(kickedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildKick::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown));
|
||||||
|
VALIDATE_READ(bitStream.Read(kickedPlayer));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildSetRank::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(targetPlayer);
|
||||||
|
bitStream.Write(rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildSetRank::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(targetPlayer));
|
||||||
|
VALIDATE_READ(bitStream.Read(rank));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildDisband::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildDisband::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatPackets::GuildStatus::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(characterID);
|
||||||
|
bitStream.Write(guildID);
|
||||||
|
bitStream.Write(guildName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatPackets::GuildStatus::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(characterID));
|
||||||
|
VALIDATE_READ(bitStream.Read(guildID));
|
||||||
|
VALIDATE_READ(bitStream.Read(guildName));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -375,6 +375,104 @@ namespace ChatPackets {
|
|||||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Guilds (docs/Guilds.md). The client sends GUILD_INVITE, GUILD_INVITE_RESPONSE, GUILD_LEAVE and GUILD_GET_ALL
|
||||||
|
// (routed like the ones above); the others are DLU's world <-> chat packets.
|
||||||
|
|
||||||
|
// Invite a player (by name) to the sender's guild
|
||||||
|
struct GuildInvite : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
uint32_t unknown{};
|
||||||
|
LUWString invitedPlayer{ 33 };
|
||||||
|
|
||||||
|
GuildInvite() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_INVITE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The answer to the invite in the message box (button 1 accepts: declined 0)
|
||||||
|
struct GuildInviteResponse : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
uint32_t unknown{};
|
||||||
|
uint8_t declined{};
|
||||||
|
|
||||||
|
GuildInviteResponse() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_INVITE_RESPONSE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Leave the guild. The client sends 66 more bytes of whatever was on its stack; they are not read.
|
||||||
|
struct GuildLeave : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
uint32_t unknown{};
|
||||||
|
|
||||||
|
GuildLeave() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_LEAVE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The player's guild and its members (answered with ClientPackets::GuildData)
|
||||||
|
struct GuildGetAll : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
uint32_t unknown{};
|
||||||
|
|
||||||
|
GuildGetAll() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_GET_ALL) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// World -> chat: the name from the client's TMP_GUILD_CREATE
|
||||||
|
struct GuildCreate : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
LUWString guildName{ 31 };
|
||||||
|
|
||||||
|
GuildCreate() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_CREATE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// World -> chat: /gkick (the client never sends GUILD_KICK; the layout is the team kick's)
|
||||||
|
struct GuildKick : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
uint32_t unknown{};
|
||||||
|
LUWString kickedPlayer{ 33 };
|
||||||
|
|
||||||
|
GuildKick() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_KICK) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// World -> chat: change a member's rank (eGuildRank; LEADER hands the guild over)
|
||||||
|
struct GuildSetRank : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
LUWString targetPlayer{ 33 };
|
||||||
|
uint8_t rank{};
|
||||||
|
|
||||||
|
GuildSetRank() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_SET_RANK) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// World -> chat: the leader disbands the guild
|
||||||
|
struct GuildDisband : public LUBitStream {
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
|
||||||
|
GuildDisband() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_DISBAND) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chat -> the character's world: its guild changed; the world puts it in the character component (guild 0: none).
|
||||||
|
// guildName is what other players may see (empty while the name waits for moderation).
|
||||||
|
struct GuildStatus : public LUBitStream {
|
||||||
|
LWOOBJID characterID{};
|
||||||
|
LWOOBJID guildID{};
|
||||||
|
LUWString guildName{ 31 };
|
||||||
|
|
||||||
|
GuildStatus() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_GET_STATUS) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat-service packets the client receives, from the chat server (routed through the player's world) or from its world.
|
* Chat-service packets the client receives, from the chat server (routed through the player's world) or from its world.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -913,3 +913,175 @@ namespace ClientPackets {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace ClientPackets {
|
||||||
|
LUWString Guild::FixedName(const std::string& name, const uint32_t size) {
|
||||||
|
auto wide = GeneralUtils::UTF8ToUTF16(name);
|
||||||
|
if (size > 0 && wide.size() >= size) wide.resize(size - 1);
|
||||||
|
return LUWString(wide, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void WriteName(RakNet::BitStream& bitStream, const std::string& name, const uint32_t size) {
|
||||||
|
bitStream.Write(Guild::FixedName(name, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadName(RakNet::BitStream& bitStream, std::string& name, const uint32_t size) {
|
||||||
|
LUWString value(size);
|
||||||
|
VALIDATE_READ(bitStream.Read(value));
|
||||||
|
name = value.GetAsString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ReadByteBool(RakNet::BitStream& bitStream, bool& value) {
|
||||||
|
uint8_t byte{};
|
||||||
|
VALIDATE_READ(bitStream.Read(byte));
|
||||||
|
value = byte != 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildCreateResponse::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(result);
|
||||||
|
bitStream.Write(guildID);
|
||||||
|
WriteName(bitStream, guildName, Guild::NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildCreateResponse::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(result));
|
||||||
|
VALIDATE_READ(bitStream.Read(guildID));
|
||||||
|
return ReadName(bitStream, guildName, Guild::NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildInvite::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
WriteName(bitStream, inviterName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
WriteName(bitStream, guildName, Guild::NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildInvite::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(ReadName(bitStream, inviterName, Guild::PLAYER_NAME_SIZE));
|
||||||
|
return ReadName(bitStream, guildName, Guild::NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildInviteInitialResponse::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(response);
|
||||||
|
WriteName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildInviteInitialResponse::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(response));
|
||||||
|
return ReadName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildInviteFinalResponse::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(response);
|
||||||
|
WriteName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildInviteFinalResponse::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(response));
|
||||||
|
return ReadName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildInviteConfirm::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write<uint8_t>(failed);
|
||||||
|
WriteName(bitStream, guildName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildInviteConfirm::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(ReadByteBool(bitStream, failed));
|
||||||
|
return ReadName(bitStream, guildName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildAddPlayer::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
WriteName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(rank);
|
||||||
|
WriteZone(bitStream, zoneID);
|
||||||
|
bitStream.Write<uint8_t>(online);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildAddPlayer::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(ReadName(bitStream, playerName, Guild::PLAYER_NAME_SIZE));
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(rank));
|
||||||
|
VALIDATE_READ(ReadZone(bitStream, zoneID));
|
||||||
|
return ReadByteBool(bitStream, online);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildRemovePlayer::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(reason);
|
||||||
|
WriteName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write(newLeaderID);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildRemovePlayer::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(reason));
|
||||||
|
VALIDATE_READ(ReadName(bitStream, playerName, Guild::PLAYER_NAME_SIZE));
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(bitStream.Read(newLeaderID));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildLoginLogout::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
WriteName(bitStream, playerName, Guild::PLAYER_NAME_SIZE);
|
||||||
|
bitStream.Write(playerID);
|
||||||
|
bitStream.Write<uint8_t>(online);
|
||||||
|
WriteZone(bitStream, zoneID);
|
||||||
|
bitStream.Write<uint8_t>(worldUpdateOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildLoginLogout::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(ReadName(bitStream, playerName, Guild::PLAYER_NAME_SIZE));
|
||||||
|
VALIDATE_READ(bitStream.Read(playerID));
|
||||||
|
VALIDATE_READ(ReadByteBool(bitStream, online));
|
||||||
|
VALIDATE_READ(ReadZone(bitStream, zoneID));
|
||||||
|
return ReadByteBool(bitStream, worldUpdateOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildData::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
constexpr uint32_t DATE_SIZE = 11;
|
||||||
|
bitStream.Write(status);
|
||||||
|
WriteName(bitStream, guildName, Guild::NAME_SIZE);
|
||||||
|
WriteName(bitStream, joinDate, DATE_SIZE);
|
||||||
|
WriteName(bitStream, foundDate, DATE_SIZE);
|
||||||
|
bitStream.Write(reputation);
|
||||||
|
bitStream.Write(unknown1);
|
||||||
|
bitStream.Write(unknown2);
|
||||||
|
bitStream.Write(unknown3);
|
||||||
|
bitStream.Write<uint16_t>(members.size());
|
||||||
|
for (const auto& member : members) {
|
||||||
|
bitStream.Write(member.rank);
|
||||||
|
bitStream.Write<uint8_t>(member.online);
|
||||||
|
WriteZone(bitStream, member.zoneID);
|
||||||
|
bitStream.Write(member.playerID);
|
||||||
|
WriteName(bitStream, member.name, Guild::PLAYER_NAME_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GuildData::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
constexpr uint32_t DATE_SIZE = 11;
|
||||||
|
VALIDATE_READ(bitStream.Read(status));
|
||||||
|
VALIDATE_READ(ReadName(bitStream, guildName, Guild::NAME_SIZE));
|
||||||
|
VALIDATE_READ(ReadName(bitStream, joinDate, DATE_SIZE));
|
||||||
|
VALIDATE_READ(ReadName(bitStream, foundDate, DATE_SIZE));
|
||||||
|
VALIDATE_READ(bitStream.Read(reputation));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown1));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown2));
|
||||||
|
VALIDATE_READ(bitStream.Read(unknown3));
|
||||||
|
uint16_t count{};
|
||||||
|
VALIDATE_READ(bitStream.Read(count));
|
||||||
|
members.clear();
|
||||||
|
for (uint16_t i = 0; i < count; i++) {
|
||||||
|
Member member;
|
||||||
|
VALIDATE_READ(bitStream.Read(member.rank));
|
||||||
|
VALIDATE_READ(ReadByteBool(bitStream, member.online));
|
||||||
|
VALIDATE_READ(ReadZone(bitStream, member.zoneID));
|
||||||
|
VALIDATE_READ(bitStream.Read(member.playerID));
|
||||||
|
VALIDATE_READ(ReadName(bitStream, member.name, Guild::PLAYER_NAME_SIZE));
|
||||||
|
members.push_back(std::move(member));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
#include "eAddFriendResponseType.h"
|
#include "eAddFriendResponseType.h"
|
||||||
#include "eAddIgnoreResponse.h"
|
#include "eAddIgnoreResponse.h"
|
||||||
#include "eBlueprintSaveResponseType.h"
|
#include "eBlueprintSaveResponseType.h"
|
||||||
|
#include "eGuildCreateResponse.h"
|
||||||
|
#include "eGuildInviteResponse.h"
|
||||||
|
#include "eGuildRank.h"
|
||||||
#include "eUgcResourceType.h"
|
#include "eUgcResourceType.h"
|
||||||
#include "MessageType/Client.h"
|
#include "MessageType/Client.h"
|
||||||
#include "MessageType/Game.h"
|
#include "MessageType/Game.h"
|
||||||
@@ -549,6 +552,133 @@ namespace ClientPackets {
|
|||||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guild packets (docs/Guilds.md), written by the chat server and routed through the player's world. The client
|
||||||
|
* copies them into packed structs: every string is a fixed-size UTF-16 buffer it reads up to the first NUL, so
|
||||||
|
* DLU writes at most size - 1 characters.
|
||||||
|
*/
|
||||||
|
namespace Guild {
|
||||||
|
// Guild names are 31 characters on the wire (30 and a NUL); player names 33
|
||||||
|
constexpr uint32_t NAME_SIZE = 31;
|
||||||
|
constexpr uint32_t PLAYER_NAME_SIZE = 33;
|
||||||
|
// A UTF-8 name as a buffer of `size` characters, cut so a NUL is always left
|
||||||
|
LUWString FixedName(const std::string& name, uint32_t size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The answer to the player's TMP_GUILD_CREATE
|
||||||
|
struct GuildCreateResponse : public LUBitStream {
|
||||||
|
eGuildCreateResponse result{};
|
||||||
|
LWOOBJID guildID{}; // the client doesn't read it
|
||||||
|
std::string guildName; // 31 characters on the wire
|
||||||
|
|
||||||
|
GuildCreateResponse() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_CREATE_RESPONSE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Someone invites the player to their guild; the client asks the player with a message box
|
||||||
|
struct GuildInvite : public LUBitStream {
|
||||||
|
std::string inviterName; // 33 characters on the wire
|
||||||
|
std::string guildName; // 31
|
||||||
|
|
||||||
|
GuildInvite() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_INVITE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// What happened to the invite the player sent
|
||||||
|
struct GuildInviteInitialResponse : public LUBitStream {
|
||||||
|
eGuildInviteResponse response{};
|
||||||
|
std::string playerName; // the invited player; 33 characters on the wire
|
||||||
|
|
||||||
|
GuildInviteInitialResponse() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_INVITE_INITIAL_RESPONSE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The invited player's answer, to the inviter
|
||||||
|
struct GuildInviteFinalResponse : public LUBitStream {
|
||||||
|
eGuildInviteFinalResponse response{};
|
||||||
|
std::string playerName; // the invited player; 33 characters on the wire
|
||||||
|
|
||||||
|
GuildInviteFinalResponse() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_INVITE_FINAL_RESPONSE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// To the player who accepted an invite: whether they are in the guild now (the client then asks for its data)
|
||||||
|
struct GuildInviteConfirm : public LUBitStream {
|
||||||
|
bool failed{}; // one byte
|
||||||
|
std::string guildName; // 33 characters on the wire
|
||||||
|
|
||||||
|
GuildInviteConfirm() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_INVITE_CONFIRM) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Someone joined the player's guild
|
||||||
|
struct GuildAddPlayer : public LUBitStream {
|
||||||
|
std::string playerName; // 33 characters on the wire
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
eGuildRank rank{};
|
||||||
|
LWOZONEID zoneID{};
|
||||||
|
bool online{}; // one byte
|
||||||
|
|
||||||
|
GuildAddPlayer() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_ADD_PLAYER) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Someone left or was kicked from the player's guild (the player themself: the client drops the guild)
|
||||||
|
struct GuildRemovePlayer : public LUBitStream {
|
||||||
|
eGuildLeaveReason reason{};
|
||||||
|
std::string playerName; // 33 characters on the wire
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
LWOOBJID newLeaderID{}; // 0: the leader didn't change; otherwise that member's rank becomes leader
|
||||||
|
|
||||||
|
GuildRemovePlayer() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_REMOVE_PLAYER) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A guildmate logged in or out, or changed worlds (worldUpdateOnly: no chat line)
|
||||||
|
struct GuildLoginLogout : public LUBitStream {
|
||||||
|
std::string playerName; // 33 characters on the wire
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
bool online{}; // one byte
|
||||||
|
LWOZONEID zoneID{};
|
||||||
|
bool worldUpdateOnly{}; // one byte
|
||||||
|
|
||||||
|
GuildLoginLogout() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_LOGIN_LOGOUT) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The player's guild and every member (the answer to GUILD_GET_ALL)
|
||||||
|
struct GuildData : public LUBitStream {
|
||||||
|
struct Member {
|
||||||
|
eGuildRank rank{};
|
||||||
|
bool online{}; // one byte
|
||||||
|
LWOZONEID zoneID{}; // the client reads it only for online members
|
||||||
|
LWOOBJID playerID{};
|
||||||
|
std::string name; // 33 characters on the wire
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t status{}; // the client only takes 0 (DLU sends 1 to a player in no guild)
|
||||||
|
std::string guildName; // 31 characters on the wire
|
||||||
|
std::string joinDate; // 11 characters on the wire; the client doesn't show it
|
||||||
|
std::string foundDate; // 11
|
||||||
|
int32_t reputation{}; // the client doesn't use it
|
||||||
|
int32_t unknown1{};
|
||||||
|
int32_t unknown2{};
|
||||||
|
uint16_t unknown3{};
|
||||||
|
std::vector<Member> members; // u16 count; the client ignores data with none
|
||||||
|
|
||||||
|
GuildData() : LUBitStream(ServiceType::CLIENT, MessageType::Client::GUILD_DATA) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CLIENTPACKETS_H
|
#endif // CLIENTPACKETS_H
|
||||||
|
|||||||
@@ -227,6 +227,19 @@ namespace WorldPackets {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TmpGuildCreate::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
auto name = guildName;
|
||||||
|
if (name.size() >= NAME_SIZE) name.resize(NAME_SIZE - 1);
|
||||||
|
bitStream.Write(LUWString(name, NAME_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TmpGuildCreate::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
LUWString name(NAME_SIZE);
|
||||||
|
VALIDATE_READ(bitStream.Read(name));
|
||||||
|
guildName = name.string;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void MailPacket::Serialize(RakNet::BitStream& bitStream) const {
|
void MailPacket::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
WriteAll(bitStream, data);
|
WriteAll(bitStream, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,6 +192,17 @@ namespace WorldPackets {
|
|||||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Client -> server. The name the player typed in the guild create box (docs/Guilds.md); the client sends it only
|
||||||
|
// when it is not in a guild. The client's name for it: MSG_WORLD_CLIENT_TMP_GUILD_CREATE.
|
||||||
|
struct TmpGuildCreate : public WorldLUBitStream {
|
||||||
|
static constexpr uint32_t NAME_SIZE = 31; // characters on the wire; the client puts at most 30 and a NUL
|
||||||
|
std::u16string guildName;
|
||||||
|
|
||||||
|
TmpGuildCreate() : WorldLUBitStream(MessageType::World::TMP_GUILD_CREATE) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
// Client -> server. The client's anti cheat noticed something.
|
// Client -> server. The client's anti cheat noticed something.
|
||||||
struct HandleFunness : public WorldLUBitStream {
|
struct HandleFunness : public WorldLUBitStream {
|
||||||
float cheatInfo{};
|
float cheatInfo{};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
SET(DNET_TESTS
|
SET(DNET_TESTS
|
||||||
"ChatPacketsTests.cpp"
|
"ChatPacketsTests.cpp"
|
||||||
|
"GuildPacketsTests.cpp"
|
||||||
"CommonAuthPacketsTests.cpp"
|
"CommonAuthPacketsTests.cpp"
|
||||||
"MasterPacketsTests.cpp"
|
"MasterPacketsTests.cpp"
|
||||||
"ServerTrafficTests.cpp"
|
"ServerTrafficTests.cpp"
|
||||||
|
|||||||
293
tests/dGameTests/dNetTests/GuildPacketsTests.cpp
Normal file
293
tests/dGameTests/dNetTests/GuildPacketsTests.cpp
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
// Guild packets (docs/Guilds.md): the bytes the 1.10.64 client reads (offsets from its packet handlers), the bytes it
|
||||||
|
// sends, and DLU's world <-> chat packets.
|
||||||
|
#include "ChatPackets.h"
|
||||||
|
#include "ClientPackets.h"
|
||||||
|
#include "WorldPackets.h"
|
||||||
|
#include "PlayerMessages.h"
|
||||||
|
#include "GameDependencies.h"
|
||||||
|
#include "PacketTestUtils.h"
|
||||||
|
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using namespace PacketTestUtils;
|
||||||
|
using GameMessageTestUtils::RoundTrip;
|
||||||
|
using GameMessageTestUtils::ExpectTruncatedFails;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Little-endian bytes, the way the client's packed structs lie in memory
|
||||||
|
struct Bytes {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
Bytes& U8(uint8_t value) { data.push_back(value); return *this; }
|
||||||
|
Bytes& U16(uint16_t value) { return U8(value & 0xFF).U8(value >> 8); }
|
||||||
|
Bytes& U32(uint32_t value) { return U16(value & 0xFFFF).U16(value >> 16); }
|
||||||
|
Bytes& U64(uint64_t value) { return U32(value & 0xFFFFFFFF).U32(value >> 32); }
|
||||||
|
// A fixed buffer of `size` UTF-16 characters, zero filled
|
||||||
|
Bytes& WStr(const std::u16string& text, size_t size) {
|
||||||
|
for (size_t i = 0; i < size; i++) U16(i < text.size() ? text[i] : 0);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Bytes& Zone(uint16_t map, uint16_t instance, uint32_t clone) { return U16(map).U16(instance).U32(clone); }
|
||||||
|
// 0x53, connection type, packet id, padding
|
||||||
|
static Bytes Header(uint16_t connection, uint32_t id) { Bytes bytes; bytes.U8(0x53).U16(connection).U32(id).U8(0); return bytes; }
|
||||||
|
PacketBytes Packet() const { return { data, static_cast<uint32_t>(data.size() * 8) }; }
|
||||||
|
};
|
||||||
|
|
||||||
|
PacketBytes StructPacket(const LUBitStream& msg) {
|
||||||
|
RakNet::BitStream bitStream;
|
||||||
|
msg.WritePacket(bitStream);
|
||||||
|
return FromBitStream(bitStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint16_t CLIENT = 5;
|
||||||
|
constexpr uint16_t CHAT = 2;
|
||||||
|
constexpr uint16_t WORLD = 4;
|
||||||
|
constexpr LWOOBJID PLAYER = 0x1000000000000123LL;
|
||||||
|
constexpr LWOOBJID OTHER = 0x1000000000000456LL;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GuildPacketsTests : public GameDependenciesTest {
|
||||||
|
protected:
|
||||||
|
void SetUp() override { SetUpDependencies(); }
|
||||||
|
void TearDown() override { TearDownDependencies(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Server -> client. Offsets in the comments are the client's, from the byte after 0x53. ----
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, CreateResponse) {
|
||||||
|
ClientPackets::GuildCreateResponse response;
|
||||||
|
response.result = eGuildCreateResponse::CREATED;
|
||||||
|
response.guildID = 7;
|
||||||
|
response.guildName = "Brick Builders";
|
||||||
|
// +7 result, +8 u64, +16 wchar[31]: 78 bytes after 0x53
|
||||||
|
const auto expected = Bytes::Header(CLIENT, 37).U8(0).U64(7).WStr(u"Brick Builders", 31);
|
||||||
|
EXPECT_EQ(expected.data.size(), 79u);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(response));
|
||||||
|
const auto copy = RoundTrip(response);
|
||||||
|
EXPECT_EQ(copy.guildName, "Brick Builders");
|
||||||
|
ExpectTruncatedFails(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, NamesAlwaysKeepANul) {
|
||||||
|
ClientPackets::GuildCreateResponse response;
|
||||||
|
response.guildName = std::string(40, 'x');
|
||||||
|
const auto packet = StructPacket(response);
|
||||||
|
ASSERT_EQ(packet.bytes.size(), 79u);
|
||||||
|
// the last character of the 31 is 0
|
||||||
|
EXPECT_EQ(packet.bytes[77], 0);
|
||||||
|
EXPECT_EQ(packet.bytes[78], 0);
|
||||||
|
EXPECT_EQ(packet.bytes[75], 'x');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, Invite) {
|
||||||
|
ClientPackets::GuildInvite invite;
|
||||||
|
invite.inviterName = "Alice";
|
||||||
|
invite.guildName = "Brick Builders";
|
||||||
|
// +7 wchar[33] inviter, +73 wchar[31] guild
|
||||||
|
const auto expected = Bytes::Header(CLIENT, 39).WStr(u"Alice", 33).WStr(u"Brick Builders", 31);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(invite));
|
||||||
|
RoundTrip(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, InviteResponses) {
|
||||||
|
ClientPackets::GuildInviteInitialResponse initial;
|
||||||
|
initial.response = eGuildInviteResponse::INVITE_PENDING;
|
||||||
|
initial.playerName = "Bob";
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CLIENT, 40).U8(3).WStr(u"Bob", 33).Packet(), StructPacket(initial));
|
||||||
|
RoundTrip(initial);
|
||||||
|
|
||||||
|
ClientPackets::GuildInviteFinalResponse final;
|
||||||
|
final.response = eGuildInviteFinalResponse::DECLINED;
|
||||||
|
final.playerName = "Bob";
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CLIENT, 41).U8(1).WStr(u"Bob", 33).Packet(), StructPacket(final));
|
||||||
|
RoundTrip(final);
|
||||||
|
|
||||||
|
ClientPackets::GuildInviteConfirm confirm;
|
||||||
|
confirm.failed = true;
|
||||||
|
confirm.guildName = "Brick Builders";
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CLIENT, 42).U8(1).WStr(u"Brick Builders", 33).Packet(), StructPacket(confirm));
|
||||||
|
EXPECT_TRUE(RoundTrip(confirm).failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, AddPlayer) {
|
||||||
|
ClientPackets::GuildAddPlayer add;
|
||||||
|
add.playerName = "Bob";
|
||||||
|
add.playerID = OTHER;
|
||||||
|
add.rank = eGuildRank::RECRUIT;
|
||||||
|
add.zoneID = LWOZONEID(1200, 3, 0);
|
||||||
|
add.online = true;
|
||||||
|
// +7 name, +73 id, +81 rank, +82 zone, +90 online: 91 bytes after 0x53
|
||||||
|
const auto expected = Bytes::Header(CLIENT, 43).WStr(u"Bob", 33).U64(OTHER).U8(4).Zone(1200, 3, 0).U8(1);
|
||||||
|
EXPECT_EQ(expected.data.size(), 92u);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(add));
|
||||||
|
const auto copy = RoundTrip(add);
|
||||||
|
EXPECT_EQ(copy.zoneID, add.zoneID);
|
||||||
|
ExpectTruncatedFails(add);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, RemovePlayer) {
|
||||||
|
ClientPackets::GuildRemovePlayer remove;
|
||||||
|
remove.reason = eGuildLeaveReason::KICKED;
|
||||||
|
remove.playerName = "Bob";
|
||||||
|
remove.playerID = OTHER;
|
||||||
|
remove.newLeaderID = PLAYER;
|
||||||
|
// +7 reason, +8 name, +74 id, +82 new leader
|
||||||
|
const auto expected = Bytes::Header(CLIENT, 44).U8(1).WStr(u"Bob", 33).U64(OTHER).U64(PLAYER);
|
||||||
|
EXPECT_EQ(expected.data.size(), 91u);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(remove));
|
||||||
|
RoundTrip(remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, LoginLogout) {
|
||||||
|
ClientPackets::GuildLoginLogout login;
|
||||||
|
login.playerName = "Bob";
|
||||||
|
login.playerID = OTHER;
|
||||||
|
login.online = true;
|
||||||
|
login.zoneID = LWOZONEID(1100, 2, 0);
|
||||||
|
login.worldUpdateOnly = true;
|
||||||
|
// +7 name, +73 id, +81 online, +82 zone, +90 world update only
|
||||||
|
const auto expected = Bytes::Header(CLIENT, 45).WStr(u"Bob", 33).U64(OTHER).U8(1).Zone(1100, 2, 0).U8(1);
|
||||||
|
EXPECT_EQ(expected.data.size(), 92u);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(login));
|
||||||
|
RoundTrip(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, Data) {
|
||||||
|
ClientPackets::GuildData data;
|
||||||
|
data.guildName = "Brick Builders";
|
||||||
|
data.joinDate = "01/02/2026";
|
||||||
|
data.foundDate = "12/31/2025";
|
||||||
|
data.members.push_back({ eGuildRank::LEADER, true, LWOZONEID(1200, 1, 0), PLAYER, "Alice" });
|
||||||
|
data.members.push_back({ eGuildRank::RECRUIT, false, LWOZONEID(0, 0, 0), OTHER, "Bob" });
|
||||||
|
// +7 status, +8 name, +70 date, +92 date, +114 reputation, +118..127, +128 count, +130 members of 84 bytes
|
||||||
|
auto expected = Bytes::Header(CLIENT, 47).U8(0).WStr(u"Brick Builders", 31).WStr(u"01/02/2026", 11).WStr(u"12/31/2025", 11)
|
||||||
|
.U32(0).U32(0).U32(0).U16(0).U16(2);
|
||||||
|
EXPECT_EQ(expected.data.size(), 131u);
|
||||||
|
expected.U8(1).U8(1).Zone(1200, 1, 0).U64(PLAYER).WStr(u"Alice", 33);
|
||||||
|
expected.U8(4).U8(0).Zone(0, 0, 0).U64(OTHER).WStr(u"Bob", 33);
|
||||||
|
EXPECT_EQ(expected.data.size(), 131u + 2 * 84u);
|
||||||
|
EXPECT_PACKET_EQ(expected.Packet(), StructPacket(data));
|
||||||
|
const auto copy = RoundTrip(data);
|
||||||
|
ASSERT_EQ(copy.members.size(), 2u);
|
||||||
|
EXPECT_EQ(copy.members[0].name, "Alice");
|
||||||
|
EXPECT_EQ(copy.members[1].playerID, OTHER);
|
||||||
|
ExpectTruncatedFails(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Client -> server ----
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, TmpGuildCreateFromClient) {
|
||||||
|
// What SendTMPGuildCreate sends (69 bytes after 0x53): header, wchar[31]
|
||||||
|
const auto bytes = Bytes::Header(WORLD, 20).WStr(u"Brick Builders", 31);
|
||||||
|
RakNet::BitStream stream(const_cast<unsigned char*>(bytes.data.data()), bytes.data.size(), false);
|
||||||
|
LUBitStream header;
|
||||||
|
ASSERT_TRUE(header.ReadHeader(stream));
|
||||||
|
EXPECT_EQ(header.connectionType, ServiceType::WORLD);
|
||||||
|
EXPECT_EQ(header.internalPacketID, static_cast<uint32_t>(MessageType::World::TMP_GUILD_CREATE));
|
||||||
|
WorldPackets::TmpGuildCreate create;
|
||||||
|
ASSERT_TRUE(create.Deserialize(stream));
|
||||||
|
EXPECT_EQ(create.guildName, u"Brick Builders");
|
||||||
|
EXPECT_EQ(stream.GetNumberOfUnreadBits(), 0u);
|
||||||
|
EXPECT_PACKET_EQ(bytes.Packet(), StructPacket(create));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, RoutedFromClientToChat) {
|
||||||
|
// The client's GUILD_INVITE (81 bytes after 0x53): header, u64 0, wchar[33]; the world routes it to chat with the
|
||||||
|
// sender in front and the first 4 bytes of the data dropped
|
||||||
|
WorldPackets::RoutePacket route;
|
||||||
|
const auto routed = Bytes().U64(0).WStr(u"Bob", 33);
|
||||||
|
route.size = static_cast<uint32_t>(routed.data.size());
|
||||||
|
route.routedService = ServiceType::CHAT;
|
||||||
|
route.routedMessageID = static_cast<uint32_t>(MessageType::Chat::GUILD_INVITE);
|
||||||
|
route.routedData = routed.data;
|
||||||
|
RakNet::BitStream bitStream;
|
||||||
|
route.ToChat(PLAYER).WritePacket(bitStream);
|
||||||
|
bitStream.ResetReadPointer();
|
||||||
|
LUBitStream header;
|
||||||
|
ASSERT_TRUE(header.ReadHeader(bitStream));
|
||||||
|
EXPECT_EQ(header.internalPacketID, static_cast<uint32_t>(MessageType::Chat::GUILD_INVITE));
|
||||||
|
ChatPackets::GuildInvite invite;
|
||||||
|
ASSERT_TRUE(invite.Deserialize(bitStream));
|
||||||
|
EXPECT_EQ(invite.playerID, PLAYER);
|
||||||
|
EXPECT_EQ(invite.invitedPlayer.GetAsString(), "Bob");
|
||||||
|
|
||||||
|
// GUILD_INVITE_RESPONSE (16 bytes): u64 0, u8 declined
|
||||||
|
route.routedMessageID = static_cast<uint32_t>(MessageType::Chat::GUILD_INVITE_RESPONSE);
|
||||||
|
route.routedData = Bytes().U64(0).U8(1).data;
|
||||||
|
route.size = 9;
|
||||||
|
RakNet::BitStream responseStream;
|
||||||
|
route.ToChat(PLAYER).WritePacket(responseStream);
|
||||||
|
ASSERT_TRUE(header.ReadHeader(responseStream));
|
||||||
|
ChatPackets::GuildInviteResponse response;
|
||||||
|
ASSERT_TRUE(response.Deserialize(responseStream));
|
||||||
|
EXPECT_EQ(response.playerID, PLAYER);
|
||||||
|
EXPECT_EQ(response.declined, 1);
|
||||||
|
|
||||||
|
// GUILD_LEAVE: the client says it is 81 bytes long; everything after the u64 is ignored
|
||||||
|
route.routedMessageID = static_cast<uint32_t>(MessageType::Chat::GUILD_LEAVE);
|
||||||
|
route.routedData = Bytes().U64(0).WStr(u"garbage", 33).data;
|
||||||
|
route.size = 74;
|
||||||
|
RakNet::BitStream leaveStream;
|
||||||
|
route.ToChat(PLAYER).WritePacket(leaveStream);
|
||||||
|
ASSERT_TRUE(header.ReadHeader(leaveStream));
|
||||||
|
ChatPackets::GuildLeave leave;
|
||||||
|
ASSERT_TRUE(leave.Deserialize(leaveStream));
|
||||||
|
EXPECT_EQ(leave.playerID, PLAYER);
|
||||||
|
|
||||||
|
// GUILD_GET_ALL (15 bytes): u64 0
|
||||||
|
route.routedMessageID = static_cast<uint32_t>(MessageType::Chat::GUILD_GET_ALL);
|
||||||
|
route.routedData = Bytes().U64(0).data;
|
||||||
|
route.size = 8;
|
||||||
|
RakNet::BitStream getAllStream;
|
||||||
|
route.ToChat(PLAYER).WritePacket(getAllStream);
|
||||||
|
ASSERT_TRUE(header.ReadHeader(getAllStream));
|
||||||
|
ChatPackets::GuildGetAll getAll;
|
||||||
|
ASSERT_TRUE(getAll.Deserialize(getAllStream));
|
||||||
|
EXPECT_EQ(getAll.playerID, PLAYER);
|
||||||
|
EXPECT_EQ(getAllStream.GetNumberOfUnreadBits(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- DLU's world <-> chat packets ----
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, WorldChatPackets) {
|
||||||
|
ChatPackets::GuildCreate create;
|
||||||
|
create.playerID = PLAYER;
|
||||||
|
create.guildName = LUWString(u"Brick Builders", 31);
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CHAT, 22).U64(PLAYER).WStr(u"Brick Builders", 31).Packet(), StructPacket(create));
|
||||||
|
RoundTrip(create);
|
||||||
|
|
||||||
|
ChatPackets::GuildKick kick;
|
||||||
|
kick.playerID = PLAYER;
|
||||||
|
kick.kickedPlayer = LUWString(u"Bob", 33);
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CHAT, 26).U64(PLAYER).U32(0).WStr(u"Bob", 33).Packet(), StructPacket(kick));
|
||||||
|
RoundTrip(kick);
|
||||||
|
|
||||||
|
ChatPackets::GuildSetRank rank;
|
||||||
|
rank.playerID = PLAYER;
|
||||||
|
rank.targetPlayer = LUWString(u"Bob", 33);
|
||||||
|
rank.rank = 2;
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(MessageType::Chat::GUILD_SET_RANK), static_cast<uint32_t>(MessageType::Chat::CREATE_TEAM) + 1);
|
||||||
|
RoundTrip(rank);
|
||||||
|
|
||||||
|
ChatPackets::GuildDisband disband;
|
||||||
|
disband.playerID = PLAYER;
|
||||||
|
EXPECT_EQ(static_cast<uint32_t>(MessageType::Chat::GUILD_DISBAND), static_cast<uint32_t>(MessageType::Chat::CREATE_TEAM) + 2);
|
||||||
|
RoundTrip(disband);
|
||||||
|
|
||||||
|
ChatPackets::GuildStatus status;
|
||||||
|
status.characterID = PLAYER;
|
||||||
|
status.guildID = 7;
|
||||||
|
status.guildName = LUWString(u"Brick Builders", 31);
|
||||||
|
EXPECT_PACKET_EQ(Bytes::Header(CHAT, 27).U64(PLAYER).U64(7).WStr(u"Brick Builders", 31).Packet(), StructPacket(status));
|
||||||
|
RoundTrip(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Game message ----
|
||||||
|
|
||||||
|
TEST_F(GuildPacketsTests, DisplayGuildCreateBox) {
|
||||||
|
GameMessages::DisplayGuildCreateBox box;
|
||||||
|
box.target = PLAYER;
|
||||||
|
// header 53 05 00 0c 00 00 00 00, target, msgId 626 (u16), bShow bit
|
||||||
|
EXPECT_PACKET_EQ(FromHex("5305000c00000000" "2301000000000010" "7202" "80", 145), GameMessageTestUtils::StructPacket(box));
|
||||||
|
RoundTrip(box);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user