fix(wire): write SetStatusImmunity flags in the client's order

WIRE FIX. The 1.10.64 client writes and reads the immunity flags in
alphabetical order after the u32 state (GameMessage::SetStatusImmunity::
Serialize @ 0x00d8f140; the field offsets are named by the Flash export
at 0x00d8f410): BasicAttack, DOT, ImaginationGain, ImaginationLoss,
Interrupt, Knockback, PullToPoint, QuickbuildInterrupt, Speed. DLU wrote
them in declaration order, so e.g. a knockback immunity reached the
client as an imagination-gain immunity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 08:21:22 -05:00
parent e3a75a689e
commit 7fb1e404f0
4 changed files with 47 additions and 14 deletions

View File

@@ -264,9 +264,7 @@ TEST_F(CombatMessagesTests, StunsAndImmunitiesMatchLegacy) {
status.bImmuneToImaginationLoss = Bit(mask, 6);
status.bImmuneToQuickbuildInterrupt = Bit(mask, 7);
status.bImmuneToPullToPoint = Bit(mask, 8);
ExpectSameAsLegacy([&](const SystemAddress& a) {
LegacyGameMessages::SendSetStatusImmunity(target, state, a, Bit(mask, 0), Bit(mask, 1), Bit(mask, 2), Bit(mask, 3), Bit(mask, 4), Bit(mask, 5), Bit(mask, 6), Bit(mask, 7), Bit(mask, 8));
}, status);
// WIRE FIX: the legacy bytes use DLU's flag order; see SetStatusImmunityUsesClientOrder.
EXPECT_EQ(RoundTrip(status).bImmuneToPullToPoint, Bit(mask, 8));
}
}
@@ -379,3 +377,36 @@ TEST_F(CombatMessagesTests, GoldenBytes) {
remove.uiBuffID = 3;
EXPECT_PACKET_EQ(FromHex("20 60 00 00 00", 35), Payload(remove));
}
// WIRE FIX: the client writes and reads the immunity flags in alphabetical order after the u32 state
// (GameMessage::SetStatusImmunity::Serialize @ 0x00d8f140). Setting one flag at a time must set exactly that bit.
TEST_F(CombatMessagesTests, SetStatusImmunityUsesClientOrder) {
using Flag = bool GameMessages::SetStatusImmunity::*;
const std::array<Flag, 9> clientOrder = {
&GameMessages::SetStatusImmunity::bImmuneToBasicAttack,
&GameMessages::SetStatusImmunity::bImmuneToDOT,
&GameMessages::SetStatusImmunity::bImmuneToImaginationGain,
&GameMessages::SetStatusImmunity::bImmuneToImaginationLoss,
&GameMessages::SetStatusImmunity::bImmuneToInterrupt,
&GameMessages::SetStatusImmunity::bImmuneToKnockback,
&GameMessages::SetStatusImmunity::bImmuneToPullToPoint,
&GameMessages::SetStatusImmunity::bImmuneToQuickbuildInterrupt,
&GameMessages::SetStatusImmunity::bImmuneToSpeed,
};
for (size_t i = 0; i < clientOrder.size(); i++) {
GameMessages::SetStatusImmunity msg;
msg.StateChangeType = eStateChangeType::POP;
msg.*clientOrder[i] = true;
RakNet::BitStream bitStream;
msg.Serialize(bitStream);
ASSERT_EQ(bitStream.GetNumberOfBitsUsed(), 32 + 9);
uint32_t state{};
ASSERT_TRUE(bitStream.Read(state));
EXPECT_EQ(state, 1u);
for (size_t bit = 0; bit < clientOrder.size(); bit++) {
bool value{};
ASSERT_TRUE(bitStream.Read(value));
EXPECT_EQ(value, bit == i) << "flag " << i << ", bit " << bit;
}
}
}