Commit graph

1400 commits

Author SHA1 Message Date
Kelsi
fe7912b5fa fix: prevent buffer overflows in Warden PE parsing
- Add bounds checks to readLE32/readLE16 — malformed Warden modules
  could cause out-of-bounds reads on untrusted PE data
- Fix unsigned underflow in PE section loading: if rawDataOffset or
  virtualAddr exceeds buffer size, the subtraction wrapped to a huge
  uint32_t causing memcpy to read/write far beyond bounds. Now skips
  the section entirely and uses std::min with pre-validated maxima
2026-03-30 15:33:03 -07:00
Kelsi
b39f0f3605 refactor: name GUID type and LFG role constants, add why-comments
- world_packets: name kGuidTypeMask/kGuidTypePet/kGuidTypeVehicle
  for chat receiver GUID type detection, with why-comment explaining
  WoW's bits-48-63 entity type encoding and 0xF0FF mask purpose
- lua_engine: name kRoleTank/kRoleHealer/kRoleDamager (0x02/0x04/0x08)
  for WotLK LFG role bitmask, add context on Leader bit (0x01) and
  source packets (SMSG_GROUP_LIST / SMSG_LFG_ROLE_CHECK_UPDATE)
2026-03-30 15:28:18 -07:00
Kelsi
a389fd2ef4 refactor: name SRP/Warden crypto constants, add why-comments
- srp: name kEphemeralBytes (19 = 152 bits, matches Blizzard client)
  and kMaxEphemeralAttempts (100) with why-comment explaining A != 0
  mod N requirement and near-zero failure probability
- warden_module: add why-comment on 0x400000 module base (default
  PE image base for 32-bit Windows executables)
- warden_module: name kRsaSignatureSize (256 = RSA-2048) with
  why-comment explaining signature stripping (placeholder modulus
  can't verify Blizzard's signatures)
2026-03-30 15:12:27 -07:00
Kelsi
d2a7d79f60 refactor: add why-comments to zone tiles, audio cache, socket buffer
- zone_manager: document tile-to-zone key encoding (tileX * 100 + tileY,
  safe because tileY < 64 < 100) and explain that ranges are empirically
  derived from the retail WoW map grid
- audio_engine: expand sample rate comment — miniaudio defaults to
  device rate causing pitch distortion if not set explicitly; name
  kMaxCachedSounds constant with memory budget explanation
- tcp_socket: add why-comment on 4 KB recv buffer sizing — covers
  typical 20-500 byte packets and worst-case ~2 KB UPDATE_OBJECT
2026-03-30 14:52:51 -07:00
Kelsi
8c7db3e6c8 refactor: name FNV-1a/transport constants, fix dead code, add comments
- vk_context: name FNV-1a hash constants (kFnv1aOffsetBasis/kFnv1aPrime)
  with why-comment on algorithm choice for sampler cache
- transport_manager: collapse redundant if/else that both set
  looping=false into single unconditional assignment, add why-comment
  explaining the time-closed path design
- transport_manager: hoist duplicate kMinFallbackZOffset constants out
  of separate if-blocks, add why-comment on icebreaker Z clamping
- entity: expand velocity smoothing comment — explain 65/35 EMA ratio
  and its tradeoff (jitter suppression vs direction change lag)
2026-03-30 14:48:06 -07:00
Kelsi
a940859e6a refactor: name auth security flags, log JSON parse failures
- auth_handler: define kSecurityFlagPin/MatrixCard/Authenticator
  constants (0x01/0x02/0x04) with why-comment explaining WoW login
  challenge securityFlags byte, replace all bare hex literals
- expansion_profile: log warning on jsonInt() parse failure instead
  of silently returning default — makes malformed expansion.json
  diagnosable without debugger
