diff --git a/patches/ole32-HGLOBALStream/0001-ole32-tests-Add-a-bunch-of-tests-for-HGLOBAL-based-I.patch b/patches/ole32-HGLOBALStream/0001-ole32-tests-Add-a-bunch-of-tests-for-HGLOBAL-based-I.patch new file mode 100644 index 00000000..f402a0c1 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0001-ole32-tests-Add-a-bunch-of-tests-for-HGLOBAL-based-I.patch @@ -0,0 +1,279 @@ +From 267315dd040ccfa527f8ba48038a268236ca1d29 Mon Sep 17 00:00:00 2001 +From: Dmitry Timoshkov +Date: Tue, 2 Feb 2016 12:49:16 +0800 +Subject: ole32/tests: Add a bunch of tests for HGLOBAL based IStream::Clone. + +--- + dlls/ole32/tests/hglobalstream.c | 244 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 244 insertions(+) + +diff --git a/dlls/ole32/tests/hglobalstream.c b/dlls/ole32/tests/hglobalstream.c +index e20d81b..0dc3c52 100644 +--- a/dlls/ole32/tests/hglobalstream.c ++++ b/dlls/ole32/tests/hglobalstream.c +@@ -2,6 +2,7 @@ + * Stream on HGLOBAL Tests + * + * Copyright 2006 Robert Shearman (for CodeWeavers) ++ * Copyright 2016 Dmitry Timoshkov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -508,11 +509,254 @@ static void test_freed_hglobal(void) + IStream_Release(pStream); + } + ++static void stream_info(IStream *stream, HGLOBAL *hmem, int *size, int *pos) ++{ ++ HRESULT hr; ++ STATSTG stat; ++ LARGE_INTEGER offset; ++ ULARGE_INTEGER newpos; ++ ++ *hmem = 0; ++ *size = *pos = -1; ++ ++ hr = GetHGlobalFromStream(stream, hmem); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ memset(&stat, 0x55, sizeof(stat)); ++ hr = IStream_Stat(stream, &stat, STATFLAG_DEFAULT); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ok(stat.type == STGTY_STREAM, "unexpected %#x\n", stat.type); ++ ok(!stat.pwcsName, "unexpected %p\n", stat.pwcsName); ++ ok(IsEqualIID(&stat.clsid, &GUID_NULL), "unexpected %s\n", wine_dbgstr_guid(&stat.clsid)); ++ ok(!stat.cbSize.HighPart, "unexpected %#x\n", stat.cbSize.HighPart); ++ *size = stat.cbSize.LowPart; ++ ++ offset.QuadPart = 0; ++ hr = IStream_Seek(stream, offset, STREAM_SEEK_CUR, &newpos); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ok(!newpos.HighPart, "unexpected %#x\n", newpos.HighPart); ++ *pos = newpos.LowPart; ++} ++ ++static void test_IStream_Clone(void) ++{ ++ static const char hello[] = "Hello World!"; ++ char buf[32]; ++ HRESULT hr; ++ IStream *stream, *clone; ++ HGLOBAL orig_hmem, hmem, hmem_clone; ++ ULARGE_INTEGER newsize; ++ LARGE_INTEGER offset; ++ int size, pos, ret; ++ ++ /* test simple case for Clone */ ++ orig_hmem = GlobalAlloc(GMEM_MOVEABLE, 0); ++ ok(orig_hmem != 0, "unexpected %p\n", orig_hmem); ++ hr = CreateStreamOnHGlobal(orig_hmem, TRUE, &stream); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem == orig_hmem, "handles should match\n"); ++ ok(size == 0, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ hr = IStream_Clone(stream, &clone); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ hr = IStream_Write(stream, hello, sizeof(hello), NULL); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++ ok(size == 13, "unexpected %d\n", size); ++ ok(pos == 13, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 13, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ buf[0] = 0; ++ hr = IStream_Read(clone, buf, sizeof(buf), NULL); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++todo_wine ++ ok(!strcmp(buf, hello), "wrong stream contents\n"); ++ ++ newsize.QuadPart = 0x8000; ++ hr = IStream_SetSize(stream, newsize); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++ ok(hmem == orig_hmem, "unexpected %p\n", hmem); ++ ok(size == 0x8000, "unexpected %#x\n", size); ++ ok(pos == 13, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++todo_wine ++ ok(pos == 13, "unexpected %d\n", pos); ++ ++ IStream_Release(clone); ++ IStream_Release(stream); ++ ++ /* exploit GMEM_FIXED forced move for the same base streams */ ++ orig_hmem = GlobalAlloc(GMEM_FIXED, 1); ++ ok(orig_hmem != 0, "unexpected %p\n", orig_hmem); ++ hr = CreateStreamOnHGlobal(orig_hmem, TRUE, &stream); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ hr = IStream_Clone(stream, &clone); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++ ok(size == 1, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++ ok(size == 1, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ newsize.QuadPart = 0x8000; ++ hr = IStream_SetSize(stream, newsize); ++todo_wine ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++todo_wine ++ ok(hmem != orig_hmem, "unexpected %p\n", hmem); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ IStream_Release(stream); ++ IStream_Release(clone); ++ ++ /* exploit GMEM_FIXED forced move for different base streams */ ++ orig_hmem = GlobalAlloc(GMEM_FIXED, 1); ++ ok(orig_hmem != 0, "unexpected %p\n", orig_hmem); ++ hr = CreateStreamOnHGlobal(orig_hmem, TRUE, &stream); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ hr = CreateStreamOnHGlobal(orig_hmem, TRUE, &clone); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++ ok(size == 1, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++ ok(size == 1, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ newsize.QuadPart = 0x8000; ++ hr = IStream_SetSize(stream, newsize); ++todo_wine ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++todo_wine ++ ok(hmem != orig_hmem, "unexpected %p\n", hmem); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++todo_wine ++ ok(hmem_clone != hmem, "handles should not match\n"); ++ ok(size == 1, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ IStream_Release(stream); ++ /* releasing clone leads to test termination under windows ++ IStream_Release(clone); ++ */ ++ ++ /* test Release for a being cloned stream */ ++ hr = CreateStreamOnHGlobal(0, TRUE, &stream); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ hr = IStream_Clone(stream, &clone); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(stream, &hmem, &size, &pos); ++ ok(hmem != 0, "unexpected %p\n", hmem); ++ ok(size == 0, "unexpected %d\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++ ok(size == 0, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ ret = IStream_Release(stream); ++ ok(ret == 0, "unexpected %d\n", ret); ++ ++ newsize.QuadPart = 0x8000; ++ hr = IStream_SetSize(clone, newsize); ++todo_wine ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++ ok(pos == 0, "unexpected %d\n", pos); ++ ++ hr = IStream_Write(clone, hello, sizeof(hello), NULL); ++todo_wine ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++todo_wine ++ ok(pos == 13, "unexpected %d\n", pos); ++ ++ offset.QuadPart = 0; ++ hr = IStream_Seek(clone, offset, STREAM_SEEK_SET, NULL); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++ ++ buf[0] = 0; ++ hr = IStream_Read(clone, buf, sizeof(buf), NULL); ++ ok(hr == S_OK, "unexpected %#x\n", hr); ++todo_wine ++ ok(!strcmp(buf, hello), "wrong stream contents\n"); ++ ++ stream_info(clone, &hmem_clone, &size, &pos); ++ ok(hmem_clone == hmem, "handles should match\n"); ++todo_wine ++ ok(size == 0x8000, "unexpected %#x\n", size); ++todo_wine ++ ok(pos == 32, "unexpected %d\n", pos); ++ ++ ret = IStream_Release(clone); ++ ok(ret == 0, "unexpected %d\n", ret); ++} ++ + START_TEST(hglobalstream) + { + HRESULT hr; + IStream *pStream; + ++ test_IStream_Clone(); ++ + hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream); + ok_ole_success(hr, "CreateStreamOnHGlobal"); + +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0002-ole32-Add-a-check-for-hglobal-pointer-to-GetHGlobalF.patch b/patches/ole32-HGLOBALStream/0002-ole32-Add-a-check-for-hglobal-pointer-to-GetHGlobalF.patch new file mode 100644 index 00000000..70237096 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0002-ole32-Add-a-check-for-hglobal-pointer-to-GetHGlobalF.patch @@ -0,0 +1,47 @@ +From a26bfabac3940176f412896241ffab5c7f92baaa Mon Sep 17 00:00:00 2001 +From: Dmitry Timoshkov +Date: Tue, 2 Feb 2016 12:53:47 +0800 +Subject: ole32: Add a check for hglobal pointer to GetHGlobalFromStream. + +--- + dlls/ole32/hglobalstream.c | 4 ++-- + dlls/ole32/tests/hglobalstream.c | 6 ++++++ + 2 files changed, 8 insertions(+), 2 deletions(-) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index 655e380..b86de56 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -621,10 +621,10 @@ HRESULT WINAPI GetHGlobalFromStream(IStream* pstm, HGLOBAL* phglobal) + { + HGLOBALStreamImpl* pStream; + +- if (pstm == NULL) ++ if (!pstm || !phglobal) + return E_INVALIDARG; + +- pStream = (HGLOBALStreamImpl*) pstm; ++ pStream = impl_from_IStream(pstm); + + /* + * Verify that the stream object was created with CreateStreamOnHGlobal. +diff --git a/dlls/ole32/tests/hglobalstream.c b/dlls/ole32/tests/hglobalstream.c +index 0dc3c52..0453e54 100644 +--- a/dlls/ole32/tests/hglobalstream.c ++++ b/dlls/ole32/tests/hglobalstream.c +@@ -555,6 +555,12 @@ static void test_IStream_Clone(void) + hr = CreateStreamOnHGlobal(orig_hmem, TRUE, &stream); + ok(hr == S_OK, "unexpected %#x\n", hr); + ++ hr = GetHGlobalFromStream(stream, NULL); ++ ok(hr == E_INVALIDARG, "unexpected %#x\n", hr); ++ ++ hr = GetHGlobalFromStream(NULL, &hmem); ++ ok(hr == E_INVALIDARG, "unexpected %#x\n", hr); ++ + stream_info(stream, &hmem, &size, &pos); + ok(hmem == orig_hmem, "handles should match\n"); + ok(size == 0, "unexpected %d\n", size); +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0003-ole32-Add-a-wrapper-for-memory-block-managed-by-HGLO.patch b/patches/ole32-HGLOBALStream/0003-ole32-Add-a-wrapper-for-memory-block-managed-by-HGLO.patch new file mode 100644 index 00000000..e10fb319 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0003-ole32-Add-a-wrapper-for-memory-block-managed-by-HGLO.patch @@ -0,0 +1,422 @@ +From 3c9954c3838d7504fad0213ade7363747e7a6805 Mon Sep 17 00:00:00 2001 +From: Dmitry Timoshkov +Date: Tue, 2 Feb 2016 13:03:58 +0800 +Subject: ole32: Add a wrapper for memory block managed by HGLOBAL based + IStream. + +Based on a suggestion of Sebastian Lackner . +--- + dlls/ole32/hglobalstream.c | 202 +++++++++++++++++++++++++++------------ + dlls/ole32/tests/hglobalstream.c | 12 --- + 2 files changed, 140 insertions(+), 74 deletions(-) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index b86de56..5eb6320 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -5,6 +5,7 @@ + * for streams contained supported by an HGLOBAL pointer. + * + * Copyright 1999 Francis Beaudet ++ * Copyright 2016 Dmitry Timoshkov + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -42,7 +43,96 @@ + + #include "wine/debug.h" + +-WINE_DEFAULT_DEBUG_CHANNEL(storage); ++WINE_DEFAULT_DEBUG_CHANNEL(hglobalstream); ++ ++struct handle_wrapper ++{ ++ LONG ref; ++ HGLOBAL hglobal; ++ ULONG size; ++ BOOL delete_on_release; ++ CRITICAL_SECTION lock; ++}; ++ ++static void handle_addref(struct handle_wrapper *handle) ++{ ++ InterlockedIncrement(&handle->ref); ++} ++ ++static void handle_release(struct handle_wrapper *handle) ++{ ++ ULONG ref = InterlockedDecrement(&handle->ref); ++ ++ if (!ref) ++ { ++ if (handle->delete_on_release) ++ { ++ GlobalFree(handle->hglobal); ++ handle->hglobal = NULL; ++ } ++ ++ DeleteCriticalSection(&handle->lock); ++ HeapFree(GetProcessHeap(), 0, handle); ++ } ++} ++ ++static void *handle_lock(struct handle_wrapper *handle) ++{ ++ return GlobalLock(handle->hglobal); ++} ++ ++static void handle_unlock(struct handle_wrapper *handle) ++{ ++ GlobalUnlock(handle->hglobal); ++} ++ ++static HGLOBAL handle_gethglobal(struct handle_wrapper *handle) ++{ ++ return handle->hglobal; ++} ++ ++static HRESULT handle_setsize(struct handle_wrapper *handle, ULONG size) ++{ ++ HRESULT hr = S_OK; ++ ++ EnterCriticalSection(&handle->lock); ++ ++ if (handle->size != size) ++ { ++ HGLOBAL hglobal = GlobalReAlloc(handle->hglobal, size, 0); ++ if (hglobal) ++ { ++ handle->hglobal = hglobal; ++ handle->size = size; ++ } ++ else ++ hr = E_OUTOFMEMORY; ++ } ++ ++ LeaveCriticalSection(&handle->lock); ++ return hr; ++} ++ ++static ULONG handle_getsize(struct handle_wrapper *handle) ++{ ++ return handle->size; ++} ++ ++static struct handle_wrapper *handle_create(HGLOBAL hglobal, BOOL delete_on_release) ++{ ++ struct handle_wrapper *handle; ++ ++ handle = HeapAlloc(GetProcessHeap(), 0, sizeof(*handle)); ++ if (handle) ++ { ++ handle->ref = 1; ++ handle->hglobal = hglobal; ++ handle->size = GlobalSize(hglobal); ++ handle->delete_on_release = delete_on_release; ++ InitializeCriticalSection(&handle->lock); ++ } ++ return handle; ++} + + /**************************************************************************** + * HGLOBALStreamImpl definition. +@@ -55,14 +145,7 @@ typedef struct + IStream IStream_iface; + LONG ref; + +- /* support for the stream */ +- HGLOBAL supportHandle; +- +- /* if TRUE the HGLOBAL is destroyed when the stream is finally released */ +- BOOL deleteOnRelease; +- +- /* size of the stream */ +- ULARGE_INTEGER streamSize; ++ struct handle_wrapper *handle; + + /* current position of the cursor */ + ULARGE_INTEGER currentPosition; +@@ -114,12 +197,7 @@ static ULONG WINAPI HGLOBALStreamImpl_Release( + + if (!ref) + { +- if (This->deleteOnRelease) +- { +- GlobalFree(This->supportHandle); +- This->supportHandle = NULL; +- } +- ++ handle_release(This->handle); + HeapFree(GetProcessHeap(), 0, This); + } + +@@ -161,15 +239,15 @@ static HRESULT WINAPI HGLOBALStreamImpl_Read( + * Using the known size of the stream, calculate the number of bytes + * to read from the block chain + */ +- bytesToReadFromBuffer = min( This->streamSize.u.LowPart - This->currentPosition.u.LowPart, cb); ++ bytesToReadFromBuffer = min( handle_getsize(This->handle) - This->currentPosition.u.LowPart, cb); + + /* + * Lock the buffer in position and copy the data. + */ +- supportBuffer = GlobalLock(This->supportHandle); ++ supportBuffer = handle_lock(This->handle); + if (!supportBuffer) + { +- WARN("read from invalid hglobal %p\n", This->supportHandle); ++ WARN("read from invalid hglobal %p\n", handle_gethglobal(This->handle)); + *pcbRead = 0; + return S_OK; + } +@@ -189,7 +267,7 @@ static HRESULT WINAPI HGLOBALStreamImpl_Read( + /* + * Cleanup + */ +- GlobalUnlock(This->supportHandle); ++ handle_unlock(This->handle); + + /* + * Always returns S_OK even if the end of the stream is reached before the +@@ -241,7 +319,7 @@ static HRESULT WINAPI HGLOBALStreamImpl_Write( + /* + * Verify if we need to grow the stream + */ +- if (newSize.u.LowPart > This->streamSize.u.LowPart) ++ if (newSize.u.LowPart > handle_getsize(This->handle)) + { + /* grow stream */ + HRESULT hr = IStream_SetSize(iface, newSize); +@@ -255,10 +333,10 @@ static HRESULT WINAPI HGLOBALStreamImpl_Write( + /* + * Lock the buffer in position and copy the data. + */ +- supportBuffer = GlobalLock(This->supportHandle); ++ supportBuffer = handle_lock(This->handle); + if (!supportBuffer) + { +- WARN("write to invalid hglobal %p\n", This->supportHandle); ++ WARN("write to invalid hglobal %p\n", handle_gethglobal(This->handle)); + return S_OK; + } + +@@ -272,7 +350,7 @@ static HRESULT WINAPI HGLOBALStreamImpl_Write( + /* + * Cleanup + */ +- GlobalUnlock(This->supportHandle); ++ handle_unlock(This->handle); + + out: + /* +@@ -318,7 +396,7 @@ static HRESULT WINAPI HGLOBALStreamImpl_Seek( + case STREAM_SEEK_CUR: + break; + case STREAM_SEEK_END: +- newPosition = This->streamSize; ++ newPosition.QuadPart = handle_getsize(This->handle); + break; + default: + hr = STG_E_SEEKERROR; +@@ -363,29 +441,13 @@ static HRESULT WINAPI HGLOBALStreamImpl_SetSize( + ULARGE_INTEGER libNewSize) /* [in] */ + { + HGLOBALStreamImpl* This = impl_from_IStream(iface); +- HGLOBAL supportHandle; + + TRACE("(%p, %d)\n", iface, libNewSize.u.LowPart); + + /* + * HighPart is ignored as shown in tests + */ +- +- if (This->streamSize.u.LowPart == libNewSize.u.LowPart) +- return S_OK; +- +- /* +- * Re allocate the HGlobal to fit the new size of the stream. +- */ +- supportHandle = GlobalReAlloc(This->supportHandle, libNewSize.u.LowPart, 0); +- +- if (supportHandle == 0) +- return E_OUTOFMEMORY; +- +- This->supportHandle = supportHandle; +- This->streamSize.u.LowPart = libNewSize.u.LowPart; +- +- return S_OK; ++ return handle_setsize(This->handle, libNewSize.u.LowPart); + } + + /*** +@@ -533,24 +595,49 @@ static HRESULT WINAPI HGLOBALStreamImpl_Stat( + + pstatstg->pwcsName = NULL; + pstatstg->type = STGTY_STREAM; +- pstatstg->cbSize = This->streamSize; ++ pstatstg->cbSize.QuadPart = handle_getsize(This->handle); + + return S_OK; + } + ++static const IStreamVtbl HGLOBALStreamImplVtbl; ++ ++static HGLOBALStreamImpl *HGLOBALStreamImpl_Create(void) ++{ ++ HGLOBALStreamImpl *This; ++ ++ This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This)); ++ if (This) ++ { ++ This->IStream_iface.lpVtbl = &HGLOBALStreamImplVtbl; ++ This->ref = 1; ++ } ++ return This; ++} ++ + static HRESULT WINAPI HGLOBALStreamImpl_Clone( + IStream* iface, + IStream** ppstm) /* [out] */ + { + HGLOBALStreamImpl* This = impl_from_IStream(iface); ++ HGLOBALStreamImpl* clone; + ULARGE_INTEGER dummy; + LARGE_INTEGER offset; +- HRESULT hr; + +- TRACE(" Cloning %p (deleteOnRelease=%d seek position=%ld)\n",iface,This->deleteOnRelease,(long)This->currentPosition.QuadPart); +- hr = CreateStreamOnHGlobal(This->supportHandle, FALSE, ppstm); +- if(FAILED(hr)) +- return hr; ++ if (!ppstm) return E_INVALIDARG; ++ ++ *ppstm = NULL; ++ ++ TRACE(" Cloning %p (seek position=%d)\n", iface, This->currentPosition.u.LowPart); ++ ++ clone = HGLOBALStreamImpl_Create(); ++ if (!clone) return E_OUTOFMEMORY; ++ ++ *ppstm = &clone->IStream_iface; ++ ++ handle_addref(This->handle); ++ clone->handle = This->handle; ++ + offset.QuadPart = (LONGLONG)This->currentPosition.QuadPart; + IStream_Seek(*ppstm, offset, STREAM_SEEK_SET, &dummy); + return S_OK; +@@ -587,28 +674,19 @@ HRESULT WINAPI CreateStreamOnHGlobal( + if (!ppstm) + return E_INVALIDARG; + +- This = HeapAlloc(GetProcessHeap(), 0, sizeof(HGLOBALStreamImpl)); ++ This = HGLOBALStreamImpl_Create(); + if (!This) return E_OUTOFMEMORY; + +- This->IStream_iface.lpVtbl = &HGLOBALStreamImplVtbl; +- This->ref = 1; +- +- /* initialize the support */ +- This->supportHandle = hGlobal; +- This->deleteOnRelease = fDeleteOnRelease; +- + /* allocate a handle if one is not supplied */ +- if (!This->supportHandle) +- This->supportHandle = GlobalAlloc(GMEM_MOVEABLE|GMEM_NODISCARD|GMEM_SHARE, 0); ++ if (!hGlobal) ++ hGlobal = GlobalAlloc(GMEM_MOVEABLE|GMEM_NODISCARD|GMEM_SHARE, 0); ++ ++ This->handle = handle_create(hGlobal, fDeleteOnRelease); + + /* start at the beginning */ + This->currentPosition.u.HighPart = 0; + This->currentPosition.u.LowPart = 0; + +- /* initialize the size of the stream to the size of the handle */ +- This->streamSize.u.HighPart = 0; +- This->streamSize.u.LowPart = GlobalSize(This->supportHandle); +- + *ppstm = &This->IStream_iface; + + return S_OK; +@@ -630,7 +708,7 @@ HRESULT WINAPI GetHGlobalFromStream(IStream* pstm, HGLOBAL* phglobal) + * Verify that the stream object was created with CreateStreamOnHGlobal. + */ + if (pStream->IStream_iface.lpVtbl == &HGLOBALStreamImplVtbl) +- *phglobal = pStream->supportHandle; ++ *phglobal = handle_gethglobal(pStream->handle); + else + { + *phglobal = 0; +diff --git a/dlls/ole32/tests/hglobalstream.c b/dlls/ole32/tests/hglobalstream.c +index 0453e54..afda4e6 100644 +--- a/dlls/ole32/tests/hglobalstream.c ++++ b/dlls/ole32/tests/hglobalstream.c +@@ -579,14 +579,12 @@ static void test_IStream_Clone(void) + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 13, "unexpected %d\n", size); + ok(pos == 0, "unexpected %d\n", pos); + + buf[0] = 0; + hr = IStream_Read(clone, buf, sizeof(buf), NULL); + ok(hr == S_OK, "unexpected %#x\n", hr); +-todo_wine + ok(!strcmp(buf, hello), "wrong stream contents\n"); + + newsize.QuadPart = 0x8000; +@@ -601,9 +599,7 @@ todo_wine + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); +-todo_wine + ok(pos == 13, "unexpected %d\n", pos); + + IStream_Release(clone); +@@ -715,24 +711,19 @@ todo_wine + + newsize.QuadPart = 0x8000; + hr = IStream_SetSize(clone, newsize); +-todo_wine + ok(hr == S_OK, "unexpected %#x\n", hr); + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); + ok(pos == 0, "unexpected %d\n", pos); + + hr = IStream_Write(clone, hello, sizeof(hello), NULL); +-todo_wine + ok(hr == S_OK, "unexpected %#x\n", hr); + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); +-todo_wine + ok(pos == 13, "unexpected %d\n", pos); + + offset.QuadPart = 0; +@@ -742,14 +733,11 @@ todo_wine + buf[0] = 0; + hr = IStream_Read(clone, buf, sizeof(buf), NULL); + ok(hr == S_OK, "unexpected %#x\n", hr); +-todo_wine + ok(!strcmp(buf, hello), "wrong stream contents\n"); + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); +-todo_wine + ok(pos == 32, "unexpected %d\n", pos); + + ret = IStream_Release(clone); +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0004-ole32-Set-DebugInfo-Spare-0-for-handle_wrapper-lock.patch b/patches/ole32-HGLOBALStream/0004-ole32-Set-DebugInfo-Spare-0-for-handle_wrapper-lock.patch new file mode 100644 index 00000000..bef13a7a --- /dev/null +++ b/patches/ole32-HGLOBALStream/0004-ole32-Set-DebugInfo-Spare-0-for-handle_wrapper-lock.patch @@ -0,0 +1,32 @@ +From e6b772e0000dac0c50a8070b46413352e9c86174 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sat, 6 Feb 2016 03:26:10 +0100 +Subject: ole32: Set DebugInfo->Spare[0] for handle_wrapper lock. + +--- + dlls/ole32/hglobalstream.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index 5eb6320..8b7e960 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -71,6 +71,7 @@ static void handle_release(struct handle_wrapper *handle) + handle->hglobal = NULL; + } + ++ handle->lock.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&handle->lock); + HeapFree(GetProcessHeap(), 0, handle); + } +@@ -130,6 +131,7 @@ static struct handle_wrapper *handle_create(HGLOBAL hglobal, BOOL delete_on_rele + handle->size = GlobalSize(hglobal); + handle->delete_on_release = delete_on_release; + InitializeCriticalSection(&handle->lock); ++ handle->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": handle_wrapper.lock"); + } + return handle; + } +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0005-ole32-Allow-moving-a-being-reallocated-block-of-memo.patch b/patches/ole32-HGLOBALStream/0005-ole32-Allow-moving-a-being-reallocated-block-of-memo.patch new file mode 100644 index 00000000..301f2664 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0005-ole32-Allow-moving-a-being-reallocated-block-of-memo.patch @@ -0,0 +1,72 @@ +From b7849b3a81d2e55ccfc3d61a8a14bbacf878438b Mon Sep 17 00:00:00 2001 +From: Dmitry Timoshkov +Date: Tue, 2 Feb 2016 15:35:59 +0800 +Subject: ole32: Allow moving a being reallocated block of memory managed by + HGLOBAL based IStream. + +--- + dlls/ole32/hglobalstream.c | 2 +- + dlls/ole32/tests/hglobalstream.c | 8 -------- + 2 files changed, 1 insertion(+), 9 deletions(-) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index 8b7e960..3e7f67b 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -100,7 +100,7 @@ static HRESULT handle_setsize(struct handle_wrapper *handle, ULONG size) + + if (handle->size != size) + { +- HGLOBAL hglobal = GlobalReAlloc(handle->hglobal, size, 0); ++ HGLOBAL hglobal = GlobalReAlloc(handle->hglobal, size, GMEM_MOVEABLE); + if (hglobal) + { + handle->hglobal = hglobal; +diff --git a/dlls/ole32/tests/hglobalstream.c b/dlls/ole32/tests/hglobalstream.c +index afda4e6..9aa3b6c 100644 +--- a/dlls/ole32/tests/hglobalstream.c ++++ b/dlls/ole32/tests/hglobalstream.c +@@ -626,20 +626,16 @@ static void test_IStream_Clone(void) + + newsize.QuadPart = 0x8000; + hr = IStream_SetSize(stream, newsize); +-todo_wine + ok(hr == S_OK, "unexpected %#x\n", hr); + + stream_info(stream, &hmem, &size, &pos); + ok(hmem != 0, "unexpected %p\n", hmem); +-todo_wine + ok(hmem != orig_hmem, "unexpected %p\n", hmem); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); + ok(pos == 0, "unexpected %d\n", pos); + + stream_info(clone, &hmem_clone, &size, &pos); + ok(hmem_clone == hmem, "handles should match\n"); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); + ok(pos == 0, "unexpected %d\n", pos); + +@@ -667,19 +663,15 @@ todo_wine + + newsize.QuadPart = 0x8000; + hr = IStream_SetSize(stream, newsize); +-todo_wine + ok(hr == S_OK, "unexpected %#x\n", hr); + + stream_info(stream, &hmem, &size, &pos); + ok(hmem != 0, "unexpected %p\n", hmem); +-todo_wine + ok(hmem != orig_hmem, "unexpected %p\n", hmem); +-todo_wine + ok(size == 0x8000, "unexpected %#x\n", size); + ok(pos == 0, "unexpected %d\n", pos); + + stream_info(clone, &hmem_clone, &size, &pos); +-todo_wine + ok(hmem_clone != hmem, "handles should not match\n"); + ok(size == 1, "unexpected %#x\n", size); + ok(pos == 0, "unexpected %d\n", pos); +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0006-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Rea.patch b/patches/ole32-HGLOBALStream/0006-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Rea.patch new file mode 100644 index 00000000..a4ed30b8 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0006-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Rea.patch @@ -0,0 +1,115 @@ +From f5c31437019892510e36f603cbde72d9204a4155 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sat, 6 Feb 2016 03:50:55 +0100 +Subject: ole32: Improve thread-safety of HGLOBALStreamImpl_Read. + +--- + dlls/ole32/hglobalstream.c | 83 ++++++++++++++++++---------------------------- + 1 file changed, 32 insertions(+), 51 deletions(-) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index 3e7f67b..27ac706 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -87,6 +87,34 @@ static void handle_unlock(struct handle_wrapper *handle) + GlobalUnlock(handle->hglobal); + } + ++static ULONG handle_read(struct handle_wrapper *handle, ULONG *pos, void *dest, ULONG len) ++{ ++ void *source; ++ ++ EnterCriticalSection(&handle->lock); ++ ++ if (*pos < handle->size) ++ len = min(handle->size - *pos, len); ++ else ++ len = 0; ++ ++ source = GlobalLock(handle->hglobal); ++ if (source) ++ { ++ memcpy(dest, (char *)source + *pos, len); ++ *pos += len; ++ GlobalUnlock(handle->hglobal); ++ } ++ else ++ { ++ WARN("read from invalid hglobal %p\n", handle->hglobal); ++ len = 0; ++ } ++ ++ LeaveCriticalSection(&handle->lock); ++ return len; ++} ++ + static HGLOBAL handle_gethglobal(struct handle_wrapper *handle) + { + return handle->hglobal; +@@ -222,59 +250,12 @@ static HRESULT WINAPI HGLOBALStreamImpl_Read( + ULONG* pcbRead) /* [out] */ + { + HGLOBALStreamImpl* This = impl_from_IStream(iface); ++ ULONG num_bytes; + +- void* supportBuffer; +- ULONG bytesReadBuffer; +- ULONG bytesToReadFromBuffer; +- +- TRACE("(%p, %p, %d, %p)\n", iface, +- pv, cb, pcbRead); +- +- /* +- * If the caller is not interested in the number of bytes read, +- * we use another buffer to avoid "if" statements in the code. +- */ +- if (pcbRead==0) +- pcbRead = &bytesReadBuffer; +- +- /* +- * Using the known size of the stream, calculate the number of bytes +- * to read from the block chain +- */ +- bytesToReadFromBuffer = min( handle_getsize(This->handle) - This->currentPosition.u.LowPart, cb); +- +- /* +- * Lock the buffer in position and copy the data. +- */ +- supportBuffer = handle_lock(This->handle); +- if (!supportBuffer) +- { +- WARN("read from invalid hglobal %p\n", handle_gethglobal(This->handle)); +- *pcbRead = 0; +- return S_OK; +- } +- +- memcpy(pv, (char *) supportBuffer+This->currentPosition.u.LowPart, bytesToReadFromBuffer); +- +- /* +- * Move the current position to the new position +- */ +- This->currentPosition.u.LowPart+=bytesToReadFromBuffer; +- +- /* +- * Return the number of bytes read. +- */ +- *pcbRead = bytesToReadFromBuffer; +- +- /* +- * Cleanup +- */ +- handle_unlock(This->handle); ++ TRACE("(%p, %p, %d, %p)\n", iface, pv, cb, pcbRead); + +- /* +- * Always returns S_OK even if the end of the stream is reached before the +- * buffer is filled +- */ ++ num_bytes = handle_read(This->handle, &This->currentPosition.u.LowPart, pv, cb); ++ if (pcbRead) *pcbRead = num_bytes; + + return S_OK; + } +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/0007-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Wri.patch b/patches/ole32-HGLOBALStream/0007-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Wri.patch new file mode 100644 index 00000000..23bb95b4 --- /dev/null +++ b/patches/ole32-HGLOBALStream/0007-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Wri.patch @@ -0,0 +1,158 @@ +From 8656639654762bd3151593fe00a0c47080a085d8 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Sat, 6 Feb 2016 04:09:45 +0100 +Subject: ole32: Improve thread-safety of HGLOBALStreamImpl_Write. + +--- + dlls/ole32/hglobalstream.c | 117 ++++++++++++++++++--------------------------- + 1 file changed, 46 insertions(+), 71 deletions(-) + +diff --git a/dlls/ole32/hglobalstream.c b/dlls/ole32/hglobalstream.c +index 27ac706..2c08710 100644 +--- a/dlls/ole32/hglobalstream.c ++++ b/dlls/ole32/hglobalstream.c +@@ -77,16 +77,6 @@ static void handle_release(struct handle_wrapper *handle) + } + } + +-static void *handle_lock(struct handle_wrapper *handle) +-{ +- return GlobalLock(handle->hglobal); +-} +- +-static void handle_unlock(struct handle_wrapper *handle) +-{ +- GlobalUnlock(handle->hglobal); +-} +- + static ULONG handle_read(struct handle_wrapper *handle, ULONG *pos, void *dest, ULONG len) + { + void *source; +@@ -115,6 +105,48 @@ static ULONG handle_read(struct handle_wrapper *handle, ULONG *pos, void *dest, + return len; + } + ++static ULONG handle_write(struct handle_wrapper *handle, ULONG *pos, const void *source, ULONG len) ++{ ++ void *dest; ++ ++ if (!len) ++ return 0; ++ ++ EnterCriticalSection(&handle->lock); ++ ++ if (*pos + len > handle->size) ++ { ++ HGLOBAL hglobal = GlobalReAlloc(handle->hglobal, *pos + len, GMEM_MOVEABLE); ++ if (hglobal) ++ { ++ handle->hglobal = hglobal; ++ handle->size = *pos + len; ++ } ++ else ++ { ++ len = 0; ++ goto done; ++ } ++ } ++ ++ dest = GlobalLock(handle->hglobal); ++ if (dest) ++ { ++ memcpy((char *)dest + *pos, source, len); ++ *pos += len; ++ GlobalUnlock(handle->hglobal); ++ } ++ else ++ { ++ WARN("write to invalid hglobal %p\n", handle->hglobal); ++ /* len = 0; */ ++ } ++ ++done: ++ LeaveCriticalSection(&handle->lock); ++ return len; ++} ++ + static HGLOBAL handle_gethglobal(struct handle_wrapper *handle) + { + return handle->hglobal; +@@ -277,71 +309,14 @@ static HRESULT WINAPI HGLOBALStreamImpl_Write( + ULONG* pcbWritten) /* [out] */ + { + HGLOBALStreamImpl* This = impl_from_IStream(iface); +- +- void* supportBuffer; +- ULARGE_INTEGER newSize; +- ULONG bytesWritten = 0; ++ ULONG num_bytes; + + TRACE("(%p, %p, %d, %p)\n", iface, pv, cb, pcbWritten); + +- /* +- * If the caller is not interested in the number of bytes written, +- * we use another buffer to avoid "if" statements in the code. +- */ +- if (pcbWritten == 0) +- pcbWritten = &bytesWritten; +- +- if (cb == 0) +- goto out; +- +- *pcbWritten = 0; +- +- newSize.u.HighPart = 0; +- newSize.u.LowPart = This->currentPosition.u.LowPart + cb; +- +- /* +- * Verify if we need to grow the stream +- */ +- if (newSize.u.LowPart > handle_getsize(This->handle)) +- { +- /* grow stream */ +- HRESULT hr = IStream_SetSize(iface, newSize); +- if (FAILED(hr)) +- { +- ERR("IStream_SetSize failed with error 0x%08x\n", hr); +- return hr; +- } +- } +- +- /* +- * Lock the buffer in position and copy the data. +- */ +- supportBuffer = handle_lock(This->handle); +- if (!supportBuffer) +- { +- WARN("write to invalid hglobal %p\n", handle_gethglobal(This->handle)); +- return S_OK; +- } +- +- memcpy((char *) supportBuffer+This->currentPosition.u.LowPart, pv, cb); +- +- /* +- * Move the current position to the new position +- */ +- This->currentPosition.u.LowPart+=cb; +- +- /* +- * Cleanup +- */ +- handle_unlock(This->handle); +- +-out: +- /* +- * Return the number of bytes read. +- */ +- *pcbWritten = cb; ++ num_bytes = handle_write(This->handle, &This->currentPosition.u.LowPart, pv, cb); ++ if (pcbWritten) *pcbWritten = num_bytes; + +- return S_OK; ++ return (num_bytes < cb) ? E_OUTOFMEMORY : S_OK; + } + + /*** +-- +2.7.0 + diff --git a/patches/ole32-HGLOBALStream/definition b/patches/ole32-HGLOBALStream/definition new file mode 100644 index 00000000..eef96faa --- /dev/null +++ b/patches/ole32-HGLOBALStream/definition @@ -0,0 +1 @@ +Fixes: Implement proper refcounting and locking for HGLOBAL based IStream diff --git a/patches/patchinstall.sh b/patches/patchinstall.sh index b72b7a1d..db1cab1b 100755 --- a/patches/patchinstall.sh +++ b/patches/patchinstall.sh @@ -238,6 +238,7 @@ patch_enable_all () enable_nvcuvid_CUDA_Video_Support="$1" enable_nvencodeapi_Video_Encoder="$1" enable_ole32_CoGetApartmentType="$1" + enable_ole32_HGLOBALStream="$1" enable_oleaut32_CreateTypeLib="$1" enable_oleaut32_TKIND_COCLASS="$1" enable_oleaut32_x86_64_Marshaller="$1" @@ -853,6 +854,9 @@ patch_enable () ole32-CoGetApartmentType) enable_ole32_CoGetApartmentType="$2" ;; + ole32-HGLOBALStream) + enable_ole32_HGLOBALStream="$2" + ;; oleaut32-CreateTypeLib) enable_oleaut32_CreateTypeLib="$2" ;; @@ -5202,6 +5206,30 @@ if test "$enable_nvencodeapi_Video_Encoder" -eq 1; then ) >> "$patchlist" fi +# Patchset ole32-HGLOBALStream +# | +# | Modified files: +# | * dlls/ole32/hglobalstream.c, dlls/ole32/tests/hglobalstream.c +# | +if test "$enable_ole32_HGLOBALStream" -eq 1; then + patch_apply ole32-HGLOBALStream/0001-ole32-tests-Add-a-bunch-of-tests-for-HGLOBAL-based-I.patch + patch_apply ole32-HGLOBALStream/0002-ole32-Add-a-check-for-hglobal-pointer-to-GetHGlobalF.patch + patch_apply ole32-HGLOBALStream/0003-ole32-Add-a-wrapper-for-memory-block-managed-by-HGLO.patch + patch_apply ole32-HGLOBALStream/0004-ole32-Set-DebugInfo-Spare-0-for-handle_wrapper-lock.patch + patch_apply ole32-HGLOBALStream/0005-ole32-Allow-moving-a-being-reallocated-block-of-memo.patch + patch_apply ole32-HGLOBALStream/0006-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Rea.patch + patch_apply ole32-HGLOBALStream/0007-ole32-Improve-thread-safety-of-HGLOBALStreamImpl_Wri.patch + ( + echo '+ { "Dmitry Timoshkov", "ole32/tests: Add a bunch of tests for HGLOBAL based IStream::Clone.", 1 },'; + echo '+ { "Dmitry Timoshkov", "ole32: Add a check for hglobal pointer to GetHGlobalFromStream.", 1 },'; + echo '+ { "Dmitry Timoshkov", "ole32: Add a wrapper for memory block managed by HGLOBAL based IStream.", 1 },'; + echo '+ { "Sebastian Lackner", "ole32: Set DebugInfo->Spare[0] for handle_wrapper lock.", 1 },'; + echo '+ { "Dmitry Timoshkov", "ole32: Allow moving a being reallocated block of memory managed by HGLOBAL based IStream.", 1 },'; + echo '+ { "Sebastian Lackner", "ole32: Improve thread-safety of HGLOBALStreamImpl_Read.", 1 },'; + echo '+ { "Sebastian Lackner", "ole32: Improve thread-safety of HGLOBALStreamImpl_Write.", 1 },'; + ) >> "$patchlist" +fi + # Patchset oleaut32-CreateTypeLib # | # | This patchset fixes the following Wine bugs: