/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
#include <opengl/win/WinDeviceInfo.hxx>
 
#include <opengl/win/blocklist_parser.hxx>
#include <config_folders.h>
 
#if !defined WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <objbase.h>
#include <setupapi.h>
#include <algorithm>
#include <cstdint>
#include <memory>
 
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <tools/stream.hxx>
#include <o3tl/char16_t2wchar_t.hxx>
 
#include <desktop/crashreport.hxx>
 
OUString* WinOpenGLDeviceInfo::mpDeviceVendors[wgl::DeviceVendorMax];
std::vector<wgl::DriverInfo> WinOpenGLDeviceInfo::maDriverInfo;
 
namespace {
 
/*
 * Compute the length of an array with constant length.  (Use of this method
 * with a non-array pointer will not compile.)
 *
 * Beware of the implicit trailing '\0' when using this with string constants.
*/
template<typename T, size_t N>
size_t ArrayLength(T (&)[N])
{
    return N;
}
 
#define GFX_DRIVER_VERSION(a,b,c,d) \
((uint64_t(a)<<48) | (uint64_t(b)<<32) | (uint64_t(c)<<16) | uint64_t(d))
 
bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type)
{
    HKEY key;
    DWORD dwcbData;
    DWORD dValue;
    DWORD resultType;
    LONG result;
    bool retval = true;
 
    result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key);
    if (result != ERROR_SUCCESS)
    {
        return false;
    }
 
    switch (type)
    {
        case REG_DWORD:
            {
                // We only use this for vram size
                dwcbData = sizeof(dValue);
                result = RegQueryValueExW(key, keyName, nullptr, &resultType,
                        reinterpret_cast<LPBYTE>(&dValue), &dwcbData);
                if (result == ERROR_SUCCESS && resultType == REG_DWORD)
                {
                    dValue = dValue / 1024 / 1024;
                    destString += OUString::number(int32_t(dValue));
                }
                else
                {
                    retval = false;
                }
                break;
            }
        case REG_MULTI_SZ:
            {
                // A chain of null-separated strings; we convert the nulls to spaces
                WCHAR wCharValue[1024];
                dwcbData = sizeof(wCharValue);
 
                result = RegQueryValueExW(key, keyName, nullptr, &resultType,
                        reinterpret_cast<LPBYTE>(wCharValue), &dwcbData);
                if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ)
                {
                    // This bit here could probably be cleaner.
                    bool isValid = false;
 
                    DWORD strLen = dwcbData/sizeof(wCharValue[0]);
                    for (DWORD i = 0; i < strLen; i++)
                    {
                        if (wCharValue[i] == '\0')
                        {
                            if (i < strLen - 1 && wCharValue[i + 1] == '\0')
                            {
                                isValid = true;
                                break;
                            }
                            else
                            {
                                wCharValue[i] = ' ';
                            }
                        }
                    }
 
                    // ensure wCharValue is null terminated
                    wCharValue[strLen-1] = '\0';
 
                    if (isValid)
                        destString = OUString(o3tl::toU(wCharValue));
 
                }
                else
                {
                    retval = false;
                }
 
                break;
            }
    }
    RegCloseKey(key);
 
    return retval;
}
 
// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD
// this function is used to extract the id's out of it
uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length)
{
    OUString id = key.toAsciiUpperCase();
    OUString aPrefix = OUString::fromUtf8(prefix);
    int32_t start = id.indexOf(aPrefix);
    if (start != -1)
    {
        id = id.copy(start + aPrefix.getLength(), length);
    }
    return id.toUInt32(16);
}
 
