diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 11223bbe..72b25a59 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -22,7 +22,6 @@ #include #include #include -#include namespace wowee::game { class TransportManager; @@ -3145,14 +3144,6 @@ private: uint8_t wardenCheckOpcodes_[9] = {}; bool loadWardenCRFile(const std::string& moduleHashHex); - // Async Warden response: avoids 5-second main-loop stalls from PAGE_A/PAGE_B code pattern searches - std::future> wardenPendingEncrypted_; // encrypted response bytes - bool wardenResponsePending_ = false; - - // ---- RX silence detection ---- - std::chrono::steady_clock::time_point lastRxTime_{}; - bool rxSilenceLogged_ = false; - // ---- XP tracking ---- uint32_t playerXp_ = 0; uint32_t playerNextLevelXp_ = 0; diff --git a/include/game/warden_memory.hpp b/include/game/warden_memory.hpp index d01a27c3..335ad7a9 100644 --- a/include/game/warden_memory.hpp +++ b/include/game/warden_memory.hpp @@ -3,7 +3,6 @@ #include #include #include -#include namespace wowee { namespace game { @@ -19,9 +18,8 @@ public: ~WardenMemory(); /** Search standard candidate dirs for WoW.exe and load it. - * @param build Client build number (e.g. 5875 for Classic 1.12.1) to select the right exe. - * @param isTurtle If true, prefer the Turtle WoW custom exe (different code bytes). */ - bool load(uint16_t build = 0, bool isTurtle = false); + * @param build Client build number (e.g. 5875 for Classic 1.12.1) to select the right exe. */ + bool load(uint16_t build = 0); /** Load PE image from a specific file path. */ bool loadFromFile(const std::string& exePath); @@ -34,21 +32,6 @@ public: bool isLoaded() const { return loaded_; } - /** - * Search PE image for a byte pattern matching HMAC-SHA1(seed, pattern). - * Used for FIND_MEM_IMAGE_CODE_BY_HASH and FIND_CODE_BY_HASH scans. - * @param seed 4-byte HMAC key - * @param expectedHash 20-byte expected HMAC-SHA1 digest - * @param patternLen Length of the pattern to search for - * @param imageOnly If true, search only executable sections (.text) - * @return true if a matching pattern was found in the PE image - */ - bool searchCodePattern(const uint8_t seed[4], const uint8_t expectedHash[20], - uint8_t patternLen, bool imageOnly) const; - - /** Write a little-endian uint32 at the given virtual address in the PE image. */ - void writeLE32(uint32_t va, uint32_t value); - private: bool loaded_ = false; uint32_t imageBase_ = 0; @@ -63,15 +46,9 @@ private: bool parsePE(const std::vector& fileData); void initKuserSharedData(); void patchRuntimeGlobals(); - void patchTurtleWowBinary(); - void verifyWardenScanEntries(); - bool isTurtle_ = false; + void writeLE32(uint32_t va, uint32_t value); std::string findWowExe(uint16_t build) const; - static uint32_t expectedImageSizeForBuild(uint16_t build, bool isTurtle); - - // Cache for searchCodePattern results to avoid repeated 5-second brute-force searches. - // Key: hex string of seed(4)+hash(20)+patLen(1)+imageOnly(1) = 26 bytes. - mutable std::unordered_map codePatternCache_; + static uint32_t expectedImageSizeForBuild(uint16_t build); }; } // namespace game diff --git a/include/rendering/terrain_renderer.hpp b/include/rendering/terrain_renderer.hpp index 5bc13252..f4994792 100644 --- a/include/rendering/terrain_renderer.hpp +++ b/include/rendering/terrain_renderer.hpp @@ -171,7 +171,7 @@ private: // Descriptor pool for material sets VkDescriptorPool materialDescPool = VK_NULL_HANDLE; - static constexpr uint32_t MAX_MATERIAL_SETS = 65536; + static constexpr uint32_t MAX_MATERIAL_SETS = 16384; // Loaded terrain chunks std::vector chunks; diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 2431628e..07f3ac9d 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -656,7 +656,7 @@ private: // Descriptor pool for material sets VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE; - static constexpr uint32_t MAX_MATERIAL_SETS = 32768; + static constexpr uint32_t MAX_MATERIAL_SETS = 8192; // Texture cache (path -> VkTexture) struct TextureCacheEntry { diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 222bfabe..a7eb5cb7 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -836,40 +836,6 @@ void GameHandler::update(float deltaTime) { } } - // Drain pending async Warden response (built on background thread to avoid 5s stalls) - if (wardenResponsePending_) { - auto status = wardenPendingEncrypted_.wait_for(std::chrono::milliseconds(0)); - if (status == std::future_status::ready) { - auto plaintext = wardenPendingEncrypted_.get(); - wardenResponsePending_ = false; - if (!plaintext.empty() && wardenCrypto_) { - std::vector encrypted = wardenCrypto_->encrypt(plaintext); - network::Packet response(wireOpcode(Opcode::CMSG_WARDEN_DATA)); - for (uint8_t byte : encrypted) { - response.writeUInt8(byte); - } - if (socket && socket->isConnected()) { - socket->send(response); - LOG_WARNING("Warden: Sent async CHEAT_CHECKS_RESULT (", plaintext.size(), " bytes plaintext)"); - } - } - } - } - - // Detect RX silence (server stopped sending packets but TCP still open) - if (state == WorldState::IN_WORLD && socket && socket->isConnected() && - lastRxTime_.time_since_epoch().count() > 0) { - auto silenceMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - lastRxTime_).count(); - if (silenceMs > 10000 && !rxSilenceLogged_) { - rxSilenceLogged_ = true; - LOG_WARNING("RX SILENCE: No packets from server for ", silenceMs, "ms — possible soft disconnect"); - } - if (silenceMs > 15000 && silenceMs < 15500) { - LOG_WARNING("RX SILENCE: 15s — server appears to have stopped sending"); - } - } - // Detect server-side disconnect (socket closed during update) if (socket && !socket->isConnected() && state != WorldState::DISCONNECTED) { if (pendingIncomingPackets_.empty() && pendingUpdateObjectWork_.empty()) { @@ -8276,8 +8242,6 @@ void GameHandler::enqueueIncomingPacket(const network::Packet& packet) { pendingIncomingPackets_.pop_front(); } pendingIncomingPackets_.push_back(packet); - lastRxTime_ = std::chrono::steady_clock::now(); - rxSilenceLogged_ = false; } void GameHandler::enqueueIncomingPacketFront(network::Packet&& packet) { @@ -9358,7 +9322,16 @@ void GameHandler::handleWardenData(network::Packet& packet) { } if (match) { - LOG_WARNING("Warden: HASH_REQUEST — CR entry MATCHED, sending pre-computed reply"); + LOG_DEBUG("Warden: Found matching CR entry for seed"); + + // Log the reply we're sending + { + std::string replyHex; + for (int i = 0; i < 20; i++) { + char s[4]; snprintf(s, 4, "%02x", match->reply[i]); replyHex += s; + } + LOG_DEBUG("Warden: Sending pre-computed reply=", replyHex); + } // Send HASH_RESULT (opcode 0x04 + 20-byte reply) std::vector resp; @@ -9372,7 +9345,7 @@ void GameHandler::handleWardenData(network::Packet& packet) { std::vector newDecryptKey(match->serverKey, match->serverKey + 16); wardenCrypto_->replaceKeys(newEncryptKey, newDecryptKey); - LOG_WARNING("Warden: Switched to CR key set"); + LOG_DEBUG("Warden: Switched to CR key set"); wardenState_ = WardenState::WAIT_CHECKS; break; @@ -9381,51 +9354,129 @@ void GameHandler::handleWardenData(network::Packet& packet) { } } - // --- No CR match: decide strategy based on server strictness --- - { - std::string seedHex; - for (auto b : seed) { char s[4]; snprintf(s, 4, "%02x", b); seedHex += s; } + // --- Fallback: compute hash from loaded module --- + LOG_WARNING("Warden: No CR match, computing hash from loaded module"); - bool isTurtle = isActiveExpansion("turtle"); - bool isClassic = (build <= 6005) && !isTurtle; - - if (!isTurtle && !isClassic) { - // WotLK/TBC (AzerothCore, etc.): strict servers BAN for wrong HASH_RESULT. - // Without a matching CR entry we cannot compute the correct hash - // (requires executing the module's native init function). - // Safest action: don't respond. Server will time-out and kick (not ban). - LOG_WARNING("Warden: HASH_REQUEST seed=", seedHex, - " — no CR match, SKIPPING response to avoid account ban"); - LOG_WARNING("Warden: To fix, provide a .cr file with the correct seed→reply entry for this module"); - // Stay in WAIT_HASH_REQUEST — server will eventually kick. - break; - } - - // Turtle/Classic: lenient servers (log-only penalties, no bans). - // Send a best-effort fallback hash so we can continue the handshake. - LOG_WARNING("Warden: No CR match (seed=", seedHex, - "), sending fallback hash (lenient server)"); + if (!wardenLoadedModule_ || !wardenLoadedModule_->isLoaded()) { + LOG_WARNING("Warden: No loaded module and no CR match — using raw module fallback hash"); + // Never skip HASH_RESULT: some realms disconnect quickly if this response is missing. std::vector fallbackReply; - if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) { - const uint8_t* moduleImage = static_cast(wardenLoadedModule_->getModuleMemory()); - size_t moduleImageSize = wardenLoadedModule_->getModuleSize(); - if (moduleImage && moduleImageSize > 0) { - std::vector imageData(moduleImage, moduleImage + moduleImageSize); - fallbackReply = auth::Crypto::sha1(imageData); - } - } - if (fallbackReply.empty()) { - if (!wardenModuleData_.empty()) - fallbackReply = auth::Crypto::sha1(wardenModuleData_); - else - fallbackReply.assign(20, 0); + if (!wardenModuleData_.empty()) { + fallbackReply = auth::Crypto::sha1(wardenModuleData_); + } else if (!wardenModuleHash_.empty()) { + fallbackReply = auth::Crypto::sha1(wardenModuleHash_); + } else { + fallbackReply.assign(20, 0); } std::vector resp; resp.push_back(0x04); // WARDEN_CMSG_HASH_RESULT resp.insert(resp.end(), fallbackReply.begin(), fallbackReply.end()); sendWardenResponse(resp); + + applyWardenSeedRekey(seed); + wardenState_ = WardenState::WAIT_CHECKS; + break; + } + + { + const uint8_t* moduleImage = static_cast(wardenLoadedModule_->getModuleMemory()); + size_t moduleImageSize = wardenLoadedModule_->getModuleSize(); + const auto& decompressedData = wardenLoadedModule_->getDecompressedData(); + + if (!moduleImage || moduleImageSize == 0) { + LOG_WARNING("Warden: Loaded module has no executable image — using raw module hash fallback"); + std::vector fallbackReply = + !wardenModuleData_.empty() ? auth::Crypto::sha1(wardenModuleData_) : std::vector(20, 0); + std::vector resp; + resp.push_back(0x04); // WARDEN_CMSG_HASH_RESULT + resp.insert(resp.end(), fallbackReply.begin(), fallbackReply.end()); + sendWardenResponse(resp); + applyWardenSeedRekey(seed); + wardenState_ = WardenState::WAIT_CHECKS; + break; + } + + // --- Empirical test: try multiple SHA1 computations and check against first CR entry --- + if (!wardenCREntries_.empty()) { + const auto& firstCR = wardenCREntries_[0]; + std::string expectedHex; + for (int i = 0; i < 20; i++) { char s[4]; snprintf(s, 4, "%02x", firstCR.reply[i]); expectedHex += s; } + LOG_DEBUG("Warden: Empirical test — expected reply from CR[0]=", expectedHex); + + // Test 1: SHA1(moduleImage) + { + std::vector data(moduleImage, moduleImage + moduleImageSize); + auto h = auth::Crypto::sha1(data); + bool match = (std::memcmp(h.data(), firstCR.reply, 20) == 0); + std::string hex; for (auto b : h) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: SHA1(moduleImage)=", hex, match ? " MATCH!" : ""); + } + // Test 2: SHA1(seed || moduleImage) + { + std::vector data; + data.insert(data.end(), seed.begin(), seed.end()); + data.insert(data.end(), moduleImage, moduleImage + moduleImageSize); + auto h = auth::Crypto::sha1(data); + bool match = (std::memcmp(h.data(), firstCR.reply, 20) == 0); + std::string hex; for (auto b : h) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: SHA1(seed||image)=", hex, match ? " MATCH!" : ""); + } + // Test 3: SHA1(moduleImage || seed) + { + std::vector data(moduleImage, moduleImage + moduleImageSize); + data.insert(data.end(), seed.begin(), seed.end()); + auto h = auth::Crypto::sha1(data); + bool match = (std::memcmp(h.data(), firstCR.reply, 20) == 0); + std::string hex; for (auto b : h) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: SHA1(image||seed)=", hex, match ? " MATCH!" : ""); + } + // Test 4: SHA1(decompressedData) + { + auto h = auth::Crypto::sha1(decompressedData); + bool match = (std::memcmp(h.data(), firstCR.reply, 20) == 0); + std::string hex; for (auto b : h) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: SHA1(decompressed)=", hex, match ? " MATCH!" : ""); + } + // Test 5: SHA1(rawModuleData) + { + auto h = auth::Crypto::sha1(wardenModuleData_); + bool match = (std::memcmp(h.data(), firstCR.reply, 20) == 0); + std::string hex; for (auto b : h) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: SHA1(rawModule)=", hex, match ? " MATCH!" : ""); + } + // Test 6: Check if all CR replies are the same (constant hash) + { + bool allSame = true; + for (size_t i = 1; i < wardenCREntries_.size(); i++) { + if (std::memcmp(wardenCREntries_[i].reply, firstCR.reply, 20) != 0) { + allSame = false; + break; + } + } + LOG_DEBUG("Warden: All ", wardenCREntries_.size(), " CR replies identical? ", allSame ? "YES" : "NO"); + } + } + + // --- Compute the hash: SHA1(moduleImage) is the most likely candidate --- + // The module's hash response is typically SHA1 of the loaded module image. + // This is a constant per module (seed is not used in the hash, only for key derivation). + std::vector imageData(moduleImage, moduleImage + moduleImageSize); + auto reply = auth::Crypto::sha1(imageData); + + { + std::string hex; + for (auto b : reply) { char s[4]; snprintf(s, 4, "%02x", b); hex += s; } + LOG_DEBUG("Warden: Sending SHA1(moduleImage)=", hex); + } + + // Send HASH_RESULT (opcode 0x04 + 20-byte hash) + std::vector resp; + resp.push_back(0x04); + resp.insert(resp.end(), reply.begin(), reply.end()); + sendWardenResponse(resp); + applyWardenSeedRekey(seed); } @@ -9461,309 +9512,6 @@ void GameHandler::handleWardenData(network::Packet& packet) { uint8_t xorByte = decrypted.back(); LOG_DEBUG("Warden: XOR byte = 0x", [&]{ char s[4]; snprintf(s,4,"%02x",xorByte); return std::string(s); }()); - // Quick-scan for PAGE_A/PAGE_B checks (these trigger 5-second brute-force searches) - { - bool hasSlowChecks = false; - for (size_t i = pos; i < decrypted.size() - 1; i++) { - uint8_t d = decrypted[i] ^ xorByte; - if (d == wardenCheckOpcodes_[2] || d == wardenCheckOpcodes_[3]) { - hasSlowChecks = true; - break; - } - } - if (hasSlowChecks && !wardenResponsePending_) { - LOG_WARNING("Warden: PAGE_A/PAGE_B detected — building response async to avoid main-loop stall"); - // Ensure wardenMemory_ is loaded on main thread before launching async task - if (!wardenMemory_) { - wardenMemory_ = std::make_unique(); - if (!wardenMemory_->load(static_cast(build), isActiveExpansion("turtle"))) { - LOG_WARNING("Warden: Could not load WoW.exe for MEM_CHECK"); - } - } - // Capture state by value (decrypted, strings) and launch async. - // The async task returns plaintext response bytes; main thread encrypts+sends in update(). - size_t capturedPos = pos; - wardenPendingEncrypted_ = std::async(std::launch::async, - [this, decrypted, strings, xorByte, capturedPos]() -> std::vector { - // This runs on a background thread — same logic as the synchronous path below. - // BEGIN: duplicated check processing (kept in sync with synchronous path) - enum CheckType { CT_MEM=0, CT_PAGE_A=1, CT_PAGE_B=2, CT_MPQ=3, CT_LUA=4, - CT_DRIVER=5, CT_TIMING=6, CT_PROC=7, CT_MODULE=8, CT_UNKNOWN=9 }; - size_t checkEnd = decrypted.size() - 1; - size_t pos = capturedPos; - - auto decodeCheckType = [&](uint8_t raw) -> CheckType { - uint8_t decoded = raw ^ xorByte; - if (decoded == wardenCheckOpcodes_[0]) return CT_MEM; - if (decoded == wardenCheckOpcodes_[1]) return CT_MODULE; - if (decoded == wardenCheckOpcodes_[2]) return CT_PAGE_A; - if (decoded == wardenCheckOpcodes_[3]) return CT_PAGE_B; - if (decoded == wardenCheckOpcodes_[4]) return CT_MPQ; - if (decoded == wardenCheckOpcodes_[5]) return CT_LUA; - if (decoded == wardenCheckOpcodes_[6]) return CT_PROC; - if (decoded == wardenCheckOpcodes_[7]) return CT_DRIVER; - if (decoded == wardenCheckOpcodes_[8]) return CT_TIMING; - return CT_UNKNOWN; - }; - auto resolveString = [&](uint8_t idx) -> std::string { - if (idx == 0) return {}; - size_t i = idx - 1; - return i < strings.size() ? strings[i] : std::string(); - }; - auto isKnownWantedCodeScan = [&](const uint8_t seed[4], const uint8_t hash[20], - uint32_t off, uint8_t len) -> bool { - auto tryMatch = [&](const uint8_t* pat, size_t patLen) { - uint8_t out[SHA_DIGEST_LENGTH]; unsigned int outLen = 0; - HMAC(EVP_sha1(), seed, 4, pat, patLen, out, &outLen); - return outLen == SHA_DIGEST_LENGTH && !std::memcmp(out, hash, SHA_DIGEST_LENGTH); - }; - static const uint8_t p1[] = {0x33,0xD2,0x33,0xC9,0xE8,0x87,0x07,0x1B,0x00,0xE8}; - if (off == 13856 && len == sizeof(p1) && tryMatch(p1, sizeof(p1))) return true; - static const uint8_t p2[] = {0x56,0x57,0xFC,0x8B,0x54,0x24,0x14,0x8B, - 0x74,0x24,0x10,0x8B,0x44,0x24,0x0C,0x8B,0xCA,0x8B,0xF8,0xC1, - 0xE9,0x02,0x74,0x02,0xF3,0xA5,0xB1,0x03,0x23,0xCA,0x74,0x02, - 0xF3,0xA4,0x5F,0x5E,0xC3}; - if (len == sizeof(p2) && tryMatch(p2, sizeof(p2))) return true; - return false; - }; - - std::vector resultData; - int checkCount = 0; - int checkTypeCounts[10] = {}; - - #define WARDEN_ASYNC_HANDLER 1 - // The check processing loop is identical to the synchronous path. - // See the synchronous case 0x02 below for the canonical version. - while (pos < checkEnd) { - CheckType ct = decodeCheckType(decrypted[pos]); - pos++; - checkCount++; - if (ct <= CT_UNKNOWN) checkTypeCounts[ct]++; - - switch (ct) { - case CT_TIMING: { - resultData.push_back(0x01); - uint32_t ticks = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - resultData.push_back(ticks & 0xFF); - resultData.push_back((ticks >> 8) & 0xFF); - resultData.push_back((ticks >> 16) & 0xFF); - resultData.push_back((ticks >> 24) & 0xFF); - break; - } - case CT_MEM: { - if (pos + 6 > checkEnd) { pos = checkEnd; break; } - uint8_t strIdx = decrypted[pos++]; - std::string moduleName = resolveString(strIdx); - uint32_t offset = decrypted[pos] | (uint32_t(decrypted[pos+1])<<8) - | (uint32_t(decrypted[pos+2])<<16) | (uint32_t(decrypted[pos+3])<<24); - pos += 4; - uint8_t readLen = decrypted[pos++]; - LOG_WARNING("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(), - " len=", (int)readLen); - if (offset == 0x00CF0BC8 && readLen == 4 && wardenMemory_ && wardenMemory_->isLoaded()) { - uint32_t now = static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); - wardenMemory_->writeLE32(0xCF0BC8, now - 2000); - } - std::vector memBuf(readLen, 0); - bool memOk = wardenMemory_ && wardenMemory_->isLoaded() && - wardenMemory_->readMemory(offset, readLen, memBuf.data()); - if (memOk) { - const char* region = "?"; - if (offset >= 0x7FFE0000 && offset < 0x7FFF0000) region = "KUSER"; - else if (offset >= 0x400000 && offset < 0x800000) region = ".text/.code"; - else if (offset >= 0x7FF000 && offset < 0x827000) region = ".rdata"; - else if (offset >= 0x827000 && offset < 0x883000) region = ".data(raw)"; - else if (offset >= 0x883000 && offset < 0xD06000) region = ".data(BSS)"; - bool allZero = true; - for (int i = 0; i < (int)readLen; i++) { if (memBuf[i] != 0) { allZero = false; break; } } - std::string hexDump; - for (int i = 0; i < (int)readLen; i++) { char hx[4]; snprintf(hx,4,"%02x ",memBuf[i]); hexDump += hx; } - LOG_WARNING("Warden: MEM_CHECK served: [", hexDump, "] region=", region, - (allZero && offset >= 0x883000 ? " \xe2\x98\x85""BSS_ZERO\xe2\x98\x85" : "")); - if (offset == 0x7FFE026C && readLen == 12) - LOG_WARNING("Warden: Applying 4-byte ULONG alignment padding for WinVersionGet"); - resultData.push_back(0x00); - resultData.insert(resultData.end(), memBuf.begin(), memBuf.end()); - } else if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) { - // Try Warden module memory - uint32_t modBase = offset & ~0xFFFFu; - uint32_t modOfs = offset - modBase; - const auto& modData = wardenLoadedModule_->getDecompressedData(); - if (modOfs + readLen <= modData.size()) { - std::memcpy(memBuf.data(), modData.data() + modOfs, readLen); - LOG_WARNING("Warden: MEM_CHECK served from Warden module (offset=0x", - [&]{char s[12];snprintf(s,12,"%x",modOfs);return std::string(s);}(), ")"); - resultData.push_back(0x00); - resultData.insert(resultData.end(), memBuf.begin(), memBuf.end()); - } else { - resultData.push_back(0xE9); - } - } else { - resultData.push_back(0xE9); - } - break; - } - case CT_PAGE_A: - case CT_PAGE_B: { - constexpr size_t kPageSize = 29; - const char* pageName = (ct == CT_PAGE_A) ? "PAGE_A" : "PAGE_B"; - bool isImageOnly = (ct == CT_PAGE_A); - if (pos + kPageSize > checkEnd) { pos = checkEnd; resultData.push_back(0x00); break; } - const uint8_t* p = decrypted.data() + pos; - const uint8_t* seed = p; - const uint8_t* sha1 = p + 4; - uint32_t off = uint32_t(p[24])|(uint32_t(p[25])<<8)|(uint32_t(p[26])<<16)|(uint32_t(p[27])<<24); - uint8_t patLen = p[28]; - bool found = false; - bool turtleFallback = false; - // Turtle fallback: if offset is within PE image range, - // this is an integrity check — skip the expensive 25-second - // brute-force search and return "found" immediately to stay - // within the server's Warden response timeout. - bool canTurtleFallback = (ct == CT_PAGE_A && isActiveExpansion("turtle") && - wardenMemory_ && wardenMemory_->isLoaded() && off < 0x600000); - if (isKnownWantedCodeScan(seed, sha1, off, patLen)) { - found = true; - } else if (canTurtleFallback) { - // Skip the expensive 25-second brute-force search; - // the turtle fallback will return "found" instantly. - found = true; - turtleFallback = true; - } else if (wardenMemory_ && wardenMemory_->isLoaded() && patLen > 0) { - found = wardenMemory_->searchCodePattern(seed, sha1, patLen, isImageOnly); - if (!found && wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) { - const uint8_t* modMem = static_cast(wardenLoadedModule_->getModuleMemory()); - size_t modSize = wardenLoadedModule_->getModuleSize(); - if (modMem && modSize >= patLen) { - for (size_t i = 0; i < modSize - patLen + 1; i++) { - uint8_t h[20]; unsigned int hl = 0; - HMAC(EVP_sha1(), seed, 4, modMem+i, patLen, h, &hl); - if (hl == 20 && !std::memcmp(h, sha1, 20)) { found = true; break; } - } - } - } - } - uint8_t pageResult = found ? 0x4A : 0x00; - LOG_WARNING("Warden: ", pageName, " offset=0x", - [&]{char s[12];snprintf(s,12,"%08x",off);return std::string(s);}(), - " patLen=", (int)patLen, " found=", found ? "yes" : "no", - turtleFallback ? " (turtle-fallback)" : ""); - pos += kPageSize; - resultData.push_back(pageResult); - break; - } - case CT_MPQ: { - if (pos + 1 > checkEnd) { pos = checkEnd; break; } - uint8_t strIdx = decrypted[pos++]; - std::string filePath = resolveString(strIdx); - LOG_WARNING("Warden: MPQ file=\"", (filePath.empty() ? "?" : filePath), "\""); - bool found = false; - std::vector hash(20, 0); - if (!filePath.empty()) { - std::string np = asciiLower(filePath); - std::replace(np.begin(), np.end(), '/', '\\'); - auto knownIt = knownDoorHashes().find(np); - if (knownIt != knownDoorHashes().end()) { found = true; hash.assign(knownIt->second.begin(), knownIt->second.end()); } - auto* am = core::Application::getInstance().getAssetManager(); - if (am && am->isInitialized() && !found) { - std::vector fd; - std::string rp = resolveCaseInsensitiveDataPath(am->getDataPath(), filePath); - if (!rp.empty()) fd = readFileBinary(rp); - if (fd.empty()) fd = am->readFile(filePath); - if (!fd.empty()) { found = true; hash = auth::Crypto::sha1(fd); } - } - } - LOG_WARNING("Warden: MPQ result=", (found ? "FOUND" : "NOT_FOUND")); - if (found) { resultData.push_back(0x00); resultData.insert(resultData.end(), hash.begin(), hash.end()); } - else { resultData.push_back(0x01); } - break; - } - case CT_LUA: { - if (pos + 1 > checkEnd) { pos = checkEnd; break; } - pos++; resultData.push_back(0x01); break; - } - case CT_DRIVER: { - if (pos + 25 > checkEnd) { pos = checkEnd; break; } - pos += 24; - uint8_t strIdx = decrypted[pos++]; - std::string dn = resolveString(strIdx); - LOG_WARNING("Warden: DRIVER=\"", (dn.empty() ? "?" : dn), "\" -> 0x00(not found)"); - resultData.push_back(0x00); break; - } - case CT_MODULE: { - if (pos + 24 > checkEnd) { pos = checkEnd; resultData.push_back(0x00); break; } - const uint8_t* p = decrypted.data() + pos; - uint8_t sb[4] = {p[0],p[1],p[2],p[3]}; - uint8_t rh[20]; std::memcpy(rh, p+4, 20); - pos += 24; - bool isWanted = hmacSha1Matches(sb, "KERNEL32.DLL", rh); - std::string mn = isWanted ? "KERNEL32.DLL" : "?"; - if (!isWanted) { - if (hmacSha1Matches(sb,"WPESPY.DLL",rh)) mn = "WPESPY.DLL"; - else if (hmacSha1Matches(sb,"TAMIA.DLL",rh)) mn = "TAMIA.DLL"; - else if (hmacSha1Matches(sb,"PRXDRVPE.DLL",rh)) mn = "PRXDRVPE.DLL"; - } - uint8_t mr = isWanted ? 0x4A : 0x00; - LOG_WARNING("Warden: MODULE \"", mn, "\" -> 0x", - [&]{char s[4];snprintf(s,4,"%02x",mr);return std::string(s);}(), - isWanted ? "(found)" : "(not found)"); - resultData.push_back(mr); break; - } - case CT_PROC: { - if (pos + 30 > checkEnd) { pos = checkEnd; break; } - pos += 30; resultData.push_back(0x01); break; - } - default: pos = checkEnd; break; - } - } - #undef WARDEN_ASYNC_HANDLER - - // Log summary - { - std::string summary; - const char* ctNames[] = {"MEM","PAGE_A","PAGE_B","MPQ","LUA","DRIVER","TIMING","PROC","MODULE","UNK"}; - for (int i = 0; i < 10; i++) { - if (checkTypeCounts[i] > 0) { - if (!summary.empty()) summary += " "; - summary += ctNames[i]; summary += "="; summary += std::to_string(checkTypeCounts[i]); - } - } - LOG_WARNING("Warden: (async) Parsed ", checkCount, " checks [", summary, - "] resultSize=", resultData.size()); - std::string fullHex; - for (size_t bi = 0; bi < resultData.size(); bi++) { - char hx[4]; snprintf(hx, 4, "%02x ", resultData[bi]); fullHex += hx; - if ((bi + 1) % 32 == 0 && bi + 1 < resultData.size()) fullHex += "\n "; - } - LOG_WARNING("Warden: RESPONSE_HEX [", fullHex, "]"); - } - - // Build plaintext response: [0x02][uint16 len][uint32 checksum][resultData] - auto resultHash = auth::Crypto::sha1(resultData); - uint32_t checksum = 0; - for (int i = 0; i < 5; i++) { - uint32_t word = resultHash[i*4] | (uint32_t(resultHash[i*4+1])<<8) - | (uint32_t(resultHash[i*4+2])<<16) | (uint32_t(resultHash[i*4+3])<<24); - checksum ^= word; - } - uint16_t rl = static_cast(resultData.size()); - std::vector resp; - resp.push_back(0x02); - resp.push_back(rl & 0xFF); resp.push_back((rl >> 8) & 0xFF); - resp.push_back(checksum & 0xFF); resp.push_back((checksum >> 8) & 0xFF); - resp.push_back((checksum >> 16) & 0xFF); resp.push_back((checksum >> 24) & 0xFF); - resp.insert(resp.end(), resultData.begin(), resultData.end()); - return resp; // plaintext; main thread will encrypt + send - }); - wardenResponsePending_ = true; - break; // exit case 0x02 — response will be sent from update() - } - } - // Check type enum indices enum CheckType { CT_MEM=0, CT_PAGE_A=1, CT_PAGE_B=2, CT_MPQ=3, CT_LUA=4, CT_DRIVER=5, CT_TIMING=6, CT_PROC=7, CT_MODULE=8, CT_UNKNOWN=9 }; @@ -9906,14 +9654,16 @@ void GameHandler::handleWardenData(network::Packet& packet) { | (uint32_t(decrypted[pos+2])<<16) | (uint32_t(decrypted[pos+3])<<24); pos += 4; uint8_t readLen = decrypted[pos++]; - LOG_WARNING("Warden: (sync) MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(), - " len=", (int)readLen, - moduleName.empty() ? "" : (" module=\"" + moduleName + "\"")); + LOG_DEBUG("Warden: MEM offset=0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}(), + " len=", (int)readLen); + if (!moduleName.empty()) { + LOG_DEBUG("Warden: MEM module=\"", moduleName, "\""); + } // Lazy-load WoW.exe PE image on first MEM_CHECK if (!wardenMemory_) { wardenMemory_ = std::make_unique(); - if (!wardenMemory_->load(static_cast(build), isActiveExpansion("turtle"))) { + if (!wardenMemory_->load(static_cast(build))) { LOG_WARNING("Warden: Could not load WoW.exe for MEM_CHECK"); } } @@ -9922,21 +9672,6 @@ void GameHandler::handleWardenData(network::Packet& packet) { std::vector memBuf(readLen, 0); if (wardenMemory_->isLoaded() && wardenMemory_->readMemory(offset, readLen, memBuf.data())) { LOG_DEBUG("Warden: MEM_CHECK served from PE image"); - } else if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) { - // Try Warden module memory (addresses outside PE range) - uint32_t modBase = offset & ~0xFFFFu; // 64KB-aligned base guess - uint32_t modOfs = offset - modBase; - const auto& modData = wardenLoadedModule_->getDecompressedData(); - if (modOfs + readLen <= modData.size()) { - std::memcpy(memBuf.data(), modData.data() + modOfs, readLen); - LOG_WARNING("Warden: MEM_CHECK served from Warden module (base=0x", - [&]{char s[12];snprintf(s,12,"%08x",modBase);return std::string(s);}(), - " offset=0x", - [&]{char s[12];snprintf(s,12,"%x",modOfs);return std::string(s);}(), ")"); - } else { - LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x", - [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}()); - } } else { LOG_WARNING("Warden: MEM_CHECK fallback to zeros for 0x", [&]{char s[12];snprintf(s,12,"%08x",offset);return std::string(s);}()); @@ -9986,16 +9721,7 @@ void GameHandler::handleWardenData(network::Packet& packet) { (uint32_t(p[26]) << 16) | (uint32_t(p[27]) << 24); uint8_t len = p[28]; if (isKnownWantedCodeScan(seedBytes, reqHash, off, len)) { - pageResult = 0x4A; - } else if (wardenMemory_ && wardenMemory_->isLoaded() && len > 0) { - if (wardenMemory_->searchCodePattern(seedBytes, reqHash, len, true)) - pageResult = 0x4A; - } - // Turtle fallback for integrity checks - if (pageResult == 0x00 && isActiveExpansion("turtle") && off < 0x600000) { - pageResult = 0x4A; - LOG_WARNING("Warden: PAGE_A turtle-fallback for offset=0x", - [&]{char s[12];snprintf(s,12,"%08x",off);return std::string(s);}()); + pageResult = 0x4A; // PatternFound } } LOG_DEBUG("Warden: PAGE_A request bytes=", consume, @@ -10163,18 +9889,7 @@ void GameHandler::handleWardenData(network::Packet& packet) { } } - // Log synchronous round summary at WARNING level for diagnostics - { - int syncCounts[10] = {}; - // Re-count (we don't have per-check counters in sync path yet) - LOG_WARNING("Warden: (sync) Parsed ", checkCount, " checks, resultSize=", resultData.size()); - std::string fullHex; - for (size_t bi = 0; bi < resultData.size(); bi++) { - char hx[4]; snprintf(hx, 4, "%02x ", resultData[bi]); fullHex += hx; - if ((bi + 1) % 32 == 0 && bi + 1 < resultData.size()) fullHex += "\n "; - } - LOG_WARNING("Warden: (sync) RESPONSE_HEX [", fullHex, "]"); - } + LOG_DEBUG("Warden: Parsed ", checkCount, " checks, result data size=", resultData.size()); // --- Compute checksum: XOR of 5 uint32s from SHA1(resultData) --- auto resultHash = auth::Crypto::sha1(resultData); diff --git a/src/game/warden_memory.cpp b/src/game/warden_memory.cpp index 26895784..aa921155 100644 --- a/src/game/warden_memory.cpp +++ b/src/game/warden_memory.cpp @@ -7,8 +7,6 @@ #include #include #include -#include -#include namespace wowee { namespace game { @@ -108,181 +106,19 @@ bool WardenMemory::parsePE(const std::vector& fileData) { " size=0x", copySize, std::dec); } - LOG_WARNING("WardenMemory: PE loaded — imageBase=0x", std::hex, imageBase_, - " imageSize=0x", imageSize_, std::dec, - " (", numSections, " sections, ", fileData.size(), " bytes on disk)"); - return true; } void WardenMemory::initKuserSharedData() { std::memset(kuserData_, 0, KUSER_SIZE); - // ------------------------------------------------------------------- - // KUSER_SHARED_DATA layout — Windows 7 SP1 x86 (from ntddk.h PDB) - // Warden reads this in 238-byte chunks for OS fingerprinting. - // All offsets verified against the canonical _KUSER_SHARED_DATA struct. - // ------------------------------------------------------------------- + // NtMajorVersion at offset 0x026C = 6 (Vista/7/8/10) + uint32_t ntMajor = 6; + std::memcpy(kuserData_ + 0x026C, &ntMajor, 4); - auto w32 = [&](uint32_t off, uint32_t v) { std::memcpy(kuserData_ + off, &v, 4); }; - auto w16 = [&](uint32_t off, uint16_t v) { std::memcpy(kuserData_ + off, &v, 2); }; - auto w8 = [&](uint32_t off, uint8_t v) { kuserData_[off] = v; }; - - // +0x000 TickCountLowDeprecated (ULONG) - w32(0x0000, 0x003F4A00); // ~70 min uptime - - // +0x004 TickCountMultiplier (ULONG) - w32(0x0004, 0x0FA00000); - - // +0x008 InterruptTime (KSYSTEM_TIME: Low4 + High1_4 + High2_4) - w32(0x0008, 0x6B49D200); - w32(0x000C, 0x00000029); - w32(0x0010, 0x00000029); - - // +0x014 SystemTime (KSYSTEM_TIME) — ~2024 epoch FILETIME - w32(0x0014, 0xA0B71B00); - w32(0x0018, 0x01DA5E80); - w32(0x001C, 0x01DA5E80); - - // +0x020 TimeZoneBias (KSYSTEM_TIME) — 0 = UTC - // (leave zeros) - - // +0x02C ImageNumberLow / ImageNumberHigh (USHORT each) - w16(0x002C, 0x014C); // IMAGE_FILE_MACHINE_I386 - w16(0x002E, 0x014C); - - // +0x030 NtSystemRoot (WCHAR[260] = 520 bytes, ends at +0x238) - const wchar_t* sysRoot = L"C:\\WINDOWS"; - for (size_t i = 0; i < 10; i++) { - w16(0x0030 + static_cast(i) * 2, static_cast(sysRoot[i])); - } - - // +0x238 MaxStackTraceDepth (ULONG) - w32(0x0238, 0); - - // +0x23C CryptoExponent (ULONG) — 65537 - w32(0x023C, 0x00010001); - - // +0x240 TimeZoneId (ULONG) — TIME_ZONE_ID_UNKNOWN - w32(0x0240, 0); - - // +0x244 LargePageMinimum (ULONG) — 2 MB - w32(0x0244, 0x00200000); - - // +0x248 Reserved2[7] (28 bytes) — zeros - // (leave zeros) - - // +0x264 NtProductType (NT_PRODUCT_TYPE = ULONG) — VER_NT_WORKSTATION - w32(0x0264, 1); - - // +0x268 ProductTypeIsValid (BOOLEAN = UCHAR) - w8(0x0268, 1); - - // +0x269 Reserved9[3] — padding - // (leave zeros) - - // +0x26C NtMajorVersion (ULONG) — 6 (Windows Vista/7/8/10) - w32(0x026C, 6); - - // +0x270 NtMinorVersion (ULONG) — 1 (Windows 7) - w32(0x0270, 1); - - // +0x274 ProcessorFeatures (BOOLEAN[64] = 64 bytes, ends at +0x2B4) - // Each entry is a single UCHAR (0 or 1). - // Index Name Value - // [0] PF_FLOATING_POINT_PRECISION_ERRATA 0 - // [1] PF_FLOATING_POINT_EMULATED 0 - // [2] PF_COMPARE_EXCHANGE_DOUBLE 1 - // [3] PF_MMX_INSTRUCTIONS_AVAILABLE 1 - // [4] PF_PPC_MOVEMEM_64BIT_OK 0 - // [5] PF_ALPHA_BYTE_INSTRUCTIONS 0 - // [6] PF_XMMI_INSTRUCTIONS_AVAILABLE (SSE) 1 - // [7] PF_3DNOW_INSTRUCTIONS_AVAILABLE 0 - // [8] PF_RDTSC_INSTRUCTION_AVAILABLE 1 - // [9] PF_PAE_ENABLED 1 - // [10] PF_XMMI64_INSTRUCTIONS_AVAILABLE(SSE2)1 - // [11] PF_SSE_DAZ_MODE_AVAILABLE 0 - // [12] PF_NX_ENABLED 1 - // [13] PF_SSE3_INSTRUCTIONS_AVAILABLE 1 - // [14] PF_COMPARE_EXCHANGE128 0 (x86 typically 0) - // [15] PF_COMPARE64_EXCHANGE128 0 - // [16] PF_CHANNELS_ENABLED 0 - // [17] PF_XSAVE_ENABLED 0 - w8(0x0274 + 2, 1); // PF_COMPARE_EXCHANGE_DOUBLE - w8(0x0274 + 3, 1); // PF_MMX - w8(0x0274 + 6, 1); // PF_SSE - w8(0x0274 + 8, 1); // PF_RDTSC - w8(0x0274 + 9, 1); // PF_PAE_ENABLED - w8(0x0274 + 10, 1); // PF_SSE2 - w8(0x0274 + 12, 1); // PF_NX_ENABLED - w8(0x0274 + 13, 1); // PF_SSE3 - - // +0x2B4 Reserved1 (ULONG) - // +0x2B8 Reserved3 (ULONG) - // +0x2BC TimeSlip (ULONG) - // +0x2C0 AlternativeArchitecture (ULONG) = 0 (StandardDesign) - // +0x2C4 AltArchitecturePad[1] (ULONG) - // +0x2C8 SystemExpirationDate (LARGE_INTEGER = 8 bytes) - // (leave zeros) - - // +0x2D0 SuiteMask (ULONG) — VER_SUITE_SINGLEUSERTS | VER_SUITE_TERMINAL - w32(0x02D0, 0x0110); // 0x0100=SINGLEUSERTS, 0x0010=TERMINAL - - // +0x2D4 KdDebuggerEnabled (BOOLEAN = UCHAR) - w8(0x02D4, 0); - - // +0x2D5 NXSupportPolicy (UCHAR) — 2 = OptIn - w8(0x02D5, 2); - - // +0x2D6 Reserved6[2] - // (leave zeros) - - // +0x2D8 ActiveConsoleId (ULONG) — session 0 or 1 - w32(0x02D8, 1); - - // +0x2DC DismountCount (ULONG) - w32(0x02DC, 0); - - // +0x2E0 ComPlusPackage (ULONG) - w32(0x02E0, 0); - - // +0x2E4 LastSystemRITEventTickCount (ULONG) — recent input tick - w32(0x02E4, 0x003F4900); - - // +0x2E8 NumberOfPhysicalPages (ULONG) — 4GB / 4KB ≈ 1M pages - w32(0x02E8, 0x000FF000); - - // +0x2EC SafeBootMode (BOOLEAN) — 0 = normal boot - w8(0x02EC, 0); - - // +0x2F0 SharedDataFlags / TraceLogging (ULONG) - w32(0x02F0, 0); - - // +0x2F8 TestRetInstruction (ULONGLONG = 8 bytes) — RET opcode - w8(0x02F8, 0xC3); // x86 RET instruction - - // +0x300 SystemCall (ULONG) - w32(0x0300, 0); - - // +0x304 SystemCallReturn (ULONG) - w32(0x0304, 0); - - // +0x308 SystemCallPad[3] (24 bytes) - // (leave zeros) - - // +0x320 TickCount (KSYSTEM_TIME) — matches TickCountLowDeprecated - w32(0x0320, 0x003F4A00); - - // +0x32C TickCountPad[1] - // (leave zeros) - - // +0x330 Cookie (ULONG) — stack cookie, random-looking value - w32(0x0330, 0x4A2F8C15); - - // +0x334 ConsoleSessionForegroundProcessId (ULONG) — some PID - w32(0x0334, 0x00001234); - - // Everything after +0x338 is typically zero on Win7 x86 + // NtMinorVersion at offset 0x0270 = 1 (Windows 7) + uint32_t ntMinor = 1; + std::memcpy(kuserData_ + 0x0270, &ntMinor, 4); } void WardenMemory::writeLE32(uint32_t va, uint32_t value) { @@ -296,52 +132,56 @@ void WardenMemory::writeLE32(uint32_t va, uint32_t value) { } void WardenMemory::patchRuntimeGlobals() { - if (imageBase_ != 0x00400000) { - LOG_WARNING("WardenMemory: unexpected imageBase=0x", std::hex, imageBase_, std::dec, - " — skipping runtime global patches"); + // Only patch Classic 1.12.1 (build 5875) WoW.exe + // Identified by: ImageBase=0x400000, ImageSize=0x906000 (unique to 1.12.1) + // Other expansions have different image sizes and different global addresses. + if (imageBase_ != 0x00400000 || imageSize_ != 0x00906000) { + LOG_INFO("WardenMemory: Not Classic 1.12.1 WoW.exe (imageSize=0x", + std::hex, imageSize_, std::dec, "), skipping runtime global patches"); return; } // Classic 1.12.1 (build 5875) runtime globals - // VMaNGOS has TWO types of Warden scans that read these addresses: + // These are in the .data BSS region - zero on disk, populated at runtime. + // We patch them with fake but valid values so Warden checks pass. // - // 1. DB-driven scans (warden_scans table): memcmp against expected bytes. - // These check CODE sections for integrity — never check runtime data addresses. - // - // 2. Scripted scans (WardenWin::LoadScriptedScans): READ and INTERPRET values. - // - "Warden locate" reads 0xCE897C as a pointer, follows chain to SYSTEM_INFO - // - "Anti-AFK hack" reads 0xCF0BC8 as a timestamp, compares vs TIMING ticks - // - "CWorld::enables" reads 0xC7B2A4, checks flag bits - // - "EndScene" reads 0xC0ED38, follows pointer chain to find EndScene address - // - // We MUST patch these for ALL clients (including Turtle WoW) because the scripted - // scans interpret the values as runtime state, not static code bytes. Returning - // raw PE data causes the Anti-AFK scan to see lastHardwareAction > currentTime - // (PE bytes happen to be a large value), triggering a kick after ~3.5 minutes. + // Offsets from CMaNGOS anticheat module (wardenwin.cpp): + // WardenModule = 0xCE897C + // OfsWardenSysInfo = 0x228 + // OfsWardenWinSysInfo = 0x08 + // g_theGxDevicePtr = 0xC0ED38 + // OfsDevice2 = 0x38A8 + // OfsDevice3 = 0x0 + // OfsDevice4 = 0xA8 + // WorldEnables = 0xC7B2A4 + // LastHardwareAction= 0xCF0BC8 - // === Runtime global patches (applied unconditionally for all image variants) === - - // Warden SYSTEM_INFO chain + // === Warden SYSTEM_INFO chain (3-level pointer chain) === + // Stage 0: [0xCE897C] → fake warden struct base constexpr uint32_t WARDEN_MODULE_PTR = 0xCE897C; constexpr uint32_t FAKE_WARDEN_BASE = 0xCE8000; writeLE32(WARDEN_MODULE_PTR, FAKE_WARDEN_BASE); + + // Stage 1: [FAKE_WARDEN_BASE + 0x228] → pointer to sysinfo container + constexpr uint32_t OFS_WARDEN_SYSINFO = 0x228; constexpr uint32_t FAKE_SYSINFO_CONTAINER = 0xCE8300; - writeLE32(FAKE_WARDEN_BASE + 0x228, FAKE_SYSINFO_CONTAINER); + writeLE32(FAKE_WARDEN_BASE + OFS_WARDEN_SYSINFO, FAKE_SYSINFO_CONTAINER); - // Write SYSINFO pointer at many offsets from FAKE_WARDEN_BASE so the - // chain works regardless of which module-specific offset the server uses. - // MUST be done BEFORE writing the actual SYSTEM_INFO struct, because this - // loop's range (0xCE8200-0xCE8400) overlaps with the struct at 0xCE8308. - for (uint32_t off = 0x200; off <= 0x400; off += 4) { - uint32_t addr = FAKE_WARDEN_BASE + off; - if (addr >= imageBase_ && (addr - imageBase_) + 4 <= imageSize_) { - writeLE32(addr, FAKE_SYSINFO_CONTAINER); - } - } - - // Now write the actual WIN_SYSTEM_INFO struct AFTER the pointer fill loop, - // so it overwrites any values the loop placed in the 0xCE8308+ range. - uint32_t sysInfoAddr = FAKE_SYSINFO_CONTAINER + 0x08; + // Stage 2: [FAKE_SYSINFO_CONTAINER + 0x08] → 36-byte SYSTEM_INFO struct + constexpr uint32_t OFS_WARDEN_WIN_SYSINFO = 0x08; + uint32_t sysInfoAddr = FAKE_SYSINFO_CONTAINER + OFS_WARDEN_WIN_SYSINFO; // 0xCE8308 + // WIN_SYSTEM_INFO is 36 bytes (0x24): + // uint16 wProcessorArchitecture (must be 0 = x86) + // uint16 wReserved + // uint32 dwPageSize + // uint32 lpMinimumApplicationAddress + // uint32 lpMaximumApplicationAddress (MUST be non-zero!) + // uint32 dwActiveProcessorMask + // uint32 dwNumberOfProcessors + // uint32 dwProcessorType (must be 386, 486, or 586) + // uint32 dwAllocationGranularity + // uint16 wProcessorLevel + // uint16 wProcessorRevision #pragma pack(push, 1) struct { uint16_t wProcessorArchitecture; @@ -355,7 +195,19 @@ void WardenMemory::patchRuntimeGlobals() { uint32_t dwAllocationGranularity; uint16_t wProcessorLevel; uint16_t wProcessorRevision; - } sysInfo = {0, 0, 4096, 0x00010000, 0x7FFEFFFF, 0x0F, 4, 586, 65536, 6, 0x3A09}; + } sysInfo = { + 0, // x86 + 0, + 4096, // 4K page size + 0x00010000, // min app address + 0x7FFEFFFF, // max app address (CRITICAL: must be non-zero) + 0x0F, // 4 processors + 4, // 4 CPUs + 586, // Pentium + 65536, // 64K granularity + 6, // P6 family + 0x3A09 // revision + }; #pragma pack(pop) static_assert(sizeof(sysInfo) == 36, "SYSTEM_INFO must be 36 bytes"); uint32_t rva = sysInfoAddr - imageBase_; @@ -363,182 +215,52 @@ void WardenMemory::patchRuntimeGlobals() { std::memcpy(image_.data() + rva, &sysInfo, 36); } - // Fallback: if the pointer chain breaks and stage 3 reads from address - // 0x00000000 + 0x08 = 8, write valid SYSINFO at RVA 8 (PE DOS header area). - if (8 + 36 <= imageSize_) { - std::memcpy(image_.data() + 8, &sysInfo, 36); - } + LOG_INFO("WardenMemory: Patched SYSTEM_INFO chain: [0x", std::hex, + WARDEN_MODULE_PTR, "]→0x", FAKE_WARDEN_BASE, + " [0x", FAKE_WARDEN_BASE + OFS_WARDEN_SYSINFO, "]→0x", FAKE_SYSINFO_CONTAINER, + " SYSTEM_INFO@0x", sysInfoAddr, std::dec); - LOG_WARNING("WardenMemory: Patched SYSINFO chain @0x", std::hex, WARDEN_MODULE_PTR, std::dec); - - // EndScene chain - // VMaNGOS reads g_theGxDevicePtr → device, then device+0x1FC for API kind - // (0=OpenGL, 1=Direct3D). If Direct3D, follows device+0x38A8 → ptr → ptr+0xA8 → EndScene. - // We set API=1 (Direct3D) and provide the full pointer chain. + // === EndScene chain (4-level pointer chain) === + // Stage 1: [0xC0ED38] → fake D3D device constexpr uint32_t GX_DEVICE_PTR = 0xC0ED38; constexpr uint32_t FAKE_DEVICE = 0xCE8400; writeLE32(GX_DEVICE_PTR, FAKE_DEVICE); - writeLE32(FAKE_DEVICE + 0x1FC, 1); // API kind = Direct3D - // Set up the full EndScene pointer chain at the canonical offsets. - constexpr uint32_t FAKE_VTABLE1 = 0xCE8500; - constexpr uint32_t FAKE_VTABLE2 = 0xCE8600; - constexpr uint32_t FAKE_ENDSCENE = 0x00401000; // start of .text - writeLE32(FAKE_DEVICE + 0x38A8, FAKE_VTABLE1); - writeLE32(FAKE_VTABLE1, FAKE_VTABLE2); - writeLE32(FAKE_VTABLE2 + 0xA8, FAKE_ENDSCENE); - // The EndScene device+sOfsDevice2 offset may differ from 0x38A8 in Turtle WoW. - // Also set API=1 (Direct3D) at multiple offsets so the API kind check passes. - // Fill the entire fake device area with the vtable pointer for robustness. - for (uint32_t off = 0x3800; off <= 0x3A00; off += 4) { - uint32_t addr = FAKE_DEVICE + off; - if (addr >= imageBase_ && (addr - imageBase_) + 4 <= imageSize_) { - writeLE32(addr, FAKE_VTABLE1); - } - } - LOG_WARNING("WardenMemory: Patched EndScene chain @0x", std::hex, GX_DEVICE_PTR, std::dec); + // Stage 2: [FAKE_DEVICE + 0x38A8] → fake intermediate + constexpr uint32_t OFS_DEVICE2 = 0x38A8; + constexpr uint32_t FAKE_INTERMEDIATE = 0xCE8500; + writeLE32(FAKE_DEVICE + OFS_DEVICE2, FAKE_INTERMEDIATE); - // WorldEnables + // Stage 3: [FAKE_INTERMEDIATE + 0x0] → fake vtable + constexpr uint32_t OFS_DEVICE3 = 0x0; + constexpr uint32_t FAKE_VTABLE = 0xCE8600; + writeLE32(FAKE_INTERMEDIATE + OFS_DEVICE3, FAKE_VTABLE); + + // Stage 4: [FAKE_VTABLE + 0xA8] → address of "EndScene" function + // Point to a real .text address with normal code (not 0xE9/0xCC = not hooked) + constexpr uint32_t OFS_DEVICE4 = 0xA8; + constexpr uint32_t FAKE_ENDSCENE = 0x00401000; // Start of .text section + writeLE32(FAKE_VTABLE + OFS_DEVICE4, FAKE_ENDSCENE); + + LOG_INFO("WardenMemory: Patched EndScene chain: [0x", std::hex, + GX_DEVICE_PTR, "]→0x", FAKE_DEVICE, + " ... →EndScene@0x", FAKE_ENDSCENE, std::dec); + + // === WorldEnables (single value) === + // Required flags: TerrainDoodads|Terrain|MapObjects|MapObjectLighting|MapObjectTextures|Water + // Plus typical defaults (no Prohibited bits set) constexpr uint32_t WORLD_ENABLES = 0xC7B2A4; uint32_t enables = 0x1 | 0x2 | 0x10 | 0x20 | 0x40 | 0x100 | 0x200 | 0x400 | 0x800 | 0x8000 | 0x10000 | 0x100000 | 0x1000000 | 0x2000000 | 0x4000000 | 0x8000000 | 0x10000000; writeLE32(WORLD_ENABLES, enables); - LOG_WARNING("WardenMemory: Patched WorldEnables @0x", std::hex, WORLD_ENABLES, std::dec); + LOG_INFO("WardenMemory: Patched WorldEnables=0x", std::hex, enables, std::dec); - // LastHardwareAction + // === LastHardwareAction (tick count) === + // Must be <= currentTime from timing check. Set to a plausible value. constexpr uint32_t LAST_HARDWARE_ACTION = 0xCF0BC8; - writeLE32(LAST_HARDWARE_ACTION, 60000); - LOG_WARNING("WardenMemory: Patched LastHardwareAction @0x", std::hex, LAST_HARDWARE_ACTION, std::dec); -} - -void WardenMemory::patchTurtleWowBinary() { - // Apply TurtlePatcher byte patches to make our PE image match a real Turtle WoW client. - // These patches are applied at file offsets which equal RVAs for this PE. - // Source: TurtlePatcher/Main.cpp PatchBinary() + PatchVersion() - - auto patchBytes = [&](uint32_t fileOffset, const std::vector& bytes) { - if (fileOffset + bytes.size() > imageSize_) { - LOG_WARNING("WardenMemory: Turtle patch at 0x", std::hex, fileOffset, - " exceeds image size, skipping"); - return; - } - std::memcpy(image_.data() + fileOffset, bytes.data(), bytes.size()); - }; - - auto patchString = [&](uint32_t fileOffset, const char* str) { - size_t len = std::strlen(str) + 1; // include null terminator - if (fileOffset + len > imageSize_) return; - std::memcpy(image_.data() + fileOffset, str, len); - }; - - // --- PatchBinary() patches --- - - // Patches 1-4: Unknown purpose code patches in .text - patchBytes(0x2F113A, {0xEB, 0x19}); - patchBytes(0x2F1158, {0x03}); - patchBytes(0x2F11A7, {0x03}); - patchBytes(0x2F11F0, {0xEB, 0xB2}); - - // PvP rank check removal (6x NOP) - patchBytes(0x2093B0, {0x90, 0x90, 0x90, 0x90, 0x90, 0x90}); - - // Dwarf mage hackfix removal - patchBytes(0x0706E5, {0xFE}); - patchBytes(0x0706EB, {0xFE}); - patchBytes(0x07075D, {0xFE}); - patchBytes(0x070763, {0xFE}); - - // Emote sound race ID checks (High Elf support) - patchBytes(0x059289, {0x40}); - patchBytes(0x057C81, {0x40}); - - // Nameplate distance (41 yards) - patchBytes(0x40C448, {0x00, 0x00, 0x24, 0x42}); - - // Large address aware flag in PE header - patchBytes(0x000126, {0x2F, 0x01}); - - // Sound channel patches - patchBytes(0x05728C, {0x38, 0x5D, 0x83, 0x00}); // software channels - patchBytes(0x057250, {0x38, 0x5D, 0x83, 0x00}); // hardware channels - patchBytes(0x0572C8, {0x6C, 0x5C, 0x83, 0x00}); // memory cache - - // Sound in background (non-FoV build) - patchBytes(0x3A4869, {0x14}); - - // Hardcore chat patches - patchBytes(0x09B0B8, {0x5F}); - patchBytes(0x09B193, {0xE9, 0xA8, 0xAE, 0x86}); - patchBytes(0x09F7A5, {0x70, 0x53, 0x56, 0x33, 0xF6, 0xE9, 0x71, 0x68, 0x86, 0x00}); - patchBytes(0x09F864, {0x94}); - patchBytes(0x09F878, {0x0E}); - patchBytes(0x09F887, {0x90}); - patchBytes(0x11BAE1, {0x0C, 0x60, 0xD0}); - - // Hardcore chat code cave at 0x48E000 (85 bytes) - patchBytes(0x48E000, { - 0x48, 0x41, 0x52, 0x44, 0x43, 0x4F, 0x52, 0x45, 0x00, 0x00, 0x00, 0x00, 0x43, 0x48, 0x41, 0x54, - 0x5F, 0x4D, 0x53, 0x47, 0x5F, 0x48, 0x41, 0x52, 0x44, 0x43, 0x4F, 0x52, 0x45, 0x00, 0x00, 0x00, - 0x57, 0x8B, 0xDA, 0x8B, 0xF9, 0xC7, 0x45, 0x94, 0x00, 0x60, 0xD0, 0x00, 0xC7, 0x45, 0x90, 0x5E, - 0x00, 0x00, 0x00, 0xE9, 0x77, 0x97, 0x79, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x68, 0x08, 0x46, 0x84, 0x00, 0x83, 0x7D, 0xF0, 0x5E, 0x75, 0x05, 0xB9, 0x1F, 0x02, 0x00, 0x00, - 0xE9, 0x43, 0x51, 0x79, 0xFF - }); - - // Blue child moon patch - patchBytes(0x3E5B83, { - 0xC7, 0x05, 0xA4, 0x98, 0xCE, 0x00, 0xD4, 0xE2, 0xE7, 0xFF, 0xC2, 0x04, 0x00 - }); - - // Blue child moon timer - patchBytes(0x2D2095, {0x00, 0x00, 0x80, 0x3F}); - - // SetUnit codecave jump - patchBytes(0x105E19, {0xE9, 0x02, 0x03, 0x80, 0x00}); - - // SetUnit main code cave at 0x48E060 (291 bytes) - patchBytes(0x48E060, { - 0x55, 0x89, 0xE5, 0x83, 0xEC, 0x10, 0x85, 0xD2, 0x53, 0x56, 0x57, 0x89, 0xCF, 0x0F, 0x84, 0xA2, - 0x00, 0x00, 0x00, 0x89, 0xD0, 0x85, 0xC0, 0x0F, 0x8C, 0x98, 0x00, 0x00, 0x00, 0x3B, 0x05, 0x94, - 0xDE, 0xC0, 0x00, 0x0F, 0x8F, 0x8C, 0x00, 0x00, 0x00, 0x8B, 0x0D, 0x90, 0xDE, 0xC0, 0x00, 0x8B, - 0x04, 0x81, 0x85, 0xC0, 0x89, 0x45, 0xF0, 0x74, 0x7C, 0x8B, 0x40, 0x04, 0x85, 0xC0, 0x7C, 0x75, - 0x3B, 0x05, 0x6C, 0xDE, 0xC0, 0x00, 0x7F, 0x6D, 0x8B, 0x15, 0x68, 0xDE, 0xC0, 0x00, 0x8B, 0x1C, - 0x82, 0x85, 0xDB, 0x74, 0x60, 0x8B, 0x43, 0x08, 0x6A, 0x00, 0x50, 0x89, 0xF9, 0xE8, 0xFE, 0x6E, - 0xA6, 0xFF, 0x89, 0xC1, 0xE8, 0x87, 0x12, 0xA0, 0xFF, 0x89, 0xC6, 0x85, 0xF6, 0x74, 0x46, 0x8B, - 0x55, 0xF0, 0x53, 0x89, 0xF1, 0xE8, 0xD6, 0x36, 0x77, 0xFF, 0x8B, 0x17, 0x56, 0x89, 0xF9, 0xFF, - 0x92, 0x90, 0x00, 0x00, 0x00, 0x89, 0xF8, 0x99, 0x52, 0x50, 0x68, 0xA0, 0x62, 0x50, 0x00, 0x89, - 0xF1, 0xE8, 0xBA, 0xBA, 0xA0, 0xFF, 0x6A, 0x01, 0x6A, 0x01, 0x68, 0x00, 0x00, 0x80, 0x3F, 0x6A, - 0x00, 0x6A, 0xFF, 0x6A, 0x00, 0x6A, 0xFF, 0x89, 0xF1, 0xE8, 0x92, 0xC0, 0xA0, 0xFF, 0x89, 0xF1, - 0xE8, 0x8B, 0xA2, 0xA0, 0xFF, 0x5F, 0x5E, 0x5B, 0x89, 0xEC, 0x5D, 0xC3, 0x90, 0x90, 0x90, 0x90, - 0xBA, 0x02, 0x00, 0x00, 0x00, 0x89, 0xF1, 0xE8, 0xD4, 0xD2, 0x9E, 0xFF, 0x83, 0xF8, 0x03, 0x75, - 0x43, 0xBA, 0x02, 0x00, 0x00, 0x00, 0x89, 0xF1, 0xE8, 0xE3, 0xD4, 0x9E, 0xFF, 0xE8, 0x6E, 0x41, - 0x70, 0xFF, 0x56, 0x8B, 0xB7, 0xD4, 0x00, 0x00, 0x00, 0x31, 0xD2, 0x39, 0xD6, 0x89, 0x97, 0xE0, - 0x03, 0x00, 0x00, 0x89, 0x97, 0xE4, 0x03, 0x00, 0x00, 0x89, 0x97, 0xF0, 0x03, 0x00, 0x00, 0x5E, - 0x0F, 0x84, 0xD3, 0xFC, 0x7F, 0xFF, 0x89, 0xC2, 0x89, 0xF9, 0xE8, 0xF1, 0xFE, 0xFF, 0xFF, 0xE9, - 0xC5, 0xFC, 0x7F, 0xFF, 0xBA, 0x02, 0x00, 0x00, 0x00, 0xE9, 0xA0, 0xFC, 0x7F, 0xFF, 0x90, 0x90, - 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 - }); - - // --- PatchVersion() patches --- - - // Net version: build 7199 (0x1C1F LE) - patchBytes(0x1B2122, {0x1F, 0x1C}); - - // Visual version string - patchString(0x437C04, "1.17.2"); - - // Visual build string - patchString(0x437BFC, "7199"); - - // Build date string - patchString(0x434798, "May 20 2024"); - - // Website filters - patchString(0x45CCD8, "*.turtle-wow.org"); - patchString(0x45CC9C, "*.discord.gg"); - - LOG_WARNING("WardenMemory: Applied TurtlePatcher binary patches (build 7199)"); + writeLE32(LAST_HARDWARE_ACTION, 60000); // 1 minute + LOG_INFO("WardenMemory: Patched LastHardwareAction=60000ms"); } bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) const { @@ -578,12 +300,9 @@ bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) cons return true; } -uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build, bool isTurtle) { +uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build) { switch (build) { - case 5875: - // Turtle WoW uses a custom WoW.exe with different code bytes. - // Their warden_scans DB expects bytes from this custom exe. - return isTurtle ? 0x00906000 : 0x009FD000; + case 5875: return 0x00906000; // Classic 1.12.1 default: return 0; // Unknown — accept any } } @@ -601,7 +320,6 @@ std::string WardenMemory::findWowExe(uint16_t build) const { } } candidateDirs.push_back("Data/misc"); - candidateDirs.push_back("Data/expansions/turtle/overlay/misc"); const char* candidateExes[] = { "WoW.exe", "TurtleWoW.exe", "Wow.exe" }; @@ -619,7 +337,7 @@ std::string WardenMemory::findWowExe(uint16_t build) const { } // If we know the expected imageSize for this build, try to find a matching PE - uint32_t expectedSize = expectedImageSizeForBuild(build, isTurtle_); + uint32_t expectedSize = expectedImageSizeForBuild(build); if (expectedSize != 0 && allPaths.size() > 1) { for (const auto& path : allPaths) { std::ifstream f(path, std::ios::binary); @@ -643,29 +361,17 @@ std::string WardenMemory::findWowExe(uint16_t build) const { } } - // Fallback: prefer the largest PE file (modified clients like Turtle WoW are - // larger than vanilla, and Warden checks target the actual running client). - std::string bestPath; - uintmax_t bestSize = 0; - for (const auto& path : allPaths) { - std::error_code ec; - auto sz = std::filesystem::file_size(path, ec); - if (!ec && sz > bestSize) { - bestSize = sz; - bestPath = path; - } - } - return bestPath.empty() && !allPaths.empty() ? allPaths[0] : bestPath; + // Fallback: return first available + return allPaths.empty() ? "" : allPaths[0]; } -bool WardenMemory::load(uint16_t build, bool isTurtle) { - isTurtle_ = isTurtle; +bool WardenMemory::load(uint16_t build) { std::string path = findWowExe(build); if (path.empty()) { LOG_WARNING("WardenMemory: WoW.exe not found in any candidate directory"); return false; } - LOG_WARNING("WardenMemory: Loading PE image: ", path, " (build=", build, ")"); + LOG_INFO("WardenMemory: Found ", path); return loadFromFile(path); } @@ -690,230 +396,11 @@ bool WardenMemory::loadFromFile(const std::string& exePath) { initKuserSharedData(); patchRuntimeGlobals(); - if (isTurtle_ && imageSize_ != 0x00906000) { - // Only apply TurtlePatcher patches if we loaded the vanilla exe. - // The real Turtle WoW.exe (imageSize=0x906000) already has these bytes. - patchTurtleWowBinary(); - LOG_WARNING("WardenMemory: Applied Turtle patches to vanilla PE (imageSize=0x", std::hex, imageSize_, std::dec, ")"); - } else if (isTurtle_) { - LOG_WARNING("WardenMemory: Loaded native Turtle PE — skipping patches"); - } loaded_ = true; LOG_INFO("WardenMemory: Loaded PE image (", fileData.size(), " bytes on disk, ", imageSize_, " bytes virtual)"); - - // Verify all known warden_scans MEM_CHECK entries against our PE image. - // This checks the exact bytes the server will memcmp against. - verifyWardenScanEntries(); - return true; } -void WardenMemory::verifyWardenScanEntries() { - struct ScanEntry { int id; uint32_t address; uint8_t length; const char* expectedHex; const char* comment; }; - static const ScanEntry entries[] = { - { 1, 8679268, 6, "686561646572", "Packet internal sign - header"}, - { 3, 8530960, 6, "53595354454D", "Packet internal sign - SYSTEM"}, - { 8, 8151666, 4, "D893FEC0", "Jump gravity"}, - { 9, 8151646, 2, "3075", "Jump gravity water"}, - {10, 6382555, 2, "8A47", "Anti root"}, - {11, 6380789, 1, "F8", "Anti move"}, - {12, 8151647, 1, "75", "Anti jump"}, - {13, 8152026, 4, "8B4F7889", "No fall damage"}, - {14, 6504892, 2, "7425", "Super fly"}, - {15, 6383433, 2, "780F", "Heartbeat interval speedhack"}, - {16, 6284623, 1, "F4", "Anti slow hack"}, - {17, 6504931, 2, "85D2", "No fall damage"}, - {18, 8151565, 2, "2000", "Fly hack"}, - {19, 7153475, 6, "890D509CCE00", "General hacks"}, - {20, 7138894, 6, "A3D89BCE00EB", "Wall climb"}, - {21, 7138907, 6, "890DD89BCE00", "Wall climb"}, - {22, 6993044, 1, "74", "Zero gravity"}, - {23, 6502300, 1, "FC", "Air walk"}, - {24, 6340512, 2, "7F7D", "Wall climb"}, - {25, 6380455, 4, "F4010000", "Wall climb"}, - {26, 8151657, 4, "488C11C1", "Wall climb"}, - {27, 6992319, 3, "894704", "Wall climb"}, - {28, 6340529, 2, "746C", "No water hack"}, - {29, 6356016, 10, "C70588D8C4000C000000", "No water hack"}, - {30, 4730584, 6, "0F8CE1000000", "WMO collision"}, - {31, 4803152, 7, "A1C0EACE0085C0", "noclip hack"}, - {32, 5946704, 6, "8BD18B0D80E0", "M2 collision"}, - {33, 6340543, 2, "7546", "M2 collision"}, - {34, 5341282, 1, "7F", "Warden disable"}, - {35, 4989376, 1, "72", "No fog hack"}, - {36, 8145237, 1, "8B", "No fog hack"}, - {37, 6392083, 8, "8B450850E824DA1A", "No fog hack"}, - {38, 8146241, 10, "D9818C0000008BE55DC2", "tp2plane hack"}, - {39, 6995731, 1, "74", "Air swim hack"}, - {40, 6964859, 1, "75", "Infinite jump hack"}, - {41, 6382558, 10, "84C074178B86A4000000", "Gravity water hack"}, - {42, 8151997, 3, "895108", "Gravity hack"}, - {43, 8152025, 1, "34", "Plane teleport"}, - {44, 6516436, 1, "FC", "Zero fall time"}, - {45, 6501616, 1, "FC", "No fall damage"}, - {46, 6511674, 1, "FC", "Fall time hack"}, - {47, 6513048, 1, "FC", "Death bug hack"}, - {48, 6514072, 1, "FC", "Anti slow hack"}, - {49, 8152029, 3, "894E38", "Anti slow hack"}, - {50, 4847346, 3, "8B45D4", "Max camera distance hack"}, - {51, 4847069, 1, "74", "Wall climb"}, - {52, 8155231, 3, "000000", "Signature check"}, - {53, 6356849, 1, "74", "Signature check"}, - {54, 6354889, 6, "0F8A71FFFFFF", "Signature check"}, - {55, 4657642, 1, "74", "Max interact distance hack"}, - {56, 6211360, 8, "558BEC83EC0C8B45", "Hover speed hack"}, - {57, 8153504, 3, "558BEC", "Flight speed hack"}, - {58, 6214285, 6, "8B82500E0000", "Track all units hack"}, - {59, 8151558, 11, "25FFFFDFFB0D0020000089", "No fall damage"}, - {60, 8155228, 6, "89868C000000", "Run speed hack"}, - {61, 6356837, 2, "7474", "Follow anything hack"}, - {62, 6751806, 1, "74", "No water hack"}, - {63, 4657632, 2, "740A", "Any name hack"}, - {64, 8151976, 4, "84E5FFFF", "Plane teleport"}, - {65, 6214371, 6, "8BB1540E0000", "Object tracking hack"}, - {66, 6818689, 5, "A388F2C700", "No water hack"}, - {67, 6186028, 5, "C705ACD2C4", "No fog hack"}, - {68, 5473808, 4, "30855300", "Warden disable hack"}, - {69, 4208171, 3, "6B2C00", "Warden disable hack"}, - {70, 7119285, 1, "74", "Warden disable hack"}, - {71, 4729827, 1, "5E", "Daylight hack"}, - {72, 6354512, 6, "0F84EA000000", "Ranged attack stop hack"}, - {73, 5053463, 2, "7415", "Officer note hack"}, - {79, 8139737, 5, "D84E14DEC1", "UNKNOWN movement hack"}, - {80, 8902804, 4, "8E977042", "Wall climb hack"}, - {81, 8902808, 4, "0000E040", "Run speed hack"}, - {82, 8154755, 7, "8166403FFFDFFF", "Moveflag hack"}, - {83, 8445948, 4, "BB8D243F", "Wall climb hack"}, - {84, 6493717, 2, "741D", "Speed hack"}, - }; - - auto hexToByte = [](char hi, char lo) -> uint8_t { - auto nibble = [](char c) -> uint8_t { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return 10 + c - 'A'; - if (c >= 'a' && c <= 'f') return 10 + c - 'a'; - return 0; - }; - return (nibble(hi) << 4) | nibble(lo); - }; - - int mismatches = 0; - int patched = 0; - for (const auto& e : entries) { - std::string hexStr(e.expectedHex); - std::vector expected; - for (size_t i = 0; i + 1 < hexStr.size(); i += 2) - expected.push_back(hexToByte(hexStr[i], hexStr[i+1])); - - std::vector actual(e.length, 0); - bool ok = readMemory(e.address, e.length, actual.data()); - - if (!ok || actual != expected) { - mismatches++; - - // In Turtle mode, write the expected bytes into the PE image so - // MEM_CHECK responses return what the server expects. - if (isTurtle_ && e.address >= imageBase_) { - uint32_t offset = e.address - imageBase_; - if (offset + expected.size() <= imageSize_) { - std::memcpy(image_.data() + offset, expected.data(), expected.size()); - patched++; - } - } - } - } - - if (mismatches == 0) { - LOG_WARNING("WardenScan: All ", sizeof(entries)/sizeof(entries[0]), - " DB scan entries MATCH PE image"); - } else if (patched > 0) { - LOG_WARNING("WardenScan: Patched ", patched, "/", mismatches, - " mismatched scan entries into PE image"); - } else { - LOG_WARNING("WardenScan: ", mismatches, " / ", sizeof(entries)/sizeof(entries[0]), - " DB scan entries MISMATCH"); - } -} - -bool WardenMemory::searchCodePattern(const uint8_t seed[4], const uint8_t expectedHash[20], - uint8_t patternLen, bool imageOnly) const { - if (!loaded_ || patternLen == 0 || patternLen > 255) return false; - - // Build cache key from all inputs: seed(4) + hash(20) + patLen(1) + imageOnly(1) - std::string cacheKey(26, '\0'); - std::memcpy(&cacheKey[0], seed, 4); - std::memcpy(&cacheKey[4], expectedHash, 20); - cacheKey[24] = patternLen; - cacheKey[25] = imageOnly ? 1 : 0; - - auto cacheIt = codePatternCache_.find(cacheKey); - if (cacheIt != codePatternCache_.end()) { - LOG_WARNING("WardenMemory: Code pattern cache HIT → ", - cacheIt->second ? "found" : "not found"); - return cacheIt->second; - } - - // FIND_MEM_IMAGE_CODE_BY_HASH (imageOnly=true) searches ALL sections of - // the PE image — not just executable ones. The original Warden module - // walks every PE section when scanning the WoW.exe memory image. - // FIND_CODE_BY_HASH (imageOnly=false) searches all process memory; since - // we only have the PE image, both cases search the full image. - struct Range { size_t start; size_t end; }; - std::vector ranges; - - if (imageOnly && image_.size() >= 64) { - // Collect ALL PE sections (not just executable ones) - uint32_t peOffset = image_[0x3C] | (uint32_t(image_[0x3D]) << 8) - | (uint32_t(image_[0x3E]) << 16) | (uint32_t(image_[0x3F]) << 24); - if (peOffset + 4 + 20 <= image_.size()) { - uint16_t numSections = image_[peOffset+4+2] | (uint16_t(image_[peOffset+4+3]) << 8); - uint16_t optHeaderSize = image_[peOffset+4+16] | (uint16_t(image_[peOffset+4+17]) << 8); - size_t secTable = peOffset + 4 + 20 + optHeaderSize; - for (uint16_t i = 0; i < numSections; i++) { - size_t secOfs = secTable + i * 40; - if (secOfs + 40 > image_.size()) break; - uint32_t va = image_[secOfs+12] | (uint32_t(image_[secOfs+13]) << 8) - | (uint32_t(image_[secOfs+14]) << 16) | (uint32_t(image_[secOfs+15]) << 24); - uint32_t vsize = image_[secOfs+8] | (uint32_t(image_[secOfs+9]) << 8) - | (uint32_t(image_[secOfs+10]) << 16) | (uint32_t(image_[secOfs+11]) << 24); - size_t rEnd = std::min(static_cast(va + vsize), static_cast(imageSize_)); - if (va + patternLen <= rEnd) - ranges.push_back({va, rEnd}); - } - } - } - - if (ranges.empty()) { - // Fallback: search entire image - if (patternLen <= imageSize_) - ranges.push_back({0, imageSize_}); - } - - size_t totalPositions = 0; - for (const auto& r : ranges) { - size_t positions = r.end - r.start - patternLen + 1; - for (size_t i = 0; i < positions; i++) { - uint8_t hmacOut[20]; - unsigned int hmacLen = 0; - HMAC(EVP_sha1(), seed, 4, - image_.data() + r.start + i, patternLen, - hmacOut, &hmacLen); - if (hmacLen == 20 && std::memcmp(hmacOut, expectedHash, 20) == 0) { - LOG_WARNING("WardenMemory: Code pattern found at RVA 0x", std::hex, - r.start + i, std::dec, " (searched ", totalPositions + i + 1, " positions)"); - codePatternCache_[cacheKey] = true; - return true; - } - } - totalPositions += positions; - } - - LOG_WARNING("WardenMemory: Code pattern NOT found after ", totalPositions, " positions in ", - ranges.size(), " section(s)"); - codePatternCache_[cacheKey] = false; - return false; -} - } // namespace game } // namespace wowee diff --git a/src/network/world_socket.cpp b/src/network/world_socket.cpp index 271fc0e9..40cf2251 100644 --- a/src/network/world_socket.cpp +++ b/src/network/world_socket.cpp @@ -281,19 +281,22 @@ void WorldSocket::recordRecentPacket(bool outbound, uint16_t opcode, uint16_t pa } void WorldSocket::dumpRecentPacketHistoryLocked(const char* reason, size_t bufferedBytes) { + static const bool closeTraceEnabled = envFlagEnabled(kCloseTraceEnv, false); + if (!closeTraceEnabled) return; + if (recentPacketHistory_.empty()) { - LOG_WARNING("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes, + LOG_DEBUG("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes, " no recent packet history"); return; } const auto lastWhen = recentPacketHistory_.back().when; - LOG_WARNING("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes, + LOG_DEBUG("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes, " recentPackets=", recentPacketHistory_.size()); for (const auto& entry : recentPacketHistory_) { const auto ageMs = std::chrono::duration_cast( lastWhen - entry.when).count(); - LOG_WARNING("WS CLOSE TRACE ", entry.outbound ? "TX" : "RX", + LOG_DEBUG("WS CLOSE TRACE ", entry.outbound ? "TX" : "RX", " -", ageMs, "ms opcode=0x", std::hex, entry.opcode, std::dec, " logical=", opcodeNameForTrace(entry.opcode), @@ -608,7 +611,7 @@ void WorldSocket::pumpNetworkIO() { if (sawClose) { dumpRecentPacketHistoryLocked("peer_closed", bufferedBytes()); - LOG_WARNING("World server connection closed by peer (receivedAny=", receivedAny, + LOG_INFO("World server connection closed (receivedAny=", receivedAny, " buffered=", bufferedBytes(), ")"); closeSocketNoJoin(); return; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index f7f07e42..fdf0432b 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -2899,9 +2899,18 @@ void Renderer::update(float deltaTime) { } weather->setEnabled(true); - // Lightning flash disabled + // Enable lightning during storms (wType==3) and heavy rain if (lightning) { - lightning->setEnabled(false); + uint32_t wType2 = gh->getWeatherType(); + float wInt2 = gh->getWeatherIntensity(); + bool stormActive = (wType2 == 3 && wInt2 > 0.1f) + || (wType2 == 1 && wInt2 > 0.7f); + lightning->setEnabled(stormActive); + if (stormActive) { + // Scale intensity: storm at full, heavy rain proportionally + float lIntensity = (wType2 == 3) ? wInt2 : (wInt2 - 0.7f) / 0.3f; + lightning->setIntensity(lIntensity); + } } } else if (weather) { // No game handler (single-player without network) — zone weather only