Duplicated wsprintf implementation in shlwapi.

diff --git a/dlls/shlwapi/Makefile.in b/dlls/shlwapi/Makefile.in
index d4b281a..b9f1be8 100644
--- a/dlls/shlwapi/Makefile.in
+++ b/dlls/shlwapi/Makefile.in
@@ -17,7 +17,8 @@
 	regstream.c \
 	shlwapi_main.c \
 	string.c \
-	url.c
+	url.c \
+	wsprintf.c
 
 EXTRASUBDIRS = tests
 
diff --git a/dlls/shlwapi/shlwapi.spec b/dlls/shlwapi/shlwapi.spec
index 30a3659..c2225e0 100644
--- a/dlls/shlwapi/shlwapi.spec
+++ b/dlls/shlwapi/shlwapi.spec
@@ -680,8 +680,8 @@
 @ stdcall UrlUnescapeW(wstr ptr ptr long) UrlUnescapeW
 @ varargs wnsprintfA(ptr long str) wnsprintfA
 @ varargs wnsprintfW(ptr long wstr) wnsprintfW
-@ forward wvnsprintfA user32.wvsnprintfA
-@ forward wvnsprintfW user32.wvsnprintfW
+@ stdcall wvnsprintfA(ptr long str ptr) wvnsprintfA
+@ stdcall wvnsprintfW(ptr long wstr ptr) wvnsprintfW
 
 
 # exported in later versions
diff --git a/dlls/shlwapi/string.c b/dlls/shlwapi/string.c
index 7d8759f..11ea0c6 100644
--- a/dlls/shlwapi/string.c
+++ b/dlls/shlwapi/string.c
@@ -634,31 +634,3 @@
     TRACE("<- '%s'\n", pszSource);
     return trimmed;
 }
