/*
 *    Gameux library coclass GameExplorer implementation
 *
 * 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 "config.h"

#include "ole2.h"
#include "sddl.h"

#include "gameux.h"
#include "gameux_private.h"

#include "initguid.h"
#include "msxml2.h"

#include "wine/debug.h"
#include "winreg.h"

WINE_DEFAULT_DEBUG_CHANNEL(gameux);

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

/*******************************************************************************
 * GameUX helper functions
 */
/*******************************************************************************
 * GAMEUX_initGameData
 *
 * Internal helper function.
 * Initializes GAME_DATA structure fields with proper values. Should be
 * called always before first usage of this structure. Implemented in gameexplorer.c
 *
 * Parameters:
 *  GameData                        [I/O]   pointer to structure to initialize
 */
static void GAMEUX_initGameData(struct GAMEUX_GAME_DATA *GameData)
{
    GameData->sGDFBinaryPath = NULL;
    GameData->sGameInstallDirectory = NULL;
    GameData->bstrName = NULL;
    GameData->bstrDescription = NULL;
}
/*******************************************************************************
 * GAMEUX_uninitGameData
 *
 * Internal helper function.
 * Properly frees all data stored or pointed by fields of GAME_DATA structure.
 * Should be called before freeing this structure. Implemented in gameexplorer.c
 *
 * Parameters:
 *  GameData                        [I/O]   pointer to structure to uninitialize
 */
static void GAMEUX_uninitGameData(struct GAMEUX_GAME_DATA *GameData)
{
    HeapFree(GetProcessHeap(), 0, GameData->sGDFBinaryPath);
    HeapFree(GetProcessHeap(), 0, GameData->sGameInstallDirectory);
    SysFreeString(GameData->bstrName);
    SysFreeString(GameData->bstrDescription);
}
/*******************************************************************************
 * GAMEUX_buildGameRegistryPath
 *
 * Internal helper function. Description available in gameux_private.h file
 */
HRESULT GAMEUX_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];

    TRACE("(0x%x, %s, %p)\n", installScope, debugstr_guid(gameInstanceId), lpRegistryPath);

    /* this will make freeing it easier for user */
    *lpRegistryPath = NULL;

    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 = HeapAlloc(GetProcessHeap(), 0, 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);
            }

            HeapFree(GetProcessHeap(), 0, 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, only if instance id was given */
    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 = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sRegistryPath)+1)*sizeof(WCHAR));
        if(!*lpRegistryPath)
            hr = E_OUTOFMEMORY;
    }

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

    TRACE("result: 0x%x, path: %s\n", hr, debugstr_w(*lpRegistryPath));
    return hr;
}
/*******************************************************************************
 * GAMEUX_WriteRegistryRecord
 *
 * Helper function, writes data associated with game (stored in GAMEUX_GAME_DATA
 * structure) into expected place in registry.
 *
 * Parameters:
 *  GameData                            [I]     structure with data which will
 *                                              be written into registry.
 *                                              Proper values of fields installScope
 *                                              and guidInstanceId are required
 *                                              to create registry key.
 *
 * Schema of naming registry keys associated with games is available in
 * description of _buildGameRegistryPath internal function.
 *
 * List of registry keys associated with structure fields:
 *  Key                              Field in GAMEUX_GAME_DATA structure
 *   ApplicationId                    guidApplicationId
 *   ConfigApplicationPath            sGameInstallDirectory
 *   ConfigGDFBinaryPath              sGDFBinaryPath
 *   Title                            bstrName
 *
 */
