/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Session.h" #include "BattlenetRpcErrorCodes.h" #include "ByteConverter.h" #include "CryptoRandom.h" #include "DatabaseEnv.h" #include "Errors.h" #include "IPLocation.h" #include "QueryCallback.h" #include "LoginRESTService.h" #include "ProtobufJSON.h" #include "RealmList.h" #include "ServiceDispatcher.h" #include "RealmList.pb.h" #include void Battlenet::Session::AccountInfo::LoadResult(PreparedQueryResult result) { // ba.id, ba.email, ba.locked, ba.lock_country, ba.last_ip, ba.LoginTicketExpiry, bab.unbandate > UNIX_TIMESTAMP() OR bab.unbandate = bab.bandate, bab.unbandate = bab.bandate FROM battlenet_accounts ba LEFT JOIN battlenet_account_bans bab WHERE email = ? Field* fields = result->Fetch(); Id = fields[0].GetUInt32(); Login = fields[1].GetString(); IsLockedToIP = fields[2].GetBool(); LockCountry = fields[3].GetString(); LastIP = fields[4].GetString(); LoginTicketExpiry = fields[5].GetUInt32(); IsBanned = fields[6].GetUInt64() != 0; IsPermanenetlyBanned = fields[7].GetUInt64() != 0; static uint32 const GameAccountFieldsOffset = 8; do { GameAccounts[result->Fetch()[GameAccountFieldsOffset].GetUInt32()].LoadResult(result->Fetch() + GameAccountFieldsOffset); } while (result->NextRow()); } void Battlenet::Session::GameAccountInfo::LoadResult(Field* fields) { // a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel Id = fields[0].GetUInt32(); Name = fields[1].GetString(); UnbanDate = fields[2].GetUInt32(); IsPermanenetlyBanned = fields[3].GetUInt32() != 0; IsBanned = IsPermanenetlyBanned || UnbanDate > time(nullptr); SecurityLevel = AccountTypes(fields[4].GetUInt8()); std::size_t hashPos = Name.find('#'); if (hashPos != std::string::npos) DisplayName = std::string("WoW") + Name.substr(hashPos + 1); else DisplayName = Name; } Battlenet::Session::Session(boost::asio::ip::tcp::socket&& socket) : BattlenetSocket(std::move(socket)), _accountInfo(new AccountInfo()), _gameAccountInfo(nullptr), _locale(), _os(), _build(0), _ipCountry(), _authed(false), _requestToken(0) { _headerLengthBuffer.Resize(2); } Battlenet::Session::~Session() { } void Battlenet::Session::AsyncHandshake() { underlying_stream().async_handshake(boost::asio::ssl::stream_base::server, std::bind(&Session::HandshakeHandler, shared_from_this(), std::placeholders::_1)); } void Battlenet::Session::Start() { std::string ip_address = GetRemoteIpAddress().to_string(); TC_LOG_TRACE("session", "{} Accepted connection", GetClientInfo()); // Verify that this IP is not in the ip_banned table LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS)); LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO); stmt->setString(0, ip_address); _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&Battlenet::Session::CheckIpCallback, this, std::placeholders::_1))); } void Battlenet::Session::CheckIpCallback(PreparedQueryResult result) { if (result) { bool banned = false; do { Field* fields = result->Fetch(); if (fields[0].GetUInt64() != 0) banned = true; } while (result->NextRow()); if (banned) { TC_LOG_DEBUG("session", "{} tries to log in using banned IP!", GetClientInfo()); CloseSocket(); return; } } AsyncHandshake(); } bool Battlenet::Session::Update() { if (!BattlenetSocket::Update()) return false; _queryProcessor.ProcessReadyCallbacks(); return true; } void Battlenet::Session::AsyncWrite(MessageBuffer* packet) { if (!IsOpen()) return; QueuePacket(std::move(*packet)); } void Battlenet::Session::SendResponse(uint32 token, pb::Message const* response) { Header header; header.set_token(token); header.set_service_id(0xFE); header.set_size(response->ByteSize()); uint16 headerSize = header.ByteSize(); EndianConvertReverse(headerSize); MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + response->GetCachedSize()); packet.Write(&headerSize, sizeof(headerSize)); uint8* ptr = packet.GetWritePointer(); packet.WriteCompleted(header.GetCachedSize()); header.SerializePartialToArray(ptr, header.GetCachedSize()); ptr = packet.GetWritePointer(); packet.WriteCompleted(response->GetCachedSize()); response->SerializeToArray(ptr, response->GetCachedSize()); AsyncWrite(&packet); } void Battlenet::Session::SendResponse(uint32 token, uint32 status) { Header header; header.set_token(token); header.set_status(status); header.set_service_id(0xFE); uint16 headerSize = header.ByteSize(); EndianConvertReverse(headerSize); MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize()); packet.Write(&headerSize, sizeof(headerSize)); uint8* ptr = packet.GetWritePointer(); packet.WriteCompleted(header.GetCachedSize()); header.SerializeToArray(ptr, header.GetCachedSize()); AsyncWrite(&packet); } void Battlenet::Session::SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const* request) { Header header; header.set_service_id(0); header.set_service_hash(serviceHash); header.set_method_id(methodId); header.set_size(request->ByteSize()); header.set_token(_requestToken++); uint16 headerSize = header.ByteSize(); EndianConvertReverse(headerSize); MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + request->GetCachedSize()); packet.Write(&headerSize, sizeof(headerSize)); uint8* ptr = packet.GetWritePointer(); packet.WriteCompleted(header.GetCachedSize()); header.SerializeToArray(ptr, header.GetCachedSize()); ptr = packet.GetWritePointer(); packet.WriteCompleted(request->GetCachedSize()); request->SerializeToArray(ptr, request->GetCachedSize()); AsyncWrite(&packet); } uint32 Battlenet::Session::HandleLogon(authentication::v1::LogonRequest const* logonRequest, std::function& continuation) { if (logonRequest->program() != "WoW") { TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with game other than WoW (using {})!", GetClientInfo(), logonRequest->program()); return ERROR_BAD_PROGRAM; } if (logonRequest->platform() != "Win" && logonRequest->platform() != "Wn64" && logonRequest->platform() != "Mc64") { TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in from an unsupported platform (using {})!", GetClientInfo(), logonRequest->platform()); return ERROR_BAD_PLATFORM; } if (!IsValidLocale(GetLocaleByName(logonRequest->locale()))) { TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with unsupported locale (using {})!", GetClientInfo(), logonRequest->locale()); return ERROR_BAD_LOCALE; } _locale = logonRequest->locale(); _os = logonRequest->platform(); _build = logonRequest->application_version(); if (logonRequest->has_cached_web_credentials()) return VerifyWebCredentials(logonRequest->cached_web_credentials(), continuation); boost::asio::ip::tcp::endpoint const& endpoint = sLoginService.GetAddressForClient(GetRemoteIpAddress()); challenge::v1::ChallengeExternalRequest externalChallenge; externalChallenge.set_payload_type("web_auth_url"); externalChallenge.set_payload(Trinity::StringFormat("https://{}:{}/bnetserver/login/", endpoint.address().to_string(), endpoint.port())); Service(this).OnExternalChallenge(&externalChallenge); return ERROR_OK; } uint32 Battlenet::Session::HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const* verifyWebCredentialsRequest, std::function& continuation) { if (verifyWebCredentialsRequest->has_web_credentials()) return VerifyWebCredentials(verifyWebCredentialsRequest->web_credentials(), continuation); return ERROR_DENIED; } uint32 Battlenet::Session::HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const* request, std::function& continuation) { if (!_authed) return ERROR_DENIED; if (request->program() != 0x576F57) { auto asPrintable = [](char c) { return std::isprint(c) ? c : ' '; }; TC_LOG_DEBUG("session", "[Battlenet::HandleGenerateWebCredentials] {} attempted to generate web cretentials with game other than WoW (using {}{}{}{})!", GetClientInfo(), asPrintable((request->program() >> 24) & 0xFF), asPrintable((request->program() >> 16) & 0xFF), asPrintable((request->program() >> 8) & 0xFF), asPrintable(request->program() & 0xFF)); return ERROR_BAD_PROGRAM; } LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_EXISTING_AUTHENTICATION_BY_ID); stmt->setUInt32(0, _accountInfo->Id); _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([this, asyncContinuation = std::move(continuation)](PreparedQueryResult result) { // just send existing credentials back (not the best but it works for now with them being stored in db) Battlenet::Services::Authentication asyncContinuationService(this); authentication::v1::GenerateWebCredentialsResponse response; response.set_web_credentials((*result)[0].GetCString()); asyncContinuation(&asyncContinuationService, ERROR_OK, &response); })); return ERROR_OK; } uint32 Battlenet::Session::VerifyWebCredentials(std::string const& webCredentials, std::function& continuation) { if (webCredentials.empty()) return ERROR_DENIED; LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_INFO); stmt->setString(0, webCredentials); std::function asyncContinuation = std::move(continuation); std::shared_ptr accountInfo = std::make_shared(); _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithChainingPreparedCallback([this, accountInfo, asyncContinuation](QueryCallback& callback, PreparedQueryResult result) { Battlenet::Services::Authentication asyncContinuationService(this); NoData response; if (!result) { asyncContinuation(&asyncContinuationService, ERROR_DENIED, &response); return; } accountInfo->LoadResult(result); if (accountInfo->LoginTicketExpiry < time(nullptr)) { asyncContinuation(&asyncContinuationService, ERROR_TIMED_OUT, &response); return; } LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID); stmt->setUInt32(0, accountInfo->Id); callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); }) .WithChainingPreparedCallback([accountInfo](QueryCallback& callback, PreparedQueryResult characterCountsResult) { if (characterCountsResult) { do { Field* fields = characterCountsResult->Fetch(); accountInfo->GameAccounts[fields[0].GetUInt32()] .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8(); } while (characterCountsResult->NextRow()); } LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS); stmt->setUInt32(0, accountInfo->Id); callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt)); }) .WithPreparedCallback([this, accountInfo, asyncContinuation](PreparedQueryResult lastPlayerCharactersResult) { if (lastPlayerCharactersResult) { do { Field* fields = lastPlayerCharactersResult->Fetch(); Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() }; Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()] .LastPlayedCharacters[realmId.GetSubRegionAddress()]; lastPlayedCharacter.RealmId = realmId; lastPlayedCharacter.CharacterName = fields[4].GetString(); lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64(); lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32(); } while (lastPlayerCharactersResult->NextRow()); } _accountInfo = accountInfo; Battlenet::Services::Authentication asyncContinuationService(this); NoData response; std::string ip_address = GetRemoteIpAddress().to_string(); // If the IP is 'locked', check that the player comes indeed from the correct IP address if (_accountInfo->IsLockedToIP) { TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to IP - '{}' is logging in from '{}'", _accountInfo->Login, _accountInfo->LastIP, ip_address); if (_accountInfo->LastIP != ip_address) { asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response); return; } } else { if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ip_address)) _ipCountry = location->CountryCode; TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to ip", _accountInfo->Login); if (_accountInfo->LockCountry.empty() || _accountInfo->LockCountry == "00") TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to country", _accountInfo->Login); else if (!_accountInfo->LockCountry.empty() && !_ipCountry.empty()) { TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to country: '{}' Player country is '{}'", _accountInfo->Login, _accountInfo->LockCountry, _ipCountry); if (_ipCountry != _accountInfo->LockCountry) { asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response); return; } } } // If the account is banned, reject the logon attempt if (_accountInfo->IsBanned) { if (_accountInfo->IsPermanenetlyBanned) { TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Banned account {} tried to login!", GetClientInfo(), _accountInfo->Login); asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_BANNED, &response); return; } else { TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Temporarily banned account {} tried to login!", GetClientInfo(), _accountInfo->Login); asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_SUSPENDED, &response); return; } } authentication::v1::LogonResult logonResult; logonResult.set_error_code(0); logonResult.mutable_account_id()->set_low(_accountInfo->Id); logonResult.mutable_account_id()->set_high(UI64LIT(0x100000000000000)); for (auto itr = _accountInfo->GameAccounts.begin(); itr != _accountInfo->GameAccounts.end(); ++itr) { EntityId* gameAccountId = logonResult.add_game_account_id(); gameAccountId->set_low(itr->second.Id); gameAccountId->set_high(UI64LIT(0x200000200576F57)); } if (!_ipCountry.empty()) logonResult.set_geoip_country(_ipCountry); std::array k = Trinity::Crypto::GetRandomBytes<64>(); logonResult.set_session_key(k.data(), 64); _authed = true; asyncContinuation(&asyncContinuationService, ERROR_OK, &response); Service(this).OnLogonComplete(&logonResult); })); return ERROR_OK; } uint32 Battlenet::Session::HandleGetAccountState(account::v1::GetAccountStateRequest const* request, account::v1::GetAccountStateResponse* response) { if (!_authed) return ERROR_DENIED; if (request->options().field_privacy_info()) { response->mutable_state()->mutable_privacy_info()->set_is_using_rid(false); response->mutable_state()->mutable_privacy_info()->set_is_visible_for_view_friends(false); response->mutable_state()->mutable_privacy_info()->set_is_hidden_from_friend_finder(true); response->mutable_tags()->set_privacy_info_tag(0xD7CA834D); } return ERROR_OK; } uint32 Battlenet::Session::HandleGetGameAccountState(account::v1::GetGameAccountStateRequest const* request, account::v1::GetGameAccountStateResponse* response) { if (!_authed) return ERROR_DENIED; if (request->options().field_game_level_info()) { auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low()); if (itr != _accountInfo->GameAccounts.end()) { response->mutable_state()->mutable_game_level_info()->set_name(itr->second.DisplayName); response->mutable_state()->mutable_game_level_info()->set_program(5730135); // WoW } response->mutable_tags()->set_game_level_info_tag(0x5C46D483); } if (request->options().field_game_status()) { auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low()); if (itr != _accountInfo->GameAccounts.end()) { response->mutable_state()->mutable_game_status()->set_is_suspended(itr->second.IsBanned); response->mutable_state()->mutable_game_status()->set_is_banned(itr->second.IsPermanenetlyBanned); response->mutable_state()->mutable_game_status()->set_suspension_expires(uint64(itr->second.UnbanDate) * 1000000); } response->mutable_state()->mutable_game_status()->set_program(5730135); // WoW response->mutable_tags()->set_game_status_tag(0x98B75F99); } return ERROR_OK; } std::unordered_map const Battlenet::Session::ClientRequestHandlers = { { "Command_RealmListTicketRequest_v1", &Battlenet::Session::GetRealmListTicket }, { "Command_LastCharPlayedRequest_v1", &Battlenet::Session::GetLastCharPlayed }, { "Command_RealmListRequest_v1", &Battlenet::Session::GetRealmList }, { "Command_RealmJoinRequest_v1", &Battlenet::Session::JoinRealm }, }; uint32 Battlenet::Session::HandleProcessClientRequest(game_utilities::v1::ClientRequest const* request, game_utilities::v1::ClientResponse* response) { if (!_authed) return ERROR_DENIED; Attribute const* command = nullptr; std::unordered_map params; auto removeSuffix = [](std::string const& string) -> std::string { size_t pos = string.rfind('_'); if (pos != std::string::npos) return string.substr(0, pos); return string; }; for (int32 i = 0; i < request->attribute_size(); ++i) { Attribute const& attr = request->attribute(i); if (strstr(attr.name().c_str(), "Command_") == attr.name().c_str()) { command = &attr; params[removeSuffix(attr.name())] = &attr.value(); } else params[attr.name()] = &attr.value(); } if (!command) { TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with no command.", GetClientInfo()); return ERROR_RPC_MALFORMED_REQUEST; } auto itr = ClientRequestHandlers.find(removeSuffix(command->name())); if (itr == ClientRequestHandlers.end()) { TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with unknown command {}.", GetClientInfo(), removeSuffix(command->name())); return ERROR_RPC_NOT_IMPLEMENTED; } return (this->*itr->second)(params, response); } static Variant const* GetParam(std::unordered_map const& params, char const* paramName) { auto itr = params.find(paramName); return itr != params.end() ? itr->second : nullptr; } uint32 Battlenet::Session::GetRealmListTicket(std::unordered_map const& params, game_utilities::v1::ClientResponse* response) { if (Variant const* identity = GetParam(params, "Param_Identity")) { ::JSON::RealmList::RealmListTicketIdentity data; std::size_t jsonStart = identity->blob_value().find(':'); if (jsonStart != std::string::npos && ::JSON::Deserialize(identity->blob_value().substr(jsonStart + 1), &data)) { auto itr = _accountInfo->GameAccounts.find(data.gameaccountid()); if (itr != _accountInfo->GameAccounts.end()) _gameAccountInfo = &itr->second; } } if (!_gameAccountInfo) return ERROR_UTIL_SERVER_INVALID_IDENTITY_ARGS; if (_gameAccountInfo->IsPermanenetlyBanned) return ERROR_GAME_ACCOUNT_BANNED; else if (_gameAccountInfo->IsBanned) return ERROR_GAME_ACCOUNT_SUSPENDED; bool clientInfoOk = false; if (Variant const* clientInfo = GetParam(params, "Param_ClientInfo")) { ::JSON::RealmList::RealmListTicketClientInformation data; std::size_t jsonStart = clientInfo->blob_value().find(':'); if (jsonStart != std::string::npos && ::JSON::Deserialize(clientInfo->blob_value().substr(jsonStart + 1), &data)) { if (_clientSecret.size() == data.info().secret().size()) { clientInfoOk = true; memcpy(_clientSecret.data(), data.info().secret().data(), _clientSecret.size()); } } } if (!clientInfoOk) return ERROR_WOW_SERVICES_DENIED_REALM_LIST_TICKET; LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_BNET_LAST_LOGIN_INFO); stmt->setString(0, GetRemoteIpAddress().to_string()); stmt->setUInt8(1, GetLocaleByName(_locale)); stmt->setString(2, _os); stmt->setUInt32(3, _accountInfo->Id); LoginDatabase.Execute(stmt); Attribute* attribute = response->add_attribute(); attribute->set_name("Param_RealmListTicket"); attribute->mutable_value()->set_blob_value("AuthRealmListTicket"); return ERROR_OK; } uint32 Battlenet::Session::GetLastCharPlayed(std::unordered_map const& params, game_utilities::v1::ClientResponse* response) { if (Variant const* subRegion = GetParam(params, "Command_LastCharPlayedRequest_v1")) { auto lastPlayerChar = _gameAccountInfo->LastPlayedCharacters.find(subRegion->string_value()); if (lastPlayerChar != _gameAccountInfo->LastPlayedCharacters.end()) { std::vector compressed = sRealmList->GetRealmEntryJSON(lastPlayerChar->second.RealmId, _build); if (compressed.empty()) return ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE; Attribute* attribute = response->add_attribute(); attribute->set_name("Param_RealmEntry"); attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size()); attribute = response->add_attribute(); attribute->set_name("Param_CharacterName"); attribute->mutable_value()->set_string_value(lastPlayerChar->second.CharacterName); attribute = response->add_attribute(); attribute->set_name("Param_CharacterGUID"); attribute->mutable_value()->set_blob_value(&lastPlayerChar->second.CharacterGUID, sizeof(lastPlayerChar->second.CharacterGUID)); attribute = response->add_attribute(); attribute->set_name("Param_LastPlayedTime"); attribute->mutable_value()->set_int_value(int32(lastPlayerChar->second.LastPlayedTime)); } return ERROR_OK; } return ERROR_UTIL_SERVER_UNKNOWN_REALM; } uint32 Battlenet::Session::GetRealmList(std::unordered_map const& params, game_utilities::v1::ClientResponse* response) { if (!_gameAccountInfo) return ERROR_USER_SERVER_BAD_WOW_ACCOUNT; std::string subRegionId; if (Variant const* subRegion = GetParam(params, "Command_RealmListRequest_v1")) subRegionId = subRegion->string_value(); std::vector compressed = sRealmList->GetRealmList(_build, subRegionId); if (compressed.empty()) return ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE; Attribute* attribute = response->add_attribute(); attribute->set_name("Param_RealmList"); attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size()); ::JSON::RealmList::RealmCharacterCountList realmCharacterCounts; for (auto const& characterCount : _gameAccountInfo->CharacterCounts) { ::JSON::RealmList::RealmCharacterCountEntry* countEntry = realmCharacterCounts.add_counts(); countEntry->set_wowrealmaddress(characterCount.first); countEntry->set_count(characterCount.second); } std::string json = "JSONRealmCharacterCountList:" + ::JSON::Serialize(realmCharacterCounts); uLongf compressedLength = compressBound(json.length()); compressed.resize(4 + compressedLength); *reinterpret_cast(compressed.data()) = json.length() + 1; if (compress(compressed.data() + 4, &compressedLength, reinterpret_cast(json.c_str()), json.length() + 1) != Z_OK) return ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE; attribute = response->add_attribute(); attribute->set_name("Param_CharacterCountList"); attribute->mutable_value()->set_blob_value(compressed.data(), compressedLength + 4); return ERROR_OK; } uint32 Battlenet::Session::JoinRealm(std::unordered_map const& params, game_utilities::v1::ClientResponse* response) { if (Variant const* realmAddress = GetParam(params, "Param_RealmAddress")) return sRealmList->JoinRealm(realmAddress->uint_value(), _build, GetRemoteIpAddress(), _clientSecret, GetLocaleByName(_locale), _os, _gameAccountInfo->Name, response); return ERROR_WOW_SERVICES_INVALID_JOIN_TICKET; } uint32 Battlenet::Session::HandleGetAllValuesForAttribute(game_utilities::v1::GetAllValuesForAttributeRequest const* request, game_utilities::v1::GetAllValuesForAttributeResponse* response) { if (!_authed) return ERROR_DENIED; if (request->attribute_key().find("Command_RealmListRequest_v1") == 0) { sRealmList->WriteSubRegions(response); return ERROR_OK; } return ERROR_RPC_NOT_IMPLEMENTED; } void Battlenet::Session::HandshakeHandler(boost::system::error_code const& error) { if (error) { TC_LOG_ERROR("session", "{} SSL Handshake failed {}", GetClientInfo(), error.message()); CloseSocket(); return; } AsyncRead(); } template inline bool PartialProcessPacket(Battlenet::Session* session, MessageBuffer& inputBuffer) { MessageBuffer& buffer = session->*outputBuffer; // We have full read header, now check the data payload if (buffer.GetRemainingSpace() > 0) { // need more data in the payload std::size_t readDataSize = std::min(inputBuffer.GetActiveSize(), buffer.GetRemainingSpace()); buffer.Write(inputBuffer.GetReadPointer(), readDataSize); inputBuffer.ReadCompleted(readDataSize); } if (buffer.GetRemainingSpace() > 0) { // Couldn't receive the whole data this time. ASSERT(inputBuffer.GetActiveSize() == 0); return false; } // just received fresh new payload if (!(session->*processMethod)()) { session->CloseSocket(); return false; } return true; } void Battlenet::Session::ReadHandler() { if (!IsOpen()) return; MessageBuffer& packet = GetReadBuffer(); while (packet.GetActiveSize() > 0) { if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderLengthHandler, &Battlenet::Session::_headerLengthBuffer>(this, packet)) break; if (!PartialProcessPacket<&Battlenet::Session::ReadHeaderHandler, &Battlenet::Session::_headerBuffer>(this, packet)) break; if (!PartialProcessPacket<&Battlenet::Session::ReadDataHandler, &Battlenet::Session::_packetBuffer>(this, packet)) break; _headerLengthBuffer.Reset(); _headerBuffer.Reset(); } AsyncRead(); } bool Battlenet::Session::ReadHeaderLengthHandler() { uint16 len = *reinterpret_cast(_headerLengthBuffer.GetReadPointer()); EndianConvertReverse(len); _headerBuffer.Resize(len); return true; } bool Battlenet::Session::ReadHeaderHandler() { Header header; if (!header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize())) return false; _packetBuffer.Resize(header.size()); return true; } bool Battlenet::Session::ReadDataHandler() { Header header; bool parseSuccess = header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize()); ASSERT(parseSuccess); if (header.service_id() != 0xFE) { sServiceDispatcher.Dispatch(this, header.service_hash(), header.token(), header.method_id(), std::move(_packetBuffer)); } else { auto itr = _responseCallbacks.find(header.token()); if (itr != _responseCallbacks.end()) { itr->second(std::move(_packetBuffer)); _responseCallbacks.erase(header.token()); } else _packetBuffer.Reset(); } return true; } std::string Battlenet::Session::GetClientInfo() const { std::ostringstream stream; stream << '[' << GetRemoteIpAddress() << ':' << GetRemotePort(); if (_accountInfo && !_accountInfo->Login.empty()) stream << ", Account: " << _accountInfo->Login; if (_gameAccountInfo) stream << ", Game account: " << _gameAccountInfo->Name; stream << ']'; return stream.str(); }