/*
 * IGameExplorer and IGameExplorer2 tests
 *
 * Copyright (C) 2010 Mariusz Pluciński
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS

#include "windows.h"
#include "ole2.h"
#include "objsafe.h"
#include "objbase.h"
#include "shlwapi.h"
#include "sddl.h"
#include "shobjidl.h"

#include "initguid.h"
#include "gameux.h"

#include "wine/test.h"

/* function from Shell32, not defined in header */
extern BOOL WINAPI GUIDFromStringW(LPCWSTR psz, LPGUID pguid);

/*******************************************************************************
 * Pointers used instead of direct calls. These procedures are not available on
 * older system, which causes problem while loading test binary.
 */
static BOOL WINAPI (*_ConvertSidToStringSidW)(PSID,LPWSTR*);
static LONG WINAPI (*_RegGetValueW)(HKEY,LPCWSTR,LPCWSTR,DWORD,LPDWORD,PVOID,LPDWORD);

/*******************************************************************************
 *_loadDynamicRoutines
 *
 * Helper function, prepares pointers to system procedures which may be not
 * available on older operating systems.
 *
 * Returns:
 *  TRUE                        procedures were loaded successfully
 *  FALSE                       procedures were not loaded successfully
 */
static BOOL _loadDynamicRoutines(void)
{
    HMODULE hAdvapiModule = GetModuleHandleA( "advapi32.dll" );

    _ConvertSidToStringSidW = (LPVOID)GetProcAddress(hAdvapiModule, "ConvertSidToStringSidW");
    if (!_ConvertSidToStringSidW) return FALSE;
    _RegGetValueW = (LPVOID)GetProcAddress(hAdvapiModule, "RegGetValueW");
    if (!_RegGetValueW) return FALSE;
    return TRUE;
}

/*******************************************************************************
 * _buildGameRegistryPath
 *
 * Helper function, builds registry path to key, where game's data are stored
 *
 * Parameters:
 *  installScope                [I]     the scope which was used in AddGame/InstallGame call
 *  gameInstanceId              [I]     game instance GUID. If NULL, then only
 *                                      path to scope is returned
 *  lpRegistryPath              [O]     pointer which will receive address to string
 *                                      containing expected registry path. Path
 *                                      is relative to HKLM registry key. It
 *                                      must be freed by calling CoTaskMemFree
 */
