From fc68c6c6b7638d8eb19dba2b47532286b02b7a3b Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 25 Feb 2026 09:26:34 -0800 Subject: [PATCH] Fix Warden module parse fallback and macOS FMOD integrity aliases --- src/auth/integrity.cpp | 60 ++++++----- src/game/warden_module.cpp | 201 ++++++++++++++++++++++++++----------- 2 files changed, 178 insertions(+), 83 deletions(-) diff --git a/src/auth/integrity.cpp b/src/auth/integrity.cpp index cdf7d146..13f71261 100644 --- a/src/auth/integrity.cpp +++ b/src/auth/integrity.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace wowee { namespace auth { @@ -41,39 +42,46 @@ bool computeIntegrityHashWin32WithExe(const std::array& checksumSal // that distribution rather than a stock 1.12.1 client, so when using Turtle's executable we include // Turtle-specific DLLs as well. const bool isTurtleExe = (exeName == "TurtleWoW.exe"); - const char* kFilesBase[] = { - nullptr, // exeName - "fmod.dll", - "ijl15.dll", - "dbghelp.dll", - "unicows.dll", + // Some macOS client layouts use FMOD dylib naming instead of fmod.dll. + // We accept the first matching filename in each alias group. + std::vector> fileGroups = { + { exeName }, + { "fmod.dll", "fmod.dylib", "libfmod.dylib", "fmodex.dll", "fmodex.dylib", "libfmod.so" }, + { "ijl15.dll" }, + { "dbghelp.dll" }, + { "unicows.dll" }, }; - const char* kFilesTurtleExtra[] = { - "twloader.dll", - "twdiscord.dll", - }; - - std::vector files; - files.reserve(1 + 4 + (isTurtleExe ? (sizeof(kFilesTurtleExtra) / sizeof(kFilesTurtleExtra[0])) : 0)); - for (const char* f : kFilesBase) { - files.push_back(f ? std::string(f) : exeName); - } if (isTurtleExe) { - for (const char* f : kFilesTurtleExtra) files.push_back(std::string(f)); + fileGroups.push_back({ "twloader.dll" }); + fileGroups.push_back({ "twdiscord.dll" }); } std::vector allFiles; - std::string err; - for (const auto& nameStr : files) { - std::vector bytes; - std::string path = miscDir; - if (!path.empty() && path.back() != '/') path += '/'; - path += nameStr; - if (!readWholeFile(path, bytes, err)) { - outError = err; + for (const auto& group : fileGroups) { + bool foundInGroup = false; + std::string groupErr; + + for (const auto& nameStr : group) { + std::vector bytes; + std::string path = miscDir; + if (!path.empty() && path.back() != '/') path += '/'; + path += nameStr; + + std::string err; + if (!readWholeFile(path, bytes, err)) { + if (groupErr.empty()) groupErr = err; + continue; + } + + allFiles.insert(allFiles.end(), bytes.begin(), bytes.end()); + foundInGroup = true; + break; + } + + if (!foundInGroup) { + outError = groupErr.empty() ? "missing required integrity file group" : groupErr; return false; } - allFiles.insert(allFiles.end(), bytes.begin(), bytes.end()); } // HMAC_SHA1(checksumSalt, allFiles) diff --git a/src/game/warden_module.cpp b/src/game/warden_module.cpp index 68a2fc9c..1c253459 100644 --- a/src/game/warden_module.cpp +++ b/src/game/warden_module.cpp @@ -529,78 +529,165 @@ bool WardenModule::parseExecutableFormat(const std::vector& exeData) { std::cout << "[WardenModule] Allocated " << moduleSize_ << " bytes of executable memory at " << moduleMemory_ << '\n'; - // Parse copy/skip pairs (MaNGOS/TrinityCore format) - // Format: repeated [2B copy_count][copy_count bytes data][2B skip_count] - // Copy = copy from source to dest, Skip = advance dest pointer (zeros) - // Terminates when copy_count == 0 - size_t pos = 4; // Skip 4-byte size header - size_t destOffset = 0; - int pairCount = 0; + auto readU16LE = [&](size_t at) -> uint16_t { + return static_cast(exeData[at] | (exeData[at + 1] << 8)); + }; - while (pos + 2 <= exeData.size()) { - // Read copy count (2 bytes LE) - uint16_t copyCount = exeData[pos] | (exeData[pos + 1] << 8); - pos += 2; + enum class PairFormat { + CopyDataSkip, // [copy][data][skip] + SkipCopyData, // [skip][copy][data] + CopySkipData // [copy][skip][data] + }; - if (copyCount == 0) { - break; // End of copy/skip pairs - } + auto tryParsePairs = [&](PairFormat format, + std::vector& imageOut, + size_t& relocPosOut, + size_t& finalOffsetOut, + int& pairCountOut) -> bool { + imageOut.assign(moduleSize_, 0); + size_t pos = 4; // Skip 4-byte final size header + size_t destOffset = 0; + int pairCount = 0; - if (copyCount > 0) { - if (pos + copyCount > exeData.size()) { - std::cerr << "[WardenModule] Copy section extends beyond data bounds" << '\n'; - #ifdef _WIN32 - VirtualFree(moduleMemory_, 0, MEM_RELEASE); - #else - munmap(moduleMemory_, moduleSize_); - #endif - moduleMemory_ = nullptr; - return false; + while (pos + 2 <= exeData.size()) { + uint16_t copyCount = 0; + uint16_t skipCount = 0; + + switch (format) { + case PairFormat::CopyDataSkip: { + copyCount = readU16LE(pos); + pos += 2; + if (copyCount == 0) { + relocPosOut = pos; + finalOffsetOut = destOffset; + pairCountOut = pairCount; + imageOut.resize(moduleSize_); + return true; + } + + if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { + return false; + } + + std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); + pos += copyCount; + destOffset += copyCount; + + if (pos + 2 > exeData.size()) { + return false; + } + skipCount = readU16LE(pos); + pos += 2; + break; + } + + case PairFormat::SkipCopyData: { + if (pos + 4 > exeData.size()) { + return false; + } + skipCount = readU16LE(pos); + pos += 2; + copyCount = readU16LE(pos); + pos += 2; + + if (skipCount == 0 && copyCount == 0) { + relocPosOut = pos; + finalOffsetOut = destOffset; + pairCountOut = pairCount; + imageOut.resize(moduleSize_); + return true; + } + + if (destOffset + skipCount > moduleSize_) { + return false; + } + destOffset += skipCount; + + if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { + return false; + } + std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); + pos += copyCount; + destOffset += copyCount; + break; + } + + case PairFormat::CopySkipData: { + if (pos + 4 > exeData.size()) { + return false; + } + copyCount = readU16LE(pos); + pos += 2; + skipCount = readU16LE(pos); + pos += 2; + + if (copyCount == 0 && skipCount == 0) { + relocPosOut = pos; + finalOffsetOut = destOffset; + pairCountOut = pairCount; + imageOut.resize(moduleSize_); + return true; + } + + if (pos + copyCount > exeData.size() || destOffset + copyCount > moduleSize_) { + return false; + } + std::memcpy(imageOut.data() + destOffset, exeData.data() + pos, copyCount); + pos += copyCount; + destOffset += copyCount; + break; + } } - if (destOffset + copyCount > moduleSize_) { - std::cerr << "[WardenModule] Copy section exceeds module size" << '\n'; - #ifdef _WIN32 - VirtualFree(moduleMemory_, 0, MEM_RELEASE); - #else - munmap(moduleMemory_, moduleSize_); - #endif - moduleMemory_ = nullptr; + if (destOffset + skipCount > moduleSize_) { return false; } - - std::memcpy( - static_cast(moduleMemory_) + destOffset, - exeData.data() + pos, - copyCount - ); - pos += copyCount; - destOffset += copyCount; + destOffset += skipCount; + pairCount++; } - // Read skip count (2 bytes LE) - uint16_t skipCount = 0; - if (pos + 2 <= exeData.size()) { - skipCount = exeData[pos] | (exeData[pos + 1] << 8); - pos += 2; - } + return false; + }; - // Advance dest pointer by skipCount (gaps are zero-filled from memset) - destOffset += skipCount; + std::vector parsedImage; + size_t parsedRelocPos = 0; + size_t parsedFinalOffset = 0; + int parsedPairCount = 0; - pairCount++; - std::cout << "[WardenModule] Pair " << pairCount << ": copy " << copyCount - << ", skip " << skipCount << " (dest offset=" << destOffset << ")" << '\n'; + PairFormat usedFormat = PairFormat::CopyDataSkip; + bool parsed = tryParsePairs(PairFormat::CopyDataSkip, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); + if (!parsed) { + usedFormat = PairFormat::SkipCopyData; + parsed = tryParsePairs(PairFormat::SkipCopyData, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); + } + if (!parsed) { + usedFormat = PairFormat::CopySkipData; + parsed = tryParsePairs(PairFormat::CopySkipData, parsedImage, parsedRelocPos, parsedFinalOffset, parsedPairCount); } - // Save position — remaining decompressed data contains relocation entries - relocDataOffset_ = pos; + if (parsed) { + std::memcpy(moduleMemory_, parsedImage.data(), parsedImage.size()); + relocDataOffset_ = parsedRelocPos; - std::cout << "[WardenModule] Parsed " << pairCount << " skip/copy pairs, final offset: " - << destOffset << "/" << finalCodeSize << '\n'; - std::cout << "[WardenModule] Relocation data starts at decompressed offset " << relocDataOffset_ - << " (" << (exeData.size() - relocDataOffset_) << " bytes remaining)" << '\n'; + const char* formatName = "copy/data/skip"; + if (usedFormat == PairFormat::SkipCopyData) formatName = "skip/copy/data"; + if (usedFormat == PairFormat::CopySkipData) formatName = "copy/skip/data"; + std::cout << "[WardenModule] Parsed " << parsedPairCount << " pairs using format " + << formatName << ", final offset: " << parsedFinalOffset << "/" << finalCodeSize << '\n'; + std::cout << "[WardenModule] Relocation data starts at decompressed offset " << relocDataOffset_ + << " (" << (exeData.size() - relocDataOffset_) << " bytes remaining)" << '\n'; + return true; + } + + // Fallback: copy raw payload (without the 4-byte size header) into module memory. + // This keeps loading alive for servers where packet flow can continue with hash/check fallbacks. + if (exeData.size() > 4) { + size_t rawCopySize = std::min(moduleSize_, exeData.size() - 4); + std::memcpy(moduleMemory_, exeData.data() + 4, rawCopySize); + } + relocDataOffset_ = 0; + std::cerr << "[WardenModule] Could not parse copy/skip pairs (all known layouts failed); using raw payload fallback" << '\n'; return true; }