mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
refactor: chat packets as structs
Every packet of the chat service is now an LUBitStream struct in dNet/ChatPackets.h (docs/PacketArchitecture.md, PR 13), and nothing in the chat server or the chat side of the world server reads or writes a packet by hand any more. - World <-> chat: LoginSessionNotify, UnexpectedDisconnect, GMLevelUpdate, GMMute, Announcement (GM announce), CreateTeam, TeamUpdate (TEAM_GET_STATUS to worlds), AchievementNotify, ShowAllRequest, FindPlayerRequest. - Client -> world -> chat (friends, ignore list, teams, general and private chat): GetFriendsList, AddFriendRequest, AddFriendResponse, RemoveFriend, GetIgnoreList, AddIgnore, RemoveIgnore, GeneralChatMessage, PrivateChatMessage, TeamInvite, TeamInviteResponse, TeamLeave, TeamKick, TeamSetLeader, TeamSetLoot, TeamGetStatus. The 77 bytes the handlers skipped are named fields now (the sender block the client fills in); the 4 unused bytes after the player ID are kept as `unknown`. - What the client receives. Chat service (ChatPackets::Client): GeneralChatMessage (replaces SendChatMessage; SendSystemMessage stays as a helper built on it), PrivateChatMessage. Client service (ClientPackets, as structs go in the file of the ServiceType in their header): SendCannedText (replaces SendMessageFail), GetFriendsListResponse, AddFriendRequest, AddFriendResponse, RemoveFriendResponse, UpdateFriendNotify, WhoResponse, ShowAllResponse, Get/Add/RemoveIgnoreResponse, TeamInvite, TeamInviteInitialResponse, and the team game messages chat writes (TeamInviteConfirm, TeamGetStatusResponse, TeamSetLeader, TeamAddPlayer, TeamRemovePlayer, TeamSetOffWorldFlag) as TeamGameMsg structs, since chat doesn't link dGame's NetGameMsg. - WORLD_ROUTE_PACKET is ChatPackets::WorldRoutePacket (its own file, dNet/WorldRoutePacket.h, since it carries packets of other services): the target and the inner packet. ChatPacketHandler::SendRouted replaces the per-function route headers and SendRoutedMsg. Dispatch: dNet/PacketDispatcher.h is a dispatch map from packet ID to (struct, handler function); packets that fail to Deserialize are logged and dropped. The chat server's switch and the world's HandlePacketChat switch are now maps. The handlers take the structs; their logic is unchanged. World code sends to chat with ChatServerLink::Send (dGame) instead of writing to Game::chatServer by hand. eChatChannel, eChatMessageResponseCode and eAddIgnoreResponse moved to dCommon/dEnums so the structs can use them. Verified: tests/dGameTests/dNetTests/Legacy/ChatPacketsLegacy.h is a verbatim copy of the old senders and readers; ChatPacketsTests (25 tests) sends a grid of inputs through both and requires identical bits, bytes and destination, checks the old readers read what the structs write, round trips every struct, checks truncated packets are refused, and has hand written golden packets for general chat, canned text, GM mute and a routed team game message. Breaking one field width (UpdateFriendNotify) and the TeamAddPlayer zone flag made the tests fail. No wire bytes changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
SET(DNET_TESTS
|
||||
"ChatPacketsTests.cpp"
|
||||
"CommonAuthPacketsTests.cpp"
|
||||
"WorldPacketsTests.cpp")
|
||||
|
||||
|
||||
900
tests/dGameTests/dNetTests/ChatPacketsTests.cpp
Normal file
900
tests/dGameTests/dNetTests/ChatPacketsTests.cpp
Normal file
@@ -0,0 +1,900 @@
|
||||
#include "ChatPackets.h"
|
||||
#include "ClientPackets.h"
|
||||
#include "WorldRoutePacket.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "PacketTestUtils.h"
|
||||
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
||||
#include "Legacy/ChatPacketsLegacy.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace PacketTestUtils;
|
||||
using GameMessageTestUtils::RoundTrip;
|
||||
using GameMessageTestUtils::ExpectTruncatedFails;
|
||||
|
||||
namespace {
|
||||
SystemAddress WorldAddress() {
|
||||
SystemAddress address;
|
||||
address.binaryAddress = 0x0100007f;
|
||||
address.port = 3000;
|
||||
return address;
|
||||
}
|
||||
|
||||
const std::array<LWOOBJID, 3> g_Ids = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL };
|
||||
const std::array<std::string, 4> g_Names = { "", "Bob", "A name with 33 characters in it!!", "Th\xc3\xa9o" };
|
||||
const std::array<std::u16string, 4> g_Messages = { u"", u"hello", u"café ☃", std::u16string(300, u'x') };
|
||||
const std::array<LWOZONEID, 3> g_Zones = { LWOZONEID(), LWOZONEID(1100, 3, 0), LWOZONEID(1150, 65535, 0xDEADBEEF) };
|
||||
|
||||
PacketBytes StructPacket(const LUBitStream& msg) {
|
||||
RakNet::BitStream bitStream;
|
||||
msg.WritePacket(bitStream);
|
||||
return FromBitStream(bitStream);
|
||||
}
|
||||
|
||||
// What ChatPacketHandler::SendRouted puts on the wire
|
||||
PacketBytes Routed(LWOOBJID target, const LUBitStream& msg) {
|
||||
ChatPackets::WorldRoutePacket route;
|
||||
route.targetID = target;
|
||||
route.routed = &msg;
|
||||
return StructPacket(route);
|
||||
}
|
||||
|
||||
// The one packet the legacy function sent
|
||||
CapturedPacket LegacyPacket(const std::function<void()>& legacySend) {
|
||||
const auto packets = Capture(legacySend);
|
||||
EXPECT_EQ(packets.size(), 1);
|
||||
return packets.empty() ? CapturedPacket{} : packets[0];
|
||||
}
|
||||
|
||||
PacketBytes Written(const std::function<void(RakNet::BitStream&)>& write) {
|
||||
RakNet::BitStream bitStream;
|
||||
write(bitStream);
|
||||
return FromBitStream(bitStream);
|
||||
}
|
||||
|
||||
// A stream positioned after the header of msg's packet
|
||||
void LoadPayload(RakNet::BitStream& bitStream, const LUBitStream& msg) {
|
||||
msg.WritePacket(bitStream);
|
||||
bitStream.IgnoreBytes(8);
|
||||
}
|
||||
|
||||
PlayerData MakePlayer(LWOOBJID id, const std::string& name, LWOZONEID zone, eGameMasterLevel gmLevel = eGameMasterLevel::CIVILIAN) {
|
||||
PlayerData player;
|
||||
player.playerID = id;
|
||||
player.playerName = name;
|
||||
player.zoneID = zone;
|
||||
player.gmLevel = gmLevel;
|
||||
player.worldServerSysAddr = WorldAddress();
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
class ChatPacketsTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
// Client-facing: general chat and system messages
|
||||
|
||||
TEST_F(ChatPacketsTests, GeneralChatMessageMatchesLegacySendChatMessage) {
|
||||
for (const uint8_t channel : { 0, 4, 12, 255 }) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const auto id : g_Ids) {
|
||||
for (const auto& message : g_Messages) {
|
||||
for (const bool mythran : { false, true }) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendChatMessage(WorldAddress(), static_cast<char>(channel), name, id, mythran, message); });
|
||||
|
||||
ChatPackets::Client::GeneralChatMessage chatMessage;
|
||||
chatMessage.chatChannel = channel;
|
||||
chatMessage.senderName = LUWString(name);
|
||||
chatMessage.senderID = id;
|
||||
chatMessage.message = message;
|
||||
const auto sent = LegacyPacket([&] { chatMessage.Broadcast(); });
|
||||
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
EXPECT_EQ(legacy.broadcast, sent.broadcast);
|
||||
EXPECT_EQ(legacy.sysAddr, sent.sysAddr);
|
||||
|
||||
const auto copy = RoundTrip(chatMessage);
|
||||
EXPECT_EQ(copy.message, message);
|
||||
EXPECT_EQ(copy.senderID, id);
|
||||
EXPECT_EQ(copy.chatChannel, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, SendSystemMessageMatchesLegacy) {
|
||||
for (const auto& address : { WorldAddress(), UNASSIGNED_SYSTEM_ADDRESS }) {
|
||||
for (const bool broadcast : { false, true }) {
|
||||
for (const auto& message : g_Messages) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendSystemMessage(address, message, broadcast); });
|
||||
const auto sent = LegacyPacket([&] { ChatPackets::SendSystemMessage(address, message, broadcast); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
EXPECT_EQ(legacy.broadcast, sent.broadcast);
|
||||
EXPECT_EQ(legacy.sysAddr, sent.sysAddr);
|
||||
}
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendSystemMessage(address, std::string("ascii text"), broadcast); });
|
||||
const auto sent = LegacyPacket([&] { ChatPackets::SendSystemMessage(address, std::string("ascii text"), broadcast); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, GeneralChatMessageGolden) {
|
||||
ChatPackets::Client::GeneralChatMessage chatMessage;
|
||||
chatMessage.chatChannel = 4;
|
||||
chatMessage.senderName = LUWString("", 33);
|
||||
chatMessage.message = u"hi";
|
||||
const auto packet = StructPacket(chatMessage);
|
||||
// 53 | CHAT (0x0002) | GENERAL_CHAT_MESSAGE (1) | pad | u64 0 | channel 4 | u32 length 2 | 66 zero bytes of name |
|
||||
// u64 sender 0 | u16 source 0 | u8 gm level 0 | 'h' 'i' | u16 0
|
||||
std::string hex = "53 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 04 02 00 00 00";
|
||||
for (int i = 0; i < 66; i++) hex += " 00";
|
||||
hex += " 00 00 00 00 00 00 00 00 00 00 00 68 00 69 00 00 00";
|
||||
EXPECT_PACKET_EQ(FromHex(hex), packet);
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, SendCannedTextMatchesLegacySendMessageFail) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendMessageFail(WorldAddress()); });
|
||||
ClientPackets::SendCannedText cannedText;
|
||||
const auto sent = LegacyPacket([&] { cannedText.Send(WorldAddress()); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
EXPECT_EQ(legacy.broadcast, sent.broadcast);
|
||||
EXPECT_EQ(legacy.sysAddr, sent.sysAddr);
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 35 00 00 00 00 00"), StructPacket(cannedText));
|
||||
RoundTrip(cannedText);
|
||||
}
|
||||
|
||||
// Routed packets from the chat server
|
||||
|
||||
TEST_F(ChatPacketsTests, WorldRoutePacketMatchesLegacySendRoutedMsg) {
|
||||
for (const auto target : g_Ids) {
|
||||
for (const bool failed : { false, true }) {
|
||||
ClientPackets::TeamInviteInitialResponse response;
|
||||
response.inviteFailedToSend = failed;
|
||||
response.playerName = LUWString("Bob");
|
||||
|
||||
RakNet::BitStream inner;
|
||||
LegacyChat::WriteTeamInviteInitialResponse(inner, failed, LUWString("Bob"));
|
||||
EXPECT_PACKET_EQ(FromBitStream(inner), StructPacket(response));
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendRoutedMsg(inner, target, WorldAddress()); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(target, response));
|
||||
EXPECT_FALSE(legacy.broadcast);
|
||||
|
||||
const auto copy = RoundTrip(response);
|
||||
EXPECT_EQ(copy.inviteFailedToSend, failed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, WorldRoutePacketGoldenAndWorldForwarding) {
|
||||
ClientPackets::TeamSetLeader msg;
|
||||
msg.target = 0x0102030405060708LL;
|
||||
msg.i64PlayerID = 0x1112131415161718LL;
|
||||
const auto packet = Routed(0x0102030405060708LL, msg);
|
||||
// WORLD_ROUTE_PACKET (0x30) | target | CLIENT GAME_MSG (0x0c) | target | TEAM_SET_LEADER (1557 = 0x0615) | player
|
||||
EXPECT_PACKET_EQ(FromHex(
|
||||
"53 02 00 30 00 00 00 00 08 07 06 05 04 03 02 01 "
|
||||
"53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 15 06 18 17 16 15 14 13 12 11"), packet);
|
||||
|
||||
// The world reads the target and passes everything after it on unchanged
|
||||
RakNet::BitStream bitStream;
|
||||
bitStream.WriteBits(packet.bytes.data(), packet.bits);
|
||||
RakNet::BitStream legacyStream;
|
||||
legacyStream.WriteBits(packet.bytes.data(), packet.bits);
|
||||
bitStream.IgnoreBytes(8);
|
||||
legacyStream.IgnoreBytes(8);
|
||||
|
||||
ChatPackets::WorldRoutePacket route;
|
||||
ASSERT_TRUE(route.Deserialize(bitStream));
|
||||
const auto [legacyTarget, legacyBytes] = LegacyChat::ReadWorldRoute(legacyStream);
|
||||
EXPECT_EQ(route.targetID, legacyTarget);
|
||||
EXPECT_EQ(route.routedData, legacyBytes);
|
||||
EXPECT_EQ(route.routedData, StructPacket(msg).bytes);
|
||||
|
||||
// And serializing what was read gives the same packet
|
||||
EXPECT_PACKET_EQ(packet, StructPacket(route));
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, GetFriendsListResponseMatchesLegacy) {
|
||||
for (const auto target : g_Ids) {
|
||||
for (size_t count = 0; count <= 3; count++) {
|
||||
PlayerData player = MakePlayer(target, "Me", g_Zones[1]);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
FriendData data;
|
||||
data.isOnline = i % 2 == 0;
|
||||
data.isBestFriend = i == 1;
|
||||
data.isFTP = i == 2;
|
||||
data.zoneID = g_Zones[i % g_Zones.size()];
|
||||
data.friendID = g_Ids[i % g_Ids.size()];
|
||||
data.friendName = g_Names[i % 3]; // names of up to 33 characters
|
||||
player.friends.push_back(data);
|
||||
}
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendFriendsList(target, player); });
|
||||
ClientPackets::GetFriendsListResponse response;
|
||||
response.friends = player.friends;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(target, response));
|
||||
EXPECT_EQ(legacy.sysAddr, WorldAddress());
|
||||
EXPECT_FALSE(legacy.broadcast);
|
||||
|
||||
const auto copy = RoundTrip(response);
|
||||
ASSERT_EQ(copy.friends.size(), count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
EXPECT_EQ(copy.friends[i].friendName, player.friends[i].friendName);
|
||||
EXPECT_EQ(copy.friends[i].zoneID, player.friends[i].zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, WhoResponseMatchesLegacy) {
|
||||
for (const auto requestor : g_Ids) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const bool online : { false, true }) {
|
||||
LegacyChat::FindPlayerRequest legacyRequest{ requestor, LUWString(name) };
|
||||
const PlayerData sender = MakePlayer(requestor, "Asker", g_Zones[1]);
|
||||
const PlayerData player = online ? MakePlayer(0x55, name, g_Zones[2]) : PlayerData();
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendWhoResponse(legacyRequest, sender, player); });
|
||||
ClientPackets::WhoResponse response;
|
||||
response.isOnline = static_cast<bool>(player);
|
||||
response.zoneID = player.zoneID;
|
||||
response.playerName = LUWString(name);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(requestor, response));
|
||||
RoundTrip(response);
|
||||
ExpectTruncatedFails(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, ShowAllResponseMatchesLegacy) {
|
||||
std::map<LWOOBJID, PlayerData> players;
|
||||
players[LWOOBJID_EMPTY] = PlayerData(); // the container's empty entry is skipped
|
||||
players[1] = MakePlayer(1, "One", g_Zones[1]);
|
||||
players[2] = MakePlayer(2, "Two", g_Zones[2]);
|
||||
players[3] = MakePlayer(3, "", g_Zones[0]);
|
||||
|
||||
for (const bool zoneData : { false, true }) {
|
||||
for (const bool individualPlayers : { false, true }) {
|
||||
for (const uint32_t playerCount : { 0u, 3u, 0xFFFFFFFFu }) {
|
||||
LegacyChat::ShowAllRequest legacyRequest{ 0x42, zoneData, individualPlayers };
|
||||
const PlayerData sender = MakePlayer(0x42, "Asker", g_Zones[1]);
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendShowAllResponse(legacyRequest, sender, playerCount, 7, players); });
|
||||
|
||||
ClientPackets::ShowAllResponse response;
|
||||
response.playerCount = playerCount;
|
||||
response.simCount = 7;
|
||||
response.displayIndividualPlayers = individualPlayers;
|
||||
response.displayZoneData = zoneData;
|
||||
if (zoneData || individualPlayers) {
|
||||
for (auto& [playerID, playerData] : players) {
|
||||
if (!playerData) continue;
|
||||
response.players.push_back({ playerData.playerName, playerData.zoneID });
|
||||
}
|
||||
}
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(0x42, response));
|
||||
|
||||
const auto copy = RoundTrip(response);
|
||||
EXPECT_EQ(copy.players.size(), response.players.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, PrivateChatMessageMatchesLegacy) {
|
||||
for (const auto& senderName : g_Names) {
|
||||
for (const auto gmLevel : { eGameMasterLevel::CIVILIAN, eGameMasterLevel::OPERATOR }) {
|
||||
for (const auto responseCode : { eChatMessageResponseCode::SENT, eChatMessageResponseCode::NOTFRIENDS, eChatMessageResponseCode::RECEIVERFREETRIAL }) {
|
||||
for (const auto channel : { eChatChannel::PRIVATE_CHAT, eChatChannel::TEAM, eChatChannel::GENERAL }) {
|
||||
for (const uint32_t size : { 0u, 5u, 40u }) {
|
||||
const PlayerData sender = MakePlayer(0x0102030405060708LL, senderName, g_Zones[1], gmLevel);
|
||||
const PlayerData receiver = MakePlayer(0x77, "Receiver", g_Zones[2], eGameMasterLevel::DEVELOPER);
|
||||
LUWString message(u"hello", size);
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendPrivateChatMessage(sender, receiver, receiver, message, channel, responseCode); });
|
||||
|
||||
ChatPackets::Client::PrivateChatMessage chatMessage;
|
||||
chatMessage.playerID = sender.playerID;
|
||||
chatMessage.chatChannel = channel;
|
||||
chatMessage.senderName = LUWString(sender.playerName);
|
||||
chatMessage.senderID = sender.playerID;
|
||||
chatMessage.senderGMLevel = sender.gmLevel;
|
||||
chatMessage.receiverName = LUWString(receiver.playerName);
|
||||
chatMessage.receiverGMLevel = receiver.gmLevel;
|
||||
chatMessage.responseCode = responseCode;
|
||||
chatMessage.message = message;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, chatMessage));
|
||||
|
||||
const auto copy = RoundTrip(chatMessage);
|
||||
EXPECT_EQ(copy.message.size, size);
|
||||
EXPECT_EQ(copy.responseCode, responseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, UpdateFriendNotifyMatchesLegacy) {
|
||||
for (const auto& zone : g_Zones) {
|
||||
for (const auto& friendZone : g_Zones) {
|
||||
for (const uint8_t notifyType : { 0, 1, 2 }) {
|
||||
for (const uint8_t isBestFriend : { 0, 1 }) {
|
||||
const PlayerData friendData = MakePlayer(0x10, "Friend", friendZone);
|
||||
const PlayerData playerData = MakePlayer(0x20, "Player", zone);
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendFriendUpdate(friendData, playerData, notifyType, isBestFriend); });
|
||||
|
||||
// As ChatPacketHandler::SendFriendUpdate fills it in
|
||||
ClientPackets::UpdateFriendNotify notify;
|
||||
notify.notifyType = notifyType;
|
||||
notify.friendName = LUWString(playerData.playerName);
|
||||
const LWOCLONEID cloneID = zone.GetCloneID() == friendData.zoneID.GetCloneID() ? 0 : zone.GetCloneID();
|
||||
notify.zoneID = LWOZONEID(zone.GetMapID(), zone.GetInstanceID(), cloneID);
|
||||
notify.isBestFriend = isBestFriend;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(friendData.playerID, notify));
|
||||
RoundTrip(notify);
|
||||
ExpectTruncatedFails(notify);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, AddFriendRequestAndResponseMatchLegacy) {
|
||||
for (const auto& name : g_Names) {
|
||||
const PlayerData receiver = MakePlayer(0x10, "Receiver", g_Zones[1]);
|
||||
PlayerData sender = MakePlayer(0x20, name, g_Zones[2]);
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendFriendRequest(receiver, sender); });
|
||||
ClientPackets::AddFriendRequest request;
|
||||
request.requestorName = LUWString(sender.playerName);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, request));
|
||||
RoundTrip(request);
|
||||
|
||||
for (int code = 0; code <= static_cast<int>(eAddFriendResponseType::FRIENDISFREETRIAL); code++) {
|
||||
const auto responseCode = static_cast<eAddFriendResponseType>(code);
|
||||
for (const bool senderOnline : { false, true }) {
|
||||
for (const uint8_t alreadyBest : { 0, 1 }) {
|
||||
for (const uint8_t bestRequest : { 0, 1 }) {
|
||||
sender.worldServerSysAddr = senderOnline ? WorldAddress() : UNASSIGNED_SYSTEM_ADDRESS;
|
||||
const auto legacyResponse = LegacyPacket([&] { LegacyChat::SendFriendResponse(receiver, sender, responseCode, alreadyBest, bestRequest); });
|
||||
|
||||
// As ChatPacketHandler::SendFriendResponse fills it in
|
||||
ClientPackets::AddFriendResponse response;
|
||||
response.responseCode = responseCode;
|
||||
response.isOnlineOrBestFriend = responseCode != eAddFriendResponseType::ACCEPTED ? alreadyBest : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS;
|
||||
response.friendName = LUWString(sender.playerName);
|
||||
response.friendID = sender.playerID;
|
||||
response.zoneID = sender.zoneID;
|
||||
response.isBestFriend = bestRequest;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyResponse), Routed(receiver.playerID, response));
|
||||
RoundTrip(response);
|
||||
ExpectTruncatedFails(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, RemoveFriendResponseMatchesLegacy) {
|
||||
for (auto name : g_Names) {
|
||||
for (const bool success : { false, true }) {
|
||||
const PlayerData receiver = MakePlayer(0x10, "Receiver", g_Zones[1]);
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendRemoveFriend(receiver, name, success); });
|
||||
ClientPackets::RemoveFriendResponse response;
|
||||
response.isSuccessful = success;
|
||||
response.friendName = LUWString(name);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, response));
|
||||
RoundTrip(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, IgnoreListResponsesMatchLegacy) {
|
||||
for (size_t count = 0; count <= 3; count++) {
|
||||
PlayerData receiver = MakePlayer(0x10, "Receiver", g_Zones[1]);
|
||||
for (size_t i = 0; i < count; i++) receiver.ignoredPlayers.emplace_back(g_Names[i], g_Ids[i]);
|
||||
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendIgnoreList(receiver, WorldAddress()); });
|
||||
ClientPackets::GetIgnoreListResponse response;
|
||||
for (const auto& ignoredPlayer : receiver.ignoredPlayers) {
|
||||
response.ignored.push_back({ ignoredPlayer.playerId, LUWString(ignoredPlayer.playerName, 36) });
|
||||
}
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, response));
|
||||
EXPECT_EQ(legacy.sysAddr, WorldAddress());
|
||||
EXPECT_FALSE(legacy.broadcast);
|
||||
RoundTrip(response);
|
||||
}
|
||||
|
||||
const PlayerData receiver = MakePlayer(0x10, "Receiver", g_Zones[1]);
|
||||
for (int code = 0; code <= 3; code++) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const auto id : g_Ids) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendAddIgnoreResponse(receiver, static_cast<LegacyChat::AddResponse>(code), name, id, WorldAddress()); });
|
||||
ClientPackets::AddIgnoreResponse response;
|
||||
response.responseCode = static_cast<eAddIgnoreResponse>(code);
|
||||
response.playerName = LUWString(name, 33);
|
||||
response.playerID = id;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, response));
|
||||
RoundTrip(response);
|
||||
ExpectTruncatedFails(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& name : g_Names) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendRemoveIgnoreResponse(receiver, name, WorldAddress()); });
|
||||
ClientPackets::RemoveIgnoreResponse response;
|
||||
response.playerName = LUWString(name, 33);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, response));
|
||||
RoundTrip(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Team packets and team game messages
|
||||
|
||||
TEST_F(ChatPacketsTests, TeamInviteMatchesLegacy) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const auto id : g_Ids) {
|
||||
const PlayerData receiver = MakePlayer(0x10, "Receiver", g_Zones[1]);
|
||||
const PlayerData sender = MakePlayer(id, name, g_Zones[2]);
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendTeamInvite(receiver, sender); });
|
||||
ClientPackets::TeamInvite invite;
|
||||
invite.senderName = LUWString(sender.playerName.c_str());
|
||||
invite.senderID = sender.playerID;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, invite));
|
||||
RoundTrip(invite);
|
||||
ExpectTruncatedFails(invite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, TeamGameMessagesMatchLegacy) {
|
||||
const std::array<std::u16string, 3> names = { u"", u"Leader", u"Léader with a much longer name than usual" };
|
||||
for (const auto receiverId : g_Ids) {
|
||||
for (const auto& zone : g_Zones) {
|
||||
const PlayerData receiver = MakePlayer(receiverId, "Receiver", g_Zones[1]);
|
||||
for (const auto& name : names) {
|
||||
for (const bool flag : { false, true }) {
|
||||
for (const uint8_t byte : { 0, 1, 255 }) {
|
||||
{
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendTeamInviteConfirm(receiver, flag, 0x42, zone, byte, byte, byte, name); });
|
||||
ClientPackets::TeamInviteConfirm msg;
|
||||
msg.target = receiver.playerID;
|
||||
msg.bLeaderIsFreeTrial = flag;
|
||||
msg.i64LeaderID = 0x42;
|
||||
msg.i64LeaderZoneID = zone;
|
||||
msg.ucLootFlag = byte;
|
||||
msg.ucNumOfOtherPlayers = byte;
|
||||
msg.ucResponseCode = byte;
|
||||
msg.wsLeaderName = name;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, msg));
|
||||
RoundTrip(msg);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
{
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendTeamStatus(receiver, 0x42, zone, byte, byte, name); });
|
||||
ClientPackets::TeamGetStatusResponse msg;
|
||||
msg.target = receiver.playerID;
|
||||
msg.i64LeaderID = 0x42;
|
||||
msg.i64LeaderZoneID = zone;
|
||||
msg.ucLootFlag = byte;
|
||||
msg.ucNumOfOtherPlayers = byte;
|
||||
msg.wsLeaderName = name;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, msg));
|
||||
RoundTrip(msg);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
}
|
||||
for (const bool local : { false, true }) {
|
||||
for (const bool noLoot : { false, true }) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendTeamAddPlayer(receiver, flag, local, noLoot, 0x42, name, zone); });
|
||||
// As TeamContainer::SendTeamAddPlayer fills it in
|
||||
ClientPackets::TeamAddPlayer msg;
|
||||
msg.target = receiver.playerID;
|
||||
msg.bIsFreeTrial = flag;
|
||||
msg.bLocal = local;
|
||||
msg.bNoLootOnDeath = noLoot;
|
||||
msg.i64PlayerID = 0x42;
|
||||
msg.wsPlayerName = name;
|
||||
msg.zoneID = receiver.zoneID.GetCloneID() == zone.GetCloneID() ? LWOZONEID(zone.GetMapID(), zone.GetInstanceID(), 0) : zone;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, msg));
|
||||
RoundTrip(msg);
|
||||
ExpectTruncatedFails(msg);
|
||||
|
||||
for (const bool leaving : { false, true }) {
|
||||
const auto legacyRemove = LegacyPacket([&] { LegacyChat::SendTeamRemovePlayer(receiver, flag, noLoot, leaving, local, 0x99, 0x42, name); });
|
||||
ClientPackets::TeamRemovePlayer remove;
|
||||
remove.target = receiver.playerID;
|
||||
remove.bDisband = flag;
|
||||
remove.bIsKicked = noLoot;
|
||||
remove.bIsLeaving = leaving;
|
||||
remove.bLocal = local;
|
||||
remove.i64LeaderID = 0x99;
|
||||
remove.i64PlayerID = 0x42;
|
||||
remove.wsPlayerName = name;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyRemove), Routed(receiver.playerID, remove));
|
||||
RoundTrip(remove);
|
||||
ExpectTruncatedFails(remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto playerId : g_Ids) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::SendTeamSetLeader(receiver, playerId); });
|
||||
ClientPackets::TeamSetLeader msg;
|
||||
msg.target = receiver.playerID;
|
||||
msg.i64PlayerID = playerId;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), Routed(receiver.playerID, msg));
|
||||
RoundTrip(msg);
|
||||
ExpectTruncatedFails(msg);
|
||||
|
||||
const auto legacyFlag = LegacyPacket([&] { LegacyChat::SendTeamSetOffWorldFlag(receiver, playerId, zone); });
|
||||
// As TeamContainer::SendTeamSetOffWorldFlag fills it in
|
||||
ClientPackets::TeamSetOffWorldFlag flagMsg;
|
||||
flagMsg.target = receiver.playerID;
|
||||
flagMsg.i64PlayerID = playerId;
|
||||
flagMsg.zoneID = receiver.zoneID.GetCloneID() == zone.GetCloneID() ? LWOZONEID(zone.GetMapID(), zone.GetInstanceID(), 0) : zone;
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyFlag), Routed(receiver.playerID, flagMsg));
|
||||
RoundTrip(flagMsg);
|
||||
ExpectTruncatedFails(flagMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, TeamUpdateMatchesLegacyAndWorldReader) {
|
||||
for (const bool deleteTeam : { false, true }) {
|
||||
for (size_t count = 0; count <= 4; count++) {
|
||||
const std::vector<LWOOBJID> members(g_Ids.begin(), g_Ids.begin() + std::min<size_t>(count, g_Ids.size()));
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::UpdateTeamsOnWorld(0x1234, 1, members, deleteTeam); });
|
||||
ChatPackets::TeamUpdate update;
|
||||
update.teamID = 0x1234;
|
||||
update.deleteTeam = deleteTeam;
|
||||
if (!deleteTeam) {
|
||||
update.lootFlag = 1;
|
||||
update.members = members;
|
||||
}
|
||||
const auto sent = LegacyPacket([&] { update.Broadcast(); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
EXPECT_TRUE(sent.broadcast);
|
||||
|
||||
RakNet::BitStream stream; LoadPayload(stream, update);
|
||||
const auto read = LegacyChat::ReadTeamStatus(stream);
|
||||
const auto copy = RoundTrip(update);
|
||||
EXPECT_EQ(read.teamID, copy.teamID);
|
||||
EXPECT_EQ(read.deleteTeam, copy.deleteTeam);
|
||||
EXPECT_EQ(static_cast<uint8_t>(read.lootOption), copy.lootFlag);
|
||||
EXPECT_EQ(read.members, copy.members);
|
||||
ExpectTruncatedFails(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, GMMuteMatchesLegacy) {
|
||||
for (const auto id : g_Ids) {
|
||||
for (const time_t expire : { static_cast<time_t>(0), static_cast<time_t>(1), static_cast<time_t>(1893456000), std::numeric_limits<time_t>::max() }) {
|
||||
const auto legacy = LegacyPacket([&] { LegacyChat::BroadcastMuteUpdate(id, expire); });
|
||||
ChatPackets::GMMute mute;
|
||||
mute.playerID = id;
|
||||
mute.expire = expire;
|
||||
const auto sent = LegacyPacket([&] { mute.Broadcast(); });
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy), FromCapture(sent));
|
||||
EXPECT_TRUE(sent.broadcast);
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteGMMute(b, id, expire); }), StructPacket(mute));
|
||||
RoundTrip(mute);
|
||||
ExpectTruncatedFails(mute);
|
||||
}
|
||||
}
|
||||
ChatPackets::GMMute mute;
|
||||
mute.playerID = 0x0102030405060708LL;
|
||||
mute.expire = 1;
|
||||
EXPECT_PACKET_EQ(FromHex("53 02 00 2e 00 00 00 00 08 07 06 05 04 03 02 01 01 00 00 00 00 00 00 00"), StructPacket(mute));
|
||||
}
|
||||
|
||||
// World -> chat
|
||||
|
||||
TEST_F(ChatPacketsTests, WorldToChatMatchesLegacy) {
|
||||
for (const auto id : g_Ids) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const auto& zone : g_Zones) {
|
||||
for (const auto gmLevel : { eGameMasterLevel::CIVILIAN, eGameMasterLevel::OPERATOR }) {
|
||||
ChatPackets::LoginSessionNotify notify;
|
||||
notify.playerID = id;
|
||||
notify.playerName = name;
|
||||
notify.zoneID = zone;
|
||||
notify.muteExpire = 1893456000;
|
||||
notify.gmLevel = gmLevel;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteLoginSessionNotify(b, id, name, zone, 1893456000, gmLevel); }), StructPacket(notify));
|
||||
const auto copy = RoundTrip(notify);
|
||||
EXPECT_EQ(copy.playerName, name);
|
||||
ExpectTruncatedFails(notify);
|
||||
|
||||
// What PlayerContainer::InsertPlayer read
|
||||
RakNet::BitStream stream; LoadPayload(stream, notify);
|
||||
PlayerData data;
|
||||
ASSERT_TRUE(LegacyChat::ReadLoginSessionNotify(stream, data));
|
||||
EXPECT_EQ(data.playerID, copy.playerID);
|
||||
EXPECT_EQ(data.playerName, copy.playerName);
|
||||
EXPECT_EQ(data.zoneID, copy.zoneID);
|
||||
EXPECT_EQ(data.muteExpire, copy.muteExpire);
|
||||
EXPECT_EQ(data.gmLevel, copy.gmLevel);
|
||||
|
||||
ChatPackets::GMLevelUpdate update;
|
||||
update.playerID = id;
|
||||
update.gmLevel = gmLevel;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteGMLevelUpdate(b, id, gmLevel); }), StructPacket(update));
|
||||
RoundTrip(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatPackets::UnexpectedDisconnect disconnect;
|
||||
disconnect.playerID = id;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteUnexpectedDisconnect(b, id); }), StructPacket(disconnect));
|
||||
RoundTrip(disconnect);
|
||||
ExpectTruncatedFails(disconnect);
|
||||
}
|
||||
|
||||
// Names longer than 33 characters were refused
|
||||
ChatPackets::LoginSessionNotify tooLong;
|
||||
tooLong.playerName = std::string(34, 'x');
|
||||
RakNet::BitStream stream; LoadPayload(stream, tooLong);
|
||||
ChatPackets::LoginSessionNotify read;
|
||||
EXPECT_FALSE(read.Deserialize(stream));
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, CreateTeamMatchesLegacy) {
|
||||
for (size_t count = 0; count <= 3; count++) {
|
||||
for (const auto& zone : g_Zones) {
|
||||
const std::vector<LWOOBJID> members(g_Ids.begin(), g_Ids.begin() + count);
|
||||
ChatPackets::CreateTeam createTeam;
|
||||
createTeam.leaderID = 0x42;
|
||||
createTeam.members = members;
|
||||
createTeam.zoneID = zone;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteCreateTeam(b, 0x42, members, zone); }), StructPacket(createTeam));
|
||||
|
||||
RakNet::BitStream stream; LoadPayload(stream, createTeam);
|
||||
const auto read = LegacyChat::ReadCreateTeam(stream);
|
||||
const auto copy = RoundTrip(createTeam);
|
||||
EXPECT_EQ(read.playerID, copy.leaderID);
|
||||
EXPECT_EQ(read.members, copy.members);
|
||||
EXPECT_EQ(read.zoneId, copy.zoneID);
|
||||
ExpectTruncatedFails(createTeam);
|
||||
}
|
||||
}
|
||||
// 4 or more players can't be a team (CreateTeamServer refused them)
|
||||
ChatPackets::CreateTeam tooBig;
|
||||
tooBig.members = { 1, 2, 3, 4 };
|
||||
RakNet::BitStream stream; LoadPayload(stream, tooBig);
|
||||
ChatPackets::CreateTeam read;
|
||||
EXPECT_FALSE(read.Deserialize(stream));
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, AnnouncementMatchesLegacy) {
|
||||
for (const auto& title : g_Names) {
|
||||
for (const auto& message : g_Names) {
|
||||
ChatPackets::Announcement announcement;
|
||||
announcement.title = title;
|
||||
announcement.message = message;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteGMAnnounce(b, title, message); }), StructPacket(announcement));
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyChat::WriteAnnouncement(b, title, message); }), StructPacket(announcement));
|
||||
|
||||
RakNet::BitStream stream; LoadPayload(stream, announcement);
|
||||
const auto [legacyTitle, legacyMessage] = LegacyChat::ReadGMAnnounce(stream);
|
||||
const auto copy = RoundTrip(announcement);
|
||||
EXPECT_EQ(legacyTitle, copy.title);
|
||||
EXPECT_EQ(legacyMessage, copy.message);
|
||||
ExpectTruncatedFails(announcement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, ShowAllAndWhoRequestsMatchLegacy) {
|
||||
for (const auto id : g_Ids) {
|
||||
for (const bool zoneData : { false, true }) {
|
||||
for (const bool individualPlayers : { false, true }) {
|
||||
LegacyChat::ShowAllRequest legacy{ id, zoneData, individualPlayers };
|
||||
ChatPackets::ShowAllRequest request;
|
||||
request.requestor = id;
|
||||
request.displayZoneData = zoneData;
|
||||
request.displayIndividualPlayers = individualPlayers;
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { legacy.Serialize(b); }), StructPacket(request));
|
||||
|
||||
RakNet::BitStream stream; LoadPayload(stream, request);
|
||||
LegacyChat::ShowAllRequest legacyRead;
|
||||
legacyRead.Deserialize(stream);
|
||||
const auto copy = RoundTrip(request);
|
||||
EXPECT_EQ(legacyRead.requestor, copy.requestor);
|
||||
EXPECT_EQ(legacyRead.displayZoneData, copy.displayZoneData);
|
||||
EXPECT_EQ(legacyRead.displayIndividualPlayers, copy.displayIndividualPlayers);
|
||||
ExpectTruncatedFails(request);
|
||||
}
|
||||
}
|
||||
for (const auto& name : g_Names) {
|
||||
LegacyChat::FindPlayerRequest legacy{ id, LUWString(name) };
|
||||
ChatPackets::FindPlayerRequest request;
|
||||
request.requestor = id;
|
||||
request.playerName = LUWString(name);
|
||||
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { legacy.Serialize(b); }), StructPacket(request));
|
||||
|
||||
RakNet::BitStream stream; LoadPayload(stream, request);
|
||||
LegacyChat::FindPlayerRequest legacyRead;
|
||||
legacyRead.Deserialize(stream);
|
||||
const auto copy = RoundTrip(request);
|
||||
EXPECT_EQ(legacyRead.playerName.string, copy.playerName.string);
|
||||
ExpectTruncatedFails(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Client -> world -> chat: the structs read what the old handlers read
|
||||
|
||||
TEST_F(ChatPacketsTests, FriendAndIgnoreRequestsReadLikeLegacy) {
|
||||
for (const auto id : g_Ids) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const uint8_t extra : { 0, 1, 3 }) {
|
||||
ChatPackets::AddFriendRequest request;
|
||||
request.playerID = id;
|
||||
request.unknown = 0xAABBCCDD;
|
||||
request.friendName = LUWString(name);
|
||||
request.isBestFriendRequest = extra;
|
||||
RakNet::BitStream stream; LoadPayload(stream, request);
|
||||
const auto legacy = LegacyChat::ReadFriendRequest(stream);
|
||||
const auto copy = RoundTrip(request);
|
||||
EXPECT_EQ(legacy.playerID, copy.playerID);
|
||||
EXPECT_EQ(legacy.name.string, copy.friendName.string);
|
||||
EXPECT_EQ(static_cast<uint8_t>(legacy.extra), copy.isBestFriendRequest);
|
||||
ExpectTruncatedFails(request);
|
||||
|
||||
ChatPackets::AddFriendResponse response;
|
||||
response.playerID = id;
|
||||
response.responseCode = static_cast<eAddFriendResponseCode>(extra);
|
||||
response.friendName = LUWString(name);
|
||||
RakNet::BitStream responseStream; LoadPayload(responseStream, response);
|
||||
const auto legacyResponse = LegacyChat::ReadFriendResponse(responseStream);
|
||||
const auto responseCopy = RoundTrip(response);
|
||||
EXPECT_EQ(legacyResponse.playerID, responseCopy.playerID);
|
||||
EXPECT_EQ(legacyResponse.name.string, responseCopy.friendName.string);
|
||||
EXPECT_EQ(static_cast<uint8_t>(legacyResponse.extra), static_cast<uint8_t>(responseCopy.responseCode));
|
||||
ExpectTruncatedFails(response);
|
||||
}
|
||||
|
||||
const auto checkPlayerAndName = [&](const auto& msg, const LUWString& name) {
|
||||
RakNet::BitStream stream; LoadPayload(stream, msg);
|
||||
const auto legacy = LegacyChat::ReadPlayerAndName(stream);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(legacy.playerID, copy.playerID);
|
||||
EXPECT_EQ(legacy.name.string, name.string);
|
||||
ExpectTruncatedFails(msg);
|
||||
};
|
||||
ChatPackets::RemoveFriend removeFriend;
|
||||
removeFriend.playerID = id;
|
||||
removeFriend.friendName = LUWString(name);
|
||||
checkPlayerAndName(removeFriend, removeFriend.friendName);
|
||||
ChatPackets::AddIgnore addIgnore;
|
||||
addIgnore.playerID = id;
|
||||
addIgnore.playerName = LUWString(name);
|
||||
checkPlayerAndName(addIgnore, addIgnore.playerName);
|
||||
ChatPackets::RemoveIgnore removeIgnore;
|
||||
removeIgnore.playerID = id;
|
||||
removeIgnore.playerName = LUWString(name);
|
||||
checkPlayerAndName(removeIgnore, removeIgnore.playerName);
|
||||
ChatPackets::TeamInvite teamInvite;
|
||||
teamInvite.playerID = id;
|
||||
teamInvite.invitedPlayer = LUWString(name);
|
||||
checkPlayerAndName(teamInvite, teamInvite.invitedPlayer);
|
||||
ChatPackets::TeamKick teamKick;
|
||||
teamKick.playerID = id;
|
||||
teamKick.kickedPlayer = LUWString(name);
|
||||
checkPlayerAndName(teamKick, teamKick.kickedPlayer);
|
||||
ChatPackets::TeamSetLeader teamSetLeader;
|
||||
teamSetLeader.playerID = id;
|
||||
teamSetLeader.promotedPlayer = LUWString(name);
|
||||
checkPlayerAndName(teamSetLeader, teamSetLeader.promotedPlayer);
|
||||
}
|
||||
|
||||
ChatPackets::TeamInviteResponse inviteResponse;
|
||||
inviteResponse.playerID = id;
|
||||
inviteResponse.declined = 1;
|
||||
inviteResponse.leaderID = 0x1234;
|
||||
RakNet::BitStream stream; LoadPayload(stream, inviteResponse);
|
||||
const auto legacy = LegacyChat::ReadTeamInviteResponse(stream);
|
||||
EXPECT_EQ(legacy.playerID, id);
|
||||
EXPECT_EQ(legacy.declined, 1);
|
||||
EXPECT_EQ(legacy.leaderID, 0x1234);
|
||||
RoundTrip(inviteResponse);
|
||||
ExpectTruncatedFails(inviteResponse);
|
||||
|
||||
ChatPackets::TeamSetLoot setLoot;
|
||||
setLoot.playerID = id;
|
||||
setLoot.lootFlag = 1;
|
||||
RakNet::BitStream lootStream; LoadPayload(lootStream, setLoot);
|
||||
const auto [lootPlayer, lootOption] = LegacyChat::ReadTeamLootOption(lootStream);
|
||||
EXPECT_EQ(lootPlayer, id);
|
||||
EXPECT_EQ(lootOption, 1);
|
||||
RoundTrip(setLoot);
|
||||
ExpectTruncatedFails(setLoot);
|
||||
|
||||
for (const auto& msg : std::initializer_list<std::function<void()>>{
|
||||
[&] { ChatPackets::GetFriendsList m; m.playerID = id; RoundTrip(m); ExpectTruncatedFails(m); },
|
||||
[&] { ChatPackets::GetIgnoreList m; m.playerID = id; RoundTrip(m); ExpectTruncatedFails(m); },
|
||||
[&] { ChatPackets::TeamGetStatus m; m.playerID = id; RoundTrip(m); ExpectTruncatedFails(m); },
|
||||
[&] { ChatPackets::TeamLeave m; m.playerID = id; RoundTrip(m); ExpectTruncatedFails(m); },
|
||||
}) msg();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, ChatMessagesReadLikeLegacy) {
|
||||
for (const auto channel : { eChatChannel::TEAM, eChatChannel::PRIVATE_CHAT, eChatChannel::LOCAL }) {
|
||||
for (const uint32_t size : { 0u, 1u, 6u, 50u }) {
|
||||
ChatPackets::GeneralChatMessage chatMessage;
|
||||
chatMessage.playerID = 0x0102030405060708LL;
|
||||
chatMessage.chatChannel = channel;
|
||||
chatMessage.messageLength = size;
|
||||
chatMessage.senderName = LUWString("Sender");
|
||||
chatMessage.senderID = 0x42;
|
||||
chatMessage.message = LUWString(u"hello", size);
|
||||
RakNet::BitStream stream; LoadPayload(stream, chatMessage);
|
||||
const auto legacy = LegacyChat::ReadChatMessage(stream);
|
||||
const auto copy = RoundTrip(chatMessage);
|
||||
EXPECT_EQ(legacy.playerID, copy.playerID);
|
||||
EXPECT_EQ(legacy.channel, copy.chatChannel);
|
||||
EXPECT_EQ(legacy.size, copy.messageLength);
|
||||
EXPECT_EQ(legacy.message.string, copy.message.string);
|
||||
ExpectTruncatedFails(chatMessage);
|
||||
|
||||
ChatPackets::PrivateChatMessage privateMessage;
|
||||
privateMessage.playerID = 0x0102030405060708LL;
|
||||
privateMessage.chatChannel = channel;
|
||||
privateMessage.messageLength = size;
|
||||
privateMessage.receiverName = LUWString("Receiver");
|
||||
privateMessage.receiverGMLevel = 3;
|
||||
privateMessage.responseCode = 4;
|
||||
privateMessage.message = LUWString(u"hello", size);
|
||||
RakNet::BitStream privateStream; LoadPayload(privateStream, privateMessage);
|
||||
const auto legacyPrivate = LegacyChat::ReadPrivateChatMessage(privateStream);
|
||||
const auto privateCopy = RoundTrip(privateMessage);
|
||||
EXPECT_EQ(legacyPrivate.playerID, privateCopy.playerID);
|
||||
EXPECT_EQ(legacyPrivate.channel, privateCopy.chatChannel);
|
||||
EXPECT_EQ(legacyPrivate.receiverName.string, privateCopy.receiverName.string);
|
||||
EXPECT_EQ(legacyPrivate.message.string, privateCopy.message.string);
|
||||
ExpectTruncatedFails(privateMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Longer than MAX_MESSAGE_LENGTH: the old handlers ignored the message, the structs refuse it
|
||||
ChatPackets::GeneralChatMessage tooLong;
|
||||
tooLong.messageLength = MAX_MESSAGE_LENGTH + 1;
|
||||
RakNet::BitStream stream; LoadPayload(stream, tooLong);
|
||||
ChatPackets::GeneralChatMessage read;
|
||||
EXPECT_FALSE(read.Deserialize(stream));
|
||||
}
|
||||
|
||||
TEST_F(ChatPacketsTests, AchievementNotifyRoundTrips) {
|
||||
ChatPackets::AchievementNotify notify;
|
||||
notify.targetPlayerName = LUWString("Friend");
|
||||
notify.missionEmailID = 1234;
|
||||
notify.earningPlayerID = 0x0102030405060708LL;
|
||||
notify.earnerName = LUWString("Earner");
|
||||
const auto copy = RoundTrip(notify);
|
||||
EXPECT_EQ(copy.missionEmailID, 1234u);
|
||||
EXPECT_EQ(copy.earnerName.string, u"Earner");
|
||||
EXPECT_EQ(copy.targetPlayerName.string, u"Friend");
|
||||
ExpectTruncatedFails(notify);
|
||||
}
|
||||
913
tests/dGameTests/dNetTests/Legacy/ChatPacketsLegacy.h
Normal file
913
tests/dGameTests/dNetTests/Legacy/ChatPacketsLegacy.h
Normal file
@@ -0,0 +1,913 @@
|
||||
#ifndef CHATPACKETSLEGACY_H
|
||||
#define CHATPACKETSLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written chat packet code that dNet/ChatPackets.h replaced (dNet/ChatPackets.cpp,
|
||||
// dChatServer/{ChatPacketHandler,ChatIgnoreList,TeamContainer,PlayerContainer}.cpp, dWorldServer/WorldServer.cpp and
|
||||
// the dGame senders at f8f977f8). Only the namespace changed, except:
|
||||
// - senders that wrote to Game::chatServer (a RakPeer the tests don't have) write into `bitStream` instead;
|
||||
// - the parts of handlers that write or read a packet are copied on their own, with the state they used passed in;
|
||||
// - old readers return what they read in a struct instead of acting on it.
|
||||
// The byte-equality tests send the same inputs through these and through the new structs and require identical
|
||||
// bytes, so the wire format is pinned even after the production code is deleted.
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "Game.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "ServiceType.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "eAddFriendResponseCode.h"
|
||||
#include "eAddFriendResponseType.h"
|
||||
#include "eChatChannel.h"
|
||||
#include "eChatMessageResponseCode.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LegacyChat {
|
||||
// dNet/ChatPackets.h
|
||||
struct ShowAllRequest{
|
||||
LWOOBJID requestor = LWOOBJID_EMPTY;
|
||||
bool displayZoneData = true;
|
||||
bool displayIndividualPlayers = true;
|
||||
void Serialize(RakNet::BitStream& bitStream);
|
||||
void Deserialize(RakNet::BitStream& inStream);
|
||||
};
|
||||
|
||||
struct FindPlayerRequest{
|
||||
LWOOBJID requestor = LWOOBJID_EMPTY;
|
||||
LUWString playerName;
|
||||
void Serialize(RakNet::BitStream& bitStream);
|
||||
void Deserialize(RakNet::BitStream& inStream);
|
||||
};
|
||||
|
||||
// dNet/ChatPackets.cpp
|
||||
inline void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::SHOW_ALL);
|
||||
bitStream.Write(this->requestor);
|
||||
bitStream.Write(this->displayZoneData);
|
||||
bitStream.Write(this->displayIndividualPlayers);
|
||||
}
|
||||
|
||||
inline void ShowAllRequest::Deserialize(RakNet::BitStream& inStream) {
|
||||
inStream.Read(this->requestor);
|
||||
inStream.Read(this->displayZoneData);
|
||||
inStream.Read(this->displayIndividualPlayers);
|
||||
}
|
||||
|
||||
inline void FindPlayerRequest::Serialize(RakNet::BitStream& bitStream) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WHO);
|
||||
bitStream.Write(this->requestor);
|
||||
bitStream.Write(this->playerName);
|
||||
}
|
||||
|
||||
inline void FindPlayerRequest::Deserialize(RakNet::BitStream& inStream) {
|
||||
inStream.Read(this->requestor);
|
||||
inStream.Read(this->playerName);
|
||||
}
|
||||
|
||||
inline void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GENERAL_CHAT_MESSAGE);
|
||||
|
||||
bitStream.Write<uint64_t>(0);
|
||||
bitStream.Write(chatChannel);
|
||||
|
||||
bitStream.Write<uint32_t>(message.size());
|
||||
bitStream.Write(LUWString(senderName));
|
||||
|
||||
bitStream.Write(playerObjectID);
|
||||
bitStream.Write<uint16_t>(0);
|
||||
bitStream.Write<char>(0);
|
||||
|
||||
for (uint32_t i = 0; i < message.size(); ++i) {
|
||||
bitStream.Write<uint16_t>(message[i]);
|
||||
}
|
||||
bitStream.Write<uint16_t>(0);
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
|
||||
inline void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, const bool broadcast = false) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GENERAL_CHAT_MESSAGE);
|
||||
|
||||
bitStream.Write<uint64_t>(0);
|
||||
bitStream.Write<char>(4);
|
||||
|
||||
bitStream.Write<uint32_t>(message.size());
|
||||
bitStream.Write(LUWString("", 33));
|
||||
|
||||
bitStream.Write<uint64_t>(0);
|
||||
bitStream.Write<uint16_t>(0);
|
||||
bitStream.Write<char>(0);
|
||||
|
||||
for (uint32_t i = 0; i < message.size(); ++i) {
|
||||
bitStream.Write<uint16_t>(message[i]);
|
||||
}
|
||||
|
||||
bitStream.Write<uint16_t>(0);
|
||||
|
||||
//This is so Wincent's announcement works:
|
||||
if (sysAddr != UNASSIGNED_SYSTEM_ADDRESS) {
|
||||
SEND_PACKET;
|
||||
return;
|
||||
}
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
|
||||
inline void SendSystemMessage(const SystemAddress& sysAddr, const std::string& message, const bool broadcast = false) {
|
||||
LegacyChat::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message), broadcast);
|
||||
}
|
||||
|
||||
inline void SendMessageFail(const SystemAddress& sysAddr) {
|
||||
//0x00 - "Chat is currently disabled."
|
||||
//0x01 - "Upgrade to a full LEGO Universe Membership to chat with other players."
|
||||
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::SEND_CANNED_TEXT);
|
||||
bitStream.Write<uint8_t>(0); //response type, options above ^
|
||||
//docs say there's a wstring here-- no idea what it's for, or if it's even needed so leaving it as is for now.
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// ChatPackets::Announcement::Serialize, with its header
|
||||
inline void WriteAnnouncement(RakNet::BitStream& bitStream, const std::string& title, const std::string& message) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_ANNOUNCE);
|
||||
bitStream.Write<uint32_t>(title.size());
|
||||
bitStream.Write(title);
|
||||
bitStream.Write<uint32_t>(message.size());
|
||||
bitStream.Write(message);
|
||||
}
|
||||
|
||||
// ChatPackets::TeamInviteInitialResponse::Serialize, with its header
|
||||
inline void WriteTeamInviteInitialResponse(RakNet::BitStream& bitstream, bool inviteFailedToSend, const LUWString& playerName) {
|
||||
BitStreamUtils::WriteHeader(bitstream, ServiceType::CLIENT, MessageType::Client::TEAM_INVITE_INITIAL_RESPONSE);
|
||||
bitstream.Write<uint8_t>(inviteFailedToSend);
|
||||
bitstream.Write(playerName);
|
||||
}
|
||||
|
||||
// ChatPackets::SendRoutedMsg, with the routed message's bytes (msg.WritePacket) given
|
||||
inline void SendRoutedMsg(RakNet::BitStream& msgPacket, const LWOOBJID targetID, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(targetID);
|
||||
|
||||
// Now write the actual packet
|
||||
bitStream.WriteBits(msgPacket.GetData(), msgPacket.GetNumberOfBitsUsed());
|
||||
Game::server->Send(bitStream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
// dChatServer/ChatPacketHandler.cpp
|
||||
|
||||
// The end of HandleFriendlistRequest
|
||||
inline void SendFriendsList(const LWOOBJID playerID, const PlayerData& player) {
|
||||
//Now, we need to send the friendlist to the server they came from:
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::GET_FRIENDS_LIST_RESPONSE);
|
||||
bitStream.Write<uint8_t>(0);
|
||||
bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it.
|
||||
bitStream.Write<uint16_t>(player.friends.size());
|
||||
|
||||
for (const auto& data : player.friends) {
|
||||
data.Serialize(bitStream);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = player.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// The end of HandleWho
|
||||
inline void SendWhoResponse(const FindPlayerRequest& request, const PlayerData& sender, const PlayerData& player) {
|
||||
bool online = player;
|
||||
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(request.requestor);
|
||||
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::WHO_RESPONSE);
|
||||
bitStream.Write<uint8_t>(online);
|
||||
bitStream.Write(player.zoneID.GetMapID());
|
||||
bitStream.Write(player.zoneID.GetInstanceID());
|
||||
bitStream.Write(player.zoneID.GetCloneID());
|
||||
bitStream.Write(request.playerName);
|
||||
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// The end of HandleShowAll, with Game::playerContainer's counts and players passed in
|
||||
inline void SendShowAllResponse(const ShowAllRequest& request, const PlayerData& sender, uint32_t playerCount, uint32_t simCount, const std::map<LWOOBJID, PlayerData>& allPlayers) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(request.requestor);
|
||||
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::SHOW_ALL_RESPONSE);
|
||||
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
|
||||
bitStream.Write(playerCount);
|
||||
bitStream.Write(simCount);
|
||||
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
|
||||
bitStream.Write<uint8_t>(request.displayZoneData);
|
||||
if (request.displayZoneData || request.displayIndividualPlayers) {
|
||||
for (auto& [playerID, playerData] : allPlayers) {
|
||||
if (!playerData) continue;
|
||||
bitStream.Write<uint8_t>(0); // structure packing
|
||||
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
|
||||
if (request.displayZoneData) {
|
||||
bitStream.Write(playerData.zoneID.GetMapID());
|
||||
bitStream.Write(playerData.zoneID.GetInstanceID());
|
||||
bitStream.Write(playerData.zoneID.GetCloneID());
|
||||
}
|
||||
}
|
||||
}
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(routeTo.playerID);
|
||||
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE);
|
||||
bitStream.Write(sender.playerID);
|
||||
bitStream.Write(channel);
|
||||
bitStream.Write<uint32_t>(0); // not used
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
bitStream.Write(sender.playerID);
|
||||
bitStream.Write<uint16_t>(0); // sourceID
|
||||
bitStream.Write(sender.gmLevel);
|
||||
bitStream.Write(LUWString(receiver.playerName));
|
||||
bitStream.Write(receiver.gmLevel);
|
||||
bitStream.Write(responseCode);
|
||||
bitStream.Write(message);
|
||||
|
||||
SystemAddress sysAddr = routeTo.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(friendData.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::UPDATE_FRIEND_NOTIFY);
|
||||
bitStream.Write<uint8_t>(notifyType);
|
||||
|
||||
std::string playerName = playerData.playerName.c_str();
|
||||
|
||||
bitStream.Write(LUWString(playerName));
|
||||
|
||||
bitStream.Write(playerData.zoneID.GetMapID());
|
||||
bitStream.Write(playerData.zoneID.GetInstanceID());
|
||||
|
||||
if (playerData.zoneID.GetCloneID() == friendData.zoneID.GetCloneID()) {
|
||||
bitStream.Write(0);
|
||||
} else {
|
||||
bitStream.Write(playerData.zoneID.GetCloneID());
|
||||
}
|
||||
|
||||
bitStream.Write<uint8_t>(isBestFriend); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
|
||||
SystemAddress sysAddr = friendData.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
// Portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
|
||||
bitStream.Write(responseCode);
|
||||
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
|
||||
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
// Then write the player name
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
// Then if this is an acceptance code, write the following extra info.
|
||||
if (responseCode == eAddFriendResponseType::ACCEPTED) {
|
||||
bitStream.Write(sender.playerID);
|
||||
bitStream.Write(sender.zoneID);
|
||||
bitStream.Write(isBestFriendRequest); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
}
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender) {
|
||||
//Make sure people aren't requesting people that they're already friends with:
|
||||
for (const auto& fr : receiver.friends) {
|
||||
if (fr.friendID == sender.playerID) {
|
||||
SendFriendResponse(sender, receiver, eAddFriendResponseType::ALREADYFRIEND, fr.isBestFriend);
|
||||
return; //we have this player as a friend, yeet this function so it doesn't send another request.
|
||||
}
|
||||
}
|
||||
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::ADD_FRIEND_REQUEST);
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::REMOVE_FRIEND_RESPONSE);
|
||||
bitStream.Write<uint8_t>(isSuccessful); //isOnline
|
||||
bitStream.Write(LUWString(personToRemove));
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// dChatServer/ChatIgnoreList.cpp
|
||||
enum class AddResponse : uint8_t {
|
||||
SUCCESS,
|
||||
ALREADY_IGNORED,
|
||||
PLAYER_NOT_FOUND,
|
||||
GENERAL_ERROR,
|
||||
};
|
||||
|
||||
inline void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const MessageType::Client type) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receivingPlayer);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, type);
|
||||
}
|
||||
|
||||
// The end of GetIgnoreList
|
||||
inline void SendIgnoreList(const PlayerData& receiver, const SystemAddress& packetSystemAddress) {
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::GET_IGNORE_LIST_RESPONSE);
|
||||
|
||||
bitStream.Write<uint8_t>(false); // Is Free Trial, but we don't care about that
|
||||
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
|
||||
|
||||
bitStream.Write<uint16_t>(receiver.ignoredPlayers.size());
|
||||
for (const auto& ignoredPlayer : receiver.ignoredPlayers) {
|
||||
bitStream.Write(ignoredPlayer.playerId);
|
||||
bitStream.Write(LUWString(ignoredPlayer.playerName, 36));
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, packetSystemAddress, false);
|
||||
}
|
||||
|
||||
// The response AddIgnore writes, with the outcome passed in
|
||||
inline void SendAddIgnoreResponse(const PlayerData& receiver, AddResponse response, const std::string& toIgnoreStr, LWOOBJID ignoredPlayerId, const SystemAddress& packetSystemAddress) {
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::ADD_IGNORE_RESPONSE);
|
||||
|
||||
bitStream.Write(response);
|
||||
|
||||
LUWString playerNameSend(toIgnoreStr, 33);
|
||||
bitStream.Write(playerNameSend);
|
||||
bitStream.Write(ignoredPlayerId);
|
||||
|
||||
Game::server->Send(bitStream, packetSystemAddress, false);
|
||||
}
|
||||
|
||||
// The end of RemoveIgnore
|
||||
inline void SendRemoveIgnoreResponse(const PlayerData& receiver, const std::string& removedIgnoreStr, const SystemAddress& packetSystemAddress) {
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::REMOVE_IGNORE_RESPONSE);
|
||||
|
||||
bitStream.Write<int8_t>(0);
|
||||
LUWString playerNameSend(removedIgnoreStr, 33);
|
||||
bitStream.Write(playerNameSend);
|
||||
|
||||
Game::server->Send(bitStream, packetSystemAddress, false);
|
||||
}
|
||||
|
||||
// dChatServer/TeamContainer.cpp
|
||||
inline void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// UpdateTeamsOnWorld, with the team's fields passed in
|
||||
inline void UpdateTeamsOnWorld(LWOOBJID teamID, uint8_t lootFlag, const std::vector<LWOOBJID>& memberIDs, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(lootFlag);
|
||||
bitStream.Write<char>(memberIDs.size());
|
||||
for (const auto memberID : memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
// dChatServer/PlayerContainer.cpp
|
||||
inline void BroadcastMuteUpdate(LWOOBJID player, time_t time) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE);
|
||||
|
||||
bitStream.Write(player);
|
||||
bitStream.Write(time);
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
// World -> chat senders (they wrote to Game::chatServer; here into bitStream)
|
||||
|
||||
// dWorldServer/WorldServer.cpp, LoadPlayer
|
||||
inline void WriteLoginSessionNotify(RakNet::BitStream& bitStream, LWOOBJID objectID, const std::string& playerName, const LWOZONEID& zone, time_t muteExpire, eGameMasterLevel gmLevel) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::LOGIN_SESSION_NOTIFY);
|
||||
bitStream.Write(objectID);
|
||||
bitStream.Write<uint32_t>(playerName.size());
|
||||
for (size_t i = 0; i < playerName.size(); i++) {
|
||||
bitStream.Write(playerName[i]);
|
||||
}
|
||||
|
||||
bitStream.Write(zone.GetMapID());
|
||||
bitStream.Write(zone.GetInstanceID());
|
||||
bitStream.Write(zone.GetCloneID());
|
||||
bitStream.Write(muteExpire);
|
||||
bitStream.Write(gmLevel);
|
||||
}
|
||||
|
||||
// dWorldServer/WorldServer.cpp and dGame/UserManager.cpp
|
||||
inline void WriteUnexpectedDisconnect(RakNet::BitStream& bitStream, LWOOBJID objectID) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT);
|
||||
bitStream.Write(objectID);
|
||||
}
|
||||
|
||||
// dGame/Entity.cpp, SetGMLevel
|
||||
inline void WriteGMLevelUpdate(RakNet::BitStream& bitStream, LWOOBJID m_ObjectID, eGameMasterLevel m_GMLevel) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GMLEVEL_UPDATE);
|
||||
bitStream.Write(m_ObjectID);
|
||||
bitStream.Write(m_GMLevel);
|
||||
}
|
||||
|
||||
// dGame/User.cpp and the /mute command
|
||||
inline void WriteGMMute(RakNet::BitStream& bitStream, LWOOBJID characterId, time_t expire) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE);
|
||||
|
||||
bitStream.Write(characterId);
|
||||
bitStream.Write(expire);
|
||||
}
|
||||
|
||||
// dGame/dComponents/ActivityComponent.cpp, ActivityInstance::StartZone
|
||||
inline void WriteCreateTeam(RakNet::BitStream& bitStream, LWOOBJID leaderID, const std::vector<LWOOBJID>& m_Participants, LWOZONEID zoneId) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::CREATE_TEAM);
|
||||
|
||||
bitStream.Write(leaderID);
|
||||
bitStream.Write(m_Participants.size());
|
||||
|
||||
for (const auto& participant : m_Participants) {
|
||||
bitStream.Write(participant);
|
||||
}
|
||||
|
||||
bitStream.Write(zoneId);
|
||||
}
|
||||
|
||||
// dGame/dUtilities/SlashCommandHandler.cpp, SendAnnouncement
|
||||
inline void WriteGMAnnounce(RakNet::BitStream& bitStream, const std::string& title, const std::string& message) {
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_ANNOUNCE);
|
||||
|
||||
bitStream.Write<uint32_t>(title.size());
|
||||
for (auto character : title) {
|
||||
bitStream.Write<char>(character);
|
||||
}
|
||||
|
||||
bitStream.Write<uint32_t>(message.size());
|
||||
for (auto character : message) {
|
||||
bitStream.Write<char>(character);
|
||||
}
|
||||
}
|
||||
|
||||
// Readers. What the old handlers read, in order; the stream is past the packet header.
|
||||
|
||||
struct PlayerAndName {
|
||||
LWOOBJID playerID{};
|
||||
LUWString name;
|
||||
char extra{};
|
||||
};
|
||||
|
||||
// HandleFriendRequest
|
||||
inline PlayerAndName ReadFriendRequest(RakNet::BitStream& inStream) {
|
||||
LWOOBJID requestorPlayerID;
|
||||
LUWString LUplayerName;
|
||||
char isBestFriendRequest{};
|
||||
|
||||
inStream.Read(requestorPlayerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(LUplayerName);
|
||||
inStream.Read(isBestFriendRequest);
|
||||
return { requestorPlayerID, LUplayerName, isBestFriendRequest };
|
||||
}
|
||||
|
||||
// HandleFriendResponse
|
||||
inline PlayerAndName ReadFriendResponse(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
eAddFriendResponseCode clientResponseCode;
|
||||
LUWString friendName;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(clientResponseCode);
|
||||
inStream.Read(friendName);
|
||||
return { playerID, friendName, static_cast<char>(clientResponseCode) };
|
||||
}
|
||||
|
||||
// HandleRemoveFriend, AddIgnore, RemoveIgnore, HandleTeamInvite, HandleTeamKick, HandleTeamPromote
|
||||
inline PlayerAndName ReadPlayerAndName(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
LUWString LUFriendName;
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(LUFriendName);
|
||||
return { playerID, LUFriendName, 0 };
|
||||
}
|
||||
|
||||
struct ChatRead {
|
||||
LWOOBJID playerID{};
|
||||
eChatChannel channel{};
|
||||
uint32_t size{};
|
||||
LUWString receiverName;
|
||||
LUWString message;
|
||||
};
|
||||
|
||||
// HandleChatMessage (without the sender check)
|
||||
inline ChatRead ReadChatMessage(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
|
||||
eChatChannel channel;
|
||||
uint32_t size;
|
||||
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(channel);
|
||||
inStream.Read(size);
|
||||
|
||||
inStream.IgnoreBytes(77);
|
||||
|
||||
LUWString message(size);
|
||||
inStream.Read(message);
|
||||
return { playerID, channel, size, LUWString(), message };
|
||||
}
|
||||
|
||||
// HandlePrivateChatMessage (without the sender check)
|
||||
inline ChatRead ReadPrivateChatMessage(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
|
||||
eChatChannel channel;
|
||||
uint32_t size;
|
||||
LUWString LUReceiverName;
|
||||
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(channel);
|
||||
|
||||
inStream.Read(size);
|
||||
|
||||
inStream.IgnoreBytes(77);
|
||||
|
||||
inStream.Read(LUReceiverName);
|
||||
inStream.IgnoreBytes(2);
|
||||
|
||||
LUWString message(size);
|
||||
inStream.Read(message);
|
||||
return { playerID, channel, size, LUReceiverName, message };
|
||||
}
|
||||
|
||||
struct TeamInviteResponseRead {
|
||||
LWOOBJID playerID{};
|
||||
char declined{};
|
||||
LWOOBJID leaderID{};
|
||||
};
|
||||
|
||||
// HandleTeamInviteResponse
|
||||
inline TeamInviteResponseRead ReadTeamInviteResponse(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
return { playerID, declined, leaderID };
|
||||
}
|
||||
|
||||
// HandleTeamLootOption
|
||||
inline std::pair<LWOOBJID, char> ReadTeamLootOption(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
return { playerID, option };
|
||||
}
|
||||
|
||||
struct CreateTeamRead {
|
||||
LWOOBJID playerID{};
|
||||
std::vector<LWOOBJID> members;
|
||||
LWOZONEID zoneId;
|
||||
};
|
||||
|
||||
// TeamContainer::CreateTeamServer (without the size check)
|
||||
inline CreateTeamRead ReadCreateTeam(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
size_t membersSize = 0;
|
||||
inStream.Read(membersSize);
|
||||
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
members.reserve(membersSize);
|
||||
|
||||
for (size_t i = 0; i < membersSize; i++) {
|
||||
LWOOBJID member;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
|
||||
LWOZONEID zoneId;
|
||||
|
||||
inStream.Read(zoneId);
|
||||
return { playerID, members, zoneId };
|
||||
}
|
||||
|
||||
// PlayerContainer::InsertPlayer (into a PlayerData)
|
||||
inline bool ReadLoginSessionNotify(RakNet::BitStream& inStream, PlayerData& data) {
|
||||
LWOOBJID playerId;
|
||||
if (!inStream.Read(playerId)) {
|
||||
return false;
|
||||
}
|
||||
data.playerID = playerId;
|
||||
|
||||
uint32_t len;
|
||||
if (!inStream.Read<uint32_t>(len)) return false;
|
||||
|
||||
if (len > 33) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data.playerName.resize(len);
|
||||
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(data.playerName.data()), len);
|
||||
|
||||
if (!inStream.Read(data.zoneID)) return false;
|
||||
if (!inStream.Read(data.muteExpire)) return false;
|
||||
if (!inStream.Read(data.gmLevel)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// dWorldServer/WorldServer.cpp, HandlePacketChat GM_ANNOUNCE
|
||||
inline std::pair<std::string, std::string> ReadGMAnnounce(RakNet::BitStream& inStream) {
|
||||
std::string title;
|
||||
std::string msg;
|
||||
|
||||
uint32_t len;
|
||||
inStream.Read<uint32_t>(len);
|
||||
for (uint32_t i = 0; len > i; i++) {
|
||||
char character;
|
||||
inStream.Read<char>(character);
|
||||
title += character;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
inStream.Read<uint32_t>(len);
|
||||
for (uint32_t i = 0; len > i; i++) {
|
||||
char character;
|
||||
inStream.Read<char>(character);
|
||||
msg += character;
|
||||
}
|
||||
return { title, msg };
|
||||
}
|
||||
|
||||
struct TeamStatusRead {
|
||||
LWOOBJID teamID = 0;
|
||||
bool deleteTeam{};
|
||||
char lootOption = 0;
|
||||
std::vector<LWOOBJID> members;
|
||||
};
|
||||
|
||||
// dWorldServer/WorldServer.cpp, HandlePacketChat TEAM_GET_STATUS
|
||||
inline TeamStatusRead ReadTeamStatus(RakNet::BitStream& inStream) {
|
||||
LWOOBJID teamID = 0;
|
||||
char lootOption = 0;
|
||||
char memberCount = 0;
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
inStream.Read(teamID);
|
||||
bool deleteTeam = inStream.ReadBit();
|
||||
|
||||
if (deleteTeam) {
|
||||
return { teamID, deleteTeam, 0, {} };
|
||||
}
|
||||
|
||||
inStream.Read(lootOption);
|
||||
inStream.Read(memberCount);
|
||||
for (char i = 0; i < memberCount; i++) {
|
||||
LWOOBJID member = LWOOBJID_EMPTY;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
return { teamID, deleteTeam, lootOption, members };
|
||||
}
|
||||
|
||||
// dWorldServer/WorldServer.cpp, HandlePacketChat WORLD_ROUTE_PACKET: the bytes sent on to the player
|
||||
inline std::pair<LWOOBJID, std::vector<uint8_t>> ReadWorldRoute(RakNet::BitStream& inStream) {
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
unsigned char data;
|
||||
while (inStream.Read(data)) {
|
||||
out.push_back(data);
|
||||
}
|
||||
return { playerID, out };
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CHATPACKETSLEGACY_H
|
||||
Reference in New Issue
Block a user