kernel32: Convert FormatMessageA/W to use an MS ABI vararg list for x86_64.
[wine] / dlls / kernel32 / format_msg.c
1 /*
2  * FormatMessage implementation
3  *
4  * Copyright 1996 Marcus Meissner
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "ntstatus.h"
28 #define WIN32_NO_STATUS
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winerror.h"
32 #include "winternl.h"
33 #include "winuser.h"
34 #include "winnls.h"
35 #include "wine/unicode.h"
36 #include "kernel_private.h"
37 #include "wine/debug.h"
38
39 WINE_DEFAULT_DEBUG_CHANNEL(resource);
40
41
42 /* Messages used by FormatMessage
43  *
44  * They can be specified either directly or using a message ID and
45  * loading them from the resource.
46  *
47  * The resourcedata has following format:
48  * start:
49  * 0: DWORD nrofentries
50  * nrofentries * subentry:
51  *      0: DWORD firstentry
52  *      4: DWORD lastentry
53  *      8: DWORD offset from start to the stringentries
54  *
55  * (lastentry-firstentry) * stringentry:
56  * 0: WORD len (0 marks end)    [ includes the 4 byte header length ]
57  * 2: WORD flags
58  * 4: CHAR[len-4]
59  *      (stringentry i of a subentry refers to the ID 'firstentry+i')
60  *
61  * Yes, ANSI strings in win32 resources. Go figure.
62  */
63
64 static const WCHAR PCNTFMTWSTR[] = { '%','%','%','s',0 };
65 static const WCHAR FMTWSTR[] = { '%','s',0 };
66
67 /**********************************************************************
68  *      load_messageW           (internal)
69  */
70 static LPWSTR load_messageW( HMODULE module, UINT id, WORD lang )
71 {
72     const MESSAGE_RESOURCE_ENTRY *mre;
73     WCHAR *buffer;
74
75     TRACE("module = %p, id = %08x\n", module, id );
76
77     if (!module) module = GetModuleHandleW( NULL );
78     if (RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre ) != STATUS_SUCCESS) return NULL;
79
80     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
81     {
82         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
83         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
84         memcpy( buffer, mre->Text, len );
85     }
86     else
87     {
88         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
89         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
90         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
91     }
92     TRACE("returning %s\n", wine_dbgstr_w(buffer));
93     return buffer;
94 }
95
96
97 /**********************************************************************
98  *      load_messageA           (internal)
99  */
100 static LPSTR load_messageA( HMODULE module, UINT id, WORD lang )
101 {
102     const MESSAGE_RESOURCE_ENTRY *mre;
103     char *buffer;
104
105     TRACE("module = %p, id = %08x\n", module, id );
106
107     if (!module) module = GetModuleHandleW( NULL );
108     if (RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre ) != STATUS_SUCCESS) return NULL;
109
110     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
111     {
112         int len = WideCharToMultiByte( CP_ACP, 0, (const WCHAR *)mre->Text, -1, NULL, 0, NULL, NULL );
113         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
114         WideCharToMultiByte( CP_ACP, 0, (const WCHAR *)mre->Text, -1, buffer, len, NULL, NULL );
115     }
116     else
117     {
118         int len = strlen((const char*)mre->Text) + 1;
119         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
120         memcpy( buffer, mre->Text, len );
121     }
122     TRACE("returning %s\n", wine_dbgstr_a(buffer));
123     return buffer;
124 }
125
126
127 /***********************************************************************
128  *           FormatMessageA   (KERNEL32.@)
129  * FIXME: missing wrap,
130  */
131 DWORD WINAPI FormatMessageA(
132         DWORD   dwFlags,
133         LPCVOID lpSource,
134         DWORD   dwMessageId,
135         DWORD   dwLanguageId,
136         LPSTR   lpBuffer,
137         DWORD   nSize,
138         __ms_va_list* _args )
139 {
140     LPDWORD args=(LPDWORD)_args;
141     DWORD ret = 0;
142 #if defined(__i386__) || defined(__sparc__)
143 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
144     LPSTR       target,t;
145     DWORD       talloced;
146     LPSTR       from,f;
147     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
148     BOOL    eos = FALSE;
149     CHAR        ch;
150
151     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
152           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
153     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
154         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
155            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
156
157     if (!lpBuffer)
158     {
159         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
160         return 0;
161     }
162
163     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
164         FIXME("line wrapping (%u) not supported.\n", width);
165     from = NULL;
166     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
167     {
168         from = HeapAlloc( GetProcessHeap(), 0, strlen((LPCSTR)lpSource)+1 );
169         strcpy( from, (LPCSTR)lpSource );
170     }
171     else {
172         from = NULL;
173         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
174             from = load_messageA( (HMODULE)lpSource, dwMessageId, dwLanguageId );
175         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
176             from = load_messageA( kernel32_handle, dwMessageId, dwLanguageId );
177
178         if (!from)
179         {
180             SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
181             return 0;
182         }
183     }
184     target      = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100);
185     t   = target;
186     talloced= 100;
187
188 #define ADD_TO_T(c) do { \
189         *t++=c;\
190         if ((DWORD)(t-target) == talloced) {\
191             target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2);\
192             t = target+talloced;\
193             talloced*=2;\
194       }\
195 } while (0)
196
197     if (from) {
198         f=from;
199         if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
200             while (*f && !eos)
201                 ADD_TO_T(*f++);
202         }
203         else {
204             while (*f && !eos) {
205                 if (*f=='%') {
206                     int insertnr;
207                     char *fmtstr,*x,*lastf;
208                     DWORD *argliststart;
209
210                     fmtstr = NULL;
211                     lastf = f;
212                     f++;
213                     if (!*f) {
214                         ADD_TO_T('%');
215                         continue;
216                     }
217                     switch (*f) {
218                     case '1':case '2':case '3':case '4':case '5':
219                     case '6':case '7':case '8':case '9':
220                         insertnr=*f-'0';
221                         switch (f[1]) {
222                         case '0':case '1':case '2':case '3':
223                         case '4':case '5':case '6':case '7':
224                         case '8':case '9':
225                             f++;
226                             insertnr=insertnr*10+*f-'0';
227                             f++;
228                             break;
229                         default:
230                             f++;
231                             break;
232                         }
233                         if (*f=='!') {
234                             f++;
235                             if (NULL!=(x=strchr(f,'!'))) {
236                                 *x='\0';
237                                 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
238                                 sprintf(fmtstr,"%%%s",f);
239                                 f=x+1;
240                             } else {
241                                 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
242                                 sprintf(fmtstr,"%%%s",f);
243                                 f+=strlen(f); /*at \0*/
244                             }
245                         } else {
246                             if(!args) break;
247                             fmtstr = HeapAlloc(GetProcessHeap(),0,3);
248                             strcpy( fmtstr, "%s" );
249                         }
250                         if (args) {
251                             int sz;
252                             LPSTR b;
253
254                             if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
255                                 argliststart=args+insertnr-1;
256                             else
257                                 argliststart=(*(DWORD**)args)+insertnr-1;
258
259                                 /* FIXME: precision and width components are not handled correctly */
260                             if ( (strcmp(fmtstr, "%ls") == 0) || (strcmp(fmtstr,"%S") == 0) ) {
261                                 sz = WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, NULL, 0, NULL, NULL);
262                                 b = HeapAlloc(GetProcessHeap(), 0, sz);
263                                 WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, b, sz, NULL, NULL);
264                             } else {
265                                 b = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sz = 1000);
266                                 /* CMF - This makes a BIG assumption about va_list */
267                                 TRACE("A BIG assumption\n");
268                                 vsnprintf(b, sz, fmtstr, (va_list) argliststart);
269                             }
270                             for (x=b; *x; x++) ADD_TO_T(*x);
271
272                             HeapFree(GetProcessHeap(),0,b);
273                         } else {
274                                 /* NULL args - copy formatstr
275                                  * (probably wrong)
276                                  */
277                             while ((lastf<f)&&(*lastf)) {
278                                 ADD_TO_T(*lastf++);
279                             }
280                         }
281                         HeapFree(GetProcessHeap(),0,fmtstr);
282                         break;
283                     case 'n':
284                         ADD_TO_T('\r');
285                         ADD_TO_T('\n');
286                         f++;
287                         break;
288                     case '0':
289                         eos = TRUE;
290                         f++;
291                         break;
292                     default:
293                         ADD_TO_T(*f++);
294                         break;
295                     }
296                 } else {
297                     ch = *f;
298                     f++;
299                     if (ch == '\r') {
300                         if (*f == '\n')
301                             f++;
302                         if(width)
303                             ADD_TO_T(' ');
304                         else
305                         {
306                             ADD_TO_T('\r');
307                             ADD_TO_T('\n');
308                         }
309                     } else {
310                         if (ch == '\n')
311                         {
312                             if(width)
313                                 ADD_TO_T(' ');
314                             else
315                             {
316                                 ADD_TO_T('\r');
317                                 ADD_TO_T('\n');
318                             }
319                         }
320                         else
321                             ADD_TO_T(ch);
322                     }
323                 }
324             }
325         }
326         *t='\0';
327     }
328     talloced = strlen(target)+1;
329     if (nSize && talloced<nSize) {
330         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize);
331     }
332     TRACE("-- %s\n",debugstr_a(target));
333     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
334         *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT,max(nSize, talloced));
335         memcpy(*(LPSTR*)lpBuffer,target,talloced);
336     } else {
337         lstrcpynA(lpBuffer,target,nSize);
338     }
339     HeapFree(GetProcessHeap(),0,target);
340     HeapFree(GetProcessHeap(),0,from);
341     ret = (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ? strlen(*(LPSTR*)lpBuffer) : strlen(lpBuffer);
342 #endif /* __i386__ */
343     TRACE("-- returning %d\n", ret);
344     return ret;
345 }
346 #undef ADD_TO_T
347
348
349 /***********************************************************************
350  *           FormatMessageW   (KERNEL32.@)
351  */
352 DWORD WINAPI FormatMessageW(
353         DWORD   dwFlags,
354         LPCVOID lpSource,
355         DWORD   dwMessageId,
356         DWORD   dwLanguageId,
357         LPWSTR  lpBuffer,
358         DWORD   nSize,
359         __ms_va_list* _args )
360 {
361     LPDWORD args=(LPDWORD)_args;
362 #if defined(__i386__) || defined(__sparc__)
363 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
364     LPWSTR target,t;
365     DWORD talloced;
366     LPWSTR from,f;
367     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
368     BOOL eos = FALSE;
369     WCHAR ch;
370
371     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
372           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
373     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
374         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
375            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
376
377     if (!lpBuffer)
378     {
379         SetLastError(ERROR_INVALID_PARAMETER);
380         return 0;
381     }
382
383     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
384         FIXME("line wrapping not supported.\n");
385     from = NULL;
386     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
387         from = HeapAlloc( GetProcessHeap(), 0, (strlenW((LPCWSTR)lpSource) + 1) *
388             sizeof(WCHAR) );
389         strcpyW( from, (LPCWSTR)lpSource );
390     }
391     else {
392         from = NULL;
393         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
394             from = load_messageW( (HMODULE)lpSource, dwMessageId, dwLanguageId );
395         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
396             from = load_messageW( kernel32_handle, dwMessageId, dwLanguageId );
397
398         if (!from)
399         {
400             SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
401             return 0;
402         }
403     }
404     target = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR) );
405     t = target;
406     talloced= 100;
407
408 #define ADD_TO_T(c)  do {\
409     *t++=c;\
410     if ((DWORD)(t-target) == talloced) {\
411         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
412         t = target+talloced;\
413         talloced*=2;\
414     } \
415 } while (0)
416
417     if (from) {
418         f=from;
419         if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
420             while (*f && !eos)
421                 ADD_TO_T(*f++);
422         }
423         else {
424             while (*f && !eos) {
425                 if (*f=='%') {
426                     int insertnr;
427                     WCHAR *fmtstr,*sprintfbuf,*x;
428                     DWORD *argliststart;
429
430                     fmtstr = NULL;
431                     f++;
432                     if (!*f) {
433                         ADD_TO_T('%');
434                         continue;
435                     }
436
437                     switch (*f) {
438                     case '1':case '2':case '3':case '4':case '5':
439                     case '6':case '7':case '8':case '9':
440                         insertnr=*f-'0';
441                         switch (f[1]) {
442                         case '0':case '1':case '2':case '3':
443                         case '4':case '5':case '6':case '7':
444                         case '8':case '9':
445                             f++;
446                             insertnr=insertnr*10+*f-'0';
447                             f++;
448                             break;
449                         default:
450                             f++;
451                             break;
452                         }
453                         if (*f=='!') {
454                             f++;
455                             if (NULL!=(x=strchrW(f,'!'))) {
456                                 *x='\0';
457                                 fmtstr=HeapAlloc( GetProcessHeap(), 0,(strlenW(f)+2)*sizeof(WCHAR));
458                                 sprintfW(fmtstr,PCNTFMTWSTR,f);
459                                 f=x+1;
460                             } else {
461                                 fmtstr=HeapAlloc(GetProcessHeap(),0,(strlenW(f)+2)*sizeof(WCHAR));
462                                 sprintfW(fmtstr,PCNTFMTWSTR,f);
463                                 f+=strlenW(f); /*at \0*/
464                             }
465                         } else {
466                             if(!args) break;
467                             fmtstr = HeapAlloc( GetProcessHeap(),0,3*sizeof(WCHAR));
468                             strcpyW( fmtstr, FMTWSTR );
469                         }
470                         if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
471                             argliststart=args+insertnr-1;
472                         else
473                             argliststart=(*(DWORD**)args)+insertnr-1;
474
475                         if (fmtstr[strlenW(fmtstr)-1]=='s' && argliststart[0]) {
476                             DWORD xarr[3];
477
478                             xarr[0]=*(argliststart+0);
479                             /* possible invalid pointers */
480                             xarr[1]=*(argliststart+1);
481                             xarr[2]=*(argliststart+2);
482                             sprintfbuf=HeapAlloc(GetProcessHeap(),0,(strlenW((LPWSTR)argliststart[0])*2+1)*sizeof(WCHAR));
483
484                             /* CMF - This makes a BIG assumption about va_list */
485                             vsprintfW(sprintfbuf, fmtstr, (va_list) xarr);
486                         } else {
487                             sprintfbuf=HeapAlloc(GetProcessHeap(),0,100);
488
489                             /* CMF - This makes a BIG assumption about va_list */
490                             vsprintfW(sprintfbuf, fmtstr, (va_list) argliststart);
491                         }
492                         x=sprintfbuf;
493                         while (*x) {
494                             ADD_TO_T(*x++);
495                         }
496                         HeapFree(GetProcessHeap(),0,sprintfbuf);
497                         HeapFree(GetProcessHeap(),0,fmtstr);
498                         break;
499                     case 'n':
500                         ADD_TO_T('\r');
501                         ADD_TO_T('\n');
502                         f++;
503                         break;
504                     case '0':
505                         eos = TRUE;
506                         f++;
507                         break;
508                     default:
509                         ADD_TO_T(*f++);
510                         break;
511                     }
512                 } else {
513                     ch = *f;
514                     f++;
515                     if (ch == '\r') {
516                         if (*f == '\n')
517                             f++;
518                         if(width)
519                             ADD_TO_T(' ');
520                         else
521                         {
522                             ADD_TO_T('\r');
523                             ADD_TO_T('\n');
524                         }
525                     } else {
526                         if (ch == '\n')
527                         {
528                             if(width)
529                                 ADD_TO_T(' ');
530                             else
531                             {
532                                 ADD_TO_T('\r');
533                                 ADD_TO_T('\n');
534                             }
535                         }
536                         else
537                             ADD_TO_T(ch);
538                     }
539                 }
540             }
541         }
542         *t='\0';
543     }
544     talloced = strlenW(target)+1;
545     if (nSize && talloced<nSize)
546         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize*sizeof(WCHAR));
547     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
548         /* nSize is the MINIMUM size */
549         DWORD len = strlenW(target) + 1;
550         *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT,len*sizeof(WCHAR));
551         strcpyW(*(LPWSTR*)lpBuffer, target);
552     }
553     else lstrcpynW(lpBuffer, target, nSize);
554
555     HeapFree(GetProcessHeap(),0,target);
556     HeapFree(GetProcessHeap(),0,from);
557     TRACE("ret=%s\n", wine_dbgstr_w((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
558         *(LPWSTR*)lpBuffer : lpBuffer));
559     return (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
560         strlenW(*(LPWSTR*)lpBuffer):
561             strlenW(lpBuffer);
562 #else
563     return 0;
564 #endif /* __i386__ */
565 }
566 #undef ADD_TO_T