feat(Core): replace ACE network with Boost.Asio (#6574)

This commit is contained in:
Kargatum 2021-07-16 15:43:56 +07:00 committed by GitHub
parent 7449496bb5
commit 8568c4fb33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 3242 additions and 4712 deletions

View file

@ -28,4 +28,6 @@ void Acore::Banner::Show(char const* applicationName, void(*log)(char const* tex
{
logExtraInfo();
}
log(" ");
}

View file

@ -52,7 +52,6 @@ target_link_libraries(common
PRIVATE
acore-core-interface
PUBLIC
ace
boost
argon2
g3dlib

View file

@ -14,13 +14,12 @@
#include <utility>
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
#include <ace/config-all.h>
#include <ws2tcpip.h>
#if AC_COMPILER == AC_COMPILER_INTEL
# if !defined(BOOST_ASIO_HAS_MOVE)
# define BOOST_ASIO_HAS_MOVE
# endif // !defined(BOOST_ASIO_HAS_MOVE)
# endif // if WARHEAD_COMPILER == WARHEAD_COMPILER_INTEL
# endif // if AC_COMPILER == AC_COMPILER_INTEL
#else
#include <cstdlib>
#include <netdb.h>

View file

@ -16,12 +16,12 @@
#define ACORE_BIGENDIAN 1
#if !defined(ACORE_ENDIAN)
# if defined (ACE_BIG_ENDIAN)
# if defined (BOOST_BIG_ENDIAN)
# define ACORE_ENDIAN ACORE_BIGENDIAN
# else //ACE_BYTE_ORDER != ACE_BIG_ENDIAN
# else
# define ACORE_ENDIAN ACORE_LITTLEENDIAN
# endif //ACE_BYTE_ORDER
#endif //ACORE_ENDIAN
# endif
#endif
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
# define ACORE_PATH_MAX MAX_PATH

View file

@ -12,11 +12,6 @@
#include <windows.h>
#include <winsvc.h>
// stupid ACE define
#ifdef main
#undef main
#endif //main
#if !defined(WINADVAPI)
#if !defined(_ADVAPI32_)
#define WINADVAPI DECLSPEC_IMPORT

View file

@ -0,0 +1,154 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#ifndef MPSCQueue_h__
#define MPSCQueue_h__
#include <atomic>
#include <utility>
namespace Acore::Impl
{
// C++ implementation of Dmitry Vyukov's lock free MPSC queue
// http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue
template<typename T>
class MPSCQueueNonIntrusive
{
public:
MPSCQueueNonIntrusive() : _head(new Node()), _tail(_head.load(std::memory_order_relaxed))
{
Node* front = _head.load(std::memory_order_relaxed);
front->Next.store(nullptr, std::memory_order_relaxed);
}
~MPSCQueueNonIntrusive()
{
T* output;
while (Dequeue(output))
delete output;
Node* front = _head.load(std::memory_order_relaxed);
delete front;
}
void Enqueue(T* input)
{
Node* node = new Node(input);
Node* prevHead = _head.exchange(node, std::memory_order_acq_rel);
prevHead->Next.store(node, std::memory_order_release);
}
bool Dequeue(T*& result)
{
Node* tail = _tail.load(std::memory_order_relaxed);
Node* next = tail->Next.load(std::memory_order_acquire);
if (!next)
return false;
result = next->Data;
_tail.store(next, std::memory_order_release);
delete tail;
return true;
}
private:
struct Node
{
Node() = default;
explicit Node(T* data) : Data(data)
{
Next.store(nullptr, std::memory_order_relaxed);
}
T* Data;
std::atomic<Node*> Next;
};
std::atomic<Node*> _head;
std::atomic<Node*> _tail;
MPSCQueueNonIntrusive(MPSCQueueNonIntrusive const&) = delete;
MPSCQueueNonIntrusive& operator=(MPSCQueueNonIntrusive const&) = delete;
};
// C++ implementation of Dmitry Vyukov's lock free MPSC queue
// http://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
template<typename T, std::atomic<T*> T::* IntrusiveLink>
class MPSCQueueIntrusive
{
public:
MPSCQueueIntrusive() : _dummyPtr(reinterpret_cast<T*>(std::addressof(_dummy))), _head(_dummyPtr), _tail(_dummyPtr)
{
// _dummy is constructed from aligned_storage and is intentionally left uninitialized (it might not be default constructible)
// so we init only its IntrusiveLink here
std::atomic<T*>* dummyNext = new (&(_dummyPtr->*IntrusiveLink)) std::atomic<T*>();
dummyNext->store(nullptr, std::memory_order_relaxed);
}
~MPSCQueueIntrusive()
{
T* output;
while (Dequeue(output))
delete output;
}
void Enqueue(T* input)
{
(input->*IntrusiveLink).store(nullptr, std::memory_order_release);
T* prevHead = _head.exchange(input, std::memory_order_acq_rel);
(prevHead->*IntrusiveLink).store(input, std::memory_order_release);
}
bool Dequeue(T*& result)
{
T* tail = _tail.load(std::memory_order_relaxed);
T* next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
if (tail == _dummyPtr)
{
if (!next)
return false;
_tail.store(next, std::memory_order_release);
tail = next;
next = (next->*IntrusiveLink).load(std::memory_order_acquire);
}
if (next)
{
_tail.store(next, std::memory_order_release);
result = tail;
return true;
}
T* head = _head.load(std::memory_order_acquire);
if (tail != head)
return false;
Enqueue(_dummyPtr);
next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
if (next)
{
_tail.store(next, std::memory_order_release);
result = tail;
return true;
}
return false;
}
private:
std::aligned_storage_t<sizeof(T), alignof(T)> _dummy;
T* _dummyPtr;
std::atomic<T*> _head;
std::atomic<T*> _tail;
MPSCQueueIntrusive(MPSCQueueIntrusive const&) = delete;
MPSCQueueIntrusive& operator=(MPSCQueueIntrusive const&) = delete;
};
}
template<typename T, std::atomic<T*> T::* IntrusiveLink = nullptr>
using MPSCQueue = std::conditional_t<IntrusiveLink != nullptr, Acore::Impl::MPSCQueueIntrusive<T, IntrusiveLink>, Acore::Impl::MPSCQueueNonIntrusive<T>>;
#endif // MPSCQueue_h__

View file

@ -100,7 +100,7 @@ private:
typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; }
template<typename E = T>
typename std::enable_if < !std::is_pointer<E>::value >::type DeleteQueuedObject(E const& /*packet*/) { }
typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { }
};
#endif

View file

@ -1,100 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#ifndef _PCQ_H
#define _PCQ_H
#include <condition_variable>
#include <mutex>
#include <queue>
#include <atomic>
#include <type_traits>
template <typename T>
class ProducerConsumerQueue
{
private:
std::mutex _queueLock;
std::queue<T> _queue;
std::condition_variable _condition;
std::atomic<bool> _shutdown;
public:
ProducerConsumerQueue<T>() : _shutdown(false) { }
void Push(const T& value)
{
std::lock_guard<std::mutex> lock(_queueLock);
_queue.push(std::move(value));
_condition.notify_one();
}
bool Empty()
{
std::lock_guard<std::mutex> lock(_queueLock);
return _queue.empty();
}
bool Pop(T& value)
{
std::lock_guard<std::mutex> lock(_queueLock);
if (_queue.empty() || _shutdown)
return false;
value = _queue.front();
_queue.pop();
return true;
}
void WaitAndPop(T& value)
{
std::unique_lock<std::mutex> lock(_queueLock);
// we could be using .wait(lock, predicate) overload here but it is broken
// https://connect.microsoft.com/VisualStudio/feedback/details/1098841
while (_queue.empty() && !_shutdown)
_condition.wait(lock);
if (_queue.empty() || _shutdown)
return;
value = _queue.front();
_queue.pop();
}
void Cancel()
{
std::unique_lock<std::mutex> lock(_queueLock);
while (!_queue.empty())
{
T& value = _queue.front();
DeleteQueuedObject(value);
_queue.pop();
}
_shutdown = true;
_condition.notify_all();
}
private:
template<typename E = T>
typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; }
template<typename E = T>
typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { }
};
#endif

View file

@ -7,9 +7,9 @@
#include "Util.h"
#include "Common.h"
#include "Containers.h"
#include "IpAddress.h"
#include "StringConvert.h"
#include "StringFormat.h"
#include <ace/Default_Constants.h>
#include <algorithm>
#include <cctype>
#include <cstdarg>
@ -17,8 +17,10 @@
#include <iomanip>
#include <sstream>
#include <string>
#include <cctype>
#include <cstdarg>
#include <ctime>
#include <utf8.h>
// #include "IpAddress.h"
Tokenizer::Tokenizer(const std::string& src, const char sep, uint32 vectorReserve)
{
@ -295,26 +297,9 @@ bool IsIPAddress(char const* ipaddress)
return false;
}
// Let the big boys do it.
// Drawback: all valid ip address formats are recognized e.g.: 12.23, 121234, 0xABCD)
return inet_addr(ipaddress) != INADDR_NONE;
}
std::string GetAddressString(ACE_INET_Addr const& addr)
{
char buf[ACE_MAX_FULLY_QUALIFIED_NAME_LEN + 16];
addr.addr_to_string(buf, ACE_MAX_FULLY_QUALIFIED_NAME_LEN + 16);
return buf;
}
bool IsIPAddrInNetwork(ACE_INET_Addr const& net, ACE_INET_Addr const& addr, ACE_INET_Addr const& subnetMask)
{
uint32 mask = subnetMask.get_ip_address();
if ((net.get_ip_address() & mask) == (addr.get_ip_address() & mask))
{
return true;
}
return false;
boost::system::error_code error;
Acore::Net::make_address(ipaddress, error);
return !error;
}
/// create PID file

View file

@ -10,7 +10,6 @@
#include "Containers.h"
#include "Define.h"
#include "Errors.h"
#include <ace/INET_Addr.h>
#include <algorithm>
#include <array>
#include <cctype>
@ -18,6 +17,9 @@
#include <map>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <array>
// Searcher for map of structs
template<typename T, class S> struct Finder
@ -383,12 +385,6 @@ bool Utf8ToUpperOnlyLatin(std::string& utf8String);
bool IsIPAddress(char const* ipaddress);
/// Checks if address belongs to the a network with specified submask
bool IsIPAddrInNetwork(ACE_INET_Addr const& net, ACE_INET_Addr const& addr, ACE_INET_Addr const& subnetMask);
/// Transforms ACE_INET_Addr address into string format "dotted_ip:port"
std::string GetAddressString(ACE_INET_Addr const& addr);
uint32 CreatePIDFile(const std::string& filename);
uint32 GetPID();

View file

@ -13,28 +13,26 @@
*/
#include "AppenderDB.h"
#include "AuthSocketMgr.h"
#include "Banner.h"
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "DeadlineTimer.h"
#include "GitRevision.h"
#include "IPLocation.h"
#include "Log.h"
#include "RealmAcceptor.h"
#include "RealmList.h"
#include "DatabaseLoader.h"
#include "IoContext.h"
#include "MySQLThreading.h"
#include "ProcessPriority.h"
#include "RealmList.h"
#include "SecretMgr.h"
#include "SharedDefines.h"
#include "SignalHandler.h"
#include "Util.h"
#include "ProcessPriority.h"
#include <ace/ACE.h>
#include <ace/Dev_Poll_Reactor.h>
#include <ace/Sig_Handler.h>
#include <ace/TP_Reactor.h>
#include <boost/asio/signal_set.hpp>
#include <boost/version.hpp>
#include <csignal>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
@ -42,23 +40,26 @@
#define _ACORE_REALM_CONFIG "authserver.conf"
#endif
using boost::asio::ip::tcp;
bool StartDB();
void StopDB();
bool stopEvent = false; // Setting it to true stops the server
void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int signalNumber);
void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error);
void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error);
/// Print out the usage string for this program on the console.
void usage(const char* prog)
{
LOG_INFO("server.authserver", "Usage: \n %s [<options>]\n"
" -c config_file use config_file as configuration file\n\r",
prog);
" -c config_file use config_file as configuration file\n\r", prog);
}
/// Launch the auth server
extern int main(int argc, char** argv)
int main(int argc, char** argv)
{
Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_AUTHSERVER;
signal(SIGABRT, &Acore::AbortHandler);
// Command line parsing to get the configuration file name
std::string configFile = sConfigMgr->GetConfigPath() + std::string(_ACORE_REALM_CONFIG);
@ -99,18 +100,9 @@ extern int main(int argc, char** argv)
LOG_INFO("server.authserver", "> Using configuration file %s.", sConfigMgr->GetFilename().c_str());
LOG_INFO("server.authserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
LOG_INFO("server.authserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
LOG_INFO("server.authserver", "> Using ACE version: %s\n", ACE_VERSION);
}
);
#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL)
ACE_Reactor::instance(new ACE_Reactor(new ACE_Dev_Poll_Reactor(ACE::max_handles(), 1), 1), true);
#else
ACE_Reactor::instance(new ACE_Reactor(new ACE_TP_Reactor(), true), true);
#endif
LOG_INFO("server.authserver", "Max allowed open files is %d", ACE::max_handles());
// authserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
@ -133,75 +125,70 @@ extern int main(int argc, char** argv)
// Load IP Location Database
sIPLocation->Load();
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Get the list of realms for the server
sRealmList->Initialize(sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20));
sRealmList->Initialize(*ioContext, sConfigMgr->GetOption<int32>("RealmsStateUpdateDelay", 20));
std::shared_ptr<void> sRealmListHandle(nullptr, [](void*) { sRealmList->Close(); });
if (sRealmList->GetRealms().empty())
{
LOG_ERROR("server.authserver", "No valid realms specified.");
return 1;
}
// Launch the listening network socket
RealmAcceptor acceptor;
int32 rmport = sConfigMgr->GetOption<int32>("RealmServerPort", 3724);
if (rmport < 0 || rmport > 0xFFFF)
// Start the listening port (acceptor) for auth connections
int32 port = sConfigMgr->GetOption<int32>("RealmServerPort", 3724);
if (port < 0 || port > 0xFFFF)
{
LOG_ERROR("server.authserver", "The specified RealmServerPort (%d) is out of the allowed range (1-65535)", rmport);
LOG_ERROR("server.authserver", "Specified port out of allowed range (1-65535)");
return 1;
}
std::string bind_ip = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
ACE_INET_Addr bind_addr(uint16(rmport), bind_ip.c_str());
if (acceptor.open(bind_addr, ACE_Reactor::instance(), ACE_NONBLOCK) == -1)
if (!sAuthSocketMgr.StartNetwork(*ioContext, bindIp, port))
{
LOG_ERROR("server.authserver", "Auth server can not bind to %s:%d (possible error: port already in use)", bind_ip.c_str(), rmport);
LOG_ERROR("server.authserver", "Failed to initialize network");
return 1;
}
LOG_INFO("server.authserver", "Authserver listening to %s:%d", bind_ip.c_str(), rmport);
std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); });
// Initialize the signal handlers
Acore::SignalHandler signalHandler;
auto const _handler = [](int) { stopEvent = true; };
// Register authservers's signal handlers
signalHandler.handle_signal(SIGINT, _handler);
signalHandler.handle_signal(SIGTERM, _handler);
// Set signal handlers
boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signalHandler.handle_signal(SIGBREAK, _handler);
signals.add(SIGBREAK);
#endif
signals.async_wait(std::bind(&SignalHandler, std::weak_ptr<Acore::Asio::IoContext>(ioContext), std::placeholders::_1, std::placeholders::_2));
// Set process priority according to configuration settings
SetProcessPriority("server.authserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false));
// maximum counter for next ping
uint32 numLoops = (sConfigMgr->GetOption<int32>("MaxPingTime", 30) * (MINUTE * 1000000 / 100000));
uint32 loopCounter = 0;
// Enabled a timed callback for handling the database keep alive ping
int32 dbPingInterval = sConfigMgr->GetOption<int32>("MaxPingTime", 30);
std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext);
dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval));
dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(dbPingTimer), dbPingInterval, std::placeholders::_1));
// Wait for termination signal
while (!stopEvent)
{
// dont move this outside the loop, the reactor will modify it
ACE_Time_Value interval(0, 100000);
int32 banExpiryCheckInterval = sConfigMgr->GetOption<int32>("BanExpiryCheckInterval", 60);
std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = std::make_shared<Acore::Asio::DeadlineTimer>(*ioContext);
banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval));
banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, std::weak_ptr<Acore::Asio::DeadlineTimer>(banExpiryCheckTimer), banExpiryCheckInterval, std::placeholders::_1));
if (ACE_Reactor::instance()->run_reactor_event_loop(interval) == -1)
break;
// Start the io service worker loop
ioContext->run();
if ((++loopCounter) == numLoops)
{
loopCounter = 0;
LOG_INFO("server.authserver", "Ping MySQL to keep connection alive");
LoginDatabase.KeepAlive();
}
}
// Close the Database Pool and library
StopDB();
banExpiryCheckTimer->cancel();
dbPingTimer->cancel();
LOG_INFO("server.authserver", "Halting process...");
signals.cancel();
return 0;
}
@ -231,3 +218,44 @@ void StopDB()
LoginDatabase.Close();
MySQL::Library_End();
}
void SignalHandler(std::weak_ptr<Acore::Asio::IoContext> ioContextRef, boost::system::error_code const& error, int /*signalNumber*/)
{
if (!error)
{
if (std::shared_ptr<Acore::Asio::IoContext> ioContext = ioContextRef.lock())
{
ioContext->stop();
}
}
}
void KeepDatabaseAliveHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> dbPingTimerRef, int32 dbPingInterval, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<Acore::Asio::DeadlineTimer> dbPingTimer = dbPingTimerRef.lock())
{
LOG_INFO("server.authserver", "Ping MySQL to keep connection alive");
LoginDatabase.KeepAlive();
dbPingTimer->expires_from_now(boost::posix_time::minutes(dbPingInterval));
dbPingTimer->async_wait(std::bind(&KeepDatabaseAliveHandler, dbPingTimerRef, dbPingInterval, std::placeholders::_1));
}
}
}
void BanExpiryHandler(std::weak_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimerRef, int32 banExpiryCheckInterval, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<Acore::Asio::DeadlineTimer> banExpiryCheckTimer = banExpiryCheckTimerRef.lock())
{
LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS));
banExpiryCheckTimer->expires_from_now(boost::posix_time::seconds(banExpiryCheckInterval));
banExpiryCheckTimer->async_wait(std::bind(&BanExpiryHandler, banExpiryCheckTimerRef, banExpiryCheckInterval, std::placeholders::_1));
}
}
}

View file

@ -1,6 +1,5 @@
#include "Configuration/Config.h"
#include "Database/DatabaseEnv.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "RealmList.h"
#include "RealmSocket.h"
#include "Common.h"

View file

@ -0,0 +1,866 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#include "AuthSession.h"
#include "AES.h"
#include "AuthCodes.h"
#include "Config.h"
#include "CryptoGenerics.h"
#include "CryptoRandom.h"
#include "DatabaseEnv.h"
#include "Errors.h"
#include "CryptoHash.h"
#include "IPLocation.h"
#include "Log.h"
#include "RealmList.h"
#include "SecretMgr.h"
#include "Timer.h"
#include "TOTP.h"
#include "Util.h"
#include <boost/lexical_cast.hpp>
#include <openssl/crypto.h>
using boost::asio::ip::tcp;
enum eAuthCmd
{
AUTH_LOGON_CHALLENGE = 0x00,
AUTH_LOGON_PROOF = 0x01,
AUTH_RECONNECT_CHALLENGE = 0x02,
AUTH_RECONNECT_PROOF = 0x03,
REALM_LIST = 0x10,
XFER_INITIATE = 0x30,
XFER_DATA = 0x31,
XFER_ACCEPT = 0x32,
XFER_RESUME = 0x33,
XFER_CANCEL = 0x34
};
#pragma pack(push, 1)
typedef struct AUTH_LOGON_CHALLENGE_C
{
uint8 cmd;
uint8 error;
uint16 size;
uint8 gamename[4];
uint8 version1;
uint8 version2;
uint8 version3;
uint16 build;
uint8 platform[4];
uint8 os[4];
uint8 country[4];
uint32 timezone_bias;
uint32 ip;
uint8 I_len;
uint8 I[1];
} sAuthLogonChallenge_C;
static_assert(sizeof(sAuthLogonChallenge_C) == (1 + 1 + 2 + 4 + 1 + 1 + 1 + 2 + 4 + 4 + 4 + 4 + 4 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_C
{
uint8 cmd;
Acore::Crypto::SRP6::EphemeralKey A;
Acore::Crypto::SHA1::Digest clientM;
Acore::Crypto::SHA1::Digest crc_hash;
uint8 number_of_keys;
uint8 securityFlags;
} sAuthLogonProof_C;
static_assert(sizeof(sAuthLogonProof_C) == (1 + 32 + 20 + 20 + 1 + 1));
typedef struct AUTH_LOGON_PROOF_S
{
uint8 cmd;
uint8 error;
Acore::Crypto::SHA1::Digest M2;
uint32 AccountFlags;
uint32 SurveyId;
uint16 LoginFlags;
} sAuthLogonProof_S;
static_assert(sizeof(sAuthLogonProof_S) == (1 + 1 + 20 + 4 + 4 + 2));
typedef struct AUTH_LOGON_PROOF_S_OLD
{
uint8 cmd;
uint8 error;
Acore::Crypto::SHA1::Digest M2;
uint32 unk2;
} sAuthLogonProof_S_Old;
static_assert(sizeof(sAuthLogonProof_S_Old) == (1 + 1 + 20 + 4));
typedef struct AUTH_RECONNECT_PROOF_C
{
uint8 cmd;
uint8 R1[16];
Acore::Crypto::SHA1::Digest R2, R3;
uint8 number_of_keys;
} sAuthReconnectProof_C;
static_assert(sizeof(sAuthReconnectProof_C) == (1 + 16 + 20 + 20 + 1));
#pragma pack(pop)
std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } };
#define MAX_ACCEPTED_CHALLENGE_SIZE (sizeof(AUTH_LOGON_CHALLENGE_C) + 16)
#define AUTH_LOGON_CHALLENGE_INITIAL_SIZE 4
#define REALM_LIST_PACKET_SIZE 5
std::unordered_map<uint8, AuthHandler> AuthSession::InitHandlers()
{
std::unordered_map<uint8, AuthHandler> handlers;
handlers[AUTH_LOGON_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleLogonChallenge };
handlers[AUTH_LOGON_PROOF] = { STATUS_LOGON_PROOF, sizeof(AUTH_LOGON_PROOF_C), &AuthSession::HandleLogonProof };
handlers[AUTH_RECONNECT_CHALLENGE] = { STATUS_CHALLENGE, AUTH_LOGON_CHALLENGE_INITIAL_SIZE, &AuthSession::HandleReconnectChallenge };
handlers[AUTH_RECONNECT_PROOF] = { STATUS_RECONNECT_PROOF, sizeof(AUTH_RECONNECT_PROOF_C), &AuthSession::HandleReconnectProof };
handlers[REALM_LIST] = { STATUS_AUTHED, REALM_LIST_PACKET_SIZE, &AuthSession::HandleRealmList };
return handlers;
}
std::unordered_map<uint8, AuthHandler> const Handlers = AuthSession::InitHandlers();
void AccountInfo::LoadResult(Field* fields)
{
// 0 1 2 3 4 5
// SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins,
// 6 7
// ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate,
// 8 9
// ipb.unbandate > UNIX_TIMESTAMP() OR ipb.unbandate = ipb.bandate, ipb.unbandate = ipb.bandate,
// 10
// aa.gmlevel (, more query-specific fields)
// FROM account a LEFT JOIN account_access aa ON a.id = aa.id LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 LEFT JOIN ip_banned ipb ON ipb.ip = ? WHERE a.username = ?
Id = fields[0].GetUInt32();
Login = fields[1].GetString();
IsLockedToIP = fields[2].GetBool();
LockCountry = fields[3].GetString();
LastIP = fields[4].GetString();
FailedLogins = fields[5].GetUInt32();
IsBanned = fields[6].GetBool() || fields[8].GetBool();
IsPermanentlyBanned = fields[7].GetBool() || fields[9].GetBool();
SecurityLevel = static_cast<AccountTypes>(fields[10].GetUInt8()) > SEC_CONSOLE ? SEC_CONSOLE : static_cast<AccountTypes>(fields[10].GetUInt8());
// Use our own uppercasing of the account name instead of using UPPER() in mysql query
// This is how the account was created in the first place and changing it now would result in breaking
// login for all accounts having accented characters in their name
Utf8ToUpperOnlyLatin(Login);
}
AuthSession::AuthSession(tcp::socket&& socket) :
Socket(std::move(socket)), _status(STATUS_CHALLENGE), _build(0), _expversion(0) { }
void AuthSession::Start()
{
std::string ip_address = GetRemoteIpAddress().to_string();
LOG_TRACE("session", "Accepted connection from %s", ip_address.c_str());
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
stmt->setString(0, ip_address);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::CheckIpCallback, this, std::placeholders::_1)));
}
bool AuthSession::Update()
{
if (!AuthSocket::Update())
return false;
_queryProcessor.ProcessReadyCallbacks();
return true;
}
void AuthSession::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)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
LOG_DEBUG("session", "[AuthSession::CheckIpCallback] Banned ip '%s:%d' tries to login!", GetRemoteIpAddress().to_string().c_str(), GetRemotePort());
return;
}
}
AsyncRead();
}
void AuthSession::ReadHandler()
{
MessageBuffer& packet = GetReadBuffer();
while (packet.GetActiveSize())
{
uint8 cmd = packet.GetReadPointer()[0];
auto itr = Handlers.find(cmd);
if (itr == Handlers.end())
{
// well we dont handle this, lets just ignore it
packet.Reset();
break;
}
if (_status != itr->second.status)
{
CloseSocket();
return;
}
uint16 size = uint16(itr->second.packetSize);
if (packet.GetActiveSize() < size)
break;
if (cmd == AUTH_LOGON_CHALLENGE || cmd == AUTH_RECONNECT_CHALLENGE)
{
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(packet.GetReadPointer());
size += challenge->size;
if (size > MAX_ACCEPTED_CHALLENGE_SIZE)
{
CloseSocket();
return;
}
}
if (packet.GetActiveSize() < size)
break;
if (!(*this.*itr->second.handler)())
{
CloseSocket();
return;
}
packet.ReadCompleted(size);
}
AsyncRead();
}
void AuthSession::SendPacket(ByteBuffer& packet)
{
if (!IsOpen())
return;
if (!packet.empty())
{
MessageBuffer buffer(packet.size());
buffer.Write(packet.contents(), packet.size());
QueuePacket(std::move(buffer));
}
}
bool AuthSession::HandleLogonChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string login((char const*)challenge->I, challenge->I_len);
LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str());
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
std::array<char, 5> os;
os.fill('\0');
memcpy(os.data(), challenge->os, sizeof(challenge->os));
_os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
_localizationName.resize(4);
for (int i = 0; i < 4; ++i)
_localizationName[i] = challenge->country[4 - i - 1];
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
stmt->setString(0, GetRemoteIpAddress().to_string());
stmt->setString(1, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::LogonChallengeCallback, this, std::placeholders::_1)));
return true;
}
void AuthSession::LogonChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_LOGON_CHALLENGE);
pkt << uint8(0x00);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
std::string ipAddress = GetRemoteIpAddress().to_string();
uint16 port = GetRemotePort();
// If the IP is 'locked', check that the player comes indeed from the correct IP address
if (_accountInfo.IsLockedToIP)
{
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to IP - '%s' is logging in from '%s'", _accountInfo.Login.c_str(), _accountInfo.LastIP.c_str(), ipAddress.c_str());
if (_accountInfo.LastIP != ipAddress)
{
pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
SendPacket(pkt);
return;
}
}
else
{
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(ipAddress))
_ipCountry = location->CountryCode;
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to ip", _accountInfo.Login.c_str());
if (_accountInfo.LockCountry.empty() || _accountInfo.LockCountry == "00")
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is not locked to country", _accountInfo.Login.c_str());
else if (!_ipCountry.empty())
{
LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' is locked to country: '%s' Player country is '%s'", _accountInfo.Login.c_str(), _accountInfo.LockCountry.c_str(), _ipCountry.c_str());
if (_ipCountry != _accountInfo.LockCountry)
{
pkt << uint8(WOW_FAIL_UNLOCKABLE_LOCK);
SendPacket(pkt);
return;
}
}
}
// If the account is banned, reject the logon attempt
if (_accountInfo.IsBanned)
{
if (_accountInfo.IsPermanentlyBanned)
{
pkt << uint8(WOW_FAIL_BANNED);
SendPacket(pkt);
LOG_INFO("server.authserver.banned", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
return;
}
else
{
pkt << uint8(WOW_FAIL_SUSPENDED);
SendPacket(pkt);
LOG_INFO("server.authserver.banned", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
return;
}
}
uint8 securityFlags = 0;
// Check if a TOTP token is needed
if (!fields[11].IsNull())
{
securityFlags = 4;
_totpSecret = fields[11].GetBinary();
if (auto const& secret = sSecretMgr->GetSecret(SECRET_TOTP_MASTER_KEY))
{
bool success = Acore::Crypto::AEDecrypt<Acore::Crypto::AES>(*_totpSecret, *secret);
if (!success)
{
pkt << uint8(WOW_FAIL_DB_BUSY);
LOG_ERROR("server.authserver", "[AuthChallenge] Account '%s' has invalid ciphertext for TOTP token key stored", _accountInfo.Login.c_str());
SendPacket(pkt);
return;
}
}
}
_srp6.emplace(
_accountInfo.Login,
fields[12].GetBinary<Acore::Crypto::SRP6::SALT_LENGTH>(),
fields[13].GetBinary<Acore::Crypto::SRP6::VERIFIER_LENGTH>());
// Fill the response packet with the result
if (AuthHelper::IsAcceptedClientBuild(_build))
{
pkt << uint8(WOW_SUCCESS);
pkt.append(_srp6->B);
pkt << uint8(1);
pkt.append(_srp6->g);
pkt << uint8(32);
pkt.append(_srp6->N);
pkt.append(_srp6->s);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
pkt << uint8(securityFlags); // security flags (0x0...0x04)
if (securityFlags & 0x01) // PIN input
{
pkt << uint32(0);
pkt << uint64(0) << uint64(0); // 16 bytes hash?
}
if (securityFlags & 0x02) // Matrix input
{
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint8(0);
pkt << uint64(0);
}
if (securityFlags & 0x04) // Security token input
pkt << uint8(1);
LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s is using '%s' locale (%u)",
ipAddress.c_str(), port, _accountInfo.Login.c_str(), _localizationName.c_str(), GetLocaleByName(_localizationName));
_status = STATUS_LOGON_PROOF;
}
else
pkt << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(pkt);
}
// Logon Proof command handler
bool AuthSession::HandleLogonProof()
{
LOG_DEBUG("server.authserver", "Entering _HandleLogonProof");
_status = STATUS_CLOSED;
// Read the packet
sAuthLogonProof_C* logonProof = reinterpret_cast<sAuthLogonProof_C*>(GetReadBuffer().GetReadPointer());
// If the client has no valid version
if (_expversion == NO_VALID_EXP_FLAG)
{
// Check if we have the appropriate patch on the disk
LOG_DEBUG("network", "Client with invalid version, patching is not implemented");
return false;
}
// Check if SRP6 results match (password is correct), else send an error
if (Optional<SessionKey> K = _srp6->VerifyChallengeResponse(logonProof->A, logonProof->clientM))
{
_sessionKey = *K;
// Check auth token
bool tokenSuccess = false;
bool sentToken = (logonProof->securityFlags & 0x04);
if (sentToken && _totpSecret)
{
uint8 size = *(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C));
std::string token(reinterpret_cast<char*>(GetReadBuffer().GetReadPointer() + sizeof(sAuthLogonProof_C) + sizeof(size)), size);
GetReadBuffer().ReadCompleted(sizeof(size) + size);
uint32 incomingToken = atoi(token.c_str());
tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken);
memset(_totpSecret->data(), 0, _totpSecret->size());
}
else if (!sentToken && !_totpSecret)
tokenSuccess = true;
if (!tokenSuccess)
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
return true;
}
if (!VerifyVersion(logonProof->A.data(), logonProof->A.size(), logonProof->crc_hash, false))
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str());
// Update the sessionkey, last_ip, last login time and reset number of failed logins in the account table for this account
// No SQL injection (escaped user name) and IP address as received by socket
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LOGONPROOF);
stmt->setBinary(0, _sessionKey);
stmt->setString(1, GetRemoteIpAddress().to_string());
stmt->setUInt32(2, GetLocaleByName(_localizationName));
stmt->setString(3, _os);
stmt->setString(4, _accountInfo.Login);
LoginDatabase.DirectExecute(stmt);
// Finish SRP6 and send the final result to the client
Acore::Crypto::SHA1::Digest M2 = Acore::Crypto::SRP6::GetSessionVerifier(logonProof->A, logonProof->clientM, _sessionKey);
ByteBuffer packet;
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
sAuthLogonProof_S proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.AccountFlags = 0x00800000; // 0x01 = GM, 0x08 = Trial, 0x00800000 = Pro pass (arena tournament)
proof.SurveyId = 0;
proof.LoginFlags = 0; // 0x1 = has account message
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
else
{
sAuthLogonProof_S_Old proof;
proof.M2 = M2;
proof.cmd = AUTH_LOGON_PROOF;
proof.error = 0;
proof.unk2 = 0x00;
packet.resize(sizeof(proof));
std::memcpy(packet.contents(), &proof, sizeof(proof));
}
SendPacket(packet);
_status = STATUS_AUTHED;
}
else
{
ByteBuffer packet;
packet << uint8(AUTH_LOGON_PROOF);
packet << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
packet << uint16(0); // LoginFlags, 1 has account message
SendPacket(packet);
LOG_INFO("server.authserver.hack", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!",
GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str());
uint32 MaxWrongPassCount = sConfigMgr->GetOption<int32>("WrongPass.MaxCount", 0);
// We can not include the failed account login hook. However, this is a workaround to still log this.
if (sConfigMgr->GetOption<bool>("WrongPass.Logging", false))
{
LoginDatabasePreparedStatement* logstmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_FALP_IP_LOGGING);
logstmt->setUInt32(0, _accountInfo.Id);
logstmt->setString(1, GetRemoteIpAddress().to_string());
logstmt->setString(2, "Login to WoW Failed - Incorrect Password");
LoginDatabase.Execute(logstmt);
}
if (MaxWrongPassCount > 0)
{
//Increment number of failed logins by one and if it reaches the limit temporarily ban that account or IP
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_FAILEDLOGINS);
stmt->setString(0, _accountInfo.Login);
LoginDatabase.Execute(stmt);
if (++_accountInfo.FailedLogins >= MaxWrongPassCount)
{
uint32 WrongPassBanTime = sConfigMgr->GetOption<int32>("WrongPass.BanTime", 600);
bool WrongPassBanType = sConfigMgr->GetOption<bool>("WrongPass.BanType", false);
if (WrongPassBanType)
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_AUTO_BANNED);
stmt->setUInt32(0, _accountInfo.Id);
stmt->setUInt32(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins);
}
else
{
stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
stmt->setString(0, GetRemoteIpAddress().to_string());
stmt->setUInt32(1, WrongPassBanTime);
LoginDatabase.Execute(stmt);
LOG_DEBUG("server.authserver", "'%s:%d' [AuthChallenge] IP got banned for '%u' seconds because account %s failed to authenticate '%u' times",
GetRemoteIpAddress().to_string().c_str(), GetRemotePort(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins);
}
}
}
}
return true;
}
bool AuthSession::HandleReconnectChallenge()
{
_status = STATUS_CLOSED;
sAuthLogonChallenge_C* challenge = reinterpret_cast<sAuthLogonChallenge_C*>(GetReadBuffer().GetReadPointer());
if (challenge->size - (sizeof(sAuthLogonChallenge_C) - AUTH_LOGON_CHALLENGE_INITIAL_SIZE - 1) != challenge->I_len)
return false;
std::string login((char const*)challenge->I, challenge->I_len);
LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str());
_build = challenge->build;
_expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
std::array<char, 5> os;
os.fill('\0');
memcpy(os.data(), challenge->os, sizeof(challenge->os));
_os = os.data();
// Restore string order as its byte order is reversed
std::reverse(_os.begin(), _os.end());
_localizationName.resize(4);
for (int i = 0; i < 4; ++i)
_localizationName[i] = challenge->country[4 - i - 1];
// Get the account details from the account table
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE);
stmt->setString(0, GetRemoteIpAddress().to_string());
stmt->setString(1, login);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::ReconnectChallengeCallback, this, std::placeholders::_1)));
return true;
}
void AuthSession::ReconnectChallengeCallback(PreparedQueryResult result)
{
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_CHALLENGE);
if (!result)
{
pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
SendPacket(pkt);
return;
}
Field* fields = result->Fetch();
_accountInfo.LoadResult(fields);
_sessionKey = fields[11].GetBinary<SESSION_KEY_LENGTH>();
Acore::Crypto::GetRandomBytes(_reconnectProof);
_status = STATUS_RECONNECT_PROOF;
pkt << uint8(WOW_SUCCESS);
pkt.append(_reconnectProof);
pkt.append(VersionChallenge.data(), VersionChallenge.size());
SendPacket(pkt);
}
bool AuthSession::HandleReconnectProof()
{
LOG_DEBUG("server.authserver", "Entering _HandleReconnectProof");
_status = STATUS_CLOSED;
sAuthReconnectProof_C* reconnectProof = reinterpret_cast<sAuthReconnectProof_C*>(GetReadBuffer().GetReadPointer());
if (_accountInfo.Login.empty())
return false;
BigNumber t1;
t1.SetBinary(reconnectProof->R1, 16);
Acore::Crypto::SHA1 sha;
sha.UpdateData(_accountInfo.Login);
sha.UpdateData(t1.ToByteArray<16>());
sha.UpdateData(_reconnectProof);
sha.UpdateData(_sessionKey);
sha.Finalize();
if (sha.GetDigest() == reconnectProof->R2)
{
if (!VerifyVersion(reconnectProof->R1, sizeof(reconnectProof->R1), reconnectProof->R3, true))
{
ByteBuffer packet;
packet << uint8(AUTH_RECONNECT_PROOF);
packet << uint8(WOW_FAIL_VERSION_INVALID);
SendPacket(packet);
return true;
}
// Sending response
ByteBuffer pkt;
pkt << uint8(AUTH_RECONNECT_PROOF);
pkt << uint8(WOW_SUCCESS);
pkt << uint16(0); // LoginFlags, 1 has account message
SendPacket(pkt);
_status = STATUS_AUTHED;
return true;
}
else
{
LOG_ERROR("server.authserver.hack", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.", GetRemoteIpAddress().to_string().c_str(),
GetRemotePort(), _accountInfo.Login.c_str());
return false;
}
}
bool AuthSession::HandleRealmList()
{
LOG_DEBUG("server.authserver", "Entering _HandleRealmList");
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS);
stmt->setUInt32(0, _accountInfo.Id);
_queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&AuthSession::RealmListCallback, this, std::placeholders::_1)));
_status = STATUS_WAITING_FOR_REALM_LIST;
return true;
}
void AuthSession::RealmListCallback(PreparedQueryResult result)
{
std::map<uint32, uint8> characterCounts;
if (result)
{
do
{
Field* fields = result->Fetch();
characterCounts[fields[0].GetUInt32()] = fields[1].GetUInt8();
} while (result->NextRow());
}
// Circle through realms in the RealmList and construct the return packet (including # of user characters in each realm)
ByteBuffer pkt;
size_t RealmListSize = 0;
for (auto const& [realmHandle, realm] : sRealmList->GetRealms())
{
// don't work with realms which not compatible with the client
bool okBuild = ((_expversion & POST_BC_EXP_FLAG) && realm.Build == _build) || ((_expversion & PRE_BC_EXP_FLAG) && !AuthHelper::IsPreBCAcceptedClientBuild(realm.Build));
// No SQL injection. id of realm is controlled by the database.
uint32 flag = realm.Flags;
RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(realm.Build);
if (!okBuild)
{
if (!buildInfo)
continue;
flag |= REALM_FLAG_OFFLINE | REALM_FLAG_SPECIFYBUILD; // tell the client what build the realm is for
}
if (!buildInfo)
flag &= ~REALM_FLAG_SPECIFYBUILD;
std::string name = realm.Name;
if (_expversion & PRE_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
{
std::ostringstream ss;
ss << name << " (" << buildInfo->MajorVersion << '.' << buildInfo->MinorVersion << '.' << buildInfo->BugfixVersion << ')';
name = ss.str();
}
uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0;
pkt << uint8(realm.Type); // realm type
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
pkt << uint8(lock); // if 1, then realm locked
pkt << uint8(flag); // RealmFlags
pkt << name;
pkt << boost::lexical_cast<std::string>(realm.GetAddressForClient(GetRemoteIpAddress()));
pkt << float(realm.PopulationLevel);
pkt << uint8(characterCounts[realm.Id.Realm]);
pkt << uint8(realm.Timezone); // realm category
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
pkt << uint8(realm.Id.Realm);
else
pkt << uint8(0x0); // 1.12.1 and 1.12.2 clients
if (_expversion & POST_BC_EXP_FLAG && flag & REALM_FLAG_SPECIFYBUILD)
{
pkt << uint8(buildInfo->MajorVersion);
pkt << uint8(buildInfo->MinorVersion);
pkt << uint8(buildInfo->BugfixVersion);
pkt << uint16(buildInfo->Build);
}
++RealmListSize;
}
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
pkt << uint8(0x10);
pkt << uint8(0x00);
}
else // 1.12.1 and 1.12.2 clients
{
pkt << uint8(0x00);
pkt << uint8(0x02);
}
// make a ByteBuffer which stores the RealmList's size
ByteBuffer RealmListSizeBuffer;
RealmListSizeBuffer << uint32(0);
if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
RealmListSizeBuffer << uint16(RealmListSize);
else
RealmListSizeBuffer << uint32(RealmListSize);
ByteBuffer hdr;
hdr << uint8(REALM_LIST);
hdr << uint16(pkt.size() + RealmListSizeBuffer.size());
hdr.append(RealmListSizeBuffer); // append RealmList's size buffer
hdr.append(pkt); // append realms in the realmlist
SendPacket(hdr);
_status = STATUS_AUTHED;
}
bool AuthSession::VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect)
{
if (!sConfigMgr->GetOption<bool>("StrictVersionCheck", false))
return true;
Acore::Crypto::SHA1::Digest zeros;
Acore::Crypto::SHA1::Digest const* versionHash = nullptr;
if (!isReconnect)
{
RealmBuildInfo const* buildInfo = sRealmList->GetBuildInfo(_build);
if (!buildInfo)
return false;
if (_os == "Win")
versionHash = &buildInfo->WindowsHash;
else if (_os == "OSX")
versionHash = &buildInfo->MacHash;
if (!versionHash)
return false;
if (zeros == *versionHash)
return true; // not filled serverside
}
else
versionHash = &zeros;
Acore::Crypto::SHA1 version;
version.UpdateData(a, aLength);
version.UpdateData(*versionHash);
version.Finalize();
return (versionProof == version.GetDigest());
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#ifndef __AUTHSESSION_H__
#define __AUTHSESSION_H__
#include "AsyncCallbackProcessor.h"
#include "BigNumber.h"
#include "ByteBuffer.h"
#include "Common.h"
#include "CryptoHash.h"
#include "Optional.h"
#include "Socket.h"
#include "SRP6.h"
#include "QueryResult.h"
#include <memory>
#include <boost/asio/ip/tcp.hpp>
using boost::asio::ip::tcp;
class Field;
struct AuthHandler;
enum AuthStatus
{
STATUS_CHALLENGE = 0,
STATUS_LOGON_PROOF,
STATUS_RECONNECT_PROOF,
STATUS_AUTHED,
STATUS_WAITING_FOR_REALM_LIST,
STATUS_CLOSED
};
struct AccountInfo
{
void LoadResult(Field* fields);
uint32 Id = 0;
std::string Login;
bool IsLockedToIP = false;
std::string LockCountry;
std::string LastIP;
uint32 FailedLogins = 0;
bool IsBanned = false;
bool IsPermanentlyBanned = false;
AccountTypes SecurityLevel = SEC_PLAYER;
};
class AuthSession : public Socket<AuthSession>
{
typedef Socket<AuthSession> AuthSocket;
public:
static std::unordered_map<uint8, AuthHandler> InitHandlers();
AuthSession(tcp::socket&& socket);
void Start() override;
bool Update() override;
void SendPacket(ByteBuffer& packet);
protected:
void ReadHandler() override;
private:
bool HandleLogonChallenge();
bool HandleLogonProof();
bool HandleReconnectChallenge();
bool HandleReconnectProof();
bool HandleRealmList();
void CheckIpCallback(PreparedQueryResult result);
void LogonChallengeCallback(PreparedQueryResult result);
void ReconnectChallengeCallback(PreparedQueryResult result);
void RealmListCallback(PreparedQueryResult result);
bool VerifyVersion(uint8 const* a, int32 aLength, Acore::Crypto::SHA1::Digest const& versionProof, bool isReconnect);
Optional<Acore::Crypto::SRP6> _srp6;
SessionKey _sessionKey = {};
std::array<uint8, 16> _reconnectProof = {};
AuthStatus _status;
AccountInfo _accountInfo;
Optional<std::vector<uint8>> _totpSecret;
std::string _localizationName;
std::string _os;
std::string _ipCountry;
uint16 _build;
uint8 _expversion;
QueryCallbackProcessor _queryProcessor;
};
#pragma pack(push, 1)
struct AuthHandler
{
AuthStatus status;
size_t packetSize;
bool (AuthSession::* handler)();
};
#pragma pack(pop)
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,97 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef _AUTHSOCKET_H
#define _AUTHSOCKET_H
#include "Common.h"
#include "CryptoHash.h"
#include "Optional.h"
#include "RealmSocket.h"
#include "SRP6.h"
#include <mutex>
class ACE_INET_Addr;
class Field;
struct Realm;
enum eStatus
{
STATUS_CHALLENGE,
STATUS_LOGON_PROOF,
STATUS_RECON_PROOF,
STATUS_PATCH, // unused
STATUS_AUTHED,
STATUS_CLOSED
};
struct AccountInfo
{
void LoadResult(Field* fields);
uint32 Id = 0;
std::string Login;
bool IsLockedToIP = false;
std::string LockCountry;
std::string LastIP;
uint32 FailedLogins = 0;
bool IsBanned = false;
bool IsPermanentlyBanned = false;
AccountTypes SecurityLevel = SEC_PLAYER;
};
// Handle login commands
class AuthSocket: public RealmSocket::Session
{
public:
const static int s_BYTE_SIZE = 32;
AuthSocket(RealmSocket& socket);
~AuthSocket() override;
void OnRead() override;
void OnAccept() override;
void OnClose() override;
static ACE_INET_Addr const& GetAddressForClient(Realm const& realm, ACE_INET_Addr const& clientAddr);
bool _HandleLogonChallenge();
bool _HandleLogonProof();
bool _HandleReconnectChallenge();
bool _HandleReconnectProof();
bool _HandleRealmList();
//data transfer handle for patch
bool _HandleXferResume();
bool _HandleXferCancel();
bool _HandleXferAccept();
FILE* pPatch;
std::mutex patcherLock;
private:
RealmSocket& socket_;
RealmSocket& socket() { return socket_; }
std::optional<Acore::Crypto::SRP6> _srp6;
SessionKey _sessionKey = {};
std::array<uint8, 16> _reconnectProof = {};
eStatus _status;
AccountInfo _accountInfo;
Optional<std::vector<uint8>> _totpSecret;
// Since GetLocaleByName() is _NOT_ bijective, we have to store the locale as a string. Otherwise we can't differ
// between enUS and enGB, which is important for the patch system
std::string _localizationName;
std::string _os;
std::string _ipCountry;
uint16 _build;
uint8 _expversion;
};
#endif

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#ifndef AuthSocketMgr_h__
#define AuthSocketMgr_h__
#include "SocketMgr.h"
#include "AuthSession.h"
class AuthSocketMgr : public SocketMgr<AuthSession>
{
typedef SocketMgr<AuthSession> BaseSocketMgr;
public:
static AuthSocketMgr& Instance()
{
static AuthSocketMgr instance;
return instance;
}
bool StartNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount = 1) override
{
if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount))
return false;
_acceptor->AsyncAcceptWithCallback<&AuthSocketMgr::OnSocketAccept>();
return true;
}
protected:
NetworkThread<AuthSession>* CreateThreads() const override
{
return new NetworkThread<AuthSession>[1];
}
static void OnSocketAccept(tcp::socket&& sock, uint32 threadIndex)
{
Instance().OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex);
}
};
#define sAuthSocketMgr AuthSocketMgr::Instance()
#endif // AuthSocketMgr_h__

View file

@ -1,58 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef __REALMACCEPTOR_H__
#define __REALMACCEPTOR_H__
#include <ace/Acceptor.h>
#include <ace/SOCK_Acceptor.h>
#include "RealmSocket.h"
#include "AuthSocket.h"
class RealmAcceptor : public ACE_Acceptor<RealmSocket, ACE_SOCK_Acceptor>
{
public:
RealmAcceptor() = default;
virtual ~RealmAcceptor()
{
if (reactor())
reactor()->cancel_timer(this, 1);
}
protected:
virtual int make_svc_handler(RealmSocket*& sh)
{
if (sh == nullptr)
ACE_NEW_RETURN(sh, RealmSocket, -1);
sh->reactor(reactor());
sh->set_session(new AuthSocket(*sh));
return 0;
}
virtual int handle_timeout(const ACE_Time_Value& /*current_time*/, const void* /*act = 0*/)
{
LOG_INFO("network", "Resuming acceptor");
reactor()->cancel_timer(this, 1);
return reactor()->register_handler(this, ACE_Event_Handler::ACCEPT_MASK);
}
virtual int handle_accept_error()
{
#if defined(ENFILE) && defined(EMFILE)
if (errno == ENFILE || errno == EMFILE)
{
LOG_ERROR("network", "Out of file descriptors, suspending incoming connections for 10 seconds");
reactor()->remove_handler(this, ACE_Event_Handler::ACCEPT_MASK | ACE_Event_Handler::DONT_CALL);
reactor()->schedule_timer(this, nullptr, ACE_Time_Value(10));
}
#endif
return 0;
}
};
#endif

View file

@ -167,6 +167,23 @@ MySQLExecutable = ""
IPLocationFile = ""
#
# BanExpiryCheckInterval
# Description: Time (in seconds) between checks for expired bans
# Default: 60
#
BanExpiryCheckInterval = 60
#
# StrictVersionCheck
# Description: Prevent modified clients from connecting
# Default: 0 - (Disabled)
# 1 - (Enabled)
#
StrictVersionCheck = 0
#
###################################################################################################

View file

@ -1,299 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef _CALLBACK_H
#define _CALLBACK_H
#include <ace/Future.h>
#include <ace/Future_Set.h>
#include "QueryResult.h"
typedef ACE_Future<QueryResult> QueryResultFuture;
typedef ACE_Future<PreparedQueryResult> PreparedQueryResultFuture;
/*! A simple template using ACE_Future to manage callbacks from the thread and object that
issued the request. <ParamType> is variable type of parameter that is used as parameter
for the callback function.
*/
#define CALLBACK_STAGE_INVALID uint8(-1)
template <typename Result, typename ParamType, bool chain = false>
class QueryCallback
{
public:
QueryCallback() : _param(), _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {}
//! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery
void SetFutureResult(ACE_Future<Result> value)
{
_result = value;
}
ACE_Future<Result> GetFutureResult()
{
return _result;
}
int IsReady()
{
return _result.ready();
}
void GetResult(Result& res)
{
_result.get(res);
}
void FreeResult()
{
_result.cancel();
}
void SetParam(ParamType value)
{
_param = value;
}
ParamType GetParam()
{
return _param;
}
//! Resets the stage of the callback chain
void ResetStage()
{
if (!chain)
return;
_stage = 0;
}
//! Advances the callback chain to the next stage, so upper level code can act on its results accordingly
void NextStage()
{
if (!chain)
return;
++_stage;
}
//! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid)
uint8 GetStage()
{
return _stage;
}
//! Resets all underlying variables (param, result and stage)
void Reset()
{
SetParam(nullptr);
FreeResult();
ResetStage();
}
private:
ACE_Future<Result> _result;
ParamType _param;
uint8 _stage;
};
template <typename Result, typename ParamType1, typename ParamType2, bool chain = false>
class QueryCallback_2
{
public:
QueryCallback_2() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {}
//! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery
void SetFutureResult(ACE_Future<Result> value)
{
_result = value;
}
ACE_Future<Result> GetFutureResult()
{
return _result;
}
int IsReady()
{
return _result.ready();
}
void GetResult(Result& res)
{
_result.get(res);
}
void FreeResult()
{
_result.cancel();
}
void SetFirstParam(ParamType1 value)
{
_param_1 = value;
}
void SetSecondParam(ParamType2 value)
{
_param_2 = value;
}
ParamType1 GetFirstParam()
{
return _param_1;
}
ParamType2 GetSecondParam()
{
return _param_2;
}
//! Resets the stage of the callback chain
void ResetStage()
{
if (!chain)
return;
_stage = 0;
}
//! Advances the callback chain to the next stage, so upper level code can act on its results accordingly
void NextStage()
{
if (!chain)
return;
++_stage;
}
//! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid)
uint8 GetStage()
{
return _stage;
}
//! Resets all underlying variables (param, result and stage)
void Reset()
{
SetFirstParam(0);
SetSecondParam(nullptr);
FreeResult();
ResetStage();
}
private:
ACE_Future<Result> _result;
ParamType1 _param_1;
ParamType2 _param_2;
uint8 _stage;
};
template <typename Result, typename ParamType1, typename ParamType2, typename ParamType3, bool chain = false>
class QueryCallback_3
{
public:
QueryCallback_3() : _stage(chain ? 0 : CALLBACK_STAGE_INVALID) {}
//! The parameter of this function should be a resultset returned from either .AsyncQuery or .AsyncPQuery
void SetFutureResult(ACE_Future<Result> value)
{
_result = value;
}
ACE_Future<Result> GetFutureResult()
{
return _result;
}
int IsReady()
{
return _result.ready();
}
void GetResult(Result& res)
{
_result.get(res);
}
void FreeResult()
{
_result.cancel();
}
void SetFirstParam(ParamType1 value)
{
_param_1 = value;
}
void SetSecondParam(ParamType2 value)
{
_param_2 = value;
}
void SetThirdParam(ParamType3 value)
{
_param_3 = value;
}
ParamType1 GetFirstParam()
{
return _param_1;
}
ParamType2 GetSecondParam()
{
return _param_2;
}
ParamType3 GetThirdParam()
{
return _param_3;
}
//! Resets the stage of the callback chain
void ResetStage()
{
if (!chain)
return;
_stage = 0;
}
//! Advances the callback chain to the next stage, so upper level code can act on its results accordingly
void NextStage()
{
if (!chain)
return;
++_stage;
}
//! Returns the callback stage (or CALLBACK_STAGE_INVALID if invalid)
uint8 GetStage()
{
return _stage;
}
//! Resets all underlying variables (param, result and stage)
void Reset()
{
SetFirstParam(nullptr);
SetSecondParam(nullptr);
SetThirdParam(nullptr);
FreeResult();
ResetStage();
}
private:
ACE_Future<Result> _result;
ParamType1 _param_1;
ParamType2 _param_2;
ParamType3 _param_3;
uint8 _stage;
};
#endif

View file

@ -4,7 +4,7 @@
*/
#include "DatabaseWorker.h"
#include "ProducerConsumerQueue.h"
#include "PCQueue.h"
#include "SQLOperation.h"
DatabaseWorker::DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection)

View file

@ -14,7 +14,7 @@
#include "MySQLPreparedStatement.h"
#include "MySQLWorkaround.h"
#include "PreparedStatement.h"
#include "ProducerConsumerQueue.h"
#include "PCQueue.h"
#include "QueryCallback.h"
#include "QueryHolder.h"
#include "QueryResult.h"

View file

@ -20,7 +20,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
"LEFT JOIN account_access aa ON a.id = aa.id "
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
"WHERE a.username = ?", CONNECTION_SYNCH);
"WHERE a.username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_RECONNECTCHALLENGE,
"SELECT a.id, a.username, a.locked, a.lock_country, a.last_ip, a.failed_logins, "
"ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, ab.unbandate = ab.bandate, "
@ -30,14 +30,15 @@ void LoginDatabaseConnection::DoPrepareStatements()
"LEFT JOIN account_access aa ON a.id = aa.id "
"LEFT JOIN account_banned ab ON ab.id = a.id AND ab.active = 1 "
"LEFT JOIN ip_banned ipb ON ipb.ip = ? "
"WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_SYNCH);
"WHERE a.username = ? AND a.session_key IS NOT NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME, "SELECT a.id, a.session_key, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, "
"aa.gmlevel, ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id FROM account a LEFT JOIN account_access aa ON a.id = aa.id AND aa.RealmID IN (-1, ?) "
"LEFT JOIN account_banned ab ON a.id = ab.id AND ab.active = 1 LEFT JOIN account r ON a.id = r.recruiter WHERE a.username = ? "
"AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_SYNCH);
"AND a.session_key IS NOT NULL ORDER BY aa.RealmID DESC LIMIT 1", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_INFO, "SELECT unbandate > UNIX_TIMESTAMP() OR unbandate = bandate AS banned, NULL as country FROM ip_banned WHERE ip = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_REALMLIST, "SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE flag <> 3 ORDER BY name", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_EXPIRED_IP_BANS, "DELETE FROM ip_banned WHERE unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_EXPIRED_ACCOUNT_BANS, "UPDATE account_banned SET active = 0 WHERE active = 1 AND unbandate<>bandate AND unbandate<=UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_BANNED, "SELECT * FROM ip_banned WHERE ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_AUTO_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, 'Trinity realmd', 'Failed login autoban')", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_IP_BANNED_ALL, "SELECT ip, bandate, unbandate, bannedby, banreason FROM ip_banned WHERE (bandate = unbandate OR unbandate > UNIX_TIMESTAMP()) ORDER BY unbandate", CONNECTION_SYNCH);
@ -55,6 +56,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_NAME, "SELECT id, username FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL, "SELECT id, username FROM account WHERE email = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_NUM_CHARS_ON_REALM, "SELECT numchars FROM realmcharacters WHERE realmid = ? AND acctid= ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_REALM_CHARACTER_COUNTS, "SELECT realmid, numchars FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_IP, "SELECT id, username FROM account WHERE last_ip = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_ACCOUNT_BY_ID, "SELECT 1 FROM account WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_IP_BANNED, "INSERT INTO ip_banned (ip, bandate, unbandate, bannedby, banreason) VALUES (?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()+?, ?, ?)", CONNECTION_ASYNC);

View file

@ -19,6 +19,7 @@ enum LoginDatabaseStatements : uint32
LOGIN_SEL_REALMLIST,
LOGIN_DEL_EXPIRED_IP_BANS,
LOGIN_UPD_EXPIRED_ACCOUNT_BANS,
LOGIN_SEL_IP_INFO,
LOGIN_SEL_IP_BANNED,
LOGIN_INS_IP_AUTO_BANNED,
LOGIN_SEL_ACCOUNT_BANNED,
@ -37,6 +38,7 @@ enum LoginDatabaseStatements : uint32
LOGIN_SEL_ACCOUNT_INFO_BY_NAME,
LOGIN_SEL_ACCOUNT_LIST_BY_EMAIL,
LOGIN_SEL_NUM_CHARS_ON_REALM,
LOGIN_SEL_REALM_CHARACTER_COUNTS,
LOGIN_SEL_ACCOUNT_BY_IP,
LOGIN_INS_IP_BANNED,
LOGIN_DEL_IP_NOT_BANNED,

View file

@ -4,188 +4,319 @@
#include "ArenaSpectator.h"
#include "BattlegroundMgr.h"
#include "Chat.h"
#include "LFGMgr.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Map.h"
#include "Pet.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "World.h"
namespace ArenaSpectator
bool ArenaSpectator::HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args)
{
bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args)
Player* player = handler->GetSession()->GetPlayer();
std::list<std::string> errors;
if (!*args)
{
Player* player = handler->GetSession()->GetPlayer();
std::list<std::string> errors;
if (!*args)
handler->SendSysMessage("Missing player name.");
return true;
}
if (player->IsSpectator())
{
if (player->FindMap() && player->FindMap()->IsBattleArena())
{
handler->SendSysMessage("Missing player name.");
HandleSpectatorWatchCommand(handler, args);
return true;
}
if (player->IsSpectator())
{
if (player->FindMap() && player->FindMap()->IsBattleArena())
handler->PSendSysMessage("You are already spectacting arena.");
return true;
}
if (player->getClass() == CLASS_DEATH_KNIGHT && player->GetMapId() == 609)
{
handler->PSendSysMessage("Death Knights can't spectate before finishing questline.");
return true;
}
std::string name = std::string(args);
Player* spectate = ObjectAccessor::FindPlayerByName(name);
if (!spectate)
{
handler->SendSysMessage("Requested player not found.");
return true;
}
if (spectate->IsSpectator())
{
handler->SendSysMessage("Requested player is a spectator.");
return true;
}
if (!spectate->FindMap() || !spectate->FindMap()->IsBattleArena())
{
handler->SendSysMessage("Requested player is not in arena.");
return true;
}
BattlegroundMap* bgmap = ((BattlegroundMap*)spectate->FindMap());
if (!bgmap->GetBG() || bgmap->GetBG()->GetStatus() == STATUS_WAIT_LEAVE)
{
handler->SendSysMessage("This arena battle has finished.");
return true;
}
if (player->IsBeingTeleported() || !player->IsInWorld())
errors.push_back("Can't use while being teleported.");
if (!player->FindMap() || player->FindMap()->Instanceable())
errors.push_back("Can't use while in instance, bg or arena.");
if (player->GetVehicle())
errors.push_back("Can't be on a vehicle.");
if (player->IsInCombat())
errors.push_back("Can't be in combat.");
if (player->isUsingLfg())
errors.push_back("Can't spectate while using LFG system.");
if (player->InBattlegroundQueue())
errors.push_back("Can't be queued for arena or bg.");
if (player->GetGroup())
errors.push_back("Can't be in a group.");
if (player->HasUnitState(UNIT_STATE_ISOLATED))
errors.push_back("Can't be isolated.");
if (player->m_mover != player)
errors.push_back("You must control yourself.");
if (player->IsInFlight())
errors.push_back("Can't be in flight.");
if (player->IsMounted())
errors.push_back("Dismount before spectating.");
if (!player->IsAlive())
errors.push_back("Must be alive.");
if (!player->m_Controlled.empty())
errors.push_back("Can't be controlling creatures.");
const Unit::VisibleAuraMap* va = player->GetVisibleAuras();
for (auto itr = va->begin(); itr != va->end(); ++itr)
if (Aura* aura = itr->second->GetBase())
if (!itr->second->IsPositive() && !aura->IsPermanent() && aura->GetDuration() < HOUR * IN_MILLISECONDS)
{
HandleSpectatorWatchCommand(handler, args);
return true;
}
handler->PSendSysMessage("You are already spectacting arena.");
return true;
}
if (player->getClass() == CLASS_DEATH_KNIGHT && player->GetMapId() == 609)
{
handler->PSendSysMessage("Death Knights can't spectate before finishing questline.");
return true;
}
std::string name = std::string(args);
Player* spectate = ObjectAccessor::FindPlayerByName(name);
if (!spectate)
{
handler->SendSysMessage("Requested player not found.");
return true;
}
if (spectate->IsSpectator())
{
handler->SendSysMessage("Requested player is a spectator.");
return true;
}
if (!spectate->FindMap() || !spectate->FindMap()->IsBattleArena())
{
handler->SendSysMessage("Requested player is not in arena.");
return true;
}
BattlegroundMap* bgmap = ((BattlegroundMap*)spectate->FindMap());
if (!bgmap->GetBG() || bgmap->GetBG()->GetStatus() == STATUS_WAIT_LEAVE)
{
handler->SendSysMessage("This arena battle has finished.");
return true;
}
if (player->IsBeingTeleported() || !player->IsInWorld())
errors.push_back("Can't use while being teleported.");
if (!player->FindMap() || player->FindMap()->Instanceable())
errors.push_back("Can't use while in instance, bg or arena.");
if (player->GetVehicle())
errors.push_back("Can't be on a vehicle.");
if (player->IsInCombat())
errors.push_back("Can't be in combat.");
if (player->isUsingLfg())
errors.push_back("Can't spectate while using LFG system.");
if (player->InBattlegroundQueue())
errors.push_back("Can't be queued for arena or bg.");
if (player->GetGroup())
errors.push_back("Can't be in a group.");
if (player->HasUnitState(UNIT_STATE_ISOLATED))
errors.push_back("Can't be isolated.");
if (player->m_mover != player)
errors.push_back("You must control yourself.");
if (player->IsInFlight())
errors.push_back("Can't be in flight.");
if (player->IsMounted())
errors.push_back("Dismount before spectating.");
if (!player->IsAlive())
errors.push_back("Must be alive.");
if (!player->m_Controlled.empty())
errors.push_back("Can't be controlling creatures.");
const Unit::VisibleAuraMap* va = player->GetVisibleAuras();
for (Unit::VisibleAuraMap::const_iterator itr = va->begin(); itr != va->end(); ++itr)
if (Aura* aura = itr->second->GetBase())
if (!itr->second->IsPositive() && !aura->IsPermanent() && aura->GetDuration() < HOUR * IN_MILLISECONDS)
switch (aura->GetSpellInfo()->Id)
{
switch (aura->GetSpellInfo()->Id)
{
case lfg::LFG_SPELL_DUNGEON_DESERTER:
case lfg::LFG_SPELL_DUNGEON_COOLDOWN:
case 26013: // bg deserter
case 57724: // sated
case 57723: // exhaustion
case 25771: // forbearance
case 15007: // resurrection sickness
case 24755: // Tricked or Treated (z eventu)
continue;
}
errors.push_back("Can't have negative auras.");
break;
case lfg::LFG_SPELL_DUNGEON_DESERTER:
case lfg::LFG_SPELL_DUNGEON_COOLDOWN:
case 26013: // bg deserter
case 57724: // sated
case 57723: // exhaustion
case 25771: // forbearance
case 15007: // resurrection sickness
case 24755: // Tricked or Treated (z eventu)
continue;
}
if (uint32 inviteInstanceId = player->GetPendingSpectatorInviteInstanceId())
{
if (Battleground* tbg = sBattlegroundMgr->GetBattleground(inviteInstanceId))
tbg->RemoveToBeTeleported(player->GetGUID());
player->SetPendingSpectatorInviteInstanceId(0);
}
bool bgPreparation = false;
if ((!handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS) ||
(handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_WAIT_JOIN && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS))
{
bgPreparation = true;
handler->SendSysMessage("Arena is not in progress yet. You will be invited as soon as it starts.");
bgmap->GetBG()->AddToBeTeleported(player->GetGUID(), spectate->GetGUID());
player->SetPendingSpectatorInviteInstanceId(spectate->GetBattlegroundId());
}
if (!errors.empty())
{
handler->PSendSysMessage("To spectate, please fix the following:");
for (std::list<std::string>::const_iterator itr = errors.begin(); itr != errors.end(); ++itr)
handler->PSendSysMessage("- %s", (*itr).c_str());
return true;
}
if (bgPreparation)
return true;
player->SetPendingSpectatorForBG(spectate->GetBattlegroundId());
player->SetBattlegroundId(spectate->GetBattlegroundId(), spectate->GetBattlegroundTypeId(), PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL);
player->SetEntryPoint();
float z = spectate->GetMapId() == 618 ? std::max(28.27f, spectate->GetPositionZ() + 0.25f) : spectate->GetPositionZ() + 0.25f;
player->TeleportTo(spectate->GetMapId(), spectate->GetPositionX(), spectate->GetPositionY(), z, spectate->GetOrientation(), TELE_TO_GM_MODE);
return true;
}
bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args)
{
if (!*args)
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player->IsSpectator())
return true;
if (!player->FindMap() || !player->FindMap()->IsBattleArena())
return true;
Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return true;
std::string name = std::string(args);
Player* spectate = ObjectAccessor::FindPlayerByName(name);
if (!spectate || !spectate->IsAlive() || spectate->IsSpectator() || spectate->GetGUID() == player->GetGUID() || !spectate->IsInWorld() || !spectate->FindMap() || spectate->IsBeingTeleported() || spectate->FindMap() != player->FindMap() || !bg->IsPlayerInBattleground(spectate->GetGUID()))
return true;
if (WorldObject* o = player->GetViewpoint())
if (Unit* u = o->ToUnit())
{
u->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, player->GetGUID());
player->RemoveAurasDueToSpell(SPECTATOR_SPELL_BINDSIGHT, player->GetGUID(), (1 << EFFECT_1));
if (u->GetGUID() == spectate->GetGUID())
return true;
errors.push_back("Can't have negative auras.");
break;
}
if (player->GetGuidValue(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint!
return true;
if (uint32 inviteInstanceId = player->GetPendingSpectatorInviteInstanceId())
{
if (Battleground* tbg = sBattlegroundMgr->GetBattleground(inviteInstanceId))
tbg->RemoveToBeTeleported(player->GetGUID());
player->SetPendingSpectatorInviteInstanceId(0);
}
if (player->HaveAtClient(spectate))
player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true);
bool bgPreparation = false;
if ((!handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS) ||
(handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_WAIT_JOIN && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS))
{
bgPreparation = true;
handler->SendSysMessage("Arena is not in progress yet. You will be invited as soon as it starts.");
bgmap->GetBG()->AddToBeTeleported(player->GetGUID(), spectate->GetGUID());
player->SetPendingSpectatorInviteInstanceId(spectate->GetBattlegroundId());
}
if (!errors.empty())
{
handler->PSendSysMessage("To spectate, please fix the following:");
for (std::list<std::string>::const_iterator itr = errors.begin(); itr != errors.end(); ++itr)
handler->PSendSysMessage("- %s", (*itr).c_str());
return true;
}
if (bgPreparation)
return true;
float z = spectate->GetMapId() == 618 ? std::max(28.27f, spectate->GetPositionZ() + 0.25f) : spectate->GetPositionZ() + 0.25f;
player->SetPendingSpectatorForBG(spectate->GetBattlegroundId());
player->SetBattlegroundId(spectate->GetBattlegroundId(), spectate->GetBattlegroundTypeId(), PLAYER_MAX_BATTLEGROUND_QUEUES, false, false, TEAM_NEUTRAL);
player->SetEntryPoint();
player->TeleportTo(spectate->GetMapId(), spectate->GetPositionX(), spectate->GetPositionY(), z, spectate->GetOrientation(), TELE_TO_GM_MODE);
return true;
}
bool ArenaSpectator::HandleSpectatorWatchCommand(ChatHandler* handler, char const* args)
{
if (!*args)
return true;
Player* player = handler->GetSession()->GetPlayer();
if (!player->IsSpectator())
return true;
if (!player->FindMap() || !player->FindMap()->IsBattleArena())
return true;
Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return true;
std::string name = std::string(args);
Player* spectate = ObjectAccessor::FindPlayerByName(name);
if (!spectate || !spectate->IsAlive() || spectate->IsSpectator() || spectate->GetGUID() == player->GetGUID() || !spectate->IsInWorld() || !spectate->FindMap() || spectate->IsBeingTeleported() || spectate->FindMap() != player->FindMap() || !bg->IsPlayerInBattleground(spectate->GetGUID()))
return true;
if (WorldObject* o = player->GetViewpoint())
if (Unit* u = o->ToUnit())
{
u->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, player->GetGUID());
player->RemoveAurasDueToSpell(SPECTATOR_SPELL_BINDSIGHT, player->GetGUID(), (1 << EFFECT_1));
if (u->GetGUID() == spectate->GetGUID())
return true;
}
if (player->GetUInt64Value(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint!
return true;
if (player->HaveAtClient(spectate))
player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true);
return true;
}
void ArenaSpectator::CreatePacket(WorldPacket& data, std::string const& message)
{
size_t len = message.length();
data.Initialize(SMSG_MESSAGECHAT, 1 + 4 + 8 + 4 + 8 + 4 + 1 + len + 1);
data << uint8(CHAT_MSG_WHISPER);
data << uint32(LANG_ADDON);
data << uint64(0);
data << uint32(0);
data << uint64(0);
data << uint32(len + 1);
data << message;
data << uint8(0);
}
void ArenaSpectator::HandleResetCommand(Player* player)
{
if (!player->FindMap() || !player->IsInWorld() || !player->FindMap()->IsBattleArena())
return;
Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return;
Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers();
for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
{
if (player->HasReceivedSpectatorResetFor(itr->first))
continue;
Player* plr = itr->second;
player->AddReceivedSpectatorResetFor(itr->first);
SendCommand_String(player, itr->first, "NME", plr->GetName().c_str());
// Xinef: addon compatibility
SendCommand_UInt32Value(player, itr->first, "TEM", plr->GetBgTeamId() == TEAM_ALLIANCE ? ALLIANCE : HORDE);
SendCommand_UInt32Value(player, itr->first, "CLA", plr->getClass());
SendCommand_UInt32Value(player, itr->first, "MHP", plr->GetMaxHealth());
SendCommand_UInt32Value(player, itr->first, "CHP", plr->GetHealth());
SendCommand_UInt32Value(player, itr->first, "STA", plr->IsAlive() ? 1 : 0);
Powers ptype = plr->getPowerType();
SendCommand_UInt32Value(player, itr->first, "PWT", ptype);
SendCommand_UInt32Value(player, itr->first, "MPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetMaxPower(ptype) / 10 : plr->GetMaxPower(ptype));
SendCommand_UInt32Value(player, itr->first, "CPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetPower(ptype) / 10 : plr->GetPower(ptype));
Pet* pet = plr->GetPet();
SendCommand_UInt32Value(player, itr->first, "PHP", pet && pet->GetCreatureTemplate()->family ? (uint32)pet->GetHealthPct() : 0);
SendCommand_UInt32Value(player, itr->first, "PET", pet ? pet->GetCreatureTemplate()->family : 0);
SendCommand_GUID(player, itr->first, "TRG", plr->GetTarget());
SendCommand_UInt32Value(player, itr->first, "RES", 1);
SendCommand_UInt32Value(player, itr->first, "CDC", 1);
SendCommand_UInt32Value(player, itr->first, "TIM", (bg->GetStartTime() < 46 * MINUTE * IN_MILLISECONDS) ? (46 * MINUTE * IN_MILLISECONDS - bg->GetStartTime()) / IN_MILLISECONDS : 0);
// "SPE" not here (only possible to send starting a new cast)
// send all "CD"
SpellCooldowns const& sc = plr->GetSpellCooldownMap();
for (SpellCooldowns::const_iterator itrc = sc.begin(); itrc != sc.end(); ++itrc)
if (itrc->second.sendToSpectator && itrc->second.maxduration >= SPECTATOR_COOLDOWN_MIN * IN_MILLISECONDS && itrc->second.maxduration <= SPECTATOR_COOLDOWN_MAX * IN_MILLISECONDS)
if (uint32 cd = (getMSTimeDiff(getMSTime(), itrc->second.end) / 1000))
SendCommand_Cooldown(player, itr->first, "ACD", itrc->first, cd, itrc->second.maxduration / 1000);
// send all visible "AUR"
Unit::VisibleAuraMap const* visibleAuras = plr->GetVisibleAuras();
for (Unit::VisibleAuraMap::const_iterator aitr = visibleAuras->begin(); aitr != visibleAuras->end(); ++aitr)
{
Aura* aura = aitr->second->GetBase();
if (ShouldSendAura(aura, aitr->second->GetEffectMask(), plr->GetGUID(), false))
SendCommand_Aura(player, itr->first, "AUR", aura->GetCasterGUID(), aura->GetSpellInfo()->Id, aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel, aura->GetDuration(), aura->GetMaxDuration(), (aura->GetCharges() > 1 ? aura->GetCharges() : aura->GetStackAmount()), false);
}
}
}
bool ArenaSpectator::ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove)
{
if (aura->GetSpellInfo()->SpellIconID == 1 || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_NO_AURA_ICON))
return false;
if (remove || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_DONT_BREAK_STEALTH) || aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_GENERIC)
return true;
for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
{
if (effMask & (1 << i))
{
AuraType at = aura->GetEffect(i)->GetAuraType();
if ((aura->GetEffect(i)->GetAmount() && (aura->GetSpellInfo()->IsPositive() || targetGUID != aura->GetCasterGUID())) ||
at == SPELL_AURA_MECHANIC_IMMUNITY || at == SPELL_AURA_EFFECT_IMMUNITY || at == SPELL_AURA_STATE_IMMUNITY || at == SPELL_AURA_SCHOOL_IMMUNITY || at == SPELL_AURA_DISPEL_IMMUNITY)
return true;
}
}
return false;
}
template<>
AC_GAME_API void ArenaSpectator::SendPacketTo(const Player* player, std::string&& message)
{
WorldPacket data;
CreatePacket(data, message);
player->GetSession()->SendPacket(&data);
}
template<>
AC_GAME_API void ArenaSpectator::SendPacketTo(const Map* map, std::string&& message)
{
if (!map->IsBattleArena())
return;
Battleground* bg = ((BattlegroundMap*)map)->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return;
WorldPacket data;
CreatePacket(data, message);
bg->SpectatorsSendPacket(data);
}

View file

@ -5,15 +5,17 @@
#ifndef AZEROTHCORE_ARENASPECTATOR_H
#define AZEROTHCORE_ARENASPECTATOR_H
#include "Battleground.h"
#include "Chat.h"
#include "LFGMgr.h"
#include "Map.h"
#include "Pet.h"
#include "Player.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"
#include "World.h"
#include "Common.h"
#include "ObjectDefines.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "StringFormat.h"
class Aura;
class Player;
class Map;
class WorldPacket;
#define SPECTATOR_ADDON_VERSION 27
#define SPECTATOR_BUFFER_LEN 150
@ -25,196 +27,78 @@
namespace ArenaSpectator
{
template<class T> inline void SendCommand(T* o, const char* format, ...) ATTR_PRINTF(2, 3);
inline void CreatePacket(WorldPacket& data, const char* m);
inline void SendPacketTo(const Player* p, const char* m);
inline void SendPacketTo(const Map* map, const char* m);
inline void HandleResetCommand(Player* p);
inline bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove);
template<class T> inline void SendCommand_String(T* p, ObjectGuid targetGUID, const char* prefix, const std::string& c);
template<class T> inline void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t);
template<class T> inline void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t);
template<class T> inline void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime);
template<class T> inline void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur);
template<class T> inline void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove);
bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args);
bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args);
// definitions below:
template<class T>
void SendCommand(T* o, const char* format, ...)
{
if (!format)
return;
char buffer[SPECTATOR_BUFFER_LEN];
va_list ap;
va_start(ap, format);
vsnprintf(buffer, SPECTATOR_BUFFER_LEN, format, ap);
va_end(ap);
SendPacketTo(o, buffer);
}
AC_GAME_API void SendPacketTo(const T* object, std::string&& message);
void CreatePacket(WorldPacket& data, const char* m)
template<class T, typename Format, typename... Args>
inline void SendCommand(T* o, Format&& fmt, Args&& ... args)
{
size_t len = strlen(m);
data.Initialize(SMSG_MESSAGECHAT, 1 + 4 + 8 + 4 + 8 + 4 + 1 + len + 1);
data << uint8(CHAT_MSG_WHISPER);
data << uint32(LANG_ADDON);
data << uint64(0);
data << uint32(0);
data << uint64(0);
data << uint32(len + 1);
data << m;
data << uint8(0);
}
void SendPacketTo(const Player* p, const char* m)
{
WorldPacket data;
CreatePacket(data, m);
p->GetSession()->SendPacket(&data);
}
void SendPacketTo(const Map* map, const char* m)
{
if (!map->IsBattleArena())
return;
Battleground* bg = ((BattlegroundMap*)map)->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return;
WorldPacket data;
CreatePacket(data, m);
bg->SpectatorsSendPacket(data);
SendPacketTo(o, Acore::StringFormat(std::forward<Format>(fmt), std::forward<Args>(args)...));
}
template<class T>
void SendCommand_String(T* o, ObjectGuid targetGUID, const char* prefix, const char* c)
inline void SendCommand_String(T* o, ObjectGuid targetGUID, const char* prefix, const char* c)
{
if (!targetGUID.IsPlayer())
return;
SendCommand(o, "%s0x%016llX;%s=%s;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, c);
SendCommand(o, "%s0x%016llX;%s=%s;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, c);
}
template<class T>
void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t)
inline void SendCommand_UInt32Value(T* o, ObjectGuid targetGUID, const char* prefix, uint32 t)
{
if (!targetGUID.IsPlayer())
return;
SendCommand(o, "%s0x%016llX;%s=%u;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, t);
SendCommand(o, "%s0x%016llX;%s=%u;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, t);
}
template<class T>
void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t)
inline void SendCommand_GUID(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid t)
{
if (!targetGUID.IsPlayer())
return;
SendCommand(o, "%s0x%016llX;%s=0x%016llX;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, (unsigned long long)t.GetRawValue());
SendCommand(o, "%s0x%016llX;%s=0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, t.GetRawValue());
}
template<class T>
void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime)
inline void SendCommand_Spell(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, int32 casttime)
{
if (!targetGUID.IsPlayer())
return;
SendCommand(o, "%s0x%016llX;%s=%u,%i;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, id, casttime);
SendCommand(o, "%s0x%016llX;%s=%u,%i;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, id, casttime);
}
template<class T>
void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur)
inline void SendCommand_Cooldown(T* o, ObjectGuid targetGUID, const char* prefix, uint32 id, uint32 dur, uint32 maxdur)
{
if (!targetGUID.IsPlayer())
return;
if (const SpellInfo* si = sSpellMgr->GetSpellInfo(id))
if (si->SpellIconID == 1)
return;
SendCommand(o, "%s0x%016llX;%s=%u,%u,%u;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, id, dur, maxdur);
SendCommand(o, "%s0x%016llX;%s=%u,%u,%u;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, id, dur, maxdur);
}
template<class T>
void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove)
inline void SendCommand_Aura(T* o, ObjectGuid targetGUID, const char* prefix, ObjectGuid caster, uint32 id, bool isDebuff, uint32 dispel, int32 dur, int32 maxdur, uint32 stack, bool remove)
{
if (!targetGUID.IsPlayer())
return;
SendCommand(o, "%s0x%016llX;%s=%u,%u,%i,%i,%u,%u,%u,0x%016llX;", SPECTATOR_ADDON_PREFIX, (unsigned long long)targetGUID.GetRawValue(), prefix, remove ? 1 : 0, stack, dur, maxdur, id, dispel, isDebuff ? 1 : 0, (unsigned long long)caster.GetRawValue());
SendCommand(o, "%s0x%016llX;%s=%u,%u,%i,%i,%u,%u,%u,0x%016llX;", SPECTATOR_ADDON_PREFIX, targetGUID.GetRawValue(), prefix, remove ? 1 : 0, stack, dur, maxdur, id, dispel, isDebuff ? 1 : 0, caster.GetRawValue());
}
void HandleResetCommand(Player* p)
{
if (!p->FindMap() || !p->IsInWorld() || !p->FindMap()->IsBattleArena())
return;
Battleground* bg = ((BattlegroundMap*)p->FindMap())->GetBG();
if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
return;
Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers();
for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
{
if (p->HasReceivedSpectatorResetFor(itr->first))
continue;
Player* plr = itr->second;
p->AddReceivedSpectatorResetFor(itr->first);
SendCommand_String(p, itr->first, "NME", plr->GetName().c_str());
// Xinef: addon compatibility
SendCommand_UInt32Value(p, itr->first, "TEM", plr->GetBgTeamId() == TEAM_ALLIANCE ? ALLIANCE : HORDE);
SendCommand_UInt32Value(p, itr->first, "CLA", plr->getClass());
SendCommand_UInt32Value(p, itr->first, "MHP", plr->GetMaxHealth());
SendCommand_UInt32Value(p, itr->first, "CHP", plr->GetHealth());
SendCommand_UInt32Value(p, itr->first, "STA", plr->IsAlive() ? 1 : 0);
Powers ptype = plr->getPowerType();
SendCommand_UInt32Value(p, itr->first, "PWT", ptype);
SendCommand_UInt32Value(p, itr->first, "MPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetMaxPower(ptype) / 10 : plr->GetMaxPower(ptype));
SendCommand_UInt32Value(p, itr->first, "CPW", ptype == POWER_RAGE || ptype == POWER_RUNIC_POWER ? plr->GetPower(ptype) / 10 : plr->GetPower(ptype));
Pet* pet = plr->GetPet();
SendCommand_UInt32Value(p, itr->first, "PHP", pet && pet->GetCreatureTemplate()->family ? (uint32)pet->GetHealthPct() : 0);
SendCommand_UInt32Value(p, itr->first, "PET", pet ? pet->GetCreatureTemplate()->family : 0);
SendCommand_GUID(p, itr->first, "TRG", plr->GetTarget());
SendCommand_UInt32Value(p, itr->first, "RES", 1);
SendCommand_UInt32Value(p, itr->first, "CDC", 1);
SendCommand_UInt32Value(p, itr->first, "TIM", (bg->GetStartTime() < 46 * MINUTE * IN_MILLISECONDS) ? (46 * MINUTE * IN_MILLISECONDS - bg->GetStartTime()) / IN_MILLISECONDS : 0);
// "SPE" not here (only possible to send starting a new cast)
// send all "CD"
SpellCooldowns const& sc = plr->GetSpellCooldownMap();
for (SpellCooldowns::const_iterator itrc = sc.begin(); itrc != sc.end(); ++itrc)
if (itrc->second.sendToSpectator && itrc->second.maxduration >= SPECTATOR_COOLDOWN_MIN * IN_MILLISECONDS && itrc->second.maxduration <= SPECTATOR_COOLDOWN_MAX * IN_MILLISECONDS)
if (uint32 cd = (getMSTimeDiff(World::GetGameTimeMS(), itrc->second.end) / 1000))
SendCommand_Cooldown(p, itr->first, "ACD", itrc->first, cd, itrc->second.maxduration / 1000);
// send all visible "AUR"
Unit::VisibleAuraMap const* visibleAuras = plr->GetVisibleAuras();
for (Unit::VisibleAuraMap::const_iterator aitr = visibleAuras->begin(); aitr != visibleAuras->end(); ++aitr)
{
Aura* aura = aitr->second->GetBase();
if (ShouldSendAura(aura, aitr->second->GetEffectMask(), plr->GetGUID(), false))
SendCommand_Aura(p, itr->first, "AUR", aura->GetCasterGUID(), aura->GetSpellInfo()->Id, aura->GetSpellInfo()->IsPositive(), aura->GetSpellInfo()->Dispel, aura->GetDuration(), aura->GetMaxDuration(), (aura->GetCharges() > 1 ? aura->GetCharges() : aura->GetStackAmount()), false);
}
}
}
bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove)
{
if (aura->GetSpellInfo()->SpellIconID == 1 || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR1_NO_AURA_ICON))
return false;
if (remove || aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_AURA_CC) || aura->GetSpellInfo()->SpellFamilyName == SPELLFAMILY_GENERIC)
return true;
for(uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
{
if (effMask & (1 << i))
{
AuraType at = aura->GetEffect(i)->GetAuraType();
if ((aura->GetEffect(i)->GetAmount() && (aura->GetSpellInfo()->IsPositive() || targetGUID != aura->GetCasterGUID())) ||
at == SPELL_AURA_MECHANIC_IMMUNITY || at == SPELL_AURA_EFFECT_IMMUNITY || at == SPELL_AURA_STATE_IMMUNITY || at == SPELL_AURA_SCHOOL_IMMUNITY || at == SPELL_AURA_DISPEL_IMMUNITY)
return true;
}
}
return false;
}
AC_GAME_API bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args);
AC_GAME_API bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args);
AC_GAME_API void CreatePacket(WorldPacket& data, std::string const& message);
AC_GAME_API void HandleResetCommand(Player* player);
AC_GAME_API bool ShouldSendAura(Aura* aura, uint8 effMask, ObjectGuid targetGUID, bool remove);
}
#endif

View file

@ -4,6 +4,7 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "Pet.h"
#include "ArenaSpectator.h"
#include "Battleground.h"
#include "Common.h"
@ -13,7 +14,7 @@
#include "InstanceScript.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Pet.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "SpellAuraEffects.h"
#include "SpellAuras.h"

View file

@ -58,7 +58,6 @@
#include "QuestDef.h"
#include "QueryHolder.h"
#include "ReputationMgr.h"
#include "revision.h"
#include "Realm.h"
#include "SavingSystem.h"
#include "ScriptMgr.h"

View file

@ -12,7 +12,7 @@
#include "Player.h"
#include "PoolMgr.h"
#include "ReputationMgr.h"
#include "revision.h"
#include "GitRevision.h"
#include "ScriptMgr.h"
#include "SpellAuraEffects.h"
@ -559,8 +559,8 @@ void Player::AddQuest(Quest const* quest, Object* questGiver)
auto stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_QUEST_TRACK);
stmt->setUInt32(0, quest_id);
stmt->setUInt32(1, GetGUID().GetCounter());
stmt->setString(2, _HASH);
stmt->setString(3, _DATE);
stmt->setString(2, GitRevision::GetHash());
stmt->setString(3, GitRevision::GetDate());
// add to Quest Tracker
CharacterDatabase.Execute(stmt);

View file

@ -3884,6 +3884,7 @@ void Unit::HandleSafeUnitPointersOnDelete(Unit* thisUnit)
return;
for (std::set<SafeUnitPointer*>::iterator itr = thisUnit->SafeUnitPointerSet.begin(); itr != thisUnit->SafeUnitPointerSet.end(); ++itr)
(*itr)->UnitDeleted();
thisUnit->SafeUnitPointerSet.clear();
}

View file

@ -4,8 +4,6 @@
//add here most rarely modified headers to speed up debug build compilation
#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
#include "Common.h"
#include "Log.h"
#include "MapManager.h"
@ -14,3 +12,4 @@
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "SharedDefines.h"
#include "WorldSocket.h"

View file

@ -345,18 +345,18 @@ void ScriptMgr::OnNetworkStop()
FOREACH_SCRIPT(ServerScript)->OnNetworkStop();
}
void ScriptMgr::OnSocketOpen(WorldSocket* socket)
void ScriptMgr::OnSocketOpen(std::shared_ptr<WorldSocket> socket)
{
ASSERT(socket);
FOREACH_SCRIPT(ServerScript)->OnSocketOpen(socket);
}
void ScriptMgr::OnSocketClose(WorldSocket* socket, bool wasNew)
void ScriptMgr::OnSocketClose(std::shared_ptr<WorldSocket> socket)
{
ASSERT(socket);
FOREACH_SCRIPT(ServerScript)->OnSocketClose(socket, wasNew);
FOREACH_SCRIPT(ServerScript)->OnSocketClose(socket);
}
void ScriptMgr::OnPacketReceive(WorldSession* session, WorldPacket const& packet)

View file

@ -144,11 +144,11 @@ public:
virtual void OnNetworkStop() { }
// Called when a remote socket establishes a connection to the server. Do not store the socket object.
virtual void OnSocketOpen(WorldSocket* /*socket*/) { }
virtual void OnSocketOpen(std::shared_ptr<WorldSocket> /*socket*/) { }
// Called when a socket is closed. Do not store the socket object, and do not rely on the connection
// being open; it is not.
virtual void OnSocketClose(WorldSocket* /*socket*/, bool /*wasNew*/) { }
virtual void OnSocketClose(std::shared_ptr<WorldSocket> /*socket*/) { }
// Called when a packet is sent to a client. The packet object is a copy of the original packet, so reading
// and modifying it is safe.
@ -1456,8 +1456,8 @@ public: /* SpellScriptLoader */
public: /* ServerScript */
void OnNetworkStart();
void OnNetworkStop();
void OnSocketOpen(WorldSocket* socket);
void OnSocketClose(WorldSocket* socket, bool wasNew);
void OnSocketOpen(std::shared_ptr<WorldSocket> socket);
void OnSocketClose(std::shared_ptr<WorldSocket> socket);
void OnPacketReceive(WorldSession* session, WorldPacket const& packet);
void OnPacketSend(WorldSession* session, WorldPacket const& packet);

View file

@ -31,7 +31,7 @@ bool WorldPackets::Strings::Utf8::Validate(std::string const& value)
//bool WorldPackets::Strings::Hyperlinks::Validate(std::string const& value)
//{
// if (!Warhead::Hyperlinks::CheckAllLinks(value))
// if (!Acore::Hyperlinks::CheckAllLinks(value))
// throw InvalidHyperlinkException(value);
//
// return true;

View file

