kernel32: Transition FormatMessageA to the internal use of Unicode strings.
[wine] / dlls / kernel32 / format_msg.c
1 /*
2  * FormatMessage implementation
3  *
4  * Copyright 1996 Marcus Meissner
5  * Copyright 2009 Alexandre Julliard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include "ntstatus.h"
29 #define WIN32_NO_STATUS
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winerror.h"
33 #include "winternl.h"
34 #include "winuser.h"
35 #include "winnls.h"
36 #include "wine/unicode.h"
37 #include "kernel_private.h"
38 #include "wine/debug.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(resource);
41
42 struct format_args
43 {
44     ULONG_PTR    *args;
45     __ms_va_list *list;
46     int           last;
47 };
48
49 /* Messages used by FormatMessage
50  *
51  * They can be specified either directly or using a message ID and
52  * loading them from the resource.
53  *
54  * The resourcedata has following format:
55  * start:
56  * 0: DWORD nrofentries
57  * nrofentries * subentry:
58  *      0: DWORD firstentry
59  *      4: DWORD lastentry
60  *      8: DWORD offset from start to the stringentries
61  *
62  * (lastentry-firstentry) * stringentry:
63  * 0: WORD len (0 marks end)    [ includes the 4 byte header length ]
64  * 2: WORD flags
65  * 4: CHAR[len-4]
66  *      (stringentry i of a subentry refers to the ID 'firstentry+i')
67  *
68  * Yes, ANSI strings in win32 resources. Go figure.
69  */
70
71 static const WCHAR PCNTFMTWSTR[] = { '%','%','%','s',0 };
72 static const WCHAR FMTWSTR[] = { '%','s',0 };
73
74 /**********************************************************************
75  *      load_messageW           (internal)
76  */
77 static LPWSTR load_messageW( HMODULE module, UINT id, WORD lang )
78 {
79     const MESSAGE_RESOURCE_ENTRY *mre;
80     WCHAR *buffer;
81     NTSTATUS status;
82
83     TRACE("module = %p, id = %08x\n", module, id );
84
85     if (!module) module = GetModuleHandleW( NULL );
86     if ((status = RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre )) != STATUS_SUCCESS)
87     {
88         SetLastError( RtlNtStatusToDosError(status) );
89         return NULL;
90     }
91
92     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
93     {
94         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
95         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
96         memcpy( buffer, mre->Text, len );
97     }
98     else
99     {
100         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
101         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
102         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
103     }
104     TRACE("returning %s\n", wine_dbgstr_w(buffer));
105     return buffer;
106 }
107
108 /**********************************************************************
109  *      get_arg    (internal)
110  */
111 static ULONG_PTR get_arg( int nr, DWORD flags, struct format_args *args )
112 {
113     if (nr == -1) nr = args->last + 1;
114     if (args->list)
115     {
116         if (!args->args) args->args = HeapAlloc( GetProcessHeap(), 0, 99 * sizeof(ULONG_PTR) );
117         while (nr > args->last)
118             args->args[args->last++] = va_arg( *args->list, ULONG_PTR );
119     }
120     if (nr > args->last) args->last = nr;
121     return args->args[nr - 1];
122 }
123
124
125 /**********************************************************************
126  *      format_insertA    (internal)
127  */
128 static LPCWSTR format_insertA( int insert, LPCWSTR format, DWORD flags,
129                                struct format_args *args, LPWSTR *result )
130 {
131     static const WCHAR fmt_lu[] = {'%','l','u',0};
132     WCHAR *wstring = NULL, *p, fmt[256];
133     ULONG_PTR arg;
134     int size;
135
136     if (*format != '!')  /* simple string */
137     {
138         char *str = (char *)get_arg( insert, flags, args );
139         DWORD length = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
140         *result = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
141         MultiByteToWideChar(CP_ACP, 0, str, -1, *result, length);
142         return format;
143     }
144
145     format++;
146     p = fmt;
147     *p++ = '%';
148
149     while (*format == '0' ||
150            *format == '+' ||
151            *format == '-' ||
152            *format == ' ' ||
153            *format == '*' ||
154            *format == '#')
155     {
156         if (*format == '*')
157         {
158             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
159             insert = -1;
160             format++;
161         }
162         else *p++ = *format++;
163     }
164     while (isdigit(*format)) *p++ = *format++;
165
166     if (*format == '.')
167     {
168         *p++ = *format++;
169         if (*format == '*')
170         {
171             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
172             insert = -1;
173             format++;
174         }
175         else
176             while (isdigit(*format)) *p++ = *format++;
177     }
178
179     /* replicate MS bug: drop an argument when using va_list with width/precision */
180     if (insert == -1 && args->list) args->last--;
181     arg = get_arg( insert, flags, args );
182
183     /* check for wide string format */
184     if ((format[0] == 'l' && format[1] == 's') ||
185         (format[0] == 'l' && format[1] == 'S') ||
186         (format[0] == 'w' && format[1] == 's') ||
187         (format[0] == 'S'))
188     {
189         *p++ = 's';
190     }
191     /* check for wide character format */
192     else if ((format[0] == 'l' && format[1] == 'c') ||
193              (format[0] == 'l' && format[1] == 'C') ||
194              (format[0] == 'w' && format[1] == 'c') ||
195              (format[0] == 'C'))
196     {
197         *p++ = 'c';
198     }
199     /* check for ascii string format */
200     else if ((format[0] == 'h' && format[1] == 's') ||
201              (format[0] == 'h' && format[1] == 'S') ||
202              (format[0] == 's'))
203     {
204         DWORD len = MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, NULL, 0 );
205         wstring = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
206         MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, wstring, len );
207         arg = (ULONG_PTR)wstring;
208         *p++ = 's';
209     }
210     /* check for ascii character format */
211     else if ((format[0] == 'h' && format[1] == 'c') ||
212              (format[0] == 'h' && format[1] == 'C') ||
213              (format[0] == 'c'))
214     {
215         char ch = arg;
216         wstring = HeapAlloc( GetProcessHeap(), 0, 2 * sizeof(WCHAR) );
217         MultiByteToWideChar( CP_ACP, 0, &ch, 1, wstring, 1 );
218         wstring[1] = 0;
219         arg = (ULONG_PTR)wstring;
220         *p++ = 's';
221     }
222     /* FIXME: handle I64 etc. */
223     else while (*format && *format != '!') *p++ = *format++;
224
225     *p = 0;
226     size = 256;
227     for (;;)
228     {
229         WCHAR *ret = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR));
230         int needed = snprintfW( ret, size, fmt, arg );
231         if (needed == -1 || needed >= size)
232         {
233             HeapFree( GetProcessHeap(), 0, ret );
234             size = max( needed + 1, size * 2 );
235         }
236         else
237         {
238             *result = ret;
239             break;
240         }
241     }
242
243     while (*format && *format != '!') format++;
244     if (*format == '!') format++;
245
246     HeapFree( GetProcessHeap(), 0, wstring );
247     return format;
248 }
249
250
251 /**********************************************************************
252  *      format_insertW    (internal)
253  */
254 static LPCWSTR format_insertW( int insert, LPCWSTR format, DWORD flags,
255                                struct format_args *args, LPWSTR *result )
256 {
257     static const WCHAR fmt_lu[] = {'%','l','u',0};
258     WCHAR *wstring = NULL, *p, fmt[256];
259     ULONG_PTR arg;
260     int size;
261
262     if (*format != '!')  /* simple string */
263     {
264         WCHAR *str = (WCHAR *)get_arg( insert, flags, args );
265         *result = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) );
266         strcpyW( *result, str );
267         return format;
268     }
269
270     format++;
271     p = fmt;
272     *p++ = '%';
273
274     while (*format == '0' ||
275            *format == '+' ||
276            *format == '-' ||
277            *format == ' ' ||
278            *format == '*' ||
279            *format == '#')
280     {
281         if (*format == '*')
282         {
283             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
284             insert = -1;
285             format++;
286         }
287         else *p++ = *format++;
288     }
289     while (isdigitW(*format)) *p++ = *format++;
290
291     if (*format == '.')
292     {
293         *p++ = *format++;
294         if (*format == '*')
295         {
296             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
297             insert = -1;
298             format++;
299         }
300         else
301             while (isdigitW(*format)) *p++ = *format++;
302     }
303
304     /* replicate MS bug: drop an argument when using va_list with width/precision */
305     if (insert == -1 && args->list) args->last--;
306     arg = get_arg( insert, flags, args );
307
308     /* check for ascii string format */
309     if ((format[0] == 'h' && format[1] == 's') ||
310         (format[0] == 'h' && format[1] == 'S') ||
311         (format[0] == 'S'))
312     {
313         DWORD len = MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, /*FIXME*/ NULL, 0 );
314         wstring = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
315         MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, wstring, len );
316         arg = (ULONG_PTR)wstring;
317         *p++ = 's';
318     }
319     /* check for ascii character format */
320     else if ((format[0] == 'h' && format[1] == 'c') ||
321              (format[0] == 'h' && format[1] == 'C') ||
322              (format[0] == 'C'))
323     {
324         char ch = arg;
325         wstring = HeapAlloc( GetProcessHeap(), 0, 2 * sizeof(WCHAR) );
326         MultiByteToWideChar( CP_ACP, 0, &ch, 1, wstring, 1 );
327         wstring[1] = 0;
328         arg = (ULONG_PTR)wstring;
329         *p++ = 's';
330     }
331     /* check for wide string format */
332     else if ((format[0] == 'l' && format[1] == 's') ||
333              (format[0] == 'l' && format[1] == 'S') ||
334              (format[0] == 'w' && format[1] == 's'))
335     {
336         *p++ = 's';
337     }
338     /* check for wide character format */
339     else if ((format[0] == 'l' && format[1] == 'c') ||
340              (format[0] == 'l' && format[1] == 'C') ||
341              (format[0] == 'w' && format[1] == 'c'))
342     {
343         *p++ = 'c';
344     }
345     /* FIXME: handle I64 etc. */
346     else while (*format && *format != '!') *p++ = *format++;
347
348     *p = 0;
349     size = 256;
350     for (;;)
351     {
352         WCHAR *ret = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) );
353         int needed = snprintfW( ret, size, fmt, arg );
354         if (needed == -1 || needed >= size)
355         {
356             HeapFree( GetProcessHeap(), 0, ret );
357             size = max( needed + 1, size * 2 );
358         }
359         else
360         {
361             *result = ret;
362             break;
363         }
364     }
365
366     while (*format && *format != '!') format++;
367     if (*format == '!') format++;
368
369     HeapFree( GetProcessHeap(), 0, wstring );
370     return format;
371 }
372
373 /**********************************************************************
374  *      format_messageA    (internal)
375  */
376 static LPWSTR format_messageA( DWORD dwFlags, LPCWSTR fmtstr, struct format_args *format_args )
377 {
378     LPWSTR target,t;
379     DWORD talloced;
380     LPCWSTR f;
381     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
382     BOOL eos = FALSE;
383     WCHAR ch;
384
385     target = t = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR));
386     talloced = 100;
387
388 #define ADD_TO_T(c) do { \
389         *t++=c;\
390         if ((DWORD)(t-target) == talloced) {\
391             target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
392             t = target+talloced;\
393             talloced*=2;\
394       }\
395 } while (0)
396
397     f = fmtstr;
398     if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
399         while (*f && !eos)
400             ADD_TO_T(*f++);
401     }
402     else {
403         while (*f && !eos) {
404             if (*f=='%') {
405                 int insertnr;
406                 WCHAR *str,*x;
407
408                 f++;
409                 if (!*f) {
410                     ADD_TO_T('%');
411                     continue;
412                 }
413                 switch (*f) {
414                 case '1':case '2':case '3':case '4':case '5':
415                 case '6':case '7':case '8':case '9':
416                     insertnr = *f-'0';
417                     switch (f[1]) {
418                     case '0':case '1':case '2':case '3':
419                     case '4':case '5':case '6':case '7':
420                     case '8':case '9':
421                         f++;
422                         insertnr = insertnr*10 + *f-'0';
423                         f++;
424                         break;
425                     default:
426                         f++;
427                         break;
428                     }
429                     f = format_insertA( insertnr, f, dwFlags, format_args, &str );
430                     for (x = str; *x; x++) ADD_TO_T(*x);
431                     HeapFree( GetProcessHeap(), 0, str );
432                     break;
433                 case 'n':
434                     ADD_TO_T('\r');
435                     ADD_TO_T('\n');
436                     f++;
437                     break;
438                 case '0':
439                     eos = TRUE;
440                     f++;
441                     break;
442                 default:
443                     ADD_TO_T(*f++);
444                     break;
445                 }
446             } else {
447                 ch = *f;
448                 f++;
449                 if (ch == '\r') {
450                     if (*f == '\n')
451                         f++;
452                     if(width)
453                         ADD_TO_T(' ');
454                     else
455                     {
456                         ADD_TO_T('\r');
457                         ADD_TO_T('\n');
458                     }
459                 } else {
460                     if (ch == '\n')
461                     {
462                         if(width)
463                             ADD_TO_T(' ');
464                         else
465                         {
466                             ADD_TO_T('\r');
467                             ADD_TO_T('\n');
468                         }
469                     }
470                     else
471                         ADD_TO_T(ch);
472                 }
473             }
474         }
475     }
476     *t = '\0';
477
478     return target;
479 }
480 #undef ADD_TO_T
481
482 /**********************************************************************
483  *      format_messageW    (internal)
484  */
485 static LPWSTR format_messageW( DWORD dwFlags, LPCWSTR fmtstr, struct format_args *format_args )
486 {
487     LPWSTR target,t;
488     DWORD talloced;
489     LPCWSTR f;
490     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
491     BOOL eos = FALSE;
492     WCHAR ch;
493
494     target = t = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR) );
495     talloced = 100;
496
497 #define ADD_TO_T(c)  do {\
498     *t++=c;\
499     if ((DWORD)(t-target) == talloced) {\
500         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
501         t = target+talloced;\
502         talloced*=2;\
503     } \
504 } while (0)
505
506     f = fmtstr;
507     if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
508         while (*f && !eos)
509             ADD_TO_T(*f++);
510     }
511     else {
512         while (*f && !eos) {
513             if (*f=='%') {
514                 int insertnr;
515                 WCHAR *str,*x;
516
517                 f++;
518                 if (!*f) {
519                     ADD_TO_T('%');
520                     continue;
521                 }
522
523                 switch (*f) {
524                 case '1':case '2':case '3':case '4':case '5':
525                 case '6':case '7':case '8':case '9':
526                     insertnr = *f-'0';
527                     switch (f[1]) {
528                     case '0':case '1':case '2':case '3':
529                     case '4':case '5':case '6':case '7':
530                     case '8':case '9':
531                         f++;
532                         insertnr = insertnr*10 + *f-'0';
533                         f++;
534                         break;
535                     default:
536                         f++;
537                         break;
538                     }
539                     f = format_insertW( insertnr, f, dwFlags, format_args, &str );
540                     for (x = str; *x; x++) ADD_TO_T(*x);
541                     HeapFree( GetProcessHeap(), 0, str );
542                     break;
543                 case 'n':
544                     ADD_TO_T('\r');
545                     ADD_TO_T('\n');
546                     f++;
547                     break;
548                 case '0':
549                     eos = TRUE;
550                     f++;
551                     break;
552                 default:
553                     ADD_TO_T(*f++);
554                     break;
555                 }
556             } else {
557                 ch = *f;
558                 f++;
559                 if (ch == '\r') {
560                     if (*f == '\n')
561                         f++;
562                     if(width)
563                         ADD_TO_T(' ');
564                     else
565                     {
566                         ADD_TO_T('\r');
567                         ADD_TO_T('\n');
568                     }
569                 } else {
570                     if (ch == '\n')
571                     {
572                         if(width)
573                             ADD_TO_T(' ');
574                         else
575                         {
576                             ADD_TO_T('\r');
577                             ADD_TO_T('\n');
578                         }
579                     }
580                     else
581                         ADD_TO_T(ch);
582                 }
583             }
584         }
585     }
586     *t = '\0';
587
588     return target;
589 }
590 #undef ADD_TO_T
591
592 /***********************************************************************
593  *           FormatMessageA   (KERNEL32.@)
594  * FIXME: missing wrap,
595  */
596 DWORD WINAPI FormatMessageA(
597         DWORD   dwFlags,
598         LPCVOID lpSource,
599         DWORD   dwMessageId,
600         DWORD   dwLanguageId,
601         LPSTR   lpBuffer,
602         DWORD   nSize,
603         __ms_va_list* args )
604 {
605     struct format_args format_args;
606     DWORD ret = 0;
607     LPWSTR      target;
608     DWORD       destlength;
609     LPWSTR      from;
610     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
611
612     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
613           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
614     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
615         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
616            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
617
618     if ((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) && !lpBuffer)
619     {
620         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
621         return 0;
622     }
623
624     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
625     {
626         format_args.args = (ULONG_PTR *)args;
627         format_args.list = NULL;
628         format_args.last = 0;
629     }
630     else
631     {
632         format_args.args = NULL;
633         format_args.list = args;
634         format_args.last = 0;
635     }
636
637     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
638         FIXME("line wrapping (%u) not supported.\n", width);
639     from = NULL;
640     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
641     {
642         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
643         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
644         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
645     }
646     else {
647         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
648             from = load_messageW( (HMODULE)lpSource, dwMessageId, dwLanguageId );
649         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
650             from = load_messageW( kernel32_handle, dwMessageId, dwLanguageId );
651         if (!from) return 0;
652     }
653
654     target = format_messageA( dwFlags, from, &format_args );
655
656     TRACE("-- %s\n", debugstr_w(target));
657     destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
658     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
659         LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
660         WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
661         *((LPSTR*)lpBuffer) = buf;
662     } else {
663         if (nSize < destlength)
664         {
665             SetLastError(ERROR_INSUFFICIENT_BUFFER);
666             goto failure;
667         }
668
669         WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
670     }
671
672     ret = destlength - 1; /* null terminator */
673 failure:
674     HeapFree(GetProcessHeap(),0,target);
675     HeapFree(GetProcessHeap(),0,from);
676     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
677     TRACE("-- returning %u\n", ret);
678     return ret;
679 }
680 #undef ADD_TO_T
681
682
683 /***********************************************************************
684  *           FormatMessageW   (KERNEL32.@)
685  */
686 DWORD WINAPI FormatMessageW(
687         DWORD   dwFlags,
688         LPCVOID lpSource,
689         DWORD   dwMessageId,
690         DWORD   dwLanguageId,
691         LPWSTR  lpBuffer,
692         DWORD   nSize,
693         __ms_va_list* args )
694 {
695     struct format_args format_args;
696     DWORD ret = 0;
697     LPWSTR target;
698     DWORD talloced;
699     LPWSTR from;
700     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
701
702     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
703           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
704     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
705         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
706            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
707
708     if (!lpBuffer)
709     {
710         SetLastError(ERROR_INVALID_PARAMETER);
711         return 0;
712     }
713
714     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
715     {
716         format_args.args = (ULONG_PTR *)args;
717         format_args.list = NULL;
718         format_args.last = 0;
719     }
720     else
721     {
722         format_args.args = NULL;
723         format_args.list = args;
724         format_args.last = 0;
725     }
726
727     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
728         FIXME("line wrapping not supported.\n");
729     from = NULL;
730     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
731         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
732             sizeof(WCHAR) );
733         strcpyW( from, lpSource );
734     }
735     else {
736         from = NULL;
737         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
738             from = load_messageW( (HMODULE)lpSource, dwMessageId, dwLanguageId );
739         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
740             from = load_messageW( kernel32_handle, dwMessageId, dwLanguageId );
741         if (!from) return 0;
742     }
743
744     target = format_messageW( dwFlags, from, &format_args );
745
746     talloced = strlenW(target)+1;
747     TRACE("-- %s\n",debugstr_w(target));
748     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
749         /* nSize is the MINIMUM size */
750         *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
751         strcpyW(*(LPWSTR*)lpBuffer, target);
752     }
753     else
754     {
755         if (nSize < talloced)
756         {
757             SetLastError(ERROR_INSUFFICIENT_BUFFER);
758             goto failure;
759         }
760         strcpyW(lpBuffer, target);
761     }
762
763     ret = talloced - 1; /* null terminator */
764 failure:
765     HeapFree(GetProcessHeap(),0,target);
766     HeapFree(GetProcessHeap(),0,from);
767     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
768     TRACE("-- returning %u\n", ret);
769     return ret;
770 }
771 #undef ADD_TO_T