/*
 * ReactOS Device Manager Applet
 * Copyright (C) 2004 - 2005 ReactOS Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
/* $Id: devmgr.c 12852 2005-01-06 13:58:04Z mf $
 *
 * PROJECT:         ReactOS devmgr.dll
 * FILE:            lib/devmgr/misc.c
 * PURPOSE:         ReactOS Device Manager
 * PROGRAMMER:      Thomas Weidenmueller <w3seek@reactos.com>
 * UPDATE HISTORY:
 *      2005/11/24  Created
 */
#include <precomp.h>

#define NDEBUG
#include <debug.h>

HINSTANCE hDllInstance = NULL;


INT
LengthOfStrResource(IN HINSTANCE hInst,
                    IN UINT uID)
{
    HRSRC hrSrc;
    HGLOBAL hRes;
    LPWSTR lpName, lpStr;

    if (hInst == NULL)
    {
        return -1;
    }

    /* There are always blocks of 16 strings */
    lpName = (LPWSTR)MAKEINTRESOURCE((uID >> 4) + 1);

    /* Find the string table block */
    if ((hrSrc = FindResourceW(hInst, lpName, (LPWSTR)RT_STRING)) &&
        (hRes = LoadResource(hInst, hrSrc)) &&
        (lpStr = LockResource(hRes)))
    {
        UINT x;

        /* Find the string we're looking for */
        uID &= 0xF; /* position in the block, same as % 16 */
        for (x = 0; x < uID; x++)
        {
            lpStr += (*lpStr) + 1;
        }

        /* Found the string */
        return (int)(*lpStr);
    }
    return -1;
}


static INT
AllocAndLoadString(OUT LPWSTR *lpTarget,
                   IN HINSTANCE hInst,
                   IN UINT uID)
{
    INT ln;

    ln = LengthOfStrResource(hInst,
                             uID);
    if (ln++ > 0)
    {
        (*lpTarget) = (LPWSTR)LocalAlloc(LMEM_FIXED,
                                         ln * sizeof(WCHAR));
        if ((*lpTarget) != NULL)
        {
            INT Ret;
            if (!(Ret = LoadStringW(hInst, uID, *lpTarget, ln)))
            {
                LocalFree((HLOCAL)(*lpTarget));
            }
            return Ret;
        }
    }
    return 0;
}


static INT
AllocAndLoadStringsCat(OUT LPWSTR *lpTarget,
                       IN HINSTANCE hInst,
                       IN UINT *uID,
                       IN UINT nIDs)
{
    INT ln = 0;
    UINT i;

    for (i = 0;
         i != nIDs;
         i++)
    {
        ln += LengthOfStrResource(hInst,
                                  uID[i]);
    }

    if (ln != 0)
    {
        (*lpTarget) = (LPWSTR)LocalAlloc(LMEM_FIXED,
                                         (ln + 1) * sizeof(WCHAR));
        if ((*lpTarget) != NULL)
        {
            LPWSTR s = *lpTarget;
            INT Ret = 0;

            for (i = 0;
                 i != nIDs;
                 i++)
            {
                if (!(Ret = LoadStringW(hInst, uID[i], s, ln)))
                {
                    LocalFree((HLOCAL)(*lpTarget));
                }

                s += Ret;
            }

            return s - *lpTarget;
        }
    }
    return 0;
}


DWORD
LoadAndFormatString(IN HINSTANCE hInstance,
                    IN UINT uID,
                    OUT LPWSTR *lpTarget,
                    ...)
{
    DWORD Ret = 0;
    LPWSTR lpFormat;
    va_list lArgs;

    if (AllocAndLoadString(&lpFormat,
                           hInstance,
                           uID) != 0)
    {
        va_start(lArgs, lpTarget);
        /* let's use FormatMessage to format it because it has the ability to allocate
           memory automatically */
        Ret = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                             lpFormat,
                             0,
                             0,
                             (LPWSTR)lpTarget,
                             0,
                             &lArgs);
        va_end(lArgs);

        LocalFree((HLOCAL)lpFormat);
    }

    return Ret;
}