static HRESULT GAMEUX_WriteRegistryRecord(struct GAMEUX_GAME_DATA *GameData)
{
    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 sTitle[] =
            {'T','i','t','l','e',0};
    static const WCHAR sDescription[] =
            {'D','e','s','c','r','i','p','t','i','o','n',0};

    HRESULT hr, hr2;
    LPWSTR lpRegistryKey;
    HKEY hKey;
    WCHAR sGameApplicationId[40];

    TRACE("(%p)\n", GameData);

    hr = GAMEUX_buildGameRegistryPath(GameData->installScope, &GameData->guidInstanceId, &lpRegistryKey);

    if(SUCCEEDED(hr))
        hr = (StringFromGUID2(&GameData->guidApplicationId, sGameApplicationId, sizeof(sGameApplicationId)/sizeof(sGameApplicationId[0])) ? S_OK : E_FAIL);

    if(SUCCEEDED(hr))
        hr = HRESULT_FROM_WIN32(RegCreateKeyExW(HKEY_LOCAL_MACHINE, lpRegistryKey,
                                                0, NULL, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, NULL,
                                                &hKey, NULL));

    if(SUCCEEDED(hr))
    {
        /* write game data to registry key */
        hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, sConfigApplicationPath, 0,
                                               REG_SZ, (LPBYTE)(GameData->sGameInstallDirectory),
                                               (lstrlenW(GameData->sGameInstallDirectory)+1)*sizeof(WCHAR)));

        if(SUCCEEDED(hr))
            hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, sConfigGDFBinaryPath, 0,
                                                   REG_SZ, (LPBYTE)(GameData->sGDFBinaryPath),
                                                   (lstrlenW(GameData->sGDFBinaryPath)+1)*sizeof(WCHAR)));

        if(SUCCEEDED(hr))
            hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, sApplicationId, 0,
                                                   REG_SZ, (LPBYTE)(sGameApplicationId),
                                                   (lstrlenW(sGameApplicationId)+1)*sizeof(WCHAR)));

        if(SUCCEEDED(hr))
            hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, sTitle, 0,
                                                   REG_SZ, (LPBYTE)(GameData->bstrName),
                                                   (lstrlenW(GameData->bstrName)+1)*sizeof(WCHAR)));

        if(SUCCEEDED(hr))
            hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, sDescription, 0,
                                                   REG_SZ, (LPBYTE)(GameData->bstrDescription ? GameData->bstrDescription : GameData->bstrName),
                                                   (lstrlenW(GameData->bstrDescription ? GameData->bstrDescription : GameData->bstrName)+1)*sizeof(WCHAR)));

        RegCloseKey(hKey);

        if(FAILED(hr))
        {
            /* if something failed, remove whole key */
            hr2 = RegDeleteKeyExW(HKEY_LOCAL_MACHINE, lpRegistryKey, KEY_WOW64_64KEY, 0);
            /* do not overwrite old failure code with new success code */
            if(FAILED(hr2))
                hr = hr2;
        }
    }

    HeapFree(GetProcessHeap(), 0, lpRegistryKey);
    TRACE("returning 0x%x\n", hr);
    return hr;
}
/*******************************************************************************
 * GAMEUX_ProcessGameDefinitionElement
 *
 * Helper function, parses single element from Game Definition
 *
 * Parameters:
 *  lpXMLElement                        [I]     game definition element
 *  GameData                            [O]     structure, where parsed
 *                                              data will be stored
 */
static HRESULT GAMEUX_ProcessGameDefinitionElement(
        IXMLDOMElement *element,
        struct GAMEUX_GAME_DATA *GameData)
{
    static const WCHAR sName[] =
            {'N','a','m','e',0};
    static const WCHAR sDescription[] =
            {'D','e','s','c','r','i','p','t','i','o','n',0};

    HRESULT hr;
    BSTR bstrElementName;

    TRACE("(%p, %p)\n", element, GameData);

    hr = IXMLDOMElement_get_nodeName(element, &bstrElementName);
    if(SUCCEEDED(hr))
    {
        /* check element name */
        if(lstrcmpW(bstrElementName, sName) == 0)
            hr = IXMLDOMElement_get_text(element, &GameData->bstrName);

        else if(lstrcmpW(bstrElementName, sDescription) == 0)
            hr = IXMLDOMElement_get_text(element, &GameData->bstrDescription);

        else
            FIXME("entry %s in Game Definition File not yet supported\n", debugstr_w(bstrElementName));

        SysFreeString(bstrElementName);
    }

