From 47ae0819ae0db119ee46a1977768cdb7734f7508 Mon Sep 17 00:00:00 2001 From: Paul Gofman Date: Mon, 23 May 2022 15:29:28 -0500 Subject: [PATCH] lsteamclient: Execute SteamNetworkingSocketsDebugOutput callback from a Wine thread. CW-Bug-Id: #20684 The callback is called from a native Unix thread which is not initialized with Wine, meaning it, e. g., has a leftover TEB from another thread. Only native Unix functions may be called from such a thread. --- lsteamclient/steamclient_main.c | 116 ++++++++++++++++++++++++++++ lsteamclient/steamclient_private.h | 23 ++++++ lsteamclient/steamclient_wrappers.c | 10 ++- 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/lsteamclient/steamclient_main.c b/lsteamclient/steamclient_main.c index 5edec4d8..941113dc 100644 --- a/lsteamclient/steamclient_main.c +++ b/lsteamclient/steamclient_main.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "windef.h" #include "winbase.h" @@ -28,6 +30,17 @@ char g_tmppath[PATH_MAX]; static CRITICAL_SECTION steamclient_cs = { NULL, -1, 0, 0, 0, 0 }; static HANDLE steam_overlay_event; +static HANDLE callback_thread_handle; + +#define MAX_CALLBACK_QUEUE_SIZE 4 +struct callback_data *callback_queue[MAX_CALLBACK_QUEUE_SIZE]; +static unsigned int callback_queue_size; +static BOOL callback_queue_done; +static UINT64 callback_queue_current_seq_number; +static pthread_mutex_t callback_queue_mutex; +static pthread_cond_t callback_queue_callback_event; +static pthread_cond_t callback_queue_ready_event; +static pthread_cond_t callback_queue_complete_event; BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) { @@ -40,6 +53,22 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) steam_overlay_event = CreateEventA(NULL, TRUE, FALSE, "__wine_steamclient_GameOverlayActivated"); break; case DLL_PROCESS_DETACH: + if (callback_thread_handle) + { + /* Unfortunately we don't have a clear place to shutdown the thread so just kill it. */ + /* An explicit sync events and handle cleanup is to protect from unloading and loading + * .so again which may end up not actually reloading anyting and leaving the values of our static + * variables. */ + TerminateThread(callback_thread_handle, -1); + WaitForSingleObject(callback_thread_handle, INFINITE); + pthread_mutex_destroy(&callback_queue_mutex); + pthread_cond_destroy(&callback_queue_callback_event); + pthread_cond_destroy(&callback_queue_ready_event); + pthread_cond_destroy(&callback_queue_complete_event); + CloseHandle(callback_thread_handle); + callback_thread_handle = NULL; + TRACE("Terminated callback thread.\n"); + } CloseHandle(steam_overlay_event); break; } @@ -573,6 +602,84 @@ done: return ret; } +void execute_callback(struct callback_data *cb_data) +{ + /* No TRACEs or other Wine calls here, this is executed from Unix native thread + * which is not initialized by Wine. */ + cb_data->complete = FALSE; + pthread_mutex_lock(&callback_queue_mutex); + while (!callback_queue_done && callback_queue_size == MAX_CALLBACK_QUEUE_SIZE) + pthread_cond_wait(&callback_queue_ready_event, &callback_queue_mutex); + if (callback_queue_done) + { + pthread_mutex_unlock(&callback_queue_mutex); + return; + } + callback_queue[callback_queue_size++] = cb_data; + pthread_cond_broadcast(&callback_queue_callback_event); + while (!callback_queue_done && !cb_data->complete) + pthread_cond_wait(&callback_queue_complete_event, &callback_queue_mutex); + pthread_mutex_unlock(&callback_queue_mutex); +} + +static BOOL get_next_callback(struct callback_data *cb_data, UINT64 *cookie) +{ + BOOL ret; + + pthread_mutex_lock(&callback_queue_mutex); + while (!callback_queue_done && !callback_queue_size) + pthread_cond_wait(&callback_queue_callback_event, &callback_queue_mutex); + + if ((ret = !callback_queue_done)) + { + assert(callback_queue_size); + --callback_queue_size; + *cookie = (UINT64)(ULONG_PTR)callback_queue[callback_queue_size]; + *cb_data = *callback_queue[callback_queue_size]; + } + pthread_cond_broadcast(&callback_queue_ready_event); + pthread_mutex_unlock(&callback_queue_mutex); + return ret; +} + +static void callback_complete(UINT64 cookie) +{ + struct callback_data *cb_data = (struct callback_data *)(ULONG_PTR)cookie; + + pthread_mutex_lock(&callback_queue_mutex); + cb_data->complete = TRUE; + pthread_cond_broadcast(&callback_queue_complete_event); + pthread_mutex_unlock(&callback_queue_mutex); +} + +typedef void (WINAPI *win_FSteamNetworkingSocketsDebugOutput)(ESteamNetworkingSocketsDebugOutputType nType, + const char *pszMsg); + +static DWORD WINAPI callback_thread(void *dummy) +{ + struct callback_data cb_data; + UINT64 cookie; + + while (get_next_callback( &cb_data, &cookie)) + { + switch (cb_data.type) + { + case SOCKET_DEBUG_OUTPUT: + TRACE("SOCKET_DEBUG_OUTPUT func %p, type %u, msg %s.\n", + cb_data.func, cb_data.sockets_debug_output.type, + wine_dbgstr_a(cb_data.sockets_debug_output.msg)); + ((win_FSteamNetworkingSocketsDebugOutput)cb_data.func)(cb_data.sockets_debug_output.type, + cb_data.sockets_debug_output.msg); + callback_complete(cookie); + break; + default: + ERR("Unexpected callback type %u.\n", cb_data.type); + break; + } + } + return 0; +} + static void *steamclient_lib; static void *(*steamclient_CreateInterface)(const char *name, int *return_code); static bool (*steamclient_BGetCallback)(HSteamPipe a, CallbackMsg_t *b, int32 *c); @@ -583,6 +690,7 @@ static void (*steamclient_ReleaseThreadLocalMemory)(int); static int load_steamclient(void) { char path[PATH_MAX], resolved_path[PATH_MAX]; + DWORD callback_thread_id; if(steamclient_lib) return 1; @@ -643,6 +751,14 @@ static int load_steamclient(void) return 0; } + pthread_mutex_init(&callback_queue_mutex, NULL); + pthread_cond_init(&callback_queue_callback_event, NULL); + pthread_cond_init(&callback_queue_ready_event, NULL); + pthread_cond_init(&callback_queue_complete_event, NULL); + + callback_thread_handle = CreateThread(NULL, 0, callback_thread, NULL, 0, &callback_thread_id); + TRACE("Created callback thread 0x%04x.\n", callback_thread_id); + return 1; } diff --git a/lsteamclient/steamclient_private.h b/lsteamclient/steamclient_private.h index b5898bc7..03efa745 100644 --- a/lsteamclient/steamclient_private.h +++ b/lsteamclient/steamclient_private.h @@ -61,6 +61,29 @@ bool do_cb_wrap(HSteamPipe pipe, void *linux_side, bool (*cpp_func)(void *, Stea void *alloc_mem_for_iface(size_t size, const char *iface_version); +enum callback_type +{ + SOCKET_DEBUG_OUTPUT = 1, +}; + +struct callback_data +{ + enum callback_type type; + void *func; + int complete; + union + { + struct + { + unsigned int type; + const char *msg; + } + sockets_debug_output; + }; +}; + +void execute_callback(struct callback_data *cb_data); + #ifdef __cplusplus } #endif diff --git a/lsteamclient/steamclient_wrappers.c b/lsteamclient/steamclient_wrappers.c index 8e29c2c6..adb35391 100644 --- a/lsteamclient/steamclient_wrappers.c +++ b/lsteamclient/steamclient_wrappers.c @@ -293,13 +293,19 @@ void *create_LinuxISteamMatchmakingRulesResponse(void *win, const char *version) return ret; } - /***** FSteamNetworkingSocketsDebugOutput *****/ static void (__attribute__((ms_abi)) *stored_FSteamNetworkingSocketsDebugOutput)(ESteamNetworkingSocketsDebugOutputType nType, const char *pszMsg); static void lin_FSteamNetworkingSocketsDebugOutput(ESteamNetworkingSocketsDebugOutputType nType, const char *pszMsg) { - stored_FSteamNetworkingSocketsDebugOutput(nType, pszMsg); + struct callback_data cb_data = { 0 }; + /* Only Unix native calls from here (not even TRACE): + * this is native Unix thread which is not initialized by Wine. */ + cb_data.type = SOCKET_DEBUG_OUTPUT; + cb_data.func = stored_FSteamNetworkingSocketsDebugOutput; + cb_data.sockets_debug_output.type = nType; + cb_data.sockets_debug_output.msg = pszMsg; + execute_callback(&cb_data); } void *manual_convert_FSteamNetworkingSocketsDebugOutput(void *win_func)