DWORD
LoadAndFormatStringsCat(IN HINSTANCE hInstance,
                        IN UINT *uID,
                        IN UINT nIDs,
                        OUT LPWSTR *lpTarget,
                        ...)
{
    DWORD Ret = 0;
    LPWSTR lpFormat;
    va_list lArgs;

    if (AllocAndLoadStringsCat(&lpFormat,
                               hInstance,
                               uID,
                               nIDs) != 0)
    {
        va_start(lArgs, lpTarget);
        /* let's use FormatMessage to format it because it has the ability to allocate
           memory automatically */
        Ret = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                             lpFormat,
                             0,
                             0,
                             (LPWSTR)lpTarget,
                             0,
                             &lArgs);
        va_end(lArgs);

        LocalFree((HLOCAL)lpFormat);
    }

    return Ret;
}


LPARAM
ListViewGetSelectedItemData(IN HWND hwnd)
{
    int Index;

    Index = ListView_GetNextItem(hwnd,
                                 -1,
                                 LVNI_SELECTED);
    if (Index != -1)
    {
        LVITEM li;

        li.mask = LVIF_PARAM;
        li.iItem = Index;
        li.iSubItem = 0;

        if (ListView_GetItem(hwnd,
                             &li))
        {
            return li.lParam;
        }
    }

    return 0;
}


LPWSTR
ConvertMultiByteToUnicode(IN LPCSTR lpMultiByteStr,
                          IN UINT uCodePage)
{
    LPWSTR lpUnicodeStr;
    INT nLength;

    nLength = MultiByteToWideChar(uCodePage,
                                  0,
                                  lpMultiByteStr,
                                  -1,
                                  NULL,
                                  0);
    if (nLength == 0)
        return NULL;

    lpUnicodeStr = HeapAlloc(GetProcessHeap(),
                             0,
                             nLength * sizeof(WCHAR));
    if (lpUnicodeStr == NULL)
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return NULL;
    }

    if (!MultiByteToWideChar(uCodePage,
                             0,
                             lpMultiByteStr,
                             nLength,
                             lpUnicodeStr,
                             nLength))
    {
        HeapFree(GetProcessHeap(),
                 0,
                 lpUnicodeStr);
        return NULL;
    }

    return lpUnicodeStr;
}