    return hr;
}
/*******************************************************************************
 * GAMEUX_ParseGameDefinition
 *
 * Helper function, loads data from given XML element into fields of GAME_DATA
 * structure
 *
 * Parameters:
 *  lpXMLGameDefinitionElement          [I]     Game Definition XML element
 *  GameData                            [O]     structure where data loaded from
 *                                              XML element will be stored in
 */
static HRESULT GAMEUX_ParseGameDefinition(
        IXMLDOMElement *gdElement,
        struct GAMEUX_GAME_DATA *GameData)
{
    static const WCHAR sGameId[] = {'g','a','m','e','I','D',0};

    HRESULT hr = S_OK;
    BSTR bstrAttribute;
    VARIANT variant;
    IXMLDOMNodeList *childrenList;
    IXMLDOMNode *nextNode;
    IXMLDOMElement *nextElement;

    TRACE("(%p, %p)\n", gdElement, GameData);

    bstrAttribute = SysAllocString(sGameId);
    if(!bstrAttribute)
        hr = E_OUTOFMEMORY;

    hr = IXMLDOMElement_getAttribute(gdElement, bstrAttribute, &variant);

    if(SUCCEEDED(hr))
    {
        hr = ( GUIDFromStringW(V_BSTR(&variant), &GameData->guidApplicationId)==TRUE ? S_OK : E_FAIL);

        SysFreeString(V_BSTR(&variant));
    }

    SysFreeString(bstrAttribute);

    /* browse subnodes */
    if(SUCCEEDED(hr))
        hr = IXMLDOMElement_get_childNodes(gdElement, &childrenList);

    if(SUCCEEDED(hr))
    {
        do
        {
            hr = IXMLDOMNodeList_nextNode(childrenList, &nextNode);

            if(hr == S_OK)
            {
                hr = IXMLDOMNode_QueryInterface(nextNode, &IID_IXMLDOMElement,
                                                (LPVOID*)&nextElement);

                if(SUCCEEDED(hr))
                {
                    hr = GAMEUX_ProcessGameDefinitionElement(nextElement, GameData);
                    IXMLDOMElement_Release(nextElement);
                }

                IXMLDOMElement_Release(nextNode);
            }
        }
        while(hr == S_OK);
        hr = S_OK;

        IXMLDOMNodeList_Release(childrenList);
    }

    return hr;
}

struct parse_gdf_thread_param
{
    struct GAMEUX_GAME_DATA *GameData;
    HRESULT hr;
};

/*******************************************************************************
 * GAMEUX_ParseGDFBinary
 *
 * Helper function, loads given binary and parses embed GDF if there's any.
 *
 * Parameters:
 *  GameData                [I/O]   Structure with game's data. Content of field
 *                                  sGDFBinaryPath defines path to binary, from
 *                                  which embed GDF will be loaded. Data from
 *                                  GDF will be stored in other fields of this
 *                                  structure.
 */
