Add integrity hash support and SRP tuning options

This commit is contained in:
Kelsi
2026-02-13 01:32:15 -08:00
parent 3d29065046
commit fb580f2a5a
10 changed files with 591 additions and 22 deletions

View File

@@ -90,6 +90,7 @@ set(WOWEE_SOURCES
src/auth/auth_opcodes.cpp
src/auth/auth_packets.cpp
src/auth/pin_auth.cpp
src/auth/integrity.cpp
src/auth/srp.cpp
src/auth/big_num.cpp
src/auth/crypto.cpp
@@ -444,6 +445,28 @@ set_target_properties(auth_probe PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# ---- Tool: auth_login_probe (challenge + proof probe) ----
add_executable(auth_login_probe
tools/auth_login_probe/main.cpp
src/auth/auth_packets.cpp
src/auth/auth_opcodes.cpp
src/auth/crypto.cpp
src/auth/integrity.cpp
src/auth/big_num.cpp
src/auth/srp.cpp
src/network/packet.cpp
src/network/socket.cpp
src/network/tcp_socket.cpp
src/core/logger.cpp
)
target_include_directories(auth_login_probe PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(auth_login_probe PRIVATE Threads::Threads OpenSSL::Crypto)
set_target_properties(auth_login_probe PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# ---- Tool: blp_convert (BLP ↔ PNG) ----
add_executable(blp_convert
tools/blp_convert/main.cpp

View File

@@ -111,6 +111,7 @@ private:
uint8_t securityFlags_ = 0;
uint32_t pinGridSeed_ = 0;
std::array<uint8_t, 16> pinServerSalt_{}; // from LOGON_CHALLENGE response
std::array<uint8_t, 16> checksumSalt_{}; // from LOGON_CHALLENGE response (integrity salt)
std::string pendingSecurityCode_;
};

View File

@@ -37,6 +37,7 @@ struct LogonChallengeResponse {
std::vector<uint8_t> g; // Generator (variable, usually 1 byte)
std::vector<uint8_t> N; // Prime modulus (variable, usually 256 bytes)
std::vector<uint8_t> salt; // Salt (32 bytes)
std::array<uint8_t, 16> checksumSalt{}; // aka "crc_salt"/integrity salt
uint8_t securityFlags;
// PIN extension (securityFlags & 0x01)
@@ -66,6 +67,7 @@ public:
static network::Packet build(const std::vector<uint8_t>& A,
const std::vector<uint8_t>& M1,
uint8_t securityFlags,
const std::array<uint8_t, 20>* crcHash,
const std::array<uint8_t, 16>* pinClientSalt,
const std::array<uint8_t, 20>* pinHash);
};

View File

@@ -0,0 +1,35 @@
#pragma once
#include <array>
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace auth {
// Computes the LOGON_PROOF "CRC hash" / integrity hash for the legacy WoW login protocol.
//
// Algorithm (per WoWDev/gtker docs):
// checksum = HMAC_SHA1(checksumSalt, concatenated_file_bytes)
// crc_hash = SHA1(clientPublicKey || checksum)
//
// clientPublicKey is the 32-byte A as sent on the wire.
//
// Returns false if any file is missing/unreadable.
bool computeIntegrityHashWin32(const std::array<uint8_t, 16>& checksumSalt,
const std::vector<uint8_t>& clientPublicKeyA,
const std::string& miscDir,
std::array<uint8_t, 20>& outHash,
std::string& outError);
// Same as computeIntegrityHashWin32, but allows selecting the EXE filename used in the file set.
bool computeIntegrityHashWin32WithExe(const std::array<uint8_t, 16>& checksumSalt,
const std::vector<uint8_t>& clientPublicKeyA,
const std::string& miscDir,
const std::string& exeName,
std::array<uint8_t, 20>& outHash,
std::string& outError);
} // namespace auth
} // namespace wowee

View File

@@ -27,6 +27,15 @@ public:
const std::vector<uint8_t>& N,
const std::vector<uint8_t>& salt);
// Some SRP implementations use k = H(N|g) instead of the WoW-specific k=3.
// Default is false (k=3).
void setUseHashedK(bool enabled) { useHashedK_ = enabled; }
// Controls how SHA1 outputs are interpreted when converted to big integers (x, u, optionally k).
// Many SRP implementations treat hash outputs as big-endian integers.
// Default is false (treat hash outputs as little-endian integers).
void setHashBigEndian(bool enabled) { hashBigEndian_ = enabled; }
// Get client public ephemeral (A) - send to server
std::vector<uint8_t> getA() const;
@@ -73,6 +82,8 @@ private:
std::vector<uint8_t> stored_auth_hash; // Pre-computed SHA1(UPPER(user):UPPER(pass))
bool initialized = false;
bool useHashedK_ = false;
bool hashBigEndian_ = false;
};
} // namespace auth