BOOL
GetDeviceManufacturerString(IN HDEVINFO DeviceInfoSet,
                            IN PSP_DEVINFO_DATA DeviceInfoData,
                            OUT LPWSTR szBuffer,
                            IN DWORD BufferSize)
{
    DWORD RegDataType;
    BOOL Ret = FALSE;

    if (!SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
                                          DeviceInfoData,
                                          SPDRP_MFG,
                                          &RegDataType,
                                          (PBYTE)szBuffer,
                                          BufferSize * sizeof(WCHAR),
                                          NULL) ||
        RegDataType != REG_SZ)
    {
        szBuffer[0] = L'\0';
        if (LoadString(hDllInstance,
                       IDS_UNKNOWN,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }
    else
    {
        /* FIXME - check string for NULL termination! */
        Ret = TRUE;
    }

    return Ret;
}


BOOL
GetDeviceLocationString(IN DEVINST dnDevInst  OPTIONAL,
                        IN DEVINST dnParentDevInst  OPTIONAL,
                        OUT LPWSTR szBuffer,
                        IN DWORD BufferSize)
{
    DWORD RegDataType;
    ULONG DataSize;
    CONFIGRET cRet;
    LPWSTR szFormatted;
    BOOL Ret = FALSE;

    DataSize = BufferSize * sizeof(WCHAR);
    szBuffer[0] = L'\0';
    if (dnParentDevInst != 0)
    {
        /* query the parent node name */
        if (CM_Get_DevNode_Registry_Property(dnParentDevInst,
                                             CM_DRP_DEVICEDESC,
                                             &RegDataType,
                                             szBuffer,
                                             &DataSize,
                                             0) == CR_SUCCESS &&
             RegDataType == REG_SZ &&
             LoadAndFormatString(hDllInstance,
                                 IDS_DEVONPARENT,
                                 &szFormatted,
                                 szBuffer) != 0)
        {
            wcsncpy(szBuffer,
                    szFormatted,
                    BufferSize - 1);
            szBuffer[BufferSize - 1] = L'\0';
            LocalFree((HLOCAL)szFormatted);
            Ret = TRUE;
        }
    }
    else if (dnDevInst != 0)
    {
        cRet = CM_Get_DevNode_Registry_Property(dnDevInst,
                                                CM_DRP_LOCATION_INFORMATION,
                                                &RegDataType,
                                                szBuffer,
                                                &DataSize,
                                                0);
        if (cRet == CR_SUCCESS && RegDataType == REG_SZ)
        {
            /* FIXME - check string for NULL termination! */
            Ret = TRUE;
        }

        if (Ret && szBuffer[0] >= L'0' && szBuffer[0] <= L'9')
        {
            /* convert the string to an integer value and create a
               formatted string */
            ULONG ulLocation = (ULONG)wcstoul(szBuffer,
                                              NULL,
                                              10);
            if (LoadAndFormatString(hDllInstance,
                                    IDS_LOCATIONSTR,
                                    &szFormatted,
                                    ulLocation,
                                    szBuffer) != 0)
            {
                wcsncpy(szBuffer,
                        szFormatted,
                        BufferSize - 1);
                szBuffer[BufferSize - 1] = L'\0';
                LocalFree((HLOCAL)szFormatted);
            }
            else
                Ret = FALSE;
        }
    }

    if (!Ret &&
        LoadString(hDllInstance,
                   IDS_UNKNOWN,
                   szBuffer,
                   BufferSize))
    {
        Ret = TRUE;
    }

    return Ret;
}


BOOL
GetDeviceStatusString(IN DEVINST DevInst,
                      IN HMACHINE hMachine,
                      OUT LPWSTR szBuffer,
                      IN DWORD BufferSize)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    UINT MessageId = IDS_UNKNOWN;
    BOOL Ret = FALSE;

    szBuffer[0] = L'\0';
    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        if (Status & DN_HAS_PROBLEM)
        {
            UINT uRet;

            uRet = DeviceProblemText(hMachine,
                                     DevInst,
                                     ProblemNumber,
                                     szBuffer,
                                     (BufferSize != 0 ? BufferSize : BufferSize - 1));

            Ret = (uRet != 0 && uRet < BufferSize);
        }
        else
        {
            if (!(Status & (DN_DRIVER_LOADED | DN_STARTED)))
            {
                MessageId = IDS_NODRIVERLOADED;
            }
            else
            {
                MessageId = IDS_DEV_NO_PROBLEM;
            }

            goto GeneralMessage;
        }
    }
    else
    {
GeneralMessage:
        if (LoadString(hDllInstance,
                        MessageId,
                        szBuffer,
                        (int)BufferSize))
        {
            Ret = TRUE;
        }
    }

    return Ret;
}


