summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/Banner.cpp2
-rw-r--r--src/common/CMakeLists.txt1
-rw-r--r--src/common/Common.h3
-rw-r--r--src/common/Define.h8
-rw-r--r--src/common/Platform/ServiceWin32.cpp5
-rw-r--r--src/common/Threading/MPSCQueue.h154
-rw-r--r--src/common/Threading/PCQueue.h2
-rw-r--r--src/common/Threading/ProducerConsumerQueue.h100
-rw-r--r--src/common/Utilities/Util.cpp29
-rw-r--r--src/common/Utilities/Util.h10
-rw-r--r--src/server/authserver/Main.cpp156
-rw-r--r--src/server/authserver/PrecompiledHeaders/authPCH.h5
-rw-r--r--src/server/authserver/Server/AuthSession.cpp866
-rw-r--r--src/server/authserver/Server/AuthSession.h109
-rw-r--r--src/server/authserver/Server/AuthSocket.cpp1137
-rw-r--r--src/server/authserver/Server/AuthSocket.h97
-rw-r--r--src/server/authserver/Server/AuthSocketMgr.h46
-rw-r--r--src/server/authserver/Server/RealmAcceptor.h58
-rw-r--r--src/server/authserver/authserver.conf.dist17
-rw-r--r--src/server/database/Database/Callback.h299
-rw-r--r--src/server/database/Database/DatabaseWorker.cpp2
-rw-r--r--src/server/database/Database/DatabaseWorkerPool.cpp2
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.cpp10
-rw-r--r--src/server/database/Database/Implementation/LoginDatabase.h2
-rw-r--r--src/server/game/ArenaSpectator/ArenaSpectator.cpp431
-rw-r--r--src/server/game/ArenaSpectator/ArenaSpectator.h190
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp3
-rw-r--r--src/server/game/Entities/Player/Player.cpp1
-rw-r--r--src/server/game/Entities/Player/PlayerQuest.cpp6
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp1
-rw-r--r--src/server/game/PrecompiledHeaders/gamePCH.h3
-rw-r--r--src/server/game/Scripting/ScriptMgr.cpp6
-rw-r--r--src/server/game/Scripting/ScriptMgr.h8
-rw-r--r--src/server/game/Server/Packets/PacketUtilities.cpp2
-rw-r--r--src/server/game/Server/Protocol/PacketLog.cpp53
-rw-r--r--src/server/game/Server/Protocol/PacketLog.h5
-rw-r--r--src/server/game/Server/Protocol/ServerPktHeader.h50
-rw-r--r--src/server/game/Server/WorldSession.cpp157
-rw-r--r--src/server/game/Server/WorldSession.h17
-rw-r--r--src/server/game/Server/WorldSocket.cpp1191
-rw-r--r--src/server/game/Server/WorldSocket.h242
-rw-r--r--src/server/game/Server/WorldSocketAcceptor.h53
-rw-r--r--src/server/game/Server/WorldSocketMgr.cpp334
-rw-r--r--src/server/game/Server/WorldSocketMgr.h47
-rw-r--r--src/server/game/World/World.cpp16
-rw-r--r--src/server/scripts/Commands/cs_misc.cpp1
-rw-r--r--src/server/scripts/Commands/cs_server.cpp1
-rw-r--r--src/server/scripts/Commands/cs_spectator.cpp20
-rw-r--r--src/server/shared/Network/AsyncAcceptor.h8
-rw-r--r--src/server/shared/Network/RealmSocket.cpp277
-rw-r--r--src/server/shared/Network/RealmSocket.h67
-rw-r--r--src/server/shared/Realms/Realm.cpp44
-rw-r--r--src/server/shared/Realms/Realm.h11
-rw-r--r--src/server/shared/Realms/RealmList.cpp171
-rw-r--r--src/server/shared/Realms/RealmList.h28
-rw-r--r--src/server/worldserver/CommandLine/CliRunnable.cpp4
-rw-r--r--src/server/worldserver/CommandLine/CliRunnable.h8
-rw-r--r--src/server/worldserver/Main.cpp633
-rw-r--r--src/server/worldserver/Master.cpp436
-rw-r--r--src/server/worldserver/Master.h34
-rw-r--r--src/server/worldserver/WorldThread/WorldRunnable.cpp126
-rw-r--r--src/server/worldserver/WorldThread/WorldRunnable.h29
-rw-r--r--src/server/worldserver/worldserver.conf.dist13
-rw-r--r--src/test/mocks/WorldMock.h1
64 files changed, 3189 insertions, 4659 deletions
diff --git a/src/common/Banner.cpp b/src/common/Banner.cpp
index b2a9517658..6e6db0fcc7 100644
--- a/src/common/Banner.cpp
+++ b/src/common/Banner.cpp
@@ -28,4 +28,6 @@ void Acore::Banner::Show(char const* applicationName, void(*log)(char const* tex
{
logExtraInfo();
}
+
+ log(" ");
}
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 7a2997089b..704cdc8bba 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -52,7 +52,6 @@ target_link_libraries(common
PRIVATE
acore-core-interface
PUBLIC
- ace
boost
argon2
g3dlib
diff --git a/src/common/Common.h b/src/common/Common.h
index cb488bd002..76d4073afc 100644
--- a/src/common/Common.h
+++ b/src/common/Common.h
@@ -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>
diff --git a/src/common/Define.h b/src/common/Define.h
index 73857029c1..1720eb4185 100644
--- a/src/common/Define.h
+++ b/src/common/Define.h
@@ -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
diff --git a/src/common/Platform/ServiceWin32.cpp b/src/common/Platform/ServiceWin32.cpp
index 7a96fccae1..475529a8f5 100644
--- a/src/common/Platform/ServiceWin32.cpp
+++ b/src/common/Platform/ServiceWin32.cpp
@@ -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
diff --git a/src/common/Threading/MPSCQueue.h b/src/common/Threading/MPSCQueue.h
new file mode 100644
index 0000000000..23b92b4a39
--- /dev/null
+++ b/src/common/Threading/MPSCQueue.h
@@ -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__
diff --git a/src/common/Threading/PCQueue.h b/src/common/Threading/PCQueue.h
index 97120888e8..29320b51be 100644
--- a/src/common/Threading/PCQueue.h
+++ b/src/common/Threading/PCQueue.h
@@ -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
diff --git a/src/common/Threading/ProducerConsumerQueue.h b/src/common/Threading/ProducerConsumerQueue.h
deleted file mode 100644
index 4ad29f174a..0000000000
--- a/src/common/Threading/ProducerConsumerQueue.h
+++ /dev/null
@@ -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
diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp
index 3df1616898..5628323251 100644
--- a/src/common/Utilities/Util.cpp
+++ b/src/common/Utilities/Util.cpp
@@ -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
diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h
index 09b9cc11fa..6bb20d6fcc 100644
--- a/src/common/Utilities/Util.h
+++ b/src/common/Utilities/Util.h
@@ -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();
diff --git a/src/server/authserver/Main.cpp b/src/server/authserver/Main.cpp
index 0447337c5f..a9505596d2 100644
--- a/src/server/authserver/Main.cpp
+++ b/src/server/authserver/Main.cpp
@@ -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);
-
- // Initialize the signal handlers
- Acore::SignalHandler signalHandler;
- auto const _handler = [](int) { stopEvent = true; };
+ std::shared_ptr<void> sAuthSocketMgrHandle(nullptr, [](void*) { sAuthSocketMgr.StopNetwork(); });
- // 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));
+ }
+ }
+}
diff --git a/src/server/authserver/PrecompiledHeaders/authPCH.h b/src/server/authserver/PrecompiledHeaders/authPCH.h
index 5fc3b0a341..3c813ae54d 100644
--- a/src/server/authserver/PrecompiledHeaders/authPCH.h
+++ b/src/server/authserver/PrecompiledHeaders/authPCH.h
@@ -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"
diff --git a/src/server/authserver/Server/AuthSession.cpp b/src/server/authserver/Server/AuthSession.cpp
new file mode 100644
index 0000000000..b5c3f66c33
--- /dev/null
+++ b/src/server/authserver/Server/AuthSession.cpp
@@ -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());
+}
diff --git a/src/server/authserver/Server/AuthSession.h b/src/server/authserver/Server/AuthSession.h
new file mode 100644
index 0000000000..216a9c95cb
--- /dev/null
+++ b/src/server/authserver/Server/AuthSession.h
@@ -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
diff --git a/src/server/authserver/Server/AuthSocket.cpp b/src/server/authserver/Server/AuthSocket.cpp
deleted file mode 100644
index baf9cd26e6..0000000000
--- a/src/server/authserver/Server/AuthSocket.cpp
+++ /dev/null
@@ -1,1137 +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 "AuthSocket.h"
-#include "AES.h"
-#include "AuthCodes.h"
-#include "ByteBuffer.h"
-#include "Common.h"
-#include "Config.h"
-#include "CryptoGenerics.h"
-#include "CryptoHash.h"
-#include "CryptoRandom.h"
-#include "DatabaseEnv.h"
-#include "IPLocation.h"
-#include "Log.h"
-#include "RealmList.h"
-#include "SecretMgr.h"
-#include "TOTP.h"
-#include "Threading.h"
-#include "Util.h"
-#include <algorithm>
-#include <openssl/crypto.h>
-#include <openssl/md5.h>
-#include <sstream>
-
-#define ChunkSize 2048
-
-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
-};
-
-// GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push, N), also any gcc version not support it at some paltform
-#if defined(__GNUC__)
-#pragma pack(1)
-#else
-#pragma pack(push, 1)
-#endif
-
-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;
-
-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; // 0x00-0x04
-} sAuthLogonProof_C;
-
-typedef struct AUTH_LOGON_PROOF_S
-{
- uint8 cmd;
- uint8 error;
- Acore::Crypto::SHA1::Digest M2;
- uint32 unk1;
- uint32 unk2;
- uint16 unk3;
-} sAuthLogonProof_S;
-
-typedef struct AUTH_LOGON_PROOF_S_OLD
-{
- uint8 cmd;
- uint8 error;
- Acore::Crypto::SHA1::Digest M2;
- uint32 unk2;
-} sAuthLogonProof_S_Old;
-
-typedef struct AUTH_RECONNECT_PROOF_C
-{
- uint8 cmd;
- uint8 R1[16];
- Acore::Crypto::SHA1::Digest R2, R3;
- uint8 number_of_keys;
-} sAuthReconnectProof_C;
-
-typedef struct XFER_INIT
-{
- uint8 cmd; // XFER_INITIATE
- uint8 fileNameLen; // strlen(fileName);
- uint8 fileName[5]; // fileName[fileNameLen]
- uint64 file_size; // file size (bytes)
- uint8 md5[MD5_DIGEST_LENGTH]; // MD5
-} XFER_INIT;
-
-typedef struct XFER_DATA
-{
- uint8 opcode;
- uint16 data_size;
- uint8 data[ChunkSize];
-} XFER_DATA_STRUCT;
-
-typedef struct AuthHandler
-{
- eAuthCmd cmd;
- uint32 status;
- bool (AuthSocket::*handler)();
-} AuthHandler;
-
-// GCC have alternative #pragma pack() syntax and old gcc version not support pack(pop), also any gcc version not support it at some paltform
-#if defined(__GNUC__)
-#pragma pack()
-#else
-#pragma pack(pop)
-#endif
-
-// Launch a thread to transfer a patch to the client
-class PatcherRunnable: public Acore::Runnable
-{
-public:
- PatcherRunnable(class AuthSocket*);
- void run() override;
-
-private:
- AuthSocket* mySocket;
-};
-
-typedef struct PATCH_INFO
-{
- uint8 md5[MD5_DIGEST_LENGTH];
-} PATCH_INFO;
-
-// Caches MD5 hash of client patches present on the server
-class Patcher
-{
-public:
- typedef std::map<std::string, PATCH_INFO*> Patches;
- ~Patcher();
- Patcher();
- [[nodiscard]] Patches::const_iterator begin() const { return _patches.begin(); }
- [[nodiscard]] Patches::const_iterator end() const { return _patches.end(); }
- void LoadPatchMD5(char*);
- bool GetHash(char* pat, uint8 mymd5[16]);
-
-private:
- void LoadPatchesInfo();
- Patches _patches;
-};
-
-std::array<uint8, 16> VersionChallenge = { { 0xBA, 0xA3, 0x1E, 0x99, 0xA0, 0x0B, 0x21, 0x57, 0xFC, 0x37, 0x3F, 0xB3, 0x69, 0xCD, 0xD2, 0xF1 } };
-
-const AuthHandler table[] =
-{
- { AUTH_LOGON_CHALLENGE, STATUS_CHALLENGE, &AuthSocket::_HandleLogonChallenge },
- { AUTH_LOGON_PROOF, STATUS_LOGON_PROOF, &AuthSocket::_HandleLogonProof },
- { AUTH_RECONNECT_CHALLENGE, STATUS_CHALLENGE, &AuthSocket::_HandleReconnectChallenge },
- { AUTH_RECONNECT_PROOF, STATUS_RECON_PROOF, &AuthSocket::_HandleReconnectProof },
- { REALM_LIST, STATUS_AUTHED, &AuthSocket::_HandleRealmList },
- { XFER_ACCEPT, STATUS_PATCH, &AuthSocket::_HandleXferAccept },
- { XFER_RESUME, STATUS_PATCH, &AuthSocket::_HandleXferResume },
- { XFER_CANCEL, STATUS_PATCH, &AuthSocket::_HandleXferCancel }
-};
-
-#define AUTH_TOTAL_COMMANDS 8
-
-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);
-}
-
-// Holds the MD5 hash of client patches present on the server
-Patcher PatchesCache;
-
-// Constructor - set the N and g values for SRP6
-AuthSocket::AuthSocket(RealmSocket& socket) :
- pPatch(nullptr), socket_(socket), _status(STATUS_CHALLENGE), _build(0),
- _expversion(0)
-{
-}
-
-// Close patch file descriptor before leaving
-AuthSocket::~AuthSocket() = default;
-
-// Accept the connection
-void AuthSocket::OnAccept()
-{
- LOG_INFO("server.authserver", "'%s:%d' Accepting connection", socket().getRemoteAddress().c_str(), socket().getRemotePort());
-}
-
-void AuthSocket::OnClose()
-{
- LOG_DEBUG("server.authserver", "AuthSocket::OnClose");
-}
-
-// Read the packet from the client
-void AuthSocket::OnRead()
-{
-#define MAX_AUTH_LOGON_CHALLENGES_IN_A_ROW 3
- uint32 challengesInARow = 0;
-
-#define MAX_AUTH_GET_REALM_LIST 10
- uint32 challengesInARowRealmList = 0;
-
- uint8 _cmd;
- while (true)
- {
- if (!socket().recv_soft((char*)&_cmd, 1))
- return;
-
- if (_cmd == AUTH_LOGON_CHALLENGE)
- {
- ++challengesInARow;
- if (challengesInARow == MAX_AUTH_LOGON_CHALLENGES_IN_A_ROW)
- {
- LOG_INFO("server.authserver", "Got %u AUTH_LOGON_CHALLENGE in a row from '%s', possible ongoing DoS", challengesInARow, socket().getRemoteAddress().c_str());
- socket().shutdown();
- return;
- }
- }
- else if (_cmd == REALM_LIST)
- {
- challengesInARowRealmList++;
- if (challengesInARowRealmList == MAX_AUTH_GET_REALM_LIST)
- {
- LOG_INFO("server.authserver", "Got %u REALM_LIST in a row from '%s', possible ongoing DoS", challengesInARowRealmList, socket().getRemoteAddress().c_str());
- socket().shutdown();
- return;
- }
- }
-
- size_t i;
-
- // Circle through known commands and call the correct command handler
- for (i = 0; i < AUTH_TOTAL_COMMANDS; ++i)
- {
- if ((uint8)table[i].cmd == _cmd && (table[i].status == _status))
- {
- LOG_DEBUG("server.authserver", "Got data for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len());
-
- if (!(*this.*table[i].handler)())
- {
- LOG_DEBUG("server.authserver", "Command handler failed for cmd %u recv length %u", (uint32)_cmd, (uint32)socket().recv_len());
- return;
- }
- break;
- }
- }
-
- // Report unknown packets in the error log
- if (i == AUTH_TOTAL_COMMANDS)
- {
- LOG_DEBUG("server.authserver", "Got unknown packet from '%s'", socket().getRemoteAddress().c_str());
- socket().shutdown();
- return;
- }
- }
-}
-
-std::map<std::string, uint32> LastLoginAttemptTimeForIP;
-uint32 LastLoginAttemptCleanTime = 0;
-std::mutex LastLoginAttemptMutex;
-
-// Logon Challenge command handler
-bool AuthSocket::_HandleLogonChallenge()
-{
- LOG_DEBUG("server.authserver", "Entering _HandleLogonChallenge");
- if (socket().recv_len() < sizeof(sAuthLogonChallenge_C))
- return false;
-
- ///- Session is closed unless overriden
- _status = STATUS_CLOSED;
-
- // Read the first 4 bytes (header) to get the length of the remaining of the packet
- std::vector<uint8> buf;
- buf.resize(4);
-
- socket().recv((char*)&buf[0], 4);
-
- EndianConvertPtr<uint16>(&buf[0]);
-
- uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size;
- LOG_DEBUG("server.authserver", "[AuthChallenge] got header, body is %#04x bytes", remaining);
-
- if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining))
- return false;
-
- //No big fear of memory outage (size is int16, i.e. < 65536)
- buf.resize(remaining + buf.size() + 1);
- buf[buf.size() - 1] = 0;
- sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0];
-
- // Read the remaining of the packet
- socket().recv((char*)&buf[4], remaining);
- LOG_DEBUG("server.authserver", "[AuthChallenge] got full packet, %#04x bytes", ch->size);
- LOG_DEBUG("server.authserver", "[AuthChallenge] name(%d): '%s'", ch->I_len, ch->I);
-
- // BigEndian code, nop in little endian case
- // size already converted
- EndianConvertPtr<uint32>(&ch->gamename[0]);
- EndianConvert(ch->build);
- EndianConvertPtr<uint32>(&ch->platform[0]);
- EndianConvertPtr<uint32>(&ch->os[0]);
- EndianConvertPtr<uint32>(&ch->country[0]);
- EndianConvert(ch->timezone_bias);
- EndianConvert(ch->ip);
-
- std::string login((char const*)ch->I, ch->I_len);
- LOG_DEBUG("server.authserver", "[AuthChallenge] '%s'", login.c_str());
-
- _build = ch->build;
- _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
- _os = (const char*)ch->os;
-
- if (_os.size() > 4)
- return false;
-
- // 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] = ch->country[4 - i - 1];
-
- ByteBuffer pkt;
- pkt << uint8(AUTH_LOGON_CHALLENGE);
- pkt << uint8(0x00);
-
- auto SendAuthPacket = [&]()
- {
- socket().send((char const*)pkt.contents(), pkt.size());
- };
-
- // Verify that this IP is not in the ip_banned table
- LoginDatabase.Execute(LoginDatabase.GetPreparedStatement(LOGIN_DEL_EXPIRED_IP_BANS));
-
- std::string const& ipAddress = socket().getRemoteAddress();
- uint32 port = socket().getRemotePort();
-
- // Get the account details from the account table
- // No SQL injection (prepared statement)
- auto stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_LOGONCHALLENGE);
- stmt->setString(0, ipAddress);
- stmt->setString(1, login);
-
- PreparedQueryResult res2 = LoginDatabase.Query(stmt);
- if (!res2) //no account
- {
- pkt << uint8(WOW_FAIL_UNKNOWN_ACCOUNT);
- SendAuthPacket();
- return true;
- }
-
- Field* fields = res2->Fetch();
-
- _accountInfo.LoadResult(fields);
-
- // 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)
- {
- LOG_DEBUG("server.authserver", "[AuthChallenge] Account IP differs");
- pkt << uint8(WOW_FAIL_LOCKED_ENFORCED);
- SendAuthPacket();
- return true;
- }
- }
- 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);
- SendAuthPacket();
- return true;
- }
- }
- }
-
- // If the account is banned, reject the logon attempt
- if (_accountInfo.IsBanned)
- {
- if (_accountInfo.IsPermanentlyBanned)
- {
- pkt << uint8(WOW_FAIL_BANNED);
- LOG_DEBUG("server.authserver.banned", "'%s:%d' [AuthChallenge] Banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
- }
- else
- {
- pkt << uint8(WOW_FAIL_SUSPENDED);
- LOG_DEBUG("server.authserver.banned", "'%s:%d' [AuthChallenge] Temporarily banned account %s tried to login!", ipAddress.c_str(), port, _accountInfo.Login.c_str());
- }
-
- SendAuthPacket();
- return true;
- }
-
- uint8 securityFlags = 0;
-
- // Check if a TOTP token is needed
- if (sConfigMgr->GetOption<bool>("EnableTOTP", false) && !fields[11].IsNull())
- {
- LOG_DEBUG("server.authserver", "[AuthChallenge] Account '%s' using TOTP", _accountInfo.Login.c_str());
-
- 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());
- SendAuthPacket();
- return true;
- }
- }
- }
-
- _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_FAIL_VERSION_INVALID);
- SendAuthPacket();
- return true;
- }
-
- pkt << uint8(WOW_SUCCESS);
-
- // B may be calculated < 32B so we force minimal length to 32B
- 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 locale (%u)",
- ipAddress.c_str(), port, _accountInfo.Login.c_str(), GetLocaleByName(_localizationName));
-
- ///- All good, await client's proof
- _status = STATUS_LOGON_PROOF;
-
- SendAuthPacket();
- return true;
-}
-
-// Logon Proof command handler
-bool AuthSocket::_HandleLogonProof()
-{
- LOG_TRACE("server.authserver", "Entering _HandleLogonProof");
-
- // Read the packet
- sAuthLogonProof_C lp;
-
- if (!socket().recv((char*)&lp, sizeof(sAuthLogonProof_C)))
- {
- return false;
- }
-
- _status = STATUS_CLOSED;
-
- // 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("server.authserver", "Client with invalid version, patching is not implemented");
- socket().shutdown();
- return true;
- }
-
- if (std::optional<SessionKey> K = _srp6->VerifyChallengeResponse(lp.A, lp.clientM))
- {
- _sessionKey = *K;
- LOG_DEBUG("server.authserver", "'%s:%d' User '%s' successfully authenticated", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str());
-
- // Check auth token
- bool tokenSuccess = false;
- bool sentToken = (lp.securityFlags & 0x04);
-
- if (sentToken && _totpSecret)
- {
- uint8 size;
- socket().recv((char*)&size, 1);
- char* token = new char[size + 1];
- token[size] = '\0';
- socket().recv(token, size);
- unsigned int incomingToken = atoi(token);
- delete[] token;
-
- tokenSuccess = Acore::Crypto::TOTP::ValidateToken(*_totpSecret, incomingToken);
- memset(_totpSecret->data(), 0, _totpSecret->size());
- }
- else if (!sentToken && !_totpSecret)
- tokenSuccess = true;
-
- if (!tokenSuccess)
- {
- LOG_DEBUG("server.authsrver", "[AuthChallenge] account %s failed token", _accountInfo.Login.c_str());
- char data[4] = { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0 };
- socket().send(data, sizeof(data));
- }
-
- LOG_DEBUG("network", "'%s:%d' User '%s' successfully authenticated", socket().getRemoteAddress().c_str(), socket().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, socket().getRemoteAddress().c_str());
- 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(lp.A, lp.clientM, _sessionKey);
-
- 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.unk1 = 0x00800000; // Accountflags. 0x01 = GM, 0x08 = Trial, 0x00800000 = Pro pass (arena tournament)
- proof.unk2 = 0x00; // SurveyId
- proof.unk3 = 0x00; // 0x1 = has account message
- socket().send((char*)&proof, sizeof(proof));
- }
- else
- {
- sAuthLogonProof_S_Old proof;
- proof.M2 = M2;
- proof.cmd = AUTH_LOGON_PROOF;
- proof.error = 0;
- proof.unk2 = 0x00;
- socket().send((char*)&proof, sizeof(proof));
- }
-
- ///- Set _status to authed!
- _status = STATUS_AUTHED;
- }
- else
- {
- char data[4] = { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0 };
- socket().send(data, sizeof(data));
-
- LOG_INFO("server.authserver.hack", "'%s:%d' [AuthChallenge] account %s tried to login with invalid password!",
- socket().getRemoteAddress().c_str(), socket().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->setString(0, _accountInfo.Login);
- logstmt->setString(1, socket().getRemoteAddress());
- logstmt->setString(2, "Logged on failed AccountLogin due wrong 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("network", "'%s:%d' [AuthChallenge] account %s got banned for '%u' seconds because it failed to authenticate '%u' times",
- socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str(), WrongPassBanTime, _accountInfo.FailedLogins);
- }
- else
- {
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_IP_AUTO_BANNED);
- stmt->setString(0, socket().getRemoteAddress());
- stmt->setUInt32(1, WrongPassBanTime);
- LoginDatabase.Execute(stmt);
-
- LOG_DEBUG("network", "'%s:%d' [AuthChallenge] IP %s got banned for '%u' seconds because account %s failed to authenticate '%u' times",
- socket().getRemoteAddress().c_str(), socket().getRemotePort(), socket().getRemoteAddress().c_str(), WrongPassBanTime, _accountInfo.Login.c_str(), _accountInfo.FailedLogins);
- }
- }
- }
- }
-
- return true;
-}
-
-// Reconnect Challenge command handler
-bool AuthSocket::_HandleReconnectChallenge()
-{
- LOG_TRACE("server.authserver", "Entering _HandleReconnectChallenge");
-
- if (socket().recv_len() < sizeof(sAuthLogonChallenge_C))
- return false;
-
- // Read the first 4 bytes (header) to get the length of the remaining of the packet
- std::vector<uint8> buf;
- buf.resize(4);
-
- socket().recv((char*)&buf[0], 4);
-
- EndianConvertPtr<uint16>(&buf[0]);
-
- uint16 remaining = ((sAuthLogonChallenge_C*)&buf[0])->size;
- LOG_DEBUG("server.authserver", "[ReconnectChallenge] got header, body is %#04x bytes", remaining);
-
- if ((remaining < sizeof(sAuthLogonChallenge_C) - buf.size()) || (socket().recv_len() < remaining))
- return false;
-
- ///- Session is closed unless overriden
- _status = STATUS_CLOSED;
-
- // No big fear of memory outage (size is int16, i.e. < 65536)
- buf.resize(remaining + buf.size() + 1);
- buf[buf.size() - 1] = 0;
- sAuthLogonChallenge_C* ch = (sAuthLogonChallenge_C*)&buf[0];
-
- // Read the remaining of the packet
- socket().recv((char*)&buf[4], remaining);
- LOG_DEBUG("server.authserver", "[ReconnectChallenge] got full packet, %#04x bytes", ch->size);
- LOG_DEBUG("server.authserver", "[ReconnectChallenge] name(%d): '%s'", ch->I_len, ch->I);
-
- std::string login((char const*)ch->I, ch->I_len);
- LOG_DEBUG("server.authserver", "[ReconnectChallenge] '%s'", login.c_str());
-
- // Reinitialize build, expansion and the account securitylevel
- _build = ch->build;
- _expversion = uint8(AuthHelper::IsPostBCAcceptedClientBuild(_build) ? POST_BC_EXP_FLAG : (AuthHelper::IsPreBCAcceptedClientBuild(_build) ? PRE_BC_EXP_FLAG : NO_VALID_EXP_FLAG));
- _os = (const char*)ch->os;
-
- if (_os.size() > 4)
- return false;
-
- auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_RECONNECTCHALLENGE);
- stmt->setString(0, socket().getRemoteAddress());
- stmt->setString(1, login);
- PreparedQueryResult result = LoginDatabase.Query(stmt);
-
- // Stop if the account is not found
- if (!result)
- {
- LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login and we cannot find his session key in the database.", socket().getRemoteAddress().c_str(), socket().getRemotePort(), login.c_str());
- socket().shutdown();
- return false;
- }
-
- Field* fields = result->Fetch();
- _accountInfo.LoadResult(fields);
-
- // Restore string order as its byte order is reversed
- std::reverse(_os.begin(), _os.end());
-
- _sessionKey = fields[11].GetBinary<SESSION_KEY_LENGTH>();
- Acore::Crypto::GetRandomBytes(_reconnectProof);
-
- ///- All good, await client's proof
- _status = STATUS_RECON_PROOF;
-
- // Sending response
- ByteBuffer pkt;
- pkt << uint8(AUTH_RECONNECT_CHALLENGE);
- pkt << uint8(WOW_SUCCESS);
- pkt.append(_reconnectProof); // 16 bytes random
- pkt.append(VersionChallenge.data(), VersionChallenge.size());
- socket().send((char const*)pkt.contents(), pkt.size());
- return true;
-}
-
-// Reconnect Proof command handler
-bool AuthSocket::_HandleReconnectProof()
-{
- LOG_TRACE("server.authserver", "Entering _HandleReconnectProof");
- // Read the packet
- sAuthReconnectProof_C lp;
- if (!socket().recv((char*)&lp, sizeof(sAuthReconnectProof_C)))
- return false;
-
- _status = STATUS_CLOSED;
-
- if (_accountInfo.Login.empty())
- return false;
-
- BigNumber t1;
- t1.SetBinary(lp.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() == lp.R2)
- {
- // Sending response
- ByteBuffer pkt;
- pkt << uint8(AUTH_RECONNECT_PROOF);
- pkt << uint8(0x00);
- pkt << uint16(0x00); // 2 bytes zeros
- socket().send((char const*)pkt.contents(), pkt.size());
-
- ///- Set _status to authed!
- _status = STATUS_AUTHED;
-
- return true;
- }
- else
- {
- LOG_ERROR("server.authserver.hack", "'%s:%d' [ERROR] user %s tried to login, but session is invalid.",
- socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str());
- socket().shutdown();
- return false;
- }
-}
-
-ACE_INET_Addr const& AuthSocket::GetAddressForClient(Realm const& realm, ACE_INET_Addr const& clientAddr)
-{
- // Attempt to send best address for client
- if (clientAddr.is_loopback())
- {
- // Try guessing if realm is also connected locally
- if (realm.LocalAddress->is_loopback() || realm.ExternalAddress->is_loopback())
- return clientAddr;
-
- // Assume that user connecting from the machine that authserver is located on
- // has all realms available in his local network
- return *realm.LocalAddress;
- }
-
- // Check if connecting client is in the same network
- if (IsIPAddrInNetwork(*realm.LocalAddress, clientAddr, *realm.LocalSubnetMask))
- {
- return *realm.LocalAddress;
- }
-
- // Return external IP
- return *realm.ExternalAddress;
-}
-
-// Realm List command handler
-bool AuthSocket::_HandleRealmList()
-{
- LOG_TRACE("server.authserver", "Entering _HandleRealmList");
- if (socket().recv_len() < 5)
- return false;
-
- socket().recv_skip(5);
-
- // Get the user id (else close the connection)
- // No SQL injection (prepared statement)
- LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ID_BY_NAME);
- stmt->setString(0, _accountInfo.Login);
-
- PreparedQueryResult result = LoginDatabase.Query(stmt);
- if (!result)
- {
- LOG_ERROR("server.authserver", "'%s:%d' [ERROR] user %s tried to login but we cannot find him in the database.", socket().getRemoteAddress().c_str(), socket().getRemotePort(), _accountInfo.Login.c_str());
- socket().shutdown();
- return false;
- }
-
- Field* fields = result->Fetch();
- uint32 id = fields[0].GetUInt32();
-
- // Update realm list if need
- sRealmList->UpdateIfNeed();
-
- ACE_INET_Addr clientAddr;
- socket().peer().get_remote_addr(clientAddr);
-
- // 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& [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();
- }
-
- // We don't need the port number from which client connects with but the realm's port
- clientAddr.set_port_number(realm.ExternalAddress->get_port_number());
-
- uint8 lock = (realm.AllowedSecurityLevel > _accountInfo.SecurityLevel) ? 1 : 0;
-
- uint8 AmountOfCharacters = 0;
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_NUM_CHARS_ON_REALM);
- stmt->setUInt32(0, realm.Id.Realm);
- stmt->setUInt32(1, id);
- result = LoginDatabase.Query(stmt);
- if (result)
- AmountOfCharacters = (*result)[0].GetUInt8();
-
- pkt << realm.Type; // realm type
- if (_expversion & POST_BC_EXP_FLAG) // only 2.x and 3.x clients
- pkt << lock; // if 1, then realm locked
- pkt << uint8(flag); // RealmFlags
- pkt << name;
- pkt << GetAddressString(GetAddressForClient(realm, clientAddr));
- pkt << realm.PopulationLevel;
- pkt << AmountOfCharacters;
- pkt << 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
-
- socket().send((char const*)hdr.contents(), hdr.size());
-
- return true;
-}
-
-// Resume patch transfer
-bool AuthSocket::_HandleXferResume()
-{
- LOG_TRACE("server.authserver", "Entering _HandleXferResume");
- // Check packet length and patch existence
- if (socket().recv_len() < 9 || !pPatch) // FIXME: pPatch is never used
- {
- LOG_ERROR("server.authserver", "Error while resuming patch transfer (wrong packet)");
- return false;
- }
-
- // Launch a PatcherRunnable thread starting at given patch file offset
- uint64 start;
- socket().recv_skip(1);
- socket().recv((char*)&start, sizeof(start));
- fseek(pPatch, long(start), 0);
-
- Acore::Thread u(new PatcherRunnable(this));
- return true;
-}
-
-// Cancel patch transfer
-bool AuthSocket::_HandleXferCancel()
-{
- LOG_DEBUG("server.authserver", "Entering _HandleXferCancel");
-
- // Close and delete the socket
- socket().recv_skip(1); //clear input buffer
- socket().shutdown();
-
- return true;
-}
-
-// Accept patch transfer
-bool AuthSocket::_HandleXferAccept()
-{
- LOG_DEBUG("server.authserver", "Entering _HandleXferAccept");
-
- // Check packet length and patch existence
- if (!pPatch)
- {
- LOG_ERROR("server.authserver", "Error while accepting patch transfer (wrong packet)");
- return false;
- }
-
- // Launch a PatcherRunnable thread, starting at the beginning of the patch file
- socket().recv_skip(1); // clear input buffer
- fseek(pPatch, 0, 0);
-
- Acore::Thread u(new PatcherRunnable(this));
- return true;
-}
-
-PatcherRunnable::PatcherRunnable(class AuthSocket* as)
-{
- mySocket = as;
-}
-
-// Send content of patch file to the client
-void PatcherRunnable::run() { }
-
-// Preload MD5 hashes of existing patch files on server
-#ifndef _WIN32
-#include <cerrno>
-#include <dirent.h>
-void Patcher::LoadPatchesInfo()
-{
- DIR* dirp;
- struct dirent* dp;
- dirp = opendir("./patches/");
-
- if (!dirp)
- return;
-
- while (dirp)
- {
- errno = 0;
- if ((dp = readdir(dirp)) != nullptr)
- {
- int l = strlen(dp->d_name);
-
- if (l < 8)
- continue;
-
- if (!memcmp(&dp->d_name[l - 4], ".mpq", 4))
- LoadPatchMD5(dp->d_name);
- }
- else
- {
- if (errno != 0)
- {
- closedir(dirp);
- return;
- }
- break;
- }
- }
-
- if (dirp)
- closedir(dirp);
-}
-#else
-void Patcher::LoadPatchesInfo()
-{
- WIN32_FIND_DATA fil;
- HANDLE hFil = FindFirstFile("./patches/*.mpq", &fil);
- if (hFil == INVALID_HANDLE_VALUE)
- return; // no patches were found
-
- do
- LoadPatchMD5(fil.cFileName);
- while (FindNextFile(hFil, &fil));
-}
-#endif
-
-// Calculate and store MD5 hash for a given patch file
-void Patcher::LoadPatchMD5(char* szFileName)
-{
- // Try to open the patch file
- std::string path = "./patches/";
- path += szFileName;
- FILE* pPatch = fopen(path.c_str(), "rb");
- LOG_DEBUG("server.authserver", "Loading patch info from %s\n", path.c_str());
-
- if (!pPatch)
- {
- LOG_ERROR("server.authserver", "Error loading patch %s\n", path.c_str());
- return;
- }
-
- // Calculate the MD5 hash
- MD5_CTX ctx;
- MD5_Init(&ctx);
- uint8* buf = new uint8[512 * 1024];
-
- while (!feof(pPatch))
- {
- size_t read = fread(buf, 1, 512 * 1024, pPatch);
- MD5_Update(&ctx, buf, read);
- }
-
- delete [] buf;
- fclose(pPatch);
-
- // Store the result in the internal patch hash map
- _patches[path] = new PATCH_INFO;
- MD5_Final((uint8*)&_patches[path]->md5, &ctx);
-}
-
-// Get cached MD5 hash for a given patch file
-bool Patcher::GetHash(char* pat, uint8 mymd5[16])
-{
- for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i)
- if (!stricmp(pat, i->first.c_str()))
- {
- memcpy(mymd5, i->second->md5, 16);
- return true;
- }
-
- return false;
-}
-
-// Launch the patch hashing mechanism on object creation
-Patcher::Patcher()
-{
- LoadPatchesInfo();
-}
-
-// Empty and delete the patch map on termination
-Patcher::~Patcher()
-{
- for (Patches::iterator i = _patches.begin(); i != _patches.end(); ++i)
- delete i->second;
-}
diff --git a/src/server/authserver/Server/AuthSocket.h b/src/server/authserver/Server/AuthSocket.h
deleted file mode 100644
index 5b8e91bbf5..0000000000
--- a/src/server/authserver/Server/AuthSocket.h
+++ /dev/null
@@ -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
diff --git a/src/server/authserver/Server/AuthSocketMgr.h b/src/server/authserver/Server/AuthSocketMgr.h
new file mode 100644
index 0000000000..5fed06c880
--- /dev/null
+++ b/src/server/authserver/Server/AuthSocketMgr.h
@@ -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__
diff --git a/src/server/authserver/Server/RealmAcceptor.h b/src/server/authserver/Server/RealmAcceptor.h
deleted file mode 100644
index f11c3b81fb..0000000000
--- a/src/server/authserver/Server/RealmAcceptor.h
+++ /dev/null
@@ -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
diff --git a/src/server/authserver/authserver.conf.dist b/src/server/authserver/authserver.conf.dist
index c16aa95b97..b7d339ff9f 100644
--- a/src/server/authserver/authserver.conf.dist
+++ b/src/server/authserver/authserver.conf.dist
@@ -168,6 +168,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
+
+#
###################################################################################################
###################################################################################################
diff --git a/src/server/database/Database/Callback.h b/src/server/database/Database/Callback.h
deleted file mode 100644
index 32da3adc4b..0000000000
--- a/src/server/database/Database/Callback.h
+++ /dev/null
@@ -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
diff --git a/src/server/database/Database/DatabaseWorker.cpp b/src/server/database/Database/DatabaseWorker.cpp
index e1d2926627..4e1bebb684 100644
--- a/src/server/database/Database/DatabaseWorker.cpp
+++ b/src/server/database/Database/DatabaseWorker.cpp
@@ -4,7 +4,7 @@
*/
#include "DatabaseWorker.h"
-#include "ProducerConsumerQueue.h"
+#include "PCQueue.h"
#include "SQLOperation.h"
DatabaseWorker::DatabaseWorker(ProducerConsumerQueue<SQLOperation*>* newQueue, MySQLConnection* connection)
diff --git a/src/server/database/Database/DatabaseWorkerPool.cpp b/src/server/database/Database/DatabaseWorkerPool.cpp
index 5c22bc26f2..08f2178f87 100644
--- a/src/server/database/Database/DatabaseWorkerPool.cpp
+++ b/src/server/database/Database/DatabaseWorkerPool.cpp
@@ -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"
diff --git a/src/server/database/Database/Implementation/LoginDatabase.cpp b/src/server/database/Database/Implementation/LoginDatabase.cpp
index c369aaa79b..62e8c09f71 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.cpp
+++ b/src/server/database/Database/Implementation/LoginDatabase.cpp
@@ -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);
diff --git a/src/server/database/Database/Implementation/LoginDatabase.h b/src/server/database/Database/Implementation/LoginDatabase.h
index 312440d6fd..9ea01c0b87 100644
--- a/src/server/database/Database/Implementation/LoginDatabase.h
+++ b/src/server/database/Database/Implementation/LoginDatabase.h
@@ -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,
diff --git a/src/server/game/ArenaSpectator/ArenaSpectator.cpp b/src/server/game/ArenaSpectator/ArenaSpectator.cpp
index 04fcf7b77b..dd8bf4e004 100644
--- a/src/server/game/ArenaSpectator/ArenaSpectator.cpp
+++ b/src/server/game/ArenaSpectator/ArenaSpectator.cpp
@@ -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())
- {
- 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;
- }
+ handler->SendSysMessage("Missing player name.");
+ 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)
+ if (player->IsSpectator())
+ {
+ if (player->FindMap() && player->FindMap()->IsBattleArena())
{
- handler->SendSysMessage("This arena battle has finished.");
+ 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->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)
+ 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)
+ {
+ 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);
- }
+ errors.push_back("Can't have negative auras.");
+ break;
+ }
- bool bgPreparation = false;
- if ((!handler->GetSession()->GetSecurity() && bgmap->GetBG()->GetStatus() != STATUS_IN_PROGRESS) ||
+ 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());
- }
+ {
+ 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())
+ 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())
{
- 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());
+ u->RemoveAurasByType(SPELL_AURA_BIND_SIGHT, player->GetGUID());
+ player->RemoveAurasDueToSpell(SPECTATOR_SPELL_BINDSIGHT, player->GetGUID(), (1 << EFFECT_1));
- return true;
+ if (u->GetGUID() == spectate->GetGUID())
+ 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);
+ if (player->GetUInt64Value(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint!
return true;
- }
- bool HandleSpectatorWatchCommand(ChatHandler* handler, char const* args)
- {
- if (!*args)
- return true;
+ if (player->HaveAtClient(spectate))
+ player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true);
- Player* player = handler->GetSession()->GetPlayer();
- if (!player->IsSpectator())
- return true;
+ return true;
+}
- if (!player->FindMap() || !player->FindMap()->IsBattleArena())
- 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);
+}
- Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG();
- if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
- return true;
+void ArenaSpectator::HandleResetCommand(Player* player)
+{
+ if (!player->FindMap() || !player->IsInWorld() || !player->FindMap()->IsBattleArena())
+ return;
- 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;
+ Battleground* bg = ((BattlegroundMap*)player->FindMap())->GetBG();
+ if (!bg || bg->GetStatus() != STATUS_IN_PROGRESS)
+ return;
- 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));
+ Battleground::BattlegroundPlayerMap const& pl = bg->GetPlayers();
+ for (Battleground::BattlegroundPlayerMap::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
+ {
+ if (player->HasReceivedSpectatorResetFor(itr->first))
+ continue;
- if (u->GetGUID() == spectate->GetGUID())
- return true;
- }
+ Player* plr = itr->second;
+ player->AddReceivedSpectatorResetFor(itr->first);
- if (player->GetGuidValue(PLAYER_FARSIGHT) || player->m_seer != player) // pussywizard: below this point we must not have a viewpoint!
- return true;
+ 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);
+ }
+ }
+}
- if (player->HaveAtClient(spectate))
- player->CastSpell(spectate, SPECTATOR_SPELL_BINDSIGHT, true);
+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);
+} \ No newline at end of file
diff --git a/src/server/game/ArenaSpectator/ArenaSpectator.h b/src/server/game/ArenaSpectator/ArenaSpectator.h
index 433e7af63a..f87adc54fa 100644
--- a/src/server/game/ArenaSpectator/ArenaSpectator.h
+++ b/src/server/game/ArenaSpectator/ArenaSpectator.h
@@ -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);
- }
-
- void CreatePacket(WorldPacket& data, const char* m)
- {
- 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);
- }
+ AC_GAME_API void SendPacketTo(const T* object, std::string&& message);
- void SendPacketTo(const Map* map, const char* m)
+ template<class T, typename Format, typename... Args>
+ inline void SendCommand(T* o, Format&& fmt, Args&& ... args)
{
- 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());
- }
- 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);
- }
- }
+ 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());
}
- 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
diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp
index fb5b4dbbfc..047f65034d 100644
--- a/src/server/game/Entities/Pet/Pet.cpp
+++ b/src/server/game/Entities/Pet/Pet.cpp
@@ -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"
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index d50b8fbfb1..2d33da0414 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -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"
diff --git a/src/server/game/Entities/Player/PlayerQuest.cpp b/src/server/game/Entities/Player/PlayerQuest.cpp
index be3f753b35..cf1cf3664a 100644
--- a/src/server/game/Entities/Player/PlayerQuest.cpp
+++ b/src/server/game/Entities/Player/PlayerQuest.cpp
@@ -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);
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index bc44bb30be..bb7c61bfab 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -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();
}
diff --git a/src/server/game/PrecompiledHeaders/gamePCH.h b/src/server/game/PrecompiledHeaders/gamePCH.h
index d3eda38b6c..047a26b264 100644
--- a/src/server/game/PrecompiledHeaders/gamePCH.h
+++ b/src/server/game/PrecompiledHeaders/gamePCH.h
@@ -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"
diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp
index b2bed632da..c46982de3d 100644
--- a/src/server/game/Scripting/ScriptMgr.cpp
+++ b/src/server/game/Scripting/ScriptMgr.cpp
@@ -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)
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index a744228e44..fc1033f8ae 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -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);
diff --git a/src/server/game/Server/Packets/PacketUtilities.cpp b/src/server/game/Server/Packets/PacketUtilities.cpp
index e73ab73806..d11b96a44f 100644
--- a/src/server/game/Server/Packets/PacketUtilities.cpp
+++ b/src/server/game/Server/Packets/PacketUtilities.cpp
@@ -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;
diff --git a/src/server/game/Server/Protocol/PacketLog.cpp b/src/server/game/Server/Protocol/PacketLog.cpp
index 7af5fa09e5..073b314278 100644
--- a/src/server/game/Server/Protocol/PacketLog.cpp
+++ b/src/server/game/Server/Protocol/PacketLog.cpp
@@ -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);
diff --git a/src/server/game/Server/Protocol/PacketLog.h b/src/server/game/Server/Protocol/PacketLog.h
index 5d2bcbaf09..f86cd3ecc7 100644
--- a/src/server/game/Server/Protocol/PacketLog.h
+++ b/src/server/game/Server/Protocol/PacketLog.h
@@ -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;
diff --git a/src/server/game/Server/Protocol/ServerPktHeader.h b/src/server/game/Server/Protocol/ServerPktHeader.h
new file mode 100644
index 0000000000..ebbc777d11
--- /dev/null
+++ b/src/server/game/Server/Protocol/ServerPktHeader.h
@@ -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
diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp
index 01a6e1ed26..2d3837bbde 100644
--- a/src/server/game/Server/WorldSession.cpp
+++ b/src/server/game/Server/WorldSession.cpp
@@ -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)
{
+ ///- 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 (updater.ProcessUnsafe())
- {
UpdateTimeOutTime(diff);
- /// 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");
- }
-
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,30 +405,47 @@ 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;
+ _recvQueue.readd(requeuePackets.begin(), requeuePackets.end());
+
+ if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
+ {
+ // Send time sync packet every 10s.
+ if (_timeSyncTimer > 0)
+ {
+ if (diff >= _timeSyncTimer)
+ {
+ SendTimeSync();
+ }
+ else
+ {
+ _timeSyncTimer -= diff;
+ }
+ }
}
- if (m_Socket && !m_Socket->IsClosed())
- ProcessQueryCallbacks();
+ 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->IsClosed() && _warden)
+ if (m_Socket && m_Socket->IsOpen() && _warden)
{
_warden->Update(diff);
}
- time_t currTime = time(nullptr);
- if (ShouldLogOut(currTime) && !m_playerLoading)
+ if (ShouldLogOut(currentTime) && !m_playerLoading)
{
LogoutPlayer(true);
}
- if (m_Socket && m_Socket->IsClosed())
+ if (m_Socket && !m_Socket->IsOpen())
{
- m_Socket->RemoveReference();
+ if (GetPlayer() && _warden)
+ _warden->Update(diff);
+
m_Socket = nullptr;
}
@@ -445,45 +455,30 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
}
}
- if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
- {
- // Send time sync packet every 10s.
- if (_timeSyncTimer > 0)
- {
- if (diff >= _timeSyncTimer)
- {
- SendTimeSync();
- }
- else
- {
- _timeSyncTimer -= diff;
- }
- }
- }
-
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;
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 49664d75db..9f605c6e1f 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -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
/// @}
diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp
index 0dee40c2ba..032f95e13e 100644
--- a/src/server/game/Server/WorldSocket.cpp
+++ b/src/server/game/Server/WorldSocket.cpp
@@ -4,914 +4,511 @@
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*/
+#include "WorldSocket.h"
#include "AccountMgr.h"
-#include "ByteBuffer.h"
-#include "Common.h"
+#include "BigNumber.h"
#include "CryptoHash.h"
#include "CryptoRandom.h"
#include "DatabaseEnv.h"
#include "IPLocation.h"
-#include "Log.h"
#include "Opcodes.h"
#include "PacketLog.h"
-#include "Player.h"
+#include "Random.h"
#include "Realm.h"
#include "ScriptMgr.h"
-#include "SharedDefines.h"
-#include "Util.h"
#include "World.h"
-#include "WorldPacket.h"
#include "WorldSession.h"
-#include "WorldSocket.h"
-#include "WorldSocketMgr.h"
-#include <ace/Message_Block.h>
-#include <ace/OS_NS_string.h>
-#include <ace/OS_NS_unistd.h>
-#include <ace/Reactor.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/os_include/sys/os_types.h>
-#include <thread>
-
-#ifdef ELUNA
-#include "LuaEngine.h"
-#endif
-
-#if defined(__GNUC__)
-#pragma pack(1)
-#else
-#pragma pack(push, 1)
-#endif
-
-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];
-};
-
-struct ClientPktHeader
-{
- uint16 size;
- uint32 cmd;
-};
-
-#if defined(__GNUC__)
-#pragma pack()
-#else
-#pragma pack(pop)
-#endif
-
-struct AccountInfo
-{
- uint32 Id;
- ::SessionKey SessionKey;
- std::string LastIP;
- bool IsLockedToIP;
- std::string LockCountry;
- uint8 Expansion;
- int64 MuteTime;
- LocaleConstant Locale;
- uint32 Recruiter;
- std::string OS;
- bool IsRectuiter;
- AccountTypes Security;
- bool IsBanned;
- uint32 TotalTime;
-
- explicit AccountInfo(Field* fields)
- {
- // 0 1 2 3 4 5 6 7 8 9 10 11
- // SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, aa.gmLevel,
- // 12 13
- // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id
- // FROM account a
- // LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?)
- // LEFT JOIN account_banned ab ON a.id = ab.id
- // LEFT JOIN account r ON a.id = r.recruiter
- // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1
- Id = fields[0].GetUInt32();
- SessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>();
- LastIP = fields[2].GetString();
- IsLockedToIP = fields[3].GetBool();
- LockCountry = fields[4].GetString();
- Expansion = fields[5].GetUInt8();
- MuteTime = fields[6].GetInt64();
- Locale = LocaleConstant(fields[7].GetUInt8());
- Recruiter = fields[8].GetUInt32();
- OS = fields[9].GetString();
- TotalTime = fields[10].GetUInt32();
- Security = AccountTypes(fields[11].GetUInt8());
- IsBanned = fields[12].GetUInt64() != 0;
- IsRectuiter = fields[13].GetUInt32() != 0;
-
- uint32 world_expansion = sWorld->getIntConfig(CONFIG_EXPANSION);
- if (Expansion > world_expansion)
- Expansion = world_expansion;
-
- if (Locale >= TOTAL_LOCALES)
- Locale = LOCALE_enUS;
- }
-};
-
-WorldSocket::WorldSocket(void): WorldHandler(),
- m_LastPingTime(SystemTimePoint::min()), m_OverSpeedPings(0), m_Session(0),
- m_RecvWPct(0), m_RecvPct(), m_Header(sizeof (ClientPktHeader)),
- m_OutBuffer(0), m_OutBufferSize(65536), m_OutActive(false)
-{
- Acore::Crypto::GetRandomBytes(m_Seed);
-
- reference_counting_policy().value (ACE_Event_Handler::Reference_Counting_Policy::ENABLED);
+#include <memory>
- msg_queue()->high_water_mark(8 * 1024 * 1024);
- msg_queue()->low_water_mark(8 * 1024 * 1024);
-}
+using boost::asio::ip::tcp;
-WorldSocket::~WorldSocket(void)
+WorldSocket::WorldSocket(tcp::socket&& socket)
+ : Socket(std::move(socket)), _OverSpeedPings(0), _worldSession(nullptr), _authed(false), _sendBufferSize(4096)
{
- delete m_RecvWPct;
-
- if (m_OutBuffer)
- m_OutBuffer->release();
-
- closing_ = true;
-
- peer().close();
+ Acore::Crypto::GetRandomBytes(_authSeed);
+ _headerBuffer.Resize(sizeof(ClientPktHeader));
}
-bool WorldSocket::IsClosed(void) const
-{
- return closing_;
-}
+WorldSocket::~WorldSocket() = default;
-void WorldSocket::CloseSocket(std::string const& reason)
+void WorldSocket::Start()
{
- if (!reason.empty())
- LOG_DEBUG("network", "Socket closed because of: %s", reason.c_str());
+ std::string ip_address = GetRemoteIpAddress().to_string();
- {
- std::lock_guard<std::mutex> guard(m_OutBufferLock);
-
- if (closing_)
- return;
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_IP_INFO);
+ stmt->setString(0, ip_address);
- closing_ = true;
- peer().close_writer();
- }
-
- {
- std::lock_guard<std::mutex> guard(m_SessionLock);
-
- m_Session = nullptr;
- }
-}
-
-const std::string& WorldSocket::GetRemoteAddress(void) const
-{
- return m_Address;
+ _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSocket::CheckIpCallback, this, std::placeholders::_1)));
}
-int WorldSocket::SendPacket(WorldPacket const& pct)
+void WorldSocket::CheckIpCallback(PreparedQueryResult result)
{
- std::lock_guard<std::mutex> guard(m_OutBufferLock);
-
- if (closing_)
- return -1;
-
- // Dump outgoing packet.
- if (sPacketLog->CanLogPacket())
- sPacketLog->LogPacket(pct, SERVER_TO_CLIENT);
-
- ServerPktHeader header(pct.size() + 2, pct.GetOpcode());
-
- if (m_Crypt.IsInitialized())
- m_Crypt.EncryptSend((uint8*)header.header, header.getHeaderLength());
-
- if (m_OutBuffer->space() >= pct.size() + header.getHeaderLength() && msg_queue()->is_empty())
+ if (result)
{
- // Put the packet on the buffer.
- if (m_OutBuffer->copy((char*) header.header, header.getHeaderLength()) == -1)
- ABORT();
-
- if (!pct.empty())
- if (m_OutBuffer->copy((char*) pct.contents(), pct.size()) == -1)
- ABORT();
- }
- else
- {
- // Enqueue the packet.
- ACE_Message_Block* mb;
-
- ACE_NEW_RETURN(mb, ACE_Message_Block(pct.size() + header.getHeaderLength()), -1);
-
- mb->copy((char*) header.header, header.getHeaderLength());
+ bool banned = false;
+ do
+ {
+ Field* fields = result->Fetch();
+ if (fields[0].GetUInt64() != 0)
+ banned = true;
- if (!pct.empty())
- mb->copy((const char*)pct.contents(), pct.size());
+ } while (result->NextRow());
- if (msg_queue()->enqueue_tail(mb, (ACE_Time_Value*)&ACE_Time_Value::zero) == -1)
+ if (banned)
{
- LOG_ERROR("network", "WorldSocket::SendPacket enqueue_tail failed");
- mb->release();
- return -1;
+ SendAuthResponseError(AUTH_REJECT);
+ LOG_ERROR("network", "WorldSocket::CheckIpCallback: Sent Auth Response (IP %s banned).", GetRemoteIpAddress().to_string().c_str());
+ DelayedCloseSocket();
+ return;
}
}
- return 0;
-}
-
-long WorldSocket::AddReference(void)
-{
- return static_cast<long> (add_reference());
+ AsyncRead();
+ HandleSendAuthSession();
}
-long WorldSocket::RemoveReference(void)
+bool WorldSocket::Update()
{
- return static_cast<long> (remove_reference());
-}
-
-int WorldSocket::open(void* a)
-{
- ACE_UNUSED_ARG (a);
-
- // Prevent double call to this func.
- if (m_OutBuffer)
- return -1;
-
- // This will also prevent the socket from being Updated
- // while we are initializing it.
- m_OutActive = true;
-
- // Hook for the manager.
- if (sWorldSocketMgr->OnSocketOpen(this) == -1)
- return -1;
-
- // Allocate the buffer.
- ACE_NEW_RETURN (m_OutBuffer, ACE_Message_Block (m_OutBufferSize), -1);
-
- // Store peer address.
- ACE_INET_Addr remote_addr;
-
- if (peer().get_remote_addr(remote_addr) == -1)
+ EncryptablePacket* queued;
+ MessageBuffer buffer(_sendBufferSize);
+ while (_bufferQueue.Dequeue(queued))
{
- LOG_ERROR("network", "WorldSocket::open: peer().get_remote_addr errno = %s", ACE_OS::strerror (errno));
- return -1;
- }
+ ServerPktHeader header(queued->size() + 2, queued->GetOpcode());
+ if (queued->NeedsEncryption())
+ _authCrypt.EncryptSend(header.header, header.getHeaderLength());
- m_Address = remote_addr.get_host_addr();
+ if (buffer.GetRemainingSpace() < queued->size() + header.getHeaderLength())
+ {
+ QueuePacket(std::move(buffer));
+ buffer.Resize(_sendBufferSize);
+ }
- // Send startup packet.
- WorldPacket packet (SMSG_AUTH_CHALLENGE, 24);
- packet << uint32(1); // 1...31
- packet.append(m_Seed);
- packet.append(Acore::Crypto::GetRandomBytes<32>()); // new encryption seeds
+ if (buffer.GetRemainingSpace() >= queued->size() + header.getHeaderLength())
+ {
+ buffer.Write(header.header, header.getHeaderLength());
+ if (!queued->empty())
+ buffer.Write(queued->contents(), queued->size());
+ }
+ else // single packet larger than 4096 bytes
+ {
+ MessageBuffer packetBuffer(queued->size() + header.getHeaderLength());
+ packetBuffer.Write(header.header, header.getHeaderLength());
+ if (!queued->empty())
+ packetBuffer.Write(queued->contents(), queued->size());
- if (SendPacket(packet) == -1)
- return -1;
+ QueuePacket(std::move(packetBuffer));
+ }
- // Register with ACE Reactor
- if (reactor()->register_handler(this, ACE_Event_Handler::READ_MASK | ACE_Event_Handler::WRITE_MASK) == -1)
- {
- LOG_ERROR("network", "WorldSocket::open: unable to register client handler errno = %s", ACE_OS::strerror (errno));
- return -1;
+ delete queued;
}
- // reactor takes care of the socket from now on
- remove_reference();
+ if (buffer.GetActiveSize() > 0)
+ QueuePacket(std::move(buffer));
- return 0;
-}
-
-int WorldSocket::close(u_long)
-{
- shutdown();
+ if (!BaseSocket::Update())
+ return false;
- closing_ = true;
+ _queryProcessor.ProcessReadyCallbacks();
- remove_reference();
-
- return 0;
+ return true;
}
-int WorldSocket::handle_input(ACE_HANDLE)
+void WorldSocket::HandleSendAuthSession()
{
- if (closing_)
- return -1;
-
- switch (handle_input_missing_data())
- {
- case -1 :
- {
- if ((errno == EWOULDBLOCK) ||
- (errno == EAGAIN))
- {
- return Update(); // interesting line, isn't it ?
- }
-
- LOG_DEBUG("network", "WorldSocket::handle_input: Peer error closing connection errno = %s", ACE_OS::strerror (errno));
-
- errno = ECONNRESET;
- return -1;
- }
- case 0:
- {
- LOG_DEBUG("network", "WorldSocket::handle_input: Peer has closed connection");
+ WorldPacket packet(SMSG_AUTH_CHALLENGE, 40);
+ packet << uint32(1); // 1...31
+ packet.append(_authSeed);
- errno = ECONNRESET;
- return -1;
- }
- case 1:
- return 1;
- default:
- return Update(); // another interesting line ;)
- }
+ packet.append(Acore::Crypto::GetRandomBytes<32>()); // new encryption seeds
- return -1;
+ SendPacketAndLogOpcode(packet);
}
-int WorldSocket::handle_output(ACE_HANDLE)
+void WorldSocket::OnClose()
{
- if (closing_)
- return -1;
-
- std::lock_guard<std::mutex> guard(m_OutBufferLock);
-
- size_t send_len = m_OutBuffer->length();
-
- if (send_len == 0)
- return handle_output_queue();
-
-#ifdef MSG_NOSIGNAL
- ssize_t n = peer().send (m_OutBuffer->rd_ptr(), send_len, MSG_NOSIGNAL);
-#else
- ssize_t n = peer().send (m_OutBuffer->rd_ptr(), send_len);
-#endif // MSG_NOSIGNAL
-
- if (n == 0)
- return -1;
- else if (n == -1)
{
- if (errno == EWOULDBLOCK || errno == EAGAIN)
- return schedule_wakeup_output();
-
- return -1;
+ std::lock_guard<std::mutex> sessionGuard(_worldSessionLock);
+ _worldSession = nullptr;
}
- else if (n < (ssize_t)send_len) //now n > 0
- {
- m_OutBuffer->rd_ptr (static_cast<size_t> (n));
-
- // move the data to the base of the buffer
- m_OutBuffer->crunch();
-
- return schedule_wakeup_output();
- }
- else //now n == send_len
- {
- m_OutBuffer->reset();
-
- return handle_output_queue();
- }
-
- ACE_NOTREACHED (return 0);
}
-int WorldSocket::handle_output_queue()
+void WorldSocket::ReadHandler()
{
- if (msg_queue()->is_empty())
- return cancel_wakeup_output();
-
- ACE_Message_Block* mblk;
+ if (!IsOpen())
+ return;
- if (msg_queue()->dequeue_head(mblk, (ACE_Time_Value*)&ACE_Time_Value::zero) == -1)
+ MessageBuffer& packet = GetReadBuffer();
+ while (packet.GetActiveSize() > 0)
{
- LOG_ERROR("network", "WorldSocket::handle_output_queue dequeue_head");
- return -1;
- }
-
- const size_t send_len = mblk->length();
+ if (_headerBuffer.GetRemainingSpace() > 0)
+ {
+ // need to receive the header
+ std::size_t readHeaderSize = std::min(packet.GetActiveSize(), _headerBuffer.GetRemainingSpace());
+ _headerBuffer.Write(packet.GetReadPointer(), readHeaderSize);
+ packet.ReadCompleted(readHeaderSize);
-#ifdef MSG_NOSIGNAL
- ssize_t n = peer().send(mblk->rd_ptr(), send_len, MSG_NOSIGNAL);
-#else
- ssize_t n = peer().send(mblk->rd_ptr(), send_len);
-#endif // MSG_NOSIGNAL
+ if (_headerBuffer.GetRemainingSpace() > 0)
+ {
+ // Couldn't receive the whole header this time.
+ ASSERT(packet.GetActiveSize() == 0);
+ break;
+ }
- if (n == 0)
- {
- mblk->release();
+ // We just received nice new header
+ if (!ReadHeaderHandler())
+ {
+ CloseSocket();
+ return;
+ }
+ }
- return -1;
- }
- else if (n == -1)
- {
- if (errno == EWOULDBLOCK || errno == EAGAIN)
+ // We have full read header, now check the data payload
+ if (_packetBuffer.GetRemainingSpace() > 0)
{
- msg_queue()->enqueue_head(mblk, (ACE_Time_Value*) &ACE_Time_Value::zero);
- return schedule_wakeup_output();
+ // need more data in the payload
+ std::size_t readDataSize = std::min(packet.GetActiveSize(), _packetBuffer.GetRemainingSpace());
+ _packetBuffer.Write(packet.GetReadPointer(), readDataSize);
+ packet.ReadCompleted(readDataSize);
+
+ if (_packetBuffer.GetRemainingSpace() > 0)
+ {
+ // Couldn't receive the whole data this time.
+ ASSERT(packet.GetActiveSize() == 0);
+ break;
+ }
}
- mblk->release();
- return -1;
- }
- else if (n < (ssize_t)send_len) //now n > 0
- {
- mblk->rd_ptr(static_cast<size_t> (n));
+ // just received fresh new payload
+ ReadDataHandlerResult result = ReadDataHandler();
+ _headerBuffer.Reset();
- if (msg_queue()->enqueue_head(mblk, (ACE_Time_Value*) &ACE_Time_Value::zero) == -1)
+ if (result != ReadDataHandlerResult::Ok)
{
- LOG_ERROR("network", "WorldSocket::handle_output_queue enqueue_head");
- mblk->release();
- return -1;
- }
-
- return schedule_wakeup_output();
- }
- else //now n == send_len
- {
- mblk->release();
+ if (result != ReadDataHandlerResult::WaitingForQuery)
+ {
+ CloseSocket();
+ }
- return msg_queue()->is_empty() ? cancel_wakeup_output() : ACE_Event_Handler::WRITE_MASK;
+ return;
+ }
}
- return -1;
+ AsyncRead();
}
-int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask)
+bool WorldSocket::ReadHeaderHandler()
{
- // Critical section
- {
- std::lock_guard<std::mutex> guard(m_OutBufferLock);
+ ASSERT(_headerBuffer.GetActiveSize() == sizeof(ClientPktHeader));
- closing_ = true;
-
- if (h == ACE_INVALID_HANDLE)
- peer().close_writer();
- }
-
- // Critical section
+ if (_authCrypt.IsInitialized())
{
- std::lock_guard<decltype(m_SessionLock)> guard(m_SessionLock);
-
- m_Session = nullptr;
+ _authCrypt.DecryptRecv(_headerBuffer.GetReadPointer(), sizeof(ClientPktHeader));
}
- reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK);
- return 0;
-}
-
-int WorldSocket::Update(void)
-{
- if (closing_)
- return -1;
-
- if (m_OutActive)
- return 0;
+ ClientPktHeader* header = reinterpret_cast<ClientPktHeader*>(_headerBuffer.GetReadPointer());
+ EndianConvertReverse(header->size);
+ EndianConvert(header->cmd);
+ if (!header->IsValidSize() || !header->IsValidOpcode())
{
- std::lock_guard<std::mutex> guard(m_OutBufferLock);
+ LOG_ERROR("network", "WorldSocket::ReadHeaderHandler(): client %s sent malformed packet (size: %hu, cmd: %u)",
+ GetRemoteIpAddress().to_string().c_str(), header->size, header->cmd);
- if (m_OutBuffer->length() == 0 && msg_queue()->is_empty())
- return 0;
+ return false;
}
- int ret;
- do
- ret = handle_output (get_handle());
- while (ret > 0);
+ header->size -= sizeof(header->cmd);
+ _packetBuffer.Resize(header->size);
- return ret;
+ return true;
}
-int WorldSocket::handle_input_header(void)
+struct AuthSession
{
- ASSERT(m_RecvWPct == nullptr);
- ASSERT(m_Header.length() == sizeof(ClientPktHeader));
-
- if (m_Crypt.IsInitialized())
- m_Crypt.DecryptRecv((uint8*) m_Header.rd_ptr(), sizeof(ClientPktHeader));
-
- ClientPktHeader& header = *((ClientPktHeader*) m_Header.rd_ptr());
+ uint32 BattlegroupID = 0;
+ uint32 LoginServerType = 0;
+ uint32 RealmID = 0;
+ uint32 Build = 0;
+ std::array<uint8, 4> LocalChallenge = {};
+ uint32 LoginServerID = 0;
+ uint32 RegionID = 0;
+ uint64 DosResponse = 0;
+ Acore::Crypto::SHA1::Digest Digest = {};
+ std::string Account;
+ ByteBuffer AddonInfo;
+};
- EndianConvertReverse(header.size);
- EndianConvert(header.cmd);
+struct AccountInfo
+{
+ uint32 Id;
+ ::SessionKey SessionKey;
+ std::string LastIP;
+ bool IsLockedToIP;
+ std::string LockCountry;
+ uint8 Expansion;
+ int64 MuteTime;
+ LocaleConstant Locale;
+ uint32 Recruiter;
+ std::string OS;
+ bool IsRectuiter;
+ AccountTypes Security;
+ bool IsBanned;
+ uint32 TotalTime;
- if ((header.size < 4) || (header.size > 10240) || (header.cmd > 10240))
+ explicit AccountInfo(Field* fields)
{
- LOG_ERROR("network", "WorldSocket::handle_input_header(): client (%s) sent malformed packet (size: %hd, cmd: %d)",
- GetRemoteAddress().c_str(), header.size, header.cmd);
-
- errno = EINVAL;
- return -1;
- }
-
- header.size -= 4;
+ // 0 1 2 3 4 5 6 7 8 9 10 11
+ // SELECT a.id, a.sessionkey, a.last_ip, a.locked, a.lock_country, a.expansion, a.mutetime, a.locale, a.recruiter, a.os, a.totaltime, aa.gmLevel,
+ // 12 13
+ // ab.unbandate > UNIX_TIMESTAMP() OR ab.unbandate = ab.bandate, r.id
+ // FROM account a
+ // LEFT JOIN account_access aa ON a.id = aa.AccountID AND aa.RealmID IN (-1, ?)
+ // LEFT JOIN account_banned ab ON a.id = ab.id
+ // LEFT JOIN account r ON a.id = r.recruiter
+ // WHERE a.username = ? ORDER BY aa.RealmID DESC LIMIT 1
+ Id = fields[0].GetUInt32();
+ SessionKey = fields[1].GetBinary<SESSION_KEY_LENGTH>();
+ LastIP = fields[2].GetString();
+ IsLockedToIP = fields[3].GetBool();
+ LockCountry = fields[4].GetString();
+ Expansion = fields[5].GetUInt8();
+ MuteTime = fields[6].GetInt64();
+ Locale = LocaleConstant(fields[7].GetUInt8());
+ Recruiter = fields[8].GetUInt32();
+ OS = fields[9].GetString();
+ TotalTime = fields[10].GetUInt32();
+ Security = AccountTypes(fields[11].GetUInt8());
+ IsBanned = fields[12].GetUInt64() != 0;
+ IsRectuiter = fields[13].GetUInt32() != 0;
- ACE_NEW_RETURN (m_RecvWPct, WorldPacket ((uint16) header.cmd, header.size), -1);
+ uint32 world_expansion = sWorld->getIntConfig(CONFIG_EXPANSION);
+ if (Expansion > world_expansion)
+ Expansion = world_expansion;
- if (header.size > 0)
- {
- m_RecvWPct->resize (header.size);
- m_RecvPct.base ((char*) m_RecvWPct->contents(), m_RecvWPct->size());
- }
- else
- {
- ASSERT(m_RecvPct.space() == 0);
+ if (Locale >= TOTAL_LOCALES)
+ Locale = LOCALE_enUS;
}
+};
- return 0;
-}
-
-int WorldSocket::handle_input_payload(void)
-{
- // set errno properly here on error !!!
- // now have a header and payload
-
- ASSERT(m_RecvPct.space() == 0);
- ASSERT(m_Header.space() == 0);
- ASSERT(m_RecvWPct != nullptr);
-
- const int ret = ProcessIncoming (m_RecvWPct);
-
- m_RecvPct.base (nullptr, 0);
- m_RecvPct.reset();
- m_RecvWPct = nullptr;
-
- m_Header.reset();
-
- if (ret == -1)
- errno = EINVAL;
-
- return ret;
-}
-
-int WorldSocket::handle_input_missing_data(void)
+WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
{
- char buf [4096];
+ ClientPktHeader* header = reinterpret_cast<ClientPktHeader*>(_headerBuffer.GetReadPointer());
+ OpcodeClient opcode = static_cast<OpcodeClient>(header->cmd);
- ACE_Data_Block db (sizeof (buf),
- ACE_Message_Block::MB_DATA,
- buf,
- 0,
- 0,
- ACE_Message_Block::DONT_DELETE,
- 0);
+ WorldPacket packet(opcode, std::move(_packetBuffer));
+ WorldPacket* packetToQueue;
- ACE_Message_Block message_block(&db,
- ACE_Message_Block::DONT_DELETE,
- 0);
-
- const size_t recv_size = message_block.space();
-
- const ssize_t n = peer().recv (message_block.wr_ptr(),
- recv_size);
-
- if (n <= 0)
- return int(n);
+ if (sPacketLog->CanLogPacket())
+ sPacketLog->LogPacket(packet, CLIENT_TO_SERVER, GetRemoteIpAddress(), GetRemotePort());
- message_block.wr_ptr (n);
+ std::unique_lock<std::mutex> sessionGuard(_worldSessionLock, std::defer_lock);
- while (message_block.length() > 0)
+ switch (opcode)
{
- if (m_Header.space() > 0)
+ case CMSG_PING:
{
- //need to receive the header
- const size_t to_header = (message_block.length() > m_Header.space() ? m_Header.space() : message_block.length());
- m_Header.copy (message_block.rd_ptr(), to_header);
- message_block.rd_ptr (to_header);
-
- if (m_Header.space() > 0)
+ LogOpcodeText(opcode, sessionGuard);
+ try
{
- // Couldn't receive the whole header this time.
- ASSERT(message_block.length() == 0);
- errno = EWOULDBLOCK;
- return -1;
+ return HandlePing(packet) ? ReadDataHandlerResult::Ok : ReadDataHandlerResult::Error;
}
-
- // We just received nice new header
- if (handle_input_header() == -1)
+ catch (ByteBufferException const&)
{
- ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN));
- return -1;
}
+ LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_PING", GetRemoteIpAddress().to_string().c_str());
+ return ReadDataHandlerResult::Error;
}
-
- // Its possible on some error situations that this happens
- // for example on closing when epoll receives more chunked data and stuff
- // hope this is not hack, as proper m_RecvWPct is asserted around
- if (!m_RecvWPct)
+ case CMSG_AUTH_SESSION:
{
- LOG_ERROR("network", "Forcing close on input m_RecvWPct = nullptr");
- errno = EINVAL;
- return -1;
- }
-
- // We have full read header, now check the data payload
- if (m_RecvPct.space() > 0)
- {
- //need more data in the payload
- const size_t to_data = (message_block.length() > m_RecvPct.space() ? m_RecvPct.space() : message_block.length());
- m_RecvPct.copy (message_block.rd_ptr(), to_data);
- message_block.rd_ptr (to_data);
+ LogOpcodeText(opcode, sessionGuard);
+ if (_authed)
+ {
+ // locking just to safely log offending user is probably overkill but we are disconnecting him anyway
+ if (sessionGuard.try_lock())
+ LOG_ERROR("network", "WorldSocket::ProcessIncoming: received duplicate CMSG_AUTH_SESSION from %s", _worldSession->GetPlayerInfo().c_str());
+ return ReadDataHandlerResult::Error;
+ }
- if (m_RecvPct.space() > 0)
+ try
{
- // Couldn't receive the whole data this time.
- ASSERT(message_block.length() == 0);
- errno = EWOULDBLOCK;
- return -1;
+ HandleAuthSession(packet);
+ return ReadDataHandlerResult::WaitingForQuery;
}
- }
+ catch (ByteBufferException const&) { }
- //just received fresh new payload
- if (handle_input_payload() == -1)
- {
- ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN));
- return -1;
+ LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_AUTH_SESSION", GetRemoteIpAddress().to_string().c_str());
+ return ReadDataHandlerResult::Error;
}
+ case CMSG_KEEP_ALIVE: // todo: handle this packet in the same way of CMSG_TIME_SYNC_RESP
+ sessionGuard.lock();
+ LogOpcodeText(opcode, sessionGuard);
+ if (_worldSession)
+ _worldSession->ResetTimeOutTime(true);
+ return ReadDataHandlerResult::Ok;
+ case CMSG_TIME_SYNC_RESP:
+ packetToQueue = new WorldPacket(std::move(packet), std::chrono::steady_clock::now());
+ break;
+ default:
+ packetToQueue = new WorldPacket(std::move(packet));
+ break;
}
- return size_t(n) == recv_size ? 1 : 2;
-}
-
-int WorldSocket::cancel_wakeup_output()
-{
- if (!m_OutActive)
- return 0;
+ sessionGuard.lock();
- m_OutActive = false;
+ LogOpcodeText(opcode, sessionGuard);
- if (reactor()->cancel_wakeup
- (this, ACE_Event_Handler::WRITE_MASK) == -1)
+ if (!_worldSession)
{
- // would be good to store errno from reactor with errno guard
- LOG_ERROR("network", "WorldSocket::cancel_wakeup_output");
- return -1;
+ LOG_ERROR("network.opcode", "ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
+ delete packetToQueue;
+ return ReadDataHandlerResult::Error;
}
- return 0;
-}
-
-int WorldSocket::schedule_wakeup_output()
-{
- if (m_OutActive)
- return 0;
-
- m_OutActive = true;
-
- if (reactor()->schedule_wakeup
- (this, ACE_Event_Handler::WRITE_MASK) == -1)
+ OpcodeHandler const* handler = opcodeTable[opcode];
+ if (!handler)
{
- LOG_ERROR("network", "WorldSocket::schedule_wakeup_output");
- return -1;
+ LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet.GetOpcode())).c_str(), _worldSession->GetPlayerInfo().c_str());
+ delete packetToQueue;
+ return ReadDataHandlerResult::Error;
}
- return 0;
-}
-
-int WorldSocket::ProcessIncoming(WorldPacket* new_pct)
-{
- ASSERT(new_pct);
-
- // manage memory ;)
- std::unique_ptr<WorldPacket> aptr(new_pct);
-
- OpcodeClient opcode = static_cast<OpcodeClient>(aptr->GetOpcode());
+ // Our Idle timer will reset on any non PING opcodes on login screen, allowing us to catch people idling.
+ _worldSession->ResetTimeOutTime(false);
- if (closing_)
- return -1;
+ // Copy the packet to the heap before enqueuing
+ _worldSession->QueuePacket(packetToQueue);
- // Dump received packet.
- if (sPacketLog->CanLogPacket())
- sPacketLog->LogPacket(*new_pct, CLIENT_TO_SERVER);
+ return ReadDataHandlerResult::Ok;
+}
- try
+void WorldSocket::LogOpcodeText(OpcodeClient opcode, std::unique_lock<std::mutex> const& guard) const
+{
+ if (!guard)
{
- switch (opcode)
- {
- case CMSG_PING:
- try
- {
- return HandlePing(*new_pct);
- }
- catch (ByteBufferPositionException const&) { }
- LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client sent malformed CMSG_PING");
- return -1;
- case CMSG_AUTH_SESSION:
- if (m_Session)
- {
- LOG_ERROR("network", "WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again");
- return -1;
- }
- return HandleAuthSession (*new_pct);
- case CMSG_KEEP_ALIVE:
- if (m_Session)
- m_Session->ResetTimeOutTime(true);
- return 0;
- case CMSG_TIME_SYNC_RESP:
- new_pct = new WorldPacket(std::move(*new_pct), std::chrono::steady_clock::now());
- break;
- default:
- break;
- }
+ LOG_TRACE("network.opcode", "C->S: %s %s", GetRemoteIpAddress().to_string().c_str(), GetOpcodeNameForLogging(opcode).c_str());
}
- catch (ByteBufferException const&)
+ else
{
- LOG_ERROR("network", "WorldSocket::ProcessIncoming ByteBufferException occured while parsing an instant handled packet (opcode: %u) from client %s, accountid=%u. Disconnected client.",
- aptr->GetOpcode(), GetRemoteAddress().c_str(), m_Session ? m_Session->GetAccountId() : 0);
-
- if (sLog->ShouldLog("network", LogLevel::LOG_LEVEL_DEBUG))
- {
- LOG_DEBUG("network", "Dumping error causing packet:");
- new_pct->hexlike();
- }
-
- return -1;
+ LOG_TRACE("network.opcode", "C->S: %s %s", (_worldSession ? _worldSession->GetPlayerInfo() : GetRemoteIpAddress().to_string()).c_str(),
+ GetOpcodeNameForLogging(opcode).c_str());
}
+}
- std::lock_guard<std::mutex> guard(m_SessionLock);
-
- OpcodeHandler const* handler = opcodeTable[opcode];
- if (!handler)
- {
- LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(static_cast<OpcodeClient>(aptr->GetOpcode())).c_str(), m_Session->GetPlayerInfo().c_str());
- return -1;
- }
+void WorldSocket::SendPacketAndLogOpcode(WorldPacket const& packet)
+{
+ LOG_TRACE("network.opcode", "S->C: %s %s", GetRemoteIpAddress().to_string().c_str(), GetOpcodeNameForLogging(static_cast<OpcodeServer>(packet.GetOpcode())).c_str());
+ SendPacket(packet);
+}
- if (m_Session != nullptr)
- {
- // Our Idle timer will reset on any non PING or TIME_SYNC opcodes.
- // Catches people idling on the login screen and any lingering ingame connections.
- if (opcode != CMSG_PING && opcode != CMSG_TIME_SYNC_RESP)
- {
- m_Session->ResetTimeOutTime(false);
- }
+void WorldSocket::SendPacket(WorldPacket const& packet)
+{
+ if (!IsOpen())
+ return;
- // OK, give the packet to WorldSession
- aptr.release();
- m_Session->QueuePacket(new_pct);
- return 0;
- }
+ if (sPacketLog->CanLogPacket())
+ sPacketLog->LogPacket(packet, SERVER_TO_CLIENT, GetRemoteIpAddress(), GetRemotePort());
- LOG_ERROR("network", "WorldSocket::ProcessIncoming: Client not authed opcode = %u", aptr->GetOpcode());
- return -1;
+ _bufferQueue.Enqueue(new EncryptablePacket(packet, _authCrypt.IsInitialized()));
}
-int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
+void WorldSocket::HandleAuthSession(WorldPacket & recvPacket)
{
- // NOTE: ATM the socket is singlethread, have this in mind ...
- uint32 loginServerID, loginServerType, regionID, battlegroupID, realmid;
- uint64 DosResponse;
- uint32 BuiltNumberClient;
- std::string accountName;
- WorldPacket packet, SendAddonPacked;
- std::array<uint8, 4> clientSeed;
- Acore::Crypto::SHA1::Digest digest;
-
- if (sWorld->IsClosed())
- {
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_REJECT);
- SendPacket(packet);
-
- LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", GetRemoteAddress().c_str());
- return -1;
- }
+ std::shared_ptr<AuthSession> authSession = std::make_shared<AuthSession>();
// Read the content of the packet
- recvPacket >> BuiltNumberClient; // for now no use
- recvPacket >> loginServerID;
- recvPacket >> accountName;
- recvPacket >> loginServerType;
- recvPacket.read(clientSeed);
- recvPacket >> regionID;
- recvPacket >> battlegroupID;
- recvPacket >> realmid;
- recvPacket >> DosResponse;
- recvPacket.read(digest);
-
- LOG_DEBUG("network", "WorldSocket::HandleAuthSession: client %u, loginServerID %u, accountName %s, loginServerType %u",
- BuiltNumberClient, loginServerID, accountName.c_str(), loginServerType);
-
- // Get the account information from the realmd database
- // 0 1 2 3 4 5 6 7 8 9 10
- // SELECT id, sessionkey, last_ip, locked, lock_country, expansion, mutetime, locale, recruiter, os, totaltime FROM account WHERE username = ?
- auto* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME);
+ recvPacket >> authSession->Build;
+ recvPacket >> authSession->LoginServerID;
+ recvPacket >> authSession->Account;
+ recvPacket >> authSession->LoginServerType;
+ recvPacket.read(authSession->LocalChallenge);
+ recvPacket >> authSession->RegionID;
+ recvPacket >> authSession->BattlegroupID;
+ recvPacket >> authSession->RealmID; // realmId from auth_database.realmlist table
+ recvPacket >> authSession->DosResponse;
+ recvPacket.read(authSession->Digest);
+ authSession->AddonInfo.resize(recvPacket.size() - recvPacket.rpos());
+ recvPacket.read(authSession->AddonInfo.contents(), authSession->AddonInfo.size()); // .contents will throw if empty, thats what we want
+
+ // Get the account information from the auth database
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_INFO_BY_NAME);
stmt->setInt32(0, int32(realm.Id.Realm));
- stmt->setString(1, accountName);
+ stmt->setString(1, authSession->Account);
- PreparedQueryResult result = LoginDatabase.Query(stmt);
+ _queryProcessor.AddCallback(LoginDatabase.AsyncQuery(stmt).WithPreparedCallback(std::bind(&WorldSocket::HandleAuthSessionCallback, this, authSession, std::placeholders::_1)));
+}
+void WorldSocket::HandleAuthSessionCallback(std::shared_ptr<AuthSession> authSession, PreparedQueryResult result)
+{
// Stop if the account is not found
if (!result)
{
// We can not log here, as we do not know the account. Thus, no accountId.
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_UNKNOWN_ACCOUNT);
-
- SendPacket(packet);
-
+ SendAuthResponseError(AUTH_UNKNOWN_ACCOUNT);
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (unknown account).");
- return -1;
+ DelayedCloseSocket();
+ return;
}
AccountInfo account(result->Fetch());
- // For hook purposes, we get Remoteaddress at this point
- std::string address = GetRemoteAddress(); // Originally, this variable should be called address, but for some reason, that variable name was already on the core.
+ // For hook purposes, we get Remoteaddress at this point.
+ std::string address = GetRemoteIpAddress().to_string();
// As we don't know if attempted login process by ip works, we update last_attempt_ip right away
- stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP);
+ LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_ATTEMPT_IP);
stmt->setString(0, address);
- stmt->setString(1, accountName);
+ stmt->setString(1, authSession->Account);
LoginDatabase.Execute(stmt);
// This also allows to check for possible "hack" attempts on account
// even if auth credentials are bad, try using the session key we have - client cannot read auth response error without it
- m_Crypt.Init(account.SessionKey);
+ _authCrypt.Init(account.SessionKey);
// First reject the connection if packet contains invalid data or realm state doesn't allow logging in
if (sWorld->IsClosed())
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_REJECT);
- SendPacket(packet);
-
- LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", address.c_str());
- sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ SendAuthResponseError(AUTH_REJECT);
+ LOG_ERROR("network", "WorldSocket::HandleAuthSession: World closed, denying client (%s).", GetRemoteIpAddress().to_string().c_str());
+ DelayedCloseSocket();
+ return;
}
- if (realmid != realm.Id.Realm)
+ if (authSession->RealmID != realm.Id.Realm)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(REALM_LIST_REALM_NOT_FOUND);
- SendPacket(packet);
-
- LOG_ERROR("server", "WorldSocket::HandleAuthSession: Client %s requested connecting with realm id %u but this realm has id %u set in config.",
- address.c_str(), realmid, realm.Id.Realm);
- sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ SendAuthResponseError(REALM_LIST_REALM_NOT_FOUND);
+ LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client %s requested connecting with realm id %u but this realm has id %u set in config.",
+ GetRemoteIpAddress().to_string().c_str(), authSession->RealmID, realm.Id.Realm);
+ DelayedCloseSocket();
+ return;
}
// Must be done before WorldSession is created
bool wardenActive = sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED);
if (wardenActive && account.OS != "Win" && account.OS != "OSX")
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_REJECT);
- SendPacket(packet);
+ SendAuthResponseError(AUTH_REJECT);
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Client %s attempted to log in using invalid client OS (%s).", address.c_str(), account.OS.c_str());
- sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ DelayedCloseSocket();
+ return;
}
// Check that Key and account name are the same on client and server
- uint8 t[4] = { 0x00, 0x00, 0x00, 0x00 };
+ uint8 t[4] = { 0x00,0x00,0x00,0x00 };
Acore::Crypto::SHA1 sha;
- sha.UpdateData(accountName);
+ sha.UpdateData(authSession->Account);
sha.UpdateData(t);
- sha.UpdateData(clientSeed);
- sha.UpdateData(m_Seed);
+ sha.UpdateData(authSession->LocalChallenge);
+ sha.UpdateData(_authSeed);
sha.UpdateData(account.SessionKey);
sha.Finalize();
- if (sha.GetDigest() != digest)
+ if (sha.GetDigest() != authSession->Digest)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_FAILED);
- SendPacket(packet);
- LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", account.Id, accountName.c_str(), address.c_str());
- return -1;
+ SendAuthResponseError(AUTH_FAILED);
+ LOG_ERROR("network", "WorldSocket::HandleAuthSession: Authentication failed for account: %u ('%s') address: %s", account.Id, authSession->Account.c_str(), address.c_str());
+ DelayedCloseSocket();
+ return;
}
if (IpLocationRecord const* location = sIPLocation->GetLocationRecord(address))
@@ -922,36 +519,34 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
{
if (account.LastIP != address)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_FAILED);
- SendPacket(packet);
+ SendAuthResponseError(AUTH_FAILED);
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account IP differs. Original IP: %s, new IP: %s).", account.LastIP.c_str(), address.c_str());
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ DelayedCloseSocket();
+ return;
}
}
else if (!account.LockCountry.empty() && account.LockCountry != "00" && !_ipCountry.empty())
{
if (account.LockCountry != _ipCountry)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_REJECT);
+ SendAuthResponseError(AUTH_FAILED);
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account country differs. Original country: %s, new country: %s).", account.LockCountry.c_str(), _ipCountry.c_str());
// We could log on hook only instead of an additional db log, however action logger is config based. Better keep DB logging as well
sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ DelayedCloseSocket();
+ return;
}
}
if (account.IsBanned)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_BANNED);
- SendPacket(packet);
+ SendAuthResponseError(AUTH_BANNED);
LOG_ERROR("network", "WorldSocket::HandleAuthSession: Sent Auth Response (Account banned).");
sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ DelayedCloseSocket();
+ return;
}
// Check locked state for server
@@ -959,54 +554,55 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
LOG_DEBUG("network", "Allowed Level: %u Player Level %u", allowedAccountType, account.Security);
if (allowedAccountType > SEC_PLAYER && account.Security < allowedAccountType)
{
- packet.Initialize(SMSG_AUTH_RESPONSE, 1);
- packet << uint8(AUTH_UNAVAILABLE);
+ SendAuthResponseError(AUTH_UNAVAILABLE);
LOG_DEBUG("network", "WorldSocket::HandleAuthSession: User tries to login but his security level is not enough");
sScriptMgr->OnFailedAccountLogin(account.Id);
- return -1;
+ DelayedCloseSocket();
+ return;
}
- LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.", accountName.c_str(), address.c_str());
+ LOG_DEBUG("network", "WorldSocket::HandleAuthSession: Client '%s' authenticated successfully from %s.", authSession->Account.c_str(), address.c_str());
// Update the last_ip in the database as it was successful for login
stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_LAST_IP);
stmt->setString(0, address);
- stmt->setString(1, accountName);
- LoginDatabase.Execute(stmt);
+ stmt->setString(1, authSession->Account);
- sScriptMgr->OnLastIpUpdate(account.Id, address);
+ LoginDatabase.Execute(stmt);
- bool skipQueue = false;
+ // At this point, we can safely hook a successful login
+ sScriptMgr->OnAccountLogin(account.Id);
- if (account.Security > SEC_PLAYER)
- skipQueue = true;
+ _authed = true;
- // NOTE ATM the socket is single-threaded, have this in mind ...
- ACE_NEW_RETURN(m_Session,
- WorldSession(account.Id, this, AccountTypes(account.Security), account.Expansion, account.MuteTime, account.Locale, account.Recruiter, account.IsRectuiter, skipQueue, account.TotalTime), -1);
+ _worldSession = new WorldSession(account.Id, std::move(authSession->Account), shared_from_this(), account.Security,
+ account.Expansion, account.MuteTime, account.Locale, account.Recruiter, account.IsRectuiter, account.Security ? true : false, account.TotalTime);
- m_Session->LoadGlobalAccountData();
- m_Session->LoadTutorialsData();
- m_Session->ReadAddonsInfo(recvPacket);
-
- // At this point, we can safely hook a successful login
- sScriptMgr->OnAccountLogin(account.Id);
+ _worldSession->ReadAddonsInfo(authSession->AddonInfo);
// Initialize Warden system only if it is enabled by config
if (wardenActive)
- m_Session->InitWarden(account.SessionKey, account.OS);
+ {
+ _worldSession->InitWarden(account.SessionKey, account.OS);
+ }
- // Sleep this Network thread for
- uint32 sleepTime = sWorld->getIntConfig(CONFIG_SESSION_ADD_DELAY);
- std::this_thread::sleep_for(Microseconds(sleepTime));
+ sWorld->AddSession(_worldSession);
+
+ AsyncRead();
+}
- sWorld->AddSession(m_Session);
+void WorldSocket::SendAuthResponseError(uint8 code)
+{
+ WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
+ packet << uint8(code);
- return 0;
+ SendPacketAndLogOpcode(packet);
}
-int WorldSocket::HandlePing(WorldPacket& recvPacket)
+bool WorldSocket::HandlePing(WorldPacket& recvPacket)
{
+ using namespace std::chrono;
+
uint32 ping;
uint32 latency;
@@ -1014,60 +610,57 @@ int WorldSocket::HandlePing(WorldPacket& recvPacket)
recvPacket >> ping;
recvPacket >> latency;
- if (m_LastPingTime == SystemTimePoint::min())
- m_LastPingTime = std::chrono::system_clock::now(); // for 1st ping
+ if (_LastPingTime == steady_clock::time_point())
+ {
+ _LastPingTime = steady_clock::now();
+ }
else
{
- auto now = std::chrono::system_clock::now();
- Seconds seconds = std::chrono::duration_cast<Seconds>(now - m_LastPingTime);
- m_LastPingTime = now;
+ steady_clock::time_point now = steady_clock::now();
+ steady_clock::duration diff = now - _LastPingTime;
+
+ _LastPingTime = now;
- if (seconds.count() < 27)
+ if (diff < seconds(27))
{
- ++m_OverSpeedPings;
+ ++_OverSpeedPings;
- uint32 max_count = sWorld->getIntConfig(CONFIG_MAX_OVERSPEED_PINGS);
+ uint32 maxAllowed = sWorld->getIntConfig(CONFIG_MAX_OVERSPEED_PINGS);
- if (max_count && m_OverSpeedPings > max_count)
+ if (maxAllowed && _OverSpeedPings > maxAllowed)
{
- std::lock_guard<std::mutex> guard(m_SessionLock);
+ std::unique_lock<std::mutex> sessionGuard(_worldSessionLock);
- if (m_Session && AccountMgr::IsPlayerAccount(m_Session->GetSecurity()))
+ if (_worldSession && AccountMgr::IsPlayerAccount(_worldSession->GetSecurity()))
{
- Player* _player = m_Session->GetPlayer();
- LOG_ERROR("network", "WorldSocket::HandlePing: Player (account: %u, %s, name: %s) kicked for over-speed pings (address: %s)",
- m_Session->GetAccountId(),
- _player ? _player->GetGUID().ToString().c_str() : "",
- _player ? _player->GetName().c_str() : "<none>",
- GetRemoteAddress().c_str());
-
- return -1;
+ LOG_ERROR("network", "WorldSocket::HandlePing: %s kicked for over-speed pings (address: %s)",
+ _worldSession->GetPlayerInfo().c_str(), GetRemoteIpAddress().to_string().c_str());
+
+ return false;
}
}
}
else
- m_OverSpeedPings = 0;
+ {
+ _OverSpeedPings = 0;
+ }
}
- // critical section
{
- std::lock_guard<std::mutex> guard(m_SessionLock);
+ std::lock_guard<std::mutex> sessionGuard(_worldSessionLock);
- if (m_Session)
- {
- m_Session->SetLatency (latency);
- }
+ if (_worldSession)
+ _worldSession->SetLatency(latency);
else
{
- LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, "
- "but is not authenticated or got recently kicked, "
- " address = %s",
- GetRemoteAddress().c_str());
- return -1;
+ LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = %s", GetRemoteIpAddress().to_string().c_str());
+ return false;
}
}
- WorldPacket packet (SMSG_PONG, 4);
+ WorldPacket packet(SMSG_PONG, 4);
packet << ping;
- return SendPacket(packet);
+ SendPacketAndLogOpcode(packet);
+
+ return true;
}
diff --git a/src/server/game/Server/WorldSocket.h b/src/server/game/Server/WorldSocket.h
index 3bd34e2bd1..ab07efa957 100644
--- a/src/server/game/Server/WorldSocket.h
+++ b/src/server/game/Server/WorldSocket.h
@@ -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>
-
-#if !defined (ACE_LACKS_PRAGMA_ONCE)
-#pragma once
-#endif /* ACE_LACKS_PRAGMA_ONCE */
-
-class ACE_Message_Block;
-class WorldPacket;
-class WorldSession;
+#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>
-namespace WorldPackets
-{
- class ServerPacket;
-}
+using boost::asio::ip::tcp;
-/// 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
+class EncryptablePacket : public WorldPacket
{
public:
- WorldSocket (void);
- virtual ~WorldSocket (void);
-
- friend class WorldSocketMgr;
-
- /// Check if socket is closed.
- bool IsClosed (void) const;
-
- /// Close the socket.
- void CloseSocket(std::string const& reason);
+ EncryptablePacket(WorldPacket const& packet, bool encrypt) : WorldPacket(packet), _encrypt(encrypt)
+ {
+ SocketQueueLink.store(nullptr, std::memory_order_relaxed);
+ }
- /// Get address of connected peer.
- const std::string& GetRemoteAddress (void) const;
+ bool NeedsEncryption() const { return _encrypt; }
- /// Send A packet on the socket, this function is reentrant.
- /// @param pct packet to send
- /// @return -1 of failure
- int SendPacket(const WorldPacket& pct);
+ std::atomic<EncryptablePacket*> SocketQueueLink;
- /// Add reference to this object.
- long AddReference (void);
-
- /// Remove reference to this object.
- long RemoveReference (void);
+private:
+ bool _encrypt;
+};
- /// things called by ACE framework.
+namespace WorldPackets
+{
+ class ServerPacket;
+}
- /// Called on open, the void* is the acceptor.
- virtual int open (void*);
+#pragma pack(push, 1)
+struct ClientPktHeader
+{
+ uint16 size;
+ uint32 cmd;
- /// Called on failures inside of the acceptor, don't call from your code.
- virtual int close (u_long);
+ bool IsValidSize() const { return size >= 4 && size < 10240; }
+ bool IsValidOpcode() const { return cmd < NUM_OPCODE_HANDLERS; }
+};
+#pragma pack(pop)
- /// Called when we can read from the socket.
- virtual int handle_input (ACE_HANDLE = ACE_INVALID_HANDLE);
+struct AuthSession;
- /// Called when the socket can write.
- virtual int handle_output (ACE_HANDLE = ACE_INVALID_HANDLE);
+class AC_GAME_API WorldSocket : public Socket<WorldSocket>
+{
+ typedef Socket<WorldSocket> BaseSocket;
- /// 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);
+public:
+ WorldSocket(tcp::socket&& socket);
+ ~WorldSocket();
- /// Called by WorldSocketMgr/ReactorRunnable.
- int Update (void);
+ WorldSocket(WorldSocket const& right) = delete;
+ WorldSocket& operator=(WorldSocket const& right) = delete;
-private:
- /// Helper functions for processing incoming data.
- int handle_input_header (void);
- int handle_input_payload (void);
- int handle_input_missing_data (void);
+ void Start() override;
+ bool Update() override;
- /// 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();
+ void SendPacket(WorldPacket const& packet);
- /// Drain the queue if its not empty.
- int handle_output_queue();
+ void SetSendBufferSize(std::size_t sendBufferSize) { _sendBufferSize = sendBufferSize; }
- /// process one incoming packet.
- /// @param new_pct received packet, note that you need to delete it.
- int ProcessIncoming (WorldPacket* new_pct);
+protected:
+ void OnClose() override;
+ void ReadHandler() override;
+ bool ReadHeaderHandler();
- /// Called by ProcessIncoming() on CMSG_AUTH_SESSION.
- int HandleAuthSession (WorldPacket& recvPacket);
+ enum class ReadDataHandlerResult
+ {
+ Ok = 0,
+ Error = 1,
+ WaitingForQuery = 2
+ };
- /// Called by ProcessIncoming() on CMSG_PING.
- int HandlePing (WorldPacket& recvPacket);
+ ReadDataHandlerResult ReadDataHandler();
private:
- /// Time in which the last ping was received
- SystemTimePoint m_LastPingTime;
+ void CheckIpCallback(PreparedQueryResult result);
- /// Keep track of over-speed pings, to prevent ping flood.
- uint32 m_OverSpeedPings;
+ /// 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;
- /// Address of the remote peer
- std::string m_Address;
+ /// 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);
- /// Class used for managing encryption of the headers
- AuthCrypt m_Crypt;
+ bool HandlePing(WorldPacket& recvPacket);
- /// Mutex lock to protect m_Session
- std::mutex m_SessionLock;
+ std::array<uint8, 4> _authSeed;
+ AuthCrypt _authCrypt;
- /// Session to which received packets are routed
- WorldSession* m_Session;
+ TimePoint _LastPingTime;
+ uint32 _OverSpeedPings;
- /// here are stored the fragments of the received data
- WorldPacket* m_RecvWPct;
+ std::mutex _worldSessionLock;
+ WorldSession* _worldSession;
+ bool _authed;
- /// 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
diff --git a/src/server/game/Server/WorldSocketAcceptor.h b/src/server/game/Server/WorldSocketAcceptor.h
deleted file mode 100644
index 1b058ca36b..0000000000
--- a/src/server/game/Server/WorldSocketAcceptor.h
+++ /dev/null
@@ -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_ */
-/// @}
diff --git a/src/server/game/Server/WorldSocketMgr.cpp b/src/server/game/Server/WorldSocketMgr.cpp
index 138ffbf30a..48f71f079e 100644
--- a/src/server/game/Server/WorldSocketMgr.cpp
+++ b/src/server/game/Server/WorldSocketMgr.cpp
@@ -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)
{
-public:
- ReactorRunnable() :
- m_Reactor(0),
- m_Connections(0),
- m_ThreadId(-1)
- {
- 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;
- }
+ sWorldSocketMgr.OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex);
+}
- int AddSocket (WorldSocket* sock)
+class WorldSocketThread : public NetworkThread<WorldSocket>
+{
+public:
+ void SocketAdded(std::shared_ptr<WorldSocket> sock) override
{
- 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()
- {
- return m_Reactor;
- }
-
-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
+ void SocketRemoved(std::shared_ptr<WorldSocket> sock) 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;
+ sScriptMgr->OnSocketClose(sock);
}
-
-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)
-{
-}
-
-WorldSocketMgr::~WorldSocketMgr()
+ BaseSocketMgr(), _socketSystemSendBufferSize(-1), _socketApplicationSendBufferSize(65536), _tcpNoDelay(true)
{
- 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);
-
- 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];
+ _tcpNoDelay = sConfigMgr->GetOption<bool>("Network.TcpNodelay", true);
- 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;
- }
-
- m_Acceptor = new WorldSocketAcceptor;
-
- 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;
+ return false;
}
- for (size_t i = 0; i < m_NetThreadsCount; ++i)
- m_NetThreads[i].Start();
+ if (!BaseSocketMgr::StartNetwork(ioContext, bindIp, port, threadCount))
+ return false;
- 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;
+ BaseSocketMgr::OnSocketOpen(std::forward<tcp::socket>(sock), threadIndex);
+}
- return m_NetThreads[min].AddSocket (sock);
+NetworkThread<WorldSocket>* WorldSocketMgr::CreateThreads() const
+{
+ return new WorldSocketThread[GetNetworkThreadCount()];
}
diff --git a/src/server/game/Server/WorldSocketMgr.h b/src/server/game/Server/WorldSocketMgr.h
index a46583d4bb..c2ae6e8fe2 100644
--- a/src/server/game/Server/WorldSocketMgr.h
+++ b/src/server/game/Server/WorldSocketMgr.h
@@ -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
/// @}
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index 2e3edd63eb..0152f8f548 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -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"
diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp
index 1561c9fa81..c4099745f8 100644
--- a/src/server/scripts/Commands/cs_misc.cpp
+++ b/src/server/scripts/Commands/cs_misc.cpp
@@ -5,7 +5,6 @@
*/
#include "AccountMgr.h"
-#include "ace/INET_Addr.h"
#include "ArenaTeamMgr.h"
#include "BattlegroundMgr.h"
#include "CellImpl.h"
diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp
index 9a31c92ade..441207b780 100644
--- a/src/server/scripts/Commands/cs_server.cpp
+++ b/src/server/scripts/Commands/cs_server.cpp
@@ -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());
diff --git a/src/server/scripts/Commands/cs_spectator.cpp b/src/server/scripts/Commands/cs_spectator.cpp
index 980f421bc0..b93bc16258 100644
--- a/src/server/scripts/Commands/cs_spectator.cpp
+++ b/src/server/scripts/Commands/cs_spectator.cpp
@@ -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()
diff --git a/src/server/shared/Network/AsyncAcceptor.h b/src/server/shared/Network/AsyncAcceptor.h
index 5b218bf227..64f52d1808 100644
--- a/src/server/shared/Network/AsyncAcceptor.h
+++ b/src/server/shared/Network/AsyncAcceptor.h
@@ -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());
diff --git a/src/server/shared/Network/RealmSocket.cpp b/src/server/shared/Network/RealmSocket.cpp
deleted file mode 100644
index ec44dbbf6f..0000000000
--- a/src/server/shared/Network/RealmSocket.cpp
+++ /dev/null
@@ -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;
-}
diff --git a/src/server/shared/Network/RealmSocket.h b/src/server/shared/Network/RealmSocket.h
deleted file mode 100644
index 53dc513ad5..0000000000
--- a/src/server/shared/Network/RealmSocket.h
+++ /dev/null
@@ -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__ */
diff --git a/src/server/shared/Realms/Realm.cpp b/src/server/shared/Realms/Realm.cpp
new file mode 100644
index 0000000000..4e114e0b6c
--- /dev/null
+++ b/src/server/shared/Realms/Realm.cpp
@@ -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);
+}
diff --git a/src/server/shared/Realms/Realm.h b/src/server/shared/Realms/Realm.h
index e95e197c5a..00949c994b 100644
--- a/src/server/shared/Realms/Realm.h
+++ b/src/server/shared/Realms/Realm.h
@@ -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__
diff --git a/src/server/shared/Realms/RealmList.cpp b/src/server/shared/Realms/RealmList.cpp
index 909c79e696..913dab1216 100644
--- a/src/server/shared/Realms/RealmList.cpp
+++ b/src/server/shared/Realms/RealmList.cpp
@@ -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,69 +141,86 @@ 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<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);
}
-
- Optional<ACE_INET_Addr> localAddress = ACE_INET_Addr(port, localAddressString.c_str(), AF_INET);
- 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<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)
+ catch (std::exception const& ex)
{
- icon = REALM_TYPE_PVP;
+ LOG_ERROR("server.authserver", "Realmlist::UpdateRealms has thrown an exception: %s", ex.what());
+ ABORT();
}
+ } while (result->NextRow());
+ }
- 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);
+ for (auto itr = existingRealms.begin(); itr != existingRealms.end(); ++itr)
+ LOG_INFO("server.authserver", "Removed realm \"%s\".", itr->second.c_str());
- 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());
+ if (_updateInterval)
+ {
+ _updateTimer->expires_from_now(boost::posix_time::seconds(_updateInterval));
+ _updateTimer->async_wait(std::bind(&RealmList::UpdateRealms, this, std::placeholders::_1));
}
}
diff --git a/src/server/shared/Realms/RealmList.h b/src/server/shared/Realms/RealmList.h
index 938fb5aa36..a976cc3b51 100644
--- a/src/server/shared/Realms/RealmList.h
+++ b/src/server/shared/Realms/RealmList.h
@@ -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
diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp
index bda81a18f5..9faf561c9e 100644
--- a/src/server/worldserver/CommandLine/CliRunnable.cpp
+++ b/src/server/worldserver/CommandLine/CliRunnable.cpp
@@ -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", "");
diff --git a/src/server/worldserver/CommandLine/CliRunnable.h b/src/server/worldserver/CommandLine/CliRunnable.h
index 47b424b752..4730f513bd 100644
--- a/src/server/worldserver/CommandLine/CliRunnable.h
+++ b/src/server/worldserver/CommandLine/CliRunnable.h
@@ -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
diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp
index 2e458e8b44..5e6ad698d2 100644
--- a/src/server/worldserver/Main.cpp
+++ b/src/server/worldserver/Main.cpp
@@ -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 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 ret;
+ 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;
+ }
+}
diff --git a/src/server/worldserver/Master.cpp b/src/server/worldserver/Master.cpp
deleted file mode 100644
index 81fc7cddd4..0000000000
--- a/src/server/worldserver/Master.cpp
+++ /dev/null
@@ -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;
-}
diff --git a/src/server/worldserver/Master.h b/src/server/worldserver/Master.h
deleted file mode 100644
index fff192516b..0000000000
--- a/src/server/worldserver/Master.h
+++ /dev/null
@@ -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
-
-/// @}
diff --git a/src/server/worldserver/WorldThread/WorldRunnable.cpp b/src/server/worldserver/WorldThread/WorldRunnable.cpp
deleted file mode 100644
index 9c99a7a7fa..0000000000
--- a/src/server/worldserver/WorldThread/WorldRunnable.cpp
+++ /dev/null
@@ -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.");
-}
diff --git a/src/server/worldserver/WorldThread/WorldRunnable.h b/src/server/worldserver/WorldThread/WorldRunnable.h
deleted file mode 100644
index 742ec02618..0000000000
--- a/src/server/worldserver/WorldThread/WorldRunnable.h
+++ /dev/null
@@ -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
-/// @}
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index 9bbdf6fe33..954bc665e7 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -198,6 +198,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.
# Example: "C:/acore/IP2LOCATION-LITE-DB1.CSV"
diff --git a/src/test/mocks/WorldMock.h b/src/test/mocks/WorldMock.h
index 15c2c8cd1b..d58f0d16f8 100644
--- a/src/test/mocks/WorldMock.h
+++ b/src/test/mocks/WorldMock.h
@@ -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: