1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ex/locale.c
5 * PURPOSE: Locale (Language) Support for the Executive
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 * Eric Kohl
8 * Thomas Weidenmueller (w3seek@reactos.org
9 */
10
11 /* INCLUDES ******************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16
17 /* GLOBALS *******************************************************************/
18
19 /* System IDs: EN_US */
20 LCID PsDefaultSystemLocaleId = 0x00000409;
21 LANGID PsInstallUILanguageId = LANGIDFROMLCID(0x00000409);
22
23 /* UI/Thread IDs: Same as system */
24 LCID PsDefaultThreadLocaleId = 0x00000409;
25 LANGID PsDefaultUILanguageId = LANGIDFROMLCID(0x00000409);
26
27 /* DEFINES *******************************************************************/
28
29 #define BOGUS_LOCALE_ID 0xFFFF0000
30
31 /* PRIVATE FUNCTIONS *********************************************************/
32
33 /**
34 * @brief
35 * Validates the registry data of a NLS locale.
36 *
37 * @param[in] LocaleData
38 * A pointer to partial information that contains
39 * the NLS locale data.
40 *
41 * @return
42 * Returns TRUE if the following conditions are met,
43 * otherwise FALSE is returned.
44 */
45 static
46 __inline
47 BOOLEAN
ExpValidateNlsLocaleData(_In_ PKEY_VALUE_PARTIAL_INFORMATION LocaleData)48 ExpValidateNlsLocaleData(
49 _In_ PKEY_VALUE_PARTIAL_INFORMATION LocaleData)
50 {
51 PWCHAR Data;
52
53 /* Is this a null-terminated string type? */
54 if (LocaleData->Type != REG_SZ)
55 {
56 return FALSE;
57 }
58
59 /* Does it have a consistent length? */
60 if (LocaleData->DataLength < sizeof(WCHAR))
61 {
62 return FALSE;
63 }
64
65 /* Is the locale set and null-terminated? */
66 Data = (PWSTR)LocaleData->Data;
67 if (Data[0] != L'1' || Data[1] != UNICODE_NULL)
68 {
69 return FALSE;
70 }
71
72 /* All of the conditions above are met */
73 return TRUE;
74 }
75
76 /**
77 * @brief
78 * Validates a NLS locale. Whether a locale is valid
79 * or not depends on the following conditions:
80 *
81 * - The locale must exist in the Locale key, otherwise
82 * in the Alternate Sorts key;
83 *
84 * - The locale must exist in the Language Groups key, and
85 * the queried value must be readable;
86 *
87 * - The locale registry data must be of REG_SIZE type,
88 * has a consistent length and the locale belongs to
89 * a supported language group that is set.
90 *
91 * @param[in] LocaleId
92 * A locale identifier that corresponds to a specific
93 * locale to be validated.
94 *
95 * @return
96 * Returns STATUS_SUCCESS if the function has successfully
97 * validated the locale and it is valid. STATUS_OBJECT_NAME_NOT_FOUND
98 * is returned if the following locale does not exist on the system.
99 * A failure NTSTATUS code is returned otherwise.
100 */
101 static
102 NTSTATUS
ExpValidateNlsLocaleId(_In_ LCID LocaleId)103 ExpValidateNlsLocaleId(
104 _In_ LCID LocaleId)
105 {
106 NTSTATUS Status;
107 HANDLE NlsLocaleKey = NULL, AltSortKey = NULL, LangGroupKey = NULL;
108 OBJECT_ATTRIBUTES NlsLocalKeyAttrs, AltSortKeyAttrs, LangGroupKeyAttrs;
109 PKEY_VALUE_PARTIAL_INFORMATION BufferKey;
110 WCHAR ValueBuffer[20], LocaleIdBuffer[20];
111 ULONG ReturnedLength;
112 UNICODE_STRING LocaleIdString;
113 static UNICODE_STRING NlsLocaleKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale");
114 static UNICODE_STRING AltSortKeyPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Locale\\Alternate Sorts");
115 static UNICODE_STRING LangGroupPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Language Groups");
116
117 /* Initialize the registry path attributes */
118 InitializeObjectAttributes(&NlsLocalKeyAttrs,
119 &NlsLocaleKeyPath,
120 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
121 NULL,
122 NULL);
123
124 InitializeObjectAttributes(&AltSortKeyAttrs,
125 &AltSortKeyPath,
126 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
127 NULL,
128 NULL);
129
130 InitializeObjectAttributes(&LangGroupKeyAttrs,
131 &LangGroupPath,
132 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
133 NULL,
134 NULL);
135
136 /* Copy the locale ID into a buffer */
137 swprintf(LocaleIdBuffer,
138 L"%08lx",
139 (ULONG)LocaleId);
140
141 /* And build the LCID string */
142 RtlInitUnicodeString(&LocaleIdString, LocaleIdBuffer);
143
144 /* Open the NLS locale key */
145 Status = ZwOpenKey(&NlsLocaleKey,
146 KEY_QUERY_VALUE,
147 &NlsLocalKeyAttrs);
148 if (!NT_SUCCESS(Status))
149 {
150 DPRINT1("Failed to open %wZ (Status 0x%lx)\n", NlsLocaleKeyPath, Status);
151 return Status;
152 }
153
154 /* Open the NLS alternate sort locales key */
155 Status = ZwOpenKey(&AltSortKey,
156 KEY_QUERY_VALUE,
157 &AltSortKeyAttrs);
158 if (!NT_SUCCESS(Status))
159 {
160 DPRINT1("Failed to open %wZ (Status 0x%lx)\n", AltSortKeyPath, Status);
161 goto Quit;
162 }
163
164 /* Open the NLS language groups key */
165 Status = ZwOpenKey(&LangGroupKey,
166 KEY_QUERY_VALUE,
167 &LangGroupKeyAttrs);
168 if (!NT_SUCCESS(Status))
169 {
170 DPRINT1("Failed to open %wZ (Status 0x%lx)\n", LangGroupPath, Status);
171 goto Quit;
172 }
173
174 /* Check if the captured locale ID exists in the list of other locales */
175 BufferKey = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
176 Status = ZwQueryValueKey(NlsLocaleKey,
177 &LocaleIdString,
178 KeyValuePartialInformation,
179 BufferKey,
180 sizeof(ValueBuffer),
181 &ReturnedLength);
182 if (!NT_SUCCESS(Status))
183 {
184 /* We failed, retry by looking at the alternate sorts locales */
185 Status = ZwQueryValueKey(AltSortKey,
186 &LocaleIdString,
187 KeyValuePartialInformation,
188 BufferKey,
189 sizeof(ValueBuffer),
190 &ReturnedLength);
191 if (!NT_SUCCESS(Status))
192 {
193 DPRINT1("Failed to query value from Alternate Sorts key (Status 0x%lx)\n", Status);
194 goto Quit;
195 }
196 }
197
198 /* Ensure the queried locale is of the right key type with a sane length */
199 if (BufferKey->Type != REG_SZ ||
200 BufferKey->DataLength < sizeof(WCHAR))
201 {
202 DPRINT1("The queried locale is of bad value type or length (Type %lu, DataLength %lu)\n",
203 BufferKey->Type, BufferKey->DataLength);
204 Status = STATUS_OBJECT_NAME_NOT_FOUND;
205 goto Quit;
206 }
207
208 /* We got what we need, now query the locale from the language groups */
209 RtlInitUnicodeString(&LocaleIdString, (PWSTR)BufferKey->Data);
210 Status = ZwQueryValueKey(LangGroupKey,
211 &LocaleIdString,
212 KeyValuePartialInformation,
213 BufferKey,
214 sizeof(ValueBuffer),
215 &ReturnedLength);
216 if (!NT_SUCCESS(Status))
217 {
218 DPRINT1("Failed to query value from Language Groups key (Status 0x%lx)\n", Status);
219 goto Quit;
220 }
221
222 /*
223 * We have queried the locale with its data. However we are not finished here yet,
224 * because the locale data could be malformed or the locale itself was not set
225 * so ensure all of these conditions are met.
226 */
227 if (!ExpValidateNlsLocaleData(BufferKey))
228 {
229 DPRINT1("The locale data is not valid!\n");
230 Status = STATUS_OBJECT_NAME_NOT_FOUND;
231 }
232
233 Quit:
234 if (LangGroupKey != NULL)
235 {
236 ZwClose(LangGroupKey);
237 }
238
239 if (AltSortKey != NULL)
240 {
241 ZwClose(AltSortKey);
242 }
243
244 if (NlsLocaleKey != NULL)
245 {
246 ZwClose(NlsLocaleKey);
247 }
248
249 return Status;
250 }
251
252 NTSTATUS
253 NTAPI
ExpGetCurrentUserUILanguage(IN PCWSTR MuiName,OUT LANGID * LanguageId)254 ExpGetCurrentUserUILanguage(IN PCWSTR MuiName,
255 OUT LANGID* LanguageId)
256 {
257 UCHAR ValueBuffer[256];
258 PKEY_VALUE_PARTIAL_INFORMATION ValueInfo;
259 OBJECT_ATTRIBUTES ObjectAttributes;
260 UNICODE_STRING KeyName =
261 RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
262 UNICODE_STRING ValueName;
263 UNICODE_STRING ValueString;
264 ULONG ValueLength;
265 ULONG Value;
266 HANDLE UserKey;
267 HANDLE KeyHandle;
268 NTSTATUS Status;
269 PAGED_CODE();
270
271 /* Setup the key name */
272 RtlInitUnicodeString(&ValueName, MuiName);
273
274 /* Open the use key */
275 Status = RtlOpenCurrentUser(KEY_READ, &UserKey);
276 if (!NT_SUCCESS(Status)) return Status;
277
278 /* Initialize the attributes and open the key */
279 InitializeObjectAttributes(&ObjectAttributes,
280 &KeyName,
281 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
282 UserKey,
283 NULL);
284 Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE,&ObjectAttributes);
285 if (NT_SUCCESS(Status))
286 {
287 /* Set buffer and query the current value */
288 ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
289 Status = ZwQueryValueKey(KeyHandle,
290 &ValueName,
291 KeyValuePartialInformation,
292 ValueBuffer,
293 sizeof(ValueBuffer),
294 &ValueLength);
295 if (NT_SUCCESS(Status))
296 {
297 /* Success, is the value the right type? */
298 if (ValueInfo->Type == REG_SZ)
299 {
300 /* It is. Initialize the data and convert it */
301 RtlInitUnicodeString(&ValueString, (PWSTR)ValueInfo->Data);
302 Status = RtlUnicodeStringToInteger(&ValueString, 16, &Value);
303 if (NT_SUCCESS(Status))
304 {
305 /* Return the language */
306 *LanguageId = (USHORT)Value;
307 }
308 }
309 else
310 {
311 /* Fail */
312 Status = STATUS_UNSUCCESSFUL;
313 }
314 }
315
316 /* Close the key */
317 ZwClose(KeyHandle);
318 }
319
320 /* Close the user key and return */
321 ZwClose(UserKey);
322 return Status;
323 }
324
325 NTSTATUS
326 NTAPI
ExpSetCurrentUserUILanguage(IN PCWSTR MuiName,IN LANGID LanguageId)327 ExpSetCurrentUserUILanguage(IN PCWSTR MuiName,
328 IN LANGID LanguageId)
329 {
330 OBJECT_ATTRIBUTES ObjectAttributes;
331 UNICODE_STRING KeyName = RTL_CONSTANT_STRING(L"Control Panel\\Desktop");
332 UNICODE_STRING ValueName;
333 WCHAR ValueBuffer[8];
334 ULONG ValueLength;
335 HANDLE UserHandle;
336 HANDLE KeyHandle;
337 NTSTATUS Status;
338 PAGED_CODE();
339
340 /* Check that the passed language ID is not bogus */
341 if (LanguageId & BOGUS_LOCALE_ID)
342 {
343 return STATUS_INVALID_PARAMETER;
344 }
345
346 /* Setup the key name */
347 RtlInitUnicodeString(&ValueName, MuiName);
348
349 /* Open the use key */
350 Status = RtlOpenCurrentUser(KEY_WRITE, &UserHandle);
351 if (!NT_SUCCESS(Status)) return Status;
352
353 /* Initialize the attributes */
354 InitializeObjectAttributes(&ObjectAttributes,
355 &KeyName,
356 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
357 UserHandle,
358 NULL);
359
360 /* Validate the language ID */
361 Status = ExpValidateNlsLocaleId(MAKELCID(LanguageId, SORT_DEFAULT));
362 if (NT_SUCCESS(Status))
363 {
364 /* Open the key */
365 Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
366 if (NT_SUCCESS(Status))
367 {
368 /* Setup the value name */
369 ValueLength = swprintf(ValueBuffer,
370 L"%04lX",
371 (ULONG)LanguageId);
372
373 /* Set the length for the call and set the value */
374 ValueLength = (ValueLength + 1) * sizeof(WCHAR);
375 Status = ZwSetValueKey(KeyHandle,
376 &ValueName,
377 0,
378 REG_SZ,
379 ValueBuffer,
380 ValueLength);
381
382 /* Close the handle for this key */
383 ZwClose(KeyHandle);
384 }
385 }
386
387 /* Close the user key and return status */
388 ZwClose(UserHandle);
389 return Status;
390 }
391
392 /* PUBLIC FUNCTIONS **********************************************************/
393
394 NTSTATUS
395 NTAPI
NtQueryDefaultLocale(IN BOOLEAN UserProfile,OUT PLCID DefaultLocaleId)396 NtQueryDefaultLocale(IN BOOLEAN UserProfile,
397 OUT PLCID DefaultLocaleId)
398 {
399 NTSTATUS Status = STATUS_SUCCESS;
400 PAGED_CODE();
401
402 /* Enter SEH for probing */
403 _SEH2_TRY
404 {
405 /* Check if we came from user mode */
406 if (KeGetPreviousMode() != KernelMode)
407 {
408 /* Probe the language ID */
409 ProbeForWriteLangId(DefaultLocaleId);
410 }
411
412 /* Check if we have a user profile */
413 if (UserProfile)
414 {
415 /* Return session wide thread locale */
416 *DefaultLocaleId = MmGetSessionLocaleId();
417 }
418 else
419 {
420 /* Return system locale */
421 *DefaultLocaleId = PsDefaultSystemLocaleId;
422 }
423 }
424 _SEH2_EXCEPT(ExSystemExceptionFilter())
425 {
426 /* Get exception code */
427 Status = _SEH2_GetExceptionCode();
428 }
429 _SEH2_END;
430
431 /* Return status */
432 return Status;
433 }
434
435 NTSTATUS
436 NTAPI
NtSetDefaultLocale(IN BOOLEAN UserProfile,IN LCID DefaultLocaleId)437 NtSetDefaultLocale(IN BOOLEAN UserProfile,
438 IN LCID DefaultLocaleId)
439 {
440 OBJECT_ATTRIBUTES ObjectAttributes;
441 UNICODE_STRING KeyName;
442 UNICODE_STRING ValueName;
443 UNICODE_STRING LocaleString;
444 HANDLE KeyHandle = NULL;
445 ULONG ValueLength;
446 WCHAR ValueBuffer[20];
447 HANDLE UserKey = NULL;
448 NTSTATUS Status;
449 UCHAR KeyValueBuffer[256];
450 PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation;
451 PAGED_CODE();
452
453 /* Check that the passed locale ID is not bogus */
454 if (DefaultLocaleId & BOGUS_LOCALE_ID)
455 {
456 return STATUS_INVALID_PARAMETER;
457 }
458
459 /* Check if we have a profile */
460 if (UserProfile)
461 {
462 /* Open the user's key */
463 Status = RtlOpenCurrentUser(KEY_WRITE, &UserKey);
464 if (!NT_SUCCESS(Status)) return Status;
465
466 /* Initialize the registry location */
467 RtlInitUnicodeString(&KeyName, L"Control Panel\\International");
468 RtlInitUnicodeString(&ValueName, L"Locale");
469 }
470 else
471 {
472 /* Initialize the system registry location */
473 RtlInitUnicodeString(&KeyName,
474 L"\\Registry\\Machine\\System\\CurrentControlSet"
475 L"\\Control\\Nls\\Language");
476 RtlInitUnicodeString(&ValueName, L"Default");
477 }
478
479 /* Initialize the object attributes */
480 InitializeObjectAttributes(&ObjectAttributes,
481 &KeyName,
482 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
483 UserKey,
484 NULL);
485
486 /* Check if we don't have a default locale yet */
487 if (!DefaultLocaleId)
488 {
489 /* Open the key for reading */
490 Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &ObjectAttributes);
491 if (!NT_SUCCESS(Status))
492 {
493 goto Cleanup;
494 }
495
496 /* Query the key value */
497 KeyValueInformation = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueBuffer;
498 Status = ZwQueryValueKey(KeyHandle,
499 &ValueName,
500 KeyValuePartialInformation,
501 KeyValueInformation,
502 sizeof(KeyValueBuffer),
503 &ValueLength);
504 if (!NT_SUCCESS(Status))
505 {
506 goto Cleanup;
507 }
508
509 /* Check if this is a REG_DWORD */
510 if ((KeyValueInformation->Type == REG_DWORD) &&
511 (KeyValueInformation->DataLength == sizeof(ULONG)))
512 {
513 /* It contains the LCID as a DWORD */
514 DefaultLocaleId = *((ULONG*)KeyValueInformation->Data);
515 }
516 /* Otherwise check for a REG_SZ */
517 else if (KeyValueInformation->Type == REG_SZ)
518 {
519 /* Initialize a unicode string from the value data */
520 LocaleString.Buffer = (PWCHAR)KeyValueInformation->Data;
521 LocaleString.Length = (USHORT)KeyValueInformation->DataLength;
522 LocaleString.MaximumLength = LocaleString.Length;
523
524 /* Convert the hex string to a number */
525 RtlUnicodeStringToInteger(&LocaleString, 16, &DefaultLocaleId);
526 }
527 else
528 {
529 Status = STATUS_UNSUCCESSFUL;
530 }
531 }
532 else
533 {
534 /* We have a locale, validate it */
535 Status = ExpValidateNlsLocaleId(DefaultLocaleId);
536 if (NT_SUCCESS(Status))
537 {
538 /* Open the key now */
539 Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &ObjectAttributes);
540 if (NT_SUCCESS(Status))
541 {
542 /* Check if we had a profile */
543 if (UserProfile)
544 {
545 /* Fill in the buffer */
546 ValueLength = swprintf(ValueBuffer,
547 L"%08lx",
548 (ULONG)DefaultLocaleId);
549 }
550 else
551 {
552 /* Fill in the buffer */
553 ValueLength = swprintf(ValueBuffer,
554 L"%04lx",
555 (ULONG)DefaultLocaleId & 0xFFFF);
556 }
557
558 /* Set the length for the registry call */
559 ValueLength = (ValueLength + 1) * sizeof(WCHAR);
560
561 /* Now write the actual value */
562 Status = ZwSetValueKey(KeyHandle,
563 &ValueName,
564 0,
565 REG_SZ,
566 ValueBuffer,
567 ValueLength);
568 }
569 }
570 }
571
572 Cleanup:
573
574 /* Close the locale key */
575 if (KeyHandle)
576 {
577 ObCloseHandle(KeyHandle, KernelMode);
578 }
579
580 /* Close the user key */
581 if (UserKey)
582 {
583 ObCloseHandle(UserKey, KernelMode);
584 }
585
586 /* Check for success */
587 if (NT_SUCCESS(Status))
588 {
589 /* Check if it was for a user */
590 if (UserProfile)
591 {
592 /* Set the session wide thread locale */
593 MmSetSessionLocaleId(DefaultLocaleId);
594 }
595 else
596 {
597 /* Set system locale */
598 PsDefaultSystemLocaleId = DefaultLocaleId;
599 }
600 }
601
602 /* Return status */
603 return Status;
604 }
605
606 /*
607 * @implemented
608 */
609 NTSTATUS
610 NTAPI
NtQueryInstallUILanguage(OUT LANGID * LanguageId)611 NtQueryInstallUILanguage(OUT LANGID* LanguageId)
612 {
613 NTSTATUS Status = STATUS_SUCCESS;
614 PAGED_CODE();
615
616 /* Enter SEH for probing */
617 _SEH2_TRY
618 {
619 /* Check if we came from user mode */
620 if (KeGetPreviousMode() != KernelMode)
621 {
622 /* Probe the Language ID */
623 ProbeForWriteLangId(LanguageId);
624 }
625
626 /* Return it */
627 *LanguageId = PsInstallUILanguageId;
628 }
629 _SEH2_EXCEPT(ExSystemExceptionFilter())
630 {
631 /* Get exception code */
632 Status = _SEH2_GetExceptionCode();
633 }
634 _SEH2_END;
635
636 /* Return status */
637 return Status;
638 }
639
640 /*
641 * @implemented
642 */
643 NTSTATUS
644 NTAPI
NtQueryDefaultUILanguage(OUT LANGID * LanguageId)645 NtQueryDefaultUILanguage(OUT LANGID* LanguageId)
646 {
647 NTSTATUS Status;
648 LANGID SafeLanguageId;
649 PAGED_CODE();
650
651 /* Call the executive helper routine */
652 Status = ExpGetCurrentUserUILanguage(L"MultiUILanguageId", &SafeLanguageId);
653
654 /* Enter SEH for probing */
655 _SEH2_TRY
656 {
657 /* Check if we came from user mode */
658 if (KeGetPreviousMode() != KernelMode)
659 {
660 /* Probe the Language ID */
661 ProbeForWriteLangId(LanguageId);
662 }
663
664 if (NT_SUCCESS(Status))
665 {
666 /* Success, return the language */
667 *LanguageId = SafeLanguageId;
668 }
669 else
670 {
671 /* Failed, use fallback value */
672 // NOTE: Windows doesn't use PsDefaultUILanguageId.
673 *LanguageId = PsInstallUILanguageId;
674 }
675 }
676 _SEH2_EXCEPT(ExSystemExceptionFilter())
677 {
678 /* Return exception code */
679 _SEH2_YIELD(return _SEH2_GetExceptionCode());
680 }
681 _SEH2_END;
682
683 /* Return success */
684 return STATUS_SUCCESS;
685 }
686
687 /*
688 * @implemented
689 */
690 NTSTATUS
691 NTAPI
NtSetDefaultUILanguage(IN LANGID LanguageId)692 NtSetDefaultUILanguage(IN LANGID LanguageId)
693 {
694 NTSTATUS Status;
695 PAGED_CODE();
696
697 /* Check if the caller specified a language id */
698 if (LanguageId)
699 {
700 /* Set the pending MUI language id */
701 Status = ExpSetCurrentUserUILanguage(L"MUILanguagePending", LanguageId);
702 }
703 else
704 {
705 /* Otherwise get the pending MUI language id */
706 Status = ExpGetCurrentUserUILanguage(L"MUILanguagePending", &LanguageId);
707 if (!NT_SUCCESS(Status))
708 {
709 return Status;
710 }
711
712 /* And apply it as actual */
713 Status = ExpSetCurrentUserUILanguage(L"MultiUILanguageId", LanguageId);
714 }
715
716 return Status;
717 }
718
719 /* EOF */
720