From bb5be10adc81ccd91cd1f683401af3bb7d6dfd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=A1k?= Date: Wed, 7 May 2025 17:46:51 +0200 Subject: [PATCH 1/4] sse client to listen commands (from mapedit) --- game/globals.h | 2 +- game/skeldal.c | 68 +++++++++++++--- game/skeldal.h | 1 + platform/CMakeLists.txt | 24 +++--- platform/linux/app_start.cpp | 6 +- platform/mtqueue.cpp | 44 ++++++++++ platform/mtqueue.h | 32 ++++++++ platform/sse_receiver.cpp | 142 +++++++++++++++++++++++++++++++++ platform/sse_receiver.h | 30 +++++++ platform/windows/app_start.cpp | 16 ++-- 10 files changed, 336 insertions(+), 29 deletions(-) create mode 100644 platform/mtqueue.cpp create mode 100644 platform/mtqueue.h create mode 100644 platform/sse_receiver.cpp create mode 100644 platform/sse_receiver.h diff --git a/game/globals.h b/game/globals.h index ec3fe1d..66658db 100644 --- a/game/globals.h +++ b/game/globals.h @@ -39,7 +39,7 @@ #define E_MENU_SELECT 36 #define E_CLOSE_MAP 37 #define E_CLOSE_GEN 38 -#define E_HACKER 39 +#define E_EXTERNAL_MSG 40 //external message arrived from sse_listener //side flags diff --git a/game/skeldal.c b/game/skeldal.c index ee1b6ec..c289f50 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -280,6 +280,10 @@ INIS sinit[]= int last_ms_cursor=-1; int vmode=2; +#include + +static SSE_RECEIVER *sse_receiver = NULL; +static MTQUEUE *mtqueue = NULL; void purge_temps(char _) { temp_storage_clear(); @@ -741,7 +745,8 @@ void done_skeldal(void) cur_config = NULL; } kill_timer(); - + if (sse_receiver) sse_receiver_stop(sse_receiver); + if (mtqueue) mtqueue_destroy(mtqueue); } @@ -980,6 +985,38 @@ void show_loading_picture(char *filename) ablock_free(p); } +void sse_listener_watch(EVENT_MSG *msg, void **userdata) { + if (msg->msg == E_WATCH) { + char *s = mtqueue_pop(mtqueue); + if (s) { + send_message(E_EXTERNAL_MSG, s); + free(s); + } + } +} +void sse_listener_init(const char *hostport) { + + char *host = local_strdup(hostport); + char *port = strrchr(host,':'); + if (port == NULL) { + port = local_strdup("80"); + } else { + *port = 0; + ++port; + } + + MTQUEUE *q = mtqueue_create(); + SSE_RECEIVER *rcv = sse_receiver_install(q, host, port); + + if (rcv == NULL) { + mtqueue_destroy(q); + return; + } + mtqueue = q; + sse_receiver = rcv; + send_message(E_ADD, E_WATCH, sse_listener_watch); + +} void init_skeldal(const INI_CONFIG *cfg) { @@ -989,10 +1026,11 @@ void init_skeldal(const INI_CONFIG *cfg) cti_texty(); timer_tree.next=NULL; - init_events(); + init_events(); steam_init(); + char verr = game_display_init(ini_section_open(cfg, "video"), "Skeldal"); if (!verr) { @@ -1120,15 +1158,21 @@ char doNotLoadMapState=0; static int reload_map_handler(EVENT_MSG *msg,void **usr) { extern char running_battle; - if (msg->msg==E_RELOADMAP) + if (msg->msg==E_EXTERNAL_MSG) { - int i; - ReloadMapInfo *minfo=va_arg(msg->data, ReloadMapInfo *); - const char *fname=minfo->fname; - int sektor=minfo->sektor; - strcopy_n(loadlevel.name,fname,sizeof(loadlevel.name)); - loadlevel.start_pos=sektor; - for(i=0;idata, const char *); + char fname[13]; + int sector; + int i; + + if (sscanf(m, "RELOAD %12s %d", fname, §or) != 2) return 0; + + strcopy_n(loadlevel.name,fname,sizeof(loadlevel.name)); + loadlevel.start_pos=sector; + for(i=0;isse_hostport) { + sse_listener_init(start_cfg->sse_hostport); + } + start_check(); purge_temps(1); clrscr(); diff --git a/game/skeldal.h b/game/skeldal.h index baf737c..ce44873 100644 --- a/game/skeldal.h +++ b/game/skeldal.h @@ -13,6 +13,7 @@ typedef struct { const char *config_path; const char *lang_path; + const char *sse_hostport; } SKELDAL_CONFIG; diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 343a8e2..90cde4f 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -12,17 +12,19 @@ target_sources(skeldal_platform PRIVATE timer.cpp getopt.c achievements.cpp + mtqueue.cpp + sse_receiver.cpp ) -set(all_libs - skeldal_main - skeldal_libs +set(all_libs + skeldal_main + skeldal_libs skeldal_platform skeldal_sdl skeldal_libs ${SDL2_LIBRARIES} ${STANDARD_LIBRARIES}) - + if(WIN32) target_sources(skeldal_platform PRIVATE windows/save_folder.cpp @@ -35,7 +37,7 @@ if(WIN32) windows/skeldal.rc ) target_compile_definitions(skeldal_platform PRIVATE PLATFORM_WINDOWS) - if(STEAM_ENABLED) + if(STEAM_ENABLED) if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/win64/steam_api64.lib) @@ -49,7 +51,7 @@ if(WIN32) set(STEAMLIB "") set(STEAMDLL "") endif() - + target_link_libraries(skeldal ${all_libs} ${STEAMLIB}) if(STEAMDLL) @@ -69,7 +71,7 @@ elseif(UNIX AND NOT APPLE) linux/app_start.cpp ) target_compile_definitions(skeldal_bin PRIVATE PLATFORM_LINUX) - if(STEAM_ENABLED) + if(STEAM_ENABLED) if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64-bit set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/linux64/libsteam_api.so) @@ -83,10 +85,10 @@ elseif(UNIX AND NOT APPLE) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/linux/skeldal.sh ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/skeldal.sh) - target_link_libraries(skeldal_bin ${all_libs} ${STEAMLIB}) + target_link_libraries(skeldal_bin ${all_libs} ${STEAMLIB}) message(STATUS "Building for Linux") -elseif(APPLE) +elseif(APPLE) target_sources(skeldal_platform PRIVATE mac_os/save_folder.cpp ) @@ -96,10 +98,10 @@ elseif(APPLE) ) target_compile_definitions(mylib PRIVATE PLATFORM_MACOS) set(STEAMLIB ${STEAMWORKS_SDK_DIR}/redistributable_bin/osx/libsteam_api.dylib) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") message(STATUS "Building for macOS") target_link_libraries(skeldal ${all_libs} ${STEAMLIB}) -else() +else() error("Platform not detected, please add new platform here") endif() set_property(TARGET skeldal_platform PROPERTY CXX_STANDARD 20) diff --git a/platform/linux/app_start.cpp b/platform/linux/app_start.cpp index 7ec40d4..760efd1 100644 --- a/platform/linux/app_start.cpp +++ b/platform/linux/app_start.cpp @@ -19,6 +19,7 @@ void show_help(const char *arg0) { "-a path for adventure file (.adv)\n" "-l set language (cz|en)\n" "-s generate string-tables (for localization) and exit\n" + "-L connect to host:port to listen commands (mapedit)\n" "-h this help\n"); exit(1); } @@ -33,13 +34,15 @@ int main(int argc, char **argv) { std::string adv_config_file; std::string gen_stringtable_path; std::string lang; - for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:")) != -1; ) { + std::string sse_hostport; + for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) { switch (optchr) { case 'f': config_name = optarg;break; case 'a': adv_config_file = optarg;break; case 'h': show_help(argv[0]);break; case 'l': lang = optarg;break; case 's': gen_stringtable_path = optarg;break; + case 'L': sse_hostport = optarg;break; default: show_help_short(); return 1; } @@ -53,6 +56,7 @@ int main(int argc, char **argv) { cfg.adventure_path = adv_config_file.empty()?NULL:adv_config_file.c_str(); cfg.config_path = config_name.c_str(); cfg.lang_path = lang.empty()?NULL:lang.c_str(); + cfg.sse_hostport = sse_hostport.c_str(); try { if (!gen_stringtable_path.empty()) { diff --git a/platform/mtqueue.cpp b/platform/mtqueue.cpp new file mode 100644 index 0000000..bc9d23d --- /dev/null +++ b/platform/mtqueue.cpp @@ -0,0 +1,44 @@ +#include "mtqueue.h" + +#include +#include +#include +#include +#include +struct StringDeleter { + void operator()(char *x) { + free(x); + } +}; + +std::unique_ptr alloc_string(const char *x) { + return std::unique_ptr(strdup(x)); +} + + +typedef struct tag_mtqueue { + std::queue > _q; + std::mutex _mx; + + +} MTQUEUE; + +MTQUEUE *mtqueue_create() { + return new MTQUEUE(); +} +void mtqueue_push(MTQUEUE *q, const char *message) { + std::lock_guard _(q->_mx); + q->_q.push(alloc_string(message)); +} +char *mtqueue_pop(MTQUEUE *q) { + std::lock_guard _(q->_mx); + if (q->_q.empty()) return NULL; + else { + char *c = q->_q.front().release(); + q->_q.pop(); + return c; + } +} +void mtqueue_destroy(MTQUEUE *q) { + delete q; +} diff --git a/platform/mtqueue.h b/platform/mtqueue.h new file mode 100644 index 0000000..07919e0 --- /dev/null +++ b/platform/mtqueue.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct tag_mtqueue MTQUEUE; +///Create multithread queue +MTQUEUE *mtqueue_create(); +///push to queue (string is copied) +/** + * @param q queue + * @param message message (string is copied) + */ +void mtqueue_push(MTQUEUE *q, const char *message); +///pop from the queue +/** + * + * @param q queue + * @return NULL, if queue is empty, or string. You have to release + * string by calling free() when you finish. + */ +char *mtqueue_pop(MTQUEUE *q); + +///destroy the queue +void mtqueue_destroy(MTQUEUE *q); + + +#ifdef __cplusplus +} +#endif diff --git a/platform/sse_receiver.cpp b/platform/sse_receiver.cpp new file mode 100644 index 0000000..f043e83 --- /dev/null +++ b/platform/sse_receiver.cpp @@ -0,0 +1,142 @@ +#include "sse_receiver.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "ws2_32.lib") + typedef SOCKET sock_t; + #define CLOSESOCK closesocket + #define sock_init() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); } + #define sock_cleanup() WSACleanup() + #define SHUT_RD SD_RECEIVE +#else + #include + #include + #include + #include + #include + typedef int sock_t; + #define INVALID_SOCKET -1 + #define CLOSESOCK close + #define sock_init() + #define sock_cleanup() +#endif + +#define BUFFER_SIZE 1024 + + +void sse_client_loop(const char *host, const char *port, std::function callback, std::stop_token tkn) { + sock_init(); + + struct addrinfo hints = {}, *res = NULL; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(host, port, &hints, &res) != 0) { + perror("getaddrinfo"); + return; + } + + sock_t sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock == INVALID_SOCKET) { + perror("socket"); + freeaddrinfo(res); + return; + } + + + + if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { + perror("connect"); + CLOSESOCK(sock); + freeaddrinfo(res); + return; + } + + + freeaddrinfo(res); + + { + std::stop_callback _cb(tkn, [&]{ + shutdown(sock, SHUT_RD); + }); + + // Send HTTP GET request + char req[512]; + snprintf(req, sizeof(req), + "GET /command HTTP/1.1\r\n" + "Host: %s\r\n" + "Accept: text/event-stream\r\n" + "Connection: keep-alive\r\n\r\n", host); + send(sock, req, strlen(req), 0); + + // Read response and extract "data: " lines + char buffer[BUFFER_SIZE]; + int buf_len = 0; + + while (!tkn.stop_requested()) { + int n = recv(sock, buffer + buf_len, BUFFER_SIZE - buf_len - 1, 0); + if (n <= 0) { + break; + } + + buf_len += n; + buffer[buf_len] = '\0'; + + char *line_start = buffer; + while (1) { + char *newline = strstr(line_start, "\n"); + if (!newline) break; + + *newline = '\0'; + + if (strncmp(line_start, "data: ", 6) == 0) { + callback(line_start + 6); + } + + line_start = newline + 1; + } + + // Move leftover data to start + buf_len = strlen(line_start); + memmove(buffer, line_start, buf_len); + } + + } + CLOSESOCK(sock); + sock_cleanup(); + return; +} + + + + +typedef struct tag_sse_receiver { + std::jthread thr; +} +SSE_RECEIVER; + +SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port) { + SSE_RECEIVER *sse = new SSE_RECEIVER; + sse->thr = std::jthread([sse, q, host = std::string(host), port = std::string(port)](std::stop_token tkn){ + while (!tkn.stop_requested()) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + sse_client_loop(host.c_str(), port.c_str(), [q](const char *msg){ + mtqueue_push(q, msg); + }, tkn); + } + }); + return sse; +} +void sse_receiver_stop(SSE_RECEIVER *inst) { + delete inst; +} + diff --git a/platform/sse_receiver.h b/platform/sse_receiver.h new file mode 100644 index 0000000..044c5a7 --- /dev/null +++ b/platform/sse_receiver.h @@ -0,0 +1,30 @@ +#pragma once + +#include "mtqueue.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct tag_sse_receiver SSE_RECEIVER; + +///Install sse receiver +/** + * @param q mtqueue, which receives messages received by the receiver + * @param host host + * @param port port + * @return pointer to instance of receiver + */ +SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port); + +///Stops the receiver +/** + * @param inst instance of receiver + * @note the associated queue is not destroyed + */ +void sse_receiver_stop(SSE_RECEIVER *inst); + + +#ifdef __cplusplus +} +#endif diff --git a/platform/windows/app_start.cpp b/platform/windows/app_start.cpp index 8ecc5a9..64903a4 100644 --- a/platform/windows/app_start.cpp +++ b/platform/windows/app_start.cpp @@ -10,7 +10,7 @@ #include void show_help(std::ostream &out, const char *arg0) { - out << + out << "Brany Skeldalu (Gates of Skeldal) portable game player\n" "Copyright (c) 2025 Ondrej Novak. All rights reserved.\n\n" "This work is licensed under the terms of the MIT license.\n" @@ -22,7 +22,8 @@ void show_help(std::ostream &out, const char *arg0) { "-a path for adventure file (.adv)\n" "-l set language (cz|en)\n" "-s generate string-tables (for localization) and exit\n" - "-h this help\n"; + "-L connect to host:port to listen commands (mapedit)\n" + "-h this help\n"; } void show_help_short(std::ostream &out) { @@ -30,20 +31,22 @@ void show_help_short(std::ostream &out) { } -int main(int argc, char **argv) { +int main(int argc, char **argv) { std::string config_name = SKELDALINI; std::string adv_config_file; std::string gen_stringtable_path; std::string lang; + std::string sse_hostport; std::ostringstream console; - for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:")) != -1; ) { + for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) { switch (optchr) { case 'f': config_name = optarg;break; case 'a': adv_config_file = optarg;break; case 'h': show_help(console, argv[0]);break; case 'l': lang = optarg;break; case 's': gen_stringtable_path = optarg;break; - default: show_help_short(console);break; + case 'L': sse_hostport = optarg;break; + default: show_help_short(console);break; } } @@ -52,7 +55,7 @@ int main(int argc, char **argv) { show_help(console, argv[0]); } - SKELDAL_CONFIG cfg; + SKELDAL_CONFIG cfg = {}; cfg.short_help = []{}; cfg.show_error = [](const char *txt) { char buff[MAX_PATH]; @@ -63,6 +66,7 @@ int main(int argc, char **argv) { cfg.adventure_path = adv_config_file.empty()?NULL:adv_config_file.c_str(); cfg.config_path = config_name.c_str(); cfg.lang_path = lang.empty()?NULL:lang.c_str(); + cfg.sse_hostport = sse_hostport.c_str(); { std::string msg = console.str(); From 240c8764fb783c731299f4a45581b9fe0a252eb6 Mon Sep 17 00:00:00 2001 From: Ondrej Novak Date: Wed, 7 May 2025 22:01:26 +0200 Subject: [PATCH 2/4] improve sse receiver --- game/skeldal.c | 57 ++++++------ platform/CMakeLists.txt | 3 +- platform/mtqueue.cpp | 44 --------- platform/mtqueue.h | 32 ------- platform/sse_receiver.c | 181 ++++++++++++++++++++++++++++++++++++++ platform/sse_receiver.cpp | 142 ------------------------------ platform/sse_receiver.h | 20 +---- 7 files changed, 213 insertions(+), 266 deletions(-) delete mode 100644 platform/mtqueue.cpp delete mode 100644 platform/mtqueue.h create mode 100644 platform/sse_receiver.c delete mode 100644 platform/sse_receiver.cpp diff --git a/game/skeldal.c b/game/skeldal.c index c289f50..24c5859 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -283,7 +283,7 @@ int vmode=2; #include static SSE_RECEIVER *sse_receiver = NULL; -static MTQUEUE *mtqueue = NULL; + void purge_temps(char _) { temp_storage_clear(); @@ -745,8 +745,7 @@ void done_skeldal(void) cur_config = NULL; } kill_timer(); - if (sse_receiver) sse_receiver_stop(sse_receiver); - if (mtqueue) mtqueue_destroy(mtqueue); + if (sse_receiver) sse_receiver_destroy(sse_receiver); } @@ -987,10 +986,9 @@ void show_loading_picture(char *filename) void sse_listener_watch(EVENT_MSG *msg, void **userdata) { if (msg->msg == E_WATCH) { - char *s = mtqueue_pop(mtqueue); + const char *s = sse_receiver_receive(sse_receiver); if (s) { send_message(E_EXTERNAL_MSG, s); - free(s); } } } @@ -1005,14 +1003,11 @@ void sse_listener_init(const char *hostport) { ++port; } - MTQUEUE *q = mtqueue_create(); - SSE_RECEIVER *rcv = sse_receiver_install(q, host, port); + SSE_RECEIVER *rcv = sse_receiver_create(host, port); if (rcv == NULL) { - mtqueue_destroy(q); return; } - mtqueue = q; sse_receiver = rcv; send_message(E_ADD, E_WATCH, sse_listener_watch); @@ -1165,28 +1160,32 @@ extern char running_battle; int sector; int i; - if (sscanf(m, "RELOAD %12s %d", fname, §or) != 2) return 0; + if (sscanf(m, "RELOAD %12s %d", fname, §or) == 2) { - strcopy_n(loadlevel.name,fname,sizeof(loadlevel.name)); - loadlevel.start_pos=sector; - for(i=0;i -#include -#include -#include -#include -struct StringDeleter { - void operator()(char *x) { - free(x); - } -}; - -std::unique_ptr alloc_string(const char *x) { - return std::unique_ptr(strdup(x)); -} - - -typedef struct tag_mtqueue { - std::queue > _q; - std::mutex _mx; - - -} MTQUEUE; - -MTQUEUE *mtqueue_create() { - return new MTQUEUE(); -} -void mtqueue_push(MTQUEUE *q, const char *message) { - std::lock_guard _(q->_mx); - q->_q.push(alloc_string(message)); -} -char *mtqueue_pop(MTQUEUE *q) { - std::lock_guard _(q->_mx); - if (q->_q.empty()) return NULL; - else { - char *c = q->_q.front().release(); - q->_q.pop(); - return c; - } -} -void mtqueue_destroy(MTQUEUE *q) { - delete q; -} diff --git a/platform/mtqueue.h b/platform/mtqueue.h deleted file mode 100644 index 07919e0..0000000 --- a/platform/mtqueue.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - - -typedef struct tag_mtqueue MTQUEUE; -///Create multithread queue -MTQUEUE *mtqueue_create(); -///push to queue (string is copied) -/** - * @param q queue - * @param message message (string is copied) - */ -void mtqueue_push(MTQUEUE *q, const char *message); -///pop from the queue -/** - * - * @param q queue - * @return NULL, if queue is empty, or string. You have to release - * string by calling free() when you finish. - */ -char *mtqueue_pop(MTQUEUE *q); - -///destroy the queue -void mtqueue_destroy(MTQUEUE *q); - - -#ifdef __cplusplus -} -#endif diff --git a/platform/sse_receiver.c b/platform/sse_receiver.c new file mode 100644 index 0000000..c16015e --- /dev/null +++ b/platform/sse_receiver.c @@ -0,0 +1,181 @@ +// sse_receiver.c +#include "sse_receiver.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "ws2_32.lib") + typedef SOCKET sock_t; + #define CLOSESOCK closesocket + #define sock_init() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); } + #define sock_cleanup() WSACleanup() +#else + #include + #include + #include + #include + #include + typedef int sock_t; + #define INVALID_SOCKET -1 + #define CLOSESOCK close + #define sock_init() + #define sock_cleanup() +#endif + +#define BUFFER_SIZE 4096 + +struct tag_sse_receiver { + char host[256]; + char port[16]; + sock_t sock; + int connected; + char buffer[BUFFER_SIZE+1]; + size_t buf_len; + size_t line_len; + time_t next_attempt; +}; + +static void set_nonblocking(sock_t sock) { +#ifdef _WIN32 + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); +#else + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); +#endif +} + +static int connect_and_handshake(SSE_RECEIVER *sse) { + struct addrinfo hints = {0}, *res = NULL; + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (getaddrinfo(sse->host, sse->port, &hints, &res) != 0) { + return 0; + } + + sock_t sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock == INVALID_SOCKET) { + freeaddrinfo(res); + return 0; + } + + if (connect(sock, res->ai_addr, (int)res->ai_addrlen) != 0) { + CLOSESOCK(sock); + freeaddrinfo(res); + return 0; + } + + freeaddrinfo(res); + + const char *req_fmt = + "GET /command HTTP/1.1\r\n" + "Host: %s\r\n" + "Accept: text/event-stream\r\n" + "Connection: keep-alive\r\n\r\n"; + + char req[512]; + snprintf(req, sizeof(req), req_fmt, sse->host); + send(sock, req, (int)strlen(req), 0); + + set_nonblocking(sock); + sse->sock = sock; + sse->buf_len = 0; + sse->line_len = 0; + sse->connected = 1; + + return 1; +} + +SSE_RECEIVER *sse_receiver_create(const char *host, const char *port) { + sock_init(); + SSE_RECEIVER *sse = calloc(1, sizeof(SSE_RECEIVER)); + strncpy(sse->host, host, sizeof(sse->host) - 1); + strncpy(sse->port, port, sizeof(sse->port) - 1); + sse->sock = INVALID_SOCKET; + sse->connected = 0; + sse->buf_len = 0; + sse->line_len = 0; + sse->next_attempt = 0; + return sse; +} + +size_t find_sep(const char *buffer, size_t size, char sep) { + for (size_t i = 0; i < size; ++i) { + if (buffer[i] == sep) return i; + } + return size; +} + +const char *sse_receiver_receive(SSE_RECEIVER *sse) { + while(1) { + size_t rm = sse->buf_len-sse->line_len; + if (rm) { + const char *r = sse->buffer+sse->line_len; + size_t sep = find_sep(r, rm, '\n'); + if (sep < rm) { + sep += sse->line_len; + sse->buffer[sep] = 0; + sse->line_len = sep+1; + + if (strncmp(r,"data:",5) == 0) { + r+=5; + while (*r && isspace(*r)) ++r; + if (*r) { + return r; + } + } + continue; + } + if (r != sse->buffer) memmove(sse->buffer, r, rm); + } + sse->line_len = 0; + sse->buf_len = rm; + + if (!sse->connected) { + time_t t = time(NULL); + if (t < sse->next_attempt) { + return NULL; + } + if (!connect_and_handshake(sse)) { + snprintf(sse->buffer, sizeof(sse->buffer) - 1, + "MESSAGE Failed to connect the command server %s:%s ", + sse->host, sse->port); + sse->next_attempt = t+5; + return sse->buffer; + } else { + return "MESSAGE Connected to command server"; + } + } + + int n = recv(sse->sock, sse->buffer + sse->buf_len, (int)(BUFFER_SIZE - sse->buf_len), 0); + if (n <= 0) { +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) +#else + if (errno != EWOULDBLOCK && errno != EAGAIN) +#endif + { + CLOSESOCK(sse->sock); + sse->connected = 0; + } + return NULL; + } + + sse->buf_len += n; + } +} + +void sse_receiver_destroy(SSE_RECEIVER *sse) { + if (sse->connected) { + CLOSESOCK(sse->sock); + } + free(sse); + sock_cleanup(); +} diff --git a/platform/sse_receiver.cpp b/platform/sse_receiver.cpp deleted file mode 100644 index f043e83..0000000 --- a/platform/sse_receiver.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "sse_receiver.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 - #include - #include - #pragma comment(lib, "ws2_32.lib") - typedef SOCKET sock_t; - #define CLOSESOCK closesocket - #define sock_init() { WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa); } - #define sock_cleanup() WSACleanup() - #define SHUT_RD SD_RECEIVE -#else - #include - #include - #include - #include - #include - typedef int sock_t; - #define INVALID_SOCKET -1 - #define CLOSESOCK close - #define sock_init() - #define sock_cleanup() -#endif - -#define BUFFER_SIZE 1024 - - -void sse_client_loop(const char *host, const char *port, std::function callback, std::stop_token tkn) { - sock_init(); - - struct addrinfo hints = {}, *res = NULL; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - - if (getaddrinfo(host, port, &hints, &res) != 0) { - perror("getaddrinfo"); - return; - } - - sock_t sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (sock == INVALID_SOCKET) { - perror("socket"); - freeaddrinfo(res); - return; - } - - - - if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { - perror("connect"); - CLOSESOCK(sock); - freeaddrinfo(res); - return; - } - - - freeaddrinfo(res); - - { - std::stop_callback _cb(tkn, [&]{ - shutdown(sock, SHUT_RD); - }); - - // Send HTTP GET request - char req[512]; - snprintf(req, sizeof(req), - "GET /command HTTP/1.1\r\n" - "Host: %s\r\n" - "Accept: text/event-stream\r\n" - "Connection: keep-alive\r\n\r\n", host); - send(sock, req, strlen(req), 0); - - // Read response and extract "data: " lines - char buffer[BUFFER_SIZE]; - int buf_len = 0; - - while (!tkn.stop_requested()) { - int n = recv(sock, buffer + buf_len, BUFFER_SIZE - buf_len - 1, 0); - if (n <= 0) { - break; - } - - buf_len += n; - buffer[buf_len] = '\0'; - - char *line_start = buffer; - while (1) { - char *newline = strstr(line_start, "\n"); - if (!newline) break; - - *newline = '\0'; - - if (strncmp(line_start, "data: ", 6) == 0) { - callback(line_start + 6); - } - - line_start = newline + 1; - } - - // Move leftover data to start - buf_len = strlen(line_start); - memmove(buffer, line_start, buf_len); - } - - } - CLOSESOCK(sock); - sock_cleanup(); - return; -} - - - - -typedef struct tag_sse_receiver { - std::jthread thr; -} -SSE_RECEIVER; - -SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port) { - SSE_RECEIVER *sse = new SSE_RECEIVER; - sse->thr = std::jthread([sse, q, host = std::string(host), port = std::string(port)](std::stop_token tkn){ - while (!tkn.stop_requested()) { - std::this_thread::sleep_for(std::chrono::seconds(2)); - sse_client_loop(host.c_str(), port.c_str(), [q](const char *msg){ - mtqueue_push(q, msg); - }, tkn); - } - }); - return sse; -} -void sse_receiver_stop(SSE_RECEIVER *inst) { - delete inst; -} - diff --git a/platform/sse_receiver.h b/platform/sse_receiver.h index 044c5a7..a567c67 100644 --- a/platform/sse_receiver.h +++ b/platform/sse_receiver.h @@ -1,28 +1,14 @@ #pragma once -#include "mtqueue.h" #ifdef __cplusplus extern "C" { #endif typedef struct tag_sse_receiver SSE_RECEIVER; - -///Install sse receiver -/** - * @param q mtqueue, which receives messages received by the receiver - * @param host host - * @param port port - * @return pointer to instance of receiver - */ -SSE_RECEIVER *sse_receiver_install(MTQUEUE *q, const char *host, const char *port); - -///Stops the receiver -/** - * @param inst instance of receiver - * @note the associated queue is not destroyed - */ -void sse_receiver_stop(SSE_RECEIVER *inst); +SSE_RECEIVER *sse_receiver_create(const char *host, const char *port); +const char *sse_receiver_receive(SSE_RECEIVER *sse); +void sse_receiver_destroy(SSE_RECEIVER *inst); #ifdef __cplusplus From 47edde89d5a573d5946c645e5bdb1ce820a20fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=A1k?= Date: Fri, 9 May 2025 08:02:11 +0200 Subject: [PATCH 3/4] fix compile error on linux --- platform/sse_receiver.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/platform/sse_receiver.c b/platform/sse_receiver.c index c16015e..50d5cdd 100644 --- a/platform/sse_receiver.c +++ b/platform/sse_receiver.c @@ -1,5 +1,7 @@ // sse_receiver.c #include "sse_receiver.h" + +#include #include #include #include @@ -34,7 +36,7 @@ struct tag_sse_receiver { char port[16]; sock_t sock; int connected; - char buffer[BUFFER_SIZE+1]; + char buffer[BUFFER_SIZE+1]; size_t buf_len; size_t line_len; time_t next_attempt; @@ -118,7 +120,7 @@ const char *sse_receiver_receive(SSE_RECEIVER *sse) { if (rm) { const char *r = sse->buffer+sse->line_len; size_t sep = find_sep(r, rm, '\n'); - if (sep < rm) { + if (sep < rm) { sep += sse->line_len; sse->buffer[sep] = 0; sse->line_len = sep+1; @@ -129,13 +131,13 @@ const char *sse_receiver_receive(SSE_RECEIVER *sse) { if (*r) { return r; } - } + } continue; } if (r != sse->buffer) memmove(sse->buffer, r, rm); - } + } sse->line_len = 0; - sse->buf_len = rm; + sse->buf_len = rm; if (!sse->connected) { time_t t = time(NULL); @@ -143,8 +145,8 @@ const char *sse_receiver_receive(SSE_RECEIVER *sse) { return NULL; } if (!connect_and_handshake(sse)) { - snprintf(sse->buffer, sizeof(sse->buffer) - 1, - "MESSAGE Failed to connect the command server %s:%s ", + snprintf(sse->buffer, sizeof(sse->buffer) - 1, + "MESSAGE Failed to connect the command server %s:%s ", sse->host, sse->port); sse->next_attempt = t+5; return sse->buffer; @@ -169,7 +171,7 @@ const char *sse_receiver_receive(SSE_RECEIVER *sse) { } sse->buf_len += n; - } + } } void sse_receiver_destroy(SSE_RECEIVER *sse) { From 6ee879dbe5aa7907c56cd4d297eaa0aa258df386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=A1k?= Date: Mon, 26 May 2025 20:09:42 +0200 Subject: [PATCH 4/4] change switch to c (as command) --- game/skeldal.c | 4 ++-- platform/linux/app_start.cpp | 6 +++--- platform/windows/app_start.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/game/skeldal.c b/game/skeldal.c index e252d79..9903d67 100644 --- a/game/skeldal.c +++ b/game/skeldal.c @@ -1036,7 +1036,7 @@ void init_skeldal(const INI_CONFIG *cfg) exit(1); } showview = game_display_update_rect; - game_display_set_icon(getWindowIcon(), getWindowIconSize()); +// game_display_set_icon(getWindowIcon(), getWindowIconSize()); init_joystick(ini_section_open(cfg, "controller")); general_engine_init(); @@ -1188,7 +1188,7 @@ extern char running_battle; } else if (strncmp(m, "MESSAGE ", 8) == 0) { bott_disp_text(m+8); } - + } return 0; } diff --git a/platform/linux/app_start.cpp b/platform/linux/app_start.cpp index 3e658df..bfc1ddb 100644 --- a/platform/linux/app_start.cpp +++ b/platform/linux/app_start.cpp @@ -20,7 +20,7 @@ void show_help(const char *arg0) { "-a path for adventure file (.adv)\n" "-l set language (cz|en)\n" "-s generate string-tables (for localization) and exit\n" - "-L connect to host:port to listen commands (mapedit)\n" + "-c connect to host:port for remote control (mapedit)\n" "-h this help\n"); exit(1); } @@ -36,14 +36,14 @@ int main(int argc, char **argv) { std::string gen_stringtable_path; std::string lang; std::string sse_hostport; - for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) { + for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:c:")) != -1; ) { switch (optchr) { case 'f': config_name = optarg;break; case 'a': adv_config_file = optarg;break; case 'h': show_help(argv[0]);break; case 'l': lang = optarg;break; case 's': gen_stringtable_path = optarg;break; - case 'L': sse_hostport = optarg;break; + case 'c': sse_hostport = optarg;break; default: show_help_short(); return 1; } diff --git a/platform/windows/app_start.cpp b/platform/windows/app_start.cpp index cf08557..f5f5ea4 100644 --- a/platform/windows/app_start.cpp +++ b/platform/windows/app_start.cpp @@ -23,7 +23,7 @@ void show_help(std::ostream &out, const char *arg0) { "-a path for adventure file (.adv)\n" "-l set language (cz|en)\n" "-s generate string-tables (for localization) and exit\n" - "-L connect to host:port to listen commands (mapedit)\n" + "-c connect to host:port for remote control (mapedit)\n" "-h this help\n"; } @@ -39,7 +39,7 @@ int main(int argc, char **argv) { std::string lang; std::string sse_hostport; std::ostringstream console; - for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:L:")) != -1; ) { + for (int optchr = -1; (optchr = getopt(argc, argv, "hf:a:s:l:c:")) != -1; ) { switch (optchr) { case 'f': config_name = optarg;break; case 'a': adv_config_file = optarg;break;