Fix Classic field extraction, Warden PE patches, and auth/opcode corrections

Update field extraction in both CREATE_OBJECT and VALUES handlers to check
specific fields (maxHealth, level, faction, etc.) before power/maxpower range
checks. In Classic 1.12.1, power indices 23-27 are adjacent to maxHealth (28),
and maxPower indices 29-33 are adjacent to level (34) and faction (35), so
range checks like "key >= powerBase && key < powerBase+7" were incorrectly
capturing those fields.

Add build-aware WoW.exe selection and runtime global patching for Warden
SYSTEM_INFO, EndScene, WorldEnables, and LastHardwareAction chains. Fix
Classic opcodes and auth session addon data format for CMaNGOS compatibility.
This commit is contained in:
Kelsi 2026-02-20 00:18:03 -08:00
parent 38ad368c82
commit e8864941dc
5 changed files with 279 additions and 49 deletions

View file

@ -121,6 +121,146 @@ void WardenMemory::initKuserSharedData() {
std::memcpy(kuserData_ + 0x0270, &ntMinor, 4);
}
void WardenMemory::writeLE32(uint32_t va, uint32_t value) {
if (va < imageBase_) return;
uint32_t rva = va - imageBase_;
if (rva + 4 > imageSize_) return;
image_[rva] = value & 0xFF;
image_[rva+1] = (value >> 8) & 0xFF;
image_[rva+2] = (value >> 16) & 0xFF;
image_[rva+3] = (value >> 24) & 0xFF;
}
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");
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.
//
// 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
// === Warden SYSTEM_INFO chain (3-level pointer chain) ===
// Stage 0: [0xCE897C] → fake warden struct base
constexpr uint32_t WARDEN_MODULE_PTR = 0xCE897C;
constexpr uint32_t FAKE_WARDEN_BASE = 0xCE8000;
writeLE32(WARDEN_MODULE_PTR, FAKE_WARDEN_BASE);
// Stage 1: [FAKE_WARDEN_BASE + 0x228] → pointer to sysinfo container
constexpr uint32_t OFS_WARDEN_SYSINFO = 0x228;
constexpr uint32_t FAKE_SYSINFO_CONTAINER = 0xCE8300;
writeLE32(FAKE_WARDEN_BASE + OFS_WARDEN_SYSINFO, 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
struct {
uint16_t wProcessorArchitecture;
uint16_t wReserved;
uint32_t dwPageSize;
uint32_t lpMinimumApplicationAddress;
uint32_t lpMaximumApplicationAddress;
uint32_t dwActiveProcessorMask;
uint32_t dwNumberOfProcessors;
uint32_t dwProcessorType;
uint32_t dwAllocationGranularity;
uint16_t wProcessorLevel;
uint16_t wProcessorRevision;
} __attribute__((packed)) 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
};
static_assert(sizeof(sysInfo) == 36, "SYSTEM_INFO must be 36 bytes");
uint32_t rva = sysInfoAddr - imageBase_;
if (rva + 36 <= imageSize_) {
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);
// === EndScene chain (4-level pointer chain) ===
// Stage 1: [0xC0ED38] → fake D3D device
constexpr uint32_t GX_DEVICE_PTR = 0xC0ED38;
constexpr uint32_t FAKE_DEVICE = 0xCE8400;
writeLE32(GX_DEVICE_PTR, FAKE_DEVICE);
// 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);
// Stage 3: [FAKE_INTERMEDIATE + 0x0] → fake vtable
constexpr uint32_t OFS_DEVICE3 = 0x0;
constexpr uint32_t FAKE_VTABLE = 0xCE8600;
writeLE32(FAKE_INTERMEDIATE + OFS_DEVICE3, FAKE_VTABLE);
// Stage 4: [FAKE_VTABLE + 0xA8] → address of "EndScene" function
// Point to a real .text address with normal code (not 0xE9/0xCC = not hooked)
constexpr uint32_t OFS_DEVICE4 = 0xA8;
constexpr uint32_t FAKE_ENDSCENE = 0x00401000; // Start of .text section
writeLE32(FAKE_VTABLE + OFS_DEVICE4, FAKE_ENDSCENE);
LOG_INFO("WardenMemory: Patched EndScene chain: [0x", std::hex,
GX_DEVICE_PTR, "]→0x", FAKE_DEVICE,
" ... →EndScene@0x", FAKE_ENDSCENE, std::dec);
// === WorldEnables (single value) ===
// Required flags: TerrainDoodads|Terrain|MapObjects|MapObjectLighting|MapObjectTextures|Water
// Plus typical defaults (no Prohibited bits set)
constexpr uint32_t WORLD_ENABLES = 0xC7B2A4;
uint32_t enables = 0x1 | 0x2 | 0x10 | 0x20 | 0x40 | 0x100 | 0x200 | 0x400 | 0x800
| 0x8000 | 0x10000 | 0x100000 | 0x1000000 | 0x2000000
| 0x4000000 | 0x8000000 | 0x10000000;
writeLE32(WORLD_ENABLES, enables);
LOG_INFO("WardenMemory: Patched WorldEnables=0x", std::hex, enables, std::dec);
// === LastHardwareAction (tick count) ===
// Must be <= currentTime from timing check. Set to a plausible value.
constexpr uint32_t LAST_HARDWARE_ACTION = 0xCF0BC8;
writeLE32(LAST_HARDWARE_ACTION, 60000); // 1 minute
LOG_INFO("WardenMemory: Patched LastHardwareAction=60000ms");
}
bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) const {
if (length == 0) return true;
@ -139,36 +279,73 @@ bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) cons
return true;
}
std::string WardenMemory::findWowExe() const {
uint32_t WardenMemory::expectedImageSizeForBuild(uint16_t build) {
switch (build) {
case 5875: return 0x00906000; // Classic 1.12.1
default: return 0; // Unknown — accept any
}
}
std::string WardenMemory::findWowExe(uint16_t build) const {
std::vector<std::string> candidateDirs;
if (const char* env = std::getenv("WOWEE_INTEGRITY_DIR")) {
if (env && *env) candidateDirs.push_back(env);
}
candidateDirs.push_back("Data/misc");
if (const char* home = std::getenv("HOME")) {
if (home && *home) {
candidateDirs.push_back(std::string(home) + "/Downloads");
candidateDirs.push_back(std::string(home) + "/Downloads/twmoa_1180");
candidateDirs.push_back(std::string(home) + "/twmoa_1180");
}
}
candidateDirs.push_back("Data/misc");
const char* candidateExes[] = { "WoW.exe", "TurtleWoW.exe", "Wow.exe" };
// Collect all candidate paths
std::vector<std::string> allPaths;
for (const auto& dir : candidateDirs) {
for (const char* exe : candidateExes) {
std::string path = dir;
if (!path.empty() && path.back() != '/') path += '/';
path += exe;
if (std::filesystem::exists(path)) {
allPaths.push_back(path);
}
}
}
// If we know the expected imageSize for this build, try to find a matching PE
uint32_t expectedSize = expectedImageSizeForBuild(build);
if (expectedSize != 0 && allPaths.size() > 1) {
for (const auto& path : allPaths) {
std::ifstream f(path, std::ios::binary);
if (!f.is_open()) continue;
// Read PE headers to get imageSize
f.seekg(0, std::ios::end);
auto fileSize = f.tellg();
if (fileSize < 256) continue;
f.seekg(0x3C);
uint32_t peOfs = 0;
f.read(reinterpret_cast<char*>(&peOfs), 4);
if (peOfs + 4 + 20 + 60 > static_cast<uint32_t>(fileSize)) continue;
f.seekg(peOfs + 4 + 20 + 56); // OptionalHeader + 56 = SizeOfImage
uint32_t imgSize = 0;
f.read(reinterpret_cast<char*>(&imgSize), 4);
if (imgSize == expectedSize) {
LOG_INFO("WardenMemory: Matched build ", build, " to ", path,
" (imageSize=0x", std::hex, imgSize, std::dec, ")");
return path;
}
}
}
return "";
// Fallback: return first available
return allPaths.empty() ? "" : allPaths[0];
}
bool WardenMemory::load() {
std::string path = findWowExe();
bool WardenMemory::load(uint16_t build) {
std::string path = findWowExe(build);
if (path.empty()) {
LOG_WARNING("WardenMemory: WoW.exe not found in any candidate directory");
return false;
@ -197,6 +374,7 @@ bool WardenMemory::loadFromFile(const std::string& exePath) {
}
initKuserSharedData();
patchRuntimeGlobals();
loaded_ = true;
LOG_INFO("WardenMemory: Loaded PE image (", fileData.size(), " bytes on disk, ",
imageSize_, " bytes virtual)");