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