TrinityCore
Loading...
Searching...
No Matches
Session.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "Session.h"
19#include "AccountService.h"
22#include "ByteConverter.h"
23#include "CryptoRandom.h"
24#include "DatabaseEnv.h"
25#include "Errors.h"
26#include "Hash.h"
27#include "IPLocation.h"
29#include "LoginRESTService.h"
30#include "MapUtils.h"
31#include "ProtobufJSON.h"
32#include "QueryCallback.h"
33#include "RealmList.h"
34#include "RealmList.pb.h"
35#include "ServiceDispatcher.h"
36#include "SslContext.h"
37#include "Timezone.h"
38#include "attribute_types.pb.h"
41#include "rpc_types.pb.h"
42#include <rapidjson/document.h>
43#include <zlib.h>
44
46{
47 // 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 = ?
48 Field* fields = result->Fetch();
49 Id = fields[0].GetUInt32();
50 Login = fields[1].GetString();
51 IsLockedToIP = fields[2].GetBool();
52 LockCountry = fields[3].GetString();
53 LastIP = fields[4].GetString();
54 LoginTicketExpiry = fields[5].GetUInt32();
55 IsBanned = fields[6].GetUInt64() != 0;
56 IsPermanenetlyBanned = fields[7].GetUInt64() != 0;
57
58 static constexpr uint32 GameAccountFieldsOffset = 8;
59
60 do
61 {
62 GameAccounts[result->Fetch()[GameAccountFieldsOffset].GetUInt32()].LoadResult(result->Fetch() + GameAccountFieldsOffset);
63
64 } while (result->NextRow());
65}
66
68{
69 // a.id, a.username, ab.unbandate, ab.unbandate = ab.bandate, aa.SecurityLevel
70 Id = fields[0].GetUInt32();
71 Name = fields[1].GetString();
72 UnbanDate = fields[2].GetUInt32();
73 IsPermanenetlyBanned = fields[3].GetUInt32() != 0;
74 IsBanned = IsPermanenetlyBanned || UnbanDate > time(nullptr);
75 SecurityLevel = AccountTypes(fields[4].GetUInt8());
76
77 std::size_t hashPos = Name.find('#');
78 if (hashPos != std::string::npos)
79 DisplayName = std::string("WoW") + Name.substr(hashPos + 1);
80 else
81 DisplayName = Name;
82}
83
90
92
93std::shared_ptr<Battlenet::Session::Socket> Battlenet::Session::CreateSocket(Trinity::Net::IoContextTcpSocket&& socket)
94{
95 return std::make_shared<Socket>(std::move(socket), SslContext::instance());
96}
97
99{
100 TC_LOG_TRACE("session", "{} Accepted connection", GetClientInfo());
101
102 // build initializer chain
103 std::array<std::shared_ptr<Trinity::Net::SocketConnectionInitializer>, 3> initializers =
104 { {
105 std::make_shared<Trinity::Net::IpBanCheckConnectionInitializer<Session>>(this),
106 std::make_shared<Trinity::Net::SslHandshakeConnectionInitializer<Socket>>(_socket.get()),
107 std::make_shared<Trinity::Net::ReadConnectionInitializer<Socket, Session>>(_socket.get(), this),
108 } };
109
111}
112
114{
115 if (!_socket->Update())
116 return false;
117
118 _queryProcessor.ProcessReadyCallbacks();
119
120 return true;
121}
122
124{
125 if (!_socket->IsOpen())
126 return;
127
128 _socket->QueuePacket(std::move(*packet));
129}
130
131void Battlenet::Session::SendResponse(uint32 token, pb::Message const* response)
132{
133 Header header;
134 header.set_token(token);
135 header.set_service_id(0xFE);
136 header.set_size(response->ByteSize());
137
138 uint16 headerSize = header.ByteSize();
139 EndianConvertReverse(headerSize);
140
141 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + response->GetCachedSize());
142 packet.Write(&headerSize, sizeof(headerSize));
143 uint8* ptr = packet.GetWritePointer();
144 packet.WriteCompleted(header.GetCachedSize());
145 header.SerializePartialToArray(ptr, header.GetCachedSize());
146 ptr = packet.GetWritePointer();
147 packet.WriteCompleted(response->GetCachedSize());
148 response->SerializeToArray(ptr, response->GetCachedSize());
149
150 AsyncWrite(&packet);
151}
152
154{
155 Header header;
156 header.set_token(token);
157 header.set_status(status);
158 header.set_service_id(0xFE);
159
160 uint16 headerSize = header.ByteSize();
161 EndianConvertReverse(headerSize);
162
163 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize());
164 packet.Write(&headerSize, sizeof(headerSize));
165 uint8* ptr = packet.GetWritePointer();
166 packet.WriteCompleted(header.GetCachedSize());
167 header.SerializeToArray(ptr, header.GetCachedSize());
168
169 AsyncWrite(&packet);
170}
171
172void Battlenet::Session::SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const* request)
173{
174 Header header;
175 header.set_service_id(0);
176 header.set_service_hash(serviceHash);
177 header.set_method_id(methodId);
178 header.set_size(request->ByteSize());
179 header.set_token(_requestToken++);
180
181 uint16 headerSize = header.ByteSize();
182 EndianConvertReverse(headerSize);
183
184 MessageBuffer packet(sizeof(headerSize) + header.GetCachedSize() + request->GetCachedSize());
185 packet.Write(&headerSize, sizeof(headerSize));
186 uint8* ptr = packet.GetWritePointer();
187 packet.WriteCompleted(header.GetCachedSize());
188 header.SerializeToArray(ptr, header.GetCachedSize());
189 ptr = packet.GetWritePointer();
190 packet.WriteCompleted(request->GetCachedSize());
191 request->SerializeToArray(ptr, request->GetCachedSize());
192
193 AsyncWrite(&packet);
194}
195
197{
198 _queryProcessor.AddCallback(std::move(queryCallback));
199}
200
201uint32 Battlenet::Session::HandleLogon(authentication::v1::LogonRequest const* logonRequest, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
202{
203 if (logonRequest->program() != "WoW")
204 {
205 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with game other than WoW (using {})!", GetClientInfo(), logonRequest->program());
206 return ERROR_BAD_PROGRAM;
207 }
208
209 if (!ClientBuild::Platform::IsValid(logonRequest->platform()))
210 {
211 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in from an unsupported platform (using {})!", GetClientInfo(), logonRequest->platform());
212 return ERROR_BAD_PLATFORM;
213 }
214
215 if (!IsValidLocale(GetLocaleByName(logonRequest->locale())))
216 {
217 TC_LOG_DEBUG("session", "[Battlenet::LogonRequest] {} attempted to log in with unsupported locale (using {})!", GetClientInfo(), logonRequest->locale());
218 return ERROR_BAD_LOCALE;
219 }
220
221 _locale = logonRequest->locale();
222 _os = logonRequest->platform();
223 _build = logonRequest->application_version();
224
225 _timezoneOffset = [&]
226 {
227 if (!logonRequest->has_device_id())
228 return 0min;
229
230 rapidjson::Document doc;
231 doc.Parse(logonRequest->device_id().c_str(), logonRequest->device_id().length());
232 if (doc.HasParseError())
233 return 0min;
234
235 auto itr = doc.FindMember("UTCO");
236 if (itr == doc.MemberEnd())
237 return 0min;
238
239 if (!itr->value.IsUint())
240 return 0min;
241
242 return Trinity::Timezone::GetOffsetByHash(itr->value.GetUint());
243 }();
244
245 if (logonRequest->has_cached_web_credentials())
246 return VerifyWebCredentials(logonRequest->cached_web_credentials(), continuation);
247
249 externalChallenge.set_payload_type("web_auth_url");
250 externalChallenge.set_payload(Trinity::StringFormat("http{}://{}:{}/bnetserver/login/", !SslContext::UsesDevWildcardCertificate() ? "s" : "",
251 sLoginService.GetHostnameForClient(_socket->GetRemoteIpAddress()), sLoginService.GetPort()));
252 Service<challenge::v1::ChallengeListener>(this).OnExternalChallenge(&externalChallenge);
253 return ERROR_OK;
254}
255
256uint32 Battlenet::Session::HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const* verifyWebCredentialsRequest, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
257{
258 if (verifyWebCredentialsRequest->has_web_credentials())
259 return VerifyWebCredentials(verifyWebCredentialsRequest->web_credentials(), continuation);
260
261 return ERROR_DENIED;
262}
263
264uint32 Battlenet::Session::HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const* request, std::function<void(ServiceBase*, uint32, google::protobuf::Message const*)>& continuation)
265{
266 if (!_authed)
267 return ERROR_DENIED;
268
269 switch (request->program())
270 {
271 case 0x417070: // App
272 case 0x576F57: // WoW
273 break;
274 default:
275 {
276 auto asPrintable = [](char c) { return std::isprint(c) ? c : ' '; };
277
278 TC_LOG_DEBUG("session", "[Battlenet::HandleGenerateWebCredentials] {} attempted to generate web cretentials with game other than WoW (using {}{}{}{})!",
279 GetClientInfo(), asPrintable((request->program() >> 24) & 0xFF), asPrintable((request->program() >> 16) & 0xFF),
280 asPrintable((request->program() >> 8) & 0xFF), asPrintable(request->program() & 0xFF));
281 return ERROR_BAD_PROGRAM;
282 }
283 }
284
286 stmt->setUInt32(0, _accountInfo->Id);
287
288 QueueQuery(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback([this, asyncContinuation = std::move(continuation)](PreparedQueryResult result)
289 {
290 // just send existing credentials back (not the best but it works for now with them being stored in db)
291 Battlenet::Services::Authentication asyncContinuationService(this);
292 authentication::v1::GenerateWebCredentialsResponse response;
293 response.set_web_credentials((*result)[0].GetCString());
294 asyncContinuation(&asyncContinuationService, ERROR_OK, &response);
295 }));
296
297 return ERROR_OK;
298}
299
300uint32 Battlenet::Session::VerifyWebCredentials(std::string const& webCredentials, std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)>& continuation)
301{
302 if (webCredentials.empty())
303 return ERROR_DENIED;
304
306 stmt->setString(0, webCredentials);
307
308 std::function<void(ServiceBase*, uint32, ::google::protobuf::Message const*)> asyncContinuation = std::move(continuation);
309 std::shared_ptr<AccountInfo> accountInfo = std::make_shared<AccountInfo>();
310 QueueQuery(LoginDatabase.AsyncQuery(stmt).WithChainingPreparedCallback([this, accountInfo, asyncContinuation](QueryCallback& callback, PreparedQueryResult result)
311 {
312 Battlenet::Services::Authentication asyncContinuationService(this);
313 NoData response;
314 if (!result)
315 {
316 asyncContinuation(&asyncContinuationService, ERROR_DENIED, &response);
317 return;
318 }
319
320 accountInfo->LoadResult(result);
321
322 if (accountInfo->LoginTicketExpiry < time(nullptr))
323 {
324 asyncContinuation(&asyncContinuationService, ERROR_TIMED_OUT, &response);
325 return;
326 }
327
329 stmt->setUInt32(0, accountInfo->Id);
330 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
331 })
332 .WithChainingPreparedCallback([accountInfo](QueryCallback& callback, PreparedQueryResult characterCountsResult)
333 {
334 if (characterCountsResult)
335 {
336 do
337 {
338 Field* fields = characterCountsResult->Fetch();
339 accountInfo->GameAccounts[fields[0].GetUInt32()]
340 .CharacterCounts[Battlenet::RealmHandle{ fields[3].GetUInt8(), fields[4].GetUInt8(), fields[2].GetUInt32() }.GetAddress()] = fields[1].GetUInt8();
341
342 } while (characterCountsResult->NextRow());
343 }
344
346 stmt->setUInt32(0, accountInfo->Id);
347 callback.SetNextQuery(LoginDatabase.AsyncQuery(stmt));
348 })
349 .WithPreparedCallback([this, accountInfo, asyncContinuation](PreparedQueryResult lastPlayerCharactersResult)
350 {
351 if (lastPlayerCharactersResult)
352 {
353 do
354 {
355 Field* fields = lastPlayerCharactersResult->Fetch();
356 Battlenet::RealmHandle realmId{ fields[1].GetUInt8(), fields[2].GetUInt8(), fields[3].GetUInt32() };
357 Battlenet::Session::LastPlayedCharacterInfo& lastPlayedCharacter = accountInfo->GameAccounts[fields[0].GetUInt32()]
358 .LastPlayedCharacters[realmId.GetSubRegionAddress()];
359
360 lastPlayedCharacter.RealmId = realmId;
361 lastPlayedCharacter.CharacterName = fields[4].GetString();
362 lastPlayedCharacter.CharacterGUID = fields[5].GetUInt64();
363 lastPlayedCharacter.LastPlayedTime = fields[6].GetUInt32();
364
365 } while (lastPlayerCharactersResult->NextRow());
366 }
367
368 _accountInfo = accountInfo;
369 Battlenet::Services::Authentication asyncContinuationService(this);
370 NoData response;
371
372 std::string ip_address = _socket->GetRemoteIpAddress().to_string();
373
374 // If the IP is 'locked', check that the player comes indeed from the correct IP address
375 if (_accountInfo->IsLockedToIP)
376 {
377 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to IP - '{}' is logging in from '{}'",
378 _accountInfo->Login, _accountInfo->LastIP, ip_address);
379
380 if (_accountInfo->LastIP != ip_address)
381 {
382 asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response);
383 return;
384 }
385 }
386 else
387 {
388 if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ip_address))
389 _ipCountry = location->CountryCode;
390
391 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to ip", _accountInfo->Login);
392 if (_accountInfo->LockCountry.empty() || _accountInfo->LockCountry == "00")
393 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is not locked to country", _accountInfo->Login);
394 else if (!_accountInfo->LockCountry.empty() && !_ipCountry.empty())
395 {
396 TC_LOG_DEBUG("session", "[Session::HandleVerifyWebCredentials] Account '{}' is locked to country: '{}' Player country is '{}'",
397 _accountInfo->Login, _accountInfo->LockCountry, _ipCountry);
398
399 if (_ipCountry != _accountInfo->LockCountry)
400 {
401 asyncContinuation(&asyncContinuationService, ERROR_RISK_ACCOUNT_LOCKED, &response);
402 return;
403 }
404 }
405 }
406
407 // If the account is banned, reject the logon attempt
408 if (_accountInfo->IsBanned)
409 {
410 if (_accountInfo->IsPermanenetlyBanned)
411 {
412 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
413 asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_BANNED, &response);
414 return;
415 }
416 else
417 {
418 TC_LOG_DEBUG("session", "{} [Session::HandleVerifyWebCredentials] Temporarily banned account {} tried to login!", GetClientInfo(), _accountInfo->Login);
419 asyncContinuation(&asyncContinuationService, ERROR_GAME_ACCOUNT_SUSPENDED, &response);
420 return;
421 }
422 }
423
424 authentication::v1::LogonResult logonResult;
425 logonResult.set_error_code(0);
426 logonResult.mutable_account_id()->set_low(_accountInfo->Id);
427 logonResult.mutable_account_id()->set_high(UI64LIT(0x100000000000000));
428 for (auto const& [id, gameAccountInfo] : accountInfo->GameAccounts)
429 {
430 EntityId* gameAccountId = logonResult.add_game_account_id();
431 gameAccountId->set_low(gameAccountInfo.Id);
432 gameAccountId->set_high(UI64LIT(0x200000200576F57));
433 }
434
435 if (!_ipCountry.empty())
436 logonResult.set_geoip_country(_ipCountry);
437
438 std::array<uint8, 64> k = Trinity::Crypto::GetRandomBytes<64>();
439 logonResult.set_session_key(k.data(), 64);
440
441 _authed = true;
442
443 asyncContinuation(&asyncContinuationService, ERROR_OK, &response);
444 Service<authentication::v1::AuthenticationListener>(this).OnLogonComplete(&logonResult);
445 }));
446
447 return ERROR_OK;
448}
449
451{
452 if (!_authed)
453 return ERROR_DENIED;
454
455 if (request->options().field_privacy_info())
456 {
457 response->mutable_state()->mutable_privacy_info()->set_is_using_rid(false);
458 response->mutable_state()->mutable_privacy_info()->set_is_visible_for_view_friends(false);
459 response->mutable_state()->mutable_privacy_info()->set_is_hidden_from_friend_finder(true);
460
461 response->mutable_tags()->set_privacy_info_tag(0xD7CA834D);
462 }
463
464 return ERROR_OK;
465}
466
468{
469 if (!_authed)
470 return ERROR_DENIED;
471
472 if (request->options().field_game_level_info())
473 {
474 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
475 if (itr != _accountInfo->GameAccounts.end())
476 {
477 response->mutable_state()->mutable_game_level_info()->set_name(itr->second.DisplayName);
478 response->mutable_state()->mutable_game_level_info()->set_program(5730135); // WoW
479 }
480
481 response->mutable_tags()->set_game_level_info_tag(0x5C46D483);
482 }
483
484 if (request->options().field_game_status())
485 {
486 auto itr = _accountInfo->GameAccounts.find(request->game_account_id().low());
487 if (itr != _accountInfo->GameAccounts.end())
488 {
489 response->mutable_state()->mutable_game_status()->set_is_suspended(itr->second.IsBanned);
490 response->mutable_state()->mutable_game_status()->set_is_banned(itr->second.IsPermanenetlyBanned);
491 response->mutable_state()->mutable_game_status()->set_suspension_expires(uint64(itr->second.UnbanDate) * 1000000);
492 }
493
494 response->mutable_state()->mutable_game_status()->set_program(5730135); // WoW
495 response->mutable_tags()->set_game_status_tag(0x98B75F99);
496 }
497
498 return ERROR_OK;
499}
500
501std::unordered_map<std::string, Battlenet::Session::ClientRequestHandler> const Battlenet::Session::ClientRequestHandlers =
502{
503 { "Command_RealmListTicketRequest_v1", &Battlenet::Session::GetRealmListTicket },
504 { "Command_LastCharPlayedRequest_v1", &Battlenet::Session::GetLastCharPlayed },
505 { "Command_RealmListRequest_v1", &Battlenet::Session::GetRealmList },
506 { "Command_RealmJoinRequest_v1", &Battlenet::Session::JoinRealm },
507};
508
510{
511 if (!_authed)
512 return ERROR_DENIED;
513
514 Attribute const* command = nullptr;
515 std::unordered_map<std::string, Variant const*> params;
516 auto removeSuffix = [](std::string const& string) -> std::string
517 {
518 size_t pos = string.rfind('_');
519 if (pos != std::string::npos)
520 return string.substr(0, pos);
521
522 return string;
523 };
524
525 for (int32 i = 0; i < request->attribute_size(); ++i)
526 {
527 Attribute const& attr = request->attribute(i);
528 if (strstr(attr.name().c_str(), "Command_") == attr.name().c_str())
529 {
530 command = &attr;
531 params[removeSuffix(attr.name())] = &attr.value();
532 }
533 else
534 params[attr.name()] = &attr.value();
535 }
536
537 if (!command)
538 {
539 TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with no command.", GetClientInfo());
541 }
542
543 auto itr = ClientRequestHandlers.find(removeSuffix(command->name()));
544 if (itr == ClientRequestHandlers.end())
545 {
546 TC_LOG_ERROR("session.rpc", "{} sent ClientRequest with unknown command {}.", GetClientInfo(), removeSuffix(command->name()));
548 }
549
550 return (this->*itr->second)(params, response);
551}
552
553uint32 Battlenet::Session::GetRealmListTicket(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
554{
555 if (Variant const* identity = Trinity::Containers::MapGetValuePtr(params, "Param_Identity"))
556 {
558 std::size_t jsonStart = identity->blob_value().find(':');
559 if (jsonStart != std::string::npos && ::JSON::Deserialize(identity->blob_value().substr(jsonStart + 1), &data))
560 {
561 auto itr = _accountInfo->GameAccounts.find(data.gameaccountid());
562 if (itr != _accountInfo->GameAccounts.end())
563 _gameAccountInfo = &itr->second;
564 }
565 }
566
567 if (!_gameAccountInfo)
569
570 if (_gameAccountInfo->IsPermanenetlyBanned)
572 else if (_gameAccountInfo->IsBanned)
574
575 bool clientInfoOk = false;
576 if (Variant const* clientInfo = Trinity::Containers::MapGetValuePtr(params, "Param_ClientInfo"))
577 {
579 std::size_t jsonStart = clientInfo->blob_value().find(':');
580 if (jsonStart != std::string::npos && ::JSON::Deserialize(clientInfo->blob_value().substr(jsonStart + 1), &data))
581 {
582 if (_clientSecret.size() == data.info().secret().size())
583 {
584 clientInfoOk = true;
585 memcpy(_clientSecret.data(), data.info().secret().data(), _clientSecret.size());
586 }
587
588 _clientInfo = { .Platform = data.info().platformtype(), .Arch = data.info().clientarch(), .Type = data.info().type() };
589 }
590 }
591
592 if (!clientInfoOk)
594
596 stmt->setString(0, _socket->GetRemoteIpAddress().to_string());
597 stmt->setUInt8(1, GetLocaleByName(_locale));
598 stmt->setString(2, _os);
599 stmt->setUInt32(3, _accountInfo->Id);
600
601 LoginDatabase.Execute(stmt);
602
603 Attribute* attribute = response->add_attribute();
604 attribute->set_name("Param_RealmListTicket");
605 attribute->mutable_value()->set_blob_value("AuthRealmListTicket");
606
607 return ERROR_OK;
608}
609
610uint32 Battlenet::Session::GetLastCharPlayed(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
611{
612 if (Variant const* subRegion = Trinity::Containers::MapGetValuePtr(params, "Command_LastCharPlayedRequest_v1"))
613 {
614 auto lastPlayerChar = _gameAccountInfo->LastPlayedCharacters.find(subRegion->string_value());
615 if (lastPlayerChar != _gameAccountInfo->LastPlayedCharacters.end())
616 {
617 std::vector<uint8> compressed = sRealmList->GetRealmEntryJSON(lastPlayerChar->second.RealmId, _build, _gameAccountInfo->SecurityLevel);
618
619 if (compressed.empty())
621
622 Attribute* attribute = response->add_attribute();
623 attribute->set_name("Param_RealmEntry");
624 attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size());
625
626 attribute = response->add_attribute();
627 attribute->set_name("Param_CharacterName");
628 attribute->mutable_value()->set_string_value(lastPlayerChar->second.CharacterName);
629
630 attribute = response->add_attribute();
631 attribute->set_name("Param_CharacterGUID");
632 attribute->mutable_value()->set_blob_value(&lastPlayerChar->second.CharacterGUID, sizeof(lastPlayerChar->second.CharacterGUID));
633
634 attribute = response->add_attribute();
635 attribute->set_name("Param_LastPlayedTime");
636 attribute->mutable_value()->set_int_value(int32(lastPlayerChar->second.LastPlayedTime));
637 }
638
639 return ERROR_OK;
640 }
641
643}
644
645uint32 Battlenet::Session::GetRealmList(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
646{
647 if (!_gameAccountInfo)
649
650 std::string subRegionId;
651 if (Variant const* subRegion = Trinity::Containers::MapGetValuePtr(params, "Command_RealmListRequest_v1"))
652 subRegionId = subRegion->string_value();
653
654 std::vector<uint8> compressed = sRealmList->GetRealmList(_build, _gameAccountInfo->SecurityLevel, subRegionId);
655
656 if (compressed.empty())
658
659 Attribute* attribute = response->add_attribute();
660 attribute->set_name("Param_RealmList");
661 attribute->mutable_value()->set_blob_value(compressed.data(), compressed.size());
662
664 for (auto const& characterCount : _gameAccountInfo->CharacterCounts)
665 {
666 ::JSON::RealmList::RealmCharacterCountEntry* countEntry = realmCharacterCounts.add_counts();
667 countEntry->set_wowrealmaddress(characterCount.first);
668 countEntry->set_count(characterCount.second);
669 }
670
671 std::string json = "JSONRealmCharacterCountList:" + ::JSON::Serialize(realmCharacterCounts);
672
673 uLongf compressedLength = compressBound(json.length());
674 compressed.resize(4 + compressedLength);
675 *reinterpret_cast<uint32*>(compressed.data()) = json.length() + 1;
676
677 if (compress(compressed.data() + 4, &compressedLength, reinterpret_cast<uint8 const*>(json.c_str()), json.length() + 1) != Z_OK)
679
680 attribute = response->add_attribute();
681 attribute->set_name("Param_CharacterCountList");
682 attribute->mutable_value()->set_blob_value(compressed.data(), compressedLength + 4);
683 return ERROR_OK;
684}
685
686uint32 Battlenet::Session::JoinRealm(std::unordered_map<std::string, Variant const*> const& params, game_utilities::v1::ClientResponse* response)
687{
688 if (Variant const* realmAddress = Trinity::Containers::MapGetValuePtr(params, "Param_RealmAddress"))
689 return sRealmList->JoinRealm(realmAddress->uint_value(), _build, _clientInfo, _socket->GetRemoteIpAddress(), _clientSecret, GetLocaleByName(_locale),
690 _os, _timezoneOffset, _gameAccountInfo->Name, _gameAccountInfo->SecurityLevel, response);
691
693}
694
696{
697 if (!_authed)
698 return ERROR_DENIED;
699
700 if (request->attribute_key().find("Command_RealmListRequest_v1") == 0)
701 {
702 sRealmList->WriteSubRegions(response);
703 return ERROR_OK;
704 }
705
707}
708
709template<bool(Battlenet::Session::*processMethod)(), MessageBuffer Battlenet::Session::*outputBuffer>
711{
712 MessageBuffer& buffer = session->*outputBuffer;
713
714 // We have full read header, now check the data payload
715 if (buffer.GetRemainingSpace() > 0)
716 {
717 // need more data in the payload
718 std::size_t readDataSize = std::min(inputBuffer.GetActiveSize(), buffer.GetRemainingSpace());
719 buffer.Write(inputBuffer.GetReadPointer(), readDataSize);
720 inputBuffer.ReadCompleted(readDataSize);
721 }
722 else
723 return { }; // go to next buffer
724
725 if (buffer.GetRemainingSpace() > 0)
726 {
727 // Couldn't receive the whole data this time.
728 ASSERT(inputBuffer.GetActiveSize() == 0);
730 }
731
732 // just received fresh new payload
733 if (!(session->*processMethod)())
734 {
735 session->CloseSocket();
737 }
738
739 return { }; // go to next buffer
740}
741
743{
744 MessageBuffer& packet = _socket->GetReadBuffer();
745 while (packet.GetActiveSize() > 0)
746 {
747 if (Optional<Trinity::Net::SocketReadCallbackResult> partialResult = PartialProcessPacket<&Session::ReadHeaderLengthHandler, &Session::_headerLengthBuffer>(this, packet))
748 return *partialResult;
749
750 if (Optional<Trinity::Net::SocketReadCallbackResult> partialResult = PartialProcessPacket<&Session::ReadHeaderHandler, &Session::_headerBuffer>(this, packet))
751 return *partialResult;
752
753 if (Optional<Trinity::Net::SocketReadCallbackResult> partialResult = PartialProcessPacket<&Session::ReadDataHandler, &Session::_packetBuffer>(this, packet))
754 return *partialResult;
755
756 _headerLengthBuffer.Reset();
757 _headerBuffer.Reset();
758 }
759
761}
762
764{
765 uint16 len = *reinterpret_cast<uint16*>(_headerLengthBuffer.GetReadPointer());
767 _headerBuffer.Resize(len);
768 return true;
769}
770
772{
773 Header header;
774 if (!header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize()))
775 return false;
776
777 _packetBuffer.Resize(header.size());
778 return true;
779}
780
782{
783 Header header;
784 bool parseSuccess = header.ParseFromArray(_headerBuffer.GetReadPointer(), _headerBuffer.GetActiveSize());
785 ASSERT(parseSuccess);
786
787 if (header.service_id() != 0xFE)
788 {
789 sServiceDispatcher.Dispatch(this, header.service_hash(), header.token(), header.method_id(), std::move(_packetBuffer));
790 }
791 else
792 {
793 auto itr = _responseCallbacks.find(header.token());
794 if (itr != _responseCallbacks.end())
795 {
796 itr->second(std::move(_packetBuffer));
797 _responseCallbacks.erase(header.token());
798 }
799 else
800 _packetBuffer.Reset();
801 }
802
803 return true;
804}
805
807{
808 std::ostringstream stream;
809 stream << '[' << _socket->GetRemoteIpAddress() << ':' << _socket->GetRemotePort();
810 if (_accountInfo && !_accountInfo->Login.empty())
811 stream << ", Account: " << _accountInfo->Login;
812
813 if (_gameAccountInfo)
814 stream << ", Game account: " << _gameAccountInfo->Name;
815
816 stream << ']';
817
818 return std::move(stream).str();
819}
@ ERROR_GAME_ACCOUNT_BANNED
@ ERROR_RISK_ACCOUNT_LOCKED
@ ERROR_RPC_NOT_IMPLEMENTED
@ ERROR_RPC_MALFORMED_REQUEST
@ ERROR_WOW_SERVICES_INVALID_JOIN_TICKET
@ ERROR_UTIL_SERVER_INVALID_IDENTITY_ARGS
@ ERROR_BAD_PROGRAM
@ ERROR_UTIL_SERVER_FAILED_TO_SERIALIZE_RESPONSE
@ ERROR_USER_SERVER_BAD_WOW_ACCOUNT
@ ERROR_GAME_ACCOUNT_SUSPENDED
@ ERROR_BAD_LOCALE
@ ERROR_WOW_SERVICES_DENIED_REALM_LIST_TICKET
@ ERROR_BAD_PLATFORM
@ ERROR_UTIL_SERVER_UNKNOWN_REALM
@ ERROR_TIMED_OUT
void EndianConvertReverse(T &)
LocaleConstant GetLocaleByName(std::string_view name)
Definition Common.cpp:36
constexpr bool IsValidLocale(LocaleConstant locale)
Definition Common.h:98
AccountTypes
Definition Common.h:42
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< LoginDatabaseConnection > LoginDatabase
Accessor to the realm/login database.
uint8_t uint8
Definition Define.h:156
int32_t int32
Definition Define.h:150
uint64_t uint64
Definition Define.h:153
#define UI64LIT(N)
Definition Define.h:139
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
std::unordered_set< uint32 > params[2]
#define ASSERT
Definition Errors.h:80
#define sIPLocation
Definition IPLocation.h:56
#define TC_LOG_DEBUG(filterType__, message__,...)
Definition Log.h:181
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define TC_LOG_TRACE(filterType__, message__,...)
Definition Log.h:178
@ LOGIN_SEL_BNET_CHARACTER_COUNTS_BY_BNET_ID
@ LOGIN_UPD_BNET_LAST_LOGIN_INFO
@ LOGIN_SEL_BNET_EXISTING_AUTHENTICATION_BY_ID
@ LOGIN_SEL_BNET_ACCOUNT_INFO
@ LOGIN_SEL_BNET_LAST_PLAYER_CHARACTERS
#define sLoginService
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
#define sRealmList
Definition RealmList.h:93
#define sServiceDispatcher
static Optional< Trinity::Net::SocketReadCallbackResult > PartialProcessPacket(Battlenet::Session *session, MessageBuffer &inputBuffer)
Definition Session.cpp:710
uint32 VerifyWebCredentials(std::string const &webCredentials, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition Session.cpp:300
uint32 JoinRealm(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition Session.cpp:686
std::string GetClientInfo() const
Definition Session.cpp:806
ClientBuild::VariantId _clientInfo
Definition Session.h:181
uint32 HandleGenerateWebCredentials(authentication::v1::GenerateWebCredentialsRequest const *request, std::function< void(ServiceBase *, uint32, google::protobuf::Message const *)> &continuation)
Definition Session.cpp:264
static std::unordered_map< std::string, ClientRequestHandler > const ClientRequestHandlers
Definition Session.h:501
void SendResponse(uint32 token, pb::Message const *response)
Definition Session.cpp:131
Trinity::Net::SocketReadCallbackResult ReadHandler()
Definition Session.cpp:742
uint32 GetRealmList(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition Session.cpp:645
uint32 HandleProcessClientRequest(game_utilities::v1::ClientRequest const *request, game_utilities::v1::ClientResponse *response)
Definition Session.cpp:509
uint32 HandleVerifyWebCredentials(authentication::v1::VerifyWebCredentialsRequest const *verifyWebCredentialsRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition Session.cpp:256
std::string _ipCountry
Definition Session.h:184
uint32 GetLastCharPlayed(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition Session.cpp:610
static std::shared_ptr< Socket > CreateSocket(Trinity::Net::IoContextTcpSocket &&socket)
Definition Session.cpp:93
uint32 HandleGetGameAccountState(account::v1::GetGameAccountStateRequest const *request, account::v1::GetGameAccountStateResponse *response)
Definition Session.cpp:467
Session(Trinity::Net::IoContextTcpSocket &&socket)
Definition Session.cpp:84
bool ReadHeaderLengthHandler()
Definition Session.cpp:763
void AsyncWrite(MessageBuffer *packet)
Definition Session.cpp:123
uint32 HandleGetAllValuesForAttribute(game_utilities::v1::GetAllValuesForAttributeRequest const *request, game_utilities::v1::GetAllValuesForAttributeResponse *response)
Definition Session.cpp:695
std::array< uint8, 32 > _clientSecret
Definition Session.h:186
GameAccountInfo * _gameAccountInfo
Definition Session.h:176
uint32 HandleLogon(authentication::v1::LogonRequest const *logonRequest, std::function< void(ServiceBase *, uint32, ::google::protobuf::Message const *)> &continuation)
Definition Session.cpp:201
void QueueQuery(QueryCallback &&queryCallback)
Definition Session.cpp:196
void SendRequest(uint32 serviceHash, uint32 methodId, pb::Message const *request, std::function< void(MessageBuffer)> callback)
Definition Session.h:127
uint32 _requestToken
Definition Session.h:193
std::string _locale
Definition Session.h:178
std::shared_ptr< Socket > _socket
Definition Session.h:170
std::string _os
Definition Session.h:179
Minutes _timezoneOffset
Definition Session.h:182
MessageBuffer _headerLengthBuffer
Definition Session.h:171
uint32 HandleGetAccountState(account::v1::GetAccountStateRequest const *request, account::v1::GetAccountStateResponse *response)
Definition Session.cpp:450
bool ReadHeaderHandler()
Definition Session.cpp:771
uint32 GetRealmListTicket(std::unordered_map< std::string, Variant const * > const &params, game_utilities::v1::ClientResponse *response)
Definition Session.cpp:553
std::shared_ptr< AccountInfo > _accountInfo
Definition Session.h:175
static bool UsesDevWildcardCertificate()
Definition SslContext.h:32
static boost::asio::ssl::context & instance()
Class used to access individual fields of database query result.
Definition Field.h:94
uint64 GetUInt64() const noexcept
Definition Field.cpp:71
bool GetBool() const noexcept
Definition Field.h:102
uint32 GetUInt32() const noexcept
Definition Field.cpp:57
uint8 GetUInt8() const noexcept
Definition Field.cpp:29
std::string GetString() const noexcept
Definition Field.cpp:113
const ::std::string & secret() const
inline ::google::protobuf::uint32 type() const
inline ::google::protobuf::uint32 clientarch() const
inline ::google::protobuf::uint32 platformtype() const
void set_wowrealmaddress(::google::protobuf::uint32 value)
void set_count(::google::protobuf::uint32 value)
inline ::JSON::RealmList::RealmCharacterCountEntry * add_counts()
const ::JSON::RealmList::ClientInformation & info() const
inline ::google::protobuf::uint32 gameaccountid() const
void Resize(size_type bytes)
size_type GetRemainingSpace() const
void ReadCompleted(size_type bytes)
void WriteCompleted(size_type bytes)
uint8 * GetReadPointer()
size_type GetActiveSize() const
uint8 * GetWritePointer()
void Write(void const *data, std::size_t size)
void setString(uint8 index, std::string &&value)
void setUInt32(uint8 index, uint32 value)
void setUInt8(uint8 index, uint8 value)
void SetNextQuery(QueryCallback &&next)
const ::bgs::protocol::Variant & value() const
const ::std::string & name() const
inline ::bgs::protocol::Variant * mutable_value()
void set_name(const ::std::string &value)
void set_high(::google::protobuf::uint64 value)
void set_low(::google::protobuf::uint64 value)
inline ::google::protobuf::uint32 method_id() const
void set_method_id(::google::protobuf::uint32 value)
int GetCachedSize() const
inline ::google::protobuf::uint32 service_id() const
void set_token(::google::protobuf::uint32 value)
void set_service_id(::google::protobuf::uint32 value)
void set_status(::google::protobuf::uint32 value)
inline ::google::protobuf::uint32 size() const
inline ::google::protobuf::uint32 service_hash() const
void set_service_hash(::google::protobuf::uint32 value)
inline ::google::protobuf::uint32 token() const
void set_size(::google::protobuf::uint32 value)
void set_int_value(::google::protobuf::int64 value)
void set_blob_value(const ::std::string &value)
void set_string_value(const ::std::string &value)
TC_SHARED_API bool IsValid(std::string_view platform)
TC_SHARED_API bool Deserialize(std::string const &json, google::protobuf::Message *message)
TC_SHARED_API std::string Serialize(google::protobuf::Message const &message)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition MapUtils.h:37
SocketReadCallbackResult
Definition Socket.h:49
boost::asio::basic_stream_socket< boost::asio::ip::tcp, boost::asio::io_context::executor_type > IoContextTcpSocket
Definition Socket.h:40
Minutes GetOffsetByHash(uint32 hash)
Definition Timezone.cpp:130
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
STL namespace.
std::unordered_map< uint32, GameAccountInfo > GameAccounts
Definition Session.h:108
void LoadResult(PreparedQueryResult result)
Definition Session.cpp:45
void LoadResult(Field const *fields)
Definition Session.cpp:67
static std::shared_ptr< SocketConnectionInitializer > & SetupChain(std::span< std::shared_ptr< SocketConnectionInitializer > > initializers)