diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index 72f6f1340..45c181713 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -248,7 +248,9 @@ namespace ClientPackets { ldfData.Insert(u"name", name); ldfData.Insert(u"gmlevel", static_cast(gmLevel)); ldfData.Insert(u"chatmode", chatMode); - ldfData.Insert(u"reputation", reputation); + // Live sent reputation as LDF type 8 (451 of 451 character data packets); the old packet has it as type 9 + if (sendWorldKeys) ldfData.Insert(u"reputation", static_cast(reputation)); + else ldfData.Insert(u"reputation", reputation); ldfData.Insert(u"propertycloneid", propertyCloneID); if (txfring) ldfData.Insert(u"txfring", true); if (sendWorldKeys) { @@ -373,6 +375,7 @@ namespace ClientPackets { uint64_t value{}; VALIDATE_READ(data.Read(value)); if (key == u"levelid") { levelID = value; sendWorldKeys = true; } + else if (key == u"reputation") reputation = static_cast(value); break; } case LDF_TYPE_FLOAT: { diff --git a/tests/dGameTests/dNetTests/WorldPacketsTests.cpp b/tests/dGameTests/dNetTests/WorldPacketsTests.cpp index e1634db71..a4aa9d638 100644 --- a/tests/dGameTests/dNetTests/WorldPacketsTests.cpp +++ b/tests/dGameTests/dNetTests/WorldPacketsTests.cpp @@ -1,3 +1,5 @@ +#include "ZCompression.h" +#include "LDFFormat.h" #include "ChatPackets.h" #include "ClientPackets.h" #include "GameDependencies.h" @@ -365,6 +367,66 @@ TEST_F(WorldPacketsTests, CreateCharacterSendsTheWorldKeys) { EXPECT_FALSE(without.requiresRename); } +// Live sent reputation as LDF type 8 (a 64-bit number), not as an object ID (type 9), in every character data packet. +// The entries are sorted by key: reputation follows propertycloneid. +TEST_F(WorldPacketsTests, CreateCharacterSendsReputationAsLdfType8) { + const auto reputationType = [](const ClientPackets::CreateCharacter& packet) { + RakNet::BitStream bitStream; + packet.Serialize(bitStream); + // The LDF is compressed: size + 9, the compressed flag, the size, the compressed size, the data + uint32_t sizePlusHeader{}; + uint8_t compressed{}; + uint32_t uncompressedSize{}; + uint32_t compressedSize{}; + EXPECT_TRUE(bitStream.Read(sizePlusHeader)); + EXPECT_TRUE(bitStream.Read(compressed)); + EXPECT_TRUE(bitStream.Read(uncompressedSize)); + EXPECT_TRUE(bitStream.Read(compressedSize)); + std::vector compressedData(compressedSize); + EXPECT_TRUE(bitStream.ReadAlignedBytes(compressedData.data(), compressedSize)); + std::vector uncompressed(uncompressedSize); + int32_t error{}; + EXPECT_EQ(ZCompression::Decompress(compressedData.data(), compressedSize, uncompressed.data(), uncompressedSize, error), static_cast(uncompressedSize)); + RakNet::BitStream data(uncompressed.data(), uncompressed.size(), false); + uint32_t count{}; + EXPECT_TRUE(data.Read(count)); + for (uint32_t i = 0; i < count; i++) { + uint8_t keyBytes{}; + EXPECT_TRUE(data.Read(keyBytes)); + std::u16string key(keyBytes / 2, u'\0'); + for (auto& character : key) EXPECT_TRUE(data.Read(character)); + uint8_t type{}; + EXPECT_TRUE(data.Read(type)); + if (key == u"reputation") { + int64_t value{}; + EXPECT_TRUE(data.Read(value)); + EXPECT_EQ(value, 300); + return static_cast(type); + } + switch (type) { + case LDF_TYPE_S32: case LDF_TYPE_U32: case LDF_TYPE_FLOAT: data.IgnoreBytes(4); break; + case LDF_TYPE_BOOLEAN: data.IgnoreBytes(1); break; + case LDF_TYPE_U64: case LDF_TYPE_OBJID: data.IgnoreBytes(8); break; + case LDF_TYPE_UTF_16: { uint32_t length{}; EXPECT_TRUE(data.Read(length)); data.IgnoreBytes(length * 2); break; } + case LDF_TYPE_UTF_8: { uint32_t length{}; EXPECT_TRUE(data.Read(length)); data.IgnoreBytes(length); break; } + default: ADD_FAILURE() << "LDF type " << static_cast(type); return -1; + } + } + return -1; + }; + + ClientPackets::CreateCharacter packet; + packet.xmlData = ""; + packet.reputation = 300; + EXPECT_EQ(reputationType(packet), 8); + EXPECT_EQ(RoundTrip(packet).reputation, 300); + + // The packet from before the world keys keeps its type + packet.sendWorldKeys = false; + EXPECT_EQ(reputationType(packet), 9); + EXPECT_EQ(RoundTrip(packet).reputation, 300); +} + TEST_F(WorldPacketsTests, CharacterListMatchesLegacy) { FakeCharacter first; FakeCharacter second;