static HRESULT _buildGameRegistryPath(GAME_INSTALL_SCOPE installScope,
        LPCGUID gameInstanceId,
        LPWSTR* lpRegistryPath)
{
    static const WCHAR sGameUxRegistryPath[] = {'S','O','F','T','W','A','R','E','\\',
            'M','i','c','r','o','s','o','f','t','\\','W','i','n','d','o','w','s','\\',
            'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\','G','a','m','e','U','X',0};
    static const WCHAR sGames[] = {'G','a','m','e','s',0};
    static const WCHAR sBackslash[] = {'\\',0};

    HRESULT hr = S_OK;
    HANDLE hToken = NULL;
    PTOKEN_USER pTokenUser = NULL;
    DWORD dwLength;
    LPWSTR lpSID = NULL;
    WCHAR sInstanceId[40];
    WCHAR sRegistryPath[8192];

    lstrcpyW(sRegistryPath, sGameUxRegistryPath);
    lstrcatW(sRegistryPath, sBackslash);

    if(installScope == GIS_CURRENT_USER)
    {
        /* build registry path containing user's SID */
        if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
            hr = HRESULT_FROM_WIN32(GetLastError());

        if(SUCCEEDED(hr))
        {
            if(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) &&
                    GetLastError()!=ERROR_INSUFFICIENT_BUFFER)
                hr = HRESULT_FROM_WIN32(GetLastError());

            if(SUCCEEDED(hr))
            {
                pTokenUser = CoTaskMemAlloc(dwLength);
                if(!pTokenUser)
                    hr = E_OUTOFMEMORY;
            }

            if(SUCCEEDED(hr))
                if(!GetTokenInformation(hToken, TokenUser, (LPVOID)pTokenUser, dwLength, &dwLength))
                    hr = HRESULT_FROM_WIN32(GetLastError());

            if(SUCCEEDED(hr))
                if(!_ConvertSidToStringSidW(pTokenUser->User.Sid, &lpSID))
                    hr = HRESULT_FROM_WIN32(GetLastError());

            if(SUCCEEDED(hr))
            {
                lstrcatW(sRegistryPath, lpSID);
                LocalFree(lpSID);
            }

            CoTaskMemFree(pTokenUser);
            CloseHandle(hToken);
        }
    }
    else if(installScope == GIS_ALL_USERS)
        /* build registry path without SID */
        lstrcatW(sRegistryPath, sGames);
    else
        hr = E_INVALIDARG;

    /* put game's instance id on the end of path, but only if id was passes */
    if(gameInstanceId)
    {
        if(SUCCEEDED(hr))
            hr = (StringFromGUID2(gameInstanceId, sInstanceId, sizeof(sInstanceId)/sizeof(sInstanceId[0])) ? S_OK : E_FAIL);

        if(SUCCEEDED(hr))
        {
            lstrcatW(sRegistryPath, sBackslash);
            lstrcatW(sRegistryPath, sInstanceId);
        }
    }

    if(SUCCEEDED(hr))
    {
        *lpRegistryPath = CoTaskMemAlloc((lstrlenW(sRegistryPath)+1)*sizeof(WCHAR));
        if(!*lpRegistryPath)
            hr = E_OUTOFMEMORY;
    }

    if(SUCCEEDED(hr))
        lstrcpyW(*lpRegistryPath, sRegistryPath);

    return hr;
}
/*******************************************************************************
 *  _validateRegistryValue
 *
 * Helper function, verifies single registry value with expected
 *
 * Parameters:
 *  hKey                        [I]     handle to game's key. Key must be opened
 *  keyPath                     [I]     string with path to game's key. Used only
 *                                      to display more useful message on test fail
 *  valueName                   [I]     name of value to check
 *  dwExpectedType              [I]     expected type of value. It should be
 *                                      one of RRF_RT_* flags
 *  lpExpectedContent           [I]     expected content of value. It should be
 *                                      pointer to variable with same type as
 *                                      passed in dwExpectedType
 *
 * Returns:
 *  S_OK                                value exists and contains expected data
 *  S_FALSE                             value exists, but contains other data
 *                                      than expected
 *  E_OUTOFMEMORY                       allocation problem
 *  HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
 *                                      value does not exist
 *
 * Note: this function returns error codes instead of failing test case, because
 * it is sometimes expected that given value may not exist on some systems, or
 * contain other data.
 */
static HRESULT _validateRegistryValue(
        HKEY hKey,
        LPCWSTR keyPath,
        LPCWSTR valueName,
        DWORD dwExpectedType,
        LPCVOID lpExpectedContent)
{
    HRESULT hr;
    DWORD dwType, dwSize;
    LPVOID lpData = NULL;

    hr = HRESULT_FROM_WIN32(_RegGetValueW(hKey, NULL, valueName, dwExpectedType, &dwType, NULL, &dwSize));
    if(FAILED(hr)) trace("registry value cannot be opened\n"
                "   key: %s\n"
                "   value: %s\n"
                "   expected type: 0x%x\n"
                "   found type: 0x%x\n"
                "   result code: 0x%0x\n",
                wine_dbgstr_w(keyPath),
                wine_dbgstr_w(valueName),
                dwExpectedType,
                dwType,
                hr);

    if(SUCCEEDED(hr))
    {
        lpData = CoTaskMemAlloc(dwSize);
        if(!lpData)
            hr = E_OUTOFMEMORY;
    }

    if(SUCCEEDED(hr))
        hr = HRESULT_FROM_WIN32(_RegGetValueW(hKey, NULL, valueName, dwExpectedType, &dwType, lpData, &dwSize));

    if(SUCCEEDED(hr))
    {
        if(memcmp(lpData, lpExpectedContent, dwSize)==0)
            hr = S_OK;
        else
        {
            if(dwExpectedType == RRF_RT_REG_SZ)
                /* if value type is REG_SZ, display expected and found values */
                trace("not expected content of registry value\n"
                        "   key: %s\n"
                        "   value: %s\n"
                        "   expected REG_SZ content: %s\n"
                        "   found REG_SZ content:    %s\n",
                        wine_dbgstr_w(keyPath),
                        wine_dbgstr_w(valueName),
                        wine_dbgstr_w(lpExpectedContent),
                        wine_dbgstr_w(lpData));
            else
                /* in the other case, do not display content */
                trace("not expected content of registry value\n"
                        "   key: %s\n"
                        "   value: %s\n"
                        "   value type: 0x%x\n",
                        wine_dbgstr_w(keyPath),
                        wine_dbgstr_w(valueName),
                        dwType);

            hr = S_FALSE;
        }
    }

    CoTaskMemFree(lpData);
    return hr;
}
/*******************************************************************************
 *  _validateGameRegistryValues
 *
 * Helper function, verifies values in game's registry key
 *
 * Parameters:
 *  line                        [I]     place of original call. Used only to display
 *                                      more useful message on test fail
 *  hKey                        [I]     handle to game's key. Key must be opened
 *                                      with KEY_READ access permission
 *  keyPath                     [I]     string with path to game's key. Used only
 *                                      to display more useful message on test fail
 *  gameApplicationId           [I]     game application identifier
 *  gameExePath                 [I]     directory where game executable is stored
 *  gameExeName                 [I]     full path to executable, including directory and file name
 */
static void _validateGameRegistryValues(int line,
        HKEY hKey,
        LPCWSTR keyPath,
        LPCGUID gameApplicationId,
        LPCWSTR gameExePath,
        LPCWSTR gameExeName)
{
    static const WCHAR sApplicationId[] = {'A','p','p','l','i','c','a','t','i','o','n','I','d',0};
    static const WCHAR sConfigApplicationPath[] = {'C','o','n','f','i','g','A','p','p','l','i','c','a','t','i','o','n','P','a','t','h',0};
    static const WCHAR sConfigGDFBinaryPath[] = {'C','o','n','f','i','g','G','D','F','B','i','n','a','r','y','P','a','t','h',0};
    static const WCHAR sDescription[] = {'D','e','s','c','r','i','p','t','i','o','n',0};
    static const WCHAR sExampleGame[] = {'E','x','a','m','p','l','e',' ','G','a','m','e',0};
    static const WCHAR sGameDescription[] = {'G','a','m','e',' ','D','e','s','c','r','i','p','t','i','o','n',0};
    static const WCHAR sTitle[] = {'T','i','t','l','e',0};

    HRESULT hr;
    WCHAR sGameApplicationId[40];

    hr = (StringFromGUID2(gameApplicationId, sGameApplicationId, sizeof(sGameApplicationId)/sizeof(sGameApplicationId[0])) ? S_OK : E_FAIL);
    ok_(__FILE__, line)(hr == S_OK, "cannot convert game application id to string\n");

    /* these values exist up from Vista */
    hr = _validateRegistryValue(hKey, keyPath,   sApplicationId,            RRF_RT_REG_SZ,      sGameApplicationId);
    ok_(__FILE__, line)(hr==S_OK, "failed while checking registry value (error 0x%x)\n", hr);
    hr = _validateRegistryValue(hKey, keyPath,   sConfigApplicationPath,    RRF_RT_REG_SZ,      gameExePath);
    ok_(__FILE__, line)(hr==S_OK, "failed while checking registry value (error 0x%x)\n", hr);
    hr = _validateRegistryValue(hKey, keyPath,   sConfigGDFBinaryPath,      RRF_RT_REG_SZ,      gameExeName);
    ok_(__FILE__, line)(hr==S_OK, "failed while checking registry value (error 0x%x)\n", hr);
    hr = _validateRegistryValue(hKey, keyPath,   sTitle,                    RRF_RT_REG_SZ,      sExampleGame);
    ok_(__FILE__, line)(hr==S_OK, "failed while checking registry value (error 0x%x)\n", hr);

    /* this value exists up from Win7 */
    hr = _validateRegistryValue(hKey, keyPath,   sDescription,              RRF_RT_REG_SZ,      sGameDescription);
    ok_(__FILE__, line)(hr==S_OK || broken(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)), "failed while checking registry value (error 0x%x)\n", hr);
}
/*******************************************************************************
 *  _validateGameKey
 *
 * Helper function, verifies current state of game's registry key with expected.
 *
 * Parameters:
 *  line                        [I]     place of original call. Used only to display
 *                                      more useful message on test fail
 *  installScope                [I]     the scope which was used in AddGame/InstallGame call
 *  gameInstanceId              [I]     game instance identifier
 *  gameApplicationId           [I]     game application identifier
 *  gameExePath                 [I]     directory where game executable is stored
 *  gameExeName                 [I]     full path to executable, including directory and file name
 *  presenceExpected            [I]     is it expected that game should be currently
 *                                      registered or not. Should be TRUE if checking
 *                                      after using AddGame/InstallGame, and FALSE
 *                                      if checking after RemoveGame/UninstallGame
 */
