From 247205020f3a0c78ad09911aca1af71a3dd5c10a Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:47:43 +0100 Subject: [PATCH 1/7] Revert "Revert the socket stuff, for now" This reverts commit f2a275d7fd6c40a98cc816629f2f51f44d1d332d. --- docs/game_data/spel2.lua | 12 ++- docs/src/includes/_globals.md | 5 +- docs/src/includes/_types.md | 5 ++ examples/udp.lua | 82 ++++++++++++++++++++ src/game_api/script/usertypes/socket_lua.cpp | 16 ++-- src/game_api/socket.cpp | 51 ++++++++---- src/game_api/socket.hpp | 14 +++- 7 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 examples/udp.lua diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index f65ec4daa..c4e3535ac 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1403,11 +1403,12 @@ function toggle_journal() end ---@param page integer ---@return nil function show_journal(chapter, page) end ----Start an UDP server on specified address and run callback when data arrives. Return a string from the callback to reply. Requires unsafe mode. ----The server will be closed once the handle is released. +---Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. +---The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. +---The callback signature is optional on_message(string msg, string src) ---@param host string ---@param port integer ----@param cb function +---@param cb fun(msg: string, src: string): string? ---@return UdpServer function udp_listen(host, port, cb) end ---Send data to specified UDP address. Requires unsafe mode. @@ -6568,6 +6569,11 @@ function Quad:is_point_inside(x, y, epsilon) end ---@field lava_bubbles_positions ScreenArenaScoreLavaBubble[] @size: 15 ---@class UdpServer + ---@field close fun(self): nil @Closes the server. + ---@field open fun(self): boolean @Returns true if the port was opened successfully and the server hasn't been closed yet. + ---@field error fun(self): string @Returns a string explaining the last error, at least if open() returned false. + ---@field host string + ---@field port integer ---@class LogicList ---@field tutorial LogicTutorial @Handles dropping of the torch and rope in intro routine (first time play) diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 8a6291a92..48c45b064 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -2530,8 +2530,9 @@ The callback signature is nil on_data(string response, string error) #### [UdpServer](#UdpServer) udp_listen(string host, int port, function cb) -Start an UDP server on specified address and run callback when data arrives. Return a string from the callback to reply. Requires unsafe mode. -The server will be closed once the handle is released. +Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. +The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. +
The callback signature is optional on_message(string msg, string src) ### udp_send diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index a026e4caf..de23c4bec 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -1096,6 +1096,11 @@ tuple<[Vec2](#Vec2), [Vec2](#Vec2), [Vec2](#Vec2)> | [split()](https://git Type | Name | Description ---- | ---- | ----------- +nil | [close()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=close) | Closes the server. +bool | [open()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=open) | Returns true if the port was opened successfully and the server hasn't been closed yet. +string | [error()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=error) | Returns a string explaining the last error, at least if open() returned false. +string | [host](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=host) | +int | [port](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=port) | ### Vec2 diff --git a/examples/udp.lua b/examples/udp.lua new file mode 100644 index 000000000..2ed5b2bf8 --- /dev/null +++ b/examples/udp.lua @@ -0,0 +1,82 @@ +meta = { + name = "UDP echo example", + description = "Opens UDP server on a random port and echoes sent messages, closes the server on 'gg'", + author = "Dregu", + unsafe = true +} + +options = { + host = "127.0.0.1", + port = 0 +} + +function init() + deinit() + + server = { + -- Save messages to queue instead of printing directly, cause that doesn't work from the server thread + queue = {}, + + -- Open server on an ephemeral random port, push messages to queue and echo back to sender + socket = udp_listen(options.host, options.port, function(msg, src) + msg = msg:gsub("%s*$", "") + table.insert(server.queue, { msg = msg, src = src }) + if msg == "gg" then + return "bye!\n" + else + return "echo: " .. msg .. "\n" + end + end) + } + + -- If port was opened successfully, start checking the message queue + if server.socket:open() then + print(F "Listening on {server.socket.port}, please send some UDP datagrams or 'gg' to close") + server.inter = set_global_interval(function() + for _, msg in pairs(server.queue) do + print(F "Received: '{msg.msg}' from {msg.src}") + if msg.msg == "gg" then + server.socket:close() + clear_callback() + print("Server is now closed, have a nice day") + end + end + server.queue = {} + end, 1) + else + print(F "Failed to open server: {server.socket:error()}") + end +end + +function deinit() + if server then + server.socket:close() + if server.inter then clear_callback(server.inter) end + server = nil + end +end + +set_callback(init, ON.LOAD) +set_callback(init, ON.SCRIPT_ENABLE) +set_callback(deinit, ON.SCRIPT_DISABLE) + +register_option_callback("x", nil, function(ctx) + options.host = ctx:win_input_text("Host", options.host) + options.port = ctx:win_input_int("Port", options.port) + if options.port < 0 then options.port = 0 end + if options.port > 65535 then options.port = 65535 end + if ctx:win_button("Start server") then init() end + ctx:win_inline() + if ctx:win_button("Stop server") then deinit() end + if server then + ctx:win_text(server.socket:error()) + end + if server and server.socket:open() then + ctx:win_text(F "Listening on {server.socket.host}:{server.socket.port}\nTry sending something with udp_send:") + if ctx:win_button("Send 'Hello World!'") then udp_send(server.socket.host, server.socket.port, "Hello World!") end + ctx:win_inline() + if ctx:win_button("Send 'gg'") then udp_send(server.socket.host, server.socket.port, "gg") end + else + ctx:win_text("Stopped") + end +end) diff --git a/src/game_api/script/usertypes/socket_lua.cpp b/src/game_api/script/usertypes/socket_lua.cpp index 364c31ef6..2de673063 100644 --- a/src/game_api/script/usertypes/socket_lua.cpp +++ b/src/game_api/script/usertypes/socket_lua.cpp @@ -13,15 +13,15 @@ namespace NSocket void register_usertypes(sol::state& lua) { lua.new_usertype( - "UdpServer", - sol::no_constructor); - /// Start an UDP server on specified address and run callback when data arrives. Return a string from the callback to reply. Requires unsafe mode. - /// The server will be closed once the handle is released. - lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) -> UdpServer* + "UdpServer", sol::no_constructor, "close", &UdpServer::close, "open", &UdpServer::open, "error", &UdpServer::error, "host", sol::readonly(&UdpServer::host), "port", sol::readonly(&UdpServer::port)); + + /// Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. + /// The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. + ///
The callback signature is optional on_message(string msg, string src) + // lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) -> UdpServer* + lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) { - // TODO: change the return to std::unique_ptr after fixing the dead lock with the destructor - UdpServer* server = new UdpServer(std::move(host), std::move(port), make_safe_cb(std::move(cb))); - return server; + return std::unique_ptr{new UdpServer(std::move(host), std::move(port), make_safe_cb(std::move(cb)))}; }; /// Send data to specified UDP address. Requires unsafe mode. diff --git a/src/game_api/socket.cpp b/src/game_api/socket.cpp index 5240b5b39..276224baa 100644 --- a/src/game_api/socket.cpp +++ b/src/game_api/socket.cpp @@ -2,6 +2,7 @@ #include // for GetModuleHandleA, GetProcAddress #include // for max +#include // for chrono #include // for DetourAttach, DetourTransactionBegin #include // for exception #include // for operator new @@ -56,39 +57,57 @@ void dump_network() void udp_data(sockpp::udp_socket socket, UdpServer* server) { ssize_t n; - char buf[500]; + static thread_local char buf[32768]; sockpp::inet_address src; - while (server->kill_thr.test(std::memory_order_acquire) && (n = socket.recv_from(buf, sizeof(buf), &src)) > 0) + while (server->kill_thr.test(std::memory_order_acquire) && socket.is_open()) { - std::optional ret = server->cb(std::string(buf, n)); - if (ret) + while ((n = socket.recv_from(buf, sizeof(buf), &src)) > 0) { - socket.send_to(ret.value(), src); + std::optional ret = server->cb(std::string(buf, n), src.to_string()); + if (ret) + { + socket.send_to(ret.value(), src); + } } + std::this_thread::sleep_for(std::chrono::microseconds(1)); } } UdpServer::UdpServer(std::string host_, in_port_t port_, std::function cb_) : host(host_), port(port_), cb(cb_) { - sock.bind(sockpp::inet_address(host, port)); - kill_thr.test_and_set(); - thr = std::thread(udp_data, std::move(sock), this); + if (sock.bind(sockpp::inet_address(host, port))) + { + const auto addr = (sockpp::inet_address)sock.address(); + port = addr.port(); + is_opened = true; + sock.set_non_blocking(); + kill_thr.test_and_set(); + thr = std::thread(udp_data, std::move(sock), this); + } } -void UdpServer::clear() // TODO: fix and expose: this and the destructor causes deadlock +void UdpServer::close() { + is_closed = true; kill_thr.clear(std::memory_order_release); - thr.join(); + sock.close(); + sock.shutdown(); + if (thr.joinable()) + thr.join(); +} +bool UdpServer::open() +{ + return is_opened && !is_closed && sock.last_error() == 0; +} +std::string UdpServer::error() +{ + auto err = sock.error_str(sock.last_error()); + return err.substr(0, err.size() - 2); } UdpServer::~UdpServer() { - if (thr.joinable()) - { - kill_thr.clear(std::memory_order_release); - thr.join(); - } + close(); } - bool http_get(const char* sURL, std::string& out, std::string& err) { const int BUFFER_SIZE = 32768; diff --git a/src/game_api/socket.hpp b/src/game_api/socket.hpp index c6904967a..bcccfa028 100644 --- a/src/game_api/socket.hpp +++ b/src/game_api/socket.hpp @@ -11,11 +11,19 @@ class UdpServer { public: - using SocketCb = std::optional(std::string); + using SocketCb = std::optional(std::string, std::string); UdpServer(std::string host, in_port_t port, std::function cb); ~UdpServer(); - void clear(); + + /// Closes the server. + void close(); + + /// Returns true if the port was opened successfully and the server hasn't been closed yet. + bool open(); + + /// Returns a string explaining the last error, at least if open() returned false. + std::string error(); std::string host; in_port_t port; @@ -23,6 +31,8 @@ class UdpServer std::thread thr; std::atomic_flag kill_thr; sockpp::udp_socket sock; + bool is_opened{false}; + bool is_closed{false}; }; class HttpRequest From 962479bc35619c441861c62e94f74abeef3164df Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:45:09 +0100 Subject: [PATCH 2/7] update sockpp to 1.0 --- src/sockpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sockpp b/src/sockpp index 93855d54e..e6c4688a5 160000 --- a/src/sockpp +++ b/src/sockpp @@ -1 +1 @@ -Subproject commit 93855d54e78ea2684abe0f75caf4dbdf98bffcab +Subproject commit e6c4688a576d95f42dd7628cefe68092f6c5cd0f From a34816ae7dd9ea0b36ce974259e0c6961113f3dc Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:47:12 +0100 Subject: [PATCH 3/7] Improve `UdpServer` --- docs/game_data/spel2.lua | 24 ++--- docs/src/includes/_globals.md | 21 +++-- docs/src/includes/_types.md | 11 ++- src/game_api/script/usertypes/socket_lua.cpp | 35 +++++++- src/game_api/socket.cpp | 93 +++++++++++++------- src/game_api/socket.hpp | 48 +++++++--- src/game_api/state.cpp | 18 ++-- 7 files changed, 164 insertions(+), 86 deletions(-) diff --git a/docs/game_data/spel2.lua b/docs/game_data/spel2.lua index c4e3535ac..5db8c70b0 100644 --- a/docs/game_data/spel2.lua +++ b/docs/game_data/spel2.lua @@ -1403,14 +1403,6 @@ function toggle_journal() end ---@param page integer ---@return nil function show_journal(chapter, page) end ----Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. ----The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. ----The callback signature is optional on_message(string msg, string src) ----@param host string ----@param port integer ----@param cb fun(msg: string, src: string): string? ----@return UdpServer -function udp_listen(host, port, cb) end ---Send data to specified UDP address. Requires unsafe mode. ---@param host string ---@param port integer @@ -6570,10 +6562,12 @@ function Quad:is_point_inside(x, y, epsilon) end ---@class UdpServer ---@field close fun(self): nil @Closes the server. - ---@field open fun(self): boolean @Returns true if the port was opened successfully and the server hasn't been closed yet. - ---@field error fun(self): string @Returns a string explaining the last error, at least if open() returned false. - ---@field host string - ---@field port integer + ---@field is_open fun(self): boolean @Returns true if the port was opened successfully and the server hasn't been closed yet. + ---@field last_error fun(self): string @Returns a string explaining the last error + ---@field host fun(self): string + ---@field port fun(self): integer + ---@field send fun(self, message: string, host: string, port: integer): sinteger @Send message to given host and port, returns numbers of bytes send, or -1 on failure + ---@field read fun(self, fun: function): sinteger @Read message from the socket buffer. Reads maximum of 32KiB at the time. If the message is longer, it will be split to portions of 32KiB.
On failure or empty buffer returns -1, on success calls the function with signature `nil(message, source)` ---@class LogicList ---@field tutorial LogicTutorial @Handles dropping of the torch and rope in intro routine (first time play) @@ -6951,6 +6945,12 @@ function Quad:new(_bottom_left_x, _bottom_left_y, _bottom_right_x, _bottom_right ---@return Quad function Quad:new(aabb) end +UdpServer = nil +---@param host string +---@param port integer +---@return UdpServer +function UdpServer:new(host, port) end + MagmamanSpawnPosition = nil ---@param x_ integer ---@param y_ integer diff --git a/docs/src/includes/_globals.md b/docs/src/includes/_globals.md index 48c45b064..6cdf9714f 100644 --- a/docs/src/includes/_globals.md +++ b/docs/src/includes/_globals.md @@ -2523,17 +2523,6 @@ Send a synchronous HTTP GET request and return response as a string or nil on an Send an asynchronous HTTP GET request and run the callback when done. If there is an error, response will be nil and vice versa. The callback signature is nil on_data(string response, string error) -### udp_listen - - -> Search script examples for [udp_listen](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=udp_listen) - -#### [UdpServer](#UdpServer) udp_listen(string host, int port, function cb) - -Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. -The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. -
The callback signature is optional on_message(string msg, string src) - ### udp_send @@ -4077,6 +4066,16 @@ Use [GuiDrawContext](#GuiDrawContext)`.win_popid` instead `nil win_image(IMAGE image, float width, float height)`
Use [GuiDrawContext](#GuiDrawContext)`.win_image` instead +### udp_listen + + +> Search script examples for [udp_listen](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=udp_listen) + +`nil udp_listen(string host, int port, function cb)`
+Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. +The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. +
The callback signature is optional on_message(string msg, string src) + ### read_prng diff --git a/docs/src/includes/_types.md b/docs/src/includes/_types.md index de23c4bec..0132465d7 100644 --- a/docs/src/includes/_types.md +++ b/docs/src/includes/_types.md @@ -1096,11 +1096,14 @@ tuple<[Vec2](#Vec2), [Vec2](#Vec2), [Vec2](#Vec2)> | [split()](https://git Type | Name | Description ---- | ---- | ----------- +[UdpServer](#UdpServer) | [new(string host, int port)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=UdpServer) | nil | [close()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=close) | Closes the server. -bool | [open()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=open) | Returns true if the port was opened successfully and the server hasn't been closed yet. -string | [error()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=error) | Returns a string explaining the last error, at least if open() returned false. -string | [host](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=host) | -int | [port](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=port) | +bool | [is_open()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=is_open) | Returns true if the port was opened successfully and the server hasn't been closed yet. +string | [last_error()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=last_error) | Returns a string explaining the last error +string | [host()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=host) | +int | [port()](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=port) | +sint | [send(string message, string host, int port)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=send) | Send message to given host and port, returns numbers of bytes send, or -1 on failure +sint | [read(function fun)](https://github.com/spelunky-fyi/overlunky/search?l=Lua&q=read) | Read message from the socket buffer. Reads maximum of 32KiB at the time. If the message is longer, it will be split to portions of 32KiB.
On failure or empty buffer returns -1, on success calls the function with signature `nil(message, source)` ### Vec2 diff --git a/src/game_api/script/usertypes/socket_lua.cpp b/src/game_api/script/usertypes/socket_lua.cpp index 2de673063..b6ae8a4f2 100644 --- a/src/game_api/script/usertypes/socket_lua.cpp +++ b/src/game_api/script/usertypes/socket_lua.cpp @@ -1,6 +1,7 @@ #include "socket_lua.hpp" #include "socket.hpp" +#include // for unique_ptr #include // for global_table, proxy_key_t, function #include // for ssize_t @@ -8,20 +9,46 @@ #include "script/lua_backend.hpp" // for LuaBackend #include "script/safe_cb.hpp" // for make_safe_cb +class UdpServer_lua +{ + std::unique_ptr m_ptr; + + public: + UdpServer_lua(std::unique_ptr ptr) + : m_ptr(std::move(ptr)){}; +}; + namespace NSocket { void register_usertypes(sol::state& lua) { lua.new_usertype( - "UdpServer", sol::no_constructor, "close", &UdpServer::close, "open", &UdpServer::open, "error", &UdpServer::error, "host", sol::readonly(&UdpServer::host), "port", sol::readonly(&UdpServer::port)); + "UdpServer", + sol::constructors(), + "close", + &UdpServer::close, + "is_open", + &UdpServer::is_open, + "last_error", + &UdpServer::last_error, + "host", + &UdpServer::get_host, + "port", + &UdpServer::get_port, + "send", + &UdpServer::send, + "read", + &UdpServer::read); + /// Deprecated /// Start an UDP server on specified address and run callback when data arrives. Set port to 0 to use a random ephemeral port. Return a string from the callback to reply. /// The server will be closed lazily by garbage collection once the handle is released, or immediately by calling close(). Requires unsafe mode. ///
The callback signature is optional on_message(string msg, string src) - // lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) -> UdpServer* - lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) + lua["udp_listen"] = [](std::string host, in_port_t port, sol::function cb) //-> sol::any { - return std::unique_ptr{new UdpServer(std::move(host), std::move(port), make_safe_cb(std::move(cb)))}; + auto server = std::unique_ptr{new UdpServer(std::move(host), port)}; + server->start_callback(make_safe_cb(std::move(cb))); + return UdpServer_lua(std::move(server)); }; /// Send data to specified UDP address. Requires unsafe mode. diff --git a/src/game_api/socket.cpp b/src/game_api/socket.cpp index 276224baa..2ec00111c 100644 --- a/src/game_api/socket.cpp +++ b/src/game_api/socket.cpp @@ -1,17 +1,14 @@ #include "socket.hpp" #include // for GetModuleHandleA, GetProcAddress -#include // for max #include // for chrono #include // for DetourAttach, DetourTransactionBegin -#include // for exception #include // for operator new #include // for inet_address #include // for udp_socket #include // for thread #include // for get #include // for move -#include // for max, min #include // for InternetCloseHandle, InternetOpenA, InternetG... #include // for sockaddr_in, SOCKET #include // for inet_ntop @@ -54,60 +51,90 @@ void dump_network() } } -void udp_data(sockpp::udp_socket socket, UdpServer* server) +void UdpServer::callback(sockpp::udp_socket sock, std::function cb) { - ssize_t n; static thread_local char buf[32768]; - sockpp::inet_address src; - while (server->kill_thr.test(std::memory_order_acquire) && socket.is_open()) + while (m_opened.load(std::memory_order::relaxed) && m_sock.is_open()) { - while ((n = socket.recv_from(buf, sizeof(buf), &src)) > 0) + sockpp::inet_address src; + ssize_t n; + while ((n = sock.recv_from(buf, sizeof(buf), &src)) > 0) { - std::optional ret = server->cb(std::string(buf, n), src.to_string()); - if (ret) - { - socket.send_to(ret.value(), src); - } + std::optional ret = cb(std::string(buf, n), src.to_string()); + if (ret.has_value()) + sock.send_to(ret.value(), src); } std::this_thread::sleep_for(std::chrono::microseconds(1)); } + m_opened = false; + sock.shutdown(); } -UdpServer::UdpServer(std::string host_, in_port_t port_, std::function cb_) - : host(host_), port(port_), cb(cb_) +UdpServer::UdpServer(std::string host, in_port_t port) + : m_host(host), m_port(port) { - if (sock.bind(sockpp::inet_address(host, port))) + if (m_sock.bind(sockpp::inet_address(host, port))) { - const auto addr = (sockpp::inet_address)sock.address(); + const auto addr = sockpp::inet_address(m_sock.address()); port = addr.port(); - is_opened = true; - sock.set_non_blocking(); - kill_thr.test_and_set(); - thr = std::thread(udp_data, std::move(sock), this); + m_opened = true; + m_sock.set_non_blocking(); } } void UdpServer::close() { - is_closed = true; - kill_thr.clear(std::memory_order_release); - sock.close(); - sock.shutdown(); - if (thr.joinable()) - thr.join(); + m_opened = false; + if (!m_thread.joinable()) + m_sock.close(); } -bool UdpServer::open() +ssize_t UdpServer::send(std::string message, std::string host, in_port_t port) { - return is_opened && !is_closed && sock.last_error() == 0; + if (!is_open()) + return -1; + + return m_sock.send_to(message, sockpp::inet_address(host, port)); } -std::string UdpServer::error() +ssize_t UdpServer::read(std::function fun) { - auto err = sock.error_str(sock.last_error()); - return err.substr(0, err.size() - 2); + if (!is_open()) + return -1; + + static char buf[32768]; + sockpp::inet_address src; + auto ret = m_sock.recv_from(buf, sizeof(buf), &src); + + if (ret > -1) + fun(std::string(buf, static_cast(ret)), src.to_string()); + + return ret; +} +bool UdpServer::is_open() const +{ + return m_opened.load(std::memory_order::memory_order_relaxed) && m_sock.is_open() && m_sock.last_error() > -1; +} +void UdpServer::start_callback(std::function cb) +{ + if (!m_thread.joinable()) + { + auto sock_copy = m_sock.clone(); + m_thread = std::thread(&UdpServer::callback, this, std::move(sock_copy), std::move(cb)); + } +} +std::string UdpServer::last_error() const +{ + auto err = m_sock.last_error_str(); + err.resize(err.size() - 2); + return err; } UdpServer::~UdpServer() { close(); + if (m_thread.joinable()) + m_thread.join(); + + m_sock.close(); } + bool http_get(const char* sURL, std::string& out, std::string& err) { const int BUFFER_SIZE = 32768; @@ -116,7 +143,7 @@ bool http_get(const char* sURL, std::string& out, std::string& err) const char* sHeader = NULL; HINTERNET hInternet; HINTERNET hConnect; - char acBuffer[BUFFER_SIZE]; + static thread_local char acBuffer[BUFFER_SIZE]; DWORD iReadBytes; DWORD iBytesToRead = 0; DWORD iReadBytesOfRq = 4; diff --git a/src/game_api/socket.hpp b/src/game_api/socket.hpp index bcccfa028..6f12cb9ee 100644 --- a/src/game_api/socket.hpp +++ b/src/game_api/socket.hpp @@ -12,27 +12,47 @@ class UdpServer { public: using SocketCb = std::optional(std::string, std::string); + using ReadFun = void(std::string, std::string); - UdpServer(std::string host, in_port_t port, std::function cb); + UdpServer(std::string host, in_port_t port); ~UdpServer(); /// Closes the server. void close(); + /// Send message to given host and port, returns numbers of bytes send, or -1 on failure + ssize_t send(std::string message, std::string host, in_port_t port); + + /// Read message from the socket buffer. Reads maximum of 32KiB at the time. If the message is longer, it will be split to portions of 32KiB. + /// On failure or empty buffer returns -1, on success calls the function with signature `nil(message, source)` + ssize_t read(std::function fun); + /// Returns true if the port was opened successfully and the server hasn't been closed yet. - bool open(); - - /// Returns a string explaining the last error, at least if open() returned false. - std::string error(); - - std::string host; - in_port_t port; - std::function cb; - std::thread thr; - std::atomic_flag kill_thr; - sockpp::udp_socket sock; - bool is_opened{false}; - bool is_closed{false}; + bool is_open() const; + + /// Returns a string explaining the last error + std::string last_error() const; + + std::string get_host() const + { + return m_host; + } + in_port_t get_port() const + { + return m_port; + } + + // added only for backwards compatibility, do not use or expose + void start_callback(std::function cb_); + + private: + void callback(sockpp::udp_socket sock, std::function cb); + std::string m_host; + in_port_t m_port; + std::thread m_thread; + sockpp::udp_socket m_sock; + + std::atomic m_opened{false}; }; class HttpRequest diff --git a/src/game_api/state.cpp b/src/game_api/state.cpp index 1e9a5aa92..8555cf074 100644 --- a/src/game_api/state.cpp +++ b/src/game_api/state.cpp @@ -1,13 +1,14 @@ #include "state.hpp" -#include // for GetCurrentThread, LONG, NO_ERROR -#include // for abs -#include // for size_t, abs -#include // for DetourAttach, DetourTransactionBegin -#include // for _Func_class, function -#include // for operator new -#include // for allocator, operator""sv, operator""s -#include // for move +#include // for GetCurrentThread, LONG, NO_ERROR +#include // for abs +#include // for size_t, abs +#include // for DetourAttach, DetourTransactionBegin +#include // for _Func_class, function +#include // for operator new +#include // for initialize +#include // for allocator, operator""sv, operator""s +#include // for move #include "bucket.hpp" // for Bucket #include "containers/custom_allocator.hpp" // @@ -986,6 +987,7 @@ void API::init(SoundManager* sound_manager) init_process_input_hook(); init_game_loop_hook(); init_heap_clone_hook(); + sockpp::initialize(); auto bucket = Bucket::get(); bucket->count++; From fefdfe784151af31321f7d6922c45237b0680a26 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 7 Nov 2025 00:39:47 +0100 Subject: [PATCH 4/7] Update CMakeLists.txt --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa84f70d3..abca8687d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,6 +216,8 @@ if(BUILD_OVERLUNKY) endif() add_subdirectory(sockpp) +set(SOCKPP_BUILD_SHARED OFF) +set(SOCKPP_BUILD_STATIC ON) target_include_directories(spel2_api PUBLIC sockpp/include) target_link_libraries_system(spel2_api PUBLIC sockpp-static) From 87ed1f8fb666f3408a4dc8cc37b5b0128f1db257 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 7 Nov 2025 00:43:32 +0100 Subject: [PATCH 5/7] Update CMakeLists.txt --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abca8687d..668509d9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,10 +214,10 @@ if(BUILD_OVERLUNKY) endif() endif() endif() - -add_subdirectory(sockpp) set(SOCKPP_BUILD_SHARED OFF) set(SOCKPP_BUILD_STATIC ON) + +add_subdirectory(sockpp) target_include_directories(spel2_api PUBLIC sockpp/include) target_link_libraries_system(spel2_api PUBLIC sockpp-static) From 1a51b64673772680c1cd84bc295930499a6f693c Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 7 Nov 2025 01:10:21 +0100 Subject: [PATCH 6/7] Update CMakeLists.txt --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 668509d9a..b0dbb55d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,7 +214,6 @@ if(BUILD_OVERLUNKY) endif() endif() endif() -set(SOCKPP_BUILD_SHARED OFF) set(SOCKPP_BUILD_STATIC ON) add_subdirectory(sockpp) From 6a613c524f1bc59be18cc0373ed50be1d5c0f9ed Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 7 Nov 2025 01:29:13 +0100 Subject: [PATCH 7/7] Update CMakeLists.txt --- src/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0dbb55d1..e60ae7ac6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,7 +214,9 @@ if(BUILD_OVERLUNKY) endif() endif() endif() -set(SOCKPP_BUILD_STATIC ON) + +set(SOCKPP_BUILD_SHARED OFF CACHE BOOL "No shared") +set(SOCKPP_BUILD_STATIC ON CACHE BOOL "Static") add_subdirectory(sockpp) target_include_directories(spel2_api PUBLIC sockpp/include)