static DWORD WINAPI GAMEUX_ParseGDFBinary(void *thread_param)
{
    struct parse_gdf_thread_param *ctx = thread_param;
    struct GAMEUX_GAME_DATA *GameData = ctx->GameData;
    static const WCHAR sRes[] = {'r','e','s',':','/','/',0};
    static const WCHAR sDATA[] = {'D','A','T','A',0};
    static const WCHAR sSlash[] = {'/',0};

    HRESULT hr = S_OK;
    WCHAR sResourcePath[MAX_PATH];
    VARIANT variant;
    VARIANT_BOOL isSuccessful;
    IXMLDOMDocument *document;
    IXMLDOMNode *gdNode;
    IXMLDOMElement *root, *gdElement;

    TRACE("(%p)->sGDFBinaryPath = %s\n", GameData, debugstr_w(GameData->sGDFBinaryPath));

    /* prepare path to GDF, using res:// prefix */
    lstrcpyW(sResourcePath, sRes);
    lstrcatW(sResourcePath, GameData->sGDFBinaryPath);
    lstrcatW(sResourcePath, sSlash);
    lstrcatW(sResourcePath, sDATA);
    lstrcatW(sResourcePath, sSlash);
    lstrcatW(sResourcePath, ID_GDF_XML_STR);

    CoInitialize(NULL);

    hr = CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
            &IID_IXMLDOMDocument, (void**)&document);

    if(SUCCEEDED(hr))
    {
        /* load GDF into MSXML */
        V_VT(&variant) = VT_BSTR;
        V_BSTR(&variant) = SysAllocString(sResourcePath);
        if(!V_BSTR(&variant))
            hr = E_OUTOFMEMORY;

        if(SUCCEEDED(hr))
        {
            hr = IXMLDOMDocument_load(document, variant, &isSuccessful);
            if(hr == S_FALSE || isSuccessful == VARIANT_FALSE)
                hr = E_FAIL;
        }

        SysFreeString(V_BSTR(&variant));

        if(SUCCEEDED(hr))
        {
            hr = IXMLDOMDocument_get_documentElement(document, &root);
            if(hr == S_FALSE)
                hr = E_FAIL;
        }

        if(SUCCEEDED(hr))
        {
            hr = IXMLDOMElement_get_firstChild(root, &gdNode);
            if(hr == S_FALSE)
                hr = E_FAIL;

            if(SUCCEEDED(hr))
            {
                hr = IXMLDOMNode_QueryInterface(gdNode, &IID_IXMLDOMElement, (LPVOID*)&gdElement);
                if(SUCCEEDED(hr))
                {
                    hr = GAMEUX_ParseGameDefinition(gdElement, GameData);
                    IXMLDOMElement_Release(gdElement);
                }

                IXMLDOMNode_Release(gdNode);
            }

            IXMLDOMElement_Release(root);
        }

        IXMLDOMDocument_Release(document);
    }

    CoUninitialize();
    ctx->hr = hr;
    return 0;
}

/*******************************************************************
 * GAMEUX_RemoveRegistryRecord
 *
 * Helper function, removes registry key associated with given game instance
 */
static HRESULT GAMEUX_RemoveRegistryRecord(GUID* pInstanceID)
{
    HRESULT hr;
    LPWSTR lpRegistryPath = NULL;
    TRACE("(%s)\n", debugstr_guid(pInstanceID));

    /* first, check is game installed for all users */
    hr = GAMEUX_buildGameRegistryPath(GIS_ALL_USERS, pInstanceID, &lpRegistryPath);
    if(SUCCEEDED(hr))
        hr = HRESULT_FROM_WIN32(RegDeleteKeyExW(HKEY_LOCAL_MACHINE, lpRegistryPath, KEY_WOW64_64KEY, 0));

    HeapFree(GetProcessHeap(), 0, lpRegistryPath);

    /* if not, check current user */
    if(FAILED(hr))
    {
        hr = GAMEUX_buildGameRegistryPath(GIS_CURRENT_USER, pInstanceID, &lpRegistryPath);
        if(SUCCEEDED(hr))
            hr = HRESULT_FROM_WIN32(RegDeleteKeyExW(HKEY_LOCAL_MACHINE, lpRegistryPath, KEY_WOW64_64KEY, 0));

        HeapFree(GetProcessHeap(), 0, lpRegistryPath);
    }

    return hr;
}
/*******************************************************************************
 *  GAMEUX_RegisterGame
 *
 * Internal helper function. Registers game associated with given GDF binary in
 * Game Explorer. Implemented in gameexplorer.c
 *
 * Parameters:
 *  sGDFBinaryPath                  [I]     path to binary containing GDF file in
 *                                          resources
 *  sGameInstallDirectory           [I]     path to directory, where game installed
 *                                          it's files.
 *  installScope                    [I]     scope of game installation
 *  pInstanceID                     [I/O]   pointer to game instance identifier.
 *                                          If pointing to GUID_NULL, then new
 *                                          identifier will be generated automatically
 *                                          and returned via this parameter
 */
