mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
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:
parent
38ad368c82
commit
e8864941dc
5 changed files with 279 additions and 49 deletions
|
|
@ -70,14 +70,14 @@
|
|||
"CMSG_GUILD_MOTD": "0x091",
|
||||
"SMSG_GUILD_INFO": "0x088",
|
||||
"SMSG_GUILD_ROSTER": "0x08A",
|
||||
"CMSG_GUILD_QUERY": "0x051",
|
||||
"SMSG_GUILD_QUERY_RESPONSE": "0x052",
|
||||
"CMSG_GUILD_QUERY": "0x054",
|
||||
"SMSG_GUILD_QUERY_RESPONSE": "0x055",
|
||||
"SMSG_GUILD_INVITE": "0x083",
|
||||
"CMSG_GUILD_REMOVE": "0x08E",
|
||||
"SMSG_GUILD_EVENT": "0x092",
|
||||
"SMSG_GUILD_COMMAND_RESULT": "0x093",
|
||||
"MSG_RAID_READY_CHECK": "0x322",
|
||||
"CMSG_DUEL_PROPOSED": "0x166",
|
||||
"SMSG_ITEM_PUSH_RESULT": "0x166",
|
||||
"CMSG_DUEL_ACCEPTED": "0x16C",
|
||||
"CMSG_DUEL_CANCELLED": "0x16D",
|
||||
"SMSG_DUEL_REQUESTED": "0x167",
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
"CMSG_BINDER_ACTIVATE": "0x1B5",
|
||||
"SMSG_LOG_XPGAIN": "0x1D0",
|
||||
"SMSG_MONSTER_MOVE": "0x0DD",
|
||||
"SMSG_COMPRESSED_MOVES": "0x06B",
|
||||
"SMSG_COMPRESSED_MOVES": "0x2FB",
|
||||
"CMSG_ATTACKSWING": "0x141",
|
||||
"CMSG_ATTACKSTOP": "0x142",
|
||||
"SMSG_ATTACKSTART": "0x143",
|
||||
|
|
@ -179,7 +179,7 @@
|
|||
"CMSG_SELL_ITEM": "0x1A0",
|
||||
"SMSG_SELL_ITEM": "0x1A1",
|
||||
"CMSG_BUY_ITEM": "0x1A2",
|
||||
"CMSG_BUYBACK_ITEM": "0x1A6",
|
||||
"CMSG_BUYBACK_ITEM": "0x290",
|
||||
"SMSG_BUY_FAILED": "0x1A5",
|
||||
"CMSG_TRAINER_LIST": "0x1B0",
|
||||
"SMSG_TRAINER_LIST": "0x1B1",
|
||||
|
|
@ -241,5 +241,5 @@
|
|||
"SMSG_CHANNEL_NOTIFY": "0x099",
|
||||
"CMSG_CHANNEL_LIST": "0x09A",
|
||||
"SMSG_CHANNEL_LIST": "0x09B",
|
||||
"SMSG_INSPECT_TALENT": "0x3F4"
|
||||
"SMSG_DUEL_REQUESTED": "0x167"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ public:
|
|||
WardenMemory();
|
||||
~WardenMemory();
|
||||
|
||||
/** Search standard candidate dirs for WoW.exe and load it. */
|
||||
bool load();
|
||||
/** 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);
|
||||
|
||||
/** Load PE image from a specific file path. */
|
||||
bool loadFromFile(const std::string& exePath);
|
||||
|
|
@ -44,7 +45,10 @@ private:
|
|||
|
||||
bool parsePE(const std::vector<uint8_t>& fileData);
|
||||
void initKuserSharedData();
|
||||
std::string findWowExe() const;
|
||||
void patchRuntimeGlobals();
|
||||
void writeLE32(uint32_t va, uint32_t value);
|
||||
std::string findWowExe(uint16_t build) const;
|
||||
static uint32_t expectedImageSizeForBuild(uint16_t build);
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -3431,12 +3431,12 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
|||
// Lazy-load WoW.exe PE image on first MEM_CHECK
|
||||
if (!wardenMemory_) {
|
||||
wardenMemory_ = std::make_unique<WardenMemory>();
|
||||
if (!wardenMemory_->load()) {
|
||||
if (!wardenMemory_->load(static_cast<uint16_t>(build))) {
|
||||
LOG_WARNING("Warden: Could not load WoW.exe for MEM_CHECK");
|
||||
}
|
||||
}
|
||||
|
||||
// Read real bytes from PE image (falls back to zeros if unavailable)
|
||||
// Read bytes from PE image (includes patched runtime globals)
|
||||
std::vector<uint8_t> memBuf(readLen, 0);
|
||||
if (wardenMemory_->isLoaded() && wardenMemory_->readMemory(offset, readLen, memBuf.data())) {
|
||||
LOG_INFO("Warden: MEM_CHECK served from PE image");
|
||||
|
|
@ -4161,6 +4161,11 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
const uint16_t ufNpcFlags = fieldIndex(UF::UNIT_NPC_FLAGS);
|
||||
const uint16_t ufBytes0 = fieldIndex(UF::UNIT_FIELD_BYTES_0);
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
// Check all specific fields BEFORE power/maxpower range checks.
|
||||
// In Classic, power indices (23-27) are adjacent to maxHealth (28),
|
||||
// and maxPower indices (29-33) are adjacent to level (34) and faction (35).
|
||||
// A range check like "key >= powerBase && key < powerBase+7" would
|
||||
// incorrectly capture maxHealth/level/faction in Classic's tight layout.
|
||||
if (key == ufHealth) {
|
||||
unit->setHealth(val);
|
||||
if (block.objectType == ObjectType::UNIT && val == 0) {
|
||||
|
|
@ -4170,16 +4175,15 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in dead");
|
||||
}
|
||||
} else if (key == ufBytes0) {
|
||||
unit->setPowerType(static_cast<uint8_t>((val >> 24) & 0xFF));
|
||||
} else if (key >= ufPowerBase && key < ufPowerBase + 7) {
|
||||
unit->setPowerByType(static_cast<uint8_t>(key - ufPowerBase), val);
|
||||
} else if (key == ufMaxHealth) { unit->setMaxHealth(val); }
|
||||
else if (key >= ufMaxPowerBase && key < ufMaxPowerBase + 7) {
|
||||
unit->setMaxPowerByType(static_cast<uint8_t>(key - ufMaxPowerBase), val);
|
||||
}
|
||||
else if (key == ufFaction) { unit->setFactionTemplate(val); }
|
||||
else if (key == ufLevel) {
|
||||
unit->setLevel(val);
|
||||
} else if (key == ufFaction) { unit->setFactionTemplate(val); }
|
||||
else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
else if (key == ufBytes0) {
|
||||
unit->setPowerType(static_cast<uint8_t>((val >> 24) & 0xFF));
|
||||
} else if (key == ufDisplayId) { unit->setDisplayId(val); }
|
||||
else if (key == ufNpcFlags) { unit->setNpcFlags(val); }
|
||||
else if (key == ufDynFlags) {
|
||||
unit->setDynamicFlags(val);
|
||||
if (block.objectType == ObjectType::UNIT &&
|
||||
|
|
@ -4187,8 +4191,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
unitInitiallyDead = true;
|
||||
}
|
||||
}
|
||||
else if (key == ufLevel) { unit->setLevel(val); }
|
||||
else if (key == ufDisplayId) { unit->setDisplayId(val); }
|
||||
// Power/maxpower range checks AFTER all specific fields
|
||||
else if (key >= ufPowerBase && key < ufPowerBase + 7) {
|
||||
unit->setPowerByType(static_cast<uint8_t>(key - ufPowerBase), val);
|
||||
} else if (key >= ufMaxPowerBase && key < ufMaxPowerBase + 7) {
|
||||
unit->setMaxPowerByType(static_cast<uint8_t>(key - ufMaxPowerBase), val);
|
||||
}
|
||||
else if (key == ufMountDisplayId) {
|
||||
if (block.guid == playerGuid) {
|
||||
uint32_t old = currentMountDisplayId_;
|
||||
|
|
@ -4557,15 +4565,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
npcRespawnNotified = true;
|
||||
}
|
||||
}
|
||||
} else if (key == ufBytes0) {
|
||||
unit->setPowerType(static_cast<uint8_t>((val >> 24) & 0xFF));
|
||||
} else if (key >= ufPowerBase && key < ufPowerBase + 7) {
|
||||
unit->setPowerByType(static_cast<uint8_t>(key - ufPowerBase), val);
|
||||
// Specific fields checked BEFORE power/maxpower range checks
|
||||
// (Classic packs maxHealth/level/faction adjacent to power indices)
|
||||
} else if (key == ufMaxHealth) { unit->setMaxHealth(val); }
|
||||
else if (key >= ufMaxPowerBase && key < ufMaxPowerBase + 7) {
|
||||
unit->setMaxPowerByType(static_cast<uint8_t>(key - ufMaxPowerBase), val);
|
||||
}
|
||||
else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
else if (key == ufBytes0) {
|
||||
unit->setPowerType(static_cast<uint8_t>((val >> 24) & 0xFF));
|
||||
} else if (key == ufFlags) { unit->setUnitFlags(val); }
|
||||
else if (key == ufDynFlags) {
|
||||
uint32_t oldDyn = unit->getDynamicFlags();
|
||||
unit->setDynamicFlags(val);
|
||||
|
|
@ -4648,6 +4653,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
}
|
||||
unit->setMountDisplayId(val);
|
||||
} else if (key == ufNpcFlags) { unit->setNpcFlags(val); }
|
||||
// Power/maxpower range checks AFTER all specific fields
|
||||
else if (key >= ufPowerBase && key < ufPowerBase + 7) {
|
||||
unit->setPowerByType(static_cast<uint8_t>(key - ufPowerBase), val);
|
||||
} else if (key >= ufMaxPowerBase && key < ufMaxPowerBase + 7) {
|
||||
unit->setMaxPowerByType(static_cast<uint8_t>(key - ufMaxPowerBase), val);
|
||||
}
|
||||
}
|
||||
|
||||
// Some units/players are created without displayId and get it later via VALUES.
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
|
|
|
|||
|
|
@ -73,26 +73,57 @@ network::Packet AuthSessionPacket::build(uint32_t build,
|
|||
// Authentication hash/digest (20 bytes)
|
||||
packet.writeBytes(authHash.data(), authHash.size());
|
||||
|
||||
// Addon info - compressed block with 0 addons
|
||||
// AzerothCore format: uint32 decompressedSize + zlib compressed data
|
||||
// Decompressed format: uint32 addonCount + [addons...] + uint32 clientTime
|
||||
uint8_t addonData[8] = {
|
||||
0, 0, 0, 0, // addon count = 0
|
||||
0, 0, 0, 0 // client time = 0
|
||||
};
|
||||
uint32_t decompressedSize = 8;
|
||||
// Addon info - compressed block
|
||||
// Format differs between expansions:
|
||||
// Vanilla/TBC (CMaNGOS): while-loop of {string name, uint8 flags, uint32 modulusCRC, uint32 urlCRC}
|
||||
// WotLK (AzerothCore): uint32 addonCount + {string name, uint8 enabled, uint32 crc, uint32 unk} + uint32 clientTime
|
||||
std::vector<uint8_t> addonData;
|
||||
if (isTbc) {
|
||||
// Vanilla/TBC: each addon entry = null-terminated name + uint8 flags + uint32 modulusCRC + uint32 urlCRC
|
||||
// Send standard Blizzard addons that CMaNGOS anticheat expects for fingerprinting
|
||||
static const char* vanillaAddons[] = {
|
||||
"Blizzard_AuctionUI", "Blizzard_BattlefieldMinimap", "Blizzard_BindingUI",
|
||||
"Blizzard_CombatText", "Blizzard_CraftUI", "Blizzard_GMSurveyUI",
|
||||
"Blizzard_InspectUI", "Blizzard_MacroUI", "Blizzard_RaidUI",
|
||||
"Blizzard_TalentUI", "Blizzard_TradeSkillUI", "Blizzard_TrainerUI"
|
||||
};
|
||||
static const uint32_t standardModulusCRC = 0x4C1C776D;
|
||||
for (const char* name : vanillaAddons) {
|
||||
// string (null-terminated)
|
||||
size_t len = strlen(name);
|
||||
addonData.insert(addonData.end(), reinterpret_cast<const uint8_t*>(name),
|
||||
reinterpret_cast<const uint8_t*>(name) + len + 1);
|
||||
// uint8 flags = 1 (enabled)
|
||||
addonData.push_back(0x01);
|
||||
// uint32 modulusCRC (little-endian)
|
||||
addonData.push_back(static_cast<uint8_t>(standardModulusCRC & 0xFF));
|
||||
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 8) & 0xFF));
|
||||
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 16) & 0xFF));
|
||||
addonData.push_back(static_cast<uint8_t>((standardModulusCRC >> 24) & 0xFF));
|
||||
// uint32 urlCRC = 0
|
||||
addonData.push_back(0); addonData.push_back(0);
|
||||
addonData.push_back(0); addonData.push_back(0);
|
||||
}
|
||||
} else {
|
||||
// WotLK: uint32 addonCount + entries + uint32 clientTime
|
||||
// Send 0 addons
|
||||
addonData = { 0, 0, 0, 0, // addonCount = 0
|
||||
0, 0, 0, 0 }; // clientTime = 0
|
||||
}
|
||||
uint32_t decompressedSize = static_cast<uint32_t>(addonData.size());
|
||||
|
||||
// Compress with zlib
|
||||
uLongf compressedSize = compressBound(decompressedSize);
|
||||
std::vector<uint8_t> compressed(compressedSize);
|
||||
int ret = compress(compressed.data(), &compressedSize, addonData, decompressedSize);
|
||||
int ret = compress(compressed.data(), &compressedSize, addonData.data(), decompressedSize);
|
||||
if (ret == Z_OK) {
|
||||
compressed.resize(compressedSize);
|
||||
// Write decompressedSize, then compressed bytes
|
||||
packet.writeUInt32(decompressedSize);
|
||||
packet.writeBytes(compressed.data(), compressed.size());
|
||||
LOG_DEBUG("Addon info: decompressedSize=", decompressedSize,
|
||||
" compressedSize=", compressedSize);
|
||||
" compressedSize=", compressedSize, " addons=",
|
||||
isTbc ? "12 vanilla" : "0 wotlk");
|
||||
} else {
|
||||
LOG_ERROR("zlib compress failed with code: ", ret);
|
||||
packet.writeUInt32(0);
|
||||
|
|
@ -191,16 +222,22 @@ bool AuthChallengeParser::parse(network::Packet& packet, AuthChallengeData& data
|
|||
return false;
|
||||
}
|
||||
|
||||
if (packet.getSize() < 8) {
|
||||
// TBC format: just the server seed (4 bytes)
|
||||
if (packet.getSize() <= 4) {
|
||||
// Original vanilla/TBC format: just the server seed (4 bytes)
|
||||
data.unknown1 = 0;
|
||||
data.serverSeed = packet.readUInt32();
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (TBC format):");
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (TBC format, 4 bytes):");
|
||||
} else if (packet.getSize() < 40) {
|
||||
// Vanilla with encryption seeds (36 bytes): serverSeed + 32 bytes seeds
|
||||
// No "unknown1" prefix — first uint32 IS the server seed
|
||||
data.unknown1 = 0;
|
||||
data.serverSeed = packet.readUInt32();
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (Classic+seeds format, ", packet.getSize(), " bytes):");
|
||||
} else {
|
||||
// WotLK format: unknown1 + serverSeed + encryption seeds
|
||||
// WotLK format (40+ bytes): unknown1 + serverSeed + 32 bytes encryption seeds
|
||||
data.unknown1 = packet.readUInt32();
|
||||
data.serverSeed = packet.readUInt32();
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (WotLK format):");
|
||||
LOG_INFO("Parsed SMSG_AUTH_CHALLENGE (WotLK format, ", packet.getSize(), " bytes):");
|
||||
LOG_INFO(" Unknown1: 0x", std::hex, data.unknown1, std::dec);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue