Files
DarkflameServer/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp
Aaron Kimbrell 0c2727ad3c 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>
2026-09-28 22:30:49 -05:00

267 lines
10 KiB
C++

#include "Action.h"
#include "Amf3.h"
#include "AMFDeserialize.h"
#include "ClientPackets.h"
#include "GameMessages.h"
#include "GameDependencies.h"
#include <gtest/gtest.h>
// Message includes
#include "AddActionMessage.h"
#include "AddStripMessage.h"
#include "AddMessage.h"
#include "MigrateActionsMessage.h"
#include "MoveToInventoryMessage.h"
#include "MergeStripsMessage.h"
#include "RearrangeStripMessage.h"
#include "RemoveActionsMessage.h"
#include "RemoveStripMessage.h"
#include "RenameMessage.h"
#include "SplitStripMessage.h"
#include "UpdateActionMessage.h"
#include "UpdateStripUiMessage.h"
class GameMessageTests: public GameDependenciesTest {
protected:
void SetUp() override {
SetUpDependencies();
}
void TearDown() override {
TearDownDependencies();
}
std::string ReadFromFile(std::string filename) {
std::ifstream file(filename, std::ios::binary);
std::string readFile;
while (file.good()) {
char readCharacter = file.get();
readFile.push_back(readCharacter);
}
return readFile;
}
std::unique_ptr<AMFArrayValue> ReadArrayFromBitStream(RakNet::BitStream& inStream) {
AMFDeserialize des;
AMFBaseValue* readArray = des.Read(inStream).release();
EXPECT_EQ(readArray->GetValueType(), eAmf::Array);
return std::unique_ptr<AMFArrayValue>{ static_cast<AMFArrayValue*>(readArray) };
}
};
/**
* @brief Tests that the serialization struct BlueprintLoadItemResponse is serialized correctly
*
*/
TEST_F(GameMessageTests, SendBlueprintLoadItemResponse) {
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);
// First read in the packets' header
uint8_t rakNetPacketId{};
uint16_t remoteServiceType{};
uint32_t packetId{};
uint8_t always0{};
bitStream->Read(rakNetPacketId);
bitStream->Read(remoteServiceType);
bitStream->Read(packetId);
bitStream->Read(always0);
ASSERT_EQ(rakNetPacketId, 0x53);
ASSERT_EQ(remoteServiceType, 0x5);
ASSERT_EQ(packetId, 0x17);
ASSERT_EQ(always0, 0x00);
// Next read in packet data
uint8_t bSuccess{}; // unsigned bool
LWOOBJID previousId{};
LWOOBJID newId{};
bitStream->Read(bSuccess);
bitStream->Read(previousId);
bitStream->Read(newId);
ASSERT_EQ(bSuccess, static_cast<uint8_t>(true));
ASSERT_EQ(previousId, 515);
ASSERT_EQ(newId, 990);
ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 0);
}
TEST_F(GameMessageTests, ControlBehaviorAddStrip) {
auto data = ReadFromFile("addStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddStripMessage addStrip(*arr);
ASSERT_FLOAT_EQ(addStrip.GetPosition().GetX(), 50.65);
ASSERT_FLOAT_EQ(addStrip.GetPosition().GetY(), 178.05);
ASSERT_EQ(addStrip.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(addStrip.GetActionContext().GetStateId()), 0);
ASSERT_EQ(addStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetType(), "DropImagination");
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterName(), "Amount");
ASSERT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterString(), "");
ASSERT_FLOAT_EQ(addStrip.GetActionsToAdd().front().GetValueParameterDouble(), 1.0);
}
TEST_F(GameMessageTests, ControlBehaviorRemoveStrip) {
auto data = ReadFromFile("removeStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RemoveStripMessage removeStrip(*arr);
ASSERT_EQ(static_cast<int32_t>(removeStrip.GetActionContext().GetStripId()), 1);
ASSERT_EQ(static_cast<int32_t>(removeStrip.GetActionContext().GetStateId()), 0);
ASSERT_EQ(removeStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorMergeStrips) {
auto data = ReadFromFile("mergeStrips");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
MergeStripsMessage mergeStrips(*arr);
ASSERT_EQ(mergeStrips.GetSourceActionContext().GetStripId(), 2);
ASSERT_EQ(mergeStrips.GetDestinationActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(mergeStrips.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(mergeStrips.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(mergeStrips.GetDstActionIndex(), 0);
ASSERT_EQ(mergeStrips.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorSplitStrip) {
auto data = ReadFromFile("splitStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
SplitStripMessage splitStrip(*arr);
ASSERT_EQ(splitStrip.GetBehaviorId(), -1);
ASSERT_FLOAT_EQ(splitStrip.GetPosition().GetX(), 275.65);
ASSERT_FLOAT_EQ(splitStrip.GetPosition().GetY(), 28.7);
ASSERT_EQ(splitStrip.GetSourceActionContext().GetStripId(), 0);
ASSERT_EQ(splitStrip.GetDestinationActionContext().GetStripId(), 2);
ASSERT_EQ(static_cast<uint32_t>(splitStrip.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(splitStrip.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(splitStrip.GetSrcActionIndex(), 1);
}
TEST_F(GameMessageTests, ControlBehaviorUpdateStripUI) {
auto data = ReadFromFile("updateStripUI");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
UpdateStripUiMessage updateStripUi(*arr);
ASSERT_FLOAT_EQ(updateStripUi.GetPosition().GetX(), 116.65);
ASSERT_FLOAT_EQ(updateStripUi.GetPosition().GetY(), 35.35);
ASSERT_EQ(updateStripUi.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(updateStripUi.GetActionContext().GetStateId()), 0);
ASSERT_EQ(updateStripUi.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorAddAction) {
auto data = ReadFromFile("addAction");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddActionMessage addAction(*arr);
ASSERT_EQ(addAction.GetActionIndex(), 3);
ASSERT_EQ(addAction.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(addAction.GetActionContext().GetStateId()), 0);
ASSERT_EQ(addAction.GetAction().GetType(), "DoDamage");
ASSERT_EQ(addAction.GetAction().GetValueParameterName(), "");
ASSERT_EQ(addAction.GetAction().GetValueParameterString(), "");
ASSERT_EQ(addAction.GetAction().GetValueParameterDouble(), 0.0);
ASSERT_EQ(addAction.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorMigrateActions) {
auto data = ReadFromFile("migrateActions");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
MigrateActionsMessage migrateActions(*arr);
ASSERT_EQ(migrateActions.GetSrcActionIndex(), 1);
ASSERT_EQ(migrateActions.GetDstActionIndex(), 2);
ASSERT_EQ(migrateActions.GetSourceActionContext().GetStripId(), 1);
ASSERT_EQ(migrateActions.GetDestinationActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(migrateActions.GetSourceActionContext().GetStateId()), 0);
ASSERT_EQ(static_cast<uint32_t>(migrateActions.GetDestinationActionContext().GetStateId()), 0);
ASSERT_EQ(migrateActions.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorRearrangeStrip) {
auto data = ReadFromFile("rearrangeStrip");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RearrangeStripMessage rearrangeStrip(*arr);
ASSERT_EQ(rearrangeStrip.GetSrcActionIndex(), 2);
ASSERT_EQ(rearrangeStrip.GetDstActionIndex(), 1);
ASSERT_EQ(rearrangeStrip.GetActionContext().GetStripId(), 0);
ASSERT_EQ(rearrangeStrip.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(static_cast<uint32_t>(rearrangeStrip.GetActionContext().GetStateId()), 0);
}
TEST_F(GameMessageTests, ControlBehaviorAdd) {
auto data = ReadFromFile("add");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
AddMessage add(*arr, LWOOBJID_EMPTY);
ASSERT_EQ(add.GetBehaviorId(), 10446);
ASSERT_EQ(add.GetBehaviorIndex(), 0);
}
TEST_F(GameMessageTests, ControlBehaviorRemoveActions) {
auto data = ReadFromFile("removeActions");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RemoveActionsMessage removeActions(*arr);
ASSERT_EQ(removeActions.GetBehaviorId(), -1);
ASSERT_EQ(removeActions.GetActionIndex(), 1);
ASSERT_EQ(removeActions.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(removeActions.GetActionContext().GetStateId()), 0);
}
TEST_F(GameMessageTests, ControlBehaviorRename) {
auto data = ReadFromFile("rename");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
RenameMessage rename(*arr);
ASSERT_EQ(rename.GetName(), "test");
ASSERT_EQ(rename.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
}
TEST_F(GameMessageTests, ControlBehaviorUpdateAction) {
auto data = ReadFromFile("updateAction");
RakNet::BitStream inStream(reinterpret_cast<unsigned char*>(&data[0]), data.length(), true);
const auto arr = ReadArrayFromBitStream(inStream);
UpdateActionMessage updateAction(*arr);
ASSERT_EQ(updateAction.GetAction().GetType(), "FlyDown");
ASSERT_EQ(updateAction.GetAction().GetValueParameterName(), "Distance");
ASSERT_EQ(updateAction.GetAction().GetValueParameterString(), "");
ASSERT_EQ(updateAction.GetAction().GetValueParameterDouble(), 50.0);
ASSERT_EQ(updateAction.GetBehaviorId(), BehaviorMessageBase::DefaultBehaviorId);
ASSERT_EQ(updateAction.GetActionIndex(), 1);
ASSERT_EQ(updateAction.GetActionContext().GetStripId(), 0);
ASSERT_EQ(static_cast<uint32_t>(updateAction.GetActionContext().GetStateId()), 0);
}