[02/10] msvcirt: Implement strstreambuf constructors and destructor (try 2)

Iván Matellanes matellanesivan at gmail.com
Thu Sep 17 04:06:47 CDT 2015


Try 2: fix test failures
---
 dlls/msvcirt/msvcirt.c       |  58 +++++++++++++---
 dlls/msvcirt/tests/msvcirt.c | 156 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+), 9 deletions(-)

diff --git a/dlls/msvcirt/msvcirt.c b/dlls/msvcirt/msvcirt.c
index 460d69a..b50745c 100644
--- a/dlls/msvcirt/msvcirt.c
+++ b/dlls/msvcirt/msvcirt.c
@@ -1139,7 +1139,9 @@ int __thiscall filebuf_underflow(filebuf *this)
 DEFINE_THISCALL_WRAPPER(strstreambuf_copy_ctor, 8)
 strstreambuf* __thiscall strstreambuf_copy_ctor(strstreambuf *this, const strstreambuf *copy)
 {
-    FIXME("(%p %p) stub\n", this, copy);
+    TRACE("(%p %p)\n", this, copy);
+    *this = *copy;
+    this->base.vtable = &MSVCP_strstreambuf_vtable;
     return this;
 }
 
@@ -1148,7 +1150,14 @@ strstreambuf* __thiscall strstreambuf_copy_ctor(strstreambuf *this, const strstr
 DEFINE_THISCALL_WRAPPER(strstreambuf_dynamic_ctor, 8)
 strstreambuf* __thiscall strstreambuf_dynamic_ctor(strstreambuf* this, int length)
 {
-    FIXME("(%p %d) stub\n", this, length);
+    TRACE("(%p %d)\n", this, length);
+    streambuf_ctor(&this->base);
+    this->base.vtable = &MSVCP_strstreambuf_vtable;
+    this->dynamic = 1;
+    this->increase = length;
+    this->constant = 0;
+    this->f_alloc = NULL;
+    this->f_free = NULL;
     return this;
 }
 
@@ -1157,7 +1166,10 @@ strstreambuf* __thiscall strstreambuf_dynamic_ctor(strstreambuf* this, int lengt
 DEFINE_THISCALL_WRAPPER(strstreambuf_funcs_ctor, 12)
 strstreambuf* __thiscall strstreambuf_funcs_ctor(strstreambuf* this, allocFunction falloc, freeFunction ffree)
 {
-    FIXME("(%p %p %p) stub\n", this, falloc, ffree);
+    TRACE("(%p %p %p)\n", this, falloc, ffree);
+    strstreambuf_dynamic_ctor(this, 1);
+    this->f_alloc = falloc;
+    this->f_free = ffree;
     return this;
 }
 
@@ -1166,7 +1178,28 @@ strstreambuf* __thiscall strstreambuf_funcs_ctor(strstreambuf* this, allocFuncti
 DEFINE_THISCALL_WRAPPER(strstreambuf_buffer_ctor, 16)
 strstreambuf* __thiscall strstreambuf_buffer_ctor(strstreambuf *this, char *buffer, int length, char *put)
 {
-    FIXME("(%p %p %d %p) stub\n", this, buffer, length, put);
+    char *end_buffer;
+
+    TRACE("(%p %p %d %p)\n", this, buffer, length, put);
+
+    if (length > 0)
+        end_buffer = buffer + length;
+    else if (length == 0)
+        end_buffer = buffer + strlen(buffer);
+    else
+        end_buffer = (char*) -1;
+
+    streambuf_ctor(&this->base);
+    streambuf_setb(&this->base, buffer, end_buffer, 0);
+    if (put == NULL) {
+        streambuf_setg(&this->base, buffer, buffer, end_buffer);
+    } else {
+        streambuf_setg(&this->base, buffer, buffer, put);
+        streambuf_setp(&this->base, put, end_buffer);
+    }
+    this->base.vtable = &MSVCP_strstreambuf_vtable;
+    this->dynamic = 0;
+    this->constant = 1;
     return this;
 }
 
@@ -1175,8 +1208,8 @@ strstreambuf* __thiscall strstreambuf_buffer_ctor(strstreambuf *this, char *buff
 DEFINE_THISCALL_WRAPPER(strstreambuf_ubuffer_ctor, 16)
 strstreambuf* __thiscall strstreambuf_ubuffer_ctor(strstreambuf *this, unsigned char *buffer, int length, unsigned char *put)
 {
-    FIXME("(%p %p %d %p) stub\n", this, buffer, length, put);
-    return this;
+    TRACE("(%p %p %d %p)\n", this, buffer, length, put);
+    return strstreambuf_buffer_ctor(this, (char*)buffer, length, (char*)put);
 }
 
 /* ??0strstreambuf@@QAE at XZ */
@@ -1184,8 +1217,8 @@ strstreambuf* __thiscall strstreambuf_ubuffer_ctor(strstreambuf *this, unsigned
 DEFINE_THISCALL_WRAPPER(strstreambuf_ctor, 4)
 strstreambuf* __thiscall strstreambuf_ctor(strstreambuf *this)
 {
-    FIXME("(%p) stub\n", this);
-    return this;
+    TRACE("(%p)\n", this);
+    return strstreambuf_dynamic_ctor(this, 1);
 }
 
 /* ??1strstreambuf@@UAE at XZ */
@@ -1193,7 +1226,14 @@ strstreambuf* __thiscall strstreambuf_ctor(strstreambuf *this)
 DEFINE_THISCALL_WRAPPER(strstreambuf_dtor, 4)
 void __thiscall strstreambuf_dtor(strstreambuf *this)
 {
-    FIXME("(%p) stub\n", this);
+    TRACE("(%p)\n", this);
+    if (this->dynamic && this->base.base) {
+        if (this->f_free)
+            this->f_free(this->base.base);
+        else
+            MSVCRT_operator_delete(this->base.base);
+    }
+    streambuf_dtor(&this->base);
 }
 
 /* ??4strstreambuf@@QAEAAV0 at ABV0@@Z */
diff --git a/dlls/msvcirt/tests/msvcirt.c b/dlls/msvcirt/tests/msvcirt.c
index b381b30..932e6fb 100644
--- a/dlls/msvcirt/tests/msvcirt.c
+++ b/dlls/msvcirt/tests/msvcirt.c
@@ -27,6 +27,8 @@ typedef void (*vtable_ptr)(void);
 typedef LONG streamoff;
 typedef LONG streampos;
 typedef int filedesc;
+typedef void* (__cdecl *allocFunction)(LONG);
+typedef void (__cdecl *freeFunction)(void*);
 
 typedef enum {
     IOSTATE_goodbit   = 0x0,
@@ -102,6 +104,17 @@ typedef struct {
     int close;
 } filebuf;
 
+/* class strstreambuf */
+typedef struct {
+    streambuf base;
+    int dynamic;
+    int increase;
+    int unknown;
+    int constant;
+    allocFunction f_alloc;
+    freeFunction f_free;
+} strstreambuf;
+
 /* class ios */
 struct _ostream;
 typedef struct {
@@ -132,6 +145,7 @@ typedef struct _ostream {
 #endif
 
 static void* (__cdecl *p_operator_new)(unsigned int);
+static void (__cdecl *p_operator_delete)(void*);
 
 /* streambuf */
 static streambuf* (*__thiscall p_streambuf_reserve_ctor)(streambuf*, char*, int);
@@ -172,6 +186,14 @@ static int (*__thiscall p_filebuf_overflow)(filebuf*, int);
 static int (*__thiscall p_filebuf_underflow)(filebuf*);
 static streampos (*__thiscall p_filebuf_seekoff)(filebuf*, streamoff, ios_seek_dir, int);
 
+/* strstreambuf */
+static strstreambuf* (*__thiscall p_strstreambuf_dynamic_ctor)(strstreambuf*, int);
+static strstreambuf* (*__thiscall p_strstreambuf_funcs_ctor)(strstreambuf*, allocFunction, freeFunction);
+static strstreambuf* (*__thiscall p_strstreambuf_buffer_ctor)(strstreambuf*, char*, int, char*);
+static strstreambuf* (*__thiscall p_strstreambuf_ubuffer_ctor)(strstreambuf*, unsigned char*, int, unsigned char*);
+static strstreambuf* (*__thiscall p_strstreambuf_ctor)(strstreambuf*);
+static void (*__thiscall p_strstreambuf_dtor)(strstreambuf*);
+
 /* ios */
 static ios* (*__thiscall p_ios_copy_ctor)(ios*, const ios*);
 static ios* (*__thiscall p_ios_ctor)(ios*);
@@ -278,6 +300,7 @@ static BOOL init(void)
 
     if(sizeof(void*) == 8) { /* 64-bit initialization */
         p_operator_new = (void*)GetProcAddress(msvcrt, "??2 at YAPEAX_K@Z");
+        p_operator_delete = (void*)GetProcAddress(msvcrt, "??3 at YAXPEAX@Z");
 
         SET(p_streambuf_reserve_ctor, "??0streambuf@@IEAA at PEADH@Z");
         SET(p_streambuf_ctor, "??0streambuf@@IEAA at XZ");
@@ -316,6 +339,13 @@ static BOOL init(void)
         SET(p_filebuf_underflow, "?underflow at filebuf@@UEAAHXZ");
         SET(p_filebuf_seekoff, "?seekoff at filebuf@@UEAAJJW4seek_dir at ios@@H at Z");
 
+        SET(p_strstreambuf_dynamic_ctor, "??0strstreambuf@@QEAA at H@Z");
+        SET(p_strstreambuf_funcs_ctor, "??0strstreambuf@@QEAA at P6APEAXJ@ZP6AXPEAX at Z@Z");
+        SET(p_strstreambuf_buffer_ctor, "??0strstreambuf@@QEAA at PEADH0@Z");
+        SET(p_strstreambuf_ubuffer_ctor, "??0strstreambuf@@QEAA at PEAEH0@Z");
+        SET(p_strstreambuf_ctor, "??0strstreambuf@@QEAA at XZ");
+        SET(p_strstreambuf_dtor, "??1strstreambuf@@UEAA at XZ");
+
         SET(p_ios_copy_ctor, "??0ios@@IEAA at AEBV0@@Z");
         SET(p_ios_ctor, "??0ios@@IEAA at XZ");
         SET(p_ios_sb_ctor, "??0ios@@QEAA at PEAVstreambuf@@@Z");
@@ -342,6 +372,7 @@ static BOOL init(void)
         SET(p_ios_pword, "?pword at ios@@QEBAAEAPEAXH at Z");
     } else {
         p_operator_new = (void*)GetProcAddress(msvcrt, "??2 at YAPAXI@Z");
+        p_operator_delete = (void*)GetProcAddress(msvcrt, "??3 at YAXPAX@Z");
 
         SET(p_streambuf_reserve_ctor, "??0streambuf@@IAE at PADH@Z");
         SET(p_streambuf_ctor, "??0streambuf@@IAE at XZ");
@@ -380,6 +411,13 @@ static BOOL init(void)
         SET(p_filebuf_underflow, "?underflow at filebuf@@UAEHXZ");
         SET(p_filebuf_seekoff, "?seekoff at filebuf@@UAEJJW4seek_dir at ios@@H at Z");
 
+        SET(p_strstreambuf_dynamic_ctor, "??0strstreambuf@@QAE at H@Z");
+        SET(p_strstreambuf_funcs_ctor, "??0strstreambuf@@QAE at P6APAXJ@ZP6AXPAX at Z@Z");
+        SET(p_strstreambuf_buffer_ctor, "??0strstreambuf@@QAE at PADH0@Z");
+        SET(p_strstreambuf_ubuffer_ctor, "??0strstreambuf@@QAE at PAEH0@Z");
+        SET(p_strstreambuf_ctor, "??0strstreambuf@@QAE at XZ");
+        SET(p_strstreambuf_dtor, "??1strstreambuf@@UAE at XZ");
+
         SET(p_ios_copy_ctor, "??0ios@@IAE at ABV0@@Z");
         SET(p_ios_ctor, "??0ios@@IAE at XZ");
         SET(p_ios_sb_ctor, "??0ios@@QAE at PAVstreambuf@@@Z");
@@ -1375,6 +1413,123 @@ static void test_filebuf(void)
     CloseHandle(thread);
 }
 
+static void test_strstreambuf(void)
+{
+    strstreambuf ssb1, ssb2;
+    char buffer[64];
+
+    memset(&ssb1, 0xab, sizeof(strstreambuf));
+    memset(&ssb2, 0xab, sizeof(strstreambuf));
+
+    /* constructors/destructor */
+    call_func2(p_strstreambuf_dynamic_ctor, &ssb1, 64);
+    ok(ssb1.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb1.base.allocated);
+    ok(ssb1.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb1.base.unbuffered);
+    ok(ssb1.dynamic == 1, "expected 1, got %d\n", ssb1.dynamic);
+    ok(ssb1.increase == 64, "expected 64, got %d\n", ssb1.increase);
+    ok(ssb1.constant == 0, "expected 0, got %d\n", ssb1.constant);
+    ok(ssb1.f_alloc == NULL, "expected %p, got %p\n", NULL, ssb1.f_alloc);
+    ok(ssb1.f_free == NULL, "expected %p, got %p\n", NULL, ssb1.f_free);
+    call_func1(p_strstreambuf_dtor, &ssb1);
+    call_func3(p_strstreambuf_funcs_ctor, &ssb2, (allocFunction)p_operator_new, p_operator_delete);
+    ok(ssb2.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb2.base.allocated);
+    ok(ssb2.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb2.base.unbuffered);
+    ok(ssb2.dynamic == 1, "expected 1, got %d\n", ssb2.dynamic);
+    ok(ssb2.increase == 1, "expected 1, got %d\n", ssb2.increase);
+    ok(ssb2.constant == 0, "expected 0, got %d\n", ssb2.constant);
+    ok(ssb2.f_alloc == (allocFunction)p_operator_new, "expected %p, got %p\n", p_operator_new, ssb2.f_alloc);
+    ok(ssb2.f_free == p_operator_delete, "expected %p, got %p\n", p_operator_delete, ssb2.f_free);
+    call_func1(p_strstreambuf_dtor, &ssb2);
+    memset(&ssb1, 0xab, sizeof(strstreambuf));
+    memset(&ssb2, 0xab, sizeof(strstreambuf));
+    call_func4(p_strstreambuf_buffer_ctor, &ssb1, buffer, 64, buffer + 20);
+    ok(ssb1.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb1.base.allocated);
+    ok(ssb1.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb1.base.unbuffered);
+    ok(ssb1.base.base == buffer, "wrong buffer, expected %p got %p\n", buffer, ssb1.base.base);
+    ok(ssb1.base.ebuf == buffer + 64, "wrong buffer end, expected %p got %p\n", buffer + 64, ssb1.base.ebuf);
+    ok(ssb1.base.eback == buffer, "wrong get base, expected %p got %p\n", buffer, ssb1.base.eback);
+    ok(ssb1.base.gptr == buffer, "wrong get pointer, expected %p got %p\n", buffer, ssb1.base.gptr);
+    ok(ssb1.base.egptr == buffer + 20, "wrong get end, expected %p got %p\n", buffer + 20, ssb1.base.egptr);
+    ok(ssb1.base.pbase == buffer + 20, "wrong put base, expected %p got %p\n", buffer + 20, ssb1.base.pbase);
+    ok(ssb1.base.pptr == buffer + 20, "wrong put pointer, expected %p got %p\n", buffer + 20, ssb1.base.pptr);
+    ok(ssb1.base.epptr == buffer + 64, "wrong put end, expected %p got %p\n", buffer + 64, ssb1.base.epptr);
+    ok(ssb1.dynamic == 0, "expected 0, got %d\n", ssb1.dynamic);
+    ok(ssb1.constant == 1, "expected 1, got %d\n", ssb1.constant);
+    call_func1(p_strstreambuf_dtor, &ssb1);
+    strcpy(buffer, "Test");
+    call_func4(p_strstreambuf_buffer_ctor, &ssb2, buffer, 0, buffer + 20);
+    ok(ssb2.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb2.base.allocated);
+    ok(ssb2.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb2.base.unbuffered);
+    ok(ssb2.base.base == buffer, "wrong buffer, expected %p got %p\n", buffer, ssb2.base.base);
+    ok(ssb2.base.ebuf == buffer + 4, "wrong buffer end, expected %p got %p\n", buffer + 4, ssb2.base.ebuf);
+    ok(ssb2.base.eback == buffer, "wrong get base, expected %p got %p\n", buffer, ssb2.base.eback);
+    ok(ssb2.base.gptr == buffer, "wrong get pointer, expected %p got %p\n", buffer, ssb2.base.gptr);
+    ok(ssb2.base.egptr == buffer + 20, "wrong get end, expected %p got %p\n", buffer + 20, ssb2.base.egptr);
+    ok(ssb2.base.pbase == buffer + 20, "wrong put base, expected %p got %p\n", buffer + 20, ssb2.base.pbase);
+    ok(ssb2.base.pptr == buffer + 20, "wrong put pointer, expected %p got %p\n", buffer + 20, ssb2.base.pptr);
+    ok(ssb2.base.epptr == buffer + 4, "wrong put end, expected %p got %p\n", buffer + 4, ssb2.base.epptr);
+    ok(ssb2.dynamic == 0, "expected 0, got %d\n", ssb2.dynamic);
+    ok(ssb2.constant == 1, "expected 1, got %d\n", ssb2.constant);
+    call_func1(p_strstreambuf_dtor, &ssb2);
+    memset(&ssb1, 0xab, sizeof(strstreambuf));
+    memset(&ssb2, 0xab, sizeof(strstreambuf));
+    call_func4(p_strstreambuf_buffer_ctor, &ssb1, NULL, 16, buffer + 20);
+    ok(ssb1.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb1.base.allocated);
+    ok(ssb1.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb1.base.unbuffered);
+    ok(ssb1.base.base == NULL, "wrong buffer, expected %p got %p\n", NULL, ssb1.base.base);
+    ok(ssb1.base.ebuf == (char*)16, "wrong buffer end, expected %p got %p\n", (char*)16, ssb1.base.ebuf);
+    ok(ssb1.base.eback == NULL, "wrong get base, expected %p got %p\n", NULL, ssb1.base.eback);
+    ok(ssb1.base.gptr == NULL, "wrong get pointer, expected %p got %p\n", NULL, ssb1.base.gptr);
+    ok(ssb1.base.egptr == buffer + 20, "wrong get end, expected %p got %p\n", buffer + 20, ssb1.base.egptr);
+    ok(ssb1.base.pbase == buffer + 20, "wrong put base, expected %p got %p\n", buffer + 20, ssb1.base.pbase);
+    ok(ssb1.base.pptr == buffer + 20, "wrong put pointer, expected %p got %p\n", buffer + 20, ssb1.base.pptr);
+    ok(ssb1.base.epptr == (char*)16, "wrong put end, expected %p got %p\n", (char*)16, ssb1.base.epptr);
+    ok(ssb1.dynamic == 0, "expected 0, got %d\n", ssb1.dynamic);
+    ok(ssb1.constant == 1, "expected 1, got %d\n", ssb1.constant);
+    call_func1(p_strstreambuf_dtor, &ssb1);
+    call_func4(p_strstreambuf_buffer_ctor, &ssb2, buffer, 0, NULL);
+    ok(ssb2.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb2.base.allocated);
+    ok(ssb2.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb2.base.unbuffered);
+    ok(ssb2.base.base == buffer, "wrong buffer, expected %p got %p\n", buffer, ssb2.base.base);
+    ok(ssb2.base.ebuf == buffer + 4, "wrong buffer end, expected %p got %p\n", buffer + 4, ssb2.base.ebuf);
+    ok(ssb2.base.eback == buffer, "wrong get base, expected %p got %p\n", buffer, ssb2.base.eback);
+    ok(ssb2.base.gptr == buffer, "wrong get pointer, expected %p got %p\n", buffer, ssb2.base.gptr);
+    ok(ssb2.base.egptr == buffer + 4, "wrong get end, expected %p got %p\n", buffer + 4, ssb2.base.egptr);
+    ok(ssb2.base.pbase == NULL, "wrong put base, expected %p got %p\n", NULL, ssb2.base.pbase);
+    ok(ssb2.base.pptr == NULL, "wrong put pointer, expected %p got %p\n", NULL, ssb2.base.pptr);
+    ok(ssb2.base.epptr == NULL, "wrong put end, expected %p got %p\n", NULL, ssb2.base.epptr);
+    ok(ssb2.dynamic == 0, "expected 0, got %d\n", ssb2.dynamic);
+    ok(ssb2.constant == 1, "expected 1, got %d\n", ssb2.constant);
+    call_func1(p_strstreambuf_dtor, &ssb2);
+    memset(&ssb1, 0xab, sizeof(strstreambuf));
+    memset(&ssb2, 0xab, sizeof(strstreambuf));
+    call_func4(p_strstreambuf_buffer_ctor, &ssb1, buffer, -5, buffer + 20);
+    ok(ssb1.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb1.base.allocated);
+    ok(ssb1.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb1.base.unbuffered);
+    ok(ssb1.base.base == buffer, "wrong buffer, expected %p got %p\n", buffer, ssb1.base.base);
+    ok(ssb1.base.ebuf == buffer + 0x7fffffff || ssb1.base.ebuf == (char*)-1,
+        "wrong buffer end, expected %p + 0x7fffffff or -1, got %p\n", buffer, ssb1.base.ebuf);
+    ok(ssb1.base.eback == buffer, "wrong get base, expected %p got %p\n", buffer, ssb1.base.eback);
+    ok(ssb1.base.gptr == buffer, "wrong get pointer, expected %p got %p\n", buffer, ssb1.base.gptr);
+    ok(ssb1.base.egptr == buffer + 20, "wrong get end, expected %p got %p\n", buffer + 20, ssb1.base.egptr);
+    ok(ssb1.base.pbase == buffer + 20, "wrong put base, expected %p got %p\n", buffer + 20, ssb1.base.pbase);
+    ok(ssb1.base.pptr == buffer + 20, "wrong put pointer, expected %p got %p\n", buffer + 20, ssb1.base.pptr);
+    ok(ssb1.base.epptr == buffer + 0x7fffffff || ssb1.base.epptr == (char*)-1,
+        "wrong put end, expected %p + 0x7fffffff or -1, got %p\n", buffer, ssb1.base.epptr);
+    ok(ssb1.dynamic == 0, "expected 0, got %d\n", ssb1.dynamic);
+    ok(ssb1.constant == 1, "expected 1, got %d\n", ssb1.constant);
+    call_func1(p_strstreambuf_dtor, &ssb1);
+    call_func1(p_strstreambuf_ctor, &ssb2);
+    ok(ssb2.base.allocated == 0, "wrong allocate value, expected 0 got %d\n", ssb2.base.allocated);
+    ok(ssb2.base.unbuffered == 0, "wrong unbuffered value, expected 0 got %d\n", ssb2.base.unbuffered);
+    ok(ssb2.dynamic == 1, "expected 1, got %d\n", ssb2.dynamic);
+    ok(ssb2.increase == 1, "expected 1, got %d\n", ssb2.increase);
+    ok(ssb2.constant == 0, "expected 0, got %d\n", ssb2.constant);
+    ok(ssb2.f_alloc == NULL, "expected %p, got %p\n", NULL, ssb2.f_alloc);
+    ok(ssb2.f_free == NULL, "expected %p, got %p\n", NULL, ssb2.f_free);
+    call_func1(p_strstreambuf_dtor, &ssb2);
+}
+
 struct ios_lock_arg
 {
     ios *ios_obj;
@@ -1659,6 +1814,7 @@ START_TEST(msvcirt)
 
     test_streambuf();
     test_filebuf();
+    test_strstreambuf();
     test_ios();
 
     FreeLibrary(msvcrt);
-- 
2.1.4




More information about the wine-patches mailing list