mirror of
git://source.winehq.org/git/wine.git
synced 2024-09-02 17:03:37 +00:00
![Zhiyi Zhang](/assets/img/avatar_default.png)
Initialize the calendars variable after checking if locale is NULL before using it to avoid NULL pointer references. Fix a Excel 2016 crash when formatting dates with the custom format 'ddd'. It calls EnumDateFormatsExEx() with the 'yi-Hebr' locale, which is added in Win10. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=55099
8150 lines
267 KiB
C
8150 lines
267 KiB
C
/*
|
|
* Locale support
|
|
*
|
|
* Copyright 1995 Martin von Loewis
|
|
* Copyright 1998 David Lee Lambert
|
|
* Copyright 2000 Julio César Gázquez
|
|
* Copyright 2003 Jon Griffiths
|
|
* Copyright 2005 Dmitry Timoshkov
|
|
* Copyright 2002, 2019 Alexandre Julliard
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "ntstatus.h"
|
|
#define WIN32_NO_STATUS
|
|
#define WINNORMALIZEAPI
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winreg.h"
|
|
#include "winnls.h"
|
|
#include "winuser.h"
|
|
#include "winternl.h"
|
|
#include "kernelbase.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(nls);
|
|
|
|
#define CALINFO_MAX_YEAR 2029
|
|
|
|
static HMODULE kernelbase_handle;
|
|
|
|
struct registry_entry
|
|
{
|
|
const WCHAR *value;
|
|
const WCHAR *subkey;
|
|
enum { NOT_CACHED, CACHED, MISSING } status;
|
|
WCHAR data[80];
|
|
};
|
|
|
|
static const WCHAR world_subkey[] = { 0xd83c, 0xdf0e, 0xd83c, 0xdf0f, 0xd83c, 0xdf0d, 0 }; /* 🌎🌏🌍 */
|
|
|
|
static struct registry_entry entry_icalendartype = { L"iCalendarType" };
|
|
static struct registry_entry entry_icountry = { L"iCountry" };
|
|
static struct registry_entry entry_icurrdigits = { L"iCurrDigits" };
|
|
static struct registry_entry entry_icurrency = { L"iCurrency" };
|
|
static struct registry_entry entry_idigits = { L"iDigits" };
|
|
static struct registry_entry entry_idigitsubstitution = { L"NumShape" };
|
|
static struct registry_entry entry_ifirstdayofweek = { L"iFirstDayOfWeek" };
|
|
static struct registry_entry entry_ifirstweekofyear = { L"iFirstWeekOfYear" };
|
|
static struct registry_entry entry_ilzero = { L"iLZero" };
|
|
static struct registry_entry entry_imeasure = { L"iMeasure" };
|
|
static struct registry_entry entry_inegcurr = { L"iNegCurr" };
|
|
static struct registry_entry entry_inegnumber = { L"iNegNumber" };
|
|
static struct registry_entry entry_ipapersize = { L"iPaperSize" };
|
|
static struct registry_entry entry_s1159 = { L"s1159" };
|
|
static struct registry_entry entry_s2359 = { L"s2359" };
|
|
static struct registry_entry entry_scurrency = { L"sCurrency" };
|
|
static struct registry_entry entry_sdecimal = { L"sDecimal" };
|
|
static struct registry_entry entry_sgrouping = { L"sGrouping" };
|
|
static struct registry_entry entry_sintlsymbol = { L"Currencies", world_subkey };
|
|
static struct registry_entry entry_slist = { L"sList" };
|
|
static struct registry_entry entry_slongdate = { L"sLongDate" };
|
|
static struct registry_entry entry_smondecimalsep = { L"sMonDecimalSep" };
|
|
static struct registry_entry entry_smongrouping = { L"sMonGrouping" };
|
|
static struct registry_entry entry_smonthousandsep = { L"sMonThousandSep" };
|
|
static struct registry_entry entry_snativedigits = { L"sNativeDigits" };
|
|
static struct registry_entry entry_snegativesign = { L"sNegativeSign" };
|
|
static struct registry_entry entry_spositivesign = { L"sPositiveSign" };
|
|
static struct registry_entry entry_sshortdate = { L"sShortDate" };
|
|
static struct registry_entry entry_sshorttime = { L"sShortTime" };
|
|
static struct registry_entry entry_sthousand = { L"sThousand" };
|
|
static struct registry_entry entry_stimeformat = { L"sTimeFormat" };
|
|
static struct registry_entry entry_syearmonth = { L"sYearMonth" };
|
|
|
|
|
|
static const struct { UINT cp; const WCHAR *name; } codepage_names[] =
|
|
{
|
|
{ 37, L"IBM EBCDIC US Canada" },
|
|
{ 424, L"IBM EBCDIC Hebrew" },
|
|
{ 437, L"OEM United States" },
|
|
{ 500, L"IBM EBCDIC International" },
|
|
{ 708, L"Arabic ASMO" },
|
|
{ 720, L"Arabic (Transparent ASMO)" },
|
|
{ 737, L"OEM Greek 437G" },
|
|
{ 775, L"OEM Baltic" },
|
|
{ 850, L"OEM Multilingual Latin 1" },
|
|
{ 852, L"OEM Slovak Latin 2" },
|
|
{ 855, L"OEM Cyrillic" },
|
|
{ 856, L"Hebrew PC" },
|
|
{ 857, L"OEM Turkish" },
|
|
{ 860, L"OEM Portuguese" },
|
|
{ 861, L"OEM Icelandic" },
|
|
{ 862, L"OEM Hebrew" },
|
|
{ 863, L"OEM Canadian French" },
|
|
{ 864, L"OEM Arabic" },
|
|
{ 865, L"OEM Nordic" },
|
|
{ 866, L"OEM Russian" },
|
|
{ 869, L"OEM Greek" },
|
|
{ 874, L"ANSI/OEM Thai" },
|
|
{ 875, L"IBM EBCDIC Greek" },
|
|
{ 878, L"Russian KOI8" },
|
|
{ 932, L"ANSI/OEM Japanese Shift-JIS" },
|
|
{ 936, L"ANSI/OEM Simplified Chinese GBK" },
|
|
{ 949, L"ANSI/OEM Korean Unified Hangul" },
|
|
{ 950, L"ANSI/OEM Traditional Chinese Big5" },
|
|
{ 1006, L"IBM Arabic" },
|
|
{ 1026, L"IBM EBCDIC Latin 5 Turkish" },
|
|
{ 1250, L"ANSI Eastern Europe" },
|
|
{ 1251, L"ANSI Cyrillic" },
|
|
{ 1252, L"ANSI Latin 1" },
|
|
{ 1253, L"ANSI Greek" },
|
|
{ 1254, L"ANSI Turkish" },
|
|
{ 1255, L"ANSI Hebrew" },
|
|
{ 1256, L"ANSI Arabic" },
|
|
{ 1257, L"ANSI Baltic" },
|
|
{ 1258, L"ANSI/OEM Viet Nam" },
|
|
{ 1361, L"Korean Johab" },
|
|
{ 10000, L"Mac Roman" },
|
|
{ 10001, L"Mac Japanese" },
|
|
{ 10002, L"Mac Traditional Chinese" },
|
|
{ 10003, L"Mac Korean" },
|
|
{ 10004, L"Mac Arabic" },
|
|
{ 10005, L"Mac Hebrew" },
|
|
{ 10006, L"Mac Greek" },
|
|
{ 10007, L"Mac Cyrillic" },
|
|
{ 10008, L"Mac Simplified Chinese" },
|
|
{ 10010, L"Mac Romanian" },
|
|
{ 10017, L"Mac Ukrainian" },
|
|
{ 10021, L"Mac Thai" },
|
|
{ 10029, L"Mac Latin 2" },
|
|
{ 10079, L"Mac Icelandic" },
|
|
{ 10081, L"Mac Turkish" },
|
|
{ 10082, L"Mac Croatian" },
|
|
{ 20127, L"US-ASCII (7bit)" },
|
|
{ 20866, L"Russian KOI8" },
|
|
{ 20932, L"EUC-JP" },
|
|
{ 20949, L"Korean Wansung" },
|
|
{ 21866, L"Ukrainian KOI8" },
|
|
{ 28591, L"ISO 8859-1 Latin 1" },
|
|
{ 28592, L"ISO 8859-2 Latin 2 (East European)" },
|
|
{ 28593, L"ISO 8859-3 Latin 3 (South European)" },
|
|
{ 28594, L"ISO 8859-4 Latin 4 (Baltic old)" },
|
|
{ 28595, L"ISO 8859-5 Cyrillic" },
|
|
{ 28596, L"ISO 8859-6 Arabic" },
|
|
{ 28597, L"ISO 8859-7 Greek" },
|
|
{ 28598, L"ISO 8859-8 Hebrew" },
|
|
{ 28599, L"ISO 8859-9 Latin 5 (Turkish)" },
|
|
{ 28600, L"ISO 8859-10 Latin 6 (Nordic)" },
|
|
{ 28601, L"ISO 8859-11 Latin (Thai)" },
|
|
{ 28603, L"ISO 8859-13 Latin 7 (Baltic)" },
|
|
{ 28604, L"ISO 8859-14 Latin 8 (Celtic)" },
|
|
{ 28605, L"ISO 8859-15 Latin 9 (Euro)" },
|
|
{ 28606, L"ISO 8859-16 Latin 10 (Balkan)" },
|
|
{ 65000, L"65000 (UTF-7)" },
|
|
{ 65001, L"65001 (UTF-8)" }
|
|
};
|
|
|
|
/* Unicode expanded ligatures */
|
|
static const WCHAR ligatures[][5] =
|
|
{
|
|
{ 0x00c6, 'A','E',0 },
|
|
{ 0x00de, 'T','H',0 },
|
|
{ 0x00df, 's','s',0 },
|
|
{ 0x00e6, 'a','e',0 },
|
|
{ 0x00fe, 't','h',0 },
|
|
{ 0x0132, 'I','J',0 },
|
|
{ 0x0133, 'i','j',0 },
|
|
{ 0x0152, 'O','E',0 },
|
|
{ 0x0153, 'o','e',0 },
|
|
{ 0x01c4, 'D',0x017d,0 },
|
|
{ 0x01c5, 'D',0x017e,0 },
|
|
{ 0x01c6, 'd',0x017e,0 },
|
|
{ 0x01c7, 'L','J',0 },
|
|
{ 0x01c8, 'L','j',0 },
|
|
{ 0x01c9, 'l','j',0 },
|
|
{ 0x01ca, 'N','J',0 },
|
|
{ 0x01cb, 'N','j',0 },
|
|
{ 0x01cc, 'n','j',0 },
|
|
{ 0x01e2, 0x0100,0x0112,0 },
|
|
{ 0x01e3, 0x0101,0x0113,0 },
|
|
{ 0x01f1, 'D','Z',0 },
|
|
{ 0x01f2, 'D','z',0 },
|
|
{ 0x01f3, 'd','z',0 },
|
|
{ 0x01fc, 0x00c1,0x00c9,0 },
|
|
{ 0x01fd, 0x00e1,0x00e9,0 },
|
|
{ 0x05f0, 0x05d5,0x05d5,0 },
|
|
{ 0x05f1, 0x05d5,0x05d9,0 },
|
|
{ 0x05f2, 0x05d9,0x05d9,0 },
|
|
{ 0xfb00, 'f','f',0 },
|
|
{ 0xfb01, 'f','i',0 },
|
|
{ 0xfb02, 'f','l',0 },
|
|
{ 0xfb03, 'f','f','i',0 },
|
|
{ 0xfb04, 'f','f','l',0 },
|
|
{ 0xfb05, 0x017f,'t',0 },
|
|
{ 0xfb06, 's','t',0 },
|
|
};
|
|
|
|
struct calendar
|
|
{
|
|
USHORT icalintvalue; /* 00 */
|
|
USHORT itwodigityearmax; /* 02 */
|
|
UINT sshortdate; /* 04 */
|
|
UINT syearmonth; /* 08 */
|
|
UINT slongdate; /* 0c */
|
|
UINT serastring; /* 10 */
|
|
UINT iyearoffsetrange; /* 14 */
|
|
UINT sdayname; /* 18 */
|
|
UINT sabbrevdayname; /* 1c */
|
|
UINT smonthname; /* 20 */
|
|
UINT sabbrevmonthname; /* 24 */
|
|
UINT scalname; /* 28 */
|
|
UINT smonthday; /* 2c */
|
|
UINT sabbreverastring; /* 30 */
|
|
UINT sshortestdayname; /* 34 */
|
|
UINT srelativelongdate; /* 38 */
|
|
UINT unused[3]; /* 3c */
|
|
};
|
|
|
|
static const struct geo_id
|
|
{
|
|
GEOID id;
|
|
WCHAR latitude[12];
|
|
WCHAR longitude[12];
|
|
GEOCLASS class;
|
|
GEOID parent;
|
|
WCHAR iso2[4];
|
|
WCHAR iso3[4];
|
|
USHORT uncode;
|
|
USHORT dialcode;
|
|
WCHAR currcode[4];
|
|
WCHAR currsymbol[8];
|
|
} *geo_ids;
|
|
|
|
static const struct geo_index
|
|
{
|
|
WCHAR name[4];
|
|
UINT idx;
|
|
} *geo_index;
|
|
|
|
static unsigned int geo_ids_count;
|
|
static unsigned int geo_index_count;
|
|
|
|
enum charmaps
|
|
{
|
|
CHARMAP_FOLDDIGITS,
|
|
CHARMAP_COMPAT,
|
|
CHARMAP_HIRAGANA,
|
|
CHARMAP_KATAKANA,
|
|
CHARMAP_HALFWIDTH,
|
|
CHARMAP_FULLWIDTH,
|
|
CHARMAP_TRADITIONAL,
|
|
CHARMAP_SIMPLIFIED,
|
|
NB_CHARMAPS
|
|
};
|
|
|
|
static const USHORT *charmaps[NB_CHARMAPS];
|
|
|
|
/* NLS normalization file */
|
|
struct norm_table
|
|
{
|
|
WCHAR name[13]; /* 00 file name */
|
|
USHORT checksum[3]; /* 1a checksum? */
|
|
USHORT version[4]; /* 20 Unicode version */
|
|
USHORT form; /* 28 normalization form */
|
|
USHORT len_factor; /* 2a factor for length estimates */
|
|
USHORT unknown1; /* 2c */
|
|
USHORT decomp_size; /* 2e decomposition hash size */
|
|
USHORT comp_size; /* 30 composition hash size */
|
|
USHORT unknown2; /* 32 */
|
|
USHORT classes; /* 34 combining classes table offset */
|
|
USHORT props_level1; /* 36 char properties table level 1 offset */
|
|
USHORT props_level2; /* 38 char properties table level 2 offset */
|
|
USHORT decomp_hash; /* 3a decomposition hash table offset */
|
|
USHORT decomp_map; /* 3c decomposition character map table offset */
|
|
USHORT decomp_seq; /* 3e decomposition character sequences offset */
|
|
USHORT comp_hash; /* 40 composition hash table offset */
|
|
USHORT comp_seq; /* 42 composition character sequences offset */
|
|
/* BYTE[] combining class values */
|
|
/* BYTE[0x2200] char properties index level 1 */
|
|
/* BYTE[] char properties index level 2 */
|
|
/* WORD[] decomposition hash table */
|
|
/* WORD[] decomposition character map */
|
|
/* WORD[] decomposition character sequences */
|
|
/* WORD[] composition hash table */
|
|
/* WORD[] composition character sequences */
|
|
};
|
|
|
|
static CPTABLEINFO ansi_cpinfo;
|
|
static CPTABLEINFO oem_cpinfo;
|
|
static UINT unix_cp = CP_UTF8;
|
|
static LCID system_lcid;
|
|
static LCID user_lcid;
|
|
static HKEY intl_key;
|
|
static HKEY nls_key;
|
|
static HKEY tz_key;
|
|
static const NLS_LOCALE_LCID_INDEX *lcids_index;
|
|
static const NLS_LOCALE_LCNAME_INDEX *lcnames_index;
|
|
static const NLS_LOCALE_HEADER *locale_table;
|
|
static const WCHAR *locale_strings;
|
|
static const NLS_LOCALE_DATA *system_locale;
|
|
static const NLS_LOCALE_DATA *user_locale;
|
|
|
|
static CPTABLEINFO codepages[128];
|
|
static unsigned int nb_codepages;
|
|
|
|
static struct norm_table *norm_info;
|
|
|
|
struct sortguid
|
|
{
|
|
GUID id; /* sort GUID */
|
|
UINT flags; /* flags */
|
|
UINT compr; /* offset to compression table */
|
|
UINT except; /* exception table offset in sortkey table */
|
|
UINT ling_except; /* exception table offset for linguistic casing */
|
|
UINT casemap; /* linguistic casemap table offset */
|
|
};
|
|
|
|
/* flags for sortguid */
|
|
#define FLAG_HAS_3_BYTE_WEIGHTS 0x01
|
|
#define FLAG_REVERSEDIACRITICS 0x10
|
|
#define FLAG_DOUBLECOMPRESSION 0x20
|
|
#define FLAG_INVERSECASING 0x40
|
|
|
|
struct sort_expansion
|
|
{
|
|
WCHAR exp[2];
|
|
};
|
|
|
|
struct jamo_sort
|
|
{
|
|
BYTE is_old;
|
|
BYTE leading;
|
|
BYTE vowel;
|
|
BYTE trailing;
|
|
BYTE weight;
|
|
BYTE pad[3];
|
|
};
|
|
|
|
struct sort_compression
|
|
{
|
|
UINT offset;
|
|
WCHAR minchar, maxchar;
|
|
WORD len[8];
|
|
};
|
|
|
|
static inline int compression_size( int len ) { return 2 + len + (len & 1); }
|
|
|
|
union char_weights
|
|
{
|
|
UINT val;
|
|
struct { BYTE primary, script, diacritic, _case; };
|
|
};
|
|
|
|
/* bits for case weights */
|
|
#define CASE_FULLWIDTH 0x01 /* full width kana (vs. half width) */
|
|
#define CASE_FULLSIZE 0x02 /* full size kana (vs. small) */
|
|
#define CASE_SUBSCRIPT 0x08 /* sub/super script */
|
|
#define CASE_UPPER 0x10 /* upper case */
|
|
#define CASE_KATAKANA 0x20 /* katakana (vs. hiragana) */
|
|
#define CASE_COMPR_2 0x40 /* compression exists for >= 2 chars */
|
|
#define CASE_COMPR_4 0x80 /* compression exists for >= 4 chars */
|
|
#define CASE_COMPR_6 0xc0 /* compression exists for >= 6 chars */
|
|
|
|
enum sortkey_script
|
|
{
|
|
SCRIPT_UNSORTABLE = 0,
|
|
SCRIPT_NONSPACE_MARK = 1,
|
|
SCRIPT_EXPANSION = 2,
|
|
SCRIPT_EASTASIA_SPECIAL = 3,
|
|
SCRIPT_JAMO_SPECIAL = 4,
|
|
SCRIPT_EXTENSION_A = 5,
|
|
SCRIPT_PUNCTUATION = 6,
|
|
SCRIPT_SYMBOL_1 = 7,
|
|
SCRIPT_SYMBOL_2 = 8,
|
|
SCRIPT_SYMBOL_3 = 9,
|
|
SCRIPT_SYMBOL_4 = 10,
|
|
SCRIPT_SYMBOL_5 = 11,
|
|
SCRIPT_SYMBOL_6 = 12,
|
|
SCRIPT_DIGIT = 13,
|
|
SCRIPT_LATIN = 14,
|
|
SCRIPT_GREEK = 15,
|
|
SCRIPT_CYRILLIC = 16,
|
|
SCRIPT_KANA = 34,
|
|
SCRIPT_HEBREW = 40,
|
|
SCRIPT_ARABIC = 41,
|
|
SCRIPT_PUA_FIRST = 169,
|
|
SCRIPT_PUA_LAST = 175,
|
|
SCRIPT_CJK_FIRST = 192,
|
|
SCRIPT_CJK_LAST = 239,
|
|
};
|
|
|
|
static const struct sortguid **locale_sorts;
|
|
static const struct sortguid *current_locale_sort;
|
|
|
|
static struct
|
|
{
|
|
UINT version; /* NLS version */
|
|
UINT guid_count; /* number of sort GUIDs */
|
|
UINT exp_count; /* number of character expansions */
|
|
UINT compr_count; /* number of compression tables */
|
|
const UINT *keys; /* sortkey table, indexed by char */
|
|
const USHORT *casemap; /* casemap table, in l_intl.nls format */
|
|
const WORD *ctypes; /* CT_CTYPE1,2,3 values */
|
|
const BYTE *ctype_idx; /* index to map char to ctypes array entry */
|
|
const struct sortguid *guids; /* table of sort GUIDs */
|
|
const struct sort_expansion *expansions; /* character expansions */
|
|
const struct sort_compression *compressions; /* character compression tables */
|
|
const WCHAR *compr_data; /* data for individual compressions */
|
|
const struct jamo_sort *jamo; /* table for Jamo compositions */
|
|
} sort;
|
|
|
|
static CRITICAL_SECTION locale_section;
|
|
static CRITICAL_SECTION_DEBUG critsect_debug =
|
|
{
|
|
0, 0, &locale_section,
|
|
{ &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
|
|
0, 0, { (DWORD_PTR)(__FILE__ ": locale_section") }
|
|
};
|
|
static CRITICAL_SECTION locale_section = { &critsect_debug, -1, 0, 0, 0, 0 };
|
|
|
|
|
|
static void load_locale_nls(void)
|
|
{
|
|
struct
|
|
{
|
|
UINT ctypes;
|
|
UINT unknown1;
|
|
UINT unknown2;
|
|
UINT unknown3;
|
|
UINT locales;
|
|
UINT charmaps;
|
|
UINT geoids;
|
|
UINT scripts;
|
|
} *header;
|
|
struct geo_header
|
|
{
|
|
WCHAR signature[4]; /* L"geo" */
|
|
UINT total_size;
|
|
UINT ids_offset;
|
|
UINT ids_count;
|
|
UINT index_offset;
|
|
UINT index_count;
|
|
} *geo_header;
|
|
|
|
LARGE_INTEGER dummy;
|
|
const USHORT *map_ptr;
|
|
unsigned int i;
|
|
|
|
RtlGetLocaleFileMappingAddress( (void **)&header, &system_lcid, &dummy );
|
|
locale_table = (const NLS_LOCALE_HEADER *)((char *)header + header->locales);
|
|
lcids_index = (const NLS_LOCALE_LCID_INDEX *)((char *)locale_table + locale_table->lcids_offset);
|
|
lcnames_index = (const NLS_LOCALE_LCNAME_INDEX *)((char *)locale_table + locale_table->lcnames_offset);
|
|
locale_strings = (const WCHAR *)((char *)locale_table + locale_table->strings_offset);
|
|
geo_header = (struct geo_header *)((char *)header + header->geoids);
|
|
geo_ids = (const struct geo_id *)((char *)geo_header + geo_header->ids_offset);
|
|
geo_index = (const struct geo_index *)((char *)geo_header + geo_header->index_offset);
|
|
geo_ids_count = geo_header->ids_count;
|
|
geo_index_count = geo_header->index_count;
|
|
map_ptr = (const USHORT *)((char *)header + header->charmaps);
|
|
for (i = 0; i < NB_CHARMAPS; i++, map_ptr += *map_ptr) charmaps[i] = map_ptr + 1;
|
|
}
|
|
|
|
|
|
static void load_sortdefault_nls(void)
|
|
{
|
|
const struct
|
|
{
|
|
UINT sortkeys;
|
|
UINT casemaps;
|
|
UINT ctypes;
|
|
UINT sortids;
|
|
} *header;
|
|
|
|
const WORD *ctype;
|
|
const UINT *table;
|
|
UINT i;
|
|
SIZE_T size;
|
|
const struct sort_compression *last_compr;
|
|
|
|
NtGetNlsSectionPtr( 9, 0, NULL, (void **)&header, &size );
|
|
|
|
sort.keys = (UINT *)((char *)header + header->sortkeys);
|
|
sort.casemap = (USHORT *)((char *)header + header->casemaps);
|
|
|
|
ctype = (WORD *)((char *)header + header->ctypes);
|
|
sort.ctypes = ctype + 2;
|
|
sort.ctype_idx = (BYTE *)ctype + ctype[1] + 2;
|
|
|
|
table = (UINT *)((char *)header + header->sortids);
|
|
sort.version = table[0];
|
|
sort.guid_count = table[1];
|
|
sort.guids = (struct sortguid *)(table + 2);
|
|
|
|
table = (UINT *)(sort.guids + sort.guid_count);
|
|
sort.exp_count = table[0];
|
|
sort.expansions = (struct sort_expansion *)(table + 1);
|
|
|
|
table = (UINT *)(sort.expansions + sort.exp_count);
|
|
sort.compr_count = table[0];
|
|
sort.compressions = (struct sort_compression *)(table + 1);
|
|
sort.compr_data = (WCHAR *)(sort.compressions + sort.compr_count);
|
|
|
|
last_compr = sort.compressions + sort.compr_count - 1;
|
|
table = (UINT *)(sort.compr_data + last_compr->offset);
|
|
for (i = 0; i < 7; i++) table += last_compr->len[i] * ((i + 5) / 2);
|
|
table += 1 + table[0] / 2; /* skip multiple weights */
|
|
sort.jamo = (struct jamo_sort *)(table + 1);
|
|
|
|
locale_sorts = RtlAllocateHeap( GetProcessHeap(), HEAP_ZERO_MEMORY,
|
|
locale_table->nb_lcnames * sizeof(*locale_sorts) );
|
|
}
|
|
|
|
|
|
static const struct sortguid *find_sortguid( const GUID *guid )
|
|
{
|
|
int pos, ret, min = 0, max = sort.guid_count - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
pos = (min + max) / 2;
|
|
ret = memcmp( guid, &sort.guids[pos].id, sizeof(*guid) );
|
|
if (!ret) return &sort.guids[pos];
|
|
if (ret > 0) min = pos + 1;
|
|
else max = pos - 1;
|
|
}
|
|
ERR( "no sort found for %s\n", debugstr_guid( guid ));
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const NLS_LOCALE_DATA *get_locale_data( UINT idx )
|
|
{
|
|
ULONG offset = locale_table->locales_offset + idx * locale_table->locale_size;
|
|
return (const NLS_LOCALE_DATA *)((const char *)locale_table + offset);
|
|
}
|
|
|
|
|
|
static const struct calendar *get_calendar_data( const NLS_LOCALE_DATA *locale, UINT id )
|
|
{
|
|
if (id == CAL_HIJRI) id = locale->islamic_cal[0];
|
|
else if (id == CAL_PERSIAN) id = locale->islamic_cal[1];
|
|
|
|
if (!id || id > locale_table->nb_calendars) return NULL;
|
|
return (const struct calendar *)((const char *)locale_table + locale_table->calendars_offset +
|
|
(id - 1) * locale_table->calendar_size);
|
|
}
|
|
|
|
|
|
static int compare_locale_names( const WCHAR *n1, const WCHAR *n2 )
|
|
{
|
|
for (;;)
|
|
{
|
|
WCHAR ch1 = *n1++;
|
|
WCHAR ch2 = *n2++;
|
|
if (ch1 >= 'a' && ch1 <= 'z') ch1 -= 'a' - 'A';
|
|
else if (ch1 == '_') ch1 = '-';
|
|
if (ch2 >= 'a' && ch2 <= 'z') ch2 -= 'a' - 'A';
|
|
else if (ch2 == '_') ch2 = '-';
|
|
if (!ch1 || ch1 != ch2) return ch1 - ch2;
|
|
}
|
|
}
|
|
|
|
|
|
static const NLS_LOCALE_LCNAME_INDEX *find_lcname_entry( const WCHAR *name )
|
|
{
|
|
int min = 0, max = locale_table->nb_lcnames - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int res, pos = (min + max) / 2;
|
|
const WCHAR *str = locale_strings + lcnames_index[pos].name;
|
|
res = compare_locale_names( name, str + 1 );
|
|
if (res < 0) max = pos - 1;
|
|
else if (res > 0) min = pos + 1;
|
|
else return &lcnames_index[pos];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const NLS_LOCALE_LCID_INDEX *find_lcid_entry( LCID lcid )
|
|
{
|
|
int min = 0, max = locale_table->nb_lcids - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int pos = (min + max) / 2;
|
|
if (lcid < lcids_index[pos].id) max = pos - 1;
|
|
else if (lcid > lcids_index[pos].id) min = pos + 1;
|
|
else return &lcids_index[pos];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const struct geo_id *find_geo_id_entry( GEOID id )
|
|
{
|
|
int min = 0, max = geo_ids_count - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int pos = (min + max) / 2;
|
|
if (id < geo_ids[pos].id) max = pos - 1;
|
|
else if (id > geo_ids[pos].id) min = pos + 1;
|
|
else return &geo_ids[pos];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const struct geo_id *find_geo_name_entry( const WCHAR *name )
|
|
{
|
|
int min = 0, max = geo_index_count - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int res, pos = (min + max) / 2;
|
|
res = wcsicmp( name, geo_index[pos].name );
|
|
if (res < 0) max = pos - 1;
|
|
else if (res > 0) min = pos + 1;
|
|
else return &geo_ids[geo_index[pos].idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const NLS_LOCALE_DATA *get_locale_by_name( const WCHAR *name, LCID *lcid )
|
|
{
|
|
const NLS_LOCALE_LCNAME_INDEX *entry;
|
|
|
|
if (name == LOCALE_NAME_USER_DEFAULT)
|
|
{
|
|
*lcid = user_lcid;
|
|
return user_locale;
|
|
}
|
|
if (!(entry = find_lcname_entry( name ))) return NULL;
|
|
*lcid = entry->id;
|
|
return get_locale_data( entry->idx );
|
|
}
|
|
|
|
|
|
static const struct sortguid *get_language_sort( const WCHAR *name )
|
|
{
|
|
const NLS_LOCALE_LCNAME_INDEX *entry;
|
|
const NLS_LOCALE_DATA *locale;
|
|
WCHAR guidstr[39];
|
|
const struct sortguid *ret;
|
|
UNICODE_STRING str;
|
|
LCID lcid;
|
|
GUID guid;
|
|
HKEY key = 0;
|
|
DWORD size, type;
|
|
|
|
if (name == LOCALE_NAME_USER_DEFAULT)
|
|
{
|
|
if (current_locale_sort) return current_locale_sort;
|
|
name = locale_strings + user_locale->sname + 1;
|
|
}
|
|
|
|
if (!(entry = find_lcname_entry( name )))
|
|
{
|
|
WARN( "unknown locale %s\n", debugstr_w(name) );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return NULL;
|
|
}
|
|
if ((ret = locale_sorts[entry - lcnames_index])) return ret;
|
|
|
|
lcid = entry->id;
|
|
name = locale_strings + entry->name + 1;
|
|
locale = get_locale_data( entry->idx );
|
|
if (!RegOpenKeyExW( nls_key, L"Sorting\\Ids", 0, KEY_READ, &key ))
|
|
{
|
|
for (;;)
|
|
{
|
|
size = sizeof(guidstr);
|
|
if (!RegQueryValueExW( key, name, NULL, &type, (BYTE *)guidstr, &size ) && type == REG_SZ)
|
|
{
|
|
RtlInitUnicodeString( &str, guidstr );
|
|
if (!RtlGUIDFromString( &str, &guid )) ret = find_sortguid( &guid );
|
|
break;
|
|
}
|
|
if (!name[0]) break;
|
|
name = locale_strings + (SORTIDFROMLCID( lcid ) ? locale->sname : locale->sparent) + 1;
|
|
if (!(locale = get_locale_by_name( name, &lcid ))) break;
|
|
}
|
|
RegCloseKey( key );
|
|
}
|
|
if (!ret) ret = &sort.guids[0];
|
|
locale_sorts[entry - lcnames_index] = ret;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* NlsValidateLocale (kernelbase.@)
|
|
*
|
|
* Note: it seems to return some internal data on Windows, we simply return the locale.nls data pointer.
|
|
*/
|
|
const NLS_LOCALE_DATA * WINAPI NlsValidateLocale( LCID *lcid, ULONG flags )
|
|
{
|
|
const NLS_LOCALE_LCNAME_INDEX *name_entry;
|
|
const NLS_LOCALE_LCID_INDEX *entry;
|
|
const NLS_LOCALE_DATA *locale;
|
|
|
|
switch (*lcid)
|
|
{
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
*lcid = system_lcid;
|
|
return system_locale;
|
|
case LOCALE_NEUTRAL:
|
|
case LOCALE_USER_DEFAULT:
|
|
case LOCALE_CUSTOM_DEFAULT:
|
|
case LOCALE_CUSTOM_UNSPECIFIED:
|
|
case LOCALE_CUSTOM_UI_DEFAULT:
|
|
*lcid = user_lcid;
|
|
return user_locale;
|
|
default:
|
|
if (!(entry = find_lcid_entry( *lcid ))) return NULL;
|
|
locale = get_locale_data( entry->idx );
|
|
if ((flags & LOCALE_ALLOW_NEUTRAL_NAMES) || locale->inotneutral) return locale;
|
|
if ((name_entry = find_lcname_entry( locale_strings + locale->ssortlocale + 1 )))
|
|
locale = get_locale_data( name_entry->idx );
|
|
return locale;
|
|
}
|
|
}
|
|
|
|
|
|
static int locale_return_data( const WCHAR *data, int datalen, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
if (type & LOCALE_RETURN_NUMBER)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (!len) return datalen;
|
|
if (datalen > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
memcpy( buffer, data, datalen * sizeof(WCHAR) );
|
|
return datalen;
|
|
}
|
|
|
|
|
|
static BOOL set_registry_entry( struct registry_entry *entry, const WCHAR *data )
|
|
{
|
|
DWORD size = (wcslen(data) + 1) * sizeof(WCHAR);
|
|
LSTATUS ret;
|
|
|
|
if (size > sizeof(entry->data))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
TRACE( "setting %s to %s\n", debugstr_w(entry->value), debugstr_w(data) );
|
|
|
|
RtlEnterCriticalSection( &locale_section );
|
|
if (!(ret = RegSetKeyValueW( intl_key, entry->subkey, entry->value, REG_SZ, (BYTE *)data, size )))
|
|
{
|
|
wcscpy( entry->data, data );
|
|
entry->status = CACHED;
|
|
}
|
|
RtlLeaveCriticalSection( &locale_section );
|
|
if (ret) SetLastError( ret );
|
|
return !ret;
|
|
}
|
|
|
|
|
|
static int locale_return_reg_string( struct registry_entry *entry, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
DWORD size;
|
|
LRESULT res;
|
|
int ret = -1;
|
|
|
|
if (type & LOCALE_NOUSEROVERRIDE) return -1;
|
|
|
|
RtlEnterCriticalSection( &locale_section );
|
|
switch (entry->status)
|
|
{
|
|
case NOT_CACHED:
|
|
size = sizeof(entry->data);
|
|
if (entry->subkey)
|
|
{
|
|
HKEY key;
|
|
if (!(res = RegOpenKeyExW( intl_key, entry->subkey, 0, KEY_READ, &key )))
|
|
{
|
|
res = RegQueryValueExW( key, entry->value, NULL, NULL, (BYTE *)entry->data, &size );
|
|
RegCloseKey( key );
|
|
}
|
|
}
|
|
else res = RegQueryValueExW( intl_key, entry->value, NULL, NULL, (BYTE *)entry->data, &size );
|
|
|
|
if (res)
|
|
{
|
|
entry->status = MISSING;
|
|
break;
|
|
}
|
|
entry->status = CACHED;
|
|
/* fall through */
|
|
case CACHED:
|
|
ret = locale_return_data( entry->data, wcslen(entry->data) + 1, type, buffer, len );
|
|
break;
|
|
case MISSING:
|
|
break;
|
|
}
|
|
RtlLeaveCriticalSection( &locale_section );
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int locale_return_string( DWORD pos, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
return locale_return_data( locale_strings + pos + 1, locale_strings[pos] + 1, type, buffer, len );
|
|
}
|
|
|
|
|
|
static int locale_return_number( UINT val, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
int ret;
|
|
WCHAR tmp[80];
|
|
|
|
if (!(type & LOCALE_RETURN_NUMBER))
|
|
{
|
|
switch (LOWORD(type))
|
|
{
|
|
case LOCALE_ILANGUAGE:
|
|
case LOCALE_IDEFAULTLANGUAGE:
|
|
ret = swprintf( tmp, ARRAY_SIZE(tmp), L"%04x", val ) + 1;
|
|
break;
|
|
case LOCALE_IDEFAULTEBCDICCODEPAGE:
|
|
ret = swprintf( tmp, ARRAY_SIZE(tmp), L"%03u", val ) + 1;
|
|
break;
|
|
default:
|
|
ret = swprintf( tmp, ARRAY_SIZE(tmp), L"%u", val ) + 1;
|
|
break;
|
|
}
|
|
}
|
|
else ret = sizeof(UINT) / sizeof(WCHAR);
|
|
|
|
if (!len) return ret;
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
|
|
if (type & LOCALE_RETURN_NUMBER) memcpy( buffer, &val, sizeof(val) );
|
|
else wcscpy( buffer, tmp );
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int locale_return_reg_number( struct registry_entry *entry, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
int ret, val;
|
|
WCHAR *end, tmp[80];
|
|
|
|
if (type & LOCALE_RETURN_NUMBER)
|
|
{
|
|
ret = locale_return_reg_string( entry, type & ~LOCALE_RETURN_NUMBER, tmp, ARRAY_SIZE( tmp ));
|
|
if (ret == -1) return ret;
|
|
val = wcstol( tmp, &end, 10 );
|
|
if (*end) /* invalid number */
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
return locale_return_number( val, type, buffer, len );
|
|
}
|
|
return locale_return_reg_string( entry, type, buffer, len );
|
|
}
|
|
|
|
|
|
static int locale_return_grouping( DWORD pos, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
WORD i, count = locale_strings[pos];
|
|
const WCHAR *str = locale_strings + pos + 1;
|
|
int ret;
|
|
|
|
if (type & LOCALE_RETURN_NUMBER)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
ret = 2 * count;
|
|
if (str[count - 1]) ret += 2; /* for final zero */
|
|
|
|
if (!len) return ret;
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (!str[i]) /* explicit null termination */
|
|
{
|
|
buffer[-1] = 0;
|
|
return ret;
|
|
}
|
|
*buffer++ = '0' + str[i];
|
|
*buffer++ = ';';
|
|
}
|
|
*buffer++ = '0';
|
|
*buffer = 0;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int locale_return_strarray( DWORD pos, WORD idx, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
const DWORD *array = (const DWORD *)(locale_strings + pos + 1);
|
|
WORD count = locale_strings[pos];
|
|
|
|
return locale_return_string( idx < count ? array[idx] : 0, type, buffer, len );
|
|
}
|
|
|
|
|
|
static int locale_return_strarray_concat( DWORD pos, LCTYPE type, WCHAR *buffer, int len )
|
|
{
|
|
WORD i, count = locale_strings[pos];
|
|
const DWORD *array = (const DWORD *)(locale_strings + pos + 1);
|
|
int ret;
|
|
|
|
if (type & LOCALE_RETURN_NUMBER)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
for (i = 0, ret = 1; i < count; i++) ret += locale_strings[array[i]];
|
|
|
|
if (!len) return ret;
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
memcpy( buffer, locale_strings + array[i] + 1, locale_strings[array[i]] * sizeof(WCHAR) );
|
|
buffer += locale_strings[array[i]];
|
|
}
|
|
*buffer = 0;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int cal_return_number( UINT val, CALTYPE type, WCHAR *buffer, int len, DWORD *value )
|
|
{
|
|
WCHAR tmp[12];
|
|
int ret;
|
|
|
|
if (type & CAL_RETURN_NUMBER)
|
|
{
|
|
*value = val;
|
|
return sizeof(UINT) / sizeof(WCHAR);
|
|
}
|
|
ret = swprintf( tmp, ARRAY_SIZE(tmp), L"%u", val );
|
|
return locale_return_data( tmp, ret + 1, 0, buffer, len );
|
|
}
|
|
|
|
|
|
/* find the first format char in a format string */
|
|
static WCHAR *find_format( WCHAR *str, const WCHAR *accept )
|
|
{
|
|
for ( ; *str; str++)
|
|
{
|
|
if (*str == '\'')
|
|
{
|
|
if (!(str = wcschr( str + 1, '\'' ))) return NULL;
|
|
}
|
|
else if (wcschr( accept, *str ))
|
|
{
|
|
/* ignore "ddd" and "dddd" */
|
|
if (str[0] != 'd' || str[1] != 'd' || str[2] != 'd') return str;
|
|
str += 2;
|
|
while (str[1] == 'd') str++;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* replace the separator in a date/time format string */
|
|
static WCHAR *locale_replace_separator( WCHAR *buffer, const WCHAR *sep )
|
|
{
|
|
UINT pos = 0;
|
|
WCHAR res[80];
|
|
WCHAR *next, *str = find_format( buffer, L"dMyHhms" );
|
|
|
|
if (!str) return buffer;
|
|
pos = str - buffer;
|
|
memcpy( res, buffer, pos * sizeof(WCHAR) );
|
|
for (;;)
|
|
{
|
|
res[pos++] = *str++;
|
|
while (str[0] == str[-1]) res[pos++] = *str++; /* copy repeated chars */
|
|
if (!(next = find_format( str, L"dMyHhms" ))) break;
|
|
wcscpy( res + pos, sep );
|
|
pos += wcslen(sep);
|
|
str = next;
|
|
}
|
|
wcscpy( res + pos, str );
|
|
return wcscpy( buffer, res );
|
|
}
|
|
|
|
|
|
/* FIXME: hardcoded, sortname is apparently not available in locale.nls */
|
|
static const WCHAR *get_locale_sortname( LCID lcid )
|
|
{
|
|
switch (PRIMARYLANGID( lcid ))
|
|
{
|
|
case LANG_CHINESE:
|
|
switch (SORTIDFROMLCID( lcid ))
|
|
{
|
|
case SORT_CHINESE_PRCP:
|
|
switch (SUBLANGID( lcid ))
|
|
{
|
|
case SUBLANG_CHINESE_TRADITIONAL:
|
|
case SUBLANG_CHINESE_HONGKONG:
|
|
case 0x1f:
|
|
return L"Stroke Count";
|
|
default:
|
|
return L"Pronunciation";
|
|
}
|
|
case SORT_CHINESE_UNICODE: return L"Unicode";
|
|
case SORT_CHINESE_PRC: return L"Stroke Count";
|
|
case SORT_CHINESE_BOPOMOFO: return L"Bopomofo";
|
|
case SORT_CHINESE_RADICALSTROKE: return L"Radical/Stroke";
|
|
case 5: return L"Surname";
|
|
}
|
|
break;
|
|
|
|
case LANG_GEORGIAN:
|
|
if (SORTIDFROMLCID( lcid ) == SORT_GEORGIAN_MODERN) return L"Modern";
|
|
return L"Traditional";
|
|
|
|
case LANG_GERMAN:
|
|
switch (SUBLANGID( lcid ))
|
|
{
|
|
case SUBLANG_NEUTRAL:
|
|
case SUBLANG_DEFAULT:
|
|
if (SORTIDFROMLCID( lcid ) == SORT_GERMAN_PHONE_BOOK) return L"Phone Book (DIN)";
|
|
return L"Dictionary";
|
|
}
|
|
break;
|
|
|
|
case LANG_HUNGARIAN:
|
|
if (SORTIDFROMLCID( lcid ) == SORT_HUNGARIAN_TECHNICAL) return L"Technical";
|
|
break;
|
|
|
|
case LANG_INVARIANT:
|
|
if (SORTIDFROMLCID( lcid ) == SORT_INVARIANT_MATH) return L"Default";
|
|
return L"Maths Alphanumerics";
|
|
|
|
case LANG_JAPANESE:
|
|
switch (SORTIDFROMLCID( lcid ))
|
|
{
|
|
case SORT_JAPANESE_XJIS: return L"XJIS";
|
|
case SORT_JAPANESE_UNICODE: return L"Unicode";
|
|
case SORT_JAPANESE_RADICALSTROKE: return L"Radical/Stroke";
|
|
}
|
|
break;
|
|
|
|
case LANG_KOREAN:
|
|
if (SORTIDFROMLCID( lcid ) == SORT_KOREAN_UNICODE) return L"Unicode";
|
|
return L"Dictionary";
|
|
|
|
case LANG_SPANISH:
|
|
switch (SUBLANGID( lcid ))
|
|
{
|
|
case SUBLANG_NEUTRAL:
|
|
case SUBLANG_SPANISH_MODERN:
|
|
return L"International";
|
|
case SUBLANG_DEFAULT:
|
|
return L"Traditional";
|
|
}
|
|
break;
|
|
}
|
|
return L"Default";
|
|
}
|
|
|
|
|
|
/* get locale information from the locale.nls file */
|
|
static int get_locale_info( const NLS_LOCALE_DATA *locale, LCID lcid, LCTYPE type,
|
|
WCHAR *buffer, int len )
|
|
{
|
|
static const WCHAR spermille[] = { 0x2030, 0 }; /* this one seems hardcoded */
|
|
static const BYTE ipossignposn[] = { 3, 3, 4, 2, 1, 1, 3, 4, 1, 3, 4, 2, 4, 3, 3, 1 };
|
|
static const BYTE inegsignposn[] = { 0, 3, 4, 2, 0, 1, 3, 4, 1, 3, 4, 2, 4, 3, 0, 0 };
|
|
static const BYTE inegsymprecedes[] = { 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, };
|
|
const WCHAR *sort;
|
|
WCHAR *str, *end, tmp[80];
|
|
UINT val;
|
|
int ret;
|
|
|
|
if (locale != user_locale) type |= LOCALE_NOUSEROVERRIDE;
|
|
|
|
switch (LOWORD(type))
|
|
{
|
|
case LOCALE_ILANGUAGE:
|
|
/* return default language for neutral locales */
|
|
val = locale->inotneutral ? locale->ilanguage : locale->idefaultlanguage;
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_SLOCALIZEDDISPLAYNAME:
|
|
/* FIXME: localization */
|
|
return locale_return_string( locale->sengdisplayname, type, buffer, len );
|
|
|
|
case LOCALE_SABBREVLANGNAME:
|
|
return locale_return_string( locale->sabbrevlangname, type, buffer, len );
|
|
|
|
case LOCALE_SNATIVELANGNAME:
|
|
return locale_return_string( locale->snativelangname, type, buffer, len );
|
|
|
|
case LOCALE_ICOUNTRY:
|
|
if ((ret = locale_return_reg_number( &entry_icountry, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->icountry, type, buffer, len );
|
|
|
|
case LOCALE_SLOCALIZEDCOUNTRYNAME:
|
|
/* FIXME: localization */
|
|
return locale_return_string( locale->sengcountry, type, buffer, len );
|
|
|
|
case LOCALE_SABBREVCTRYNAME:
|
|
return locale_return_string( locale->sabbrevctryname, type, buffer, len );
|
|
|
|
case LOCALE_SNATIVECTRYNAME:
|
|
return locale_return_string( locale->snativectryname, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTLANGUAGE:
|
|
return locale_return_number( locale->idefaultlanguage, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTCOUNTRY:
|
|
return locale_return_number( locale->icountry, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTCODEPAGE:
|
|
val = locale->idefaultcodepage == CP_UTF8 ? CP_OEMCP : locale->idefaultcodepage;
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_SLIST:
|
|
if ((ret = locale_return_reg_string( &entry_slist, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->slist, type, buffer, len );
|
|
|
|
case LOCALE_IMEASURE:
|
|
if ((ret = locale_return_reg_number( &entry_imeasure, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->imeasure, type, buffer, len );
|
|
|
|
case LOCALE_SDECIMAL:
|
|
if ((ret = locale_return_reg_string( &entry_sdecimal, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->sdecimal, type, buffer, len );
|
|
|
|
case LOCALE_STHOUSAND:
|
|
if ((ret = locale_return_reg_string( &entry_sthousand, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->sthousand, type, buffer, len );
|
|
|
|
case LOCALE_SGROUPING:
|
|
if ((ret = locale_return_reg_string( &entry_sgrouping, type, buffer, len )) != -1) return ret;
|
|
return locale_return_grouping( locale->sgrouping, type, buffer, len );
|
|
|
|
case LOCALE_IDIGITS:
|
|
if ((ret = locale_return_reg_number( &entry_idigits, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->idigits, type, buffer, len );
|
|
|
|
case LOCALE_ILZERO:
|
|
if ((ret = locale_return_reg_number( &entry_ilzero, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->ilzero, type, buffer, len );
|
|
|
|
case LOCALE_SNATIVEDIGITS:
|
|
if ((ret = locale_return_reg_string( &entry_snativedigits, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray_concat( locale->snativedigits, type, buffer, len );
|
|
|
|
case LOCALE_SCURRENCY:
|
|
if ((ret = locale_return_reg_string( &entry_scurrency, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->scurrency, type, buffer, len );
|
|
|
|
case LOCALE_SINTLSYMBOL:
|
|
if ((ret = locale_return_reg_string( &entry_sintlsymbol, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->sintlsymbol, type, buffer, len );
|
|
|
|
case LOCALE_SMONDECIMALSEP:
|
|
if ((ret = locale_return_reg_string( &entry_smondecimalsep, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->smondecimalsep, type, buffer, len );
|
|
|
|
case LOCALE_SMONTHOUSANDSEP:
|
|
if ((ret = locale_return_reg_string( &entry_smonthousandsep, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->smonthousandsep, type, buffer, len );
|
|
|
|
case LOCALE_SMONGROUPING:
|
|
if ((ret = locale_return_reg_string( &entry_smongrouping, type, buffer, len )) != -1) return ret;
|
|
return locale_return_grouping( locale->smongrouping, type, buffer, len );
|
|
|
|
case LOCALE_ICURRDIGITS:
|
|
case LOCALE_IINTLCURRDIGITS:
|
|
if ((ret = locale_return_reg_number( &entry_icurrdigits, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->icurrdigits, type, buffer, len );
|
|
|
|
case LOCALE_ICURRENCY:
|
|
if ((ret = locale_return_reg_number( &entry_icurrency, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->icurrency, type, buffer, len );
|
|
|
|
case LOCALE_INEGCURR:
|
|
if ((ret = locale_return_reg_number( &entry_inegcurr, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->inegcurr, type, buffer, len );
|
|
|
|
case LOCALE_SDATE:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SSHORTDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"dMy" ))) break;
|
|
while (str[1] == str[0]) str++; /* skip repeated chars */
|
|
if (!(end = find_format( ++str, L"dMy" ))) break;
|
|
*end++ = 0;
|
|
return locale_return_data( str, end - str, type, buffer, len );
|
|
|
|
case LOCALE_STIME:
|
|
if (!get_locale_info( locale, lcid, LOCALE_STIMEFORMAT | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"Hhms" ))) break;
|
|
while (str[1] == str[0]) str++; /* skip repeated chars */
|
|
if (!(end = find_format( ++str, L"Hhms" ))) break;
|
|
*end++ = 0;
|
|
return locale_return_data( str, end - str, type, buffer, len );
|
|
|
|
case LOCALE_SSHORTDATE:
|
|
if ((ret = locale_return_reg_string( &entry_sshortdate, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray( locale->sshortdate, 0, type, buffer, len );
|
|
|
|
case LOCALE_SLONGDATE:
|
|
if ((ret = locale_return_reg_string( &entry_slongdate, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray( locale->slongdate, 0, type, buffer, len );
|
|
|
|
case LOCALE_IDATE:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SSHORTDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
/* if both year and day are found before month, the last one takes precedence */
|
|
for (val = 0, str = find_format( tmp, L"dMy" ); str; str = find_format( str + 1, L"dMy" ))
|
|
{
|
|
if (*str == 'M') break;
|
|
val = (*str == 'y' ? 2 : 1);
|
|
}
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_ILDATE:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SLONGDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
/* if both year and day are found before month, the last one takes precedence */
|
|
for (val = 0, str = find_format( tmp, L"dMy" ); str; str = find_format( str + 1, L"dMy" ))
|
|
{
|
|
if (*str == 'M') break;
|
|
val = (*str == 'y' ? 2 : 1);
|
|
}
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_ITIME:
|
|
if (!get_locale_info( locale, lcid, LOCALE_STIMEFORMAT | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"Hh" ))) break;
|
|
return locale_return_number( *str == 'H', type, buffer, len );
|
|
|
|
case LOCALE_ICENTURY:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SSHORTDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"y" ))) break;
|
|
return locale_return_number( !wcsncmp( str, L"yyyy", 4 ), type, buffer, len );
|
|
|
|
case LOCALE_ITLZERO:
|
|
if (!get_locale_info( locale, lcid, LOCALE_STIMEFORMAT | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"Hh" ))) break;
|
|
return locale_return_number( str[1] == str[0], type, buffer, len );
|
|
|
|
case LOCALE_IDAYLZERO:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SSHORTDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"d" ))) break;
|
|
return locale_return_number( str[1] == 'd', type, buffer, len );
|
|
|
|
case LOCALE_IMONLZERO:
|
|
if (!get_locale_info( locale, lcid, LOCALE_SSHORTDATE | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"M" ))) break;
|
|
return locale_return_number( str[1] == 'M', type, buffer, len );
|
|
|
|
case LOCALE_S1159:
|
|
if ((ret = locale_return_reg_string( &entry_s1159, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->s1159, type, buffer, len );
|
|
|
|
case LOCALE_S2359:
|
|
if ((ret = locale_return_reg_string( &entry_s2359, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->s2359, type, buffer, len );
|
|
|
|
case LOCALE_SDAYNAME1:
|
|
case LOCALE_SDAYNAME2:
|
|
case LOCALE_SDAYNAME3:
|
|
case LOCALE_SDAYNAME4:
|
|
case LOCALE_SDAYNAME5:
|
|
case LOCALE_SDAYNAME6:
|
|
case LOCALE_SDAYNAME7:
|
|
return locale_return_strarray( locale->sdayname,
|
|
LOWORD(type - LOCALE_SDAYNAME1 + 1) % 7, type, buffer, len );
|
|
|
|
case LOCALE_SABBREVDAYNAME1:
|
|
case LOCALE_SABBREVDAYNAME2:
|
|
case LOCALE_SABBREVDAYNAME3:
|
|
case LOCALE_SABBREVDAYNAME4:
|
|
case LOCALE_SABBREVDAYNAME5:
|
|
case LOCALE_SABBREVDAYNAME6:
|
|
case LOCALE_SABBREVDAYNAME7:
|
|
return locale_return_strarray( locale->sabbrevdayname,
|
|
LOWORD(type - LOCALE_SABBREVDAYNAME1 + 1) % 7, type, buffer, len );
|
|
|
|
case LOCALE_SMONTHNAME1:
|
|
case LOCALE_SMONTHNAME2:
|
|
case LOCALE_SMONTHNAME3:
|
|
case LOCALE_SMONTHNAME4:
|
|
case LOCALE_SMONTHNAME5:
|
|
case LOCALE_SMONTHNAME6:
|
|
case LOCALE_SMONTHNAME7:
|
|
case LOCALE_SMONTHNAME8:
|
|
case LOCALE_SMONTHNAME9:
|
|
case LOCALE_SMONTHNAME10:
|
|
case LOCALE_SMONTHNAME11:
|
|
case LOCALE_SMONTHNAME12:
|
|
return locale_return_strarray( ((type & LOCALE_RETURN_GENITIVE_NAMES) && locale->sgenitivemonth) ?
|
|
locale->sgenitivemonth : locale->smonthname,
|
|
type - LOCALE_SMONTHNAME1, type, buffer, len );
|
|
|
|
case LOCALE_SABBREVMONTHNAME1:
|
|
case LOCALE_SABBREVMONTHNAME2:
|
|
case LOCALE_SABBREVMONTHNAME3:
|
|
case LOCALE_SABBREVMONTHNAME4:
|
|
case LOCALE_SABBREVMONTHNAME5:
|
|
case LOCALE_SABBREVMONTHNAME6:
|
|
case LOCALE_SABBREVMONTHNAME7:
|
|
case LOCALE_SABBREVMONTHNAME8:
|
|
case LOCALE_SABBREVMONTHNAME9:
|
|
case LOCALE_SABBREVMONTHNAME10:
|
|
case LOCALE_SABBREVMONTHNAME11:
|
|
case LOCALE_SABBREVMONTHNAME12:
|
|
return locale_return_strarray( ((type & LOCALE_RETURN_GENITIVE_NAMES) && locale->sabbrevgenitivemonth) ?
|
|
locale->sabbrevgenitivemonth : locale->sabbrevmonthname,
|
|
type - LOCALE_SABBREVMONTHNAME1, type, buffer, len );
|
|
|
|
case LOCALE_SPOSITIVESIGN:
|
|
if ((ret = locale_return_reg_string( &entry_spositivesign, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->spositivesign, type, buffer, len );
|
|
|
|
case LOCALE_SNEGATIVESIGN:
|
|
if ((ret = locale_return_reg_string( &entry_snegativesign, type, buffer, len )) != -1) return ret;
|
|
return locale_return_string( locale->snegativesign, type, buffer, len );
|
|
|
|
case LOCALE_IPOSSIGNPOSN:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_INEGCURR | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( ipossignposn[val], type, buffer, len );
|
|
|
|
case LOCALE_INEGSIGNPOSN:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_INEGCURR | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( inegsignposn[val], type, buffer, len );
|
|
|
|
case LOCALE_IPOSSYMPRECEDES:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_ICURRENCY | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( !(val & 1), type, buffer, len );
|
|
|
|
case LOCALE_IPOSSEPBYSPACE:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_ICURRENCY | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( !!(val & 2), type, buffer, len );
|
|
|
|
case LOCALE_INEGSYMPRECEDES:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_INEGCURR | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( inegsymprecedes[val], type, buffer, len );
|
|
|
|
case LOCALE_INEGSEPBYSPACE:
|
|
if (!get_locale_info( locale, lcid,
|
|
LOCALE_INEGCURR | LOCALE_RETURN_NUMBER | (type & LOCALE_NOUSEROVERRIDE),
|
|
(WCHAR *)&val, sizeof(val)/sizeof(WCHAR) )) break;
|
|
return locale_return_number( (val >= 8), type, buffer, len );
|
|
|
|
case LOCALE_FONTSIGNATURE:
|
|
return locale_return_data( locale_strings + locale->fontsignature + 1,
|
|
locale_strings[locale->fontsignature], type, buffer, len );
|
|
|
|
case LOCALE_SISO639LANGNAME:
|
|
return locale_return_string( locale->siso639langname, type, buffer, len );
|
|
|
|
case LOCALE_SISO3166CTRYNAME:
|
|
return locale_return_string( locale->siso3166ctryname, type, buffer, len );
|
|
|
|
case LOCALE_IGEOID:
|
|
return locale_return_number( locale->igeoid, type, buffer, len );
|
|
|
|
case LOCALE_SNAME:
|
|
if (SORTIDFROMLCID(lcid)) /* custom sort locale */
|
|
{
|
|
const NLS_LOCALE_LCID_INDEX *entry = find_lcid_entry( lcid & ~0x80000000 );
|
|
if (entry) return locale_return_string( entry->name, type, buffer, len );
|
|
}
|
|
return locale_return_string( locale->sname, type, buffer, len );
|
|
|
|
case LOCALE_SDURATION:
|
|
return locale_return_strarray( locale->sduration, 0, type, buffer, len );
|
|
|
|
case LOCALE_SKEYBOARDSTOINSTALL:
|
|
return locale_return_string( locale->skeyboardstoinstall, type, buffer, len );
|
|
|
|
case LOCALE_SSHORTESTDAYNAME1:
|
|
case LOCALE_SSHORTESTDAYNAME2:
|
|
case LOCALE_SSHORTESTDAYNAME3:
|
|
case LOCALE_SSHORTESTDAYNAME4:
|
|
case LOCALE_SSHORTESTDAYNAME5:
|
|
case LOCALE_SSHORTESTDAYNAME6:
|
|
case LOCALE_SSHORTESTDAYNAME7:
|
|
return locale_return_strarray( locale->sshortestdayname,
|
|
LOWORD(type - LOCALE_SSHORTESTDAYNAME1 + 1) % 7, type, buffer, len );
|
|
|
|
case LOCALE_SISO639LANGNAME2:
|
|
return locale_return_string( locale->siso639langname2, type, buffer, len );
|
|
|
|
case LOCALE_SISO3166CTRYNAME2:
|
|
return locale_return_string( locale->siso3166ctryname2, type, buffer, len );
|
|
|
|
case LOCALE_SNAN:
|
|
return locale_return_string( locale->snan, type, buffer, len );
|
|
|
|
case LOCALE_SPOSINFINITY:
|
|
return locale_return_string( locale->sposinfinity, type, buffer, len );
|
|
|
|
case LOCALE_SNEGINFINITY:
|
|
return locale_return_string( locale->sneginfinity, type, buffer, len );
|
|
|
|
case LOCALE_SSCRIPTS:
|
|
return locale_return_string( locale->sscripts, type, buffer, len );
|
|
|
|
case LOCALE_SPARENT:
|
|
return locale_return_string( locale->sparent, type, buffer, len );
|
|
|
|
case LOCALE_SCONSOLEFALLBACKNAME:
|
|
return locale_return_string( locale->sconsolefallbackname, type, buffer, len );
|
|
|
|
case LOCALE_SLOCALIZEDLANGUAGENAME:
|
|
/* FIXME: localization */
|
|
return locale_return_string( locale->senglanguage, type, buffer, len );
|
|
|
|
case LOCALE_IREADINGLAYOUT:
|
|
return locale_return_number( locale->ireadinglayout, type, buffer, len );
|
|
|
|
case LOCALE_INEUTRAL:
|
|
return locale_return_number( !locale->inotneutral, type, buffer, len );
|
|
|
|
case LOCALE_SENGLISHDISPLAYNAME:
|
|
return locale_return_string( locale->sengdisplayname, type, buffer, len );
|
|
|
|
case LOCALE_SNATIVEDISPLAYNAME:
|
|
return locale_return_string( locale->snativedisplayname, type, buffer, len );
|
|
|
|
case LOCALE_INEGATIVEPERCENT:
|
|
return locale_return_number( locale->inegativepercent, type, buffer, len );
|
|
|
|
case LOCALE_IPOSITIVEPERCENT:
|
|
return locale_return_number( locale->ipositivepercent, type, buffer, len );
|
|
|
|
case LOCALE_SPERCENT:
|
|
return locale_return_string( locale->spercent, type, buffer, len );
|
|
|
|
case LOCALE_SPERMILLE:
|
|
return locale_return_data( spermille, ARRAY_SIZE(spermille), type, buffer, len );
|
|
|
|
case LOCALE_SMONTHDAY:
|
|
return locale_return_strarray( locale->smonthday, 0, type, buffer, len );
|
|
|
|
case LOCALE_SSHORTTIME:
|
|
if ((ret = locale_return_reg_string( &entry_sshorttime, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray( locale->sshorttime, 0, type, buffer, len );
|
|
|
|
case LOCALE_SOPENTYPELANGUAGETAG:
|
|
return locale_return_string( locale->sopentypelanguagetag, type, buffer, len );
|
|
|
|
case LOCALE_SSORTLOCALE:
|
|
if (SORTIDFROMLCID(lcid)) /* custom sort locale */
|
|
{
|
|
const NLS_LOCALE_LCID_INDEX *entry = find_lcid_entry( lcid & ~0x80000000 );
|
|
if (entry) return locale_return_string( entry->name, type, buffer, len );
|
|
}
|
|
return locale_return_string( locale->ssortlocale, type, buffer, len );
|
|
|
|
case LOCALE_SRELATIVELONGDATE:
|
|
return locale_return_string( locale->srelativelongdate, type, buffer, len );
|
|
|
|
case 0x007d: /* undocumented */
|
|
return locale_return_number( 0, type, buffer, len );
|
|
|
|
case LOCALE_SSHORTESTAM:
|
|
return locale_return_string( locale->sshortestam, type, buffer, len );
|
|
|
|
case LOCALE_SSHORTESTPM:
|
|
return locale_return_string( locale->sshortestpm, type, buffer, len );
|
|
|
|
case LOCALE_SENGLANGUAGE:
|
|
return locale_return_string( locale->senglanguage, type, buffer, len );
|
|
|
|
case LOCALE_SENGCOUNTRY:
|
|
return locale_return_string( locale->sengcountry, type, buffer, len );
|
|
|
|
case LOCALE_STIMEFORMAT:
|
|
if ((ret = locale_return_reg_string( &entry_stimeformat, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray( locale->stimeformat, 0, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTANSICODEPAGE:
|
|
val = locale->idefaultansicodepage == CP_UTF8 ? CP_ACP : locale->idefaultansicodepage;
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_ITIMEMARKPOSN:
|
|
if (!get_locale_info( locale, lcid, LOCALE_STIMEFORMAT | (type & LOCALE_NOUSEROVERRIDE),
|
|
tmp, ARRAY_SIZE( tmp ))) break;
|
|
if (!(str = find_format( tmp, L"Hhmst" ))) break;
|
|
return locale_return_number( *str == 't', type, buffer, len );
|
|
|
|
case LOCALE_SYEARMONTH:
|
|
if ((ret = locale_return_reg_string( &entry_syearmonth, type, buffer, len )) != -1) return ret;
|
|
return locale_return_strarray( locale->syearmonth, 0, type, buffer, len );
|
|
|
|
case LOCALE_SENGCURRNAME:
|
|
return locale_return_string( locale->sengcurrname, type, buffer, len );
|
|
|
|
case LOCALE_SNATIVECURRNAME:
|
|
return locale_return_string( locale->snativecurrname, type, buffer, len );
|
|
|
|
case LOCALE_ICALENDARTYPE:
|
|
if ((ret = locale_return_reg_number( &entry_icalendartype, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale_strings[locale->scalendartype + 1], type, buffer, len );
|
|
|
|
case LOCALE_IPAPERSIZE:
|
|
if ((ret = locale_return_reg_number( &entry_ipapersize, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->ipapersize, type, buffer, len );
|
|
|
|
case LOCALE_IOPTIONALCALENDAR:
|
|
return locale_return_number( locale_strings[locale->scalendartype + 2], type, buffer, len );
|
|
|
|
case LOCALE_IFIRSTDAYOFWEEK:
|
|
if ((ret = locale_return_reg_number( &entry_ifirstdayofweek, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( (locale->ifirstdayofweek + 6) % 7, type, buffer, len );
|
|
|
|
case LOCALE_IFIRSTWEEKOFYEAR:
|
|
if ((ret = locale_return_reg_number( &entry_ifirstweekofyear, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->ifirstweekofyear, type, buffer, len );
|
|
|
|
case LOCALE_SMONTHNAME13:
|
|
return locale_return_strarray( ((type & LOCALE_RETURN_GENITIVE_NAMES) && locale->sgenitivemonth) ?
|
|
locale->sgenitivemonth : locale->smonthname,
|
|
12, type, buffer, len );
|
|
|
|
case LOCALE_SABBREVMONTHNAME13:
|
|
return locale_return_strarray( ((type & LOCALE_RETURN_GENITIVE_NAMES) && locale->sabbrevgenitivemonth) ?
|
|
locale->sabbrevgenitivemonth : locale->sabbrevmonthname,
|
|
12, type, buffer, len );
|
|
|
|
case LOCALE_INEGNUMBER:
|
|
if ((ret = locale_return_reg_number( &entry_inegnumber, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->inegnumber, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTMACCODEPAGE:
|
|
val = locale->idefaultmaccodepage == CP_UTF8 ? CP_MACCP : locale->idefaultmaccodepage;
|
|
return locale_return_number( val, type, buffer, len );
|
|
|
|
case LOCALE_IDEFAULTEBCDICCODEPAGE:
|
|
return locale_return_number( locale->idefaultebcdiccodepage, type, buffer, len );
|
|
|
|
case LOCALE_SSORTNAME:
|
|
sort = get_locale_sortname( lcid );
|
|
return locale_return_data( sort, wcslen(sort) + 1, type, buffer, len );
|
|
|
|
case LOCALE_IDIGITSUBSTITUTION:
|
|
if ((ret = locale_return_reg_number( &entry_idigitsubstitution, type, buffer, len )) != -1) return ret;
|
|
return locale_return_number( locale->idigitsubstitution, type, buffer, len );
|
|
}
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* get calendar information from the locale.nls file */
|
|
static int get_calendar_info( const NLS_LOCALE_DATA *locale, CALID id, CALTYPE type,
|
|
WCHAR *buffer, int len, DWORD *value )
|
|
{
|
|
unsigned int i, val = 0;
|
|
const struct calendar *cal;
|
|
|
|
if (type & CAL_RETURN_NUMBER)
|
|
{
|
|
if (buffer || len || !value) goto invalid;
|
|
}
|
|
else if (len < 0 || value) goto invalid;
|
|
|
|
if (id != CAL_GREGORIAN)
|
|
{
|
|
const USHORT *ids = locale_strings + locale->scalendartype;
|
|
for (i = 0; i < ids[0]; i++) if (ids[1 + i] == id) break;
|
|
if (i == ids[0]) goto invalid;
|
|
}
|
|
if (!(cal = get_calendar_data( locale, id ))) goto invalid;
|
|
|
|
switch (LOWORD(type))
|
|
{
|
|
case CAL_ICALINTVALUE:
|
|
return cal_return_number( cal->icalintvalue, type, buffer, len, value );
|
|
|
|
case CAL_SCALNAME:
|
|
return locale_return_strarray( locale->calnames, id - 1, type, buffer, len );
|
|
|
|
case CAL_IYEAROFFSETRANGE:
|
|
if (cal->iyearoffsetrange)
|
|
{
|
|
const DWORD *array = (const DWORD *)(locale_strings + cal->iyearoffsetrange + 1);
|
|
const short *info = (const short *)locale_strings + array[0];
|
|
val = (info[5] < 0) ? -info[5] : info[5] + 1; /* year zero */
|
|
}
|
|
return cal_return_number( val, type, buffer, len, value );
|
|
|
|
case CAL_SERASTRING:
|
|
if (id == CAL_GREGORIAN) return locale_return_string( locale->serastring, type, buffer, len );
|
|
if (cal->iyearoffsetrange)
|
|
{
|
|
const DWORD *array = (const DWORD *)(locale_strings + cal->iyearoffsetrange + 1);
|
|
const short *info = (const short *)locale_strings + array[0];
|
|
val = info[1] - 1;
|
|
}
|
|
return locale_return_strarray( cal->serastring, val, type, buffer, len );
|
|
|
|
case CAL_SSHORTDATE:
|
|
val = (id == CAL_GREGORIAN) ? locale->sshortdate : cal->sshortdate;
|
|
return locale_return_strarray( val, 0, type, buffer, len );
|
|
|
|
case CAL_SLONGDATE:
|
|
val = (id == CAL_GREGORIAN) ? locale->slongdate : cal->slongdate;
|
|
return locale_return_strarray( val, 0, type, buffer, len );
|
|
|
|
case CAL_SDAYNAME1:
|
|
case CAL_SDAYNAME2:
|
|
case CAL_SDAYNAME3:
|
|
case CAL_SDAYNAME4:
|
|
case CAL_SDAYNAME5:
|
|
case CAL_SDAYNAME6:
|
|
case CAL_SDAYNAME7:
|
|
val = (id == CAL_GREGORIAN) ? locale->sdayname : cal->sdayname;
|
|
return locale_return_strarray( val, (LOWORD(type) - CAL_SDAYNAME1 + 1) % 7, type, buffer, len );
|
|
|
|
case CAL_SABBREVDAYNAME1:
|
|
case CAL_SABBREVDAYNAME2:
|
|
case CAL_SABBREVDAYNAME3:
|
|
case CAL_SABBREVDAYNAME4:
|
|
case CAL_SABBREVDAYNAME5:
|
|
case CAL_SABBREVDAYNAME6:
|
|
case CAL_SABBREVDAYNAME7:
|
|
val = (id == CAL_GREGORIAN) ? locale->sabbrevdayname : cal->sabbrevdayname;
|
|
return locale_return_strarray( val, (LOWORD(type) - CAL_SABBREVDAYNAME1 + 1) % 7, type, buffer, len );
|
|
case CAL_SMONTHNAME1:
|
|
case CAL_SMONTHNAME2:
|
|
case CAL_SMONTHNAME3:
|
|
case CAL_SMONTHNAME4:
|
|
case CAL_SMONTHNAME5:
|
|
case CAL_SMONTHNAME6:
|
|
case CAL_SMONTHNAME7:
|
|
case CAL_SMONTHNAME8:
|
|
case CAL_SMONTHNAME9:
|
|
case CAL_SMONTHNAME10:
|
|
case CAL_SMONTHNAME11:
|
|
case CAL_SMONTHNAME12:
|
|
case CAL_SMONTHNAME13:
|
|
if (id != CAL_GREGORIAN) val = cal->smonthname;
|
|
else if ((type & CAL_RETURN_GENITIVE_NAMES) && locale->sgenitivemonth) val = locale->sgenitivemonth;
|
|
else val = locale->smonthname;
|
|
return locale_return_strarray( val, LOWORD(type) - CAL_SMONTHNAME1, type, buffer, len );
|
|
|
|
case CAL_SABBREVMONTHNAME1:
|
|
case CAL_SABBREVMONTHNAME2:
|
|
case CAL_SABBREVMONTHNAME3:
|
|
case CAL_SABBREVMONTHNAME4:
|
|
case CAL_SABBREVMONTHNAME5:
|
|
case CAL_SABBREVMONTHNAME6:
|
|
case CAL_SABBREVMONTHNAME7:
|
|
case CAL_SABBREVMONTHNAME8:
|
|
case CAL_SABBREVMONTHNAME9:
|
|
case CAL_SABBREVMONTHNAME10:
|
|
case CAL_SABBREVMONTHNAME11:
|
|
case CAL_SABBREVMONTHNAME12:
|
|
case CAL_SABBREVMONTHNAME13:
|
|
if (id != CAL_GREGORIAN) val = cal->sabbrevmonthname;
|
|
else if ((type & CAL_RETURN_GENITIVE_NAMES) && locale->sabbrevgenitivemonth) val = locale->sabbrevgenitivemonth;
|
|
else val = locale->sabbrevmonthname;
|
|
return locale_return_strarray( val, LOWORD(type) - CAL_SABBREVMONTHNAME1, type, buffer, len );
|
|
|
|
case CAL_SYEARMONTH:
|
|
val = (id == CAL_GREGORIAN) ? locale->syearmonth : cal->syearmonth;
|
|
return locale_return_strarray( val, 0, type, buffer, len );
|
|
|
|
case CAL_ITWODIGITYEARMAX:
|
|
return cal_return_number( cal->itwodigityearmax, type, buffer, len, value );
|
|
|
|
case CAL_SSHORTESTDAYNAME1:
|
|
case CAL_SSHORTESTDAYNAME2:
|
|
case CAL_SSHORTESTDAYNAME3:
|
|
case CAL_SSHORTESTDAYNAME4:
|
|
case CAL_SSHORTESTDAYNAME5:
|
|
case CAL_SSHORTESTDAYNAME6:
|
|
case CAL_SSHORTESTDAYNAME7:
|
|
val = (id == CAL_GREGORIAN) ? locale->sshortestdayname : cal->sshortestdayname;
|
|
return locale_return_strarray( val, (LOWORD(type) - CAL_SSHORTESTDAYNAME1 + 1) % 7, type, buffer, len );
|
|
|
|
case CAL_SMONTHDAY:
|
|
val = (id == CAL_GREGORIAN) ? locale->smonthday : cal->smonthday;
|
|
return locale_return_strarray( val, 0, type, buffer, len );
|
|
|
|
case CAL_SABBREVERASTRING:
|
|
if (id == CAL_GREGORIAN) return locale_return_string( locale->sabbreverastring, type, buffer, len );
|
|
if (cal->iyearoffsetrange)
|
|
{
|
|
const DWORD *array = (const DWORD *)(locale_strings + cal->iyearoffsetrange + 1);
|
|
const short *info = (const short *)locale_strings + array[0];
|
|
val = info[1] - 1;
|
|
}
|
|
return locale_return_strarray( cal->sabbreverastring, val, type, buffer, len );
|
|
|
|
case CAL_SRELATIVELONGDATE:
|
|
val = (id == CAL_GREGORIAN) ? locale->srelativelongdate : cal->srelativelongdate;
|
|
return locale_return_string( val, type, buffer, len );
|
|
|
|
case CAL_SENGLISHERANAME:
|
|
case CAL_SENGLISHABBREVERANAME:
|
|
/* not supported on Windows */
|
|
break;
|
|
}
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
|
|
invalid:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* get geo information from the locale.nls file */
|
|
static int get_geo_info( const struct geo_id *geo, enum SYSGEOTYPE type,
|
|
WCHAR *buffer, int len, LANGID lang )
|
|
{
|
|
WCHAR tmp[12];
|
|
const WCHAR *str = tmp;
|
|
int ret;
|
|
|
|
switch (type)
|
|
{
|
|
case GEO_NATION:
|
|
if (geo->class != GEOCLASS_NATION) return 0;
|
|
/* fall through */
|
|
case GEO_ID:
|
|
swprintf( tmp, ARRAY_SIZE(tmp), L"%u", geo->id );
|
|
break;
|
|
case GEO_ISO_UN_NUMBER:
|
|
swprintf( tmp, ARRAY_SIZE(tmp), L"%03u", geo->uncode );
|
|
break;
|
|
case GEO_PARENT:
|
|
swprintf( tmp, ARRAY_SIZE(tmp), L"%u", geo->parent );
|
|
break;
|
|
case GEO_DIALINGCODE:
|
|
swprintf( tmp, ARRAY_SIZE(tmp), L"%u", geo->dialcode );
|
|
break;
|
|
case GEO_ISO2:
|
|
str = geo->iso2;
|
|
break;
|
|
case GEO_ISO3:
|
|
str = geo->iso3;
|
|
break;
|
|
case GEO_LATITUDE:
|
|
str = geo->latitude;
|
|
break;
|
|
case GEO_LONGITUDE:
|
|
str = geo->longitude;
|
|
break;
|
|
case GEO_CURRENCYCODE:
|
|
str = geo->currcode;
|
|
break;
|
|
case GEO_CURRENCYSYMBOL:
|
|
str = geo->currsymbol;
|
|
break;
|
|
case GEO_RFC1766:
|
|
case GEO_LCID:
|
|
case GEO_FRIENDLYNAME:
|
|
case GEO_OFFICIALNAME:
|
|
case GEO_TIMEZONES:
|
|
case GEO_OFFICIALLANGUAGES:
|
|
case GEO_NAME:
|
|
FIXME( "type %u is not supported\n", type );
|
|
SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
|
|
return 0;
|
|
default:
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
ret = lstrlenW(str) + 1;
|
|
if (!buffer || !len) return ret;
|
|
|
|
memcpy( buffer, str, min( ret, len ) * sizeof(WCHAR) );
|
|
if (len < ret) SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return len < ret ? 0 : ret;
|
|
}
|
|
|
|
|
|
/* update a registry value based on the current user locale info */
|
|
static void update_registry_value( UINT type, const WCHAR *subkey, const WCHAR *value )
|
|
{
|
|
WCHAR buffer[80];
|
|
UINT len = get_locale_info( user_locale, user_lcid, type, buffer, ARRAY_SIZE(buffer) );
|
|
if (len) RegSetKeyValueW( intl_key, subkey, value, REG_SZ, (BYTE *)buffer, len * sizeof(WCHAR) );
|
|
}
|
|
|
|
|
|
/* update all registry values upon user locale change */
|
|
static void update_locale_registry(void)
|
|
{
|
|
WCHAR buffer[80];
|
|
UINT len;
|
|
|
|
len = swprintf( buffer, ARRAY_SIZE(buffer), L"%08x", GetUserDefaultLCID() );
|
|
RegSetValueExW( intl_key, L"Locale", 0, REG_SZ, (BYTE *)buffer, (len + 1) * sizeof(WCHAR) );
|
|
|
|
#define UPDATE(val,entry) update_registry_value( LOCALE_NOUSEROVERRIDE | (val), (entry).subkey, (entry).value )
|
|
UPDATE( LOCALE_ICALENDARTYPE, entry_icalendartype );
|
|
UPDATE( LOCALE_ICOUNTRY, entry_icountry );
|
|
UPDATE( LOCALE_ICURRDIGITS, entry_icurrdigits );
|
|
UPDATE( LOCALE_ICURRENCY, entry_icurrency );
|
|
UPDATE( LOCALE_IDIGITS, entry_idigits );
|
|
UPDATE( LOCALE_IDIGITSUBSTITUTION, entry_idigitsubstitution );
|
|
UPDATE( LOCALE_IFIRSTDAYOFWEEK, entry_ifirstdayofweek );
|
|
UPDATE( LOCALE_IFIRSTWEEKOFYEAR, entry_ifirstweekofyear );
|
|
UPDATE( LOCALE_ILZERO, entry_ilzero );
|
|
UPDATE( LOCALE_IMEASURE, entry_imeasure );
|
|
UPDATE( LOCALE_INEGCURR, entry_inegcurr );
|
|
UPDATE( LOCALE_INEGNUMBER, entry_inegnumber );
|
|
UPDATE( LOCALE_IPAPERSIZE, entry_ipapersize );
|
|
UPDATE( LOCALE_S1159, entry_s1159 );
|
|
UPDATE( LOCALE_S2359, entry_s2359 );
|
|
UPDATE( LOCALE_SCURRENCY, entry_scurrency );
|
|
UPDATE( LOCALE_SDECIMAL, entry_sdecimal );
|
|
UPDATE( LOCALE_SGROUPING, entry_sgrouping );
|
|
UPDATE( LOCALE_SINTLSYMBOL, entry_sintlsymbol );
|
|
UPDATE( LOCALE_SLIST, entry_slist );
|
|
UPDATE( LOCALE_SLONGDATE, entry_slongdate );
|
|
UPDATE( LOCALE_SMONDECIMALSEP, entry_smondecimalsep );
|
|
UPDATE( LOCALE_SMONGROUPING, entry_smongrouping );
|
|
UPDATE( LOCALE_SMONTHOUSANDSEP, entry_smonthousandsep );
|
|
UPDATE( LOCALE_SNATIVEDIGITS, entry_snativedigits );
|
|
UPDATE( LOCALE_SNEGATIVESIGN, entry_snegativesign );
|
|
UPDATE( LOCALE_SPOSITIVESIGN, entry_spositivesign );
|
|
UPDATE( LOCALE_SSHORTDATE, entry_sshortdate );
|
|
UPDATE( LOCALE_SSHORTTIME, entry_sshorttime );
|
|
UPDATE( LOCALE_STHOUSAND, entry_sthousand );
|
|
UPDATE( LOCALE_STIMEFORMAT, entry_stimeformat );
|
|
UPDATE( LOCALE_SYEARMONTH, entry_syearmonth );
|
|
#undef UPDATE
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_IDATE, NULL, L"iDate" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_ITIME, NULL, L"iTime" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_ITIMEMARKPOSN, NULL, L"iTimePrefix" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_ITLZERO, NULL, L"iTLZero" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_SDATE, NULL, L"sDate" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_STIME, NULL, L"sTime" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_SABBREVLANGNAME, NULL, L"sLanguage" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_SCOUNTRY, NULL, L"sCountry" );
|
|
update_registry_value( LOCALE_NOUSEROVERRIDE | LOCALE_SNAME, NULL, L"LocaleName" );
|
|
SetUserGeoID( user_locale->igeoid );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* init_locale
|
|
*/
|
|
void init_locale( HMODULE module )
|
|
{
|
|
USHORT utf8[2] = { 0, CP_UTF8 };
|
|
USHORT *ansi_ptr, *oem_ptr;
|
|
WCHAR bufferW[LOCALE_NAME_MAX_LENGTH];
|
|
DYNAMIC_TIME_ZONE_INFORMATION timezone;
|
|
const WCHAR *user_locale_name;
|
|
DWORD count;
|
|
SIZE_T size;
|
|
HKEY hkey;
|
|
|
|
kernelbase_handle = module;
|
|
load_locale_nls();
|
|
load_sortdefault_nls();
|
|
|
|
if (system_lcid == LOCALE_CUSTOM_UNSPECIFIED) system_lcid = MAKELANGID( LANG_ENGLISH, SUBLANG_DEFAULT );
|
|
system_locale = NlsValidateLocale( &system_lcid, 0 );
|
|
|
|
NtQueryDefaultLocale( TRUE, &user_lcid );
|
|
if (!(user_locale = NlsValidateLocale( &user_lcid, 0 )))
|
|
{
|
|
if (GetEnvironmentVariableW( L"WINEUSERLOCALE", bufferW, ARRAY_SIZE(bufferW) ))
|
|
user_locale = get_locale_by_name( bufferW, &user_lcid );
|
|
if (!user_locale) user_locale = system_locale;
|
|
}
|
|
user_lcid = user_locale->ilanguage;
|
|
if (user_lcid == LOCALE_CUSTOM_UNSPECIFIED) user_lcid = LOCALE_CUSTOM_DEFAULT;
|
|
|
|
if (GetEnvironmentVariableW( L"WINEUNIXCP", bufferW, ARRAY_SIZE(bufferW) ))
|
|
unix_cp = wcstoul( bufferW, NULL, 10 );
|
|
|
|
NtGetNlsSectionPtr( 12, NormalizationC, NULL, (void **)&norm_info, &size );
|
|
|
|
ansi_ptr = NtCurrentTeb()->Peb->AnsiCodePageData ? NtCurrentTeb()->Peb->AnsiCodePageData : utf8;
|
|
oem_ptr = NtCurrentTeb()->Peb->OemCodePageData ? NtCurrentTeb()->Peb->OemCodePageData : utf8;
|
|
RtlInitCodePageTable( ansi_ptr, &ansi_cpinfo );
|
|
RtlInitCodePageTable( oem_ptr, &oem_cpinfo );
|
|
|
|
RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\Nls",
|
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &nls_key, NULL );
|
|
RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
|
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &tz_key, NULL );
|
|
RegCreateKeyExW( HKEY_CURRENT_USER, L"Control Panel\\International",
|
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &intl_key, NULL );
|
|
|
|
current_locale_sort = get_language_sort( LOCALE_NAME_USER_DEFAULT );
|
|
|
|
if (GetDynamicTimeZoneInformation( &timezone ) != TIME_ZONE_ID_INVALID &&
|
|
!RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Control\\TimeZoneInformation",
|
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL ))
|
|
{
|
|
RegSetValueExW( hkey, L"StandardName", 0, REG_SZ, (BYTE *)timezone.StandardName,
|
|
(lstrlenW(timezone.StandardName) + 1) * sizeof(WCHAR) );
|
|
RegSetValueExW( hkey, L"TimeZoneKeyName", 0, REG_SZ, (BYTE *)timezone.TimeZoneKeyName,
|
|
(lstrlenW(timezone.TimeZoneKeyName) + 1) * sizeof(WCHAR) );
|
|
RegCloseKey( hkey );
|
|
}
|
|
|
|
/* Update registry contents if the user locale has changed.
|
|
* This simulates the action of the Windows control panel. */
|
|
|
|
user_locale_name = locale_strings + user_locale->sname + 1;
|
|
count = sizeof(bufferW);
|
|
if (!RegQueryValueExW( intl_key, L"LocaleName", NULL, NULL, (BYTE *)bufferW, &count ))
|
|
{
|
|
if (!wcscmp( bufferW, user_locale_name )) return; /* unchanged */
|
|
TRACE( "updating registry, locale changed %s -> %s\n",
|
|
debugstr_w(bufferW), debugstr_w(user_locale_name) );
|
|
}
|
|
else TRACE( "updating registry, locale changed none -> %s\n", debugstr_w(user_locale_name) );
|
|
|
|
update_locale_registry();
|
|
|
|
if (!RegCreateKeyExW( nls_key, L"Codepage",
|
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL ))
|
|
{
|
|
count = swprintf( bufferW, ARRAY_SIZE(bufferW), L"%03d", GetACP() );
|
|
RegSetValueExW( hkey, L"ACP", 0, REG_SZ, (BYTE *)bufferW, (count + 1) * sizeof(WCHAR) );
|
|
count = swprintf( bufferW, ARRAY_SIZE(bufferW), L"%03d", GetOEMCP() );
|
|
RegSetValueExW( hkey, L"OEMCP", 0, REG_SZ, (BYTE *)bufferW, (count + 1) * sizeof(WCHAR) );
|
|
count = swprintf( bufferW, ARRAY_SIZE(bufferW), L"%03d", system_locale->idefaultmaccodepage );
|
|
RegSetValueExW( hkey, L"MACCP", 0, REG_SZ, (BYTE *)bufferW, (count + 1) * sizeof(WCHAR) );
|
|
RegCloseKey( hkey );
|
|
}
|
|
}
|
|
|
|
|
|
static inline WCHAR casemap( const USHORT *table, WCHAR ch )
|
|
{
|
|
return ch + table[table[table[ch >> 8] + ((ch >> 4) & 0x0f)] + (ch & 0x0f)];
|
|
}
|
|
|
|
|
|
static inline unsigned int casemap_high( const USHORT *table, WCHAR high, WCHAR low )
|
|
{
|
|
unsigned int off = table[table[256 + (high - 0xd800)] + ((low >> 5) & 0x1f)] + 2 * (low & 0x1f);
|
|
return 0x10000 + ((high - 0xd800) << 10) + (low - 0xdc00) + MAKELONG( table[off], table[off+1] );
|
|
}
|
|
|
|
|
|
static inline BOOL table_has_high_planes( const USHORT *table )
|
|
{
|
|
return table[0] >= 0x500;
|
|
}
|
|
|
|
|
|
static inline int put_utf16( WCHAR *dst, int pos, int dstlen, unsigned int ch )
|
|
{
|
|
if (ch >= 0x10000)
|
|
{
|
|
if (pos < dstlen - 1)
|
|
{
|
|
ch -= 0x10000;
|
|
dst[pos] = 0xd800 | (ch >> 10);
|
|
dst[pos + 1] = 0xdc00 | (ch & 0x3ff);
|
|
}
|
|
return 2;
|
|
}
|
|
if (pos < dstlen) dst[pos] = ch;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline WORD get_char_type( DWORD type, WCHAR ch )
|
|
{
|
|
const BYTE *ptr = sort.ctype_idx + ((const WORD *)sort.ctype_idx)[ch >> 8];
|
|
ptr = sort.ctype_idx + ((const WORD *)ptr)[(ch >> 4) & 0x0f] + (ch & 0x0f);
|
|
return sort.ctypes[*ptr * 3 + type / 2];
|
|
}
|
|
|
|
|
|
static inline void map_byterev( const WCHAR *src, int len, WCHAR *dst )
|
|
{
|
|
while (len--) *dst++ = RtlUshortByteSwap( *src++ );
|
|
}
|
|
|
|
|
|
static int casemap_string( const USHORT *table, const WCHAR *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
if (table_has_high_planes( table ))
|
|
{
|
|
unsigned int ch;
|
|
int pos = 0;
|
|
|
|
while (srclen)
|
|
{
|
|
if (srclen > 1 && IS_SURROGATE_PAIR( src[0], src[1] ))
|
|
{
|
|
ch = casemap_high( table, src[0], src[1] );
|
|
src += 2;
|
|
srclen -= 2;
|
|
}
|
|
else
|
|
{
|
|
ch = casemap( table, *src );
|
|
src++;
|
|
srclen--;
|
|
}
|
|
pos += put_utf16( dst, pos, dstlen, ch );
|
|
}
|
|
return pos;
|
|
}
|
|
else
|
|
{
|
|
int pos, ret = srclen;
|
|
|
|
for (pos = 0; pos < dstlen && srclen; pos++, src++, srclen--)
|
|
dst[pos] = casemap( table, *src );
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
static union char_weights get_char_weights( WCHAR c, UINT except )
|
|
{
|
|
union char_weights ret;
|
|
|
|
ret.val = except ? sort.keys[sort.keys[except + (c >> 8)] + (c & 0xff)] : sort.keys[c];
|
|
return ret;
|
|
}
|
|
|
|
|
|
static BYTE rol( BYTE val, BYTE count )
|
|
{
|
|
return (val << count) | (val >> (8 - count));
|
|
}
|
|
|
|
|
|
static BYTE get_char_props( const struct norm_table *info, unsigned int ch )
|
|
{
|
|
const BYTE *level1 = (const BYTE *)((const USHORT *)info + info->props_level1);
|
|
const BYTE *level2 = (const BYTE *)((const USHORT *)info + info->props_level2);
|
|
BYTE off = level1[ch / 128];
|
|
|
|
if (!off || off >= 0xfb) return rol( off, 5 );
|
|
return level2[(off - 1) * 128 + ch % 128];
|
|
}
|
|
|
|
|
|
static const WCHAR *get_decomposition( WCHAR ch, unsigned int *ret_len )
|
|
{
|
|
const struct pair { WCHAR src; USHORT dst; } *pairs;
|
|
const USHORT *hash_table = (const USHORT *)norm_info + norm_info->decomp_hash;
|
|
const WCHAR *ret;
|
|
unsigned int i, pos, end, len, hash;
|
|
|
|
*ret_len = 1;
|
|
hash = ch % norm_info->decomp_size;
|
|
pos = hash_table[hash];
|
|
if (pos >> 13)
|
|
{
|
|
if (get_char_props( norm_info, ch ) != 0xbf) return NULL;
|
|
ret = (const USHORT *)norm_info + norm_info->decomp_seq + (pos & 0x1fff);
|
|
len = pos >> 13;
|
|
}
|
|
else
|
|
{
|
|
pairs = (const struct pair *)((const USHORT *)norm_info + norm_info->decomp_map);
|
|
|
|
/* find the end of the hash bucket */
|
|
for (i = hash + 1; i < norm_info->decomp_size; i++) if (!(hash_table[i] >> 13)) break;
|
|
if (i < norm_info->decomp_size) end = hash_table[i];
|
|
else for (end = pos; pairs[end].src; end++) ;
|
|
|
|
for ( ; pos < end; pos++)
|
|
{
|
|
if (pairs[pos].src != (WCHAR)ch) continue;
|
|
ret = (const USHORT *)norm_info + norm_info->decomp_seq + (pairs[pos].dst & 0x1fff);
|
|
len = pairs[pos].dst >> 13;
|
|
break;
|
|
}
|
|
if (pos >= end) return NULL;
|
|
}
|
|
|
|
if (len == 7) while (ret[len]) len++;
|
|
if (!ret[0]) len = 0; /* ignored char */
|
|
*ret_len = len;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static WCHAR compose_chars( WCHAR ch1, WCHAR ch2 )
|
|
{
|
|
const USHORT *table = (const USHORT *)norm_info + norm_info->comp_hash;
|
|
const WCHAR *chars = (const USHORT *)norm_info + norm_info->comp_seq;
|
|
unsigned int hash, start, end, i;
|
|
WCHAR ch[3];
|
|
|
|
hash = (ch1 + 95 * ch2) % norm_info->comp_size;
|
|
start = table[hash];
|
|
end = table[hash + 1];
|
|
while (start < end)
|
|
{
|
|
for (i = 0; i < 3; i++, start++)
|
|
{
|
|
ch[i] = chars[start];
|
|
if (IS_HIGH_SURROGATE( ch[i] )) start++;
|
|
}
|
|
if (ch[0] == ch1 && ch[1] == ch2) return ch[2];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static UINT get_locale_codepage( const NLS_LOCALE_DATA *locale, ULONG flags )
|
|
{
|
|
UINT ret = locale->idefaultansicodepage;
|
|
if ((flags & LOCALE_USE_CP_ACP) || ret == CP_UTF8) ret = ansi_cpinfo.CodePage;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static UINT get_lcid_codepage( LCID lcid, ULONG flags )
|
|
{
|
|
UINT ret = ansi_cpinfo.CodePage;
|
|
|
|
if (!(flags & LOCALE_USE_CP_ACP) && lcid != system_lcid)
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
if (locale) ret = locale->idefaultansicodepage;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static const CPTABLEINFO *get_codepage_table( UINT codepage )
|
|
{
|
|
static const CPTABLEINFO utf7_cpinfo = { CP_UTF7, 5, '?', 0xfffd, '?', '?' };
|
|
static const CPTABLEINFO utf8_cpinfo = { CP_UTF8, 4, '?', 0xfffd, '?', '?' };
|
|
unsigned int i;
|
|
USHORT *ptr;
|
|
SIZE_T size;
|
|
|
|
switch (codepage)
|
|
{
|
|
case CP_ACP:
|
|
return &ansi_cpinfo;
|
|
case CP_OEMCP:
|
|
return &oem_cpinfo;
|
|
case CP_MACCP:
|
|
codepage = system_locale->idefaultmaccodepage;
|
|
break;
|
|
case CP_THREAD_ACP:
|
|
codepage = get_lcid_codepage( NtCurrentTeb()->CurrentLocale, 0 );
|
|
break;
|
|
}
|
|
if (codepage == ansi_cpinfo.CodePage) return &ansi_cpinfo;
|
|
if (codepage == oem_cpinfo.CodePage) return &oem_cpinfo;
|
|
if (codepage == CP_UTF8) return &utf8_cpinfo;
|
|
if (codepage == CP_UTF7) return &utf7_cpinfo;
|
|
|
|
RtlEnterCriticalSection( &locale_section );
|
|
|
|
for (i = 0; i < nb_codepages; i++) if (codepages[i].CodePage == codepage) goto done;
|
|
|
|
if (i == ARRAY_SIZE( codepages ))
|
|
{
|
|
RtlLeaveCriticalSection( &locale_section );
|
|
ERR( "too many codepages\n" );
|
|
return NULL;
|
|
}
|
|
if (NtGetNlsSectionPtr( 11, codepage, NULL, (void **)&ptr, &size ))
|
|
{
|
|
RtlLeaveCriticalSection( &locale_section );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return NULL;
|
|
}
|
|
RtlInitCodePageTable( ptr, &codepages[i] );
|
|
nb_codepages++;
|
|
done:
|
|
RtlLeaveCriticalSection( &locale_section );
|
|
return &codepages[i];
|
|
}
|
|
|
|
|
|
static const WCHAR *get_ligature( WCHAR wc )
|
|
{
|
|
int low = 0, high = ARRAY_SIZE( ligatures ) -1;
|
|
while (low <= high)
|
|
{
|
|
int pos = (low + high) / 2;
|
|
if (ligatures[pos][0] < wc) low = pos + 1;
|
|
else if (ligatures[pos][0] > wc) high = pos - 1;
|
|
else return ligatures[pos] + 1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static NTSTATUS expand_ligatures( const WCHAR *src, int srclen, WCHAR *dst, int *dstlen )
|
|
{
|
|
int i, len, pos = 0;
|
|
NTSTATUS ret = STATUS_SUCCESS;
|
|
const WCHAR *expand;
|
|
|
|
for (i = 0; i < srclen; i++)
|
|
{
|
|
if (!(expand = get_ligature( src[i] )))
|
|
{
|
|
expand = src + i;
|
|
len = 1;
|
|
}
|
|
else len = lstrlenW( expand );
|
|
|
|
if (*dstlen && ret == STATUS_SUCCESS)
|
|
{
|
|
if (pos + len <= *dstlen) memcpy( dst + pos, expand, len * sizeof(WCHAR) );
|
|
else ret = STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
pos += len;
|
|
}
|
|
*dstlen = pos;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static NTSTATUS fold_digits( const WCHAR *src, int srclen, WCHAR *dst, int *dstlen )
|
|
{
|
|
NTSTATUS ret = STATUS_SUCCESS;
|
|
int len = casemap_string( charmaps[CHARMAP_FOLDDIGITS], src, srclen, dst, *dstlen );
|
|
|
|
if (*dstlen && *dstlen < len) ret = STATUS_BUFFER_TOO_SMALL;
|
|
*dstlen = len;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static NTSTATUS fold_string( DWORD flags, const WCHAR *src, int srclen, WCHAR *dst, int *dstlen )
|
|
{
|
|
NTSTATUS ret;
|
|
WCHAR *tmp;
|
|
|
|
switch (flags)
|
|
{
|
|
case MAP_PRECOMPOSED:
|
|
return RtlNormalizeString( NormalizationC, src, srclen, dst, dstlen );
|
|
case MAP_FOLDCZONE:
|
|
case MAP_PRECOMPOSED | MAP_FOLDCZONE:
|
|
return RtlNormalizeString( NormalizationKC, src, srclen, dst, dstlen );
|
|
case MAP_COMPOSITE:
|
|
return RtlNormalizeString( NormalizationD, src, srclen, dst, dstlen );
|
|
case MAP_COMPOSITE | MAP_FOLDCZONE:
|
|
return RtlNormalizeString( NormalizationKD, src, srclen, dst, dstlen );
|
|
case MAP_FOLDDIGITS:
|
|
return fold_digits( src, srclen, dst, dstlen );
|
|
case MAP_EXPAND_LIGATURES:
|
|
case MAP_EXPAND_LIGATURES | MAP_FOLDCZONE:
|
|
return expand_ligatures( src, srclen, dst, dstlen );
|
|
case MAP_FOLDDIGITS | MAP_PRECOMPOSED:
|
|
if (!(tmp = RtlAllocateHeap( GetProcessHeap(), 0, srclen * sizeof(WCHAR) )))
|
|
return STATUS_NO_MEMORY;
|
|
fold_digits( src, srclen, tmp, &srclen );
|
|
ret = RtlNormalizeString( NormalizationC, tmp, srclen, dst, dstlen );
|
|
break;
|
|
case MAP_FOLDDIGITS | MAP_FOLDCZONE:
|
|
case MAP_FOLDDIGITS | MAP_PRECOMPOSED | MAP_FOLDCZONE:
|
|
if (!(tmp = RtlAllocateHeap( GetProcessHeap(), 0, srclen * sizeof(WCHAR) )))
|
|
return STATUS_NO_MEMORY;
|
|
fold_digits( src, srclen, tmp, &srclen );
|
|
ret = RtlNormalizeString( NormalizationKC, tmp, srclen, dst, dstlen );
|
|
break;
|
|
case MAP_FOLDDIGITS | MAP_COMPOSITE:
|
|
if (!(tmp = RtlAllocateHeap( GetProcessHeap(), 0, srclen * sizeof(WCHAR) )))
|
|
return STATUS_NO_MEMORY;
|
|
fold_digits( src, srclen, tmp, &srclen );
|
|
ret = RtlNormalizeString( NormalizationD, tmp, srclen, dst, dstlen );
|
|
break;
|
|
case MAP_FOLDDIGITS | MAP_COMPOSITE | MAP_FOLDCZONE:
|
|
if (!(tmp = RtlAllocateHeap( GetProcessHeap(), 0, srclen * sizeof(WCHAR) )))
|
|
return STATUS_NO_MEMORY;
|
|
fold_digits( src, srclen, tmp, &srclen );
|
|
ret = RtlNormalizeString( NormalizationKD, tmp, srclen, dst, dstlen );
|
|
break;
|
|
case MAP_EXPAND_LIGATURES | MAP_FOLDDIGITS:
|
|
case MAP_EXPAND_LIGATURES | MAP_FOLDDIGITS | MAP_FOLDCZONE:
|
|
if (!(tmp = RtlAllocateHeap( GetProcessHeap(), 0, srclen * sizeof(WCHAR) )))
|
|
return STATUS_NO_MEMORY;
|
|
fold_digits( src, srclen, tmp, &srclen );
|
|
ret = expand_ligatures( tmp, srclen, dst, dstlen );
|
|
break;
|
|
default:
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
RtlFreeHeap( GetProcessHeap(), 0, tmp );
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int mbstowcs_cpsymbol( DWORD flags, const char *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
int len, i;
|
|
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (!dstlen) return srclen;
|
|
len = min( srclen, dstlen );
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
unsigned char c = src[i];
|
|
dst[i] = (c < 0x20) ? c : c + 0xf000;
|
|
}
|
|
if (len < srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
static int mbstowcs_utf7( DWORD flags, const char *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
static const signed char base64_decoding_table[] =
|
|
{
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x00-0x0F */
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10-0x1F */
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 0x20-0x2F */
|
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 0x30-0x3F */
|
|
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40-0x4F */
|
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 0x50-0x5F */
|
|
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60-0x6F */
|
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 /* 0x70-0x7F */
|
|
};
|
|
|
|
const char *source_end = src + srclen;
|
|
int offset = 0, pos = 0;
|
|
DWORD byte_pair = 0;
|
|
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
#define OUTPUT(ch) \
|
|
do { \
|
|
if (dstlen > 0) \
|
|
{ \
|
|
if (pos >= dstlen) goto overflow; \
|
|
dst[pos] = (ch); \
|
|
} \
|
|
pos++; \
|
|
} while(0)
|
|
|
|
while (src < source_end)
|
|
{
|
|
if (*src == '+')
|
|
{
|
|
src++;
|
|
if (src >= source_end) break;
|
|
if (*src == '-')
|
|
{
|
|
/* just a plus sign escaped as +- */
|
|
OUTPUT( '+' );
|
|
src++;
|
|
continue;
|
|
}
|
|
|
|
do
|
|
{
|
|
signed char sextet = *src;
|
|
if (sextet == '-')
|
|
{
|
|
/* skip over the dash and end base64 decoding
|
|
* the current, unfinished byte pair is discarded */
|
|
src++;
|
|
offset = 0;
|
|
break;
|
|
}
|
|
if (sextet < 0)
|
|
{
|
|
/* the next character of src is < 0 and therefore not part of a base64 sequence
|
|
* the current, unfinished byte pair is NOT discarded in this case
|
|
* this is probably a bug in Windows */
|
|
break;
|
|
}
|
|
sextet = base64_decoding_table[sextet];
|
|
if (sextet == -1)
|
|
{
|
|
/* -1 means that the next character of src is not part of a base64 sequence
|
|
* in other words, all sextets in this base64 sequence have been processed
|
|
* the current, unfinished byte pair is discarded */
|
|
offset = 0;
|
|
break;
|
|
}
|
|
|
|
byte_pair = (byte_pair << 6) | sextet;
|
|
offset += 6;
|
|
if (offset >= 16)
|
|
{
|
|
/* this byte pair is done */
|
|
OUTPUT( byte_pair >> (offset - 16) );
|
|
offset -= 16;
|
|
}
|
|
src++;
|
|
}
|
|
while (src < source_end);
|
|
}
|
|
else
|
|
{
|
|
OUTPUT( (unsigned char)*src );
|
|
src++;
|
|
}
|
|
}
|
|
return pos;
|
|
|
|
overflow:
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
#undef OUTPUT
|
|
}
|
|
|
|
|
|
static int mbstowcs_utf8( DWORD flags, const char *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
DWORD reslen;
|
|
NTSTATUS status;
|
|
|
|
if (!dstlen) dst = NULL;
|
|
status = RtlUTF8ToUnicodeN( dst, dstlen * sizeof(WCHAR), &reslen, src, srclen );
|
|
if (status == STATUS_SOME_NOT_MAPPED)
|
|
{
|
|
if (flags & MB_ERR_INVALID_CHARS)
|
|
{
|
|
SetLastError( ERROR_NO_UNICODE_TRANSLATION );
|
|
return 0;
|
|
}
|
|
}
|
|
else if (!set_ntstatus( status )) reslen = 0;
|
|
|
|
return reslen / sizeof(WCHAR);
|
|
}
|
|
|
|
|
|
static inline int is_private_use_area_char( WCHAR code )
|
|
{
|
|
return (code >= 0xe000 && code <= 0xf8ff);
|
|
}
|
|
|
|
|
|
static int check_invalid_chars( const CPTABLEINFO *info, const unsigned char *src, int srclen )
|
|
{
|
|
if (info->DBCSOffsets)
|
|
{
|
|
for ( ; srclen; src++, srclen-- )
|
|
{
|
|
USHORT off = info->DBCSOffsets[*src];
|
|
if (off)
|
|
{
|
|
if (srclen == 1) break; /* partial char, error */
|
|
if (info->DBCSOffsets[off + src[1]] == info->UniDefaultChar &&
|
|
((src[0] << 8) | src[1]) != info->TransUniDefaultChar) break;
|
|
src++;
|
|
srclen--;
|
|
continue;
|
|
}
|
|
if (info->MultiByteTable[*src] == info->UniDefaultChar && *src != info->TransUniDefaultChar)
|
|
break;
|
|
if (is_private_use_area_char( info->MultiByteTable[*src] )) break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( ; srclen; src++, srclen-- )
|
|
{
|
|
if (info->MultiByteTable[*src] == info->UniDefaultChar && *src != info->TransUniDefaultChar)
|
|
break;
|
|
if (is_private_use_area_char( info->MultiByteTable[*src] )) break;
|
|
}
|
|
}
|
|
return !!srclen;
|
|
|
|
}
|
|
|
|
|
|
static int mbstowcs_decompose( const CPTABLEINFO *info, const unsigned char *src, int srclen,
|
|
WCHAR *dst, int dstlen )
|
|
{
|
|
WCHAR ch;
|
|
USHORT off;
|
|
int len;
|
|
const WCHAR *decomp;
|
|
unsigned int decomp_len;
|
|
|
|
if (info->DBCSOffsets)
|
|
{
|
|
if (!dstlen) /* compute length */
|
|
{
|
|
for (len = 0; srclen; srclen--, src++, len += decomp_len)
|
|
{
|
|
if ((off = info->DBCSOffsets[*src]))
|
|
{
|
|
if (srclen > 1 && src[1])
|
|
{
|
|
src++;
|
|
srclen--;
|
|
ch = info->DBCSOffsets[off + *src];
|
|
}
|
|
else ch = info->UniDefaultChar;
|
|
}
|
|
else ch = info->MultiByteTable[*src];
|
|
get_decomposition( ch, &decomp_len );
|
|
}
|
|
return len;
|
|
}
|
|
|
|
for (len = dstlen; srclen && len; srclen--, src++, dst += decomp_len, len -= decomp_len)
|
|
{
|
|
if ((off = info->DBCSOffsets[*src]))
|
|
{
|
|
if (srclen > 1 && src[1])
|
|
{
|
|
src++;
|
|
srclen--;
|
|
ch = info->DBCSOffsets[off + *src];
|
|
}
|
|
else ch = info->UniDefaultChar;
|
|
}
|
|
else ch = info->MultiByteTable[*src];
|
|
|
|
if ((decomp = get_decomposition( ch, &decomp_len )))
|
|
{
|
|
if (len < decomp_len) break;
|
|
memcpy( dst, decomp, decomp_len * sizeof(WCHAR) );
|
|
}
|
|
else *dst = ch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!dstlen) /* compute length */
|
|
{
|
|
for (len = 0; srclen; srclen--, src++, len += decomp_len)
|
|
get_decomposition( info->MultiByteTable[*src], &decomp_len );
|
|
return len;
|
|
}
|
|
|
|
for (len = dstlen; srclen && len; srclen--, src++, dst += decomp_len, len -= decomp_len)
|
|
{
|
|
ch = info->MultiByteTable[*src];
|
|
if ((decomp = get_decomposition( ch, &decomp_len )))
|
|
{
|
|
if (len < decomp_len) break;
|
|
memcpy( dst, decomp, decomp_len * sizeof(WCHAR) );
|
|
}
|
|
else *dst = ch;
|
|
}
|
|
}
|
|
|
|
if (srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return dstlen - len;
|
|
}
|
|
|
|
|
|
static int mbstowcs_sbcs( const CPTABLEINFO *info, const unsigned char *src, int srclen,
|
|
WCHAR *dst, int dstlen )
|
|
{
|
|
const USHORT *table = info->MultiByteTable;
|
|
int ret = srclen;
|
|
|
|
if (!dstlen) return srclen;
|
|
|
|
if (dstlen < srclen) /* buffer too small: fill it up to dstlen and return error */
|
|
{
|
|
srclen = dstlen;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
ret = 0;
|
|
}
|
|
|
|
while (srclen >= 16)
|
|
{
|
|
dst[0] = table[src[0]];
|
|
dst[1] = table[src[1]];
|
|
dst[2] = table[src[2]];
|
|
dst[3] = table[src[3]];
|
|
dst[4] = table[src[4]];
|
|
dst[5] = table[src[5]];
|
|
dst[6] = table[src[6]];
|
|
dst[7] = table[src[7]];
|
|
dst[8] = table[src[8]];
|
|
dst[9] = table[src[9]];
|
|
dst[10] = table[src[10]];
|
|
dst[11] = table[src[11]];
|
|
dst[12] = table[src[12]];
|
|
dst[13] = table[src[13]];
|
|
dst[14] = table[src[14]];
|
|
dst[15] = table[src[15]];
|
|
src += 16;
|
|
dst += 16;
|
|
srclen -= 16;
|
|
}
|
|
|
|
/* now handle the remaining characters */
|
|
src += srclen;
|
|
dst += srclen;
|
|
switch (srclen)
|
|
{
|
|
case 15: dst[-15] = table[src[-15]];
|
|
case 14: dst[-14] = table[src[-14]];
|
|
case 13: dst[-13] = table[src[-13]];
|
|
case 12: dst[-12] = table[src[-12]];
|
|
case 11: dst[-11] = table[src[-11]];
|
|
case 10: dst[-10] = table[src[-10]];
|
|
case 9: dst[-9] = table[src[-9]];
|
|
case 8: dst[-8] = table[src[-8]];
|
|
case 7: dst[-7] = table[src[-7]];
|
|
case 6: dst[-6] = table[src[-6]];
|
|
case 5: dst[-5] = table[src[-5]];
|
|
case 4: dst[-4] = table[src[-4]];
|
|
case 3: dst[-3] = table[src[-3]];
|
|
case 2: dst[-2] = table[src[-2]];
|
|
case 1: dst[-1] = table[src[-1]];
|
|
case 0: break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int mbstowcs_dbcs( const CPTABLEINFO *info, const unsigned char *src, int srclen,
|
|
WCHAR *dst, int dstlen )
|
|
{
|
|
USHORT off;
|
|
int i;
|
|
|
|
if (!dstlen)
|
|
{
|
|
for (i = 0; srclen; i++, src++, srclen--)
|
|
if (info->DBCSOffsets[*src] && srclen > 1 && src[1]) { src++; srclen--; }
|
|
return i;
|
|
}
|
|
|
|
for (i = dstlen; srclen && i; i--, srclen--, src++, dst++)
|
|
{
|
|
if ((off = info->DBCSOffsets[*src]))
|
|
{
|
|
if (srclen > 1 && src[1])
|
|
{
|
|
src++;
|
|
srclen--;
|
|
*dst = info->DBCSOffsets[off + *src];
|
|
}
|
|
else *dst = info->UniDefaultChar;
|
|
}
|
|
else *dst = info->MultiByteTable[*src];
|
|
}
|
|
if (srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return dstlen - i;
|
|
}
|
|
|
|
|
|
static int mbstowcs_codepage( const CPTABLEINFO *info, DWORD flags, const char *src, int srclen,
|
|
WCHAR *dst, int dstlen )
|
|
{
|
|
CPTABLEINFO local_info;
|
|
const unsigned char *str = (const unsigned char *)src;
|
|
|
|
if ((flags & MB_USEGLYPHCHARS) && info->MultiByteTable[256] == 256)
|
|
{
|
|
local_info = *info;
|
|
local_info.MultiByteTable += 257;
|
|
info = &local_info;
|
|
}
|
|
if ((flags & MB_ERR_INVALID_CHARS) && check_invalid_chars( info, str, srclen ))
|
|
{
|
|
SetLastError( ERROR_NO_UNICODE_TRANSLATION );
|
|
return 0;
|
|
}
|
|
|
|
if (flags & MB_COMPOSITE) return mbstowcs_decompose( info, str, srclen, dst, dstlen );
|
|
|
|
if (info->DBCSOffsets)
|
|
return mbstowcs_dbcs( info, str, srclen, dst, dstlen );
|
|
else
|
|
return mbstowcs_sbcs( info, str, srclen, dst, dstlen );
|
|
}
|
|
|
|
|
|
static int wcstombs_cpsymbol( DWORD flags, const WCHAR *src, int srclen, char *dst, int dstlen,
|
|
const char *defchar, BOOL *used )
|
|
{
|
|
int len, i;
|
|
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (defchar || used)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (!dstlen) return srclen;
|
|
len = min( srclen, dstlen );
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (src[i] < 0x20) dst[i] = src[i];
|
|
else if (src[i] >= 0xf020 && src[i] < 0xf100) dst[i] = src[i] - 0xf000;
|
|
else
|
|
{
|
|
SetLastError( ERROR_NO_UNICODE_TRANSLATION );
|
|
return 0;
|
|
}
|
|
}
|
|
if (srclen > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
static int wcstombs_utf7( DWORD flags, const WCHAR *src, int srclen, char *dst, int dstlen,
|
|
const char *defchar, BOOL *used )
|
|
{
|
|
static const char directly_encodable[] =
|
|
{
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0x00 - 0x0f */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
|
|
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* 0x20 - 0x2f */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 0x30 - 0x3f */
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* 0x70 - 0x7a */
|
|
};
|
|
#define ENCODABLE(ch) ((ch) <= 0x7a && directly_encodable[(ch)])
|
|
|
|
static const char base64_encoding_table[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
const WCHAR *source_end = src + srclen;
|
|
int pos = 0;
|
|
|
|
if (defchar || used)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
#define OUTPUT(ch) \
|
|
do { \
|
|
if (dstlen > 0) \
|
|
{ \
|
|
if (pos >= dstlen) goto overflow; \
|
|
dst[pos] = (ch); \
|
|
} \
|
|
pos++; \
|
|
} while (0)
|
|
|
|
while (src < source_end)
|
|
{
|
|
if (*src == '+')
|
|
{
|
|
OUTPUT( '+' );
|
|
OUTPUT( '-' );
|
|
src++;
|
|
}
|
|
else if (ENCODABLE(*src))
|
|
{
|
|
OUTPUT( *src );
|
|
src++;
|
|
}
|
|
else
|
|
{
|
|
unsigned int offset = 0, byte_pair = 0;
|
|
|
|
OUTPUT( '+' );
|
|
while (src < source_end && !ENCODABLE(*src))
|
|
{
|
|
byte_pair = (byte_pair << 16) | *src;
|
|
offset += 16;
|
|
while (offset >= 6)
|
|
{
|
|
offset -= 6;
|
|
OUTPUT( base64_encoding_table[(byte_pair >> offset) & 0x3f] );
|
|
}
|
|
src++;
|
|
}
|
|
if (offset)
|
|
{
|
|
/* Windows won't create a padded base64 character if there's no room for the - sign
|
|
* as well ; this is probably a bug in Windows */
|
|
if (dstlen > 0 && pos + 1 >= dstlen) goto overflow;
|
|
byte_pair <<= (6 - offset);
|
|
OUTPUT( base64_encoding_table[byte_pair & 0x3f] );
|
|
}
|
|
/* Windows always explicitly terminates the base64 sequence
|
|
even though RFC 2152 (page 3, rule 2) does not require this */
|
|
OUTPUT( '-' );
|
|
}
|
|
}
|
|
return pos;
|
|
|
|
overflow:
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
#undef OUTPUT
|
|
#undef ENCODABLE
|
|
}
|
|
|
|
|
|
static int wcstombs_utf8( DWORD flags, const WCHAR *src, int srclen, char *dst, int dstlen,
|
|
const char *defchar, BOOL *used )
|
|
{
|
|
DWORD reslen;
|
|
NTSTATUS status;
|
|
|
|
if (used) *used = FALSE;
|
|
if (!dstlen) dst = NULL;
|
|
status = RtlUnicodeToUTF8N( dst, dstlen, &reslen, src, srclen * sizeof(WCHAR) );
|
|
if (status == STATUS_SOME_NOT_MAPPED)
|
|
{
|
|
if (flags & WC_ERR_INVALID_CHARS)
|
|
{
|
|
SetLastError( ERROR_NO_UNICODE_TRANSLATION );
|
|
return 0;
|
|
}
|
|
if (used) *used = TRUE;
|
|
}
|
|
else if (!set_ntstatus( status )) reslen = 0;
|
|
return reslen;
|
|
}
|
|
|
|
|
|
static int wcstombs_sbcs( const CPTABLEINFO *info, const WCHAR *src, unsigned int srclen,
|
|
char *dst, unsigned int dstlen )
|
|
{
|
|
const char *table = info->WideCharTable;
|
|
int ret = srclen;
|
|
|
|
if (!dstlen) return srclen;
|
|
|
|
if (dstlen < srclen)
|
|
{
|
|
/* buffer too small: fill it up to dstlen and return error */
|
|
srclen = dstlen;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
ret = 0;
|
|
}
|
|
|
|
while (srclen >= 16)
|
|
{
|
|
dst[0] = table[src[0]];
|
|
dst[1] = table[src[1]];
|
|
dst[2] = table[src[2]];
|
|
dst[3] = table[src[3]];
|
|
dst[4] = table[src[4]];
|
|
dst[5] = table[src[5]];
|
|
dst[6] = table[src[6]];
|
|
dst[7] = table[src[7]];
|
|
dst[8] = table[src[8]];
|
|
dst[9] = table[src[9]];
|
|
dst[10] = table[src[10]];
|
|
dst[11] = table[src[11]];
|
|
dst[12] = table[src[12]];
|
|
dst[13] = table[src[13]];
|
|
dst[14] = table[src[14]];
|
|
dst[15] = table[src[15]];
|
|
src += 16;
|
|
dst += 16;
|
|
srclen -= 16;
|
|
}
|
|
|
|
/* now handle remaining characters */
|
|
src += srclen;
|
|
dst += srclen;
|
|
switch(srclen)
|
|
{
|
|
case 15: dst[-15] = table[src[-15]];
|
|
case 14: dst[-14] = table[src[-14]];
|
|
case 13: dst[-13] = table[src[-13]];
|
|
case 12: dst[-12] = table[src[-12]];
|
|
case 11: dst[-11] = table[src[-11]];
|
|
case 10: dst[-10] = table[src[-10]];
|
|
case 9: dst[-9] = table[src[-9]];
|
|
case 8: dst[-8] = table[src[-8]];
|
|
case 7: dst[-7] = table[src[-7]];
|
|
case 6: dst[-6] = table[src[-6]];
|
|
case 5: dst[-5] = table[src[-5]];
|
|
case 4: dst[-4] = table[src[-4]];
|
|
case 3: dst[-3] = table[src[-3]];
|
|
case 2: dst[-2] = table[src[-2]];
|
|
case 1: dst[-1] = table[src[-1]];
|
|
case 0: break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int wcstombs_dbcs( const CPTABLEINFO *info, const WCHAR *src, unsigned int srclen,
|
|
char *dst, unsigned int dstlen )
|
|
{
|
|
const USHORT *table = info->WideCharTable;
|
|
int i;
|
|
|
|
if (!dstlen)
|
|
{
|
|
for (i = 0; srclen; src++, srclen--, i++) if (table[*src] & 0xff00) i++;
|
|
return i;
|
|
}
|
|
|
|
for (i = dstlen; srclen && i; i--, srclen--, src++)
|
|
{
|
|
if (table[*src] & 0xff00)
|
|
{
|
|
if (i == 1) break; /* do not output a partial char */
|
|
i--;
|
|
*dst++ = table[*src] >> 8;
|
|
}
|
|
*dst++ = (char)table[*src];
|
|
}
|
|
if (srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return dstlen - i;
|
|
}
|
|
|
|
|
|
static inline int is_valid_sbcs_mapping( const CPTABLEINFO *info, DWORD flags, unsigned int wch )
|
|
{
|
|
const unsigned char *table = info->WideCharTable;
|
|
|
|
if (wch >= 0x10000) return 0;
|
|
if ((flags & WC_NO_BEST_FIT_CHARS) || table[wch] == info->DefaultChar)
|
|
return (info->MultiByteTable[table[wch]] == wch);
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline int is_valid_dbcs_mapping( const CPTABLEINFO *info, DWORD flags, unsigned int wch )
|
|
{
|
|
const unsigned short *table = info->WideCharTable;
|
|
unsigned short ch;
|
|
|
|
if (wch >= 0x10000) return 0;
|
|
ch = table[wch];
|
|
if ((flags & WC_NO_BEST_FIT_CHARS) || ch == info->DefaultChar)
|
|
{
|
|
if (ch >> 8) return info->DBCSOffsets[info->DBCSOffsets[ch >> 8] + (ch & 0xff)] == wch;
|
|
return info->MultiByteTable[ch] == wch;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int wcstombs_sbcs_slow( const CPTABLEINFO *info, DWORD flags, const WCHAR *src, unsigned int srclen,
|
|
char *dst, unsigned int dstlen, const char *defchar, BOOL *used )
|
|
{
|
|
const char *table = info->WideCharTable;
|
|
const char def = defchar ? *defchar : (char)info->DefaultChar;
|
|
int i;
|
|
BOOL tmp;
|
|
WCHAR wch;
|
|
unsigned int composed;
|
|
|
|
if (!used) used = &tmp; /* avoid checking on every char */
|
|
*used = FALSE;
|
|
|
|
if (!dstlen)
|
|
{
|
|
for (i = 0; srclen; i++, src++, srclen--)
|
|
{
|
|
wch = *src;
|
|
if ((flags & WC_COMPOSITECHECK) && (srclen > 1) && (composed = compose_chars( src[0], src[1] )))
|
|
{
|
|
/* now check if we can use the composed char */
|
|
if (is_valid_sbcs_mapping( info, flags, composed ))
|
|
{
|
|
/* we have a good mapping, use it */
|
|
src++;
|
|
srclen--;
|
|
continue;
|
|
}
|
|
/* no mapping for the composed char, check the other flags */
|
|
if (flags & WC_DEFAULTCHAR) /* use the default char instead */
|
|
{
|
|
*used = TRUE;
|
|
src++; /* skip the non-spacing char */
|
|
srclen--;
|
|
continue;
|
|
}
|
|
if (flags & WC_DISCARDNS) /* skip the second char of the composition */
|
|
{
|
|
src++;
|
|
srclen--;
|
|
}
|
|
/* WC_SEPCHARS is the default */
|
|
}
|
|
if (!*used) *used = !is_valid_sbcs_mapping( info, flags, wch );
|
|
}
|
|
return i;
|
|
}
|
|
|
|
for (i = dstlen; srclen && i; dst++, i--, src++, srclen--)
|
|
{
|
|
wch = *src;
|
|
if ((flags & WC_COMPOSITECHECK) && (srclen > 1) && (composed = compose_chars( src[0], src[1] )))
|
|
{
|
|
/* now check if we can use the composed char */
|
|
if (is_valid_sbcs_mapping( info, flags, composed ))
|
|
{
|
|
/* we have a good mapping, use it */
|
|
*dst = table[composed];
|
|
src++;
|
|
srclen--;
|
|
continue;
|
|
}
|
|
/* no mapping for the composed char, check the other flags */
|
|
if (flags & WC_DEFAULTCHAR) /* use the default char instead */
|
|
{
|
|
*dst = def;
|
|
*used = TRUE;
|
|
src++; /* skip the non-spacing char */
|
|
srclen--;
|
|
continue;
|
|
}
|
|
if (flags & WC_DISCARDNS) /* skip the second char of the composition */
|
|
{
|
|
src++;
|
|
srclen--;
|
|
}
|
|
/* WC_SEPCHARS is the default */
|
|
}
|
|
|
|
*dst = table[wch];
|
|
if (!is_valid_sbcs_mapping( info, flags, wch ))
|
|
{
|
|
*dst = def;
|
|
*used = TRUE;
|
|
}
|
|
}
|
|
if (srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return dstlen - i;
|
|
}
|
|
|
|
|
|
static int wcstombs_dbcs_slow( const CPTABLEINFO *info, DWORD flags, const WCHAR *src, unsigned int srclen,
|
|
char *dst, unsigned int dstlen, const char *defchar, BOOL *used )
|
|
{
|
|
const USHORT *table = info->WideCharTable;
|
|
WCHAR wch, defchar_value;
|
|
unsigned int composed;
|
|
unsigned short res;
|
|
BOOL tmp;
|
|
int i;
|
|
|
|
if (!defchar[1]) defchar_value = (unsigned char)defchar[0];
|
|
else defchar_value = ((unsigned char)defchar[0] << 8) | (unsigned char)defchar[1];
|
|
|
|
if (!used) used = &tmp; /* avoid checking on every char */
|
|
*used = FALSE;
|
|
|
|
if (!dstlen)
|
|
{
|
|
if (!defchar && !used && !(flags & WC_COMPOSITECHECK))
|
|
{
|
|
for (i = 0; srclen; srclen--, src++, i++) if (table[*src] & 0xff00) i++;
|
|
return i;
|
|
}
|
|
for (i = 0; srclen; srclen--, src++, i++)
|
|
{
|
|
wch = *src;
|
|
if ((flags & WC_COMPOSITECHECK) && (srclen > 1) && (composed = compose_chars( src[0], src[1] )))
|
|
{
|
|
/* now check if we can use the composed char */
|
|
if (is_valid_dbcs_mapping( info, flags, composed ))
|
|
{
|
|
/* we have a good mapping for the composed char, use it */
|
|
res = table[composed];
|
|
if (res & 0xff00) i++;
|
|
src++;
|
|
srclen--;
|
|
continue;
|
|
}
|
|
/* no mapping for the composed char, check the other flags */
|
|
if (flags & WC_DEFAULTCHAR) /* use the default char instead */
|
|
{
|
|
if (defchar_value & 0xff00) i++;
|
|
*used = TRUE;
|
|
src++; /* skip the non-spacing char */
|
|
srclen--;
|
|
continue;
|
|
}
|
|
if (flags & WC_DISCARDNS) /* skip the second char of the composition */
|
|
{
|
|
src++;
|
|
srclen--;
|
|
}
|
|
/* WC_SEPCHARS is the default */
|
|
}
|
|
|
|
res = table[wch];
|
|
if (!is_valid_dbcs_mapping( info, flags, wch ))
|
|
{
|
|
res = defchar_value;
|
|
*used = TRUE;
|
|
}
|
|
if (res & 0xff00) i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
for (i = dstlen; srclen && i; i--, srclen--, src++)
|
|
{
|
|
wch = *src;
|
|
if ((flags & WC_COMPOSITECHECK) && (srclen > 1) && (composed = compose_chars( src[0], src[1] )))
|
|
{
|
|
/* now check if we can use the composed char */
|
|
if (is_valid_dbcs_mapping( info, flags, composed ))
|
|
{
|
|
/* we have a good mapping for the composed char, use it */
|
|
res = table[composed];
|
|
src++;
|
|
srclen--;
|
|
goto output_char;
|
|
}
|
|
/* no mapping for the composed char, check the other flags */
|
|
if (flags & WC_DEFAULTCHAR) /* use the default char instead */
|
|
{
|
|
res = defchar_value;
|
|
*used = TRUE;
|
|
src++; /* skip the non-spacing char */
|
|
srclen--;
|
|
goto output_char;
|
|
}
|
|
if (flags & WC_DISCARDNS) /* skip the second char of the composition */
|
|
{
|
|
src++;
|
|
srclen--;
|
|
}
|
|
/* WC_SEPCHARS is the default */
|
|
}
|
|
|
|
res = table[wch];
|
|
if (!is_valid_dbcs_mapping( info, flags, wch ))
|
|
{
|
|
res = defchar_value;
|
|
*used = TRUE;
|
|
}
|
|
|
|
output_char:
|
|
if (res & 0xff00)
|
|
{
|
|
if (i == 1) break; /* do not output a partial char */
|
|
i--;
|
|
*dst++ = res >> 8;
|
|
}
|
|
*dst++ = (char)res;
|
|
}
|
|
if (srclen)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return dstlen - i;
|
|
}
|
|
|
|
|
|
static int wcstombs_codepage( const CPTABLEINFO *info, DWORD flags, const WCHAR *src, int srclen,
|
|
char *dst, int dstlen, const char *defchar, BOOL *used )
|
|
{
|
|
if (flags || defchar || used)
|
|
{
|
|
if (!defchar) defchar = (const char *)&info->DefaultChar;
|
|
if (info->DBCSOffsets)
|
|
return wcstombs_dbcs_slow( info, flags, src, srclen, dst, dstlen, defchar, used );
|
|
else
|
|
return wcstombs_sbcs_slow( info, flags, src, srclen, dst, dstlen, defchar, used );
|
|
}
|
|
if (info->DBCSOffsets)
|
|
return wcstombs_dbcs( info, src, srclen, dst, dstlen );
|
|
else
|
|
return wcstombs_sbcs( info, src, srclen, dst, dstlen );
|
|
}
|
|
|
|
|
|
struct sortkey
|
|
{
|
|
BYTE *buf;
|
|
BYTE *new_buf; /* allocated buf if static buf is not large enough */
|
|
UINT size; /* buffer size */
|
|
UINT max; /* max possible size */
|
|
UINT len; /* current key length */
|
|
};
|
|
|
|
static void append_sortkey( struct sortkey *key, BYTE val )
|
|
{
|
|
if (key->len >= key->max) return;
|
|
if (key->len >= key->size)
|
|
{
|
|
key->new_buf = RtlAllocateHeap( GetProcessHeap(), 0, key->max );
|
|
if (key->new_buf) memcpy( key->new_buf, key->buf, key->len );
|
|
else key->max = 0;
|
|
key->buf = key->new_buf;
|
|
key->size = key->max;
|
|
}
|
|
key->buf[key->len++] = val;
|
|
}
|
|
|
|
static void reverse_sortkey( struct sortkey *key )
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < key->len / 2; i++)
|
|
{
|
|
BYTE tmp = key->buf[key->len - i - 1];
|
|
key->buf[key->len - i - 1] = key->buf[i];
|
|
key->buf[i] = tmp;
|
|
}
|
|
}
|
|
|
|
static int compare_sortkeys( const struct sortkey *key1, const struct sortkey *key2, BOOL shorter_wins )
|
|
{
|
|
int ret = memcmp( key1->buf, key2->buf, min( key1->len, key2->len ));
|
|
if (!ret) ret = shorter_wins ? key2->len - key1->len : key1->len - key2->len;
|
|
return ret;
|
|
}
|
|
|
|
static void append_normal_weights( const struct sortguid *sortid, struct sortkey *key_primary,
|
|
struct sortkey *key_diacritic, struct sortkey *key_case,
|
|
union char_weights weights, DWORD flags )
|
|
{
|
|
append_sortkey( key_primary, weights.script );
|
|
append_sortkey( key_primary, weights.primary );
|
|
|
|
if ((weights.script >= SCRIPT_PUA_FIRST && weights.script <= SCRIPT_PUA_LAST) ||
|
|
((sortid->flags & FLAG_HAS_3_BYTE_WEIGHTS) &&
|
|
(weights.script >= SCRIPT_CJK_FIRST && weights.script <= SCRIPT_CJK_LAST)))
|
|
{
|
|
append_sortkey( key_primary, weights.diacritic );
|
|
append_sortkey( key_case, weights._case );
|
|
return;
|
|
}
|
|
if (weights.script <= SCRIPT_ARABIC && weights.script != SCRIPT_HEBREW)
|
|
{
|
|
if (flags & LINGUISTIC_IGNOREDIACRITIC) weights.diacritic = 2;
|
|
if (flags & LINGUISTIC_IGNORECASE) weights._case = 2;
|
|
}
|
|
append_sortkey( key_diacritic, weights.diacritic );
|
|
append_sortkey( key_case, weights._case );
|
|
}
|
|
|
|
static void append_nonspace_weights( struct sortkey *key, union char_weights weights, DWORD flags )
|
|
{
|
|
if (flags & LINGUISTIC_IGNOREDIACRITIC) weights.diacritic = 2;
|
|
if (key->len) key->buf[key->len - 1] += weights.diacritic;
|
|
else append_sortkey( key, weights.diacritic );
|
|
}
|
|
|
|
static void append_expansion_weights( const struct sortguid *sortid, struct sortkey *key_primary,
|
|
struct sortkey *key_diacritic, struct sortkey *key_case,
|
|
union char_weights weights, DWORD flags, BOOL is_compare )
|
|
{
|
|
/* sortkey and comparison behave differently here */
|
|
if (is_compare)
|
|
{
|
|
if (weights.script == SCRIPT_UNSORTABLE) return;
|
|
if (weights.script == SCRIPT_NONSPACE_MARK)
|
|
{
|
|
append_nonspace_weights( key_diacritic, weights, flags );
|
|
return;
|
|
}
|
|
}
|
|
append_normal_weights( sortid, key_primary, key_diacritic, key_case, weights, flags );
|
|
}
|
|
|
|
static const UINT *find_compression( const WCHAR *src, const WCHAR *table, int count, int len )
|
|
{
|
|
int elem_size = compression_size( len ), min = 0, max = count - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int pos = (min + max) / 2;
|
|
int res = wcsncmp( src, table + pos * elem_size, len );
|
|
if (!res) return (UINT *)(table + (pos + 1) * elem_size) - 1;
|
|
if (res > 0) min = pos + 1;
|
|
else max = pos - 1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* find a compression for a char sequence */
|
|
/* return the number of extra chars to skip */
|
|
static int get_compression_weights( UINT compression, const WCHAR *compr_tables[8],
|
|
const WCHAR *src, int srclen, union char_weights *weights )
|
|
{
|
|
const struct sort_compression *compr = sort.compressions + compression;
|
|
const UINT *ret;
|
|
BYTE size = weights->_case & CASE_COMPR_6;
|
|
int i, maxlen = 1;
|
|
|
|
if (compression >= sort.compr_count) return 0;
|
|
if (size == CASE_COMPR_6) maxlen = 8;
|
|
else if (size == CASE_COMPR_4) maxlen = 5;
|
|
else if (size == CASE_COMPR_2) maxlen = 3;
|
|
maxlen = min( maxlen, srclen );
|
|
for (i = 0; i < maxlen; i++) if (src[i] < compr->minchar || src[i] > compr->maxchar) break;
|
|
maxlen = i;
|
|
if (!compr_tables[0])
|
|
{
|
|
compr_tables[0] = sort.compr_data + compr->offset;
|
|
for (i = 1; i < 8; i++)
|
|
compr_tables[i] = compr_tables[i - 1] + compr->len[i - 1] * compression_size( i + 1 );
|
|
}
|
|
for (i = maxlen - 2; i >= 0; i--)
|
|
{
|
|
if (!(ret = find_compression( src, compr_tables[i], compr->len[i], i + 2 ))) continue;
|
|
weights->val = *ret;
|
|
return i + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* get the zero digit for the digit character range that contains 'ch' */
|
|
static WCHAR get_digit_zero_char( WCHAR ch )
|
|
{
|
|
static const WCHAR zeroes[] =
|
|
{
|
|
0x0030, 0x0660, 0x06f0, 0x0966, 0x09e6, 0x0a66, 0x0ae6, 0x0b66, 0x0be6, 0x0c66,
|
|
0x0ce6, 0x0d66, 0x0e50, 0x0ed0, 0x0f20, 0x1040, 0x1090, 0x17e0, 0x1810, 0x1946,
|
|
0x1bb0, 0x1c40, 0x1c50, 0xa620, 0xa8d0, 0xa900, 0xaa50, 0xff10
|
|
};
|
|
int min = 0, max = ARRAY_SIZE( zeroes ) - 1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int pos = (min + max) / 2;
|
|
if (zeroes[pos] <= ch && zeroes[pos] + 9 >= ch) return zeroes[pos];
|
|
if (zeroes[pos] < ch) min = pos + 1;
|
|
else max = pos - 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* append weights for digits when using SORT_DIGITSASNUMBERS */
|
|
/* return the number of extra chars to skip */
|
|
static int append_digit_weights( struct sortkey *key, const WCHAR *src, UINT srclen )
|
|
{
|
|
UINT i, zero, len, lzero;
|
|
BYTE val, values[19];
|
|
|
|
if (!(zero = get_digit_zero_char( *src ))) return -1;
|
|
|
|
values[0] = *src - zero;
|
|
for (len = 1; len < ARRAY_SIZE(values) && len < srclen; len++)
|
|
{
|
|
if (src[len] < zero || src[len] > zero + 9) break;
|
|
values[len] = src[len] - zero;
|
|
}
|
|
for (lzero = 0; lzero < len; lzero++) if (values[lzero]) break;
|
|
|
|
append_sortkey( key, SCRIPT_DIGIT );
|
|
append_sortkey( key, 2 );
|
|
append_sortkey( key, 2 + len - lzero );
|
|
for (i = lzero, val = 2; i < len; i++)
|
|
{
|
|
if ((len - i) % 2) append_sortkey( key, (val << 4) + values[i] + 2 );
|
|
else val = values[i] + 2;
|
|
}
|
|
append_sortkey( key, 0xfe - lzero );
|
|
return len - 1;
|
|
}
|
|
|
|
/* append the extra weights for kana prolonged sound / repeat marks */
|
|
static int append_extra_kana_weights( struct sortkey keys[4], const WCHAR *src, int pos, UINT except,
|
|
BYTE case_mask, union char_weights *weights )
|
|
{
|
|
BYTE extra1 = 3, case_weight = weights->_case;
|
|
|
|
if (weights->primary <= 1)
|
|
{
|
|
while (pos > 0)
|
|
{
|
|
union char_weights prev = get_char_weights( src[--pos], except );
|
|
if (prev.script == SCRIPT_UNSORTABLE || prev.script == SCRIPT_NONSPACE_MARK) continue;
|
|
if (prev.script == SCRIPT_EXPANSION) return 0;
|
|
if (prev.script != SCRIPT_EASTASIA_SPECIAL)
|
|
{
|
|
*weights = prev;
|
|
return 1;
|
|
}
|
|
if (prev.primary <= 1) continue;
|
|
|
|
case_weight = prev._case & case_mask;
|
|
if (weights->primary == 1) /* prolonged sound mark */
|
|
{
|
|
prev.primary &= 0x87;
|
|
case_weight &= ~CASE_FULLWIDTH;
|
|
case_weight |= weights->_case & CASE_FULLWIDTH;
|
|
}
|
|
extra1 = 4 + weights->primary;
|
|
weights->primary = prev.primary;
|
|
goto done;
|
|
}
|
|
return 0;
|
|
}
|
|
done:
|
|
append_sortkey( &keys[0], 0xc4 | (case_weight & CASE_FULLSIZE) );
|
|
append_sortkey( &keys[1], extra1 );
|
|
append_sortkey( &keys[2], 0xc4 | (case_weight & CASE_KATAKANA) );
|
|
append_sortkey( &keys[3], 0xc4 | (case_weight & CASE_FULLWIDTH) );
|
|
weights->script = SCRIPT_KANA;
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define HANGUL_SBASE 0xac00
|
|
#define HANGUL_LCOUNT 19
|
|
#define HANGUL_VCOUNT 21
|
|
#define HANGUL_TCOUNT 28
|
|
|
|
static int append_hangul_weights( struct sortkey *key, const WCHAR *src, int srclen, UINT except )
|
|
{
|
|
int leading_idx = 0x115f - 0x1100; /* leading filler */
|
|
int vowel_idx = 0x1160 - 0x1100; /* vowel filler */
|
|
int trailing_idx = -1;
|
|
BYTE leading_off, vowel_off, trailing_off;
|
|
union char_weights weights;
|
|
WCHAR composed;
|
|
BYTE filler_mask = 0;
|
|
int pos = 0;
|
|
|
|
/* leading */
|
|
if (src[pos] >= 0x1100 && src[pos] <= 0x115f) leading_idx = src[pos++] - 0x1100;
|
|
else if (src[pos] >= 0xa960 && src[pos] <= 0xa97c) leading_idx = src[pos++] - (0xa960 - 0x100);
|
|
|
|
/* vowel */
|
|
if (srclen > pos)
|
|
{
|
|
if (src[pos] >= 0x1160 && src[pos] <= 0x11a7) vowel_idx = src[pos++] - 0x1100;
|
|
else if (src[pos] >= 0xd7b0 && src[pos] <= 0xd7c6) vowel_idx = src[pos++] - (0xd7b0 - 0x11d);
|
|
}
|
|
|
|
/* trailing */
|
|
if (srclen > pos)
|
|
{
|
|
if (src[pos] >= 0x11a8 && src[pos] <= 0x11ff) trailing_idx = src[pos++] - 0x1100;
|
|
else if (src[pos] >= 0xd7cb && src[pos] <= 0xd7fb) trailing_idx = src[pos++] - (0xd7cb - 0x134);
|
|
}
|
|
|
|
if (!sort.jamo[leading_idx].is_old && !sort.jamo[vowel_idx].is_old &&
|
|
(trailing_idx == -1 || !sort.jamo[trailing_idx].is_old))
|
|
{
|
|
/* not old Hangul, only use leading char; vowel and trailing will be handled in the next pass */
|
|
pos = 1;
|
|
vowel_idx = 0x1160 - 0x1100;
|
|
trailing_idx = -1;
|
|
}
|
|
|
|
leading_off = max( sort.jamo[leading_idx].leading, sort.jamo[vowel_idx].leading );
|
|
vowel_off = max( sort.jamo[leading_idx].vowel, sort.jamo[vowel_idx].vowel );
|
|
trailing_off = max( sort.jamo[leading_idx].trailing, sort.jamo[vowel_idx].trailing );
|
|
if (trailing_idx != -1) trailing_off = max( trailing_off, sort.jamo[trailing_idx].trailing );
|
|
composed = HANGUL_SBASE + (leading_off * HANGUL_VCOUNT + vowel_off) * HANGUL_TCOUNT + trailing_off;
|
|
|
|
if (leading_idx == 0x115f - 0x1100 || vowel_idx == 0x1160 - 0x1100)
|
|
{
|
|
filler_mask = 0x80;
|
|
composed--;
|
|
}
|
|
if (composed < HANGUL_SBASE) composed = 0x3260;
|
|
|
|
weights = get_char_weights( composed, except );
|
|
append_sortkey( key, weights.script );
|
|
append_sortkey( key, weights.primary );
|
|
append_sortkey( key, 0xff );
|
|
append_sortkey( key, sort.jamo[leading_idx].weight | filler_mask );
|
|
append_sortkey( key, 0xff );
|
|
append_sortkey( key, sort.jamo[vowel_idx].weight );
|
|
append_sortkey( key, 0xff );
|
|
append_sortkey( key, trailing_idx != -1 ? sort.jamo[trailing_idx].weight : 2 );
|
|
return pos - 1;
|
|
}
|
|
|
|
/* put one of the elements of a sortkey into the dst buffer */
|
|
static int put_sortkey( BYTE *dst, int dstlen, int pos, const struct sortkey *key, BYTE terminator )
|
|
{
|
|
if (dstlen > pos + key->len)
|
|
{
|
|
memcpy( dst + pos, key->buf, key->len );
|
|
dst[pos + key->len] = terminator;
|
|
}
|
|
return pos + key->len + 1;
|
|
}
|
|
|
|
|
|
struct sortkey_state
|
|
{
|
|
struct sortkey key_primary;
|
|
struct sortkey key_diacritic;
|
|
struct sortkey key_case;
|
|
struct sortkey key_special;
|
|
struct sortkey key_extra[4];
|
|
UINT primary_pos;
|
|
BYTE buffer[3 * 128];
|
|
};
|
|
|
|
static void init_sortkey_state( struct sortkey_state *s, DWORD flags, UINT srclen,
|
|
BYTE *primary_buf, UINT primary_size )
|
|
{
|
|
/* buffer for secondary weights */
|
|
BYTE *secondary_buf = s->buffer;
|
|
UINT secondary_size;
|
|
|
|
memset( s, 0, offsetof( struct sortkey_state, buffer ));
|
|
|
|
s->key_primary.buf = primary_buf;
|
|
s->key_primary.size = primary_size;
|
|
|
|
if (!(flags & NORM_IGNORENONSPACE)) /* reserve space for diacritics */
|
|
{
|
|
secondary_size = sizeof(s->buffer) / 3;
|
|
s->key_diacritic.buf = secondary_buf;
|
|
s->key_diacritic.size = secondary_size;
|
|
secondary_buf += secondary_size;
|
|
}
|
|
else secondary_size = sizeof(s->buffer) / 2;
|
|
|
|
s->key_case.buf = secondary_buf;
|
|
s->key_case.size = secondary_size;
|
|
s->key_special.buf = secondary_buf + secondary_size;
|
|
s->key_special.size = secondary_size;
|
|
|
|
s->key_primary.max = srclen * 8;
|
|
s->key_case.max = srclen * 3;
|
|
s->key_special.max = srclen * 4;
|
|
s->key_extra[2].max = s->key_extra[3].max = srclen;
|
|
if (!(flags & NORM_IGNORENONSPACE))
|
|
{
|
|
s->key_diacritic.max = srclen * 3;
|
|
s->key_extra[0].max = s->key_extra[1].max = srclen;
|
|
}
|
|
}
|
|
|
|
static BOOL remove_unneeded_weights( const struct sortguid *sortid, struct sortkey_state *s )
|
|
{
|
|
const BYTE ignore[4] = { 0xc4 | CASE_FULLSIZE, 0x03, 0xc4 | CASE_KATAKANA, 0xc4 | CASE_FULLWIDTH };
|
|
int i, j;
|
|
|
|
if (sortid->flags & FLAG_REVERSEDIACRITICS) reverse_sortkey( &s->key_diacritic );
|
|
|
|
for (i = s->key_diacritic.len; i > 0; i--) if (s->key_diacritic.buf[i - 1] > 2) break;
|
|
s->key_diacritic.len = i;
|
|
|
|
for (i = s->key_case.len; i > 0; i--) if (s->key_case.buf[i - 1] > 2) break;
|
|
s->key_case.len = i;
|
|
|
|
if (!s->key_extra[2].len) return FALSE;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
for (j = s->key_extra[i].len; j > 0; j--) if (s->key_extra[i].buf[j - 1] != ignore[i]) break;
|
|
s->key_extra[i].len = j;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void free_sortkey_state( struct sortkey_state *s )
|
|
{
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_primary.new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_diacritic.new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_case.new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_special.new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_extra[0].new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_extra[1].new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_extra[2].new_buf );
|
|
RtlFreeHeap( GetProcessHeap(), 0, s->key_extra[3].new_buf );
|
|
}
|
|
|
|
static int append_weights( const struct sortguid *sortid, DWORD flags,
|
|
const WCHAR *src, int srclen, int pos, BYTE case_mask, UINT except,
|
|
const WCHAR *compr_tables[8], struct sortkey_state *s, BOOL is_compare )
|
|
{
|
|
union char_weights weights = get_char_weights( src[pos], except );
|
|
WCHAR idx = (weights.val >> 16) & ~(CASE_COMPR_6 << 8); /* expansion index */
|
|
int ret = 1;
|
|
|
|
if (weights._case & CASE_COMPR_6)
|
|
ret += get_compression_weights( sortid->compr, compr_tables, src + pos, srclen - pos, &weights );
|
|
|
|
weights._case &= case_mask;
|
|
|
|
switch (weights.script)
|
|
{
|
|
case SCRIPT_UNSORTABLE:
|
|
break;
|
|
|
|
case SCRIPT_NONSPACE_MARK:
|
|
append_nonspace_weights( &s->key_diacritic, weights, flags );
|
|
break;
|
|
|
|
case SCRIPT_EXPANSION:
|
|
while (weights.script == SCRIPT_EXPANSION)
|
|
{
|
|
weights = get_char_weights( sort.expansions[idx].exp[0], except );
|
|
weights._case &= case_mask;
|
|
append_expansion_weights( sortid, &s->key_primary, &s->key_diacritic,
|
|
&s->key_case, weights, flags, is_compare );
|
|
weights = get_char_weights( sort.expansions[idx].exp[1], except );
|
|
idx = weights.val >> 16;
|
|
weights._case &= case_mask;
|
|
}
|
|
append_expansion_weights( sortid, &s->key_primary, &s->key_diacritic,
|
|
&s->key_case, weights, flags, is_compare );
|
|
break;
|
|
|
|
case SCRIPT_EASTASIA_SPECIAL:
|
|
if (!append_extra_kana_weights( s->key_extra, src, pos, except, case_mask, &weights ))
|
|
{
|
|
append_sortkey( &s->key_primary, 0xff );
|
|
append_sortkey( &s->key_primary, 0xff );
|
|
break;
|
|
}
|
|
weights._case = 2;
|
|
append_normal_weights( sortid, &s->key_primary, &s->key_diacritic, &s->key_case, weights, flags );
|
|
break;
|
|
|
|
case SCRIPT_JAMO_SPECIAL:
|
|
ret += append_hangul_weights( &s->key_primary, src + pos, srclen - pos, except );
|
|
append_sortkey( &s->key_diacritic, 2 );
|
|
append_sortkey( &s->key_case, 2 );
|
|
break;
|
|
|
|
case SCRIPT_EXTENSION_A:
|
|
append_sortkey( &s->key_primary, 0xfd );
|
|
append_sortkey( &s->key_primary, 0xff );
|
|
append_sortkey( &s->key_primary, weights.primary );
|
|
append_sortkey( &s->key_primary, weights.diacritic );
|
|
append_sortkey( &s->key_diacritic, 2 );
|
|
append_sortkey( &s->key_case, 2 );
|
|
break;
|
|
|
|
case SCRIPT_PUNCTUATION:
|
|
if (flags & NORM_IGNORESYMBOLS) break;
|
|
if (!(flags & SORT_STRINGSORT))
|
|
{
|
|
short len = -((s->key_primary.len + s->primary_pos) / 2) - 1;
|
|
if (flags & LINGUISTIC_IGNORECASE) weights._case = 2;
|
|
if (flags & LINGUISTIC_IGNOREDIACRITIC) weights.diacritic = 2;
|
|
append_sortkey( &s->key_special, len >> 8 );
|
|
append_sortkey( &s->key_special, len & 0xff );
|
|
append_sortkey( &s->key_special, weights.primary );
|
|
append_sortkey( &s->key_special, weights._case | (weights.diacritic << 3) );
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case SCRIPT_SYMBOL_1:
|
|
case SCRIPT_SYMBOL_2:
|
|
case SCRIPT_SYMBOL_3:
|
|
case SCRIPT_SYMBOL_4:
|
|
case SCRIPT_SYMBOL_5:
|
|
case SCRIPT_SYMBOL_6:
|
|
if (flags & NORM_IGNORESYMBOLS) break;
|
|
append_sortkey( &s->key_primary, weights.script );
|
|
append_sortkey( &s->key_primary, weights.primary );
|
|
append_sortkey( &s->key_diacritic, weights.diacritic );
|
|
append_sortkey( &s->key_case, weights._case );
|
|
break;
|
|
|
|
case SCRIPT_DIGIT:
|
|
if (flags & SORT_DIGITSASNUMBERS)
|
|
{
|
|
int len = append_digit_weights( &s->key_primary, src + pos, srclen - pos );
|
|
if (len >= 0)
|
|
{
|
|
ret += len;
|
|
append_sortkey( &s->key_diacritic, weights.diacritic );
|
|
append_sortkey( &s->key_case, weights._case );
|
|
break;
|
|
}
|
|
}
|
|
/* fall through */
|
|
default:
|
|
append_normal_weights( sortid, &s->key_primary, &s->key_diacritic, &s->key_case, weights, flags );
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* implementation of LCMAP_SORTKEY */
|
|
static int get_sortkey( const struct sortguid *sortid, DWORD flags,
|
|
const WCHAR *src, int srclen, BYTE *dst, int dstlen )
|
|
{
|
|
struct sortkey_state s;
|
|
BYTE primary_buf[256];
|
|
int ret = 0, pos = 0;
|
|
BOOL have_extra;
|
|
BYTE case_mask = 0x3f;
|
|
UINT except = sortid->except;
|
|
const WCHAR *compr_tables[8];
|
|
|
|
compr_tables[0] = NULL;
|
|
if (flags & NORM_IGNORECASE) case_mask &= ~(CASE_UPPER | CASE_SUBSCRIPT);
|
|
if (flags & NORM_IGNOREWIDTH) case_mask &= ~CASE_FULLWIDTH;
|
|
if (flags & NORM_IGNOREKANATYPE) case_mask &= ~CASE_KATAKANA;
|
|
if ((flags & NORM_LINGUISTIC_CASING) && except && sortid->ling_except) except = sortid->ling_except;
|
|
|
|
init_sortkey_state( &s, flags, srclen, primary_buf, sizeof(primary_buf) );
|
|
|
|
while (pos < srclen)
|
|
pos += append_weights( sortid, flags, src, srclen, pos, case_mask, except, compr_tables, &s, FALSE );
|
|
|
|
have_extra = remove_unneeded_weights( sortid, &s );
|
|
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_primary, 0x01 );
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_diacritic, 0x01 );
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_case, 0x01 );
|
|
|
|
if (have_extra)
|
|
{
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_extra[0], 0xff );
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_extra[1], 0x02 );
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_extra[2], 0xff );
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_extra[3], 0xff );
|
|
}
|
|
if (dstlen > ret) dst[ret] = 0x01;
|
|
ret++;
|
|
|
|
ret = put_sortkey( dst, dstlen, ret, &s.key_special, 0 );
|
|
|
|
free_sortkey_state( &s );
|
|
|
|
if (dstlen && dstlen < ret)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
if (flags & LCMAP_BYTEREV)
|
|
map_byterev( (WCHAR *)dst, min( ret, dstlen ) / sizeof(WCHAR), (WCHAR *)dst );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* implementation of CompareStringEx */
|
|
static int compare_string( const struct sortguid *sortid, DWORD flags,
|
|
const WCHAR *src1, int srclen1, const WCHAR *src2, int srclen2 )
|
|
{
|
|
struct sortkey_state s1;
|
|
struct sortkey_state s2;
|
|
BYTE primary1[32];
|
|
BYTE primary2[32];
|
|
int i, ret, len, pos1 = 0, pos2 = 0;
|
|
BOOL have_extra1, have_extra2;
|
|
BYTE case_mask = 0x3f;
|
|
UINT except = sortid->except;
|
|
const WCHAR *compr_tables[8];
|
|
|
|
compr_tables[0] = NULL;
|
|
if (flags & NORM_IGNORECASE) case_mask &= ~(CASE_UPPER | CASE_SUBSCRIPT);
|
|
if (flags & NORM_IGNOREWIDTH) case_mask &= ~CASE_FULLWIDTH;
|
|
if (flags & NORM_IGNOREKANATYPE) case_mask &= ~CASE_KATAKANA;
|
|
if ((flags & NORM_LINGUISTIC_CASING) && except && sortid->ling_except) except = sortid->ling_except;
|
|
|
|
init_sortkey_state( &s1, flags, srclen1, primary1, sizeof(primary1) );
|
|
init_sortkey_state( &s2, flags, srclen2, primary2, sizeof(primary2) );
|
|
|
|
while (pos1 < srclen1 || pos2 < srclen2)
|
|
{
|
|
while (pos1 < srclen1 && !s1.key_primary.len)
|
|
pos1 += append_weights( sortid, flags, src1, srclen1, pos1,
|
|
case_mask, except, compr_tables, &s1, TRUE );
|
|
|
|
while (pos2 < srclen2 && !s2.key_primary.len)
|
|
pos2 += append_weights( sortid, flags, src2, srclen2, pos2,
|
|
case_mask, except, compr_tables, &s2, TRUE );
|
|
|
|
if (!(len = min( s1.key_primary.len, s2.key_primary.len ))) break;
|
|
if ((ret = memcmp( primary1, primary2, len ))) goto done;
|
|
memmove( primary1, primary1 + len, s1.key_primary.len - len );
|
|
memmove( primary2, primary2 + len, s2.key_primary.len - len );
|
|
s1.key_primary.len -= len;
|
|
s2.key_primary.len -= len;
|
|
s1.primary_pos += len;
|
|
s2.primary_pos += len;
|
|
}
|
|
|
|
if ((ret = s1.key_primary.len - s2.key_primary.len)) goto done;
|
|
|
|
have_extra1 = remove_unneeded_weights( sortid, &s1 );
|
|
have_extra2 = remove_unneeded_weights( sortid, &s2 );
|
|
|
|
if ((ret = compare_sortkeys( &s1.key_diacritic, &s2.key_diacritic, FALSE ))) goto done;
|
|
if ((ret = compare_sortkeys( &s1.key_case, &s2.key_case, FALSE ))) goto done;
|
|
|
|
if (have_extra1 && have_extra2)
|
|
{
|
|
for (i = 0; i < 4; i++)
|
|
if ((ret = compare_sortkeys( &s1.key_extra[i], &s2.key_extra[i], i != 1 ))) goto done;
|
|
}
|
|
else if ((ret = have_extra1 - have_extra2)) goto done;
|
|
|
|
ret = compare_sortkeys( &s1.key_special, &s2.key_special, FALSE );
|
|
|
|
done:
|
|
free_sortkey_state( &s1 );
|
|
free_sortkey_state( &s2 );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* implementation of FindNLSStringEx */
|
|
static int find_substring( const struct sortguid *sortid, DWORD flags, const WCHAR *src, int srclen,
|
|
const WCHAR *value, int valuelen, int *reslen )
|
|
{
|
|
struct sortkey_state s;
|
|
struct sortkey_state val;
|
|
BYTE primary[32];
|
|
BYTE primary_val[256];
|
|
int i, start, len, found = -1, foundlen = 0, pos = 0;
|
|
BOOL have_extra, have_extra_val;
|
|
BYTE case_mask = 0x3f;
|
|
UINT except = sortid->except;
|
|
const WCHAR *compr_tables[8];
|
|
|
|
compr_tables[0] = NULL;
|
|
if (flags & NORM_IGNORECASE) case_mask &= ~(CASE_UPPER | CASE_SUBSCRIPT);
|
|
if (flags & NORM_IGNOREWIDTH) case_mask &= ~CASE_FULLWIDTH;
|
|
if (flags & NORM_IGNOREKANATYPE) case_mask &= ~CASE_KATAKANA;
|
|
if ((flags & NORM_LINGUISTIC_CASING) && except && sortid->ling_except) except = sortid->ling_except;
|
|
|
|
init_sortkey_state( &s, flags, srclen, primary, sizeof(primary) );
|
|
|
|
/* build the value sortkey just once */
|
|
init_sortkey_state( &val, flags, valuelen, primary_val, sizeof(primary_val) );
|
|
while (pos < valuelen)
|
|
pos += append_weights( sortid, flags, value, valuelen, pos,
|
|
case_mask, except, compr_tables, &val, TRUE );
|
|
have_extra_val = remove_unneeded_weights( sortid, &val );
|
|
|
|
for (start = 0; start < srclen; start++)
|
|
{
|
|
pos = start;
|
|
for (len = start + 1; len <= srclen; len++)
|
|
{
|
|
while (pos < len && s.primary_pos <= val.key_primary.len)
|
|
{
|
|
while (pos < len && !s.key_primary.len)
|
|
pos += append_weights( sortid, flags, src, srclen, pos,
|
|
case_mask, except, compr_tables, &s, TRUE );
|
|
|
|
if (s.primary_pos + s.key_primary.len > val.key_primary.len ||
|
|
memcmp( primary, val.key_primary.buf + s.primary_pos, s.key_primary.len ))
|
|
{
|
|
len = srclen + 1;
|
|
goto next;
|
|
}
|
|
s.primary_pos += s.key_primary.len;
|
|
s.key_primary.len = 0;
|
|
}
|
|
if (s.primary_pos < val.key_primary.len) continue;
|
|
|
|
have_extra = remove_unneeded_weights( sortid, &s );
|
|
if (compare_sortkeys( &s.key_diacritic, &val.key_diacritic, FALSE )) goto next;
|
|
if (compare_sortkeys( &s.key_case, &val.key_case, FALSE )) goto next;
|
|
|
|
if (have_extra && have_extra_val)
|
|
{
|
|
for (i = 0; i < 4; i++)
|
|
if (compare_sortkeys( &s.key_extra[i], &val.key_extra[i], i != 1 )) goto next;
|
|
}
|
|
else if (have_extra || have_extra_val) goto next;
|
|
|
|
if (compare_sortkeys( &s.key_special, &val.key_special, FALSE )) goto next;
|
|
|
|
found = start;
|
|
foundlen = pos - start;
|
|
len = srclen; /* no need to continue checking longer strings */
|
|
|
|
next:
|
|
/* reset state */
|
|
s.key_primary.len = s.key_diacritic.len = s.key_case.len = s.key_special.len = 0;
|
|
s.key_extra[0].len = s.key_extra[1].len = s.key_extra[2].len = s.key_extra[3].len = 0;
|
|
s.primary_pos = 0;
|
|
pos = start;
|
|
}
|
|
if (flags & FIND_STARTSWITH) break;
|
|
if (flags & FIND_FROMSTART && found != -1) break;
|
|
}
|
|
|
|
if (found != -1)
|
|
{
|
|
if ((flags & FIND_ENDSWITH) && found + foundlen != srclen) found = -1;
|
|
else if (reslen) *reslen = foundlen;
|
|
}
|
|
free_sortkey_state( &s );
|
|
free_sortkey_state( &val );
|
|
return found;
|
|
}
|
|
|
|
|
|
/* map buffer to full-width katakana */
|
|
static int map_to_fullwidth( const USHORT *table, const WCHAR *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
int pos, len;
|
|
|
|
for (pos = 0; srclen; pos++, src += len, srclen -= len)
|
|
{
|
|
unsigned int wch = casemap( charmaps[CHARMAP_FULLWIDTH], *src );
|
|
|
|
len = 1;
|
|
if (srclen > 1)
|
|
{
|
|
if (table_has_high_planes( charmaps[CHARMAP_FULLWIDTH] ) && IS_SURROGATE_PAIR( src[0], src[1] ))
|
|
{
|
|
len = 2;
|
|
wch = casemap_high( charmaps[CHARMAP_FULLWIDTH], src[0], src[1] );
|
|
if (wch >= 0x10000)
|
|
{
|
|
put_utf16( dst, pos, dstlen, wch );
|
|
pos++;
|
|
continue;
|
|
}
|
|
}
|
|
else if (src[1] == 0xff9e) /* dakuten (voiced sound) */
|
|
{
|
|
len = 2;
|
|
if ((*src >= 0xff76 && *src <= 0xff84) ||
|
|
(*src >= 0xff8a && *src <= 0xff8e) ||
|
|
*src == 0x30fd)
|
|
wch++;
|
|
else if (*src == 0xff73)
|
|
wch = 0x30f4; /* KATAKANA LETTER VU */
|
|
else if (*src == 0xff9c)
|
|
wch = 0x30f7; /* KATAKANA LETTER VA */
|
|
else if (*src == 0x30f0)
|
|
wch = 0x30f8; /* KATAKANA LETTER VI */
|
|
else if (*src == 0x30f1)
|
|
wch = 0x30f9; /* KATAKANA LETTER VE */
|
|
else if (*src == 0xff66)
|
|
wch = 0x30fa; /* KATAKANA LETTER VO */
|
|
else
|
|
len = 1;
|
|
}
|
|
else if (src[1] == 0xff9f) /* handakuten (semi-voiced sound) */
|
|
{
|
|
if (*src >= 0xff8a && *src <= 0xff8e)
|
|
{
|
|
wch += 2;
|
|
len = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pos < dstlen) dst[pos] = table ? casemap( table, wch ) : wch;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
static inline int nonspace_ignored( WCHAR ch )
|
|
{
|
|
if (get_char_type( CT_CTYPE2, ch ) != C2_OTHERNEUTRAL) return FALSE;
|
|
return (get_char_type( CT_CTYPE3, ch ) & (C3_NONSPACING | C3_DIACRITIC));
|
|
}
|
|
|
|
/* remove ignored chars for NORM_IGNORENONSPACE/NORM_IGNORESYMBOLS */
|
|
static int map_remove_ignored( DWORD flags, const WCHAR *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
int pos;
|
|
|
|
for (pos = 0; srclen; src++, srclen--)
|
|
{
|
|
if (flags & NORM_IGNORESYMBOLS)
|
|
{
|
|
if (get_char_type( CT_CTYPE1, *src ) & C1_PUNCT) continue;
|
|
if (get_char_type( CT_CTYPE3, *src ) & C3_SYMBOL) continue;
|
|
}
|
|
if (flags & NORM_IGNORENONSPACE)
|
|
{
|
|
WCHAR buffer[8];
|
|
const WCHAR *decomp;
|
|
unsigned int i, j, len;
|
|
|
|
if ((decomp = get_decomposition( *src, &len )) && len > 1)
|
|
{
|
|
for (i = j = 0; i < len; i++)
|
|
if (!nonspace_ignored( decomp[i] )) buffer[j++] = decomp[i];
|
|
|
|
if (i > j) /* something was removed */
|
|
{
|
|
if (pos + j <= dstlen) memcpy( dst + pos, buffer, j * sizeof(WCHAR) );
|
|
pos += j;
|
|
continue;
|
|
}
|
|
}
|
|
else if (nonspace_ignored( *src )) continue;
|
|
}
|
|
if (pos < dstlen) dst[pos] = *src;
|
|
pos++;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
/* map full-width characters to single or double half-width characters. */
|
|
static int map_to_halfwidth( const USHORT *table, const WCHAR *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
static const BYTE katakana_map[] =
|
|
{
|
|
0x01, 0x00, 0x01, 0x00, /* U+30a8- */
|
|
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, /* U+30b0- */
|
|
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, /* U+30b8- */
|
|
0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, /* U+30c0- */
|
|
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* U+30c8- */
|
|
0x01, 0x02, 0x00, 0x01, 0x02, 0x00, 0x01, 0x02, /* U+30d0- */
|
|
0x00, 0x01, 0x02, 0x00, 0x01, 0x02, 0x00, 0x00, /* U+30d8- */
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* U+30e0- */
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* U+30e8- */
|
|
0x00, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x08, /* U+30f0- */
|
|
0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x01 /* U+30f8- */
|
|
};
|
|
int pos;
|
|
|
|
for (pos = 0; srclen; src++, srclen--)
|
|
{
|
|
WCHAR ch = table ? casemap( table, *src ) : *src;
|
|
USHORT shift = ch - 0x30ac;
|
|
BYTE k;
|
|
|
|
if (shift < ARRAY_SIZE(katakana_map) && (k = katakana_map[shift]))
|
|
{
|
|
if (pos < dstlen - 1)
|
|
{
|
|
dst[pos] = casemap( charmaps[CHARMAP_HALFWIDTH], ch - k );
|
|
dst[pos + 1] = (k == 2) ? 0xff9f : 0xff9e;
|
|
}
|
|
pos += 2;
|
|
}
|
|
else
|
|
{
|
|
if (pos < dstlen) dst[pos] = casemap( charmaps[CHARMAP_HALFWIDTH], ch );
|
|
pos++;
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
static int lcmap_string( const struct sortguid *sortid, DWORD flags,
|
|
const WCHAR *src, int srclen, WCHAR *dst, int dstlen )
|
|
{
|
|
const USHORT *case_table = NULL;
|
|
int ret;
|
|
|
|
if (flags & (LCMAP_LOWERCASE | LCMAP_UPPERCASE))
|
|
{
|
|
if ((flags & LCMAP_TITLECASE) == LCMAP_TITLECASE) /* FIXME */
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
case_table = sort.casemap + (flags & LCMAP_LINGUISTIC_CASING ? sortid->casemap : 0);
|
|
case_table = case_table + 2 + (flags & LCMAP_LOWERCASE ? case_table[1] : 0);
|
|
}
|
|
|
|
switch (flags & ~(LCMAP_BYTEREV | LCMAP_LOWERCASE | LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING))
|
|
{
|
|
case LCMAP_HIRAGANA:
|
|
ret = casemap_string( charmaps[CHARMAP_HIRAGANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_KATAKANA:
|
|
ret = casemap_string( charmaps[CHARMAP_KATAKANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_HALFWIDTH:
|
|
ret = map_to_halfwidth( NULL, src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_HIRAGANA | LCMAP_HALFWIDTH:
|
|
ret = map_to_halfwidth( charmaps[CHARMAP_HIRAGANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_KATAKANA | LCMAP_HALFWIDTH:
|
|
ret = map_to_halfwidth( charmaps[CHARMAP_KATAKANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_FULLWIDTH:
|
|
ret = map_to_fullwidth( NULL, src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_HIRAGANA | LCMAP_FULLWIDTH:
|
|
ret = map_to_fullwidth( charmaps[CHARMAP_HIRAGANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_KATAKANA | LCMAP_FULLWIDTH:
|
|
ret = map_to_fullwidth( charmaps[CHARMAP_KATAKANA], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_SIMPLIFIED_CHINESE:
|
|
ret = casemap_string( charmaps[CHARMAP_SIMPLIFIED], src, srclen, dst, dstlen );
|
|
break;
|
|
case LCMAP_TRADITIONAL_CHINESE:
|
|
ret = casemap_string( charmaps[CHARMAP_TRADITIONAL], src, srclen, dst, dstlen );
|
|
break;
|
|
case NORM_IGNORENONSPACE:
|
|
case NORM_IGNORESYMBOLS:
|
|
case NORM_IGNORENONSPACE | NORM_IGNORESYMBOLS:
|
|
if (flags & ~(NORM_IGNORENONSPACE | NORM_IGNORESYMBOLS | LCMAP_BYTEREV))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
ret = map_remove_ignored( flags, src, srclen, dst, dstlen );
|
|
break;
|
|
case 0:
|
|
if (case_table)
|
|
{
|
|
ret = casemap_string( case_table, src, srclen, dst, dstlen );
|
|
case_table = NULL;
|
|
break;
|
|
}
|
|
if (flags & LCMAP_BYTEREV)
|
|
{
|
|
ret = min( srclen, dstlen );
|
|
memcpy( dst, src, ret * sizeof(WCHAR) );
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default:
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (dstlen && case_table) ret = casemap_string( case_table, dst, ret, dst, dstlen );
|
|
if (flags & LCMAP_BYTEREV) map_byterev( dst, min( dstlen, ret ), dst );
|
|
|
|
if (dstlen && dstlen < ret)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int compare_tzdate( const TIME_FIELDS *tf, const SYSTEMTIME *compare )
|
|
{
|
|
static const int month_lengths[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
int first, last, limit, dayinsecs;
|
|
|
|
if (tf->Month < compare->wMonth) return -1; /* We are in a month before the date limit. */
|
|
if (tf->Month > compare->wMonth) return 1; /* We are in a month after the date limit. */
|
|
|
|
/* if year is 0 then date is in day-of-week format, otherwise
|
|
* it's absolute date.
|
|
*/
|
|
if (!compare->wYear)
|
|
{
|
|
/* wDay is interpreted as number of the week in the month
|
|
* 5 means: the last week in the month */
|
|
/* calculate the day of the first DayOfWeek in the month */
|
|
first = (6 + compare->wDayOfWeek - tf->Weekday + tf->Day) % 7 + 1;
|
|
/* check needed for the 5th weekday of the month */
|
|
last = month_lengths[tf->Month - 1] +
|
|
(tf->Month == 2 && (!(tf->Year % 4) && (tf->Year % 100 || !(tf->Year % 400))));
|
|
limit = first + 7 * (compare->wDay - 1);
|
|
if (limit > last) limit -= 7;
|
|
}
|
|
else limit = compare->wDay;
|
|
|
|
limit = ((limit * 24 + compare->wHour) * 60 + compare->wMinute) * 60;
|
|
dayinsecs = ((tf->Day * 24 + tf->Hour) * 60 + tf->Minute) * 60 + tf->Second;
|
|
return dayinsecs - limit;
|
|
}
|
|
|
|
|
|
static DWORD get_timezone_id( const TIME_ZONE_INFORMATION *info, LARGE_INTEGER time, BOOL is_local )
|
|
{
|
|
int year;
|
|
BOOL before_standard_date, after_daylight_date;
|
|
LARGE_INTEGER t2;
|
|
TIME_FIELDS tf;
|
|
|
|
if (!info->DaylightDate.wMonth) return TIME_ZONE_ID_UNKNOWN;
|
|
|
|
/* if year is 0 then date is in day-of-week format, otherwise it's absolute date */
|
|
if (info->StandardDate.wMonth == 0 ||
|
|
(info->StandardDate.wYear == 0 &&
|
|
(info->StandardDate.wDay < 1 || info->StandardDate.wDay > 5 ||
|
|
info->DaylightDate.wDay < 1 || info->DaylightDate.wDay > 5)))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return TIME_ZONE_ID_INVALID;
|
|
}
|
|
|
|
if (!is_local) time.QuadPart -= info->Bias * (LONGLONG)600000000;
|
|
RtlTimeToTimeFields( &time, &tf );
|
|
year = tf.Year;
|
|
if (!is_local)
|
|
{
|
|
t2.QuadPart = time.QuadPart - info->DaylightBias * (LONGLONG)600000000;
|
|
RtlTimeToTimeFields( &t2, &tf );
|
|
}
|
|
if (tf.Year == year)
|
|
before_standard_date = compare_tzdate( &tf, &info->StandardDate ) < 0;
|
|
else
|
|
before_standard_date = tf.Year < year;
|
|
|
|
if (!is_local)
|
|
{
|
|
t2.QuadPart = time.QuadPart - info->StandardBias * (LONGLONG)600000000;
|
|
RtlTimeToTimeFields( &t2, &tf );
|
|
}
|
|
if (tf.Year == year)
|
|
after_daylight_date = compare_tzdate( &tf, &info->DaylightDate ) >= 0;
|
|
else
|
|
after_daylight_date = tf.Year > year;
|
|
|
|
if (info->DaylightDate.wMonth < info->StandardDate.wMonth) /* Northern hemisphere */
|
|
{
|
|
if (before_standard_date && after_daylight_date) return TIME_ZONE_ID_DAYLIGHT;
|
|
}
|
|
else /* Down south */
|
|
{
|
|
if (before_standard_date || after_daylight_date) return TIME_ZONE_ID_DAYLIGHT;
|
|
}
|
|
return TIME_ZONE_ID_STANDARD;
|
|
}
|
|
|
|
|
|
/* Note: the Internal_ functions are not documented. The number of parameters
|
|
* should be correct, but their exact meaning may not.
|
|
*/
|
|
|
|
/******************************************************************************
|
|
* Internal_EnumCalendarInfo (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumCalendarInfo( CALINFO_ENUMPROCW proc,
|
|
const NLS_LOCALE_DATA *locale, CALID id,
|
|
CALTYPE type, BOOL unicode, BOOL ex,
|
|
BOOL exex, LPARAM lparam )
|
|
{
|
|
const USHORT *calendars;
|
|
USHORT cal = id;
|
|
WCHAR buffer[256];
|
|
INT ret, i, count = 1;
|
|
|
|
if (!proc || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
if (id == ENUM_ALL_CALENDARS)
|
|
{
|
|
count = locale_strings[locale->scalendartype];
|
|
calendars = locale_strings + locale->scalendartype + 1;
|
|
}
|
|
else if (id <= CAL_UMALQURA)
|
|
{
|
|
calendars = &cal;
|
|
count = 1;
|
|
}
|
|
else
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
id = calendars[i];
|
|
if (type & CAL_RETURN_NUMBER)
|
|
ret = get_calendar_info( locale, id, type, NULL, 0, (LPDWORD)buffer );
|
|
else if (unicode)
|
|
ret = get_calendar_info( locale, id, type, buffer, ARRAY_SIZE(buffer), NULL );
|
|
else
|
|
{
|
|
WCHAR bufW[256];
|
|
ret = get_calendar_info( locale, id, type, bufW, ARRAY_SIZE(bufW), NULL );
|
|
if (ret) WideCharToMultiByte( get_locale_codepage( locale, type ), 0,
|
|
bufW, -1, (char *)buffer, sizeof(buffer), NULL, NULL );
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
if (exex) ret = ((CALINFO_ENUMPROCEXEX)proc)( buffer, id, NULL, lparam );
|
|
else if (ex) ret = ((CALINFO_ENUMPROCEXW)proc)( buffer, id );
|
|
else ret = proc( buffer );
|
|
}
|
|
if (!ret) break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static BOOL call_enum_date_func( DATEFMT_ENUMPROCW proc, const NLS_LOCALE_DATA *locale, DWORD flags,
|
|
DWORD str, WCHAR *buffer, CALID id, BOOL unicode,
|
|
BOOL ex, BOOL exex, LPARAM lparam )
|
|
{
|
|
char buffA[256];
|
|
|
|
if (str) memcpy( buffer, locale_strings + str + 1, (locale_strings[str] + 1) * sizeof(WCHAR) );
|
|
if (exex) return ((DATEFMT_ENUMPROCEXEX)proc)( buffer, id, lparam );
|
|
if (ex) return ((DATEFMT_ENUMPROCEXW)proc)( buffer, id );
|
|
if (unicode) return proc( buffer );
|
|
WideCharToMultiByte( get_locale_codepage( locale, flags ), 0, buffer, -1,
|
|
buffA, ARRAY_SIZE(buffA), NULL, NULL );
|
|
return proc( (WCHAR *)buffA );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* Internal_EnumDateFormats (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumDateFormats( DATEFMT_ENUMPROCW proc,
|
|
const NLS_LOCALE_DATA *locale, DWORD flags,
|
|
BOOL unicode, BOOL ex, BOOL exex, LPARAM lparam )
|
|
{
|
|
WCHAR buffer[256];
|
|
INT i, j, ret;
|
|
DWORD pos;
|
|
const struct calendar *cal;
|
|
const USHORT *calendars;
|
|
const DWORD *array;
|
|
|
|
if (!proc || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
calendars = locale_strings + locale->scalendartype;
|
|
|
|
switch (flags & ~LOCALE_USE_CP_ACP)
|
|
{
|
|
case 0:
|
|
case DATE_SHORTDATE:
|
|
if (!get_locale_info( locale, 0, LOCALE_SSHORTDATE, buffer, ARRAY_SIZE(buffer) )) return FALSE;
|
|
pos = locale->sshortdate;
|
|
break;
|
|
case DATE_LONGDATE:
|
|
if (!get_locale_info( locale, 0, LOCALE_SLONGDATE, buffer, ARRAY_SIZE(buffer) )) return FALSE;
|
|
pos = locale->slongdate;
|
|
break;
|
|
case DATE_YEARMONTH:
|
|
if (!get_locale_info( locale, 0, LOCALE_SYEARMONTH, buffer, ARRAY_SIZE(buffer) )) return FALSE;
|
|
pos = locale->syearmonth;
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
/* first the user override data */
|
|
|
|
ret = call_enum_date_func( proc, locale, flags, 0, buffer, 1, unicode, ex, exex, lparam );
|
|
|
|
/* then the remaining locale data */
|
|
|
|
array = (const DWORD *)(locale_strings + pos + 1);
|
|
for (i = 1; ret && i < locale_strings[pos]; i++)
|
|
ret = call_enum_date_func( proc, locale, flags, array[i], buffer, 1, unicode, ex, exex, lparam );
|
|
|
|
/* then the extra calendars */
|
|
|
|
for (i = 0; ret && i < calendars[0]; i++)
|
|
{
|
|
if (calendars[i + 1] == 1) continue;
|
|
if (!(cal = get_calendar_data( locale, calendars[i + 1] ))) continue;
|
|
switch (flags & ~LOCALE_USE_CP_ACP)
|
|
{
|
|
case 0:
|
|
case DATE_SHORTDATE:
|
|
pos = cal->sshortdate;
|
|
break;
|
|
case DATE_LONGDATE:
|
|
pos = cal->slongdate;
|
|
break;
|
|
case DATE_YEARMONTH:
|
|
pos = cal->syearmonth;
|
|
break;
|
|
}
|
|
array = (const DWORD *)(locale_strings + pos + 1);
|
|
for (j = 0; ret && j < locale_strings[pos]; j++)
|
|
ret = call_enum_date_func( proc, locale, flags, array[j], buffer,
|
|
calendars[i + 1], unicode, ex, exex, lparam );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Internal_EnumLanguageGroupLocales (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumLanguageGroupLocales( LANGGROUPLOCALE_ENUMPROCW proc, LGRPID id,
|
|
DWORD flags, LONG_PTR param, BOOL unicode )
|
|
{
|
|
WCHAR name[10], value[10];
|
|
DWORD name_len, value_len, type, index = 0, alt = 0;
|
|
HKEY key, altkey;
|
|
LCID lcid;
|
|
|
|
if (!proc || id < LGRPID_WESTERN_EUROPE || id > LGRPID_ARMENIAN)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
if (RegOpenKeyExW( nls_key, L"Locale", 0, KEY_READ, &key )) return FALSE;
|
|
if (RegOpenKeyExW( key, L"Alternate Sorts", 0, KEY_READ, &altkey )) altkey = 0;
|
|
|
|
for (;;)
|
|
{
|
|
name_len = ARRAY_SIZE(name);
|
|
value_len = sizeof(value);
|
|
if (RegEnumValueW( alt ? altkey : key, index++, name, &name_len, NULL,
|
|
&type, (BYTE *)value, &value_len ))
|
|
{
|
|
if (alt++) break;
|
|
index = 0;
|
|
continue;
|
|
}
|
|
if (type != REG_SZ) continue;
|
|
if (id != wcstoul( value, NULL, 16 )) continue;
|
|
lcid = wcstoul( name, NULL, 16 );
|
|
if (!unicode)
|
|
{
|
|
char nameA[10];
|
|
WideCharToMultiByte( CP_ACP, 0, name, -1, nameA, sizeof(nameA), NULL, NULL );
|
|
if (!((LANGGROUPLOCALE_ENUMPROCA)proc)( id, lcid, nameA, param )) break;
|
|
}
|
|
else if (!proc( id, lcid, name, param )) break;
|
|
}
|
|
RegCloseKey( altkey );
|
|
RegCloseKey( key );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* Internal_EnumSystemCodePages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumSystemCodePages( CODEPAGE_ENUMPROCW proc, DWORD flags,
|
|
BOOL unicode )
|
|
{
|
|
WCHAR name[10];
|
|
DWORD name_len, type, index = 0;
|
|
HKEY key;
|
|
|
|
if (RegOpenKeyExW( nls_key, L"Codepage", 0, KEY_READ, &key )) return FALSE;
|
|
|
|
for (;;)
|
|
{
|
|
name_len = ARRAY_SIZE(name);
|
|
if (RegEnumValueW( key, index++, name, &name_len, NULL, &type, NULL, NULL )) break;
|
|
if (type != REG_SZ) continue;
|
|
if (!wcstoul( name, NULL, 10 )) continue;
|
|
if (!unicode)
|
|
{
|
|
char nameA[10];
|
|
WideCharToMultiByte( CP_ACP, 0, name, -1, nameA, sizeof(nameA), NULL, NULL );
|
|
if (!((CODEPAGE_ENUMPROCA)proc)( nameA )) break;
|
|
}
|
|
else if (!proc( name )) break;
|
|
}
|
|
RegCloseKey( key );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Internal_EnumSystemLanguageGroups (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumSystemLanguageGroups( LANGUAGEGROUP_ENUMPROCW proc,
|
|
DWORD flags, LONG_PTR param, BOOL unicode )
|
|
{
|
|
WCHAR name[10], value[10], descr[80];
|
|
DWORD name_len, value_len, type, index = 0;
|
|
HKEY key;
|
|
LGRPID id;
|
|
|
|
if (!proc)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
switch (flags)
|
|
{
|
|
case 0:
|
|
flags = LGRPID_INSTALLED;
|
|
break;
|
|
case LGRPID_INSTALLED:
|
|
case LGRPID_SUPPORTED:
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
|
|
if (RegOpenKeyExW( nls_key, L"Language Groups", 0, KEY_READ, &key )) return FALSE;
|
|
|
|
for (;;)
|
|
{
|
|
name_len = ARRAY_SIZE(name);
|
|
value_len = sizeof(value);
|
|
if (RegEnumValueW( key, index++, name, &name_len, NULL, &type, (BYTE *)value, &value_len )) break;
|
|
if (type != REG_SZ) continue;
|
|
|
|
id = wcstoul( name, NULL, 16 );
|
|
|
|
if (!(flags & LGRPID_SUPPORTED) && !wcstoul( value, NULL, 10 )) continue;
|
|
if (!LoadStringW( kernelbase_handle, id, descr, ARRAY_SIZE(descr) )) descr[0] = 0;
|
|
TRACE( "%p: %lu %s %s %lx %Ix\n", proc, id, debugstr_w(name), debugstr_w(descr), flags, param );
|
|
if (!unicode)
|
|
{
|
|
char nameA[10], descrA[80];
|
|
WideCharToMultiByte( CP_ACP, 0, name, -1, nameA, sizeof(nameA), NULL, NULL );
|
|
WideCharToMultiByte( CP_ACP, 0, descr, -1, descrA, sizeof(descrA), NULL, NULL );
|
|
if (!((LANGUAGEGROUP_ENUMPROCA)proc)( id, nameA, descrA, flags, param )) break;
|
|
}
|
|
else if (!proc( id, name, descr, flags, param )) break;
|
|
}
|
|
RegCloseKey( key );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* Internal_EnumTimeFormats (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumTimeFormats( TIMEFMT_ENUMPROCW proc,
|
|
const NLS_LOCALE_DATA *locale, DWORD flags,
|
|
BOOL unicode, BOOL ex, LPARAM lparam )
|
|
{
|
|
WCHAR buffer[256];
|
|
INT ret = TRUE;
|
|
const DWORD *array;
|
|
DWORD pos, i;
|
|
|
|
if (!proc || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
switch (flags & ~LOCALE_USE_CP_ACP)
|
|
{
|
|
case 0:
|
|
if (!get_locale_info( locale, 0, LOCALE_STIMEFORMAT, buffer, ARRAY_SIZE(buffer) )) return FALSE;
|
|
pos = locale->stimeformat;
|
|
break;
|
|
case TIME_NOSECONDS:
|
|
if (!get_locale_info( locale, 0, LOCALE_SSHORTTIME, buffer, ARRAY_SIZE(buffer) )) return FALSE;
|
|
pos = locale->sshorttime;
|
|
break;
|
|
default:
|
|
FIXME( "Unknown time format %lx\n", flags );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
array = (const DWORD *)(locale_strings + pos + 1);
|
|
for (i = 0; ret && i < locale_strings[pos]; i++)
|
|
{
|
|
if (i) memcpy( buffer, locale_strings + array[i] + 1,
|
|
(locale_strings[array[i]] + 1) * sizeof(WCHAR) );
|
|
|
|
if (ex) ret = ((TIMEFMT_ENUMPROCEX)proc)( buffer, lparam );
|
|
else if (unicode) ret = proc( buffer );
|
|
else
|
|
{
|
|
char buffA[256];
|
|
WideCharToMultiByte( get_locale_codepage( locale, flags ), 0, buffer, -1,
|
|
buffA, ARRAY_SIZE(buffA), NULL, NULL );
|
|
ret = proc( (WCHAR *)buffA );
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* Internal_EnumUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH Internal_EnumUILanguages( UILANGUAGE_ENUMPROCW proc, DWORD flags,
|
|
LONG_PTR param, BOOL unicode )
|
|
{
|
|
WCHAR nameW[LOCALE_NAME_MAX_LENGTH];
|
|
char nameA[LOCALE_NAME_MAX_LENGTH];
|
|
DWORD i;
|
|
|
|
if (!proc)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
if (flags & ~(MUI_LANGUAGE_ID | MUI_LANGUAGE_NAME))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < locale_table->nb_lcnames; i++)
|
|
{
|
|
if (!lcnames_index[i].name) continue; /* skip invariant locale */
|
|
if (lcnames_index[i].id & 0x80000000) continue; /* skip aliases */
|
|
if (!get_locale_data( lcnames_index[i].idx )->inotneutral) continue; /* skip neutral locales */
|
|
if (SORTIDFROMLCID( lcnames_index[i].id )) continue; /* skip alternate sorts */
|
|
if (flags & MUI_LANGUAGE_NAME)
|
|
{
|
|
const WCHAR *str = locale_strings + lcnames_index[i].name;
|
|
|
|
if (unicode)
|
|
{
|
|
memcpy( nameW, str + 1, (*str + 1) * sizeof(WCHAR) );
|
|
if (!proc( nameW, param )) break;
|
|
}
|
|
else
|
|
{
|
|
WideCharToMultiByte( CP_ACP, 0, str + 1, -1, nameA, sizeof(nameA), NULL, NULL );
|
|
if (!((UILANGUAGE_ENUMPROCA)proc)( nameA, param )) break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (lcnames_index[i].id == LOCALE_CUSTOM_UNSPECIFIED) continue; /* skip locales with no lcid */
|
|
if (unicode)
|
|
{
|
|
swprintf( nameW, ARRAY_SIZE(nameW), L"%04lx", lcnames_index[i].id );
|
|
if (!proc( nameW, param )) break;
|
|
}
|
|
else
|
|
{
|
|
sprintf( nameA, "%04x", lcnames_index[i].id );
|
|
if (!((UILANGUAGE_ENUMPROCA)proc)( nameA, param )) break;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* CompareStringEx (kernelbase.@)
|
|
*/
|
|
INT WINAPI CompareStringEx( const WCHAR *locale, DWORD flags, const WCHAR *str1, int len1,
|
|
const WCHAR *str2, int len2, NLSVERSIONINFO *version,
|
|
void *reserved, LPARAM handle )
|
|
{
|
|
const struct sortguid *sortid;
|
|
const DWORD supported_flags = NORM_IGNORECASE | NORM_IGNORENONSPACE | NORM_IGNORESYMBOLS |
|
|
SORT_STRINGSORT | NORM_IGNOREKANATYPE | NORM_IGNOREWIDTH |
|
|
NORM_LINGUISTIC_CASING | LINGUISTIC_IGNORECASE |
|
|
LINGUISTIC_IGNOREDIACRITIC | SORT_DIGITSASNUMBERS |
|
|
0x10000000 | LOCALE_USE_CP_ACP;
|
|
/* 0x10000000 is related to diacritics in Arabic, Japanese, and Hebrew */
|
|
int ret;
|
|
|
|
if (version) FIXME( "unexpected version parameter\n" );
|
|
if (reserved) FIXME( "unexpected reserved value\n" );
|
|
if (handle) FIXME( "unexpected handle\n" );
|
|
|
|
if (flags & ~supported_flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (!(sortid = get_language_sort( locale ))) return 0;
|
|
|
|
if (!str1 || !str2)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (len1 < 0) len1 = lstrlenW(str1);
|
|
if (len2 < 0) len2 = lstrlenW(str2);
|
|
|
|
ret = compare_string( sortid, flags, str1, len1, str2, len2 );
|
|
if (ret < 0) return CSTR_LESS_THAN;
|
|
if (ret > 0) return CSTR_GREATER_THAN;
|
|
return CSTR_EQUAL;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* CompareStringA (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH CompareStringA( LCID lcid, DWORD flags, const char *str1, int len1,
|
|
const char *str2, int len2 )
|
|
{
|
|
WCHAR *buf1W = NtCurrentTeb()->StaticUnicodeBuffer;
|
|
WCHAR *buf2W = buf1W + 130;
|
|
LPWSTR str1W, str2W;
|
|
INT len1W = 0, len2W = 0, ret;
|
|
UINT locale_cp = CP_ACP;
|
|
|
|
if (!str1 || !str2)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (flags & SORT_DIGITSASNUMBERS)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (len1 < 0) len1 = strlen(str1);
|
|
if (len2 < 0) len2 = strlen(str2);
|
|
|
|
locale_cp = get_lcid_codepage( lcid, flags );
|
|
if (len1)
|
|
{
|
|
if (len1 <= 130) len1W = MultiByteToWideChar( locale_cp, 0, str1, len1, buf1W, 130 );
|
|
if (len1W) str1W = buf1W;
|
|
else
|
|
{
|
|
len1W = MultiByteToWideChar( locale_cp, 0, str1, len1, NULL, 0 );
|
|
str1W = HeapAlloc( GetProcessHeap(), 0, len1W * sizeof(WCHAR) );
|
|
if (!str1W)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return 0;
|
|
}
|
|
MultiByteToWideChar( locale_cp, 0, str1, len1, str1W, len1W );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
len1W = 0;
|
|
str1W = buf1W;
|
|
}
|
|
|
|
if (len2)
|
|
{
|
|
if (len2 <= 130) len2W = MultiByteToWideChar( locale_cp, 0, str2, len2, buf2W, 130 );
|
|
if (len2W) str2W = buf2W;
|
|
else
|
|
{
|
|
len2W = MultiByteToWideChar( locale_cp, 0, str2, len2, NULL, 0 );
|
|
str2W = HeapAlloc( GetProcessHeap(), 0, len2W * sizeof(WCHAR) );
|
|
if (!str2W)
|
|
{
|
|
if (str1W != buf1W) HeapFree( GetProcessHeap(), 0, str1W );
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return 0;
|
|
}
|
|
MultiByteToWideChar( locale_cp, 0, str2, len2, str2W, len2W );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
len2W = 0;
|
|
str2W = buf2W;
|
|
}
|
|
|
|
ret = CompareStringW( lcid, flags, str1W, len1W, str2W, len2W );
|
|
|
|
if (str1W != buf1W) HeapFree( GetProcessHeap(), 0, str1W );
|
|
if (str2W != buf2W) HeapFree( GetProcessHeap(), 0, str2W );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* CompareStringW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH CompareStringW( LCID lcid, DWORD flags, const WCHAR *str1, int len1,
|
|
const WCHAR *str2, int len2 )
|
|
{
|
|
const WCHAR *locale = LOCALE_NAME_USER_DEFAULT;
|
|
const NLS_LOCALE_LCID_INDEX *entry;
|
|
|
|
switch (lcid)
|
|
{
|
|
case LOCALE_NEUTRAL:
|
|
case LOCALE_USER_DEFAULT:
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
case LOCALE_CUSTOM_DEFAULT:
|
|
case LOCALE_CUSTOM_UNSPECIFIED:
|
|
case LOCALE_CUSTOM_UI_DEFAULT:
|
|
break;
|
|
default:
|
|
if (lcid == user_lcid || lcid == system_lcid) break;
|
|
if (!(entry = find_lcid_entry( lcid )))
|
|
{
|
|
WARN( "unknown locale %04lx\n", lcid );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
locale = locale_strings + entry->name + 1;
|
|
break;
|
|
}
|
|
|
|
return CompareStringEx( locale, flags, str1, len1, str2, len2, NULL, NULL, 0 );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* CompareStringOrdinal (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH CompareStringOrdinal( const WCHAR *str1, INT len1,
|
|
const WCHAR *str2, INT len2, BOOL ignore_case )
|
|
{
|
|
int ret;
|
|
|
|
if (!str1 || !str2)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (len1 < 0) len1 = lstrlenW( str1 );
|
|
if (len2 < 0) len2 = lstrlenW( str2 );
|
|
|
|
ret = RtlCompareUnicodeStrings( str1, len1, str2, len2, ignore_case );
|
|
if (ret < 0) return CSTR_LESS_THAN;
|
|
if (ret > 0) return CSTR_GREATER_THAN;
|
|
return CSTR_EQUAL;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* ConvertDefaultLocale (kernelbase.@)
|
|
*/
|
|
LCID WINAPI DECLSPEC_HOTPATCH ConvertDefaultLocale( LCID lcid )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
if (locale) lcid = locale->ilanguage;
|
|
return lcid;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumCalendarInfoW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumCalendarInfoW( CALINFO_ENUMPROCW proc, LCID lcid,
|
|
CALID id, CALTYPE type )
|
|
{
|
|
return Internal_EnumCalendarInfo( proc, NlsValidateLocale( &lcid, 0 ),
|
|
id, type, TRUE, FALSE, FALSE, 0 );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumCalendarInfoExW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumCalendarInfoExW( CALINFO_ENUMPROCEXW proc, LCID lcid,
|
|
CALID id, CALTYPE type )
|
|
{
|
|
return Internal_EnumCalendarInfo( (CALINFO_ENUMPROCW)proc, NlsValidateLocale( &lcid, 0 ),
|
|
id, type, TRUE, TRUE, FALSE, 0 );
|
|
}
|
|
|
|
/******************************************************************************
|
|
* EnumCalendarInfoExEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumCalendarInfoExEx( CALINFO_ENUMPROCEXEX proc, LPCWSTR locale, CALID id,
|
|
LPCWSTR reserved, CALTYPE type, LPARAM lparam )
|
|
{
|
|
LCID lcid;
|
|
return Internal_EnumCalendarInfo( (CALINFO_ENUMPROCW)proc, get_locale_by_name( locale, &lcid ),
|
|
id, type, TRUE, TRUE, TRUE, lparam );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumDateFormatsW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumDateFormatsW( DATEFMT_ENUMPROCW proc, LCID lcid, DWORD flags )
|
|
{
|
|
return Internal_EnumDateFormats( proc, NlsValidateLocale( &lcid, 0 ),
|
|
flags, TRUE, FALSE, FALSE, 0 );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumDateFormatsExW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumDateFormatsExW( DATEFMT_ENUMPROCEXW proc, LCID lcid, DWORD flags )
|
|
{
|
|
return Internal_EnumDateFormats( (DATEFMT_ENUMPROCW)proc, NlsValidateLocale( &lcid, 0 ),
|
|
flags, TRUE, TRUE, FALSE, 0 );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumDateFormatsExEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumDateFormatsExEx( DATEFMT_ENUMPROCEXEX proc, const WCHAR *locale,
|
|
DWORD flags, LPARAM lparam )
|
|
{
|
|
LCID lcid;
|
|
return Internal_EnumDateFormats( (DATEFMT_ENUMPROCW)proc, get_locale_by_name( locale, &lcid ),
|
|
flags, TRUE, TRUE, TRUE, lparam );
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumDynamicTimeZoneInformation (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH EnumDynamicTimeZoneInformation( DWORD index,
|
|
DYNAMIC_TIME_ZONE_INFORMATION *info )
|
|
{
|
|
DYNAMIC_TIME_ZONE_INFORMATION tz;
|
|
LSTATUS ret;
|
|
DWORD size;
|
|
|
|
if (!info) return ERROR_INVALID_PARAMETER;
|
|
|
|
size = ARRAY_SIZE(tz.TimeZoneKeyName);
|
|
ret = RegEnumKeyExW( tz_key, index, tz.TimeZoneKeyName, &size, NULL, NULL, NULL, NULL );
|
|
if (ret) return ret;
|
|
|
|
tz.DynamicDaylightTimeDisabled = TRUE;
|
|
if (!GetTimeZoneInformationForYear( 0, &tz, (TIME_ZONE_INFORMATION *)info )) return GetLastError();
|
|
|
|
lstrcpyW( info->TimeZoneKeyName, tz.TimeZoneKeyName );
|
|
info->DynamicDaylightTimeDisabled = FALSE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumLanguageGroupLocalesW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumLanguageGroupLocalesW( LANGGROUPLOCALE_ENUMPROCW proc, LGRPID id,
|
|
DWORD flags, LONG_PTR param )
|
|
{
|
|
return Internal_EnumLanguageGroupLocales( proc, id, flags, param, TRUE );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumUILanguagesW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumUILanguagesW( UILANGUAGE_ENUMPROCW proc, DWORD flags, LONG_PTR param )
|
|
{
|
|
return Internal_EnumUILanguages( proc, flags, param, TRUE );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* EnumSystemCodePagesW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemCodePagesW( CODEPAGE_ENUMPROCW proc, DWORD flags )
|
|
{
|
|
return Internal_EnumSystemCodePages( proc, flags, TRUE );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumSystemGeoID (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemGeoID( GEOCLASS class, GEOID parent, GEO_ENUMPROC proc )
|
|
{
|
|
INT i;
|
|
|
|
TRACE( "(%ld, %ld, %p)\n", class, parent, proc );
|
|
|
|
if (!proc)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
if (class != GEOCLASS_NATION && class != GEOCLASS_REGION && class != GEOCLASS_ALL)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < geo_ids_count; i++)
|
|
{
|
|
if (class != GEOCLASS_ALL && geo_ids[i].class != class) continue;
|
|
if (parent && geo_ids[i].parent != parent) continue;
|
|
if (!proc( geo_ids[i].id )) break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumSystemLanguageGroupsW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemLanguageGroupsW( LANGUAGEGROUP_ENUMPROCW proc,
|
|
DWORD flags, LONG_PTR param )
|
|
{
|
|
return Internal_EnumSystemLanguageGroups( proc, flags, param, TRUE );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumSystemLocalesA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemLocalesA( LOCALE_ENUMPROCA proc, DWORD flags )
|
|
{
|
|
char name[10];
|
|
DWORD i;
|
|
|
|
for (i = 0; i < locale_table->nb_lcnames; i++)
|
|
{
|
|
if (!lcnames_index[i].name) continue; /* skip invariant locale */
|
|
if (lcnames_index[i].id == LOCALE_CUSTOM_UNSPECIFIED) continue; /* skip locales with no lcid */
|
|
if (lcnames_index[i].id & 0x80000000) continue; /* skip aliases */
|
|
if (!get_locale_data( lcnames_index[i].idx )->inotneutral) continue; /* skip neutral locales */
|
|
if (!SORTIDFROMLCID( lcnames_index[i].id ) != !(flags & LCID_ALTERNATE_SORTS))
|
|
continue; /* skip alternate sorts */
|
|
sprintf( name, "%08x", lcnames_index[i].id );
|
|
if (!proc( name )) break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumSystemLocalesW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemLocalesW( LOCALE_ENUMPROCW proc, DWORD flags )
|
|
{
|
|
WCHAR name[10];
|
|
DWORD i;
|
|
|
|
for (i = 0; i < locale_table->nb_lcnames; i++)
|
|
{
|
|
if (!lcnames_index[i].name) continue; /* skip invariant locale */
|
|
if (lcnames_index[i].id == LOCALE_CUSTOM_UNSPECIFIED) continue; /* skip locales with no lcid */
|
|
if (lcnames_index[i].id & 0x80000000) continue; /* skip aliases */
|
|
if (!get_locale_data( lcnames_index[i].idx )->inotneutral) continue; /* skip neutral locales */
|
|
if (!SORTIDFROMLCID( lcnames_index[i].id ) != !(flags & LCID_ALTERNATE_SORTS))
|
|
continue; /* skip alternate sorts */
|
|
swprintf( name, ARRAY_SIZE(name), L"%08lx", lcnames_index[i].id );
|
|
if (!proc( name )) break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* EnumSystemLocalesEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumSystemLocalesEx( LOCALE_ENUMPROCEX proc, DWORD wanted_flags,
|
|
LPARAM param, void *reserved )
|
|
{
|
|
WCHAR buffer[LOCALE_NAME_MAX_LENGTH];
|
|
DWORD i, flags;
|
|
|
|
if (reserved)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < locale_table->nb_lcnames; i++)
|
|
{
|
|
const NLS_LOCALE_DATA *locale = get_locale_data( lcnames_index[i].idx );
|
|
const WCHAR *str = locale_strings + lcnames_index[i].name;
|
|
|
|
if (lcnames_index[i].id & 0x80000000) continue; /* skip aliases */
|
|
memcpy( buffer, str + 1, (*str + 1) * sizeof(WCHAR) );
|
|
if (SORTIDFROMLCID( lcnames_index[i].id ) || wcschr( str + 1, '_' ))
|
|
flags = LOCALE_ALTERNATE_SORTS;
|
|
else
|
|
flags = LOCALE_WINDOWS | (locale->inotneutral ? LOCALE_SPECIFICDATA : LOCALE_NEUTRALDATA);
|
|
if (wanted_flags && !(flags & wanted_flags)) continue;
|
|
if (!proc( buffer, flags, param )) break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumTimeFormatsW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumTimeFormatsW( TIMEFMT_ENUMPROCW proc, LCID lcid, DWORD flags )
|
|
{
|
|
return Internal_EnumTimeFormats( proc, NlsValidateLocale( &lcid, 0 ), flags, TRUE, FALSE, 0 );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumTimeFormatsEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH EnumTimeFormatsEx( TIMEFMT_ENUMPROCEX proc, const WCHAR *locale,
|
|
DWORD flags, LPARAM lparam )
|
|
{
|
|
LCID lcid;
|
|
return Internal_EnumTimeFormats( (TIMEFMT_ENUMPROCW)proc, get_locale_by_name( locale, &lcid ),
|
|
flags, TRUE, TRUE, lparam );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* FindNLSString (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH FindNLSString( LCID lcid, DWORD flags, const WCHAR *src,
|
|
int srclen, const WCHAR *value, int valuelen, int *found )
|
|
{
|
|
const WCHAR *locale = LOCALE_NAME_USER_DEFAULT;
|
|
const NLS_LOCALE_LCID_INDEX *entry;
|
|
|
|
switch (lcid)
|
|
{
|
|
case LOCALE_NEUTRAL:
|
|
case LOCALE_USER_DEFAULT:
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
case LOCALE_CUSTOM_DEFAULT:
|
|
case LOCALE_CUSTOM_UNSPECIFIED:
|
|
case LOCALE_CUSTOM_UI_DEFAULT:
|
|
break;
|
|
default:
|
|
if (lcid == user_lcid || lcid == system_lcid) break;
|
|
if (!(entry = find_lcid_entry( lcid )))
|
|
{
|
|
WARN( "unknown locale %04lx\n", lcid );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
locale = locale_strings + entry->name + 1;
|
|
break;
|
|
}
|
|
|
|
return FindNLSStringEx( locale, flags, src, srclen, value, valuelen, found, NULL, NULL, 0 );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* FindNLSStringEx (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH FindNLSStringEx( const WCHAR *locale, DWORD flags, const WCHAR *src,
|
|
int srclen, const WCHAR *value, int valuelen, int *found,
|
|
NLSVERSIONINFO *version, void *reserved, LPARAM handle )
|
|
{
|
|
const struct sortguid *sortid;
|
|
|
|
TRACE( "%s %lx %s %d %s %d %p %p %p %Id\n", wine_dbgstr_w(locale), flags,
|
|
wine_dbgstr_w(src), srclen, wine_dbgstr_w(value), valuelen, found,
|
|
version, reserved, handle );
|
|
|
|
if (version) FIXME( "unexpected version parameter\n" );
|
|
if (reserved) FIXME( "unexpected reserved value\n" );
|
|
if (handle) FIXME( "unexpected handle\n" );
|
|
|
|
if (!src || !srclen || srclen < -1 || !value || !valuelen || valuelen < -1)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return -1;
|
|
}
|
|
if (!(sortid = get_language_sort( locale ))) return -1;
|
|
|
|
if (srclen == -1) srclen = lstrlenW(src);
|
|
if (valuelen == -1) valuelen = lstrlenW(value);
|
|
|
|
return find_substring( sortid, flags, src, srclen, value, valuelen, found );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* FindStringOrdinal (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH FindStringOrdinal( DWORD flag, const WCHAR *src, INT src_size,
|
|
const WCHAR *val, INT val_size, BOOL ignore_case )
|
|
{
|
|
INT offset, inc, count;
|
|
|
|
TRACE( "%#lx %s %d %s %d %d\n", flag, wine_dbgstr_w(src), src_size,
|
|
wine_dbgstr_w(val), val_size, ignore_case );
|
|
|
|
if (!src || !val)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return -1;
|
|
}
|
|
|
|
if (flag != FIND_FROMSTART && flag != FIND_FROMEND && flag != FIND_STARTSWITH && flag != FIND_ENDSWITH)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return -1;
|
|
}
|
|
|
|
if (src_size == -1) src_size = lstrlenW( src );
|
|
if (val_size == -1) val_size = lstrlenW( val );
|
|
|
|
SetLastError( ERROR_SUCCESS );
|
|
src_size -= val_size;
|
|
if (src_size < 0) return -1;
|
|
|
|
count = flag & (FIND_FROMSTART | FIND_FROMEND) ? src_size + 1 : 1;
|
|
offset = flag & (FIND_FROMSTART | FIND_STARTSWITH) ? 0 : src_size;
|
|
inc = flag & (FIND_FROMSTART | FIND_STARTSWITH) ? 1 : -1;
|
|
while (count--)
|
|
{
|
|
if (CompareStringOrdinal( src + offset, val_size, val, val_size, ignore_case ) == CSTR_EQUAL)
|
|
return offset;
|
|
offset += inc;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* FoldStringW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH FoldStringW( DWORD flags, LPCWSTR src, INT srclen, LPWSTR dst, INT dstlen )
|
|
{
|
|
NTSTATUS status;
|
|
WCHAR *buf = dst;
|
|
int len = dstlen;
|
|
|
|
if (!src || !srclen || dstlen < 0 || (dstlen && !dst) || src == dst)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (srclen == -1) srclen = lstrlenW(src) + 1;
|
|
|
|
if (!dstlen && (flags & (MAP_PRECOMPOSED | MAP_FOLDCZONE | MAP_COMPOSITE)))
|
|
{
|
|
len = srclen * 4;
|
|
if (!(buf = RtlAllocateHeap( GetProcessHeap(), 0, len * sizeof(WCHAR) )))
|
|
{
|
|
SetLastError( ERROR_OUTOFMEMORY );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
status = fold_string( flags, src, srclen, buf, &len );
|
|
if (buf != dst) RtlFreeHeap( GetProcessHeap(), 0, buf );
|
|
if (status != STATUS_BUFFER_TOO_SMALL) break;
|
|
if (!(buf = RtlAllocateHeap( GetProcessHeap(), 0, len * sizeof(WCHAR) )))
|
|
{
|
|
SetLastError( ERROR_OUTOFMEMORY );
|
|
return 0;
|
|
}
|
|
}
|
|
if (status == STATUS_INVALID_PARAMETER_1)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (!set_ntstatus( status )) return 0;
|
|
|
|
if (dstlen && dstlen < len) SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return len;
|
|
}
|
|
|
|
|
|
static const WCHAR *get_message( DWORD flags, const void *src, UINT id, UINT lang,
|
|
BOOL ansi, WCHAR **buffer )
|
|
{
|
|
DWORD len;
|
|
|
|
if (!(flags & FORMAT_MESSAGE_FROM_STRING))
|
|
{
|
|
const MESSAGE_RESOURCE_ENTRY *entry;
|
|
NTSTATUS status = STATUS_INVALID_PARAMETER;
|
|
|
|
if (flags & FORMAT_MESSAGE_FROM_HMODULE)
|
|
{
|
|
HMODULE module = (HMODULE)src;
|
|
if (!module) module = GetModuleHandleW( 0 );
|
|
status = RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &entry );
|
|
}
|
|
if (status && (flags & FORMAT_MESSAGE_FROM_SYSTEM))
|
|
{
|
|
/* Fold win32 hresult to its embedded error code. */
|
|
if (HRESULT_SEVERITY(id) == SEVERITY_ERROR && HRESULT_FACILITY(id) == FACILITY_WIN32)
|
|
id = HRESULT_CODE( id );
|
|
status = RtlFindMessage( kernelbase_handle, RT_MESSAGETABLE, lang, id, &entry );
|
|
}
|
|
if (!set_ntstatus( status )) return NULL;
|
|
|
|
src = entry->Text;
|
|
ansi = !(entry->Flags & MESSAGE_RESOURCE_UNICODE);
|
|
}
|
|
|
|
if (!ansi) return src;
|
|
len = MultiByteToWideChar( CP_ACP, 0, src, -1, NULL, 0 );
|
|
if (!(*buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
|
|
MultiByteToWideChar( CP_ACP, 0, src, -1, *buffer, len );
|
|
return *buffer;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* FormatMessageA (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH FormatMessageA( DWORD flags, const void *source, DWORD msgid, DWORD langid,
|
|
char *buffer, DWORD size, va_list *args )
|
|
{
|
|
DWORD ret = 0;
|
|
ULONG len, retsize = 0;
|
|
ULONG width = (flags & FORMAT_MESSAGE_MAX_WIDTH_MASK);
|
|
const WCHAR *src;
|
|
WCHAR *result, *message = NULL;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(0x%lx,%p,%#lx,0x%lx,%p,%lu,%p)\n", flags, source, msgid, langid, buffer, size, args );
|
|
|
|
if (flags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
|
|
{
|
|
if (!buffer)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return 0;
|
|
}
|
|
*(char **)buffer = NULL;
|
|
}
|
|
if (size >= 32768)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (width == 0xff) width = ~0u;
|
|
|
|
if (!(src = get_message( flags, source, msgid, langid, TRUE, &message ))) return 0;
|
|
|
|
if (!(result = HeapAlloc( GetProcessHeap(), 0, 65536 )))
|
|
status = STATUS_NO_MEMORY;
|
|
else
|
|
status = RtlFormatMessage( src, width, !!(flags & FORMAT_MESSAGE_IGNORE_INSERTS),
|
|
TRUE, !!(flags & FORMAT_MESSAGE_ARGUMENT_ARRAY), args,
|
|
result, 65536, &retsize );
|
|
|
|
HeapFree( GetProcessHeap(), 0, message );
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
goto done;
|
|
}
|
|
if (!set_ntstatus( status )) goto done;
|
|
|
|
len = WideCharToMultiByte( CP_ACP, 0, result, retsize / sizeof(WCHAR), NULL, 0, NULL, NULL );
|
|
if (len <= 1)
|
|
{
|
|
SetLastError( ERROR_NO_WORK_DONE );
|
|
goto done;
|
|
}
|
|
|
|
if (flags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
|
|
{
|
|
char *buf = LocalAlloc( LMEM_ZEROINIT, max( size, len ));
|
|
if (!buf)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
goto done;
|
|
}
|
|
*(char **)buffer = buf;
|
|
WideCharToMultiByte( CP_ACP, 0, result, retsize / sizeof(WCHAR), buf, max( size, len ), NULL, NULL );
|
|
}
|
|
else if (len > size)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
goto done;
|
|
}
|
|
else WideCharToMultiByte( CP_ACP, 0, result, retsize / sizeof(WCHAR), buffer, size, NULL, NULL );
|
|
|
|
ret = len - 1;
|
|
|
|
done:
|
|
HeapFree( GetProcessHeap(), 0, result );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* FormatMessageW (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH FormatMessageW( DWORD flags, const void *source, DWORD msgid, DWORD langid,
|
|
WCHAR *buffer, DWORD size, va_list *args )
|
|
{
|
|
ULONG retsize = 0;
|
|
ULONG width = (flags & FORMAT_MESSAGE_MAX_WIDTH_MASK);
|
|
const WCHAR *src;
|
|
WCHAR *message = NULL;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(0x%lx,%p,%#lx,0x%lx,%p,%lu,%p)\n", flags, source, msgid, langid, buffer, size, args );
|
|
|
|
if (!buffer)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (width == 0xff) width = ~0u;
|
|
|
|
if (flags & FORMAT_MESSAGE_ALLOCATE_BUFFER) *(LPWSTR *)buffer = NULL;
|
|
|
|
if (!(src = get_message( flags, source, msgid, langid, FALSE, &message ))) return 0;
|
|
|
|
if (flags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
|
|
{
|
|
WCHAR *result;
|
|
va_list args_copy;
|
|
ULONG alloc = max( size * sizeof(WCHAR), 65536 );
|
|
|
|
for (;;)
|
|
{
|
|
if (!(result = HeapAlloc( GetProcessHeap(), 0, alloc )))
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
break;
|
|
}
|
|
if (args && !(flags & FORMAT_MESSAGE_ARGUMENT_ARRAY))
|
|
{
|
|
va_copy( args_copy, *args );
|
|
status = RtlFormatMessage( src, width, !!(flags & FORMAT_MESSAGE_IGNORE_INSERTS),
|
|
FALSE, FALSE, &args_copy, result, alloc, &retsize );
|
|
va_end( args_copy );
|
|
}
|
|
else
|
|
status = RtlFormatMessage( src, width, !!(flags & FORMAT_MESSAGE_IGNORE_INSERTS),
|
|
FALSE, TRUE, args, result, alloc, &retsize );
|
|
|
|
if (!status)
|
|
{
|
|
if (retsize <= sizeof(WCHAR)) HeapFree( GetProcessHeap(), 0, result );
|
|
else *(WCHAR **)buffer = HeapReAlloc( GetProcessHeap(), HEAP_REALLOC_IN_PLACE_ONLY,
|
|
result, max( retsize, size * sizeof(WCHAR) ));
|
|
break;
|
|
}
|
|
HeapFree( GetProcessHeap(), 0, result );
|
|
if (status != STATUS_BUFFER_OVERFLOW) break;
|
|
alloc *= 2;
|
|
}
|
|
}
|
|
else status = RtlFormatMessage( src, width, !!(flags & FORMAT_MESSAGE_IGNORE_INSERTS),
|
|
FALSE, !!(flags & FORMAT_MESSAGE_ARGUMENT_ARRAY), args,
|
|
buffer, size * sizeof(WCHAR), &retsize );
|
|
|
|
HeapFree( GetProcessHeap(), 0, message );
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
if (size) buffer[size - 1] = 0;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
if (!set_ntstatus( status )) return 0;
|
|
if (retsize <= sizeof(WCHAR)) SetLastError( ERROR_NO_WORK_DONE );
|
|
return retsize / sizeof(WCHAR) - 1;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetACP (kernelbase.@)
|
|
*/
|
|
UINT WINAPI GetACP(void)
|
|
{
|
|
return ansi_cpinfo.CodePage;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCPInfo (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetCPInfo( UINT codepage, CPINFO *cpinfo )
|
|
{
|
|
const CPTABLEINFO *table;
|
|
|
|
if (!cpinfo || !(table = get_codepage_table( codepage )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
cpinfo->MaxCharSize = table->MaximumCharacterSize;
|
|
memcpy( cpinfo->DefaultChar, &table->DefaultChar, sizeof(cpinfo->DefaultChar) );
|
|
memcpy( cpinfo->LeadByte, table->LeadByte, sizeof(cpinfo->LeadByte) );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCPInfoExW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI GetCPInfoExW( UINT codepage, DWORD flags, CPINFOEXW *cpinfo )
|
|
{
|
|
const CPTABLEINFO *table;
|
|
int min, max, pos;
|
|
|
|
if (!cpinfo || !(table = get_codepage_table( codepage )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
cpinfo->MaxCharSize = table->MaximumCharacterSize;
|
|
memcpy( cpinfo->DefaultChar, &table->DefaultChar, sizeof(cpinfo->DefaultChar) );
|
|
memcpy( cpinfo->LeadByte, table->LeadByte, sizeof(cpinfo->LeadByte) );
|
|
cpinfo->CodePage = table->CodePage;
|
|
cpinfo->UnicodeDefaultChar = table->UniDefaultChar;
|
|
|
|
min = 0;
|
|
max = ARRAY_SIZE(codepage_names) - 1;
|
|
cpinfo->CodePageName[0] = 0;
|
|
while (min <= max)
|
|
{
|
|
pos = (min + max) / 2;
|
|
if (codepage_names[pos].cp < cpinfo->CodePage) min = pos + 1;
|
|
else if (codepage_names[pos].cp > cpinfo->CodePage) max = pos - 1;
|
|
else
|
|
{
|
|
wcscpy( cpinfo->CodePageName, codepage_names[pos].name );
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCalendarInfoW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetCalendarInfoW( LCID lcid, CALID calendar, CALTYPE type,
|
|
WCHAR *buffer, INT len, DWORD *value )
|
|
{
|
|
const NLS_LOCALE_DATA *locale;
|
|
|
|
TRACE( "%04lx %lu 0x%lx %p %d %p\n", lcid, calendar, type, buffer, len, value );
|
|
|
|
if (!(locale = NlsValidateLocale( &lcid, 0 )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_calendar_info( locale, calendar, type, buffer, len, value );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCalendarInfoEx (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetCalendarInfoEx( const WCHAR *name, CALID calendar, const WCHAR *reserved,
|
|
CALTYPE type, WCHAR *buffer, INT len, DWORD *value )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
TRACE( "%s %lu 0x%lx %p %d %p\n", debugstr_w(name), calendar, type, buffer, len, value );
|
|
|
|
if (!locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_calendar_info( locale, calendar, type, buffer, len, value );
|
|
}
|
|
|
|
|
|
static CRITICAL_SECTION tzname_section;
|
|
static CRITICAL_SECTION_DEBUG tzname_section_debug =
|
|
{
|
|
0, 0, &tzname_section,
|
|
{ &tzname_section_debug.ProcessLocksList, &tzname_section_debug.ProcessLocksList },
|
|
0, 0, { (DWORD_PTR)(__FILE__ ": tzname_section") }
|
|
};
|
|
static CRITICAL_SECTION tzname_section = { &tzname_section_debug, -1, 0, 0, 0, 0 };
|
|
static struct {
|
|
LCID lcid;
|
|
WCHAR key_name[128];
|
|
WCHAR standard_name[32];
|
|
WCHAR daylight_name[32];
|
|
} cached_tzname;
|
|
|
|
/***********************************************************************
|
|
* GetDynamicTimeZoneInformation (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH GetDynamicTimeZoneInformation( DYNAMIC_TIME_ZONE_INFORMATION *info )
|
|
{
|
|
HKEY key;
|
|
LARGE_INTEGER now;
|
|
|
|
if (!set_ntstatus( RtlQueryDynamicTimeZoneInformation( (RTL_DYNAMIC_TIME_ZONE_INFORMATION *)info )))
|
|
return TIME_ZONE_ID_INVALID;
|
|
|
|
RtlEnterCriticalSection( &tzname_section );
|
|
if (cached_tzname.lcid == GetThreadLocale() &&
|
|
!wcscmp( info->TimeZoneKeyName, cached_tzname.key_name ))
|
|
{
|
|
wcscpy( info->StandardName, cached_tzname.standard_name );
|
|
wcscpy( info->DaylightName, cached_tzname.daylight_name );
|
|
RtlLeaveCriticalSection( &tzname_section );
|
|
}
|
|
else
|
|
{
|
|
RtlLeaveCriticalSection( &tzname_section );
|
|
if (!RegOpenKeyExW( tz_key, info->TimeZoneKeyName, 0, KEY_ALL_ACCESS, &key ))
|
|
{
|
|
RegLoadMUIStringW( key, L"MUI_Std", info->StandardName,
|
|
sizeof(info->StandardName), NULL, 0, system_dir );
|
|
RegLoadMUIStringW( key, L"MUI_Dlt", info->DaylightName,
|
|
sizeof(info->DaylightName), NULL, 0, system_dir );
|
|
RegCloseKey( key );
|
|
}
|
|
else return TIME_ZONE_ID_INVALID;
|
|
|
|
RtlEnterCriticalSection( &tzname_section );
|
|
cached_tzname.lcid = GetThreadLocale();
|
|
wcscpy( cached_tzname.key_name, info->TimeZoneKeyName );
|
|
wcscpy( cached_tzname.standard_name, info->StandardName );
|
|
wcscpy( cached_tzname.daylight_name, info->DaylightName );
|
|
RtlLeaveCriticalSection( &tzname_section );
|
|
}
|
|
|
|
NtQuerySystemTime( &now );
|
|
return get_timezone_id( (TIME_ZONE_INFORMATION *)info, now, FALSE );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetDynamicTimeZoneInformationEffectiveYears (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH GetDynamicTimeZoneInformationEffectiveYears( const DYNAMIC_TIME_ZONE_INFORMATION *info,
|
|
DWORD *first, DWORD *last )
|
|
{
|
|
HKEY key, dst_key = 0;
|
|
DWORD type, count, ret = ERROR_FILE_NOT_FOUND;
|
|
|
|
if (RegOpenKeyExW( tz_key, info->TimeZoneKeyName, 0, KEY_ALL_ACCESS, &key )) return ret;
|
|
|
|
if (RegOpenKeyExW( key, L"Dynamic DST", 0, KEY_ALL_ACCESS, &dst_key )) goto done;
|
|
count = sizeof(DWORD);
|
|
if (RegQueryValueExW( dst_key, L"FirstEntry", NULL, &type, (BYTE *)first, &count )) goto done;
|
|
if (type != REG_DWORD) goto done;
|
|
count = sizeof(DWORD);
|
|
if (RegQueryValueExW( dst_key, L"LastEntry", NULL, &type, (BYTE *)last, &count )) goto done;
|
|
if (type != REG_DWORD) goto done;
|
|
ret = 0;
|
|
|
|
done:
|
|
RegCloseKey( dst_key );
|
|
RegCloseKey( key );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetFileMUIInfo (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI /* DECLSPEC_HOTPATCH */ GetFileMUIInfo( DWORD flags, const WCHAR *path,
|
|
FILEMUIINFO *info, DWORD *size )
|
|
{
|
|
FIXME( "stub: %lu, %s, %p, %p\n", flags, debugstr_w(path), info, size );
|
|
SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetFileMUIPath (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI /* DECLSPEC_HOTPATCH */ GetFileMUIPath( DWORD flags, const WCHAR *filepath,
|
|
WCHAR *language, ULONG *languagelen,
|
|
WCHAR *muipath, ULONG *muipathlen,
|
|
ULONGLONG *enumerator )
|
|
{
|
|
FIXME( "stub: 0x%lx, %s, %s, %p, %p, %p, %p\n", flags, debugstr_w(filepath),
|
|
debugstr_w(language), languagelen, muipath, muipathlen, enumerator );
|
|
SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetGeoInfoW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetGeoInfoW( GEOID id, GEOTYPE type, WCHAR *data, int count, LANGID lang )
|
|
{
|
|
const struct geo_id *ptr = find_geo_id_entry( id );
|
|
|
|
TRACE( "%ld %ld %p %d %d\n", id, type, data, count, lang );
|
|
|
|
if (!ptr)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_geo_info( ptr, type, data, count, lang );
|
|
}
|
|
|
|
|
|
INT WINAPI DECLSPEC_HOTPATCH GetGeoInfoEx( WCHAR *location, GEOTYPE type, WCHAR *data, int data_count )
|
|
{
|
|
const struct geo_id *ptr = find_geo_name_entry( location );
|
|
|
|
TRACE( "%s %lx %p %d\n", wine_dbgstr_w(location), type, data, data_count );
|
|
|
|
if (!ptr)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (type == GEO_LCID || type == GEO_NATION || type == GEO_RFC1766)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
return get_geo_info( ptr, type, data, data_count, 0 );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetLocaleInfoA (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetLocaleInfoA( LCID lcid, LCTYPE lctype, char *buffer, INT len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale;
|
|
WCHAR *bufferW;
|
|
INT lenW, ret;
|
|
|
|
TRACE( "lcid=0x%lx lctype=0x%lx %p %d\n", lcid, lctype, buffer, len );
|
|
|
|
if (len < 0 || (len && !buffer))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (LOWORD(lctype) == LOCALE_SSHORTTIME || (lctype & LOCALE_RETURN_GENITIVE_NAMES))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (!(locale = NlsValidateLocale( &lcid, 0 )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (LOWORD(lctype) == LOCALE_FONTSIGNATURE || (lctype & LOCALE_RETURN_NUMBER))
|
|
{
|
|
ret = get_locale_info( locale, lcid, lctype, (WCHAR *)buffer, len / sizeof(WCHAR) );
|
|
return ret * sizeof(WCHAR);
|
|
}
|
|
|
|
if (!(lenW = get_locale_info( locale, lcid, lctype, NULL, 0 ))) return 0;
|
|
|
|
if (!(bufferW = RtlAllocateHeap( GetProcessHeap(), 0, lenW * sizeof(WCHAR) )))
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return 0;
|
|
}
|
|
ret = get_locale_info( locale, lcid, lctype, bufferW, lenW );
|
|
if (ret) ret = WideCharToMultiByte( get_locale_codepage( locale, lctype ), 0,
|
|
bufferW, ret, buffer, len, NULL, NULL );
|
|
RtlFreeHeap( GetProcessHeap(), 0, bufferW );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetLocaleInfoW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetLocaleInfoW( LCID lcid, LCTYPE lctype, WCHAR *buffer, INT len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale;
|
|
|
|
if (len < 0 || (len && !buffer))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(lcid=0x%lx,lctype=0x%lx,%p,%d)\n", lcid, lctype, buffer, len );
|
|
|
|
if (!(locale = NlsValidateLocale( &lcid, 0 )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_locale_info( locale, lcid, lctype, buffer, len );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetLocaleInfoEx (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetLocaleInfoEx( const WCHAR *name, LCTYPE info, WCHAR *buffer, INT len )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
TRACE( "%s 0x%lx %p %d\n", debugstr_w(name), info, buffer, len );
|
|
|
|
if (!locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_locale_info( locale, lcid, info, buffer, len );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetNLSVersion (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetNLSVersion( NLS_FUNCTION func, LCID lcid, NLSVERSIONINFO *info )
|
|
{
|
|
WCHAR locale[LOCALE_NAME_MAX_LENGTH];
|
|
|
|
if (info->dwNLSVersionInfoSize < offsetof( NLSVERSIONINFO, dwEffectiveId ))
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return FALSE;
|
|
}
|
|
if (!LCIDToLocaleName( lcid, locale, LOCALE_NAME_MAX_LENGTH, LOCALE_ALLOW_NEUTRAL_NAMES ))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
return GetNLSVersionEx( func, locale, (NLSVERSIONINFOEX *)info );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetNLSVersionEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetNLSVersionEx( NLS_FUNCTION func, const WCHAR *locale,
|
|
NLSVERSIONINFOEX *info )
|
|
{
|
|
const struct sortguid *sortid;
|
|
|
|
if (func != COMPARE_STRING)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
if (info->dwNLSVersionInfoSize < sizeof(*info) &&
|
|
(info->dwNLSVersionInfoSize != offsetof( NLSVERSIONINFO, dwEffectiveId )))
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(sortid = get_language_sort( locale ))) return FALSE;
|
|
|
|
info->dwNLSVersion = info->dwDefinedVersion = sort.version;
|
|
if (info->dwNLSVersionInfoSize >= sizeof(*info))
|
|
{
|
|
info->dwEffectiveId = LocaleNameToLCID( locale, 0 );
|
|
info->guidCustomVersion = sortid->id;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetOEMCP (kernelbase.@)
|
|
*/
|
|
UINT WINAPI GetOEMCP(void)
|
|
{
|
|
return oem_cpinfo.CodePage;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetProcessPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetProcessPreferredUILanguages( DWORD flags, ULONG *count,
|
|
WCHAR *buffer, ULONG *size )
|
|
{
|
|
return set_ntstatus( RtlGetProcessPreferredUILanguages( flags, count, buffer, size ));
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetStringTypeA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetStringTypeA( LCID locale, DWORD type, const char *src, int count,
|
|
WORD *chartype )
|
|
{
|
|
UINT cp;
|
|
INT countW;
|
|
LPWSTR srcW;
|
|
BOOL ret = FALSE;
|
|
|
|
if (count == -1) count = strlen(src) + 1;
|
|
|
|
cp = get_lcid_codepage( locale, 0 );
|
|
countW = MultiByteToWideChar(cp, 0, src, count, NULL, 0);
|
|
if((srcW = HeapAlloc(GetProcessHeap(), 0, countW * sizeof(WCHAR))))
|
|
{
|
|
MultiByteToWideChar(cp, 0, src, count, srcW, countW);
|
|
/*
|
|
* NOTE: the target buffer has 1 word for each CHARACTER in the source
|
|
* string, with multibyte characters there maybe be more bytes in count
|
|
* than character space in the buffer!
|
|
*/
|
|
ret = GetStringTypeW(type, srcW, countW, chartype);
|
|
HeapFree(GetProcessHeap(), 0, srcW);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetStringTypeW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetStringTypeW( DWORD type, const WCHAR *src, INT count, WORD *chartype )
|
|
{
|
|
if (!src)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
if (type != CT_CTYPE1 && type != CT_CTYPE2 && type != CT_CTYPE3)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
if (count == -1) count = lstrlenW(src) + 1;
|
|
|
|
while (count--) *chartype++ = get_char_type( type, *src++ );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetStringTypeExW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetStringTypeExW( LCID locale, DWORD type, const WCHAR *src, int count,
|
|
WORD *chartype )
|
|
{
|
|
/* locale is ignored for Unicode */
|
|
return GetStringTypeW( type, src, count, chartype );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetSystemDefaultLCID (kernelbase.@)
|
|
*/
|
|
LCID WINAPI DECLSPEC_HOTPATCH GetSystemDefaultLCID(void)
|
|
{
|
|
return system_lcid;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetSystemDefaultLangID (kernelbase.@)
|
|
*/
|
|
LANGID WINAPI DECLSPEC_HOTPATCH GetSystemDefaultLangID(void)
|
|
{
|
|
return LANGIDFROMLCID( GetSystemDefaultLCID() );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetSystemDefaultLocaleName (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetSystemDefaultLocaleName( LPWSTR name, INT count )
|
|
{
|
|
return get_locale_info( system_locale, system_lcid, LOCALE_SNAME, name, count );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetSystemDefaultUILanguage (kernelbase.@)
|
|
*/
|
|
LANGID WINAPI DECLSPEC_HOTPATCH GetSystemDefaultUILanguage(void)
|
|
{
|
|
LANGID lang;
|
|
NtQueryInstallUILanguage( &lang );
|
|
return lang;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetSystemPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetSystemPreferredUILanguages( DWORD flags, ULONG *count,
|
|
WCHAR *buffer, ULONG *size )
|
|
{
|
|
return set_ntstatus( RtlGetSystemPreferredUILanguages( flags, 0, count, buffer, size ));
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetThreadPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetThreadPreferredUILanguages( DWORD flags, ULONG *count,
|
|
WCHAR *buffer, ULONG *size )
|
|
{
|
|
return set_ntstatus( RtlGetThreadPreferredUILanguages( flags, count, buffer, size ));
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetTimeZoneInformation (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH GetTimeZoneInformation( TIME_ZONE_INFORMATION *info )
|
|
{
|
|
DYNAMIC_TIME_ZONE_INFORMATION tzinfo;
|
|
DWORD ret = GetDynamicTimeZoneInformation( &tzinfo );
|
|
|
|
memcpy( info, &tzinfo, sizeof(*info) );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetTimeZoneInformationForYear (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetTimeZoneInformationForYear( USHORT year,
|
|
DYNAMIC_TIME_ZONE_INFORMATION *dynamic,
|
|
TIME_ZONE_INFORMATION *info )
|
|
{
|
|
DYNAMIC_TIME_ZONE_INFORMATION local_info;
|
|
HKEY key = 0, dst_key;
|
|
DWORD count;
|
|
LRESULT ret;
|
|
struct
|
|
{
|
|
LONG bias;
|
|
LONG std_bias;
|
|
LONG dlt_bias;
|
|
SYSTEMTIME std_date;
|
|
SYSTEMTIME dlt_date;
|
|
} data;
|
|
|
|
TRACE( "(%u,%p)\n", year, info );
|
|
|
|
if (!dynamic)
|
|
{
|
|
if (GetDynamicTimeZoneInformation( &local_info ) == TIME_ZONE_ID_INVALID) return FALSE;
|
|
dynamic = &local_info;
|
|
}
|
|
|
|
if ((ret = RegOpenKeyExW( tz_key, dynamic->TimeZoneKeyName, 0, KEY_ALL_ACCESS, &key ))) goto done;
|
|
if (RegLoadMUIStringW( key, L"MUI_Std", info->StandardName,
|
|
sizeof(info->StandardName), NULL, 0, system_dir ))
|
|
{
|
|
count = sizeof(info->StandardName);
|
|
if ((ret = RegQueryValueExW( key, L"Std", NULL, NULL, (BYTE *)info->StandardName, &count )))
|
|
goto done;
|
|
}
|
|
if (RegLoadMUIStringW( key, L"MUI_Dlt", info->DaylightName,
|
|
sizeof(info->DaylightName), NULL, 0, system_dir ))
|
|
{
|
|
count = sizeof(info->DaylightName);
|
|
if ((ret = RegQueryValueExW( key, L"Dlt", NULL, NULL, (BYTE *)info->DaylightName, &count )))
|
|
goto done;
|
|
}
|
|
|
|
ret = ERROR_FILE_NOT_FOUND;
|
|
if (!dynamic->DynamicDaylightTimeDisabled &&
|
|
!RegOpenKeyExW( key, L"Dynamic DST", 0, KEY_ALL_ACCESS, &dst_key ))
|
|
{
|
|
WCHAR yearW[16];
|
|
swprintf( yearW, ARRAY_SIZE(yearW), L"%u", year );
|
|
count = sizeof(data);
|
|
ret = RegQueryValueExW( dst_key, yearW, NULL, NULL, (BYTE *)&data, &count );
|
|
RegCloseKey( dst_key );
|
|
}
|
|
if (ret)
|
|
{
|
|
count = sizeof(data);
|
|
ret = RegQueryValueExW( key, L"TZI", NULL, NULL, (BYTE *)&data, &count );
|
|
}
|
|
|
|
if (!ret)
|
|
{
|
|
info->Bias = data.bias;
|
|
info->StandardBias = data.std_bias;
|
|
info->DaylightBias = data.dlt_bias;
|
|
info->StandardDate = data.std_date;
|
|
info->DaylightDate = data.dlt_date;
|
|
}
|
|
|
|
done:
|
|
RegCloseKey( key );
|
|
if (ret) SetLastError( ret );
|
|
return !ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetUserDefaultLCID (kernelbase.@)
|
|
*/
|
|
LCID WINAPI DECLSPEC_HOTPATCH GetUserDefaultLCID(void)
|
|
{
|
|
return user_lcid;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetUserDefaultLangID (kernelbase.@)
|
|
*/
|
|
LANGID WINAPI DECLSPEC_HOTPATCH GetUserDefaultLangID(void)
|
|
{
|
|
return LANGIDFROMLCID( GetUserDefaultLCID() );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetUserDefaultLocaleName (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH GetUserDefaultLocaleName( LPWSTR name, INT len )
|
|
{
|
|
return get_locale_info( user_locale, user_lcid, LOCALE_SNAME, name, len );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetUserDefaultUILanguage (kernelbase.@)
|
|
*/
|
|
LANGID WINAPI DECLSPEC_HOTPATCH GetUserDefaultUILanguage(void)
|
|
{
|
|
return LANGIDFROMLCID( GetUserDefaultLCID() );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetUserGeoID (kernelbase.@)
|
|
*/
|
|
GEOID WINAPI DECLSPEC_HOTPATCH GetUserGeoID( GEOCLASS geoclass )
|
|
{
|
|
GEOID ret = 39070;
|
|
const WCHAR *name;
|
|
WCHAR bufferW[40];
|
|
HKEY hkey;
|
|
|
|
switch (geoclass)
|
|
{
|
|
case GEOCLASS_NATION:
|
|
name = L"Nation";
|
|
break;
|
|
case GEOCLASS_REGION:
|
|
name = L"Region";
|
|
break;
|
|
default:
|
|
WARN("Unknown geoclass %ld\n", geoclass);
|
|
return GEOID_NOT_AVAILABLE;
|
|
}
|
|
if (!RegOpenKeyExW( intl_key, L"Geo", 0, KEY_ALL_ACCESS, &hkey ))
|
|
{
|
|
DWORD count = sizeof(bufferW);
|
|
if (!RegQueryValueExW( hkey, name, NULL, NULL, (BYTE *)bufferW, &count ))
|
|
ret = wcstol( bufferW, NULL, 10 );
|
|
RegCloseKey( hkey );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetUserPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH GetUserPreferredUILanguages( DWORD flags, ULONG *count,
|
|
WCHAR *buffer, ULONG *size )
|
|
{
|
|
return set_ntstatus( RtlGetUserPreferredUILanguages( flags, 0, count, buffer, size ));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IdnToAscii (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH IdnToAscii( DWORD flags, const WCHAR *src, INT srclen,
|
|
WCHAR *dst, INT dstlen )
|
|
{
|
|
NTSTATUS status = RtlIdnToAscii( flags, src, srclen, dst, &dstlen );
|
|
if (!set_ntstatus( status )) return 0;
|
|
return dstlen;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IdnToNameprepUnicode (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH IdnToNameprepUnicode( DWORD flags, const WCHAR *src, INT srclen,
|
|
WCHAR *dst, INT dstlen )
|
|
{
|
|
NTSTATUS status = RtlIdnToNameprepUnicode( flags, src, srclen, dst, &dstlen );
|
|
if (!set_ntstatus( status )) return 0;
|
|
return dstlen;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IdnToUnicode (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH IdnToUnicode( DWORD flags, const WCHAR *src, INT srclen,
|
|
WCHAR *dst, INT dstlen )
|
|
{
|
|
NTSTATUS status = RtlIdnToUnicode( flags, src, srclen, dst, &dstlen );
|
|
if (!set_ntstatus( status )) return 0;
|
|
return dstlen;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharAlphaA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharAlphaA( CHAR c )
|
|
{
|
|
WCHAR wc;
|
|
DWORD reslen;
|
|
RtlMultiByteToUnicodeN( &wc, sizeof(WCHAR), &reslen, &c, 1 );
|
|
return reslen && (get_char_type( CT_CTYPE1, wc ) & C1_ALPHA);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharAlphaW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharAlphaW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_ALPHA);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharAlphaNumericA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharAlphaNumericA( CHAR c )
|
|
{
|
|
WCHAR wc;
|
|
DWORD reslen;
|
|
RtlMultiByteToUnicodeN( &wc, sizeof(WCHAR), &reslen, &c, 1 );
|
|
return reslen && (get_char_type( CT_CTYPE1, wc ) & (C1_ALPHA | C1_DIGIT));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharAlphaNumericW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharAlphaNumericW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & (C1_ALPHA | C1_DIGIT));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharBlankW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharBlankW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_BLANK);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharCntrlW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharCntrlW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_CNTRL);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharDigitW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharDigitW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_DIGIT);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharLowerA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharLowerA( CHAR c )
|
|
{
|
|
WCHAR wc;
|
|
DWORD reslen;
|
|
RtlMultiByteToUnicodeN( &wc, sizeof(WCHAR), &reslen, &c, 1 );
|
|
return reslen && (get_char_type( CT_CTYPE1, wc ) & C1_LOWER);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharLowerW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharLowerW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_LOWER);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharPunctW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharPunctW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_PUNCT);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharSpaceA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharSpaceA( CHAR c )
|
|
{
|
|
WCHAR wc;
|
|
DWORD reslen;
|
|
RtlMultiByteToUnicodeN( &wc, sizeof(WCHAR), &reslen, &c, 1 );
|
|
return reslen && (get_char_type( CT_CTYPE1, wc ) & C1_SPACE);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharSpaceW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharSpaceW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_SPACE);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharUpperA (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharUpperA( CHAR c )
|
|
{
|
|
WCHAR wc;
|
|
DWORD reslen;
|
|
RtlMultiByteToUnicodeN( &wc, sizeof(WCHAR), &reslen, &c, 1 );
|
|
return reslen && (get_char_type( CT_CTYPE1, wc ) & C1_UPPER);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharUpperW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharUpperW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_UPPER);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsCharXDigitW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsCharXDigitW( WCHAR wc )
|
|
{
|
|
return !!(get_char_type( CT_CTYPE1, wc ) & C1_XDIGIT);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsDBCSLeadByte (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsDBCSLeadByte( BYTE testchar )
|
|
{
|
|
return ansi_cpinfo.DBCSCodePage && ansi_cpinfo.DBCSOffsets[testchar];
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsDBCSLeadByteEx (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsDBCSLeadByteEx( UINT codepage, BYTE testchar )
|
|
{
|
|
const CPTABLEINFO *table = get_codepage_table( codepage );
|
|
return table && table->DBCSCodePage && table->DBCSOffsets[testchar];
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsNormalizedString (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsNormalizedString( NORM_FORM form, const WCHAR *str, INT len )
|
|
{
|
|
BOOLEAN res;
|
|
if (!set_ntstatus( RtlIsNormalizedString( form, str, len, &res ))) res = FALSE;
|
|
return res;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsValidCodePage (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsValidCodePage( UINT codepage )
|
|
{
|
|
switch (codepage)
|
|
{
|
|
case CP_ACP:
|
|
case CP_OEMCP:
|
|
case CP_MACCP:
|
|
case CP_THREAD_ACP:
|
|
return FALSE;
|
|
default:
|
|
return get_codepage_table( codepage ) != NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsValidLanguageGroup (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsValidLanguageGroup( LGRPID id, DWORD flags )
|
|
{
|
|
WCHAR name[10], value[10];
|
|
DWORD type, value_len = sizeof(value);
|
|
BOOL ret = FALSE;
|
|
HKEY key;
|
|
|
|
if (RegOpenKeyExW( nls_key, L"Language Groups", 0, KEY_READ, &key )) return FALSE;
|
|
|
|
swprintf( name, ARRAY_SIZE(name), L"%x", id );
|
|
if (!RegQueryValueExW( key, name, NULL, &type, (BYTE *)value, &value_len ) && type == REG_SZ)
|
|
ret = (flags & LGRPID_SUPPORTED) || wcstoul( value, NULL, 10 );
|
|
|
|
RegCloseKey( key );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsValidLocale (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsValidLocale( LCID lcid, DWORD flags )
|
|
{
|
|
switch (lcid)
|
|
{
|
|
case LOCALE_NEUTRAL:
|
|
case LOCALE_USER_DEFAULT:
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
return FALSE;
|
|
default:
|
|
return !!NlsValidateLocale( &lcid, LOCALE_ALLOW_NEUTRAL_NAMES );
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsValidLocaleName (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsValidLocaleName( const WCHAR *locale )
|
|
{
|
|
if (locale == LOCALE_NAME_USER_DEFAULT) return FALSE;
|
|
return !!find_lcname_entry( locale );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsNLSDefinedString (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH IsNLSDefinedString( NLS_FUNCTION func, DWORD flags, NLSVERSIONINFO *info,
|
|
const WCHAR *str, int len )
|
|
{
|
|
int i;
|
|
|
|
if (func != COMPARE_STRING)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
if (info)
|
|
{
|
|
if (info->dwNLSVersionInfoSize != sizeof(*info) &&
|
|
(info->dwNLSVersionInfoSize != offsetof( NLSVERSIONINFO, dwEffectiveId )))
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (len < 0) len = lstrlenW( str ) + 1;
|
|
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (is_private_use_area_char( str[i] )) return FALSE;
|
|
if (IS_LOW_SURROGATE( str[i] )) return FALSE;
|
|
if (IS_HIGH_SURROGATE( str[i] ))
|
|
{
|
|
if (++i == len) return FALSE;
|
|
if (!IS_LOW_SURROGATE( str[i] )) return FALSE;
|
|
continue;
|
|
}
|
|
if (!(get_char_type( CT_CTYPE1, str[i] ) & C1_DEFINED)) return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* IsValidNLSVersion (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH IsValidNLSVersion( NLS_FUNCTION func, const WCHAR *locale,
|
|
NLSVERSIONINFOEX *info )
|
|
{
|
|
static const GUID GUID_NULL;
|
|
NLSVERSIONINFOEX infoex;
|
|
DWORD ret;
|
|
|
|
if (func != COMPARE_STRING)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
if (info->dwNLSVersionInfoSize < sizeof(*info) &&
|
|
(info->dwNLSVersionInfoSize != offsetof( NLSVERSIONINFO, dwEffectiveId )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
infoex.dwNLSVersionInfoSize = sizeof(infoex);
|
|
if (!GetNLSVersionEx( func, locale, &infoex )) return FALSE;
|
|
|
|
ret = (infoex.dwNLSVersion & ~0xff) == (info->dwNLSVersion & ~0xff);
|
|
if (ret && !IsEqualGUID( &info->guidCustomVersion, &GUID_NULL ))
|
|
ret = find_sortguid( &info->guidCustomVersion ) != NULL;
|
|
|
|
if (!ret) SetLastError( ERROR_SUCCESS );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* LCIDToLocaleName (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH LCIDToLocaleName( LCID lcid, WCHAR *name, INT count, DWORD flags )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, flags );
|
|
|
|
if (!locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
return get_locale_info( locale, lcid, LOCALE_SNAME, name, count );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* LCMapStringEx (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH LCMapStringEx( const WCHAR *locale, DWORD flags, const WCHAR *src, int srclen,
|
|
WCHAR *dst, int dstlen, NLSVERSIONINFO *version,
|
|
void *reserved, LPARAM handle )
|
|
{
|
|
const struct sortguid *sortid = NULL;
|
|
|
|
if (version) FIXME( "unsupported version structure %p\n", version );
|
|
if (reserved) FIXME( "unsupported reserved pointer %p\n", reserved );
|
|
if (handle)
|
|
{
|
|
static int once;
|
|
if (!once++) FIXME( "unsupported lparam %Ix\n", handle );
|
|
}
|
|
|
|
if (!src || !srclen || dstlen < 0)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (srclen < 0) srclen = lstrlenW(src) + 1;
|
|
|
|
TRACE( "(%s,0x%08lx,%s,%d,%p,%d)\n",
|
|
debugstr_w(locale), flags, debugstr_wn(src, srclen), srclen, dst, dstlen );
|
|
|
|
flags &= ~LOCALE_USE_CP_ACP;
|
|
|
|
if (src == dst && (flags & ~(LCMAP_LOWERCASE | LCMAP_UPPERCASE)))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (flags & (LCMAP_LOWERCASE | LCMAP_UPPERCASE | LCMAP_SORTKEY))
|
|
{
|
|
if (!(sortid = get_language_sort( locale ))) return 0;
|
|
}
|
|
if (flags & LCMAP_HASH)
|
|
{
|
|
FIXME( "LCMAP_HASH %s not supported\n", debugstr_wn( src, srclen ));
|
|
return 0;
|
|
}
|
|
if (flags & LCMAP_SORTHANDLE)
|
|
{
|
|
FIXME( "LCMAP_SORTHANDLE not supported\n" );
|
|
return 0;
|
|
}
|
|
if (flags & LCMAP_SORTKEY) return get_sortkey( sortid, flags, src, srclen, (BYTE *)dst, dstlen );
|
|
|
|
return lcmap_string( sortid, flags, src, srclen, dst, dstlen );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* LCMapStringA (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH LCMapStringA( LCID lcid, DWORD flags, const char *src, int srclen,
|
|
char *dst, int dstlen )
|
|
{
|
|
WCHAR *bufW = NtCurrentTeb()->StaticUnicodeBuffer;
|
|
LPWSTR srcW, dstW;
|
|
INT ret = 0, srclenW, dstlenW;
|
|
UINT locale_cp = CP_ACP;
|
|
|
|
if (!src || !srclen || dstlen < 0)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
locale_cp = get_lcid_codepage( lcid, flags );
|
|
|
|
srclenW = MultiByteToWideChar( locale_cp, 0, src, srclen, bufW, 260 );
|
|
if (srclenW) srcW = bufW;
|
|
else
|
|
{
|
|
srclenW = MultiByteToWideChar( locale_cp, 0, src, srclen, NULL, 0 );
|
|
srcW = HeapAlloc( GetProcessHeap(), 0, srclenW * sizeof(WCHAR) );
|
|
if (!srcW)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
return 0;
|
|
}
|
|
MultiByteToWideChar( locale_cp, 0, src, srclen, srcW, srclenW );
|
|
}
|
|
|
|
if (flags & LCMAP_SORTKEY)
|
|
{
|
|
if (src == dst)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
goto done;
|
|
}
|
|
ret = LCMapStringW( lcid, flags, srcW, srclenW, (WCHAR *)dst, dstlen );
|
|
goto done;
|
|
}
|
|
|
|
if (flags & SORT_STRINGSORT)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
goto done;
|
|
}
|
|
|
|
dstlenW = LCMapStringW( lcid, flags, srcW, srclenW, NULL, 0 );
|
|
if (!dstlenW) goto done;
|
|
|
|
dstW = HeapAlloc( GetProcessHeap(), 0, dstlenW * sizeof(WCHAR) );
|
|
if (!dstW)
|
|
{
|
|
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
|
|
goto done;
|
|
}
|
|
LCMapStringW( lcid, flags, srcW, srclenW, dstW, dstlenW );
|
|
ret = WideCharToMultiByte( locale_cp, 0, dstW, dstlenW, dst, dstlen, NULL, NULL );
|
|
HeapFree( GetProcessHeap(), 0, dstW );
|
|
|
|
done:
|
|
if (srcW != bufW) HeapFree( GetProcessHeap(), 0, srcW );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* LCMapStringW (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH LCMapStringW( LCID lcid, DWORD flags, const WCHAR *src, int srclen,
|
|
WCHAR *dst, int dstlen )
|
|
{
|
|
const WCHAR *locale = LOCALE_NAME_USER_DEFAULT;
|
|
const NLS_LOCALE_LCID_INDEX *entry;
|
|
|
|
switch (lcid)
|
|
{
|
|
case LOCALE_NEUTRAL:
|
|
case LOCALE_USER_DEFAULT:
|
|
case LOCALE_SYSTEM_DEFAULT:
|
|
case LOCALE_CUSTOM_DEFAULT:
|
|
case LOCALE_CUSTOM_UNSPECIFIED:
|
|
case LOCALE_CUSTOM_UI_DEFAULT:
|
|
break;
|
|
default:
|
|
if (lcid == user_lcid || lcid == system_lcid) break;
|
|
if (!(entry = find_lcid_entry( lcid )))
|
|
{
|
|
WARN( "unknown locale %04lx\n", lcid );
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
locale = locale_strings + entry->name + 1;
|
|
break;
|
|
}
|
|
|
|
return LCMapStringEx( locale, flags, src, srclen, dst, dstlen, NULL, NULL, 0 );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* LocaleNameToLCID (kernelbase.@)
|
|
*/
|
|
LCID WINAPI DECLSPEC_HOTPATCH LocaleNameToLCID( const WCHAR *name, DWORD flags )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (!locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (!(flags & LOCALE_ALLOW_NEUTRAL_NAMES) && !locale->inotneutral)
|
|
lcid = locale->idefaultlanguage;
|
|
return lcid;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* MultiByteToWideChar (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH MultiByteToWideChar( UINT codepage, DWORD flags, const char *src, INT srclen,
|
|
WCHAR *dst, INT dstlen )
|
|
{
|
|
const CPTABLEINFO *info;
|
|
int ret;
|
|
|
|
if (!src || !srclen || (!dst && dstlen) || dstlen < 0)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (srclen < 0) srclen = strlen(src) + 1;
|
|
|
|
switch (codepage)
|
|
{
|
|
case CP_SYMBOL:
|
|
ret = mbstowcs_cpsymbol( flags, src, srclen, dst, dstlen );
|
|
break;
|
|
case CP_UTF7:
|
|
ret = mbstowcs_utf7( flags, src, srclen, dst, dstlen );
|
|
break;
|
|
case CP_UNIXCP:
|
|
codepage = unix_cp;
|
|
/* fall through */
|
|
default:
|
|
if (!(info = get_codepage_table( codepage )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (flags & ~(MB_PRECOMPOSED | MB_COMPOSITE | MB_USEGLYPHCHARS | MB_ERR_INVALID_CHARS))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (info->CodePage == CP_UTF8)
|
|
ret = mbstowcs_utf8( flags, src, srclen, dst, dstlen );
|
|
else
|
|
ret = mbstowcs_codepage( info, flags, src, srclen, dst, dstlen );
|
|
break;
|
|
}
|
|
TRACE( "cp %d %s -> %s, ret = %d\n", codepage, debugstr_an(src, srclen), debugstr_wn(dst, ret), ret );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* NormalizeString (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH NormalizeString(NORM_FORM form, const WCHAR *src, INT src_len,
|
|
WCHAR *dst, INT dst_len)
|
|
{
|
|
NTSTATUS status = RtlNormalizeString( form, src, src_len, dst, &dst_len );
|
|
|
|
switch (status)
|
|
{
|
|
case STATUS_OBJECT_NAME_NOT_FOUND:
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
case STATUS_BUFFER_TOO_SMALL:
|
|
case STATUS_NO_UNICODE_TRANSLATION:
|
|
dst_len = -dst_len;
|
|
break;
|
|
}
|
|
SetLastError( RtlNtStatusToDosError( status ));
|
|
return dst_len;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* ResolveLocaleName (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH ResolveLocaleName( LPCWSTR name, LPWSTR buffer, INT len )
|
|
{
|
|
LCID lcid;
|
|
UINT pos, datalen;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (!locale)
|
|
{
|
|
static const WCHAR valid[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
WCHAR *p, tmp[LOCALE_NAME_MAX_LENGTH];
|
|
|
|
if (wcsspn( name, valid ) < wcslen( name ))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
lstrcpynW( tmp, name, LOCALE_NAME_MAX_LENGTH );
|
|
while (!locale)
|
|
{
|
|
for (p = tmp + wcslen(tmp) - 1; p >= tmp; p--) if (*p == '-' || *p == '_') break;
|
|
if (p <= tmp) break;
|
|
*p = 0;
|
|
locale = get_locale_by_name( tmp, &lcid );
|
|
}
|
|
}
|
|
|
|
pos = locale ? (locale->inotneutral ? locale->sname : locale->ssortlocale) : 0;
|
|
datalen = locale_strings[pos] + 1;
|
|
|
|
if (!len) return datalen;
|
|
lstrcpynW( buffer, locale_strings + pos + 1, len );
|
|
if (datalen > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return datalen;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* SetLocaleInfoW (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SetLocaleInfoW( LCID lcid, LCTYPE lctype, const WCHAR *data )
|
|
{
|
|
WCHAR *str, tmp[80];
|
|
|
|
if (!data)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
|
|
switch (LOWORD(lctype))
|
|
{
|
|
case LOCALE_ICALENDARTYPE: return set_registry_entry( &entry_icalendartype, data );
|
|
case LOCALE_ICURRDIGITS: return set_registry_entry( &entry_icurrdigits, data );
|
|
case LOCALE_ICURRENCY: return set_registry_entry( &entry_icurrency, data );
|
|
case LOCALE_IDIGITS: return set_registry_entry( &entry_idigits, data );
|
|
case LOCALE_IDIGITSUBSTITUTION: return set_registry_entry( &entry_idigitsubstitution, data );
|
|
case LOCALE_IFIRSTDAYOFWEEK: return set_registry_entry( &entry_ifirstdayofweek, data );
|
|
case LOCALE_IFIRSTWEEKOFYEAR: return set_registry_entry( &entry_ifirstweekofyear, data );
|
|
case LOCALE_ILZERO: return set_registry_entry( &entry_ilzero, data );
|
|
case LOCALE_IMEASURE: return set_registry_entry( &entry_imeasure, data );
|
|
case LOCALE_INEGCURR: return set_registry_entry( &entry_inegcurr, data );
|
|
case LOCALE_INEGNUMBER: return set_registry_entry( &entry_inegnumber, data );
|
|
case LOCALE_IPAPERSIZE: return set_registry_entry( &entry_ipapersize, data );
|
|
case LOCALE_S1159: return set_registry_entry( &entry_s1159, data );
|
|
case LOCALE_S2359: return set_registry_entry( &entry_s2359, data );
|
|
case LOCALE_SCURRENCY: return set_registry_entry( &entry_scurrency, data );
|
|
case LOCALE_SDECIMAL: return set_registry_entry( &entry_sdecimal, data );
|
|
case LOCALE_SGROUPING: return set_registry_entry( &entry_sgrouping, data );
|
|
case LOCALE_SLIST: return set_registry_entry( &entry_slist, data );
|
|
case LOCALE_SLONGDATE: return set_registry_entry( &entry_slongdate, data );
|
|
case LOCALE_SMONDECIMALSEP: return set_registry_entry( &entry_smondecimalsep, data );
|
|
case LOCALE_SMONGROUPING: return set_registry_entry( &entry_smongrouping, data );
|
|
case LOCALE_SMONTHOUSANDSEP: return set_registry_entry( &entry_smonthousandsep, data );
|
|
case LOCALE_SNATIVEDIGITS: return set_registry_entry( &entry_snativedigits, data );
|
|
case LOCALE_SNEGATIVESIGN: return set_registry_entry( &entry_snegativesign, data );
|
|
case LOCALE_SPOSITIVESIGN: return set_registry_entry( &entry_spositivesign, data );
|
|
case LOCALE_SSHORTTIME: return set_registry_entry( &entry_sshorttime, data );
|
|
case LOCALE_STHOUSAND: return set_registry_entry( &entry_sthousand, data );
|
|
case LOCALE_SYEARMONTH: return set_registry_entry( &entry_syearmonth, data );
|
|
|
|
case LOCALE_SDATE:
|
|
if (!get_locale_info( user_locale, user_lcid, LOCALE_SSHORTDATE, tmp, ARRAY_SIZE(tmp) )) break;
|
|
data = locale_replace_separator( tmp, data );
|
|
/* fall through */
|
|
case LOCALE_SSHORTDATE:
|
|
if (!set_registry_entry( &entry_sshortdate, data )) return FALSE;
|
|
update_registry_value( LOCALE_IDATE, NULL, L"iDate" );
|
|
update_registry_value( LOCALE_SDATE, NULL, L"sDate" );
|
|
return TRUE;
|
|
|
|
case LOCALE_STIME:
|
|
if (!get_locale_info( user_locale, user_lcid, LOCALE_STIMEFORMAT, tmp, ARRAY_SIZE(tmp) )) break;
|
|
data = locale_replace_separator( tmp, data );
|
|
/* fall through */
|
|
case LOCALE_STIMEFORMAT:
|
|
if (!set_registry_entry( &entry_stimeformat, data )) return FALSE;
|
|
update_registry_value( LOCALE_ITIME, NULL, L"iTime" );
|
|
update_registry_value( LOCALE_ITIMEMARKPOSN, NULL, L"iTimePrefix" );
|
|
update_registry_value( LOCALE_ITLZERO, NULL, L"iTLZero" );
|
|
update_registry_value( LOCALE_STIME, NULL, L"sTime" );
|
|
return TRUE;
|
|
|
|
case LOCALE_ITIME:
|
|
if (!get_locale_info( user_locale, user_lcid, LOCALE_STIMEFORMAT, tmp, ARRAY_SIZE(tmp) )) break;
|
|
if (!(str = find_format( tmp, L"Hh" ))) break;
|
|
while (*str == 'h' || *str == 'H') *str++ = (*data == '0' ? 'h' : 'H');
|
|
if (!set_registry_entry( &entry_stimeformat, tmp )) break;
|
|
update_registry_value( LOCALE_ITIME, NULL, L"iTime" );
|
|
return TRUE;
|
|
|
|
case LOCALE_SINTLSYMBOL:
|
|
if (!set_registry_entry( &entry_sintlsymbol, data )) return FALSE;
|
|
/* if restoring the original value, restore the original LOCALE_SCURRENCY as well */
|
|
if (!wcsicmp( data, locale_strings + user_locale->sintlsymbol + 1 ))
|
|
data = locale_strings + user_locale->scurrency + 1;
|
|
set_registry_entry( &entry_scurrency, data );
|
|
return TRUE;
|
|
}
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetCalendarInfoW (kernelbase.@)
|
|
*/
|
|
INT WINAPI /* DECLSPEC_HOTPATCH */ SetCalendarInfoW( LCID lcid, CALID calendar, CALTYPE type, const WCHAR *data )
|
|
{
|
|
FIXME( "(%08lx,%08lx,%08lx,%s): stub\n", lcid, calendar, type, debugstr_w(data) );
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetProcessPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SetProcessPreferredUILanguages( DWORD flags, PCZZWSTR buffer, ULONG *count )
|
|
{
|
|
return set_ntstatus( RtlSetProcessPreferredUILanguages( flags, buffer, count ));
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetThreadPreferredUILanguages (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SetThreadPreferredUILanguages( DWORD flags, PCZZWSTR buffer, ULONG *count )
|
|
{
|
|
return set_ntstatus( RtlSetThreadPreferredUILanguages( flags, buffer, count ));
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetTimeZoneInformation (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SetTimeZoneInformation( const TIME_ZONE_INFORMATION *info )
|
|
{
|
|
return set_ntstatus( RtlSetTimeZoneInformation( (const RTL_TIME_ZONE_INFORMATION *)info ));
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* SetUserGeoID (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SetUserGeoID( GEOID id )
|
|
{
|
|
const struct geo_id *geo = find_geo_id_entry( id );
|
|
WCHAR bufferW[10];
|
|
HKEY hkey;
|
|
|
|
if (!geo)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
if (!RegCreateKeyExW( intl_key, L"Geo", 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, NULL ))
|
|
{
|
|
const WCHAR *name = geo->class == GEOCLASS_NATION ? L"Nation" : L"Region";
|
|
swprintf( bufferW, ARRAY_SIZE(bufferW), L"%u", geo->id );
|
|
RegSetValueExW( hkey, name, 0, REG_SZ, (BYTE *)bufferW, (lstrlenW(bufferW) + 1) * sizeof(WCHAR) );
|
|
|
|
if (geo->class == GEOCLASS_NATION || wcscmp( geo->iso2, L"XX" ))
|
|
lstrcpyW( bufferW, geo->iso2 );
|
|
else
|
|
swprintf( bufferW, ARRAY_SIZE(bufferW), L"%03u", geo->uncode );
|
|
RegSetValueExW( hkey, L"Name", 0, REG_SZ, (BYTE *)bufferW, (lstrlenW(bufferW) + 1) * sizeof(WCHAR) );
|
|
RegCloseKey( hkey );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SystemTimeToTzSpecificLocalTime (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH SystemTimeToTzSpecificLocalTime( const TIME_ZONE_INFORMATION *info,
|
|
const SYSTEMTIME *system,
|
|
SYSTEMTIME *local )
|
|
{
|
|
TIME_ZONE_INFORMATION tzinfo;
|
|
LARGE_INTEGER ft;
|
|
|
|
if (!info)
|
|
{
|
|
RtlQueryTimeZoneInformation( (RTL_TIME_ZONE_INFORMATION *)&tzinfo );
|
|
info = &tzinfo;
|
|
}
|
|
|
|
if (!SystemTimeToFileTime( system, (FILETIME *)&ft )) return FALSE;
|
|
switch (get_timezone_id( info, ft, FALSE ))
|
|
{
|
|
case TIME_ZONE_ID_UNKNOWN:
|
|
ft.QuadPart -= info->Bias * (LONGLONG)600000000;
|
|
break;
|
|
case TIME_ZONE_ID_STANDARD:
|
|
ft.QuadPart -= (info->Bias + info->StandardBias) * (LONGLONG)600000000;
|
|
break;
|
|
case TIME_ZONE_ID_DAYLIGHT:
|
|
ft.QuadPart -= (info->Bias + info->DaylightBias) * (LONGLONG)600000000;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return FileTimeToSystemTime( (FILETIME *)&ft, local );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* TzSpecificLocalTimeToSystemTime (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI DECLSPEC_HOTPATCH TzSpecificLocalTimeToSystemTime( const TIME_ZONE_INFORMATION *info,
|
|
const SYSTEMTIME *local,
|
|
SYSTEMTIME *system )
|
|
{
|
|
TIME_ZONE_INFORMATION tzinfo;
|
|
LARGE_INTEGER ft;
|
|
|
|
if (!info)
|
|
{
|
|
RtlQueryTimeZoneInformation( (RTL_TIME_ZONE_INFORMATION *)&tzinfo );
|
|
info = &tzinfo;
|
|
}
|
|
|
|
if (!SystemTimeToFileTime( local, (FILETIME *)&ft )) return FALSE;
|
|
switch (get_timezone_id( info, ft, TRUE ))
|
|
{
|
|
case TIME_ZONE_ID_UNKNOWN:
|
|
ft.QuadPart += info->Bias * (LONGLONG)600000000;
|
|
break;
|
|
case TIME_ZONE_ID_STANDARD:
|
|
ft.QuadPart += (info->Bias + info->StandardBias) * (LONGLONG)600000000;
|
|
break;
|
|
case TIME_ZONE_ID_DAYLIGHT:
|
|
ft.QuadPart += (info->Bias + info->DaylightBias) * (LONGLONG)600000000;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return FileTimeToSystemTime( (FILETIME *)&ft, system );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* VerLanguageNameA (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH VerLanguageNameA( DWORD lang, LPSTR buffer, DWORD size )
|
|
{
|
|
return GetLocaleInfoA( MAKELCID( lang, SORT_DEFAULT ), LOCALE_SENGLANGUAGE, buffer, size );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* VerLanguageNameW (kernelbase.@)
|
|
*/
|
|
DWORD WINAPI DECLSPEC_HOTPATCH VerLanguageNameW( DWORD lang, LPWSTR buffer, DWORD size )
|
|
{
|
|
return GetLocaleInfoW( MAKELCID( lang, SORT_DEFAULT ), LOCALE_SENGLANGUAGE, buffer, size );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* WideCharToMultiByte (kernelbase.@)
|
|
*/
|
|
INT WINAPI DECLSPEC_HOTPATCH WideCharToMultiByte( UINT codepage, DWORD flags, LPCWSTR src, INT srclen,
|
|
LPSTR dst, INT dstlen, LPCSTR defchar, BOOL *used )
|
|
{
|
|
const CPTABLEINFO *info;
|
|
int ret;
|
|
|
|
if (!src || !srclen || (!dst && dstlen) || dstlen < 0)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (srclen < 0) srclen = lstrlenW(src) + 1;
|
|
|
|
switch (codepage)
|
|
{
|
|
case CP_SYMBOL:
|
|
ret = wcstombs_cpsymbol( flags, src, srclen, dst, dstlen, defchar, used );
|
|
break;
|
|
case CP_UTF7:
|
|
ret = wcstombs_utf7( flags, src, srclen, dst, dstlen, defchar, used );
|
|
break;
|
|
case CP_UNIXCP:
|
|
codepage = unix_cp;
|
|
/* fall through */
|
|
default:
|
|
if (!(info = get_codepage_table( codepage )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (flags & ~(WC_DISCARDNS | WC_SEPCHARS | WC_DEFAULTCHAR | WC_ERR_INVALID_CHARS |
|
|
WC_COMPOSITECHECK | WC_NO_BEST_FIT_CHARS))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
if (info->CodePage == CP_UTF8)
|
|
ret = wcstombs_utf8( flags, src, srclen, dst, dstlen, defchar, used );
|
|
else
|
|
ret = wcstombs_codepage( info, flags, src, srclen, dst, dstlen, defchar, used );
|
|
break;
|
|
}
|
|
TRACE( "cp %d %s -> %s, ret = %d\n", codepage, debugstr_wn(src, srclen), debugstr_an(dst, ret), ret );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetUserDefaultGeoName (kernelbase.@)
|
|
*/
|
|
INT WINAPI GetUserDefaultGeoName(LPWSTR geo_name, int count)
|
|
{
|
|
WCHAR buffer[32];
|
|
LSTATUS status;
|
|
DWORD size;
|
|
HKEY key;
|
|
|
|
TRACE( "geo_name %p, count %d.\n", geo_name, count );
|
|
|
|
if (count && !geo_name)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (!(status = RegOpenKeyExW( intl_key, L"Geo", 0, KEY_ALL_ACCESS, &key )))
|
|
{
|
|
size = sizeof(buffer);
|
|
status = RegQueryValueExW( key, L"Name", NULL, NULL, (BYTE *)buffer, &size );
|
|
RegCloseKey( key );
|
|
}
|
|
if (status)
|
|
{
|
|
const struct geo_id *geo = find_geo_id_entry( GetUserGeoID( GEOCLASS_NATION ));
|
|
if (geo && geo->id != 39070)
|
|
lstrcpyW( buffer, geo->iso2 );
|
|
else
|
|
lstrcpyW( buffer, L"001" );
|
|
}
|
|
size = lstrlenW( buffer ) + 1;
|
|
if (count < size)
|
|
{
|
|
if (!count)
|
|
return size;
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
lstrcpyW( geo_name, buffer );
|
|
return size;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetUserDefaultGeoName (kernelbase.@)
|
|
*/
|
|
BOOL WINAPI SetUserGeoName(PWSTR geo_name)
|
|
{
|
|
const struct geo_id *geo;
|
|
|
|
TRACE( "geo_name %s.\n", debugstr_w( geo_name ));
|
|
|
|
if (!geo_name || !(geo = find_geo_name_entry( geo_name )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return FALSE;
|
|
}
|
|
return SetUserGeoID( geo->id );
|
|
}
|
|
|
|
|
|
static void grouping_to_string( UINT grouping, WCHAR *buffer )
|
|
{
|
|
UINT last_digit = grouping % 10;
|
|
WCHAR tmp[10], *p = tmp;
|
|
|
|
/* The string is confusingly different when it comes to repetitions (trailing zeros). For a string,
|
|
* a 0 signals that the format needs to be repeated, which is the opposite of the grouping integer. */
|
|
if (last_digit == 0)
|
|
{
|
|
grouping /= 10;
|
|
|
|
/* Special case: two or more trailing zeros result in zero-sided groupings, with no repeats */
|
|
if (grouping % 10 == 0)
|
|
last_digit = ~0;
|
|
}
|
|
|
|
while (grouping)
|
|
{
|
|
*p++ = '0' + grouping % 10;
|
|
grouping /= 10;
|
|
}
|
|
while (p > tmp)
|
|
{
|
|
*buffer++ = *(--p);
|
|
if (p > tmp) *buffer++ = ';';
|
|
}
|
|
if (last_digit != 0)
|
|
{
|
|
*buffer++ = ';';
|
|
*buffer++ = '0';
|
|
if (last_digit == ~0)
|
|
{
|
|
/* Add another trailing zero due to the weird way trailing zeros work in grouping string */
|
|
*buffer++ = ';';
|
|
*buffer++ = '0';
|
|
}
|
|
}
|
|
*buffer = 0;
|
|
}
|
|
|
|
|
|
static WCHAR *prepend_str( WCHAR *end, const WCHAR *str )
|
|
{
|
|
UINT len = wcslen( str );
|
|
return memcpy( end - len, str, len * sizeof(WCHAR) );
|
|
}
|
|
|
|
|
|
/* format a positive number with decimal part; helper for get_number_format */
|
|
static WCHAR *format_number( WCHAR *end, const WCHAR *value, const WCHAR *decimal_sep,
|
|
const WCHAR *thousand_sep, const WCHAR *grouping, UINT digits, BOOL lzero )
|
|
{
|
|
BOOL round = FALSE, repeat = FALSE;
|
|
UINT i, len = 0, prev = ~0;
|
|
const WCHAR *frac = NULL;
|
|
|
|
*(--end) = 0;
|
|
|
|
for (i = 0; value[i]; i++)
|
|
{
|
|
if (value[i] >= '0' && value[i] <= '9') continue;
|
|
if (value[i] != '.') return NULL;
|
|
if (frac) return NULL;
|
|
frac = value + i + 1;
|
|
}
|
|
|
|
/* format fractional part */
|
|
|
|
len = frac ? wcslen( frac ) : 0;
|
|
|
|
if (len > digits)
|
|
{
|
|
round = frac[digits] >= '5';
|
|
len = digits;
|
|
}
|
|
while (digits > len)
|
|
{
|
|
(*--end) = '0';
|
|
digits--;
|
|
}
|
|
while (len)
|
|
{
|
|
WCHAR ch = frac[--len];
|
|
if (round)
|
|
{
|
|
if (ch != '9')
|
|
{
|
|
ch++;
|
|
round = FALSE;
|
|
}
|
|
else ch = '0';
|
|
}
|
|
*(--end) = ch;
|
|
}
|
|
if (*end) end = prepend_str( end, decimal_sep );
|
|
|
|
/* format integer part */
|
|
|
|
len = frac ? frac - value - 1 : wcslen( value );
|
|
|
|
while (len && *value == '0')
|
|
{
|
|
value++;
|
|
len--;
|
|
}
|
|
if (len) lzero = FALSE;
|
|
|
|
/* leading 0s are ignored */
|
|
while (grouping[0] == '0' && grouping[1] == ';')
|
|
grouping += 2;
|
|
|
|
while (len)
|
|
{
|
|
UINT limit = prev;
|
|
|
|
if (!repeat)
|
|
{
|
|
limit = *grouping - '0';
|
|
if (grouping[1] == ';')
|
|
{
|
|
grouping += 2;
|
|
if (limit)
|
|
prev = limit;
|
|
else
|
|
{
|
|
/* Trailing 0;0 is a special case */
|
|
prev = ~0;
|
|
if (grouping[0] == '0' && grouping[1] != ';')
|
|
{
|
|
repeat = TRUE;
|
|
limit = prev;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
repeat = TRUE;
|
|
if (!limit)
|
|
limit = prev;
|
|
else
|
|
prev = ~0;
|
|
}
|
|
}
|
|
|
|
while (len && limit--)
|
|
{
|
|
WCHAR ch = value[--len];
|
|
if (round)
|
|
{
|
|
if (ch != '9')
|
|
{
|
|
ch++;
|
|
round = FALSE;
|
|
}
|
|
else ch = '0';
|
|
}
|
|
*(--end) = ch;
|
|
}
|
|
if (len) end = prepend_str( end, thousand_sep );
|
|
}
|
|
if (round) *(--end) = '1';
|
|
else if (lzero) *(--end) = '0';
|
|
return end;
|
|
}
|
|
|
|
|
|
static int get_number_format( const NLS_LOCALE_DATA *locale, DWORD flags, const WCHAR *value,
|
|
const NUMBERFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
WCHAR *num, fmt_decimal[4], fmt_thousand[4], fmt_neg[5], grouping[24], output[256];
|
|
const WCHAR *decimal_sep = fmt_decimal, *thousand_sep = fmt_thousand;
|
|
DWORD digits, lzero, order;
|
|
int ret = 0;
|
|
BOOL negative = (*value == '-');
|
|
|
|
flags &= LOCALE_NOUSEROVERRIDE;
|
|
|
|
if (!format)
|
|
{
|
|
get_locale_info( locale, 0, LOCALE_SGROUPING | flags, grouping, ARRAY_SIZE(grouping) );
|
|
get_locale_info( locale, 0, LOCALE_SDECIMAL | flags, fmt_decimal, ARRAY_SIZE(fmt_decimal) );
|
|
get_locale_info( locale, 0, LOCALE_STHOUSAND | flags, fmt_thousand, ARRAY_SIZE(fmt_thousand) );
|
|
get_locale_info( locale, 0, LOCALE_IDIGITS | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&digits, sizeof(DWORD)/sizeof(WCHAR) );
|
|
get_locale_info( locale, 0, LOCALE_ILZERO | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&lzero, sizeof(DWORD)/sizeof(WCHAR) );
|
|
get_locale_info( locale, 0, LOCALE_INEGNUMBER | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&order, sizeof(DWORD)/sizeof(WCHAR) );
|
|
}
|
|
else
|
|
{
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
decimal_sep = format->lpDecimalSep;
|
|
thousand_sep = format->lpThousandSep;
|
|
grouping_to_string( format->Grouping, grouping );
|
|
digits = format->NumDigits;
|
|
lzero = format->LeadingZero;
|
|
order = format->NegativeOrder;
|
|
if (!decimal_sep || !thousand_sep)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (negative)
|
|
{
|
|
value++;
|
|
get_locale_info( locale, 0, LOCALE_SNEGATIVESIGN | flags, fmt_neg, ARRAY_SIZE(fmt_neg) );
|
|
}
|
|
|
|
if (!(num = format_number( output + ARRAY_SIZE(output) - 6, value,
|
|
decimal_sep, thousand_sep, grouping, digits, lzero )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (negative)
|
|
{
|
|
switch (order)
|
|
{
|
|
case 0: /* (1.1) */
|
|
num = prepend_str( num, L"(" );
|
|
wcscat( num, L")" );
|
|
break;
|
|
case 2: /* - 1.1 */
|
|
num = prepend_str( num, L" " );
|
|
/* fall through */
|
|
case 1: /* -1.1 */
|
|
num = prepend_str( num, fmt_neg );
|
|
break;
|
|
case 4: /* 1.1 - */
|
|
wcscat( num, L" " );
|
|
/* fall through */
|
|
case 3: /* 1.1- */
|
|
wcscat( num, fmt_neg );
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ret = wcslen( num ) + 1;
|
|
if (!len) return ret;
|
|
lstrcpynW( buffer, num, len );
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int get_currency_format( const NLS_LOCALE_DATA *locale, DWORD flags, const WCHAR *value,
|
|
const CURRENCYFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
WCHAR *num, fmt_decimal[4], fmt_thousand[4], fmt_symbol[13], fmt_neg[5], grouping[20], output[256];
|
|
const WCHAR *decimal_sep = fmt_decimal, *thousand_sep = fmt_thousand, *symbol = fmt_symbol;
|
|
DWORD digits, lzero, pos_order, neg_order;
|
|
int ret = 0;
|
|
BOOL negative = (*value == '-');
|
|
|
|
flags &= LOCALE_NOUSEROVERRIDE;
|
|
|
|
if (!format)
|
|
{
|
|
get_locale_info( locale, 0, LOCALE_SCURRENCY | flags, fmt_symbol, ARRAY_SIZE(fmt_symbol) );
|
|
get_locale_info( locale, 0, LOCALE_SMONGROUPING | flags, grouping, ARRAY_SIZE(grouping) );
|
|
get_locale_info( locale, 0, LOCALE_SMONDECIMALSEP | flags, fmt_decimal, ARRAY_SIZE(fmt_decimal) );
|
|
get_locale_info( locale, 0, LOCALE_SMONTHOUSANDSEP | flags, fmt_thousand, ARRAY_SIZE(fmt_thousand) );
|
|
get_locale_info( locale, 0, LOCALE_ICURRDIGITS | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&digits, sizeof(DWORD)/sizeof(WCHAR) );
|
|
get_locale_info( locale, 0, LOCALE_ILZERO | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&lzero, sizeof(DWORD)/sizeof(WCHAR) );
|
|
get_locale_info( locale, 0, LOCALE_ICURRENCY | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&pos_order, sizeof(DWORD)/sizeof(WCHAR) );
|
|
get_locale_info( locale, 0, LOCALE_INEGCURR | LOCALE_RETURN_NUMBER | flags,
|
|
(WCHAR *)&neg_order, sizeof(DWORD)/sizeof(WCHAR) );
|
|
}
|
|
else
|
|
{
|
|
if (flags)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
decimal_sep = format->lpDecimalSep;
|
|
thousand_sep = format->lpThousandSep;
|
|
symbol = format->lpCurrencySymbol;
|
|
grouping_to_string( format->Grouping, grouping );
|
|
digits = format->NumDigits;
|
|
lzero = format->LeadingZero;
|
|
pos_order = format->PositiveOrder;
|
|
neg_order = format->NegativeOrder;
|
|
if (!decimal_sep || !thousand_sep || !symbol)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (negative)
|
|
{
|
|
value++;
|
|
get_locale_info( locale, 0, LOCALE_SNEGATIVESIGN | flags, fmt_neg, ARRAY_SIZE(fmt_neg) );
|
|
}
|
|
|
|
if (!(num = format_number( output + ARRAY_SIZE(output) - 20, value,
|
|
decimal_sep, thousand_sep, grouping, digits, lzero )))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
if (negative)
|
|
{
|
|
switch (neg_order)
|
|
{
|
|
case 14: /* ($ 1.1) */
|
|
num = prepend_str( num, L" " );
|
|
/* fall through */
|
|
case 0: /* ($1.1) */
|
|
num = prepend_str( num, symbol );
|
|
num = prepend_str( num, L"(" );
|
|
wcscat( num, L")" );
|
|
break;
|
|
case 9: /* -$ 1.1 */
|
|
num = prepend_str( num, L" " );
|
|
/* fall through */
|
|
case 1: /* -$1.1 */
|
|
num = prepend_str( num, symbol );
|
|
num = prepend_str( num, fmt_neg );
|
|
break;
|
|
case 2: /* $-1.1 */
|
|
num = prepend_str( num, fmt_neg );
|
|
num = prepend_str( num, symbol );
|
|
break;
|
|
case 11: /* $ 1.1- */
|
|
num = prepend_str( num, L" " );
|
|
/* fall through */
|
|
case 3: /* $1.1- */
|
|
num = prepend_str( num, symbol );
|
|
wcscat( num, fmt_neg );
|
|
break;
|
|
case 15: /* (1.1 $) */
|
|
wcscat( num, L" " );
|
|
/* fall through */
|
|
case 4: /* (1.1$) */
|
|
wcscat( num, symbol );
|
|
num = prepend_str( num, L"(" );
|
|
wcscat( num, L")" );
|
|
break;
|
|
case 8: /* -1.1 $ */
|
|
wcscat( num, L" " );
|
|
/* fall through */
|
|
case 5: /* -1.1$ */
|
|
num = prepend_str( num, fmt_neg );
|
|
wcscat( num, symbol );
|
|
break;
|
|
case 6: /* 1.1-$ */
|
|
wcscat( num, fmt_neg );
|
|
wcscat( num, symbol );
|
|
break;
|
|
case 10: /* 1.1 $- */
|
|
wcscat( num, L" " );
|
|
/* fall through */
|
|
case 7: /* 1.1$- */
|
|
wcscat( num, symbol );
|
|
wcscat( num, fmt_neg );
|
|
break;
|
|
case 12: /* $ -1.1 */
|
|
num = prepend_str( num, fmt_neg );
|
|
num = prepend_str( num, L" " );
|
|
num = prepend_str( num, symbol );
|
|
break;
|
|
case 13: /* 1.1- $ */
|
|
wcscat( num, fmt_neg );
|
|
wcscat( num, L" " );
|
|
wcscat( num, symbol );
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (pos_order)
|
|
{
|
|
case 2: /* $ 1.1 */
|
|
num = prepend_str( num, L" " );
|
|
/* fall through */
|
|
case 0: /* $1.1 */
|
|
num = prepend_str( num, symbol );
|
|
break;
|
|
case 3: /* 1.1 $ */
|
|
wcscat( num, L" " );
|
|
/* fall through */
|
|
case 1: /* 1.1$ */
|
|
wcscat( num, symbol );
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ret = wcslen( num ) + 1;
|
|
if (!len) return ret;
|
|
lstrcpynW( buffer, num, len );
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* get the length of a date/time formatting pattern */
|
|
static int get_pattern_len( const WCHAR *pattern, const WCHAR *accept )
|
|
{
|
|
int i;
|
|
|
|
if (*pattern == '\'')
|
|
{
|
|
for (i = 1; pattern[i]; i++)
|
|
{
|
|
if (pattern[i] != '\'') continue;
|
|
if (pattern[++i] != '\'') return i;
|
|
}
|
|
return i;
|
|
}
|
|
if (!wcschr( accept, *pattern )) return 1;
|
|
for (i = 1; pattern[i]; i++) if (pattern[i] != pattern[0]) break;
|
|
return i;
|
|
}
|
|
|
|
|
|
static int get_date_format( const NLS_LOCALE_DATA *locale, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len )
|
|
{
|
|
DWORD override = flags & LOCALE_NOUSEROVERRIDE;
|
|
DWORD genitive = 0;
|
|
WCHAR *p, fmt[80], output[256];
|
|
SYSTEMTIME time;
|
|
int ret, val, count, i;
|
|
|
|
if (!format)
|
|
{
|
|
if (flags & DATE_USE_ALT_CALENDAR) FIXME( "alt calendar not supported\n" );
|
|
switch (flags & (DATE_SHORTDATE | DATE_LONGDATE | DATE_YEARMONTH | DATE_MONTHDAY))
|
|
{
|
|
case 0:
|
|
case DATE_SHORTDATE:
|
|
get_locale_info( locale, 0, LOCALE_SSHORTDATE | override, fmt, ARRAY_SIZE(fmt) );
|
|
break;
|
|
case DATE_LONGDATE:
|
|
get_locale_info( locale, 0, LOCALE_SLONGDATE | override, fmt, ARRAY_SIZE(fmt) );
|
|
break;
|
|
case DATE_YEARMONTH:
|
|
get_locale_info( locale, 0, LOCALE_SYEARMONTH | override, fmt, ARRAY_SIZE(fmt) );
|
|
break;
|
|
case DATE_MONTHDAY:
|
|
get_locale_info( locale, 0, LOCALE_SMONTHDAY | override, fmt, ARRAY_SIZE(fmt) );
|
|
break;
|
|
default:
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
format = fmt;
|
|
}
|
|
else if (override || (flags & (DATE_SHORTDATE | DATE_LONGDATE | DATE_YEARMONTH | DATE_MONTHDAY)))
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (systime)
|
|
{
|
|
FILETIME ft;
|
|
|
|
time = *systime;
|
|
time.wHour = time.wMinute = time.wSecond = time.wMilliseconds = 0;
|
|
if (!SystemTimeToFileTime( &time, &ft ) || !FileTimeToSystemTime( &ft, &time )) return 0;
|
|
}
|
|
else GetLocalTime( &time );
|
|
|
|
for (p = output; *format; format += count)
|
|
{
|
|
count = get_pattern_len( format, L"yMd" );
|
|
|
|
switch (*format)
|
|
{
|
|
case '\'':
|
|
for (i = 1; i < count; i++)
|
|
{
|
|
if (format[i] == '\'') i++;
|
|
if (i < count) *p++ = format[i];
|
|
}
|
|
break;
|
|
|
|
case 'y':
|
|
p += swprintf( p, output + ARRAY_SIZE(output) - p, L"%02u",
|
|
(count <= 2) ? time.wYear % 100 : time.wYear );
|
|
break;
|
|
|
|
case 'M':
|
|
if (count <= 2)
|
|
{
|
|
p += swprintf( p, output + ARRAY_SIZE(output) - p, L"%.*u", count, time.wMonth );
|
|
break;
|
|
}
|
|
val = (count == 3 ? LOCALE_SABBREVMONTHNAME1 : LOCALE_SMONTHNAME1) + time.wMonth - 1;
|
|
if (!genitive)
|
|
{
|
|
for (i = count; format[i]; i += get_pattern_len( format + i, L"yMd" ))
|
|
{
|
|
if (format[i] != 'd') continue;
|
|
if (format[i + 1] != 'd' || format[i + 2] != 'd')
|
|
genitive = LOCALE_RETURN_GENITIVE_NAMES;
|
|
break;
|
|
}
|
|
}
|
|
p += get_locale_info( locale, 0, val | override | genitive,
|
|
p, output + ARRAY_SIZE(output) - p ) - 1;
|
|
break;
|
|
|
|
case 'd':
|
|
if (count <= 2)
|
|
{
|
|
genitive = LOCALE_RETURN_GENITIVE_NAMES;
|
|
p += swprintf( p, output + ARRAY_SIZE(output) - p, L"%.*u", count, time.wDay );
|
|
break;
|
|
}
|
|
genitive = 0;
|
|
val = (count == 3 ? LOCALE_SABBREVDAYNAME1 : LOCALE_SDAYNAME1) + (time.wDayOfWeek + 6) % 7;
|
|
p += get_locale_info( locale, 0, val | override, p, output + ARRAY_SIZE(output) - p ) - 1;
|
|
break;
|
|
|
|
case 'g':
|
|
p += locale_return_string( count >= 2 ? locale->serastring : locale->sabbreverastring,
|
|
override, p, output + ARRAY_SIZE(output) - p ) - 1;
|
|
break;
|
|
|
|
default:
|
|
*p++ = *format;
|
|
break;
|
|
}
|
|
}
|
|
*p++ = 0;
|
|
ret = p - output;
|
|
|
|
if (!len) return ret;
|
|
lstrcpynW( buffer, output, len );
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int get_time_format( const NLS_LOCALE_DATA *locale, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len )
|
|
{
|
|
DWORD override = flags & LOCALE_NOUSEROVERRIDE;
|
|
WCHAR *p, *last, fmt[80], output[256];
|
|
SYSTEMTIME time;
|
|
int i, ret, val, count;
|
|
BOOL skip = FALSE;
|
|
|
|
if (!format)
|
|
{
|
|
get_locale_info( locale, 0, LOCALE_STIMEFORMAT | override, fmt, ARRAY_SIZE(fmt) );
|
|
format = fmt;
|
|
}
|
|
else if (override)
|
|
{
|
|
SetLastError( ERROR_INVALID_FLAGS );
|
|
return 0;
|
|
}
|
|
|
|
if (systime)
|
|
{
|
|
time = *systime;
|
|
if (time.wMilliseconds > 999 || time.wSecond > 59 || time.wMinute > 59 || time.wHour > 23)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
}
|
|
else GetLocalTime( &time );
|
|
|
|
for (p = last = output; *format; format += count)
|
|
{
|
|
count = get_pattern_len( format, L"Hhmst" );
|
|
|
|
switch (*format)
|
|
{
|
|
case '\'':
|
|
for (i = 1; i < count; i++)
|
|
{
|
|
if (format[i] == '\'') i++;
|
|
if (!skip && i < count) *p++ = format[i];
|
|
}
|
|
continue;
|
|
|
|
case 'H':
|
|
val = time.wHour;
|
|
break;
|
|
|
|
case 'h':
|
|
val = time.wHour;
|
|
if (!(flags & TIME_FORCE24HOURFORMAT))
|
|
{
|
|
val %= 12;
|
|
if (!val) val = 12;
|
|
}
|
|
break;
|
|
|
|
case 'm':
|
|
if (flags & TIME_NOMINUTESORSECONDS)
|
|
{
|
|
p = last;
|
|
skip = TRUE;
|
|
continue;
|
|
}
|
|
val = time.wMinute;
|
|
break;
|
|
|
|
case 's':
|
|
if (flags & (TIME_NOMINUTESORSECONDS | TIME_NOSECONDS))
|
|
{
|
|
p = last;
|
|
skip = TRUE;
|
|
continue;
|
|
}
|
|
val = time.wSecond;
|
|
break;
|
|
|
|
case 't':
|
|
if (flags & TIME_NOTIMEMARKER)
|
|
{
|
|
p = last;
|
|
skip = TRUE;
|
|
continue;
|
|
}
|
|
val = time.wHour < 12 ? LOCALE_S1159 : LOCALE_S2359;
|
|
ret = get_locale_info( locale, 0, val | override, p, output + ARRAY_SIZE(output) - p );
|
|
p += (count > 1) ? ret - 1 : 1;
|
|
skip = FALSE;
|
|
continue;
|
|
|
|
default:
|
|
if (!skip || *format == ' ') *p++ = *format;
|
|
continue;
|
|
}
|
|
|
|
p += swprintf( p, output + ARRAY_SIZE(output) - p, L"%.*u", min( 2, count ), val );
|
|
last = p;
|
|
skip = FALSE;
|
|
}
|
|
*p++ = 0;
|
|
ret = p - output;
|
|
|
|
if (!len) return ret;
|
|
lstrcpynW( buffer, output, len );
|
|
if (ret > len)
|
|
{
|
|
SetLastError( ERROR_INSUFFICIENT_BUFFER );
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* GetNumberFormatW (kernelbase.@)
|
|
*/
|
|
int WINAPI GetNumberFormatW( LCID lcid, DWORD flags, const WCHAR *value,
|
|
const NUMBERFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
|
|
if (len < 0 || (len && !buffer) || !value || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%04lx,%lx,%s,%p,%p,%d)\n", lcid, flags, debugstr_w(value), format, buffer, len );
|
|
return get_number_format( locale, flags, value, format, buffer, len );
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* GetNumberFormatEx (kernelbase.@)
|
|
*/
|
|
int WINAPI GetNumberFormatEx( const WCHAR *name, DWORD flags, const WCHAR *value,
|
|
const NUMBERFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (len < 0 || (len && !buffer) || !value || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%s,%lx,%s,%p,%p,%d)\n", debugstr_w(name), flags, debugstr_w(value), format, buffer, len );
|
|
return get_number_format( locale, flags, value, format, buffer, len );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCurrencyFormatW (kernelbase.@)
|
|
*/
|
|
int WINAPI GetCurrencyFormatW( LCID lcid, DWORD flags, const WCHAR *value,
|
|
const CURRENCYFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
|
|
if (len < 0 || (len && !buffer) || !value || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%04lx,%lx,%s,%p,%p,%d)\n", lcid, flags, debugstr_w(value), format, buffer, len );
|
|
return get_currency_format( locale, flags, value, format, buffer, len );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetCurrencyFormatEx (kernelbase.@)
|
|
*/
|
|
int WINAPI GetCurrencyFormatEx( const WCHAR *name, DWORD flags, const WCHAR *value,
|
|
const CURRENCYFMTW *format, WCHAR *buffer, int len )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (len < 0 || (len && !buffer) || !value || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%s,%lx,%s,%p,%p,%d)\n", debugstr_w(name), flags, debugstr_w(value), format, buffer, len );
|
|
return get_currency_format( locale, flags, value, format, buffer, len );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetDateFormatA (KERNEL32.@)
|
|
*/
|
|
int WINAPI GetDateFormatA( LCID lcid, DWORD flags, const SYSTEMTIME *time,
|
|
const char *format, char *buffer, int len )
|
|
{
|
|
UINT cp = get_lcid_codepage( lcid, flags );
|
|
WCHAR formatW[128], output[128];
|
|
int ret;
|
|
|
|
TRACE( "(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, flags, time, debugstr_a(format), buffer, len );
|
|
|
|
if (len < 0 || (len && !buffer))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (format)
|
|
{
|
|
MultiByteToWideChar( cp, 0, format, -1, formatW, ARRAY_SIZE(formatW) );
|
|
ret = GetDateFormatW( lcid, flags, time, formatW, output, ARRAY_SIZE(output) );
|
|
}
|
|
else ret = GetDateFormatW( lcid, flags, time, NULL, output, ARRAY_SIZE(output) );
|
|
|
|
if (ret) ret = WideCharToMultiByte( cp, 0, output, -1, buffer, len, 0, 0 );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetDateFormatW (kernelbase.@)
|
|
*/
|
|
int WINAPI GetDateFormatW( LCID lcid, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
|
|
if (len < 0 || (len && !buffer) || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%04lx,%lx,%p,%s,%p,%d)\n", lcid, flags, systime, debugstr_w(format), buffer, len );
|
|
return get_date_format( locale, flags, systime, format, buffer, len );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetDateFormatEx (kernelbase.@)
|
|
*/
|
|
int WINAPI GetDateFormatEx( const WCHAR *name, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len, const WCHAR *calendar )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (len < 0 || (len && !buffer) || !locale || calendar)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%s,%lx,%p,%s,%p,%d)\n", debugstr_w(name), flags, systime, debugstr_w(format), buffer, len );
|
|
return get_date_format( locale, flags, systime, format, buffer, len );
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* GetTimeFormatA (kernelbase.@)
|
|
*/
|
|
int WINAPI GetTimeFormatA( LCID lcid, DWORD flags, const SYSTEMTIME *time,
|
|
const char *format, char *buffer, int len )
|
|
{
|
|
UINT cp = get_lcid_codepage( lcid, flags );
|
|
WCHAR formatW[128], output[128];
|
|
int ret;
|
|
|
|
TRACE( "(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, flags, time, debugstr_a(format), buffer, len );
|
|
|
|
if (len < 0 || (len && !buffer))
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
if (format)
|
|
{
|
|
MultiByteToWideChar( cp, 0, format, -1, formatW, ARRAY_SIZE(formatW) );
|
|
ret = GetTimeFormatW( lcid, flags, time, formatW, output, ARRAY_SIZE(output) );
|
|
}
|
|
else ret = GetTimeFormatW( lcid, flags, time, NULL, output, ARRAY_SIZE(output) );
|
|
|
|
if (ret) ret = WideCharToMultiByte( cp, 0, output, -1, buffer, len, 0, 0 );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetTimeFormatW (kernelbase.@)
|
|
*/
|
|
int WINAPI GetTimeFormatW( LCID lcid, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len )
|
|
{
|
|
const NLS_LOCALE_DATA *locale = NlsValidateLocale( &lcid, 0 );
|
|
|
|
if (len < 0 || (len && !buffer) || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%04lx,%lx,%p,%s,%p,%d)\n", lcid, flags, systime, debugstr_w(format), buffer, len );
|
|
return get_time_format( locale, flags, systime, format, buffer, len );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* GetTimeFormatEx (kernelbase.@)
|
|
*/
|
|
int WINAPI GetTimeFormatEx( const WCHAR *name, DWORD flags, const SYSTEMTIME *systime,
|
|
const WCHAR *format, WCHAR *buffer, int len )
|
|
{
|
|
LCID lcid;
|
|
const NLS_LOCALE_DATA *locale = get_locale_by_name( name, &lcid );
|
|
|
|
if (len < 0 || (len && !buffer) || !locale)
|
|
{
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
return 0;
|
|
}
|
|
|
|
TRACE( "(%s,%lx,%p,%s,%p,%d)\n", debugstr_w(name), flags, systime, debugstr_w(format), buffer, len );
|
|
return get_time_format( locale, flags, systime, format, buffer, len );
|
|
}
|