@ -1,17 +1,18 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2008-2021 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "PacketLog.h"
#include "Config.h"
#include "IpAddress.h"
#include "Timer.h"
#include "WorldPacket.h"
#pragma pack(push, 1)
// Packet logging structures in PKT 3.1 format
// Packet logging structures in PKT 3.1 format
struct LogHeader
{
char Signature[3];
@ -27,11 +28,19 @@ struct LogHeader
struct PacketHeader
{
char Direction[4];
// used to uniquely identify a connection
struct OptionalData
{
uint8 SocketIPBytes[16];
uint32 SocketPort;
};
uint32 Direction;
uint32 ConnectionId;
uint32 ArrivalTicks;
uint32 OptionalDataSize;
uint32 Length;
OptionalData OptionalData;
uint32 Opcode;
};
@ -45,7 +54,9 @@ PacketLog::PacketLog() : _file(nullptr)
PacketLog::~PacketLog()
{
if (_file)
{
fclose(_file);
}
_file = nullptr;
}
@ -60,9 +71,10 @@ void PacketLog::Initialize()
{
std::string logsDir = sConfigMgr->GetOption<std::string>("LogsDir", "");
if (!logsDir.empty())
if ((logsDir.at(logsDir.length() - 1) != '/') && (logsDir.at(logsDir.length() - 1) != '\\'))
logsDir.push_back('/');
if (!logsDir.empty() && (logsDir.at(logsDir.length() - 1) != '/') && (logsDir.at(logsDir.length() - 1) != '\\'))
{
logsDir.push_back('/');
}
std::string logname = sConfigMgr->GetOption<std::string>("PacketLogFile", "");
if (!logname.empty())
@ -80,23 +92,42 @@ void PacketLog::Initialize()
header.SniffStartTicks = getMSTime();
header.OptionalDataSize = 0;
fwrite(&header, sizeof(header), 1, _file);
if (CanLogPacket())
{
fwrite(&header, sizeof(header), 1, _file);
}
}
}
void PacketLog::LogPacket(WorldPacket const& packet, Direction direction)
void PacketLog::LogPacket(WorldPacket const& packet, Direction direction, boost::asio::ip::address const& addr, uint16 port)
{
std::lock_guard<std::mutex> lock(_logPacketLock);
PacketHeader header;
*reinterpret_cast<uint32*>(header.Direction) = direction == CLIENT_TO_SERVER ? 0x47534d43 : 0x47534d53;
header.Direction = direction == CLIENT_TO_SERVER ? 0x47534d43 : 0x47534d53;
header.ConnectionId = 0;
header.ArrivalTicks = getMSTime();
header.OptionalDataSize = 0;
header.Length = packet.size() + 4;
header.OptionalDataSize = sizeof(header.OptionalData);
memset(header.OptionalData.SocketIPBytes, 0, sizeof(header.OptionalData.SocketIPBytes));
if (addr.is_v4())
{
auto bytes = addr.to_v4().to_bytes();
memcpy(header.OptionalData.SocketIPBytes, bytes.data(), bytes.size());
}
else if (addr.is_v6())
{
auto bytes = addr.to_v6().to_bytes();
memcpy(header.OptionalData.SocketIPBytes, bytes.data(), bytes.size());
}
header.OptionalData.SocketPort = port;
header.Length = packet.size() + sizeof(header.Opcode);
header.Opcode = packet.GetOpcode();
fwrite(&header, sizeof(header), 1, _file);
if (!packet.empty())
{
fwrite(packet.contents(), 1, packet.size(), _file);

View file

@ -8,6 +8,7 @@
#define ACORE_PACKETLOG_H
#include "Common.h"
#include <boost/asio/ip/address.hpp>
#include <mutex>
enum Direction
@ -18,7 +19,7 @@ enum Direction
class WorldPacket;
class PacketLog
class AC_GAME_API PacketLog
{
private:
PacketLog();
@ -31,7 +32,7 @@ public:
void Initialize();
bool CanLogPacket() const { return (_file != nullptr); }
void LogPacket(WorldPacket const& packet, Direction direction);
void LogPacket(WorldPacket const& packet, Direction direction, boost::asio::ip::address const& addr, uint16 port);
private:
FILE* _file;

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#ifndef __SERVERPKTHDR_H__
#define __SERVERPKTHDR_H__
#include "Log.h"
#pragma pack(push, 1)
struct ServerPktHeader
{
/**
* size is the length of the payload _plus_ the length of the opcode
*/
ServerPktHeader(uint32 size, uint16 cmd) : size(size)
{
uint8 headerIndex=0;
if (isLargePacket())
{
LOG_DEBUG("network", "initializing large server to client packet. Size: %u, cmd: %u", size, cmd);
header[headerIndex++] = 0x80 | (0xFF & (size >> 16));
}
header[headerIndex++] = 0xFF &(size >> 8);
header[headerIndex++] = 0xFF & size;
header[headerIndex++] = 0xFF & cmd;
header[headerIndex++] = 0xFF & (cmd >> 8);
}
uint8 getHeaderLength()
{
// cmd = 2 bytes, size= 2||3bytes
return 2 + (isLargePacket() ? 3 : 2);
}
bool isLargePacket() const
{
return size > 0x7FFF;
}
const uint32 size;
uint8 header[5];
};
#pragma pack(pop)
#endif

View file

@ -8,7 +8,7 @@
\ingroup u2w
*/
#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
#include "WorldSession.h"
#include "AccountMgr.h"
#include "BattlegroundMgr.h"
#include "Common.h"
@ -25,6 +25,7 @@
#include "PacketUtilities.h"
#include "Pet.h"
#include "Player.h"
#include "QueryHolder.h"
#include "SavingSystem.h"
#include "ScriptMgr.h"
#include "SocialMgr.h"
@ -34,9 +35,8 @@
#include "WardenWin.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "QueryHolder.h"
#include "zlib.h"
#include "WorldSocket.h"
#include <zlib.h>
#ifdef ELUNA
#include "LuaEngine.h"
@ -91,7 +91,8 @@ bool WorldSessionFilter::Process(WorldPacket* packet)
}
/// WorldSession constructor
WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime) :
WorldSession::WorldSession(uint32 id, std::string&& name, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion,
time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime) :
m_muteTime(mute_time),
m_timeOutTime(0),
_lastAuctionListItemsMSTime(0),
@ -103,6 +104,7 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
_security(sec),
_skipQueue(skipQueue),
_accountId(id),
_accountName(std::move(name)),
m_expansion(expansion),
m_total_time(TotalTime),
_logoutTime(0),
@ -125,7 +127,6 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
{
memset(m_Tutorials, 0, sizeof(m_Tutorials));
_warden = nullptr;
_offlineTime = 0;
_kicked = false;
_shouldSetOfflineInDB = true;
@ -135,10 +136,9 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
if (sock)
{
m_Address = sock->GetRemoteAddress();
sock->AddReference();
m_Address = sock->GetRemoteIpAddress().to_string();
ResetTimeOutTime(false);
LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = %u;", GetAccountId());
LoginDatabase.PExecute("UPDATE account SET online = 1 WHERE id = %u;", GetAccountId()); // One-time query
}
}
@ -154,17 +154,10 @@ WorldSession::~WorldSession()
/// - If have unclosed socket, close it
if (m_Socket)
{
m_Socket->CloseSocket("WorldSession destructor");
m_Socket->RemoveReference();
m_Socket->CloseSocket();
m_Socket = nullptr;
}
if (_warden)
{
delete _warden;
_warden = nullptr;
}
///- empty incoming packet queue
WorldPacket* packet = nullptr;
while (_recvQueue.next(packet))
@ -257,9 +250,7 @@ void WorldSession::SendPacket(WorldPacket const* packet)
#endif
LOG_TRACE("network.opcode", "S->C: %s %s", GetPlayerInfo().c_str(), GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet->GetOpcode())).c_str());
if (m_Socket->SendPacket(*packet) == -1)
m_Socket->CloseSocket("m_Socket->SendPacket(*packet) == -1");
m_Socket->SendPacket(*packet);
}
/// Add an incoming packet to the queue
@ -290,26 +281,28 @@ void WorldSession::LogUnprocessedTail(WorldPacket* packet)
/// Update the WorldSession (triggered by World update)
bool WorldSession::Update(uint32 diff, PacketFilter& updater)
{
if (updater.ProcessUnsafe())
{
UpdateTimeOutTime(diff);
///- Before we process anything:
/// If necessary, kick the player because the client didn't send anything for too long
/// (or they've been idling in character select)
if (sWorld->getBoolConfig(CONFIG_CLOSE_IDLE_CONNECTIONS) && IsConnectionIdle() && m_Socket)
m_Socket->CloseSocket();
/// If necessary, kick the player because the client didn't send anything for too long
/// (or they've been idling in character select)
if (sWorld->getBoolConfig(CONFIG_CLOSE_IDLE_CONNECTIONS) && IsConnectionIdle() && m_Socket)
m_Socket->CloseSocket("Client didn't send anything for too long");
}
if (updater.ProcessUnsafe())
UpdateTimeOutTime(diff);
HandleTeleportTimeout(updater.ProcessUnsafe());
uint32 _startMSTime = getMSTime();
///- Retrieve packets from the receive queue and call the appropriate handlers
/// not process packets if socket already closed
WorldPacket* packet = nullptr;
//! Delete packet after processing by default
bool deletePacket = true;
WorldPacket* firstDelayedPacket = nullptr;
std::vector<WorldPacket*> requeuePackets;
uint32 processedPackets = 0;
time_t currentTime = time(nullptr);
while (m_Socket && !m_Socket->IsClosed() && !_recvQueue.empty() && _recvQueue.peek(true) != firstDelayedPacket && _recvQueue.next(packet, updater))
while (m_Socket && _recvQueue.next(packet, updater))
{
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
@ -412,38 +405,9 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
//Any leftover will be processed in next update
if (processedPackets > MAX_PROCESSED_PACKETS_IN_SAME_WORLDSESSION_UPDATE)
break;
if (getMSTimeDiff(_startMSTime, getMSTime()) >= 3) // limit (by time) packets processed in one update, prevent DDoS
break;
}
if (m_Socket && !m_Socket->IsClosed())
ProcessQueryCallbacks();
if (updater.ProcessUnsafe())
{
if (m_Socket && !m_Socket->IsClosed() && _warden)
{
_warden->Update(diff);
}
time_t currTime = time(nullptr);
if (ShouldLogOut(currTime) && !m_playerLoading)
{
LogoutPlayer(true);
}
if (m_Socket && m_Socket->IsClosed())
{
m_Socket->RemoveReference();
m_Socket = nullptr;
}
if (!m_Socket)
{
return false;
}
}
_recvQueue.readd(requeuePackets.begin(), requeuePackets.end());
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
@ -461,29 +425,60 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
}
}
ProcessQueryCallbacks();
//check if we are safe to proceed with logout
//logout procedure should happen only in World::UpdateSessions() method!!!
if (updater.ProcessUnsafe())
{
if (m_Socket && m_Socket->IsOpen() && _warden)
{
_warden->Update(diff);
}
if (ShouldLogOut(currentTime) && !m_playerLoading)
{
LogoutPlayer(true);
}
if (m_Socket && !m_Socket->IsOpen())
{
if (GetPlayer() && _warden)
_warden->Update(diff);
m_Socket = nullptr;
}
if (!m_Socket)
{
return false;
}
}
return true;
}
bool WorldSession::HandleSocketClosed()
{
if (m_Socket && m_Socket->IsClosed() && !IsKicked() && GetPlayer() && !PlayerLogout() && GetPlayer()->m_taxi.empty() && GetPlayer()->IsInWorld() && !World::IsStopped())
if (m_Socket && !m_Socket->IsOpen() && !IsKicked() && GetPlayer() && !PlayerLogout() && GetPlayer()->m_taxi.empty() && GetPlayer()->IsInWorld() && !World::IsStopped())
{
m_Socket->RemoveReference();
m_Socket = nullptr;
GetPlayer()->TradeCancel(false);
return true;
}
return false;
}
bool WorldSession::IsSocketClosed() const {
return !m_Socket || m_Socket->IsClosed();
bool WorldSession::IsSocketClosed() const
{
return !m_Socket || !m_Socket->IsOpen();
}
void WorldSession::HandleTeleportTimeout(bool updateInSessions)
{
// pussywizard: handle teleport ack timeout
if (m_Socket && !m_Socket->IsClosed() && GetPlayer() && GetPlayer()->IsBeingTeleported())
if (m_Socket && m_Socket->IsOpen() && GetPlayer() && GetPlayer()->IsBeingTeleported())
{
time_t currTime = time(nullptr);
if (updateInSessions) // session update from World::UpdateSessions
@ -681,7 +676,12 @@ void WorldSession::LogoutPlayer(bool save)
void WorldSession::KickPlayer(std::string const& reason, bool setKicked)
{
if (m_Socket)
m_Socket->CloseSocket(reason);
{
LOG_INFO("network.kick", "Account: %u Character: '%s' %s kicked with reason: %s", GetAccountId(), _player ? _player->GetName().c_str() : "<none>",
_player ? _player->GetGUID().ToString().c_str() : "", reason.c_str());
m_Socket->CloseSocket();
}
if (setKicked)
SetKicked(true); // pussywizard: the session won't be left ingame for 60 seconds and to also kick offline session
@ -947,41 +947,41 @@ void WorldSession::ReadMovementInfo(WorldPacket& data, MovementInfo* mi)
It will freeze clients that receive this player's movement info.
*/
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ROOT),
MOVEMENTFLAG_ROOT);
MOVEMENTFLAG_ROOT);
//! Cannot hover without SPELL_AURA_HOVER
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_HOVER) && !GetPlayer()->HasAuraType(SPELL_AURA_HOVER),
MOVEMENTFLAG_HOVER);
MOVEMENTFLAG_HOVER);
//! Cannot ascend and descend at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_ASCENDING) && mi->HasMovementFlag(MOVEMENTFLAG_DESCENDING),
MOVEMENTFLAG_ASCENDING | MOVEMENTFLAG_DESCENDING);
MOVEMENTFLAG_ASCENDING | MOVEMENTFLAG_DESCENDING);
//! Cannot move left and right at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_RIGHT),
MOVEMENTFLAG_LEFT | MOVEMENTFLAG_RIGHT);
MOVEMENTFLAG_LEFT | MOVEMENTFLAG_RIGHT);
//! Cannot strafe left and right at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_LEFT) && mi->HasMovementFlag(MOVEMENTFLAG_STRAFE_RIGHT),
MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT);
MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT);
//! Cannot pitch up and down at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_PITCH_UP) && mi->HasMovementFlag(MOVEMENTFLAG_PITCH_DOWN),
MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN);
MOVEMENTFLAG_PITCH_UP | MOVEMENTFLAG_PITCH_DOWN);
//! Cannot move forwards and backwards at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FORWARD) && mi->HasMovementFlag(MOVEMENTFLAG_BACKWARD),
MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD);
MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_BACKWARD);
//! Cannot walk on water without SPELL_AURA_WATER_WALK
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_WATERWALKING) &&
!GetPlayer()->HasAuraType(SPELL_AURA_WATER_WALK) &&
!GetPlayer()->HasAuraType(SPELL_AURA_GHOST),
MOVEMENTFLAG_WATERWALKING);
!GetPlayer()->HasAuraType(SPELL_AURA_WATER_WALK) &&
!GetPlayer()->HasAuraType(SPELL_AURA_GHOST),
MOVEMENTFLAG_WATERWALKING);
//! Cannot feather fall without SPELL_AURA_FEATHER_FALL
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FALLING_SLOW) && !GetPlayer()->HasAuraType(SPELL_AURA_FEATHER_FALL),
MOVEMENTFLAG_FALLING_SLOW);
MOVEMENTFLAG_FALLING_SLOW);
/*! Cannot fly if no fly auras present. Exception is being a GM.
Note that we check for account level instead of Player::IsGameMaster() because in some
@ -990,14 +990,14 @@ void WorldSession::ReadMovementInfo(WorldPacket& data, MovementInfo* mi)
*/
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY) && GetSecurity() == SEC_PLAYER && !GetPlayer()->m_mover->HasAuraType(SPELL_AURA_FLY) && !GetPlayer()->m_mover->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED),
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY);
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY);
//! Cannot fly and fall at the same time
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY) && mi->HasMovementFlag(MOVEMENTFLAG_FALLING),
MOVEMENTFLAG_FALLING);
MOVEMENTFLAG_FALLING);
REMOVE_VIOLATING_FLAGS(mi->HasMovementFlag(MOVEMENTFLAG_SPLINE_ENABLED) &&
(!GetPlayer()->movespline->Initialized() || GetPlayer()->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED);
(!GetPlayer()->movespline->Initialized() || GetPlayer()->movespline->Finalized()), MOVEMENTFLAG_SPLINE_ENABLED);
#undef REMOVE_VIOLATING_FLAGS
}
@ -1040,7 +1040,7 @@ void WorldSession::WriteMovementInfo(WorldPacket* data, MovementInfo* mi)
*data << mi->splineElevation;
}
void WorldSession::ReadAddonsInfo(WorldPacket& data)
void WorldSession::ReadAddonsInfo(ByteBuffer& data)
{
if (data.rpos() + 4 > data.size())
return;
@ -1218,7 +1218,7 @@ void WorldSession::InitWarden(SessionKey const& k, std::string const& os)
{
if (os == "Win")
{
_warden = new WardenWin();
_warden = std::make_unique<WardenWin>();
_warden->Init(this, k);
}
else if (os == "OSX")
@ -1533,6 +1533,9 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co
return maxPacketCounterAllowed;
}
WorldSession::DosProtection::DosProtection(WorldSession* s) :
Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY)) { }
void WorldSession::ResetTimeSync()
{
_timeSyncNextCounter = 0;

View file

@ -230,14 +230,14 @@ struct PacketCounter
class WorldSession
{
public:
WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime);
WorldSession(uint32 id, std::string&& name, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale, uint32 recruiter, bool isARecruiter, bool skipQueue, uint32 TotalTime);
~WorldSession();
bool PlayerLoading() const { return m_playerLoading; }
bool PlayerLogout() const { return m_playerLogout; }
bool PlayerLogoutWithSave() const { return m_playerLogout && m_playerSave; }
void ReadAddonsInfo(WorldPacket& data);
void ReadAddonsInfo(ByteBuffer& data);
void SendAddonsInfo();
void ReadMovementInfo(WorldPacket& data, MovementInfo* mi);
@ -987,7 +987,7 @@ protected:
{
friend class World;
public:
DosProtection(WorldSession* s) : Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY)) { }
DosProtection(WorldSession* s);
bool EvaluateOpcode(WorldPacket& p, time_t time) const;
protected:
enum Policy
@ -1035,20 +1035,20 @@ private:
ObjectGuid::LowType m_GUIDLow;
Player* _player;
WorldSocket* m_Socket;
std::shared_ptr<WorldSocket> m_Socket;
std::string m_Address;
// std::string m_LAddress; // Last Attempted Remote Adress - we can not set attempted ip for a non-existing session!
AccountTypes _security;
bool _skipQueue;
uint32 _accountId;
std::string _accountName;
uint8 m_expansion;
uint32 m_total_time;
typedef std::list<AddonInfo> AddonsList;
// Warden
Warden* _warden; // Remains nullptr if Warden system is not enabled by config
std::unique_ptr<Warden> _warden; // Remains nullptr if Warden system is not enabled by config
time_t _logoutTime;
bool m_inQueue; // session wait in auth.queue
@ -1057,7 +1057,7 @@ private:
bool m_playerSave;
LocaleConstant m_sessionDbcLocale;
LocaleConstant m_sessionDbLocaleIndex;
uint32 m_latency;
std::atomic<uint32> m_latency;
AccountData m_accountData[NUM_ACCOUNT_DATA_TYPES];
uint32 m_Tutorials[MAX_ACCOUNT_TUTORIAL_VALUES];
bool m_TutorialsChanged;
@ -1081,6 +1081,9 @@ private:
std::map<uint32, uint32> _pendingTimeSyncRequests; // key: counter. value: server time when packet with that counter was sent.
uint32 _timeSyncNextCounter;
uint32 _timeSyncTimer;
WorldSession(WorldSession const& right) = delete;
WorldSession& operator=(WorldSession const& right) = delete;
};
#endif
/// @}

File diff suppressed because it is too large Load diff

View file

@ -4,197 +4,121 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \addtogroup u2w User to World Communication
* @{
* \file WorldSocket.h
* \author Derex <derex101@gmail.com>
*/
#ifndef __WORLDSOCKET_H__
#define __WORLDSOCKET_H__
#ifndef _WORLDSOCKET_H
#define _WORLDSOCKET_H
#include "AuthCrypt.h"
#include "Common.h"
#include "Duration.h"
#include <ace/Message_Block.h>
#include <ace/SOCK_Stream.h>
#include <ace/Svc_Handler.h>
#include <ace/Synch_Traits.h>
#include <ace/Unbounded_Queue.h>
#include <mutex>
#include "AuthCrypt.h"
#include "ServerPktHeader.h"
#include "Socket.h"
#include "Util.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "MPSCQueue.h"
#include <boost/asio/ip/tcp.hpp>
#if !defined (ACE_LACKS_PRAGMA_ONCE)
#pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
using boost::asio::ip::tcp;
class ACE_Message_Block;
class WorldPacket;
class WorldSession;
class EncryptablePacket : public WorldPacket
{
public:
EncryptablePacket(WorldPacket const& packet, bool encrypt) : WorldPacket(packet), _encrypt(encrypt)
{
SocketQueueLink.store(nullptr, std::memory_order_relaxed);
}
bool NeedsEncryption() const { return _encrypt; }
std::atomic<EncryptablePacket*> SocketQueueLink;
private:
bool _encrypt;
};
namespace WorldPackets
{
class ServerPacket;
}
/// Handler that can communicate over stream sockets.
typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> WorldHandler;
/**
* WorldSocket.
*
* This class is responsible for the communication with
* remote clients.
* Most methods return -1 on failure.
* The class uses reference counting.
*
* For output the class uses one buffer (64K usually) and
* a queue where it stores packet if there is no place on
* the queue. The reason this is done, is because the server
* does really a lot of small-size writes to it, and it doesn't
* scale well to allocate memory for every. When something is
* written to the output buffer the socket is not immediately
* activated for output (again for the same reason), there
* is 10ms celling (thats why there is Update() method).
* This concept is similar to TCP_CORK, but TCP_CORK
* uses 200ms celling. As result overhead generated by
* sending packets from "producer" threads is minimal,
* and doing a lot of writes with small size is tolerated.
*
* The calls to Update() method are managed by WorldSocketMgr
* and ReactorRunnable.
*
* For input, the class uses one 4096 bytes buffer on stack
* to which it does recv() calls. And then received data is
* distributed where its needed. 4096 matches pretty well the
* traffic generated by client for now.
*
* The input/output do speculative reads/writes (AKA it tryes
* to read all data available in the kernel buffer or tryes to
* write everything available in userspace buffer),
* which is ok for using with Level and Edge Triggered IO
* notification.
*
*/
class WorldSocket : public WorldHandler
#pragma pack(push, 1)
struct ClientPktHeader
{
uint16 size;
uint32 cmd;
bool IsValidSize() const { return size >= 4 && size < 10240; }
bool IsValidOpcode() const { return cmd < NUM_OPCODE_HANDLERS; }
};
#pragma pack(pop)
struct AuthSession;
class AC_GAME_API WorldSocket : public Socket<WorldSocket>
{
typedef Socket<WorldSocket> BaseSocket;
public:
WorldSocket (void);
virtual ~WorldSocket (void);
WorldSocket(tcp::socket&& socket);
~WorldSocket();
friend class WorldSocketMgr;
WorldSocket(WorldSocket const& right) = delete;
WorldSocket& operator=(WorldSocket const& right) = delete;
/// Check if socket is closed.
bool IsClosed (void) const;
void Start() override;
bool Update() override;
/// Close the socket.
void CloseSocket(std::string const& reason);
void SendPacket(WorldPacket const& packet);
/// Get address of connected peer.
const std::string& GetRemoteAddress (void) const;
void SetSendBufferSize(std::size_t sendBufferSize) { _sendBufferSize = sendBufferSize; }
/// Send A packet on the socket, this function is reentrant.
/// @param pct packet to send
/// @return -1 of failure
int SendPacket(const WorldPacket& pct);
protected:
void OnClose() override;
void ReadHandler() override;
bool ReadHeaderHandler();
/// Add reference to this object.
long AddReference (void);
enum class ReadDataHandlerResult
{
Ok = 0,
Error = 1,
WaitingForQuery = 2
};
/// Remove reference to this object.
long RemoveReference (void);
/// things called by ACE framework.
/// Called on open, the void* is the acceptor.
virtual int open (void*);
/// Called on failures inside of the acceptor, don't call from your code.
virtual int close (u_long);
/// Called when we can read from the socket.
virtual int handle_input (ACE_HANDLE = ACE_INVALID_HANDLE);
/// Called when the socket can write.
virtual int handle_output (ACE_HANDLE = ACE_INVALID_HANDLE);
/// Called when connection is closed or error happens.
virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE,
ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK);
/// Called by WorldSocketMgr/ReactorRunnable.
int Update (void);
ReadDataHandlerResult ReadDataHandler();
private:
/// Helper functions for processing incoming data.
int handle_input_header (void);
int handle_input_payload (void);
int handle_input_missing_data (void);
void CheckIpCallback(PreparedQueryResult result);
/// Help functions to mark/unmark the socket for output.
/// @param g the guard is for m_OutBufferLock, the function will release it
int cancel_wakeup_output();
int schedule_wakeup_output();
/// writes network.opcode log
/// accessing WorldSession is not threadsafe, only do it when holding _worldSessionLock
void LogOpcodeText(OpcodeClient opcode, std::unique_lock<std::mutex> const& guard) const;
/// Drain the queue if its not empty.
int handle_output_queue();
/// sends and logs network.opcode without accessing WorldSession
void SendPacketAndLogOpcode(WorldPacket const& packet);
void HandleSendAuthSession();
void HandleAuthSession(WorldPacket& recvPacket);
void HandleAuthSessionCallback(std::shared_ptr<AuthSession> authSession, PreparedQueryResult result);
void LoadSessionPermissionsCallback(PreparedQueryResult result);
void SendAuthResponseError(uint8 code);
/// process one incoming packet.
/// @param new_pct received packet, note that you need to delete it.
int ProcessIncoming (WorldPacket* new_pct);
bool HandlePing(WorldPacket& recvPacket);
/// Called by ProcessIncoming() on CMSG_AUTH_SESSION.
int HandleAuthSession (WorldPacket& recvPacket);
std::array<uint8, 4> _authSeed;
AuthCrypt _authCrypt;
/// Called by ProcessIncoming() on CMSG_PING.
int HandlePing (WorldPacket& recvPacket);
TimePoint _LastPingTime;
uint32 _OverSpeedPings;
private:
/// Time in which the last ping was received
SystemTimePoint m_LastPingTime;
std::mutex _worldSessionLock;
WorldSession* _worldSession;
bool _authed;
/// Keep track of over-speed pings, to prevent ping flood.
uint32 m_OverSpeedPings;
/// Address of the remote peer
std::string m_Address;
/// Class used for managing encryption of the headers
AuthCrypt m_Crypt;
/// Mutex lock to protect m_Session
std::mutex m_SessionLock;
/// Session to which received packets are routed
WorldSession* m_Session;
/// here are stored the fragments of the received data
WorldPacket* m_RecvWPct;
/// This block actually refers to m_RecvWPct contents,
/// which allows easy and safe writing to it.
/// It wont free memory when its deleted. m_RecvWPct takes care of freeing.
ACE_Message_Block m_RecvPct;
/// Fragment of the received header.
ACE_Message_Block m_Header;
/// Mutex for protecting output related data.
std::mutex m_OutBufferLock;
/// Buffer used for writing output.
ACE_Message_Block* m_OutBuffer;
/// Size of the m_OutBuffer.
size_t m_OutBufferSize;
/// True if the socket is registered with the reactor for output
bool m_OutActive;
std::array<uint8, 4> m_Seed;
MessageBuffer _headerBuffer;
MessageBuffer _packetBuffer;
MPSCQueue<EncryptablePacket, &EncryptablePacket::SocketQueueLink> _bufferQueue;
std::size_t _sendBufferSize;
QueryCallbackProcessor _queryProcessor;
std::string _ipCountry;
};
#endif /* _WORLDSOCKET_H */
/// @}
#endif

View file

