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

Gabriel Ivăncescu gabrielopcode at gmail.com
Tue Mar 17 11:07:51 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>
---

v2: Duplicated the tables and implementation of RtlVerifyVersionInfo to avoid
extra internal exports from ntdll. Also used goto instead of many nested ifs.

 dlls/kernel32/tests/version.c |   2 +-
 dlls/kernelbase/version.c     | 170 +++++++++++++++++++++++++++++++---
 2 files changed, 157 insertions(+), 15 deletions(-)

diff --git a/dlls/kernel32/tests/version.c b/dlls/kernel32/tests/version.c
index 1bb3e20..c9e92a9 100644
--- a/dlls/kernel32/tests/version.c
+++ b/dlls/kernel32/tests/version.c
@@ -610,7 +610,7 @@ static void test_VerifyVersionInfo(void)
 
         if (rtlinfo.dwMajorVersion != 6 || rtlinfo.dwMinorVersion != 2)
         {
-            win_skip("GetVersionEx and VerifyVersionInfo are faking values\n");
+            skip("GetVersionEx and VerifyVersionInfo are faking values\n");
             return;
         }
     }
diff --git a/dlls/kernelbase/version.c b/dlls/kernelbase/version.c
index cf46e9c..dc8cc61 100644
--- a/dlls/kernelbase/version.c
+++ b/dlls/kernelbase/version.c
@@ -28,6 +28,8 @@
 #include <stdio.h>
 #include <sys/types.h>
 
+#include "ntstatus.h"
+#define WIN32_NO_STATUS
 #define NONAMELESSUNION
 #define NONAMELESSSTRUCT
 #include "windef.h"
@@ -116,6 +118,132 @@ typedef struct
     (VS_VERSION_INFO_STRUCT32 *)( (LPBYTE)ver + (((ver)->wLength + 3) & ~3) )
 
 
+/***********************************************************************
+ * Win8 info, reported if app doesn't provide compat GUID in manifest.
+ */
+static const RTL_OSVERSIONINFOEXW windows8_version_data =
+{
+    sizeof(RTL_OSVERSIONINFOEXW), 6, 2, 0x23f0, VER_PLATFORM_WIN32_NT,
+    {0}, 0, 0, VER_SUITE_SINGLEUSERTS, VER_NT_WORKSTATION, 0
+};
+
+
+/***********************************************************************
+ * Windows versions that need compatibility GUID specified in manifest
+ * in order to be reported by the APIs.
+ */
+static const struct
+{
+    RTL_OSVERSIONINFOEXW info;
+    GUID guid;
+} version_data[] =
+{
+    /* Windows 8.1 */
+    {
+        {
+            sizeof(RTL_OSVERSIONINFOEXW), 6, 3, 0x2580, VER_PLATFORM_WIN32_NT,
+            {0}, 0, 0, VER_SUITE_SINGLEUSERTS, VER_NT_WORKSTATION, 0
+        },
+        {0x1f676c76,0x80e1,0x4239,{0x95,0xbb,0x83,0xd0,0xf6,0xd0,0xda,0x78}}
+    },
+    /* Windows 10 */
+    {
+        {
+            sizeof(RTL_OSVERSIONINFOEXW), 10, 0, 0x42ee, VER_PLATFORM_WIN32_NT,
+            {0}, 0, 0, VER_SUITE_SINGLEUSERTS, VER_NT_WORKSTATION, 0
+        },
+        {0x8e0f7a12,0xbfb3,0x4fe8,{0xb9,0xa5,0x48,0xfd,0x50,0xa1,0x5a,0x9a}}
+    }
+};
+
+
+/***********************************************************************
+ * Holds the current version (including compatibility mode).
+ * Call init_current_version before using it.
+ */
+static RTL_OSVERSIONINFOEXW current_version;
+
+
+/******************************************************************************
+ *  init_current_version
+ *
+ * Initialize the current_version variable.
+ *
+ * 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.
+ *
+ */
+static BOOL CALLBACK init_current_version_callback(PINIT_ONCE init_once, PVOID parameter, PVOID *context)
+{
+    /*ACTIVATION_CONTEXT_COMPATIBILITY_INFORMATION*/DWORD *acci;
+    RTL_OSVERSIONINFOEXW tmp_version;
+    const RTL_OSVERSIONINFOEXW *ver;
+    SIZE_T req;
+    int idx;
+
+    if (!set_ntstatus(RtlGetVersion(&tmp_version))) return FALSE;
+    ver = &tmp_version;
+
+    for (idx = ARRAY_SIZE(version_data); idx--;)
+        if ( tmp_version.dwMajorVersion >  version_data[idx].info.dwMajorVersion ||
+            (tmp_version.dwMajorVersion == version_data[idx].info.dwMajorVersion &&
+             tmp_version.dwMinorVersion >= version_data[idx].info.dwMinorVersion))
+            break;
+
+    if (idx < 0) goto done;
+    ver = &windows8_version_data;
+
+    if (RtlQueryInformationActivationContext(0, NULL, NULL,
+            CompatibilityInformationInActivationContext, NULL, 0, &req) != STATUS_BUFFER_TOO_SMALL
+        || !req)
+        goto done;
+
+    if (!(acci = HeapAlloc(GetProcessHeap(), 0, req)))
+    {
+        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+        return FALSE;
+    }
+
+    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_data[idx].guid))
+                {
+                    ver = &version_data[idx].info;
+
+                    if (ver->dwMajorVersion == tmp_version.dwMajorVersion &&
+                        ver->dwMinorVersion == tmp_version.dwMinorVersion)
+                        ver = &tmp_version;
+
+                    idx = 0;  /* break from outer loop */
+                    break;
+                }
+            }
+        } while(idx--);
+    }
+    HeapFree(GetProcessHeap(), 0, acci);
+
+done:
+    memcpy(&current_version, ver, sizeof(current_version));
+    return TRUE;
+}
+
+static BOOL init_current_version(void)
+{
+    static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
+
+    return InitOnceExecuteOnce(&init_once, init_current_version_callback, NULL, NULL);
+}
+
+
 /**********************************************************************
  *  find_entry_by_id
  *
@@ -1317,8 +1445,6 @@ DWORD WINAPI GetVersion(void)
  */
 BOOL WINAPI GetVersionExA( OSVERSIONINFOA *info )
 {
-    RTL_OSVERSIONINFOEXW infoW;
-
     if (info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOA) &&
         info->dwOSVersionInfoSize != sizeof(OSVERSIONINFOEXA))
     {
@@ -1327,23 +1453,22 @@ BOOL WINAPI GetVersionExA( OSVERSIONINFOA *info )
         return FALSE;
     }
 
-    infoW.dwOSVersionInfoSize = sizeof(infoW);
-    if (!set_ntstatus( RtlGetVersion( &infoW ))) return FALSE;
+    if (!init_current_version()) 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 = current_version.dwMajorVersion;
+    info->dwMinorVersion = current_version.dwMinorVersion;
+    info->dwBuildNumber  = current_version.dwBuildNumber;
+    info->dwPlatformId   = current_version.dwPlatformId;
+    WideCharToMultiByte( CP_ACP, 0, current_version.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 = current_version.wServicePackMajor;
+        vex->wServicePackMinor = current_version.wServicePackMinor;
+        vex->wSuiteMask        = current_version.wSuiteMask;
+        vex->wProductType      = current_version.wProductType;
     }
     return TRUE;
 }
@@ -1360,5 +1485,22 @@ BOOL WINAPI GetVersionExW( OSVERSIONINFOW *info )
         WARN( "wrong OSVERSIONINFO size from app (got: %d)\n", info->dwOSVersionInfoSize );
         return FALSE;
     }
-    return set_ntstatus( RtlGetVersion( (RTL_OSVERSIONINFOEXW *)info ));
+
+    if (!init_current_version()) return FALSE;
+
+    info->dwMajorVersion = current_version.dwMajorVersion;
+    info->dwMinorVersion = current_version.dwMinorVersion;
+    info->dwBuildNumber  = current_version.dwBuildNumber;
+    info->dwPlatformId   = current_version.dwPlatformId;
+    wcscpy( info->szCSDVersion, current_version.szCSDVersion );
+
+    if (info->dwOSVersionInfoSize == sizeof(RTL_OSVERSIONINFOEXW))
+    {
+        OSVERSIONINFOEXW *vex = (OSVERSIONINFOEXW *)info;
+        vex->wServicePackMajor = current_version.wServicePackMajor;
+        vex->wServicePackMinor = current_version.wServicePackMinor;
+        vex->wSuiteMask        = current_version.wSuiteMask;
+        vex->wProductType      = current_version.wProductType;
+    }
+    return TRUE;
 }
-- 
2.21.0




More information about the wine-devel mailing list