- moved chat panel logic out of `game_screen` into `chat_panel`
- added chat_panel.hpp and chat_panel.cpp
- updated game_screen.hpp and game_screen.cpp to integrate new `ChatPanel` component
- updated build config in CMakeLists.txt to include new UI module sources
Replace the incorrectly extracted RSA-2048 modulus (which contained
the exponent bytes embedded inside it) with the verified Blizzard
public key used across all pre-Cataclysm clients (1.12.1, 2.4.3,
3.3.5a).
Key confirmed against two independent sources:
- namreeb/WardenSigning ClientKey.hpp (72 verified sniffed modules)
- SkullSecurity wiki Warden_Modules documentation
The modulus starts with 0x6BCE F52D... and ends with ...03F4 AFC7.
Exponent remains 65537 (0x010001).
Verification algorithm: SHA1(module_data + "MAIEV.MOD"), 0xBB-padded
to 256 bytes, RSA verify-recover with raw (no-padding) mode.
Signature failures are non-fatal (log warning, continue loading) so
private-server modules signed with custom keys still work. This is
necessary because servers like ChromieCraft/AzerothCore may use their
own signing keys.
Also update warden_module.hpp status: all implementation items now ✅.
Complete the last major Warden stub — the import table parser that
resolves Windows API calls in loaded modules. This is the critical
missing piece for strict servers like Warmane.
Implementation:
- Parse Warden module import table from decompressed data (after
relocation entries): alternating libraryName\0 / functionName\0
pairs, terminated by null library name
- For each import, look up the emulator's pre-registered stub address
(VirtualAlloc, GetTickCount, ReadProcessMemory, etc.)
- Auto-stub unrecognized APIs with a no-op returning 0 — prevents
module crashes on unimplemented Windows functions
- Patch each IAT slot (sequential dwords at module image base) with
the resolved stub address
- Add WardenEmulator::getAPIAddress() public accessor for IAT lookups
- Fix initialization order: bindAPIs() now runs inside initializeModule()
after emulator setup but before entry point call
The full Warden pipeline is now: RC4 decrypt → RSA verify → zlib
decompress → parse executable → relocate → create emulator → register
API hooks → bind imports (IAT patch) → call entry point → extract
exported functions (packetHandler, tick, generateRC4Keys, unload).
Implement the three stubbed Warden module callbacks that were previously
TODO placeholders:
- **sendPacket**: Encrypts module output via WardenCrypto RC4 and sends
as CMSG_WARDEN_DATA through the game socket. Enables modules to send
responses back to the server (required for strict servers like Warmane).
- **validateModule**: Compares the module's provided 16-byte MD5 hash
against the hash received during download. Logs error on mismatch
(indicates corrupted module transit).
- **generateRC4**: Derives new encrypt/decrypt RC4 keys from a 16-byte
seed using SHA1Randx, then replaces the active WardenCrypto key state.
Handles mid-session re-keying requested by the module.
Architecture:
- Add setCallbackDependencies() to inject WardenCrypto* and socket send
function into WardenModule before load() is called
- Use thread_local WardenModule* so C function pointer callbacks (which
can't capture state) can reach the module's dependencies during init
- Wire dependencies from WardenHandler before module load
Also update warden_module.hpp status markers — RSA verification, zlib,
executable parsing, relocation, and Unicorn emulation are all implemented
(were incorrectly marked as TODO). Only API binding/IAT patching and
RSA modulus verification against real WoW.exe remain as gaps.
Remove all OpenGL/GLEW code and dependencies. The Vulkan renderer has
been the sole active backend for months; these files were dead code.
Deleted (8 files, 641 lines):
- rendering/mesh.cpp+hpp: OpenGL VAO/VBO/EBO wrapper (never instantiated)
- rendering/shader.cpp+hpp: OpenGL GLSL compiler (replaced by VkShaderModule)
- rendering/scene.cpp+hpp: Scene graph holding Mesh objects (created but
never populated — all rendering uses Vulkan sub-renderers directly)
- rendering/video_player.cpp+hpp: FFmpeg+GL texture uploader (never
included by any other file — login video feature can be re-implemented
with VkTexture when needed)
Cleaned up:
- renderer.hpp: remove Scene forward-decl, getScene() accessor, scene member
- renderer.cpp: remove scene.hpp/shader.hpp includes, Scene create/destroy
- application.cpp: remove stale "GL/glew.h removed" comment
- CMakeLists.txt: remove find_package(OpenGL/GLEW), source/header entries,
and target_link_libraries for OpenGL::GL and GLEW::GLEW
- PKGBUILD: remove glew dependency
- BUILD_INSTRUCTIONS.md: remove glew from all platform install commands
- Delete 4 legacy GLSL 330 shaders (basic.vert/frag, terrain.vert/frag)
left over from OpenGL→Vulkan migration — Vulkan equivalents exist as
*.glsl files compiled to SPIR-V by the build system
- Delete orphaned mpq_manager.hpp/cpp (694 lines) — not in CMakeLists,
not included by any file, unreferenced StormLib integration attempt
- Add comments to water.frag.glsl wave constants explaining the
multi-octave noise design: non-axis-aligned directions prevent tiling,
frequency increases and amplitude decreases per octave for natural
water appearance
- packet_parsers_tbc: explain spline waypoint cap (DoS prevention),
spline compression flags (Catmull-Rom 0x80000 / linear 0x2000 use
uncompressed format, others use packed delta), spell hit target cap
(128 >> real AOE max of ~20), guild roster cap (1000 safety limit)
- ambient_sound_manager: explain 1.5s bell toll spacing — matches
retail WoW cadence, allows each toll to ring out before the next
- character_preview.hpp: explain 4:5 portrait aspect ratio for
full-body character display in creation/selection screen
- 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)
- Fix move constructor and move assignment: set other.ownsSampler_ to
false after transfer (was incorrectly set to true, leaving moved-from
object claiming ownership of a null sampler)
- Fix destroy(): reset ownsSampler_ to false after clearing sampler
handle (was set to true, inconsistent with null handle state)
- Extract finalizeSampler() from 3 duplicated cache-or-create blocks
in createSampler() overloads and createShadowSampler() (-24 lines)
- Add SPIR-V alignment why-comment in vk_shader.cpp
- 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)
- 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
- 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
- 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)
- Dockerfile: fix LLVM apt repo codename (jammy → noble) for ubuntu:24.04
- build-linux.sh: add missing mkdir -p /wowee-build-src before tar extraction
- Dockerfile: remove dead ENV OSXCROSS_VERSION=1.5 and its unset
- CMakeLists: scope -undefined dynamic_lookup to wowee target only
- GameServices: remove redundant game:: qualifier inside namespace game
- application.cpp: zero out gameServices_ after gameHandler reset in shutdown
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`
queryTimeMs and queryCallCount on WMORenderer and M2Renderer were plain
mutable doubles/uint32s written by getFloorHeight (dispatched on async
threads from CameraController) and read by the main thread. This is
undefined behavior per C++ — thread sanitizer would flag it. Changed to
std::atomic with relaxed ordering (adequate for diagnostics) and updated
QueryTimer to use atomic fetch_add/compare_exchange.
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.
pendingPlayerSpawns_, deferredEquipmentQueue_, and
pendingGameObjectSpawns_ are consumed from the front via erase(begin()),
which is O(n) on vector (shifts all elements). With many spawns queued
(entering a city), this made the processing loop O(n²). deque supports
O(1) front erasure. pendingCreatureSpawns_ already used deque.
Both operands are size_t (unsigned), so if readPos > data.size() the
subtraction wrapped to ~0 instead of returning 0. This could happen
via setReadPos() which has no bounds check. Downstream hasRemaining()
was already safe but getRemainingSize() callers (e.g. hasFullPackedGuid)
would see billions of bytes available.
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.
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.
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.
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.
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.
selectCharacter had 30+ if(spellHandler_) guards reaching into
SpellHandler internals (knownSpells_, spellCooldowns_, playerAuras_,
etc.) to clear per-character state. Consolidated into resetAllState()
so SpellHandler owns its own reset logic and new state fields don't
require editing GameHandler.
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.
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().
Only ASCENDING was cleared — the DESCENDING flag was never toggled,
so outgoing movement packets during flight descent had incorrect flags.
Also clears DESCENDING on start-ascend and stop-ascend for symmetry.
Replaces static heartbeat log counter with member variable (was shared
across instances and not thread-safe) and demotes to LOG_DEBUG.
SMSG_ENVIRONMENTALDAMAGELOG (alias) registration at line 173 silently
overwrote the canonical SMSG_ENVIRONMENTAL_DAMAGE_LOG handler at line
108. The alias handler discarded envType (fall/lava/drowning), so the
UI couldn't differentiate environmental damage sources. Removed the
dead alias handler and its method; the canonical inline handler with
envType forwarding is now the sole registration.
The packet only contains uint8 count + count×uint64 GUIDs, but the
handler called readString() after each GUID. This consumed raw bytes of
subsequent GUIDs as a string, corrupting all entries after the first.
Now stores GUIDs in ignoreListGuids_ and resolves names asynchronously
via SMSG_NAME_QUERY_RESPONSE, matching the friends list pattern.
Also fixes unsafe static_pointer_cast in ready check (no type guard)
and removes redundant packetHasRemaining wrapper (duplicates Packet API).
Both the health==0 and dynFlags UNIT_DYNFLAG_DEAD paths duplicated the
same corpse-position caching and death-state logic with a subtle
asymmetry (only health path called stopAutoAttack). Extracted into
markPlayerDead() so coordinate swapping and state changes happen in one
place. stopAutoAttack remains at the health==0 call site since the
dynFlags path doesn't need it.
Three identical copies (game_handler.cpp, spell_handler.cpp,
quest_handler.cpp) plus two forward declarations (inventory_handler.cpp,
social_handler.cpp) replaced with a single inline definition in
game_utils.hpp. All affected files already include this header, so
quality color table changes now propagate from one source of truth.
Two fixes for item name resolution:
1. Clear entry from pendingItemQueries_ even when response parsing fails.
Previously a malformed response left the entry stuck in pending forever,
blocking all retries so the UI permanently showed "Item 12345".
2. Add 5-second periodic cleanup of pendingItemQueries_ so lost/dropped
responses don't permanently block item info resolution.
SpellHandler::updateTimers() was never called after PR #23 extraction,
so cast bar timers, spell cooldowns, and unit cast state timers never
ticked. Also removes duplicate cast/queue/spell members left in
GameHandler that shadowed the SpellHandler versions, and fixes
MovementHandler writing to those stale members on world portal.
Demotes SMSG_SPELL_START/CAST_RESULT debug logs to LOG_DEBUG.
- Restore 0x02→0x80 Classic harmful-to-WotLK debuff bit mapping in
syncClassicAurasFromFields so downstream checks work across expansions
- Extract handleDisplayIdChange helper to deduplicate identical logic
in onValuesUpdateUnit and onValuesUpdatePlayer
- Remove unused newItemCreated parameter from handleValuesUpdate
- Fix indentation on PLAYER_DEAD/PLAYER_ALIVE/PLAYER_UNGHOST emit calls
- split applyUpdateObjectBlock into handleCreateObject,
handleValuesUpdate, handleMovementUpdate
- extract concern helpers — createEntityFromBlock,
applyPlayerTransportState, applyUnitFieldsOnCreate/OnUpdate,
applyPlayerStatFields, dispatchEntitySpawn, trackItemOnCreate,
updateItemOnValuesUpdate, syncClassicAurasFromFields,
detectPlayerMountChange, updateNonPlayerTransportAttachment
- UnitFieldIndices, PlayerFieldIndices, UnitFieldUpdateResult
structs with static resolve() — eliminate repeated fieldIndex() calls
- IObjectTypeHandler strategy interface; concrete handlers
UnitTypeHandler, PlayerTypeHandler, GameObjectTypeHandler,
ItemTypeHandler, CorpseTypeHandler registered in typeHandlers_ map;
handleCreateObject and handleValuesUpdate now dispatch via
getTypeHandler() — adding a new object type requires zero changes
to existing handler methods
- PendingEvents member bus; all 27 inline owner_.fireAddonEvent()
calls in the update path replaced with pendingEvents_.emit(); events
flushed via flushPendingEvents() at the end of each handler, decoupling
field-parse logic from the addon callback system
entity_controller.cpp: 1520-line monolith → longest method ~200 lines,
cyclomatic complexity ~180 → ~5; zero duplicated CREATE/VALUES blocks
Moves entity lifecycle, name/creature/game-object caches, transport GUID
tracking, and the entire update-object pipeline out of GameHandler into a
new EntityController class (friend-class pattern, same as CombatHandler
et al.).
What moved:
- applyUpdateObjectBlock() — 1,520-line core of all entity creation,
field updates, and movement application
- processOutOfRangeObjects() / finalizeUpdateObjectBatch()
- handleUpdateObject() / handleCompressedUpdateObject() / handleDestroyObject()
- handleNameQueryResponse() / handleCreatureQueryResponse()
- handleGameObjectQueryResponse() / handleGameObjectPageText()
- handlePageTextQueryResponse()
- enqueueUpdateObjectWork() / processPendingUpdateObjectWork()
- playerNameCache, playerClassRaceCache_, pendingNameQueries
- creatureInfoCache, pendingCreatureQueries
- gameObjectInfoCache_, pendingGameObjectQueries_
- transportGuids_, serverUpdatedTransportGuids_
- EntityManager (accessed by other handlers via getEntityManager())
8 opcodes re-registered by EntityController::registerOpcodes():
SMSG_UPDATE_OBJECT, SMSG_COMPRESSED_UPDATE_OBJECT, SMSG_DESTROY_OBJECT,
SMSG_NAME_QUERY_RESPONSE, SMSG_CREATURE_QUERY_RESPONSE,
SMSG_GAMEOBJECT_QUERY_RESPONSE, SMSG_GAMEOBJECT_PAGETEXT,
SMSG_PAGE_TEXT_QUERY_RESPONSE
Other handler files (combat, movement, social, spell, inventory, quest,
chat) updated to access EntityManager via getEntityManager() and the
name cache via getPlayerNameCache() — no logic changes.
Also included:
- .clang-tidy: add modernize-use-nodiscard,
modernize-use-designated-initializers; set -std=c++20 in ExtraArgs
- test.sh: prepend clang's own resource include dir before GCC's to
silence xmmintrin.h / ia32intrin.h conflicts during clang-tidy runs
Line counts:
entity_controller.hpp 147 lines (new)
entity_controller.cpp 2172 lines (new)
game_handler.cpp 8095 lines (was 10143, −2048)
Build: 0 errors, 0 warnings.
RAW FIELDS dump shows item entries at odd indices: 283, 285, 287, 289...
With base=283, stride=2: 17 of 19 slots have valid item IDs (14200,
12020, 14378, etc). Slots 12-13 (trinkets) correctly empty.
With base=284: only 5 entries, and values are enchant IDs (913, 905, 904)
— these are the field AFTER each entry, confirming base was off by 1.
Stormwind WMO collision takes 25+ seconds to fully load. The warmup
ground check couldn't detect the WMO floor because collision data
wasn't finalized yet. Player spawned and immediately fell through
the unloaded WMO floor into the terrain below (Dun Morogh).
New approach: suspendGravityFor(10s) after world entry. Gravity is
disabled (Z position frozen) until either:
1. A floor is detected by the collision system (gravity resumes instantly)
2. The 10-second timer expires (gravity resumes as fallback)
This handles the case where WMO collision loads during the first few
seconds of gameplay — the player hovers at spawn Z until the floor
appears, then lands normally.
Also fixes faction language for chat (ORCISH for Horde, COMMON for
Alliance) and adds SMSG_MESSAGECHAT diagnostic logging.
hasPendingGroupInvite() and getPendingInviterName() were inline getters
reading GameHandler's stale copies. SocialHandler owns the canonical
pendingGroupInvite/pendingInviterName state. Players were auto-added to
groups without seeing the accept/decline popup.
Now delegates to socialHandler_.