mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: async Warden PAGE_A/PAGE_B checks to prevent main-loop stalls
Move 5-second brute-force HMAC-SHA1 code pattern searches to a background thread via std::async. The main loop now detects PAGE_A/B checks, launches the response builder async, and drains the result in update() — encrypting and sending on the main thread to keep wardenCrypto_ RC4 state thread-safe. Also adds Turtle WoW PE binary support (isTurtle flag, dedicated exe search, runtime patches), searchCodePattern with result caching, writeLE32 public API, and Warden scan entry verification.
This commit is contained in:
parent
f0a515ff9c
commit
a3279ea1ad
6 changed files with 890 additions and 116 deletions
|
|
@ -22,6 +22,7 @@
|
|||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
||||
namespace wowee::game {
|
||||
class TransportManager;
|
||||
|
|
@ -3144,6 +3145,10 @@ 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;
|
||||
|
||||
// ---- 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
|
||||
|
|
|
|||
|
|
@ -836,6 +836,26 @@ 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 server-side disconnect (socket closed during update)
|
||||
if (socket && !socket->isConnected() && state != WorldState::DISCONNECTED) {
|
||||
if (pendingIncomingPackets_.empty() && pendingUpdateObjectWork_.empty()) {
|
||||
|
|
@ -9512,6 +9532,281 @@ 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 {
|
||||
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;
|
||||
if (isKnownWantedCodeScan(seed, sha1, off, patLen)) {
|
||||
found = true;
|
||||
} else if (wardenMemory_ && wardenMemory_->isLoaded() && patLen > 0) {
|
||||
found = wardenMemory_->searchCodePattern(seed, sha1, patLen, isImageOnly);
|
||||
if (!found && wardenLoadedModule_ && wardenLoadedModule_->isLoaded()) {
|
||||
const auto& md = wardenLoadedModule_->getDecompressedData();
|
||||
if (md.size() >= patLen) {
|
||||
for (size_t i = 0; i < md.size() - patLen + 1; i++) {
|
||||
uint8_t h[20]; unsigned int hl = 0;
|
||||
HMAC(EVP_sha1(), seed, 4, md.data()+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");
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -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,136 @@ 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)
|
||||
// Populate KUSER_SHARED_DATA with realistic Windows 7 SP1 values.
|
||||
// Warden reads this in 238-byte chunks for OS fingerprinting.
|
||||
|
||||
// 0x0000: TickCountLowDeprecated (uint32) — non-zero uptime ticks
|
||||
uint32_t tickCountLow = 0x003F4A00; // ~70 minutes uptime
|
||||
std::memcpy(kuserData_ + 0x0000, &tickCountLow, 4);
|
||||
|
||||
// 0x0004: TickCountMultiplier (uint32) — standard value
|
||||
uint32_t tickMult = 0x0FA00000;
|
||||
std::memcpy(kuserData_ + 0x0004, &tickMult, 4);
|
||||
|
||||
// 0x0008: InterruptTime (KSYSTEM_TIME = LowPart(4) + High1Time(4) + High2Time(4))
|
||||
uint32_t intTimeLow = 0x6B49D200; // plausible 100ns interrupt time
|
||||
uint32_t intTimeHigh = 0x00000029;
|
||||
std::memcpy(kuserData_ + 0x0008, &intTimeLow, 4);
|
||||
std::memcpy(kuserData_ + 0x000C, &intTimeHigh, 4);
|
||||
std::memcpy(kuserData_ + 0x0010, &intTimeHigh, 4);
|
||||
|
||||
// 0x0014: SystemTime (KSYSTEM_TIME) — ~2024 epoch in Windows FILETIME
|
||||
uint32_t sysTimeLow = 0xA0B71B00;
|
||||
uint32_t sysTimeHigh = 0x01DA5E80;
|
||||
std::memcpy(kuserData_ + 0x0014, &sysTimeLow, 4);
|
||||
std::memcpy(kuserData_ + 0x0018, &sysTimeHigh, 4);
|
||||
std::memcpy(kuserData_ + 0x001C, &sysTimeHigh, 4);
|
||||
|
||||
// 0x0020: TimeZoneBias (KSYSTEM_TIME) — 0 for UTC
|
||||
// Leave as zeros (UTC timezone)
|
||||
|
||||
// 0x002C: ImageNumberLow (uint16) = 0x014C (IMAGE_FILE_MACHINE_I386)
|
||||
uint16_t imageNumLow = 0x014C;
|
||||
std::memcpy(kuserData_ + 0x002C, &imageNumLow, 2);
|
||||
|
||||
// 0x002E: ImageNumberHigh (uint16) = 0x014C
|
||||
uint16_t imageNumHigh = 0x014C;
|
||||
std::memcpy(kuserData_ + 0x002E, &imageNumHigh, 2);
|
||||
|
||||
// 0x0030: NtSystemRoot (wchar_t[260]) = L"C:\\WINDOWS"
|
||||
const wchar_t* sysRoot = L"C:\\WINDOWS";
|
||||
size_t rootLen = 10; // chars including null
|
||||
for (size_t i = 0; i < rootLen; i++) {
|
||||
uint16_t wc = static_cast<uint16_t>(sysRoot[i]);
|
||||
std::memcpy(kuserData_ + 0x0030 + i * 2, &wc, 2);
|
||||
}
|
||||
|
||||
// 0x0238: MaxStackTraceDepth (uint32)
|
||||
uint32_t maxStack = 0;
|
||||
std::memcpy(kuserData_ + 0x0238, &maxStack, 4);
|
||||
|
||||
// 0x023C: CryptoExponent (uint32) — typical value
|
||||
uint32_t cryptoExp = 0x00010001; // 65537
|
||||
std::memcpy(kuserData_ + 0x023C, &cryptoExp, 4);
|
||||
|
||||
// 0x0240: TimeZoneId (uint32) = 0 (TIME_ZONE_ID_UNKNOWN)
|
||||
uint32_t tzId = 0;
|
||||
std::memcpy(kuserData_ + 0x0240, &tzId, 4);
|
||||
|
||||
// 0x0248: LargePageMinimum (uint32)
|
||||
uint32_t largePage = 0x00200000; // 2MB
|
||||
std::memcpy(kuserData_ + 0x0248, &largePage, 4);
|
||||
|
||||
// 0x0264: ActiveConsoleId (uint32) = 0
|
||||
uint32_t consoleId = 0;
|
||||
std::memcpy(kuserData_ + 0x0264, &consoleId, 4);
|
||||
|
||||
// 0x0268: DismountCount (uint32) = 0
|
||||
// already zero
|
||||
|
||||
// 0x026C: NtMajorVersion (uint32) = 6 (Vista/7/8/10)
|
||||
uint32_t ntMajor = 6;
|
||||
std::memcpy(kuserData_ + 0x026C, &ntMajor, 4);
|
||||
|
||||
// NtMinorVersion at offset 0x0270 = 1 (Windows 7)
|
||||
// 0x0270: NtMinorVersion (uint32) = 1 (Windows 7)
|
||||
uint32_t ntMinor = 1;
|
||||
std::memcpy(kuserData_ + 0x0270, &ntMinor, 4);
|
||||
|
||||
// 0x0274: NtProductType (uint32) = 1 (NtProductWinNt = workstation)
|
||||
uint32_t productType = 1;
|
||||
std::memcpy(kuserData_ + 0x0274, &productType, 4);
|
||||
|
||||
// 0x0278: ProductTypeIsValid (uint8) = 1
|
||||
kuserData_[0x0278] = 1;
|
||||
|
||||
// 0x027C: NtMajorVersion (duplicate? actually NativeMajorVersion)
|
||||
// 0x0280: NtMinorVersion (NativeMinorVersion)
|
||||
|
||||
// 0x0294: ProcessorFeatures (BOOLEAN[64]) — leave mostly zero
|
||||
// PF_FLOATING_POINT_PRECISION_ERRATA = 0 at [0]
|
||||
// PF_FLOATING_POINT_EMULATED = 0 at [1]
|
||||
// PF_COMPARE_EXCHANGE_DOUBLE = 1 at [2]
|
||||
kuserData_[0x0294 + 2] = 1;
|
||||
// PF_MMX_INSTRUCTIONS_AVAILABLE = 1 at [3]
|
||||
kuserData_[0x0294 + 3] = 1;
|
||||
// PF_XMMI_INSTRUCTIONS_AVAILABLE (SSE) = 1 at [6]
|
||||
kuserData_[0x0294 + 6] = 1;
|
||||
// PF_XMMI64_INSTRUCTIONS_AVAILABLE (SSE2) = 1 at [10]
|
||||
kuserData_[0x0294 + 10] = 1;
|
||||
// PF_NX_ENABLED = 1 at [12]
|
||||
kuserData_[0x0294 + 12] = 1;
|
||||
|
||||
// 0x02D4: Reserved1 (uint32)
|
||||
|
||||
// 0x02D8: ActiveProcessorCount (uint32) = 4
|
||||
uint32_t procCount = 4;
|
||||
std::memcpy(kuserData_ + 0x02D8, &procCount, 4);
|
||||
|
||||
// 0x0300: NumberOfPhysicalPages (uint32) — 4GB / 4KB = ~1M pages
|
||||
uint32_t physPages = 0x000FF000;
|
||||
std::memcpy(kuserData_ + 0x0300, &physPages, 4);
|
||||
|
||||
// 0x0304: SafeBootMode (uint8) = 0 (normal boot)
|
||||
// already zero
|
||||
|
||||
// 0x0308: SharedDataFlags / TraceLogging (uint32) — leave zero
|
||||
|
||||
// 0x0320: TickCount (KSYSTEM_TIME) — same as TickCountLowDeprecated
|
||||
std::memcpy(kuserData_ + 0x0320, &tickCountLow, 4);
|
||||
|
||||
// 0x0330: Cookie (uint32) — stack cookie, random value
|
||||
uint32_t cookie = 0x4A2F8C15;
|
||||
std::memcpy(kuserData_ + 0x0330, &cookie, 4);
|
||||
}
|
||||
|
||||
void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
|
||||
|
|
@ -132,56 +251,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 +310,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 +318,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 +533,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 +556,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 +574,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 +598,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 +645,225 @@ bool WardenMemory::loadFromFile(const std::string& exePath) {
|
|||
|
||||
initKuserSharedData();
|
||||
patchRuntimeGlobals();
|
||||
if (isTurtle_) {
|
||||
patchTurtleWowBinary();
|
||||
}
|
||||
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;
|
||||
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++;
|
||||
std::string expHex, actHex;
|
||||
for (auto b : expected) { char s[4]; snprintf(s, 4, "%02X", b); expHex += s; }
|
||||
for (auto b : actual) { char s[4]; snprintf(s, 4, "%02X", b); actHex += s; }
|
||||
LOG_WARNING("WardenScan MISMATCH id=", e.id,
|
||||
" addr=0x", [&]{char s[12];snprintf(s,12,"%08X",e.address);return std::string(s);}(),
|
||||
" (", e.comment, ")",
|
||||
" expected=[", expHex, "] actual=[", actHex, "]",
|
||||
ok ? "" : " (readMemory FAILED)");
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches == 0) {
|
||||
LOG_WARNING("WardenScan: All ", sizeof(entries)/sizeof(entries[0]),
|
||||
" DB scan entries MATCH PE image ✓");
|
||||
} else {
|
||||
LOG_WARNING("WardenScan: ", mismatches, " / ", sizeof(entries)/sizeof(entries[0]),
|
||||
" DB scan entries MISMATCH! These will trigger cheat flags.");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Determine search range. For imageOnly (FIND_MEM_IMAGE_CODE_BY_HASH),
|
||||
// search only the .text section (RVA 0x1000, typically the first code section).
|
||||
// For FIND_CODE_BY_HASH, search the entire PE image.
|
||||
size_t searchStart = 0;
|
||||
size_t searchEnd = imageSize_;
|
||||
|
||||
// Collect search ranges: for imageOnly, all executable sections; otherwise entire image
|
||||
struct Range { size_t start; size_t end; };
|
||||
std::vector<Range> ranges;
|
||||
|
||||
if (imageOnly && image_.size() >= 64) {
|
||||
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 characteristics = image_[secOfs+36] | (uint32_t(image_[secOfs+37]) << 8)
|
||||
| (uint32_t(image_[secOfs+38]) << 16) | (uint32_t(image_[secOfs+39]) << 24);
|
||||
// Include sections with MEM_EXECUTE or CNT_CODE
|
||||
if (characteristics & (0x20000000 | 0x20)) {
|
||||
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()) {
|
||||
// Search entire image
|
||||
if (searchStart + patternLen <= searchEnd)
|
||||
ranges.push_back({searchStart, searchEnd});
|
||||
}
|
||||
|
||||
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