2026-03-30 14:43:50 -07:00
Kelsi
28e5cd9281 refactor: replace magic bag slot offset 19 with FIRST_BAG_EQUIP_SLOT
- Add Inventory::FIRST_BAG_EQUIP_SLOT = 19 constant with why-comment
  explaining WoW equip slot layout (bags occupy slots 19-22)
- Replace all 19 occurrences of magic number 19 in bag slot calculations
  across inventory_handler, spell_handler, inventory, and game_handler
- Add UNIT_FIELD_FLAGS / UNIT_FLAG_PVP comment in combat_handler
- Add why-comment on network packet budget constants (prevent server
  data bursts from starving the render loop)
2026-03-30 14:20:39 -07:00
Kelsi
6dfac314ee fix: remove dead code, name constants, add why-comments
- renderer: remove no-op assignment (mountAnims_.stand = 0 when already 0)
- renderer: add why-comments on blacksmith WMO ID 96048 (ambient forge
  sounds) with TODO for other smithy buildings
- terrain_renderer: replace 1e30f sentinel with numeric_limits::max(),
  name terrain view distance constant (1200 units ≈ 9 ADT tiles)
- social_handler: add missing LFG case 15, document case 0 nullptr
  return (success = no error message), add enum name comments
2026-03-30 14:10:32 -07:00
Kelsi
4acebff65c refactor: extract fallback textures, add why-comments, name WMO constant
- character_renderer: extract duplicated fallback texture creation
  (white/transparent/flat-normal) into createFallbackTextures() — was
  copy-pasted between initialize() and clear()
- wmo_renderer: replace magic 8192 with kMaxRetryTracked constant,
  add why-comment explaining the fallback-retry set cap (Dalaran has
  2000+ unique WMO groups)
- quest_handler: add why-comment on reqCount=0 fallback — escort/event
  quests can report kill credit without objective counts in query response
2026-03-30 14:06:30 -07:00
Kelsi
f313eec24e refactor: replace magic slot offset 23 with NUM_EQUIP_SLOTS, simplify channel search
- Replace all 11 occurrences of magic number 23 in backpack slot
  calculations with Inventory::NUM_EQUIP_SLOTS across inventory_handler,
  spell_handler, and inventory.cpp
- Add why-comment to NUM_EQUIP_SLOTS explaining WoW slot layout
  (equipment 0-22, backpack starts at 23 in bag 0xFF)
- Add why-comment on 0x80000000 bit mask in item query response
  (high bit flags negative/missing entry response)
- Replace manual channel membership loops with std::find in
  chat_handler.cpp (YOU_JOINED and PLAYER_ALREADY_MEMBER cases)
- Add why-comment on PLAYER_ALREADY_MEMBER reconnect edge case
2026-03-30 14:01:34 -07:00
Kelsi
a9ce22f315 refactor: extract findOnUseSpellId helper, add warden hash comment
- spell_handler: extract duplicated item on-use spell lookup into
  findOnUseSpellId() — was copy-pasted in useItemBySlot and useItemInBag
- warden_handler: add why-comment explaining the door model HMAC-SHA1
  hash table (wall-hack detection for unmodified 3.3.5a client data)
2026-03-30 13:56:45 -07:00
Kelsi
76f493f7d9 refactor: replace goto with structured control flow
- spell_handler.cpp: replace goto-done with do/while(false) for pet
  spell packet parsing — bail on truncated data while always firing
  events afterward
- water_renderer.cpp: replace goto-found_neighbor with immediately
  invoked lambda to break out of nested neighbor search loops
2026-03-30 13:47:14 -07:00
Kelsi Rae Davis
fe080bed4b
Merge pull request #31 from ldmonster/chore/break-application-from-gamehandler
[chore] refactor: Break Application::getInstance() from GameHandler
2026-03-30 13:26:29 -07:00
Paul
a86efaaa18 [refactor] Break Application::getInstance() from GameHandler
Introduce `GameServices` struct — an explicit dependency bundle that
`Application` populates and passes to `GameHandler` at construction time.
Eliminates all 47 hidden `Application::getInstance()` calls in
`src/game/*.cpp`, completing SOLID-D (dependency-inversion) cleanup.

