#include "auth/integrity.hpp" #include "auth/crypto.hpp" #include #include #include namespace wowee { namespace auth { static bool readWholeFile(const std::string& path, std::vector& out, std::string& err) { std::ifstream f(path, std::ios::binary); if (!f.is_open()) { err = "missing: " + path; return false; } f.seekg(0, std::ios::end); std::streamoff size = f.tellg(); if (size < 0) size = 0; f.seekg(0, std::ios::beg); out.resize(static_cast(size)); if (size > 0) { f.read(reinterpret_cast(out.data()), size); if (!f) { err = "read failed: " + path; return false; } } return true; } bool computeIntegrityHashWin32WithExe(const std::array& checksumSalt, const std::vector& clientPublicKeyA, const std::string& miscDir, const std::string& exeName, std::array& outHash, std::string& outError) { // Files expected by 1.12.x Windows clients for the integrity check. // If this needs to vary by build, make it data-driven in expansion.json later. // // Turtle WoW ships a custom loader DLL. Some Turtle auth servers appear to validate integrity against // 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"); // 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" }, }; if (isTurtleExe) { fileGroups.push_back({ "twloader.dll" }); fileGroups.push_back({ "twdiscord.dll" }); } std::vector allFiles; 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; } } // HMAC_SHA1(checksumSalt, allFiles) std::vector key(checksumSalt.begin(), checksumSalt.end()); const std::vector checksum = Crypto::hmacSHA1(key, allFiles); // 20 bytes // SHA1(A || checksum) std::vector shaIn; shaIn.reserve(clientPublicKeyA.size() + checksum.size()); shaIn.insert(shaIn.end(), clientPublicKeyA.begin(), clientPublicKeyA.end()); shaIn.insert(shaIn.end(), checksum.begin(), checksum.end()); const std::vector finalHash = Crypto::sha1(shaIn); if (finalHash.size() != outHash.size()) { outError = "unexpected sha1 size"; return false; } std::copy(finalHash.begin(), finalHash.end(), outHash.begin()); return true; } bool computeIntegrityHashWin32(const std::array& checksumSalt, const std::vector& clientPublicKeyA, const std::string& miscDir, std::array& outHash, std::string& outError) { return computeIntegrityHashWin32WithExe(checksumSalt, clientPublicKeyA, miscDir, "WoW.exe", outHash, outError); } } // namespace auth } // namespace wowee