[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