Implemented better format message support based on WINE.
[reactos.git] / reactos / lib / kernel32 / misc / errormsg.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 /* $Id: errormsg.c,v 1.9 2003/11/05 20:32:07 sedwards Exp $
22 *
23 * COPYRIGHT: See COPYING in the top level directory
24 * PROJECT: ReactOS Kernel32
25 * PURPOSE: Format Message
26 * FILE: lib/user32/controls/static.c
27 * PROGRAMER: Steven Edwards lib/kernel32/misc/errormsg.c
28 * REVISION HISTORY: 2003/11/05 Created
29 * NOTES: Adapted from Wine
30 */
31
32 #include <ddk/ntddk.h>
33 #include <kernel32/kernel32.h>
34 #include <kernel32/error.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <wine/unicode.h>
38
39 /* Wine porting stuff */
40
41 #define TRACE DPRINT
42 #define FIXME DPRINT
43 typedef struct tagMESSAGE_RESOURCE_ENTRY {
44 WORD Length;
45 WORD Flags;
46 BYTE Text[1];
47 } MESSAGE_RESOURCE_ENTRY,*PMESSAGE_RESOURCE_ENTRY;
48 #define MESSAGE_RESOURCE_UNICODE 0x0001
49
50 typedef struct tagMESSAGE_RESOURCE_BLOCK {
51 DWORD LowId;
52 DWORD HighId;
53 DWORD OffsetToEntries;
54 } MESSAGE_RESOURCE_BLOCK,*PMESSAGE_RESOURCE_BLOCK;
55
56 typedef struct tagMESSAGE_RESOURCE_DATA {
57 DWORD NumberOfBlocks;
58 MESSAGE_RESOURCE_BLOCK Blocks[ 1 ];
59 } MESSAGE_RESOURCE_DATA,*PMESSAGE_RESOURCE_DATA;
60
61 /* These are needed so that we can call the functions from inside kernel itself */
62
63 LPVOID WINAPI HeapAlloc( HANDLE heap, DWORD flags, SIZE_T size )
64 {
65 return RtlAllocateHeap( heap, flags, size );
66 }
67
68 BOOL WINAPI HeapFree( HANDLE heap, DWORD flags, LPVOID ptr )
69 {
70 return RtlFreeHeap( heap, flags, ptr );
71 }
72
73 LPVOID WINAPI HeapReAlloc( HANDLE heap, DWORD flags, LPVOID ptr, SIZE_T size )
74 {
75 return RtlReAllocateHeap( heap, flags, ptr, size );
76 }
77
78 /* strdup macros */
79 /* DO NOT USE IT!! it will go away soon */
80
81 inline static LPSTR HEAP_strdupWtoA( HANDLE heap, DWORD flags, LPCWSTR str )
82 {
83 LPSTR ret;
84 INT len;
85
86 if (!str) return NULL;
87 len = WideCharToMultiByte( CP_ACP, 0, str, -1, NULL, 0, NULL, NULL );
88 ret = HeapAlloc( heap, flags, len );
89 if(ret) WideCharToMultiByte( CP_ACP, 0, str, -1, ret, len, NULL, NULL );
90 return ret;
91 }
92
93 /* End of WINE porting Code */
94
95 /* Messages...used by FormatMessage32* (KERNEL32.something)
96 *
97 * They can be specified either directly or using a message ID and
98 * loading them from the resource.
99 *
100 * The resourcedata has following format:
101 * start:
102 * 0: DWORD nrofentries
103 * nrofentries * subentry:
104 * 0: DWORD firstentry
105 * 4: DWORD lastentry
106 * 8: DWORD offset from start to the stringentries
107 *
108 * (lastentry-firstentry) * stringentry:
109 * 0: WORD len (0 marks end) [ includes the 4 byte header length ]
110 * 2: WORD flags
111 * 4: CHAR[len-4]
112 * (stringentry i of a subentry refers to the ID 'firstentry+i')
113 *
114 * Yes, ANSI strings in win32 resources. Go figure.
115 */
116
117 /**********************************************************************
118 * load_messageA (internal)
119 */
120 static INT load_messageA( HMODULE instance, UINT id, WORD lang,
121 LPSTR buffer, INT buflen )
122 {
123 const MESSAGE_RESOURCE_ENTRY *mre;
124 int i,slen;
125
126 TRACE("instance = %08lx, id = %08lx, buffer = %p, length = %ld\n", (DWORD)instance, (DWORD)id, buffer, (DWORD)buflen);
127
128 //if (RtlFindMessage( instance, RT_MESSAGETABLE, lang, id, &mre ) != STATUS_SUCCESS) return 0;
129
130 slen=mre->Length;
131 TRACE(" - strlen=%d\n",slen);
132 i = min(buflen - 1, slen);
133 if (buffer == NULL)
134 return slen;
135 if (i>0) {
136 if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
137 WideCharToMultiByte( CP_ACP, 0, (LPWSTR)mre->Text, -1, buffer, i, NULL, NULL );
138 else
139 lstrcpynA(buffer, (LPSTR)mre->Text, i);
140 buffer[i]=0;
141 } else {
142 if (buflen>1) {
143 buffer[0]=0;
144 return 0;
145 }
146 }
147 if (buffer)
148 TRACE("'%s' copied !\n", buffer);
149 return i;
150 }
151
152 #if 0 /* FIXME */
153 /**********************************************************************
154 * load_messageW (internal)
155 */
156 static INT load_messageW( HMODULE instance, UINT id, WORD lang,
157 LPWSTR buffer, INT buflen )
158 {
159 INT retval;
160 LPSTR buffer2 = NULL;
161 if (buffer && buflen)
162 buffer2 = HeapAlloc( GetProcessHeap(), 0, buflen );
163 retval = load_messageA(instance,id,lang,buffer2,buflen);
164 if (buffer)
165 {
166 if (retval) {
167 lstrcpynAtoW( buffer, buffer2, buflen );
168 retval = strlenW( buffer );
169 }
170 HeapFree( GetProcessHeap(), 0, buffer2 );
171 }
172 return retval;
173 }
174 #endif
175
176
177 /***********************************************************************
178 * FormatMessageA (KERNEL32.@)
179 * FIXME: missing wrap,
180 */
181 DWORD WINAPI FormatMessageA(
182 DWORD dwFlags,
183 LPCVOID lpSource,
184 DWORD dwMessageId,
185 DWORD dwLanguageId,
186 LPSTR lpBuffer,
187 DWORD nSize,
188 va_list* _args )
189 {
190 LPDWORD args=(LPDWORD)_args;
191 #if defined(__i386__) || defined(__sparc__)
192 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
193 LPSTR target,t;
194 DWORD talloced;
195 LPSTR from,f;
196 DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
197 BOOL eos = FALSE;
198 INT bufsize;
199 HMODULE hmodule = (HMODULE)lpSource;
200 CHAR ch;
201
202 TRACE("(0x%lx,%p,%ld,0x%lx,%p,%ld,%p)\n",
203 dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
204 if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
205 &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
206 || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
207
208 if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
209 FIXME("line wrapping (%lu) not supported.\n", width);
210 from = NULL;
211 if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
212 {
213 from = HeapAlloc( GetProcessHeap(), 0, strlen((LPSTR)lpSource)+1 );
214 strcpy( from, (LPSTR)lpSource );
215 }
216 else {
217 bufsize = 0;
218
219 if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
220 {
221 if (!hmodule)
222 hmodule = GetModuleHandleW(NULL);
223 bufsize=load_messageA(hmodule,dwMessageId,dwLanguageId,NULL,100);
224 }
225 if ((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM) && (!bufsize))
226 {
227 hmodule = GetModuleHandleA("kernel32");
228 bufsize=load_messageA(hmodule,dwMessageId,dwLanguageId,NULL,100);
229 }
230
231 if (!bufsize) {
232 SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
233 return 0;
234 }
235
236 from = HeapAlloc( GetProcessHeap(), 0, bufsize + 1 );
237 load_messageA(hmodule,dwMessageId,dwLanguageId,from,bufsize+1);
238 }
239 target = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100);
240 t = target;
241 talloced= 100;
242
243 #define ADD_TO_T(c) do { \
244 *t++=c;\
245 if (t-target == talloced) {\
246 target = (char*)HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2);\
247 t = target+talloced;\
248 talloced*=2;\
249 }\
250 } while (0)
251
252 if (from) {
253 f=from;
254 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
255 while (*f && !eos)
256 ADD_TO_T(*f++);
257 }
258 else {
259 while (*f && !eos) {
260 if (*f=='%') {
261 int insertnr;
262 char *fmtstr,*x,*lastf;
263 DWORD *argliststart;
264
265 fmtstr = NULL;
266 lastf = f;
267 f++;
268 if (!*f) {
269 ADD_TO_T('%');
270 continue;
271 }
272 switch (*f) {
273 case '1':case '2':case '3':case '4':case '5':
274 case '6':case '7':case '8':case '9':
275 insertnr=*f-'0';
276 switch (f[1]) {
277 case '0':case '1':case '2':case '3':
278 case '4':case '5':case '6':case '7':
279 case '8':case '9':
280 f++;
281 insertnr=insertnr*10+*f-'0';
282 f++;
283 break;
284 default:
285 f++;
286 break;
287 }
288 if (*f=='!') {
289 f++;
290 if (NULL!=(x=strchr(f,'!'))) {
291 *x='\0';
292 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
293 sprintf(fmtstr,"%%%s",f);
294 f=x+1;
295 } else {
296 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
297 sprintf(fmtstr,"%%%s",f);
298 f+=strlen(f); /*at \0*/
299 }
300 } else {
301 if(!args) break;
302 fmtstr = HeapAlloc(GetProcessHeap(),0,3);
303 strcpy( fmtstr, "%s" );
304 }
305 if (args) {
306 int sz;
307 LPSTR b;
308
309 if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
310 argliststart=args+insertnr-1;
311 else
312 argliststart=(*(DWORD**)args)+insertnr-1;
313
314 /* FIXME: precision and width components are not handled correctly */
315 if ( (strcmp(fmtstr, "%ls") == 0) || (strcmp(fmtstr,"%S") == 0) ) {
316 sz = WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, NULL, 0, NULL, NULL);
317 b = HeapAlloc(GetProcessHeap(), 0, sz);
318 WideCharToMultiByte( CP_ACP, 0, *(WCHAR**)argliststart, -1, b, sz, NULL, NULL);
319 } else {
320 b = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sz = 1000);
321 /* CMF - This makes a BIG assumption about va_list */
322 TRACE("A BIG assumption\n");
323 _vsnprintf(b, sz, fmtstr, (va_list) argliststart);
324 }
325 for (x=b; *x; x++) ADD_TO_T(*x);
326
327 HeapFree(GetProcessHeap(),0,b);
328 } else {
329 /* NULL args - copy formatstr
330 * (probably wrong)
331 */
332 while ((lastf<f)&&(*lastf)) {
333 ADD_TO_T(*lastf++);
334 }
335 }
336 HeapFree(GetProcessHeap(),0,fmtstr);
337 break;
338 case 'n':
339 ADD_TO_T('\r');
340 ADD_TO_T('\n');
341 f++;
342 break;
343 case '0':
344 eos = TRUE;
345 f++;
346 break;
347 default:
348 ADD_TO_T(*f++);
349 break;
350 }
351 } else {
352 ch = *f;
353 f++;
354 if (ch == '\r') {
355 if (*f == '\n')
356 f++;
357 if(width)
358 ADD_TO_T(' ');
359 else
360 {
361 ADD_TO_T('\r');
362 ADD_TO_T('\n');
363 }
364 } else {
365 if (ch == '\n')
366 {
367 if(width)
368 ADD_TO_T(' ');
369 else
370 {
371 ADD_TO_T('\r');
372 ADD_TO_T('\n');
373 }
374 }
375 else
376 ADD_TO_T(ch);
377 }
378 }
379 }
380 }
381 *t='\0';
382 }
383 talloced = strlen(target)+1;
384 if (nSize && talloced<nSize) {
385 target = (char*)HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize);
386 }
387 //TRACE("-- %s\n",debugstr_a(target));
388 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
389 *((LPVOID*)lpBuffer) = (LPVOID)LocalAlloc(GMEM_ZEROINIT,max(nSize, talloced));
390 memcpy(*(LPSTR*)lpBuffer,target,talloced);
391 } else {
392 lstrcpynA(lpBuffer,target,nSize);
393 }
394 HeapFree(GetProcessHeap(),0,target);
395 if (from) HeapFree(GetProcessHeap(),0,from);
396 TRACE("-- returning %d\n", (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ? strlen(*(LPSTR*)lpBuffer):strlen(lpBuffer));
397 return (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
398 strlen(*(LPSTR*)lpBuffer):
399 strlen(lpBuffer);
400 #else
401 return 0;
402 #endif /* __i386__ */
403 }
404 #undef ADD_TO_T
405
406
407 /***********************************************************************
408 * FormatMessageW (KERNEL32.@)
409 */
410 DWORD WINAPI FormatMessageW(
411 DWORD dwFlags,
412 LPCVOID lpSource,
413 DWORD dwMessageId,
414 DWORD dwLanguageId,
415 LPWSTR lpBuffer,
416 DWORD nSize,
417 va_list* _args )
418 {
419 LPDWORD args=(LPDWORD)_args;
420 #if defined(__i386__) || defined(__sparc__)
421 /* This implementation is completely dependent on the format of the va_list on x86 CPUs */
422 LPSTR target,t;
423 DWORD talloced;
424 LPSTR from,f;
425 DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
426 BOOL eos = FALSE;
427 INT bufsize;
428 HMODULE hmodule = (HMODULE)lpSource;
429 CHAR ch;
430
431 TRACE("(0x%lx,%p,%ld,0x%lx,%p,%ld,%p)\n",
432 dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
433 if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
434 &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
435 || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
436
437 if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
438 FIXME("line wrapping not supported.\n");
439 from = NULL;
440 if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
441 from = HEAP_strdupWtoA(GetProcessHeap(),0,(LPWSTR)lpSource);
442 }
443 else {
444 bufsize = 0;
445
446 if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
447 {
448 if (!hmodule)
449 hmodule = GetModuleHandleW(NULL);
450 bufsize=load_messageA(hmodule,dwMessageId,dwLanguageId,NULL,100);
451 }
452 if ((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM) && (!bufsize))
453 {
454 hmodule = GetModuleHandleA("kernel32");
455 bufsize=load_messageA(hmodule,dwMessageId,dwLanguageId,NULL,100);
456 }
457
458 if (!bufsize) {
459 SetLastError (ERROR_RESOURCE_LANG_NOT_FOUND);
460 return 0;
461 }
462
463 from = HeapAlloc( GetProcessHeap(), 0, bufsize + 1 );
464 load_messageA(hmodule,dwMessageId,dwLanguageId,from,bufsize+1);
465 }
466 target = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 );
467 t = target;
468 talloced= 100;
469
470 #define ADD_TO_T(c) do {\
471 *t++=c;\
472 if (t-target == talloced) {\
473 target = (char*)HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2);\
474 t = target+talloced;\
475 talloced*=2;\
476 } \
477 } while (0)
478
479 if (from) {
480 f=from;
481 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
482 while (*f && !eos)
483 ADD_TO_T(*f++);
484 }
485 else {
486 while (*f && !eos) {
487 if (*f=='%') {
488 int insertnr;
489 char *fmtstr,*sprintfbuf,*x;
490 DWORD *argliststart;
491
492 fmtstr = NULL;
493 f++;
494 if (!*f) {
495 ADD_TO_T('%');
496 continue;
497 }
498
499 switch (*f) {
500 case '1':case '2':case '3':case '4':case '5':
501 case '6':case '7':case '8':case '9':
502 insertnr=*f-'0';
503 switch (f[1]) {
504 case '0':case '1':case '2':case '3':
505 case '4':case '5':case '6':case '7':
506 case '8':case '9':
507 f++;
508 insertnr=insertnr*10+*f-'0';
509 f++;
510 break;
511 default:
512 f++;
513 break;
514 }
515 if (*f=='!') {
516 f++;
517 if (NULL!=(x=strchr(f,'!'))) {
518 *x='\0';
519 fmtstr=HeapAlloc( GetProcessHeap(), 0, strlen(f)+2);
520 sprintf(fmtstr,"%%%s",f);
521 f=x+1;
522 } else {
523 fmtstr=HeapAlloc(GetProcessHeap(),0,strlen(f)+2);
524 sprintf(fmtstr,"%%%s",f);
525 f+=strlen(f); /*at \0*/
526 }
527 } else {
528 if(!args) break;
529 fmtstr = HeapAlloc( GetProcessHeap(),0,3);
530 strcpy( fmtstr, "%s" );
531 }
532 if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
533 argliststart=args+insertnr-1;
534 else
535 argliststart=(*(DWORD**)args)+insertnr-1;
536
537 if (fmtstr[strlen(fmtstr)-1]=='s' && argliststart[0]) {
538 DWORD xarr[3];
539
540 xarr[0]=(DWORD)HEAP_strdupWtoA(GetProcessHeap(),0,(LPWSTR)(*(argliststart+0)));
541 /* possible invalid pointers */
542 xarr[1]=*(argliststart+1);
543 xarr[2]=*(argliststart+2);
544 sprintfbuf=HeapAlloc(GetProcessHeap(),0,strlenW((LPWSTR)argliststart[0])*2+1);
545
546 /* CMF - This makes a BIG assumption about va_list */
547 vsprintf(sprintfbuf, fmtstr, (va_list) xarr);
548 HeapFree(GetProcessHeap(), 0, (LPVOID) xarr[0]);
549 } else {
550 sprintfbuf=HeapAlloc(GetProcessHeap(),0,100);
551
552 /* CMF - This makes a BIG assumption about va_list */
553 vsprintf(sprintfbuf, fmtstr, (va_list) argliststart);
554 }
555 x=sprintfbuf;
556 while (*x) {
557 ADD_TO_T(*x++);
558 }
559 HeapFree(GetProcessHeap(),0,sprintfbuf);
560 HeapFree(GetProcessHeap(),0,fmtstr);
561 break;
562 case 'n':
563 ADD_TO_T('\r');
564 ADD_TO_T('\n');
565 f++;
566 break;
567 case '0':
568 eos = TRUE;
569 f++;
570 break;
571 default:
572 ADD_TO_T(*f++);
573 break;
574 }
575 } else {
576 ch = *f;
577 f++;
578 if (ch == '\r') {
579 if (*f == '\n')
580 f++;
581 if(width)
582 ADD_TO_T(' ');
583 else
584 {
585 ADD_TO_T('\r');
586 ADD_TO_T('\n');
587 }
588 } else {
589 if (ch == '\n')
590 {
591 if(width)
592 ADD_TO_T(' ');
593 else
594 {
595 ADD_TO_T('\r');
596 ADD_TO_T('\n');
597 }
598 }
599 else
600 ADD_TO_T(ch);
601 }
602 }
603 }
604 }
605 *t='\0';
606 }
607 talloced = strlen(target)+1;
608 if (nSize && talloced<nSize)
609 target = (char*)HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,nSize);
610 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
611 /* nSize is the MINIMUM size */
612 DWORD len = MultiByteToWideChar( CP_ACP, 0, target, -1, NULL, 0 );
613 *((LPVOID*)lpBuffer) = (LPVOID)LocalAlloc(GMEM_ZEROINIT,len*sizeof(WCHAR));
614 MultiByteToWideChar( CP_ACP, 0, target, -1, *(LPWSTR*)lpBuffer, len );
615 }
616 else
617 {
618 if (nSize > 0 && !MultiByteToWideChar( CP_ACP, 0, target, -1, lpBuffer, nSize ))
619 lpBuffer[nSize-1] = 0;
620 }
621 HeapFree(GetProcessHeap(),0,target);
622 if (from) HeapFree(GetProcessHeap(),0,from);
623 return (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) ?
624 strlenW(*(LPWSTR*)lpBuffer):
625 strlenW(lpBuffer);
626 #else
627 return 0;
628 #endif /* __i386__ */
629 }
630 #undef ADD_TO_T