/**
 * @file lldxhardware.cpp
 * @brief LLDXHardware implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * 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;
 * version 2.1 of the License only.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifdef LL_WINDOWS

// Culled from some Microsoft sample code

#include "linden_common.h"

#define INITGUID
#include <dxdiag.h>
#undef INITGUID

#include <wbemidl.h>
#include <comdef.h>

#include <boost/tokenizer.hpp>

#include "lldxhardware.h"

#include "llerror.h"

#include "llstring.h"
#include "llstl.h"
#include "lltimer.h"

void (*gWriteDebug)(const char* msg) = NULL;
LLDXHardware gDXHardware;

//-----------------------------------------------------------------------------
// Defines, and constants
//-----------------------------------------------------------------------------
#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }
#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p);   (p)=NULL; } }
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }

typedef BOOL ( WINAPI* PfnCoSetProxyBlanket )( IUnknown* pProxy, DWORD dwAuthnSvc, DWORD dwAuthzSvc,
                                               OLECHAR* pServerPrincName, DWORD dwAuthnLevel, DWORD dwImpLevel,
                                               RPC_AUTH_IDENTITY_HANDLE pAuthInfo, DWORD dwCapabilities );

HRESULT GetVideoMemoryViaWMI(WCHAR* strInputDeviceID, DWORD* pdwAdapterRam)
{
    HRESULT hr;
    bool bGotMemory = false;
    IWbemLocator* pIWbemLocator = nullptr;
    IWbemServices* pIWbemServices = nullptr;
    BSTR pNamespace = nullptr;

    *pdwAdapterRam = 0;
    CoInitializeEx(0, COINIT_APARTMENTTHREADED);

    hr = CoCreateInstance( CLSID_WbemLocator,
                           nullptr,
                           CLSCTX_INPROC_SERVER,
                           IID_IWbemLocator,
                           ( LPVOID* )&pIWbemLocator );
#ifdef PRINTF_DEBUGGING
    if( FAILED( hr ) ) wprintf( L"WMI: CoCreateInstance failed: 0x%0.8x\n", hr );
#endif

    if( SUCCEEDED( hr ) && pIWbemLocator )
    {
        // Using the locator, connect to WMI in the given namespace.
        pNamespace = SysAllocString( L"\\\\.\\root\\cimv2" );

        hr = pIWbemLocator->ConnectServer( pNamespace, nullptr, nullptr, 0L,
                                           0L, nullptr, nullptr, &pIWbemServices );
#ifdef PRINTF_DEBUGGING
        if( FAILED( hr ) ) wprintf( L"WMI: pIWbemLocator->ConnectServer failed: 0x%0.8x\n", hr );
#endif
        if( SUCCEEDED( hr ) && pIWbemServices != 0 )
        {
            HINSTANCE hinstOle32 = nullptr;

            hinstOle32 = LoadLibraryW( L"ole32.dll" );
            if( hinstOle32 )
            {
                PfnCoSetProxyBlanket pfnCoSetProxyBlanket = nullptr;

                pfnCoSetProxyBlanket = ( PfnCoSetProxyBlanket )GetProcAddress( hinstOle32, "CoSetProxyBlanket" );
                if( pfnCoSetProxyBlanket != 0 )
                {
                    // Switch security level to IMPERSONATE.
                    pfnCoSetProxyBlanket( pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr,
                                          RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, 0 );
                }

                FreeLibrary( hinstOle32 );
            }

            IEnumWbemClassObject* pEnumVideoControllers = nullptr;
            BSTR pClassName = nullptr;

            pClassName = SysAllocString( L"Win32_VideoController" );

            hr = pIWbemServices->CreateInstanceEnum( pClassName, 0,
                                                     nullptr, &pEnumVideoControllers );
#ifdef PRINTF_DEBUGGING
            if( FAILED( hr ) ) wprintf( L"WMI: pIWbemServices->CreateInstanceEnum failed: 0x%0.8x\n", hr );
#endif

            if( SUCCEEDED( hr ) && pEnumVideoControllers )
            {
                IWbemClassObject* pVideoControllers[10] = {0};
                DWORD uReturned = 0;
                BSTR pPropName = nullptr;

                // Get the first one in the list
                pEnumVideoControllers->Reset();
                hr = pEnumVideoControllers->Next( 5000,             // timeout in 5 seconds
                                                  10,                  // return the first 10
                                                  pVideoControllers,
                                                  &uReturned );
#ifdef PRINTF_DEBUGGING
                if( FAILED( hr ) ) wprintf( L"WMI: pEnumVideoControllers->Next failed: 0x%0.8x\n", hr );
                if( uReturned == 0 ) wprintf( L"WMI: pEnumVideoControllers uReturned == 0\n" );
#endif

                VARIANT var;
                if( SUCCEEDED( hr ) )
                {
                    bool bFound = false;
                    for( UINT iController = 0; iController < uReturned; iController++ )
                    {
                        if ( !pVideoControllers[iController] )
                            continue;

                        // if strInputDeviceID is set find this specific device and return memory or specific device
                        // if strInputDeviceID is not set return the best device
                        if (strInputDeviceID)
                        {
                            pPropName = SysAllocString( L"PNPDeviceID" );
                            hr = pVideoControllers[iController]->Get( pPropName, 0L, &var, nullptr, nullptr );
#ifdef PRINTF_DEBUGGING
                            if( FAILED( hr ) )
                                wprintf( L"WMI: pVideoControllers[iController]->Get PNPDeviceID failed: 0x%0.8x\n", hr );
#endif
                            if( SUCCEEDED( hr ) && strInputDeviceID)
                            {
                                if( wcsstr( var.bstrVal, strInputDeviceID ) != 0 )
                                    bFound = true;
                            }
                            VariantClear( &var );
                            if( pPropName ) SysFreeString( pPropName );
                        }

                        if( bFound || !strInputDeviceID )
                        {
                            pPropName = SysAllocString( L"AdapterRAM" );
                            hr = pVideoControllers[iController]->Get( pPropName, 0L, &var, nullptr, nullptr );
#ifdef PRINTF_DEBUGGING
                            if( FAILED( hr ) )
                                wprintf( L"WMI: pVideoControllers[iController]->Get AdapterRAM failed: 0x%0.8x\n",
                                         hr );
#endif
                            if( SUCCEEDED( hr ) )
                            {
                                bGotMemory = true;
                                *pdwAdapterRam = llmax(var.ulVal, *pdwAdapterRam);
                            }
                            VariantClear( &var );
                            if( pPropName ) SysFreeString( pPropName );
                        }

                        SAFE_RELEASE( pVideoControllers[iController] );

                        if (bFound)
                        {
                            break;
                        }
                    }
                }
            }

            if( pClassName )
                SysFreeString( pClassName );
            SAFE_RELEASE( pEnumVideoControllers );
        }

        if( pNamespace )
            SysFreeString( pNamespace );
        SAFE_RELEASE( pIWbemServices );
    }

    SAFE_RELEASE( pIWbemLocator );

    CoUninitialize();

    if( bGotMemory )
        return S_OK;
    else
        return E_FAIL;
}

//static
U32 LLDXHardware::getMBVideoMemoryViaWMI()
{
    DWORD vram = 0;
    if (SUCCEEDED(GetVideoMemoryViaWMI(NULL, &vram)))
    {
        return vram / (1024 * 1024);;
    }
    return 0;
}

//Getting the version of graphics controller driver via WMI
std::string LLDXHardware::getDriverVersionWMI(EGPUVendor vendor)
{
    std::string mDriverVersion;
    HRESULT hres;
    CoInitializeEx(0, COINIT_APARTMENTTHREADED);
    IWbemLocator *pLoc = NULL;

    hres = CoCreateInstance(
        CLSID_WbemLocator,
        0,
        CLSCTX_INPROC_SERVER,
        IID_IWbemLocator, (LPVOID *)&pLoc);

    if (FAILED(hres))
    {
        LL_DEBUGS("AppInit") << "Failed to initialize COM library. Error code = 0x" << hres << LL_ENDL;
        return std::string();                  // Program has failed.
    }

    IWbemServices *pSvc = NULL;

    // Connect to the root\cimv2 namespace with
    // the current user and obtain pointer pSvc
    // to make IWbemServices calls.
    hres = pLoc->ConnectServer(
        _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
        NULL,                    // User name. NULL = current user
        NULL,                    // User password. NULL = current
        0,                       // Locale. NULL indicates current
        NULL,                    // Security flags.
        0,                       // Authority (e.g. Kerberos)
        0,                       // Context object
        &pSvc                    // pointer to IWbemServices proxy
        );

    if (FAILED(hres))
    {
        LL_WARNS("AppInit") << "Could not connect. Error code = 0x" << hres << LL_ENDL;
        pLoc->Release();
        CoUninitialize();
        return std::string();                // Program has failed.
    }

    LL_DEBUGS("AppInit") << "Connected to ROOT\\CIMV2 WMI namespace" << LL_ENDL;

    // Set security levels on the proxy -------------------------
    hres = CoSetProxyBlanket(
        pSvc,                        // Indicates the proxy to set
        RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
        RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
        NULL,                        // Server principal name
        RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
        NULL,                        // client identity
        EOAC_NONE                    // proxy capabilities
        );

    if (FAILED(hres))
    {
        LL_WARNS("AppInit") << "Could not set proxy blanket. Error code = 0x" << hres << LL_ENDL;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return std::string();               // Program has failed.
    }
    IEnumWbemClassObject* pEnumerator = NULL;

    // Get the data from the query
    ULONG uReturn = 0;
    hres = pSvc->ExecQuery(
        bstr_t("WQL"),
        bstr_t("SELECT * FROM Win32_VideoController"), //Consider using Availability to filter out disabled controllers
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL,
        &pEnumerator);

    if (FAILED(hres))
    {
        LL_WARNS("AppInit") << "Query for operating system name failed." << " Error code = 0x" << hres << LL_ENDL;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return std::string();               // Program has failed.
    }

    while (pEnumerator)
    {
        IWbemClassObject *pclsObj = NULL;
        HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
            &pclsObj, &uReturn);

        if (0 == uReturn)
        {
            break;               // If quantity less then 1.
        }

        if (vendor != GPU_ANY)
        {
            VARIANT vtCaptionProp;
            // Might be preferable to check "AdapterCompatibility" here instead of caption.
            hr = pclsObj->Get(L"Caption", 0, &vtCaptionProp, 0, 0);

            if (FAILED(hr))
            {
                LL_WARNS("AppInit") << "Query for Caption property failed." << " Error code = 0x" << hr << LL_ENDL;
                pSvc->Release();
                pLoc->Release();
                CoUninitialize();
                return std::string();               // Program has failed.
            }

            // use characters in the returned driver version
            BSTR caption(vtCaptionProp.bstrVal);

            //convert BSTR to std::string
            std::wstring ws(caption, SysStringLen(caption));
            std::string caption_str = ll_convert_wide_to_string(ws);
            LLStringUtil::toLower(caption_str);

            bool found = false;
            switch (vendor)
            {
            case GPU_INTEL:
                found = caption_str.find("intel") != std::string::npos;
                break;
            case GPU_NVIDIA:
                found = caption_str.find("nvidia") != std::string::npos;
                break;
            case GPU_AMD:
                found = caption_str.find("amd") != std::string::npos
                        || caption_str.find("ati ") != std::string::npos
                        || caption_str.find("radeon") != std::string::npos;
                break;
            default:
                break;
            }

            if (found)
            {
                VariantClear(&vtCaptionProp);
            }
            else
            {
                VariantClear(&vtCaptionProp);
                pclsObj->Release();
                continue;
            }
        }

        VARIANT vtVersionProp;

        // Get the value of the DriverVersion property
        hr = pclsObj->Get(L"DriverVersion", 0, &vtVersionProp, 0, 0);

        if (FAILED(hr))
        {
            LL_WARNS("AppInit") << "Query for DriverVersion property failed." << " Error code = 0x" << hr << LL_ENDL;
            pSvc->Release();
            pLoc->Release();
            CoUninitialize();
            return std::string();               // Program has failed.
        }

        // use characters in the returned driver version
        BSTR driverVersion(vtVersionProp.bstrVal);

        //convert BSTR to std::string
        std::wstring ws(driverVersion, SysStringLen(driverVersion));
        std::string str = ll_convert_wide_to_string(ws);
        LL_INFOS("AppInit") << " DriverVersion : " << str << LL_ENDL;

        if (mDriverVersion.empty())
        {
            mDriverVersion = str;
        }
        else if (mDriverVersion != str)
        {
            if (vendor == GPU_ANY)
            {
                // Expected from systems with gpus from different vendors
                LL_INFOS("DriverVersion") << "Multiple video drivers detected. Version of second driver: " << str << LL_ENDL;
            }
            else
            {
                // Not Expected!
                LL_WARNS("DriverVersion") << "Multiple video drivers detected from same vendor. Version of second driver : " << str << LL_ENDL;
            }
        }

        VariantClear(&vtVersionProp);
        pclsObj->Release();
    }

    // Cleanup
    // ========
    if (pSvc)
    {
        pSvc->Release();
    }
    if (pLoc)
    {
        pLoc->Release();
    }
    if (pEnumerator)
    {
        pEnumerator->Release();
    }

    // supposed to always call CoUninitialize even if init returned false
    CoUninitialize();

    return mDriverVersion;
}

void get_wstring(IDxDiagContainer* containerp, WCHAR* wszPropName, WCHAR* wszPropValue, int outputSize)
{
    HRESULT hr;
    VARIANT var;

    VariantInit( &var );
    hr = containerp->GetProp(wszPropName, &var );
    if( SUCCEEDED(hr) )
    {
        // Switch off the type.  There's 4 different types:
        switch( var.vt )
        {
            case VT_UI4:
                swprintf( wszPropValue, outputSize, L"%d", var.ulVal ); /* Flawfinder: ignore */
                break;
            case VT_I4:
                swprintf( wszPropValue, outputSize, L"%d", var.lVal );  /* Flawfinder: ignore */
                break;
            case VT_BOOL:
                wcscpy( wszPropValue, (var.boolVal) ? L"true" : L"false" ); /* Flawfinder: ignore */
                break;
            case VT_BSTR:
                wcsncpy( wszPropValue, var.bstrVal, outputSize-1 ); /* Flawfinder: ignore */
                wszPropValue[outputSize-1] = 0;
                break;
        }
    }
    // Clear the variant (this is needed to free BSTR memory)
    VariantClear( &var );
}

std::string get_string(IDxDiagContainer *containerp, WCHAR *wszPropName)
{
    WCHAR wszPropValue[256];
    get_wstring(containerp, wszPropName, wszPropValue, 256);

    return utf16str_to_utf8str(wszPropValue);
}


LLVersion::LLVersion()
{
    mValid = false;
    S32 i;
    for (i = 0; i < 4; i++)
    {
        mFields[i] = 0;
    }
}

bool LLVersion::set(const std::string &version_string)
{
    S32 i;
    for (i = 0; i < 4; i++)
    {
        mFields[i] = 0;
    }
    // Split the version string.
    std::string str(version_string);
    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
    boost::char_separator<char> sep(".", "", boost::keep_empty_tokens);
    tokenizer tokens(str, sep);

    tokenizer::iterator iter = tokens.begin();
    S32 count = 0;
    for (;(iter != tokens.end()) && (count < 4);++iter)
    {
        mFields[count] = atoi(iter->c_str());
        count++;
    }
    if (count < 4)
    {
        //LL_WARNS() << "Potentially bogus version string!" << version_string << LL_ENDL;
        for (i = 0; i < 4; i++)
        {
            mFields[i] = 0;
        }
        mValid = false;
    }
    else
    {
        mValid = true;
    }
    return mValid;
}

S32 LLVersion::getField(const S32 field_num)
{
    if (!mValid)
    {
        return -1;
    }
    else
    {
        return mFields[field_num];
    }
}

std::string LLDXDriverFile::dump()
{
    if (gWriteDebug)
    {
        gWriteDebug("Filename:");
        gWriteDebug(mName.c_str());
        gWriteDebug("\n");
        gWriteDebug("Ver:");
        gWriteDebug(mVersionString.c_str());
        gWriteDebug("\n");
        gWriteDebug("Date:");
        gWriteDebug(mDateString.c_str());
        gWriteDebug("\n");
    }
    LL_INFOS() << mFilepath << LL_ENDL;
    LL_INFOS() << mName << LL_ENDL;
    LL_INFOS() << mVersionString << LL_ENDL;
    LL_INFOS() << mDateString << LL_ENDL;

    return "";
}

LLDXDevice::~LLDXDevice()
{
    for_each(mDriverFiles.begin(), mDriverFiles.end(), DeletePairedPointer());
    mDriverFiles.clear();
}

std::string LLDXDevice::dump()
{
    if (gWriteDebug)
    {
        gWriteDebug("StartDevice\n");
        gWriteDebug("DeviceName:");
        gWriteDebug(mName.c_str());
        gWriteDebug("\n");
        gWriteDebug("PCIString:");
        gWriteDebug(mPCIString.c_str());
        gWriteDebug("\n");
    }
    LL_INFOS() << LL_ENDL;
    LL_INFOS() << "DeviceName:" << mName << LL_ENDL;
    LL_INFOS() << "PCIString:" << mPCIString << LL_ENDL;
    LL_INFOS() << "Drivers" << LL_ENDL;
    LL_INFOS() << "-------" << LL_ENDL;
    for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
             end = mDriverFiles.end();
         iter != end; iter++)
    {
        LLDXDriverFile *filep = iter->second;
        filep->dump();
    }
    if (gWriteDebug)
    {
        gWriteDebug("EndDevice\n");
    }

    return "";
}

LLDXDriverFile *LLDXDevice::findDriver(const std::string &driver)
{
    for (driver_file_map_t::iterator iter = mDriverFiles.begin(),
             end = mDriverFiles.end();
         iter != end; iter++)
    {
        LLDXDriverFile *filep = iter->second;
        if (!utf8str_compare_insensitive(filep->mName,driver))
        {
            return filep;
        }
    }

    return NULL;
}

LLDXHardware::LLDXHardware()
{
    mVRAM = 0;
    gWriteDebug = NULL;
}

void LLDXHardware::cleanup()
{
  // for_each(mDevices.begin(), mDevices.end(), DeletePairedPointer());
  // mDevices.clear();
}

/*
std::string LLDXHardware::dumpDevices()
{
    if (gWriteDebug)
    {
        gWriteDebug("\n");
        gWriteDebug("StartAllDevices\n");
    }
    for (device_map_t::iterator iter = mDevices.begin(),
             end = mDevices.end();
         iter != end; iter++)
    {
        LLDXDevice *devicep = iter->second;
        devicep->dump();
    }
    if (gWriteDebug)
    {
        gWriteDebug("EndAllDevices\n\n");
    }
    return "";
}

LLDXDevice *LLDXHardware::findDevice(const std::string &vendor, const std::string &devices)
{
    // Iterate through different devices tokenized in devices string
    std::string str(devices);
    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
    boost::char_separator<char> sep("|", "", boost::keep_empty_tokens);
    tokenizer tokens(str, sep);

    tokenizer::iterator iter = tokens.begin();
    for (;iter != tokens.end();++iter)
    {
        std::string dev_str = *iter;
        for (device_map_t::iterator iter = mDevices.begin(),
                 end = mDevices.end();
             iter != end; iter++)
        {
            LLDXDevice *devicep = iter->second;
            if ((devicep->mVendorID == vendor)
                && (devicep->mDeviceID == dev_str))
            {
                return devicep;
            }
        }
    }

    return NULL;
}
*/

bool LLDXHardware::getInfo(bool vram_only)
{
    LLTimer hw_timer;
    bool ok = false;
    HRESULT       hr;

    // CLSID_DxDiagProvider does not work with Multithreaded?
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    IDxDiagProvider *dx_diag_providerp = NULL;
    IDxDiagContainer *dx_diag_rootp = NULL;
    IDxDiagContainer *devices_containerp = NULL;
    // IDxDiagContainer *system_device_containerp= NULL;
    IDxDiagContainer *device_containerp = NULL;
    IDxDiagContainer *file_containerp = NULL;
    IDxDiagContainer *driver_containerp = NULL;
    DWORD dw_device_count;

    mVRAM = 0;

    // CoCreate a IDxDiagProvider*
    LL_DEBUGS("AppInit") << "CoCreateInstance IID_IDxDiagProvider" << LL_ENDL;
    hr = CoCreateInstance(CLSID_DxDiagProvider,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IDxDiagProvider,
                          (LPVOID*) &dx_diag_providerp);

    if (FAILED(hr))
    {
        LL_WARNS("AppInit") << "No DXDiag provider found!  DirectX 9 not installed!" << LL_ENDL;
        gWriteDebug("No DXDiag provider found!  DirectX 9 not installed!\n");
        goto LCleanup;
    }
    if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
    {
        // Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize
        // Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are
        // digital signed as logo'd by WHQL which may connect via internet to update
        // WHQL certificates.
        DXDIAG_INIT_PARAMS dx_diag_init_params;
        ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS));

        dx_diag_init_params.dwSize                  = sizeof(DXDIAG_INIT_PARAMS);
        dx_diag_init_params.dwDxDiagHeaderVersion   = DXDIAG_DX9_SDK_VERSION;
        dx_diag_init_params.bAllowWHQLChecks        = TRUE;
        dx_diag_init_params.pReserved               = NULL;

        LL_DEBUGS("AppInit") << "dx_diag_providerp->Initialize" << LL_ENDL;
        hr = dx_diag_providerp->Initialize(&dx_diag_init_params);
        if(FAILED(hr))
        {
            goto LCleanup;
        }

        LL_DEBUGS("AppInit") << "dx_diag_providerp->GetRootContainer" << LL_ENDL;
        hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp );
        if(FAILED(hr) || !dx_diag_rootp)
        {
            goto LCleanup;
        }

        HRESULT hr;

        // Get display driver information
        LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer" << LL_ENDL;
        hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp);
        if(FAILED(hr) || !devices_containerp)
        {
            // do not release 'dirty' devices_containerp at this stage, only dx_diag_rootp
            devices_containerp = NULL;
            goto LCleanup;
        }

        // make sure there is something inside
        hr = devices_containerp->GetNumberOfChildContainers(&dw_device_count);
        if (FAILED(hr) || dw_device_count == 0)
        {
            goto LCleanup;
        }

        // Get device 0
        // By default 0 device is the primary one, howhever in case of various hybrid graphics
        // like itegrated AMD and PCI AMD GPUs system might switch.
        LL_DEBUGS("AppInit") << "devices_containerp->GetChildContainer" << LL_ENDL;
        hr = devices_containerp->GetChildContainer(L"0", &device_containerp);
        if(FAILED(hr) || !device_containerp)
        {
            goto LCleanup;
        }

        DWORD vram = 0;

        WCHAR deviceID[512];

        get_wstring(device_containerp, L"szDeviceID", deviceID, 512);
        // Example: searches id like 1F06 in pnp string (aka VEN_10DE&DEV_1F06)
        // doesn't seem to work on some systems since format is unrecognizable
        // but in such case keyDeviceID works
        if (SUCCEEDED(GetVideoMemoryViaWMI(deviceID, &vram)))
        {
            mVRAM = vram/(1024*1024);
        }
        else
        {
            get_wstring(device_containerp, L"szKeyDeviceID", deviceID, 512);
            LL_WARNS() << "szDeviceID" << deviceID << LL_ENDL;
            // '+9' to avoid ENUM\\PCI\\ prefix
            // Returns string like Enum\\PCI\\VEN_10DE&DEV_1F06&SUBSYS...
            // and since GetVideoMemoryViaWMI searches by PNPDeviceID it is sufficient
            if (SUCCEEDED(GetVideoMemoryViaWMI(deviceID + 9, &vram)))
            {
                mVRAM = vram / (1024 * 1024);
            }
        }

        if (mVRAM == 0)
        { // Get the English VRAM string
          std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish");

          // We don't need the device any more
          SAFE_RELEASE(device_containerp);

          // Dump the string as an int into the structure
          char *stopstring;
          mVRAM = strtol(ram_str.c_str(), &stopstring, 10);
          LL_INFOS("AppInit") << "VRAM Detected: " << mVRAM << " DX9 string: " << ram_str << LL_ENDL;
        }

        if (vram_only)
        {
            ok = true;
            goto LCleanup;
        }


        /* for now, we ONLY do vram_only the rest of this
           is commented out, to ensure no-one is tempted
           to use it

        // Now let's get device and driver information
        // Get the IDxDiagContainer object called "DxDiag_SystemDevices".
        // This call may take some time while dxdiag gathers the info.
        DWORD num_devices = 0;
        WCHAR wszContainer[256];
        LL_DEBUGS("AppInit") << "dx_diag_rootp->GetChildContainer DxDiag_SystemDevices" << LL_ENDL;
        hr = dx_diag_rootp->GetChildContainer(L"DxDiag_SystemDevices", &system_device_containerp);
        if (FAILED(hr))
        {
            goto LCleanup;
        }

        hr = system_device_containerp->GetNumberOfChildContainers(&num_devices);
        if (FAILED(hr))
        {
            goto LCleanup;
        }

        LL_DEBUGS("AppInit") << "DX9 iterating over devices" << LL_ENDL;
        S32 device_num = 0;
        for (device_num = 0; device_num < (S32)num_devices; device_num++)
        {
            hr = system_device_containerp->EnumChildContainerNames(device_num, wszContainer, 256);
            if (FAILED(hr))
            {
                goto LCleanup;
            }

            hr = system_device_containerp->GetChildContainer(wszContainer, &device_containerp);
            if (FAILED(hr) || device_containerp == NULL)
            {
                goto LCleanup;
            }

            std::string device_name = get_string(device_containerp, L"szDescription");

            std::string device_id = get_string(device_containerp, L"szDeviceID");

            LLDXDevice *dxdevicep = new LLDXDevice;
            dxdevicep->mName = device_name;
            dxdevicep->mPCIString = device_id;
            mDevices[dxdevicep->mPCIString] = dxdevicep;

            // Split the PCI string based on vendor, device, subsys, rev.
            std::string str(device_id);
            typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
            boost::char_separator<char> sep("&\\", "", boost::keep_empty_tokens);
            tokenizer tokens(str, sep);

            tokenizer::iterator iter = tokens.begin();
            S32 count = 0;
            bool valid = true;
            for (;(iter != tokens.end()) && (count < 3);++iter)
            {
                switch (count)
                {
                case 0:
                    if (strcmp(iter->c_str(), "PCI"))
                    {
                        valid = false;
                    }
                    break;
                case 1:
                    dxdevicep->mVendorID = iter->c_str();
                    break;
                case 2:
                    dxdevicep->mDeviceID = iter->c_str();
                    break;
                default:
                    // Ignore it
                    break;
                }
                count++;
            }




            // Now, iterate through the related drivers
            hr = device_containerp->GetChildContainer(L"Drivers", &driver_containerp);
            if (FAILED(hr) || !driver_containerp)
            {
                goto LCleanup;
            }

            DWORD num_files = 0;
            hr = driver_containerp->GetNumberOfChildContainers(&num_files);
            if (FAILED(hr))
            {
                goto LCleanup;
            }

            S32 file_num = 0;
            for (file_num = 0; file_num < (S32)num_files; file_num++ )
            {

                hr = driver_containerp->EnumChildContainerNames(file_num, wszContainer, 256);
                if (FAILED(hr))
                {
                    goto LCleanup;
                }

                hr = driver_containerp->GetChildContainer(wszContainer, &file_containerp);
                if (FAILED(hr) || file_containerp == NULL)
                {
                    goto LCleanup;
                }

                std::string driver_path = get_string(file_containerp, L"szPath");
                std::string driver_name = get_string(file_containerp, L"szName");
                std::string driver_version = get_string(file_containerp, L"szVersion");
                std::string driver_date = get_string(file_containerp, L"szDatestampEnglish");

                LLDXDriverFile *dxdriverfilep = new LLDXDriverFile;
                dxdriverfilep->mName = driver_name;
                dxdriverfilep->mFilepath= driver_path;
                dxdriverfilep->mVersionString = driver_version;
                dxdriverfilep->mVersion.set(driver_version);
                dxdriverfilep->mDateString = driver_date;

                dxdevicep->mDriverFiles[driver_name] = dxdriverfilep;

                SAFE_RELEASE(file_containerp);
            }
            SAFE_RELEASE(device_containerp);
        }
        */
    }

    // dumpDevices();
    ok = true;

LCleanup:
    if (!ok)
    {
        LL_WARNS("AppInit") << "DX9 probe failed" << LL_ENDL;
        gWriteDebug("DX9 probe failed\n");
    }

    SAFE_RELEASE(file_containerp);
    SAFE_RELEASE(driver_containerp);
    SAFE_RELEASE(device_containerp);
    SAFE_RELEASE(devices_containerp);
    SAFE_RELEASE(dx_diag_rootp);
    SAFE_RELEASE(dx_diag_providerp);

    CoUninitialize();

    return ok;
    }

LLSD LLDXHardware::getDisplayInfo()
{
    LLTimer hw_timer;
    HRESULT       hr;
    LLSD ret;
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    IDxDiagProvider *dx_diag_providerp = NULL;
    IDxDiagContainer *dx_diag_rootp = NULL;
    IDxDiagContainer *devices_containerp = NULL;
    IDxDiagContainer *device_containerp = NULL;
    IDxDiagContainer *file_containerp = NULL;
    IDxDiagContainer *driver_containerp = NULL;
    DWORD dw_device_count;

    // CoCreate a IDxDiagProvider*
    LL_INFOS() << "CoCreateInstance IID_IDxDiagProvider" << LL_ENDL;
    hr = CoCreateInstance(CLSID_DxDiagProvider,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IDxDiagProvider,
                          (LPVOID*) &dx_diag_providerp);

    if (FAILED(hr))
    {
        LL_WARNS() << "No DXDiag provider found!  DirectX 9 not installed!" << LL_ENDL;
        gWriteDebug("No DXDiag provider found!  DirectX 9 not installed!\n");
        goto LCleanup;
    }
    if (SUCCEEDED(hr)) // if FAILED(hr) then dx9 is not installed
    {
        // Fill out a DXDIAG_INIT_PARAMS struct and pass it to IDxDiagContainer::Initialize
        // Passing in TRUE for bAllowWHQLChecks, allows dxdiag to check if drivers are
        // digital signed as logo'd by WHQL which may connect via internet to update
        // WHQL certificates.
        DXDIAG_INIT_PARAMS dx_diag_init_params;
        ZeroMemory(&dx_diag_init_params, sizeof(DXDIAG_INIT_PARAMS));

        dx_diag_init_params.dwSize                  = sizeof(DXDIAG_INIT_PARAMS);
        dx_diag_init_params.dwDxDiagHeaderVersion   = DXDIAG_DX9_SDK_VERSION;
        dx_diag_init_params.bAllowWHQLChecks        = TRUE;
        dx_diag_init_params.pReserved               = NULL;

        LL_INFOS() << "dx_diag_providerp->Initialize" << LL_ENDL;
        hr = dx_diag_providerp->Initialize(&dx_diag_init_params);
        if(FAILED(hr))
        {
            goto LCleanup;
        }

        LL_INFOS() << "dx_diag_providerp->GetRootContainer" << LL_ENDL;
        hr = dx_diag_providerp->GetRootContainer( &dx_diag_rootp );
        if(FAILED(hr) || !dx_diag_rootp)
        {
            goto LCleanup;
        }

        HRESULT hr;

        // Get display driver information
        LL_INFOS() << "dx_diag_rootp->GetChildContainer" << LL_ENDL;
        hr = dx_diag_rootp->GetChildContainer(L"DxDiag_DisplayDevices", &devices_containerp);
        if(FAILED(hr) || !devices_containerp)
        {
            // do not release 'dirty' devices_containerp at this stage, only dx_diag_rootp
            devices_containerp = NULL;
            goto LCleanup;
        }

        // make sure there is something inside
        hr = devices_containerp->GetNumberOfChildContainers(&dw_device_count);
        if (FAILED(hr) || dw_device_count == 0)
        {
            goto LCleanup;
        }

        // Get device 0
        LL_INFOS() << "devices_containerp->GetChildContainer" << LL_ENDL;
        hr = devices_containerp->GetChildContainer(L"0", &device_containerp);
        if(FAILED(hr) || !device_containerp)
        {
            goto LCleanup;
        }

        // Get the English VRAM string
        std::string ram_str = get_string(device_containerp, L"szDisplayMemoryEnglish");


        // Dump the string as an int into the structure
        char *stopstring;
        ret["VRAM"] = strtol(ram_str.c_str(), &stopstring, 10);
        std::string device_name = get_string(device_containerp, L"szDescription");
        ret["DeviceName"] = device_name;
        std::string device_driver=  get_string(device_containerp, L"szDriverVersion");
        ret["DriverVersion"] = device_driver;

        // ATI has a slightly different version string
        if(device_name.length() >= 4 && device_name.substr(0,4) == "ATI ")
        {
            // get the key
            HKEY hKey;
            const DWORD RV_SIZE = 100;
            WCHAR release_version[RV_SIZE];

            // Hard coded registry entry.  Using this since it's simpler for now.
            // And using EnumDisplayDevices to get a registry key also requires
            // a hard coded Query value.
            if(ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\ATI Technologies\\CBT"), &hKey))
            {
                // get the value
                DWORD dwType = REG_SZ;
                DWORD dwSize = sizeof(WCHAR) * RV_SIZE;
                if(ERROR_SUCCESS == RegQueryValueEx(hKey, TEXT("ReleaseVersion"),
                    NULL, &dwType, (LPBYTE)release_version, &dwSize))
                {
                    // print the value
                    // windows doesn't guarantee to be null terminated
                    release_version[RV_SIZE - 1] = NULL;
                    ret["DriverVersion"] = utf16str_to_utf8str(release_version);

                }
                RegCloseKey(hKey);
            }
        }
    }

LCleanup:
    if (!ret.isMap() || (ret.size() == 0))
    {
        LL_INFOS() << "Failed to get data, cleaning up" << LL_ENDL;
    }
    SAFE_RELEASE(file_containerp);
    SAFE_RELEASE(driver_containerp);
    SAFE_RELEASE(device_containerp);
    SAFE_RELEASE(devices_containerp);
    SAFE_RELEASE(dx_diag_rootp);
    SAFE_RELEASE(dx_diag_providerp);

    CoUninitialize();
    return ret;
}

void LLDXHardware::setWriteDebugFunc(void (*func)(const char*))
{
    gWriteDebug = func;
}

#endif