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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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