gdiplus: Add a test for GdipCreateMatrix3.
[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
180     if(SUCCEEDED(hr))
181         for(i=0; i<MAX_CATEGORIES; ++i)
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, (LPVOID*)&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             SysFreeString(V_BSTR(&vValue));
213
214             if(SUCCEEDED(hr))
215             {
216                 for(j=0; j<MAX_STATS_PER_CATEGORY; ++j)
217                 {
218                     if(lstrlenW(stats->categories[i].stats[j].sName)==0)
219                         continue;
220
221                     V_VT(&vValue) = VT_INT;
222                     V_INT(&vValue) = NODE_ELEMENT;
223
224                     hr = IXMLDOMDocument_createNode(document, vValue, bstrStatistic, NULL, &statisticsNode);
225
226                     if(SUCCEEDED(hr))
227                         hr = IXMLDOMNode_QueryInterface(statisticsNode, &IID_IXMLDOMElement, (LPVOID*)&statisticsElement);
228
229                     V_INT(&vValue) = j;
230                     if(SUCCEEDED(hr))
231                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrIndex, vValue);
232
233                     if(SUCCEEDED(hr))
234                     {
235                         V_VT(&vValue) = VT_BSTR;
236                         V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sName);
237                         if(!V_BSTR(&vValue))
238                             hr = E_OUTOFMEMORY;
239                     }
240
241                     if(SUCCEEDED(hr))
242                     {
243                         TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue)));
244                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrName, vValue);
245                     }
246
247                     SysFreeString(V_BSTR(&vValue));
248
249                     if(SUCCEEDED(hr))
250                     {
251                         V_VT(&vValue) = VT_BSTR;
252                         V_BSTR(&vValue) = SysAllocString(stats->categories[i].stats[j].sValue);
253                         if(!V_BSTR(&vValue))
254                             hr = E_OUTOFMEMORY;
255                     }
256
257                     if(SUCCEEDED(hr))
258                     {
259                         TRACE("    storing statistic %d: name: %s\n", j, debugstr_w(V_BSTR(&vValue)));
260                         hr = IXMLDOMElement_setAttribute(statisticsElement, bstrValue, vValue);
261                     }
262
263                     SysFreeString(V_BSTR(&vValue));
264
265                     if(SUCCEEDED(hr))
266                         hr = IXMLDOMElement_appendChild(categoryNode, statisticsNode, &statisticsNode);
267
268                     IXMLDOMElement_Release(statisticsElement);
269                     IXMLDOMNode_Release(statisticsNode);
270                 }
271             }
272
273             if(SUCCEEDED(hr))
274                 hr = IXMLDOMElement_appendChild(root, categoryNode, &categoryNode);
275
276             IXMLDOMElement_Release(categoryElement);
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 contaning 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. It's 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;
368     DWORD dwLength = 49*sizeof(WCHAR);/* place for GUID */
369
370     TRACE("(%s, %p)\n", debugstr_w(GDFBinaryPath), lpApplicationId);
371
372     if(!GDFBinaryPath)
373         return E_INVALIDARG;
374
375     installScope = GIS_CURRENT_USER;
376     hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId);
377
378     if(hr == S_FALSE)
379     {
380         installScope = GIS_ALL_USERS;
381         hr = GAMEUX_FindGameInstanceId(GDFBinaryPath, installScope, &instanceId);
382     }
383
384     if(hr == S_FALSE)
385         /* game not registered, so statistics cannot be used */
386         hr = E_FAIL;
387
388     if(SUCCEEDED(hr))
389         /* game is registered, let's read it's application id from registry */
390         hr = GAMEUX_buildGameRegistryPath(installScope, &instanceId, &lpRegistryPath);
391
392     if(SUCCEEDED(hr))
393         hr = HRESULT_FROM_WIN32(RegGetValueW(HKEY_LOCAL_MACHINE,
394                 lpRegistryPath, sApplicationId, RRF_RT_REG_SZ,
395                 NULL, lpApplicationId, &dwLength));
396
397     HeapFree(GetProcessHeap(), 0, lpRegistryPath);
398
399     TRACE("found app id: %s, return: %#x\n", debugstr_w(lpApplicationId), hr);
400     return hr;
401 }
402 /*******************************************************************
403  * IGameStatistics implementation
404  */
405 typedef struct _GameStatisticsImpl
406 {
407     const struct IGameStatisticsVtbl *lpVtbl;
408     LONG ref;
409     struct GAMEUX_STATS stats;
410 } GameStatisticsImpl;
411
412 static inline GameStatisticsImpl *impl_from_IGameStatistics( IGameStatistics *iface )
413 {
414     return (GameStatisticsImpl *)((char*)iface - FIELD_OFFSET(GameStatisticsImpl, lpVtbl));
415 }
416 static inline IGameStatistics *IGameStatistics_from_impl( GameStatisticsImpl* This )
417 {
418     return (struct IGameStatistics*)&This->lpVtbl;
419 }
420
421
422 static HRESULT WINAPI GameStatisticsImpl_QueryInterface(
423         IGameStatistics *iface,
424         REFIID riid,
425         void **ppvObject)
426 {
427     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
428
429     TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject );
430
431     *ppvObject = NULL;
432
433     if ( IsEqualGUID( riid, &IID_IUnknown ) ||
434          IsEqualGUID( riid, &IID_IGameStatistics ) )
435     {
436         *ppvObject = iface;
437     }
438     else
439     {
440         FIXME("interface %s not implemented\n", debugstr_guid(riid));
441         return E_NOINTERFACE;
442     }
443
444     IGameStatistics_AddRef( iface );
445     return S_OK;
446 }
447
448 static ULONG WINAPI GameStatisticsImpl_AddRef(IGameStatistics *iface)
449 {
450     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
451     LONG ref;
452
453     ref = InterlockedIncrement(&This->ref);
454
455     TRACE("(%p): ref=%d\n", This, ref);
456     return ref;
457 }
458
459 static ULONG WINAPI GameStatisticsImpl_Release(IGameStatistics *iface)
460 {
461     GameStatisticsImpl *This = impl_from_IGameStatistics( iface );
462     LONG ref;
463
464     ref = InterlockedDecrement( &This->ref );
465     TRACE("(%p): ref=%d\n", This, ref);
466
467     if ( ref == 0 )
468     {
469         TRACE("freeing IGameStatistics\n");
470         HeapFree( GetProcessHeap(), 0, This );
471     }
472
473     return ref;
474 }
475
476 static HRESULT WINAPI GameStatisticsImpl_GetMaxCategoryLength(
477     IGameStatistics *iface,
478     UINT *cch)
479 {
480     TRACE("(%p, %p)\n", iface, cch);
481     if(!cch)
482         return E_INVALIDARG;
483
484     *cch = MAX_CATEGORY_LENGTH;
485     return S_OK;
486 }
487
488 static HRESULT WINAPI GameStatisticsImpl_GetMaxNameLength(
489     IGameStatistics *iface,
490     UINT *cch)
491 {
492     TRACE("(%p, %p)\n", iface, cch);
493     if(!cch)
494         return E_INVALIDARG;
495
496     *cch = MAX_NAME_LENGTH;
497     return S_OK;
498 }
499
500 static HRESULT WINAPI GameStatisticsImpl_GetMaxValueLength(
501     IGameStatistics *iface,
502     UINT *cch)
503 {
504     TRACE("(%p, %p)\n", iface, cch);
505     if(!cch)
506         return E_INVALIDARG;
507
508     *cch = MAX_VALUE_LENGTH;
509     return S_OK;
510 }
511
512 static HRESULT WINAPI GameStatisticsImpl_GetMaxCategories(
513     IGameStatistics *iface,
514     WORD *pMax)
515 {
516     TRACE("(%p, %p)\n", iface, pMax);
517     if(!pMax)
518         return E_INVALIDARG;
519
520     *pMax = MAX_CATEGORIES;
521     return S_OK;
522 }
523
524 static HRESULT WINAPI GameStatisticsImpl_GetMaxStatsPerCategory(
525     IGameStatistics *iface,
526     WORD *pMax)
527 {
528     TRACE("(%p, %p)\n", iface, pMax);
529     if(!pMax)
530         return E_INVALIDARG;
531
532     *pMax = MAX_STATS_PER_CATEGORY;
533     return S_OK;
534 }
535
536 static HRESULT WINAPI GameStatisticsImpl_SetCategoryTitle(
537     IGameStatistics *iface,
538     WORD categoryIndex,
539     LPCWSTR title)
540 {
541     HRESULT hr = S_OK;
542     DWORD dwLength;
543     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
544
545     TRACE("(%p, %d, %s)\n", This, categoryIndex, debugstr_w(title));
546
547     if(!title || categoryIndex >= MAX_CATEGORIES)
548         return E_INVALIDARG;
549
550     dwLength = lstrlenW(title);
551
552     if(dwLength > MAX_CATEGORY_LENGTH)
553     {
554         hr = S_FALSE;
555         dwLength = MAX_CATEGORY_LENGTH;
556     }
557
558     lstrcpynW(This->stats.categories[categoryIndex].sName,
559               title, dwLength+1);
560
561     return hr;
562 }
563
564 static HRESULT WINAPI GameStatisticsImpl_GetCategoryTitle(
565     IGameStatistics *iface,
566     WORD categoryIndex,
567     LPWSTR *pTitle)
568 {
569     FIXME("stub\n");
570     return E_NOTIMPL;
571 }
572
573 static HRESULT WINAPI GameStatisticsImpl_GetStatistic(
574     IGameStatistics *iface,
575     WORD categoryIndex,
576     WORD statIndex,
577     LPWSTR *pName,
578     LPWSTR *pValue)
579 {
580     FIXME("stub\n");
581     return E_NOTIMPL;
582 }
583
584 static HRESULT WINAPI GameStatisticsImpl_SetStatistic(
585     IGameStatistics *iface,
586     WORD categoryIndex,
587     WORD statIndex,
588     LPCWSTR name,
589     LPCWSTR value)
590 {
591     HRESULT hr = S_OK;
592     DWORD dwNameLen, dwValueLen;
593     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
594
595     TRACE("(%p, %d, %d, %s, %s)\n", This, categoryIndex, statIndex,
596           debugstr_w(name), debugstr_w(value));
597
598     if(!name)
599         return S_FALSE;
600
601     if(categoryIndex >= MAX_CATEGORIES || statIndex >= MAX_STATS_PER_CATEGORY)
602         return E_INVALIDARG;
603
604     dwNameLen = lstrlenW(name);
605
606     if(dwNameLen > MAX_NAME_LENGTH)
607     {
608         hr = S_FALSE;
609         dwNameLen = MAX_NAME_LENGTH;
610     }
611
612     lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sName,
613               name, dwNameLen+1);
614
615     if(value)
616     {
617         dwValueLen = lstrlenW(value);
618
619         if(dwValueLen > MAX_VALUE_LENGTH)
620         {
621             hr = S_FALSE;
622             dwValueLen = MAX_VALUE_LENGTH;
623         }
624
625         lstrcpynW(This->stats.categories[categoryIndex].stats[statIndex].sValue,
626                   value, dwValueLen+1);
627     }
628     else
629         /* Windows allows to pass NULL as value */
630         This->stats.categories[categoryIndex].stats[statIndex].sValue[0] = 0;
631
632     return hr;
633 }
634
635 static HRESULT WINAPI GameStatisticsImpl_Save(
636     IGameStatistics *iface,
637     BOOL trackChanges)
638 {
639     GameStatisticsImpl *This = impl_from_IGameStatistics(iface);
640     HRESULT hr = S_OK;
641
642     TRACE("(%p, %d)\n", This, trackChanges);
643
644     if(trackChanges == TRUE)
645         FIXME("tracking changes not yet implemented\n");
646
647     hr = GAMEUX_updateStatisticsFile(&This->stats);
648
649     return hr;
650 }
651
652 static HRESULT WINAPI GameStatisticsImpl_SetLastPlayedCategory(
653     IGameStatistics *iface,
654     UINT categoryIndex)
655 {
656     FIXME("stub\n");
657     return E_NOTIMPL;
658 }
659
660 static HRESULT WINAPI GameStatisticsImpl_GetLastPlayedCategory(
661     IGameStatistics *iface,
662     UINT *pCategoryIndex)
663 {
664     FIXME("stub\n");
665     return E_NOTIMPL;
666 }
667
668 static const struct IGameStatisticsVtbl GameStatisticsImplVtbl =
669 {
670     GameStatisticsImpl_QueryInterface,
671     GameStatisticsImpl_AddRef,
672     GameStatisticsImpl_Release,
673     GameStatisticsImpl_GetMaxCategoryLength,
674     GameStatisticsImpl_GetMaxNameLength,
675     GameStatisticsImpl_GetMaxValueLength,
676     GameStatisticsImpl_GetMaxCategories,
677     GameStatisticsImpl_GetMaxStatsPerCategory,
678     GameStatisticsImpl_SetCategoryTitle,
679     GameStatisticsImpl_GetCategoryTitle,
680     GameStatisticsImpl_GetStatistic,
681     GameStatisticsImpl_SetStatistic,
682     GameStatisticsImpl_Save,
683     GameStatisticsImpl_SetLastPlayedCategory,
684     GameStatisticsImpl_GetLastPlayedCategory
685 };
686
687
688 HRESULT create_IGameStatistics(GameStatisticsImpl** ppStats)
689 {
690     TRACE("(%p)\n", ppStats);
691
692     *ppStats = HeapAlloc( GetProcessHeap(), 0, sizeof(**ppStats));
693     if(!(*ppStats))
694         return E_OUTOFMEMORY;
695
696     (*ppStats)->lpVtbl = &GameStatisticsImplVtbl;
697     (*ppStats)->ref = 1;
698
699     TRACE("returing coclass: %p\n", *ppStats);
700     return S_OK;
701 }
702
703 /*******************************************************************************
704  * IGameStatisticsMgr implementation
705  */
706 typedef struct _GameStatisticsMgrImpl
707 {
708     const struct IGameStatisticsMgrVtbl *lpVtbl;
709     LONG ref;
710 } GameStatisticsMgrImpl;
711
712 static inline GameStatisticsMgrImpl *impl_from_IGameStatisticsMgr( IGameStatisticsMgr *iface )
713 {
714     return (GameStatisticsMgrImpl *)((char*)iface - FIELD_OFFSET(GameStatisticsMgrImpl, lpVtbl));
715 }
716
717
718 static HRESULT WINAPI GameStatisticsMgrImpl_QueryInterface(
719         IGameStatisticsMgr *iface,
720         REFIID riid,
721         void **ppvObject)
722 {
723     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
724
725     TRACE("%p %s %p\n", This, debugstr_guid( riid ), ppvObject );
726
727     *ppvObject = NULL;
728
729     if(IsEqualGUID(riid, &IID_IUnknown) ||
730        IsEqualGUID(riid, &IID_IGameStatisticsMgr) )
731     {
732         *ppvObject = iface;
733     }
734     else
735     {
736         FIXME("interface %s not implemented\n", debugstr_guid(riid));
737         return E_NOINTERFACE;
738     }
739
740     IGameStatisticsMgr_AddRef( iface );
741     return S_OK;
742 }
743
744 static ULONG WINAPI GameStatisticsMgrImpl_AddRef(IGameStatisticsMgr *iface)
745 {
746     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
747     LONG ref;
748
749     ref = InterlockedIncrement(&This->ref);
750
751     TRACE("(%p): ref=%d\n", This, ref);
752     return ref;
753 }
754
755 static ULONG WINAPI GameStatisticsMgrImpl_Release(IGameStatisticsMgr *iface)
756 {
757     GameStatisticsMgrImpl *This = impl_from_IGameStatisticsMgr( iface );
758     LONG ref;
759
760     ref = InterlockedDecrement(&This->ref);
761     TRACE("(%p): ref=%d\n", This, ref);
762
763     if ( ref == 0 )
764     {
765         TRACE("freeing GameStatistics object\n");
766         HeapFree( GetProcessHeap(), 0, This);
767     }
768
769     return ref;
770 }
771
772 static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_GetGameStatistics(
773         IGameStatisticsMgr* iface,
774         LPCWSTR GDFBinaryPath,
775         GAMESTATS_OPEN_TYPE openType,
776         GAMESTATS_OPEN_RESULT *pOpenResult,
777         IGameStatistics **ppiStats)
778 {
779     HRESULT hr;
780     WCHAR lpApplicationId[49];
781     GameStatisticsImpl *statisticsImpl;
782
783     TRACE("(%p, %s, 0x%x, %p, %p)\n", iface, debugstr_w(GDFBinaryPath), openType, pOpenResult, ppiStats);
784
785     hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId);
786
787     if(SUCCEEDED(hr))
788         hr = create_IGameStatistics(&statisticsImpl);
789
790     if(SUCCEEDED(hr))
791     {
792         *ppiStats = IGameStatistics_from_impl(statisticsImpl);
793         hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, statisticsImpl->stats.sStatsFile);
794     }
795
796     if(SUCCEEDED(hr))
797     {
798         FIXME("loading game statistics not yet implemented\n");
799         hr = E_NOTIMPL;
800     }
801
802     return hr;
803 }
804
805 static HRESULT STDMETHODCALLTYPE GameStatisticsMgrImpl_RemoveGameStatistics(
806         IGameStatisticsMgr* iface,
807         LPCWSTR GDFBinaryPath)
808 {
809     HRESULT hr;
810     WCHAR lpApplicationId[49];
811     WCHAR sStatsFile[MAX_PATH];
812
813     TRACE("(%p, %s)\n", iface, debugstr_w(GDFBinaryPath));
814
815     hr = GAMEUX_getAppIdFromGDFPath(GDFBinaryPath, lpApplicationId);
816
817     if(SUCCEEDED(hr))
818         hr = GAMEUX_buildStatisticsFilePath(lpApplicationId, sStatsFile);
819
820     if(SUCCEEDED(hr))
821         hr = (DeleteFileW(sStatsFile)==TRUE ? S_OK : HRESULT_FROM_WIN32(GetLastError()));
822
823     return hr;
824 }
825
826 static const struct IGameStatisticsMgrVtbl GameStatisticsMgrImplVtbl =
827 {
828     GameStatisticsMgrImpl_QueryInterface,
829     GameStatisticsMgrImpl_AddRef,
830     GameStatisticsMgrImpl_Release,
831     GameStatisticsMgrImpl_GetGameStatistics,
832     GameStatisticsMgrImpl_RemoveGameStatistics,
833 };
834
835 HRESULT GameStatistics_create(
836         IUnknown *pUnkOuter,
837         IUnknown **ppObj)
838 {
839     GameStatisticsMgrImpl *pGameStatistics;
840
841     TRACE("(%p, %p)\n", pUnkOuter, ppObj);
842
843     pGameStatistics = HeapAlloc( GetProcessHeap(), 0, sizeof (*pGameStatistics) );
844
845     if( !pGameStatistics )
846         return E_OUTOFMEMORY;
847
848     pGameStatistics->lpVtbl = &GameStatisticsMgrImplVtbl;
849     pGameStatistics->ref = 1;
850
851     *ppObj = (IUnknown*)(&pGameStatistics->lpVtbl);
852
853     TRACE("returning iface %p\n", *ppObj);
854     return S_OK;
855 }