[PATCH 1/4] kernelbase: Implement compatibility mode for GetVersionEx.

Gabriel Ivăncescu gabrielopcode at gmail.com
Mon Mar 16 08:05:58 CDT 2020


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 at 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;
+}
-- 
2.21.0




More information about the wine-devel mailing list