xref: /reactos/ntoskrnl/ex/locale.c (revision 6438b856)
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