Changes:
- New `include/game/game_services.hpp` — `struct GameServices` carrying
  pointers to `Renderer`, `AssetManager`, `ExpansionRegistry`, and two
  taxi-mount display IDs
- `GameHandler(GameServices&)` replaces default constructor; exposes
  `services() const` accessor for domain handlers
- `Application` holds `game::GameServices gameServices_`; populates it
  after all subsystems are created, then constructs `GameHandler`
  (fixes latent init-order bug: `GameHandler` was previously created
  before `AssetManager` / `ExpansionRegistry`)
- `game_handler.cpp`: duplicate `isActiveExpansion` / `isClassicLikeExpansion` /
  `isPreWotlk` anonymous-namespace helpers removed; `game_utils.hpp`
  included instead
- All domain handlers (`InventoryHandler`, `SpellHandler`, `MovementHandler`,
  `CombatHandler`, `QuestHandler`, `SocialHandler`, `WardenHandler`) replace
  `Application::getInstance().getXxx()` with `owner_.services().xxx`
2026-03-30 09:17:42 +03:00
Kelsi
169595433a debug: add GO interaction diagnostics at every decision point
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Adds [GO-DIAG] WARNING-level logs at:
- Right-click dispatch (raypick hit / re-interact with target)
- interactWithGameObject entry + all BLOCKED paths
- SMSG_SPELL_GO (wasInTimedCast, lastGoGuid, pendingGoGuid state)
- SMSG_LOOT_RESPONSE (items, gold, guid)
- Raypick candidate GO positions (entity pos + hit center + radius)

These logs will pinpoint exactly where the interaction fails:
- No GO-DIAG lines = GOs not in entity manager / not visible
- Raypick GO pos=(0,0,0) = GO position not set from update block
- BLOCKED = guard condition preventing interaction
- SPELL_GO wasInTimedCast=false = timer race (already fixed)
2026-03-29 23:09:28 -07:00
Kelsi
5e83d04f4a fix: GO cast timer fallback cleared state before SMSG_SPELL_GO arrived
The client-side cast timer expires ~50-200ms before the server sends
SMSG_SPELL_GO (float precision + frame timing). Previously the fallback
called resetCastState() which set casting_=false and currentCastSpellId_
=0. When SMSG_SPELL_GO arrived moments later, wasInTimedCast evaluated
to false (false && spellId==0), so the loot path (CMSG_LOOT via
lastInteractedGoGuid_) was never taken. Quest chests never opened.

Now the fallback skips resetCastState() for GO interaction casts, letting
the cast bar sit at 100% until SMSG_SPELL_GO arrives and handles cleanup
properly with wasInTimedCast=true.
2026-03-29 22:58:49 -07:00
Kelsi
cfbae93ce3 fix: client timer fallback re-sent CMSG_GAMEOBJ_USE and cleared loot guid
When the client-side cast timer expired slightly before SMSG_SPELL_GO
arrived, the fallback at update():1367 called performGameObjectInteraction
Now which sent a DUPLICATE CMSG_GAMEOBJ_USE to the server (confusing its
GO state machine), then resetCastState() cleared lastInteractedGoGuid_.
When SMSG_SPELL_GO finally arrived, the guid was gone so CMSG_LOOT was
never sent — quest chests produced no loot window.

Fix: the fallback no longer re-sends USE (server drives the interaction
via SMSG_SPELL_GO). resetCastState() no longer clears
lastInteractedGoGuid_ so the SMSG_SPELL_GO handler can still send LOOT.
2026-03-29 22:45:17 -07:00
Kelsi
785f03a599 fix: stale GO interaction guard broke future casts; premature LOOT interfered
Two remaining GO interaction bugs:

