Since Windows 8.1, these functions have been deprecated and run in a sort of compatibility mode, reporting Windows 8 unless the application supplies a manifest that specifies compatibility with newer Windows versions explicitly (by listing their GUIDs).
Some applications have bad non-forward-compatible checks based on GetVersionEx, and depend on this behavior (they do not supply a manifest). Currently, they break on Wine if we use a Windows 10 prefix for example, since we always report the real version. One example is the game Rock of Ages.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
I'm introducing an exported internal ntdll function, since I didn't want to duplicate the table for kernelbase. I don't know if this is the best way, so let me know if I should proceed differently.
dlls/kernelbase/version.c | 54 +++++++++++++++++++------ dlls/ntdll/ntdll.spec | 1 + dlls/ntdll/version.c | 85 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 13 deletions(-)
diff --git a/dlls/kernelbase/version.c b/dlls/kernelbase/version.c index cf46e9c..d07c1c9 100644 --- a/dlls/kernelbase/version.c +++ b/dlls/kernelbase/version.c @@ -43,6 +43,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(ver);
+extern CDECL const RTL_OSVERSIONINFOEXW *__wine_get_compat_win_version(void); + typedef struct { WORD offset; @@ -1317,7 +1319,7 @@ DWORD WINAPI GetVersion(void) */ BOOL WINAPI GetVersionExA( OSVERSIONINFOA *info ) { - RTL_OSVERSIONINFOEXW infoW; + const RTL_OSVERSIONINFOEXW *ver;
if (info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOA) && info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOEXA)) @@ -1327,23 +1329,26 @@ BOOL WINAPI GetVersionExA( OSVERSIONINFOA *info ) return FALSE; }
- infoW.dwOSVersionInfoSize = sizeof(infoW); - if (!set_ntstatus( RtlGetVersion( &infoW ))) return FALSE; + if (!(ver = __wine_get_compat_win_version())) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + }
- info->dwMajorVersion = infoW.dwMajorVersion; - info->dwMinorVersion = infoW.dwMinorVersion; - info->dwBuildNumber = infoW.dwBuildNumber; - info->dwPlatformId = infoW.dwPlatformId; - WideCharToMultiByte( CP_ACP, 0, infoW.szCSDVersion, -1, + info->dwMajorVersion = ver->dwMajorVersion; + info->dwMinorVersion = ver->dwMinorVersion; + info->dwBuildNumber = ver->dwBuildNumber; + info->dwPlatformId = ver->dwPlatformId; + WideCharToMultiByte( CP_ACP, 0, ver->szCSDVersion, -1, info->szCSDVersion, sizeof(info->szCSDVersion), NULL, NULL );
if (info->dwOSVersionInfoSize == sizeof(OSVERSIONINFOEXA)) { OSVERSIONINFOEXA *vex = (OSVERSIONINFOEXA *)info; - vex->wServicePackMajor = infoW.wServicePackMajor; - vex->wServicePackMinor = infoW.wServicePackMinor; - vex->wSuiteMask = infoW.wSuiteMask; - vex->wProductType = infoW.wProductType; + vex->wServicePackMajor = ver->wServicePackMajor; + vex->wServicePackMinor = ver->wServicePackMinor; + vex->wSuiteMask = ver->wSuiteMask; + vex->wProductType = ver->wProductType; } return TRUE; } @@ -1354,11 +1359,34 @@ BOOL WINAPI GetVersionExA( OSVERSIONINFOA *info ) */ BOOL WINAPI GetVersionExW( OSVERSIONINFOW *info ) { + const RTL_OSVERSIONINFOEXW *ver; + if (info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOW) && info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOEXW)) { WARN( "wrong OSVERSIONINFO size from app (got: %d)\n", info->dwOSVersionInfoSize ); return FALSE; } - return set_ntstatus( RtlGetVersion( (RTL_OSVERSIONINFOEXW *)info )); + + if (!(ver = __wine_get_compat_win_version())) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + + info->dwMajorVersion = ver->dwMajorVersion; + info->dwMinorVersion = ver->dwMinorVersion; + info->dwBuildNumber = ver->dwBuildNumber; + info->dwPlatformId = ver->dwPlatformId; + wcscpy( info->szCSDVersion, ver->szCSDVersion ); + + if(info->dwOSVersionInfoSize == sizeof(RTL_OSVERSIONINFOEXW)) + { + OSVERSIONINFOEXW *vex = (OSVERSIONINFOEXW *)info; + vex->wServicePackMajor = ver->wServicePackMajor; + vex->wServicePackMinor = ver->wServicePackMinor; + vex->wSuiteMask = ver->wSuiteMask; + vex->wProductType = ver->wProductType; + } + return TRUE; } diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 0ea72e3..5f58e4a 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -1572,6 +1572,7 @@ @ cdecl wine_get_version() NTDLL_wine_get_version @ cdecl wine_get_build_id() NTDLL_wine_get_build_id @ cdecl wine_get_host_version(ptr ptr) NTDLL_wine_get_host_version +@ cdecl __wine_get_compat_win_version()
# Codepages @ cdecl __wine_get_unix_codepage() diff --git a/dlls/ntdll/version.c b/dlls/ntdll/version.c index 61e48f6..f48f725 100644 --- a/dlls/ntdll/version.c +++ b/dlls/ntdll/version.c @@ -191,6 +191,13 @@ static const RTL_OSVERSIONINFOEXW VersionData[NB_WINDOWS_VERSIONS] =
};
+/* GUIDs for Windows versions WIN81 and later, needed for compatibility manifest check */ +static const GUID version_compat_guid[NB_WINDOWS_VERSIONS - WIN81] = +{ + {0x1f676c76,0x80e1,0x4239,{0x95,0xbb,0x83,0xd0,0xf6,0xd0,0xda,0x78}}, /* WIN81 */ + {0x8e0f7a12,0xbfb3,0x4fe8,{0xb9,0xa5,0x48,0xfd,0x50,0xa1,0x5a,0x9a}}, /* WIN10 */ +}; + static const struct { WCHAR name[12]; WINDOWS_VERSION ver; } version_names[] = { { {'w','i','n','2','0',0}, WIN20 }, @@ -781,3 +788,81 @@ NTSTATUS WINAPI RtlVerifyVersionInfo( const RTL_OSVERSIONINFOEXW *info,
return STATUS_SUCCESS; } + +/****************************************************************************** + * __wine_get_compat_win_version (NTDLL.@) + * + * Get a pointer to a RTL_OSVERSIONINFOEXW that matches the compatibility mode. + * + * For compatibility, Windows 8.1 and later report Win8 version unless the app + * has a manifest that confirms its compatibility with newer versions of Windows. + * + * RETURNS + * A pointer to a RTL_OSVERSIONINFOEXW containing the version in compatibility + * mode, or NULL if out of memory. + */ +CDECL const RTL_OSVERSIONINFOEXW *__wine_get_compat_win_version(void) +{ + static const RTL_OSVERSIONINFOEXW *compat_ver; + + if (!compat_ver) + { + /*ACTIVATION_CONTEXT_COMPATIBILITY_INFORMATION*/DWORD *acci; + const RTL_OSVERSIONINFOEXW *ver = current_version; + SIZE_T req; + int idx; + + for (idx = ARRAY_SIZE(version_compat_guid); idx--;) + { + const RTL_OSVERSIONINFOEXW *v = &VersionData[WIN81 + idx]; + + if ( current_version->dwMajorVersion > v->dwMajorVersion || + (current_version->dwMajorVersion == v->dwMajorVersion && + current_version->dwMinorVersion >= v->dwMinorVersion)) + break; + } + + if (idx >= 0) + { + ver = &VersionData[WIN8]; + + if (RtlQueryInformationActivationContext(0, NULL, NULL, + CompatibilityInformationInActivationContext, NULL, 0, &req) == STATUS_BUFFER_TOO_SMALL + && req) + { + if (!(acci = RtlAllocateHeap(GetProcessHeap(), 0, req))) + return NULL; + + if (RtlQueryInformationActivationContext(0, NULL, NULL, + CompatibilityInformationInActivationContext, acci, req, &req) == STATUS_SUCCESS) + { + do + { + COMPATIBILITY_CONTEXT_ELEMENT *elements = (COMPATIBILITY_CONTEXT_ELEMENT*)(acci + 1); + DWORD i, count = *acci; + + for (i = 0; i < count; i++) + { + if (elements[i].Type == ACTCX_COMPATIBILITY_ELEMENT_TYPE_OS && + IsEqualGUID(&elements[i].Id, &version_compat_guid[idx])) + { + ver = &VersionData[WIN81 + idx]; + + if (ver->dwMajorVersion == current_version->dwMajorVersion && + ver->dwMinorVersion == current_version->dwMinorVersion) + ver = current_version; + + idx = 0; /* break from outer loop */ + break; + } + } + } while(idx--); + } + RtlFreeHeap(GetProcessHeap(), 0, acci); + } + } + interlocked_cmpxchg_ptr((void**)&compat_ver, (void*)ver, NULL); + } + + return compat_ver; +}