static HRESULT GAMEUX_RegisterGame(LPCWSTR sGDFBinaryPath,
        LPCWSTR sGameInstallDirectory,
        GAME_INSTALL_SCOPE installScope,
        GUID *pInstanceID)
{
    HRESULT hr = S_OK;
    struct GAMEUX_GAME_DATA GameData;

    TRACE("(%s, %s, 0x%x, %s)\n", debugstr_w(sGDFBinaryPath), debugstr_w(sGameInstallDirectory), installScope, debugstr_guid(pInstanceID));

    GAMEUX_initGameData(&GameData);
    GameData.sGDFBinaryPath = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sGDFBinaryPath)+1)*sizeof(WCHAR));
    lstrcpyW(GameData.sGDFBinaryPath, sGDFBinaryPath);
    GameData.sGameInstallDirectory = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(sGameInstallDirectory)+1)*sizeof(WCHAR));
    lstrcpyW(GameData.sGameInstallDirectory, sGameInstallDirectory);
    GameData.installScope = installScope;

    /* generate GUID if it was not provided by user */
    if(IsEqualGUID(pInstanceID, &GUID_NULL))
        hr = CoCreateGuid(pInstanceID);

    GameData.guidInstanceId = *pInstanceID;

    /* load data from GDF binary */
    if(SUCCEEDED(hr))
    {
        struct parse_gdf_thread_param thread_param;
        HANDLE thread;
        DWORD ret;

        thread_param.GameData = &GameData;
        if(!(thread = CreateThread(NULL, 0, GAMEUX_ParseGDFBinary, &thread_param, 0, &ret)))
        {
            ERR("Failed to create thread.\n");
            hr = E_FAIL;
            goto done;
        }
        ret = WaitForSingleObject(thread, INFINITE);
        CloseHandle(thread);
        if(ret != WAIT_OBJECT_0)
        {
            ERR("Wait failed (%#x).\n", ret);
            hr = E_FAIL;
            goto done;
        }
        hr = thread_param.hr;
    }

    /* save data to registry */
    if(SUCCEEDED(hr))
        hr = GAMEUX_WriteRegistryRecord(&GameData);

done:
    GAMEUX_uninitGameData(&GameData);
    TRACE("returning 0x%08x\n", hr);
    return hr;
}
/*******************************************************************************
 * GAMEUX_IsGameKeyExist
 *
 * Helper function, checks if game's registry ath exists in given scope
 *
 * Parameters:
 *  installScope            [I]     scope to search game in
 *  InstanceID              [I]     game instance identifier
 *  lpRegistryPath          [O]     place to store address of registry path to
 *                                  the game. It is filled only if key exists.
 *                                  It must be freed by HeapFree(GetProcessHeap(), 0, ...)
 *
 * Returns:
 *  S_OK                key was found properly
 *  S_FALSE             key does not exists
 *
 */