static void _validateGameRegistryKey(int line,
        GAME_INSTALL_SCOPE installScope,
        LPCGUID gameInstanceId,
        LPCGUID gameApplicationId,
        LPCWSTR gameExePath,
        LPCWSTR gameExeName,
        BOOL presenceExpected)
{
    HRESULT hr;
    LPWSTR lpRegistryPath = NULL;
    HKEY hKey;

    /* check key presence */
    hr = _buildGameRegistryPath(installScope, gameInstanceId, &lpRegistryPath);

    if(SUCCEEDED(hr))
    {
        hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE, lpRegistryPath, 0,
                KEY_READ | KEY_WOW64_64KEY, &hKey));

        if(presenceExpected)
            ok_(__FILE__, line)(hr == S_OK,
                    "problem while trying to open registry key (HKLM): %s, error: 0x%x\n", wine_dbgstr_w(lpRegistryPath), hr);
        else
            ok_(__FILE__, line)(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND),
                    "other than expected (FILE_NOT_FOUND) error while trying to open registry key (HKLM): %s, error: 0x%x\n", wine_dbgstr_w(lpRegistryPath), hr);
    }

    if(SUCCEEDED(hr))
    {
        if(presenceExpected)
            /* if the key exists and we expected it, let's verify its content */
            _validateGameRegistryValues(line, hKey, lpRegistryPath, gameApplicationId, gameExePath, gameExeName);

        RegCloseKey(hKey);
    }

    CoTaskMemFree(lpRegistryPath);
}

/*******************************************************************************
 * _LoadRegistryString
 *
 * Helper function, loads string from registry value and allocates buffer for it
 *
 * Parameters:
 *  hRootKey                        [I]     base key for reading. Should be opened
 *                                          with KEY_READ permission
 *  lpRegistryKey                   [I]     name of registry key, subkey of root key
 *  lpRegistryValue                 [I]     name of registry value
 *  lpValue                         [O]     pointer where address of received
 *                                          value will be stored. Value should be
 *                                          freed by CoTaskMemFree call
 */
static HRESULT _LoadRegistryString(HKEY hRootKey,
        LPCWSTR lpRegistryKey,
        LPCWSTR lpRegistryValue,
        LPWSTR* lpValue)
{
    HRESULT hr;
    DWORD dwSize;

    *lpValue = NULL;

    hr = HRESULT_FROM_WIN32(_RegGetValueW(hRootKey, lpRegistryKey, lpRegistryValue,
            RRF_RT_REG_SZ, NULL, NULL, &dwSize));

    if(SUCCEEDED(hr))
    {
        *lpValue = CoTaskMemAlloc(dwSize);
        if(!*lpValue)
            hr = E_OUTOFMEMORY;
    }
    if(SUCCEEDED(hr))
        hr = HRESULT_FROM_WIN32(_RegGetValueW(hRootKey, lpRegistryKey, lpRegistryValue,
                RRF_RT_REG_SZ, NULL, *lpValue, &dwSize));

    return hr;
}
/*******************************************************************************
 * _findGameInstanceId
 *
 * Helper function. Searches for instance identifier of given game in given
 * installation scope.
 *
 * Parameters:
 *  line                                    [I]     line to display messages
 *  sGDFBinaryPath                          [I]     path to binary containing GDF
 *  installScope                            [I]     game install scope to search in
 *  pInstanceId                             [O]     instance identifier of given game
 */
static void _findGameInstanceId(int line,
        LPWSTR sGDFBinaryPath,
        GAME_INSTALL_SCOPE installScope,
        GUID* pInstanceId)
{
    static const WCHAR sConfigGDFBinaryPath[] =
            {'C','o','n','f','i','g','G','D','F','B','i','n','a','r','y','P','a','t','h',0};

    HRESULT hr;
    BOOL found = FALSE;
    LPWSTR lpRegistryPath = NULL;
    HKEY hRootKey;
    DWORD dwSubKeys, dwSubKeyLen, dwMaxSubKeyLen, i;
    LPWSTR lpName = NULL, lpValue = NULL;

    hr = _buildGameRegistryPath(installScope, NULL, &lpRegistryPath);
    ok_(__FILE__, line)(SUCCEEDED(hr), "cannot get registry path to given scope: %d\n", installScope);

    if(SUCCEEDED(hr))
        /* enumerate all subkeys of received one and search them for value "ConfigGGDFBinaryPath" */
        hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                lpRegistryPath, 0, KEY_READ | KEY_WOW64_64KEY, &hRootKey));
    ok_(__FILE__, line)(SUCCEEDED(hr), "cannot open key registry key: %s\n", wine_dbgstr_w(lpRegistryPath));

    if(SUCCEEDED(hr))
    {
        hr = HRESULT_FROM_WIN32(RegQueryInfoKeyW(hRootKey, NULL, NULL, NULL,
                &dwSubKeys, &dwMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL));

        if(SUCCEEDED(hr))
        {
            ++dwMaxSubKeyLen; /* for string terminator */
            lpName = CoTaskMemAlloc(dwMaxSubKeyLen*sizeof(WCHAR));
            if(!lpName) hr = E_OUTOFMEMORY;
            ok_(__FILE__, line)(SUCCEEDED(hr), "cannot allocate memory for key name");
        }

        if(SUCCEEDED(hr))
        {
            for(i=0; i<dwSubKeys && !found; ++i)
            {
                dwSubKeyLen = dwMaxSubKeyLen;
                hr = HRESULT_FROM_WIN32(RegEnumKeyExW(hRootKey, i, lpName, &dwSubKeyLen,
                        NULL, NULL, NULL, NULL));

                if(SUCCEEDED(hr))
                    hr = _LoadRegistryString(hRootKey, lpName,
                                             sConfigGDFBinaryPath, &lpValue);

                if(SUCCEEDED(hr))
                    if(lstrcmpW(lpValue, sGDFBinaryPath)==0)
                    {
                        /* key found, let's copy instance id and exit */
                        hr = (GUIDFromStringW(lpName, pInstanceId) ? S_OK : E_FAIL);
                        ok(SUCCEEDED(hr), "cannot convert subkey to guid: %s\n",
                               wine_dbgstr_w(lpName));

                        found = TRUE;
                    }
                CoTaskMemFree(lpValue);
            }
        }

        CoTaskMemFree(lpName);
        RegCloseKey(hRootKey);
    }

    CoTaskMemFree(lpRegistryPath);
    ok_(__FILE__, line)(found==TRUE, "cannot find game with GDF path %s in scope %d\n",
            wine_dbgstr_w(sGDFBinaryPath), installScope);
}
/*******************************************************************************
 * Test routines
 */
