diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec index b8eaa6cdde2..1cccaf22098 100644 --- a/dlls/kernel32/kernel32.spec +++ b/dlls/kernel32/kernel32.spec @@ -724,6 +724,8 @@ @ stdcall -import GetModuleHandleExA(long ptr ptr) @ stdcall -import GetModuleHandleExW(long ptr ptr) @ stdcall -import GetModuleHandleW(wstr) +@ stdcall -import GetNLSVersion(long long ptr) +@ stdcall -import GetNLSVersionEx(long wstr ptr) # @ stub GetNamedPipeAttribute # @ stub GetNamedPipeClientComputerNameA # @ stub GetNamedPipeClientComputerNameW @@ -738,8 +740,6 @@ @ stdcall -arch=x86_64 GetNextUmsListItem(ptr) @ stub GetNextVDMCommand @ stub GetNlsSectionName -# @ stub GetNLSVersion -# @ stub GetNLSVersionEx # @ stub GetNumaAvailableMemory @ stdcall GetNumaAvailableMemoryNode(long ptr) @ stdcall GetNumaAvailableMemoryNodeEx(long ptr) diff --git a/dlls/kernel32/tests/locale.c b/dlls/kernel32/tests/locale.c index acd7ce6fe26..3f755aa2b92 100644 --- a/dlls/kernel32/tests/locale.c +++ b/dlls/kernel32/tests/locale.c @@ -85,6 +85,8 @@ static LANGID (WINAPI *pSetThreadUILanguage)(LANGID); static LANGID (WINAPI *pGetThreadUILanguage)(VOID); static INT (WINAPI *pNormalizeString)(NORM_FORM, LPCWSTR, INT, LPWSTR, INT); static INT (WINAPI *pFindStringOrdinal)(DWORD, LPCWSTR lpStringSource, INT, LPCWSTR, INT, BOOL); +static BOOL (WINAPI *pGetNLSVersion)(NLS_FUNCTION,LCID,NLSVERSIONINFO*); +static BOOL (WINAPI *pGetNLSVersionEx)(NLS_FUNCTION,LPCWSTR,NLSVERSIONINFOEX*); static NTSTATUS (WINAPI *pRtlNormalizeString)(ULONG, LPCWSTR, INT, LPWSTR, INT*); static NTSTATUS (WINAPI *pRtlIsNormalizedString)(ULONG, LPCWSTR, INT, BOOLEAN*); static NTSTATUS (WINAPI *pNtGetNlsSectionPtr)(ULONG,ULONG,void*,void**,SIZE_T*); @@ -125,6 +127,8 @@ static void InitFunctionPointers(void) X(GetThreadUILanguage); X(NormalizeString); X(FindStringOrdinal); + X(GetNLSVersion); + X(GetNLSVersionEx); mod = GetModuleHandleA("ntdll"); X(RtlUpcaseUnicodeChar); @@ -6633,6 +6637,134 @@ static void test_SpecialCasing(void) } } +static void test_NLSVersion(void) +{ + static const GUID guid_null = { 0 }; + static const GUID guid_def = { 0x000000001, 0x57ee, 0x1e5c, {0x00,0xb4,0xd0,0x00,0x0b,0xb1,0xe1,0x1e}}; + static const GUID guid_fr = { 0x000000003, 0x57ee, 0x1e5c, {0x00,0xb4,0xd0,0x00,0x0b,0xb1,0xe1,0x1e}}; + static const GUID guid_ja = { 0x000000046, 0x57ee, 0x1e5c, {0x00,0xb4,0xd0,0x00,0x0b,0xb1,0xe1,0x1e}}; + BOOL ret; + NLSVERSIONINFOEX info; + + if (!pGetNLSVersion) + { + win_skip( "GetNLSVersion not available\n" ); + return; + } + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersion( COMPARE_STRING, MAKELANGID( LANG_FRENCH, SUBLANG_FRENCH_CANADIAN ), + (NLSVERSIONINFO *)&info ); + ok( ret, "GetNLSVersion failed err %u\n", GetLastError() ); + ok( info.dwEffectiveId == MAKELANGID( LANG_FRENCH, SUBLANG_FRENCH_CANADIAN ), + "wrong id %x\n", info.dwEffectiveId ); + ok( IsEqualIID( &info.guidCustomVersion, &guid_fr ) || + broken( IsEqualIID( &info.guidCustomVersion, &guid_null )), /* <= win7 */ + "wrong guid %s\n", debugstr_guid(&info.guidCustomVersion) ); + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = 8; + ret = pGetNLSVersion( COMPARE_STRING, LOCALE_USER_DEFAULT, (NLSVERSIONINFO *)&info ); + ok( !ret, "GetNLSVersion succeeded\n" ); + ok( GetLastError() == ERROR_INSUFFICIENT_BUFFER, "wrong error %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersion( 2, LOCALE_USER_DEFAULT, (NLSVERSIONINFO *)&info ); + ok( !ret, "GetNLSVersion succeeded\n" ); + ok( GetLastError() == ERROR_INVALID_FLAGS, "wrong error %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersion( COMPARE_STRING, 0xdeadbeef, (NLSVERSIONINFO *)&info ); + ok( !ret, "GetNLSVersion succeeded\n" ); + ok( GetLastError() == ERROR_INVALID_PARAMETER, "wrong error %u\n", GetLastError() ); + + if (pGetNLSVersionEx) + { + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( COMPARE_STRING, L"ja-JP", &info ); + ok( ret, "GetNLSVersionEx failed err %u\n", GetLastError() ); + ok( info.dwEffectiveId == MAKELANGID( LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN ), + "wrong id %x\n", info.dwEffectiveId ); + ok( IsEqualIID( &info.guidCustomVersion, &guid_ja ) || + broken( IsEqualIID( &info.guidCustomVersion, &guid_null )), /* <= win7 */ + "wrong guid %s\n", debugstr_guid(&info.guidCustomVersion) ); + trace( "version %08x %08x %08x %s\n", info.dwNLSVersion, info.dwDefinedVersion, info.dwEffectiveId, + debugstr_guid(&info.guidCustomVersion) ); + + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( COMPARE_STRING, L"fr", &info ); + ok( !ret == !pIsValidLocaleName(L"fr"), "GetNLSVersionEx doesn't match IsValidLocaleName\n" ); + if (ret) + { + ok( info.dwEffectiveId == MAKELANGID( LANG_FRENCH, SUBLANG_DEFAULT ), + "wrong id %x\n", info.dwEffectiveId ); + ok( IsEqualIID( &info.guidCustomVersion, &guid_fr ) || + broken( IsEqualIID( &info.guidCustomVersion, &guid_null )), /* <= win7 */ + "wrong guid %s\n", debugstr_guid(&info.guidCustomVersion) ); + } + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = sizeof(info) - 1; + ret = pGetNLSVersionEx( COMPARE_STRING, L"en-US", &info ); + ok( !ret, "GetNLSVersionEx succeeded\n" ); + ok( GetLastError() == ERROR_INSUFFICIENT_BUFFER, "wrong error %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = offsetof( NLSVERSIONINFO, dwEffectiveId ); + ret = pGetNLSVersionEx( COMPARE_STRING, L"en-US", &info ); + ok( ret, "GetNLSVersionEx failed err %u\n", GetLastError() ); + ok( info.dwEffectiveId == 0xcccccccc, "wrong id %x\n", info.dwEffectiveId ); + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( 2, L"en-US", &info ); + ok( !ret, "GetNLSVersionEx succeeded\n" ); + ok( GetLastError() == ERROR_INVALID_FLAGS, "wrong error %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( COMPARE_STRING, L"foobar", &info ); + ok( !ret, "GetNLSVersionEx succeeded\n" ); + ok( GetLastError() == ERROR_INVALID_PARAMETER, "wrong error %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( COMPARE_STRING, L"zz-XX", &info ); + if (!ret) ok( GetLastError() == ERROR_INVALID_PARAMETER, "wrong error %u\n", GetLastError() ); + ok( !ret == !pIsValidLocaleName(L"zz-XX"), "GetNLSVersionEx doesn't match IsValidLocaleName\n" ); + if (ret) + { + ok( info.dwEffectiveId == LOCALE_CUSTOM_UNSPECIFIED, "wrong id %x\n", info.dwEffectiveId ); + ok( IsEqualIID( &info.guidCustomVersion, &guid_def ), + "wrong guid %s\n", debugstr_guid(&info.guidCustomVersion) ); + } + + SetLastError( 0xdeadbeef ); + memset( &info, 0xcc, sizeof(info) ); + info.dwNLSVersionInfoSize = sizeof(info); + ret = pGetNLSVersionEx( COMPARE_STRING, LOCALE_NAME_INVARIANT, &info ); + ok( ret, "GetNLSVersionEx failed err %u\n", GetLastError() ); + if (ret) + { + ok( info.dwEffectiveId == LOCALE_INVARIANT, "wrong id %x\n", info.dwEffectiveId ); + ok( IsEqualIID( &info.guidCustomVersion, &guid_def ) || + broken( IsEqualIID( &info.guidCustomVersion, &guid_null )), /* <= win7 */ + "wrong guid %s\n", debugstr_guid(&info.guidCustomVersion) ); + } + else ok( GetLastError() == ERROR_INVALID_PARAMETER, "wrong error %u\n", GetLastError() ); + } + else win_skip( "GetNLSVersionEx not available\n" ); +} + START_TEST(locale) { InitFunctionPointers(); @@ -6682,6 +6814,7 @@ START_TEST(locale) test_SetThreadUILanguage(); test_NormalizeString(); test_SpecialCasing(); + test_NLSVersion(); /* this requires collation table patch to make it MS compatible */ if (0) test_sorting(); } diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index 4f39f15f0b0..fbcae55e983 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -565,8 +565,8 @@ @ stdcall GetModuleHandleExW(long ptr ptr) @ stdcall GetModuleHandleW(wstr) # @ stub GetModuleInformation -@ stub GetNLSVersion -@ stub GetNLSVersionEx +@ stdcall GetNLSVersion(long long ptr) +@ stdcall GetNLSVersionEx(long wstr ptr) @ stub GetNamedLocaleHashNode @ stub GetNamedPipeAttribute @ stub GetNamedPipeClientComputerNameW diff --git a/dlls/kernelbase/locale.c b/dlls/kernelbase/locale.c index 03ac58333d1..b9a53b0ef35 100644 --- a/dlls/kernelbase/locale.c +++ b/dlls/kernelbase/locale.c @@ -4260,6 +4260,60 @@ INT WINAPI DECLSPEC_HOTPATCH GetLocaleInfoEx( const WCHAR *locale, LCTYPE info, } +/****************************************************************************** + * 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 ) +{ + LCID lcid = 0; + + 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 (!(lcid = LocaleNameToLCID( locale, 0 ))) return FALSE; + + info->dwNLSVersion = info->dwDefinedVersion = sort.version; + if (info->dwNLSVersionInfoSize >= sizeof(*info)) + { + const struct sortguid *sortid = get_language_sort( locale ); + info->dwEffectiveId = lcid; + info->guidCustomVersion = sortid ? sortid->id : default_sort_guid; + } + return TRUE; +} + + /****************************************************************************** * GetOEMCP (kernelbase.@) */ diff --git a/include/winnls.h b/include/winnls.h index c7c2cfb2404..e563968cf9a 100644 --- a/include/winnls.h +++ b/include/winnls.h @@ -735,6 +735,8 @@ typedef struct _nlsversioninfo { DWORD dwNLSVersionInfoSize; DWORD dwNLSVersion; DWORD dwDefinedVersion; + DWORD dwEffectiveId; + GUID guidCustomVersion; } NLSVERSIONINFO, *LPNLSVERSIONINFO; typedef struct _nlsversioninfoex { @@ -745,6 +747,9 @@ typedef struct _nlsversioninfoex { GUID guidCustomVersion; } NLSVERSIONINFOEX, *LPNLSVERSIONINFOEX; +enum SYSNLS_FUNCTION { COMPARE_STRING = 1 }; +typedef DWORD NLS_FUNCTION; + /* Define a bunch of callback types */ typedef BOOL (CALLBACK *CALINFO_ENUMPROCEXEX)(LPWSTR,CALID,LPWSTR,LPARAM); @@ -913,6 +918,8 @@ WINBASEAPI INT WINAPI GetLocaleInfoA(LCID,LCTYPE,LPSTR,INT); WINBASEAPI INT WINAPI GetLocaleInfoW(LCID,LCTYPE,LPWSTR,INT); #define GetLocaleInfo WINELIB_NAME_AW(GetLocaleInfo) WINBASEAPI INT WINAPI GetLocaleInfoEx(LPCWSTR,LCTYPE,LPWSTR,INT); +WINBASEAPI BOOL WINAPI GetNLSVersion(NLS_FUNCTION,LCID,NLSVERSIONINFO*); +WINBASEAPI BOOL WINAPI GetNLSVersionEx(NLS_FUNCTION,LPCWSTR,NLSVERSIONINFOEX*); WINBASEAPI INT WINAPI GetNumberFormatA(LCID,DWORD,LPCSTR,const NUMBERFMTA*,LPSTR,INT); WINBASEAPI INT WINAPI GetNumberFormatW(LCID,DWORD,LPCWSTR,const NUMBERFMTW*,LPWSTR,INT); #define GetNumberFormat WINELIB_NAME_AW(GetNumberFormat)