1. pendingGameObjectInteractGuid_ was never cleared after SMSG_SPELL_GO
   or SMSG_CAST_FAILED, leaving it stale. This suppressed CMSG_CANCEL_CAST
   for ALL subsequent spell casts (not just GO casts), causing the server
   to think the player was still casting when they weren't.

2. For chest-like GOs, CMSG_LOOT was sent simultaneously with
   CMSG_GAMEOBJ_USE. If the server starts a timed cast ("Opening"),
   the GO isn't lootable until the cast completes — the premature LOOT
   gets an empty response or is dropped, potentially corrupting the
   server's loot state. Now defers LOOT to handleSpellGo which sends it
   after the cast completes (via lastInteractedGoGuid_).
2026-03-29 22:36:30 -07:00
Kelsi
6b5e924027 fix: GO interaction casts canceled by any movement — quest credit lost
pendingGameObjectInteractGuid_ was always cleared to 0 right before
the interaction, which defeated the cancel-protection guard in
cancelCast(). Any positional movement (WASD, jump) during a GO
interaction cast (e.g., "Opening" on a quest chest) sent
CMSG_CANCEL_CAST to the server, aborting the interaction and
preventing quest objective credit.

Now sets pendingGameObjectInteractGuid_ to the GO guid so:
1. cancelCast() skips CMSG_CANCEL_CAST for GO-triggered casts
2. The cast-completion fallback can re-trigger loot after timer expires
3. isGameObjectInteractionCasting() returns true during GO casts
2026-03-29 22:22:20 -07:00
Kelsi
c1c28d4216 fix: quest objective GOs never granted credit — REPORT_USE skipped for chests
CMSG_GAMEOBJ_REPORT_USE was only sent for non-chest GOs. Chest-type
(type=3) and name-matched chest-like GOs (Bundle of Wood, etc.) went
through a separate path that sent CMSG_GAMEOBJ_USE + CMSG_LOOT but
skipped REPORT_USE. On AzerothCore, REPORT_USE triggers the server-side
HandleGameobjectReportUse which calls GossipHello on the GO script —
this is where many quest objective scripts grant credit.

Restructured so CMSG_GAMEOBJ_USE is sent first for all GO types,
then chest-like GOs additionally send CMSG_LOOT, and REPORT_USE fires
for everything except mailboxes.
2026-03-29 22:04:23 -07:00
Kelsi
8376757f7e cleanup: migrate 20 remaining setReadPos(getSize()) to skipAll()
The Packet::skipAll() method was introduced to replace the verbose
setReadPos(getSize()) pattern. 186 instances were migrated earlier,
but 20 survived in domain handler files created after the migration.
Also removes a redundant single-element for-loop wrapper around
SMSG_LOOT_CLEAR_MONEY registration.
2026-03-29 21:51:03 -07:00
Kelsi
ae32b27d6c fix: misplaced brace included 4 unrelated handlers inside instance-difficulty loop
Same class of bug as inventory_handler fix b9ecc26f. The for-loop over
{SMSG_INSTANCE_DIFFICULTY, MSG_SET_DUNGEON_DIFFICULTY} was missing its
closing brace, so GUILD_DECLINE, RAF_EXPIRED, RAF_FAILURE, and
PVP_AFK_RESULT registrations executed inside the loop body — each
registered twice (once per opcode). Currently harmless since duplicate
registration is idempotent, but structurally wrong.
2026-03-29 21:50:55 -07:00
Kelsi
01ab2a315c fix: achievement message silently truncated by char[256] snprintf
Long achievement names combined with sender name could exceed 256
bytes, silently cutting the message mid-word in chat. Replaced with
std::string concatenation which grows dynamically.
2026-03-29 21:35:56 -07:00
Kelsi
e42f8f1c03 fix: misleading indentation on reputation addon event dispatch
The two fireAddonEvent calls were indented as if conditional on
repChangeCallback_ but actually execute unconditionally (no braces).
Fixed indentation and added clarifying comment.
2026-03-29 21:35:49 -07:00
Kelsi
5583573beb fix: eliminate last std::rand() calls — music shuffle and UI weather
zone_manager.cpp used std::rand() for music track selection with modulo
bias and global state. game_screen.cpp used std::rand() for rain/snow
particle positions. Both now use local std::mt19937 seeded from
random_device. Also removes the global srand(time(nullptr)) call since
no code depends on the C rand() seed anymore.

No std::rand() or srand() calls remain in the codebase.
2026-03-29 21:01:51 -07:00
Kelsi
294c91d84a fix: migrate 197 unsafe packet bounds checks to hasRemaining/getRemainingSize
All domain handler files used 'packet.getSize() - packet.getReadPos()'
which underflows to ~2^64 when readPos exceeds size (documented in
commit ed63b029). The game_handler.cpp and packet_parsers were migrated
to hasRemaining(N) in an earlier cleanup, but the domain handlers were
created after that migration by the PR #23 split, copying the old
unsafe patterns back in. Now uses hasRemaining(N) for comparisons and
getRemainingSize() for assignments across all 7 handler files.
2026-03-29 20:53:26 -07:00
Kelsi
fa15a3de1f fix: transport elapsed time lost millisecond precision after ~4.5 hours
elapsedTime_ was float (32-bit, ~7 significant digits). At 16384
seconds the float can only represent integers, so elapsedTime_*1000
jumps in 1-second steps — ships and elevators visibly jerk. Changed to
double (53-bit mantissa) which maintains sub-millisecond precision for
~285 million years. Also changed lastServerUpdate to double to match.
2026-03-29 20:14:45 -07:00
Kelsi
c1f6364814 cleanup: remove misleading (void)flags — variable IS used for crit check
The cast falsely suggests flags is unused but it's read on the next
line for isCrit = (flags & 0x02). Also inlines the periodicLog discard.
2026-03-29 20:05:45 -07:00
Kelsi
59bbeaca62 fix: ::toupper/::tolower UB on signed char at 5 remaining call sites
std::toupper(int) and std::tolower(int) have undefined behavior when
passed a negative value. These sites passed raw signed char without
casting to unsigned char first, unlike the rest of the codebase which
already uses the correct pattern. Affects auth (account names), world
packets, and mount sound path matching.
2026-03-29 19:58:36 -07:00
Kelsi
c4d2b1709e fix: SMSG_RANDOM_ROLL parsed fields in wrong order — garbled /roll output
WotLK format is min(4)+max(4)+result(4)+guid(8)=20 bytes. The parser
read guid(8) first (treating min|max as a uint64), then targetGuid(8)
(non-existent field), then the actual values at wrong offsets. Every
/roll message showed garbled numbers and a bogus roller identity.

Also adds a hasRemaining guard for the 64 bytes of damage/armor/resist
fields in the item query parser — previously read past end with silent
zero-fill on truncated packets.
2026-03-29 19:51:07 -07:00
Kelsi
e5b4e86600 fix: misleading indentation on BAG_UPDATE/UNIT_INVENTORY_CHANGED emits
The two emit calls were indented 12 spaces (suggesting a nested block)
instead of 8 (matching the enclosing if). Same class of maintenance
trap as the PLAYER_ALIVE/PLAYER_UNGHOST fix in b3abf04d.
2026-03-29 19:31:29 -07:00
Kelsi
061a21da8f fix: guild event string never appended; /leave left stale party state
1. Contradictory condition (!numStrings && numStrings >= 1) was always
   false, so unknown guild event messages never included the server's
   context string. Fixed to just numStrings >= 1.

2. leaveParty() only sent the packet without clearing partyData or
   firing addon events, so /leave left party frames visible until the
   server pushed an update. Now delegates to leaveGroup() which handles
   both the packet and local state cleanup.