-
-/*************************************************************************
- *      wnsprintfA	[SHLWAPI.@]
- */
-int WINAPIV wnsprintfA(LPSTR lpOut, int cchLimitIn, LPCSTR lpFmt, ...)
-{
-    va_list valist;
-    INT res;
-
-    va_start( valist, lpFmt );
-    res = wvsnprintfA( lpOut, cchLimitIn, lpFmt, valist );
-    va_end( valist );
-    return res;
-}
-
-/*************************************************************************
- *      wnsprintfW	[SHLWAPI.@]
- */
-int WINAPIV wnsprintfW(LPWSTR lpOut, int cchLimitIn, LPCWSTR lpFmt, ...)
-{
-    va_list valist;
-    INT res;
-
-    va_start( valist, lpFmt );
-    res = wvsnprintfW( lpOut, cchLimitIn, lpFmt, valist );
-    va_end( valist );
-    return res;
-}
diff --git a/dlls/shlwapi/wsprintf.c b/dlls/shlwapi/wsprintf.c
new file mode 100644
index 0000000..b99e495
--- /dev/null
+++ b/dlls/shlwapi/wsprintf.c
@@ -0,0 +1,504 @@
+/*
+ * wsprintf functions
+ *
+ * Copyright 1996 Alexandre Julliard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * NOTE:
+ * This code is duplicated in user32. If you change something here make sure
+ * to change it in user32 too.
+ */
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(string);
+
+
+#define WPRINTF_LEFTALIGN   0x0001  /* Align output on the left ('-' prefix) */
+#define WPRINTF_PREFIX_HEX  0x0002  /* Prefix hex with 0x ('#' prefix) */
+#define WPRINTF_ZEROPAD     0x0004  /* Pad with zeros ('0' prefix) */
+#define WPRINTF_LONG        0x0008  /* Long arg ('l' prefix) */
+#define WPRINTF_SHORT       0x0010  /* Short arg ('h' prefix) */
+#define WPRINTF_UPPER_HEX   0x0020  /* Upper-case hex ('X' specifier) */
+#define WPRINTF_WIDE        0x0040  /* Wide arg ('w' prefix) */
+
+typedef enum
+{
+    WPR_UNKNOWN,
+    WPR_CHAR,
+    WPR_WCHAR,
+    WPR_STRING,
+    WPR_WSTRING,
+    WPR_SIGNED,
+    WPR_UNSIGNED,
+    WPR_HEXA
+} WPRINTF_TYPE;
+
+typedef struct
+{
+    UINT         flags;
+    UINT         width;
+    UINT         precision;
+    WPRINTF_TYPE   type;
+} WPRINTF_FORMAT;
+
+typedef union {
+    WCHAR   wchar_view;
+    CHAR    char_view;
+    LPCSTR  lpcstr_view;
+    LPCWSTR lpcwstr_view;
+    INT     int_view;
+} WPRINTF_DATA;
+
+static const CHAR null_stringA[] = "(null)";
+static const WCHAR null_stringW[] = { '(', 'n', 'u', 'l', 'l', ')', 0 };
+
+/***********************************************************************
+ *           WPRINTF_ParseFormatA
+ *
+ * Parse a format specification. A format specification has the form:
+ *
+ * [-][#][0][width][.precision]type
+ *
+ * Return value is the length of the format specification in characters.
+ */
+static INT WPRINTF_ParseFormatA( LPCSTR format, WPRINTF_FORMAT *res )
+{
+    LPCSTR p = format;
+
+    res->flags = 0;
+    res->width = 0;
+    res->precision = 0;
+    if (*p == '-') { res->flags |= WPRINTF_LEFTALIGN; p++; }
+    if (*p == '#') { res->flags |= WPRINTF_PREFIX_HEX; p++; }
+    if (*p == '0') { res->flags |= WPRINTF_ZEROPAD; p++; }
+    while ((*p >= '0') && (*p <= '9'))  /* width field */
+    {
+        res->width = res->width * 10 + *p - '0';
+        p++;
+    }
+    if (*p == '.')  /* precision field */
+    {
+        p++;
+        while ((*p >= '0') && (*p <= '9'))
+        {
+            res->precision = res->precision * 10 + *p - '0';
+            p++;
+        }
+    }
+    if (*p == 'l') { res->flags |= WPRINTF_LONG; p++; }
+    else if (*p == 'h') { res->flags |= WPRINTF_SHORT; p++; }
+    else if (*p == 'w') { res->flags |= WPRINTF_WIDE; p++; }
+    switch(*p)
+    {
+    case 'c':
+        res->type = (res->flags & WPRINTF_LONG) ? WPR_WCHAR : WPR_CHAR;
+        break;
+    case 'C':
+        res->type = (res->flags & WPRINTF_SHORT) ? WPR_CHAR : WPR_WCHAR;
+        break;
+    case 'd':
+    case 'i':
+        res->type = WPR_SIGNED;
+        break;
+    case 's':
+        res->type = (res->flags & (WPRINTF_LONG |WPRINTF_WIDE)) ? WPR_WSTRING : WPR_STRING;
+        break;
+    case 'S':
+        res->type = (res->flags & (WPRINTF_SHORT|WPRINTF_WIDE)) ? WPR_STRING : WPR_WSTRING;
+        break;
+    case 'u':
+        res->type = WPR_UNSIGNED;
+        break;
+    case 'X':
+        res->flags |= WPRINTF_UPPER_HEX;
+        /* fall through */
+    case 'x':
+        res->type = WPR_HEXA;
+        break;
+    default: /* unknown format char */
+        res->type = WPR_UNKNOWN;
+        p--;  /* print format as normal char */
+        break;
+    }
+    return (INT)(p - format) + 1;
+}
+
+
+/***********************************************************************
+ *           WPRINTF_ParseFormatW
+ *
+ * Parse a format specification. A format specification has the form:
+ *
+ * [-][#][0][width][.precision]type
+ *
+ * Return value is the length of the format specification in characters.
+ */
+static INT WPRINTF_ParseFormatW( LPCWSTR format, WPRINTF_FORMAT *res )
+{
+    LPCWSTR p = format;
+
+    res->flags = 0;
+    res->width = 0;
+    res->precision = 0;
+    if (*p == '-') { res->flags |= WPRINTF_LEFTALIGN; p++; }
+    if (*p == '#') { res->flags |= WPRINTF_PREFIX_HEX; p++; }
+    if (*p == '0') { res->flags |= WPRINTF_ZEROPAD; p++; }
+    while ((*p >= '0') && (*p <= '9'))  /* width field */
+    {
+        res->width = res->width * 10 + *p - '0';
+        p++;
+    }
+    if (*p == '.')  /* precision field */
+    {
+        p++;
+        while ((*p >= '0') && (*p <= '9'))
+        {
+            res->precision = res->precision * 10 + *p - '0';
+            p++;
+        }
+    }
+    if (*p == 'l') { res->flags |= WPRINTF_LONG; p++; }
+    else if (*p == 'h') { res->flags |= WPRINTF_SHORT; p++; }
+    else if (*p == 'w') { res->flags |= WPRINTF_WIDE; p++; }
+    switch((CHAR)*p)
+    {
+    case 'c':
+        res->type = (res->flags & WPRINTF_SHORT) ? WPR_CHAR : WPR_WCHAR;
+        break;
+    case 'C':
+        res->type = (res->flags & WPRINTF_LONG) ? WPR_WCHAR : WPR_CHAR;
+        break;
+    case 'd':
+    case 'i':
+        res->type = WPR_SIGNED;
+        break;
+    case 's':
+        res->type = ((res->flags & WPRINTF_SHORT) && !(res->flags & WPRINTF_WIDE)) ? WPR_STRING : WPR_WSTRING;
+        break;
+    case 'S':
+        res->type = (res->flags & (WPRINTF_LONG|WPRINTF_WIDE)) ? WPR_WSTRING : WPR_STRING;
+        break;
+    case 'u':
+        res->type = WPR_UNSIGNED;
+        break;
+    case 'X':
+        res->flags |= WPRINTF_UPPER_HEX;
+        /* fall through */
+    case 'x':
+        res->type = WPR_HEXA;
+        break;
+    default:
+        res->type = WPR_UNKNOWN;
+        p--;  /* print format as normal char */
+        break;
+    }
+    return (INT)(p - format) + 1;
+}
+
+
+/***********************************************************************
+ *           WPRINTF_GetLen
+ */
+static UINT WPRINTF_GetLen( WPRINTF_FORMAT *format, WPRINTF_DATA *arg,
+                              LPSTR number, UINT maxlen )
+{
+    UINT len;
+
+    if (format->flags & WPRINTF_LEFTALIGN) format->flags &= ~WPRINTF_ZEROPAD;
+    if (format->width > maxlen) format->width = maxlen;
+    switch(format->type)
+    {
+    case WPR_CHAR:
+    case WPR_WCHAR:
+        return (format->precision = 1);
+    case WPR_STRING:
+        if (!arg->lpcstr_view) arg->lpcstr_view = null_stringA;
+        for (len = 0; !format->precision || (len < format->precision); len++)
+            if (!*(arg->lpcstr_view + len)) break;
+        if (len > maxlen) len = maxlen;
+        return (format->precision = len);
+    case WPR_WSTRING:
+        if (!arg->lpcwstr_view) arg->lpcwstr_view = null_stringW;
+        for (len = 0; !format->precision || (len < format->precision); len++)
+            if (!*(arg->lpcwstr_view + len)) break;
+        if (len > maxlen) len = maxlen;
+        return (format->precision = len);
+    case WPR_SIGNED:
+        len = sprintf( number, "%d", arg->int_view );
+        break;
+    case WPR_UNSIGNED:
+        len = sprintf( number, "%u", (UINT)arg->int_view );
+        break;
+    case WPR_HEXA:
+        len = sprintf( number,
+                       (format->flags & WPRINTF_UPPER_HEX) ? "%X" : "%x",
+                       (UINT)arg->int_view);
+        break;
+    default:
+        return 0;
+    }
+    if (len > maxlen) len = maxlen;
+    if (format->precision < len) format->precision = len;
+    if (format->precision > maxlen) format->precision = maxlen;
+    if ((format->flags & WPRINTF_ZEROPAD) && (format->width > format->precision))
+        format->precision = format->width;
+    if (format->flags & WPRINTF_PREFIX_HEX) len += 2;
+    return len;
+}
+
+
+/***********************************************************************
+ *           wvnsprintfA   (SHLWAPI.@)
+ */
+INT WINAPI wvnsprintfA( LPSTR buffer, UINT maxlen, LPCSTR spec, va_list args )
+{
+    WPRINTF_FORMAT format;
+    LPSTR p = buffer;
+    UINT i, len, sign;
+    CHAR number[20];
+    WPRINTF_DATA argData;
+
+    TRACE("%p %u %s\n", buffer, maxlen, debugstr_a(spec));
+
+    while (*spec && (maxlen > 1))
+    {
+        if (*spec != '%') { *p++ = *spec++; maxlen--; continue; }
+        spec++;
+        if (*spec == '%') { *p++ = *spec++; maxlen--; continue; }
+        spec += WPRINTF_ParseFormatA( spec, &format );
+
+        switch(format.type)
+        {
+        case WPR_WCHAR:
+            argData.wchar_view = (WCHAR)va_arg( args, int );
+            break;
+        case WPR_CHAR:
+            argData.char_view = (CHAR)va_arg( args, int );
+            break;
+        case WPR_STRING:
+            argData.lpcstr_view = va_arg( args, LPCSTR );
+            break;
+        case WPR_WSTRING:
+            argData.lpcwstr_view = va_arg( args, LPCWSTR );
+            break;
+        case WPR_HEXA:
+        case WPR_SIGNED:
+        case WPR_UNSIGNED:
+            argData.int_view = va_arg( args, INT );
+            break;
+        default:
+            argData.wchar_view = 0;
+            break;
+        }
+
+        len = WPRINTF_GetLen( &format, &argData, number, maxlen - 1 );
+        sign = 0;
+        if (!(format.flags & WPRINTF_LEFTALIGN))
+            for (i = format.precision; i < format.width; i++, maxlen--)
+                *p++ = ' ';
+        switch(format.type)
+        {
+        case WPR_WCHAR:
+            *p++ = argData.wchar_view;
+            break;
+        case WPR_CHAR:
+            *p++ = argData.char_view;
+            break;
+        case WPR_STRING:
+            memcpy( p, argData.lpcstr_view, len );
+            p += len;
+            break;
+        case WPR_WSTRING:
+            {
+                LPCWSTR ptr = argData.lpcwstr_view;
+                for (i = 0; i < len; i++) *p++ = (CHAR)*ptr++;
+            }
+            break;
+        case WPR_HEXA:
+            if ((format.flags & WPRINTF_PREFIX_HEX) && (maxlen > 3))
+            {
+                *p++ = '0';
+                *p++ = (format.flags & WPRINTF_UPPER_HEX) ? 'X' : 'x';
+                maxlen -= 2;
+                len -= 2;
+            }
+            /* fall through */
+        case WPR_SIGNED:
+            /* Transfer the sign now, just in case it will be zero-padded*/
+            if (number[0] == '-')
+            {
+                *p++ = '-';
+                sign = 1;
+            }
+            /* fall through */
+        case WPR_UNSIGNED:
+            for (i = len; i < format.precision; i++, maxlen--) *p++ = '0';
+            memcpy( p, number + sign, len - sign  );
+            p += len - sign;
+            break;
+        case WPR_UNKNOWN:
+            continue;
+        }
+        if (format.flags & WPRINTF_LEFTALIGN)
+            for (i = format.precision; i < format.width; i++, maxlen--)
+                *p++ = ' ';
+        maxlen -= len;
+    }
+    *p = 0;
+    TRACE("%s\n",debugstr_a(buffer));
+    return (maxlen > 1) ? (INT)(p - buffer) : -1;
+}
+
+
+/***********************************************************************
+ *           wvnsprintfW   (SHLWAPI.@)
+ */
+INT WINAPI wvnsprintfW( LPWSTR buffer, UINT maxlen, LPCWSTR spec, va_list args )
+{
+    WPRINTF_FORMAT format;
+    LPWSTR p = buffer;
+    UINT i, len, sign;
+    CHAR number[20];
+    WPRINTF_DATA argData;
+
+    TRACE("%p %u %s\n", buffer, maxlen, debugstr_w(spec));
+
+    while (*spec && (maxlen > 1))
+    {
+        if (*spec != '%') { *p++ = *spec++; maxlen--; continue; }
+        spec++;
+        if (*spec == '%') { *p++ = *spec++; maxlen--; continue; }
+        spec += WPRINTF_ParseFormatW( spec, &format );
+
+        switch(format.type)
+        {
+        case WPR_WCHAR:
+            argData.wchar_view = (WCHAR)va_arg( args, int );
+            break;
+        case WPR_CHAR:
+            argData.char_view = (CHAR)va_arg( args, int );
+            break;
+        case WPR_STRING:
+            argData.lpcstr_view = va_arg( args, LPCSTR );
+            break;
+        case WPR_WSTRING:
+            argData.lpcwstr_view = va_arg( args, LPCWSTR );
+            break;
+        case WPR_HEXA:
+        case WPR_SIGNED:
+        case WPR_UNSIGNED:
+            argData.int_view = va_arg( args, INT );
+            break;
+        default:
+            argData.wchar_view = 0;
+            break;
+        }
+
+        len = WPRINTF_GetLen( &format, &argData, number, maxlen - 1 );
+        sign = 0;
+        if (!(format.flags & WPRINTF_LEFTALIGN))
+            for (i = format.precision; i < format.width; i++, maxlen--)
+                *p++ = ' ';
+        switch(format.type)
+        {
+        case WPR_WCHAR:
+            *p++ = argData.wchar_view;
+            break;
+        case WPR_CHAR:
+            *p++ = argData.char_view;
+            break;
+        case WPR_STRING:
+            {
+                LPCSTR ptr = argData.lpcstr_view;
+                for (i = 0; i < len; i++) *p++ = (WCHAR)*ptr++;
+            }
+            break;
+        case WPR_WSTRING:
+            if (len) memcpy( p, argData.lpcwstr_view, len * sizeof(WCHAR) );
+            p += len;
+            break;
+        case WPR_HEXA:
+            if ((format.flags & WPRINTF_PREFIX_HEX) && (maxlen > 3))
+            {
+                *p++ = '0';
+                *p++ = (format.flags & WPRINTF_UPPER_HEX) ? 'X' : 'x';
+                maxlen -= 2;
+                len -= 2;
+            }
+            /* fall through */
+        case WPR_SIGNED:
+            /* Transfer the sign now, just in case it will be zero-padded*/
+            if (number[0] == '-')
+            {
+                *p++ = '-';
+                sign = 1;
+            }
+            /* fall through */
+        case WPR_UNSIGNED:
+            for (i = len; i < format.precision; i++, maxlen--) *p++ = '0';
+            for (i = sign; i < len; i++) *p++ = (WCHAR)number[i];
+            break;
+        case WPR_UNKNOWN:
+            continue;
+        }
+        if (format.flags & WPRINTF_LEFTALIGN)
+            for (i = format.precision; i < format.width; i++, maxlen--)
+                *p++ = ' ';
+        maxlen -= len;
+    }
+    *p = 0;
+    TRACE("%s\n",debugstr_w(buffer));
+    return (maxlen > 1) ? (INT)(p - buffer) : -1;
+}
+
+
+/*************************************************************************
+ *           wnsprintfA   (SHLWAPI.@)
+ */
+int WINAPIV wnsprintfA(LPSTR lpOut, int cchLimitIn, LPCSTR lpFmt, ...)
+{
+    va_list valist;
+    INT res;
+
+    va_start( valist, lpFmt );
+    res = wvnsprintfA( lpOut, cchLimitIn, lpFmt, valist );
+    va_end( valist );
+    return res;
+}
+
+
+/*************************************************************************
+ *           wnsprintfW   (SHLWAPI.@)
+ */
+int WINAPIV wnsprintfW(LPWSTR lpOut, int cchLimitIn, LPCWSTR lpFmt, ...)
+{
+    va_list valist;
+    INT res;
+
+    va_start( valist, lpFmt );
+    res = wvnsprintfW( lpOut, cchLimitIn, lpFmt, valist );
+    va_end( valist );
+    return res;
+}
diff --git a/dlls/user/user32.spec b/dlls/user/user32.spec
index e078d35..82e4542 100644
--- a/dlls/user/user32.spec
+++ b/dlls/user/user32.spec
@@ -671,9 +671,3 @@
 @ stdcall SendDriverMessage16(long long long long) SendDriverMessage16
 @ stdcall SetWindowsHookEx16(long long long long) SetWindowsHookEx16
 @ stdcall UserYield16() UserYield16
