mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-16 09:34:08 +00:00
refactor: building game messages and blueprint packets as structs
Converts build mode, arranging, modular build and brick by brick
messages to NetGameMsgs in BuildingMessages.{h,cpp}: StartArrangingWithItem,
FinishArrangingWithItem, ModularBuildEnd and SetBuildModeConfirmed
(sent), and StartBuildingWithItem, DoneArrangingWithItem,
ModularBuildFinish, ModularBuildMoveAndEquip, ModularBuildConvertModel,
SetBuildMode, BuildModeSet, UnUseBBBModel, BBBLoadItemRequest and
BBBSaveRequest (received, registered in GameMessageHandler's map with
their handlers' logic kept). The BLUEPRINT_SAVE_RESPONSE and
BLUEPRINT_LOAD_RESPONSE_ITEMID client packets become the LUBitStream
structs ClientPackets::BlueprintSaveResponse and BlueprintLoadItemResponse,
also used by WorldServer's level load. The never-called
SendBBBSaveResponse is gone.
SetBuildModeConfirmed keeps writing modeValue's and startPos's default
flags as always set, as DLU did. BuildModeSet and UnUseBBBModel now read
their whole client layout (DLU read only the first fields).
No wire change and no change in recipients. Verified byte for byte
against a frozen verbatim copy of the old functions and inline packet
writes over an input grid, received messages compared with the old
handlers' read sequences with every truncated payload rejected, hand
computed golden bytes, round trips and a mutation check. Layouts
confirmed against the 1.10.64 client in Ghidra.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
388
tests/dGameTests/dGameMessagesTests/BuildingMessagesTests.cpp
Normal file
388
tests/dGameTests/dGameMessagesTests/BuildingMessagesTests.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "BuildingMessages.h"
|
||||
#include "ClientPackets.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "GameMessageTestUtils.h"
|
||||
#include "Legacy/BuildingMessagesLegacy.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace GameMessageTestUtils;
|
||||
|
||||
namespace {
|
||||
const std::vector<int32_t> g_Ints = { 0, 1, -1, 8, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
|
||||
const std::vector<NiPoint3> g_Points = { NiPoint3Constant::ZERO, NiPoint3(1.5f, -2.0f, 1e6f) };
|
||||
|
||||
PacketBytes Payload(const GameMessages::NetGameMsg& msg) {
|
||||
RakNet::BitStream bitStream;
|
||||
msg.Serialize(bitStream);
|
||||
return FromBitStream(bitStream);
|
||||
}
|
||||
|
||||
// Serializes msg, then reads it with the legacy read sequence; returns the result and how many bits it read.
|
||||
template<typename Result>
|
||||
std::pair<Result, uint32_t> ReadWithLegacy(const GameMessages::NetGameMsg& msg, const std::function<Result(RakNet::BitStream&)>& read) {
|
||||
RakNet::BitStream wire;
|
||||
msg.Serialize(wire);
|
||||
RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false);
|
||||
auto result = read(legacyStream);
|
||||
return { result, static_cast<uint32_t>(legacyStream.GetReadOffset()) };
|
||||
}
|
||||
|
||||
uint32_t Bits(const GameMessages::NetGameMsg& msg) { return Payload(msg).bits; }
|
||||
|
||||
// A packet struct sent to one client and to UNASSIGNED must match the legacy bytes. The legacy code always did
|
||||
// SEND_PACKET (never a broadcast); LUBitStream::Send broadcasts for UNASSIGNED, which none of the callers pass.
|
||||
void ExpectPacketSameAsLegacy(const std::function<void(const SystemAddress&)>& legacySend, const LUBitStream& packet) {
|
||||
const auto address = ClientAddress();
|
||||
const auto legacy = Capture([&] { legacySend(address); });
|
||||
const auto ours = Capture([&] { packet.Send(address); });
|
||||
ASSERT_EQ(legacy.size(), 1);
|
||||
ASSERT_EQ(ours.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0]));
|
||||
EXPECT_EQ(legacy[0].broadcast, ours[0].broadcast);
|
||||
EXPECT_EQ(legacy[0].sysAddr, ours[0].sysAddr);
|
||||
}
|
||||
}
|
||||
|
||||
class BuildingMessagesTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
TEST_F(BuildingMessagesTests, StartArrangingWithItemMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const bool firstTime : { false, true }) {
|
||||
for (const auto area : g_Targets) {
|
||||
for (const auto value : g_Ints) {
|
||||
for (const auto& point : g_Points) {
|
||||
GameMessages::StartArrangingWithItem msg;
|
||||
msg.target = target;
|
||||
msg.firstTime = firstTime;
|
||||
msg.buildAreaID = area;
|
||||
msg.buildStartPos = point;
|
||||
msg.sourceBag = value;
|
||||
msg.sourceID = ~area;
|
||||
msg.sourceLot = value / 2;
|
||||
msg.sourceType = -value;
|
||||
msg.targetID = area / 3;
|
||||
msg.targetLot = value / 5;
|
||||
msg.targetPos = NiPoint3(point.z, point.y, point.x);
|
||||
msg.targetType = value / 7;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) {
|
||||
LegacyGameMessages::SendStartArrangingWithItem(&entity, a, firstTime, area, point, value, msg.sourceID, msg.sourceLot, msg.sourceType, msg.targetID, msg.targetLot, msg.targetPos, msg.targetType);
|
||||
}, msg, SendMode::SendToClient);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.buildAreaID, area);
|
||||
EXPECT_EQ(copy.targetType, msg.targetType);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The struct's defaults are the old declaration's default arguments
|
||||
GameMessages::StartArrangingWithItem defaults;
|
||||
defaults.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendStartArrangingWithItem(&entity, a, true, LWOOBJID_EMPTY, NiPoint3Constant::ZERO, 0, LWOOBJID_EMPTY, 0, 8, 0, 0, NiPoint3Constant::ZERO, 0); }, defaults, SendMode::SendToClient);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BuildingMessagesTests, FinishArrangingAndModularBuildEndMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
// The legacy functions sent to the entity's own address.
|
||||
const auto address = entity.GetSystemAddress();
|
||||
for (const auto area : g_Targets) {
|
||||
const auto legacy = Capture([&] { LegacyGameMessages::SendFinishArrangingWithItem(&entity, area); });
|
||||
const auto ours = Capture([&] {
|
||||
GameMessages::FinishArrangingWithItem finish;
|
||||
finish.target = target;
|
||||
finish.buildAreaID = area;
|
||||
finish.SendToClient(address);
|
||||
});
|
||||
ASSERT_EQ(legacy.size(), 1);
|
||||
ASSERT_EQ(ours.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacy[0]), FromCapture(ours[0]));
|
||||
EXPECT_EQ(legacy[0].broadcast, ours[0].broadcast);
|
||||
EXPECT_EQ(legacy[0].sysAddr, ours[0].sysAddr);
|
||||
|
||||
GameMessages::FinishArrangingWithItem finish;
|
||||
finish.buildAreaID = area;
|
||||
finish.newTargetPos = NiPoint3(1, 2, 3);
|
||||
finish.oldItemLot = 7;
|
||||
const auto copy = RoundTrip(finish);
|
||||
EXPECT_EQ(copy.buildAreaID, area);
|
||||
EXPECT_EQ(copy.newTargetPos, finish.newTargetPos);
|
||||
ExpectTruncatedFails(finish);
|
||||
}
|
||||
|
||||
const auto legacyEnd = Capture([&] { LegacyGameMessages::SendModularBuildEnd(&entity); });
|
||||
const auto ourEnd = Capture([&] {
|
||||
GameMessages::ModularBuildEnd end;
|
||||
end.target = target;
|
||||
end.SendToClient(address);
|
||||
});
|
||||
ASSERT_EQ(legacyEnd.size(), 1);
|
||||
ASSERT_EQ(ourEnd.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyEnd[0]), FromCapture(ourEnd[0]));
|
||||
EXPECT_EQ(legacyEnd[0].sysAddr, ourEnd[0].sysAddr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BuildingMessagesTests, SetBuildModeConfirmedMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
for (const bool start : { false, true }) {
|
||||
for (const bool warn : { false, true }) {
|
||||
for (const auto value : g_Ints) {
|
||||
for (const auto& point : g_Points) {
|
||||
GameMessages::SetBuildModeConfirmed msg;
|
||||
msg.target = target;
|
||||
msg.start = start;
|
||||
msg.warnVisitors = warn;
|
||||
msg.modePaused = !start;
|
||||
msg.modeValue = value;
|
||||
msg.playerId = ~target;
|
||||
msg.startPos = point;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendSetBuildModeConfirmed(target, a, start, warn, !start, value, ~target, point); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.modeValue, value);
|
||||
EXPECT_EQ(copy.startPos, point);
|
||||
EXPECT_EQ(copy.warnVisitors, warn);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BuildingMessagesTests, BlueprintPacketsMatchLegacy) {
|
||||
for (const auto id : g_Targets) {
|
||||
for (const bool success : { false, true }) {
|
||||
ClientPackets::BlueprintLoadItemResponse load;
|
||||
load.success = success;
|
||||
load.itemId = id;
|
||||
load.destItemId = ~id;
|
||||
ExpectPacketSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendBlueprintLoadItemResponse(a, success, id, ~id); }, load);
|
||||
}
|
||||
|
||||
ClientPackets::BlueprintSaveResponse failed;
|
||||
failed.reasonCode = eBlueprintSaveResponseType::PlacementFailed;
|
||||
ExpectPacketSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::WriteUnUseModelSaveResponse(a); }, failed);
|
||||
|
||||
const std::vector<std::vector<std::string>> chunkSets = { {}, { "sd0" }, { std::string("\x73\x64\x30\x01\xff", 5), std::string(1000, '\x7f'), "" } };
|
||||
std::vector<std::pair<LWOOBJID, std::vector<std::string>>> saved;
|
||||
ClientPackets::BlueprintSaveResponse save;
|
||||
save.localId = id;
|
||||
save.reasonCode = eBlueprintSaveResponseType::EverythingWorked;
|
||||
std::vector<std::pair<LWOOBJID, std::string>> levelModels;
|
||||
ClientPackets::BlueprintSaveResponse level;
|
||||
level.reasonCode = eBlueprintSaveResponseType::EverythingWorked;
|
||||
for (size_t i = 0; i <= chunkSets.size(); i++) {
|
||||
ExpectPacketSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::WriteBBBSaveResponse(a, id, saved); }, save);
|
||||
ExpectPacketSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::WriteLevelLoadSaveResponse(a, levelModels); }, level);
|
||||
if (i == chunkSets.size()) break;
|
||||
saved.emplace_back(id + static_cast<LWOOBJID>(i), chunkSets[i]);
|
||||
auto& model = save.models.emplace_back();
|
||||
model.blueprintId = id + static_cast<LWOOBJID>(i);
|
||||
for (const auto& chunk : chunkSets[i]) model.data += chunk;
|
||||
levelModels.emplace_back(model.blueprintId, model.data);
|
||||
level.models.push_back(model);
|
||||
}
|
||||
|
||||
// Round trip
|
||||
RakNet::BitStream bitStream;
|
||||
save.WritePacket(bitStream);
|
||||
ClientPackets::BlueprintSaveResponse copy;
|
||||
ASSERT_TRUE(copy.ReadHeader(bitStream));
|
||||
ASSERT_TRUE(copy.Deserialize(bitStream));
|
||||
EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0);
|
||||
ASSERT_EQ(copy.models.size(), save.models.size());
|
||||
for (size_t i = 0; i < copy.models.size(); i++) EXPECT_EQ(copy.models[i].data, save.models[i].data);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BuildingMessagesTests, InboundReadsLikeLegacy) {
|
||||
for (const auto id : g_Targets) {
|
||||
for (const auto value : g_Ints) {
|
||||
for (const bool flag : { false, true }) {
|
||||
GameMessages::StartBuildingWithItem start;
|
||||
start.firstTime = flag;
|
||||
start.success = !flag;
|
||||
start.sourceBag = value;
|
||||
start.sourceId = id;
|
||||
start.sourceLot = value / 2;
|
||||
start.sourceType = value / 3;
|
||||
start.targetId = ~id;
|
||||
start.targetLot = -value;
|
||||
start.targetPos = NiPoint3(1, 2, 3);
|
||||
start.targetType = value / 5;
|
||||
const auto [legacy, bits] = ReadWithLegacy<LegacyGameMessages::LegacyStartBuildingWithItem>(start, LegacyGameMessages::ReadStartBuildingWithItem);
|
||||
EXPECT_EQ(bits, Bits(start));
|
||||
const auto copy = RoundTrip(start);
|
||||
EXPECT_EQ(copy.firstTime, legacy.firstTime);
|
||||
EXPECT_EQ(copy.success, legacy.success);
|
||||
EXPECT_EQ(copy.sourceBag, legacy.sourceBag);
|
||||
EXPECT_EQ(copy.sourceId, legacy.sourceId);
|
||||
EXPECT_EQ(copy.sourceLot, legacy.sourceLot);
|
||||
EXPECT_EQ(copy.sourceType, legacy.sourceType);
|
||||
EXPECT_EQ(copy.targetId, legacy.targetId);
|
||||
EXPECT_EQ(copy.targetLot, legacy.targetLot);
|
||||
EXPECT_EQ(copy.targetPos, legacy.targetPosition);
|
||||
EXPECT_EQ(copy.targetType, legacy.targetType);
|
||||
ExpectTruncatedFails(start);
|
||||
|
||||
for (const auto& point : g_Points) {
|
||||
GameMessages::SetBuildMode mode;
|
||||
mode.start = flag;
|
||||
mode.distanceType = value;
|
||||
mode.modePaused = !flag;
|
||||
mode.modeValue = -value;
|
||||
mode.playerId = id;
|
||||
mode.startPos = point;
|
||||
const auto [legacyMode, modeBits] = ReadWithLegacy<LegacyGameMessages::LegacySetBuildMode>(mode, LegacyGameMessages::ReadSetBuildMode);
|
||||
EXPECT_EQ(modeBits, Bits(mode));
|
||||
const auto modeCopy = RoundTrip(mode);
|
||||
EXPECT_EQ(modeCopy.start, legacyMode.start);
|
||||
EXPECT_EQ(modeCopy.distanceType, legacyMode.distanceType);
|
||||
EXPECT_EQ(modeCopy.modePaused, legacyMode.modePaused);
|
||||
EXPECT_EQ(modeCopy.modeValue, legacyMode.modeValue);
|
||||
EXPECT_EQ(modeCopy.playerId, legacyMode.playerId);
|
||||
EXPECT_EQ(modeCopy.startPos, legacyMode.startPosition);
|
||||
ExpectTruncatedFails(mode);
|
||||
|
||||
// BuildModeSet has SetBuildMode's layout; DLU only ever read its first field.
|
||||
GameMessages::BuildModeSet set;
|
||||
set.start = flag;
|
||||
set.distanceType = value;
|
||||
set.modeValue = -value;
|
||||
set.playerId = id;
|
||||
set.startPos = point;
|
||||
EXPECT_EQ(ReadWithLegacy<bool>(set, LegacyGameMessages::ReadBuildModeSet).first, flag);
|
||||
EXPECT_EQ(RoundTrip(set).startPos, point);
|
||||
ExpectTruncatedFails(set);
|
||||
}
|
||||
}
|
||||
|
||||
GameMessages::DoneArrangingWithItem done;
|
||||
done.newSourceBag = value;
|
||||
done.newSourceID = id;
|
||||
done.newSourceLot = value / 2;
|
||||
done.newSourceType = value / 3;
|
||||
done.newTargetID = ~id;
|
||||
done.newTargetLot = -value;
|
||||
done.newTargetType = value / 5;
|
||||
done.newTargetPosition = NiPoint3(4, 5, 6);
|
||||
done.oldItemBag = value / 7;
|
||||
done.oldItemID = id / 3;
|
||||
done.oldItemLot = value / 11;
|
||||
done.oldItemType = value / 13;
|
||||
const auto [legacyDone, doneBits] = ReadWithLegacy<LegacyGameMessages::LegacyDoneArranging>(done, LegacyGameMessages::ReadDoneArrangingWithItem);
|
||||
EXPECT_EQ(doneBits, Bits(done));
|
||||
const auto doneCopy = RoundTrip(done);
|
||||
EXPECT_EQ(doneCopy.newSourceBag, legacyDone.newSourceBAG);
|
||||
EXPECT_EQ(doneCopy.newSourceID, legacyDone.newSourceID);
|
||||
EXPECT_EQ(doneCopy.newSourceLot, legacyDone.newSourceLOT);
|
||||
EXPECT_EQ(doneCopy.newSourceType, legacyDone.newSourceTYPE);
|
||||
EXPECT_EQ(doneCopy.newTargetID, legacyDone.newTargetID);
|
||||
EXPECT_EQ(doneCopy.newTargetLot, legacyDone.newTargetLOT);
|
||||
EXPECT_EQ(doneCopy.newTargetType, legacyDone.newTargetTYPE);
|
||||
EXPECT_EQ(doneCopy.newTargetPosition, legacyDone.newTargetPOS);
|
||||
EXPECT_EQ(doneCopy.oldItemBag, legacyDone.oldItemBAG);
|
||||
EXPECT_EQ(doneCopy.oldItemID, legacyDone.oldItemID);
|
||||
EXPECT_EQ(doneCopy.oldItemLot, legacyDone.oldItemLOT);
|
||||
EXPECT_EQ(doneCopy.oldItemType, legacyDone.oldItemTYPE);
|
||||
ExpectTruncatedFails(done);
|
||||
|
||||
GameMessages::ModularBuildMoveAndEquip move;
|
||||
move.templateID = value;
|
||||
EXPECT_EQ(ReadWithLegacy<LOT>(move, LegacyGameMessages::ReadModularBuildMoveAndEquip).first, value);
|
||||
EXPECT_EQ(RoundTrip(move).templateID, value);
|
||||
ExpectTruncatedFails(move);
|
||||
}
|
||||
|
||||
GameMessages::ModularBuildConvertModel convert;
|
||||
convert.modelID = id;
|
||||
EXPECT_EQ(ReadWithLegacy<LWOOBJID>(convert, LegacyGameMessages::ReadObjectId).first, id);
|
||||
EXPECT_EQ(RoundTrip(convert).modelID, id);
|
||||
ExpectTruncatedFails(convert);
|
||||
|
||||
GameMessages::BBBLoadItemRequest load;
|
||||
load.itemID = id;
|
||||
EXPECT_EQ(ReadWithLegacy<LWOOBJID>(load, LegacyGameMessages::ReadObjectId).first, id);
|
||||
EXPECT_EQ(RoundTrip(load).itemID, id);
|
||||
ExpectTruncatedFails(load);
|
||||
|
||||
for (const bool hasTransform : { false, true }) {
|
||||
GameMessages::UnUseBBBModel unUse;
|
||||
unUse.bHasWorldTransform = hasTransform;
|
||||
unUse.modelID = id;
|
||||
const auto legacyUnUse = ReadWithLegacy<std::pair<bool, LWOOBJID>>(unUse, LegacyGameMessages::ReadUnUseModel).first;
|
||||
const auto unUseCopy = RoundTrip(unUse);
|
||||
EXPECT_EQ(unUseCopy.bHasWorldTransform, legacyUnUse.first);
|
||||
EXPECT_EQ(unUseCopy.modelID, legacyUnUse.second);
|
||||
ExpectTruncatedFails(unUse);
|
||||
unUse.worldPos = NiPoint3(1, 2, 3);
|
||||
unUse.worldRot = NiQuaternion(0.5f, 0.5f, 0.5f, 0.5f);
|
||||
const auto withTransform = RoundTrip(unUse);
|
||||
EXPECT_EQ(withTransform.worldPos, unUse.worldPos);
|
||||
EXPECT_EQ(withTransform.worldRot, unUse.worldRot);
|
||||
}
|
||||
|
||||
for (const auto& data : { std::string(), std::string("\x73\x64\x30\x01\xff\x00\x10", 7), std::string(5000, 'z') }) {
|
||||
GameMessages::BBBSaveRequest save;
|
||||
save.localID = id;
|
||||
save.lxfmlDataCompressed = data;
|
||||
save.timeTakenInMs = 1234;
|
||||
const auto [legacySave, saveBits] = ReadWithLegacy<LegacyGameMessages::LegacyBBBSaveRequest>(save, LegacyGameMessages::ReadBBBSaveRequest);
|
||||
EXPECT_EQ(saveBits, Bits(save));
|
||||
const auto saveCopy = RoundTrip(save);
|
||||
EXPECT_EQ(saveCopy.localID, legacySave.localId);
|
||||
EXPECT_EQ(saveCopy.lxfmlDataCompressed, legacySave.sd0);
|
||||
EXPECT_EQ(saveCopy.timeTakenInMs, legacySave.timeTaken);
|
||||
}
|
||||
GameMessages::BBBSaveRequest small;
|
||||
small.lxfmlDataCompressed = "abc";
|
||||
ExpectTruncatedFails(small);
|
||||
}
|
||||
|
||||
// ModularBuildFinish: the old handler read the parts only for 3 to 7 of them.
|
||||
for (const std::vector<LOT> modules : { std::vector<LOT>{ 1, 2, 3 }, std::vector<LOT>{ 8129, 8129, -1, 4, 5, 6, 7 } }) {
|
||||
GameMessages::ModularBuildFinish finish;
|
||||
finish.modules = modules;
|
||||
const auto [legacy, bits] = ReadWithLegacy<std::vector<uint32_t>>(finish, LegacyGameMessages::ReadModularBuildFinish);
|
||||
EXPECT_EQ(bits, Bits(finish));
|
||||
ASSERT_EQ(legacy.size(), modules.size());
|
||||
for (size_t i = 0; i < modules.size(); i++) EXPECT_EQ(static_cast<LOT>(legacy[i]), modules[i]);
|
||||
EXPECT_EQ(RoundTrip(finish).modules, modules);
|
||||
ExpectTruncatedFails(finish);
|
||||
}
|
||||
}
|
||||
|
||||
// Independent of the legacy code: hand computed payloads (RakNet writes MSB first).
|
||||
TEST_F(BuildingMessagesTests, GoldenBytes) {
|
||||
GameMessages::SetBuildModeConfirmed confirmed;
|
||||
confirmed.start = true;
|
||||
// 1, 1, 0, flag 1, 01 00 00 00, 8 zero bytes (player), flag 1, 12 zero bytes (position)
|
||||
EXPECT_PACKET_EQ(FromHex("d0 10 00 00 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 00", 197), Payload(confirmed));
|
||||
|
||||
GameMessages::ModularBuildFinish finish;
|
||||
finish.modules = { 6416, 1, 2 };
|
||||
EXPECT_PACKET_EQ(FromHex("03 10 19 00 00 01 00 00 00 02 00 00 00"), Payload(finish));
|
||||
|
||||
ClientPackets::BlueprintLoadItemResponse load;
|
||||
load.success = true;
|
||||
load.itemId = 515;
|
||||
load.destItemId = 990;
|
||||
RakNet::BitStream bitStream;
|
||||
load.WritePacket(bitStream);
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 17 00 00 00 00 01 03 02 00 00 00 00 00 00 de 03 00 00 00 00 00 00"), FromBitStream(bitStream));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
SET(DGAMEMESSAGES_TESTS
|
||||
"ActivityMessagesTests.cpp"
|
||||
"BuildingMessagesTests.cpp"
|
||||
"GameMessageTests.cpp"
|
||||
"GameMsgSplitTests.cpp"
|
||||
"MissionMessagesTests.cpp"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Action.h"
|
||||
#include "Amf3.h"
|
||||
#include "AMFDeserialize.h"
|
||||
#include "ClientPackets.h"
|
||||
#include "GameMessages.h"
|
||||
#include "GameDependencies.h"
|
||||
|
||||
@@ -51,7 +52,11 @@ protected:
|
||||
*
|
||||
*/
|
||||
TEST_F(GameMessageTests, SendBlueprintLoadItemResponse) {
|
||||
GameMessages::SendBlueprintLoadItemResponse(UNASSIGNED_SYSTEM_ADDRESS, true, 515, 990);
|
||||
ClientPackets::BlueprintLoadItemResponse response;
|
||||
response.success = true;
|
||||
response.itemId = 515;
|
||||
response.destItemId = 990;
|
||||
response.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
auto* bitStream = static_cast<dServerMock*>(Game::server)->GetMostRecentBitStream();
|
||||
ASSERT_NE(bitStream, nullptr);
|
||||
ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 200);
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
#ifndef BUILDINGMESSAGESLEGACY_H
|
||||
#define BUILDINGMESSAGESLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written GameMessages functions that BuildingMessages.h and the ClientPackets blueprint
|
||||
// packets replaced (dGame/dGameMessages/GameMessages.cpp and dWorldServer/WorldServer.cpp, branched from origin/main
|
||||
// 129199e4). Only the namespace changed. The Write* functions are the BLUEPRINT_SAVE_RESPONSE write sequences that
|
||||
// were inline in HandleUnUseModel, HandleBBBSaveRequest and WorldServer's level load, wrapped in a function; the
|
||||
// Read* functions are the read sequences of the replaced GameMessages::Handle* functions.
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "eBlueprintSaveResponseType.h"
|
||||
#include "Entity.h"
|
||||
#include "Game.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "NiPoint3.h"
|
||||
#include "ServiceType.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LegacyGameMessages {
|
||||
inline void SendStartArrangingWithItem(
|
||||
Entity* entity,
|
||||
const SystemAddress& sysAddr,
|
||||
bool bFirstTime,
|
||||
const LWOOBJID& buildAreaID,
|
||||
NiPoint3 buildStartPOS,
|
||||
int sourceBAG,
|
||||
const LWOOBJID& sourceID,
|
||||
LOT sourceLOT,
|
||||
int sourceTYPE,
|
||||
const LWOOBJID& targetID,
|
||||
LOT targetLOT,
|
||||
NiPoint3 targetPOS,
|
||||
int targetTYPE
|
||||
) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::START_ARRANGING_WITH_ITEM);
|
||||
|
||||
bitStream.Write(bFirstTime);
|
||||
bitStream.Write(buildAreaID != LWOOBJID_EMPTY);
|
||||
if (buildAreaID != LWOOBJID_EMPTY) bitStream.Write(buildAreaID);
|
||||
bitStream.Write(buildStartPOS);
|
||||
bitStream.Write(sourceBAG);
|
||||
bitStream.Write(sourceID);
|
||||
bitStream.Write(sourceLOT);
|
||||
bitStream.Write(sourceTYPE);
|
||||
bitStream.Write(targetID);
|
||||
bitStream.Write(targetLOT);
|
||||
bitStream.Write(targetPOS);
|
||||
bitStream.Write(targetTYPE);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendFinishArrangingWithItem(Entity* entity, const LWOOBJID& buildArea) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bool bFirstTime = true;
|
||||
const LWOOBJID& buildAreaID = buildArea;
|
||||
int newSourceBAG = 0;
|
||||
const LWOOBJID& newSourceID = LWOOBJID_EMPTY;
|
||||
LOT newSourceLOT = LOT_NULL;
|
||||
int newSourceTYPE = 0;
|
||||
const LWOOBJID& newTargetID = LWOOBJID_EMPTY;
|
||||
LOT newTargetLOT = LOT_NULL;
|
||||
int newTargetTYPE = 0;
|
||||
NiPoint3 newTargetPOS = NiPoint3();
|
||||
int oldItemBAG = 0;
|
||||
const LWOOBJID& oldItemID = LWOOBJID_EMPTY;
|
||||
LOT oldItemLOT = LOT_NULL;
|
||||
int oldItemTYPE = 0;
|
||||
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::FINISH_ARRANGING_WITH_ITEM);
|
||||
|
||||
bitStream.Write(buildAreaID != LWOOBJID_EMPTY);
|
||||
if (buildAreaID != LWOOBJID_EMPTY) bitStream.Write(buildAreaID);
|
||||
bitStream.Write(newSourceBAG);
|
||||
bitStream.Write(newSourceID);
|
||||
bitStream.Write(newSourceLOT);
|
||||
bitStream.Write(newSourceTYPE);
|
||||
bitStream.Write(newTargetID);
|
||||
bitStream.Write(newTargetLOT);
|
||||
bitStream.Write(newTargetTYPE);
|
||||
bitStream.Write(newTargetPOS);
|
||||
bitStream.Write(oldItemBAG);
|
||||
bitStream.Write(oldItemID);
|
||||
bitStream.Write(oldItemLOT);
|
||||
bitStream.Write(oldItemTYPE);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendModularBuildEnd(Entity* entity) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::MODULAR_BUILD_END);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendSetBuildModeConfirmed(LWOOBJID objectId, const SystemAddress& sysAddr, bool start, bool warnVisitors, bool modePaused, int32_t modeValue, LWOOBJID playerId, NiPoint3 startPos) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SET_BUILD_MODE_CONFIRMED);
|
||||
|
||||
bitStream.Write(start);
|
||||
bitStream.Write(warnVisitors);
|
||||
bitStream.Write(modePaused);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(modeValue);
|
||||
bitStream.Write(playerId);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(startPos);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendBlueprintLoadItemResponse(const SystemAddress& sysAddr, bool success, LWOOBJID oldItemId, LWOOBJID newItemId) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_LOAD_RESPONSE_ITEMID);
|
||||
bitStream.Write<uint8_t>(success);
|
||||
bitStream.Write<LWOOBJID>(oldItemId);
|
||||
bitStream.Write<LWOOBJID>(newItemId);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// GameMessages::HandleUnUseModel, when unknown was set
|
||||
inline void WriteUnUseModelSaveResponse(const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||
bitStream.Write<LWOOBJID>(LWOOBJID_EMPTY); //always zero so that a check on the client passes
|
||||
bitStream.Write(eBlueprintSaveResponseType::PlacementFailed); // Sending a non-zero error code here prevents the client from deleting its in progress build for some reason?
|
||||
bitStream.Write<uint32_t>(0);
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// GameMessages::HandleBBBSaveRequest's response (models: blueprint ID and the sd0 chunks written one after another)
|
||||
inline void WriteBBBSaveResponse(const SystemAddress& sysAddr, LWOOBJID localId, const std::vector<std::pair<LWOOBJID, std::vector<std::string>>>& splitLxfmls) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||
bitStream.Write(localId);
|
||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||
bitStream.Write<uint32_t>(splitLxfmls.size());
|
||||
for (size_t i = 0; i < splitLxfmls.size(); ++i) {
|
||||
const auto blueprintID = splitLxfmls[i].first;
|
||||
// Write the ID and data to the response packet
|
||||
bitStream.Write(blueprintID);
|
||||
|
||||
const auto& newSd0 = splitLxfmls[i].second;
|
||||
uint32_t newSd0Size{};
|
||||
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
|
||||
bitStream.Write(newSd0Size);
|
||||
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
||||
}
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// WorldServer's level load: the property's brick built models (blueprint ID and LXFML)
|
||||
inline void WriteLevelLoadSaveResponse(const SystemAddress& sysAddr, const std::vector<std::pair<LWOOBJID, std::string>>& bbbModels) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||
bitStream.Write<LWOOBJID>(LWOOBJID_EMPTY); //always zero so that a check on the client passes
|
||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||
bitStream.Write<uint32_t>(bbbModels.size());
|
||||
for (auto& bbbModel : bbbModels) {
|
||||
size_t lxfmlSize = bbbModel.second.size();
|
||||
|
||||
// write data
|
||||
LWOOBJID blueprintID = bbbModel.first;
|
||||
bitStream.Write(blueprintID);
|
||||
bitStream.Write<uint32_t>(lxfmlSize);
|
||||
bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(bbbModel.second.c_str()), lxfmlSize);
|
||||
}
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// GameMessages::HandleStartBuildingWithItem
|
||||
struct LegacyStartBuildingWithItem { bool firstTime{}; bool success{}; int32_t sourceBag{}; LWOOBJID sourceId{}; LOT sourceLot{}; int32_t sourceType{}; LWOOBJID targetId{}; LOT targetLot{}; NiPoint3 targetPosition{}; int32_t targetType{}; };
|
||||
inline LegacyStartBuildingWithItem ReadStartBuildingWithItem(RakNet::BitStream& inStream) {
|
||||
bool firstTime{};
|
||||
bool success{};
|
||||
int32_t sourceBag{};
|
||||
LWOOBJID sourceId{};
|
||||
LOT sourceLot{};
|
||||
int32_t sourceType{};
|
||||
LWOOBJID targetId{};
|
||||
LOT targetLot{};
|
||||
NiPoint3 targetPosition{};
|
||||
int32_t targetType{};
|
||||
|
||||
inStream.Read(firstTime);
|
||||
inStream.Read(success);
|
||||
inStream.Read(sourceBag);
|
||||
inStream.Read(sourceId);
|
||||
inStream.Read(sourceLot);
|
||||
inStream.Read(sourceType);
|
||||
inStream.Read(targetId);
|
||||
inStream.Read(targetLot);
|
||||
inStream.Read(targetPosition);
|
||||
inStream.Read(targetType);
|
||||
return { firstTime, success, sourceBag, sourceId, sourceLot, sourceType, targetId, targetLot, targetPosition, targetType };
|
||||
}
|
||||
|
||||
// GameMessages::HandleSetBuildMode
|
||||
struct LegacySetBuildMode { bool start{}; int32_t distanceType{}; bool modePaused{}; int modeValue{}; LWOOBJID playerId{}; NiPoint3 startPosition; };
|
||||
inline LegacySetBuildMode ReadSetBuildMode(RakNet::BitStream& inStream) {
|
||||
bool start{};
|
||||
int32_t distanceType = -1;
|
||||
bool modePaused{};
|
||||
int modeValue = 1;
|
||||
LWOOBJID playerId{};
|
||||
NiPoint3 startPosition = NiPoint3Constant::ZERO;
|
||||
|
||||
inStream.Read(start);
|
||||
|
||||
if (inStream.ReadBit())
|
||||
inStream.Read(distanceType);
|
||||
|
||||
inStream.Read(modePaused);
|
||||
|
||||
if (inStream.ReadBit())
|
||||
inStream.Read(modeValue);
|
||||
|
||||
inStream.Read(playerId);
|
||||
|
||||
if (inStream.ReadBit())
|
||||
inStream.Read(startPosition);
|
||||
return { start, distanceType, modePaused, modeValue, playerId, startPosition };
|
||||
}
|
||||
|
||||
// GameMessages::HandleBuildModeSet (reads only the first field)
|
||||
inline bool ReadBuildModeSet(RakNet::BitStream& inStream) {
|
||||
bool bStart = false;
|
||||
|
||||
inStream.Read(bStart);
|
||||
return bStart;
|
||||
}
|
||||
|
||||
// GameMessages::HandleDoneArrangingWithItem
|
||||
struct LegacyDoneArranging { int newSourceBAG{}; LWOOBJID newSourceID{}; LOT newSourceLOT{}; int newSourceTYPE{}; LWOOBJID newTargetID{}; LOT newTargetLOT{}; int newTargetTYPE{}; NiPoint3 newTargetPOS; int oldItemBAG{}; LWOOBJID oldItemID{}; LOT oldItemLOT{}; int oldItemTYPE{}; };
|
||||
inline LegacyDoneArranging ReadDoneArrangingWithItem(RakNet::BitStream& inStream) {
|
||||
int newSourceBAG = 0;
|
||||
LWOOBJID newSourceID = 0;
|
||||
LOT newSourceLOT = 0;
|
||||
int newSourceTYPE = 0;
|
||||
LWOOBJID newTargetID = 0;
|
||||
LOT newTargetLOT = 0;
|
||||
int newTargetTYPE = 0;
|
||||
NiPoint3 newTargetPOS;
|
||||
int oldItemBAG = 0;
|
||||
LWOOBJID oldItemID = 0;
|
||||
LOT oldItemLOT = 0;
|
||||
int oldItemTYPE = 0;
|
||||
|
||||
inStream.Read(newSourceBAG);
|
||||
inStream.Read(newSourceID);
|
||||
inStream.Read(newSourceLOT);
|
||||
inStream.Read(newSourceTYPE);
|
||||
inStream.Read(newTargetID);
|
||||
inStream.Read(newTargetLOT);
|
||||
inStream.Read(newTargetTYPE);
|
||||
inStream.Read(newTargetPOS);
|
||||
inStream.Read(oldItemBAG);
|
||||
inStream.Read(oldItemID);
|
||||
inStream.Read(oldItemLOT);
|
||||
inStream.Read(oldItemTYPE);
|
||||
return { newSourceBAG, newSourceID, newSourceLOT, newSourceTYPE, newTargetID, newTargetLOT, newTargetTYPE, newTargetPOS, oldItemBAG, oldItemID, oldItemLOT, oldItemTYPE };
|
||||
}
|
||||
|
||||
// GameMessages::HandleModularBuildFinish (the parts are only read for 3 to 7 parts)
|
||||
inline std::vector<uint32_t> ReadModularBuildFinish(RakNet::BitStream& inStream) {
|
||||
uint8_t count; // 3 for rockets, 7 for cars
|
||||
|
||||
inStream.Read(count);
|
||||
|
||||
std::vector<uint32_t> modList;
|
||||
if (count >= 3 && count < 8) {
|
||||
for (uint32_t k = 0; k < count; k++) {
|
||||
uint32_t mod;
|
||||
inStream.Read(mod);
|
||||
modList.push_back(mod);
|
||||
}
|
||||
}
|
||||
return modList;
|
||||
}
|
||||
|
||||
// GameMessages::HandleModularBuildMoveAndEquip
|
||||
inline LOT ReadModularBuildMoveAndEquip(RakNet::BitStream& inStream) {
|
||||
LOT templateID;
|
||||
|
||||
inStream.Read(templateID);
|
||||
return templateID;
|
||||
}
|
||||
|
||||
// GameMessages::HandleModularBuildConvertModel and HandleBBBLoadItemRequest
|
||||
inline LWOOBJID ReadObjectId(RakNet::BitStream& inStream) {
|
||||
LWOOBJID modelID;
|
||||
|
||||
inStream.Read(modelID);
|
||||
return modelID;
|
||||
}
|
||||
|
||||
// GameMessages::HandleUnUseModel (reads only the first two fields)
|
||||
inline std::pair<bool, LWOOBJID> ReadUnUseModel(RakNet::BitStream& inStream) {
|
||||
bool unknown{};
|
||||
LWOOBJID objIdToAddToInventory{};
|
||||
inStream.Read(unknown);
|
||||
inStream.Read(objIdToAddToInventory);
|
||||
return { unknown, objIdToAddToInventory };
|
||||
}
|
||||
|
||||
// GameMessages::HandleBBBSaveRequest
|
||||
struct LegacyBBBSaveRequest { LWOOBJID localId{}; std::string sd0; uint32_t timeTaken{}; };
|
||||
inline LegacyBBBSaveRequest ReadBBBSaveRequest(RakNet::BitStream& inStream) {
|
||||
LWOOBJID localId;
|
||||
|
||||
inStream.Read(localId);
|
||||
|
||||
uint32_t sd0Size;
|
||||
inStream.Read(sd0Size);
|
||||
|
||||
std::unique_ptr<char[]> sd0Data;
|
||||
sd0Data.reset(new char[sd0Size]);
|
||||
|
||||
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
|
||||
|
||||
uint32_t timeTaken;
|
||||
inStream.Read(timeTaken);
|
||||
return { localId, std::string(sd0Data.get(), sd0Size), timeTaken };
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BUILDINGMESSAGESLEGACY_H
|
||||
Reference in New Issue
Block a user