// OS version in 16.16 major/minor form
// based on http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
enum {
    kWindowsUnknown    = 0,
    kWindowsXP         = 0x00050001,
    kWindowsServer2003 = 0x00050002,
    kWindowsVista      = 0x00060000,
    kWindows7          = 0x00060001,
    kWindows8          = 0x00060002,
    kWindows8_1        = 0x00060003,
    kWindows10         = 0x000A0000  // Major 10 Minor 0
};
 
 
wgl::OperatingSystem WindowsVersionToOperatingSystem(int32_t aWindowsVersion)
{
    switch(aWindowsVersion)
    {
        case kWindowsXP:
            return wgl::DRIVER_OS_WINDOWS_XP;
        case kWindowsServer2003:
            return wgl::DRIVER_OS_WINDOWS_SERVER_2003;
        case kWindowsVista:
            return wgl::DRIVER_OS_WINDOWS_VISTA;
        case kWindows7:
            return wgl::DRIVER_OS_WINDOWS_7;
        case kWindows8:
            return wgl::DRIVER_OS_WINDOWS_8;
        case kWindows8_1:
            return wgl::DRIVER_OS_WINDOWS_8_1;
        case kWindows10:
            return wgl::DRIVER_OS_WINDOWS_10;
        case kWindowsUnknown:
        default:
            return wgl::DRIVER_OS_UNKNOWN;
    };
}
 
 
int32_t WindowsOSVersion()
{
    static int32_t winVersion = kWindowsUnknown;
 
    if (winVersion == kWindowsUnknown)
    {
        // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
        // subject to manifest-based behavior since Windows 8.1, so give wrong results.
        // Another approach would be to use NetWkstaGetInfo, but that has some small
        // reported delays (some milliseconds), and might get slower in domains with
        // poor network connections.
        // So go with a solution described at https://msdn.microsoft.com/en-us/library/ms724429
        HINSTANCE hLibrary = LoadLibraryW(L"kernel32.dll");
        if (hLibrary != nullptr)
        {
            wchar_t szPath[MAX_PATH];
            DWORD dwCount = GetModuleFileNameW(hLibrary, szPath, SAL_N_ELEMENTS(szPath));
            FreeLibrary(hLibrary);
            if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
            {
                dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
                if (dwCount != 0)
                {
                    std::unique_ptr<char[]> ver(new char[dwCount]);
                    if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
                    {
                        void* pBlock = nullptr;
                        UINT dwBlockSz = 0;
                        if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
                        {
                            VS_FIXEDFILEINFO *vinfo = static_cast<VS_FIXEDFILEINFO *>(pBlock);
                            winVersion = int32_t(vinfo->dwProductVersionMS);
                        }
                    }
                }
            }
        }
    }
 
    return winVersion;
}
 
// This allows us to pad driver version 'substrings' with 0s, this
// effectively allows us to treat the version numbers as 'decimals'. This is
// a little strange but this method seems to do the right thing for all
// different vendor's driver strings. i.e. .98 will become 9800, which is
// larger than .978 which would become 9780.
void PadDriverDecimal(char *aString)
{
    for (int i = 0; i < 4; i++)
    {
        if (!aString[i])
        {
            for (int c = i; c < 4; c++)
            {
                aString[c] = '0';
            }
            break;
        }
    }
    aString[4] = 0;
}
 
// All destination string storage needs to have at least 5 bytes available.
bool SplitDriverVersion(const char *aSource, char *aAStr, char *aBStr, char *aCStr, char *aDStr)
{
    // sscanf doesn't do what we want here to we parse this manually.
    int len = strlen(aSource);
    char *dest[4] = { aAStr, aBStr, aCStr, aDStr };
    unsigned destIdx = 0;
    unsigned destPos = 0;
 
    for (int i = 0; i < len; i++)
    {
        if (destIdx >= ArrayLength(dest))
        {
            // Invalid format found. Ensure we don't access dest beyond bounds.
            return false;
        }
 
        if (aSource[i] == '.')
        {
            dest[destIdx++][destPos] = 0;
            destPos = 0;
            continue;
        }
 
        if (destPos > 3)
        {
            // Ignore more than 4 chars. Ensure we never access dest[destIdx]
            // beyond its bounds.
            continue;
        }
 
        dest[destIdx][destPos++] = aSource[i];
    }
 
    // Add last terminator.
    dest[destIdx][destPos] = 0;
 
    if (destIdx != ArrayLength(dest) - 1)
    {
        return false;
    }
    return true;
}
 
/* Other interesting places for info:
 *   IDXGIAdapter::GetDesc()
 *   IDirectDraw7::GetAvailableVidMem()
 *   e->GetAvailableTextureMem()
 * */
 
template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars)
{
    rString += "0x";
    OUString aValue = OUString::number(value, 16);
    sal_Int32 nLength = aValue.getLength();
    sal_uInt32 nPadLength = nChars - nLength;
    assert(nPadLength >= 0);
    OUStringBuffer aBuffer;
    for (sal_uInt32 i = 0; i < nPadLength; ++i)
    {
        aBuffer.append("0");
    }
    rString += aBuffer.makeStringAndClear() + aValue;
}
 
#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\"
}
 
namespace wgl {
 
bool ParseDriverVersion(const OUString& aVersion, uint64_t& rNumericVersion)
{
    rNumericVersion = 0;
 
#if defined(_WIN32)
    int a, b, c, d;
    char aStr[8], bStr[8], cStr[8], dStr[8];
    /* honestly, why do I even bother */
    OString aOVersion = OUStringToOString(aVersion, RTL_TEXTENCODING_UTF8);
    if (!SplitDriverVersion(aOVersion.getStr(), aStr, bStr, cStr, dStr))
        return false;
 
    PadDriverDecimal(bStr);
    PadDriverDecimal(cStr);
    PadDriverDecimal(dStr);
 
    a = atoi(aStr);
    b = atoi(bStr);
    c = atoi(cStr);
    d = atoi(dStr);
 
    if (a < 0 || a > 0xffff) return false;
    if (b < 0 || b > 0xffff) return false;
    if (c < 0 || c > 0xffff) return false;
    if (d < 0 || d > 0xffff) return false;
 
    rNumericVersion = GFX_DRIVER_VERSION(a, b, c, d);
    return true;
#else
    return false;
#endif
}
 
uint64_t DriverInfo::allDriverVersions = ~(uint64_t(0));
 
DriverInfo::DriverInfo()
    : meOperatingSystem(wgl::DRIVER_OS_UNKNOWN),
    mnOperatingSystemVersion(0),
    maAdapterVendor(WinOpenGLDeviceInfo::GetDeviceVendor(VendorAll)),
    mbWhitelisted(false),
    meComparisonOp(DRIVER_COMPARISON_IGNORED),
    mnDriverVersion(0),
    mnDriverVersionMax(0)
{}
 
DriverInfo::DriverInfo(OperatingSystem os, const OUString& vendor,
        VersionComparisonOp op,
        uint64_t driverVersion,
        bool bWhitelisted,
        const char *suggestedVersion /* = nullptr */)
    : meOperatingSystem(os),
    mnOperatingSystemVersion(0),
    maAdapterVendor(vendor),
    mbWhitelisted(bWhitelisted),
    meComparisonOp(op),
    mnDriverVersion(driverVersion),
    mnDriverVersionMax(0)
{
    if (suggestedVersion)
        maSuggestedVersion = OStringToOUString(OString(suggestedVersion), RTL_TEXTENCODING_UTF8);
}
 
DriverInfo::~DriverInfo()
{
}
 
}
 
WinOpenGLDeviceInfo::WinOpenGLDeviceInfo():
    mbHasDualGPU(false),
    mbRDP(false)
{
    GetData();
    FillBlacklist();
}
 
WinOpenGLDeviceInfo::~WinOpenGLDeviceInfo()
{
}
 
namespace {
 
struct compareIgnoreAsciiCase
{
    explicit compareIgnoreAsciiCase(const OUString& rString)
        : maString(rString)
    {
    }
 
    bool operator()(const OUString& rCompare)
    {
        return maString.equalsIgnoreAsciiCase(rCompare);
    }
 
private:
    OUString maString;
};
 
}
 
bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList(std::vector<wgl::DriverInfo>& aDeviceInfos,
                                                      OUString const & sDriverVersion, OUString const & sAdapterVendorID,
                                                      OUString const & sAdapterDeviceID, uint32_t nWindowsVersion)
{
    uint64_t driverVersion;
    wgl::ParseDriverVersion(sDriverVersion, driverVersion);
 
    wgl::OperatingSystem eOS = WindowsVersionToOperatingSystem(nWindowsVersion);
    bool match = false;
    for (std::vector<wgl::DriverInfo>::size_type i = 0; i < aDeviceInfos.size(); i++)
    {
        if (aDeviceInfos[i].meOperatingSystem != wgl::DRIVER_OS_ALL &&
                aDeviceInfos[i].meOperatingSystem != eOS)
        {
            continue;
        }
 
        if (aDeviceInfos[i].mnOperatingSystemVersion && aDeviceInfos[i].mnOperatingSystemVersion != nWindowsVersion)
        {
            continue;
        }
 
        if (!aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(GetDeviceVendor(wgl::VendorAll)) &&
                !aDeviceInfos[i].maAdapterVendor.equalsIgnoreAsciiCase(sAdapterVendorID))
        {
            continue;
        }
 
        if (std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(), compareIgnoreAsciiCase("all")) &&
            std::none_of(aDeviceInfos[i].maDevices.begin(), aDeviceInfos[i].maDevices.end(), compareIgnoreAsciiCase(sAdapterDeviceID)))
        {
            continue;
        }
 
        switch (aDeviceInfos[i].meComparisonOp)
        {
            case wgl::DRIVER_LESS_THAN:
                match = driverVersion < aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_LESS_THAN_OR_EQUAL:
                match = driverVersion <= aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_GREATER_THAN:
                match = driverVersion > aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_GREATER_THAN_OR_EQUAL:
                match = driverVersion >= aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_EQUAL:
                match = driverVersion == aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_NOT_EQUAL:
                match = driverVersion != aDeviceInfos[i].mnDriverVersion;
                break;
            case wgl::DRIVER_BETWEEN_EXCLUSIVE:
                match = driverVersion > aDeviceInfos[i].mnDriverVersion && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
                break;
            case wgl::DRIVER_BETWEEN_INCLUSIVE:
                match = driverVersion >= aDeviceInfos[i].mnDriverVersion && driverVersion <= aDeviceInfos[i].mnDriverVersionMax;
                break;
            case wgl::DRIVER_BETWEEN_INCLUSIVE_START:
                match = driverVersion >= aDeviceInfos[i].mnDriverVersion && driverVersion < aDeviceInfos[i].mnDriverVersionMax;
                break;
            case wgl::DRIVER_COMPARISON_IGNORED:
                // We don't have a comparison op, so we match everything.
                match = true;
                break;
            default:
                SAL_WARN("vcl.opengl", "Bogus op in GfxDriverInfo");
                break;
        }
 
        if (match || aDeviceInfos[i].mnDriverVersion == wgl::DriverInfo::allDriverVersions)
        {
            // white listed drivers
            if (aDeviceInfos[i].mbWhitelisted)
            {
                SAL_WARN("vcl.opengl", "whitelisted driver");
                return false;
            }
 
            match = true;
            SAL_WARN("vcl.opengl", "use : " << aDeviceInfos[i].maSuggestedVersion);
            break;
        }
    }
 
    SAL_INFO("vcl.opengl", (match ? "BLACKLISTED" : "not blacklisted"));
    return match;
}
 
bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList()
{
    return FindBlocklistedDeviceInList(maDriverInfo, maDriverVersion, maAdapterVendorID, maAdapterDeviceID, mnWindowsVersion);
}
 
namespace {
 
OUString getCacheFolder()
{
    OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
    rtl::Bootstrap::expandMacros(url);
 
    osl::Directory::create(url);
 
    return url;
}
 
void writeToLog(SvStream& rStrm, const char* pKey, const OUString & rVal)
{
    rStrm.WriteCharPtr(pKey);
    rStrm.WriteCharPtr(": ");
    rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8));
    rStrm.WriteChar('\n');
}
 
}
 
bool WinOpenGLDeviceInfo::isDeviceBlocked()
{
    CrashReporter::AddKeyValue("OpenGLVendor", maAdapterVendorID);
    CrashReporter::AddKeyValue("OpenGLDevice", maAdapterDeviceID);
    CrashReporter::AddKeyValue("OpenGLDriver", maDriverVersion);
 
    SAL_INFO("vcl.opengl", maDriverVersion);
    SAL_INFO("vcl.opengl", maDriverDate);
    SAL_INFO("vcl.opengl", maDeviceID);
    SAL_INFO("vcl.opengl", maAdapterVendorID);
    SAL_INFO("vcl.opengl", maAdapterDeviceID);
    SAL_INFO("vcl.opengl", maAdapterSubsysID);
    SAL_INFO("vcl.opengl", maDeviceKey);
    SAL_INFO("vcl.opengl", maDeviceString);
 
    OUString aCacheFolder = getCacheFolder();
 
    OUString aCacheFile(aCacheFolder + "/opengl_device.log");
    SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE);
 
    writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion);
    writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate);
    writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID);
    writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID);
    writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID);
    writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID);
    writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey);
    writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString);
 
    // Check if the device is blocked from the downloaded blocklist. If not, check
    // the static list after that. This order is used so that we can later escape
    // out of static blocks (i.e. if we were wrong or something was patched, we
    // can back out our static block without doing a release).
    if (mbRDP)
    {
        SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions");
        return true;
    }
 
    return FindBlocklistedDeviceInList();
}
 
void WinOpenGLDeviceInfo::GetData()
{
    DISPLAY_DEVICEW displayDevice;
    displayDevice.cb = sizeof(displayDevice);
 
    mnWindowsVersion = WindowsOSVersion();
    int deviceIndex = 0;
 
    while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0))
    {
        if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
        {
            break;
        }
        deviceIndex++;
    }
 
    // make sure the string is null terminated
    // (using the term "null" here to mean a zero UTF-16 unit)
    if (wcsnlen(displayDevice.DeviceKey, ArrayLength(displayDevice.DeviceKey))
            == ArrayLength(displayDevice.DeviceKey))
    {
        // we did not find a null
        SAL_WARN("vcl.opengl", "string not null terminated");
        return;
    }
 
    /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */
    /* check that DeviceKey begins with DEVICE_KEY_PREFIX */
    /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */
    if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, ArrayLength(DEVICE_KEY_PREFIX)-1) != 0)
    {
        SAL_WARN("vcl.opengl", "incorrect DeviceKey");
        return;
    }
 
    // chop off DEVICE_KEY_PREFIX
    maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + ArrayLength(DEVICE_KEY_PREFIX)-1;
 
    maDeviceID = o3tl::toU(displayDevice.DeviceID);
    maDeviceString = o3tl::toU(displayDevice.DeviceString);
 
    if (maDeviceID.isEmpty() &&
        (maDeviceString == "RDPDD Chained DD" ||
         (maDeviceString == "RDPUDD Chained DD")))
    {
        // we need to block RDP as it does not provide OpenGL 2.1+
        mbRDP = true;
        SAL_WARN("vcl.opengl", "RDP => blocked");
        return;
    }
 
    /* create a device information set composed of the current display device */
    HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr,
            DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES);
 
    if (devinfo != INVALID_HANDLE_VALUE)
    {
        HKEY key;
        LONG result;
        WCHAR value[255];
        DWORD dwcbData;
        SP_DEVINFO_DATA devinfoData;
        DWORD memberIndex = 0;
 
        devinfoData.cbSize = sizeof(devinfoData);
        /* enumerate device information elements in the device information set */
        while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
        {
            /* get a string that identifies the device's driver key */
            if (SetupDiGetDeviceRegistryPropertyW(devinfo,
                        &devinfoData,
                        SPDRP_DRIVER,
                        nullptr,
                        reinterpret_cast<PBYTE>(value),
                        sizeof(value),
                        nullptr))
            {
                OUString  driverKey("System\\CurrentControlSet\\Control\\Class\\");
                driverKey += o3tl::toU(value);
                result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key);
                if (result == ERROR_SUCCESS)
                {
                    /* we've found the driver we're looking for */
                    dwcbData = sizeof(value);
                    result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
                            reinterpret_cast<LPBYTE>(value), &dwcbData);
                    if (result == ERROR_SUCCESS)
                    {
                        maDriverVersion = OUString(o3tl::toU(value));
                    }
                    else
                    {
                        // If the entry wasn't found, assume the worst (0.0.0.0).
                        maDriverVersion = OUString("0.0.0.0");
                    }
                    dwcbData = sizeof(value);
                    result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
                            reinterpret_cast<LPBYTE>(value), &dwcbData);
                    if (result == ERROR_SUCCESS)
                    {
                        maDriverDate = o3tl::toU(value);
                    }
                    else
                    {
                        // Again, assume the worst
                        maDriverDate = OUString("01-01-1970");
                    }
                    RegCloseKey(key);
                    break;
                }
            }
        }
 
        SetupDiDestroyDeviceInfoList(devinfo);
    }
    else
    {
        SAL_WARN("vcl.opengl", "invalid handle value");
    }
 
    appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4);
    appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4);
    appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8);
 
    // We now check for second display adapter.
 
    // Device interface class for display adapters.
    CLSID GUID_DISPLAY_DEVICE_ARRIVAL;
    HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}",
            &GUID_DISPLAY_DEVICE_ARRIVAL);
    if (hresult == NOERROR)
    {
        devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL,
                nullptr, nullptr,
                DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
 
        if (devinfo != INVALID_HANDLE_VALUE)
        {
            HKEY key;
            LONG result;
            WCHAR value[255];
            DWORD dwcbData;
            SP_DEVINFO_DATA devinfoData;
            DWORD memberIndex = 0;
            devinfoData.cbSize = sizeof(devinfoData);
 
            OUString aAdapterDriver2;
            OUString aDeviceID2;
            OUString aDriverVersion2;
            OUString aDriverDate2;
            uint32_t adapterVendorID2;
            uint32_t adapterDeviceID2;
 
            /* enumerate device information elements in the device information set */
            while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData))
            {
                /* get a string that identifies the device's driver key */
                if (SetupDiGetDeviceRegistryPropertyW(devinfo,
                            &devinfoData,
                            SPDRP_DRIVER,
                            nullptr,
                            reinterpret_cast<PBYTE>(value),
                            sizeof(value),
                            nullptr))
                {
                    OUString driverKey2("System\\CurrentControlSet\\Control\\Class\\");
                    driverKey2 += o3tl::toU(value);
                    result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key);
                    if (result == ERROR_SUCCESS)
                    {
                        dwcbData = sizeof(value);
                        result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr,
                                nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
                        if (result != ERROR_SUCCESS)
                        {
                            continue;
                        }
                        aDeviceID2 = o3tl::toU(value);
                        OUString aAdapterVendorID2String;
                        OUString aAdapterDeviceID2String;
                        adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4);
                        appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4);
                        adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4);
                        appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4);
                        if (maAdapterVendorID == aAdapterVendorID2String &&
                                maAdapterDeviceID == aAdapterDeviceID2String)
                        {
                            RegCloseKey(key);
                            continue;
                        }
 
                        // If this device is missing driver information, it is unlikely to
                        // be a real display adapter.
                        if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers",
                                        aAdapterDriver2, REG_MULTI_SZ))
                        {
                            RegCloseKey(key);
                            continue;
                        }
                        dwcbData = sizeof(value);
                        result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr,
                                reinterpret_cast<LPBYTE>(value), &dwcbData);
                        if (result != ERROR_SUCCESS)
                        {
                            RegCloseKey(key);
                            continue;
                        }
                        aDriverVersion2 = o3tl::toU(value);
                        dwcbData = sizeof(value);
                        result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr,
                                reinterpret_cast<LPBYTE>(value), &dwcbData);
                        if (result != ERROR_SUCCESS)
                        {
                            RegCloseKey(key);
                            continue;
                        }
                        aDriverDate2 = o3tl::toU(value);
                        dwcbData = sizeof(value);
                        result = RegQueryValueExW(key, L"Device Description", nullptr,
                                nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData);
                        if (result != ERROR_SUCCESS)
                        {
                            dwcbData = sizeof(value);
                            result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr,
                                    reinterpret_cast<LPBYTE>(value), &dwcbData);
                        }
                        RegCloseKey(key);
                        if (result == ERROR_SUCCESS)
                        {
                            mbHasDualGPU = true;
                            maDeviceString2 = o3tl::toU(value);
                            maDeviceID2 = aDeviceID2;
                            maDeviceKey2 = driverKey2;
                            maDriverVersion2 = aDriverVersion2;
                            maDriverDate2 = aDriverDate2;
                            appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4);
                            appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4);
                            appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8);
                            break;
                        }
                    }
                }
            }
 
            SetupDiDestroyDeviceInfoList(devinfo);
        }
    }
}
 
// Macro for assigning a device vendor id to a string.
#define DECLARE_VENDOR_ID(name, deviceId) \
    case name: \
        *mpDeviceVendors[id] = deviceId; \
break;
 
OUString WinOpenGLDeviceInfo::GetDeviceVendor(wgl::DeviceVendor id)
{
    assert(id >= 0 && id < wgl::DeviceVendorMax);
 
    if (mpDeviceVendors[id])
        return *mpDeviceVendors[id];
 
    mpDeviceVendors[id] = new OUString();
 
    switch (id)
    {
        DECLARE_VENDOR_ID(wgl::VendorAll, "");
        DECLARE_VENDOR_ID(wgl::VendorIntel, "0x8086");
        DECLARE_VENDOR_ID(wgl::VendorNVIDIA, "0x10de");
        DECLARE_VENDOR_ID(wgl::VendorAMD, "0x1022");
        DECLARE_VENDOR_ID(wgl::VendorATI, "0x1002");
        DECLARE_VENDOR_ID(wgl::VendorMicrosoft, "0x1414");
        // Suppress a warning.
        DECLARE_VENDOR_ID(wgl::DeviceVendorMax, "");
    }
 
    return *mpDeviceVendors[id];
}
 
namespace {
 
 
OUString getBlacklistFile()
{
    OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
    rtl::Bootstrap::expandMacros(url);
 
    return url + "/opengl/opengl_blacklist_windows.xml";
}
 
 
}
 
void WinOpenGLDeviceInfo::FillBlacklist()
{
    OUString aURL = getBlacklistFile();
    WinBlocklistParser aParser(aURL, maDriverInfo);
    try {
        aParser.parse();
    }
    catch (...)
    {
        SAL_WARN("vcl.opengl", "error parsing blacklist");
        maDriverInfo.clear();
    }
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: mbDeleteDevices.

V557 Array overrun is possible. The 'id' index is pointing beyond array bound.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: mbDeleteDevices.