static void test_create(BOOL* gameExplorerAvailable, BOOL* gameExplorer2Available)
{
    HRESULT hr;

    IGameExplorer* ge = NULL;
    IGameExplorer2* ge2 = NULL;

    /* interface available up from Vista */
    hr = CoCreateInstance( &CLSID_GameExplorer, NULL, CLSCTX_INPROC_SERVER, &IID_IGameExplorer, (LPVOID*)&ge);
    if(ge)
    {
        ok(hr == S_OK, "IGameExplorer creating failed (result false)\n");
        *gameExplorerAvailable = TRUE;
        IGameExplorer_Release(ge);
    }
    else
        win_skip("IGameExplorer cannot be created\n");

    /* interface available up from Win7 */
    hr = CoCreateInstance( &CLSID_GameExplorer, NULL, CLSCTX_INPROC_SERVER, &IID_IGameExplorer2, (LPVOID*)&ge2);
    if(ge2)
    {
        ok( hr == S_OK, "IGameExplorer2 creating failed (result false)\n");
        *gameExplorer2Available = TRUE;
        IGameExplorer2_Release(ge2);
    }
    else
        win_skip("IGameExplorer2 cannot be created\n");
}

static void test_add_remove_game(void)
{
    static const GUID defaultGUID = {0x01234567, 0x89AB, 0xCDEF,
        { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}};
    static const GUID applicationId = { 0x17A6558E, 0x60BE, 0x4078,
        { 0xB6, 0x6F, 0x9C, 0x3A, 0xDA, 0x2A, 0x32, 0xE6 }};

    HRESULT hr;

    IGameExplorer* ge = NULL;
    WCHAR sExeName[MAX_PATH];
    WCHAR sExePath[MAX_PATH];
    BSTR bstrExeName = NULL, bstrExePath = NULL;
    DWORD dwExeNameLen;
    GUID guid;

    hr = CoCreateInstance(&CLSID_GameExplorer, NULL, CLSCTX_INPROC_SERVER, &IID_IGameExplorer, (LPVOID*) & ge);
    ok(ge != NULL, "cannot create coclass IGameExplorer\n");
    ok(hr == S_OK, "cannot create coclass IGameExplorer\n");

    if(ge)
    {
        /* prepare path to binary */
        dwExeNameLen = GetModuleFileNameW(NULL, sExeName, sizeof (sExeName) / sizeof (sExeName[0]));
        ok(dwExeNameLen != 0, "GetModuleFileNameW returned invalid value\n");
        lstrcpynW(sExePath, sExeName, StrRChrW(sExeName, NULL, '\\') - sExeName + 1);
        bstrExeName = SysAllocString(sExeName);
        ok(bstrExeName != NULL, "cannot allocate string for exe name\n");
        bstrExePath = SysAllocString(sExePath);
        ok(bstrExePath != NULL, "cannot allocate string for exe path\n");

        if(bstrExeName && bstrExePath)
        {
            trace("prepared EXE name: %s\n", wine_dbgstr_w(bstrExeName));
            trace("prepared EXE path: %s\n", wine_dbgstr_w(bstrExePath));


            /* try to register game with provided guid */
            memcpy(&guid, &defaultGUID, sizeof (guid));

            hr = IGameExplorer_AddGame(ge, bstrExeName, bstrExePath, GIS_CURRENT_USER, &guid);
            ok(SUCCEEDED(hr), "IGameExplorer::AddGame failed (error 0x%08x)\n", hr);
            ok(memcmp(&guid, &defaultGUID, sizeof (guid)) == 0, "AddGame unexpectedly modified GUID\n");

            if(SUCCEEDED(hr))
            {
                _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, TRUE);

                hr = IGameExplorer_RemoveGame(ge, guid);
                ok(SUCCEEDED(hr), "IGameExplorer::RemoveGame failed (error 0x%08x)\n", hr);
            }

            _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, FALSE);


            /* try to register game with empty guid */
            memcpy(&guid, &GUID_NULL, sizeof (guid));

            hr = IGameExplorer_AddGame(ge, bstrExeName, bstrExePath, GIS_CURRENT_USER, &guid);
            ok(SUCCEEDED(hr), "IGameExplorer::AddGame failed (error 0x%08x)\n", hr);
            ok(memcmp(&guid, &GUID_NULL, sizeof (guid)) != 0, "AddGame did not modify GUID\n");

            if(SUCCEEDED(hr))
            {
                _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, TRUE);

                hr = IGameExplorer_RemoveGame(ge, guid);
                ok(SUCCEEDED(hr), "IGameExplorer::RemoveGame failed (error 0x%08x)\n", hr);
            }

            _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, FALSE);
        }

        /* free allocated resources */
        SysFreeString(bstrExePath);
        SysFreeString(bstrExeName);

        IGameExplorer_Release(ge);
    }
}

