Fix auth protocol to match real WoW 3.3.5a client format

Three critical bugs fixed:
- LOGON_CHALLENGE request: set protocol byte to 0x03 (was 0x00) and
  reverse FourCC strings (game/platform/os/locale) to match real client
- Response parsers: remove double-read of opcode byte that shifted all
  field reads by one, preventing successful auth with real servers
- LOGON_PROOF response sizes: success=32 bytes, failure=4 bytes to match
  TrinityCore/AzerothCore format

Also adds missing auth result codes (0x13-0x20, 0xFF) including
IGR_WITHOUT_BNET (0x17) which Warmane was returning.
This commit is contained in:
Kelsi 2026-02-05 12:39:34 -08:00
parent 1b414d24d8
commit 5ef11fdc7d
4 changed files with 53 additions and 52 deletions

View file

@ -12,25 +12,33 @@ network::Packet LogonChallengePacket::build(const std::string& account, const Cl
std::string upperAccount = account;
std::transform(upperAccount.begin(), upperAccount.end(), upperAccount.begin(), ::toupper);
// Calculate packet size
// Opcode(1) + unknown(1) + size(2) + game(4) + version(3) + build(2) +
// platform(4) + os(4) + locale(4) + timezone(4) + ip(4) + accountLen(1) + account(N)
// Calculate payload size (everything after cmd + error + size)
// game(4) + version(3) + build(2) + platform(4) + os(4) + locale(4) +
// timezone(4) + ip(4) + accountLen(1) + account(N)
uint16_t payloadSize = 30 + upperAccount.length();
network::Packet packet(static_cast<uint16_t>(AuthOpcode::LOGON_CHALLENGE));
// Unknown byte
packet.writeUInt8(0x00);
// Protocol version (real WoW 3.3.5a client sends 3)
packet.writeUInt8(0x03);
// Payload size
packet.writeUInt16(payloadSize);
// Game name (4 bytes, null-padded)
packet.writeBytes(reinterpret_cast<const uint8_t*>(info.game.c_str()),
std::min<size_t>(4, info.game.length()));
for (size_t i = info.game.length(); i < 4; ++i) {
packet.writeUInt8(0);
}
// Helper: write a FourCC string as reversed bytes (little-endian FourCC)
// Real WoW client stores FourCC as big-endian uint32, written in LE byte order
auto writeFourCC = [&packet](const std::string& str) {
uint8_t buf[4] = {0, 0, 0, 0};
for (size_t i = 0; i < std::min<size_t>(4, str.length()); ++i) {
buf[i] = static_cast<uint8_t>(str[i]);
}
for (int i = 3; i >= 0; --i) {
packet.writeUInt8(buf[i]);
}
};
// Game name (4 bytes, reversed FourCC)
writeFourCC(info.game);
// Version (3 bytes)
packet.writeUInt8(info.majorVersion);
@ -40,26 +48,14 @@ network::Packet LogonChallengePacket::build(const std::string& account, const Cl
// Build (2 bytes)
packet.writeUInt16(info.build);
// Platform (4 bytes, null-padded)
packet.writeBytes(reinterpret_cast<const uint8_t*>(info.platform.c_str()),
std::min<size_t>(4, info.platform.length()));
for (size_t i = info.platform.length(); i < 4; ++i) {
packet.writeUInt8(0);
}
// Platform (4 bytes, reversed FourCC)
writeFourCC(info.platform);
// OS (4 bytes, null-padded)
packet.writeBytes(reinterpret_cast<const uint8_t*>(info.os.c_str()),
std::min<size_t>(4, info.os.length()));
for (size_t i = info.os.length(); i < 4; ++i) {
packet.writeUInt8(0);
}
// OS (4 bytes, reversed FourCC)
writeFourCC(info.os);
// Locale (4 bytes, null-padded)
packet.writeBytes(reinterpret_cast<const uint8_t*>(info.locale.c_str()),
std::min<size_t>(4, info.locale.length()));
for (size_t i = info.locale.length(); i < 4; ++i) {
packet.writeUInt8(0);
}
// Locale (4 bytes, reversed FourCC)
writeFourCC(info.locale);
// Timezone
packet.writeUInt32(info.timezone);
@ -80,14 +76,9 @@ network::Packet LogonChallengePacket::build(const std::string& account, const Cl
}
bool LogonChallengeResponseParser::parse(network::Packet& packet, LogonChallengeResponse& response) {
// Read opcode (should be LOGON_CHALLENGE)
uint8_t opcode = packet.readUInt8();
if (opcode != static_cast<uint8_t>(AuthOpcode::LOGON_CHALLENGE)) {
LOG_ERROR("Invalid opcode in LOGON_CHALLENGE response: ", (int)opcode);
return false;
}
// Note: opcode byte already consumed by handlePacket()
// Unknown byte
// Unknown/protocol byte
packet.readUInt8();
// Status
@ -180,12 +171,7 @@ network::Packet LogonProofPacket::build(const std::vector<uint8_t>& A,
}
bool LogonProofResponseParser::parse(network::Packet& packet, LogonProofResponse& response) {
// Read opcode (should be LOGON_PROOF)
uint8_t opcode = packet.readUInt8();
if (opcode != static_cast<uint8_t>(AuthOpcode::LOGON_PROOF)) {
LOG_ERROR("Invalid opcode in LOGON_PROOF response: ", (int)opcode);
return false;
}
// Note: opcode byte already consumed by handlePacket()
// Status
response.status = packet.readUInt8();
@ -222,12 +208,7 @@ network::Packet RealmListPacket::build() {
}
bool RealmListResponseParser::parse(network::Packet& packet, RealmListResponse& response) {
// Read opcode (should be REALM_LIST)
uint8_t opcode = packet.readUInt8();
if (opcode != static_cast<uint8_t>(AuthOpcode::REALM_LIST)) {
LOG_ERROR("Invalid opcode in REALM_LIST response: ", (int)opcode);
return false;
}
// Note: opcode byte already consumed by handlePacket()
// Packet size (2 bytes) - we already know the size, skip it
uint16_t packetSize = packet.readUInt16();