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 #ifdef __REACTOS__
23 
24 #include <k32.h>
25 
26 #define NDEBUG
27 #include <debug.h>
28 DEBUG_CHANNEL(resource);
29 
30 extern HMODULE kernel32_handle;
31 
32 #else /* __REACTOS__ */
33 
34 #include "config.h"
35 
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <string.h>
39 
40 #include "ntstatus.h"
41 #define WIN32_NO_STATUS
42 #include "windef.h"
43 #include "winbase.h"
44 #include "winerror.h"
45 #include "winternl.h"
46 #include "winuser.h"
47 #include "winnls.h"
48 #include "wine/unicode.h"
49 #include "kernel_private.h"
50 #include "wine/debug.h"
51 
52 WINE_DEFAULT_DEBUG_CHANNEL(resource);
53 
54 #endif /* __REACTOS__ */
55 
56 struct format_args
57 {
58     ULONG_PTR    *args;
59     __ms_va_list *list;
60     int           last;
61 };
62 
63 /* Messages used by FormatMessage
64  *
65  * They can be specified either directly or using a message ID and
66  * loading them from the resource.
67  *
68  * The resourcedata has following format:
69  * start:
70  * 0: DWORD nrofentries
71  * nrofentries * subentry:
72  *	0: DWORD firstentry
73  *	4: DWORD lastentry
74  *      8: DWORD offset from start to the stringentries
75  *
76  * (lastentry-firstentry) * stringentry:
77  * 0: WORD len (0 marks end)	[ includes the 4 byte header length ]
78  * 2: WORD flags
79  * 4: CHAR[len-4]
80  * 	(stringentry i of a subentry refers to the ID 'firstentry+i')
81  *
82  * Yes, ANSI strings in win32 resources. Go figure.
83  */
84 
85 /**********************************************************************
86  *	load_message    (internal)
87  */
load_message(HMODULE module,UINT id,WORD lang)88 static LPWSTR load_message( HMODULE module, UINT id, WORD lang )
89 {
90 #ifdef __REACTOS__
91     MESSAGE_RESOURCE_ENTRY *mre;
92 #else
93     const MESSAGE_RESOURCE_ENTRY *mre;
94 #endif
95     WCHAR *buffer;
96     NTSTATUS status;
97 
98     TRACE("module = %p, id = %08x\n", module, id );
99 
100     if (!module) module = GetModuleHandleW( NULL );
101 #ifdef __REACTOS__
102     status = RtlFindMessage(module, (ULONG_PTR)RT_MESSAGETABLE, lang, id, &mre);
103     if (!NT_SUCCESS(status))
104 #else
105     if ((status = RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre )) != STATUS_SUCCESS)
106 #endif
107     {
108         SetLastError( RtlNtStatusToDosError(status) );
109         return NULL;
110     }
111 
112     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
113     {
114         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
115         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
116         memcpy( buffer, mre->Text, len );
117     }
118     else
119     {
120         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
121         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
122         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
123     }
124     TRACE("returning %s\n", wine_dbgstr_w(buffer));
125     return buffer;
126 }
127 
search_message(DWORD flags,HMODULE module,UINT id,WORD lang)128 static LPWSTR search_message( DWORD flags, HMODULE module, UINT id, WORD lang )
129 {
130     LPWSTR from = NULL;
131     if (flags & FORMAT_MESSAGE_FROM_HMODULE)
132         from = load_message( module, id, lang );
133     if (!from && (flags & FORMAT_MESSAGE_FROM_SYSTEM))
134     {
135         /* Fold win32 hresult to its embedded error code. */
136         if (HRESULT_SEVERITY(id) == SEVERITY_ERROR &&
137             HRESULT_FACILITY(id) == FACILITY_WIN32)
138         {
139             id = HRESULT_CODE(id);
140         }
141         from = load_message( kernel32_handle, id, lang );
142     }
143     return from;
144 }
145 
146 /**********************************************************************
147  *	get_arg    (internal)
148  */
get_arg(int nr,DWORD flags,struct format_args * args)149 static ULONG_PTR get_arg( int nr, DWORD flags, struct format_args *args )
150 {
151     if (nr == -1) nr = args->last + 1;
152     if (args->list)
153     {
154         if (!args->args) args->args = HeapAlloc( GetProcessHeap(), 0, 99 * sizeof(ULONG_PTR) );
155         while (nr > args->last)
156             args->args[args->last++] = va_arg( *args->list, ULONG_PTR );
157     }
158     if (nr > args->last) args->last = nr;
159     return args->args[nr - 1];
160 }
161 
162 /**********************************************************************
163  *	format_insert    (internal)
164  */
format_insert(BOOL unicode_caller,int insert,LPCWSTR format,DWORD flags,struct format_args * args,LPWSTR * result)165 static LPCWSTR format_insert( BOOL unicode_caller, int insert, LPCWSTR format,
166                               DWORD flags, struct format_args *args,
167                               LPWSTR *result )
168 {
169     static const WCHAR fmt_u[] = {'%','u',0};
170     WCHAR *wstring = NULL, *p, fmt[256];
171     ULONG_PTR arg;
172     int size;
173 
174     if (*format != '!')  /* simple string */
175     {
176         arg = get_arg( insert, flags, args );
177         if (unicode_caller || !arg)
178         {
179             static const WCHAR nullW[] = {'(','n','u','l','l',')',0};
180             const WCHAR *str = (const WCHAR *)arg;
181 
182             if (!str) str = nullW;
183             *result = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) );
184             strcpyW( *result, str );
185         }
186         else
187         {
188             const char *str = (const char *)arg;
189             DWORD length = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 );
190             *result = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
191             MultiByteToWideChar( CP_ACP, 0, str, -1, *result, length );
192         }
193         return format;
194     }
195 
196     format++;
197     p = fmt;
198     *p++ = '%';
199 
200     while (*format == '0' ||
201            *format == '+' ||
202            *format == '-' ||
203            *format == ' ' ||
204            *format == '*' ||
205            *format == '#')
206     {
207         if (*format == '*')
208         {
209             p += sprintfW( p, fmt_u, get_arg( insert, flags, args ));
210             insert = -1;
211             format++;
212         }
213         else *p++ = *format++;
214     }
215     while (isdigitW(*format)) *p++ = *format++;
216 
217     if (*format == '.')
218     {
219         *p++ = *format++;
220         if (*format == '*')
221         {
222             p += sprintfW( p, fmt_u, get_arg( insert, flags, args ));
223             insert = -1;
224             format++;
225         }
226         else
227             while (isdigitW(*format)) *p++ = *format++;
228     }
229 
230     /* replicate MS bug: drop an argument when using va_list with width/precision */
231     if (insert == -1 && args->list) args->last--;
232     arg = get_arg( insert, flags, args );
233 
234     /* check for ascii string format */
235     if ((format[0] == 'h' && format[1] == 's') ||
236         (format[0] == 'h' && format[1] == 'S') ||
237         (unicode_caller && format[0] == 'S') ||
238         (!unicode_caller && format[0] == 's'))
239     {
240         DWORD len = MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, NULL, 0 );
241         wstring = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
242         MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, wstring, len );
243         arg = (ULONG_PTR)wstring;
244         *p++ = 's';
245     }
246     /* check for ascii character format */
247     else if ((format[0] == 'h' && format[1] == 'c') ||
248              (format[0] == 'h' && format[1] == 'C') ||
249              (unicode_caller && format[0] == 'C') ||
250              (!unicode_caller && format[0] == 'c'))
251     {
252         char ch = arg;
253         wstring = HeapAlloc( GetProcessHeap(), 0, 2 * sizeof(WCHAR) );
254         MultiByteToWideChar( CP_ACP, 0, &ch, 1, wstring, 1 );
255         wstring[1] = 0;
256         arg = (ULONG_PTR)wstring;
257         *p++ = 's';
258     }
259     /* check for wide string format */
260     else if ((format[0] == 'l' && format[1] == 's') ||
261              (format[0] == 'l' && format[1] == 'S') ||
262              (format[0] == 'w' && format[1] == 's') ||
263              (!unicode_caller && format[0] == 'S'))
264     {
265         *p++ = 's';
266     }
267     /* check for wide character format */
268     else if ((format[0] == 'l' && format[1] == 'c') ||
269              (format[0] == 'l' && format[1] == 'C') ||
270              (format[0] == 'w' && format[1] == 'c') ||
271              (!unicode_caller && format[0] == 'C'))
272     {
273         *p++ = 'c';
274     }
275     /* FIXME: handle I64 etc. */
276     else while (*format && *format != '!') *p++ = *format++;
277 
278     *p = 0;
279     size = 256;
280     for (;;)
281     {
282         WCHAR *ret = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) );
283         int needed = snprintfW( ret, size, fmt, arg );
284         if (needed == -1 || needed >= size)
285         {
286             HeapFree( GetProcessHeap(), 0, ret );
287             size = max( needed + 1, size * 2 );
288         }
289         else
290         {
291             *result = ret;
292             break;
293         }
294     }
295 
296     while (*format && *format != '!') format++;
297     if (*format == '!') format++;
298 
299     HeapFree( GetProcessHeap(), 0, wstring );
300     return format;
301 }
302 
303 struct _format_message_data
304 {
305     LPWSTR formatted;
306     DWORD size;
307     LPWSTR t;
308     LPWSTR space;
309     BOOL inspace;
310     DWORD width, w;
311 };
312 
format_add_char(struct _format_message_data * fmd,WCHAR c)313 static void format_add_char(struct _format_message_data *fmd, WCHAR c)
314 {
315     *fmd->t++ = c;
316     if (fmd->width && fmd->width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
317     {
318         switch (c) {
319         case '\r':
320         case '\n':
321             fmd->space = NULL;
322             fmd->inspace = FALSE;
323             fmd->w = 0;
324             break;
325         case ' ':
326             if (!fmd->inspace)
327                 fmd->space = fmd->t - 1;
328             fmd->inspace = TRUE;
329             fmd->w++;
330             break;
331         default:
332             fmd->inspace = FALSE;
333             fmd->w++;
334         }
335         if (fmd->w == fmd->width) {
336             LPWSTR notspace;
337             if (fmd->space) {
338                 notspace = fmd->space;
339                 while (notspace != fmd->t && *notspace == ' ')
340                     notspace++;
341             } else
342                 notspace = fmd->space = fmd->t;
343             fmd->w = fmd->t - notspace;
344             memmove(fmd->space+2, notspace, fmd->w * sizeof(*fmd->t));
345             *fmd->space++ = '\r';
346             *fmd->space++ = '\n';
347             fmd->t = fmd->space + fmd->w;
348             fmd->space = NULL;
349             fmd->inspace = FALSE;
350         }
351     }
352     if ((DWORD)(fmd->t - fmd->formatted) == fmd->size) {
353         DWORD_PTR ispace = fmd->space - fmd->formatted;
354         /* Allocate two extra characters so we can insert a '\r\n' in
355          * the middle of a word.
356          */
357         fmd->formatted = HeapReAlloc(GetProcessHeap(), 0, fmd->formatted, (fmd->size * 2 + 2) * sizeof(WCHAR));
358         fmd->t = fmd->formatted + fmd->size;
359         if (fmd->space)
360             fmd->space = fmd->formatted + ispace;
361         fmd->size *= 2;
362     }
363 }
364 
365 /**********************************************************************
366  *	format_message    (internal)
367  */
format_message(BOOL unicode_caller,DWORD dwFlags,LPCWSTR fmtstr,struct format_args * format_args)368 static LPWSTR format_message( BOOL unicode_caller, DWORD dwFlags, LPCWSTR fmtstr,
369                               struct format_args *format_args )
370 {
371     struct _format_message_data fmd;
372     LPCWSTR f;
373     BOOL eos = FALSE;
374 
375     fmd.size = 100;
376     fmd.formatted = fmd.t = HeapAlloc( GetProcessHeap(), 0, (fmd.size + 2) * sizeof(WCHAR) );
377 
378     fmd.width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
379     fmd.w = 0;
380     fmd.inspace = FALSE;
381     fmd.space = NULL;
382     f = fmtstr;
383     while (*f && !eos) {
384         if (*f=='%') {
385             int insertnr;
386             WCHAR *str,*x;
387 
388             f++;
389             switch (*f) {
390             case '1':case '2':case '3':case '4':case '5':
391             case '6':case '7':case '8':case '9':
392                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
393                     goto ignore_inserts;
394                 else if (((dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->args) ||
395                         (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->list))
396                 {
397                     SetLastError(ERROR_INVALID_PARAMETER);
398                     HeapFree(GetProcessHeap(), 0, fmd.formatted);
399                     return NULL;
400                 }
401                 insertnr = *f-'0';
402                 switch (f[1]) {
403                 case '0':case '1':case '2':case '3':
404                 case '4':case '5':case '6':case '7':
405                 case '8':case '9':
406                     f++;
407                     insertnr = insertnr*10 + *f-'0';
408                     f++;
409                     break;
410                 default:
411                     f++;
412                     break;
413                 }
414                 f = format_insert( unicode_caller, insertnr, f, dwFlags, format_args, &str );
415                 for (x = str; *x; x++) format_add_char(&fmd, *x);
416                 HeapFree( GetProcessHeap(), 0, str );
417                 break;
418             case 'n':
419                 format_add_char(&fmd, '\r');
420                 format_add_char(&fmd, '\n');
421                 f++;
422                 break;
423             case 'r':
424                 format_add_char(&fmd, '\r');
425                 f++;
426                 break;
427             case 't':
428                 format_add_char(&fmd, '\t');
429                 f++;
430                 break;
431             case '0':
432                 eos = TRUE;
433                 f++;
434                 break;
435             case '\0':
436                 SetLastError(ERROR_INVALID_PARAMETER);
437                 HeapFree(GetProcessHeap(), 0, fmd.formatted);
438                 return NULL;
439             ignore_inserts:
440             default:
441                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
442                     format_add_char(&fmd, '%');
443                 format_add_char(&fmd, *f++);
444                 break;
445             }
446         } else {
447             WCHAR ch = *f;
448             f++;
449             if (ch == '\r') {
450                 if (*f == '\n')
451                     f++;
452                 if(fmd.width)
453                     format_add_char(&fmd, ' ');
454                 else
455                 {
456                     format_add_char(&fmd, '\r');
457                     format_add_char(&fmd, '\n');
458                 }
459             } else {
460                 if (ch == '\n')
461                 {
462                     if(fmd.width)
463                         format_add_char(&fmd, ' ');
464                     else
465                     {
466                         format_add_char(&fmd, '\r');
467                         format_add_char(&fmd, '\n');
468                     }
469                 }
470                 else
471                     format_add_char(&fmd, ch);
472             }
473         }
474     }
475     *fmd.t = '\0';
476 
477     return fmd.formatted;
478 }
479 
480 /***********************************************************************
481  *           FormatMessageA   (KERNEL32.@)
482  */
FormatMessageA(DWORD dwFlags,LPCVOID lpSource,DWORD dwMessageId,DWORD dwLanguageId,LPSTR lpBuffer,DWORD nSize,__ms_va_list * args)483 DWORD WINAPI FormatMessageA(
484 	DWORD	dwFlags,
485 	LPCVOID	lpSource,
486 	DWORD	dwMessageId,
487 	DWORD	dwLanguageId,
488 	LPSTR	lpBuffer,
489 	DWORD	nSize,
490 	__ms_va_list* args )
491 {
492     struct format_args format_args;
493     DWORD ret = 0;
494     LPWSTR	target;
495     DWORD	destlength;
496     LPWSTR	from;
497 
498     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
499           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
500 
501     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
502     {
503         if (!lpBuffer)
504         {
505             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
506             return 0;
507         }
508         else
509             *(LPSTR *)lpBuffer = NULL;
510     }
511 
512     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
513     {
514         format_args.args = (ULONG_PTR *)args;
515         format_args.list = NULL;
516         format_args.last = 0;
517     }
518     else
519     {
520         format_args.args = NULL;
521         format_args.list = args;
522         format_args.last = 0;
523     }
524 
525     from = NULL;
526     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
527     {
528         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
529         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
530         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
531     }
532     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
533     {
534         from = search_message( dwFlags, (HMODULE)lpSource, dwMessageId, dwLanguageId );
535         if (!from) return 0;
536     }
537     else
538     {
539         SetLastError(ERROR_INVALID_PARAMETER);
540         return 0;
541     }
542 
543     target = format_message( FALSE, dwFlags, from, &format_args );
544     if (!target)
545         goto failure;
546 
547     TRACE("-- %s\n", debugstr_w(target));
548 
549     /* Only try writing to an output buffer if there are processed characters
550      * in the temporary output buffer. */
551     if (*target)
552     {
553         destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
554         if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
555         {
556             LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
557             WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
558             *((LPSTR*)lpBuffer) = buf;
559         }
560         else
561         {
562             if (nSize < destlength)
563             {
564                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
565                 goto failure;
566             }
567             WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
568         }
569         ret = destlength - 1; /* null terminator */
570     }
571 
572 failure:
573     HeapFree(GetProcessHeap(),0,target);
574     HeapFree(GetProcessHeap(),0,from);
575     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
576     TRACE("-- returning %u\n", ret);
577     return ret;
578 }
579 
580 /***********************************************************************
581  *           FormatMessageW   (KERNEL32.@)
582  */
FormatMessageW(DWORD dwFlags,LPCVOID lpSource,DWORD dwMessageId,DWORD dwLanguageId,LPWSTR lpBuffer,DWORD nSize,__ms_va_list * args)583 DWORD WINAPI FormatMessageW(
584 	DWORD	dwFlags,
585 	LPCVOID	lpSource,
586 	DWORD	dwMessageId,
587 	DWORD	dwLanguageId,
588 	LPWSTR	lpBuffer,
589 	DWORD	nSize,
590 	__ms_va_list* args )
591 {
592     struct format_args format_args;
593     DWORD ret = 0;
594     LPWSTR target;
595     DWORD talloced;
596     LPWSTR from;
597 
598     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
599           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
600 
601     if (!lpBuffer)
602     {
603         SetLastError(ERROR_INVALID_PARAMETER);
604         return 0;
605     }
606 
607     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
608         *(LPWSTR *)lpBuffer = NULL;
609 
610     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
611     {
612         format_args.args = (ULONG_PTR *)args;
613         format_args.list = NULL;
614         format_args.last = 0;
615     }
616     else
617     {
618         format_args.args = NULL;
619         format_args.list = args;
620         format_args.last = 0;
621     }
622 
623     from = NULL;
624     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
625         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
626             sizeof(WCHAR) );
627         strcpyW( from, lpSource );
628     }
629     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
630     {
631         from = search_message( dwFlags, (HMODULE)lpSource, dwMessageId, dwLanguageId );
632         if (!from) return 0;
633     }
634     else
635     {
636         SetLastError(ERROR_INVALID_PARAMETER);
637         return 0;
638     }
639 
640     target = format_message( TRUE, dwFlags, from, &format_args );
641     if (!target)
642         goto failure;
643 
644     talloced = strlenW(target)+1;
645     TRACE("-- %s\n",debugstr_w(target));
646 
647     /* Only allocate a buffer if there are processed characters in the
648      * temporary output buffer. If a caller supplies the buffer, then
649      * a null terminator will be written to it. */
650     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
651     {
652         if (*target)
653         {
654             /* nSize is the MINIMUM size */
655             *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
656             strcpyW(*(LPWSTR*)lpBuffer, target);
657         }
658     }
659     else
660     {
661         if (nSize < talloced)
662         {
663             SetLastError(ERROR_INSUFFICIENT_BUFFER);
664             goto failure;
665         }
666         strcpyW(lpBuffer, target);
667     }
668 
669     ret = talloced - 1; /* null terminator */
670 failure:
671     HeapFree(GetProcessHeap(),0,target);
672     HeapFree(GetProcessHeap(),0,from);
673     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
674     TRACE("-- returning %u\n", ret);
675     return ret;
676 }
677