@ -1,53 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \addtogroup u2w User to World Communication
* @{
* \file WorldSocketMgr.h
*/
#ifndef __WORLDSOCKETACCEPTOR_H_
#define __WORLDSOCKETACCEPTOR_H_
#include "Common.h"
#include "WorldSocket.h"
#include <ace/Acceptor.h>
#include <ace/SOCK_Acceptor.h>
class WorldSocketAcceptor : public ACE_Acceptor<WorldSocket, ACE_SOCK_Acceptor>
{
public:
WorldSocketAcceptor(void) { }
virtual ~WorldSocketAcceptor(void)
{
if (reactor())
reactor()->cancel_timer(this, 1);
}
protected:
virtual int handle_timeout(const ACE_Time_Value& /*current_time*/, const void* /*act = 0*/)
{
LOG_INFO("network", "Resuming acceptor");
reactor()->cancel_timer(this, 1);
return reactor()->register_handler(this, ACE_Event_Handler::ACCEPT_MASK);
}
virtual int handle_accept_error(void)
{
#if defined(ENFILE) && defined(EMFILE)
if (errno == ENFILE || errno == EMFILE)
{
LOG_ERROR("network", "Out of file descriptors, suspending incoming connections for 10 seconds");
reactor()->remove_handler(this, ACE_Event_Handler::ACCEPT_MASK | ACE_Event_Handler::DONT_CALL);
reactor()->schedule_timer(this, nullptr, ACE_Time_Value(10));
}
#endif
return 0;
}
};
#endif /* __WORLDSOCKETACCEPTOR_H_ */
/// @}

View file

@ -4,345 +4,109 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \file WorldSocketMgr.cpp
* \ingroup u2w
* \author Derex <derex101@gmail.com>
*/
#include "Config.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "NetworkThread.h"
#include "ScriptMgr.h"
#include "WorldSocket.h"
#include "WorldSocketAcceptor.h"
#include "WorldSocketMgr.h"
#include <ace/ACE.h>
#include <ace/Dev_Poll_Reactor.h>
#include <ace/Log_Msg.h>
#include <ace/os_include/arpa/os_inet.h>
#include <ace/os_include/netinet/os_tcp.h>
#include <ace/os_include/sys/os_socket.h>
#include <ace/Reactor_Impl.h>
#include <ace/Reactor.h>
#include <ace/TP_Reactor.h>
#include <atomic>
#include <set>
#include <boost/system/error_code.hpp>
/**
* This is a helper class to WorldSocketMgr, that manages
* network threads, and assigning connections from acceptor thread
* to other network threads
*/
class ReactorRunnable : protected ACE_Task_Base
static void OnSocketAccept(tcp::socket&& sock, uint32 threadIndex)
{
sWorldSocketMgr.OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex);
}
class WorldSocketThread : public NetworkThread<WorldSocket>
{
public:
ReactorRunnable() :
m_Reactor(0),
m_Connections(0),
m_ThreadId(-1)
void SocketAdded(std::shared_ptr<WorldSocket> sock) override
{
ACE_Reactor_Impl* imp;
#if defined (ACE_HAS_EVENT_POLL) || defined (ACE_HAS_DEV_POLL)
imp = new ACE_Dev_Poll_Reactor();
imp->max_notify_iterations (128);
imp->restart (1);
#else
imp = new ACE_TP_Reactor();
imp->max_notify_iterations (128);
#endif
m_Reactor = new ACE_Reactor (imp, 1);
}
~ReactorRunnable() override
{
Stop();
Wait();
delete m_Reactor;
}
void Stop()
{
m_Reactor->end_reactor_event_loop();
}
int Start()
{
if (m_ThreadId != -1)
return -1;
return (m_ThreadId = activate());
}
void Wait() { ACE_Task_Base::wait(); }
long Connections()
{
return m_Connections;
}
int AddSocket (WorldSocket* sock)
{
std::lock_guard<std::mutex> guard(m_NewSockets_Lock);
++m_Connections;
sock->AddReference();
sock->reactor (m_Reactor);
m_NewSockets.insert (sock);
sock->SetSendBufferSize(sWorldSocketMgr.GetApplicationSendBufferSize());
sScriptMgr->OnSocketOpen(sock);
return 0;
}
ACE_Reactor* GetReactor()
void SocketRemoved(std::shared_ptr<WorldSocket> sock) override
{
return m_Reactor;
sScriptMgr->OnSocketClose(sock);
}
protected:
void AddNewSockets()
{
std::lock_guard<std::mutex> guard(m_NewSockets_Lock);
if (m_NewSockets.empty())
return;
for (SocketSet::const_iterator i = m_NewSockets.begin(); i != m_NewSockets.end(); ++i)
{
WorldSocket* sock = (*i);
if (sock->IsClosed())
{
sScriptMgr->OnSocketClose(sock, true);
sock->RemoveReference();
--m_Connections;
}
else
m_Sockets.insert (sock);
}
m_NewSockets.clear();
}
int svc() override
{
LOG_DEBUG("network", "Network Thread Starting");
ASSERT(m_Reactor);
SocketSet::iterator i, t;
while (!m_Reactor->reactor_event_loop_done())
{
// dont be too smart to move this outside the loop
// the run_reactor_event_loop will modify interval
ACE_Time_Value interval (0, 10000);
if (m_Reactor->run_reactor_event_loop (interval) == -1)
break;
AddNewSockets();
for (i = m_Sockets.begin(); i != m_Sockets.end();)
{
if ((*i)->Update() == -1)
{
t = i;
++i;
(*t)->CloseSocket("svc()");
sScriptMgr->OnSocketClose((*t), false);
(*t)->RemoveReference();
--m_Connections;
m_Sockets.erase (t);
}
else
++i;
}
}
LOG_DEBUG("network", "Network Thread exits");
return 0;
}
private:
typedef std::atomic<int> AtomicInt;
typedef std::set<WorldSocket*> SocketSet;
ACE_Reactor* m_Reactor;
AtomicInt m_Connections;
int m_ThreadId;
SocketSet m_Sockets;
SocketSet m_NewSockets;
std::mutex m_NewSockets_Lock;
};
WorldSocketMgr::WorldSocketMgr() :
m_NetThreads(0),
m_NetThreadsCount(0),
m_SockOutKBuff(-1),
m_SockOutUBuff(65536),
m_UseNoDelay(true),
m_Acceptor (0)
BaseSocketMgr(), _socketSystemSendBufferSize(-1), _socketApplicationSendBufferSize(65536), _tcpNoDelay(true)
{
}
WorldSocketMgr::~WorldSocketMgr()
{
delete [] m_NetThreads;
delete m_Acceptor;
}
WorldSocketMgr* WorldSocketMgr::instance()
WorldSocketMgr& WorldSocketMgr::Instance()
{
static WorldSocketMgr instance;
return &instance;
return instance;
}
int
WorldSocketMgr::StartReactiveIO (uint16 port, const char* address)
bool WorldSocketMgr::StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int threadCount)
{
m_UseNoDelay = sConfigMgr->GetOption<bool> ("Network.TcpNodelay", true);
_tcpNoDelay = sConfigMgr->GetOption<bool>("Network.TcpNodelay", true);
int num_threads = sConfigMgr->GetOption<int32> ("Network.Threads", 1);
if (num_threads <= 0)
{
LOG_ERROR("network", "Network.Threads is wrong in your config file");
return -1;
}
m_NetThreadsCount = static_cast<size_t> (num_threads + 1);
m_NetThreads = new ReactorRunnable[m_NetThreadsCount];
LOG_INFO("network", "Max allowed socket connections %d", ACE::max_handles());
int const max_connections = ACORE_MAX_LISTEN_CONNECTIONS;
LOG_DEBUG("network", "Max allowed socket connections %d", max_connections);
// -1 means use default
m_SockOutKBuff = sConfigMgr->GetOption<int32> ("Network.OutKBuff", -1);
_socketSystemSendBufferSize = sConfigMgr->GetOption<int32>("Network.OutKBuff", -1);
_socketApplicationSendBufferSize = sConfigMgr->GetOption<int32>("Network.OutUBuff", 65536);
m_SockOutUBuff = sConfigMgr->GetOption<int32> ("Network.OutUBuff", 65536);
if (m_SockOutUBuff <= 0)
if (_socketApplicationSendBufferSize <= 0)
{
LOG_ERROR("network", "Network.OutUBuff is wrong in your config file");
return -1;
return false;
}
m_Acceptor = new WorldSocketAcceptor;
if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount))
return false;
ACE_INET_Addr listen_addr (port, address);
if (m_Acceptor->open(listen_addr, m_NetThreads[0].GetReactor(), ACE_NONBLOCK) == -1)
{
LOG_ERROR("network", "Failed to open acceptor, check if the port is free");
return -1;
}
for (size_t i = 0; i < m_NetThreadsCount; ++i)
m_NetThreads[i].Start();
return 0;
}
int
WorldSocketMgr::StartNetwork (uint16 port, const char* address)
{
if (!sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG))
ACE_Log_Msg::instance()->priority_mask(LM_ERROR, ACE_Log_Msg::PROCESS);
if (StartReactiveIO(port, address) == -1)
return -1;
_acceptor->AsyncAcceptWithCallback<&OnSocketAccept>();
sScriptMgr->OnNetworkStart();
return 0;
return true;
}
void
WorldSocketMgr::StopNetwork()
void WorldSocketMgr::StopNetwork()
{
if (m_Acceptor)
{
m_Acceptor->close();
}
if (m_NetThreadsCount != 0)
{
for (size_t i = 0; i < m_NetThreadsCount; ++i)
m_NetThreads[i].Stop();
}
Wait();
BaseSocketMgr::StopNetwork();
sScriptMgr->OnNetworkStop();
}
void
WorldSocketMgr::Wait()
{
if (m_NetThreadsCount != 0)
{
for (size_t i = 0; i < m_NetThreadsCount; ++i)
m_NetThreads[i].Wait();
}
}
int
WorldSocketMgr::OnSocketOpen (WorldSocket* sock)
void WorldSocketMgr::OnSocketOpen(tcp::socket&& sock, uint32 threadIndex)
{
// set some options here
if (m_SockOutKBuff >= 0)
if (_socketSystemSendBufferSize >= 0)
{
if (sock->peer().set_option (SOL_SOCKET,
SO_SNDBUF,
(void*) & m_SockOutKBuff,
sizeof (int)) == -1 && errno != ENOTSUP)
boost::system::error_code err;
sock.set_option(boost::asio::socket_base::send_buffer_size(_socketSystemSendBufferSize), err);
if (err && err != boost::system::errc::not_supported)
{
LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen set_option SO_SNDBUF");
return -1;
LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen sock.set_option(boost::asio::socket_base::send_buffer_size) err = %s", err.message().c_str());
return;
}
}
static const int ndoption = 1;
// Set TCP_NODELAY.
if (m_UseNoDelay)
if (_tcpNoDelay)
{
if (sock->peer().set_option (ACE_IPPROTO_TCP,
TCP_NODELAY,
(void*)&ndoption,
sizeof (int)) == -1)
boost::system::error_code err;
sock.set_option(boost::asio::ip::tcp::no_delay(true), err);
if (err)
{
LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen: peer().set_option TCP_NODELAY errno = %s", ACE_OS::strerror (errno));
return -1;
LOG_ERROR("network", "WorldSocketMgr::OnSocketOpen sock.set_option(boost::asio::ip::tcp::no_delay) err = %s", err.message().c_str());
return;
}
}
sock->m_OutBufferSize = static_cast<size_t> (m_SockOutUBuff);
// we skip the Acceptor Thread
size_t min = 1;
ASSERT(m_NetThreadsCount >= 1);
for (size_t i = 1; i < m_NetThreadsCount; ++i)
if (m_NetThreads[i].Connections() < m_NetThreads[min].Connections())
min = i;
return m_NetThreads[min].AddSocket (sock);
BaseSocketMgr::OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex);
}
NetworkThread<WorldSocket>* WorldSocketMgr::CreateThreads() const
{
return new WorldSocketThread[GetNetworkThreadCount()];
}

View file

@ -4,58 +4,43 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \addtogroup u2w User to World Communication
* @{
* \file WorldSocketMgr.h
* \author Derex <derex101@gmail.com>
*/
#ifndef __WORLDSOCKETMGR_H
#define __WORLDSOCKETMGR_H
#include "Common.h"
#include "SocketMgr.h"
class WorldSocket;
class ReactorRunnable;
class ACE_Event_Handler;
/// Manages all sockets connected to peers and network threads
class WorldSocketMgr
class AC_GAME_API WorldSocketMgr : public SocketMgr<WorldSocket>
{
public:
friend class WorldSocket;
typedef SocketMgr<WorldSocket> BaseSocketMgr;
static WorldSocketMgr* instance();
public:
static WorldSocketMgr& Instance();
/// Start network, listen at address:port .
int StartNetwork(uint16 port, const char* address);
bool StartWorldNetwork(Acore::Asio::IoContext& ioContext, std::string const& bindIp, uint16 port, int networkThreads);
/// Stops all network threads, It will wait for all running threads .
void StopNetwork();
void StopNetwork() override;
/// Wait untill all network threads have "joined" .
void Wait();
void OnSocketOpen(tcp::socket&& sock, uint32 threadIndex) override;
private:
int OnSocketOpen(WorldSocket* sock);
std::size_t GetApplicationSendBufferSize() const { return _socketApplicationSendBufferSize; }
int StartReactiveIO(uint16 port, const char* address);
private:
protected:
WorldSocketMgr();
virtual ~WorldSocketMgr();
ReactorRunnable* m_NetThreads;
size_t m_NetThreadsCount;
NetworkThread<WorldSocket>* CreateThreads() const override;
int m_SockOutKBuff;
int m_SockOutUBuff;
bool m_UseNoDelay;
class WorldSocketAcceptor* m_Acceptor;
private:
int32 _socketSystemSendBufferSize;
int32 _socketApplicationSendBufferSize;
bool _tcpNoDelay;
};
#define sWorldSocketMgr WorldSocketMgr::instance()
#define sWorldSocketMgr WorldSocketMgr::Instance()
#endif
/// @}

View file

@ -8,6 +8,7 @@
\ingroup world
*/
#include "World.h"
#include "AccountMgr.h"
#include "AchievementMgr.h"
#include "AddonMgr.h"
@ -28,8 +29,8 @@
#include "CreatureAIRegistry.h"
#include "CreatureGroups.h"
#include "CreatureTextMgr.h"
#include "DatabaseEnv.h"
#include "DBCStores.h"
#include "DatabaseEnv.h"
#include "DisableMgr.h"
#include "DynamicVisibility.h"
#include "GameEventMgr.h"
@ -38,16 +39,16 @@
#include "GridNotifiersImpl.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "IPLocation.h"
#include "InstanceSaveMgr.h"
#include "ItemEnchantmentMgr.h"
#include "IPLocation.h"
#include "Language.h"
#include "LFGMgr.h"
#include "Language.h"
#include "Log.h"
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "MapManager.h"
#include "MMapFactory.h"
#include "MapManager.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "OutdoorPvPMgr.h"
@ -66,17 +67,18 @@
#include "Transport.h"
#include "TransportMgr.h"
#include "Util.h"
#include "Vehicle.h"
#include "VMapFactory.h"
#include "VMapManager2.h"
#include "Vehicle.h"
#include "Warden.h"
#include "WardenCheckMgr.h"
#include "WaypointMovementGenerator.h"
#include "WeatherMgr.h"
#include "WhoListCache.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include <VMapManager2.h>
#include <boost/asio/ip/address.hpp>
#include <cmath>
#ifdef ELUNA
#include "LuaEngine.h"

View file

@ -5,7 +5,6 @@
*/
#include "AccountMgr.h"
#include "ace/INET_Addr.h"
#include "ArenaTeamMgr.h"
#include "BattlegroundMgr.h"
#include "CellImpl.h"

View file

@ -117,7 +117,6 @@ public:
handler->PSendSysMessage("%s", GitRevision::GetFullVersion());
handler->PSendSysMessage("Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
handler->PSendSysMessage("Using ACE version: %s", ACE_VERSION);
handler->PSendSysMessage("Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
handler->PSendSysMessage("Using MySQL version: %u", MySQL::GetLibraryVersion());
handler->PSendSysMessage("Using CMake version: %s", GitRevision::GetCMakeVersion());

View file

@ -21,8 +21,8 @@ public:
{
{ "version", SEC_CONSOLE, false, &HandleSpectatorVersionCommand, "" },
{ "reset", SEC_CONSOLE, false, &HandleSpectatorResetCommand, "" },
{ "spectate", SEC_CONSOLE, false, &ArenaSpectator::HandleSpectatorSpectateCommand, "" },
{ "watch", SEC_CONSOLE, false, &ArenaSpectator::HandleSpectatorWatchCommand, "" },
{ "spectate", SEC_CONSOLE, false, &HandleSpectatorSpectateCommand, "" },
{ "watch", SEC_CONSOLE, false, &HandleSpectatorWatchCommand, "" },
{ "leave", SEC_CONSOLE, false, &HandleSpectatorLeaveCommand, "" },
{ "", SEC_CONSOLE, false, &HandleSpectatorCommand, "" }
};
@ -71,6 +71,22 @@ public:
player->TeleportToEntryPoint();
return true;
}
static bool HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args)
{
if (!ArenaSpectator::HandleSpectatorSpectateCommand(handler, args))
return false;
return true;
}
static bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args)
{
if (!ArenaSpectator::HandleSpectatorWatchCommand(handler, args))
return false;
return true;
}
};
void AddSC_spectator_commandscript()

View file

@ -16,9 +16,9 @@
using boost::asio::ip::tcp;
#if BOOST_VERSION >= 106600
#define WARHEAD_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections
#define ACORE_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_listen_connections
#else
#define WARHEAD_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_connections
#define ACORE_MAX_LISTEN_CONNECTIONS boost::asio::socket_base::max_connections
#endif
class AsyncAcceptor
@ -72,7 +72,7 @@ public:
return false;
}
#if WARHEAD_PLATFORM != WARHEAD_PLATFORM_WINDOWS
#if AC_PLATFORM != AC_PLATFORM_WINDOWS
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true), errorCode);
if (errorCode)
{
@ -88,7 +88,7 @@ public:
return false;
}
_acceptor.listen(WARHEAD_MAX_LISTEN_CONNECTIONS, errorCode);
_acceptor.listen(ACORE_MAX_LISTEN_CONNECTIONS, errorCode);
if (errorCode)
{
LOG_INFO("network", "Failed to start listening on %s:%u %s", _endpoint.address().to_string().c_str(), _endpoint.port(), errorCode.message().c_str());

View file

@ -1,277 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#include "Log.h"
#include "RealmSocket.h"
#include <ace/INET_Addr.h>
#include <ace/OS_NS_string.h>
RealmSocket::Session::Session() = default;
RealmSocket::Session::~Session() = default;
RealmSocket::RealmSocket() :
input_buffer_(4096),
_remoteAddress()
{
reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED);
msg_queue()->high_water_mark(8 * 1024 * 1024);
msg_queue()->low_water_mark(8 * 1024 * 1024);
}
RealmSocket::~RealmSocket()
{
if (msg_queue())
msg_queue()->close();
// delete RealmSocketObject must never be called from our code.
closing_ = true;
delete session_;
peer().close();
}
int RealmSocket::open(void* arg)
{
ACE_INET_Addr addr;
if (peer().get_remote_addr(addr) == -1)
{
LOG_ERROR("network", "Error %s while opening realm socket!", ACE_OS::strerror(errno));
return -1;
}
_remoteAddress = addr.get_host_addr();
_remotePort = addr.get_port_number();
// Register with ACE Reactor
if (Base::open(arg) == -1)
return -1;
if (session_)
session_->OnAccept();
// reactor takes care of the socket from now on
remove_reference();
return 0;
}
int RealmSocket::close(u_long)
{
shutdown();
closing_ = true;
remove_reference();
return 0;
}
const std::string& RealmSocket::getRemoteAddress() const
{
return _remoteAddress;
}
uint16 RealmSocket::getRemotePort() const
{
return _remotePort;
}
size_t RealmSocket::recv_len() const
{
return input_buffer_.length();
}
bool RealmSocket::recv_soft(char* buf, size_t len)
{
if (input_buffer_.length() < len)
return false;
ACE_OS::memcpy(buf, input_buffer_.rd_ptr(), len);
return true;
}
bool RealmSocket::recv(char* buf, size_t len)
{
bool ret = recv_soft(buf, len);
if (ret)
recv_skip(len);
return ret;
}
void RealmSocket::recv_skip(size_t len)
{
input_buffer_.rd_ptr(len);
}
ssize_t RealmSocket::noblk_send(ACE_Message_Block& message_block)
{
const size_t len = message_block.length();
if (len == 0)
return -1;
// Try to send the message directly.
#ifdef MSG_NOSIGNAL
ssize_t n = peer().send(message_block.rd_ptr(), len, MSG_NOSIGNAL);
#else
ssize_t n = peer().send(message_block.rd_ptr(), len);
#endif // MSG_NOSIGNAL
if (n < 0)
{
if (errno == EWOULDBLOCK) // Blocking signal
return 0;
else // Error happened
return -1;
}
else if (n == 0)
{
// Can this happen ?
return -1;
}
// return bytes transmitted
return n;
}
bool RealmSocket::send(const char* buf, size_t len)
{
if (buf == nullptr || len == 0)
return true;
ACE_Data_Block db(len, ACE_Message_Block::MB_DATA, (const char*)buf, nullptr, nullptr, ACE_Message_Block::DONT_DELETE, nullptr);
ACE_Message_Block message_block(&db, ACE_Message_Block::DONT_DELETE, nullptr);
message_block.wr_ptr(len);
if (msg_queue()->is_empty())
{
// Try to send it directly.
ssize_t n = noblk_send(message_block);
if (n < 0)
return false;
size_t un = size_t(n);
if (un == len)
return true;
// fall down
message_block.rd_ptr(un);
}
ACE_Message_Block* mb = message_block.clone();
if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value*)(&ACE_Time_Value::zero)) == -1)
{
mb->release();
return false;
}
if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1)
return false;
return true;
}
int RealmSocket::handle_output(ACE_HANDLE)
{
if (closing_)
return -1;
ACE_Message_Block* mb = nullptr;
if (msg_queue()->is_empty())
{
reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK);
return 0;
}
if (msg_queue()->dequeue_head(mb, (ACE_Time_Value*)(&ACE_Time_Value::zero)) == -1)
return -1;
ssize_t n = noblk_send(*mb);
if (n < 0)
{
mb->release();
return -1;
}
else if (size_t(n) == mb->length())
{
mb->release();
return 1;
}
else
{
mb->rd_ptr(n);
if (msg_queue()->enqueue_head(mb, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1)
{
mb->release();
return -1;
}
return 0;
}
ACE_NOTREACHED(return -1);
}
int RealmSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask)
{
// As opposed to WorldSocket::handle_close, we don't need locks here.
closing_ = true;
if (h == ACE_INVALID_HANDLE)
peer().close_writer();
if (session_)
session_->OnClose();
reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK);
return 0;
}
int RealmSocket::handle_input(ACE_HANDLE)
{
if (closing_)
return -1;
const ssize_t space = input_buffer_.space();
ssize_t n = peer().recv(input_buffer_.wr_ptr(), space);
if (n < 0)
return errno == EWOULDBLOCK ? 0 : -1;
else if (n == 0) // EOF
return -1;
input_buffer_.wr_ptr((size_t)n);
if (session_ != nullptr)
{
session_->OnRead();
input_buffer_.crunch();
}
// return 1 in case there is more data to read from OS
return n == space ? 1 : 0;
}
void RealmSocket::set_session(Session* session)
{
delete session_;
session_ = session;
}

View file

@ -1,67 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
#ifndef __REALMSOCKET_H__
#define __REALMSOCKET_H__
#include "Common.h"
#include <ace/Message_Block.h>
#include <ace/SOCK_Stream.h>
#include <ace/Svc_Handler.h>
#include <ace/Synch_Traits.h>
class RealmSocket : public ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
{
private:
typedef ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH> Base;
public:
class Session
{
public:
Session();
virtual ~Session();
virtual void OnRead() = 0;
virtual void OnAccept() = 0;
virtual void OnClose() = 0;
};
RealmSocket();
~RealmSocket() override;
[[nodiscard]] size_t recv_len() const;
bool recv_soft(char* buf, size_t len);
bool recv(char* buf, size_t len);
void recv_skip(size_t len);
bool send(const char* buf, size_t len);
[[nodiscard]] const std::string& getRemoteAddress() const;
[[nodiscard]] uint16 getRemotePort() const;
int open(void*) override;
int close(u_long) override;
int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE) override;
int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE) override;
int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK) override;
void set_session(Session* session);
private:
ssize_t noblk_send(ACE_Message_Block& message_block);
ACE_Message_Block input_buffer_;
Session* session_{nullptr};
std::string _remoteAddress;
uint16 _remotePort{0};
};
#endif /* __REALMSOCKET_H__ */

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3
* Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore>
*/
#include "Realm.h"
#include "IpAddress.h"
#include "IpNetwork.h"
#include <boost/asio/ip/tcp.hpp>
boost::asio::ip::tcp_endpoint Realm::GetAddressForClient(boost::asio::ip::address const& clientAddr) const
{
boost::asio::ip::address realmIp;
// Attempt to send best address for client
if (clientAddr.is_loopback())
{
// Try guessing if realm is also connected locally
if (LocalAddress->is_loopback() || ExternalAddress->is_loopback())
{
realmIp = clientAddr;
}
else
{
// Assume that user connecting from the machine that bnetserver is located on
// has all realms available in his local network
realmIp = *LocalAddress;
}
}
else
{
if (clientAddr.is_v4() && Acore::Net::IsInNetwork(LocalAddress->to_v4(), LocalSubnetMask->to_v4(), clientAddr.to_v4()))
{
realmIp = *LocalAddress;
}
else
{
realmIp = *ExternalAddress;
}
}
// Return external IP
return boost::asio::ip::tcp_endpoint(realmIp, Port);
}