BOOL
GetDriverProviderString(IN HDEVINFO DeviceInfoSet,
                        IN PSP_DEVINFO_DATA DeviceInfoData,
                        OUT LPWSTR szBuffer,
                        IN DWORD BufferSize)
{
    HKEY hKey;
    DWORD dwSize, dwType;
    BOOL Ret = FALSE;

    szBuffer[0] = L'\0';

    /* get driver provider, date and version */
    hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
                                DeviceInfoData,
                                DICS_FLAG_GLOBAL,
                                0,
                                DIREG_DRV,
                                KEY_QUERY_VALUE);
    if (hKey != INVALID_HANDLE_VALUE)
    {
        /* query the driver provider */
        dwSize = BufferSize;
        if (RegQueryValueEx(hKey,
                            REGSTR_VAL_PROVIDER_NAME,
                            NULL,
                            &dwType,
                            (LPBYTE)szBuffer,
                            &dwSize) == ERROR_SUCCESS &&
            dwType == REG_SZ &&
            szBuffer[0] != L'\0')
        {
            Ret = TRUE;
        }
        else
        {
            szBuffer[0] = L'\0';
        }

        RegCloseKey(hKey);
    }

    if (szBuffer[0] == L'\0')
    {
        /* unable to query the information */
        if (LoadString(hDllInstance,
                       IDS_UNKNOWN,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }

    return Ret;
}


BOOL
GetDriverVersionString(IN HDEVINFO DeviceInfoSet,
                       IN PSP_DEVINFO_DATA DeviceInfoData,
                       OUT LPWSTR szBuffer,
                       IN DWORD BufferSize)
{
    HKEY hKey;
    DWORD dwSize, dwType;
    BOOL Ret = FALSE;

    szBuffer[0] = L'\0';

    /* get driver provider, date and version */
    hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
                                DeviceInfoData,
                                DICS_FLAG_GLOBAL,
                                0,
                                DIREG_DRV,
                                KEY_QUERY_VALUE);
    if (hKey != INVALID_HANDLE_VALUE)
    {
        /* query the driver provider */
        dwSize = BufferSize;
        if (RegQueryValueEx(hKey,
                            L"DriverVersion",
                            NULL,
                            &dwType,
                            (LPBYTE)szBuffer,
                            &dwSize) == ERROR_SUCCESS &&
            dwType == REG_SZ &&
            szBuffer[0] != L'\0')
        {
            Ret = TRUE;
        }
        else
        {
            szBuffer[0] = L'\0';
        }

        RegCloseKey(hKey);
    }

    if (szBuffer[0] == L'\0')
    {
        /* unable to query the information */
        if (LoadString(hDllInstance,
                       IDS_NOTAVAILABLE,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }

    return Ret;
}

BOOL
GetDriverDateString(IN HDEVINFO DeviceInfoSet,
                    IN PSP_DEVINFO_DATA DeviceInfoData,
                    OUT LPWSTR szBuffer,
                    IN DWORD BufferSize)
{
    HKEY hKey;
    FILETIME DriverDate;
    SYSTEMTIME SystemTime, LocalTime;
    DWORD dwSize, dwType;
    BOOL Ret = FALSE;

    szBuffer[0] = L'\0';

    /* get driver provider, date and version */
    hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
                                DeviceInfoData,
                                DICS_FLAG_GLOBAL,
                                0,
                                DIREG_DRV,
                                KEY_QUERY_VALUE);
    if (hKey != INVALID_HANDLE_VALUE)
    {
        /* query the driver provider */
        dwSize = sizeof(FILETIME);
        if (RegQueryValueEx(hKey,
                            L"DriverDateData",
                            NULL,
                            &dwType,
                            (LPBYTE)&DriverDate,
                            &dwSize) == ERROR_SUCCESS &&
            dwType == REG_BINARY &&
            dwSize == sizeof(FILETIME) &&
            FileTimeToSystemTime(&DriverDate,
                                 &SystemTime) &&
            SystemTimeToTzSpecificLocalTime(NULL,
                                            &SystemTime,
                                            &LocalTime) &&
            GetDateFormat(LOCALE_USER_DEFAULT,
                          DATE_SHORTDATE,
                          &LocalTime,
                          NULL,
                          szBuffer,
                          BufferSize) != 0)
        {
            Ret = TRUE;
        }

        RegCloseKey(hKey);
    }

    if (!Ret)
    {
        /* unable to query the information */
        if (LoadString(hDllInstance,
                       IDS_NOTAVAILABLE,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }

    return Ret;
}



BOOL
IsDeviceHidden(IN DEVINST DevInst,
               IN HMACHINE hMachine,
               OUT BOOL *IsHidden)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        *IsHidden = ((Status & DN_NO_SHOW_IN_DM) != 0);
        Ret = TRUE;
    }

    return Ret;
}


BOOL
CanDisableDevice(IN DEVINST DevInst,
                 IN HMACHINE hMachine,
                 OUT BOOL *CanDisable)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        *CanDisable = ((Status & DN_DISABLEABLE) != 0);
        Ret = TRUE;
    }

    return Ret;
}


