From e01c32f3bd54cdd001e44e10214a8dd6e4fa1dbc Mon Sep 17 00:00:00 2001 From: Timo Kreuzer Date: Wed, 19 Feb 2025 14:48:48 +0200 Subject: [PATCH] [RTL] Implement locale functions/tables The locale table is currently hardcoded in rtl. On Vista+ (and Wine) this table is part of locale.nls, but we don't have that yet. --- sdk/lib/rtl/CMakeLists.txt | 1 + sdk/lib/rtl/locale.c | 954 +++++++++++++++++++++++++++++++++++++ 2 files changed, 955 insertions(+) create mode 100644 sdk/lib/rtl/locale.c diff --git a/sdk/lib/rtl/CMakeLists.txt b/sdk/lib/rtl/CMakeLists.txt index 202857437d5..688d9258587 100644 --- a/sdk/lib/rtl/CMakeLists.txt +++ b/sdk/lib/rtl/CMakeLists.txt @@ -57,6 +57,7 @@ list(APPEND SOURCE image.c interlck.c largeint.c + locale.c luid.c memstream.c message.c diff --git a/sdk/lib/rtl/locale.c b/sdk/lib/rtl/locale.c new file mode 100644 index 00000000000..980a715590e --- /dev/null +++ b/sdk/lib/rtl/locale.c @@ -0,0 +1,954 @@ +/* + * PROJECT: ReactOS runtime library + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: Rtl locale functions + * COPYRIGHT: Copyright 2025 Timo Kreuzer + */ + +#include + +#define NDEBUG +#include + +// See https://winprotocoldoc.z19.web.core.windows.net/MS-LCID/%5bMS-LCID%5d.pdf +// For special LCIDs see https://learn.microsoft.com/en-us/windows/win32/intl/locale-custom-constants +// and https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/926e694f-1797-4418-a922-343d1c5e91a6 +// and https://learn.microsoft.com/en-us/windows/win32/intl/locale-identifiers + +#define MAX_PRIMARY_LANGUAGE 0x3FF +#define MAX_BASIC_LCID 0x5FFF + +#define LCID_ALT_NAME 0x00100000 // Alternative name, OR'ed with LCID + +LCID RtlpUserDefaultLcid; +LCID RtlpSystemDefaultLcid; + +typedef struct _LOCALE_ENTRY +{ + const CHAR Locale[16]; + LCID Lcid : 24; + ULONG Flags : 8; +} LOCALE_ENTRY; + +// This table is sorted alphabetically to allow binary search +static const LOCALE_ENTRY RtlpLocaleTable[] = +{ + { "", 0x0007F }, // LOCALE_INVARIANT + { "af", 0x00036 }, + { "af-ZA", 0x00436 }, + { "am", 0x0005E }, + { "am-ET", 0x0045E }, + { "ar", 0x00001 }, + // { "ar-145", 0x04801 }, // reserved + { "ar-AE", 0x03801 }, + { "ar-BH", 0x03C01 }, + { "ar-DZ", 0x01401 }, + { "ar-EG", 0x00C01 }, + { "ar-IQ", 0x00801 }, + { "ar-JO", 0x02C01 }, + { "ar-KW", 0x03401 }, + { "ar-LB", 0x03001 }, + { "ar-LY", 0x01001 }, + { "ar-MA", 0x01801 }, + { "ar-OM", 0x02001 }, + // { "ar-Ploc-SA", 0x04401 }, // reserved + { "ar-QA", 0x04001 }, + { "ar-SA", 0x00401 }, + { "ar-SY", 0x02801 }, + { "ar-TN", 0x01C01 }, + { "ar-YE", 0x02401 }, + { "arn", 0x0007A }, + { "arn-CL", 0x0047A }, + { "as", 0x0004D }, + { "as-IN", 0x0044D }, + { "az", 0x0002C }, + { "az-Cyrl", 0x0742C }, + { "az-Cyrl-AZ", 0x0082C }, // Doc: reserved + { "az-Latn", 0x0782C }, + { "az-Latn-AZ", 0x0042C }, + { "ba", 0x0006D }, + { "ba-RU", 0x0046D }, + { "be", 0x00023 }, + { "be-BY", 0x00423 }, + { "bg", 0x00002 }, + { "bg-BG", 0x00402 }, + { "bin", 0x00066 }, // Doc: reserved + { "bin-NG", 0x00466 }, // Doc: reserved + { "bn", 0x00045 }, + { "bn-BD", 0x00845 }, + { "bn-IN", 0x00445 }, + { "bo", 0x00051 }, + // { "bo-BT", 0x00851 }, // reserved + { "bo-CN", 0x00451 }, + { "br", 0x0007E }, + { "br-FR", 0x0047E }, + { "bs", 0x0781A }, + { "bs-Cyrl", 0x0641A }, + { "bs-Cyrl-BA", 0x0201A }, + { "bs-Latn", 0x0681A }, + { "bs-Latn-BA", 0x0141A }, + { "ca", 0x00003 }, + { "ca-ES", 0x00403 }, + { "ca-ES-valencia", 0x00803 }, + { "chr", 0x0005C }, + { "chr-Cher", 0x07C5C }, + { "chr-Cher-US", 0x0045C }, + { "co", 0x00083 }, + { "co-FR", 0x00483 }, + { "cs", 0x00005 }, + { "cs-CZ", 0x00405 }, + { "cy", 0x00052 }, + { "cy-GB", 0x00452 }, + { "da", 0x00006 }, + { "da-DK", 0x00406 }, + { "de", 0x00007 }, + { "de-AT", 0x00C07 }, + { "de-CH", 0x00807 }, + { "de-DE", 0x00407 }, + { "de-DE_phoneb", 0x10407 }, + { "de-LI", 0x01407 }, + { "de-LU", 0x01007 }, + { "dsb", 0x07C2E }, + { "dsb-DE", 0x0082E }, + { "dv", 0x00065 }, + { "dv-MV", 0x00465 }, + { "dz-BT", 0x00C51 }, + { "el", 0x00008 }, + { "el-GR", 0x00408 }, + { "en", 0x00009 }, + { "en-029", 0x02409 }, // Doc: reserved + { "en-AE", 0x04C09 }, + { "en-AU", 0x00C09 }, + // { "en-BH", 0x05009 }, // reserved + { "en-BZ", 0x02809 }, + { "en-CA", 0x01009 }, + // { "en-EG", 0x05409 }, // reserved + { "en-GB", 0x00809 }, + { "en-HK", 0x03C09 }, + { "en-ID", 0x03809 }, // reserved + { "en-IE", 0x01809 }, + { "en-IN", 0x04009 }, + { "en-JM", 0x02009 }, + // { "en-JO", 0x05809 }, // reserved + // { "en-KW", 0x05C09 }, // reserved + { "en-MY", 0x04409 }, + { "en-NZ", 0x01409 }, + { "en-PH", 0x03409 }, + { "en-SG", 0x04809 }, + // { "en-TR", 0x6009 }, // reserved + { "en-TT", 0x02C09 }, + { "en-US", 0x00409 }, + // { "en-YE", 0x6409 }, // reserved + { "en-ZA", 0x01C09 }, + { "en-ZW", 0x03009 }, + { "es", 0x0000A }, + { "es-419", 0x0580A }, // Doc: reserved + { "es-AR", 0x02C0A }, + { "es-BO", 0x0400A }, + { "es-CL", 0x0340A }, + { "es-CO", 0x0240A }, + { "es-CR", 0x0140A }, + { "es-CU", 0x05C0A }, + { "es-DO", 0x01C0A }, + { "es-EC", 0x0300A }, + { "es-ES", 0x00C0A }, + { "es-ES_tradnl", 0x0040A }, + { "es-GT", 0x0100A }, + { "es-HN", 0x0480A }, + { "es-MX", 0x0080A }, + { "es-NI", 0x04C0A }, + { "es-PA", 0x0180A }, + { "es-PE", 0x0280A }, + { "es-PR", 0x0500A }, + { "es-PY", 0x03C0A }, + { "es-SV", 0x0440A }, + { "es-US", 0x0540A }, + { "es-UY", 0x0380A }, + { "es-VE", 0x0200A }, + { "et", 0x00025 }, + { "et-EE", 0x00425 }, + { "eu", 0x0002D }, + { "eu-ES", 0x0042D }, + { "fa", 0x00029 }, + { "fa-IR", 0x00429 }, + { "ff", 0x00067 }, + { "ff-Latn", 0x07C67 }, + { "ff-Latn-NG", 0x00467 }, + { "ff-Latn-SN", 0x00867 }, + { "ff-NG", 0x00467 | LCID_ALT_NAME }, + { "fi", 0x0000B }, + { "fi-FI", 0x0040B }, + { "fil", 0x00064 }, + { "fil-PH", 0x00464 }, + { "fo", 0x00038 }, + { "fo-FO", 0x00438 }, + { "fr", 0x0000C }, + // { "fr-015", 0x0E40C }, // reserved + { "fr-029", 0x01C0C }, + { "fr-BE", 0x0080C }, + { "fr-CA", 0x00C0C }, + { "fr-CD", 0x0240C }, + { "fr-CH", 0x0100C }, + { "fr-CI", 0x0300C }, + { "fr-CM", 0x02C0C }, + { "fr-FR", 0x0040C }, + { "fr-HT", 0x03C0C }, + { "fr-LU", 0x0140C }, + { "fr-MA", 0x0380C }, + { "fr-MC", 0x0180C }, + { "fr-ML", 0x0340C }, + { "fr-RE", 0x0200C }, + { "fr-SN", 0x0280C }, + { "fy", 0x00062 }, + { "fy-NL", 0x00462 }, + { "ga", 0x0003C }, + { "ga-IE", 0x0083C }, + { "gd", 0x00091 }, + { "gd-GB", 0x00491 }, + { "gl", 0x00056 }, + { "gl-ES", 0x00456 }, + { "gn", 0x00074 }, + { "gn-PY", 0x00474 }, + { "gsw", 0x00084 }, + { "gsw-FR", 0x00484 }, + { "gu", 0x00047 }, + { "gu-IN", 0x00447 }, + { "ha", 0x00068 }, + { "ha-Latn", 0x07C68 }, + { "ha-Latn-NG", 0x00468 }, + { "haw", 0x00075 }, + { "haw-US", 0x00475 }, + { "he", 0x0000D }, + { "he-IL", 0x0040D }, + { "hi", 0x00039 }, + { "hi-IN", 0x00439 }, + { "hr", 0x0001A }, + { "hr-BA", 0x0101A }, + { "hr-HR", 0x0041A }, + { "hsb", 0x0002E }, + { "hsb-DE", 0x0042E }, + { "hu", 0x0000E }, + { "hu-HU", 0x0040E }, + { "hu-HU_technl", 0x1040E }, + { "hy", 0x0002B }, + { "hy-AM", 0x0042B }, + { "ibb", 0x00069 }, // Doc: reserved + { "ibb-NG", 0x00469 }, // Doc: reserved + { "id", 0x00021 }, + { "id-ID", 0x00421 }, + { "ig", 0x00070 }, + { "ig-NG", 0x00470 }, + { "ii", 0x00078 }, + { "ii-CN", 0x00478 }, + { "is", 0x0000F }, + { "is-IS", 0x0040F }, + { "it", 0x00010 }, + { "it-CH", 0x00810 }, + { "it-IT", 0x00410 }, + { "iu", 0x0005D }, + { "iu-Cans", 0x0785D }, + { "iu-Cans-CA", 0x0045D }, + { "iu-Latn", 0x07C5D }, + { "iu-Latn-CA", 0x0085D }, + { "ja", 0x00011 }, + { "ja-JP", 0x00411 }, + { "ja-JP_radstr", 0x040411 }, + // { "ja-Ploc-JP", 0x00811 }, // reserved + { "ka", 0x00037 }, + { "ka-GE", 0x00437 }, + { "ka-GE_modern", 0x10437 }, + // { "khb-Talu-CN", 0x00490 }, // reserved + { "kk", 0x0003F }, + { "kk-Cyrl", 0x0003F | LCID_ALT_NAME }, // Doc: 0x0783F, reserved + { "kk-KZ", 0x0043F }, + // { "kk-Latn", 0x07C3F }, // reserved + // { "kk-Latn-KZ", 0x0083F }, // reserved + { "kl", 0x0006F }, + { "kl-GL", 0x0046F }, + { "km", 0x00053 }, + { "km-KH", 0x00453 }, + { "kn", 0x0004B }, + { "kn-IN", 0x0044B }, + { "ko", 0x00012 }, + { "ko-KR", 0x00412 }, + { "kok", 0x00057 }, + { "kok-IN", 0x00457 }, + { "kr", 0x00071 }, // Doc: reserved + { "kr-Latn-NG", 0x00471 }, + { "ks", 0x00060 }, + { "ks-Arab", 0x00460 }, // Neutral locale + { "ks-Deva-IN", 0x00860 }, + { "ku", 0x00092 }, + { "ku-Arab", 0x07C92 }, + { "ku-Arab-IQ", 0x00492 }, + { "ky", 0x00040 }, + { "ky-KG", 0x00440 }, + { "la", 0x00076 }, // reserved + { "la-001", 0x00476 }, // Doc: la-VA + { "lb", 0x0006E }, + { "lb-LU", 0x0046E }, + { "lo", 0x00054 }, + { "lo-LA", 0x00454 }, + { "lt", 0x00027 }, + { "lt-LT", 0x00427 }, + { "lv", 0x00026 }, + { "lv-LV", 0x00426 }, + { "mi", 0x00081 }, + { "mi-NZ", 0x00481 }, + { "mk", 0x0002F }, + { "mk-MK", 0x0042F }, + { "ml", 0x0004C }, + { "ml-IN", 0x0044C }, + { "mn", 0x00050 }, + { "mn-Cyrl", 0x07850 }, + { "mn-MN", 0x00450 }, + { "mn-Mong", 0x07C50 }, + { "mn-Mong-CN", 0x00850 }, // Doc: reserved + { "mn-Mong-MN", 0x00C50 }, + { "mni", 0x00058 }, // Doc: reserved + { "mni-IN", 0x00458 }, // Doc: reserved + { "moh", 0x0007C }, + { "moh-CA", 0x0047C }, + { "mr", 0x0004E }, + { "mr-IN", 0x0044E }, + { "ms", 0x0003E }, + { "ms-BN", 0x0083E }, + { "ms-MY", 0x0043E }, + { "mt", 0x0003A }, + { "mt-MT", 0x0043A }, + { "my", 0x00055 }, + { "my-MM", 0x00455 }, + { "nb", 0x07C14 }, + { "nb-NO", 0x00414 }, + { "ne", 0x00061 }, + { "ne-IN", 0x00861 }, + { "ne-NP", 0x00461 }, + { "nl", 0x00013 }, + { "nl-BE", 0x00813 }, + { "nl-NL", 0x00413 }, + { "nn", 0x07814 }, + { "nn-NO", 0x00814 }, + { "no", 0x00014 }, + { "nso", 0x0006C }, + { "nso-ZA", 0x0046C }, + { "oc", 0x00082 }, + { "oc-FR", 0x00482 }, + { "om", 0x00072 }, + { "om-ET", 0x00472 }, + { "or", 0x00048 }, + { "or-IN", 0x00448 }, + { "pa", 0x00046 }, + { "pa-Arab", 0x07C46 }, + { "pa-Arab-PK", 0x00846 }, + { "pa-IN", 0x00446 }, + { "pap", 0x00079 }, // Doc: reserved + { "pap-029", 0x00479 }, // Doc: reserved + { "pl", 0x00015 }, + { "pl-PL", 0x00415 }, + // { "plt-MG", 0x0048D }, // reserved + { "prs", 0x0008C }, + { "prs-AF", 0x0048C }, + { "ps", 0x00063 }, + { "ps-AF", 0x00463 }, + { "pt", 0x00016 }, + { "pt-BR", 0x00416 }, + { "pt-PT", 0x00816 }, + { "qps-ploc", 0x00501 }, + { "qps-ploca", 0x005FE }, + { "qps-plocm", 0x009FF }, + { "quc", 0x00086 }, // Doc: 0x00093, reserved + // { "quc-CO", 0x00493 }, // reserved + { "quc-Latn-GT", 0x00486 }, // Doc: qut-GT, reserved + { "qut", 0x00086 | LCID_ALT_NAME }, + { "qut-GT", 0x00486 | LCID_ALT_NAME }, // reserved + { "quz", 0x0006B }, + { "quz-BO", 0x0046B }, + { "quz-EC", 0x0086B }, + { "quz-PE", 0x00C6b }, + { "rm", 0x00017 }, + { "rm-CH", 0x00417 }, + { "ro", 0x00018 }, + { "ro-MD", 0x00818 }, + { "ro-RO", 0x00418 }, + { "ru", 0x00019 }, + { "ru-MD", 0x00819 }, + { "ru-RU", 0x00419 }, + { "rw", 0x00087 }, + { "rw-RW", 0x00487 }, + { "sa", 0x0004F }, + { "sa-IN", 0x0044F }, + { "sah", 0x00085 }, + { "sah-RU", 0x00485 }, + { "sd", 0x00059 }, + { "sd-Arab", 0x07C59 }, + { "sd-Arab-PK", 0x00859 }, + { "sd-Deva-IN", 0x00459 }, // Doc: reserved + { "se", 0x0003B }, + { "se-FI", 0x00C3B }, + { "se-NO", 0x0043B }, + { "se-SE", 0x0083B }, + { "si", 0x0005B }, + { "si-LK", 0x0045B }, + { "sk", 0x0001B }, + { "sk-SK", 0x0041B }, + { "sl", 0x00024 }, + { "sl-SI", 0x00424 }, + { "sma", 0x0783B }, + { "sma-NO", 0x0183B }, + { "sma-SE", 0x01C3B }, + { "smj", 0x07C3B }, + { "smj-NO", 0x0103B }, + { "smj-SE", 0x0143B }, + { "smn", 0x0703B }, + { "smn-FI", 0x0243B }, + { "sms", 0x0743B }, + { "sms-FI", 0x0203B }, + { "so", 0x00077 }, // Doc: reserved + { "so-SO", 0x00477 }, + { "sq", 0x0001C }, + { "sq-AL", 0x0041C }, + { "sr", 0x07C1A }, + { "sr-Cyrl", 0x06C1A }, + { "sr-Cyrl-BA", 0x01C1A }, + { "sr-Cyrl-CS", 0x00C1A }, + { "sr-Cyrl-ME", 0x0301A }, + { "sr-Cyrl-RS", 0x0281A }, + { "sr-Latn", 0x0701A }, + { "sr-Latn-BA", 0x0181A }, + { "sr-Latn-CS", 0x0081A }, + { "sr-Latn-ME", 0x02C1A }, + { "sr-Latn-RS", 0x0241A }, + { "st", 0x00030 }, + { "st-ZA", 0x00430 }, + { "sv", 0x0001D }, + { "sv-FI", 0x0081D }, + { "sv-SE", 0x0041D }, + { "sw", 0x00041 }, + { "sw-KE", 0x00441 }, + { "syr", 0x0005A }, + { "syr-SY", 0x0045A }, + { "ta", 0x00049 }, + { "ta-IN", 0x00449 }, + { "ta-LK", 0x00849 }, + // { "tdd-Tale-CN", 0x0048F }, // reserved + { "te", 0x0004A }, + { "te-IN", 0x0044A }, + { "tg", 0x00028 }, + { "tg-Cyrl", 0x07C28 }, + { "tg-Cyrl-TJ", 0x00428 }, + { "th", 0x0001E }, + { "th-TH", 0x0041E }, + { "ti", 0x00073 }, + { "ti-ER", 0x00873 }, + { "ti-ET", 0x00473 }, + { "tk", 0x00042 }, + { "tk-TM", 0x00442 }, + // { "tmz-MA", 0x00C5F }, // reserved + { "tn", 0x00032 }, + { "tn-BW", 0x00832 }, + { "tn-ZA", 0x00432 }, + { "tr", 0x0001F }, + { "tr-TR", 0x0041F }, + { "ts", 0x00031 }, + { "ts-ZA", 0x00431 }, + { "tt", 0x00044 }, + { "tt-RU", 0x00444 }, + { "tzm", 0x0005F }, + { "tzm-Arab-MA", 0x0045F }, + { "tzm-Latn", 0x07C5F }, + { "tzm-Latn-DZ", 0x0085F }, + { "tzm-Tfng", 0x0785F }, + { "tzm-Tfng-MA", 0x0105F }, + { "ug", 0x00080 }, + { "ug-CN", 0x00480 }, + { "uk", 0x00022 }, + { "uk-UA", 0x00422 }, + { "ur", 0x00020 }, + { "ur-IN", 0x00820 }, + { "ur-PK", 0x00420 }, + { "uz", 0x00043 }, + { "uz-Cyrl", 0x07843 }, + { "uz-Cyrl-UZ", 0x00843 }, // Doc: reserved + { "uz-Latn", 0x07C43 }, + { "uz-Latn-UZ", 0x00443 }, + { "ve", 0x00033 }, + { "ve-ZA", 0x00433 }, + { "vi", 0x0002A }, + { "vi-VN", 0x0042A }, + { "wo", 0x00088 }, + { "wo-SN", 0x00488 }, + { "xh", 0x00034 }, + { "xh-ZA", 0x00434 }, + { "yi", 0x0003D }, // Doc: reserved + { "yi-001", 0x0043D }, + { "yo", 0x0006A }, + { "yo-NG", 0x0046A }, + { "zh", 0x07804 }, + { "zh-CN", 0x00804 }, + { "zh-CN_stroke", 0x20804 }, + { "zh-Hans", 0x00004 }, + { "zh-Hant", 0x07C04 }, + { "zh-HK", 0x00C04 }, + { "zh-HK_radstr", 0x40C04 }, + { "zh-MO", 0x01404 }, + { "zh-MO_radstr", 0x41404 }, + { "zh-SG", 0x01004 }, + { "zh-SG_stroke", 0x21004 }, + { "zh-TW", 0x00404 }, + { "zh-TW_pronun", 0x30404 }, + { "zh-TW_radstr", 0x40404 }, + // { "zh-yue-HK", 0x0048E }, // reserved + { "zu", 0x00035 }, + { "zu-ZA", 0x00435 }, +}; + +// This table will be sorted by LCID at runtime +static USHORT RtlpLocaleIndexTable[_ARRAYSIZE(RtlpLocaleTable)]; + +typedef struct _SORT_ENTRY +{ + LCID Lcid; + USHORT Index; +} SORT_ENTRY, *PSORT_ENTRY; + +// Callback function for qsort to sort the SORT_ENTRY table by LCID +static int __cdecl LcidSortEntryCompare(const void* a, const void* b) +{ + PSORT_ENTRY SortEntryA = (PSORT_ENTRY)a; + PSORT_ENTRY SortEntryB = (PSORT_ENTRY)b; + return SortEntryA->Lcid - SortEntryB->Lcid; +} + +// +// Creates a temporary table, that maps the LCIDs to the index in the +// alphabetical table. This table is then sorted by LCID and the indices +// are used to create the final table. +// +NTSTATUS +NTAPI +RtlpInitializeLocaleTable(VOID) +{ + PSORT_ENTRY SortTable; + SIZE_T SortTableSize; + + NtQueryDefaultLocale(TRUE, &RtlpUserDefaultLcid); + NtQueryDefaultLocale(FALSE, &RtlpSystemDefaultLcid); + + SortTableSize = sizeof(SortTable[0]) * ARRAYSIZE(RtlpLocaleTable); + SortTable = RtlAllocateHeap(RtlGetProcessHeap(), 0, SortTableSize); + if (SortTable == NULL) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Copy the LCIDs and the index */ + for (USHORT i = 0; i < ARRAYSIZE(RtlpLocaleTable); i++) + { + SortTable[i].Lcid = RtlpLocaleTable[i].Lcid; + SortTable[i].Index = i; + } + + /* Sort the table by LCID */ + qsort(SortTable, + ARRAYSIZE(RtlpLocaleTable), + sizeof(SortTable[0]), + LcidSortEntryCompare); + + /* Copy the sorted indices to the global table */ + for (USHORT i = 0; i < ARRAYSIZE(RtlpLocaleTable); i++) + { + RtlpLocaleIndexTable[i] = SortTable[i].Index; + } + + RtlFreeHeap(RtlGetProcessHeap(), 0, SortTable); + + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +static +ULONG +FindIndexByLcid( + _In_ LCID Lcid) +{ + USHORT TableIndex; + LONG Low = 0; + LONG High = ARRAYSIZE(RtlpLocaleTable) - 1; + LONG Middle; + LCID CurrentLcid; + + while (Low <= High) + { + Middle = (Low + High) / 2; + + /* Use the indirection table to get the real table entry */ + TableIndex = RtlpLocaleIndexTable[Middle]; + ASSERT(TableIndex < ARRAYSIZE(RtlpLocaleTable)); + + /* Compare the LCID (including the alternative name flag!) */ + CurrentLcid = RtlpLocaleTable[TableIndex].Lcid; + if (CurrentLcid < Lcid) + { + Low = Middle + 1; + } + else if (CurrentLcid > Lcid) + { + High = Middle - 1; + } + else /* CurrentLcid == Lcid */ + { + return RtlpLocaleIndexTable[Middle]; + } + } + + return MAXULONG; +} + +#define LOWERCASE_CHAR(Char) \ + (((Char) >= 'A' && (Char) <= 'Z') ? ((Char) + ('a' - 'A')) : (Char)) + +_Must_inspect_result_ +static +INT +CompareLocaleNames( + _In_ PCSTR AsciiLocaleName, + _In_ PCUNICODE_STRING UnicodeLocaleName) +{ + ULONG i; + + /* Do a case-insensitive comparison. */ + for (i = 0; i < UnicodeLocaleName->Length / sizeof(WCHAR); i++) + { + /* Make the 2 chars lower case for comparison */ + CHAR Char1 = LOWERCASE_CHAR(AsciiLocaleName[i]); + WCHAR Char2 = LOWERCASE_CHAR(UnicodeLocaleName->Buffer[i]); + + /* Keep comparing, while they are equal */ + if (Char1 == Char2) + { + continue; + } + + /* Check if the ASCII string ends first */ + if (AsciiLocaleName[i] == '\0') + { + /* The 1st string is shorter than the 2nd, i.e. smaller */ + return -1; + } + + /* Return the difference between the two characters */ + return (INT)Char1 - (INT)Char2; + } + + /* The strings match up to the lenth of the unicode string. + If the ASCII string ends here, they are equal. */ + if (AsciiLocaleName[i] == '\0') + { + return 0; + } + + /* The 1st string is longer than the 2nd, i.e. larger */ + return 1; +} + +_Must_inspect_result_ +static +ULONG +FindIndexByLocaleName( + _In_ PCUNICODE_STRING LocaleName) +{ + LONG Low = 0; + LONG High = ARRAYSIZE(RtlpLocaleTable) - 1; + LONG Middle; + PCSTR CurrentLocaleName; + INT CompareResult; + + while (Low <= High) + { + Middle = (Low + High) / 2; + + CurrentLocaleName = RtlpLocaleTable[Middle].Locale; + CompareResult = CompareLocaleNames(CurrentLocaleName, LocaleName); + if (CompareResult < 0) + { + Low = Middle + 1; + } + else if (CompareResult > 0) + { + High = Middle - 1; + } + else /* CompareResult == 0 */ + { + return Middle; + } + } + + return MAXULONG; +} + +_Must_inspect_result_ +static +BOOLEAN +CopyAsciizToUnicodeString( + _Inout_ PUNICODE_STRING UnicodeString, + _In_ PCSTR AsciiString) +{ + SIZE_T AsciiLength = strlen(AsciiString); + + /* Make sure we can copy the full string, including the terminating 0 */ + if (UnicodeString->MaximumLength < (AsciiLength + 1) * sizeof(WCHAR)) + { + return FALSE; + } + + /* Copy the string manually */ + for (SIZE_T i = 0; i < AsciiLength; i++) + { + UnicodeString->Buffer[i] = (WCHAR)AsciiString[i]; + } + + /* Add the terminating 0 and update the Length */ + UnicodeString->Buffer[AsciiLength] = UNICODE_NULL; + UnicodeString->Length = (USHORT)(AsciiLength * sizeof(WCHAR)); + + return TRUE; +} + +static +BOOLEAN +IsNeutralLocale( + _In_ LCID Lcid) +{ + /* Check if the LCID is within the neutral locale range */ + if (((Lcid <= MAX_PRIMARY_LANGUAGE) && (Lcid != LOCALE_INVARIANT)) || + (Lcid == 0x0460) /* ks-Arab */ || + ((Lcid & 0xFFFF) > MAX_BASIC_LCID)) + { + return TRUE; + } + + return FALSE; +} + +NTSTATUS +NTAPI +RtlLcidToLocaleName( + _In_ LCID Lcid, + _Inout_ PUNICODE_STRING LocaleName, + _In_ ULONG Flags, + _In_ BOOLEAN AllocateDestinationString) +{ + ULONG LocaleIndex; + + /* Check for invalid flags */ + if (Flags & ~0x2) + { + DPRINT1("RtlLcidToLocaleName: Invalid flags: 0x%lx\n", Flags); + return STATUS_INVALID_PARAMETER_3; + } + + /* Check if the LocaleName buffer is valid */ + if ((LocaleName == NULL) || (LocaleName->Buffer == NULL)) + { + DPRINT1("RtlLcidToLocaleName: Invalid buffer\n"); + return STATUS_INVALID_PARAMETER_2; + } + + /* Validate LCID */ + if (Lcid & ~NLS_VALID_LOCALE_MASK) + { + DPRINT1("RtlLcidToLocaleName: Invalid LCID: 0x%lx\n", Lcid); + return STATUS_INVALID_PARAMETER_1; + } + + /* Check if neutral locales were requested */ + if ((Flags & RTL_LOCALE_ALLOW_NEUTRAL_NAMES) == 0) + { + /* Check if this is a neutral locale */ + if (IsNeutralLocale(Lcid)) + { + DPRINT("RtlLcidToLocaleName: Neutral LCID: 0x%lx\n", Lcid); + return STATUS_INVALID_PARAMETER_1; + } + } + + /* Handle special LCIDs */ + switch (Lcid) + { + case LOCALE_USER_DEFAULT: + Lcid = RtlpUserDefaultLcid; + break; + + case LOCALE_SYSTEM_DEFAULT: + case LOCALE_CUSTOM_DEFAULT: + Lcid = RtlpSystemDefaultLcid; + break; + + case LOCALE_CUSTOM_UI_DEFAULT: + return STATUS_UNSUCCESSFUL; + } + + /* Try to find the locale by LCID */ + LocaleIndex = FindIndexByLcid(Lcid); + if (LocaleIndex == MAXULONG) + { + DPRINT("RtlLcidToLocaleName: LCID 0x%lx not found\n", Lcid); + return STATUS_INVALID_PARAMETER_1; + } + + /* Copy the locale name to the buffer */ + if (!CopyAsciizToUnicodeString(LocaleName, RtlpLocaleTable[LocaleIndex].Locale)) + { + DPRINT("RtlLcidToLocaleName: Buffer too small\n"); + return STATUS_BUFFER_TOO_SMALL; + } + + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +static +NTSTATUS +RtlpLocaleNameToLcidInternal( + _In_ PCUNICODE_STRING LocaleName, + _Out_ PLCID Lcid, + _In_ ULONG Flags) +{ + ULONG LocaleIndex; + LCID FoundLcid; + + /* Check if LocaleName points to a valid unicode string */ + if ((LocaleName == NULL) || (LocaleName->Buffer == NULL)) + { + DPRINT1("RtlpLocaleNameToLcidInternal: Invalid buffer\n"); + return STATUS_INVALID_PARAMETER_1; + } + + /* Check if the Lcid pointer is valid */ + if (Lcid == NULL) + { + DPRINT1("RtlpLocaleNameToLcidInternal: Lcid is NULL\n"); + return STATUS_INVALID_PARAMETER_2; + } + + /* Check for invalid flags */ + if (Flags & ~0x3) + { + DPRINT1("RtlpLocaleNameToLcidInternal: Invalid flags: 0x%lx\n", Flags); + return STATUS_INVALID_PARAMETER_3; + } + + /* Try to find the locale */ + LocaleIndex = FindIndexByLocaleName(LocaleName); + if (LocaleIndex == MAXULONG) + { + DPRINT("RtlpLocaleNameToLcidInternal: Locale name not found\n"); + return STATUS_INVALID_PARAMETER_1; + } + + /* Extract the LCID without the flags */ + FoundLcid = RtlpLocaleTable[LocaleIndex].Lcid & NLS_VALID_LOCALE_MASK; + + /* Check if neutral locales were requested */ + if ((Flags & RTL_LOCALE_ALLOW_NEUTRAL_NAMES) == 0) + { + /* Check if this is a neutral locale */ + if (IsNeutralLocale(FoundLcid)) + { + DPRINT("RtlpLocaleNameToLcidInternal: Neutral LCID: 0x%lx\n", FoundLcid); + return STATUS_INVALID_PARAMETER_1; + } + } + + /* Copy the LCID to the output buffer */ + *Lcid = FoundLcid; + + return STATUS_SUCCESS; +} + + +NTSTATUS +NTAPI +RtlLocaleNameToLcid( + _In_ PCWSTR LocaleName, + _Out_ PLCID Lcid, + _In_ ULONG Flags) +{ + UNICODE_STRING LocaleNameString; + + /* Convert the string to a UNICODE_STRING */ + RtlInitUnicodeString(&LocaleNameString, LocaleName); + + /* Forward to internal function */ + return RtlpLocaleNameToLcidInternal(&LocaleNameString, Lcid, Flags); +} + +_Success_(return != FALSE) +BOOLEAN +NTAPI +RtlLCIDToCultureName( + _In_ LCID Lcid, + _Inout_ PUNICODE_STRING String) +{ + NTSTATUS Status; + + /* Forward to RtlLcidToLocaleName, include neutral names */ + Status = RtlLcidToLocaleName(Lcid, String, RTL_LOCALE_ALLOW_NEUTRAL_NAMES, FALSE); + if (!NT_SUCCESS(Status)) + { + DPRINT1("RtlLcidToLocaleName failed with status 0x%lx\n", Status); + return FALSE; + } + + return TRUE; +} + +_Success_(return != FALSE) +BOOLEAN +NTAPI +RtlCultureNameToLCID( + _In_ PCUNICODE_STRING String, + _Out_ PLCID Lcid) +{ + NTSTATUS Status; + + if ((String == NULL) || + (String->Buffer == NULL) || + (String->Buffer[0] == UNICODE_NULL)) + { + return FALSE; + } + + /* Forward to internal function, include neutral names */ + Status = RtlpLocaleNameToLcidInternal(String, Lcid, RTL_LOCALE_ALLOW_NEUTRAL_NAMES); + if (!NT_SUCCESS(Status)) + { + DPRINT1("RtlpLocaleNameToLcidInternal failed with status 0x%lx\n", Status); + return FALSE; + } + + return TRUE; +} + +BOOLEAN +NTAPI +RtlIsValidLocaleName( + _In_ LPCWSTR LocaleName, + _In_ ULONG Flags) +{ + UNIMPLEMENTED; + return TRUE; +} + +NTSTATUS +NTAPI +RtlConvertLCIDToString( + _In_ LCID LcidValue, + _In_ ULONG Base, + _In_ ULONG Padding, + _Out_writes_(Size) PWSTR pResultBuf, + _In_ ULONG Size) +{ + UNIMPLEMENTED; + return STATUS_NOT_IMPLEMENTED; +}