From df3f46b43beb9b175a7c75dbc09a7d239d929b31 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Oct 2026 10:33:01 -0500 Subject: [PATCH] fix(character): the character data packet sends txfring=true, as live LWOInventoryComponent_Client::LoadConfigData (0x00cde200) reads txfring from the character data; without it the client runs its inventory-full tips (msgPlayerReady 0x00bde5e0) on every world load. Of 226 captured live character data packets, 222 had the key, all true; the server never wrote it. Co-Authored-By: Claude Opus 5.5 --- dNet/ClientPackets.cpp | 8 ++++++++ dNet/ClientPackets.h | 4 ++++ dNet/PacketFields.inc | 1 + tests/dGameTests/dNetTests/WorldPacketsTests.cpp | 11 +++++++++++ 4 files changed, 24 insertions(+) diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index 94a87e7e8..424803d8e 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -250,6 +250,7 @@ namespace ClientPackets { ldfData.Insert(u"chatmode", chatMode); ldfData.Insert(u"reputation", reputation); ldfData.Insert(u"propertycloneid", propertyCloneID); + if (txfring) ldfData.Insert(u"txfring", true); data.Write(ldfData.values.size()); for (const auto& toSerialize : ldfData | std::views::values) toSerialize->WriteToPacket(data); @@ -307,6 +308,7 @@ namespace ClientPackets { if (decompressedSize != static_cast(uncompressedSize)) return false; RakNet::BitStream data(uncompressed.data(), uncompressed.size(), false); + txfring = false; // the client's value without the key uint32_t count{}; VALIDATE_READ(data.Read(count)); for (uint32_t i = 0; i < count; i++) { @@ -343,6 +345,12 @@ namespace ClientPackets { if (key == u"xmlData") xmlData = value; break; } + case LDF_TYPE_BOOLEAN: { + uint8_t value{}; + VALIDATE_READ(data.Read(value)); + if (key == u"txfring") txfring = value != 0; + break; + } default: return false; // CreateCharacter never uses other types } diff --git a/dNet/ClientPackets.h b/dNet/ClientPackets.h index cfb375594..c40a39315 100644 --- a/dNet/ClientPackets.h +++ b/dNet/ClientPackets.h @@ -185,6 +185,10 @@ namespace ClientPackets { int32_t chatMode{}; // DLU sends the GM level int64_t reputation{}; int32_t propertyCloneID{}; + // LWOInventoryComponent_Client::LoadConfigData (0x00cde200) reads it; live sent true in every character data + // packet, and with it false the client shows inventory-full tips on every world load. Not written when false + // (the client's default). + bool txfring = true; CreateCharacter() : LUBitStream(ServiceType::CLIENT, MessageType::Client::CREATE_CHARACTER) {} void Serialize(RakNet::BitStream& bitStream) const override; diff --git a/dNet/PacketFields.inc b/dNet/PacketFields.inc index 6a562e6ee..61f6e6169 100644 --- a/dNet/PacketFields.inc +++ b/dNet/PacketFields.inc @@ -639,6 +639,7 @@ json ToJson(const ClientPackets::CreateCharacter& m) { j["chatMode"] = ToJson(m.chatMode); j["reputation"] = ToJson(m.reputation); j["propertyCloneID"] = ToJson(m.propertyCloneID); + j["txfring"] = ToJson(m.txfring); return j; } json ToJson(const ClientPackets::ChatModerationString& m) { diff --git a/tests/dGameTests/dNetTests/WorldPacketsTests.cpp b/tests/dGameTests/dNetTests/WorldPacketsTests.cpp index 5eebad713..e613f0b5f 100644 --- a/tests/dGameTests/dNetTests/WorldPacketsTests.cpp +++ b/tests/dGameTests/dNetTests/WorldPacketsTests.cpp @@ -309,6 +309,7 @@ TEST_F(WorldPacketsTests, CreateCharacterMatchesLegacy) { packet.chatMode = static_cast(gm); packet.reputation = reputation; packet.propertyCloneID = cloneID; + packet.txfring = false; // the legacy sender never wrote it ExpectSameSend([&](const SystemAddress& a) { LegacyWorldPackets::SendCreateCharacter(a, reputation, objectID, xml, name, gm, cloneID); }, packet); const auto copy = RoundTrip(packet); @@ -329,6 +330,16 @@ TEST_F(WorldPacketsTests, CreateCharacterMatchesLegacy) { ExpectTruncatedFails(packet); } +// Live sent txfring=true in every character data packet (222 of the 222 that had the key, of 226) +TEST_F(WorldPacketsTests, CreateCharacterSendsTxfring) { + ClientPackets::CreateCharacter packet; + EXPECT_TRUE(packet.txfring); + EXPECT_TRUE(RoundTrip(packet).txfring); + + packet.txfring = false; + EXPECT_FALSE(RoundTrip(packet).txfring); +} + TEST_F(WorldPacketsTests, CharacterListMatchesLegacy) { FakeCharacter first; FakeCharacter second;