static HRESULT GAMEUX_IsGameKeyExist(GAME_INSTALL_SCOPE installScope,
    LPCGUID InstanceID,
    LPWSTR* lpRegistryPath) {

    HRESULT hr;
    HKEY hKey;

    hr = GAMEUX_buildGameRegistryPath(installScope, InstanceID, lpRegistryPath);

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

    if(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        hr = S_FALSE;

    if(hr == S_OK)
        RegCloseKey(hKey);
    else
    {
        /* if the key does not exist or another error occurred, do not return the path */
        HeapFree(GetProcessHeap(), 0, *lpRegistryPath);
        *lpRegistryPath = NULL;
    }

    return hr;
}
/*******************************************************************************
 * GAMEUX_LoadRegistryString
 *
 * Helper function, loads string from registry value and allocates buffer for it
 */
static HRESULT GAMEUX_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 = HeapAlloc(GetProcessHeap(), 0, 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;
}
/*******************************************************************************
 * GAMEUX_UpdateGame
 *
 * Helper function, updates stored data about game with given InstanceID
 */
static HRESULT GAMEUX_UpdateGame(LPGUID InstanceID) {
    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 sConfigApplicationPath[] = {'C','o','n','f','i','g','A','p','p','l','i','c','a','t','i','o','n','P','a','t','h',0};

    HRESULT hr;
    GAME_INSTALL_SCOPE installScope;
    LPWSTR lpRegistryPath;
    LPWSTR lpGDFBinaryPath, lpGameInstallDirectory;

    TRACE("(%s)\n", debugstr_guid(InstanceID));

    /* first, check is game exists in CURRENT_USER scope  */
    installScope = GIS_CURRENT_USER;
    hr = GAMEUX_IsGameKeyExist(installScope, InstanceID, &lpRegistryPath);

    if(hr == S_FALSE)
    {
        /* game not found in CURRENT_USER scope, let's check in ALL_USERS */
        installScope = GIS_ALL_USERS;
        hr = GAMEUX_IsGameKeyExist(installScope, InstanceID, &lpRegistryPath);
    }

    if(hr == S_FALSE)
        /* still not found? let's inform user that game does not exists */
        hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);

    if(SUCCEEDED(hr))
    {
        /* game found, it's registry path is in lpRegistryPath and install
         * scope in installScope */
        TRACE("game found in registry (path %s), updating\n", debugstr_w(lpRegistryPath));

        /* first, read required data about game */
        hr = GAMEUX_LoadRegistryString(HKEY_LOCAL_MACHINE, lpRegistryPath,
            sConfigGDFBinaryPath, &lpGDFBinaryPath);

        if(SUCCEEDED(hr))
            hr = GAMEUX_LoadRegistryString(HKEY_LOCAL_MACHINE, lpRegistryPath,
                sConfigApplicationPath, &lpGameInstallDirectory);

        /* now remove currently existing registry key */
        if(SUCCEEDED(hr))
            hr = GAMEUX_RemoveRegistryRecord(InstanceID);

        /* and add it again, it will cause in reparsing of whole GDF */
        if(SUCCEEDED(hr))
            hr = GAMEUX_RegisterGame(lpGDFBinaryPath, lpGameInstallDirectory,
                                     installScope, InstanceID);

        HeapFree(GetProcessHeap(), 0, lpGDFBinaryPath);
        HeapFree(GetProcessHeap(), 0, lpGameInstallDirectory);
    }

    HeapFree(GetProcessHeap(), 0, lpRegistryPath);
    TRACE("returning 0x%x\n", hr);
    return hr;
}
/*******************************************************************************
 * GAMEUX_FindGameInstanceId
 *
 * Internal helper function. Description available in gameux_private.h file
 */
HRESULT GAMEUX_FindGameInstanceId(
        LPCWSTR 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 = GAMEUX_buildGameRegistryPath(installScope, NULL, &lpRegistryPath);

    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));

    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;
        }

        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 = GAMEUX_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);
                        found = TRUE;
                    }
                HeapFree(GetProcessHeap(), 0, lpValue);
            }
        }

        HeapFree(GetProcessHeap(), 0, lpName);
        RegCloseKey(hRootKey);
    }

    HeapFree(GetProcessHeap(), 0, lpRegistryPath);

    if((SUCCEEDED(hr) && !found) || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
        hr = S_FALSE;

    return hr;
}
/*******************************************************************************
 * GameExplorer implementation
 */

typedef struct _GameExplorerImpl
{
    IGameExplorer IGameExplorer_iface;
    IGameExplorer2 IGameExplorer2_iface;
    LONG ref;
} GameExplorerImpl;

static inline GameExplorerImpl *impl_from_IGameExplorer(IGameExplorer *iface)
{
    return CONTAINING_RECORD(iface, GameExplorerImpl, IGameExplorer_iface);
}

static inline GameExplorerImpl *impl_from_IGameExplorer2(IGameExplorer2 *iface)
{
    return CONTAINING_RECORD(iface, GameExplorerImpl, IGameExplorer2_iface);
}