static void test_install_uninstall_game(void)
{
    static const GUID applicationId = { 0x17A6558E, 0x60BE, 0x4078,
        { 0xB6, 0x6F, 0x9C, 0x3A, 0xDA, 0x2A, 0x32, 0xE6 }};

    HRESULT hr;

    IGameExplorer2* ge2 = NULL;
    WCHAR sExeName[MAX_PATH];
    WCHAR sExePath[MAX_PATH];
    DWORD dwExeNameLen;
    GUID guid;

    hr = CoCreateInstance(&CLSID_GameExplorer, NULL, CLSCTX_INPROC_SERVER, &IID_IGameExplorer2, (LPVOID*)&ge2);
    ok(ge2 != NULL, "cannot create coclass IGameExplorer2\n");
    ok(hr == S_OK, "cannot create coclass IGameExplorer2\n");

    if(ge2)
    {
        /* prepare path to binary */
        dwExeNameLen = GetModuleFileNameW(NULL, sExeName, sizeof (sExeName) / sizeof (sExeName[0]));
        ok(dwExeNameLen != 0, "GetModuleFileNameW returned invalid value\n");
        lstrcpynW(sExePath, sExeName, StrRChrW(sExeName, NULL, '\\') - sExeName + 1);

        trace("prepared EXE name: %s\n", wine_dbgstr_w(sExeName));
        trace("prepared EXE path: %s\n", wine_dbgstr_w(sExePath));


        hr = IGameExplorer2_InstallGame(ge2, sExeName, sExePath, GIS_CURRENT_USER);
        ok(SUCCEEDED(hr), "IGameExplorer2::InstallGame failed (error 0x%08x)\n", hr);
        /* in comparison to AddGame, InstallGame does not return instance ID,
         * so we need to find it manually */
        _findGameInstanceId(__LINE__, sExeName, GIS_CURRENT_USER, &guid);

        if(SUCCEEDED(hr))
        {
            _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, TRUE);

            hr = IGameExplorer2_UninstallGame(ge2, sExeName);
            ok(SUCCEEDED(hr), "IGameExplorer2::UninstallGame failed (error 0x%08x)\n", hr);
        }

        _validateGameRegistryKey(__LINE__, GIS_CURRENT_USER, &guid, &applicationId, sExePath, sExeName, FALSE);

        IGameExplorer2_Release(ge2);
    }
}

static void run_tests(void)
{
    BOOL gameExplorerAvailable = FALSE;
    BOOL gameExplorer2Available = FALSE;

    test_create(&gameExplorerAvailable, &gameExplorer2Available);

    if(gameExplorerAvailable)
        test_add_remove_game();

    if(gameExplorer2Available)
        test_install_uninstall_game();
}

START_TEST(gameexplorer)
{
    if(_loadDynamicRoutines())
    {
        HRESULT hr;

        hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
        ok(hr == S_OK, "Failed to initialize COM, hr %#x.\n", hr);
        trace("Running apartment threaded tests.\n");
        run_tests();
        if(SUCCEEDED(hr))
            CoUninitialize();

        hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
        ok(hr == S_OK, "Failed to initialize COM, hr %#x.\n", hr);
        trace("Running multithreaded tests.\n");
        run_tests();
        if(SUCCEEDED(hr))
            CoUninitialize();
    }
    else
        /* this is not a failure, because both procedures loaded by address
         * are always available on systems which has gameux.dll */
        win_skip("too old system, cannot load required dynamic procedures\n");
}
