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