View file

@ -6,9 +6,8 @@
#ifndef Realm_h__
#define Realm_h__
#include "AsioHacksFwd.h"
#include "Common.h"
// #include "AsioHacksFwd.h"
#include <ace/INET_Addr.h>
enum RealmFlags
{
@ -56,9 +55,9 @@ struct AC_SHARED_API Realm
{
RealmHandle Id;
uint32 Build;
std::unique_ptr<ACE_INET_Addr> ExternalAddress;
std::unique_ptr<ACE_INET_Addr> LocalAddress;
std::unique_ptr<ACE_INET_Addr> LocalSubnetMask;
std::unique_ptr<boost::asio::ip::address> ExternalAddress;
std::unique_ptr<boost::asio::ip::address> LocalAddress;
std::unique_ptr<boost::asio::ip::address> LocalSubnetMask;
uint16 Port;
std::string Name;
uint8 Type;
@ -67,7 +66,7 @@ struct AC_SHARED_API Realm
AccountTypes AllowedSecurityLevel;
float PopulationLevel;
// boost::asio::ip::tcp_endpoint GetAddressForClient(boost::asio::ip::address const& clientAddr) const;
boost::asio::ip::tcp_endpoint GetAddressForClient(boost::asio::ip::address const& clientAddr) const;
};
#endif // Realm_h__

View file

@ -6,28 +6,38 @@
#include "RealmList.h"
#include "DatabaseEnv.h"
#include "DeadlineTimer.h"
#include "IoContext.h"
#include "Log.h"
#include "Optional.h"
#include "Resolver.h"
#include "Util.h"
#include <boost/asio/ip/tcp.hpp>
RealmList::RealmList() :
_updateInterval(0) { }
RealmList* RealmList::instance()
RealmList* RealmList::Instance()
{
static RealmList instance;
return &instance;
}
// Load the realm list from the database
void RealmList::Initialize(uint32 updateInterval)
void RealmList::Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval)
{
_updateInterval = updateInterval;
_updateTimer = std::make_unique<Acore::Asio::DeadlineTimer>(ioContext);
_resolver = std::make_unique<Acore::Asio::Resolver>(ioContext);
LoadBuildInfo();
// Get the content of the realmlist table in the database
UpdateRealms();
UpdateRealms(boost::system::error_code());
}
void RealmList::Close()
{
_updateTimer->cancel();
}
void RealmList::LoadBuildInfo()
@ -72,7 +82,7 @@ void RealmList::LoadBuildInfo()
}
void RealmList::UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name,
ACE_INET_Addr&& address, ACE_INET_Addr&& localAddr, ACE_INET_Addr&& localSubmask,
boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask,
uint16 port, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float population)
{
// Create new if not exist or update existed
@ -89,38 +99,30 @@ void RealmList::UpdateRealm(RealmHandle const& id, uint32 build, std::string con
if (!realm.ExternalAddress || *realm.ExternalAddress != address)
{
realm.ExternalAddress = std::make_unique<ACE_INET_Addr>(std::move(address));
realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(std::move(address));
}
if (!realm.LocalAddress || *realm.LocalAddress != localAddr)
{
realm.LocalAddress = std::make_unique<ACE_INET_Addr>(std::move(localAddr));
realm.LocalAddress = std::make_unique<boost::asio::ip::address>(std::move(localAddr));
}
if (!realm.LocalSubnetMask || *realm.LocalSubnetMask != localSubmask)
{
realm.LocalSubnetMask = std::make_unique<ACE_INET_Addr>(std::move(localSubmask));
realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(std::move(localSubmask));
}
realm.Port = port;
}
void RealmList::UpdateIfNeed()
void RealmList::UpdateRealms(boost::system::error_code const& error)
{
// maybe disabled or updated recently
if (!_updateInterval || _nextUpdateTime > time(nullptr))
if (error)
{
// Skip update if have errors
return;
}
_nextUpdateTime = time(nullptr) + _updateInterval;
// Get the content of the realmlist table in the database
UpdateRealms();
}
void RealmList::UpdateRealms()
{
LOG_DEBUG("server.authserver", "Updating Realm List...");
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_REALMLIST);
@ -139,70 +141,87 @@ void RealmList::UpdateRealms()
{
do
{
Field* fields = result->Fetch();
uint32 realmId = fields[0].GetUInt32();
std::string name = fields[1].GetString();
std::string externalAddressString = fields[2].GetString();
std::string localAddressString = fields[3].GetString();
std::string localSubmaskString = fields[4].GetString();
uint16 port = fields[5].GetUInt16();
Optional<ACE_INET_Addr> externalAddress = ACE_INET_Addr(port, externalAddressString.c_str(), AF_INET);
if (!externalAddress)
try
{
LOG_ERROR("server.authserver", "Could not resolve address %s for realm \"%s\" id %u", externalAddressString.c_str(), name.c_str(), realmId);
continue;
}
Field* fields = result->Fetch();
uint32 realmId = fields[0].GetUInt32();
std::string name = fields[1].GetString();
std::string externalAddressString = fields[2].GetString();
std::string localAddressString = fields[3].GetString();
std::string localSubmaskString = fields[4].GetString();
uint16 port = fields[5].GetUInt16();
Optional<ACE_INET_Addr> localAddress = ACE_INET_Addr(port, localAddressString.c_str(), AF_INET);
if (!localAddress)
Optional<boost::asio::ip::tcp::endpoint> externalAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), externalAddressString, "");
if (!externalAddress)
{
LOG_ERROR("server.authserver", "Could not resolve address %s for realm \"%s\" id %u", externalAddressString.c_str(), name.c_str(), realmId);
continue;
}
Optional<boost::asio::ip::tcp::endpoint> localAddress = _resolver->Resolve(boost::asio::ip::tcp::v4(), localAddressString, "");
if (!localAddress)
{
LOG_ERROR("server.authserver", "Could not resolve localAddress %s for realm \"%s\" id %u", localAddressString.c_str(), name.c_str(), realmId);
continue;
}
Optional<boost::asio::ip::tcp::endpoint> localSubmask = _resolver->Resolve(boost::asio::ip::tcp::v4(), localSubmaskString, "");
if (!localSubmask)
{
LOG_ERROR("server.authserver", "Could not resolve localSubnetMask %s for realm \"%s\" id %u", localSubmaskString.c_str(), name.c_str(), realmId);
continue;
}
uint8 icon = fields[6].GetUInt8();
if (icon == REALM_TYPE_FFA_PVP)
{
icon = REALM_TYPE_PVP;
}
if (icon >= MAX_CLIENT_REALM_TYPE)
{
icon = REALM_TYPE_NORMAL;
}
RealmFlags flag = RealmFlags(fields[7].GetUInt8());
uint8 timezone = fields[8].GetUInt8();
uint8 allowedSecurityLevel = fields[9].GetUInt8();
float pop = fields[10].GetFloat();
uint32 build = fields[11].GetUInt32();
RealmHandle id{ realmId };
UpdateRealm(id, build, name, externalAddress->address(), localAddress->address(), localSubmask->address(), port, icon, flag,
timezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop);
if (!existingRealms.count(id))
{
LOG_INFO("server.authserver", "Added realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port);
}
else
{
LOG_DEBUG("server.authserver", "Updating realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port);
}
existingRealms.erase(id);
}
catch (std::exception const& ex)
{
LOG_ERROR("server.authserver", "Could not resolve localAddress %s for realm \"%s\" id %u", localAddressString.c_str(), name.c_str(), realmId);
continue;
LOG_ERROR("server.authserver", "Realmlist::UpdateRealms has thrown an exception: %s", ex.what());
ABORT();
}
Optional<ACE_INET_Addr> localSubmask = ACE_INET_Addr(0, localSubmaskString.c_str(), AF_INET);
if (!localSubmask)
{
LOG_ERROR("server.authserver", "Could not resolve localSubnetMask %s for realm \"%s\" id %u", localSubmaskString.c_str(), name.c_str(), realmId);
continue;
}
uint8 icon = fields[6].GetUInt8();
if (icon == REALM_TYPE_FFA_PVP)
{
icon = REALM_TYPE_PVP;
}
if (icon >= MAX_CLIENT_REALM_TYPE)
{
icon = REALM_TYPE_NORMAL;
}
RealmFlags flag = RealmFlags(fields[7].GetUInt8());
uint8 timezone = fields[8].GetUInt8();
uint8 allowedSecurityLevel = fields[9].GetUInt8();
float pop = fields[10].GetFloat();
uint32 build = fields[11].GetUInt32();
RealmHandle id{ realmId };
UpdateRealm(id, build, name, std::move(externalAddress.value()), std::move(localAddress.value()), std::move(localSubmask.value()), port, icon, flag,
timezone, (allowedSecurityLevel <= SEC_ADMINISTRATOR ? AccountTypes(allowedSecurityLevel) : SEC_ADMINISTRATOR), pop);
if (!existingRealms.count(id))
{
LOG_INFO("server.authserver", "Added realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port);
}
else
{
LOG_DEBUG("server.authserver", "Updating realm \"%s\" at %s:%u.", name.c_str(), externalAddressString.c_str(), port);
}
existingRealms.erase(id);
} while (result->NextRow());
}
for (auto itr = existingRealms.begin(); itr != existingRealms.end(); ++itr)
LOG_INFO("server.authserver", "Removed realm \"%s\".", itr->second.c_str());
if (_updateInterval)
{
_updateTimer->expires_from_now(boost::posix_time::seconds(_updateInterval));
_updateTimer->async_wait(std::bind(&RealmList::UpdateRealms, this, std::placeholders::_1));
}
}
Realm const* RealmList::GetRealm(RealmHandle const& id) const

View file

@ -11,8 +11,8 @@
#include "Realm.h"
#include <array>
#include <map>
#include <vector>
#include <unordered_set>
#include <vector>
struct RealmBuildInfo
{
@ -25,19 +25,21 @@ struct RealmBuildInfo
std::array<uint8, 20> MacHash;
};
namespace boost::system
{
class error_code;
}
/// Storage object for the list of realms on the server
class AC_SHARED_API RealmList
{
public:
typedef std::map<RealmHandle, Realm> RealmMap;
RealmList();
~RealmList() = default;
static RealmList* Instance();
static RealmList* instance();
void Initialize(uint32 updateInterval);
void UpdateIfNeed();
void Initialize(Acore::Asio::IoContext& ioContext, uint32 updateInterval);
void Close();
RealmMap const& GetRealms() const { return _realms; }
Realm const* GetRealm(RealmHandle const& id) const;
@ -45,18 +47,22 @@ public:
RealmBuildInfo const* GetBuildInfo(uint32 build) const;
private:
RealmList();
~RealmList() = default;
void LoadBuildInfo();
void UpdateRealms();
void UpdateRealms(boost::system::error_code const& error);
void UpdateRealm(RealmHandle const& id, uint32 build, std::string const& name,
ACE_INET_Addr&& address, ACE_INET_Addr&& localAddr, ACE_INET_Addr&& localSubmask,
boost::asio::ip::address&& address, boost::asio::ip::address&& localAddr, boost::asio::ip::address&& localSubmask,
uint16 port, uint8 icon, RealmFlags flag, uint8 timezone, AccountTypes allowedSecurityLevel, float population);
std::vector<RealmBuildInfo> _builds;
RealmMap _realms;
uint32 _updateInterval;
time_t _nextUpdateTime;
std::unique_ptr<Acore::Asio::DeadlineTimer> _updateTimer;
std::unique_ptr<Acore::Asio::Resolver> _resolver;
};
#define sRealmList RealmList::instance()
#define sRealmList RealmList::Instance()
#endif

View file

@ -12,7 +12,7 @@
#include "Chat.h"
#include "CliRunnable.h"
#include "Common.h"
#include "Configuration/Config.h"
#include "Config.h"
#include "Language.h"
#include "Log.h"
#include "MapManager.h"
@ -124,7 +124,7 @@ int kb_hit_return()
#endif
/// %Thread start
void CliRunnable::run()
void CliThread()
{
///- Display the list of available CLI functions then beep
//TC_LOG_INFO("server.worldserver", "");

View file

@ -11,14 +11,8 @@
#ifndef __CLIRUNNABLE_H
#define __CLIRUNNABLE_H
#include "Threading.h"
/// Command Line Interface handling thread
class CliRunnable : public Acore::Runnable
{
public:
void run() override;
};
void CliThread();
#endif

View file

@ -4,24 +4,49 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/// \addtogroup Trinityd Trinity Daemon
/// @{
/// \file
#include "ACSoap.h"
#include "AppenderDB.h"
#include "AsyncAcceptor.h"
#include "AsyncAuctionListing.h"
#include "AvgDiffTracker.h"
#include "Banner.h"
#include "BattlegroundMgr.h"
#include "BigNumber.h"
#include "CliRunnable.h"
#include "Common.h"
#include "Config.h"
#include "Configuration/Config.h"
#include "Database/DatabaseEnv.h"
#include "Log.h"
#include "Master.h"
#include "DatabaseEnv.h"
#include "DatabaseLoader.h"
#include "DeadlineTimer.h"
#include "GitRevision.h"
#include "IoContext.h"
#include "MapManager.h"
#include "MySQLThreading.h"
#include "ObjectAccessor.h"
#include "OpenSSLCrypto.h"
#include "OutdoorPvPMgr.h"
#include "ProcessPriority.h"
#include "RASession.h"
#include "RealmList.h"
#include "Resolver.h"
#include "ScriptLoader.h"
#include "ScriptMgr.h"
#include "SecretMgr.h"
#include "SharedDefines.h"
#include <ace/Version.h>
#include <boost/version.hpp>
#include "World.h"
#include "WorldSocket.h"
#include "WorldSocketMgr.h"
#include <boost/asio/signal_set.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/program_options.hpp>
#include <csignal>
#include <iostream>
#include <openssl/crypto.h>
#include <openssl/opensslv.h>
#ifndef _ACORE_CORE_CONFIG
#define _ACORE_CORE_CONFIG "worldserver.conf"
#ifdef ELUNA
#include "LuaEngine.h"
#endif
#ifdef _WIN32
@ -38,6 +63,44 @@ char serviceDescription[] = "AzerothCore World of Warcraft emulator world servic
int m_ServiceStatus = -1;
#endif
#ifndef _ACORE_CORE_CONFIG
#define _ACORE_CORE_CONFIG "worldserver.conf"
#endif
#define WORLD_SLEEP_CONST 10
class FreezeDetector
{
public:
FreezeDetector(Acore::Asio::IoContext& ioContext, uint32 maxCoreStuckTime)
: _timer(ioContext), _worldLoopCounter(0), _lastChangeMsTime(getMSTime()), _maxCoreStuckTimeInMs(maxCoreStuckTime) { }
static void Start(std::shared_ptr<FreezeDetector> const& freezeDetector)
{
freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(5));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, std::weak_ptr<FreezeDetector>(freezeDetector), std::placeholders::_1));
}
static void Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error);
private:
Acore::Asio::DeadlineTimer _timer;
uint32 _worldLoopCounter;
uint32 _lastChangeMsTime;
uint32 _maxCoreStuckTimeInMs;
};
void SignalHandler(boost::system::error_code const& error, int signalNumber);
void ClearOnlineAccounts();
bool StartDB();
void StopDB();
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext);
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext);
void ShutdownCLIThread(std::thread* cliThread);
void AuctionListingRunnable();
void ShutdownAuctionListingThread(std::thread* thread);
void WorldUpdateLoop();
/// Print out the usage string for this program on the console.
void usage(const char* prog)
{
@ -52,10 +115,11 @@ void usage(const char* prog)
#endif
}
/// Launch the Trinity server
extern int main(int argc, char** argv)
/// Launch the Azeroth server
int main(int argc, char** argv)
{
Acore::Impl::CurrentServerProcessHolder::_type = SERVER_PROCESS_WORLDSERVER;
signal(SIGABRT, &Acore::AbortHandler);
///- Command line parsing to get the configuration file name
std::string configFile = sConfigMgr->GetConfigPath() + std::string(_ACORE_CORE_CONFIG);
@ -124,6 +188,9 @@ extern int main(int argc, char** argv)
// Loading modules configs
sConfigMgr->LoadModulesConfigs();
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
// Init all logs
sLog->RegisterAppender<AppenderDB>();
sLog->Initialize();
@ -134,23 +201,541 @@ extern int main(int argc, char** argv)
},
[]()
{
LOG_INFO("server.worldserver", "> Using configuration file %s.", sConfigMgr->GetFilename().c_str());
LOG_INFO("server.worldserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
LOG_INFO("server.worldserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
LOG_INFO("server.worldserver", "> Using ACE version: %s\n", ACE_VERSION);
LOG_INFO("server.worldserver", "> Using configuration file: %s", sConfigMgr->GetFilename().c_str());
LOG_INFO("server.worldserver", "> Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
LOG_INFO("server.worldserver", "> Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
}
);
///- and run the 'Master'
/// @todo Why do we need this 'Master'? Can't all of this be in the Main as for Realmd?
int ret = sMaster->Run();
OpenSSLCrypto::threadsSetup();
std::shared_ptr<void> opensslHandle(nullptr, [](void*) { OpenSSLCrypto::threadsCleanup(); });
// Seed the OpenSSL's PRNG here.
// That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login
BigNumber seed;
seed.SetRand(16 * 8);
/// worldserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
{
if (uint32 pid = CreatePIDFile(pidFile))
LOG_ERROR("server", "Daemon PID: %u\n", pid); // outError for red color in console
else
{
LOG_ERROR("server", "Cannot create PID file %s (possible error: permission)\n", pidFile.c_str());
return 1;
}
}
// Set signal handlers (this must be done before starting IoContext threads, because otherwise they would unblock and exit)
boost::asio::signal_set signals(*ioContext, SIGINT, SIGTERM);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signals.add(SIGBREAK);
#endif
signals.async_wait(SignalHandler);
// Start the Boost based thread pool
int numThreads = sConfigMgr->GetOption<int32>("ThreadPool", 1);
std::shared_ptr<std::vector<std::thread>> threadPool(new std::vector<std::thread>(), [ioContext](std::vector<std::thread>* del)
{
ioContext->stop();
for (std::thread& thr : *del)
thr.join();
delete del;
});
if (numThreads < 1)
{
numThreads = 1;
}
for (int i = 0; i < numThreads; ++i)
{
threadPool->push_back(std::thread([ioContext]()
{
ioContext->run();
}));
}
// Set process priority according to configuration settings
SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false));
// Start the databases
if (!StartDB())
return 1;
std::shared_ptr<void> dbHandle(nullptr, [](void*) { StopDB(); });
// set server offline (not connectable)
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
LoadRealmInfo(*ioContext);
// Loading modules configs
sConfigMgr->PrintLoadedModulesConfigs();
sScriptMgr->SetScriptLoader(AddScripts);
std::shared_ptr<void> sScriptMgrHandle(nullptr, [](void*)
{
sScriptMgr->Unload();
//sScriptReloadMgr->Unload();
});
///- Initialize the World
sSecretMgr->Initialize();
sWorld->SetInitialWorldSettings();
std::shared_ptr<void> mapManagementHandle(nullptr, [](void*)
{
// unload battleground templates before different singletons destroyed
sBattlegroundMgr->DeleteAllBattlegrounds();
sOutdoorPvPMgr->Die(); // unload it before MapManager
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
#ifdef ELUNA
Eluna::Uninitialize();
#endif
});
// Start the Remote Access port (acceptor) if enabled
std::unique_ptr<AsyncAcceptor> raAcceptor;
if (sConfigMgr->GetOption<bool>("Ra.Enable", false))
{
raAcceptor.reset(StartRaSocketAcceptor(*ioContext));
}
// Start soap serving thread if enabled
std::shared_ptr<std::thread> soapThread;
if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false))
{
soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetOption<int32>("SOAP.Port", 7878))),
[](std::thread* thr)
{
thr->join();
delete thr;
});
}
// Launch the worldserver listener socket
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
std::string worldListener = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
int networkThreads = sConfigMgr->GetOption<int32>("Network.Threads", 1);
if (networkThreads <= 0)
{
LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
if (!sWorldSocketMgr.StartWorldNetwork(*ioContext, worldListener, worldPort, networkThreads))
{
LOG_ERROR("server.worldserver", "Failed to initialize network");
World::StopNow(ERROR_EXIT_CODE);
return 1;
}
std::shared_ptr<void> sWorldSocketMgrHandle(nullptr, [](void*)
{
sWorld->KickAll(); // save and kick all players
sWorld->UpdateSessions(1); // real players unload required UpdateSessions call
sWorldSocketMgr.StopNetwork();
///- Clean database before leaving
ClearOnlineAccounts();
});
// Set server online (allow connecting now)
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
realm.PopulationLevel = 0.0f;
realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_VERSION_MISMATCH));
// Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec)
std::shared_ptr<FreezeDetector> freezeDetector;
if (int32 coreStuckTime = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 60))
{
freezeDetector = std::make_shared<FreezeDetector>(*ioContext, coreStuckTime * 1000);
FreezeDetector::Start(freezeDetector);
LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", coreStuckTime);
}
LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion());
sScriptMgr->OnStartup();
// Launch CliRunnable thread
std::shared_ptr<std::thread> cliThread;
#ifdef _WIN32
if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
if (sConfigMgr->GetOption<bool>("Console.Enable", true))
#endif
{
cliThread.reset(new std::thread(CliThread), &ShutdownCLIThread);
}
// Launch CliRunnable thread
std::shared_ptr<std::thread> auctionLisingThread;
auctionLisingThread.reset(new std::thread(AuctionListingRunnable),
[](std::thread* thr)
{
thr->join();
delete thr;
});
WorldUpdateLoop();
// Shutdown starts here
threadPool.reset();
sScriptMgr->OnShutdown();
// set server offline
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm);
LOG_INFO("server.worldserver", "Halting process...");
// at sMaster return function exist with codes
// 0 - normal shutdown
// 1 - shutdown at error
// 2 - restart command used, this code can be used by restarter for restart Trinityd
// 2 - restart command used, this code can be used by restarter for restart Warheadd
return ret;
return World::GetExitCode();
}
/// @}
/// Initialize connection to the databases
bool StartDB()
{
MySQL::Library_Init();
// Load databases
DatabaseLoader loader("server.worldserver");
loader
.AddDatabase(LoginDatabase, "Login")
.AddDatabase(CharacterDatabase, "Character")
.AddDatabase(WorldDatabase, "World");
if (!loader.Load())
return false;
///- Get the realm Id from the configuration file
realm.Id.Realm = sConfigMgr->GetIntDefault("RealmID", 0);
if (!realm.Id.Realm)
{
LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
return false;
}
else if (realm.Id.Realm > 255)
{
/*
* Due to the client only being able to read a realm.Id.Realm
* with a size of uint8 we can "only" store up to 255 realms
* anything further the client will behave anormaly
*/
LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255");
return false;
}
LOG_INFO("server.loading", "Loading world information...");
LOG_INFO("server.loading", "> RealmID: %u", realm.Id.Realm);
///- Clean the database before starting
ClearOnlineAccounts();
///- Insert version info into DB
WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query
sWorld->LoadDBVersion();
sWorld->LoadDBRevision();
LOG_INFO("server.loading", "> Version DB world: %s", sWorld->GetDBVersion());
return true;
}
void StopDB()
{
CharacterDatabase.Close();
WorldDatabase.Close();
LoginDatabase.Close();
MySQL::Library_End();
}
/// Clear 'online' status for all accounts with characters in this realm
void ClearOnlineAccounts()
{
// Reset online status for all accounts with characters on the current realm
// pussywizard: tc query would set online=0 even if logged in on another realm >_>
LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online = %u", realm.Id.Realm);
// Reset online status for all characters
CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
}
void ShutdownCLIThread(std::thread* cliThread)
{
if (cliThread != nullptr)
{
#ifdef _WIN32
// First try to cancel any I/O in the CLI thread
if (!CancelSynchronousIo(cliThread->native_handle()))
{
// if CancelSynchronousIo() fails, print the error and try with old way
DWORD errorCode = GetLastError();
LPCSTR errorBuffer;
DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr);
if (!formatReturnCode)
errorBuffer = "Unknown error";
LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code %u, detail: %s", uint32(errorCode), errorBuffer);
if (!formatReturnCode)
LocalFree((LPSTR)errorBuffer);
// send keyboard input to safely unblock the CLI thread
INPUT_RECORD b[4];
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
b[0].EventType = KEY_EVENT;
b[0].Event.KeyEvent.bKeyDown = TRUE;
b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
b[0].Event.KeyEvent.wRepeatCount = 1;
b[1].EventType = KEY_EVENT;
b[1].Event.KeyEvent.bKeyDown = FALSE;
b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
b[1].Event.KeyEvent.wRepeatCount = 1;
b[2].EventType = KEY_EVENT;
b[2].Event.KeyEvent.bKeyDown = TRUE;
b[2].Event.KeyEvent.dwControlKeyState = 0;
b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[2].Event.KeyEvent.wRepeatCount = 1;
b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].EventType = KEY_EVENT;
b[3].Event.KeyEvent.bKeyDown = FALSE;
b[3].Event.KeyEvent.dwControlKeyState = 0;
b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].Event.KeyEvent.wRepeatCount = 1;
DWORD numb;
WriteConsoleInput(hStdIn, b, 4, &numb);
}
#endif
cliThread->join();
delete cliThread;
}
}
void WorldUpdateLoop()
{
uint32 realCurrTime = 0;
uint32 realPrevTime = getMSTime();
LoginDatabase.WarnAboutSyncQueries(true);
CharacterDatabase.WarnAboutSyncQueries(true);
WorldDatabase.WarnAboutSyncQueries(true);
///- While we have not World::m_stopEvent, update the world
while (!World::IsStopped())
{
++World::m_worldLoopCounter;
realCurrTime = getMSTime();
uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
sWorld->Update(diff);
realPrevTime = realCurrTime;
uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime());
devDiffTracker.Update(executionTimeDiff);
avgDiffTracker.Update(executionTimeDiff > WORLD_SLEEP_CONST ? executionTimeDiff : WORLD_SLEEP_CONST);
// we know exactly how long it took to update the world, if the update took less than WORLD_SLEEP_CONST, sleep for WORLD_SLEEP_CONST - world update time
if (executionTimeDiff < WORLD_SLEEP_CONST)
{
std::this_thread::sleep_for(Milliseconds(WORLD_SLEEP_CONST - executionTimeDiff));
}
#ifdef _WIN32
if (m_ServiceStatus == 0)
World::StopNow(SHUTDOWN_EXIT_CODE);
while (m_ServiceStatus == 2)
Sleep(1000);
#endif
}
LoginDatabase.WarnAboutSyncQueries(false);
CharacterDatabase.WarnAboutSyncQueries(false);
WorldDatabase.WarnAboutSyncQueries(false);
}
void SignalHandler(boost::system::error_code const& error, int /*signalNumber*/)
{
if (!error)
World::StopNow(SHUTDOWN_EXIT_CODE);
}
void FreezeDetector::Handler(std::weak_ptr<FreezeDetector> freezeDetectorRef, boost::system::error_code const& error)
{
if (!error)
{
if (std::shared_ptr<FreezeDetector> freezeDetector = freezeDetectorRef.lock())
{
uint32 curtime = getMSTime();
uint32 worldLoopCounter = World::m_worldLoopCounter;
if (freezeDetector->_worldLoopCounter != worldLoopCounter)
{
freezeDetector->_lastChangeMsTime = curtime;
freezeDetector->_worldLoopCounter = worldLoopCounter;
}
// possible freeze
else if (getMSTimeDiff(freezeDetector->_lastChangeMsTime, curtime) > freezeDetector->_maxCoreStuckTimeInMs)
{
LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!");
ABORT();
}
freezeDetector->_timer.expires_from_now(boost::posix_time::seconds(1));
freezeDetector->_timer.async_wait(std::bind(&FreezeDetector::Handler, freezeDetectorRef, std::placeholders::_1));
}
}
}
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext)
{
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort);
if (!acceptor->Bind())
{
LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor");
delete acceptor;
return nullptr;
}
acceptor->AsyncAccept<RASession>();
return acceptor;
}
bool LoadRealmInfo(Acore::Asio::IoContext& ioContext)
{
QueryResult result = LoginDatabase.PQuery("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = %u", realm.Id.Realm);
if (!result)
return false;
Acore::Asio::Resolver resolver(ioContext);
Field* fields = result->Fetch();
realm.Name = fields[1].GetString();
Optional<boost::asio::ip::tcp::endpoint> externalAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[2].GetString(), "");
if (!externalAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[2].GetString().c_str());
return false;
}
realm.ExternalAddress = std::make_unique<boost::asio::ip::address>(externalAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localAddress = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[3].GetString(), "");
if (!localAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[3].GetString().c_str());
return false;
}
realm.LocalAddress = std::make_unique<boost::asio::ip::address>(localAddress->address());
Optional<boost::asio::ip::tcp::endpoint> localSubmask = resolver.Resolve(boost::asio::ip::tcp::v4(), fields[4].GetString(), "");
if (!localSubmask)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[4].GetString().c_str());
return false;
}
realm.LocalSubnetMask = std::make_unique<boost::asio::ip::address>(localSubmask->address());
realm.Port = fields[5].GetUInt16();
realm.Type = fields[6].GetUInt8();
realm.Flags = RealmFlags(fields[7].GetUInt8());
realm.Timezone = fields[8].GetUInt8();
realm.AllowedSecurityLevel = AccountTypes(fields[9].GetUInt8());
realm.PopulationLevel = fields[10].GetFloat();
realm.Build = fields[11].GetUInt32();
return true;
}
void AuctionListingRunnable()
{
LOG_INFO("server", "Starting up Auction House Listing thread...");
while (!World::IsStopped())
{
if (AsyncAuctionListingMgr::IsAuctionListingAllowed())
{
uint32 diff = AsyncAuctionListingMgr::GetDiff();
AsyncAuctionListingMgr::ResetDiff();
if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size())
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock());
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock());
for (auto const& delayEvent : AsyncAuctionListingMgr::GetTempList())
AsyncAuctionListingMgr::GetList().emplace_back(delayEvent);
AsyncAuctionListingMgr::GetTempList().clear();
}
for (auto& itr : AsyncAuctionListingMgr::GetList())
{
if (itr._msTimer <= diff)
itr._msTimer = 0;
else
itr._msTimer -= diff;
}
for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr)
{
if ((*itr)._msTimer != 0)
continue;
if ((*itr).Execute())
AsyncAuctionListingMgr::GetList().erase(itr);
break;
}
}
}
std::this_thread::sleep_for(1ms);
}
LOG_INFO("server", "Auction House Listing thread exiting without problems.");
}
void ShutdownAuctionListingThread(std::thread* thread)
{
if (thread != nullptr)
{
thread->join();
delete thread;
}
}

