/* * 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 "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 = NULL; IXMLDOMElement *root = NULL, *statisticsElement = NULL; IXMLDOMNode *categoryNode = NULL, *statisticsNode = NULL; 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)) for(i=0; icategories[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, (void**)&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); } if (categoryElement) IXMLDOMElement_Release(categoryElement); SysFreeString(V_BSTR(&vValue)); if(SUCCEEDED(hr)) { for(j=0; jcategories[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 = IXMLDOMNode_appendChild(categoryNode, statisticsNode, NULL); if (statisticsElement) IXMLDOMElement_Release(statisticsElement); if (statisticsNode) IXMLDOMNode_Release(statisticsNode); } } if(SUCCEEDED(hr)) hr = IXMLDOMElement_appendChild(root, categoryNode, &categoryNode); if (categoryNode) IXMLDOMNode_Release(categoryNode); if(FAILED(hr)) break; } if(SUCCEEDED(hr)) hr = IXMLDOMDocument_putref_documentElement(document, root); if (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); if (document) 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 containing statistics of game with given id. * * Parameters: * lpApplicationId [I] application id of game, * as string * lpStatisticsFile [O] array where path will be * stored. Its 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 its 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)); RegCloseKey(hKey); } } HeapFree(GetProcessHeap(), 0, lpRegistryPath); 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 = NULL, *statisticElement = NULL; IXMLDOMNode *categoryNode = NULL, *statisticNode = NULL; IXMLDOMNodeList *rootChildren = NULL, *categoryChildren = NULL; 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)); } if (statisticElement) IXMLDOMElement_Release(statisticElement); } if (statisticNode) IXMLDOMNode_Release(statisticNode); } } if (categoryChildren) IXMLDOMNodeList_Release(categoryChildren); if(SUCCEEDED(hr)) hr = S_OK; } if (categoryElement) IXMLDOMElement_Release(categoryElement); } if (categoryNode) 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); if (FAILED(hr)) return hr; 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 statistics, 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 { IGameStatistics IGameStatistics_iface; LONG ref; struct GAMEUX_STATS stats; } GameStatisticsImpl; static inline GameStatisticsImpl *impl_from_IGameStatistics( IGameStatistics *iface ) { return CONTAINING_RECORD(iface, GameStatisticsImpl, IGameStatistics_iface); } 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 passing 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); TRACE("(%p, %d)\n", This, trackChanges); if(trackChanges) FIXME("tracking changes not yet implemented\n"); return GAMEUX_updateStatisticsFile(&This->stats); } 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)->IGameStatistics_iface.lpVtbl = &GameStatisticsImplVtbl; (*ppStats)->ref = 1; TRACE("returning coclass: %p\n", *ppStats); return S_OK; } /******************************************************************************* * IGameStatisticsMgr implementation */ typedef struct _GameStatisticsMgrImpl { IGameStatisticsMgr IGameStatisticsMgr_iface; LONG ref; } GameStatisticsMgrImpl; static inline GameStatisticsMgrImpl *impl_from_IGameStatisticsMgr( IGameStatisticsMgr *iface ) { return CONTAINING_RECORD(iface, GameStatisticsMgrImpl, IGameStatisticsMgr_iface); } 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 = &statisticsImpl->IGameStatistics_iface; 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) ? 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->IGameStatisticsMgr_iface.lpVtbl = &GameStatisticsMgrImplVtbl; pGameStatistics->ref = 1; *ppObj = (IUnknown*)&pGameStatistics->IGameStatisticsMgr_iface; TRACE("returning iface %p\n", *ppObj); return S_OK; }