|  | /* | 
|  | *    Gameux library coclass GameStatistics 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 "winreg.h" | 
|  | #include "msxml2.h" | 
|  | #include "shlwapi.h" | 
|  | #include "shlobj.h" | 
|  |  | 
|  | #include "gameux.h" | 
|  | #include "gameux_private.h" | 
|  |  | 
|  | #include "wine/debug.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(gameux); | 
|  |  | 
|  | /* | 
|  | * constant definitions | 
|  | */ | 
|  | #define MAX_CATEGORY_LENGTH 60 | 
|  | #define MAX_NAME_LENGTH 30 | 
|  | #define MAX_VALUE_LENGTH 30 | 
|  | #define MAX_CATEGORIES 10 | 
|  | #define MAX_STATS_PER_CATEGORY 10 | 
|  | /******************************************************************************* | 
|  | * Game statistics helper components | 
|  | */ | 
|  | /******************************************************************************* | 
|  | * struct GAMEUX_STATS | 
|  | * | 
|  | * set of structures for containing game's data | 
|  | */ | 
|  | struct GAMEUX_STATS_STAT | 
|  | { | 
|  | WCHAR sName[MAX_NAME_LENGTH+1]; | 
|  | WCHAR sValue[MAX_VALUE_LENGTH+1]; | 
|  | }; | 
|  | struct GAMEUX_STATS_CATEGORY | 
|  | { | 
|  | WCHAR sName[MAX_CATEGORY_LENGTH+1]; | 
|  | struct GAMEUX_STATS_STAT stats[MAX_STATS_PER_CATEGORY]; | 
|  | }; | 
|  | struct GAMEUX_STATS | 
|  | { | 
|  | WCHAR sStatsFile[MAX_PATH]; | 
|  | struct GAMEUX_STATS_CATEGORY categories[MAX_CATEGORIES]; | 
|  | }; | 
|  | /******************************************************************************* | 
|  | * GAMEUX_createStatsDirectory | 
|  | * | 
|  | * Helper function, creates directory to store game statistics | 
|  | * | 
|  | * Parameters | 
|  | *  path                [I]     path to game statistics file. | 
|  | *                              base directory of this file will | 
|  | *                              be created if it doesn't exists | 
|  | */ | 
|  | static HRESULT GAMEUX_createStatsDirectory(LPCWSTR lpFilePath) | 
|  | { | 
|  | HRESULT hr; | 
|  | WCHAR lpDirectoryPath[MAX_PATH]; | 
|  | LPCWSTR lpEnd; | 
|  |  | 
|  | lpEnd = StrRChrW(lpFilePath, NULL, '\\'); | 
|  | lstrcpynW(lpDirectoryPath, lpFilePath, lpEnd-lpFilePath+1); | 
|  |  | 
|  | hr = HRESULT_FROM_WIN32(SHCreateDirectoryExW(NULL, lpDirectoryPath, NULL)); | 
|  |  | 
|  | if(hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) || | 
|  | hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) | 
|  | hr = S_FALSE; | 
|  |  | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************* | 
|  | * GAMEUX_updateStatisticsFile | 
|  | * | 
|  | * Helper function updating data stored in statistics file | 
|  | * | 
|  | * Parameters: | 
|  | *  data                [I]     pointer to struct containing | 
|  | *                              statistics data | 
|  | */ | 
|  | static HRESULT GAMEUX_updateStatisticsFile(struct GAMEUX_STATS *stats) | 
|  | { | 
|  | static const WCHAR sStatistics[] = {'S','t','a','t','i','s','t','i','c','s',0}; | 
|  | static const WCHAR sCategory[] = {'C','a','t','e','g','o','r','y',0}; | 
|  | static const WCHAR sIndex[] = {'I','n','d','e','x',0}; | 
|  | static const WCHAR sStatistic[] = {'S','t','a','t','i','s','t','i','c',0}; | 
|  | static const WCHAR sName[] = {'N','a','m','e',0}; | 
|  | static const WCHAR sValue[] = {'V','a','l','u','e',0}; | 
|  |  | 
|  | HRESULT hr = S_OK; | 
|  | IXMLDOMDocument *document; | 
|  | IXMLDOMElement *root, *categoryElement, *statisticsElement; | 
|  | IXMLDOMNode *categoryNode, *statisticsNode; | 
|  | VARIANT vStatsFilePath, vValue; | 
|  | BSTR bstrStatistics = NULL, bstrCategory = NULL, bstrIndex = NULL, | 
|  | bstrStatistic = NULL, bstrName = NULL, bstrValue = NULL; | 
|  | int i, j; | 
|  |  | 
|  | TRACE("(%p)\n", stats); | 
|  |  | 
|  | V_VT(&vStatsFilePath) = VT_BSTR; | 
|  | V_BSTR(&vStatsFilePath) = SysAllocString(stats->sStatsFile); | 
|  | if(!V_BSTR(&vStatsFilePath)) | 
|  | hr = E_OUTOFMEMORY; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, | 
|  | &IID_IXMLDOMDocument, (void**)&document); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrStatistics = SysAllocString(sStatistics); | 
|  | if(!bstrStatistics) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMDocument_createElement(document, bstrStatistics, &root); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrCategory = SysAllocString(sCategory); | 
|  | if(!bstrCategory) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrIndex = SysAllocString(sIndex); | 
|  | if(!bstrIndex) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrStatistic = SysAllocString(sStatistic); | 
|  | if(!bstrStatistic) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrName = SysAllocString(sName); | 
|  | if(!bstrName) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrValue = SysAllocString(sValue); | 
|  | if(!bstrValue) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | for(i=0; i<MAX_CATEGORIES; ++i) | 
|  | { | 
|  | if(lstrlenW(stats->categories[i].sName)==0) | 
|  | continue; | 
|  |  | 
|  | V_VT(&vValue) = VT_INT; | 
|  | V_INT(&vValue) = NODE_ELEMENT; | 
|  |  | 
|  | hr = IXMLDOMDocument_createNode(document, vValue, bstrCategory, NULL, &categoryNode); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMNode_QueryInterface(categoryNode, &IID_IXMLDOMElement, (LPVOID*)&categoryElement); | 
|  |  | 
|  | V_INT(&vValue) = i; | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMElement_setAttribute(categoryElement, bstrIndex, vValue); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | V_VT(&vValue) = VT_BSTR; | 
|  | V_BSTR(&vValue) = SysAllocString(stats->categories[i].sName); | 
|  | if(!V_BSTR(&vValue)) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | TRACE("storing category %d: %s\n", i, debugstr_w(V_BSTR(&vValue))); | 
|  | hr = IXMLDOMElement_setAttribute(categoryElement, bstrName, vValue); | 
|  | } | 
|  |  | 
|  | SysFreeString(V_BSTR(&vValue)); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | for(j=0; j<MAX_STATS_PER_CATEGORY; ++j) | 
|  | { | 
|  | if(lstrlenW(stats->categories[i].stats[j].sName)==0) | 
|  | continue; | 
|  |  | 
|  | V_VT(&vValue) = VT_INT; | 
|  | V_INT(&vValue) = NODE_ELEMENT; | 
|  |  | 
|  | hr = IXMLDOMDocument_createNode(document, vValue, bstrStatistic, NULL, &statisticsNode); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMNode_QueryInterface(statisticsNode, &IID_IXMLDOMElement, (LPVOID*)&statisticsElement); | 
|  |  | 
|  | V_INT(&vValue) = j; | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMElement_setAttribute(statisticsElement, bstrIndex, vValue); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | V_VT(&vValue) = VT_BSTR; | 
|  | V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sName); | 
|  | if(!V_BSTR(&vValue)) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue))); | 
|  | hr = IXMLDOMElement_setAttribute(statisticsElement, bstrName, vValue); | 
|  | } | 
|  |  | 
|  | SysFreeString(V_BSTR(&vValue)); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | V_VT(&vValue) = VT_BSTR; | 
|  | V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sValue); | 
|  | if(!V_BSTR(&vValue)) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue))); | 
|  | hr = IXMLDOMElement_setAttribute(statisticsElement, bstrValue, vValue); | 
|  | } | 
|  |  | 
|  | SysFreeString(V_BSTR(&vValue)); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMElement_appendChild(categoryNode, statisticsNode, &statisticsNode); | 
|  |  | 
|  | IXMLDOMElement_Release(statisticsElement); | 
|  | IXMLDOMNode_Release(statisticsNode); | 
|  | } | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMElement_appendChild(root, categoryNode, &categoryNode); | 
|  |  | 
|  | IXMLDOMElement_Release(categoryElement); | 
|  | IXMLDOMNode_Release(categoryNode); | 
|  |  | 
|  | if(FAILED(hr)) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMDocument_putref_documentElement(document, root); | 
|  |  | 
|  | IXMLDOMElement_Release(root); | 
|  |  | 
|  | TRACE("saving game statistics in %s file\n", debugstr_w(stats->sStatsFile)); | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = GAMEUX_createStatsDirectory(stats->sStatsFile); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMDocument_save(document, vStatsFilePath); | 
|  |  | 
|  | IXMLDOMDocument_Release(document); | 
|  |  | 
|  | SysFreeString(bstrValue); | 
|  | SysFreeString(bstrName); | 
|  | SysFreeString(bstrStatistic); | 
|  | SysFreeString(bstrIndex); | 
|  | SysFreeString(bstrCategory); | 
|  | SysFreeString(bstrStatistics); | 
|  | SysFreeString(V_BSTR(&vStatsFilePath)); | 
|  | TRACE("ret=0x%x\n", hr); | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************************* | 
|  | * GAMEUX_buildStatisticsFilePath | 
|  | * Creates path to file contaning statistics of game with given id. | 
|  | * | 
|  | * Parameters: | 
|  | *  lpApplicationId                         [I]     application id of game, | 
|  | *                                                  as string | 
|  | *  lpStatisticsFile                        [O]     array where path will be | 
|  | *                                                  stored. It's size must be | 
|  | *                                                  at least MAX_PATH | 
|  | */ | 
|  | static HRESULT GAMEUX_buildStatisticsFilePath( | 
|  | LPCWSTR lpApplicationId, | 
|  | LPWSTR lpStatisticsFile) | 
|  | { | 
|  | static const WCHAR sBackslash[] = {'\\',0}; | 
|  | static const WCHAR sStatisticsDir[] = {'\\','M','i','c','r','o','s','o','f','t', | 
|  | '\\','W','i','n','d','o','w','s','\\','G','a','m','e','E','x','p', | 
|  | 'l','o','r','e','r','\\','G','a','m','e','S','t','a','t','i','s', | 
|  | 't','i','c','s','\\',0}; | 
|  | static const WCHAR sDotGamestats[] = {'.','g','a','m','e','s','t','a','t','s',0}; | 
|  |  | 
|  | HRESULT hr; | 
|  |  | 
|  | hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, lpStatisticsFile); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | lstrcatW(lpStatisticsFile, sStatisticsDir); | 
|  | lstrcatW(lpStatisticsFile, lpApplicationId); | 
|  | lstrcatW(lpStatisticsFile, sBackslash); | 
|  | lstrcatW(lpStatisticsFile, lpApplicationId); | 
|  | lstrcatW(lpStatisticsFile, sDotGamestats); | 
|  | } | 
|  |  | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************************* | 
|  | * GAMEUX_getAppIdFromGDFPath | 
|  | * | 
|  | * Loads application identifier associated with given GDF binary. | 
|  | * Routine reads identifier from registry, so will fail if game | 
|  | * is not registered. | 
|  | * | 
|  | * Parameters: | 
|  | *  GDFBinaryPath                       [I]     path to gdf binary | 
|  | *  lpApplicationId                     [O]     place to store application id. | 
|  | *                                              must be at least 49 characters | 
|  | *                                              to store guid and termination 0 | 
|  | */ | 
|  | static HRESULT GAMEUX_getAppIdFromGDFPath( | 
|  | LPCWSTR GDFBinaryPath, | 
|  | LPWSTR lpApplicationId) | 
|  | { | 
|  | static const WCHAR sApplicationId[] = | 
|  | {'A','p','p','l','i','c','a','t','i','o','n','I','d',0}; | 
|  |  | 
|  | HRESULT hr; | 
|  | GAME_INSTALL_SCOPE installScope; | 
|  | GUID instanceId; | 
|  | LPWSTR lpRegistryPath = NULL; | 
|  | HKEY hKey; | 
|  | DWORD dwLength = 49*sizeof(WCHAR);/* place for GUID */ | 
|  |  | 
|  | TRACE("(%s, %p)\n", debugstr_w(GDFBinaryPath), lpApplicationId); | 
|  |  | 
|  | if(!GDFBinaryPath) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | installScope = GIS_CURRENT_USER; | 
|  | hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId); | 
|  |  | 
|  | if(hr == S_FALSE) | 
|  | { | 
|  | installScope = GIS_ALL_USERS; | 
|  | hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId); | 
|  | } | 
|  |  | 
|  | if(hr == S_FALSE) | 
|  | /* game not registered, so statistics cannot be used */ | 
|  | hr = E_FAIL; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | /* game is registered, let's read it's application id from registry */ | 
|  | hr = GAMEUX_buildGameRegistryPath(installScope, &instanceId, &lpRegistryPath); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE, | 
|  | lpRegistryPath, 0, KEY_READ | KEY_WOW64_64KEY, &hKey)); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = HRESULT_FROM_WIN32(RegGetValueW(hKey, | 
|  | NULL, sApplicationId, RRF_RT_REG_SZ, | 
|  | NULL, lpApplicationId, &dwLength)); | 
|  |  | 
|  | HeapFree(GetProcessHeap(), 0, lpRegistryPath); | 
|  | RegCloseKey(hKey); | 
|  |  | 
|  | TRACE("found app id: %s, return: %#x\n", debugstr_w(lpApplicationId), hr); | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************* | 
|  | * GAMEUX_loadGameStatisticsFromFile | 
|  | * Helper function, loads game statistics from file and stores them | 
|  | * in the structure. | 
|  | * | 
|  | * Parameters: | 
|  | *  data                [I/O]   structure containing file name to | 
|  | *                              load and data fields to store data in | 
|  | */ | 
|  | static HRESULT GAMEUX_loadStatisticsFromFile(struct GAMEUX_STATS *data) | 
|  | { | 
|  | static const WCHAR sStatistics[] = {'S','t','a','t','i','s','t','i','c','s',0}; | 
|  | static const WCHAR sCategory[] = {'C','a','t','e','g','o','r','y',0}; | 
|  | static const WCHAR sIndex[] = {'I','n','d','e','x',0}; | 
|  | static const WCHAR sStatistic[] = {'S','t','a','t','i','s','t','i','c',0}; | 
|  | static const WCHAR sName[] = {'N','a','m','e',0}; | 
|  | static const WCHAR sValue[] = {'V','a','l','u','e',0}; | 
|  |  | 
|  | HRESULT hr = S_OK; | 
|  | IXMLDOMDocument *document = NULL; | 
|  | IXMLDOMElement *root = NULL, *categoryElement, *statisticElement; | 
|  | IXMLDOMNode *categoryNode, *statisticNode; | 
|  | IXMLDOMNodeList *rootChildren = NULL, *categoryChildren; | 
|  | VARIANT vStatsFilePath, vValue; | 
|  | BSTR bstrStatistics = NULL, bstrCategory = NULL, bstrIndex = NULL, | 
|  | bstrStatistic = NULL, bstrName = NULL, bstrValue = NULL; | 
|  | VARIANT_BOOL isSuccessful =  VARIANT_FALSE; | 
|  | int i, j; | 
|  |  | 
|  | TRACE("(%p)\n", data); | 
|  |  | 
|  | V_VT(&vStatsFilePath) = VT_BSTR; | 
|  | V_BSTR(&vStatsFilePath) = SysAllocString(data->sStatsFile); | 
|  | if(!V_BSTR(&vStatsFilePath)) | 
|  | hr = E_OUTOFMEMORY; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = CoCreateInstance(&CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void**)&document); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrStatistics = SysAllocString(sStatistics); | 
|  | if(!bstrStatistics) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrCategory = SysAllocString(sCategory); | 
|  | if(!bstrCategory) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrIndex = SysAllocString(sIndex); | 
|  | if(!bstrIndex) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrStatistic = SysAllocString(sStatistic); | 
|  | if(!bstrStatistic) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrName = SysAllocString(sName); | 
|  | if(!bstrName) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | bstrValue = SysAllocString(sValue); | 
|  | if(!bstrValue) | 
|  | hr = E_OUTOFMEMORY; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = IXMLDOMDocument_load(document, vStatsFilePath, &isSuccessful); | 
|  |  | 
|  | if(hr == S_OK && isSuccessful != VARIANT_TRUE) | 
|  | hr = S_FALSE; | 
|  |  | 
|  | if( hr == S_OK ) | 
|  | hr = IXMLDOMDocument_get_documentElement(document, &root); | 
|  |  | 
|  | if(hr == S_OK) | 
|  | hr = IXMLDOMElement_get_childNodes(root, &rootChildren); | 
|  |  | 
|  | if(hr == S_OK) | 
|  | { | 
|  | hr = S_OK; | 
|  | while(hr == S_OK) | 
|  | { | 
|  | hr = IXMLDOMNodeList_nextNode(rootChildren, &categoryNode); | 
|  |  | 
|  | if(hr == S_OK) | 
|  | { | 
|  | hr = IXMLDOMNode_QueryInterface(categoryNode, &IID_IXMLDOMElement, (LPVOID*)&categoryElement); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | hr = IXMLDOMElement_getAttribute(categoryElement, bstrIndex, &vValue); | 
|  | if( hr == S_OK && V_VT(&vValue) != VT_BSTR) | 
|  | hr = E_FAIL; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | i = StrToIntW(V_BSTR(&vValue)); | 
|  | hr = IXMLDOMElement_getAttribute(categoryElement, bstrName, &vValue); | 
|  | if( hr == S_OK && V_VT(&vValue) != VT_BSTR) | 
|  | hr = E_FAIL; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | lstrcpynW(data->categories[i].sName, V_BSTR(&vValue), MAX_CATEGORY_LENGTH); | 
|  | TRACE("category %d name %s\n", i, debugstr_w(data->categories[i].sName)); | 
|  | hr = IXMLDOMElement_get_childNodes(categoryElement, &categoryChildren); | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | hr = S_OK; | 
|  | while(hr == S_OK) | 
|  | { | 
|  | hr = IXMLDOMNodeList_nextNode(categoryChildren, &statisticNode); | 
|  |  | 
|  | if(hr == S_OK) | 
|  | { | 
|  | hr = IXMLDOMNode_QueryInterface(statisticNode, &IID_IXMLDOMElement, (LPVOID*)&statisticElement); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | hr = IXMLDOMElement_getAttribute(statisticElement, bstrIndex, &vValue); | 
|  | if( hr == S_OK && V_VT(&vValue) != VT_BSTR) | 
|  | hr = E_FAIL; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | j = StrToIntW(V_BSTR(&vValue)); | 
|  | hr = IXMLDOMElement_getAttribute(statisticElement, bstrName, &vValue); | 
|  | if( hr == S_OK && V_VT(&vValue) != VT_BSTR) | 
|  | hr = E_FAIL; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | lstrcpynW(data->categories[i].stats[j].sName, V_BSTR(&vValue), MAX_NAME_LENGTH); | 
|  | hr = IXMLDOMElement_getAttribute(statisticElement, bstrValue, &vValue); | 
|  | if( hr == S_OK && V_VT(&vValue) != VT_BSTR) | 
|  | hr = E_FAIL; | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | lstrcpynW(data->categories[i].stats[j].sValue, V_BSTR(&vValue), MAX_VALUE_LENGTH); | 
|  | TRACE("statistic %d name %s value %s\n", j, | 
|  | debugstr_w(data->categories[i].stats[j].sName), | 
|  | debugstr_w(data->categories[i].stats[j].sValue)); | 
|  | } | 
|  | IXMLDOMElement_Release(statisticElement); | 
|  | } | 
|  |  | 
|  | IXMLDOMNode_Release(statisticNode); | 
|  | } | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = S_OK; | 
|  | } | 
|  | IXMLDOMElement_Release(categoryElement); | 
|  | } | 
|  |  | 
|  | IXMLDOMNode_Release(categoryNode); | 
|  | } | 
|  | } | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = S_OK; | 
|  | } | 
|  |  | 
|  | if(rootChildren) IXMLDOMNodeList_Release(rootChildren); | 
|  | if(root) IXMLDOMElement_Release(root); | 
|  | if(document) IXMLDOMDocument_Release(document); | 
|  |  | 
|  | SysFreeString(bstrValue); | 
|  | SysFreeString(bstrName); | 
|  | SysFreeString(bstrStatistic); | 
|  | SysFreeString(bstrIndex); | 
|  | SysFreeString(bstrCategory); | 
|  | SysFreeString(bstrStatistics); | 
|  | SysFreeString(V_BSTR(&vStatsFilePath)); | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************* | 
|  | * GAMEUX_loadGameStatistics | 
|  | * | 
|  | * Helper function which loads game statistics associated with game | 
|  | * into interface's internal structures | 
|  | * | 
|  | * Parameters: | 
|  | *  pStats              [O]     structure which will receive data | 
|  | *  sGameId             [I]     application instance Id, stored as string | 
|  | *                              to avoid additional conversions | 
|  | *  openType            [I]     allowed ways of opening statistics | 
|  | *  pOpenResult         [O]     way used to open statistics | 
|  | * | 
|  | */ | 
|  | static HRESULT GAMEUX_loadGameStatistics(struct GAMEUX_STATS *pStats, | 
|  | LPWSTR sGameId, | 
|  | GAMESTATS_OPEN_TYPE openType, | 
|  | GAMESTATS_OPEN_RESULT* pOpenResult) | 
|  | { | 
|  | HRESULT hr; | 
|  | TRACE("(%p, %s, %d, %p)\n", pStats, debugstr_w(sGameId), openType, pOpenResult); | 
|  |  | 
|  | hr = GAMEUX_buildStatisticsFilePath(sGameId, pStats->sStatsFile); | 
|  |  | 
|  | hr = GAMEUX_loadStatisticsFromFile(pStats); | 
|  | TRACE("ldstats finished, res: %#x\n", hr); | 
|  | if(hr == S_OK) | 
|  | { | 
|  | *pOpenResult = GAMESTATS_OPEN_OPENED; | 
|  | } | 
|  | else if(hr == S_FALSE && openType == GAMESTATS_OPEN_OPENORCREATE) /* file does not exist */ | 
|  | { | 
|  | /* create new statitics, not yet connected with file */ | 
|  | ZeroMemory(pStats->categories, sizeof(pStats->categories)); | 
|  | *pOpenResult = GAMESTATS_OPEN_CREATED; | 
|  | hr = S_OK; | 
|  | } | 
|  | else | 
|  | hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); | 
|  |  | 
|  | TRACE("openResult=%#x ret=%#x\n", *pOpenResult, hr); | 
|  | return hr; | 
|  | } | 
|  | /******************************************************************* | 
|  | * IGameStatistics implementation | 
|  | */ | 
|  | typedef struct _GameStatisticsImpl | 
|  | { | 
|  | const struct IGameStatisticsVtbl *lpVtbl; | 
|  | LONG ref; | 
|  | struct GAMEUX_STATS stats; | 
|  | } GameStatisticsImpl; | 
|  |  | 
|  | static inline GameStatisticsImpl *impl_from_IGameStatistics( IGameStatistics *iface ) | 
|  | { | 
|  | return (GameStatisticsImpl *)((char*)iface - FIELD_OFFSET(GameStatisticsImpl, lpVtbl)); | 
|  | } | 
|  | static inline IGameStatistics *IGameStatistics_from_impl( GameStatisticsImpl* This ) | 
|  | { | 
|  | return (struct IGameStatistics*)&This->lpVtbl; | 
|  | } | 
|  |  | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_QueryInterface( | 
|  | IGameStatistics *iface, | 
|  | REFIID riid, | 
|  | void **ppvObject) | 
|  | { | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics( iface ); | 
|  |  | 
|  | TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject ); | 
|  |  | 
|  | *ppvObject = NULL; | 
|  |  | 
|  | if ( IsEqualGUID( riid, &IID_IUnknown ) || | 
|  | IsEqualGUID( riid, &IID_IGameStatistics ) ) | 
|  | { | 
|  | *ppvObject = iface; | 
|  | } | 
|  | else | 
|  | { | 
|  | FIXME("interface %s not implemented\n", debugstr_guid(riid)); | 
|  | return E_NOINTERFACE; | 
|  | } | 
|  |  | 
|  | IGameStatistics_AddRef( iface ); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI GameStatisticsImpl_AddRef(IGameStatistics *iface) | 
|  | { | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics( iface ); | 
|  | LONG ref; | 
|  |  | 
|  | ref = InterlockedIncrement(&This->ref); | 
|  |  | 
|  | TRACE("(%p): ref=%d\n", This, ref); | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI GameStatisticsImpl_Release(IGameStatistics *iface) | 
|  | { | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics( iface ); | 
|  | LONG ref; | 
|  |  | 
|  | ref = InterlockedDecrement( &This->ref ); | 
|  | TRACE("(%p): ref=%d\n", This, ref); | 
|  |  | 
|  | if ( ref == 0 ) | 
|  | { | 
|  | TRACE("freeing IGameStatistics\n"); | 
|  | HeapFree( GetProcessHeap(), 0, This ); | 
|  | } | 
|  |  | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetMaxCategoryLength( | 
|  | IGameStatistics *iface, | 
|  | UINT *cch) | 
|  | { | 
|  | TRACE("(%p, %p)\n", iface, cch); | 
|  | if(!cch) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *cch = MAX_CATEGORY_LENGTH; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetMaxNameLength( | 
|  | IGameStatistics *iface, | 
|  | UINT *cch) | 
|  | { | 
|  | TRACE("(%p, %p)\n", iface, cch); | 
|  | if(!cch) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *cch = MAX_NAME_LENGTH; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetMaxValueLength( | 
|  | IGameStatistics *iface, | 
|  | UINT *cch) | 
|  | { | 
|  | TRACE("(%p, %p)\n", iface, cch); | 
|  | if(!cch) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *cch = MAX_VALUE_LENGTH; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetMaxCategories( | 
|  | IGameStatistics *iface, | 
|  | WORD *pMax) | 
|  | { | 
|  | TRACE("(%p, %p)\n", iface, pMax); | 
|  | if(!pMax) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *pMax = MAX_CATEGORIES; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetMaxStatsPerCategory( | 
|  | IGameStatistics *iface, | 
|  | WORD *pMax) | 
|  | { | 
|  | TRACE("(%p, %p)\n", iface, pMax); | 
|  | if(!pMax) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *pMax = MAX_STATS_PER_CATEGORY; | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_SetCategoryTitle( | 
|  | IGameStatistics *iface, | 
|  | WORD categoryIndex, | 
|  | LPCWSTR title) | 
|  | { | 
|  | HRESULT hr = S_OK; | 
|  | DWORD dwLength; | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics(iface); | 
|  |  | 
|  | TRACE("(%p, %d, %s)\n", This, categoryIndex, debugstr_w(title)); | 
|  |  | 
|  | if(!title || categoryIndex >= MAX_CATEGORIES) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | dwLength = lstrlenW(title); | 
|  |  | 
|  | if(dwLength > MAX_CATEGORY_LENGTH) | 
|  | { | 
|  | hr = S_FALSE; | 
|  | dwLength = MAX_CATEGORY_LENGTH; | 
|  | } | 
|  |  | 
|  | lstrcpynW(This->stats.categories[categoryIndex].sName, | 
|  | title, dwLength+1); | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetCategoryTitle( | 
|  | IGameStatistics *iface, | 
|  | WORD categoryIndex, | 
|  | LPWSTR *pTitle) | 
|  | { | 
|  | HRESULT hr = S_OK; | 
|  | LONG nLength; | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics(iface); | 
|  |  | 
|  | TRACE("%p, %d, %p\n", This, categoryIndex, pTitle); | 
|  |  | 
|  | if(!pTitle) | 
|  | return E_INVALIDARG; | 
|  | *pTitle = NULL; | 
|  |  | 
|  | if (categoryIndex >= MAX_CATEGORIES) | 
|  | hr = E_INVALIDARG; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | nLength = lstrlenW(This->stats.categories[categoryIndex].sName); | 
|  | if(nLength != 0) | 
|  | { | 
|  | *pTitle = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1)); | 
|  | lstrcpyW(*pTitle, This->stats.categories[categoryIndex].sName); | 
|  | } | 
|  | } | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetStatistic( | 
|  | IGameStatistics *iface, | 
|  | WORD categoryIndex, | 
|  | WORD statIndex, | 
|  | LPWSTR *pName, | 
|  | LPWSTR *pValue) | 
|  | { | 
|  | HRESULT hr = S_OK; | 
|  | LONG nLength; | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics(iface); | 
|  |  | 
|  | TRACE("%p, %d,%d, %p, %p\n", This, categoryIndex, statIndex, pName, pValue); | 
|  |  | 
|  | if(!pName || !pValue) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | *pName = NULL; | 
|  | *pValue = NULL; | 
|  |  | 
|  | if(categoryIndex >= MAX_CATEGORIES || statIndex >= MAX_STATS_PER_CATEGORY) | 
|  | hr = E_INVALIDARG; | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | nLength = lstrlenW(This->stats.categories[categoryIndex].stats[statIndex].sName); | 
|  | if(nLength != 0) | 
|  | { | 
|  | *pName = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1)); | 
|  | if(!(*pName)) | 
|  | hr = E_OUTOFMEMORY; | 
|  | else | 
|  | lstrcpyW(*pName, This->stats.categories[categoryIndex].stats[statIndex].sName); | 
|  | } | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | nLength = lstrlenW(This->stats.categories[categoryIndex].stats[statIndex].sValue); | 
|  | if(nLength != 0) | 
|  | { | 
|  | *pValue = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1)); | 
|  | if(!(*pValue)) | 
|  | hr = E_OUTOFMEMORY; | 
|  | else | 
|  | lstrcpyW(*pValue, This->stats.categories[categoryIndex].stats[statIndex].sValue); | 
|  | } | 
|  | } | 
|  |  | 
|  | TRACE("returning pair; %s => %s\n", debugstr_w(*pName), debugstr_w(*pValue)); | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_SetStatistic( | 
|  | IGameStatistics *iface, | 
|  | WORD categoryIndex, | 
|  | WORD statIndex, | 
|  | LPCWSTR name, | 
|  | LPCWSTR value) | 
|  | { | 
|  | HRESULT hr = S_OK; | 
|  | DWORD dwNameLen, dwValueLen; | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics(iface); | 
|  |  | 
|  | TRACE("(%p, %d, %d, %s, %s)\n", This, categoryIndex, statIndex, | 
|  | debugstr_w(name), debugstr_w(value)); | 
|  |  | 
|  | if(!name) | 
|  | return S_FALSE; | 
|  |  | 
|  | if(categoryIndex >= MAX_CATEGORIES || statIndex >= MAX_STATS_PER_CATEGORY) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | dwNameLen = lstrlenW(name); | 
|  |  | 
|  | if(dwNameLen > MAX_NAME_LENGTH) | 
|  | { | 
|  | hr = S_FALSE; | 
|  | dwNameLen = MAX_NAME_LENGTH; | 
|  | } | 
|  |  | 
|  | lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sName, | 
|  | name, dwNameLen+1); | 
|  |  | 
|  | if(value) | 
|  | { | 
|  | dwValueLen = lstrlenW(value); | 
|  |  | 
|  | if(dwValueLen > MAX_VALUE_LENGTH) | 
|  | { | 
|  | hr = S_FALSE; | 
|  | dwValueLen = MAX_VALUE_LENGTH; | 
|  | } | 
|  |  | 
|  | lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sValue, | 
|  | value, dwValueLen+1); | 
|  | } | 
|  | else | 
|  | /* Windows allows to pass NULL as value */ | 
|  | This->stats.categories[categoryIndex].stats[statIndex].sValue[0] = 0; | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_Save( | 
|  | IGameStatistics *iface, | 
|  | BOOL trackChanges) | 
|  | { | 
|  | GameStatisticsImpl *This = impl_from_IGameStatistics(iface); | 
|  | HRESULT hr = S_OK; | 
|  |  | 
|  | TRACE("(%p, %d)\n", This, trackChanges); | 
|  |  | 
|  | if(trackChanges == TRUE) | 
|  | FIXME("tracking changes not yet implemented\n"); | 
|  |  | 
|  | hr = GAMEUX_updateStatisticsFile(&This->stats); | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_SetLastPlayedCategory( | 
|  | IGameStatistics *iface, | 
|  | UINT categoryIndex) | 
|  | { | 
|  | FIXME("stub\n"); | 
|  | return E_NOTIMPL; | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsImpl_GetLastPlayedCategory( | 
|  | IGameStatistics *iface, | 
|  | UINT *pCategoryIndex) | 
|  | { | 
|  | FIXME("stub\n"); | 
|  | return E_NOTIMPL; | 
|  | } | 
|  |  | 
|  | static const struct IGameStatisticsVtbl GameStatisticsImplVtbl = | 
|  | { | 
|  | GameStatisticsImpl_QueryInterface, | 
|  | GameStatisticsImpl_AddRef, | 
|  | GameStatisticsImpl_Release, | 
|  | GameStatisticsImpl_GetMaxCategoryLength, | 
|  | GameStatisticsImpl_GetMaxNameLength, | 
|  | GameStatisticsImpl_GetMaxValueLength, | 
|  | GameStatisticsImpl_GetMaxCategories, | 
|  | GameStatisticsImpl_GetMaxStatsPerCategory, | 
|  | GameStatisticsImpl_SetCategoryTitle, | 
|  | GameStatisticsImpl_GetCategoryTitle, | 
|  | GameStatisticsImpl_GetStatistic, | 
|  | GameStatisticsImpl_SetStatistic, | 
|  | GameStatisticsImpl_Save, | 
|  | GameStatisticsImpl_SetLastPlayedCategory, | 
|  | GameStatisticsImpl_GetLastPlayedCategory | 
|  | }; | 
|  |  | 
|  |  | 
|  | static HRESULT create_IGameStatistics(GameStatisticsImpl** ppStats) | 
|  | { | 
|  | TRACE("(%p)\n", ppStats); | 
|  |  | 
|  | *ppStats = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(**ppStats)); | 
|  | if(!(*ppStats)) | 
|  | return E_OUTOFMEMORY; | 
|  |  | 
|  | (*ppStats)->lpVtbl = &GameStatisticsImplVtbl; | 
|  | (*ppStats)->ref = 1; | 
|  |  | 
|  | TRACE("returning coclass: %p\n", *ppStats); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | /******************************************************************************* | 
|  | * IGameStatisticsMgr implementation | 
|  | */ | 
|  | typedef struct _GameStatisticsMgrImpl | 
|  | { | 
|  | const struct IGameStatisticsMgrVtbl *lpVtbl; | 
|  | LONG ref; | 
|  | } GameStatisticsMgrImpl; | 
|  |  | 
|  | static inline GameStatisticsMgrImpl *impl_from_IGameStatisticsMgr( IGameStatisticsMgr *iface ) | 
|  | { | 
|  | return (GameStatisticsMgrImpl *)((char*)iface - FIELD_OFFSET(GameStatisticsMgrImpl, lpVtbl)); | 
|  | } | 
|  |  | 
|  |  | 
|  | static HRESULT WINAPI GameStatisticsMgrImpl_QueryInterface( | 
|  | IGameStatisticsMgr *iface, | 
|  | REFIID riid, | 
|  | void **ppvObject) | 
|  | { | 
|  | GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface ); | 
|  |  | 
|  | TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject ); | 
|  |  | 
|  | *ppvObject = NULL; | 
|  |  | 
|  | if(IsEqualGUID(riid, &IID_IUnknown) || | 
|  | IsEqualGUID(riid, &IID_IGameStatisticsMgr) ) | 
|  | { | 
|  | *ppvObject = iface; | 
|  | } | 
|  | else | 
|  | { | 
|  | FIXME("interface %s not implemented\n", debugstr_guid(riid)); | 
|  | return E_NOINTERFACE; | 
|  | } | 
|  |  | 
|  | IGameStatisticsMgr_AddRef( iface ); | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI GameStatisticsMgrImpl_AddRef(IGameStatisticsMgr *iface) | 
|  | { | 
|  | GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface ); | 
|  | LONG ref; | 
|  |  | 
|  | ref = InterlockedIncrement(&This->ref); | 
|  |  | 
|  | TRACE("(%p): ref=%d\n", This, ref); | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI GameStatisticsMgrImpl_Release(IGameStatisticsMgr *iface) | 
|  | { | 
|  | GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface ); | 
|  | LONG ref; | 
|  |  | 
|  | ref = InterlockedDecrement(&This->ref); | 
|  | TRACE("(%p): ref=%d\n", This, ref); | 
|  |  | 
|  | if ( ref == 0 ) | 
|  | { | 
|  | TRACE("freeing GameStatistics object\n"); | 
|  | HeapFree( GetProcessHeap(), 0, This); | 
|  | } | 
|  |  | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_GetGameStatistics( | 
|  | IGameStatisticsMgr* iface, | 
|  | LPCWSTR GDFBinaryPath, | 
|  | GAMESTATS_OPEN_TYPE openType, | 
|  | GAMESTATS_OPEN_RESULT *pOpenResult, | 
|  | IGameStatistics **ppiStats) | 
|  | { | 
|  | HRESULT hr; | 
|  | WCHAR lpApplicationId[49]; | 
|  | GameStatisticsImpl *statisticsImpl = NULL; | 
|  | IGameStatistics *output_iface; | 
|  |  | 
|  | TRACE("(%p, %s, 0x%x, %p, %p)\n", iface, debugstr_w(GDFBinaryPath), openType, pOpenResult, ppiStats); | 
|  |  | 
|  | hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = create_IGameStatistics(&statisticsImpl); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | { | 
|  | output_iface = IGameStatistics_from_impl(statisticsImpl); | 
|  | hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, statisticsImpl->stats.sStatsFile); | 
|  | } | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = GAMEUX_loadGameStatistics(&statisticsImpl->stats, lpApplicationId, openType, pOpenResult); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | *ppiStats = output_iface; | 
|  | else | 
|  | { | 
|  | HeapFree(GetProcessHeap(), 0, statisticsImpl); | 
|  | *ppiStats = NULL; | 
|  | } | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_RemoveGameStatistics( | 
|  | IGameStatisticsMgr* iface, | 
|  | LPCWSTR GDFBinaryPath) | 
|  | { | 
|  | HRESULT hr; | 
|  | WCHAR lpApplicationId[49]; | 
|  | WCHAR sStatsFile[MAX_PATH]; | 
|  |  | 
|  | TRACE("(%p, %s)\n", iface, debugstr_w(GDFBinaryPath)); | 
|  |  | 
|  | hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, sStatsFile); | 
|  |  | 
|  | if(SUCCEEDED(hr)) | 
|  | hr = (DeleteFileW(sStatsFile)==TRUE ? S_OK : HRESULT_FROM_WIN32(GetLastError())); | 
|  |  | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static const struct IGameStatisticsMgrVtbl GameStatisticsMgrImplVtbl = | 
|  | { | 
|  | GameStatisticsMgrImpl_QueryInterface, | 
|  | GameStatisticsMgrImpl_AddRef, | 
|  | GameStatisticsMgrImpl_Release, | 
|  | GameStatisticsMgrImpl_GetGameStatistics, | 
|  | GameStatisticsMgrImpl_RemoveGameStatistics, | 
|  | }; | 
|  |  | 
|  | HRESULT GameStatistics_create( | 
|  | IUnknown *pUnkOuter, | 
|  | IUnknown **ppObj) | 
|  | { | 
|  | GameStatisticsMgrImpl *pGameStatistics; | 
|  |  | 
|  | TRACE("(%p, %p)\n", pUnkOuter, ppObj); | 
|  |  | 
|  | pGameStatistics = HeapAlloc( GetProcessHeap(), 0, sizeof (*pGameStatistics) ); | 
|  |  | 
|  | if( !pGameStatistics ) | 
|  | return E_OUTOFMEMORY; | 
|  |  | 
|  | pGameStatistics->lpVtbl = &GameStatisticsMgrImplVtbl; | 
|  | pGameStatistics->ref = 1; | 
|  |  | 
|  | *ppObj = (IUnknown*)(&pGameStatistics->lpVtbl); | 
|  |  | 
|  | TRACE("returning iface %p\n", *ppObj); | 
|  | return S_OK; | 
|  | } |