2026-03-29 19:31:21 -07:00
Kelsi
3da3638790 cleanup: remove misleading (void)reasonType — variable IS used below
The cast falsely suggests reasonType is unused, but it's read on lines
3699-3702 for AFK/vote-kick differentiation. Same class of issue as
the (void)isPlayerTarget fix in commit 6731e584.
2026-03-29 19:23:05 -07:00
Kelsi
84c0ced228 fix: friend cache inserted empty key; ignore erase before server confirm
handleFriendStatus inserted into friendsCache with an empty playerName
when the name query hadn't resolved yet, creating a phantom "" entry.
Now guards with !playerName.empty().

removeIgnore erased from ignoreCache immediately without waiting for
server confirmation, desyncing the cache if the server rejected. Now
only clears the GUID set and lets the next SMSG_IGNORE_LIST rebuild
the cache, consistent with how removeFriend works.
2026-03-29 19:22:55 -07:00
Kelsi
731d9a88fb fix: SMSG_AUCTION_BIDDER_NOTIFICATION read itemEntry at wrong offset
The handler treated the second uint32 (auctionId) as itemEntry. The
real itemEntry is at byte 24 after auctionHouseId(4)+auctionId(4)+
bidderGuid(8)+bidAmount(4)+outbidAmount(4). Outbid chat messages always
referenced the wrong item.
2026-03-29 19:22:44 -07:00
Kelsi
5b9b8b59ba refactor: extract buildItemDef from 4 copy-pasted blocks in rebuildOnlineInventory
The same 25-line block copying ~20 fields from itemInfoCache_ into
ItemDef was duplicated for equipment, backpack, keyring, and bag slots.
Extracted into buildItemDef() so new fields only need adding once.
Net -100 lines.
2026-03-29 19:16:36 -07:00
Kelsi
05cfcaacf6 fix: clearTarget fired PLAYER_TARGET_CHANGED before zeroing targetGuid
Callbacks and addons querying the current target during this event saw
the old (stale) target instead of null. setTarget correctly updates the
GUID before firing — clearTarget now does the same.
2026-03-29 19:16:18 -07:00
Kelsi
bf63d8e385 fix: static lastSpellCount shared across SpellHandler instances
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
The spellbook tab dirty check used a function-local static, meaning
switching to a character with the same spell count would skip the
rebuild and return the previous character's tabs. Changed to an
instance member so each SpellHandler tracks its own count.
2026-03-29 19:08:58 -07:00
Kelsi
7462fdd41f refactor: extract buildForceAck from 5 duplicated force-ACK blocks
All five force-ACK handlers (speed, root, flag, collision-height,
knockback) repeated the same ~25-line GUID+counter+movementInfo+coord-
conversion+send sequence. Extracted into buildForceAck() which returns
a ready-to-send packet with the movement payload already written.

This also fixes a transport coordinate conversion bug: the collision-
height handler was the only one that omitted the ONTRANSPORT check,
causing position desync when riding boats/zeppelins. buildForceAck
handles transport coords uniformly for all callers.