-
-################################################################
-# Wine extensions: extra useful functions not exported under Windows
-#
-@ stdcall wvsnprintfA(ptr long str ptr) wvsnprintfA
-@ stdcall wvsnprintfW(ptr long wstr ptr) wvsnprintfW
diff --git a/dlls/user/wsprintf.c b/dlls/user/wsprintf.c
index 78182ad..0ee68dd 100644
--- a/dlls/user/wsprintf.c
+++ b/dlls/user/wsprintf.c
@@ -16,6 +16,10 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * NOTE:
+ * This code is duplicated in shlwapi. If you change something here make sure
+ * to change it in shlwapi too.
  */
 
 #include <stdarg.h>
@@ -373,9 +377,9 @@
 
 
 /***********************************************************************
- *           wvsnprintfA   (USER32.@) (Not a Windows API, but we export it from USER32 anyway)
+ *           wvsnprintfA   (internal)
  */
-INT WINAPI wvsnprintfA( LPSTR buffer, UINT maxlen, LPCSTR spec, va_list args )
+static INT wvsnprintfA( LPSTR buffer, UINT maxlen, LPCSTR spec, va_list args )
 {
     WPRINTF_FORMAT format;
     LPSTR p = buffer;
@@ -476,9 +480,9 @@
 
 
 /***********************************************************************
- *           wvsnprintfW   (USER32.@) (Not a Windows API, but we export it from USER32 anyway)
+ *           wvsnprintfW   (internal)
  */
-INT WINAPI wvsnprintfW( LPWSTR buffer, UINT maxlen, LPCWSTR spec, va_list args )
+static INT wvsnprintfW( LPWSTR buffer, UINT maxlen, LPCWSTR spec, va_list args )
 {
     WPRINTF_FORMAT format;
     LPWSTR p = buffer;
diff --git a/include/winuser.h b/include/winuser.h
index b9bec44..92d2b28 100644
--- a/include/winuser.h
+++ b/include/winuser.h
@@ -4241,12 +4241,6 @@
 HRESULT     WINAPI PrivateExtractIconsA(LPCSTR,INT,DWORD,DWORD,HICON*,DWORD,UINT,DWORD);
 HRESULT     WINAPI PrivateExtractIconsW(LPCWSTR,INT,DWORD,DWORD,HICON*,DWORD,UINT,DWORD);
 
-/* Extra functions that don't exist in the Windows API */
-
-INT         WINAPI wvsnprintfA(LPSTR,UINT,LPCSTR,va_list);
-INT         WINAPI wvsnprintfW(LPWSTR,UINT,LPCWSTR,va_list);
-#define     wvsnprintf WINELIB_NAME_AW(wvsnprintf)
-
 #ifdef __cplusplus
 }
 #endif