po: Update French translation.
[wine] / dlls / gameux / gamestatistics.c
1 /*
2  *    Gameux library coclass GameStatistics implementation
3  *
4  * Copyright (C) 2010 Mariusz PluciƄski
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 #define COBJMACROS
21
22 #include "config.h"
23
24 #include "ole2.h"
25 #include "winreg.h"
26 #include "msxml2.h"
27 #include "shlwapi.h"
28 #include "shlobj.h"
29
30 #include "gameux.h"
31 #include "gameux_private.h"
32
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(gameux);
36
37 /*
38  * constant definitions
39  */
40 #define MAX_CATEGORY_LENGTH 60
41 #define MAX_NAME_LENGTH 30
42 #define MAX_VALUE_LENGTH 30
43 #define MAX_CATEGORIES 10
44 #define MAX_STATS_PER_CATEGORY 10
45 /*******************************************************************************
46  * Game statistics helper components
47  */
48 /*******************************************************************************
49  * struct GAMEUX_STATS
50  *
51  * set of structures for containing game's data
52  */
53 struct GAMEUX_STATS_STAT
54 {
55     WCHAR sName[MAX_NAME_LENGTH+1];
56     WCHAR sValue[MAX_VALUE_LENGTH+1];
57 };
58 struct GAMEUX_STATS_CATEGORY
59 {
60     WCHAR sName[MAX_CATEGORY_LENGTH+1];
61     struct GAMEUX_STATS_STAT stats[MAX_STATS_PER_CATEGORY];
62 };
63 struct GAMEUX_STATS
64 {
65     WCHAR sStatsFile[MAX_PATH];
66     struct GAMEUX_STATS_CATEGORY categories[MAX_CATEGORIES];
67 };
68 /*******************************************************************************
69  * GAMEUX_createStatsDirectory
70  *
71  * Helper function, creates directory to store game statistics
72  *
73  * Parameters
74  *  path                [I]     path to game statistics file.
75  *                              base directory of this file will
76  *                              be created if it doesn't exists
77  */
78 static HRESULT GAMEUX_createStatsDirectory(LPCWSTR lpFilePath)
79 {
80     HRESULT hr;
81     WCHAR lpDirectoryPath[MAX_PATH];
82     LPCWSTR lpEnd;
83
84     lpEnd = StrRChrW(lpFilePath, NULL, '\\');
85     lstrcpynW(lpDirectoryPath, lpFilePath, lpEnd-lpFilePath+1);
86
87     hr = HRESULT_FROM_WIN32(SHCreateDirectoryExW(NULL, lpDirectoryPath, NULL));
88
89     if(hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS) ||
90        hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS))
91         hr = S_FALSE;
92
93     return hr;
94 }
95 /*******************************************************************
96  * GAMEUX_updateStatisticsFile
97  *
98  * Helper function updating data stored in statistics file
99  *
100  * Parameters:
101  *  data                [I]     pointer to struct containing
102  *                              statistics data
103  */
104 static HRESULT GAMEUX_updateStatisticsFile(struct GAMEUX_STATS *stats)
105 {
106     static const WCHAR sStatistics[] = {'S','t','a','t','i','s','t','i','c','s',0};
107     static const WCHAR sCategory[] = {'C','a','t','e','g','o','r','y',0};
108     static const WCHAR sIndex[] = {'I','n','d','e','x',0};
109     static const WCHAR sStatistic[] = {'S','t','a','t','i','s','t','i','c',0};
110     static const WCHAR sName[] = {'N','a','m','e',0};
111     static const WCHAR sValue[] = {'V','a','l','u','e',0};
112
113     HRESULT hr = S_OK;
114     IXMLDOMDocument *document;
115     IXMLDOMElement *root, *categoryElement, *statisticsElement;
116     IXMLDOMNode *categoryNode, *statisticsNode;
117     VARIANT vStatsFilePath, vValue;
118     BSTR bstrStatistics = NULL, bstrCategory = NULL, bstrIndex = NULL,
119         bstrStatistic = NULL, bstrName = NULL, bstrValue = NULL;
120     int i, j;
121
122     TRACE("(%p)\n", stats);
123
124     V_VT(&vStatsFilePath) = VT_BSTR;
125     V_BSTR(&vStatsFilePath) = SysAllocString(stats->sStatsFile);
126     if(!V_BSTR(&vStatsFilePath))
127         hr = E_OUTOFMEMORY;
128
129     if(SUCCEEDED(hr))
130         hr = CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
131                               &IID_IXMLDOMDocument, (void**)&document);
132
133     if(SUCCEEDED(hr))
134     {
135         bstrStatistics = SysAllocString(sStatistics);
136         if(!bstrStatistics)
137             hr = E_OUTOFMEMORY;
138     }
139
140     if(SUCCEEDED(hr))
141         hr = IXMLDOMDocument_createElement(document, bstrStatistics, &root);
142
143     if(SUCCEEDED(hr))
144     {
145         bstrCategory = SysAllocString(sCategory);
146         if(!bstrCategory)
147             hr = E_OUTOFMEMORY;
148     }
149
150     if(SUCCEEDED(hr))
151     {
152         bstrIndex = SysAllocString(sIndex);
153         if(!bstrIndex)
154             hr = E_OUTOFMEMORY;
155     }
156
157     if(SUCCEEDED(hr))
158     {
159         bstrStatistic = SysAllocString(sStatistic);
160         if(!bstrStatistic)
161             hr = E_OUTOFMEMORY;
162     }
163
164     if(SUCCEEDED(hr))
165     {
166         bstrName = SysAllocString(sName);
167         if(!bstrName)
168             hr = E_OUTOFMEMORY;
169     }
170
171     if(SUCCEEDED(hr))
172     {
173         bstrValue = SysAllocString(sValue);
174         if(!bstrValue)
175             hr = E_OUTOFMEMORY;
176     }
177
178     if(SUCCEEDED(hr))
179         for(i=0; i<MAX_CATEGORIES; ++i)
180         {
181             if(lstrlenW(stats->categories[i].sName)==0)
182                 continue;
183
184             V_VT(&vValue) = VT_INT;
185             V_INT(&vValue) = NODE_ELEMENT;
186
187             hr = IXMLDOMDocument_createNode(document, vValue, bstrCategory, NULL, &categoryNode);
188
189             if(SUCCEEDED(hr))
190                 hr = IXMLDOMNode_QueryInterface(categoryNode, &IID_IXMLDOMElement, (LPVOID*)&categoryElement);
191
192             V_INT(&vValue) = i;
193             if(SUCCEEDED(hr))
194                 hr = IXMLDOMElement_setAttribute(categoryElement, bstrIndex, vValue);
195
196             if(SUCCEEDED(hr))
197             {
198                 V_VT(&vValue) = VT_BSTR;
199                 V_BSTR(&vValue) = SysAllocString(stats->categories[i].sName);
200                 if(!V_BSTR(&vValue))
201                     hr = E_OUTOFMEMORY;
202             }
203
204             if(SUCCEEDED(hr))
205             {
206                 TRACE("storing category %d: %s\n", i, debugstr_w(V_BSTR(&vValue)));
207                 hr = IXMLDOMElement_setAttribute(categoryElement, bstrName, vValue);
208             }
209
210             SysFreeString(V_BSTR(&vValue));
211
212             if(SUCCEEDED(hr))
213             {
214                 for(j=0; j<MAX_STATS_PER_CATEGORY; ++j)
215                 {
216                     if(lstrlenW(stats->categories[i].stats[j].sName)==0)
217                         continue;
218
219                     V_VT(&vValue) = VT_INT;
220                     V_INT(&vValue) = NODE_ELEMENT;
221
222                     hr = IXMLDOMDocument_createNode(document, vValue, bstrStatistic, NULL, &statisticsNode);
223
224                     if(SUCCEEDED(hr))
225                         hr = IXMLDOMNode_QueryInterface(statisticsNode, &IID_IXMLDOMElement, (LPVOID*)&statisticsElement);
226
227                     V_INT(&vValue) = j;
228                     if(SUCCEEDED(hr))
229                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrIndex, vValue);
230
231                     if(SUCCEEDED(hr))
232                     {
233                         V_VT(&vValue) = VT_BSTR;
234                         V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sName);
235                         if(!V_BSTR(&vValue))
236                             hr = E_OUTOFMEMORY;
237                     }
238
239                     if(SUCCEEDED(hr))
240                     {
241                         TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue)));
242                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrName, vValue);
243                     }
244
245                     SysFreeString(V_BSTR(&vValue));
246
247                     if(SUCCEEDED(hr))
248                     {
249                         V_VT(&vValue) = VT_BSTR;
250                         V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sValue);
251                         if(!V_BSTR(&vValue))
252                             hr = E_OUTOFMEMORY;
253                     }
254
255                     if(SUCCEEDED(hr))
256                     {
257                         TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue)));
258                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrValue, vValue);
259                     }
260
261                     SysFreeString(V_BSTR(&vValue));
262
263                     if(SUCCEEDED(hr))
264                         hr = IXMLDOMElement_appendChild(categoryNode, statisticsNode, &statisticsNode);
265
266                     IXMLDOMElement_Release(statisticsElement);
267                     IXMLDOMNode_Release(statisticsNode);
268                 }
269             }
270
271             if(SUCCEEDED(hr))
272                 hr = IXMLDOMElement_appendChild(root, categoryNode, &categoryNode);
273
274             IXMLDOMElement_Release(categoryElement);
275             IXMLDOMNode_Release(categoryNode);
276
277             if(FAILED(hr))
278                 break;
279         }
280
281     if(SUCCEEDED(hr))
282         hr = IXMLDOMDocument_putref_documentElement(document, root);
283
284     IXMLDOMElement_Release(root);
285
286     TRACE("saving game statistics in %s file\n", debugstr_w(stats->sStatsFile));
287     if(SUCCEEDED(hr))
288         hr = GAMEUX_createStatsDirectory(stats->sStatsFile);
289
290     if(SUCCEEDED(hr))
291         hr = IXMLDOMDocument_save(document, vStatsFilePath);
292
293     IXMLDOMDocument_Release(document);
294
295     SysFreeString(bstrValue);
296     SysFreeString(bstrName);
297     SysFreeString(bstrStatistic);
298     SysFreeString(bstrIndex);
299     SysFreeString(bstrCategory);
300     SysFreeString(bstrStatistics);
301     SysFreeString(V_BSTR(&vStatsFilePath));
302     TRACE("ret=0x%x\n", hr);
303     return hr;
304 }
305 /*******************************************************************************
306  * GAMEUX_buildStatisticsFilePath
307  * Creates path to file containing statistics of game with given id.
308  *
309  * Parameters:
310  *  lpApplicationId                         [I]     application id of game,
311  *                                                  as string
312  *  lpStatisticsFile                        [O]     array where path will be
313  *                                                  stored. Its size must be
314  *                                                  at least MAX_PATH
315  */
316 static HRESULT GAMEUX_buildStatisticsFilePath(
317         LPCWSTR lpApplicationId,
318         LPWSTR lpStatisticsFile)
319 {
320     static const WCHAR sBackslash[] = {'\\',0};
321     static const WCHAR sStatisticsDir[] = {'\\','M','i','c','r','o','s','o','f','t',
322             '\\','W','i','n','d','o','w','s','\\','G','a','m','e','E','x','p',
323             'l','o','r','e','r','\\','G','a','m','e','S','t','a','t','i','s',
324             't','i','c','s','\\',0};
325     static const WCHAR sDotGamestats[] = {'.','g','a','m','e','s','t','a','t','s',0};
326
327     HRESULT hr;
328
329     hr = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, lpStatisticsFile);
330
331     if(SUCCEEDED(hr))
332     {
333         lstrcatW(lpStatisticsFile, sStatisticsDir);
334         lstrcatW(lpStatisticsFile, lpApplicationId);
335         lstrcatW(lpStatisticsFile, sBackslash);
336         lstrcatW(lpStatisticsFile, lpApplicationId);
337         lstrcatW(lpStatisticsFile, sDotGamestats);
338     }
339
340     return hr;
341 }
342 /*******************************************************************************
343  * GAMEUX_getAppIdFromGDFPath
344  *
345  * Loads application identifier associated with given GDF binary.
346  * Routine reads identifier from registry, so will fail if game
347  * is not registered.
348  *
349  * Parameters:
350  *  GDFBinaryPath                       [I]     path to gdf binary
351  *  lpApplicationId                     [O]     place to store application id.
352  *                                              must be at least 49 characters
353  *                                              to store guid and termination 0
354  */
355 static HRESULT GAMEUX_getAppIdFromGDFPath(
356         LPCWSTR GDFBinaryPath,
357         LPWSTR lpApplicationId)
358 {
359     static const WCHAR sApplicationId[] =
360             {'A','p','p','l','i','c','a','t','i','o','n','I','d',0};
361
362     HRESULT hr;
363     GAME_INSTALL_SCOPE installScope;
364     GUID instanceId;
365     LPWSTR lpRegistryPath = NULL;
366     HKEY hKey;
367     DWORD dwLength = 49*sizeof(WCHAR);/* place for GUID */
368
369     TRACE("(%s, %p)\n", debugstr_w(GDFBinaryPath), lpApplicationId);
370
371     if(!GDFBinaryPath)
372         return E_INVALIDARG;
373
374     installScope = GIS_CURRENT_USER;
375     hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId);
376
377     if(hr == S_FALSE)
378     {
379         installScope = GIS_ALL_USERS;
380         hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId);
381     }
382
383     if(hr == S_FALSE)
384         /* game not registered, so statistics cannot be used */
385         hr = E_FAIL;
386
387     if(SUCCEEDED(hr))
388         /* game is registered, let's read it's application id from registry */
389         hr = GAMEUX_buildGameRegistryPath(installScope, &instanceId, &lpRegistryPath);
390
391     if(SUCCEEDED(hr)) {
392         hr = HRESULT_FROM_WIN32(RegOpenKeyExW(HKEY_LOCAL_MACHINE,
393                 lpRegistryPath, 0, KEY_READ | KEY_WOW64_64KEY, &hKey));
394         if(SUCCEEDED(hr)) {
395             hr = HRESULT_FROM_WIN32(RegGetValueW(hKey,
396                     NULL, sApplicationId, RRF_RT_REG_SZ,
397                     NULL, lpApplicationId, &dwLength));
398             RegCloseKey(hKey);
399         }
400     }
401
402     HeapFree(GetProcessHeap(), 0, lpRegistryPath);
403
404     TRACE("found app id: %s, return: %#x\n", debugstr_w(lpApplicationId), hr);
405     return hr;
406 }
407 /*******************************************************************
408  * GAMEUX_loadGameStatisticsFromFile
409  * Helper function, loads game statistics from file and stores them
410  * in the structure.
411  *
412  * Parameters:
413  *  data                [I/O]   structure containing file name to
414  *                              load and data fields to store data in
415  */
416 static HRESULT GAMEUX_loadStatisticsFromFile(struct GAMEUX_STATS *data)
417 {
418     static const WCHAR sStatistics[] = {'S','t','a','t','i','s','t','i','c','s',0};
419     static const WCHAR sCategory[] = {'C','a','t','e','g','o','r','y',0};
420     static const WCHAR sIndex[] = {'I','n','d','e','x',0};
421     static const WCHAR sStatistic[] = {'S','t','a','t','i','s','t','i','c',0};
422     static const WCHAR sName[] = {'N','a','m','e',0};
423     static const WCHAR sValue[] = {'V','a','l','u','e',0};
424
425     HRESULT hr = S_OK;
426     IXMLDOMDocument *document = NULL;
427     IXMLDOMElement *root = NULL, *categoryElement, *statisticElement;
428     IXMLDOMNode *categoryNode, *statisticNode;
429     IXMLDOMNodeList *rootChildren = NULL, *categoryChildren;
430     VARIANT vStatsFilePath, vValue;
431     BSTR bstrStatistics = NULL, bstrCategory = NULL, bstrIndex = NULL,
432         bstrStatistic = NULL, bstrName = NULL, bstrValue = NULL;
433     VARIANT_BOOL isSuccessful =  VARIANT_FALSE;
434     int i, j;
435
436     TRACE("(%p)\n", data);
437
438     V_VT(&vStatsFilePath) = VT_BSTR;
439     V_BSTR(&vStatsFilePath) = SysAllocString(data->sStatsFile);
440     if(!V_BSTR(&vStatsFilePath))
441         hr = E_OUTOFMEMORY;
442
443     if(SUCCEEDED(hr))
444         hr = CoCreateInstance(&CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void**)&document);
445
446     if(SUCCEEDED(hr))
447     {
448         bstrStatistics = SysAllocString(sStatistics);
449         if(!bstrStatistics)
450             hr = E_OUTOFMEMORY;
451     }
452
453     if(SUCCEEDED(hr))
454     {
455         bstrCategory = SysAllocString(sCategory);
456         if(!bstrCategory)
457             hr = E_OUTOFMEMORY;
458     }
459
460     if(SUCCEEDED(hr))
461     {
462         bstrIndex = SysAllocString(sIndex);
463         if(!bstrIndex)
464             hr = E_OUTOFMEMORY;
465     }
466
467     if(SUCCEEDED(hr))
468     {
469         bstrStatistic = SysAllocString(sStatistic);
470         if(!bstrStatistic)
471             hr = E_OUTOFMEMORY;
472     }
473
474     if(SUCCEEDED(hr))
475     {
476         bstrName = SysAllocString(sName);
477         if(!bstrName)
478             hr = E_OUTOFMEMORY;
479     }
480
481     if(SUCCEEDED(hr))
482     {
483         bstrValue = SysAllocString(sValue);
484         if(!bstrValue)
485             hr = E_OUTOFMEMORY;
486     }
487
488     if(SUCCEEDED(hr))
489         hr = IXMLDOMDocument_load(document, vStatsFilePath, &isSuccessful);
490
491     if(hr == S_OK && isSuccessful != VARIANT_TRUE)
492         hr = S_FALSE;
493
494     if( hr == S_OK )
495         hr = IXMLDOMDocument_get_documentElement(document, &root);
496
497     if(hr == S_OK)
498         hr = IXMLDOMElement_get_childNodes(root, &rootChildren);
499
500     if(hr == S_OK)
501     {
502         hr = S_OK;
503         while(hr == S_OK)
504         {
505             hr = IXMLDOMNodeList_nextNode(rootChildren, &categoryNode);
506
507             if(hr == S_OK)
508             {
509                 hr = IXMLDOMNode_QueryInterface(categoryNode, &IID_IXMLDOMElement, (LPVOID*)&categoryElement);
510
511                 if(SUCCEEDED(hr))
512                 {
513                     hr = IXMLDOMElement_getAttribute(categoryElement, bstrIndex, &vValue);
514                     if( hr == S_OK && V_VT(&vValue) != VT_BSTR)
515                         hr = E_FAIL;
516
517                     if(SUCCEEDED(hr))
518                     {
519                         i = StrToIntW(V_BSTR(&vValue));
520                         hr = IXMLDOMElement_getAttribute(categoryElement, bstrName, &vValue);
521                         if( hr == S_OK && V_VT(&vValue) != VT_BSTR)
522                             hr = E_FAIL;
523                     }
524
525                     if(SUCCEEDED(hr))
526                     {
527                         lstrcpynW(data->categories[i].sName, V_BSTR(&vValue), MAX_CATEGORY_LENGTH);
528                         TRACE("category %d name %s\n", i, debugstr_w(data->categories[i].sName));
529                         hr = IXMLDOMElement_get_childNodes(categoryElement, &categoryChildren);
530                     }
531
532                     if(SUCCEEDED(hr))
533                     {
534                         hr = S_OK;
535                         while(hr == S_OK)
536                         {
537                             hr = IXMLDOMNodeList_nextNode(categoryChildren, &statisticNode);
538
539                             if(hr == S_OK)
540                             {
541                                 hr = IXMLDOMNode_QueryInterface(statisticNode, &IID_IXMLDOMElement, (LPVOID*)&statisticElement);
542
543                                 if(SUCCEEDED(hr))
544                                 {
545                                     hr = IXMLDOMElement_getAttribute(statisticElement, bstrIndex, &vValue);
546                                     if( hr == S_OK && V_VT(&vValue) != VT_BSTR)
547                                         hr = E_FAIL;
548
549                                     if(SUCCEEDED(hr))
550                                     {
551                                         j = StrToIntW(V_BSTR(&vValue));
552                                         hr = IXMLDOMElement_getAttribute(statisticElement, bstrName, &vValue);
553                                         if( hr == S_OK && V_VT(&vValue) != VT_BSTR)
554                                             hr = E_FAIL;
555                                     }
556
557                                     if(SUCCEEDED(hr))
558                                     {
559                                         lstrcpynW(data->categories[i].stats[j].sName, V_BSTR(&vValue), MAX_NAME_LENGTH);
560                                         hr = IXMLDOMElement_getAttribute(statisticElement, bstrValue, &vValue);
561                                         if( hr == S_OK && V_VT(&vValue) != VT_BSTR)
562                                             hr = E_FAIL;
563                                     }
564
565                                     if(SUCCEEDED(hr))
566                                     {
567                                         lstrcpynW(data->categories[i].stats[j].sValue, V_BSTR(&vValue), MAX_VALUE_LENGTH);
568                                         TRACE("statistic %d name %s value %s\n", j,
569                                               debugstr_w(data->categories[i].stats[j].sName),
570                                               debugstr_w(data->categories[i].stats[j].sValue));
571                                     }
572                                     IXMLDOMElement_Release(statisticElement);
573                                 }
574
575                                 IXMLDOMNode_Release(statisticNode);
576                             }
577                         }
578
579                         if(SUCCEEDED(hr))
580                             hr = S_OK;
581                     }
582                     IXMLDOMElement_Release(categoryElement);
583                 }
584
585                 IXMLDOMNode_Release(categoryNode);
586             }
587         }
588         if(SUCCEEDED(hr))
589             hr = S_OK;
590     }
591
592     if(rootChildren) IXMLDOMNodeList_Release(rootChildren);
593     if(root) IXMLDOMElement_Release(root);
594     if(document) IXMLDOMDocument_Release(document);
595
596     SysFreeString(bstrValue);
597     SysFreeString(bstrName);
598     SysFreeString(bstrStatistic);
599     SysFreeString(bstrIndex);
600     SysFreeString(bstrCategory);
601     SysFreeString(bstrStatistics);
602     SysFreeString(V_BSTR(&vStatsFilePath));
603     return hr;
604 }
605 /*******************************************************************
606  * GAMEUX_loadGameStatistics
607  *
608  * Helper function which loads game statistics associated with game
609  * into interface's internal structures
610  *
611  * Parameters:
612  *  pStats              [O]     structure which will receive data
613  *  sGameId             [I]     application instance Id, stored as string
614  *                              to avoid additional conversions
615  *  openType            [I]     allowed ways of opening statistics
616  *  pOpenResult         [O]     way used to open statistics
617  *
618  */
619 static HRESULT GAMEUX_loadGameStatistics(struct GAMEUX_STATS *pStats,
620         LPWSTR sGameId,
621         GAMESTATS_OPEN_TYPE openType,
622         GAMESTATS_OPEN_RESULT* pOpenResult)
623 {
624     HRESULT hr;
625     TRACE("(%p, %s, %d, %p)\n", pStats, debugstr_w(sGameId), openType, pOpenResult);
626
627     hr = GAMEUX_buildStatisticsFilePath(sGameId, pStats->sStatsFile);
628
629     hr = GAMEUX_loadStatisticsFromFile(pStats);
630     TRACE("ldstats finished, res: %#x\n", hr);
631     if(hr == S_OK)
632     {
633         *pOpenResult = GAMESTATS_OPEN_OPENED;
634     }
635     else if(hr == S_FALSE && openType == GAMESTATS_OPEN_OPENORCREATE) /* file does not exist */
636     {
637         /* create new statistics, not yet connected with file */
638         ZeroMemory(pStats->categories, sizeof(pStats->categories));
639         *pOpenResult = GAMESTATS_OPEN_CREATED;
640         hr = S_OK;
641     }
642     else
643         hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
644
645     TRACE("openResult=%#x ret=%#x\n", *pOpenResult, hr);
646     return hr;
647 }
648  /*******************************************************************
649  * IGameStatistics implementation
650  */
651 typedef struct _GameStatisticsImpl
652 {
653     IGameStatistics IGameStatistics_iface;
654     LONG ref;
655     struct GAMEUX_STATS stats;
656 } GameStatisticsImpl;
657
658 static inline GameStatisticsImpl *impl_from_IGameStatistics( IGameStatistics *iface )
659 {
660     return CONTAINING_RECORD(iface, GameStatisticsImpl, IGameStatistics_iface);
661 }
662
663 static HRESULT WINAPI GameStatisticsImpl_QueryInterface(
664         IGameStatistics *iface,
665         REFIID riid,
666         void **ppvObject)
667 {
668     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
669
670     TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject );
671
672     *ppvObject = NULL;
673
674     if ( IsEqualGUID( riid, &IID_IUnknown ) ||
675          IsEqualGUID( riid, &IID_IGameStatistics ) )
676     {
677         *ppvObject = iface;
678     }
679     else
680     {
681         FIXME("interface %s not implemented\n", debugstr_guid(riid));
682         return E_NOINTERFACE;
683     }
684
685     IGameStatistics_AddRef( iface );
686     return S_OK;
687 }
688
689 static ULONG WINAPI GameStatisticsImpl_AddRef(IGameStatistics *iface)
690 {
691     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
692     LONG ref;
693
694     ref = InterlockedIncrement(&This->ref);
695
696     TRACE("(%p): ref=%d\n", This, ref);
697     return ref;
698 }
699
700 static ULONG WINAPI GameStatisticsImpl_Release(IGameStatistics *iface)
701 {
702     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
703     LONG ref;
704
705     ref = InterlockedDecrement( &This->ref );
706     TRACE("(%p): ref=%d\n", This, ref);
707
708     if ( ref == 0 )
709     {
710         TRACE("freeing IGameStatistics\n");
711         HeapFree( GetProcessHeap(), 0, This );
712     }
713
714     return ref;
715 }
716
717 static HRESULT WINAPI GameStatisticsImpl_GetMaxCategoryLength(
718     IGameStatistics *iface,
719     UINT *cch)
720 {
721     TRACE("(%p, %p)\n", iface, cch);
722     if(!cch)
723         return E_INVALIDARG;
724
725     *cch = MAX_CATEGORY_LENGTH;
726     return S_OK;
727 }
728
729 static HRESULT WINAPI GameStatisticsImpl_GetMaxNameLength(
730     IGameStatistics *iface,
731     UINT *cch)
732 {
733     TRACE("(%p, %p)\n", iface, cch);
734     if(!cch)
735         return E_INVALIDARG;
736
737     *cch = MAX_NAME_LENGTH;
738     return S_OK;
739 }
740
741 static HRESULT WINAPI GameStatisticsImpl_GetMaxValueLength(
742     IGameStatistics *iface,
743     UINT *cch)
744 {
745     TRACE("(%p, %p)\n", iface, cch);
746     if(!cch)
747         return E_INVALIDARG;
748
749     *cch = MAX_VALUE_LENGTH;
750     return S_OK;
751 }
752
753 static HRESULT WINAPI GameStatisticsImpl_GetMaxCategories(
754     IGameStatistics *iface,
755     WORD *pMax)
756 {
757     TRACE("(%p, %p)\n", iface, pMax);
758     if(!pMax)
759         return E_INVALIDARG;
760
761     *pMax = MAX_CATEGORIES;
762     return S_OK;
763 }
764
765 static HRESULT WINAPI GameStatisticsImpl_GetMaxStatsPerCategory(
766     IGameStatistics *iface,
767     WORD *pMax)
768 {
769     TRACE("(%p, %p)\n", iface, pMax);
770     if(!pMax)
771         return E_INVALIDARG;
772
773     *pMax = MAX_STATS_PER_CATEGORY;
774     return S_OK;
775 }
776
777 static HRESULT WINAPI GameStatisticsImpl_SetCategoryTitle(
778     IGameStatistics *iface,
779     WORD categoryIndex,
780     LPCWSTR title)
781 {
782     HRESULT hr = S_OK;
783     DWORD dwLength;
784     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
785
786     TRACE("(%p, %d, %s)\n", This, categoryIndex, debugstr_w(title));
787
788     if(!title || categoryIndex >= MAX_CATEGORIES)
789         return E_INVALIDARG;
790
791     dwLength = lstrlenW(title);
792
793     if(dwLength > MAX_CATEGORY_LENGTH)
794     {
795         hr = S_FALSE;
796         dwLength = MAX_CATEGORY_LENGTH;
797     }
798
799     lstrcpynW(This->stats.categories[categoryIndex].sName,
800               title, dwLength+1);
801
802     return hr;
803 }
804
805 static HRESULT WINAPI GameStatisticsImpl_GetCategoryTitle(
806     IGameStatistics *iface,
807     WORD categoryIndex,
808     LPWSTR *pTitle)
809 {
810     HRESULT hr = S_OK;
811     LONG nLength;
812     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
813
814     TRACE("%p, %d, %p\n", This, categoryIndex, pTitle);
815
816     if(!pTitle)
817         return E_INVALIDARG;
818     *pTitle = NULL;
819
820     if (categoryIndex >= MAX_CATEGORIES)
821         hr = E_INVALIDARG;
822
823     if(SUCCEEDED(hr))
824     {
825         nLength = lstrlenW(This->stats.categories[categoryIndex].sName);
826         if(nLength != 0)
827         {
828             *pTitle = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1));
829             lstrcpyW(*pTitle, This->stats.categories[categoryIndex].sName);
830         }
831     }
832
833     return hr;
834 }
835
836 static HRESULT WINAPI GameStatisticsImpl_GetStatistic(
837     IGameStatistics *iface,
838     WORD categoryIndex,
839     WORD statIndex,
840     LPWSTR *pName,
841     LPWSTR *pValue)
842 {
843     HRESULT hr = S_OK;
844     LONG nLength;
845     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
846
847     TRACE("%p, %d,%d, %p, %p\n", This, categoryIndex, statIndex, pName, pValue);
848
849     if(!pName || !pValue)
850         return E_INVALIDARG;
851
852     *pName = NULL;
853     *pValue = NULL;
854
855     if(categoryIndex >= MAX_CATEGORIES || statIndex >= MAX_STATS_PER_CATEGORY)
856         hr = E_INVALIDARG;
857
858     if(SUCCEEDED(hr))
859     {
860         nLength = lstrlenW(This->stats.categories[categoryIndex].stats[statIndex].sName);
861         if(nLength != 0)
862         {
863             *pName = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1));
864             if(!(*pName))
865                 hr = E_OUTOFMEMORY;
866             else
867                 lstrcpyW(*pName, This->stats.categories[categoryIndex].stats[statIndex].sName);
868         }
869     }
870
871     if(SUCCEEDED(hr))
872     {
873         nLength = lstrlenW(This->stats.categories[categoryIndex].stats[statIndex].sValue);
874         if(nLength != 0)
875         {
876             *pValue = CoTaskMemAlloc(sizeof(WCHAR)*(nLength+1));
877             if(!(*pValue))
878                 hr = E_OUTOFMEMORY;
879             else
880                 lstrcpyW(*pValue, This->stats.categories[categoryIndex].stats[statIndex].sValue);
881         }
882     }
883
884     TRACE("returning pair; %s => %s\n", debugstr_w(*pName), debugstr_w(*pValue));
885     return hr;
886 }
887
888 static HRESULT WINAPI GameStatisticsImpl_SetStatistic(
889     IGameStatistics *iface,
890     WORD categoryIndex,
891     WORD statIndex,
892     LPCWSTR name,
893     LPCWSTR value)
894 {
895     HRESULT hr = S_OK;
896     DWORD dwNameLen, dwValueLen;
897     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
898
899     TRACE("(%p, %d, %d, %s, %s)\n", This, categoryIndex, statIndex,
900           debugstr_w(name), debugstr_w(value));
901
902     if(!name)
903         return S_FALSE;
904
905     if(categoryIndex >= MAX_CATEGORIES || statIndex >= MAX_STATS_PER_CATEGORY)
906         return E_INVALIDARG;
907
908     dwNameLen = lstrlenW(name);
909
910     if(dwNameLen > MAX_NAME_LENGTH)
911     {
912         hr = S_FALSE;
913         dwNameLen = MAX_NAME_LENGTH;
914     }
915
916     lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sName,
917               name, dwNameLen+1);
918
919     if(value)
920     {
921         dwValueLen = lstrlenW(value);
922
923         if(dwValueLen > MAX_VALUE_LENGTH)
924         {
925             hr = S_FALSE;
926             dwValueLen = MAX_VALUE_LENGTH;
927         }
928
929         lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sValue,
930                   value, dwValueLen+1);
931     }
932     else
933         /* Windows allows passing NULL as value */
934         This->stats.categories[categoryIndex].stats[statIndex].sValue[0] = 0;
935
936     return hr;
937 }
938
939 static HRESULT WINAPI GameStatisticsImpl_Save(
940     IGameStatistics *iface,
941     BOOL trackChanges)
942 {
943     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
944     HRESULT hr = S_OK;
945
946     TRACE("(%p, %d)\n", This, trackChanges);
947
948     if(trackChanges)
949         FIXME("tracking changes not yet implemented\n");
950
951     hr = GAMEUX_updateStatisticsFile(&This->stats);
952
953     return hr;
954 }
955
956 static HRESULT WINAPI GameStatisticsImpl_SetLastPlayedCategory(
957     IGameStatistics *iface,
958     UINT categoryIndex)
959 {
960     FIXME("stub\n");
961     return E_NOTIMPL;
962 }
963
964 static HRESULT WINAPI GameStatisticsImpl_GetLastPlayedCategory(
965     IGameStatistics *iface,
966     UINT *pCategoryIndex)
967 {
968     FIXME("stub\n");
969     return E_NOTIMPL;
970 }
971
972 static const struct IGameStatisticsVtbl GameStatisticsImplVtbl =
973 {
974     GameStatisticsImpl_QueryInterface,
975     GameStatisticsImpl_AddRef,
976     GameStatisticsImpl_Release,
977     GameStatisticsImpl_GetMaxCategoryLength,
978     GameStatisticsImpl_GetMaxNameLength,
979     GameStatisticsImpl_GetMaxValueLength,
980     GameStatisticsImpl_GetMaxCategories,
981     GameStatisticsImpl_GetMaxStatsPerCategory,
982     GameStatisticsImpl_SetCategoryTitle,
983     GameStatisticsImpl_GetCategoryTitle,
984     GameStatisticsImpl_GetStatistic,
985     GameStatisticsImpl_SetStatistic,
986     GameStatisticsImpl_Save,
987     GameStatisticsImpl_SetLastPlayedCategory,
988     GameStatisticsImpl_GetLastPlayedCategory
989 };
990
991
992 static HRESULT create_IGameStatistics(GameStatisticsImpl** ppStats)
993 {
994     TRACE("(%p)\n", ppStats);
995
996     *ppStats = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(**ppStats));
997     if(!(*ppStats))
998         return E_OUTOFMEMORY;
999
1000     (*ppStats)->IGameStatistics_iface.lpVtbl = &GameStatisticsImplVtbl;
1001     (*ppStats)->ref = 1;
1002
1003     TRACE("returning coclass: %p\n", *ppStats);
1004     return S_OK;
1005 }
1006
1007 /*******************************************************************************
1008  * IGameStatisticsMgr implementation
1009  */
1010 typedef struct _GameStatisticsMgrImpl
1011 {
1012     IGameStatisticsMgr IGameStatisticsMgr_iface;
1013     LONG ref;
1014 } GameStatisticsMgrImpl;
1015
1016 static inline GameStatisticsMgrImpl *impl_from_IGameStatisticsMgr( IGameStatisticsMgr *iface )
1017 {
1018     return CONTAINING_RECORD(iface, GameStatisticsMgrImpl, IGameStatisticsMgr_iface);
1019 }
1020
1021
1022 static HRESULT WINAPI GameStatisticsMgrImpl_QueryInterface(
1023         IGameStatisticsMgr *iface,
1024         REFIID riid,
1025         void **ppvObject)
1026 {
1027     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
1028
1029     TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject );
1030
1031     *ppvObject = NULL;
1032
1033     if(IsEqualGUID(riid, &IID_IUnknown) ||
1034        IsEqualGUID(riid, &IID_IGameStatisticsMgr) )
1035     {
1036         *ppvObject = iface;
1037     }
1038     else
1039     {
1040         FIXME("interface %s not implemented\n", debugstr_guid(riid));
1041         return E_NOINTERFACE;
1042     }
1043
1044     IGameStatisticsMgr_AddRef( iface );
1045     return S_OK;
1046 }
1047
1048 static ULONG WINAPI GameStatisticsMgrImpl_AddRef(IGameStatisticsMgr *iface)
1049 {
1050     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
1051     LONG ref;
1052
1053     ref = InterlockedIncrement(&This->ref);
1054
1055     TRACE("(%p): ref=%d\n", This, ref);
1056     return ref;
1057 }
1058
1059 static ULONG WINAPI GameStatisticsMgrImpl_Release(IGameStatisticsMgr *iface)
1060 {
1061     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
1062     LONG ref;
1063
1064     ref = InterlockedDecrement(&This->ref);
1065     TRACE("(%p): ref=%d\n", This, ref);
1066
1067     if ( ref == 0 )
1068     {
1069         TRACE("freeing GameStatistics object\n");
1070         HeapFree( GetProcessHeap(), 0, This);
1071     }
1072
1073     return ref;
1074 }
1075
1076 static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_GetGameStatistics(
1077         IGameStatisticsMgr* iface,
1078         LPCWSTR GDFBinaryPath,
1079         GAMESTATS_OPEN_TYPE openType,
1080         GAMESTATS_OPEN_RESULT *pOpenResult,
1081         IGameStatistics **ppiStats)
1082 {
1083     HRESULT hr;
1084     WCHAR lpApplicationId[49];
1085     GameStatisticsImpl *statisticsImpl = NULL;
1086     IGameStatistics *output_iface;
1087
1088     TRACE("(%p, %s, 0x%x, %p, %p)\n", iface, debugstr_w(GDFBinaryPath), openType, pOpenResult, ppiStats);
1089
1090     hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId);
1091
1092     if(SUCCEEDED(hr))
1093         hr = create_IGameStatistics(&statisticsImpl);
1094
1095     if(SUCCEEDED(hr))
1096     {
1097         output_iface = &statisticsImpl->IGameStatistics_iface;
1098         hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, statisticsImpl->stats.sStatsFile);
1099     }
1100
1101     if(SUCCEEDED(hr))
1102         hr = GAMEUX_loadGameStatistics(&statisticsImpl->stats, lpApplicationId, openType, pOpenResult);
1103
1104     if(SUCCEEDED(hr))
1105         *ppiStats = output_iface;
1106     else
1107     {
1108         HeapFree(GetProcessHeap(), 0, statisticsImpl);
1109         *ppiStats = NULL;
1110     }
1111
1112     return hr;
1113 }
1114
1115 static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_RemoveGameStatistics(
1116         IGameStatisticsMgr* iface,
1117         LPCWSTR GDFBinaryPath)
1118 {
1119     HRESULT hr;
1120     WCHAR lpApplicationId[49];
1121     WCHAR sStatsFile[MAX_PATH];
1122
1123     TRACE("(%p, %s)\n", iface, debugstr_w(GDFBinaryPath));
1124
1125     hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId);
1126
1127     if(SUCCEEDED(hr))
1128         hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, sStatsFile);
1129
1130     if(SUCCEEDED(hr))
1131         hr = (DeleteFileW(sStatsFile)==TRUE ? S_OK : HRESULT_FROM_WIN32(GetLastError()));
1132
1133     return hr;
1134 }
1135
1136 static const struct IGameStatisticsMgrVtbl GameStatisticsMgrImplVtbl =
1137 {
1138     GameStatisticsMgrImpl_QueryInterface,
1139     GameStatisticsMgrImpl_AddRef,
1140     GameStatisticsMgrImpl_Release,
1141     GameStatisticsMgrImpl_GetGameStatistics,
1142     GameStatisticsMgrImpl_RemoveGameStatistics,
1143 };
1144
1145 HRESULT GameStatistics_create(
1146         IUnknown *pUnkOuter,
1147         IUnknown **ppObj)
1148 {
1149     GameStatisticsMgrImpl *pGameStatistics;
1150
1151     TRACE("(%p, %p)\n", pUnkOuter, ppObj);
1152
1153     pGameStatistics = HeapAlloc( GetProcessHeap(), 0, sizeof (*pGameStatistics) );
1154
1155     if( !pGameStatistics )
1156         return E_OUTOFMEMORY;
1157
1158     pGameStatistics->IGameStatisticsMgr_iface.lpVtbl = &GameStatisticsMgrImplVtbl;
1159     pGameStatistics->ref = 1;
1160
1161     *ppObj = (IUnknown*)&pGameStatistics->IGameStatisticsMgr_iface;
1162
1163     TRACE("returning iface %p\n", *ppObj);
1164     return S_OK;
1165 }