[PATCH] winmm: Use a thread to send driver messages on Win9x

Bruno Jesus 00cpxxx at gmail.com
Thu Dec 1 00:40:22 CST 2016


More detailed explanation in the patch comments. Basically the games use a DLL that calls SuspendThread from inside the WinMM callback, this makes the program hang as we use the "main thread" to dispatch callbacks.

Games tested in Win95 config:
Heroes of Might & Magic (well known affected)
Deadlock (well known affected)
Worms 2 (just to ensure things still work)
Age of Empires (just to ensure things still work)
Shivers (just to ensure things still work)

Fixes bug https://bugs.winehq.org/show_bug.cgi?id=3930

Signed-off-by: Bruno Jesus <00cpxxx at gmail.com>
---
 dlls/winmm/waveform.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 76 insertions(+), 2 deletions(-)

diff --git a/dlls/winmm/waveform.c b/dlls/winmm/waveform.c
index 7308519..24d7e30 100644
--- a/dlls/winmm/waveform.c
+++ b/dlls/winmm/waveform.c
@@ -134,6 +134,14 @@ struct _WINMM_MMDevice {
     WINMM_Device *devices[MAX_DEVICES];
 };
 
+static struct {
+    BOOL is, checked;
+    HANDLE thread, event_main, event_thread;
+    WINMM_CBInfo *info;
+    WORD msg;
+    DWORD_PTR param1, param2;
+} win9x;
+
 static WINMM_MMDevice *g_out_mmdevices;
 static WINMM_MMDevice **g_out_map;
 static UINT g_outmmdevices_count;
@@ -286,6 +294,13 @@ static WINMM_Device *WINMM_FindUnusedDevice(WINMM_Device **devices,
 {
     UINT i;
 
+    if (!win9x.checked)
+    {
+        win9x.checked = TRUE;
+        if ((GetVersion() & 0xFF) < 5)
+            win9x.is = TRUE;
+    }
+
     for(i = 0; i < MAX_DEVICES; ++i){
         WINMM_Device *device = devices[i];
 
@@ -370,13 +385,61 @@ static WINMM_Device *WINMM_GetDeviceFromHWAVE(HWAVE hwave)
     return device;
 }
 
+DWORD WINAPI win9x_callback_thread(LPVOID param)
+{
+    while(WaitForSingleObject(win9x.event_thread, INFINITE) == WAIT_OBJECT_0)
+    {
+        if (!win9x.info->hwave) break;
+        DriverCallback(win9x.info->callback, win9x.info->flags, (HDRVR)win9x.info->hwave,
+                       win9x.msg, win9x.info->user, win9x.param1, win9x.param2);
+        SetEvent(win9x.event_main);
+    }
+    return 0;
+}
+
 /* Note: NotifyClient should never be called while holding the device lock
  * since the client may call wave* functions from within the callback. */
 static inline void WINMM_NotifyClient(WINMM_CBInfo *info, WORD msg, DWORD_PTR param1,
         DWORD_PTR param2)
 {
-    DriverCallback(info->callback, info->flags, (HDRVR)info->hwave,
-        msg, info->user, param1, param2);
+    if (win9x.is)
+    {
+        /* WAIL32.DLL from Rad Game Tools is an audio helper DLL that simplifies the use of sound
+         * effects in an application. It seems to be released in 1996 and is used at least by Heroes
+         * of Might and Magic and Deadlock. Unfortunately during a callback to the DLL it calls
+         * SuspendThread on the main thread and keeps doing some stuff from inside the callback until
+         * it does a ResumeThread and returns from the callback. This works in Windows 95/98 because
+         * they use a separate thread to send callback messages. XP changed this by going back to a
+         * single thread but the games still works due to shim.
+         */
+        if (!win9x.thread)
+        {
+            TRACE("Using old behavior of WinMM callbacks\n");
+            win9x.event_thread = CreateEventA(NULL, FALSE, FALSE, NULL);
+            win9x.event_main = CreateEventA(NULL, FALSE, FALSE, NULL);
+            win9x.thread = CreateThread(NULL, 0, win9x_callback_thread, NULL, 0, NULL);
+            if (!win9x.event_thread || !win9x.event_main || !win9x.thread)
+            {
+                ERR("Failed to create resources for callback, expect problems!\n");
+                TerminateThread(win9x.thread, 0);
+                CloseHandle(win9x.event_main);
+                CloseHandle(win9x.event_thread);
+                CloseHandle(win9x.thread);
+                win9x.event_main = win9x.event_thread = win9x.thread = NULL;
+                return;
+            }
+        }
+        /* Prepare callback data for the thread, dispatch and wait response */
+        win9x.info = info;
+        win9x.msg = msg;
+        win9x.param1 = param1;
+        win9x.param2 = param2;
+        SetEvent(win9x.event_thread);
+        WaitForSingleObject(win9x.event_main, INFINITE);
+    }
+    else
+        DriverCallback(info->callback, info->flags, (HDRVR)info->hwave,
+            msg, info->user, param1, param2);
 }
 
 static MMRESULT hr2mmr(HRESULT hr)
@@ -1419,6 +1482,17 @@ static HRESULT WINMM_CloseDevice(WINMM_Device *device)
     device->clock = NULL;
 
     HeapFree(GetProcessHeap(), 0, device->orig_fmt);
+    if (win9x.thread)
+    {
+        /* warn the thread so it can die gracefully */
+        win9x.info->hwave = 0;
+        SetEvent(win9x.event_thread);
+
+        CloseHandle(win9x.event_main);
+        CloseHandle(win9x.event_thread);
+        CloseHandle(win9x.thread);
+        win9x.event_main = win9x.event_thread = win9x.thread = NULL;
+    }
 
     return S_OK;
 }
-- 
2.9.3




More information about the wine-patches mailing list