mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Compare commits
4 commits
f0a515ff9c
...
ad511dad5e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad511dad5e | ||
|
|
e3c2269b16 | ||
|
|
6fd32ecdc6 | ||
|
|
a3279ea1ad |
8 changed files with 1073 additions and 255 deletions
|
|
@ -22,6 +22,7 @@
|
|||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
namespace wowee::game {
|
||||
class TransportManager;
|
||||
|
|
@ -3144,6 +3145,14 @@ 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<std::vector<uint8_t>> 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;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
|
@ -18,8 +19,9 @@ 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. */
|
||||
bool load(uint16_t build = 0);
|
||||
* @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);
|
||||
|
||||
/** Load PE image from a specific file path. */
|
||||
bool loadFromFile(const std::string& exePath);
|
||||
|
|
@ -32,6 +34,21 @@ 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;
|
||||
|
|
@ -46,9 +63,15 @@ private:
|
|||
bool parsePE(const std::vector<uint8_t>& fileData);
|
||||
void initKuserSharedData();
|
||||
void patchRuntimeGlobals();
|
||||
void writeLE32(uint32_t va, uint32_t value);
|
||||
void patchTurtleWowBinary();
|
||||
void verifyWardenScanEntries();
|
||||
bool isTurtle_ = false;
|
||||
std::string findWowExe(uint16_t build) const;
|
||||
static uint32_t expectedImageSizeForBuild(uint16_t build);
|
||||
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<std::string, bool> codePatternCache_;
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ private:
|
|||
|
||||
// Descriptor pool for material sets
|
||||
VkDescriptorPool materialDescPool = VK_NULL_HANDLE;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 16384;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 65536;
|
||||
|
||||
// Loaded terrain chunks
|
||||
std::vector<TerrainChunkGPU> chunks;
|
||||
|
|
|
|||
|
|
@ -656,7 +656,7 @@ private:
|
|||
|
||||
// Descriptor pool for material sets
|
||||
VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 8192;
|
||||
static constexpr uint32_t MAX_MATERIAL_SETS = 32768;
|
||||
|
||||
// Texture cache (path -> VkTexture)
|
||||
struct TextureCacheEntry {
|
||||
|
|
|
|||
|
|
@ -836,6 +836,40 @@ 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<uint8_t> 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::milliseconds>(
|
||||
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()) {
|
||||
|
|
@ -8242,6 +8276,8 @@ 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) {
|
||||
|
|
@ -9322,16 +9358,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
}
|
||||
|
||||
if (match) {
|
||||
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);
|
||||
}
|
||||
LOG_WARNING("Warden: HASH_REQUEST — CR entry MATCHED, sending pre-computed reply");
|
||||
|
||||
// Send HASH_RESULT (opcode 0x04 + 20-byte reply)
|
||||
std::vector<uint8_t> resp;
|
||||
|
|
@ -9345,7 +9372,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
std::vector<uint8_t> newDecryptKey(match->serverKey, match->serverKey + 16);
|
||||
wardenCrypto_->replaceKeys(newEncryptKey, newDecryptKey);
|
||||
|
||||
LOG_DEBUG("Warden: Switched to CR key set");
|
||||
LOG_WARNING("Warden: Switched to CR key set");
|
||||
|
||||
wardenState_ = WardenState::WAIT_CHECKS;
|
||||
break;
|
||||
|
|
@ -9354,19 +9381,44 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Fallback: compute hash from loaded module ---
|
||||
LOG_WARNING("Warden: No CR match, computing hash from loaded module");
|
||||
// --- 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; }
|
||||
|
||||
if (!wardenLoadedModule_ || !wardenLoadedModule_->isLoaded()) {
|
||||
LOG_WARNING("Warden: No loaded module and no CR match — using raw module fallback hash");
|
||||
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)");
|
||||
|
||||
// Never skip HASH_RESULT: some realms disconnect quickly if this response is missing.
|
||||
std::vector<uint8_t> fallbackReply;
|
||||
if (!wardenModuleData_.empty()) {
|
||||
if (wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
||||
const uint8_t* moduleImage = static_cast<const uint8_t*>(wardenLoadedModule_->getModuleMemory());
|
||||
size_t moduleImageSize = wardenLoadedModule_->getModuleSize();
|
||||
if (moduleImage && moduleImageSize > 0) {
|
||||
std::vector<uint8_t> imageData(moduleImage, moduleImage + moduleImageSize);
|
||||
fallbackReply = auth::Crypto::sha1(imageData);
|
||||
}
|
||||
}
|
||||
if (fallbackReply.empty()) {
|
||||
if (!wardenModuleData_.empty())
|
||||
fallbackReply = auth::Crypto::sha1(wardenModuleData_);
|
||||
} else if (!wardenModuleHash_.empty()) {
|
||||
fallbackReply = auth::Crypto::sha1(wardenModuleHash_);
|
||||
} else {
|
||||
else
|
||||
fallbackReply.assign(20, 0);
|
||||
}
|
||||
|
||||
|
|
@ -9374,109 +9426,6 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
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<const uint8_t*>(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<uint8_t> fallbackReply =
|
||||
!wardenModuleData_.empty() ? auth::Crypto::sha1(wardenModuleData_) : std::vector<uint8_t>(20, 0);
|
||||
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> resp;
|
||||
resp.push_back(0x04);
|
||||
resp.insert(resp.end(), reply.begin(), reply.end());
|
||||
sendWardenResponse(resp);
|
||||
|
||||
applyWardenSeedRekey(seed);
|
||||
}
|
||||
|
||||
|
|
@ -9512,6 +9461,309 @@ 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<WardenMemory>();
|
||||
if (!wardenMemory_->load(static_cast<uint16_t>(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<uint8_t> {
|
||||
// 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<uint8_t> 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<uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
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<uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count());
|
||||
wardenMemory_->writeLE32(0xCF0BC8, now - 2000);
|
||||
}
|
||||
std::vector<uint8_t> 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<const uint8_t*>(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<uint8_t> 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<uint8_t> 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<uint16_t>(resultData.size());
|
||||
std::vector<uint8_t> 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 };
|
||||
|
|
@ -9654,16 +9906,14 @@ 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_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, "\"");
|
||||
}
|
||||
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 + "\""));
|
||||
|
||||
// Lazy-load WoW.exe PE image on first MEM_CHECK
|
||||
if (!wardenMemory_) {
|
||||
wardenMemory_ = std::make_unique<WardenMemory>();
|
||||
if (!wardenMemory_->load(static_cast<uint16_t>(build))) {
|
||||
if (!wardenMemory_->load(static_cast<uint16_t>(build), isActiveExpansion("turtle"))) {
|
||||
LOG_WARNING("Warden: Could not load WoW.exe for MEM_CHECK");
|
||||
}
|
||||
}
|
||||
|
|
@ -9672,6 +9922,21 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
std::vector<uint8_t> 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);}());
|
||||
|
|
@ -9721,7 +9986,16 @@ 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; // PatternFound
|
||||
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);}());
|
||||
}
|
||||
}
|
||||
LOG_DEBUG("Warden: PAGE_A request bytes=", consume,
|
||||
|
|
@ -9889,7 +10163,18 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("Warden: Parsed ", checkCount, " checks, result data size=", resultData.size());
|
||||
// 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, "]");
|
||||
}
|
||||
|
||||
// --- Compute checksum: XOR of 5 uint32s from SHA1(resultData) ---
|
||||
auto resultHash = auth::Crypto::sha1(resultData);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
|
@ -106,19 +108,181 @@ bool WardenMemory::parsePE(const std::vector<uint8_t>& 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);
|
||||
|
||||
// NtMajorVersion at offset 0x026C = 6 (Vista/7/8/10)
|
||||
uint32_t ntMajor = 6;
|
||||
std::memcpy(kuserData_ + 0x026C, &ntMajor, 4);
|
||||
// -------------------------------------------------------------------
|
||||
// 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.
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// NtMinorVersion at offset 0x0270 = 1 (Windows 7)
|
||||
uint32_t ntMinor = 1;
|
||||
std::memcpy(kuserData_ + 0x0270, &ntMinor, 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<uint32_t>(i) * 2, static_cast<uint16_t>(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
|
||||
}
|
||||
|
||||
void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
|
||||
|
|
@ -132,56 +296,52 @@ void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
|
|||
}
|
||||
|
||||
void WardenMemory::patchRuntimeGlobals() {
|
||||
// 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");
|
||||
if (imageBase_ != 0x00400000) {
|
||||
LOG_WARNING("WardenMemory: unexpected imageBase=0x", std::hex, imageBase_, std::dec,
|
||||
" — skipping runtime global patches");
|
||||
return;
|
||||
}
|
||||
|
||||
// Classic 1.12.1 (build 5875) runtime globals
|
||||
// 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.
|
||||
// VMaNGOS has TWO types of Warden scans that read these addresses:
|
||||
//
|
||||
// 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
|
||||
// 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.
|
||||
|
||||
// === Warden SYSTEM_INFO chain (3-level pointer chain) ===
|
||||
// Stage 0: [0xCE897C] → fake warden struct base
|
||||
// === Runtime global patches (applied unconditionally for all image variants) ===
|
||||
|
||||
// Warden SYSTEM_INFO chain
|
||||
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 + OFS_WARDEN_SYSINFO, FAKE_SYSINFO_CONTAINER);
|
||||
writeLE32(FAKE_WARDEN_BASE + 0x228, FAKE_SYSINFO_CONTAINER);
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
#pragma pack(push, 1)
|
||||
struct {
|
||||
uint16_t wProcessorArchitecture;
|
||||
|
|
@ -195,19 +355,7 @@ void WardenMemory::patchRuntimeGlobals() {
|
|||
uint32_t dwAllocationGranularity;
|
||||
uint16_t wProcessorLevel;
|
||||
uint16_t wProcessorRevision;
|
||||
} 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
|
||||
};
|
||||
} sysInfo = {0, 0, 4096, 0x00010000, 0x7FFEFFFF, 0x0F, 4, 586, 65536, 6, 0x3A09};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(sysInfo) == 36, "SYSTEM_INFO must be 36 bytes");
|
||||
uint32_t rva = sysInfoAddr - imageBase_;
|
||||
|
|
@ -215,52 +363,182 @@ void WardenMemory::patchRuntimeGlobals() {
|
|||
std::memcpy(image_.data() + rva, &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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// === EndScene chain (4-level pointer chain) ===
|
||||
// Stage 1: [0xC0ED38] → fake D3D device
|
||||
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.
|
||||
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);
|
||||
|
||||
// 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);
|
||||
// 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 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)
|
||||
// WorldEnables
|
||||
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_INFO("WardenMemory: Patched WorldEnables=0x", std::hex, enables, std::dec);
|
||||
LOG_WARNING("WardenMemory: Patched WorldEnables @0x", std::hex, WORLD_ENABLES, std::dec);
|
||||
|
||||
// === LastHardwareAction (tick count) ===
|
||||
// Must be <= currentTime from timing check. Set to a plausible value.
|
||||
// LastHardwareAction
|
||||
constexpr uint32_t LAST_HARDWARE_ACTION = 0xCF0BC8;
|
||||
writeLE32(LAST_HARDWARE_ACTION, 60000); // 1 minute
|
||||
LOG_INFO("WardenMemory: Patched LastHardwareAction=60000ms");
|
||||
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<uint8_t>& 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)");
|
||||
}
|
||||
|
||||
bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) const {
|
||||
|
|
@ -300,9 +578,12 @@ bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) cons
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build) {
|
||||
uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build, bool isTurtle) {
|
||||
switch (build) {
|
||||
case 5875: return 0x00906000; // Classic 1.12.1
|
||||
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;
|
||||
default: return 0; // Unknown — accept any
|
||||
}
|
||||
}
|
||||
|
|
@ -320,6 +601,7 @@ 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" };
|
||||
|
||||
|
|
@ -337,7 +619,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);
|
||||
uint32_t expectedSize = expectedImageSizeForBuild(build, isTurtle_);
|
||||
if (expectedSize != 0 && allPaths.size() > 1) {
|
||||
for (const auto& path : allPaths) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
|
|
@ -361,17 +643,29 @@ std::string WardenMemory::findWowExe(uint16_t build) const {
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback: return first available
|
||||
return allPaths.empty() ? "" : allPaths[0];
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool WardenMemory::load(uint16_t build) {
|
||||
bool WardenMemory::load(uint16_t build, bool isTurtle) {
|
||||
isTurtle_ = isTurtle;
|
||||
std::string path = findWowExe(build);
|
||||
if (path.empty()) {
|
||||
LOG_WARNING("WardenMemory: WoW.exe not found in any candidate directory");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("WardenMemory: Found ", path);
|
||||
LOG_WARNING("WardenMemory: Loading PE image: ", path, " (build=", build, ")");
|
||||
return loadFromFile(path);
|
||||
}
|
||||
|
||||
|
|
@ -396,11 +690,230 @@ 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<uint8_t> expected;
|
||||
for (size_t i = 0; i + 1 < hexStr.size(); i += 2)
|
||||
expected.push_back(hexToByte(hexStr[i], hexStr[i+1]));
|
||||
|
||||
std::vector<uint8_t> 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<Range> 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<size_t>(va + vsize), static_cast<size_t>(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
|
||||
|
|
|
|||
|
|
@ -281,22 +281,19 @@ 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_DEBUG("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes,
|
||||
LOG_WARNING("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes,
|
||||
" no recent packet history");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto lastWhen = recentPacketHistory_.back().when;
|
||||
LOG_DEBUG("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes,
|
||||
LOG_WARNING("WS CLOSE TRACE reason='", reason, "' buffered=", bufferedBytes,
|
||||
" recentPackets=", recentPacketHistory_.size());
|
||||
for (const auto& entry : recentPacketHistory_) {
|
||||
const auto ageMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
lastWhen - entry.when).count();
|
||||
LOG_DEBUG("WS CLOSE TRACE ", entry.outbound ? "TX" : "RX",
|
||||
LOG_WARNING("WS CLOSE TRACE ", entry.outbound ? "TX" : "RX",
|
||||
" -", ageMs, "ms opcode=0x",
|
||||
std::hex, entry.opcode, std::dec,
|
||||
" logical=", opcodeNameForTrace(entry.opcode),
|
||||
|
|
@ -611,7 +608,7 @@ void WorldSocket::pumpNetworkIO() {
|
|||
|
||||
if (sawClose) {
|
||||
dumpRecentPacketHistoryLocked("peer_closed", bufferedBytes());
|
||||
LOG_INFO("World server connection closed (receivedAny=", receivedAny,
|
||||
LOG_WARNING("World server connection closed by peer (receivedAny=", receivedAny,
|
||||
" buffered=", bufferedBytes(), ")");
|
||||
closeSocketNoJoin();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2899,18 +2899,9 @@ void Renderer::update(float deltaTime) {
|
|||
}
|
||||
weather->setEnabled(true);
|
||||
|
||||
// Enable lightning during storms (wType==3) and heavy rain
|
||||
// Lightning flash disabled
|
||||
if (lightning) {
|
||||
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);
|
||||
}
|
||||
lightning->setEnabled(false);
|
||||
}
|
||||
} else if (weather) {
|
||||
// No game handler (single-player without network) — zone weather only
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue