mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix world server authentication for AzerothCore
- Use 6-byte CMSG header (uint16 size + uint32 opcode) for client-to-server - Enable RC4 encryption immediately after sending AUTH_SESSION - Encrypt all 6 header bytes to match AzerothCore expectations - Add 8-byte addon info (addonCount + clientTime) for proper parsing - Fix CharCreateResult enum to use correct WoW 3.3.5a response codes - Add CHAR_NAME_* error codes for name validation messages - Allow character list refresh from CHAR_LIST_RECEIVED state
This commit is contained in:
parent
b2a1fbc42c
commit
82a44659b4
4 changed files with 272 additions and 76 deletions
|
|
@ -50,7 +50,8 @@ public:
|
|||
const std::string& accountName,
|
||||
uint32_t clientSeed,
|
||||
const std::vector<uint8_t>& sessionKey,
|
||||
uint32_t serverSeed);
|
||||
uint32_t serverSeed,
|
||||
uint32_t realmId = 1);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
|
@ -83,29 +84,30 @@ public:
|
|||
* SMSG_AUTH_RESPONSE result codes
|
||||
*/
|
||||
enum class AuthResult : uint8_t {
|
||||
OK = 0x00, // Success, proceed to character screen
|
||||
FAILED = 0x01, // Generic failure
|
||||
REJECT = 0x02, // Reject
|
||||
BAD_SERVER_PROOF = 0x03, // Bad server proof
|
||||
UNAVAILABLE = 0x04, // Unavailable
|
||||
SYSTEM_ERROR = 0x05, // System error
|
||||
BILLING_ERROR = 0x06, // Billing error
|
||||
BILLING_EXPIRED = 0x07, // Billing expired
|
||||
VERSION_MISMATCH = 0x08, // Version mismatch
|
||||
UNKNOWN_ACCOUNT = 0x09, // Unknown account
|
||||
INCORRECT_PASSWORD = 0x0A, // Incorrect password
|
||||
SESSION_EXPIRED = 0x0B, // Session expired
|
||||
SERVER_SHUTTING_DOWN = 0x0C, // Server shutting down
|
||||
ALREADY_LOGGING_IN = 0x0D, // Already logging in
|
||||
LOGIN_SERVER_NOT_FOUND = 0x0E, // Login server not found
|
||||
WAIT_QUEUE = 0x0F, // Wait queue
|
||||
BANNED = 0x10, // Banned
|
||||
ALREADY_ONLINE = 0x11, // Already online
|
||||
NO_TIME = 0x12, // No game time
|
||||
DB_BUSY = 0x13, // DB busy
|
||||
SUSPENDED = 0x14, // Suspended
|
||||
PARENTAL_CONTROL = 0x15, // Parental control
|
||||
LOCKED_ENFORCED = 0x16 // Account locked
|
||||
// TrinityCore/AzerothCore auth response codes (3.3.5a)
|
||||
OK = 0x0C, // Success, proceed to character screen
|
||||
FAILED = 0x0D, // Generic failure
|
||||
REJECT = 0x0E, // Reject
|
||||
BAD_SERVER_PROOF = 0x0F, // Bad server proof
|
||||
UNAVAILABLE = 0x10, // Unavailable
|
||||
SYSTEM_ERROR = 0x11, // System error
|
||||
BILLING_ERROR = 0x12, // Billing error
|
||||
BILLING_EXPIRED = 0x13, // Billing expired
|
||||
VERSION_MISMATCH = 0x14, // Version mismatch
|
||||
UNKNOWN_ACCOUNT = 0x15, // Unknown account
|
||||
INCORRECT_PASSWORD = 0x16, // Incorrect password
|
||||
SESSION_EXPIRED = 0x17, // Session expired
|
||||
SERVER_SHUTTING_DOWN = 0x18, // Server shutting down
|
||||
ALREADY_LOGGING_IN = 0x19, // Already logging in
|
||||
LOGIN_SERVER_NOT_FOUND = 0x1A, // Login server not found
|
||||
WAIT_QUEUE = 0x1B, // Wait queue
|
||||
BANNED = 0x1C, // Banned
|
||||
ALREADY_ONLINE = 0x1D, // Already online
|
||||
NO_TIME = 0x1E, // No game time
|
||||
DB_BUSY = 0x1F, // DB busy
|
||||
SUSPENDED = 0x20, // Suspended
|
||||
PARENTAL_CONTROL = 0x21, // Parental control
|
||||
LOCKED_ENFORCED = 0x22 // Account locked
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -172,15 +174,54 @@ public:
|
|||
// Character Creation
|
||||
// ============================================================
|
||||
|
||||
// WoW 3.3.5a ResponseCodes for character creation (from ResponseCodes enum)
|
||||
enum class CharCreateResult : uint8_t {
|
||||
SUCCESS = 0x00,
|
||||
ERROR = 0x01,
|
||||
FAILED = 0x02,
|
||||
NAME_IN_USE = 0x03,
|
||||
DISABLED = 0x04,
|
||||
PVP_TEAMS_VIOLATION = 0x05,
|
||||
SERVER_LIMIT = 0x06,
|
||||
ACCOUNT_LIMIT = 0x07,
|
||||
// Success codes
|
||||
SUCCESS = 0x2F, // CHAR_CREATE_SUCCESS
|
||||
|
||||
// CHAR_CREATE error codes
|
||||
IN_PROGRESS = 0x2E, // CHAR_CREATE_IN_PROGRESS
|
||||
ERROR = 0x30, // CHAR_CREATE_ERROR
|
||||
FAILED = 0x31, // CHAR_CREATE_FAILED
|
||||
NAME_IN_USE = 0x32, // CHAR_CREATE_NAME_IN_USE
|
||||
DISABLED = 0x33, // CHAR_CREATE_DISABLED
|
||||
PVP_TEAMS_VIOLATION = 0x34, // CHAR_CREATE_PVP_TEAMS_VIOLATION
|
||||
SERVER_LIMIT = 0x35, // CHAR_CREATE_SERVER_LIMIT
|
||||
ACCOUNT_LIMIT = 0x36, // CHAR_CREATE_ACCOUNT_LIMIT
|
||||
SERVER_QUEUE = 0x37, // CHAR_CREATE_SERVER_QUEUE
|
||||
ONLY_EXISTING = 0x38, // CHAR_CREATE_ONLY_EXISTING
|
||||
EXPANSION = 0x39, // CHAR_CREATE_EXPANSION
|
||||
EXPANSION_CLASS = 0x3A, // CHAR_CREATE_EXPANSION_CLASS
|
||||
LEVEL_REQUIREMENT = 0x3B, // CHAR_CREATE_LEVEL_REQUIREMENT
|
||||
UNIQUE_CLASS_LIMIT = 0x3C, // CHAR_CREATE_UNIQUE_CLASS_LIMIT
|
||||
CHARACTER_IN_GUILD = 0x3D, // CHAR_CREATE_CHARACTER_IN_GUILD
|
||||
RESTRICTED_RACECLASS = 0x3E, // CHAR_CREATE_RESTRICTED_RACECLASS
|
||||
CHARACTER_CHOOSE_RACE= 0x3F, // CHAR_CREATE_CHARACTER_CHOOSE_RACE
|
||||
CHARACTER_ARENA_LEADER=0x40, // CHAR_CREATE_CHARACTER_ARENA_LEADER
|
||||
CHARACTER_DELETE_MAIL= 0x41, // CHAR_CREATE_CHARACTER_DELETE_MAIL
|
||||
CHARACTER_SWAP_FACTION=0x42, // CHAR_CREATE_CHARACTER_SWAP_FACTION
|
||||
CHARACTER_RACE_ONLY = 0x43, // CHAR_CREATE_CHARACTER_RACE_ONLY
|
||||
CHARACTER_GOLD_LIMIT = 0x44, // CHAR_CREATE_CHARACTER_GOLD_LIMIT
|
||||
FORCE_LOGIN = 0x45, // CHAR_CREATE_FORCE_LOGIN
|
||||
|
||||
// CHAR_NAME error codes (name validation failures)
|
||||
NAME_SUCCESS = 0x57, // CHAR_NAME_SUCCESS
|
||||
NAME_FAILURE = 0x58, // CHAR_NAME_FAILURE
|
||||
NAME_NO_NAME = 0x59, // CHAR_NAME_NO_NAME
|
||||
NAME_TOO_SHORT = 0x5A, // CHAR_NAME_TOO_SHORT
|
||||
NAME_TOO_LONG = 0x5B, // CHAR_NAME_TOO_LONG
|
||||
NAME_INVALID_CHARACTER = 0x5C, // CHAR_NAME_INVALID_CHARACTER
|
||||
NAME_MIXED_LANGUAGES = 0x5D, // CHAR_NAME_MIXED_LANGUAGES
|
||||
NAME_PROFANE = 0x5E, // CHAR_NAME_PROFANE
|
||||
NAME_RESERVED = 0x5F, // CHAR_NAME_RESERVED
|
||||
NAME_INVALID_APOSTROPHE = 0x60, // CHAR_NAME_INVALID_APOSTROPHE
|
||||
NAME_MULTIPLE_APOSTROPHES = 0x61, // CHAR_NAME_MULTIPLE_APOSTROPHES
|
||||
NAME_THREE_CONSECUTIVE = 0x62, // CHAR_NAME_THREE_CONSECUTIVE (98 decimal)
|
||||
NAME_INVALID_SPACE = 0x63, // CHAR_NAME_INVALID_SPACE
|
||||
NAME_CONSECUTIVE_SPACES = 0x64, // CHAR_NAME_CONSECUTIVE_SPACES
|
||||
NAME_RUSSIAN_CONSECUTIVE_SILENT = 0x65, // CHAR_NAME_RUSSIAN_CONSECUTIVE_SILENT_CHARACTERS
|
||||
NAME_RUSSIAN_SILENT_AT_BEGIN_OR_END = 0x66, // CHAR_NAME_RUSSIAN_SILENT_CHARACTER_AT_BEGINNING_OR_END
|
||||
NAME_DECLENSION_DOESNT_MATCH = 0x67, // CHAR_NAME_DECLENSION_DOESNT_MATCH_BASE_NAME
|
||||
};
|
||||
|
||||
struct CharCreateData {
|
||||
|
|
|
|||
|
|
@ -1115,16 +1115,17 @@ void GameHandler::sendAuthSession() {
|
|||
|
||||
LOG_DEBUG("CMSG_AUTH_SESSION packet size: ", packet.getSize(), " bytes");
|
||||
|
||||
// Send packet (NOT encrypted yet)
|
||||
// Send packet (unencrypted - this is the last unencrypted packet)
|
||||
socket->send(packet);
|
||||
|
||||
// CRITICAL: Initialize encryption AFTER sending AUTH_SESSION
|
||||
// but BEFORE receiving AUTH_RESPONSE
|
||||
LOG_INFO("Initializing RC4 header encryption...");
|
||||
// Enable encryption IMMEDIATELY after sending AUTH_SESSION
|
||||
// AzerothCore enables encryption before sending AUTH_RESPONSE,
|
||||
// so we need to be ready to decrypt the response
|
||||
LOG_INFO("Enabling encryption immediately after AUTH_SESSION");
|
||||
socket->initEncryption(sessionKey);
|
||||
|
||||
setState(WorldState::AUTH_SENT);
|
||||
LOG_INFO("CMSG_AUTH_SESSION sent, encryption initialized, waiting for response...");
|
||||
LOG_INFO("CMSG_AUTH_SESSION sent, encryption enabled, waiting for AUTH_RESPONSE...");
|
||||
}
|
||||
|
||||
void GameHandler::handleAuthResponse(network::Packet& packet) {
|
||||
|
|
@ -1143,7 +1144,9 @@ void GameHandler::handleAuthResponse(network::Packet& packet) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Authentication successful!
|
||||
// Encryption was already enabled after sending AUTH_SESSION
|
||||
LOG_INFO("AUTH_RESPONSE OK - world authentication successful");
|
||||
|
||||
setState(WorldState::AUTHENTICATED);
|
||||
|
||||
LOG_INFO("========================================");
|
||||
|
|
@ -1154,6 +1157,9 @@ void GameHandler::handleAuthResponse(network::Packet& packet) {
|
|||
|
||||
setState(WorldState::READY);
|
||||
|
||||
// Request character list automatically
|
||||
requestCharacterList();
|
||||
|
||||
// Call success callback
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
|
|
@ -1166,7 +1172,8 @@ void GameHandler::requestCharacterList() {
|
|||
setState(WorldState::CHAR_LIST_RECEIVED);
|
||||
return;
|
||||
}
|
||||
if (state != WorldState::READY && state != WorldState::AUTHENTICATED) {
|
||||
if (state != WorldState::READY && state != WorldState::AUTHENTICATED &&
|
||||
state != WorldState::CHAR_LIST_RECEIVED) {
|
||||
LOG_WARNING("Cannot request character list in state: ", (int)state);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1335,13 +1342,37 @@ void GameHandler::handleCharCreateResponse(network::Packet& packet) {
|
|||
} else {
|
||||
std::string msg;
|
||||
switch (data.result) {
|
||||
case CharCreateResult::ERROR: msg = "Server error"; break;
|
||||
case CharCreateResult::FAILED: msg = "Creation failed"; break;
|
||||
case CharCreateResult::NAME_IN_USE: msg = "Name already in use"; break;
|
||||
case CharCreateResult::DISABLED: msg = "Character creation disabled"; break;
|
||||
case CharCreateResult::PVP_TEAMS_VIOLATION: msg = "PvP faction violation"; break;
|
||||
case CharCreateResult::SERVER_LIMIT: msg = "Server character limit reached"; break;
|
||||
case CharCreateResult::ACCOUNT_LIMIT: msg = "Account character limit reached"; break;
|
||||
default: msg = "Character creation failed"; break;
|
||||
case CharCreateResult::SERVER_QUEUE: msg = "Server is queued"; break;
|
||||
case CharCreateResult::ONLY_EXISTING: msg = "Only existing characters allowed"; break;
|
||||
case CharCreateResult::EXPANSION: msg = "Expansion required"; break;
|
||||
case CharCreateResult::EXPANSION_CLASS: msg = "Expansion required for this class"; break;
|
||||
case CharCreateResult::LEVEL_REQUIREMENT: msg = "Level requirement not met"; break;
|
||||
case CharCreateResult::UNIQUE_CLASS_LIMIT: msg = "Unique class limit reached"; break;
|
||||
case CharCreateResult::RESTRICTED_RACECLASS: msg = "Race/class combination not allowed"; break;
|
||||
// Name validation errors
|
||||
case CharCreateResult::NAME_FAILURE: msg = "Invalid name"; break;
|
||||
case CharCreateResult::NAME_NO_NAME: msg = "Please enter a name"; break;
|
||||
case CharCreateResult::NAME_TOO_SHORT: msg = "Name is too short"; break;
|
||||
case CharCreateResult::NAME_TOO_LONG: msg = "Name is too long"; break;
|
||||
case CharCreateResult::NAME_INVALID_CHARACTER: msg = "Name contains invalid characters"; break;
|
||||
case CharCreateResult::NAME_MIXED_LANGUAGES: msg = "Name mixes languages"; break;
|
||||
case CharCreateResult::NAME_PROFANE: msg = "Name contains profanity"; break;
|
||||
case CharCreateResult::NAME_RESERVED: msg = "Name is reserved"; break;
|
||||
case CharCreateResult::NAME_INVALID_APOSTROPHE: msg = "Invalid apostrophe in name"; break;
|
||||
case CharCreateResult::NAME_MULTIPLE_APOSTROPHES: msg = "Name has multiple apostrophes"; break;
|
||||
case CharCreateResult::NAME_THREE_CONSECUTIVE: msg = "Name has 3+ consecutive same letters"; break;
|
||||
case CharCreateResult::NAME_INVALID_SPACE: msg = "Invalid space in name"; break;
|
||||
case CharCreateResult::NAME_CONSECUTIVE_SPACES: msg = "Name has consecutive spaces"; break;
|
||||
default: msg = "Unknown error (code " + std::to_string(static_cast<int>(data.result)) + ")"; break;
|
||||
}
|
||||
LOG_WARNING("Character creation failed: ", msg);
|
||||
LOG_WARNING("Character creation failed: ", msg, " (code=", static_cast<int>(data.result), ")");
|
||||
if (charCreateCallback_) {
|
||||
charCreateCallback_(false, msg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace wowee {
|
||||
namespace game {
|
||||
|
|
@ -14,7 +15,8 @@ network::Packet AuthSessionPacket::build(uint32_t build,
|
|||
const std::string& accountName,
|
||||
uint32_t clientSeed,
|
||||
const std::vector<uint8_t>& sessionKey,
|
||||
uint32_t serverSeed) {
|
||||
uint32_t serverSeed,
|
||||
uint32_t realmId) {
|
||||
if (sessionKey.size() != 40) {
|
||||
LOG_ERROR("Invalid session key size: ", sessionKey.size(), " (expected 40)");
|
||||
}
|
||||
|
|
@ -37,34 +39,80 @@ network::Packet AuthSessionPacket::build(uint32_t build,
|
|||
// Create packet (opcode will be added by WorldSocket)
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_AUTH_SESSION));
|
||||
|
||||
// AzerothCore 3.3.5a expects this exact field order:
|
||||
// Build, LoginServerID, Account, LoginServerType, LocalChallenge,
|
||||
// RegionID, BattlegroupID, RealmID, DosResponse, Digest, AddonInfo
|
||||
|
||||
// Build number (uint32, little-endian)
|
||||
packet.writeUInt32(build);
|
||||
|
||||
// Unknown uint32 (always 0)
|
||||
// Login server ID (uint32, always 0)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Account name (null-terminated string)
|
||||
packet.writeString(upperAccount);
|
||||
|
||||
// Unknown uint32 (always 0)
|
||||
// Login server type (uint32, always 0)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Client seed (uint32, little-endian)
|
||||
// LocalChallenge / Client seed (uint32, little-endian)
|
||||
packet.writeUInt32(clientSeed);
|
||||
|
||||
// Unknown fields (5x uint32, all zeros)
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// Region ID (uint32)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Battlegroup ID (uint32)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Realm ID (uint32)
|
||||
packet.writeUInt32(realmId);
|
||||
LOG_DEBUG(" Realm ID: ", realmId);
|
||||
|
||||
// DOS response (uint64) - required for 3.x
|
||||
packet.writeUInt32(0);
|
||||
packet.writeUInt32(0);
|
||||
|
||||
// Authentication hash/digest (20 bytes)
|
||||
packet.writeBytes(authHash.data(), authHash.size());
|
||||
|
||||
// Addon info - compressed block with 0 addons
|
||||
// AzerothCore format: uint32 decompressedSize + zlib compressed data
|
||||
// Decompressed format: uint32 addonCount + [addons...] + uint32 clientTime
|
||||
uint8_t addonData[8] = {
|
||||
0, 0, 0, 0, // addon count = 0
|
||||
0, 0, 0, 0 // client time = 0
|
||||
};
|
||||
uint32_t decompressedSize = 8;
|
||||
|
||||
// Compress with zlib
|
||||
uLongf compressedSize = compressBound(decompressedSize);
|
||||
std::vector<uint8_t> compressed(compressedSize);
|
||||
int ret = compress(compressed.data(), &compressedSize, addonData, decompressedSize);
|
||||
if (ret == Z_OK) {
|
||||
compressed.resize(compressedSize);
|
||||
// Write decompressedSize, then compressed bytes
|
||||
packet.writeUInt32(decompressedSize);
|
||||
packet.writeBytes(compressed.data(), compressed.size());
|
||||
LOG_DEBUG("Addon info: decompressedSize=", decompressedSize,
|
||||
" compressedSize=", compressedSize);
|
||||
} else {
|
||||
LOG_ERROR("zlib compress failed with code: ", ret);
|
||||
packet.writeUInt32(0);
|
||||
}
|
||||
|
||||
// Authentication hash (20 bytes)
|
||||
packet.writeBytes(authHash.data(), authHash.size());
|
||||
|
||||
// Addon CRC (uint32, can be 0)
|
||||
packet.writeUInt32(0);
|
||||
|
||||
LOG_INFO("CMSG_AUTH_SESSION packet built: ", packet.getSize(), " bytes");
|
||||
|
||||
// Dump full packet for protocol debugging
|
||||
const auto& data = packet.getData();
|
||||
std::string hexDump;
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x ", data[i]);
|
||||
hexDump += buf;
|
||||
if ((i + 1) % 16 == 0) hexDump += "\n";
|
||||
}
|
||||
LOG_DEBUG("CMSG_AUTH_SESSION full dump:\n", hexDump);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +268,25 @@ network::Packet CharCreatePacket::build(const CharCreateData& data) {
|
|||
packet.writeUInt8(data.facialHair);
|
||||
packet.writeUInt8(0); // outfitId, always 0
|
||||
|
||||
LOG_DEBUG("Built CMSG_CHAR_CREATE: name=", data.name);
|
||||
LOG_DEBUG("Built CMSG_CHAR_CREATE: name=", data.name,
|
||||
" race=", static_cast<int>(data.race),
|
||||
" class=", static_cast<int>(data.characterClass),
|
||||
" gender=", static_cast<int>(data.gender),
|
||||
" skin=", static_cast<int>(data.skin),
|
||||
" face=", static_cast<int>(data.face),
|
||||
" hair=", static_cast<int>(data.hairStyle),
|
||||
" hairColor=", static_cast<int>(data.hairColor),
|
||||
" facial=", static_cast<int>(data.facialHair));
|
||||
|
||||
// Dump full packet for protocol debugging
|
||||
const auto& pktData = packet.getData();
|
||||
std::string hexDump;
|
||||
for (size_t i = 0; i < pktData.size(); ++i) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x ", pktData[i]);
|
||||
hexDump += buf;
|
||||
}
|
||||
LOG_DEBUG("CMSG_CHAR_CREATE full dump: ", hexDump);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,40 +91,79 @@ void WorldSocket::send(const Packet& packet) {
|
|||
|
||||
const auto& data = packet.getData();
|
||||
uint16_t opcode = packet.getOpcode();
|
||||
uint16_t size = static_cast<uint16_t>(data.size());
|
||||
uint16_t payloadLen = static_cast<uint16_t>(data.size());
|
||||
|
||||
// WotLK 3.3.5 CMSG header (6 bytes total):
|
||||
// - size (2 bytes, big-endian) = payloadLen + 4 (opcode is 4 bytes for CMSG)
|
||||
// - opcode (4 bytes, little-endian)
|
||||
// Note: Client-to-server uses 4-byte opcode, server-to-client uses 2-byte
|
||||
uint16_t sizeField = payloadLen + 4;
|
||||
|
||||
// Build header (6 bytes for outgoing): size(2) + opcode(4)
|
||||
std::vector<uint8_t> sendData;
|
||||
sendData.reserve(6 + size);
|
||||
sendData.reserve(6 + payloadLen);
|
||||
|
||||
// Size (2 bytes, big-endian) - payload size only, does NOT include header
|
||||
sendData.push_back((size >> 8) & 0xFF);
|
||||
sendData.push_back(size & 0xFF);
|
||||
// Size (2 bytes, big-endian)
|
||||
uint8_t size_hi = (sizeField >> 8) & 0xFF;
|
||||
uint8_t size_lo = sizeField & 0xFF;
|
||||
sendData.push_back(size_hi);
|
||||
sendData.push_back(size_lo);
|
||||
|
||||
// Opcode (4 bytes, big-endian)
|
||||
sendData.push_back((opcode >> 24) & 0xFF);
|
||||
sendData.push_back((opcode >> 16) & 0xFF);
|
||||
sendData.push_back((opcode >> 8) & 0xFF);
|
||||
// Opcode (4 bytes, little-endian)
|
||||
sendData.push_back(opcode & 0xFF);
|
||||
sendData.push_back((opcode >> 8) & 0xFF);
|
||||
sendData.push_back(0); // High bytes are 0 for all WoW opcodes
|
||||
sendData.push_back(0);
|
||||
|
||||
// Encrypt header if encryption is enabled
|
||||
// Debug: log encryption state and header
|
||||
LOG_DEBUG("SEND opcode=0x", std::hex, opcode, std::dec,
|
||||
" encryptionEnabled=", encryptionEnabled,
|
||||
" header=[", std::hex,
|
||||
(int)sendData[0], " ", (int)sendData[1], " ",
|
||||
(int)sendData[2], " ", (int)sendData[3], " ",
|
||||
(int)sendData[4], " ", (int)sendData[5], std::dec, "]",
|
||||
" sizeField=", sizeField, " payloadLen=", payloadLen);
|
||||
|
||||
// Encrypt header if encryption is enabled (all 6 bytes)
|
||||
if (encryptionEnabled) {
|
||||
uint8_t plainHeader[6];
|
||||
memcpy(plainHeader, sendData.data(), 6);
|
||||
encryptCipher.process(sendData.data(), 6);
|
||||
LOG_DEBUG("Encrypted outgoing header: opcode=0x", std::hex, opcode, std::dec);
|
||||
LOG_DEBUG("Encrypted header: plain=[", std::hex,
|
||||
(int)plainHeader[0], " ", (int)plainHeader[1], " ",
|
||||
(int)plainHeader[2], " ", (int)plainHeader[3], " ",
|
||||
(int)plainHeader[4], " ", (int)plainHeader[5], "] -> enc=[",
|
||||
(int)sendData[0], " ", (int)sendData[1], " ",
|
||||
(int)sendData[2], " ", (int)sendData[3], " ",
|
||||
(int)sendData[4], " ", (int)sendData[5], "]", std::dec);
|
||||
}
|
||||
|
||||
// Add payload (unencrypted)
|
||||
sendData.insert(sendData.end(), data.begin(), data.end());
|
||||
|
||||
LOG_DEBUG("Sending world packet: opcode=0x", std::hex, opcode, std::dec,
|
||||
" size=", size, " bytes (", sendData.size(), " total)");
|
||||
" payload=", payloadLen, " bytes (", sendData.size(), " total)");
|
||||
|
||||
// Debug: dump packet bytes for AUTH_SESSION
|
||||
if (opcode == 0x1ED) {
|
||||
std::string hexDump = "AUTH_SESSION raw bytes: ";
|
||||
for (size_t i = 0; i < sendData.size(); ++i) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x ", sendData[i]);
|
||||
hexDump += buf;
|
||||
if ((i + 1) % 32 == 0) hexDump += "\n";
|
||||
}
|
||||
LOG_DEBUG(hexDump);
|
||||
}
|
||||
|
||||
// Send complete packet
|
||||
ssize_t sent = net::portableSend(sockfd, sendData.data(), sendData.size());
|
||||
if (sent < 0) {
|
||||
LOG_ERROR("Send failed: ", net::errorString(net::lastError()));
|
||||
} else if (static_cast<size_t>(sent) != sendData.size()) {
|
||||
LOG_WARNING("Partial send: ", sent, " of ", sendData.size(), " bytes");
|
||||
} else {
|
||||
LOG_DEBUG("Actually sent ", sent, " bytes to server");
|
||||
if (static_cast<size_t>(sent) != sendData.size()) {
|
||||
LOG_WARNING("Partial send: ", sent, " of ", sendData.size(), " bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,12 +206,19 @@ void WorldSocket::tryParsePackets() {
|
|||
decryptCipher.process(header, 4);
|
||||
}
|
||||
|
||||
// Parse header (big-endian)
|
||||
// Parse header
|
||||
// Size: 2 bytes big-endian (includes opcode, so payload = size - 2)
|
||||
uint16_t size = (header[0] << 8) | header[1];
|
||||
uint16_t opcode = (header[2] << 8) | header[3];
|
||||
// Opcode: 2 bytes little-endian
|
||||
uint16_t opcode = header[2] | (header[3] << 8);
|
||||
|
||||
// Total packet size: header(4) + payload(size)
|
||||
size_t totalSize = 4 + size;
|
||||
LOG_DEBUG("RECV encryptionEnabled=", encryptionEnabled,
|
||||
" header=[", std::hex, (int)header[0], " ", (int)header[1], " ",
|
||||
(int)header[2], " ", (int)header[3], std::dec, "]",
|
||||
" -> size=", size, " opcode=0x", std::hex, opcode, std::dec);
|
||||
|
||||
// Total packet size: size field (2) + size value (which includes opcode + payload)
|
||||
size_t totalSize = 2 + size;
|
||||
|
||||
if (receiveBuffer.size() < totalSize) {
|
||||
// Not enough data yet
|
||||
|
|
@ -183,12 +229,23 @@ void WorldSocket::tryParsePackets() {
|
|||
|
||||
// We have a complete packet!
|
||||
LOG_DEBUG("Parsing world packet: opcode=0x", std::hex, opcode, std::dec,
|
||||
" size=", size, " bytes");
|
||||
" size=", size, " totalSize=", totalSize, " bytes");
|
||||
|
||||
// Extract payload (skip header)
|
||||
std::vector<uint8_t> packetData(receiveBuffer.begin() + 4,
|
||||
receiveBuffer.begin() + totalSize);
|
||||
|
||||
// Log first few bytes of payload
|
||||
if (!packetData.empty()) {
|
||||
std::string payloadHex;
|
||||
for (size_t i = 0; i < std::min(packetData.size(), size_t(16)); i++) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%02x ", packetData[i]);
|
||||
payloadHex += buf;
|
||||
}
|
||||
LOG_DEBUG("Payload (first 16 bytes): ", payloadHex);
|
||||
}
|
||||
|
||||
// Create packet with opcode and payload
|
||||
Packet packet(opcode, packetData);
|
||||
|
||||
|
|
@ -208,13 +265,14 @@ void WorldSocket::initEncryption(const std::vector<uint8_t>& sessionKey) {
|
|||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("Initializing world server header encryption");
|
||||
LOG_INFO(">>> ENABLING ENCRYPTION - encryptionEnabled will become true <<<");
|
||||
|
||||
// Convert hardcoded keys to vectors
|
||||
std::vector<uint8_t> encryptKey(ENCRYPT_KEY, ENCRYPT_KEY + 16);
|
||||
std::vector<uint8_t> decryptKey(DECRYPT_KEY, DECRYPT_KEY + 16);
|
||||
|
||||
// Compute HMAC-SHA1(key, sessionKey) for each cipher
|
||||
// Compute HMAC-SHA1(seed, sessionKey) for each cipher
|
||||
// The 16-byte seed is the HMAC key, session key is the message
|
||||
std::vector<uint8_t> encryptHash = auth::Crypto::hmacSHA1(encryptKey, sessionKey);
|
||||
std::vector<uint8_t> decryptHash = auth::Crypto::hmacSHA1(decryptKey, sessionKey);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue