diff --git a/include/auth/auth_opcodes.hpp b/include/auth/auth_opcodes.hpp index aea87341..08399143 100644 --- a/include/auth/auth_opcodes.hpp +++ b/include/auth/auth_opcodes.hpp @@ -35,6 +35,15 @@ enum class AuthResult : uint8_t { LOCK_ENFORCED = 0x10, TRIAL_EXPIRED = 0x11, BATTLE_NET = 0x12, + ANTI_INDULGENCE = 0x13, + EXPIRED = 0x14, + NO_GAME_ACCOUNT = 0x15, + CHARGEBACK = 0x16, + IGR_WITHOUT_BNET = 0x17, + GAME_ACCOUNT_LOCKED = 0x18, + UNLOCKABLE_LOCK = 0x19, + CONVERSION_REQUIRED = 0x20, + DISCONNECTED = 0xFF, }; const char* getAuthResultString(AuthResult result); diff --git a/src/auth/auth_opcodes.cpp b/src/auth/auth_opcodes.cpp index 3ec8b99d..338805cf 100644 --- a/src/auth/auth_opcodes.cpp +++ b/src/auth/auth_opcodes.cpp @@ -31,6 +31,17 @@ const char* getAuthResultString(AuthResult result) { case AuthResult::LOCK_ENFORCED: return "Account locked - check your email"; case AuthResult::TRIAL_EXPIRED: return "Trial period has expired"; case AuthResult::BATTLE_NET: return "Battle.net error"; + case AuthResult::ANTI_INDULGENCE: return "Anti-indulgence time limit reached"; + case AuthResult::EXPIRED: return "Account subscription has expired"; + case AuthResult::NO_GAME_ACCOUNT: return "No game account found - create one on the server website"; + case AuthResult::CHARGEBACK: return "Account locked due to chargeback"; + case AuthResult::IGR_WITHOUT_BNET: + return "Internet Game Room access denied - account may require " + "registration on the server website"; + case AuthResult::GAME_ACCOUNT_LOCKED: return "Game account is locked"; + case AuthResult::UNLOCKABLE_LOCK: return "Account is locked and cannot be unlocked"; + case AuthResult::CONVERSION_REQUIRED: return "Account conversion required"; + case AuthResult::DISCONNECTED: return "Disconnected from server"; default: snprintf(authResultBuf, sizeof(authResultBuf), "Server rejected login (error code 0x%02X)", diff --git a/src/auth/auth_packets.cpp b/src/auth/auth_packets.cpp index 31c94953..0c9c41a2 100644 --- a/src/auth/auth_packets.cpp +++ b/src/auth/auth_packets.cpp @@ -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(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(info.game.c_str()), - std::min(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(4, str.length()); ++i) { + buf[i] = static_cast(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(info.platform.c_str()), - std::min(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(info.os.c_str()), - std::min(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(info.locale.c_str()), - std::min(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(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& A, } bool LogonProofResponseParser::parse(network::Packet& packet, LogonProofResponse& response) { - // Read opcode (should be LOGON_PROOF) - uint8_t opcode = packet.readUInt8(); - if (opcode != static_cast(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(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(); diff --git a/src/network/tcp_socket.cpp b/src/network/tcp_socket.cpp index 15380bfa..a2a373e8 100644 --- a/src/network/tcp_socket.cpp +++ b/src/network/tcp_socket.cpp @@ -224,14 +224,14 @@ size_t TCPSocket::getExpectedPacketSize(uint8_t opcode) { return 0; // Need more data to determine case 0x01: // LOGON_PROOF response - // opcode(1) + status(1) + M2(20) = 22 bytes on success - // opcode(1) + status(1) = 2 bytes on failure + // Success: opcode(1) + status(1) + M2(20) + accountFlags(4) + surveyId(4) + loginFlags(2) = 32 + // Failure: opcode(1) + status(1) + padding(2) = 4 if (receiveBuffer.size() >= 2) { uint8_t status = receiveBuffer[1]; if (status == 0x00) { - return 22; // Success + return 32; // Success } else { - return 2; // Failure + return 4; // Failure } } return 0; // Need more data