View File

@@ -1,5 +1,6 @@
#include "auth/auth_handler.hpp"
#include "auth/pin_auth.hpp"
#include "auth/integrity.hpp"
#include "network/tcp_socket.hpp"
#include "network/packet.hpp"
#include "core/logger.hpp"
@@ -7,6 +8,7 @@
#include <iomanip>
#include <algorithm>
#include <cctype>
#include <cstdlib>
namespace wowee {
namespace auth {
@@ -105,6 +107,7 @@ void AuthHandler::authenticate(const std::string& user, const std::string& pass,
securityFlags_ = 0;
pinGridSeed_ = 0;
pinServerSalt_ = {};
checksumSalt_ = {};
// Initialize SRP
srp = std::make_unique<SRP>();
@@ -139,6 +142,7 @@ void AuthHandler::authenticateWithHash(const std::string& user, const std::vecto
securityFlags_ = 0;
pinGridSeed_ = 0;
pinServerSalt_ = {};
checksumSalt_ = {};
// Initialize SRP with pre-computed hash
srp = std::make_unique<SRP>();
@@ -196,6 +200,7 @@ void AuthHandler::handleLogonChallengeResponse(network::Packet& packet) {
srp->feed(response.B, response.g, response.N, response.salt);
securityFlags_ = response.securityFlags;
checksumSalt_ = response.checksumSalt;
if (securityFlags_ & 0x01) {
pinGridSeed_ = response.pinGridSeed;
pinServerSalt_ = response.pinSalt;
@@ -222,6 +227,8 @@ void AuthHandler::sendLogonProof() {
std::array<uint8_t, 20> pinHash{};
const std::array<uint8_t, 16>* pinClientSaltPtr = nullptr;
const std::array<uint8_t, 20>* pinHashPtr = nullptr;
std::array<uint8_t, 20> crcHash{};
const std::array<uint8_t, 20>* crcHashPtr = nullptr;
if (securityFlags_ & 0x01) {
try {
@@ -236,20 +243,54 @@ void AuthHandler::sendLogonProof() {
}
}
// Protocol < 8 uses a shorter proof packet (no securityFlags byte).
if (clientInfo.protocolVersion < 8) {
auto packet = LogonProofPacket::buildLegacy(A, M1);
socket->send(packet);
} else {
auto packet = LogonProofPacket::build(A, M1, securityFlags_, pinClientSaltPtr, pinHashPtr);
socket->send(packet);
if (securityFlags_ & 0x04) {
// TrinityCore-style Google Authenticator token: send immediately after proof.
const std::string token = pendingSecurityCode_;
auto tokPkt = AuthenticatorTokenPacket::build(token);
socket->send(tokPkt);
// Legacy client integrity hash (aka "CRC hash"). Some servers enforce this for classic builds.
// We compute it when checksumSalt was provided (always present on success challenge) and files exist.
{
std::vector<std::string> candidateDirs;
if (const char* env = std::getenv("WOWEE_INTEGRITY_DIR")) {
if (env && *env) candidateDirs.push_back(env);
}
// Default local extraction layout
candidateDirs.push_back("Data/misc");
// Common turtle repack location used in this workspace
if (const char* home = std::getenv("HOME")) {
if (home && *home) {
candidateDirs.push_back(std::string(home) + "/Downloads/twmoa_1180");
candidateDirs.push_back(std::string(home) + "/twmoa_1180");
}
}
const char* candidateExes[] = { "WoW.exe", "TurtleWoW.exe", "Wow.exe" };
bool ok = false;
std::string lastErr;
for (const auto& dir : candidateDirs) {
for (const char* exe : candidateExes) {
std::string err;
if (computeIntegrityHashWin32WithExe(checksumSalt_, A, dir, exe, crcHash, err)) {
crcHashPtr = &crcHash;
LOG_INFO("Integrity hash computed from ", dir, " (", exe, ")");
ok = true;
break;
}
lastErr = err;
}
if (ok) break;
}
if (!ok) {
LOG_WARNING("Integrity hash not computed (", lastErr,
"). Server may reject classic clients without it. "
"Set WOWEE_INTEGRITY_DIR to your client folder.");
}
}
auto packet = LogonProofPacket::build(A, M1, securityFlags_, crcHashPtr, pinClientSaltPtr, pinHashPtr);
socket->send(packet);
if ((securityFlags_ & 0x04) && clientInfo.protocolVersion >= 8) {
// TrinityCore-style Google Authenticator token: send immediately after proof.
const std::string token = pendingSecurityCode_;
auto tokPkt = AuthenticatorTokenPacket::build(token);
socket->send(tokPkt);
}
setState(AuthState::PROOF_SENT);

View File

@@ -126,9 +126,9 @@ bool LogonChallengeResponseParser::parse(network::Packet& packet, LogonChallenge
response.salt[i] = packet.readUInt8();
}
// Unknown/padding - 16 bytes
for (int i = 0; i < 16; ++i) {
packet.readUInt8();
// Integrity salt / CRC salt - 16 bytes
for (size_t i = 0; i < response.checksumSalt.size(); ++i) {
response.checksumSalt[i] = packet.readUInt8();
}
// Security flags
@@ -162,7 +162,7 @@ bool LogonChallengeResponseParser::parse(network::Packet& packet, LogonChallenge
network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
const std::vector<uint8_t>& M1) {
return build(A, M1, 0, nullptr, nullptr);
return build(A, M1, 0, nullptr, nullptr, nullptr);
}
network::Packet LogonProofPacket::buildLegacy(const std::vector<uint8_t>& A,
@@ -185,6 +185,7 @@ network::Packet LogonProofPacket::buildLegacy(const std::vector<uint8_t>& A,
network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
const std::vector<uint8_t>& M1,
uint8_t securityFlags,
const std::array<uint8_t, 20>* crcHash,
const std::array<uint8_t, 16>* pinClientSalt,
const std::array<uint8_t, 20>* pinHash) {
if (A.size() != 32) {
@@ -202,9 +203,11 @@ network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
// M1 (client proof) - 20 bytes
packet.writeBytes(M1.data(), M1.size());
// CRC hash - 20 bytes (zeros)
for (int i = 0; i < 20; ++i) {
packet.writeUInt8(0);
// CRC hash / integrity hash - 20 bytes
if (crcHash) {
packet.writeBytes(crcHash->data(), crcHash->size());
} else {
for (int i = 0; i < 20; ++i) packet.writeUInt8(0);
}
// Number of keys

91
src/auth/integrity.cpp Normal file
View File

@@ -0,0 +1,91 @@
#include "auth/integrity.hpp"
#include "auth/crypto.hpp"
#include <fstream>
#include <sstream>
namespace wowee {
namespace auth {
static bool readWholeFile(const std::string& path, std::vector<uint8_t>& 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_t>(size));
if (size > 0) {
f.read(reinterpret_cast<char*>(out.data()), size);
if (!f) {
err = "read failed: " + path;
return false;
}
}
return true;
}
bool computeIntegrityHashWin32WithExe(const std::array<uint8_t, 16>& checksumSalt,
const std::vector<uint8_t>& clientPublicKeyA,
const std::string& miscDir,
const std::string& exeName,
std::array<uint8_t, 20>& 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.
const char* kFiles[] = {
nullptr, // exeName
"fmod.dll",
"ijl15.dll",
"dbghelp.dll",
"unicows.dll",
};
std::vector<uint8_t> allFiles;
std::string err;
for (size_t idx = 0; idx < (sizeof(kFiles) / sizeof(kFiles[0])); ++idx) {
const char* name = kFiles[idx];
std::string nameStr = name ? std::string(name) : exeName;
std::vector<uint8_t> bytes;
std::string path = miscDir;
if (!path.empty() && path.back() != '/') path += '/';
path += nameStr;
if (!readWholeFile(path, bytes, err)) {
outError = err;
return false;
}
allFiles.insert(allFiles.end(), bytes.begin(), bytes.end());
}
// HMAC_SHA1(checksumSalt, allFiles)
std::vector<uint8_t> key(checksumSalt.begin(), checksumSalt.end());
const std::vector<uint8_t> checksum = Crypto::hmacSHA1(key, allFiles); // 20 bytes
// SHA1(A || checksum)
std::vector<uint8_t> 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<uint8_t> 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<uint8_t, 16>& checksumSalt,
const std::vector<uint8_t>& clientPublicKeyA,
const std::string& miscDir,
std::array<uint8_t, 20>& outHash,
std::string& outError) {
return computeIntegrityHashWin32WithExe(checksumSalt, clientPublicKeyA, miscDir, "WoW.exe", outHash, outError);
}
} // namespace auth
} // namespace wowee

View File

@@ -58,6 +58,18 @@ void SRP::feed(const std::vector<uint8_t>& B_bytes,
this->N = BigNum(N_bytes, true);
this->s = BigNum(salt_bytes, true);
if (useHashedK_) {
// k = H(N | g) (SRP-6a style)
std::vector<uint8_t> Ng;
Ng.insert(Ng.end(), N_bytes.begin(), N_bytes.end());
Ng.insert(Ng.end(), g_bytes.begin(), g_bytes.end());
std::vector<uint8_t> k_bytes = Crypto::sha1(Ng);
k = BigNum(k_bytes, !hashBigEndian_);
LOG_DEBUG("Using hashed SRP multiplier k=H(N|g)");
} else {
k = BigNum(K_VALUE);
}
LOG_DEBUG("SRP challenge data loaded");
// Now compute everything in sequence
@@ -72,7 +84,7 @@ void SRP::feed(const std::vector<uint8_t>& B_bytes,
x_input.insert(x_input.end(), salt_bytes.begin(), salt_bytes.end());
x_input.insert(x_input.end(), auth_hash.begin(), auth_hash.end());
std::vector<uint8_t> x_bytes = Crypto::sha1(x_input);
x = BigNum(x_bytes, true);
x = BigNum(x_bytes, !hashBigEndian_);
LOG_DEBUG("Computed x (salted password hash)");
// 3. Generate client ephemeral (a, A)
@@ -151,7 +163,7 @@ void SRP::computeSessionKey() {
AB.insert(AB.end(), B_bytes_u.begin(), B_bytes_u.end());
std::vector<uint8_t> u_bytes = Crypto::sha1(AB);
u = BigNum(u_bytes, true);
u = BigNum(u_bytes, !hashBigEndian_);
LOG_DEBUG("Scrambler u calculated");

View File

@@ -0,0 +1,350 @@
#include "auth/auth_packets.hpp"
#include "auth/crypto.hpp"
#include "auth/integrity.hpp"
#include "auth/srp.hpp"
#include "network/tcp_socket.hpp"
#include "network/packet.hpp"
#include "core/logger.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
using namespace wowee;
static void usage() {
std::cerr
<< "Usage:\n"
<< " auth_login_probe <host> <port> <account> <major> <minor> <patch> <build> <proto> <locale> \\\n"
<< " (--password <pass> | --hash <hexsha1>) [--proof legacy|v8|auto]\n"
<< "\n"
<< "Notes:\n"
<< " - --hash expects SHA1(UPPER(user):UPPER(pass)) in hex.\n"
<< " - This tool only probes auth; it does not connect to world.\n";
}
static std::vector<uint8_t> hexToBytes(const std::string& hex) {
std::vector<uint8_t> out;
std::string h;
h.reserve(hex.size());
for (char c : hex) {
if (!std::isspace(static_cast<unsigned char>(c))) h.push_back(c);
}
if (h.size() % 2 != 0) throw std::runtime_error("hex length must be even");
out.reserve(h.size() / 2);
for (size_t i = 0; i < h.size(); i += 2) {
auto byteStr = h.substr(i, 2);
uint8_t b = static_cast<uint8_t>(std::stoul(byteStr, nullptr, 16));
out.push_back(b);
}
return out;
}
static std::string upperAscii(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::toupper(c)); });
return s;
}
enum class ProofFormat { Auto, Legacy, V8 };
enum class CrcAFormat { Wire, BigEndian };
enum class WireAFormat { Little, Big };
int main(int argc, char** argv) {
if (argc < 11) {
usage();
return 2;
}
const std::string host = argv[1];
const int port = std::atoi(argv[2]);
const std::string account = argv[3];
const int major = std::atoi(argv[4]);
const int minor = std::atoi(argv[5]);
const int patch = std::atoi(argv[6]);
const int build = std::atoi(argv[7]);
const int proto = std::atoi(argv[8]);
const std::string locale = argv[9];
std::string password;
std::vector<uint8_t> authHash;
bool havePassword = false;
bool haveHash = false;
ProofFormat proofFmt = ProofFormat::Auto;
CrcAFormat crcA = CrcAFormat::Wire;
WireAFormat wireA = WireAFormat::Little;
std::string integrityExe = "WoW.exe";
bool serverValuesBigEndian = false;
std::string miscDir = "Data/misc";
bool useHashedK = false;
bool hashBigEndian = false;
for (int i = 10; i < argc; ++i) {
std::string a = argv[i];
if (a == "--password" && i + 1 < argc) {
password = argv[++i];
havePassword = true;
continue;
}
if (a == "--hash" && i + 1 < argc) {
authHash = hexToBytes(argv[++i]);
haveHash = true;
continue;
}
if (a == "--proof" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "auto") proofFmt = ProofFormat::Auto;
else if (v == "legacy") proofFmt = ProofFormat::Legacy;
else if (v == "v8") proofFmt = ProofFormat::V8;
else {
std::cerr << "Unknown --proof value: " << v << "\n";
return 2;
}
continue;
}
if (a == "--crc-a" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "wire") crcA = CrcAFormat::Wire;
else if (v == "be") crcA = CrcAFormat::BigEndian;
else {
std::cerr << "Unknown --crc-a value: " << v << " (expected wire|be)\n";
return 2;
}
continue;
}
if (a == "--integrity-exe" && i + 1 < argc) {
integrityExe = argv[++i];
continue;
}
if (a == "--misc-dir" && i + 1 < argc) {
miscDir = argv[++i];
continue;
}
if (a == "--server-values" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "le") serverValuesBigEndian = false;
else if (v == "be") serverValuesBigEndian = true;
else {
std::cerr << "Unknown --server-values value: " << v << " (expected le|be)\n";
return 2;
}
continue;
}
if (a == "--wire-a" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "le") wireA = WireAFormat::Little;
else if (v == "be") wireA = WireAFormat::Big;
else {
std::cerr << "Unknown --wire-a value: " << v << " (expected le|be)\n";
return 2;
}
continue;
}
if (a == "--k" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "3") useHashedK = false;
else if (v == "hashed") useHashedK = true;
else {
std::cerr << "Unknown --k value: " << v << " (expected 3|hashed)\n";
return 2;
}
continue;
}
if (a == "--hash-endian" && i + 1 < argc) {
std::string v = argv[++i];
if (v == "le") hashBigEndian = false;
else if (v == "be") hashBigEndian = true;
else {
std::cerr << "Unknown --hash-endian value: " << v << " (expected le|be)\n";
return 2;
}
continue;
}
std::cerr << "Unknown arg: " << a << "\n";
return 2;
}
if (!havePassword && !haveHash) {
std::cerr << "Must supply --password or --hash\n";
return 2;
}
auth::ClientInfo info;
info.majorVersion = static_cast<uint8_t>(major);
info.minorVersion = static_cast<uint8_t>(minor);
info.patchVersion = static_cast<uint8_t>(patch);
info.build = static_cast<uint16_t>(build);
info.protocolVersion = static_cast<uint8_t>(proto);
info.locale = locale;
info.platform = "x86";
info.os = "Win";
std::atomic<bool> done{false};
std::atomic<bool> sawDisconnect{false};
std::atomic<bool> challengeOk{false};
std::atomic<int> proofStatus{-1};
std::atomic<int> chalCode{-1};
network::TCPSocket sock;
std::unique_ptr<auth::SRP> srp;
uint8_t securityFlags = 0;
uint32_t pinSeed = 0;
std::array<uint8_t, 16> pinSalt{};
std::array<uint8_t, 16> checksumSalt{};
auto sendProof = [&]() {
if (!srp) return;
auto A = srp->getA();
if (wireA == WireAFormat::Big) {
std::reverse(A.begin(), A.end());
}
auto M1 = srp->getM1();
ProofFormat fmt = proofFmt;
if (fmt == ProofFormat::Auto) {
fmt = (info.protocolVersion < 8) ? ProofFormat::Legacy : ProofFormat::V8;
}
// Try to compute the classic client integrity hash using local Data/misc.
std::array<uint8_t, 20> crcHash{};
const std::array<uint8_t, 20>* crcHashPtr = nullptr;
{
std::string err;
std::vector<uint8_t> crcABytes = A;
if (crcA == CrcAFormat::BigEndian) {
std::reverse(crcABytes.begin(), crcABytes.end());
}
if (auth::computeIntegrityHashWin32WithExe(checksumSalt, crcABytes, miscDir, integrityExe, crcHash, err)) {
crcHashPtr = &crcHash;
std::cerr << "Computed integrity hash using " << miscDir << " (" << integrityExe << ")\n";
} else {
std::cerr << "Integrity hash not computed: " << err << "\n";
}
}
if (fmt == ProofFormat::Legacy) {
auto pkt = auth::LogonProofPacket::buildLegacy(A, M1);
sock.send(pkt);
std::cerr << "Sent LOGON_PROOF legacy (proto=" << (int)info.protocolVersion << ")\n";
} else {
auto pkt = auth::LogonProofPacket::build(A, M1, securityFlags, crcHashPtr, nullptr, nullptr);
sock.send(pkt);
std::cerr << "Sent LOGON_PROOF v8 (secFlags=0x" << std::hex << (int)securityFlags << std::dec << ")\n";
}
};
sock.setPacketCallback([&](const network::Packet& p) {
network::Packet pkt = p;
if (pkt.getSize() < 1) return;
uint8_t opcode = pkt.readUInt8();
if (opcode == static_cast<uint8_t>(auth::AuthOpcode::LOGON_CHALLENGE)) {
auth::LogonChallengeResponse resp{};
if (!auth::LogonChallengeResponseParser::parse(pkt, resp)) {
std::cerr << "Challenge parse failed\n";
done = true;
return;
}
chalCode = static_cast<int>(resp.result);
if (!resp.isSuccess()) {
std::cerr << "Challenge FAIL: " << auth::getAuthResultString(resp.result)
<< " (0x" << std::hex << (int)resp.result << std::dec << ")\n";
done = true;
return;
}
challengeOk = true;
securityFlags = resp.securityFlags;
pinSeed = resp.pinGridSeed;
pinSalt = resp.pinSalt;
checksumSalt = resp.checksumSalt;
srp = std::make_unique<auth::SRP>();
srp->setUseHashedK(useHashedK);
srp->setHashBigEndian(hashBigEndian);
if (haveHash) {
srp->initializeWithHash(account, authHash);
} else {
srp->initialize(account, password);
}
if (serverValuesBigEndian) {
auto rev = [](std::vector<uint8_t> v) {
std::reverse(v.begin(), v.end());
return v;
};
srp->feed(rev(resp.B), rev(resp.g), rev(resp.N), rev(resp.salt));
} else {
srp->feed(resp.B, resp.g, resp.N, resp.salt);
}
sendProof();
return;
}
if (opcode == static_cast<uint8_t>(auth::AuthOpcode::LOGON_PROOF)) {
auth::LogonProofResponse resp{};
if (!auth::LogonProofResponseParser::parse(pkt, resp)) {
std::cerr << "Proof parse failed\n";
done = true;
return;
}
proofStatus = resp.status;
if (resp.isSuccess()) {
std::cerr << "Proof SUCCESS\n";
} else {
std::cerr << "Proof FAIL status=0x" << std::hex << (int)resp.status << std::dec << "\n";
}
done = true;
return;
}
});
if (!sock.connect(host, static_cast<uint16_t>(port))) {
std::cerr << "Connect failed\n";
return 3;
}
auto chal = auth::LogonChallengePacket::build(account, info);
sock.send(chal);
auto start = std::chrono::steady_clock::now();
while (!done) {
sock.update();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!sock.isConnected() && !done) {
sawDisconnect = true;
done = true;
break;
}
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count() > 6000) {
break;
}
}
sock.disconnect();
if (!done && sock.isConnected()) {
std::cerr << "Timeout\n";
return 4;
}
if (sawDisconnect && challengeOk && proofStatus.load() < 0) {
std::cerr << "Server disconnected after challenge (no proof response parsed)\n";
return 6;
}
if (chalCode.load() >= 0 && chalCode.load() != 0) return chalCode.load();
if (proofStatus.load() >= 0) return proofStatus.load();
return 0;
}