View file

@ -1,436 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \file
\ingroup Trinityd
*/
#include "ACSoap.h"
#include "AsyncAcceptor.h"
#include "BigNumber.h"
#include "CliRunnable.h"
#include "Common.h"
#include "Config.h"
#include "DatabaseEnv.h"
#include "GitRevision.h"
#include "IoContext.h"
#include "Log.h"
#include "Master.h"
#include "OpenSSLCrypto.h"
#include "RASession.h"
#include "Realm.h"
#include "ScriptMgr.h"
#include "SignalHandler.h"
#include "ScriptLoader.h"
#include "Timer.h"
#include "Util.h"
#include "World.h"
#include "WorldRunnable.h"
#include "WorldSocket.h"
#include "WorldSocketMgr.h"
#include "DatabaseLoader.h"
#include "Optional.h"
#include "MySQLThreading.h"
#include "SecretMgr.h"
#include "ProcessPriority.h"
#include <ace/Sig_Handler.h>
#ifdef _WIN32
#include "ServiceWin32.h"
extern int m_ServiceStatus;
#endif
/// Handle worldservers's termination signals
void HandleSignal(int sigNum)
{
switch (sigNum)
{
case SIGINT:
World::StopNow(RESTART_EXIT_CODE);
break;
case SIGTERM:
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
case SIGBREAK:
if (m_ServiceStatus != 1)
#endif
World::StopNow(SHUTDOWN_EXIT_CODE);
break;
default:
break;
}
}
class FreezeDetectorRunnable : public Acore::Runnable
{
private:
uint32 _loops;
uint32 _lastChange;
uint32 _delayTime;
public:
FreezeDetectorRunnable(uint32 freezeDelay) : _loops(0), _lastChange(0), _delayTime(freezeDelay) {}
void run() override
{
if (!_delayTime)
return;
LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", _delayTime / 1000);
while (!World::IsStopped())
{
uint32 curtime = getMSTime();
if (_loops != World::m_worldLoopCounter)
{
_lastChange = curtime;
_loops = World::m_worldLoopCounter;
}
else if (getMSTimeDiff(_lastChange, curtime) > _delayTime)
{
LOG_INFO("server.worldserver", "World Thread hangs, kicking out server!");
ABORT();
}
Acore::Thread::Sleep(1000);
}
LOG_INFO("server.worldserver", "Anti-freeze thread exiting without problems.");
}
};
bool LoadRealmInfo();
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext);
Master* Master::instance()
{
static Master instance;
return &instance;
}
/// Main function
int Master::Run()
{
std::shared_ptr<Acore::Asio::IoContext> ioContext = std::make_shared<Acore::Asio::IoContext>();
OpenSSLCrypto::threadsSetup();
BigNumber seed1;
seed1.SetRand(16 * 8);
/// worldserver PID file creation
std::string pidFile = sConfigMgr->GetOption<std::string>("PidFile", "");
if (!pidFile.empty())
{
if (uint32 pid = CreatePIDFile(pidFile))
LOG_ERROR("server.worldserver", "Daemon PID: %u\n", pid); // outError for red color in console
else
{
LOG_ERROR("server.worldserver", "Cannot create PID file %s (possible error: permission)\n", pidFile.c_str());
return 1;
}
}
// Set process priority according to configuration settings
SetProcessPriority("server.worldserver", sConfigMgr->GetOption<int32>(CONFIG_PROCESSOR_AFFINITY, 0), sConfigMgr->GetOption<bool>(CONFIG_HIGH_PRIORITY, false));
///- Start the databases
if (!_StartDB())
return 1;
// set server offline (not connectable)
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
LoadRealmInfo();
// Loading modules configs
sConfigMgr->PrintLoadedModulesConfigs();
///- Initialize the World
sSecretMgr->Initialize();
sScriptMgr->SetScriptLoader(AddScripts);
sWorld->SetInitialWorldSettings();
sScriptMgr->OnStartup();
///- Initialize the signal handlers
Acore::SignalHandler signalHandler;
signalHandler.handle_signal(SIGINT, &HandleSignal);
signalHandler.handle_signal(SIGTERM, &HandleSignal);
#if AC_PLATFORM == AC_PLATFORM_WINDOWS
signalHandler.handle_signal(SIGBREAK, &HandleSignal);
#endif
///- Launch WorldRunnable thread
Acore::Thread worldThread(new WorldRunnable);
worldThread.setPriority(Acore::Priority_Highest);
Acore::Thread* cliThread = nullptr;
#ifdef _WIN32
if (sConfigMgr->GetOption<bool>("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
if (sConfigMgr->GetOption<bool>("Console.Enable", true))
#endif
{
///- Launch CliRunnable thread
cliThread = new Acore::Thread(new CliRunnable);
}
// pussywizard:
Acore::Thread auctionLising_thread(new AuctionListingRunnable);
auctionLising_thread.setPriority(Acore::Priority_High);
// Start the Remote Access port (acceptor) if enabled
std::unique_ptr<AsyncAcceptor> raAcceptor;
if (sConfigMgr->GetOption<bool>("Ra.Enable", false))
raAcceptor.reset(StartRaSocketAcceptor(*ioContext));
// Start soap serving thread if enabled
std::shared_ptr<std::thread> soapThread;
if (sConfigMgr->GetOption<bool>("SOAP.Enabled", false))
{
soapThread.reset(new std::thread(ACSoapThread, sConfigMgr->GetOption<std::string>("SOAP.IP", "127.0.0.1"), sConfigMgr->GetOption<uint16>("SOAP.Port", 7878)),
[](std::thread* thr)
{
thr->join();
delete thr;
});
}
// Start up freeze catcher thread
Acore::Thread* freezeThread = nullptr;
if (uint32 freezeDelay = sConfigMgr->GetOption<int32>("MaxCoreStuckTime", 0))
{
FreezeDetectorRunnable* runnable = new FreezeDetectorRunnable(freezeDelay * 1000);
freezeThread = new Acore::Thread(runnable);
freezeThread->setPriority(Acore::Priority_Highest);
}
///- Launch the world listener socket
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
std::string bindIp = sConfigMgr->GetOption<std::string>("BindIP", "0.0.0.0");
if (sWorldSocketMgr->StartNetwork(worldPort, bindIp.c_str()) == -1)
{
LOG_ERROR("server.worldserver", "Failed to start network");
World::StopNow(ERROR_EXIT_CODE);
// go down and shutdown the server
}
// set server online (allow connecting now)
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_VERSION_MISMATCH, realm.Id.Realm);
LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion());
// when the main thread closes the singletons get unloaded
// since worldrunnable uses them, it will crash if unloaded after master
worldThread.wait();
auctionLising_thread.wait();
if (freezeThread)
{
freezeThread->wait();
delete freezeThread;
}
// set server offline
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm);
///- Clean database before leaving
ClearOnlineAccounts();
_StopDB();
LOG_INFO("server.worldserver", "Halting process...");
if (cliThread)
{
#ifdef _WIN32
// this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
//_exit(1);
// send keyboard input to safely unblock the CLI thread
INPUT_RECORD b[4];
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
b[0].EventType = KEY_EVENT;
b[0].Event.KeyEvent.bKeyDown = TRUE;
b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
b[0].Event.KeyEvent.wRepeatCount = 1;
b[1].EventType = KEY_EVENT;
b[1].Event.KeyEvent.bKeyDown = FALSE;
b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
b[1].Event.KeyEvent.wRepeatCount = 1;
b[2].EventType = KEY_EVENT;
b[2].Event.KeyEvent.bKeyDown = TRUE;
b[2].Event.KeyEvent.dwControlKeyState = 0;
b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[2].Event.KeyEvent.wRepeatCount = 1;
b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].EventType = KEY_EVENT;
b[3].Event.KeyEvent.bKeyDown = FALSE;
b[3].Event.KeyEvent.dwControlKeyState = 0;
b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
b[3].Event.KeyEvent.wRepeatCount = 1;
DWORD numb;
WriteConsoleInput(hStdIn, b, 4, &numb);
cliThread->wait();
#else
cliThread->destroy();
#endif
delete cliThread;
}
// for some unknown reason, unloading scripts here and not in worldrunnable
// fixes a memory leak related to detaching threads from the module
//UnloadScriptingModule();
OpenSSLCrypto::threadsCleanup();
// Exit the process with specified return value
return World::GetExitCode();
}
/// Initialize connection to the databases
bool Master::_StartDB()
{
MySQL::Library_Init();
// Load databases
DatabaseLoader loader("server.worldserver");
loader
.AddDatabase(LoginDatabase, "Login")
.AddDatabase(CharacterDatabase, "Character")
.AddDatabase(WorldDatabase, "World");
if (!loader.Load())
return false;
///- Get the realm Id from the configuration file
realm.Id.Realm = sConfigMgr->GetOption<int32>("RealmID", 0);
if (!realm.Id.Realm)
{
LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
return false;
}
else if (realm.Id.Realm > 255)
{
/*
* Due to the client only being able to read a realm.Id.Realm
* with a size of uint8 we can "only" store up to 255 realms
* anything further the client will behave anormaly
*/
LOG_ERROR("server.worldserver", "Realm ID must range from 1 to 255");
return false;
}
LOG_INFO("server.worldserver", "Realm running as realm ID %d", realm.Id.Realm);
///- Clean the database before starting
ClearOnlineAccounts();
///- Insert version info into DB
WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // One-time query
sWorld->LoadDBVersion();
sWorld->LoadDBRevision();
LOG_INFO("server.worldserver", "Using World DB: %s", sWorld->GetDBVersion());
return true;
}
void Master::_StopDB()
{
CharacterDatabase.Close();
WorldDatabase.Close();
LoginDatabase.Close();
MySQL::Library_End();
}
/// Clear 'online' status for all accounts with characters in this realm
void Master::ClearOnlineAccounts()
{
// Reset online status for all accounts with characters on the current realm
// pussywizard: tc query would set online=0 even if logged in on another realm >_>
LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online = %u", realm.Id.Realm);
// Reset online status for all characters
CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
}
bool LoadRealmInfo()
{
QueryResult result = LoginDatabase.PQuery("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild FROM realmlist WHERE id = %u", realm.Id.Realm);
if (!result)
{
LOG_ERROR("server.worldserver", "> Not found realm with ID %u", realm.Id.Realm);
return false;
}
Field* fields = result->Fetch();
realm.Name = fields[1].GetString();
realm.Port = fields[5].GetUInt16();
Optional<ACE_INET_Addr> externalAddress = ACE_INET_Addr(realm.Port, fields[2].GetCString(), AF_INET);
if (!externalAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[2].GetString().c_str());
return false;
}
Optional<ACE_INET_Addr> localAddress = ACE_INET_Addr(realm.Port, fields[3].GetCString(), AF_INET);
if (!localAddress)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[3].GetString().c_str());
return false;
}
Optional<ACE_INET_Addr> localSubmask = ACE_INET_Addr(0, fields[4].GetCString(), AF_INET);
if (!localSubmask)
{
LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[4].GetString().c_str());
return false;
}
realm.ExternalAddress = std::make_unique<ACE_INET_Addr>(*externalAddress);
realm.LocalAddress = std::make_unique<ACE_INET_Addr>(*localAddress);
realm.LocalSubnetMask = std::make_unique<ACE_INET_Addr>(*localSubmask);
realm.Type = fields[6].GetUInt8();
realm.Flags = RealmFlags(fields[7].GetUInt8());
realm.Timezone = fields[8].GetUInt8();
realm.AllowedSecurityLevel = AccountTypes(fields[9].GetUInt8());
realm.PopulationLevel = fields[10].GetFloat();
realm.Build = fields[11].GetUInt32();
return true;
}
AsyncAcceptor* StartRaSocketAcceptor(Acore::Asio::IoContext& ioContext)
{
uint16 raPort = uint16(sConfigMgr->GetOption<int32>("Ra.Port", 3443));
std::string raListener = sConfigMgr->GetOption<std::string>("Ra.IP", "0.0.0.0");
AsyncAcceptor* acceptor = new AsyncAcceptor(ioContext, raListener, raPort);
if (!acceptor->Bind())
{
LOG_ERROR("server.worldserver", "Failed to bind RA socket acceptor");
delete acceptor;
return nullptr;
}
acceptor->AsyncAccept<RASession>();
return acceptor;
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/// \addtogroup Trinityd
/// @{
/// \file
#ifndef _MASTER_H
#define _MASTER_H
#include "Common.h"
/// Start the server
class Master
{
public:
static Master* instance();
int Run();
private:
bool _StartDB();
void _StopDB();
void ClearOnlineAccounts();
};
#define sMaster Master::instance()
#endif
/// @}

View file

@ -1,126 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/** \file
\ingroup Trinityd
*/
#include "AsyncAuctionListing.h"
#include "AvgDiffTracker.h"
#include "BattlegroundMgr.h"
#include "Common.h"
#include "Database/DatabaseEnv.h"
#include "MapManager.h"
#include "ObjectAccessor.h"
#include "OutdoorPvPMgr.h"
#include "ScriptMgr.h"
#include "Timer.h"
#include "World.h"
#include "WorldRunnable.h"
#include "WorldSocketMgr.h"
#ifdef ELUNA
#include "LuaEngine.h"
#endif
#ifdef _WIN32
#include "ServiceWin32.h"
extern int m_ServiceStatus;
#endif
/// Heartbeat for the World
void WorldRunnable::run()
{
uint32 realCurrTime = 0;
uint32 realPrevTime = getMSTime();
///- While we have not World::m_stopEvent, update the world
while (!World::IsStopped())
{
++World::m_worldLoopCounter;
realCurrTime = getMSTime();
uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
sWorld->Update( diff );
realPrevTime = realCurrTime;
uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime());
devDiffTracker.Update(executionTimeDiff);
avgDiffTracker.Update(executionTimeDiff > WORLD_SLEEP_CONST ? executionTimeDiff : WORLD_SLEEP_CONST);
if (executionTimeDiff < WORLD_SLEEP_CONST)
Acore::Thread::Sleep(WORLD_SLEEP_CONST - executionTimeDiff);
#ifdef _WIN32
if (m_ServiceStatus == 0)
World::StopNow(SHUTDOWN_EXIT_CODE);
while (m_ServiceStatus == 2)
Sleep(1000);
#endif
}
sScriptMgr->OnShutdown();
sWorld->KickAll(); // save and kick all players
sWorld->UpdateSessions( 1 ); // real players unload required UpdateSessions call
// unload battleground templates before different singletons destroyed
sBattlegroundMgr->DeleteAllBattlegrounds();
sWorldSocketMgr->StopNetwork();
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
sOutdoorPvPMgr->Die();
sScriptMgr->Unload();
#ifdef ELUNA
Eluna::Uninitialize();
#endif
}
void AuctionListingRunnable::run()
{
LOG_INFO("auctionHouse", "Starting up Auction House Listing thread...");
while (!World::IsStopped())
{
if (AsyncAuctionListingMgr::IsAuctionListingAllowed())
{
uint32 diff = AsyncAuctionListingMgr::GetDiff();
AsyncAuctionListingMgr::ResetDiff();
if (AsyncAuctionListingMgr::GetTempList().size() || AsyncAuctionListingMgr::GetList().size())
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetLock());
{
std::lock_guard<std::mutex> guard(AsyncAuctionListingMgr::GetTempLock());
for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetTempList().begin(); itr != AsyncAuctionListingMgr::GetTempList().end(); ++itr)
AsyncAuctionListingMgr::GetList().push_back( (*itr) );
AsyncAuctionListingMgr::GetTempList().clear();
}
for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr)
{
if ((*itr)._msTimer <= diff)
(*itr)._msTimer = 0;
else
(*itr)._msTimer -= diff;
}
for (std::list<AuctionListItemsDelayEvent>::iterator itr = AsyncAuctionListingMgr::GetList().begin(); itr != AsyncAuctionListingMgr::GetList().end(); ++itr)
if ((*itr)._msTimer == 0)
{
if ((*itr).Execute())
AsyncAuctionListingMgr::GetList().erase(itr);
break;
}
}
}
Acore::Thread::Sleep(1);
}
LOG_INFO("auctionHouse", "Auction House Listing thread exiting without problems.");
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it and/or modify it under version 2 of the License, or (at your option), any later version.
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
/// \addtogroup Trinityd
/// @{
/// \file
#ifndef __WORLDRUNNABLE_H
#define __WORLDRUNNABLE_H
#include "Threading.h"
/// Heartbeat thread for the World
class WorldRunnable : public Acore::Runnable
{
public:
void run() override;
};
class AuctionListingRunnable : public Acore::Runnable
{
public:
void run() override;
};
#endif
/// @}

View file

@ -197,6 +197,19 @@ SourceDirectory = ""
MySQLExecutable = ""
#
# ThreadPool
# Description: Number of threads to be used for the global thread pool
# The thread pool is currently used for:
# - Signal handling
# - Remote access
# - Database keep-alive ping
# - Core freeze check
# - World socket networking
# Default: 2
ThreadPool = 2
#
# IPLocationFile
# Description: The path to your IP2Location database CSV file.

View file

@ -13,7 +13,6 @@
#pragma GCC diagnostic ignored "-Wunused-parameter"
void AddScripts() {}
bool ArenaSpectator::HandleSpectatorSpectateCommand(ChatHandler* handler, char const* args) { return false; }
class WorldMock: public IWorld {
public: