[CRYPT32] Sync with Wine Staging 2.2. CORE-12823

a7d1d77 crypt32: Recognize sha256/384/512 KEY OIDs.
e376123 crypt32: Remove unused parameter and dead code.
254eb21 crypt32: Fix wrong Base64 trailer WCHAR constants.
a2f2de1 crypt32: CryptStringToBinary accepts any header.
552820a crypt32: Fix Base64 issues in CryptStringToBinary.
e353ab6 crypt32: Base64 header requires a space.

svn path=/trunk/; revision=74101
This commit is contained in:
Amine Khaldi 2017-03-05 21:21:36 +00:00
parent 36c77a1616
commit 9b06dff0d9
3 changed files with 134 additions and 278 deletions

View file

@ -24,10 +24,10 @@
WINE_DEFAULT_DEBUG_CHANNEL(crypt);
#define CERT_HEADER "-----BEGIN CERTIFICATE-----"
#define CERT_HEADER_START "-----BEGIN"
#define CERT_HEADER_START "-----BEGIN "
#define CERT_DELIMITER "-----"
#define CERT_TRAILER "-----END CERTIFICATE-----"
#define CERT_TRAILER_START "-----END"
#define CERT_TRAILER_START "-----END "
#define CERT_REQUEST_HEADER "-----BEGIN NEW CERTIFICATE REQUEST-----"
#define CERT_REQUEST_TRAILER "-----END NEW CERTIFICATE REQUEST-----"
#define X509_HEADER "-----BEGIN X509 CRL-----"
@ -37,14 +37,14 @@ static const WCHAR CERT_HEADER_W[] = {
'-','-','-','-','-','B','E','G','I','N',' ','C','E','R','T','I','F','I','C',
'A','T','E','-','-','-','-','-',0 };
static const WCHAR CERT_HEADER_START_W[] = {
'-','-','-','-','-','B','E','G','I','N',0 };
'-','-','-','-','-','B','E','G','I','N',' ',0 };
static const WCHAR CERT_DELIMITER_W[] = {
'-','-','-','-','-',0 };
static const WCHAR CERT_TRAILER_W[] = {
'-','-','-','-','-','E','N','D',0 };
static const WCHAR CERT_TRAILER_START_W[] = {
'-','-','-','-','-','E','N','D',' ','C','E','R','T','I','F','I','C','A','T',
'E','-','-','-','-','-',0 };
static const WCHAR CERT_TRAILER_START_W[] = {
'-','-','-','-','-','E','N','D',' ',0 };
static const WCHAR CERT_REQUEST_HEADER_W[] = {
'-','-','-','-','-','B','E','G','I','N',' ','N','E','W',' ','C','E','R','T',
'I','F','I','C','A','T','E','R','E','Q','U','E','S','T','-','-','-','-','-',0 };
@ -477,9 +477,13 @@ BOOL WINAPI CryptBinaryToStringW(const BYTE *pbBinary,
return encoder(pbBinary, cbBinary, dwFlags, pszString, pcchString);
}
static inline BYTE decodeBase64Byte(int c)
#define BASE64_DECODE_PADDING 0x100
#define BASE64_DECODE_WHITESPACE 0x200
#define BASE64_DECODE_INVALID 0x300
static inline int decodeBase64Byte(int c)
{
BYTE ret;
int ret = BASE64_DECODE_INVALID;
if (c >= 'A' && c <= 'Z')
ret = c - 'A';
@ -491,124 +495,114 @@ static inline BYTE decodeBase64Byte(int c)
ret = 62;
else if (c == '/')
ret = 63;
else
ret = 64;
else if (c == '=')
ret = BASE64_DECODE_PADDING;
else if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
ret = BASE64_DECODE_WHITESPACE;
return ret;
}
static LONG decodeBase64Block(const char *in_buf, int in_len,
const char **nextBlock, PBYTE out_buf, DWORD *out_len)
{
int len = in_len;
const char *d = in_buf;
int ip0, ip1, ip2, ip3;
if (len < 4)
return ERROR_INVALID_DATA;
if (d[2] == '=')
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
out_buf[0] = (ip0 << 2) | (ip1 >> 4);
*out_len = 1;
}
else if (d[3] == '=')
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if ((ip2 = decodeBase64Byte(d[2])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
{
out_buf[0] = (ip0 << 2) | (ip1 >> 4);
out_buf[1] = (ip1 << 4) | (ip2 >> 2);
}
*out_len = 2;
}
else
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if ((ip2 = decodeBase64Byte(d[2])) > 63)
return ERROR_INVALID_DATA;
if ((ip3 = decodeBase64Byte(d[3])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
{
out_buf[0] = (ip0 << 2) | (ip1 >> 4);
out_buf[1] = (ip1 << 4) | (ip2 >> 2);
out_buf[2] = (ip2 << 6) | ip3;
}
*out_len = 3;
}
if (len >= 6 && d[4] == '\r' && d[5] == '\n')
*nextBlock = d + 6;
else if (len >= 5 && d[4] == '\n')
*nextBlock = d + 5;
else if (len >= 4 && d[4])
*nextBlock = d + 4;
else
*nextBlock = NULL;
return ERROR_SUCCESS;
}
/* Unlike CryptStringToBinaryA, cchString is guaranteed to be the length of the
* string to convert.
*/
typedef LONG (*StringToBinaryAFunc)(LPCSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags);
static LONG Base64ToBinary(const void* pszString, BOOL wide, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
DWORD cbIn, cbValid, cbOut, hasPadding;
BYTE block[4];
for (cbIn = cbValid = cbOut = hasPadding = 0; cbIn < cchString; ++cbIn)
{
int c = wide ? (int)((WCHAR*)pszString)[cbIn] : (int)((char*)pszString)[cbIn];
int d = decodeBase64Byte(c);
if (d == BASE64_DECODE_INVALID)
goto invalid;
if (d == BASE64_DECODE_WHITESPACE)
continue;
/* When padding starts, data is not acceptable */
if (hasPadding && d != BASE64_DECODE_PADDING)
goto invalid;
/* Padding after a full block (like "VVVV=") is ok and stops decoding */
if (d == BASE64_DECODE_PADDING && (cbValid & 3) == 0)
break;
cbValid += 1;
if (d == BASE64_DECODE_PADDING)
{
hasPadding = 1;
/* When padding reaches a full block, stop decoding */
if ((cbValid & 3) == 0)
break;
continue;
}
/* cbOut is incremented in the 4-char block as follows: "1-23" */
if ((cbValid & 3) != 2)
cbOut += 1;
}
/* Fail if the block has bad padding; omitting padding is fine */
if ((cbValid & 3) != 0 && hasPadding)
goto invalid;
/* Check available buffer size */
if (pbBinary && *pcbBinary && cbOut > *pcbBinary)
goto overflow;
/* Convert the data; this step depends on the validity checks above! */
if (pbBinary) for (cbIn = cbValid = cbOut = 0; cbIn < cchString; ++cbIn)
{
int c = wide ? (int)((WCHAR*)pszString)[cbIn] : (int)((char*)pszString)[cbIn];
int d = decodeBase64Byte(c);
if (d == BASE64_DECODE_WHITESPACE)
continue;
if (d == BASE64_DECODE_PADDING)
break;
block[cbValid & 3] = d;
cbValid += 1;
switch (cbValid & 3) {
case 1:
pbBinary[cbOut++] = (block[0] << 2);
break;
case 2:
pbBinary[cbOut-1] = (block[0] << 2) | (block[1] >> 4);
break;
case 3:
pbBinary[cbOut++] = (block[1] << 4) | (block[2] >> 2);
break;
case 0:
pbBinary[cbOut++] = (block[2] << 6) | (block[3] >> 0);
break;
}
}
*pcbBinary = cbOut;
if (pdwSkip)
*pdwSkip = 0;
if (pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64;
return ERROR_SUCCESS;
overflow:
return ERROR_INSUFFICIENT_BUFFER;
invalid:
*pcbBinary = cbOut;
return ERROR_INVALID_DATA;
}
static LONG Base64ToBinaryA(LPCSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = ERROR_SUCCESS;
const char *nextBlock;
DWORD outLen = 0;
nextBlock = pszString;
while (nextBlock && !ret)
{
DWORD len = 0;
ret = decodeBase64Block(nextBlock, cchString - (nextBlock - pszString),
&nextBlock, pbBinary ? pbBinary + outLen : NULL, &len);
if (!ret)
outLen += len;
if (cchString - (nextBlock - pszString) <= 0)
nextBlock = NULL;
}
*pcbBinary = outLen;
if (!ret)
{
if (pdwSkip)
*pdwSkip = 0;
if (pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64;
}
else if (ret == ERROR_INSUFFICIENT_BUFFER)
{
if (!pbBinary)
ret = ERROR_SUCCESS;
}
return ret;
return Base64ToBinary(pszString, FALSE, cchString, pbBinary, pcbBinary, pdwSkip, pdwFlags);
}
static LONG Base64WithHeaderAndTrailerToBinaryA(LPCSTR pszString,
DWORD cchString, LPCSTR header, LPCSTR trailer, BYTE *pbBinary,
DWORD *pcbBinary, DWORD *pdwSkip, BOOL exactHeaderAndTrailerMatch)
DWORD cchString, BYTE *pbBinary,
DWORD *pcbBinary, DWORD *pdwSkip)
{
LONG ret;
LPCSTR header = CERT_HEADER_START;
LPCSTR trailer = CERT_TRAILER_START;
LPCSTR headerBegins;
LPCSTR dataBegins;
@ -627,43 +621,20 @@ static LONG Base64WithHeaderAndTrailerToBinaryA(LPCSTR pszString,
}
dataBegins = headerBegins + strlen(header);
if (!exactHeaderAndTrailerMatch)
if (!(dataBegins = strstr(dataBegins, CERT_DELIMITER)))
{
if ((dataBegins = strstr(dataBegins, CERT_DELIMITER)))
{
dataBegins += strlen(CERT_DELIMITER);
}
else
{
return ERROR_INVALID_DATA;
}
return ERROR_INVALID_DATA;
}
dataBegins += strlen(CERT_DELIMITER);
if (*dataBegins == '\r') dataBegins++;
if (*dataBegins == '\n') dataBegins++;
if (exactHeaderAndTrailerMatch)
if (!(trailerBegins = strstr(dataBegins, trailer)))
{
trailerBegins = pszString + cchString - strlen(trailer);
if (pszString[cchString - 1] == '\n') trailerBegins--;
if (pszString[cchString - 2] == '\r') trailerBegins--;
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
if (!strncmp(trailerBegins, trailer, strlen(trailer)))
{
return ERROR_INVALID_DATA;
}
}
else
{
if (!(trailerBegins = strstr(dataBegins, trailer)))
{
return ERROR_INVALID_DATA;
}
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
return ERROR_INVALID_DATA;
}
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
if (pdwSkip)
*pdwSkip = headerBegins - pszString;
@ -680,7 +651,7 @@ static LONG Base64HeaderToBinaryA(LPCSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryA(pszString, cchString,
CERT_HEADER_START, CERT_TRAILER_START, pbBinary, pcbBinary, pdwSkip, FALSE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64HEADER;
@ -691,7 +662,7 @@ static LONG Base64RequestHeaderToBinaryA(LPCSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryA(pszString, cchString,
CERT_REQUEST_HEADER, CERT_REQUEST_TRAILER, pbBinary, pcbBinary, pdwSkip, TRUE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64REQUESTHEADER;
@ -702,7 +673,7 @@ static LONG Base64X509HeaderToBinaryA(LPCSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryA(pszString, cchString,
X509_HEADER, X509_TRAILER, pbBinary, pcbBinary, pdwSkip, TRUE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64X509CRLHEADER;
@ -824,75 +795,6 @@ BOOL WINAPI CryptStringToBinaryA(LPCSTR pszString,
return ret == ERROR_SUCCESS;
}
static LONG decodeBase64BlockW(const WCHAR *in_buf, int in_len,
const WCHAR **nextBlock, PBYTE out_buf, DWORD *out_len)
{
int len = in_len, i;
const WCHAR *d = in_buf;
int ip0, ip1, ip2, ip3;
if (len < 4)
return ERROR_INVALID_DATA;
i = 0;
if (d[2] == '=')
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
out_buf[i] = (ip0 << 2) | (ip1 >> 4);
i++;
}
else if (d[3] == '=')
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if ((ip2 = decodeBase64Byte(d[2])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
{
out_buf[i + 0] = (ip0 << 2) | (ip1 >> 4);
out_buf[i + 1] = (ip1 << 4) | (ip2 >> 2);
}
i += 2;
}
else
{
if ((ip0 = decodeBase64Byte(d[0])) > 63)
return ERROR_INVALID_DATA;
if ((ip1 = decodeBase64Byte(d[1])) > 63)
return ERROR_INVALID_DATA;
if ((ip2 = decodeBase64Byte(d[2])) > 63)
return ERROR_INVALID_DATA;
if ((ip3 = decodeBase64Byte(d[3])) > 63)
return ERROR_INVALID_DATA;
if (out_buf)
{
out_buf[i + 0] = (ip0 << 2) | (ip1 >> 4);
out_buf[i + 1] = (ip1 << 4) | (ip2 >> 2);
out_buf[i + 2] = (ip2 << 6) | ip3;
}
i += 3;
}
if (len >= 6 && d[4] == '\r' && d[5] == '\n')
*nextBlock = d + 6;
else if (len >= 5 && d[4] == '\n')
*nextBlock = d + 5;
else if (len >= 4 && d[4])
*nextBlock = d + 4;
else
*nextBlock = NULL;
*out_len = i;
return ERROR_SUCCESS;
}
/* Unlike CryptStringToBinaryW, cchString is guaranteed to be the length of the
* string to convert.
*/
@ -902,43 +804,16 @@ typedef LONG (*StringToBinaryWFunc)(LPCWSTR pszString, DWORD cchString,
static LONG Base64ToBinaryW(LPCWSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = ERROR_SUCCESS;
const WCHAR *nextBlock;
DWORD outLen = 0;
nextBlock = pszString;
while (nextBlock && !ret)
{
DWORD len = 0;
ret = decodeBase64BlockW(nextBlock, cchString - (nextBlock - pszString),
&nextBlock, pbBinary ? pbBinary + outLen : NULL, &len);
if (!ret)
outLen += len;
if (cchString - (nextBlock - pszString) <= 0)
nextBlock = NULL;
}
*pcbBinary = outLen;
if (!ret)
{
if (pdwSkip)
*pdwSkip = 0;
if (pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64;
}
else if (ret == ERROR_INSUFFICIENT_BUFFER)
{
if (!pbBinary)
ret = ERROR_SUCCESS;
}
return ret;
return Base64ToBinary(pszString, TRUE, cchString, pbBinary, pcbBinary, pdwSkip, pdwFlags);
}
static LONG Base64WithHeaderAndTrailerToBinaryW(LPCWSTR pszString,
DWORD cchString, LPCWSTR header, LPCWSTR trailer, BYTE *pbBinary,
DWORD *pcbBinary, DWORD *pdwSkip, BOOL exactHeaderAndTrailerMatch)
DWORD cchString, BYTE *pbBinary,
DWORD *pcbBinary, DWORD *pdwSkip)
{
LONG ret;
LPCWSTR header = CERT_HEADER_START_W;
LPCWSTR trailer = CERT_TRAILER_START_W;
LPCWSTR headerBegins;
LPCWSTR dataBegins;
@ -957,43 +832,20 @@ static LONG Base64WithHeaderAndTrailerToBinaryW(LPCWSTR pszString,
}
dataBegins = headerBegins + strlenW(header);
if (!exactHeaderAndTrailerMatch)
if (!(dataBegins = strstrW(dataBegins, CERT_DELIMITER_W)))
{
if ((dataBegins = strstrW(dataBegins, CERT_DELIMITER_W)))
{
dataBegins += strlenW(CERT_DELIMITER_W);
}
else
{
return ERROR_INVALID_DATA;
}
return ERROR_INVALID_DATA;
}
dataBegins += strlenW(CERT_DELIMITER_W);
if (*dataBegins == '\r') dataBegins++;
if (*dataBegins == '\n') dataBegins++;
if (exactHeaderAndTrailerMatch)
if (!(trailerBegins = strstrW(dataBegins, trailer)))
{
trailerBegins = pszString + cchString - strlenW(trailer);
if (pszString[cchString - 1] == '\n') trailerBegins--;
if (pszString[cchString - 2] == '\r') trailerBegins--;
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
if (!strncmpW(trailerBegins, trailer, strlenW(trailer)))
{
return ERROR_INVALID_DATA;
}
}
else
{
if (!(trailerBegins = strstrW(dataBegins, trailer)))
{
return ERROR_INVALID_DATA;
}
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
return ERROR_INVALID_DATA;
}
if (*(trailerBegins-1) == '\n') trailerBegins--;
if (*(trailerBegins-1) == '\r') trailerBegins--;
if (pdwSkip)
*pdwSkip = headerBegins - pszString;
@ -1010,8 +862,7 @@ static LONG Base64HeaderToBinaryW(LPCWSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryW(pszString, cchString,
CERT_HEADER_START_W, CERT_TRAILER_START_W, pbBinary, pcbBinary,
pdwSkip, FALSE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64HEADER;
@ -1022,8 +873,7 @@ static LONG Base64RequestHeaderToBinaryW(LPCWSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryW(pszString, cchString,
CERT_REQUEST_HEADER_W, CERT_REQUEST_TRAILER_W, pbBinary, pcbBinary,
pdwSkip, TRUE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64REQUESTHEADER;
@ -1034,7 +884,7 @@ static LONG Base64X509HeaderToBinaryW(LPCWSTR pszString, DWORD cchString,
BYTE *pbBinary, DWORD *pcbBinary, DWORD *pdwSkip, DWORD *pdwFlags)
{
LONG ret = Base64WithHeaderAndTrailerToBinaryW(pszString, cchString,
X509_HEADER_W, X509_TRAILER_W, pbBinary, pcbBinary, pdwSkip, TRUE);
pbBinary, pcbBinary, pdwSkip);
if (!ret && pdwFlags)
*pdwFlags = CRYPT_STRING_BASE64X509CRLHEADER;

View file

@ -1039,6 +1039,9 @@ static const WCHAR rc2[] = { 'r','c','2',0 };
static const WCHAR rc4[] = { 'r','c','4',0 };
static const WCHAR sha[] = { 's','h','a',0 };
static const WCHAR sha1[] = { 's','h','a','1',0 };
static const WCHAR sha256[] = { 's','h','a','2','5','6',0 };
static const WCHAR sha384[] = { 's','h','a','3','8','4',0 };
static const WCHAR sha512[] = { 's','h','a','5','1','2',0 };
static const WCHAR RSA[] = { 'R','S','A',0 };
static const WCHAR RSA_KEYX[] = { 'R','S','A','_','K','E','Y','X',0 };
static const WCHAR RSA_SIGN[] = { 'R','S','A','_','S','I','G','N',0 };
@ -1143,6 +1146,9 @@ static const struct OIDInfoConstructor {
{ 1, szOID_RSA_MD5, CALG_MD5, md5, NULL },
{ 1, szOID_RSA_MD4, CALG_MD4, md4, NULL },
{ 1, szOID_RSA_MD2, CALG_MD2, md2, NULL },
{ 1, szOID_NIST_sha256, CALG_SHA_256, sha256, NULL },
{ 1, szOID_NIST_sha384, CALG_SHA_384, sha384, NULL },
{ 1, szOID_NIST_sha512, CALG_SHA_512, sha512, NULL },
{ 2, szOID_OIWSEC_desCBC, CALG_DES, des, NULL },
{ 2, szOID_RSA_DES_EDE3_CBC, CALG_3DES, tripledes, NULL },

View file

@ -58,7 +58,7 @@ reactos/dll/win32/comctl32 # Synced to WineStaging-2.2
reactos/dll/win32/comdlg32 # Synced to WineStaging-2.2
reactos/dll/win32/compstui # Synced to WineStaging-2.2
reactos/dll/win32/credui # Synced to WineStaging-2.2
reactos/dll/win32/crypt32 # Synced to WineStaging-1.9.23
reactos/dll/win32/crypt32 # Synced to WineStaging-2.2
reactos/dll/win32/cryptdlg # Synced to WineStaging-1.9.11
reactos/dll/win32/cryptdll # Synced to WineStaging-1.9.11
reactos/dll/win32/cryptnet # Synced to WineStaging-1.9.11