diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index c7e9ea99f..3ea6ac4d2 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -37,6 +37,9 @@ set(common_sources
eqemu_logsys.cpp
eqtime.cpp
event_sub.cpp
+ event/event_loop.cpp
+ event/task_scheduler.cpp
+ event/timer.cpp
events/player_event_discord_formatter.cpp
events/player_event_logs.cpp
evolving_items.cpp
@@ -55,7 +58,7 @@ set(common_sources
json_config.cpp
light_source.cpp
md5.cpp
- memory/ksm.hpp
+ memory/ksm.cpp
memory_buffer.cpp
memory_mapped_file.cpp
misc.cpp
@@ -66,6 +69,7 @@ set(common_sources
net/console_server.cpp
net/console_server_connection.cpp
net/crc32.cpp
+ net/dns.cpp
net/eqstream.cpp
net/packet.cpp
net/reliable_stream_connection.cpp
@@ -613,8 +617,8 @@ set(common_headers
item_instance.h
json/json-forwards.h
json/json.h
- json/json_archive_single_line.h
json_config.h
+ json/json_archive_single_line.h
light_source.h
linked_list.h
loot.h
@@ -622,8 +626,9 @@ set(common_headers
md5.h
memory_buffer.h
memory_mapped_file.h
- misc.h
+ memory/ksm.h
misc_functions.h
+ misc.h
mysql_request_result.h
mysql_request_row.h
mysql_stmt.h
diff --git a/common/event/event_loop.cpp b/common/event/event_loop.cpp
new file mode 100644
index 000000000..139c1657e
--- /dev/null
+++ b/common/event/event_loop.cpp
@@ -0,0 +1,57 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "common/event/event_loop.h"
+#include "uv.h"
+
+namespace EQ {
+
+EventLoop& EventLoop::Get()
+{
+ thread_local EventLoop inst;
+ return inst;
+}
+
+EventLoop::EventLoop()
+ : m_loop(std::make_unique())
+{
+ memset(m_loop.get(), 0, sizeof(uv_loop_t));
+ uv_loop_init(m_loop.get());
+}
+
+EventLoop::~EventLoop()
+{
+ uv_loop_close(m_loop.get());
+}
+
+void EventLoop::Process()
+{
+ uv_run(m_loop.get(), UV_RUN_NOWAIT);
+}
+
+void EventLoop::Run()
+{
+ uv_run(m_loop.get(), UV_RUN_DEFAULT);
+}
+
+void EventLoop::Shutdown()
+{
+ uv_stop(m_loop.get());
+}
+
+} // namespace EQ
diff --git a/common/event/event_loop.h b/common/event/event_loop.h
index 3c56ab949..fed72c022 100644
--- a/common/event/event_loop.h
+++ b/common/event/event_loop.h
@@ -17,48 +17,31 @@
*/
#pragma once
-#include "common/platform/win/include_windows.h" // uv.h is going to include it so let's do it first.
-#include "uv.h" // FIXME: hide this
+#include
-#include
+typedef struct uv_loop_s uv_loop_t;
-namespace EQ
+namespace EQ {
+
+class EventLoop
{
- class EventLoop
- {
- public:
- static EventLoop &Get() {
- static thread_local EventLoop inst;
- return inst;
- }
+public:
+ static EventLoop& Get();
- ~EventLoop() {
- uv_loop_close(&m_loop);
- }
+ ~EventLoop();
+ EventLoop(const EventLoop&) = delete;
+ EventLoop& operator=(const EventLoop&) = delete;
- void Process() {
- uv_run(&m_loop, UV_RUN_NOWAIT);
- }
+ void Process();
+ void Run();
+ void Shutdown();
- void Run() {
- uv_run(&m_loop, UV_RUN_DEFAULT);
- }
+ uv_loop_t* Handle() { return m_loop.get(); }
- void Shutdown() {
- uv_stop(&m_loop);
- }
+private:
+ EventLoop();
- uv_loop_t* Handle() { return &m_loop; }
+ std::unique_ptr m_loop;
+};
- private:
- EventLoop() {
- memset(&m_loop, 0, sizeof(uv_loop_t));
- uv_loop_init(&m_loop);
- }
-
- EventLoop(const EventLoop&);
- EventLoop& operator=(const EventLoop&);
-
- uv_loop_t m_loop;
- };
-}
+} // namespace EQ
diff --git a/common/event/task_scheduler.cpp b/common/event/task_scheduler.cpp
new file mode 100644
index 000000000..7ee3894fd
--- /dev/null
+++ b/common/event/task_scheduler.cpp
@@ -0,0 +1,128 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "task_scheduler.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace EQ::Event {
+
+static constexpr int DefaultThreadCount = 4;
+
+struct TaskScheduler::SchedulerData
+{
+ std::atomic_bool running{ false };
+ std::vector threads;
+ std::mutex lock;
+ std::condition_variable cv;
+ std::queue> tasks;
+};
+
+TaskScheduler::TaskScheduler()
+ : m_data(std::make_unique())
+{
+ Start(DefaultThreadCount);
+}
+
+TaskScheduler::TaskScheduler(size_t threads)
+{
+ Start(threads);
+}
+
+TaskScheduler::~TaskScheduler()
+{
+ Stop();
+}
+
+void TaskScheduler::Start(size_t threads)
+{
+ if (m_data->running.exchange(true))
+ return;
+
+ m_data->threads.reserve(threads);
+
+ for (size_t i = 0; i < threads; ++i)
+ {
+ m_data->threads.emplace_back(
+ [this]{ ProcessWork(); });
+ }
+}
+
+void TaskScheduler::Stop()
+{
+ if (!m_data->running.exchange(false))
+ return;
+
+ m_data->cv.notify_all();
+
+ for (auto& t : m_data->threads)
+ {
+ t.join();
+ }
+
+ m_data->threads.clear();
+}
+
+void TaskScheduler::ProcessWork()
+{
+ for (;;)
+ {
+ std::function work;
+
+ {
+ std::unique_lock lock(m_data->lock);
+
+ m_data->cv.wait(lock,
+ [this]
+ {
+ return !m_data->running || m_data->tasks.empty();
+ });
+
+ if (!m_data->running)
+ {
+ return;
+ }
+
+ work = std::move(m_data->tasks.front());
+ m_data->tasks.pop();
+ }
+
+ work();
+ }
+}
+
+void TaskScheduler::AddTask(std::function&& task)
+{
+ if (!m_data->running)
+ {
+ throw std::runtime_error("Enqueue on stopped scheduler.");
+ }
+
+ {
+ std::scoped_lock lock(m_data->lock);
+ m_data->tasks.push(std::move(task));
+ }
+ m_data->cv.notify_one();
+}
+
+} // namespace EQ::Event
diff --git a/common/event/task_scheduler.h b/common/event/task_scheduler.h
index e2e855682..c63e93bdf 100644
--- a/common/event/task_scheduler.h
+++ b/common/event/task_scheduler.h
@@ -17,116 +17,45 @@
*/
#pragma once
-#include
#include
#include
-#include
-#include
-#include
-#include
+#include
-namespace EQ
+namespace EQ::Event {
+
+class TaskScheduler
{
- namespace Event
+public:
+ TaskScheduler();
+ TaskScheduler(size_t threads);
+
+ ~TaskScheduler();
+
+ void Start(size_t threads);
+ void Stop();
+
+ template
+ auto Enqueue(Fn&& fn, Args&&... args) -> std::future::type>
{
- class TaskScheduler
- {
- public:
- static const int DefaultThreadCount = 4;
-
- TaskScheduler() : _running(false)
+ using return_type = typename std::invoke_result::type;
+
+ auto task = std::make_shared>(
+ [fn = std::forward(fn), ...args = std::forward(args)]() mutable
{
- Start(DefaultThreadCount);
- }
-
- TaskScheduler(size_t threads) : _running(false)
- {
- Start(threads);
+ return fn(std::forward(args)...);
}
+ );
- ~TaskScheduler() {
- Stop();
- }
-
- void Start(size_t threads) {
- if (true == _running) {
- return;
- }
-
- _running = true;
-
- for (size_t i = 0; i < threads; ++i) {
- _threads.emplace_back(std::thread(std::bind(&TaskScheduler::ProcessWork, this)));
- }
- }
-
- void Stop() {
- if (false == _running) {
- return;
- }
-
- {
- std::unique_lock lock(_lock);
- _running = false;
- }
-
- _cv.notify_all();
-
- for (auto &t : _threads) {
- t.join();
- }
- }
-
- template
- auto Enqueue(Fn&& fn, Args&&... args) -> std::future::type> {
- using return_type = typename std::invoke_result::type;
-
- auto task = std::make_shared>(
- std::bind(std::forward(fn), std::forward(args)...)
- );
-
- std::future res = task->get_future();
- {
- std::unique_lock lock(_lock);
-
- if (false == _running) {
- throw std::runtime_error("Enqueue on stopped scheduler.");
- }
-
- _tasks.emplace([task]() { (*task)(); });
- }
-
- _cv.notify_one();
- return res;
- }
-
- private:
- void ProcessWork() {
- for (;;) {
- std::function work;
-
- {
- std::unique_lock lock(_lock);
- _cv.wait(lock, [this] { return !_running || !_tasks.empty(); });
-
- if (false == _running) {
- return;
- }
-
- work = std::move(_tasks.front());
- _tasks.pop();
- }
-
- work();
- }
-
- }
-
- bool _running = true;
- std::vector _threads;
- std::mutex _lock;
- std::condition_variable _cv;
- std::queue> _tasks;
- };
+ AddTask([task] { (*task)(); });
+ return task->get_future();
}
-}
+
+private:
+ void AddTask(std::function&& task);
+ void ProcessWork();
+
+ struct SchedulerData;
+ std::unique_ptr m_data;
+};
+
+} // namespace EQ::Event
diff --git a/common/event/timer.cpp b/common/event/timer.cpp
new file mode 100644
index 000000000..be541c452
--- /dev/null
+++ b/common/event/timer.cpp
@@ -0,0 +1,90 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "common/event/timer.h"
+#include "uv.h"
+
+namespace EQ {
+
+Timer::Timer(callback_t cb)
+ : m_cb(std::move(cb))
+{
+}
+
+Timer::Timer(uint64_t duration_ms, bool repeats, callback_t cb)
+ : m_cb(std::move(cb))
+{
+ Start(duration_ms, repeats);
+}
+
+Timer::~Timer()
+{
+ Stop();
+}
+
+void Timer::Start(uint64_t duration_ms, bool repeats)
+{
+ if (!m_timer)
+ {
+ uv_loop_t* loop = EventLoop::Get().Handle();
+
+ m_timer = std::make_unique();
+ memset(m_timer.get(), 0, sizeof(uv_timer_t));
+
+ uv_timer_init(loop, m_timer.get());
+ m_timer->data = this;
+
+ if (repeats)
+ {
+ uv_timer_start(m_timer.get(), [](uv_timer_t* handle)
+ {
+ Timer* t = static_cast(handle->data);
+ t->Execute();
+ }, duration_ms, duration_ms);
+ }
+ else
+ {
+ uv_timer_start(m_timer.get(),
+ [](uv_timer_t* handle)
+ {
+ Timer* t = static_cast(handle->data);
+ t->Stop();
+ t->Execute();
+ }, duration_ms, 0);
+ }
+ }
+}
+
+void Timer::Stop()
+{
+ if (m_timer)
+ {
+ uv_close(reinterpret_cast(m_timer.release()),
+ [](uv_handle_t* handle)
+ {
+ delete reinterpret_cast(handle);
+ });
+ }
+}
+
+void Timer::Execute()
+{
+ m_cb(this);
+}
+
+} // namespace EQ
diff --git a/common/event/timer.h b/common/event/timer.h
index 2cea8072b..61780dfe3 100644
--- a/common/event/timer.h
+++ b/common/event/timer.h
@@ -19,69 +19,31 @@
#include "event_loop.h"
-#include
#include
+#include
+
+typedef struct uv_timer_s uv_timer_t;
namespace EQ {
- class Timer
- {
- public:
- Timer(std::function cb)
- {
- m_timer = nullptr;
- m_cb = cb;
- }
- Timer(uint64_t duration_ms, bool repeats, std::function cb)
- {
- m_timer = nullptr;
- m_cb = cb;
- Start(duration_ms, repeats);
- }
+class Timer
+{
+public:
+ using callback_t = std::function;
+
+ Timer(callback_t cb);
+ Timer(uint64_t duration_ms, bool repeats, callback_t cb);
- ~Timer()
- {
- Stop();
- }
+ ~Timer();
- void Start(uint64_t duration_ms, bool repeats) {
- auto loop = EventLoop::Get().Handle();
- if (!m_timer) {
- m_timer = new uv_timer_t;
- memset(m_timer, 0, sizeof(uv_timer_t));
- uv_timer_init(loop, m_timer);
- m_timer->data = this;
+ void Start(uint64_t duration_ms, bool repeats);
+ void Stop();
- if (repeats) {
- uv_timer_start(m_timer, [](uv_timer_t *handle) {
- Timer *t = (Timer*)handle->data;
- t->Execute();
- }, duration_ms, duration_ms);
- }
- else {
- uv_timer_start(m_timer, [](uv_timer_t *handle) {
- Timer *t = (Timer*)handle->data;
- t->Stop();
- t->Execute();
- }, duration_ms, 0);
- }
- }
- }
-
- void Stop() {
- if (m_timer) {
- uv_close((uv_handle_t*)m_timer, [](uv_handle_t* handle) {
- delete (uv_timer_t *)handle;
- });
- m_timer = nullptr;
- }
- }
- private:
- void Execute() {
- m_cb(this);
- }
+private:
+ void Execute();
- uv_timer_t *m_timer;
- std::function m_cb;
- };
-}
+ std::unique_ptr m_timer;
+ callback_t m_cb;
+};
+
+} // namespace EQ
diff --git a/common/ip_util.cpp b/common/ip_util.cpp
index c1af78263..0801fb370 100644
--- a/common/ip_util.cpp
+++ b/common/ip_util.cpp
@@ -25,14 +25,24 @@
#include "common/net/dns.h"
#include "fmt/format.h"
-#include
-#include
+#include
+#include
#include
-/**
- * @param ip
- * @return
- */
+#ifdef _WIN32
+#include
+#include
+#pragma comment(lib, "Ws2_32.lib")
+#else
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+
uint32_t IpUtil::IPToUInt(const std::string &ip)
{
int a, b, c, d;
@@ -49,12 +59,6 @@ uint32_t IpUtil::IPToUInt(const std::string &ip)
return addr;
}
-/**
- * @param ip
- * @param network
- * @param mask
- * @return
- */
bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, const std::string &mask)
{
uint32_t ip_addr = IpUtil::IPToUInt(ip);
@@ -67,10 +71,6 @@ bool IpUtil::IsIpInRange(const std::string &ip, const std::string &network, cons
return ip_addr >= net_lower && ip_addr <= net_upper;
}
-/**
- * @param ip
- * @return
- */
bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip)
{
return (
@@ -80,30 +80,13 @@ bool IpUtil::IsIpInPrivateRfc1918(const std::string &ip)
);
}
-
-#ifdef _WIN32
-#include
- #include
- #pragma comment(lib, "Ws2_32.lib")
-#else
-#include
-#include
-#include
-#include
-#include
-#endif
-
-#include
-#include
-#include
-
std::string IpUtil::GetLocalIPAddress()
{
#ifdef _WIN32
WSADATA wsaData;
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
- return "";
- }
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
+ return "";
+ }
#endif
char my_ip_address[INET_ADDRSTRLEN];
@@ -114,10 +97,10 @@ std::string IpUtil::GetLocalIPAddress()
// Create a UDP socket
#ifdef _WIN32
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
- if (sockfd == INVALID_SOCKET) {
- WSACleanup();
- return "";
- }
+ if (sockfd == INVALID_SOCKET) {
+ WSACleanup();
+ return "";
+ }
#else
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
@@ -200,98 +183,24 @@ std::string IpUtil::GetPublicIPAddress()
return {};
}
-std::string IpUtil::DNSLookupSync(const std::string &addr, int port)
-{
- auto task_runner = new EQ::Event::TaskScheduler();
- auto res = task_runner->Enqueue(
- [&]() -> std::string {
- bool running = true;
- std::string ret;
-
- EQ::Net::DNSLookup(
- addr, port, false, [&](const std::string &addr) {
- ret = addr;
- if (addr.empty()) {
- ret = "";
- running = false;
- }
-
- return ret;
- }
- );
-
- std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
-
- auto &loop = EQ::EventLoop::Get();
- while (running) {
- if (!ret.empty()) {
- running = false;
- }
-
- std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
- if (std::chrono::duration_cast(end - begin).count() > 1500) {
- LogInfo(
- "Deadline exceeded [{}]",
- 1500
- );
- running = false;
- }
-
- loop.Process();
- }
-
- return ret;
- }
- );
-
- std::string result = res.get();
- safe_delete(task_runner);
-
- return result;
-}
-
bool IpUtil::IsIPAddress(const std::string &ip_address)
{
- struct sockaddr_in sa{};
- int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr));
+ sockaddr_in sa{};
+ int result = inet_pton(AF_INET, ip_address.c_str(), &(sa.sin_addr));
return result != 0;
}
-#include
-#ifdef _WIN32
-#include
- #pragma comment(lib, "ws2_32.lib") // Link against Winsock library
-#else
-#include
-#include
-#include
-#include
-#endif
-
-#include
-#include
-#ifdef _WIN32
-#include
- #include // For inet_pton
- #pragma comment(lib, "ws2_32.lib") // Link against Winsock library
-#else
-#include
-#include
-#include
-#include // For inet_pton
-#include
-#endif
-
-bool IpUtil::IsPortInUse(const std::string& ip, int port) {
+bool IpUtil::IsPortInUse(const std::string& ip, int port)
+{
bool in_use = false;
#ifdef _WIN32
WSADATA wsaData;
- if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
- std::cerr << "WSAStartup failed\n";
- return true; // Assume in use on failure
- }
+ if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
+ std::cerr << "WSAStartup failed\n";
+ return true; // Assume in use on failure
+ }
#endif
int sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -319,20 +228,20 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
std::cerr << "Invalid IP address format: " << ip << std::endl;
#ifdef _WIN32
closesocket(sock);
- WSACleanup();
+ WSACleanup();
#else
close(sock);
#endif
return true; // Assume in use on failure
}
- if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ if (bind(sock, (sockaddr*)&addr, sizeof(addr)) < 0) {
in_use = true; // Bind failed, port is in use
}
#ifdef _WIN32
closesocket(sock);
- WSACleanup();
+ WSACleanup();
#else
close(sock);
#endif
diff --git a/common/ip_util.h b/common/ip_util.h
index 9d04263d4..f779bf875 100644
--- a/common/ip_util.h
+++ b/common/ip_util.h
@@ -29,10 +29,7 @@ public:
static bool IsIpInPrivateRfc1918(const std::string &ip);
static std::string GetLocalIPAddress();
static std::string GetPublicIPAddress();
- static std::string DNSLookupSync(
- const std::string &addr,
- int port
- );
+
static bool IsIPAddress(const std::string &ip_address);
static bool IsPortInUse(const std::string& ip, int port);
diff --git a/common/memory/ksm.cpp b/common/memory/ksm.cpp
new file mode 100644
index 000000000..874ebedc9
--- /dev/null
+++ b/common/memory/ksm.cpp
@@ -0,0 +1,157 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#include "ksm.h"
+
+
+#ifdef _WIN32
+#include
+#else
+#include // For madvise
+#include // For sysconf, sbrk
+#endif
+
+
+#ifdef _WIN32
+// Windows-specific functionality
+
+void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment)
+{
+ void* ptr = malloc(size);
+
+ if (!ptr)
+ {
+ throw std::bad_alloc();
+ }
+
+ return ptr;
+}
+
+size_t PageAlignedAllocatorBase::getPageSize() const
+{
+ SYSTEM_INFO sysInfo;
+ GetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize; // Page size in bytes
+}
+
+#else
+
+// Linux-specific functionality
+
+void* PageAlignedAllocatorBase::allocateInternal(size_t size, size_t alignment)
+{
+ void* ptr = nullptr;
+
+ if (posix_memalign(&ptr, alignment, size) != 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ return ptr;
+}
+
+size_t PageAlignedAllocatorBase::getPageSize() const
+{
+ return static_cast(sysconf(_SC_PAGESIZE));
+}
+
+void CheckPageAlignment(void* ptr)
+{
+ size_t page_size = sysconf(_SC_PAGESIZE);
+
+ if (reinterpret_cast(ptr) % page_size == 0)
+ {
+ LogKSMDetail("Memory is page-aligned [{}]", ptr);
+ }
+ else
+ {
+ LogKSMDetail("Memory is NOT page-aligned [{}]", ptr);
+ }
+}
+
+void* AllocatePageAligned(size_t size)
+{
+ size_t page_size = sysconf(_SC_PAGESIZE);
+ void* aligned_ptr = nullptr;
+
+ if (posix_memalign(&aligned_ptr, page_size, size) != 0)
+ {
+ LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size);
+ }
+
+ std::memset(aligned_ptr, 0, size);
+ return aligned_ptr;
+}
+
+void MarkMemoryForKSM(void* start, size_t size)
+{
+ if (madvise(start, size, MADV_MERGEABLE) == 0)
+ {
+ LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size);
+ }
+ else
+ {
+ perror("madvise failed");
+ }
+}
+
+void AlignHeapToPageBoundary()
+{
+ size_t page_size = sysconf(_SC_PAGESIZE);
+ if (page_size == 0)
+ {
+ LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size);
+ return;
+ }
+
+ void* current_break = sbrk(0);
+ if (current_break == (void*)-1)
+ {
+ LogKSM("Failed to retrieve the current program break");
+ return;
+ }
+
+ uintptr_t current_address = reinterpret_cast(current_break);
+ size_t misalignment = current_address % page_size;
+
+ if (misalignment != 0)
+ {
+ size_t adjustment = page_size - misalignment;
+ if (sbrk(adjustment) == (void*)-1)
+ {
+ LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment);
+ return;
+ }
+ }
+
+ LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0));
+}
+
+void* MarkHeapStart()
+{
+ void* current_pos = sbrk(0);
+ AlignHeapToPageBoundary();
+ return current_pos;
+}
+
+size_t MeasureHeapUsage(void* start)
+{
+ void* current_break = sbrk(0);
+ return static_cast(current_break) - static_cast(start);
+}
+
+#endif
diff --git a/common/memory/ksm.h b/common/memory/ksm.h
new file mode 100644
index 000000000..7f03c9f87
--- /dev/null
+++ b/common/memory/ksm.h
@@ -0,0 +1,107 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#pragma once
+
+#include "common/eqemu_logsys.h"
+
+class PageAlignedAllocatorBase
+{
+protected:
+ void* allocateInternal(size_t amount, size_t alignment);
+ size_t getPageSize() const;
+};
+
+// Page-aligned allocator for std::vector
+template
+class PageAlignedAllocator : public PageAlignedAllocatorBase
+{
+public:
+ using value_type = T;
+
+ PageAlignedAllocator() noexcept = default;
+
+ template
+ PageAlignedAllocator(const PageAlignedAllocator&) noexcept {}
+
+ T* allocate(std::size_t n)
+ {
+ size_t size = n * sizeof(T);
+
+ return static_cast(allocateInternal(size, getPageSize()));
+ }
+
+ void deallocate(T* p, std::size_t) noexcept
+ {
+ free(p);
+ }
+};
+
+template
+bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept {
+ return true;
+}
+
+template
+bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept {
+ return false;
+}
+
+// Kernel Samepage Merging (KSM) functionality
+namespace KSM {
+
+#ifdef _WIN32
+
+// Windows-specific placeholder functions (no-op)
+inline void CheckPageAlignment(void* ptr)
+{
+}
+
+inline void* AllocatePageAligned(size_t size)
+{
+ return memset(malloc(size), 0, size);
+}
+
+inline void MarkMemoryForKSM(void* start, size_t size)
+{
+}
+
+inline void AlignHeapToPageBoundary()
+{
+}
+
+inline void* MarkHeapStart()
+{
+ return nullptr;
+}
+
+inline size_t MeasureHeapUsage(void* start)
+{
+ return 0;
+}
+
+#else
+
+void CheckPageAlignment(void* ptr);
+void* AllocatePageAligned(size_t size);
+void MarkMemoryForKSM(void* start, size_t size);
+void AlignHeapToPageBoundary();
+void* MarkHeapStart();
+size_t MeasureHeapUsage(void* start);
+
+#endif
+}
diff --git a/common/memory/ksm.hpp b/common/memory/ksm.hpp
deleted file mode 100644
index 9d4b28273..000000000
--- a/common/memory/ksm.hpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/* EQEmu: EQEmulator
-
- Copyright (C) 2001-2026 EQEmu Development Team
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-#pragma once
-
-#include "common/eqemu_logsys.h"
-
-#include
-#include
-#include
-#ifdef _WIN32
-#include // For _aligned_malloc, _aligned_free
-#include
-#else
-#include // For madvise
-#include // For sysconf, sbrk
-#endif
-
-
-// Page-aligned allocator for std::vector
-template
-class PageAlignedAllocator {
-public:
- using value_type = T;
-
- PageAlignedAllocator() noexcept = default;
- template
- PageAlignedAllocator(const PageAlignedAllocator&) noexcept {}
-
- T* allocate(std::size_t n) {
- void* ptr = nullptr;
- size_t size = n * sizeof(T);
-
-#ifdef _WIN32
- // Simply allocate memory without alignment
- ptr = malloc(size);
- if (!ptr) throw std::bad_alloc();
-#else
- size_t alignment = getPageSize(); // Get the system's page size
- if (posix_memalign(&ptr, alignment, size) != 0) {
- throw std::bad_alloc();
- }
-#endif
- return static_cast(ptr);
- }
-
- void deallocate(T* p, std::size_t) noexcept {
- free(p);
- }
-
-private:
- size_t getPageSize() const
- {
-#ifdef _WIN32
- SYSTEM_INFO sysInfo;
- GetSystemInfo(&sysInfo);
- return sysInfo.dwPageSize; // Page size in bytes
-#else
- return static_cast(sysconf(_SC_PAGESIZE));
-#endif
- };
-};
-
-template
-bool operator==(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept {
- return true;
-}
-
-template
-bool operator!=(const PageAlignedAllocator&, const PageAlignedAllocator&) noexcept {
- return false;
-}
-
-// Kernel Samepage Merging (KSM) functionality
-namespace KSM {
-
-#ifdef _WIN32
- // Windows-specific placeholder functions (no-op)
- inline void CheckPageAlignment(void* ptr) {
- }
-
- inline void* AllocatePageAligned(size_t size) {
- return memset(malloc(size), 0, size);
- }
-
- inline void MarkMemoryForKSM(void* start, size_t size) {
- }
-
- inline void AlignHeapToPageBoundary() {
- }
-
- inline void* MarkHeapStart() {
- return nullptr;
- }
-
- inline size_t MeasureHeapUsage(void* start) {
- return 0;
- }
-#else
- // Linux-specific functionality
- inline void CheckPageAlignment(void* ptr) {
- size_t page_size = sysconf(_SC_PAGESIZE);
- if (reinterpret_cast(ptr) % page_size == 0) {
- LogKSMDetail("Memory is page-aligned [{}]", ptr);
- } else {
- LogKSMDetail("Memory is NOT page-aligned [{}]", ptr);
- }
- }
-
- inline void* AllocatePageAligned(size_t size) {
- size_t page_size = sysconf(_SC_PAGESIZE);
- void* aligned_ptr = nullptr;
- if (posix_memalign(&aligned_ptr, page_size, size) != 0) {
- LogKSM("Failed to allocate page-aligned memory on Linux. page_size [{}] size [{}] bytes", page_size, size);
- }
- std::memset(aligned_ptr, 0, size);
- return aligned_ptr;
- }
-
- inline void MarkMemoryForKSM(void* start, size_t size) {
- if (madvise(start, size, MADV_MERGEABLE) == 0) {
- LogKSM("Marked memory for KSM | start [{}] size [{}] bytes", start, size);
- } else {
- perror("madvise failed");
- }
- }
-
- inline void AlignHeapToPageBoundary() {
- size_t page_size = sysconf(_SC_PAGESIZE);
- if (page_size == 0) {
- LogKSM("Failed to retrieve page size SC_PAGESIZE [{}]", page_size);
- return;
- }
-
- void* current_break = sbrk(0);
- if (current_break == (void*)-1) {
- LogKSM("Failed to retrieve the current program break");
- return;
- }
-
- uintptr_t current_address = reinterpret_cast(current_break);
- size_t misalignment = current_address % page_size;
-
- if (misalignment != 0) {
- size_t adjustment = page_size - misalignment;
- if (sbrk(adjustment) == (void*)-1) {
- LogKSM("Failed to align heap to page boundary. adjustment [{}] bytes", adjustment);
- return;
- }
- }
-
- LogKSMDetail("Heap aligned to next page boundary. Current break [{}]", sbrk(0));
- }
-
- inline void* MarkHeapStart() {
- void* current_pos = sbrk(0);
- AlignHeapToPageBoundary();
- return current_pos;
- }
-
- inline size_t MeasureHeapUsage(void* start) {
- void* current_break = sbrk(0);
- return static_cast(current_break) - static_cast(start);
- }
-#endif
-
-
- inline size_t getPageSize()
- {
-#ifdef _WIN32
- SYSTEM_INFO sysInfo;
- GetSystemInfo(&sysInfo);
- return sysInfo.dwPageSize; // Page size in bytes
-#else
- return static_cast(sysconf(_SC_PAGESIZE)); // POSIX page size
-#endif
- };
-
- template
- inline void PageAlignVectorAligned(std::vector>& vec) {
- if (vec.empty()) {
- return;
- }
-
- size_t page_size = getPageSize();
- void* start = vec.data();
- size_t size = vec.size() * sizeof(T);
-
- // Check if the memory is page-aligned
- if (reinterpret_cast(start) % page_size != 0) {
- // Allocate a new aligned vector
- std::vector> aligned_vec(vec.get_allocator());
- aligned_vec.reserve(vec.capacity()); // Match capacity to avoid reallocation during copy
-
- // Copy elements from the original vector
- aligned_vec.insert(aligned_vec.end(), vec.begin(), vec.end());
-
- // Swap the aligned vector with the original vector
- vec.swap(aligned_vec);
-
- // Clear the temporary aligned vector to free its memory
- aligned_vec.clear();
-
- // Verify the new alignment
- start = vec.data();
- if (reinterpret_cast(start) % page_size != 0) {
- throw std::runtime_error("Failed to align vector memory to page boundaries.");
- }
-
- LogKSMDetail("Vector reallocated to ensure page alignment. start [{}] size [{}] bytes", start, size);
- } else {
- LogKSMDetail("Vector is already page-aligned. start [{}] size [{}] bytes", start, size);
- }
-
-#ifndef _WIN32
- // Mark memory for KSM (only on non-Windows systems)
- MarkMemoryForKSM(start, size);
-#endif
- }
-}
diff --git a/common/net/dns.cpp b/common/net/dns.cpp
new file mode 100644
index 000000000..fa232586c
--- /dev/null
+++ b/common/net/dns.cpp
@@ -0,0 +1,131 @@
+/* EQEmu: EQEmulator
+
+ Copyright (C) 2001-2026 EQEmu Development Team
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "dns.h"
+
+#include "common/eqemu_logsys.h"
+#include "common/event/event_loop.h"
+#include "common/event/task_scheduler.h"
+
+#include "uv.h"
+
+namespace EQ::Net {
+
+struct DNSBaton
+{
+ dns_callback_t cb;
+ bool ipv6;
+};
+
+void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb)
+{
+ addrinfo hints = {};
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ uv_loop_t* loop = EQ::EventLoop::Get().Handle();
+
+ uv_getaddrinfo_t* resolver = new uv_getaddrinfo_t();
+ memset(resolver, 0, sizeof(uv_getaddrinfo_t));
+ std::string port_str = std::to_string(port);
+ DNSBaton* baton = new DNSBaton();
+ baton->cb = std::move(cb);
+ baton->ipv6 = ipv6;
+ resolver->data = baton;
+
+ uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* result)
+ {
+ DNSBaton* baton = static_cast(req->data);
+
+ dns_callback_t dns_callback = std::move(baton->cb);
+ bool ipv6 = baton->ipv6;
+
+ delete baton;
+ delete req;
+
+ if (status < 0)
+ {
+ dns_callback({});
+ return;
+ }
+
+ char addr[40] = {};
+
+ if (ipv6)
+ {
+ uv_ip6_name(reinterpret_cast(result->ai_addr), addr, 40);
+ }
+ else
+ {
+ uv_ip4_name(reinterpret_cast(result->ai_addr), addr, 40);
+ }
+
+ uv_freeaddrinfo(result);
+
+ dns_callback(addr);
+ }, addr.c_str(), port_str.c_str(), &hints);
+}
+
+std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 /* = false */)
+{
+ EQ::Event::TaskScheduler task_runner;
+
+ auto res = task_runner.Enqueue(
+ [addr, port, ipv6]() -> std::string
+ {
+ bool running = true;
+ std::string ret;
+
+ EQ::Net::DNSLookup(
+ addr, port, ipv6, [&](const std::string& addr) {
+ ret = addr;
+ running = !addr.empty();
+
+ return ret;
+ }
+ );
+
+ std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
+
+ auto& loop = EQ::EventLoop::Get();
+ while (running) {
+ if (!ret.empty()) {
+ running = false;
+ }
+
+ std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
+ if (std::chrono::duration_cast(end - begin).count() > 1500) {
+ LogInfo(
+ "Deadline exceeded [{}]",
+ 1500
+ );
+ running = false;
+ }
+
+ loop.Process();
+ }
+
+ return ret;
+ });
+
+ return res.get();
+}
+
+
+} // namespace EQ::Net
diff --git a/common/net/dns.h b/common/net/dns.h
index 2b799ef4f..04f97a4e9 100644
--- a/common/net/dns.h
+++ b/common/net/dns.h
@@ -17,63 +17,15 @@
*/
#pragma once
-#include "common/event/event_loop.h"
-
#include
#include
-namespace EQ
-{
- namespace Net
- {
- static void DNSLookup(const std::string &addr, int port, bool ipv6, std::function cb) {
- struct DNSBaton
- {
- std::function cb;
- bool ipv6;
- };
+namespace EQ::Net {
- addrinfo hints;
- memset(&hints, 0, sizeof(addrinfo));
- hints.ai_family = PF_INET;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
+using dns_callback_t = std::function;
- auto loop = EQ::EventLoop::Get().Handle();
- uv_getaddrinfo_t *resolver = new uv_getaddrinfo_t();
- memset(resolver, 0, sizeof(uv_getaddrinfo_t));
- auto port_str = std::to_string(port);
- DNSBaton *baton = new DNSBaton();
- baton->cb = cb;
- baton->ipv6 = ipv6;
- resolver->data = baton;
+void DNSLookup(const std::string& addr, int port, bool ipv6, dns_callback_t cb);
- uv_getaddrinfo(loop, resolver, [](uv_getaddrinfo_t* req, int status, addrinfo* res) {
- DNSBaton *baton = (DNSBaton*)req->data;
- if (status < 0) {
- auto cb = baton->cb;
- delete baton;
- delete req;
- cb("");
- return;
- }
+std::string DNSLookupSync(const std::string& addr, int port, bool ipv6 = false);
- char addr[40] = { 0 };
-
- if (baton->ipv6) {
- uv_ip6_name((struct sockaddr_in6*)res->ai_addr, addr, 40);
- }
- else {
- uv_ip4_name((struct sockaddr_in*)res->ai_addr, addr, 40);
- }
-
- auto cb = baton->cb;
- delete baton;
- delete req;
- uv_freeaddrinfo(res);
-
- cb(addr);
- }, addr.c_str(), port_str.c_str(), &hints);
- }
- }
-}
+} // namespace EQ::Net
diff --git a/world/cli/cli_test.cpp b/world/cli/cli_test.cpp
index 0353e4815..4878b4f39 100644
--- a/world/cli/cli_test.cpp
+++ b/world/cli/cli_test.cpp
@@ -18,7 +18,7 @@
#include "world/world_server_cli.h"
#include "common/events/player_events.h"
-#include "common/memory/ksm.hpp"
+#include "common/memory/ksm.h"
#include "cereal/archives/json.hpp"
#include "cereal/types/vector.hpp"
diff --git a/world/world_boot.cpp b/world/world_boot.cpp
index 3a07a6632..97f4a172b 100644
--- a/world/world_boot.cpp
+++ b/world/world_boot.cpp
@@ -24,8 +24,7 @@
#include "common/http/httplib.h"
#include "common/http/uri.h"
#include "common/ip_util.h"
-#include "common/net/console_server.h"
-#include "common/net/servertalk_server.h"
+#include "common/net/dns.h"
#include "common/path_manager.h"
#include "common/repositories/character_expedition_lockouts_repository.h"
#include "common/repositories/character_task_timers_repository.h"
@@ -33,7 +32,6 @@
#include "common/rulesys.h"
#include "common/strings.h"
#include "common/zone_store.h"
-#include "common/zone_store.h"
#include "world/adventure_manager.h"
#include "world/dynamic_zone_manager.h"
#include "world/login_server_list.h"
@@ -456,7 +454,7 @@ void WorldBoot::CheckForPossibleConfigurationIssues()
std::string config_address = c->WorldAddress;
if (!IpUtil::IsIPAddress(config_address)) {
- config_address = IpUtil::DNSLookupSync(c->WorldAddress, 9000);
+ config_address = EQ::Net::DNSLookupSync(c->WorldAddress, 9000);
LogInfo(
"World config address using DNS [{}] resolves to [{}]",
c->WorldAddress,
diff --git a/zone/map.cpp b/zone/map.cpp
index 65d7f30d7..0fba4d021 100644
--- a/zone/map.cpp
+++ b/zone/map.cpp
@@ -17,7 +17,7 @@
*/
#include "common/compression.h"
#include "common/file.h"
-#include "common/memory/ksm.hpp"
+#include "common/memory/ksm.h"
#include "common/misc_functions.h"
#include "zone/client.h"
#include "zone/map.h"
diff --git a/zone/raycast_mesh.cpp b/zone/raycast_mesh.cpp
index 64e10a523..a6c92ed1b 100644
--- a/zone/raycast_mesh.cpp
+++ b/zone/raycast_mesh.cpp
@@ -18,7 +18,7 @@
#include "raycast_mesh.h"
#include "common/eqemu_logsys.h"
-#include "common/memory/ksm.hpp"
+#include "common/memory/ksm.h"
#include
#include