BOOL
IsDeviceStarted(IN DEVINST DevInst,
                IN HMACHINE hMachine,
                OUT BOOL *IsStarted)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        *IsStarted = ((Status & DN_STARTED) != 0);
        Ret = TRUE;
    }

    return Ret;
}


BOOL
IsDriverInstalled(IN DEVINST DevInst,
                  IN HMACHINE hMachine,
                  OUT BOOL *Installed)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        *Installed = ((Status & DN_HAS_PROBLEM) != 0 ||
                      (Status & (DN_DRIVER_LOADED | DN_STARTED)) != 0);
        Ret = TRUE;
    }

    return Ret;
}


BOOL
EnableDevice(IN HDEVINFO DeviceInfoSet,
             IN PSP_DEVINFO_DATA DevInfoData  OPTIONAL,
             IN BOOL bEnable,
             IN DWORD HardwareProfile  OPTIONAL,
             OUT BOOL *bNeedReboot  OPTIONAL)
{
    SP_PROPCHANGE_PARAMS pcp;
    SP_DEVINSTALL_PARAMS dp;
    DWORD LastErr;
    BOOL Ret = FALSE;

    pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    pcp.HwProfile = HardwareProfile;

    if (bEnable)
    {
        /* try to enable the device on the global profile */
        pcp.StateChange = DICS_ENABLE;
        pcp.Scope = DICS_FLAG_GLOBAL;

        /* ignore errors */
        LastErr = GetLastError();
        if (SetupDiSetClassInstallParams(DeviceInfoSet,
                                         DevInfoData,
                                         &pcp.ClassInstallHeader,
                                         sizeof(SP_PROPCHANGE_PARAMS)))
        {
            SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,
                                      DeviceInfoSet,
                                      DevInfoData);
        }
        SetLastError(LastErr);
    }

    /* try config-specific */
    pcp.StateChange = (bEnable ? DICS_ENABLE : DICS_DISABLE);
    pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;

    if (SetupDiSetClassInstallParams(DeviceInfoSet,
                                     DevInfoData,
                                     &pcp.ClassInstallHeader,
                                     sizeof(SP_PROPCHANGE_PARAMS)) &&
        SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,
                                  DeviceInfoSet,
                                  DevInfoData))
    {
        dp.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
        if (SetupDiGetDeviceInstallParams(DeviceInfoSet,
                                          DevInfoData,
                                          &dp))
        {
            if (bNeedReboot != NULL)
            {
                *bNeedReboot = ((dp.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) != 0);
            }

            Ret = TRUE;
        }
    }
    return Ret;
}


BOOL
GetDeviceTypeString(IN PSP_DEVINFO_DATA DeviceInfoData,
                    OUT LPWSTR szBuffer,
                    IN DWORD BufferSize)
{
    BOOL Ret = FALSE;

    if (!SetupDiGetClassDescription(&DeviceInfoData->ClassGuid,
                                    szBuffer,
                                    BufferSize,
                                    NULL))
    {
        szBuffer[0] = L'\0';
        if (LoadString(hDllInstance,
                       IDS_UNKNOWN,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }
    else
    {
        /* FIXME - check string for NULL termination! */
        Ret = TRUE;
    }

    return Ret;
}


BOOL
GetDeviceDescriptionString(IN HDEVINFO DeviceInfoSet,
                           IN PSP_DEVINFO_DATA DeviceInfoData,
                           OUT LPWSTR szBuffer,
                           IN DWORD BufferSize)
{
    DWORD RegDataType;
    BOOL Ret = FALSE;

    if ((SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
                                          DeviceInfoData,
                                          SPDRP_FRIENDLYNAME,
                                          &RegDataType,
                                          (PBYTE)szBuffer,
                                          BufferSize * sizeof(WCHAR),
                                          NULL) ||
         SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
                                          DeviceInfoData,
                                          SPDRP_DEVICEDESC,
                                          &RegDataType,
                                          (PBYTE)szBuffer,
                                          BufferSize * sizeof(WCHAR),
                                          NULL)) &&
        RegDataType == REG_SZ)
    {
        /* FIXME - check string for NULL termination! */
        Ret = TRUE;
    }
    else
    {
        szBuffer[0] = L'\0';
        if (LoadString(hDllInstance,
                       IDS_UNKNOWNDEVICE,
                       szBuffer,
                       BufferSize))
        {
            Ret = TRUE;
        }
    }

    return Ret;
}


