mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
Live sent duration 0.0 in all 744 successful EnableRebuild (cancels carry the time spent building, which DLU already matches); DLU sent the reset time. Tests get EntityManager::_addEntity/_removeEntity to make an entity built outside CreateEntity findable, and helpers that read game messages back out of captured packets. Check: finish a quickbuild; the build completes, the celebrate animation plays and the built object stays up for its usual time before resetting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.9 KiB
C++
168 lines
6.9 KiB
C++
#ifndef GAMEMESSAGETESTUTILS_H
|
|
#define GAMEMESSAGETESTUTILS_H
|
|
|
|
// Shared helpers for the byte-equality tests of converted game messages (see docs/PacketArchitecture.md).
|
|
|
|
#include "GameMessages.h"
|
|
#include "PacketTestUtils.h"
|
|
|
|
#include <array>
|
|
#include <functional>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
namespace GameMessageTestUtils {
|
|
using namespace PacketTestUtils;
|
|
|
|
inline SystemAddress ClientAddress() {
|
|
SystemAddress address;
|
|
address.binaryAddress = 0x0100007f;
|
|
address.port = 2003;
|
|
return address;
|
|
}
|
|
|
|
// Synthetic object IDs: empty, a player-style ID and one with every byte different.
|
|
inline const std::array<LWOOBJID, 3> g_Targets = { LWOOBJID_EMPTY, 0x1000000000000001LL, 0x0102030405060708LL };
|
|
inline const std::array<SystemAddress, 2> g_Addresses = { ClientAddress(), UNASSIGNED_SYSTEM_ADDRESS };
|
|
|
|
// The complete packet msg.Send(sysAddr) puts on the wire.
|
|
inline PacketBytes StructPacket(const GameMessages::NetGameMsg& msg) {
|
|
RakNet::BitStream bitStream;
|
|
msg.WritePacket(bitStream);
|
|
return FromBitStream(bitStream);
|
|
}
|
|
|
|
// How the struct is sent in a comparison: Send (UNASSIGNED broadcasts; legacy functions that did
|
|
// "if (UNASSIGNED) SEND_PACKET_BROADCAST; SEND_PACKET;"), SendToClient (legacy functions with only SEND_PACKET)
|
|
// or Broadcast (legacy functions with only SEND_PACKET_BROADCAST, whatever address they were given: the struct is
|
|
// sent with Send(UNASSIGNED_SYSTEM_ADDRESS)).
|
|
enum class SendMode { Send, SendToClient, Broadcast };
|
|
|
|
// Sends the same message through the frozen legacy function and through the struct, to one client and to
|
|
// UNASSIGNED_SYSTEM_ADDRESS, and requires identical bytes and the same effective destination.
|
|
// legacySend receives the address to send to.
|
|
inline void ExpectSameAsLegacy(const std::function<void(const SystemAddress&)>& legacySend, const GameMessages::NetGameMsg& msg, SendMode mode = SendMode::Send) {
|
|
for (const auto& address : g_Addresses) {
|
|
SCOPED_TRACE(address == UNASSIGNED_SYSTEM_ADDRESS ? "unassigned address" : "single client");
|
|
const auto legacyPackets = Capture([&] { legacySend(address); });
|
|
const auto newPackets = Capture([&] {
|
|
if (mode == SendMode::Send) msg.Send(address);
|
|
else if (mode == SendMode::SendToClient) msg.SendToClient(address);
|
|
else msg.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
|
});
|
|
|
|
ASSERT_FALSE(legacyPackets.empty());
|
|
ASSERT_EQ(newPackets.size(), 1);
|
|
// The legacy broadcast path also did a second Send(UNASSIGNED_SYSTEM_ADDRESS, broadcast = false),
|
|
// which RakPeer::Send rejects without sending anything. Every copy must still match byte for byte.
|
|
for (const auto& legacyPacket : legacyPackets) {
|
|
EXPECT_PACKET_EQ(FromCapture(legacyPacket), FromCapture(newPackets[0]));
|
|
}
|
|
EXPECT_EQ(legacyPackets[0].broadcast, newPackets[0].broadcast);
|
|
EXPECT_EQ(legacyPackets[0].sysAddr, newPackets[0].sysAddr);
|
|
EXPECT_PACKET_EQ(FromCapture(newPackets[0]), StructPacket(msg));
|
|
}
|
|
}
|
|
|
|
// Serializes msg, reads it back into a fresh T and checks the copy consumed every bit and serializes to the
|
|
// same bytes. Returns the copy so callers can compare fields.
|
|
template<typename T>
|
|
T RoundTrip(const T& msg) {
|
|
RakNet::BitStream bitStream;
|
|
msg.Serialize(bitStream);
|
|
T copy;
|
|
EXPECT_TRUE(copy.Deserialize(bitStream));
|
|
EXPECT_EQ(bitStream.GetNumberOfUnreadBits(), 0);
|
|
RakNet::BitStream again;
|
|
copy.Serialize(again);
|
|
EXPECT_PACKET_EQ(FromBitStream(bitStream), FromBitStream(again));
|
|
return copy;
|
|
}
|
|
|
|
// Reads a whole game message packet copied from a live capture (hex), requires its header to carry T's message ID
|
|
// and the payload to be consumed up to the byte padding, and requires the struct to write the same bytes back.
|
|
template<typename T>
|
|
T FromLiveCapture(const std::string& hex) {
|
|
auto live = FromHex(hex);
|
|
RakNet::BitStream bitStream(live.bytes.data(), live.bytes.size(), false);
|
|
T msg;
|
|
LWOOBJID target{};
|
|
MessageType::Game msgId{};
|
|
EXPECT_TRUE(GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, msgId));
|
|
EXPECT_EQ(msgId, msg.msgId);
|
|
EXPECT_TRUE(msg.Deserialize(bitStream));
|
|
EXPECT_LT(bitStream.GetNumberOfUnreadBits(), 8u);
|
|
msg.target = target;
|
|
EXPECT_EQ(StructPacket(msg).bytes, live.bytes);
|
|
return msg;
|
|
}
|
|
|
|
// Reads a whole client -> server game message packet copied from a live capture (hex; header 53 04 00 05, then the
|
|
// object ID and the message ID), requires the message ID to be T's and the payload to be consumed up to the byte
|
|
// padding, and requires the struct to write the same payload back.
|
|
template<typename T>
|
|
T FromLiveClientCapture(const std::string& hex) {
|
|
auto live = FromHex(hex);
|
|
EXPECT_GE(live.bytes.size(), 18u);
|
|
EXPECT_EQ(std::vector<uint8_t>(live.bytes.begin(), live.bytes.begin() + 4), (std::vector<uint8_t>{ 0x53, 0x04, 0x00, 0x05 }));
|
|
RakNet::BitStream bitStream(live.bytes.data(), live.bytes.size(), false);
|
|
bitStream.IgnoreBytes(8);
|
|
T msg;
|
|
MessageType::Game msgId{};
|
|
EXPECT_TRUE(bitStream.Read(msg.target));
|
|
EXPECT_TRUE(bitStream.Read(msgId));
|
|
EXPECT_EQ(msgId, msg.msgId);
|
|
EXPECT_TRUE(msg.Deserialize(bitStream));
|
|
EXPECT_LT(bitStream.GetNumberOfUnreadBits(), 8u);
|
|
RakNet::BitStream again;
|
|
again.WriteBits(live.bytes.data(), 18 * 8);
|
|
msg.Serialize(again);
|
|
EXPECT_EQ(FromBitStream(again).bytes, live.bytes);
|
|
return msg;
|
|
}
|
|
|
|
// The game message ID of every captured game message, in send order; other packets are skipped.
|
|
inline std::vector<MessageType::Game> SentGameMessageIds(const std::vector<CapturedPacket>& packets) {
|
|
std::vector<MessageType::Game> ids;
|
|
for (const auto& packet : packets) {
|
|
RakNet::BitStream bitStream(const_cast<unsigned char*>(packet.bytes.data()), packet.bytes.size(), false);
|
|
LWOOBJID target{};
|
|
MessageType::Game msgId{};
|
|
if (GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, msgId)) ids.push_back(msgId);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
// Every captured game message of type T, read back with its target, in send order.
|
|
template<typename T>
|
|
std::vector<T> SentGameMessages(const std::vector<CapturedPacket>& packets) {
|
|
std::vector<T> found;
|
|
for (const auto& packet : packets) {
|
|
RakNet::BitStream bitStream(const_cast<unsigned char*>(packet.bytes.data()), packet.bytes.size(), false);
|
|
T msg;
|
|
LWOOBJID target{};
|
|
MessageType::Game msgId{};
|
|
if (!GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, msgId) || msgId != msg.msgId) continue;
|
|
EXPECT_TRUE(msg.Deserialize(bitStream));
|
|
msg.target = target;
|
|
found.push_back(msg);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// Checks that every strict prefix of msg's serialized payload fails to deserialize.
|
|
template<typename T>
|
|
void ExpectTruncatedFails(const T& msg) {
|
|
RakNet::BitStream full;
|
|
msg.Serialize(full);
|
|
for (uint32_t bits = 0; bits < full.GetNumberOfBitsUsed(); bits++) {
|
|
RakNet::BitStream prefix;
|
|
prefix.WriteBits(full.GetData(), bits, false);
|
|
T copy;
|
|
EXPECT_FALSE(copy.Deserialize(prefix)) << "deserialized from only " << bits << " bits";
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // GAMEMESSAGETESTUTILS_H
|