The warmup loop waited up to 20 seconds for getHeightAt() to return a
terrain height within 15 units of spawn Z before accepting the ground
as ready. In practice, the terrain was loaded and the character was
visibly standing on it, but the height sample didn't match closely
enough (terrain LOD, chunk boundary, or server Z vs client height
mismatch).
Reduce the tile-count fallback timeout from 20s to 5s: if at least 4
tiles are loaded after 5 seconds, accept the ground as ready. The
exact height check still runs in the first 5 seconds for fast-path
cases where it does match.
ChromieCraft/AzerothCore tolerates no HASH_RESULT response (continues
session without Warden checks), but immediately kicks on a WRONG hash.
The previous commit sent a fallback SHA1 which the server rejected,
breaking login that was working before.
Restore the skip behavior for WotLK/TBC: stay silent on HASH_REQUEST
when no CR match exists, and advance to WAIT_CHECKS so the rest of the
session proceeds normally. Turtle/Classic servers still get the fallback
hash since they're lenient about wrong values.
Previously, WotLK/TBC servers with no CR match would skip the
HASH_REQUEST response entirely to "avoid account bans". This caused
a guaranteed kick-on-timeout for ALL WotLK servers including
permissive ones like ChromieCraft/AzerothCore.
Now sends a best-effort fallback hash (SHA1 of module image or raw
data) for all server types. Permissive servers accept this and
continue the session normally. Strict servers (Warmane) will reject
it but only kick — same outcome as the previous skip behavior, just
faster feedback.
For strict servers, the correct fix remains providing a .cr file
with pre-computed seed→reply entries for each module.
Suppress 9 unused parameter warnings in IObjectTypeHandler interface
methods and their overrides by commenting out parameter names:
- Base class: onCreate/onValuesUpdate/onMovementUpdate default empty
implementations (parameters intentionally unused in base)
- ItemTypeHandler::onCreate: entity param forwarded only to onCreateItem
which doesn't need it
- CorpseTypeHandler::onCreate: entity param not needed for corpse spawn
Build now produces zero warnings (excluding third-party stb headers).
- 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
Complete rewrite — the previous version extensively referenced OpenGL
(glClearColor, VAO/VBO/EBO, GLSL shaders) throughout all sections.
The project has used Vulkan exclusively for months.
Key changes:
- Replace all OpenGL references with Vulkan equivalents (VkContext,
VMA, descriptor sets, pipeline cache, SPIR-V shaders)
- Update system diagram to show actual sub-renderer hierarchy
(TerrainRenderer, WMORenderer, M2Renderer, CharacterRenderer, etc.)
- Document GameHandler SOLID decomposition (8 domain handlers +
EntityController + GameServices dependency injection)
- Add Warden 4-layer architecture section
- Add audio system section (miniaudio, 5 sound managers)
- Update opcode count from "100+" to 664+
- Update UI section: talent screen and settings are implemented (not TODO)
- Document threading model (async terrain, GPU upload queue, normal maps)
- Fix dependencies list (Vulkan SDK, VMA, vk-bootstrap, Unicorn, FFmpeg)
- Add container builds and CI platforms
- Remove stale "TODO" items for features that are complete
- CONTRIBUTING.md: C++17 → C++20 (matches CMakeLists.txt)
- TROUBLESHOOTING.md: fix log path (~/.wowee/logs/ → logs/wowee.log)
- docs/authentication.md: remove stale "next milestone" (char enum
and world entry have been working for months)
- docs/srp-implementation.md: update session key status (RC4 encryption
is implemented), fix file reference to actual src/auth/srp.cpp
- docs/packet-framing.md: remove stale "next steps" (realm list is
fully implemented), update status with tested servers
- docs/WARDEN_IMPLEMENTATION.md: fix file list — handler is in
warden_handler.cpp not game_handler.cpp, add warden_memory.hpp/cpp
- docs/WARDEN_QUICK_REFERENCE.md: fix header/source paths (include/
not src/), add warden_handler and warden_memory
- docs/quickstart.md: fix clone command (--recurse-submodules, WoWee
not wowee), remove obsolete manual ImGui clone step, fix log path
- docs/server-setup.md: update version to v1.8.9-preview, date to
2026-03-30, add all supported expansions
- assets/textures/README.md: remove broken doc references
(TURTLEHD_IMPORT.md, TEXTURE_MANIFEST.txt), update integration
status to reflect working PNG override pipeline
GETTING_STARTED.md:
- Fix keybinding table: T→N for talents, Q→L for quest log, W→M for
world map, add missing keys (C, I, O, J, Y, K), remove nonexistent
minimap toggle
- Fix extract_assets.ps1 example param (-WowDirectory → positional)
- Fix Data/ directory tree to match actual manifest layout
- Fix log path: ~/.wowee/logs/ → logs/wowee.log (local directory)
EXPANSION_GUIDE.md:
- Add Turtle WoW 1.17 to supported expansions
- Update code examples to use game_utils.hpp helpers
(isActiveExpansion/isClassicLikeExpansion/isPreWotlk) instead of
removed ExpansionProfile::getActive() and GameHandler::getInstance()
- Update packet parser references (WotLK is default in domain handlers,
not a separate packet_parsers_wotlk.cpp file)
- Update references section with game_utils.hpp
- README: update status date to 2026-03-30, version to v1.8.9-preview,
add container builds line, update current focus to code quality
- CHANGELOG: move v1.8.1 entries to their own section, add v1.8.2-v1.8.9
unreleased section covering architecture (GameHandler decomposition,
Docker cross-compilation), bug fixes (7 UB/overflow/safety fixes),
and code quality (30+ constants, 55+ comments, 8 DRY extractions)
- docs/status.md: update last-updated date to 2026-03-30
- Explain icon load deferral strategy: returning null without caching
allows retry next frame when budget resets, rather than permanently
blacklisting icons that were deferred due to rate-limiting
- Explain DBC field fallback logic: hard-coded WotLK indices are a
safety net when dbc_layouts.json is missing; fieldCount >= 200
distinguishes WotLK (234 fields) from Classic (148)
- Explain M2 version 264 threshold (WotLK stores submesh/bone data
in external .skin files; Classic/TBC embed it in the M2)
- Explain M2 texture types 1 and 6 (skin and hair/scalp; empty
filenames resolved via CharSections.dbc at runtime)
- Explain 0x20 anim flag (embedded data; when clear, keyframes live
in external {Model}{SeqID}-{Var}.anim files)
- Explain geoset ID encoding (group × 100 + variant from
ItemDisplayInfo.dbc; e.g. 801 = sleeves variant 1)
- 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
- entity_controller: clamp block/dodge/parry/crit/rangedCrit percentage
fields to [0..100] after memcpy from update fields — guards against
NaN/Inf from corrupted packets reaching the UI renderer
- entity_controller: add why-comment on OBJECT_FIELD_SCALE_X raw==0
check — IEEE 754 0.0f is all-zero bits, so raw==0 means the field
was never populated; keeping default 1.0f prevents invisible entities
- inventory_screen: extract renderClassRestriction() and
renderRaceRestriction() from two identical 40-line blocks in quest
info and item info tooltips. Both used identical bitmask logic,
strncat formatting, and player-class/race validation (-49 lines net)
- world_map: add why-comment on AreaTable.dbc fallback field indices —
explains that incorrect indices silently return wrong data and why
the WotLK stock layout (ID=0, Parent=2, ExploreFlag=3) is chosen
as the safest default
- input: fix undefined behavior in SDL mouse button loop — SDL_BUTTON(0)
computes (1 << -1) which is UB. Start loop at 1 since SDL button
indices are 1-based (SDL_BUTTON_LEFT=1, RIGHT=3, MIDDLE=2)
- big_num: guard BN_bn2hex/BN_bn2dec against nullptr return on
OpenSSL allocation failure — previously constructed std::string
from nullptr which is undefined behavior
- 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
- 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)
- Name kHalfMinutesPerDay (2880) replacing 8 bare literals across
time conversion, modulo clamping, and midnight wrap arithmetic.
Add why-comment: Light.dbc stores time-of-day as half-minutes
(24h × 60m × 2 = 2880 ticks per day cycle)
- Replace hardcoded 3.14159f with glm::two_pi<float>() in sun
direction angle calculations (2 occurrences)
- terrain_manager: extract kRand16Max (65535.0f) from 8 duplicated
random normalization expressions — 16-bit mask to [0..1] float
- terrain_manager: add static_assert verifying packed alpha unpacks
to full alpha map size (ALPHA_MAP_PACKED * 2 == ALPHA_MAP_SIZE)
- camera_controller: name kCameraClipEpsilon (0.1f) with why-comment
preventing character model clipping at near-minimum distance
- 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)
- vk_pipeline: extract kColorWriteAll constant from 4 duplicated RGBA
bitmask expressions across blend mode functions, with why-comment
- frustum: name kMinNormalLenSq epsilon (1e-8) with why-comment —
prevents division by zero on degenerate planes
- dbc_loader: add why-comment on DBC field width validation — all
fields are fixed 4-byte uint32 per format spec
- pin_auth: replace 0x30 hex literal with '0' char constant, add
why-comment on ASCII encoding for server HMAC compatibility
- m2_loader: define kM2SeqFlagEmbeddedData (0x20) with why-comment —
when clear, keyframe data lives in external .anim files and M2 offsets
are file-relative (reading them from M2 produces garbage). Replaces
3 bare hex literals across parseAnimTrack and ribbon emitter parsing
- audio_engine: replace empty for-loop iterator advance with
std::advance() for clarity
- 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)
- weather: remove duplicate setZoneWeather(15) for Dustwallow Marsh —
second call silently overwrote the first with different parameters
- weather: replace duplicate static RNG in getRandomPosition() with
shared weatherRng() to avoid redundant generator state
- starfield: extract day/night cycle thresholds into named constants
(kDuskStart/kNightStart/kDawnStart/kDawnEnd/kFadeDuration)
- skybox: replace while-loop time wrapping with std::fmod — avoids
O(n) iterations on large time jumps
- asset_manager: add size guard before fsPath.substr(size-4) in
tryLoadPngOverride — resolveFile could theoretically return a
path shorter than the extension
- wmo_loader: name kDoodadNameIndexMask (0x00FFFFFF) with why-comment
explaining the 24-bit name index / 8-bit flags packing and MODN
string table reference
- window: add why-comment on LOG_WARNING usage during shutdown —
intentionally elevated so teardown progress is visible at default
log levels for crash diagnosis