Net -80 lines.
2026-03-29 19:08:42 -07:00
Kelsi
a1252a56c9 fix: forceClearTaxiAndMovementState cleared unrelated death/resurrect state
A function for taxi/movement cleanup was resetting 10 death-related
fields (playerDead_, releasedSpirit_, resurrectPending_, etc.), which
could cancel a pending resurrection or mark a dead player as alive
when called during taxi dismount. Death state is owned by
entity_controller and resurrect packet handlers, not movement cleanup.
2026-03-29 19:01:34 -07:00
Kelsi
2e1f0f15ea fix: auction house refresh failed after browse-all (empty name search)
The auto-refresh after successful bid/buyout was gated on
lastAuctionSearch_.name.length() > 0, so a browse-all search (empty
name) would never refresh. Replaced with a hasAuctionSearch_ flag
that's set on any search regardless of the name filter.
2026-03-29 19:01:06 -07:00
Kelsi
a2340dd702 fix: level-up notification never fired — early return skipped it
The character-list level update loop used 'return' instead of 'break',
exiting the handler lambda before the level-up chat message, sound
effect, callback, and PLAYER_LEVEL_UP event could fire. Since the
player GUID is always in the character list, the notification code
was effectively dead — players never saw "You have reached level N!".
2026-03-29 19:00:54 -07:00
Kelsi
6731e5845a cleanup: remove misleading (void)isPlayerTarget cast
The variable is used earlier in the function for hostile attacker
tracking, so the (void) cast falsely suggests it was unused. Leftover
from a prior refactor.
2026-03-29 18:53:39 -07:00
Kelsi
961af04b36 fix: gossip banker sent CMSG_BANKER_ACTIVATE twice; deduplicate quest icons
Icon==6 and text=="GOSSIP_OPTION_BANKER" both sent BANKER_ACTIVATE
independently. Banking NPCs match both, so the packet was sent twice —
some servers toggle the bank window open then closed. Added sentBanker
guard so only one packet is sent.

Also extracts classifyGossipQuests() from two identical 30-line blocks
in handleGossipMessage and handleQuestgiverQuestList. The icon→status
mapping (5/6/10=completable, 3/4=incomplete, 2/7/8=available) is now
in one place with a why-comment explaining these are protocol-defined.
2026-03-29 18:53:30 -07:00
Kelsi
1a6960e3f9 fix: speed ACK sent before validation caused client/server desync
If the server sent a NaN or out-of-range speed, the client echoed it
back in the ACK (confirming it to the server) but then rejected it
locally. This left the server believing the client accepted the speed
while the client used the old value — a desync only fixable by relog.
Moved validation before the ACK so bad speeds are rejected outright.
2026-03-29 18:53:16 -07:00
Kelsi
fc2c6bab40 fix: strict aliasing violation in handleQueryNextMailTime
reinterpret_cast<float*> on raw packet bytes is undefined behavior per
the C++ strict aliasing rule — compilers can optimize assuming uint8_t
and float never alias. Replaced with packet.readFloat() which uses
memcpy internally. Also switched to hasRemaining() for consistency.
2026-03-29 18:46:34 -07:00
Kelsi
2ae14d5d38 fix: RX silence 15s warning fired ~30 times per window
The 10s silence warning used a one-shot bool guard, but the 15s warning
used a 500ms time window — firing every frame (~30 times at 60fps).
Added rxSilence15sLogged_ guard consistent with the 10s pattern.
2026-03-29 18:46:25 -07:00
Kelsi
3712e6c5c1 fix: operator precedence broke stabled pet parsing — only first pet shown
!packet.hasRemaining(4) + 4 + 4 evaluated as (!hasRemaining(4))+8
due to ! binding tighter than +, making the check always truthy and
breaking out of the loop after the first pet. Hunters with multiple
stabled pets would see only one in the stable master UI.
2026-03-29 18:46:15 -07:00
Kelsi
f681de0a08 refactor: use guidToUnitId() instead of inline 4-way GUID comparison
handleSpellStart and handleSpellGo duplicated the player/target/focus/
pet GUID-to-unitId mapping that already exists in guidToUnitId(). If a
new unit-id category is added (e.g. mouseover), these inline copies
would not pick it up.
2026-03-29 18:39:52 -07:00
Kelsi
6f6571fc7a fix: pet opcodes shared unlearn handler despite incompatible formats
SMSG_PET_GUIDS, SMSG_PET_DISMISS_SOUND, and SMSG_PET_ACTION_SOUND were
registered with the same handler as SMSG_PET_UNLEARN_CONFIRM. Their
different formats (GUID lists, sound IDs with position) were misread as
unlearn cost, potentially triggering a bogus unlearn confirmation dialog.

Also extracts resetWardenState() from 13 lines duplicated verbatim
between connect() and disconnect().
2026-03-29 18:39:38 -07:00