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