static HRESULT WINAPI GameExplorerImpl_QueryInterface(
        IGameExplorer *iface,
        REFIID riid,
        void **ppvObject)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);

    TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppvObject);

    *ppvObject = NULL;

    if(IsEqualGUID(riid, &IID_IUnknown) ||
       IsEqualGUID(riid, &IID_IGameExplorer))
    {
        *ppvObject = &This->IGameExplorer_iface;
    }
    else if(IsEqualGUID(riid, &IID_IGameExplorer2))
    {
        *ppvObject = &This->IGameExplorer2_iface;
    }
    else
    {
        FIXME("interface %s not implemented\n", debugstr_guid(riid));
        return E_NOINTERFACE;
    }

    IGameExplorer_AddRef(iface);
    return S_OK;
}

static ULONG WINAPI GameExplorerImpl_AddRef(IGameExplorer *iface)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);
    LONG ref;

    ref = InterlockedIncrement(&This->ref);

    TRACE("(%p): ref=%d\n", This, ref);
    return ref;
}

static ULONG WINAPI GameExplorerImpl_Release(IGameExplorer *iface)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);
    LONG ref;

    ref = InterlockedDecrement(&This->ref);
    TRACE("(%p): ref=%d\n", This, ref);

    if(ref == 0)
    {
        TRACE("freeing GameExplorer object\n");
        HeapFree(GetProcessHeap(), 0, This);
    }

    return ref;
}

static HRESULT WINAPI GameExplorerImpl_AddGame(
        IGameExplorer *iface,
        BSTR bstrGDFBinaryPath,
        BSTR sGameInstallDirectory,
        GAME_INSTALL_SCOPE installScope,
        GUID *pInstanceID)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);
    TRACE("(%p, %s, %s, 0x%x, %s)\n", This, debugstr_w(bstrGDFBinaryPath), debugstr_w(sGameInstallDirectory), installScope, debugstr_guid(pInstanceID));
    return GAMEUX_RegisterGame(bstrGDFBinaryPath, sGameInstallDirectory, installScope, pInstanceID);
}

static HRESULT WINAPI GameExplorerImpl_RemoveGame(
        IGameExplorer *iface,
        GUID instanceID)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);

    TRACE("(%p, %s)\n", This, debugstr_guid(&instanceID));
    return GAMEUX_RemoveRegistryRecord(&instanceID);
}

static HRESULT WINAPI GameExplorerImpl_UpdateGame(
        IGameExplorer *iface,
        GUID instanceID)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);

    TRACE("(%p, %s)\n", This, debugstr_guid(&instanceID));
    return GAMEUX_UpdateGame(&instanceID);
}

static HRESULT WINAPI GameExplorerImpl_VerifyAccess(
        IGameExplorer *iface,
        BSTR sGDFBinaryPath,
        BOOL *pHasAccess)
{
    GameExplorerImpl *This = impl_from_IGameExplorer(iface);

    FIXME("(%p, %s, %p)\n", This, debugstr_w(sGDFBinaryPath), pHasAccess);
    *pHasAccess = TRUE;
    return S_OK;
}

static const struct IGameExplorerVtbl GameExplorerImplVtbl =
{
    GameExplorerImpl_QueryInterface,
    GameExplorerImpl_AddRef,
    GameExplorerImpl_Release,
    GameExplorerImpl_AddGame,
    GameExplorerImpl_RemoveGame,
    GameExplorerImpl_UpdateGame,
    GameExplorerImpl_VerifyAccess
};


static HRESULT WINAPI GameExplorer2Impl_QueryInterface(
        IGameExplorer2 *iface,
        REFIID riid,
        void **ppvObject)
{
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);
    return GameExplorerImpl_QueryInterface(&This->IGameExplorer_iface, riid, ppvObject);
}

static ULONG WINAPI GameExplorer2Impl_AddRef(IGameExplorer2 *iface)
{
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);
    return GameExplorerImpl_AddRef(&This->IGameExplorer_iface);
}

static ULONG WINAPI GameExplorer2Impl_Release(IGameExplorer2 *iface)
{
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);
    return GameExplorerImpl_Release(&This->IGameExplorer_iface);
}

static HRESULT WINAPI GameExplorer2Impl_CheckAccess(
        IGameExplorer2 *iface,
        LPCWSTR binaryGDFPath,
        BOOL *pHasAccess)
{
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);
    FIXME("stub (%p, %s, %p)\n", This, debugstr_w(binaryGDFPath), pHasAccess);
    return E_NOTIMPL;
}

static HRESULT WINAPI GameExplorer2Impl_InstallGame(
        IGameExplorer2 *iface,
        LPCWSTR binaryGDFPath,
        LPCWSTR installDirectory,
        GAME_INSTALL_SCOPE installScope)
{
    HRESULT hr;
    GUID instanceId;
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);

    TRACE("(%p, %s, %s, 0x%x)\n", This, debugstr_w(binaryGDFPath), debugstr_w(installDirectory), installScope);

    if(!binaryGDFPath)
        return E_INVALIDARG;

    hr = GAMEUX_FindGameInstanceId(binaryGDFPath, GIS_CURRENT_USER, &instanceId);

    if(hr == S_FALSE)
        hr = GAMEUX_FindGameInstanceId(binaryGDFPath, GIS_ALL_USERS, &instanceId);

    if(hr == S_FALSE)
    {
        /* if game isn't yet registered, then install it */
        instanceId = GUID_NULL;
        hr = GAMEUX_RegisterGame(binaryGDFPath, installDirectory, installScope, &instanceId);
    }
    else if(hr == S_OK)
        /* otherwise, update game */
        hr = GAMEUX_UpdateGame(&instanceId);

    return hr;
}

static HRESULT WINAPI GameExplorer2Impl_UninstallGame(
        IGameExplorer2 *iface,
        LPCWSTR binaryGDFPath)
{
    HRESULT hr;
    GUID instanceId;
    GameExplorerImpl *This = impl_from_IGameExplorer2(iface);
    TRACE("(%p, %s)\n", This, debugstr_w(binaryGDFPath));

    if(!binaryGDFPath)
        return E_INVALIDARG;

    hr = GAMEUX_FindGameInstanceId(binaryGDFPath, GIS_CURRENT_USER, &instanceId);

    if(hr == S_FALSE)
        hr = GAMEUX_FindGameInstanceId(binaryGDFPath, GIS_ALL_USERS, &instanceId);

    if(hr == S_OK)
        hr = GAMEUX_RemoveRegistryRecord(&instanceId);

    return hr;
}

static const struct IGameExplorer2Vtbl GameExplorer2ImplVtbl =
{
    GameExplorer2Impl_QueryInterface,
    GameExplorer2Impl_AddRef,
    GameExplorer2Impl_Release,
    GameExplorer2Impl_InstallGame,
    GameExplorer2Impl_UninstallGame,
    GameExplorer2Impl_CheckAccess
};

/*
 * Construction routine
 */
HRESULT GameExplorer_create(
        IUnknown* pUnkOuter,
        IUnknown** ppObj)
{
    GameExplorerImpl *pGameExplorer;

    TRACE("(%p, %p)\n", pUnkOuter, ppObj);

    pGameExplorer = HeapAlloc(GetProcessHeap(), 0, sizeof(*pGameExplorer));

    if(!pGameExplorer)
        return E_OUTOFMEMORY;

    pGameExplorer->IGameExplorer_iface.lpVtbl = &GameExplorerImplVtbl;
    pGameExplorer->IGameExplorer2_iface.lpVtbl = &GameExplorer2ImplVtbl;
    pGameExplorer->ref = 1;

    *ppObj = (IUnknown*)&pGameExplorer->IGameExplorer_iface;

    TRACE("returning iface: %p\n", *ppObj);
    return S_OK;
}