BOOL
FindCurrentDriver(IN HDEVINFO DeviceInfoSet,
                  IN PSP_DEVINFO_DATA DeviceInfoData,
                  OUT PSP_DRVINFO_DATA DriverInfoData)
{
    HKEY hKey = INVALID_HANDLE_VALUE;
    SP_DEVINSTALL_PARAMS InstallParams = {0};
    SP_DRVINFO_DETAIL_DATA DriverInfoDetailData = {0};
    WCHAR InfPath[MAX_PATH];
    WCHAR InfSection[LINE_LEN];
    DWORD dwType, dwLength;
    DWORD i = 0;
    LONG rc;
    BOOL Ret = FALSE;

    /* Steps to find the right driver:
     * 1) Get the device install parameters
     * 2) Open the driver registry key
     * 3) Read the .inf file name
     * 4) Update install params, by setting DI_ENUMSINGLEINF and .inf file name
     * 5) Build class driver list
     * 6) Read inf section and inf section extension from registry
     * 7) Enumerate drivers
     * 8) Find the one who is in the same section as current driver?
     */

    /* 1) Get the install params */
    InstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
    if (!SetupDiGetDeviceInstallParams(DeviceInfoSet,
                                       DeviceInfoData,
                                       &InstallParams))
    {
        DPRINT1("SetupDiSetDeviceInstallParams() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }

#ifdef DI_FLAGSEX_INSTALLEDDRIVER
    InstallParams.FlagsEx |= (DI_FLAGSEX_INSTALLEDDRIVER | DI_FLAGSEX_ALLOWEXCLUDEDDRVS);
    if (SetupDiSetDeviceInstallParams(DeviceInfoSet,
                                      DeviceInfoData,
                                      &InstallParams))
    {
        if (SetupDiBuildDriverInfoList(DeviceInfoSet,
                                       DeviceInfoData,
                                       SPDIT_CLASSDRIVER) &&
            SetupDiEnumDriverInfo(DeviceInfoSet,
                                  DeviceInfoData,
                                  SPDIT_CLASSDRIVER,
                                  0,
                                  DriverInfoData))
        {
            Ret = TRUE;
        }

        goto Cleanup;
    }
    InstallParams.FlagsEx &= ~(DI_FLAGSEX_INSTALLEDDRIVER | DI_FLAGSEX_ALLOWEXCLUDEDDRVS);
#endif

    /* 2) Open the driver registry key */
    hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
                                DeviceInfoData,
                                DICS_FLAG_GLOBAL,
                                0,
                                DIREG_DRV,
                                KEY_QUERY_VALUE);
    if (hKey == INVALID_HANDLE_VALUE)
    {
        DPRINT1("SetupDiOpenDevRegKey() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }

    /* 3) Read the .inf file name */
    dwLength = (sizeof(InfPath) / sizeof(InfPath[0])) - 1;
    rc = RegQueryValueEx(hKey,
                         REGSTR_VAL_INFPATH,
                         0,
                         &dwType,
                         (LPBYTE)InfPath,
                         &dwLength);
    if (rc != ERROR_SUCCESS)
    {
        SetLastError(rc);
        DPRINT1("RegQueryValueEx() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }
    else if (dwType != REG_SZ)
    {
        SetLastError(ERROR_GEN_FAILURE);
        DPRINT1("Expected registry type REG_SZ (%lu), got %lu\n", REG_SZ, dwType);
        goto Cleanup;
    }
    InfPath[(dwLength / sizeof(WCHAR)) - 1] = L'\0';

    /* 4) Update install params, by setting DI_ENUMSINGLEINF and .inf file name */
    InstallParams.Flags |= DI_ENUMSINGLEINF;
    InstallParams.FlagsEx |= DI_FLAGSEX_ALLOWEXCLUDEDDRVS;
    wcscpy(InstallParams.DriverPath, InfPath);
    if (!SetupDiSetDeviceInstallParams(DeviceInfoSet,
                                       DeviceInfoData,
                                       &InstallParams))
    {
        DPRINT1("SetupDiSetDeviceInstallParams() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }

    /* 5) Build class driver list */
    if (!SetupDiBuildDriverInfoList(DeviceInfoSet,
                                    DeviceInfoData,
                                    SPDIT_CLASSDRIVER))
    {
        DPRINT1("SetupDiBuildDriverInfoList() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }

    /* 6) Read inf section and from registry */
    dwLength = (sizeof(InfSection) / sizeof(InfSection[0])) - 1;
    rc = RegQueryValueEx(hKey,
                         REGSTR_VAL_INFSECTION,
                         0,
                         &dwType,
                         (LPBYTE)InfSection,
                         &dwLength);
    if (rc != ERROR_SUCCESS)
    {
        SetLastError(rc);
        DPRINT1("RegQueryValueEx() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }
    else if (dwType != REG_SZ)
    {
        SetLastError(ERROR_GEN_FAILURE);
        DPRINT1("Expected registry type REG_SZ (%lu), got %lu\n", REG_SZ, dwType);
        goto Cleanup;
    }
    InfPath[(dwLength / sizeof(WCHAR)) - 1] = L'\0';

    /* 7) Enumerate drivers */
    DriverInfoData->cbSize = sizeof(SP_DRVINFO_DATA);
    DriverInfoDetailData.cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
    while (SetupDiEnumDriverInfo(DeviceInfoSet,
                                 DeviceInfoData,
                                 SPDIT_CLASSDRIVER,
                                 i,
                                 DriverInfoData))
    {
        /* 8) Find the one who is in the same section as current driver */
        if (!SetupDiGetDriverInfoDetail(DeviceInfoSet,
                                        DeviceInfoData,
                                        DriverInfoData,
                                        &DriverInfoDetailData,
                                        DriverInfoDetailData.cbSize,
                                        NULL) &&
            GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            DPRINT1("SetupDiGetDriverInfoDetail() failed with error 0x%lx\n", GetLastError());
            goto Cleanup;
        }
        if (!wcsicmp(DriverInfoDetailData.SectionName,
                     InfSection) != 0)
        {
            /* We have found the right driver */
            Ret = TRUE;
            goto Cleanup;
        }

        i++;
    }
    if (GetLastError() != ERROR_NO_MORE_ITEMS)
    {
        DPRINT1("SetupDiEnumDriverInfo() failed with error 0x%lx\n", GetLastError());
        goto Cleanup;
    }

    SetLastError(ERROR_NO_DRIVER_SELECTED);

Cleanup:
    if (hKey != INVALID_HANDLE_VALUE)
        RegCloseKey(hKey);
    return Ret;
}


HINSTANCE
LoadAndInitComctl32(VOID)
{
    typedef VOID (WINAPI *PINITCOMMONCONTROLS)(VOID);
    PINITCOMMONCONTROLS pInitCommonControls;
    HINSTANCE hComCtl32;

    hComCtl32 = LoadLibrary(L"comctl32.dll");
    if (hComCtl32 != NULL)
    {
        /* initialize the common controls */
        pInitCommonControls = (PINITCOMMONCONTROLS)GetProcAddress(hComCtl32,
                                                                  "InitCommonControls");
        if (pInitCommonControls == NULL)
        {
            FreeLibrary(hComCtl32);
            return NULL;
        }

        pInitCommonControls();
    }

    return hComCtl32;
}


BOOL
STDCALL
DllMain(IN HINSTANCE hinstDLL,
        IN DWORD dwReason,
	    IN LPVOID lpvReserved)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hinstDLL);
            hDllInstance = hinstDLL;
            break;
    }

    return TRUE;
}
