[KERNEL32]
[reactos.git] / reactos / dll / win32 / kernel32 / winnls / string / nls.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS system libraries
4 * FILE: dll/win32/kernel32/misc/nls.c
5 * PURPOSE: National Language Support
6 * PROGRAMMER: Filip Navara
7 * Hartmut Birr
8 * Gunnar Andre Dalsnes
9 * Thomas Weidenmueller
10 * UPDATE HISTORY:
11 * Created 24/08/2004
12 */
13
14 /* INCLUDES *******************************************************************/
15
16 #include <k32.h>
17
18 #define NDEBUG
19 #include <debug.h>
20
21 /* GLOBAL VARIABLES ***********************************************************/
22
23 /* Sequence length based on the first character. */
24 static const char UTF8Length[128] =
25 {
26 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */
27 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */
28 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */
29 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0 - 0xBF */
30 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xC0 - 0xCF */
31 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xD0 - 0xDF */
32 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xE0 - 0xEF */
33 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 0, 0 /* 0xF0 - 0xFF */
34 };
35
36 /* First byte mask depending on UTF-8 sequence length. */
37 static const unsigned char UTF8Mask[6] = {0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01};
38
39 /* FIXME: Change to HASH table or linear array. */
40 static LIST_ENTRY CodePageListHead;
41 static CODEPAGE_ENTRY AnsiCodePage;
42 static CODEPAGE_ENTRY OemCodePage;
43 static RTL_CRITICAL_SECTION CodePageListLock;
44
45 /* FORWARD DECLARATIONS *******************************************************/
46
47 BOOL WINAPI
48 GetNlsSectionName(UINT CodePage, UINT Base, ULONG Unknown,
49 LPSTR BaseName, LPSTR Result, ULONG ResultSize);
50
51 BOOL WINAPI
52 GetCPFileNameFromRegistry(UINT CodePage, LPWSTR FileName, ULONG FileNameSize);
53
54 /* PRIVATE FUNCTIONS **********************************************************/
55
56 /**
57 * @name NlsInit
58 *
59 * Internal NLS related stuff initialization.
60 */
61
62 BOOL
63 FASTCALL
64 NlsInit(VOID)
65 {
66 UNICODE_STRING DirName;
67 OBJECT_ATTRIBUTES ObjectAttributes;
68 HANDLE Handle;
69
70 InitializeListHead(&CodePageListHead);
71 RtlInitializeCriticalSection(&CodePageListLock);
72
73 /*
74 * FIXME: Eventually this should be done only for the NLS Server
75 * process, but since we don't have anything like that (yet?) we
76 * always try to create the "\Nls" directory here.
77 */
78 RtlInitUnicodeString(&DirName, L"\\Nls");
79
80 InitializeObjectAttributes(&ObjectAttributes,
81 &DirName,
82 OBJ_CASE_INSENSITIVE | OBJ_PERMANENT,
83 NULL,
84 NULL);
85
86 if (NT_SUCCESS(NtCreateDirectoryObject(&Handle, DIRECTORY_ALL_ACCESS, &ObjectAttributes)))
87 {
88 NtClose(Handle);
89 }
90
91 /* Setup ANSI code page. */
92 AnsiCodePage.SectionHandle = NULL;
93 AnsiCodePage.SectionMapping = NtCurrentTeb()->ProcessEnvironmentBlock->AnsiCodePageData;
94
95 RtlInitCodePageTable((PUSHORT)AnsiCodePage.SectionMapping,
96 &AnsiCodePage.CodePageTable);
97 AnsiCodePage.CodePage = AnsiCodePage.CodePageTable.CodePage;
98
99 InsertTailList(&CodePageListHead, &AnsiCodePage.Entry);
100
101 /* Setup OEM code page. */
102 OemCodePage.SectionHandle = NULL;
103 OemCodePage.SectionMapping = NtCurrentTeb()->ProcessEnvironmentBlock->OemCodePageData;
104
105 RtlInitCodePageTable((PUSHORT)OemCodePage.SectionMapping,
106 &OemCodePage.CodePageTable);
107 OemCodePage.CodePage = OemCodePage.CodePageTable.CodePage;
108 InsertTailList(&CodePageListHead, &OemCodePage.Entry);
109
110 return TRUE;
111 }
112
113 /**
114 * @name NlsUninit
115 *
116 * Internal NLS related stuff uninitialization.
117 */
118
119 VOID
120 FASTCALL
121 NlsUninit(VOID)
122 {
123 PCODEPAGE_ENTRY Current;
124
125 /* Delete the code page list. */
126 while (!IsListEmpty(&CodePageListHead))
127 {
128 Current = CONTAINING_RECORD(CodePageListHead.Flink, CODEPAGE_ENTRY, Entry);
129 if (Current->SectionHandle != NULL)
130 {
131 UnmapViewOfFile(Current->SectionMapping);
132 NtClose(Current->SectionHandle);
133 }
134 RemoveHeadList(&CodePageListHead);
135 }
136 RtlDeleteCriticalSection(&CodePageListLock);
137 }
138
139 /**
140 * @name IntGetLoadedCodePageEntry
141 *
142 * Internal function to get structure containing a code page information
143 * of code page that is already loaded.
144 *
145 * @param CodePage
146 * Number of the code page. Special values like CP_OEMCP, CP_ACP
147 * or CP_UTF8 aren't allowed.
148 *
149 * @return Code page entry or NULL if the specified code page hasn't
150 * been loaded yet.
151 */
152
153 PCODEPAGE_ENTRY
154 FASTCALL
155 IntGetLoadedCodePageEntry(UINT CodePage)
156 {
157 LIST_ENTRY *CurrentEntry;
158 PCODEPAGE_ENTRY Current;
159
160 RtlEnterCriticalSection(&CodePageListLock);
161 for (CurrentEntry = CodePageListHead.Flink;
162 CurrentEntry != &CodePageListHead;
163 CurrentEntry = CurrentEntry->Flink)
164 {
165 Current = CONTAINING_RECORD(CurrentEntry, CODEPAGE_ENTRY, Entry);
166 if (Current->CodePage == CodePage)
167 {
168 RtlLeaveCriticalSection(&CodePageListLock);
169 return Current;
170 }
171 }
172 RtlLeaveCriticalSection(&CodePageListLock);
173
174 return NULL;
175 }
176
177 /**
178 * @name IntGetCodePageEntry
179 *
180 * Internal function to get structure containing a code page information.
181 *
182 * @param CodePage
183 * Number of the code page. Special values like CP_OEMCP, CP_ACP
184 * or CP_THREAD_ACP are allowed, but CP_UTF[7/8] isn't.
185 *
186 * @return Code page entry.
187 */
188
189 PCODEPAGE_ENTRY
190 FASTCALL
191 IntGetCodePageEntry(UINT CodePage)
192 {
193 CHAR SectionName[40];
194 NTSTATUS Status;
195 HANDLE SectionHandle = INVALID_HANDLE_VALUE, FileHandle;
196 PBYTE SectionMapping;
197 OBJECT_ATTRIBUTES ObjectAttributes;
198 ANSI_STRING AnsiName;
199 UNICODE_STRING UnicodeName;
200 WCHAR FileName[MAX_PATH + 1];
201 UINT FileNamePos;
202 PCODEPAGE_ENTRY CodePageEntry;
203 if (CodePage == CP_ACP)
204 {
205 return &AnsiCodePage;
206 }
207 else if (CodePage == CP_OEMCP)
208 {
209 return &OemCodePage;
210 }
211 else if (CodePage == CP_THREAD_ACP)
212 {
213 if (!GetLocaleInfoW(GetThreadLocale(),
214 LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
215 (WCHAR *)&CodePage,
216 sizeof(CodePage) / sizeof(WCHAR)))
217 {
218 /* Last error is set by GetLocaleInfoW. */
219 return NULL;
220 }
221 }
222 else if (CodePage == CP_MACCP)
223 {
224 if (!GetLocaleInfoW(LOCALE_SYSTEM_DEFAULT,
225 LOCALE_IDEFAULTMACCODEPAGE | LOCALE_RETURN_NUMBER,
226 (WCHAR *)&CodePage,
227 sizeof(CodePage) / sizeof(WCHAR)))
228 {
229 /* Last error is set by GetLocaleInfoW. */
230 return NULL;
231 }
232 }
233
234 /* Try searching for loaded page first. */
235 CodePageEntry = IntGetLoadedCodePageEntry(CodePage);
236 if (CodePageEntry != NULL)
237 {
238 return CodePageEntry;
239 }
240
241 /*
242 * Yes, we really want to lock here. Otherwise it can happen that
243 * two parallel requests will try to get the entry for the same
244 * code page and we would load it twice.
245 */
246 RtlEnterCriticalSection(&CodePageListLock);
247
248 /* Generate the section name. */
249 if (!GetNlsSectionName(CodePage,
250 10,
251 0,
252 "\\Nls\\NlsSectionCP",
253 SectionName,
254 sizeof(SectionName)))
255 {
256 RtlLeaveCriticalSection(&CodePageListLock);
257 return NULL;
258 }
259
260 RtlInitAnsiString(&AnsiName, SectionName);
261 RtlAnsiStringToUnicodeString(&UnicodeName, &AnsiName, TRUE);
262
263 InitializeObjectAttributes(&ObjectAttributes, &UnicodeName, 0, NULL, NULL);
264
265 /* Try to open the section first */
266 Status = NtOpenSection(&SectionHandle, SECTION_MAP_READ, &ObjectAttributes);
267
268 /* If the section doesn't exist, try to create it. */
269 if (Status == STATUS_UNSUCCESSFUL ||
270 Status == STATUS_OBJECT_NAME_NOT_FOUND ||
271 Status == STATUS_OBJECT_PATH_NOT_FOUND)
272 {
273 FileNamePos = GetSystemDirectoryW(FileName, MAX_PATH);
274 if (GetCPFileNameFromRegistry(CodePage,
275 FileName + FileNamePos + 1,
276 MAX_PATH - FileNamePos - 1))
277 {
278 FileName[FileNamePos] = L'\\';
279 FileName[MAX_PATH] = 0;
280 FileHandle = CreateFileW(FileName,
281 FILE_GENERIC_READ,
282 FILE_SHARE_READ,
283 NULL,
284 OPEN_EXISTING,
285 0,
286 NULL);
287
288 Status = NtCreateSection(&SectionHandle,
289 SECTION_MAP_READ,
290 &ObjectAttributes,
291 NULL,
292 PAGE_READONLY,
293 SEC_COMMIT,
294 FileHandle);
295
296 /* HACK: Check if another process was faster
297 * and already created this section. See bug 3626 for details */
298 if (Status == STATUS_OBJECT_NAME_COLLISION)
299 {
300 /* Close the file then */
301 NtClose(FileHandle);
302
303 /* And open the section */
304 Status = NtOpenSection(&SectionHandle,
305 SECTION_MAP_READ,
306 &ObjectAttributes);
307 }
308 }
309 }
310 RtlFreeUnicodeString(&UnicodeName);
311
312 if (!NT_SUCCESS(Status))
313 {
314 RtlLeaveCriticalSection(&CodePageListLock);
315 return NULL;
316 }
317
318 SectionMapping = MapViewOfFile(SectionHandle, FILE_MAP_READ, 0, 0, 0);
319 if (SectionMapping == NULL)
320 {
321 NtClose(SectionHandle);
322 RtlLeaveCriticalSection(&CodePageListLock);
323 return NULL;
324 }
325
326 CodePageEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CODEPAGE_ENTRY));
327 if (CodePageEntry == NULL)
328 {
329 NtClose(SectionHandle);
330 RtlLeaveCriticalSection(&CodePageListLock);
331 return NULL;
332 }
333
334 CodePageEntry->CodePage = CodePage;
335 CodePageEntry->SectionHandle = SectionHandle;
336 CodePageEntry->SectionMapping = SectionMapping;
337
338 RtlInitCodePageTable((PUSHORT)SectionMapping, &CodePageEntry->CodePageTable);
339
340 /* Insert the new entry to list and unlock. Uff. */
341 InsertTailList(&CodePageListHead, &CodePageEntry->Entry);
342 RtlLeaveCriticalSection(&CodePageListLock);
343
344 return CodePageEntry;
345 }
346
347 /**
348 * @name IntMultiByteToWideCharUTF8
349 *
350 * Internal version of MultiByteToWideChar for UTF8.
351 *
352 * @see MultiByteToWideChar
353 * @todo Add UTF8 validity checks.
354 */
355
356 static
357 INT
358 WINAPI
359 IntMultiByteToWideCharUTF8(DWORD Flags,
360 LPCSTR MultiByteString,
361 INT MultiByteCount,
362 LPWSTR WideCharString,
363 INT WideCharCount)
364 {
365 LPCSTR MbsEnd;
366 UCHAR Char, Length;
367 WCHAR WideChar;
368 LONG Count;
369
370 if (Flags != 0 && Flags != MB_ERR_INVALID_CHARS)
371 {
372 SetLastError(ERROR_INVALID_FLAGS);
373 return 0;
374 }
375
376 /* Does caller query for output buffer size? */
377 if (WideCharCount == 0)
378 {
379 MbsEnd = MultiByteString + MultiByteCount;
380 for (; MultiByteString < MbsEnd; WideCharCount++)
381 {
382 Char = *MultiByteString++;
383 if (Char < 0xC0)
384 continue;
385 MultiByteString += UTF8Length[Char - 0x80];
386 }
387 return WideCharCount;
388 }
389
390 MbsEnd = MultiByteString + MultiByteCount;
391 for (Count = 0; Count < WideCharCount && MultiByteString < MbsEnd; Count++)
392 {
393 Char = *MultiByteString++;
394 if (Char < 0x80)
395 {
396 *WideCharString++ = Char;
397 continue;
398 }
399 Length = UTF8Length[Char - 0x80];
400 WideChar = Char & UTF8Mask[Length];
401 while (Length && MultiByteString < MbsEnd)
402 {
403 WideChar = (WideChar << 6) | (*MultiByteString++ & 0x7f);
404 Length--;
405 }
406 *WideCharString++ = WideChar;
407 }
408
409 if (MultiByteString < MbsEnd)
410 SetLastError(ERROR_INSUFFICIENT_BUFFER);
411
412 return Count;
413 }
414
415 /**
416 * @name IntMultiByteToWideCharCP
417 *
418 * Internal version of MultiByteToWideChar for code page tables.
419 *
420 * @see MultiByteToWideChar
421 * @todo Handle MB_PRECOMPOSED, MB_COMPOSITE, MB_USEGLYPHCHARS and
422 * DBCS codepages.
423 */
424
425 static
426 INT
427 WINAPI
428 IntMultiByteToWideCharCP(UINT CodePage,
429 DWORD Flags,
430 LPCSTR MultiByteString,
431 INT MultiByteCount,
432 LPWSTR WideCharString,
433 INT WideCharCount)
434 {
435 PCODEPAGE_ENTRY CodePageEntry;
436 PCPTABLEINFO CodePageTable;
437 LPCSTR TempString;
438 INT TempLength;
439
440 /* Get code page table. */
441 CodePageEntry = IntGetCodePageEntry(CodePage);
442 if (CodePageEntry == NULL)
443 {
444 SetLastError(ERROR_INVALID_PARAMETER);
445 return 0;
446 }
447 CodePageTable = &CodePageEntry->CodePageTable;
448
449 /* Different handling for DBCS code pages. */
450 if (CodePageTable->MaximumCharacterSize > 1)
451 {
452 /* FIXME */
453
454 UCHAR Char;
455 USHORT DBCSOffset;
456 LPCSTR MbsEnd = MultiByteString + MultiByteCount;
457 INT Count;
458
459 /* Does caller query for output buffer size? */
460 if (WideCharCount == 0)
461 {
462 for (; MultiByteString < MbsEnd; WideCharCount++)
463 {
464 Char = *MultiByteString++;
465
466 if (Char < 0x80)
467 continue;
468
469 DBCSOffset = CodePageTable->DBCSOffsets[Char];
470
471 if (!DBCSOffset)
472 continue;
473
474 if (MultiByteString < MbsEnd)
475 MultiByteString++;
476 }
477
478 return WideCharCount;
479 }
480
481 for (Count = 0; Count < WideCharCount && MultiByteString < MbsEnd; Count++)
482 {
483 Char = *MultiByteString++;
484
485 if (Char < 0x80)
486 {
487 *WideCharString++ = Char;
488 continue;
489 }
490
491 DBCSOffset = CodePageTable->DBCSOffsets[Char];
492
493 if (!DBCSOffset)
494 {
495 *WideCharString++ = CodePageTable->MultiByteTable[Char];
496 continue;
497 }
498
499 if (MultiByteString < MbsEnd)
500 *WideCharString++ = CodePageTable->DBCSOffsets[DBCSOffset + *(PUCHAR)MultiByteString++];
501 }
502
503 if (MultiByteString < MbsEnd)
504 {
505 SetLastError(ERROR_INSUFFICIENT_BUFFER);
506 return 0;
507 }
508
509 return Count;
510 }
511 else /* Not DBCS code page */
512 {
513 /* Check for invalid characters. */
514 if (Flags & MB_ERR_INVALID_CHARS)
515 {
516 for (TempString = MultiByteString, TempLength = MultiByteCount;
517 TempLength > 0;
518 TempString++, TempLength--)
519 {
520 if (CodePageTable->MultiByteTable[(UCHAR)*TempString] ==
521 CodePageTable->UniDefaultChar &&
522 *TempString != CodePageEntry->CodePageTable.DefaultChar)
523 {
524 SetLastError(ERROR_NO_UNICODE_TRANSLATION);
525 return 0;
526 }
527 }
528 }
529
530 /* Does caller query for output buffer size? */
531 if (WideCharCount == 0)
532 return MultiByteCount;
533
534 /* Fill the WideCharString buffer with what will fit: Verified on WinXP */
535 for (TempLength = (WideCharCount < MultiByteCount) ? WideCharCount : MultiByteCount;
536 TempLength > 0;
537 MultiByteString++, TempLength--)
538 {
539 *WideCharString++ = CodePageTable->MultiByteTable[(UCHAR)*MultiByteString];
540 }
541
542 /* Adjust buffer size. Wine trick ;-) */
543 if (WideCharCount < MultiByteCount)
544 {
545 MultiByteCount = WideCharCount;
546 SetLastError(ERROR_INSUFFICIENT_BUFFER);
547 return 0;
548 }
549 return MultiByteCount;
550 }
551 }
552
553 /**
554 * @name IntMultiByteToWideCharSYMBOL
555 *
556 * Internal version of MultiByteToWideChar for SYMBOL.
557 *
558 * @see MultiByteToWideChar
559 */
560
561 static
562 INT
563 WINAPI
564 IntMultiByteToWideCharSYMBOL(DWORD Flags,
565 LPCSTR MultiByteString,
566 INT MultiByteCount,
567 LPWSTR WideCharString,
568 INT WideCharCount)
569 {
570 LONG Count;
571 UCHAR Char;
572 INT WideCharMaxLen;
573
574
575 if (Flags != 0)
576 {
577 SetLastError(ERROR_INVALID_FLAGS);
578 return 0;
579 }
580
581 if (WideCharCount == 0)
582 {
583 return MultiByteCount;
584 }
585
586 WideCharMaxLen = WideCharCount > MultiByteCount ? MultiByteCount : WideCharCount;
587
588 for (Count = 0; Count < WideCharMaxLen; Count++)
589 {
590 Char = MultiByteString[Count];
591 if ( Char < 0x20 )
592 {
593 WideCharString[Count] = Char;
594 }
595 else
596 {
597 WideCharString[Count] = Char + 0xf000;
598 }
599 }
600 if (MultiByteCount > WideCharMaxLen)
601 {
602 SetLastError(ERROR_INSUFFICIENT_BUFFER);
603 return 0;
604 }
605
606 return WideCharMaxLen;
607 }
608
609 /**
610 * @name IntWideCharToMultiByteSYMBOL
611 *
612 * Internal version of WideCharToMultiByte for SYMBOL.
613 *
614 * @see WideCharToMultiByte
615 */
616
617 static INT
618 WINAPI
619 IntWideCharToMultiByteSYMBOL(DWORD Flags,
620 LPCWSTR WideCharString,
621 INT WideCharCount,
622 LPSTR MultiByteString,
623 INT MultiByteCount)
624 {
625 LONG Count;
626 INT MaxLen;
627 WCHAR Char;
628
629 if (Flags!=0)
630 {
631 SetLastError(ERROR_INVALID_PARAMETER);
632 return 0;
633 }
634
635
636 if (MultiByteCount == 0)
637 {
638 return WideCharCount;
639 }
640
641 MaxLen = MultiByteCount > WideCharCount ? WideCharCount : MultiByteCount;
642 for (Count = 0; Count < MaxLen; Count++)
643 {
644 Char = WideCharString[Count];
645 if (Char < 0x20)
646 {
647 MultiByteString[Count] = (CHAR)Char;
648 }
649 else
650 {
651 if ((Char >= 0xf020) && (Char < 0xf100))
652 {
653 MultiByteString[Count] = Char - 0xf000;
654 }
655 else
656 {
657 SetLastError(ERROR_NO_UNICODE_TRANSLATION);
658 return 0;
659 }
660 }
661 }
662
663 if (WideCharCount > MaxLen)
664 {
665 SetLastError(ERROR_INSUFFICIENT_BUFFER);
666 return 0;
667 }
668 return MaxLen;
669 }
670
671 /**
672 * @name IntWideCharToMultiByteUTF8
673 *
674 * Internal version of WideCharToMultiByte for UTF8.
675 *
676 * @see WideCharToMultiByte
677 */
678
679 static INT
680 WINAPI
681 IntWideCharToMultiByteUTF8(UINT CodePage,
682 DWORD Flags,
683 LPCWSTR WideCharString,
684 INT WideCharCount,
685 LPSTR MultiByteString,
686 INT MultiByteCount,
687 LPCSTR DefaultChar,
688 LPBOOL UsedDefaultChar)
689 {
690 INT TempLength;
691 WCHAR Char;
692
693 /* Does caller query for output buffer size? */
694 if (MultiByteCount == 0)
695 {
696 for (TempLength = 0; WideCharCount;
697 WideCharCount--, WideCharString++)
698 {
699 TempLength++;
700 if (*WideCharString >= 0x80)
701 {
702 TempLength++;
703 if (*WideCharString >= 0x800)
704 TempLength++;
705 }
706 }
707 return TempLength;
708 }
709
710 for (TempLength = MultiByteCount; WideCharCount; WideCharCount--, WideCharString++)
711 {
712 Char = *WideCharString;
713 if (Char < 0x80)
714 {
715 if (!TempLength)
716 {
717 SetLastError(ERROR_INSUFFICIENT_BUFFER);
718 break;
719 }
720 TempLength--;
721 *MultiByteString++ = (CHAR)Char;
722 continue;
723 }
724
725 if (Char < 0x800) /* 0x80-0x7ff: 2 bytes */
726 {
727 if (TempLength < 2)
728 {
729 SetLastError(ERROR_INSUFFICIENT_BUFFER);
730 break;
731 }
732 MultiByteString[1] = 0x80 | (Char & 0x3f); Char >>= 6;
733 MultiByteString[0] = 0xc0 | Char;
734 MultiByteString += 2;
735 TempLength -= 2;
736 continue;
737 }
738
739 /* 0x800-0xffff: 3 bytes */
740 if (TempLength < 3)
741 {
742 SetLastError(ERROR_INSUFFICIENT_BUFFER);
743 break;
744 }
745 MultiByteString[2] = 0x80 | (Char & 0x3f); Char >>= 6;
746 MultiByteString[1] = 0x80 | (Char & 0x3f); Char >>= 6;
747 MultiByteString[0] = 0xe0 | Char;
748 MultiByteString += 3;
749 TempLength -= 3;
750 }
751
752 return MultiByteCount - TempLength;
753 }
754
755 /**
756 * @name IsValidSBCSMapping
757 *
758 * Checks if ch (single-byte character) is a valid mapping for wch
759 *
760 * @see IntWideCharToMultiByteCP
761 */
762 static
763 inline
764 BOOL
765 IntIsValidSBCSMapping(PCPTABLEINFO CodePageTable, DWORD Flags, WCHAR wch, UCHAR ch)
766 {
767 /* If the WC_NO_BEST_FIT_CHARS flag has been specified, the characters need to match exactly. */
768 if (Flags & WC_NO_BEST_FIT_CHARS)
769 return (CodePageTable->MultiByteTable[ch] == wch);
770
771 /* By default, all characters except TransDefaultChar apply as a valid mapping
772 for ch (so also "nearest" characters) */
773 if (ch != CodePageTable->TransDefaultChar)
774 return TRUE;
775
776 /* The only possible left valid mapping is the default character itself */
777 return (wch == CodePageTable->TransUniDefaultChar);
778 }
779
780 /**
781 * @name IsValidDBCSMapping
782 *
783 * Checks if ch (double-byte character) is a valid mapping for wch
784 *
785 * @see IntWideCharToMultiByteCP
786 */
787 static inline BOOL
788 IntIsValidDBCSMapping(PCPTABLEINFO CodePageTable, DWORD Flags, WCHAR wch, USHORT ch)
789 {
790 /* If ch is the default character, but the wch is not, it can't be a valid mapping */
791 if (ch == CodePageTable->TransDefaultChar && wch != CodePageTable->TransUniDefaultChar)
792 return FALSE;
793
794 /* If the WC_NO_BEST_FIT_CHARS flag has been specified, the characters need to match exactly. */
795 if (Flags & WC_NO_BEST_FIT_CHARS)
796 {
797 if(ch & 0xff00)
798 {
799 USHORT uOffset = CodePageTable->DBCSOffsets[ch >> 8];
800 /* if (!uOffset) return (CodePageTable->MultiByteTable[ch] == wch); */
801 return (CodePageTable->DBCSOffsets[uOffset + (ch & 0xff)] == wch);
802 }
803
804 return (CodePageTable->MultiByteTable[ch] == wch);
805 }
806
807 /* If we're still here, we have a valid mapping */
808 return TRUE;
809 }
810
811 /**
812 * @name IntWideCharToMultiByteCP
813 *
814 * Internal version of WideCharToMultiByte for code page tables.
815 *
816 * @see WideCharToMultiByte
817 * @todo Handle WC_COMPOSITECHECK
818 */
819 static
820 INT
821 WINAPI
822 IntWideCharToMultiByteCP(UINT CodePage,
823 DWORD Flags,
824 LPCWSTR WideCharString,
825 INT WideCharCount,
826 LPSTR MultiByteString,
827 INT MultiByteCount,
828 LPCSTR DefaultChar,
829 LPBOOL UsedDefaultChar)
830 {
831 PCODEPAGE_ENTRY CodePageEntry;
832 PCPTABLEINFO CodePageTable;
833 INT TempLength;
834
835 /* Get code page table. */
836 CodePageEntry = IntGetCodePageEntry(CodePage);
837 if (CodePageEntry == NULL)
838 {
839 SetLastError(ERROR_INVALID_PARAMETER);
840 return 0;
841 }
842 CodePageTable = &CodePageEntry->CodePageTable;
843
844
845 /* Different handling for DBCS code pages. */
846 if (CodePageTable->MaximumCharacterSize > 1)
847 {
848 /* If Flags, DefaultChar or UsedDefaultChar were given, we have to do some more work */
849 if(Flags || DefaultChar || UsedDefaultChar)
850 {
851 BOOL TempUsedDefaultChar;
852 USHORT DefChar;
853
854 /* If UsedDefaultChar is not set, set it to a temporary value, so we don't have
855 to check on every character */
856 if(!UsedDefaultChar)
857 UsedDefaultChar = &TempUsedDefaultChar;
858
859 *UsedDefaultChar = FALSE;
860
861 /* Use the CodePage's TransDefaultChar if none was given. Don't modify the DefaultChar pointer here. */
862 if(DefaultChar)
863 DefChar = DefaultChar[1] ? ((DefaultChar[0] << 8) | DefaultChar[1]) : DefaultChar[0];
864 else
865 DefChar = CodePageTable->TransDefaultChar;
866
867 /* Does caller query for output buffer size? */
868 if(!MultiByteCount)
869 {
870 for(TempLength = 0; WideCharCount; WideCharCount--, WideCharString++, TempLength++)
871 {
872 USHORT uChar;
873
874 if ((Flags & WC_COMPOSITECHECK) && WideCharCount > 1)
875 {
876 /* FIXME: Handle WC_COMPOSITECHECK */
877 }
878
879 uChar = ((PUSHORT) CodePageTable->WideCharTable)[*WideCharString];
880
881 /* Verify if the mapping is valid for handling DefaultChar and UsedDefaultChar */
882 if (!IntIsValidDBCSMapping(CodePageTable, Flags, *WideCharString, uChar))
883 {
884 uChar = DefChar;
885 *UsedDefaultChar = TRUE;
886 }
887
888 /* Increment TempLength again if this is a double-byte character */
889 if (uChar & 0xff00)
890 TempLength++;
891 }
892
893 return TempLength;
894 }
895
896 /* Convert the WideCharString to the MultiByteString and verify if the mapping is valid */
897 for(TempLength = MultiByteCount;
898 WideCharCount && TempLength;
899 TempLength--, WideCharString++, WideCharCount--)
900 {
901 USHORT uChar;
902
903 if ((Flags & WC_COMPOSITECHECK) && WideCharCount > 1)
904 {
905 /* FIXME: Handle WC_COMPOSITECHECK */
906 }
907
908 uChar = ((PUSHORT)CodePageTable->WideCharTable)[*WideCharString];
909
910 /* Verify if the mapping is valid for handling DefaultChar and UsedDefaultChar */
911 if (!IntIsValidDBCSMapping(CodePageTable, Flags, *WideCharString, uChar))
912 {
913 uChar = DefChar;
914 *UsedDefaultChar = TRUE;
915 }
916
917 /* Handle double-byte characters */
918 if (uChar & 0xff00)
919 {
920 /* Don't output a partial character */
921 if (TempLength == 1)
922 break;
923
924 TempLength--;
925 *MultiByteString++ = uChar >> 8;
926 }
927
928 *MultiByteString++ = (char)uChar;
929 }
930
931 /* WideCharCount should be 0 if all characters were converted */
932 if (WideCharCount)
933 {
934 SetLastError(ERROR_INSUFFICIENT_BUFFER);
935 return 0;
936 }
937
938 return MultiByteCount - TempLength;
939 }
940
941 /* Does caller query for output buffer size? */
942 if (!MultiByteCount)
943 {
944 for (TempLength = 0; WideCharCount; WideCharCount--, WideCharString++, TempLength++)
945 {
946 /* Increment TempLength again if this is a double-byte character */
947 if (((PWCHAR)CodePageTable->WideCharTable)[*WideCharString] & 0xff00)
948 TempLength++;
949 }
950
951 return TempLength;
952 }
953
954 /* Convert the WideCharString to the MultiByteString */
955 for (TempLength = MultiByteCount;
956 WideCharCount && TempLength;
957 TempLength--, WideCharString++, WideCharCount--)
958 {
959 USHORT uChar = ((PUSHORT) CodePageTable->WideCharTable)[*WideCharString];
960
961 /* Is this a double-byte character? */
962 if (uChar & 0xff00)
963 {
964 /* Don't output a partial character */
965 if (TempLength == 1)
966 break;
967
968 TempLength--;
969 *MultiByteString++ = uChar >> 8;
970 }
971
972 *MultiByteString++ = (char)uChar;
973 }
974
975 /* WideCharCount should be 0 if all characters were converted */
976 if (WideCharCount)
977 {
978 SetLastError(ERROR_INSUFFICIENT_BUFFER);
979 return 0;
980 }
981
982 return MultiByteCount - TempLength;
983 }
984 else /* Not DBCS code page */
985 {
986 INT nReturn;
987
988 /* If Flags, DefaultChar or UsedDefaultChar were given, we have to do some more work */
989 if (Flags || DefaultChar || UsedDefaultChar)
990 {
991 BOOL TempUsedDefaultChar;
992 CHAR DefChar;
993
994 /* If UsedDefaultChar is not set, set it to a temporary value, so we don't have
995 to check on every character */
996 if (!UsedDefaultChar)
997 UsedDefaultChar = &TempUsedDefaultChar;
998
999 *UsedDefaultChar = FALSE;
1000
1001 /* Does caller query for output buffer size? */
1002 if (!MultiByteCount)
1003 {
1004 /* Loop through the whole WideCharString and check if we can get a valid mapping for each character */
1005 for (TempLength = 0; WideCharCount; TempLength++, WideCharString++, WideCharCount--)
1006 {
1007 if ((Flags & WC_COMPOSITECHECK) && WideCharCount > 1)
1008 {
1009 /* FIXME: Handle WC_COMPOSITECHECK */
1010 }
1011
1012 if (!*UsedDefaultChar)
1013 *UsedDefaultChar = !IntIsValidSBCSMapping(CodePageTable,
1014 Flags,
1015 *WideCharString,
1016 ((PCHAR)CodePageTable->WideCharTable)[*WideCharString]);
1017 }
1018
1019 return TempLength;
1020 }
1021
1022 /* Use the CodePage's TransDefaultChar if none was given. Don't modify the DefaultChar pointer here. */
1023 if (DefaultChar)
1024 DefChar = *DefaultChar;
1025 else
1026 DefChar = (CHAR)CodePageTable->TransDefaultChar;
1027
1028 /* Convert the WideCharString to the MultiByteString and verify if the mapping is valid */
1029 for (TempLength = MultiByteCount;
1030 WideCharCount && TempLength;
1031 MultiByteString++, TempLength--, WideCharString++, WideCharCount--)
1032 {
1033 if ((Flags & WC_COMPOSITECHECK) && WideCharCount > 1)
1034 {
1035 /* FIXME: Handle WC_COMPOSITECHECK */
1036 }
1037
1038 *MultiByteString = ((PCHAR)CodePageTable->WideCharTable)[*WideCharString];
1039
1040 if (!IntIsValidSBCSMapping(CodePageTable, Flags, *WideCharString, *MultiByteString))
1041 {
1042 *MultiByteString = DefChar;
1043 *UsedDefaultChar = TRUE;
1044 }
1045 }
1046
1047 /* WideCharCount should be 0 if all characters were converted */
1048 if (WideCharCount)
1049 {
1050 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1051 return 0;
1052 }
1053
1054 return MultiByteCount - TempLength;
1055 }
1056
1057 /* Does caller query for output buffer size? */
1058 if (!MultiByteCount)
1059 return WideCharCount;
1060
1061 /* Is the buffer large enough? */
1062 if (MultiByteCount < WideCharCount)
1063 {
1064 /* Convert the string up to MultiByteCount and return 0 */
1065 WideCharCount = MultiByteCount;
1066 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1067 nReturn = 0;
1068 }
1069 else
1070 {
1071 /* Otherwise WideCharCount will be the number of converted characters */
1072 nReturn = WideCharCount;
1073 }
1074
1075 /* Convert the WideCharString to the MultiByteString */
1076 for (TempLength = WideCharCount; --TempLength >= 0; WideCharString++, MultiByteString++)
1077 {
1078 *MultiByteString = ((PCHAR)CodePageTable->WideCharTable)[*WideCharString];
1079 }
1080
1081 return nReturn;
1082 }
1083 }
1084
1085 /**
1086 * @name IntIsLeadByte
1087 *
1088 * Internal function to detect if byte is lead byte in specific character
1089 * table.
1090 */
1091
1092 static BOOL
1093 WINAPI
1094 IntIsLeadByte(PCPTABLEINFO TableInfo, BYTE Byte)
1095 {
1096 UINT i;
1097
1098 if (TableInfo->MaximumCharacterSize == 2)
1099 {
1100 for (i = 0; i < MAXIMUM_LEADBYTES && TableInfo->LeadByte[i]; i += 2)
1101 {
1102 if (Byte >= TableInfo->LeadByte[i] && Byte <= TableInfo->LeadByte[i+1])
1103 return TRUE;
1104 }
1105 }
1106
1107 return FALSE;
1108 }
1109
1110 /* PUBLIC FUNCTIONS ***********************************************************/
1111
1112 /**
1113 * @name GetNlsSectionName
1114 *
1115 * Construct a name of NLS section.
1116 *
1117 * @param CodePage
1118 * Code page number.
1119 * @param Base
1120 * Integer base used for converting to string. Usually set to 10.
1121 * @param Unknown
1122 * As the name suggests the meaning of this parameter is unknown.
1123 * The native version of Kernel32 passes it as the third parameter
1124 * to NlsConvertIntegerToString function, which is used for the
1125 * actual conversion of the code page number.
1126 * @param BaseName
1127 * Base name of the section. (ex. "\\Nls\\NlsSectionCP")
1128 * @param Result
1129 * Buffer that will hold the constructed name.
1130 * @param ResultSize
1131 * Size of the buffer for the result.
1132 *
1133 * @return TRUE if the buffer was large enough and was filled with
1134 * the requested information, FALSE otherwise.
1135 *
1136 * @implemented
1137 */
1138
1139 BOOL
1140 WINAPI
1141 GetNlsSectionName(UINT CodePage,
1142 UINT Base,
1143 ULONG Unknown,
1144 LPSTR BaseName,
1145 LPSTR Result,
1146 ULONG ResultSize)
1147 {
1148 CHAR Integer[11];
1149
1150 if (!NT_SUCCESS(RtlIntegerToChar(CodePage, Base, sizeof(Integer), Integer)))
1151 return FALSE;
1152
1153 /*
1154 * If the name including the terminating NULL character doesn't
1155 * fit in the output buffer then fail.
1156 */
1157 if (strlen(Integer) + strlen(BaseName) >= ResultSize)
1158 return FALSE;
1159
1160 lstrcpyA(Result, BaseName);
1161 lstrcatA(Result, Integer);
1162
1163 return TRUE;
1164 }
1165
1166 /**
1167 * @name GetCPFileNameFromRegistry
1168 *
1169 * Get file name of code page definition file.
1170 *
1171 * @param CodePage
1172 * Code page number to get file name of.
1173 * @param FileName
1174 * Buffer that is filled with file name of successful return. Can
1175 * be set to NULL.
1176 * @param FileNameSize
1177 * Size of the buffer to hold file name in WCHARs.
1178 *
1179 * @return TRUE if the file name was retrieved, FALSE otherwise.
1180 *
1181 * @implemented
1182 */
1183
1184 BOOL
1185 WINAPI
1186 GetCPFileNameFromRegistry(UINT CodePage, LPWSTR FileName, ULONG FileNameSize)
1187 {
1188 WCHAR ValueNameBuffer[11];
1189 UNICODE_STRING KeyName, ValueName;
1190 OBJECT_ATTRIBUTES ObjectAttributes;
1191 NTSTATUS Status;
1192 HANDLE KeyHandle;
1193 PKEY_VALUE_PARTIAL_INFORMATION Kvpi;
1194 DWORD KvpiSize;
1195 BOOL bRetValue;
1196
1197 bRetValue = FALSE;
1198
1199 /* Convert the codepage number to string. */
1200 ValueName.Buffer = ValueNameBuffer;
1201 ValueName.MaximumLength = sizeof(ValueNameBuffer);
1202
1203 if (!NT_SUCCESS(RtlIntegerToUnicodeString(CodePage, 10, &ValueName)))
1204 return bRetValue;
1205
1206 /* Open the registry key containing file name mappings. */
1207 RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\System\\"
1208 L"CurrentControlSet\\Control\\Nls\\CodePage");
1209 InitializeObjectAttributes(&ObjectAttributes, &KeyName, OBJ_CASE_INSENSITIVE,
1210 NULL, NULL);
1211 Status = NtOpenKey(&KeyHandle, KEY_READ, &ObjectAttributes);
1212 if (!NT_SUCCESS(Status))
1213 {
1214 return bRetValue;
1215 }
1216
1217 /* Allocate buffer that will be used to query the value data. */
1218 KvpiSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + (MAX_PATH * sizeof(WCHAR));
1219 Kvpi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, KvpiSize);
1220 if (Kvpi == NULL)
1221 {
1222 NtClose(KeyHandle);
1223 return bRetValue;
1224 }
1225
1226 /* Query the file name for our code page. */
1227 Status = NtQueryValueKey(KeyHandle, &ValueName, KeyValuePartialInformation,
1228 Kvpi, KvpiSize, &KvpiSize);
1229
1230 NtClose(KeyHandle);
1231
1232 /* Check if we succeded and the value is non-empty string. */
1233 if (NT_SUCCESS(Status) && Kvpi->Type == REG_SZ &&
1234 Kvpi->DataLength > sizeof(WCHAR))
1235 {
1236 bRetValue = TRUE;
1237 if (FileName != NULL)
1238 {
1239 lstrcpynW(FileName, (WCHAR*)Kvpi->Data,
1240 min(Kvpi->DataLength / sizeof(WCHAR), FileNameSize));
1241 }
1242 }
1243
1244 /* free temporary buffer */
1245 HeapFree(GetProcessHeap(),0,Kvpi);
1246 return bRetValue;
1247 }
1248
1249 /**
1250 * @name IsValidCodePage
1251 *
1252 * Detect if specified code page is valid and present in the system.
1253 *
1254 * @param CodePage
1255 * Code page number to query.
1256 *
1257 * @return TRUE if code page is present.
1258 */
1259
1260 BOOL
1261 WINAPI
1262 IsValidCodePage(UINT CodePage)
1263 {
1264 if (CodePage == 0) return FALSE;
1265 if (CodePage == CP_UTF8 || CodePage == CP_UTF7)
1266 return TRUE;
1267 if (IntGetLoadedCodePageEntry(CodePage))
1268 return TRUE;
1269 return GetCPFileNameFromRegistry(CodePage, NULL, 0);
1270 }
1271
1272 static const signed char
1273 base64inv[] =
1274 {
1275 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1276 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1277 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
1278 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
1279 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
1280 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
1281 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
1282 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
1283 };
1284
1285 static VOID Utf7Base64Decode(BYTE *pbDest, LPCSTR pszSrc, INT cchSrc)
1286 {
1287 INT i, j, n;
1288 BYTE b;
1289
1290 for(i = 0; i < cchSrc / 4 * 4; i += 4)
1291 {
1292 for(j = n = 0; j < 4; )
1293 {
1294 b = (BYTE) base64inv[(BYTE) *pszSrc++];
1295 n |= (((INT) b) << ((3 - j) * 6));
1296 j++;
1297 }
1298 for(j = 0; j < 3; j++)
1299 *pbDest++ = (BYTE) ((n >> (8 * (2 - j))) & 0xFF);
1300 }
1301 for(j = n = 0; j < cchSrc % 4; )
1302 {
1303 b = (BYTE) base64inv[(BYTE) *pszSrc++];
1304 n |= (((INT) b) << ((3 - j) * 6));
1305 j++;
1306 }
1307 for(j = 0; j < ((cchSrc % 4) * 6 / 8); j++)
1308 *pbDest++ = (BYTE) ((n >> (8 * (2 - j))) & 0xFF);
1309 }
1310
1311 static VOID myswab(LPVOID pv, INT cw)
1312 {
1313 LPBYTE pb = (LPBYTE) pv;
1314 BYTE b;
1315 while(cw > 0)
1316 {
1317 b = *pb;
1318 *pb = pb[1];
1319 pb[1] = b;
1320 pb += 2;
1321 cw--;
1322 }
1323 }
1324
1325 static INT Utf7ToWideCharSize(LPCSTR pszUtf7, INT cchUtf7)
1326 {
1327 INT n, c, cch;
1328 CHAR ch;
1329 LPCSTR pch;
1330
1331 c = 0;
1332 while(cchUtf7 > 0)
1333 {
1334 ch = *pszUtf7++;
1335 if (ch == '+')
1336 {
1337 ch = *pszUtf7;
1338 if (ch == '-')
1339 {
1340 c++;
1341 pszUtf7++;
1342 cchUtf7 -= 2;
1343 continue;
1344 }
1345 cchUtf7--;
1346 pch = pszUtf7;
1347 while(cchUtf7 > 0 && (BYTE) *pszUtf7 < 0x80 &&
1348 base64inv[*pszUtf7] >= 0)
1349 {
1350 cchUtf7--;
1351 pszUtf7++;
1352 }
1353 cch = pszUtf7 - pch;
1354 n = (cch * 3) / 8;
1355 c += n;
1356 if (cchUtf7 > 0 && *pszUtf7 == '-')
1357 {
1358 pszUtf7++;
1359 cchUtf7--;
1360 }
1361 }
1362 else
1363 {
1364 c++;
1365 cchUtf7--;
1366 }
1367 }
1368
1369 return c;
1370 }
1371
1372 static INT Utf7ToWideChar(LPCSTR pszUtf7, INT cchUtf7, LPWSTR pszWide, INT cchWide)
1373 {
1374 INT n, c, cch;
1375 CHAR ch;
1376 LPCSTR pch;
1377 WORD *pwsz;
1378
1379 c = Utf7ToWideCharSize(pszUtf7, cchUtf7);
1380 if (cchWide == 0)
1381 return c;
1382
1383 if (cchWide < c)
1384 {
1385 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1386 return 0;
1387 }
1388
1389 while(cchUtf7 > 0)
1390 {
1391 ch = *pszUtf7++;
1392 if (ch == '+')
1393 {
1394 if (*pszUtf7 == '-')
1395 {
1396 *pszWide++ = L'+';
1397 pszUtf7++;
1398 cchUtf7 -= 2;
1399 continue;
1400 }
1401 cchUtf7--;
1402 pch = pszUtf7;
1403 while(cchUtf7 > 0 && (BYTE) *pszUtf7 < 0x80 &&
1404 base64inv[*pszUtf7] >= 0)
1405 {
1406 cchUtf7--;
1407 pszUtf7++;
1408 }
1409 cch = pszUtf7 - pch;
1410 n = (cch * 3) / 8;
1411 pwsz = (WORD *) HeapAlloc(GetProcessHeap(), 0, (n + 1) * sizeof(WORD));
1412 if (pwsz == NULL)
1413 return 0;
1414 ZeroMemory(pwsz, n * sizeof(WORD));
1415 Utf7Base64Decode((BYTE *) pwsz, pch, cch);
1416 myswab(pwsz, n);
1417 CopyMemory(pszWide, pwsz, n * sizeof(WORD));
1418 HeapFree(GetProcessHeap(), 0, pwsz);
1419 pszWide += n;
1420 if (cchUtf7 > 0 && *pszUtf7 == '-')
1421 {
1422 pszUtf7++;
1423 cchUtf7--;
1424 }
1425 }
1426 else
1427 {
1428 *pszWide++ = (WCHAR) ch;
1429 cchUtf7--;
1430 }
1431 }
1432
1433 return c;
1434 }
1435
1436 /**
1437 * @name MultiByteToWideChar
1438 *
1439 * Convert a multi-byte string to wide-charater equivalent.
1440 *
1441 * @param CodePage
1442 * Code page to be used to perform the conversion. It can be also
1443 * one of the special values (CP_ACP for ANSI code page, CP_MACCP
1444 * for Macintosh code page, CP_OEMCP for OEM code page, CP_THREAD_ACP
1445 * for thread active code page, CP_UTF7 or CP_UTF8).
1446 * @param Flags
1447 * Additional conversion flags (MB_PRECOMPOSED, MB_COMPOSITE,
1448 * MB_ERR_INVALID_CHARS, MB_USEGLYPHCHARS).
1449 * @param MultiByteString
1450 * Input buffer.
1451 * @param MultiByteCount
1452 * Size of MultiByteString, or -1 if MultiByteString is NULL
1453 * terminated.
1454 * @param WideCharString
1455 * Output buffer.
1456 * @param WideCharCount
1457 * Size in WCHARs of WideCharString, or 0 if the caller just wants
1458 * to know how large WideCharString should be for a successful
1459 * conversion.
1460 *
1461 * @return Zero on error, otherwise the number of WCHARs written
1462 * in the WideCharString buffer.
1463 *
1464 * @implemented
1465 */
1466
1467 INT
1468 WINAPI
1469 MultiByteToWideChar(UINT CodePage,
1470 DWORD Flags,
1471 LPCSTR MultiByteString,
1472 INT MultiByteCount,
1473 LPWSTR WideCharString,
1474 INT WideCharCount)
1475 {
1476 /* Check the parameters. */
1477 if (MultiByteString == NULL ||
1478 MultiByteCount == 0 ||
1479 (WideCharString == NULL && WideCharCount > 0) ||
1480 (PVOID)MultiByteString == (PVOID)WideCharString)
1481 {
1482 SetLastError(ERROR_INVALID_PARAMETER);
1483 return 0;
1484 }
1485
1486 /* Determine the input string length. */
1487 if (MultiByteCount < 0)
1488 {
1489 MultiByteCount = lstrlenA(MultiByteString) + 1;
1490 }
1491
1492 switch (CodePage)
1493 {
1494 case CP_UTF8:
1495 return IntMultiByteToWideCharUTF8(Flags,
1496 MultiByteString,
1497 MultiByteCount,
1498 WideCharString,
1499 WideCharCount);
1500
1501 case CP_UTF7:
1502 if (Flags)
1503 {
1504 SetLastError(ERROR_INVALID_FLAGS);
1505 return 0;
1506 }
1507 return Utf7ToWideChar(MultiByteString, MultiByteCount,
1508 WideCharString, WideCharCount);
1509
1510 case CP_SYMBOL:
1511 return IntMultiByteToWideCharSYMBOL(Flags,
1512 MultiByteString,
1513 MultiByteCount,
1514 WideCharString,
1515 WideCharCount);
1516 default:
1517 return IntMultiByteToWideCharCP(CodePage,
1518 Flags,
1519 MultiByteString,
1520 MultiByteCount,
1521 WideCharString,
1522 WideCharCount);
1523 }
1524 }
1525
1526 static const char mustshift[] =
1527 {
1528 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1,
1529 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1530 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0,
1531 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
1532 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1533 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1534 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1535 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1
1536 };
1537
1538 static const char base64[] =
1539 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1540
1541 static INT WideCharToUtf7Size(LPCWSTR pszWide, INT cchWide)
1542 {
1543 WCHAR wch;
1544 INT c = 0;
1545 BOOL fShift = FALSE;
1546
1547 while(cchWide > 0)
1548 {
1549 wch = *pszWide;
1550 if (wch < 0x80 && !mustshift[wch])
1551 {
1552 c++;
1553 cchWide--;
1554 pszWide++;
1555 }
1556 else
1557 {
1558 if (wch == L'+')
1559 {
1560 c++;
1561 c++;
1562 cchWide--;
1563 pszWide++;
1564 continue;
1565 }
1566 if (!fShift)
1567 {
1568 c++;
1569 fShift = TRUE;
1570 }
1571 pszWide++;
1572 cchWide--;
1573 c += 3;
1574 if (cchWide > 0 && (*pszWide >= 0x80 || mustshift[*pszWide]))
1575 {
1576 pszWide++;
1577 cchWide--;
1578 c += 3;
1579 if (cchWide > 0 && (*pszWide >= 0x80 || mustshift[*pszWide]))
1580 {
1581 pszWide++;
1582 cchWide--;
1583 c += 2;
1584 }
1585 }
1586 if (cchWide > 0 && *pszWide < 0x80 && !mustshift[*pszWide])
1587 {
1588 c++;
1589 fShift = FALSE;
1590 }
1591 }
1592 }
1593 if (fShift)
1594 c++;
1595
1596 return c;
1597 }
1598
1599 static INT WideCharToUtf7(LPCWSTR pszWide, INT cchWide, LPSTR pszUtf7, INT cchUtf7)
1600 {
1601 WCHAR wch;
1602 INT c, n;
1603 WCHAR wsz[3];
1604 BOOL fShift = FALSE;
1605
1606 c = WideCharToUtf7Size(pszWide, cchWide);
1607 if (cchUtf7 == 0)
1608 return c;
1609
1610 if (cchUtf7 < c)
1611 {
1612 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1613 return 0;
1614 }
1615
1616 while(cchWide > 0)
1617 {
1618 wch = *pszWide;
1619 if (wch < 0x80 && !mustshift[wch])
1620 {
1621 *pszUtf7++ = (CHAR) wch;
1622 cchWide--;
1623 pszWide++;
1624 }
1625 else
1626 {
1627 if (wch == L'+')
1628 {
1629 *pszUtf7++ = '+';
1630 *pszUtf7++ = '-';
1631 cchWide--;
1632 pszWide++;
1633 continue;
1634 }
1635 if (!fShift)
1636 {
1637 *pszUtf7++ = '+';
1638 fShift = TRUE;
1639 }
1640 wsz[0] = *pszWide++;
1641 cchWide--;
1642 n = 1;
1643 if (cchWide > 0 && (*pszWide >= 0x80 || mustshift[*pszWide]))
1644 {
1645 wsz[1] = *pszWide++;
1646 cchWide--;
1647 n++;
1648 if (cchWide > 0 && (*pszWide >= 0x80 || mustshift[*pszWide]))
1649 {
1650 wsz[2] = *pszWide++;
1651 cchWide--;
1652 n++;
1653 }
1654 }
1655 *pszUtf7++ = base64[wsz[0] >> 10];
1656 *pszUtf7++ = base64[(wsz[0] >> 4) & 0x3F];
1657 *pszUtf7++ = base64[(wsz[0] << 2 | wsz[1] >> 14) & 0x3F];
1658 if (n >= 2)
1659 {
1660 *pszUtf7++ = base64[(wsz[1] >> 8) & 0x3F];
1661 *pszUtf7++ = base64[(wsz[1] >> 2) & 0x3F];
1662 *pszUtf7++ = base64[(wsz[1] << 4 | wsz[2] >> 12) & 0x3F];
1663 if (n >= 3)
1664 {
1665 *pszUtf7++ = base64[(wsz[2] >> 6) & 0x3F];
1666 *pszUtf7++ = base64[wsz[2] & 0x3F];
1667 }
1668 }
1669 if (cchWide > 0 && *pszWide < 0x80 && !mustshift[*pszWide])
1670 {
1671 *pszUtf7++ = '-';
1672 fShift = FALSE;
1673 }
1674 }
1675 }
1676 if (fShift)
1677 *pszUtf7 = '-';
1678
1679 return c;
1680 }
1681
1682 DWORD
1683 GetLocalisedText(DWORD dwResId, WCHAR *lpszDest, DWORD dwDestSize)
1684 {
1685 HRSRC hrsrc;
1686 LCID lcid;
1687 LANGID langId;
1688 DWORD dwId;
1689
1690 if (dwResId == 37)
1691 dwId = dwResId * 100;
1692 else
1693 dwId = dwResId;
1694
1695 lcid = GetUserDefaultLCID();
1696 lcid = ConvertDefaultLocale(lcid);
1697
1698 langId = LANGIDFROMLCID(lcid);
1699
1700 if (PRIMARYLANGID(langId) == LANG_NEUTRAL)
1701 langId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
1702
1703 hrsrc = FindResourceExW(hCurrentModule,
1704 (LPWSTR)RT_STRING,
1705 MAKEINTRESOURCEW((dwId >> 4) + 1),
1706 langId);
1707
1708 /* english fallback */
1709 if(!hrsrc)
1710 {
1711 hrsrc = FindResourceExW(hCurrentModule,
1712 (LPWSTR)RT_STRING,
1713 MAKEINTRESOURCEW((dwId >> 4) + 1),
1714 MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
1715 }
1716
1717 if (hrsrc)
1718 {
1719 HGLOBAL hmem = LoadResource(hCurrentModule, hrsrc);
1720
1721 if (hmem)
1722 {
1723 const WCHAR *p;
1724 unsigned int i;
1725 unsigned int len;
1726
1727 p = LockResource(hmem);
1728
1729 for (i = 0; i < (dwId & 0x0f); i++) p += *p + 1;
1730
1731 if(dwDestSize == 0)
1732 return *p + 1;
1733
1734 len = *p * sizeof(WCHAR);
1735
1736 if(len + sizeof(WCHAR) > dwDestSize)
1737 {
1738 SetLastError(ERROR_INSUFFICIENT_BUFFER);
1739 return FALSE;
1740 }
1741
1742 memcpy(lpszDest, p + 1, len);
1743 lpszDest[*p] = '\0';
1744
1745 return TRUE;
1746 }
1747 }
1748
1749 DPRINT1("Resource not found: dwResId = %lu\n", dwResId);
1750 SetLastError(ERROR_INVALID_PARAMETER);
1751 return FALSE;
1752 }
1753
1754 /*
1755 * @implemented
1756 */
1757 BOOL
1758 WINAPI
1759 GetCPInfo(UINT CodePage,
1760 LPCPINFO CodePageInfo)
1761 {
1762 PCODEPAGE_ENTRY CodePageEntry;
1763
1764 if (!CodePageInfo)
1765 {
1766 SetLastError(ERROR_INVALID_PARAMETER);
1767 return FALSE;
1768 }
1769
1770 CodePageEntry = IntGetCodePageEntry(CodePage);
1771 if (CodePageEntry == NULL)
1772 {
1773 switch(CodePage)
1774 {
1775 case CP_UTF7:
1776 case CP_UTF8:
1777 CodePageInfo->DefaultChar[0] = 0x3f;
1778 CodePageInfo->DefaultChar[1] = 0;
1779 CodePageInfo->LeadByte[0] = CodePageInfo->LeadByte[1] = 0;
1780 CodePageInfo->MaxCharSize = (CodePage == CP_UTF7) ? 5 : 4;
1781 return TRUE;
1782 }
1783
1784 DPRINT1("Invalid CP!: %lx\n", CodePage);
1785 SetLastError( ERROR_INVALID_PARAMETER );
1786 return FALSE;
1787 }
1788
1789 if (CodePageEntry->CodePageTable.DefaultChar & 0xff00)
1790 {
1791 CodePageInfo->DefaultChar[0] = (CodePageEntry->CodePageTable.DefaultChar & 0xff00) >> 8;
1792 CodePageInfo->DefaultChar[1] = CodePageEntry->CodePageTable.DefaultChar & 0x00ff;
1793 }
1794 else
1795 {
1796 CodePageInfo->DefaultChar[0] = CodePageEntry->CodePageTable.DefaultChar & 0xff;
1797 CodePageInfo->DefaultChar[1] = 0;
1798 }
1799
1800 if ((CodePageInfo->MaxCharSize = CodePageEntry->CodePageTable.MaximumCharacterSize) == 2)
1801 memcpy(CodePageInfo->LeadByte, CodePageEntry->CodePageTable.LeadByte, sizeof(CodePageInfo->LeadByte));
1802 else
1803 CodePageInfo->LeadByte[0] = CodePageInfo->LeadByte[1] = 0;
1804
1805 return TRUE;
1806 }
1807
1808 /*
1809 * @implemented
1810 */
1811 BOOL
1812 WINAPI
1813 GetCPInfoExW(UINT CodePage,
1814 DWORD dwFlags,
1815 LPCPINFOEXW lpCPInfoEx)
1816 {
1817 if (!GetCPInfo(CodePage, (LPCPINFO) lpCPInfoEx))
1818 return FALSE;
1819
1820 switch(CodePage)
1821 {
1822 case CP_UTF7:
1823 {
1824 lpCPInfoEx->CodePage = CP_UTF7;
1825 lpCPInfoEx->UnicodeDefaultChar = 0x3f;
1826 return GetLocalisedText((DWORD)CodePage, lpCPInfoEx->CodePageName, sizeof(lpCPInfoEx->CodePageName)) != 0;
1827 }
1828 break;
1829
1830 case CP_UTF8:
1831 {
1832 lpCPInfoEx->CodePage = CP_UTF8;
1833 lpCPInfoEx->UnicodeDefaultChar = 0x3f;
1834 return GetLocalisedText((DWORD)CodePage, lpCPInfoEx->CodePageName, sizeof(lpCPInfoEx->CodePageName)) != 0;
1835 }
1836
1837 default:
1838 {
1839 PCODEPAGE_ENTRY CodePageEntry;
1840
1841 CodePageEntry = IntGetCodePageEntry(CodePage);
1842 if (CodePageEntry == NULL)
1843 {
1844 DPRINT1("Could not get CodePage Entry! CodePageEntry = 0\n");
1845 SetLastError(ERROR_INVALID_PARAMETER);
1846 return FALSE;
1847 }
1848
1849 lpCPInfoEx->CodePage = CodePageEntry->CodePageTable.CodePage;
1850 lpCPInfoEx->UnicodeDefaultChar = CodePageEntry->CodePageTable.UniDefaultChar;
1851 return GetLocalisedText(CodePageEntry->CodePageTable.CodePage, lpCPInfoEx->CodePageName, sizeof(lpCPInfoEx->CodePageName)) != 0;
1852 }
1853 break;
1854 }
1855 }
1856
1857
1858 /*
1859 * @implemented
1860 */
1861 BOOL
1862 WINAPI
1863 GetCPInfoExA(UINT CodePage,
1864 DWORD dwFlags,
1865 LPCPINFOEXA lpCPInfoEx)
1866 {
1867 CPINFOEXW CPInfo;
1868
1869 if (!GetCPInfoExW(CodePage, dwFlags, &CPInfo))
1870 return FALSE;
1871
1872 /* the layout is the same except for CodePageName */
1873 memcpy(lpCPInfoEx, &CPInfo, sizeof(CPINFOEXA));
1874
1875 WideCharToMultiByte(CP_ACP,
1876 0,
1877 CPInfo.CodePageName,
1878 -1,
1879 lpCPInfoEx->CodePageName,
1880 sizeof(lpCPInfoEx->CodePageName),
1881 NULL,
1882 NULL);
1883 return TRUE;
1884 }
1885
1886 /**
1887 * @name WideCharToMultiByte
1888 *
1889 * Convert a wide-charater string to closest multi-byte equivalent.
1890 *
1891 * @param CodePage
1892 * Code page to be used to perform the conversion. It can be also
1893 * one of the special values (CP_ACP for ANSI code page, CP_MACCP
1894 * for Macintosh code page, CP_OEMCP for OEM code page, CP_THREAD_ACP
1895 * for thread active code page, CP_UTF7 or CP_UTF8).
1896 * @param Flags
1897 * Additional conversion flags (WC_NO_BEST_FIT_CHARS, WC_COMPOSITECHECK,
1898 * WC_DISCARDNS, WC_SEPCHARS, WC_DEFAULTCHAR).
1899 * @param WideCharString
1900 * Points to the wide-character string to be converted.
1901 * @param WideCharCount
1902 * Size in WCHARs of WideCharStr, or 0 if the caller just wants to
1903 * know how large WideCharString should be for a successful conversion.
1904 * @param MultiByteString
1905 * Points to the buffer to receive the translated string.
1906 * @param MultiByteCount
1907 * Specifies the size in bytes of the buffer pointed to by the
1908 * MultiByteString parameter. If this value is zero, the function
1909 * returns the number of bytes required for the buffer.
1910 * @param DefaultChar
1911 * Points to the character used if a wide character cannot be
1912 * represented in the specified code page. If this parameter is
1913 * NULL, a system default value is used.
1914 * @param UsedDefaultChar
1915 * Points to a flag that indicates whether a default character was
1916 * used. This parameter can be NULL.
1917 *
1918 * @return Zero on error, otherwise the number of bytes written in the
1919 * MultiByteString buffer. Or the number of bytes needed for
1920 * the MultiByteString buffer if MultiByteCount is zero.
1921 *
1922 * @implemented
1923 */
1924
1925 INT
1926 WINAPI
1927 WideCharToMultiByte(UINT CodePage,
1928 DWORD Flags,
1929 LPCWSTR WideCharString,
1930 INT WideCharCount,
1931 LPSTR MultiByteString,
1932 INT MultiByteCount,
1933 LPCSTR DefaultChar,
1934 LPBOOL UsedDefaultChar)
1935 {
1936 /* Check the parameters. */
1937 if (WideCharString == NULL ||
1938 WideCharCount == 0 ||
1939 (MultiByteString == NULL && MultiByteCount > 0) ||
1940 (PVOID)WideCharString == (PVOID)MultiByteString ||
1941 MultiByteCount < 0)
1942 {
1943 SetLastError(ERROR_INVALID_PARAMETER);
1944 return 0;
1945 }
1946
1947 /* Determine the input string length. */
1948 if (WideCharCount < 0)
1949 {
1950 WideCharCount = lstrlenW(WideCharString) + 1;
1951 }
1952
1953 switch (CodePage)
1954 {
1955 case CP_UTF8:
1956 if (DefaultChar != NULL || UsedDefaultChar != NULL)
1957 {
1958 SetLastError(ERROR_INVALID_PARAMETER);
1959 return 0;
1960 }
1961 return IntWideCharToMultiByteUTF8(CodePage,
1962 Flags,
1963 WideCharString,
1964 WideCharCount,
1965 MultiByteString,
1966 MultiByteCount,
1967 DefaultChar,
1968 UsedDefaultChar);
1969
1970 case CP_UTF7:
1971 if (DefaultChar != NULL || UsedDefaultChar != NULL)
1972 {
1973 SetLastError(ERROR_INVALID_PARAMETER);
1974 return 0;
1975 }
1976 if (Flags)
1977 {
1978 SetLastError(ERROR_INVALID_FLAGS);
1979 return 0;
1980 }
1981 return WideCharToUtf7(WideCharString, WideCharCount,
1982 MultiByteString, MultiByteCount);
1983
1984 case CP_SYMBOL:
1985 if ((DefaultChar!=NULL) || (UsedDefaultChar!=NULL))
1986 {
1987 SetLastError(ERROR_INVALID_PARAMETER);
1988 return 0;
1989 }
1990 return IntWideCharToMultiByteSYMBOL(Flags,
1991 WideCharString,
1992 WideCharCount,
1993 MultiByteString,
1994 MultiByteCount);
1995
1996 default:
1997 return IntWideCharToMultiByteCP(CodePage,
1998 Flags,
1999 WideCharString,
2000 WideCharCount,
2001 MultiByteString,
2002 MultiByteCount,
2003 DefaultChar,
2004 UsedDefaultChar);
2005 }
2006 }
2007
2008 /**
2009 * @name GetACP
2010 *
2011 * Get active ANSI code page number.
2012 *
2013 * @implemented
2014 */
2015
2016 UINT
2017 WINAPI
2018 GetACP(VOID)
2019 {
2020 return AnsiCodePage.CodePageTable.CodePage;
2021 }
2022
2023 /**
2024 * @name GetOEMCP
2025 *
2026 * Get active OEM code page number.
2027 *
2028 * @implemented
2029 */
2030
2031 UINT
2032 WINAPI
2033 GetOEMCP(VOID)
2034 {
2035 return OemCodePage.CodePageTable.CodePage;
2036 }
2037
2038 /**
2039 * @name IsDBCSLeadByteEx
2040 *
2041 * Determine if passed byte is lead byte in specified code page.
2042 *
2043 * @implemented
2044 */
2045
2046 BOOL
2047 WINAPI
2048 IsDBCSLeadByteEx(UINT CodePage, BYTE TestByte)
2049 {
2050 PCODEPAGE_ENTRY CodePageEntry;
2051
2052 CodePageEntry = IntGetCodePageEntry(CodePage);
2053 if (CodePageEntry != NULL)
2054 return IntIsLeadByte(&CodePageEntry->CodePageTable, TestByte);
2055
2056 SetLastError(ERROR_INVALID_PARAMETER);
2057 return FALSE;
2058 }
2059
2060 /**
2061 * @name IsDBCSLeadByteEx
2062 *
2063 * Determine if passed byte is lead byte in current ANSI code page.
2064 *
2065 * @implemented
2066 */
2067
2068 BOOL
2069 WINAPI
2070 IsDBCSLeadByte(BYTE TestByte)
2071 {
2072 return IntIsLeadByte(&AnsiCodePage.CodePageTable, TestByte);
2073 }
2074
2075 /*
2076 * @unimplemented
2077 */
2078 NTSTATUS WINAPI CreateNlsSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor,ULONG Size,ULONG AccessMask)
2079 {
2080 STUB;
2081 return 0;
2082 }
2083
2084 /*
2085 * @unimplemented
2086 */
2087 BOOL WINAPI IsValidUILanguage(LANGID langid)
2088 {
2089 STUB;
2090 return 0;
2091 }
2092
2093 /*
2094 * @unimplemented
2095 */
2096 VOID WINAPI NlsConvertIntegerToString(ULONG Value,ULONG Base,ULONG strsize, LPWSTR str, ULONG strsize2)
2097 {
2098 STUB;
2099 }
2100
2101 /*
2102 * @unimplemented
2103 */
2104 UINT WINAPI SetCPGlobal(UINT CodePage)
2105 {
2106 STUB;
2107 return 0;
2108 }
2109
2110 /*
2111 * @unimplemented
2112 */
2113 BOOL
2114 WINAPI
2115 ValidateLCType(int a1, unsigned int a2, int a3, int a4)
2116 {
2117 STUB;
2118 return FALSE;
2119 }
2120
2121 /*
2122 * @unimplemented
2123 */
2124 BOOL
2125 WINAPI
2126 NlsResetProcessLocale(VOID)
2127 {
2128 STUB;
2129 return TRUE;
2130 }
2131
2132 /*
2133 * @unimplemented
2134 */
2135 VOID
2136 WINAPI
2137 GetDefaultSortkeySize(LPVOID lpUnknown)
2138 {
2139 STUB;
2140 lpUnknown = NULL;
2141 }
2142
2143 /*
2144 * @unimplemented
2145 */
2146 VOID
2147 WINAPI
2148 GetLinguistLangSize(LPVOID lpUnknown)
2149 {
2150 STUB;
2151 lpUnknown = NULL;
2152 }
2153
2154 /*
2155 * @unimplemented
2156 */
2157 BOOL
2158 WINAPI
2159 ValidateLocale(IN ULONG LocaleId)
2160 {
2161 STUB;
2162 return TRUE;
2163 }
2164
2165 /*
2166 * @unimplemented
2167 */
2168 ULONG
2169 WINAPI
2170 NlsGetCacheUpdateCount(VOID)
2171 {
2172 STUB;
2173 return 0;
2174 }
2175
2176 /*
2177 * @unimplemented
2178 */
2179 BOOL
2180 WINAPI
2181 IsNLSDefinedString(IN NLS_FUNCTION Function,
2182 IN DWORD dwFlags,
2183 IN LPNLSVERSIONINFO lpVersionInformation,
2184 IN LPCWSTR lpString,
2185 IN INT cchStr)
2186 {
2187 STUB;
2188 return TRUE;
2189 }
2190
2191 /*
2192 * @unimplemented
2193 */
2194 BOOL
2195 WINAPI
2196 GetNLSVersion(IN NLS_FUNCTION Function,
2197 IN LCID Locale,
2198 IN OUT LPNLSVERSIONINFO lpVersionInformation)
2199 {
2200 STUB;
2201 return TRUE;
2202 }
2203
2204 /* EOF */