1 /*
2  * Unit tests for shelllinks
3  *
4  * Copyright 2004 Mike McCormack
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  */
21 
22 #define COBJMACROS
23 
24 #include "initguid.h"
25 #include "windows.h"
26 #include "shlguid.h"
27 #include "shobjidl.h"
28 #include "shlobj.h"
29 #include "shellapi.h"
30 #include "commoncontrols.h"
31 #include "wine/test.h"
32 
33 #include "shell32_test.h"
34 
35 #include <reactos/undocshell.h>
36 
37 #ifndef SLDF_HAS_LOGO3ID
38 #  define SLDF_HAS_LOGO3ID 0x00000800 /* not available in the Vista SDK */
39 #endif
40 
41 static void (WINAPI *pILFree)(LPITEMIDLIST);
42 static BOOL (WINAPI *pILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST);
43 static HRESULT (WINAPI *pSHILCreateFromPath)(LPCWSTR, LPITEMIDLIST *,DWORD*);
44 static HRESULT (WINAPI *pSHDefExtractIconA)(LPCSTR, int, UINT, HICON*, HICON*, UINT);
45 static HRESULT (WINAPI *pSHGetStockIconInfo)(SHSTOCKICONID, UINT, SHSTOCKICONINFO *);
46 static DWORD (WINAPI *pGetLongPathNameA)(LPCSTR, LPSTR, DWORD);
47 static DWORD (WINAPI *pGetShortPathNameA)(LPCSTR, LPSTR, DWORD);
48 static UINT (WINAPI *pSHExtractIconsW)(LPCWSTR, int, int, int, HICON *, UINT *, UINT, UINT);
49 static BOOL (WINAPI *pIsProcessDPIAware)(void);
50 
51 static const GUID _IID_IShellLinkDataList = {
52     0x45e2b4ae, 0xb1c3, 0x11d0,
53     { 0xb9, 0x2f, 0x00, 0xa0, 0xc9, 0x03, 0x12, 0xe1 }
54 };
55 
56 
57 /* For some reason SHILCreateFromPath does not work on Win98 and
58  * SHSimpleIDListFromPathA does not work on NT4. But if we call both we
59  * get what we want on all platforms.
60  */
61 static LPITEMIDLIST (WINAPI *pSHSimpleIDListFromPathAW)(LPCVOID);
62 
63 static LPITEMIDLIST path_to_pidl(const char* path)
64 {
65     LPITEMIDLIST pidl;
66 
67     if (!pSHSimpleIDListFromPathAW)
68     {
69         HMODULE hdll=GetModuleHandleA("shell32.dll");
70         pSHSimpleIDListFromPathAW=(void*)GetProcAddress(hdll, (char*)162);
71         if (!pSHSimpleIDListFromPathAW)
72             win_skip("SHSimpleIDListFromPathAW not found in shell32.dll\n");
73     }
74 
75     pidl=NULL;
76     /* pSHSimpleIDListFromPathAW maps to A on non NT platforms */
77     if (pSHSimpleIDListFromPathAW && (GetVersion() & 0x80000000))
78         pidl=pSHSimpleIDListFromPathAW(path);
79 
80     if (!pidl)
81     {
82         WCHAR* pathW;
83         HRESULT r;
84         int len;
85 
86         len=MultiByteToWideChar(CP_ACP, 0, path, -1, NULL, 0);
87         pathW=HeapAlloc(GetProcessHeap(), 0, len*sizeof(WCHAR));
88         MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, len);
89 
90         r=pSHILCreateFromPath(pathW, &pidl, NULL);
91         ok(r == S_OK, "SHILCreateFromPath failed (0x%08x)\n", r);
92         HeapFree(GetProcessHeap(), 0, pathW);
93     }
94     return pidl;
95 }
96 
97 
98 /*
99  * Test manipulation of an IShellLink's properties.
100  */
101 
102 static void test_get_set(void)
103 {
104     HRESULT r;
105     IShellLinkA *sl;
106     IShellLinkW *slW = NULL;
107     char mypath[MAX_PATH];
108     char buffer[INFOTIPSIZE];
109     LPITEMIDLIST pidl, tmp_pidl;
110     const char * str;
111     int i;
112     WORD w;
113 
114     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
115                          &IID_IShellLinkA, (LPVOID*)&sl);
116     ok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
117     if (r != S_OK)
118         return;
119 
120     /* Test Getting / Setting the description */
121     strcpy(buffer,"garbage");
122     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
123     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
124     ok(*buffer=='\0', "GetDescription returned '%s'\n", buffer);
125 
126     str="Some description";
127     r = IShellLinkA_SetDescription(sl, str);
128     ok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
129 
130     strcpy(buffer,"garbage");
131     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
132     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
133     ok(strcmp(buffer,str)==0, "GetDescription returned '%s'\n", buffer);
134 
135     r = IShellLinkA_SetDescription(sl, NULL);
136     ok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
137 
138     strcpy(buffer,"garbage");
139     r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
140     ok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
141     ok(*buffer=='\0' || broken(strcmp(buffer,str)==0), "GetDescription returned '%s'\n", buffer); /* NT4 */
142 
143     /* Test Getting / Setting the work directory */
144     strcpy(buffer,"garbage");
145     r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
146     ok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
147     ok(*buffer=='\0', "GetWorkingDirectory returned '%s'\n", buffer);
148 
149     str="c:\\nonexistent\\directory";
150     r = IShellLinkA_SetWorkingDirectory(sl, str);
151     ok(r == S_OK, "SetWorkingDirectory failed (0x%08x)\n", r);
152 
153     strcpy(buffer,"garbage");
154     r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
155     ok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
156     ok(lstrcmpiA(buffer,str)==0, "GetWorkingDirectory returned '%s'\n", buffer);
157 
158     /* Test Getting / Setting the path */
159     strcpy(buffer,"garbage");
160     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
161     todo_wine ok(r == S_FALSE || broken(r == S_OK) /* NT4/W2K */, "GetPath failed (0x%08x)\n", r);
162     ok(*buffer=='\0', "GetPath returned '%s'\n", buffer);
163 
164     CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
165                      &IID_IShellLinkW, (LPVOID*)&slW);
166     if (!slW /* Win9x */ || !pGetLongPathNameA /* NT4 */)
167         skip("SetPath with NULL parameter crashes on Win9x and some NT4\n");
168     else
169     {
170         IShellLinkW_Release(slW);
171         r = IShellLinkA_SetPath(sl, NULL);
172         ok(r==E_INVALIDARG ||
173            broken(r==S_OK), /* Some Win95 and NT4 */
174            "SetPath returned wrong error (0x%08x)\n", r);
175     }
176 
177     r = IShellLinkA_SetPath(sl, "");
178     ok(r==S_OK, "SetPath failed (0x%08x)\n", r);
179 
180     strcpy(buffer,"garbage");
181     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
182     todo_wine ok(r == S_FALSE, "GetPath failed (0x%08x)\n", r);
183     ok(*buffer=='\0', "GetPath returned '%s'\n", buffer);
184 
185     /* Win98 returns S_FALSE, but WinXP returns S_OK */
186     str="c:\\nonexistent\\file";
187     r = IShellLinkA_SetPath(sl, str);
188     ok(r==S_FALSE || r==S_OK, "SetPath failed (0x%08x)\n", r);
189 
190     strcpy(buffer,"garbage");
191     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
192     ok(r == S_OK, "GetPath failed (0x%08x)\n", r);
193     ok(lstrcmpiA(buffer,str)==0, "GetPath returned '%s'\n", buffer);
194 
195     /* Get some real path to play with */
196     GetWindowsDirectoryA( mypath, sizeof(mypath)-12 );
197     strcat(mypath, "\\regedit.exe");
198 
199     /* Test the interaction of SetPath and SetIDList */
200     tmp_pidl=NULL;
201     r = IShellLinkA_GetIDList(sl, &tmp_pidl);
202     ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
203     if (r == S_OK)
204     {
205         BOOL ret;
206 
207         strcpy(buffer,"garbage");
208         ret = SHGetPathFromIDListA(tmp_pidl, buffer);
209         ok(ret, "SHGetPathFromIDListA failed\n");
210         if (ret)
211             ok(lstrcmpiA(buffer,str)==0, "GetIDList returned '%s'\n", buffer);
212         pILFree(tmp_pidl);
213     }
214 
215     pidl=path_to_pidl(mypath);
216     ok(pidl!=NULL, "path_to_pidl returned a NULL pidl\n");
217 
218     if (pidl)
219     {
220         LPITEMIDLIST second_pidl;
221 
222         r = IShellLinkA_SetIDList(sl, pidl);
223         ok(r == S_OK, "SetIDList failed (0x%08x)\n", r);
224 
225         tmp_pidl=NULL;
226         r = IShellLinkA_GetIDList(sl, &tmp_pidl);
227         ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
228         ok(tmp_pidl && pILIsEqual(pidl, tmp_pidl),
229            "GetIDList returned an incorrect pidl\n");
230 
231         r = IShellLinkA_GetIDList(sl, &second_pidl);
232         ok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
233         ok(second_pidl && pILIsEqual(pidl, second_pidl),
234            "GetIDList returned an incorrect pidl\n");
235         ok(second_pidl != tmp_pidl, "pidls are the same\n");
236 
237         pILFree(second_pidl);
238         pILFree(tmp_pidl);
239         pILFree(pidl);
240 
241         strcpy(buffer,"garbage");
242         r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
243         ok(r == S_OK, "GetPath failed (0x%08x)\n", r);
244         todo_wine
245         ok(lstrcmpiA(buffer, mypath)==0, "GetPath returned '%s'\n", buffer);
246     }
247 
248     /* test path with quotes (IShellLinkA_SetPath returns S_FALSE on W2K and below and S_OK on XP and above */
249     r = IShellLinkA_SetPath(sl, "\"c:\\nonexistent\\file\"");
250     ok(r==S_FALSE || r == S_OK, "SetPath failed (0x%08x)\n", r);
251 
252     strcpy(buffer,"garbage");
253     r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
254     ok(r==S_OK, "GetPath failed (0x%08x)\n", r);
255     todo_wine ok(!strcmp(buffer, "C:\\nonexistent\\file") ||
256        broken(!strcmp(buffer, "C:\\\"c:\\nonexistent\\file\"")), /* NT4 */
257        "case doesn't match\n");
258 
259     r = IShellLinkA_SetPath(sl, "\"c:\\foo");
260     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
261 
262     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo");
263     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
264 
265     r = IShellLinkA_SetPath(sl, "c:\\foo\"");
266     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
267 
268     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo\"");
269     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
270 
271     r = IShellLinkA_SetPath(sl, "\"\"c:\\foo\"\"");
272     ok(r==S_FALSE || r == S_OK || r == E_INVALIDARG /* Vista */, "SetPath failed (0x%08x)\n", r);
273 
274     /* Test Getting / Setting the arguments */
275     strcpy(buffer,"garbage");
276     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
277     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
278     ok(*buffer=='\0', "GetArguments returned '%s'\n", buffer);
279 
280     str="param1 \"spaced param2\"";
281     r = IShellLinkA_SetArguments(sl, str);
282     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
283 
284     strcpy(buffer,"garbage");
285     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
286     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
287     ok(strcmp(buffer,str)==0, "GetArguments returned '%s'\n", buffer);
288 
289     strcpy(buffer,"garbage");
290     r = IShellLinkA_SetArguments(sl, NULL);
291     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
292     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
293     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
294     ok(!buffer[0] || strcmp(buffer,str)==0, "GetArguments returned '%s'\n", buffer);
295 
296     strcpy(buffer,"garbage");
297     r = IShellLinkA_SetArguments(sl, "");
298     ok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
299     r = IShellLinkA_GetArguments(sl, buffer, sizeof(buffer));
300     ok(r == S_OK, "GetArguments failed (0x%08x)\n", r);
301     ok(!buffer[0], "GetArguments returned '%s'\n", buffer);
302 
303     /* Test Getting / Setting showcmd */
304     i=0xdeadbeef;
305     r = IShellLinkA_GetShowCmd(sl, &i);
306     ok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
307     ok(i==SW_SHOWNORMAL, "GetShowCmd returned %d\n", i);
308 
309     r = IShellLinkA_SetShowCmd(sl, SW_SHOWMAXIMIZED);
310     ok(r == S_OK, "SetShowCmd failed (0x%08x)\n", r);
311 
312     i=0xdeadbeef;
313     r = IShellLinkA_GetShowCmd(sl, &i);
314     ok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
315     ok(i==SW_SHOWMAXIMIZED, "GetShowCmd returned %d'\n", i);
316 
317     /* Test Getting / Setting the icon */
318     i=0xdeadbeef;
319     strcpy(buffer,"garbage");
320     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
321     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
322     ok(*buffer=='\0', "GetIconLocation returned '%s'\n", buffer);
323     ok(i==0, "GetIconLocation returned %d\n", i);
324 
325     str="c:\\nonexistent\\file";
326     r = IShellLinkA_SetIconLocation(sl, str, 0xbabecafe);
327     ok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
328 
329     i=0xdeadbeef;
330     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
331     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
332     ok(lstrcmpiA(buffer,str)==0, "GetIconLocation returned '%s'\n", buffer);
333     ok(i==0xbabecafe, "GetIconLocation returned %d'\n", i);
334 
335     /* Test Getting / Setting the hot key */
336     w=0xbeef;
337     r = IShellLinkA_GetHotkey(sl, &w);
338     ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
339     ok(w==0, "GetHotkey returned %d\n", w);
340 
341     r = IShellLinkA_SetHotkey(sl, 0x5678);
342     ok(r == S_OK, "SetHotkey failed (0x%08x)\n", r);
343 
344     w=0xbeef;
345     r = IShellLinkA_GetHotkey(sl, &w);
346     ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
347     ok(w==0x5678, "GetHotkey returned %d'\n", w);
348 
349     IShellLinkA_Release(sl);
350 }
351 
352 
353 /*
354  * Test saving and loading .lnk files
355  */
356 
357 #define lok                   ok_(__FILE__, line)
358 #define check_lnk(a,b,c)        check_lnk_(__LINE__, (a), (b), (c))
359 
360 void create_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int save_fails)
361 {
362     HRESULT r;
363     IShellLinkA *sl;
364     IPersistFile *pf;
365 
366     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
367                          &IID_IShellLinkA, (LPVOID*)&sl);
368     lok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
369     if (r != S_OK)
370         return;
371 
372     if (desc->description)
373     {
374         r = IShellLinkA_SetDescription(sl, desc->description);
375         lok(r == S_OK, "SetDescription failed (0x%08x)\n", r);
376     }
377     if (desc->workdir)
378     {
379         r = IShellLinkA_SetWorkingDirectory(sl, desc->workdir);
380         lok(r == S_OK, "SetWorkingDirectory failed (0x%08x)\n", r);
381     }
382     if (desc->path)
383     {
384         r = IShellLinkA_SetPath(sl, desc->path);
385         lok(SUCCEEDED(r), "SetPath failed (0x%08x)\n", r);
386     }
387     if (desc->pidl)
388     {
389         r = IShellLinkA_SetIDList(sl, desc->pidl);
390         lok(r == S_OK, "SetIDList failed (0x%08x)\n", r);
391     }
392     if (desc->arguments)
393     {
394         r = IShellLinkA_SetArguments(sl, desc->arguments);
395         lok(r == S_OK, "SetArguments failed (0x%08x)\n", r);
396     }
397     if (desc->showcmd)
398     {
399         r = IShellLinkA_SetShowCmd(sl, desc->showcmd);
400         lok(r == S_OK, "SetShowCmd failed (0x%08x)\n", r);
401     }
402     if (desc->icon)
403     {
404         r = IShellLinkA_SetIconLocation(sl, desc->icon, desc->icon_id);
405         lok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
406     }
407     if (desc->hotkey)
408     {
409         r = IShellLinkA_SetHotkey(sl, desc->hotkey);
410         lok(r == S_OK, "SetHotkey failed (0x%08x)\n", r);
411     }
412 
413     r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (void**)&pf);
414     lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r);
415     if (r == S_OK)
416     {
417         LPOLESTR str;
418 
419     if (0)
420     {
421         /* crashes on XP */
422         IPersistFile_GetCurFile(pf, NULL);
423     }
424 
425         /* test GetCurFile before ::Save */
426         str = (LPWSTR)0xdeadbeef;
427         r = IPersistFile_GetCurFile(pf, &str);
428         lok(r == S_FALSE ||
429             broken(r == S_OK), /* shell32 < 5.0 */
430             "got 0x%08x\n", r);
431         lok(str == NULL, "got %p\n", str);
432 
433         r = IPersistFile_Save(pf, path, TRUE);
434         todo_wine_if (save_fails)
435             lok(r == S_OK, "save failed (0x%08x)\n", r);
436 
437         /* test GetCurFile after ::Save */
438         r = IPersistFile_GetCurFile(pf, &str);
439         lok(r == S_OK, "got 0x%08x\n", r);
440         lok(str != NULL ||
441             broken(str == NULL), /* shell32 < 5.0 */
442             "Didn't expect NULL\n");
443         if (str != NULL)
444         {
445             IMalloc *pmalloc;
446 
447             lok(!winetest_strcmpW(path, str), "Expected %s, got %s\n",
448                 wine_dbgstr_w(path), wine_dbgstr_w(str));
449 
450             SHGetMalloc(&pmalloc);
451             IMalloc_Free(pmalloc, str);
452         }
453         else
454             win_skip("GetCurFile fails on shell32 < 5.0\n");
455 
456         IPersistFile_Release(pf);
457     }
458 
459     IShellLinkA_Release(sl);
460 }
461 
462 static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int todo)
463 {
464     HRESULT r;
465     IShellLinkA *sl;
466     IPersistFile *pf;
467     char buffer[INFOTIPSIZE];
468     LPOLESTR str;
469 
470     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
471                          &IID_IShellLinkA, (LPVOID*)&sl);
472     lok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
473     if (r != S_OK)
474         return;
475 
476     r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf);
477     lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r);
478     if (r != S_OK)
479     {
480         IShellLinkA_Release(sl);
481         return;
482     }
483 
484     /* test GetCurFile before ::Load */
485     str = (LPWSTR)0xdeadbeef;
486     r = IPersistFile_GetCurFile(pf, &str);
487     lok(r == S_FALSE ||
488         broken(r == S_OK), /* shell32 < 5.0 */
489         "got 0x%08x\n", r);
490     lok(str == NULL, "got %p\n", str);
491 
492     r = IPersistFile_Load(pf, path, STGM_READ);
493     lok(r == S_OK, "load failed (0x%08x)\n", r);
494 
495     /* test GetCurFile after ::Save */
496     r = IPersistFile_GetCurFile(pf, &str);
497     lok(r == S_OK, "got 0x%08x\n", r);
498     lok(str != NULL ||
499         broken(str == NULL), /* shell32 < 5.0 */
500         "Didn't expect NULL\n");
501     if (str != NULL)
502     {
503         IMalloc *pmalloc;
504 
505         lok(!winetest_strcmpW(path, str), "Expected %s, got %s\n",
506             wine_dbgstr_w(path), wine_dbgstr_w(str));
507 
508         SHGetMalloc(&pmalloc);
509         IMalloc_Free(pmalloc, str);
510     }
511     else
512         win_skip("GetCurFile fails on shell32 < 5.0\n");
513 
514     IPersistFile_Release(pf);
515     if (r != S_OK)
516     {
517         IShellLinkA_Release(sl);
518         return;
519     }
520 
521     if (desc->description)
522     {
523         strcpy(buffer,"garbage");
524         r = IShellLinkA_GetDescription(sl, buffer, sizeof(buffer));
525         lok(r == S_OK, "GetDescription failed (0x%08x)\n", r);
526         todo_wine_if ((todo & 0x1) != 0)
527             lok(strcmp(buffer, desc->description)==0, "GetDescription returned '%s' instead of '%s'\n",
528                 buffer, desc->description);
529     }
530     if (desc->workdir)
531     {
532         strcpy(buffer,"garbage");
533         r = IShellLinkA_GetWorkingDirectory(sl, buffer, sizeof(buffer));
534         lok(r == S_OK, "GetWorkingDirectory failed (0x%08x)\n", r);
535         todo_wine_if ((todo & 0x2) != 0)
536             lok(lstrcmpiA(buffer, desc->workdir)==0, "GetWorkingDirectory returned '%s' instead of '%s'\n",
537                 buffer, desc->workdir);
538     }
539     if (desc->path)
540     {
541         strcpy(buffer,"garbage");
542         r = IShellLinkA_GetPath(sl, buffer, sizeof(buffer), NULL, SLGP_RAWPATH);
543         lok(SUCCEEDED(r), "GetPath failed (0x%08x)\n", r);
544         todo_wine_if ((todo & 0x4) != 0)
545             lok(lstrcmpiA(buffer, desc->path)==0, "GetPath returned '%s' instead of '%s'\n",
546                 buffer, desc->path);
547     }
548     if (desc->pidl)
549     {
550         LPITEMIDLIST pidl=NULL;
551         r = IShellLinkA_GetIDList(sl, &pidl);
552         lok(r == S_OK, "GetIDList failed (0x%08x)\n", r);
553         todo_wine_if ((todo & 0x8) != 0)
554             lok(pILIsEqual(pidl, desc->pidl), "GetIDList returned an incorrect pidl\n");
555     }
556     if (desc->showcmd)
557     {
558         int i=0xdeadbeef;
559         r = IShellLinkA_GetShowCmd(sl, &i);
560         lok(r == S_OK, "GetShowCmd failed (0x%08x)\n", r);
561         todo_wine_if ((todo & 0x10) != 0)
562             lok(i==desc->showcmd, "GetShowCmd returned 0x%0x instead of 0x%0x\n",
563                 i, desc->showcmd);
564     }
565     if (desc->icon)
566     {
567         int i=0xdeadbeef;
568         strcpy(buffer,"garbage");
569         r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
570         lok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
571         todo_wine_if ((todo & 0x20) != 0) {
572             lok(lstrcmpiA(buffer, desc->icon)==0, "GetIconLocation returned '%s' instead of '%s'\n",
573                 buffer, desc->icon);
574             lok(i==desc->icon_id, "GetIconLocation returned 0x%0x instead of 0x%0x\n",
575                 i, desc->icon_id);
576         }
577     }
578     if (desc->hotkey)
579     {
580         WORD i=0xbeef;
581         r = IShellLinkA_GetHotkey(sl, &i);
582         lok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
583         todo_wine_if ((todo & 0x40) != 0)
584             lok(i==desc->hotkey, "GetHotkey returned 0x%04x instead of 0x%04x\n",
585                 i, desc->hotkey);
586     }
587 
588     IShellLinkA_Release(sl);
589 }
590 
591 static void test_load_save(void)
592 {
593     WCHAR lnkfile[MAX_PATH];
594     char lnkfileA[MAX_PATH];
595     static const char lnkfileA_name[] = "\\test.lnk";
596 
597     lnk_desc_t desc;
598     char mypath[MAX_PATH];
599     char mydir[MAX_PATH];
600     char realpath[MAX_PATH];
601     char* p;
602     HANDLE hf;
603     DWORD r;
604 
605     if (!pGetLongPathNameA)
606     {
607         win_skip("GetLongPathNameA is not available\n");
608         return;
609     }
610 
611     /* Don't used a fixed path for the test.lnk file */
612     GetTempPathA(MAX_PATH, lnkfileA);
613     lstrcatA(lnkfileA, lnkfileA_name);
614     MultiByteToWideChar(CP_ACP, 0, lnkfileA, -1, lnkfile, MAX_PATH);
615 
616     /* Save an empty .lnk file */
617     memset(&desc, 0, sizeof(desc));
618     create_lnk(lnkfile, &desc, 0);
619 
620     /* It should come back as a bunch of empty strings */
621     desc.description="";
622     desc.workdir="";
623     desc.path="";
624     desc.arguments="";
625     desc.icon="";
626     check_lnk(lnkfile, &desc, 0x0);
627 
628     /* Point a .lnk file to nonexistent files */
629     desc.description="";
630     desc.workdir="c:\\Nonexitent\\work\\directory";
631     desc.path="c:\\nonexistent\\path";
632     desc.pidl=NULL;
633     desc.arguments="";
634     desc.showcmd=0;
635     desc.icon="c:\\nonexistent\\icon\\file";
636     desc.icon_id=1234;
637     desc.hotkey=0;
638     create_lnk(lnkfile, &desc, 0);
639     check_lnk(lnkfile, &desc, 0x0);
640 
641     r=GetModuleFileNameA(NULL, mypath, sizeof(mypath));
642     ok(r<sizeof(mypath), "GetModuleFileName failed (%d)\n", r);
643     strcpy(mydir, mypath);
644     p=strrchr(mydir, '\\');
645     if (p)
646         *p='\0';
647 
648     /* IShellLink returns path in long form */
649     if (!pGetLongPathNameA(mypath, realpath, MAX_PATH)) strcpy( realpath, mypath );
650 
651     /* Overwrite the existing lnk file and point it to existing files */
652     desc.description="test 2";
653     desc.workdir=mydir;
654     desc.path=realpath;
655     desc.pidl=NULL;
656     desc.arguments="/option1 /option2 \"Some string\"";
657     desc.showcmd=SW_SHOWNORMAL;
658     desc.icon=mypath;
659     desc.icon_id=0;
660     desc.hotkey=0x1234;
661     create_lnk(lnkfile, &desc, 0);
662     check_lnk(lnkfile, &desc, 0x0);
663 
664     /* Test omitting .exe from an absolute path */
665     p=strrchr(realpath, '.');
666     if (p)
667         *p='\0';
668 
669     desc.description="absolute path without .exe";
670     desc.workdir=mydir;
671     desc.path=realpath;
672     desc.pidl=NULL;
673     desc.arguments="/option1 /option2 \"Some string\"";
674     desc.showcmd=SW_SHOWNORMAL;
675     desc.icon=mypath;
676     desc.icon_id=0;
677     desc.hotkey=0x1234;
678     create_lnk(lnkfile, &desc, 0);
679     strcat(realpath, ".exe");
680     check_lnk(lnkfile, &desc, 0x4);
681 
682     /* Overwrite the existing lnk file and test link to a command on the path */
683     desc.description="command on path";
684     desc.workdir=mypath;
685     desc.path="rundll32.exe";
686     desc.pidl=NULL;
687     desc.arguments="/option1 /option2 \"Some string\"";
688     desc.showcmd=SW_SHOWNORMAL;
689     desc.icon=mypath;
690     desc.icon_id=0;
691     desc.hotkey=0x1234;
692     create_lnk(lnkfile, &desc, 0);
693     /* Check that link is created to proper location */
694     SearchPathA( NULL, desc.path, NULL, MAX_PATH, realpath, NULL);
695     desc.path=realpath;
696     check_lnk(lnkfile, &desc, 0x0);
697 
698     /* Test omitting .exe from a command on the path */
699     desc.description="command on path without .exe";
700     desc.workdir=mypath;
701     desc.path="rundll32";
702     desc.pidl=NULL;
703     desc.arguments="/option1 /option2 \"Some string\"";
704     desc.showcmd=SW_SHOWNORMAL;
705     desc.icon=mypath;
706     desc.icon_id=0;
707     desc.hotkey=0x1234;
708     create_lnk(lnkfile, &desc, 0);
709     /* Check that link is created to proper location */
710     SearchPathA( NULL, "rundll32", NULL, MAX_PATH, realpath, NULL);
711     desc.path=realpath;
712     check_lnk(lnkfile, &desc, 0x4);
713 
714     /* Create a temporary non-executable file */
715     r=GetTempPathA(sizeof(mypath), mypath);
716     ok(r<sizeof(mypath), "GetTempPath failed (%d), err %d\n", r, GetLastError());
717     r=pGetLongPathNameA(mypath, mydir, sizeof(mydir));
718     ok(r<sizeof(mydir), "GetLongPathName failed (%d), err %d\n", r, GetLastError());
719     p=strrchr(mydir, '\\');
720     if (p)
721         *p='\0';
722 
723     strcpy(mypath, mydir);
724     strcat(mypath, "\\test.txt");
725     hf = CreateFileA(mypath, GENERIC_WRITE, 0, NULL,
726                      CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
727     CloseHandle(hf);
728 
729     /* Overwrite the existing lnk file and test link to an existing non-executable file */
730     desc.description="non-executable file";
731     desc.workdir=mydir;
732     desc.path=mypath;
733     desc.pidl=NULL;
734     desc.arguments="";
735     desc.showcmd=SW_SHOWNORMAL;
736     desc.icon=mypath;
737     desc.icon_id=0;
738     desc.hotkey=0x1234;
739     create_lnk(lnkfile, &desc, 0);
740     check_lnk(lnkfile, &desc, 0x0);
741 
742     r=pGetShortPathNameA(mydir, mypath, sizeof(mypath));
743     ok(r<sizeof(mypath), "GetShortPathName failed (%d), err %d\n", r, GetLastError());
744 
745     strcpy(realpath, mypath);
746     strcat(realpath, "\\test.txt");
747     strcat(mypath, "\\\\test.txt");
748 
749     /* Overwrite the existing lnk file and test link to a short path with double backslashes */
750     desc.description="non-executable file";
751     desc.workdir=mydir;
752     desc.path=mypath;
753     desc.pidl=NULL;
754     desc.arguments="";
755     desc.showcmd=SW_SHOWNORMAL;
756     desc.icon=mypath;
757     desc.icon_id=0;
758     desc.hotkey=0x1234;
759     create_lnk(lnkfile, &desc, 0);
760     desc.path=realpath;
761     check_lnk(lnkfile, &desc, 0x0);
762 
763     r = DeleteFileA(mypath);
764     ok(r, "failed to delete file %s (%d)\n", mypath, GetLastError());
765 
766     /* Create a temporary .bat file */
767     strcpy(mypath, mydir);
768     strcat(mypath, "\\test.bat");
769     hf = CreateFileA(mypath, GENERIC_WRITE, 0, NULL,
770                      CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
771     CloseHandle(hf);
772 
773     strcpy(realpath, mypath);
774 
775     p=strrchr(mypath, '.');
776     if (p)
777         *p='\0';
778 
779     /* Try linking to the .bat file without the extension */
780     desc.description="batch file";
781     desc.workdir=mydir;
782     desc.path=mypath;
783     desc.pidl=NULL;
784     desc.arguments="";
785     desc.showcmd=SW_SHOWNORMAL;
786     desc.icon=mypath;
787     desc.icon_id=0;
788     desc.hotkey=0x1234;
789     create_lnk(lnkfile, &desc, 0);
790     desc.path = realpath;
791     check_lnk(lnkfile, &desc, 0x4);
792 
793     r = DeleteFileA(realpath);
794     ok(r, "failed to delete file %s (%d)\n", realpath, GetLastError());
795 
796     /* FIXME: Also test saving a .lnk pointing to a pidl that cannot be
797      * represented as a path.
798      */
799 
800     /* DeleteFileW is not implemented on Win9x */
801     r=DeleteFileA(lnkfileA);
802     ok(r, "failed to delete link '%s' (%d)\n", lnkfileA, GetLastError());
803 }
804 
805 static void test_datalink(void)
806 {
807     static const WCHAR lnk[] = {
808       ':',':','{','9','d','b','1','1','8','6','e','-','4','0','d','f','-','1',
809       '1','d','1','-','a','a','8','c','-','0','0','c','0','4','f','b','6','7',
810       '8','6','3','}',':','2','6',',','!','!','g','x','s','f','(','N','g',']',
811       'q','F','`','H','{','L','s','A','C','C','E','S','S','F','i','l','e','s',
812       '>','p','l','T',']','j','I','{','j','f','(','=','1','&','L','[','-','8',
813       '1','-',']',':',':',0 };
814     static const WCHAR comp[] = {
815       '2','6',',','!','!','g','x','s','f','(','N','g',']','q','F','`','H','{',
816       'L','s','A','C','C','E','S','S','F','i','l','e','s','>','p','l','T',']',
817       'j','I','{','j','f','(','=','1','&','L','[','-','8','1','-',']',0 };
818     IShellLinkDataList *dl = NULL;
819     IShellLinkW *sl = NULL;
820     HRESULT r;
821     DWORD flags = 0;
822     EXP_DARWIN_LINK *dar;
823 
824     r = CoCreateInstance( &CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
825                             &IID_IShellLinkW, (LPVOID*)&sl );
826     ok( r == S_OK ||
827         broken(r == E_NOINTERFACE), /* Win9x */
828         "CoCreateInstance failed (0x%08x)\n", r);
829     if (!sl)
830     {
831         win_skip("no shelllink\n");
832         return;
833     }
834 
835     r = IShellLinkW_QueryInterface( sl, &_IID_IShellLinkDataList, (LPVOID*) &dl );
836     ok( r == S_OK ||
837         broken(r == E_NOINTERFACE), /* NT4 */
838         "IShellLinkW_QueryInterface failed (0x%08x)\n", r);
839 
840     if (!dl)
841     {
842         win_skip("no datalink interface\n");
843         IShellLinkW_Release( sl );
844         return;
845     }
846 
847     flags = 0;
848     r = IShellLinkDataList_GetFlags( dl, &flags );
849     ok( r == S_OK, "GetFlags failed\n");
850     ok( flags == 0, "GetFlags returned wrong flags\n");
851 
852     dar = (void*)-1;
853     r = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
854     ok( r == E_FAIL, "CopyDataBlock failed\n");
855     ok( dar == NULL, "should be null\n");
856 
857     if (!pGetLongPathNameA /* NT4 */)
858         skip("SetPath with NULL parameter crashes on NT4\n");
859     else
860     {
861         r = IShellLinkW_SetPath(sl, NULL);
862         ok(r == E_INVALIDARG, "SetPath returned wrong error (0x%08x)\n", r);
863     }
864 
865     r = IShellLinkW_SetPath(sl, lnk);
866     ok(r == S_OK, "SetPath failed\n");
867 
868 if (0)
869 {
870     /* the following crashes */
871     IShellLinkDataList_GetFlags( dl, NULL );
872 }
873 
874     flags = 0;
875     r = IShellLinkDataList_GetFlags( dl, &flags );
876     ok( r == S_OK, "GetFlags failed\n");
877     /* SLDF_HAS_LOGO3ID is no longer supported on Vista+, filter it out */
878     ok( (flags & (~ SLDF_HAS_LOGO3ID)) == SLDF_HAS_DARWINID,
879         "GetFlags returned wrong flags\n");
880 
881     dar = NULL;
882     r = IShellLinkDataList_CopyDataBlock( dl, EXP_DARWIN_ID_SIG, (LPVOID*) &dar );
883     ok( r == S_OK, "CopyDataBlock failed\n");
884 
885     ok( dar && ((DATABLOCK_HEADER*)dar)->dwSignature == EXP_DARWIN_ID_SIG, "signature wrong\n");
886     ok( dar && 0==lstrcmpW(dar->szwDarwinID, comp ), "signature wrong\n");
887 
888     LocalFree( dar );
889 
890     IShellLinkDataList_Release( dl );
891     IShellLinkW_Release( sl );
892 }
893 
894 static void test_shdefextracticon(void)
895 {
896     HICON hiconlarge=NULL, hiconsmall=NULL;
897     HRESULT res;
898 
899     if (!pSHDefExtractIconA)
900     {
901         win_skip("SHDefExtractIconA is unavailable\n");
902         return;
903     }
904 
905     res = pSHDefExtractIconA("shell32.dll", 0, 0, &hiconlarge, &hiconsmall, MAKELONG(16,24));
906     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
907     ok(hiconlarge != NULL, "got null hiconlarge\n");
908     ok(hiconsmall != NULL, "got null hiconsmall\n");
909     DestroyIcon(hiconlarge);
910     DestroyIcon(hiconsmall);
911 
912     hiconsmall = NULL;
913     res = pSHDefExtractIconA("shell32.dll", 0, 0, NULL, &hiconsmall, MAKELONG(16,24));
914     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
915     ok(hiconsmall != NULL, "got null hiconsmall\n");
916     DestroyIcon(hiconsmall);
917 
918     res = pSHDefExtractIconA("shell32.dll", 0, 0, NULL, NULL, MAKELONG(16,24));
919     ok(SUCCEEDED(res), "SHDefExtractIconA failed, res=%x\n", res);
920 }
921 
922 static void test_GetIconLocation(void)
923 {
924     IShellLinkA *sl;
925     const char *str;
926     char buffer[INFOTIPSIZE], mypath[MAX_PATH];
927     int i;
928     HRESULT r;
929     LPITEMIDLIST pidl;
930 
931     r = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
932             &IID_IShellLinkA, (LPVOID*)&sl);
933     ok(r == S_OK, "no IID_IShellLinkA (0x%08x)\n", r);
934     if(r != S_OK)
935         return;
936 
937     i = 0xdeadbeef;
938     strcpy(buffer, "garbage");
939     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
940     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
941     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
942     ok(i == 0, "GetIconLocation returned %d\n", i);
943 
944     str = "c:\\some\\path";
945     r = IShellLinkA_SetPath(sl, str);
946     ok(r == S_FALSE || r == S_OK, "SetPath failed (0x%08x)\n", r);
947 
948     i = 0xdeadbeef;
949     strcpy(buffer, "garbage");
950     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
951     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
952     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
953     ok(i == 0, "GetIconLocation returned %d\n", i);
954 
955     GetWindowsDirectoryA(mypath, sizeof(mypath) - 12);
956     strcat(mypath, "\\regedit.exe");
957     pidl = path_to_pidl(mypath);
958     r = IShellLinkA_SetIDList(sl, pidl);
959     ok(r == S_OK, "SetPath failed (0x%08x)\n", r);
960     pILFree(pidl);
961 
962     i = 0xdeadbeef;
963     strcpy(buffer, "garbage");
964     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
965     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
966     ok(*buffer == '\0', "GetIconLocation returned '%s'\n", buffer);
967     ok(i == 0, "GetIconLocation returned %d\n", i);
968 
969     str = "c:\\nonexistent\\file";
970     r = IShellLinkA_SetIconLocation(sl, str, 0xbabecafe);
971     ok(r == S_OK, "SetIconLocation failed (0x%08x)\n", r);
972 
973     i = 0xdeadbeef;
974     r = IShellLinkA_GetIconLocation(sl, buffer, sizeof(buffer), &i);
975     ok(r == S_OK, "GetIconLocation failed (0x%08x)\n", r);
976     ok(lstrcmpiA(buffer,str) == 0, "GetIconLocation returned '%s'\n", buffer);
977     ok(i == 0xbabecafe, "GetIconLocation returned %d'\n", i);
978 
979     IShellLinkA_Release(sl);
980 }
981 
982 static void test_SHGetStockIconInfo(void)
983 {
984     BYTE buffer[sizeof(SHSTOCKICONINFO) + 16];
985     SHSTOCKICONINFO *sii = (SHSTOCKICONINFO *) buffer;
986     HRESULT hr;
987     INT i;
988 
989     /* not present before vista */
990     if (!pSHGetStockIconInfo)
991     {
992         win_skip("SHGetStockIconInfo not available\n");
993         return;
994     }
995 
996     /* negative values are handled */
997     memset(buffer, '#', sizeof(buffer));
998     sii->cbSize = sizeof(SHSTOCKICONINFO);
999     hr = pSHGetStockIconInfo(SIID_INVALID, SHGSI_ICONLOCATION, sii);
1000     ok(hr == E_INVALIDARG, "-1: got 0x%x (expected E_INVALIDARG)\n", hr);
1001 
1002     /* max. id for vista is 140 (no definition exists for this value) */
1003     for (i = SIID_DOCNOASSOC; i <= SIID_CLUSTEREDDRIVE; i++)
1004     {
1005         memset(buffer, '#', sizeof(buffer));
1006         sii->cbSize = sizeof(SHSTOCKICONINFO);
1007         hr = pSHGetStockIconInfo(i, SHGSI_ICONLOCATION, sii);
1008 
1009         ok(hr == S_OK,
1010             "%3d: got 0x%x, iSysImageIndex: 0x%x, iIcon: 0x%x (expected S_OK)\n",
1011             i, hr, sii->iSysImageIndex, sii->iIcon);
1012 
1013         if ((hr == S_OK) && (winetest_debug > 1))
1014             trace("%3d: got iSysImageIndex %3d, iIcon %3d and %s\n", i, sii->iSysImageIndex,
1015                   sii->iIcon, wine_dbgstr_w(sii->szPath));
1016     }
1017 
1018     /* test invalid icons indices that are invalid for all platforms */
1019     for (i = SIID_MAX_ICONS; i < (SIID_MAX_ICONS + 25) ; i++)
1020     {
1021         memset(buffer, '#', sizeof(buffer));
1022         sii->cbSize = sizeof(SHSTOCKICONINFO);
1023         hr = pSHGetStockIconInfo(i, SHGSI_ICONLOCATION, sii);
1024         ok(hr == E_INVALIDARG, "%3d: got 0x%x (expected E_INVALIDARG)\n", i, hr);
1025     todo_wine {
1026         ok(sii->iSysImageIndex == -1, "%d: got iSysImageIndex %d\n", i, sii->iSysImageIndex);
1027         ok(sii->iIcon == -1, "%d: got iIcon %d\n", i, sii->iIcon);
1028     }
1029     }
1030 
1031     /* test more returned SHSTOCKICONINFO elements without extra flags */
1032     memset(buffer, '#', sizeof(buffer));
1033     sii->cbSize = sizeof(SHSTOCKICONINFO);
1034     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, sii);
1035     ok(hr == S_OK, "got 0x%x (expected S_OK)\n", hr);
1036     ok(!sii->hIcon, "got %p (expected NULL)\n", sii->hIcon);
1037     ok(sii->iSysImageIndex == -1, "got %d (expected -1)\n", sii->iSysImageIndex);
1038 
1039     /* the exact size is required of the struct */
1040     memset(buffer, '#', sizeof(buffer));
1041     sii->cbSize = sizeof(SHSTOCKICONINFO) + 2;
1042     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, sii);
1043     ok(hr == E_INVALIDARG, "+2: got 0x%x, iSysImageIndex: 0x%x, iIcon: 0x%x\n", hr, sii->iSysImageIndex, sii->iIcon);
1044 
1045     memset(buffer, '#', sizeof(buffer));
1046     sii->cbSize = sizeof(SHSTOCKICONINFO) + 1;
1047     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, sii);
1048     ok(hr == E_INVALIDARG, "+1: got 0x%x, iSysImageIndex: 0x%x, iIcon: 0x%x\n", hr, sii->iSysImageIndex, sii->iIcon);
1049 
1050     memset(buffer, '#', sizeof(buffer));
1051     sii->cbSize = sizeof(SHSTOCKICONINFO) - 1;
1052     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, sii);
1053     ok(hr == E_INVALIDARG, "-1: got 0x%x, iSysImageIndex: 0x%x, iIcon: 0x%x\n", hr, sii->iSysImageIndex, sii->iIcon);
1054 
1055     memset(buffer, '#', sizeof(buffer));
1056     sii->cbSize = sizeof(SHSTOCKICONINFO) - 2;
1057     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, sii);
1058     ok(hr == E_INVALIDARG, "-2: got 0x%x, iSysImageIndex: 0x%x, iIcon: 0x%x\n", hr, sii->iSysImageIndex, sii->iIcon);
1059 
1060     /* there is a NULL check for the struct  */
1061     hr = pSHGetStockIconInfo(SIID_FOLDER, SHGSI_ICONLOCATION, NULL);
1062     ok(hr == E_INVALIDARG, "NULL: got 0x%x\n", hr);
1063 }
1064 
1065 static void test_SHExtractIcons(void)
1066 {
1067     static const WCHAR notepadW[] = {'n','o','t','e','p','a','d','.','e','x','e',0};
1068     static const WCHAR shell32W[] = {'s','h','e','l','l','3','2','.','d','l','l',0};
1069     static const WCHAR emptyW[] = {0};
1070     UINT ret, ret2;
1071     HICON icons[256];
1072     UINT ids[256], i;
1073 
1074     if (!pSHExtractIconsW)
1075     {
1076         win_skip("SHExtractIconsW not available\n");
1077         return;
1078     }
1079 
1080     ret = pSHExtractIconsW(emptyW, 0, 16, 16, icons, ids, 1, 0);
1081     ok(ret == ~0u, "got %u\n", ret);
1082 
1083     ret = pSHExtractIconsW(notepadW, 0, 16, 16, NULL, NULL, 1, 0);
1084     ok(ret == 1 || broken(ret == 2) /* win2k */, "got %u\n", ret);
1085 
1086     icons[0] = (HICON)0xdeadbeef;
1087     ret = pSHExtractIconsW(notepadW, 0, 16, 16, icons, NULL, 1, 0);
1088     ok(ret == 1, "got %u\n", ret);
1089     ok(icons[0] != (HICON)0xdeadbeef, "icon not set\n");
1090     DestroyIcon(icons[0]);
1091 
1092     icons[0] = (HICON)0xdeadbeef;
1093     ids[0] = 0xdeadbeef;
1094     ret = pSHExtractIconsW(notepadW, 0, 16, 16, icons, ids, 1, 0);
1095     ok(ret == 1, "got %u\n", ret);
1096     ok(icons[0] != (HICON)0xdeadbeef, "icon not set\n");
1097     ok(ids[0] != 0xdeadbeef, "id not set\n");
1098     DestroyIcon(icons[0]);
1099 
1100     ret = pSHExtractIconsW(shell32W, 0, 16, 16, NULL, NULL, 0, 0);
1101     ret2 = pSHExtractIconsW(shell32W, 4, MAKELONG(32,16), MAKELONG(32,16), NULL, NULL, 256, 0);
1102     ok(ret && ret == ret2,
1103        "icon count should be independent of requested icon sizes and base icon index\n");
1104 
1105     ret = pSHExtractIconsW(shell32W, 0, 16, 16, icons, ids, 0, 0);
1106     ok(ret == ~0u || !ret /* < vista */, "got %u\n", ret);
1107 
1108     ret = pSHExtractIconsW(shell32W, 0, 16, 16, icons, ids, 3, 0);
1109     ok(ret == 3, "got %u\n", ret);
1110     for (i = 0; i < ret; i++) DestroyIcon(icons[i]);
1111 
1112     /* count must be a multiple of two when getting two sizes */
1113     ret = pSHExtractIconsW(shell32W, 0, MAKELONG(16,32), MAKELONG(16,32), icons, ids, 3, 0);
1114     ok(!ret /* vista */ || ret == 4, "got %u\n", ret);
1115     for (i = 0; i < ret; i++) DestroyIcon(icons[i]);
1116 
1117     ret = pSHExtractIconsW(shell32W, 0, MAKELONG(16,32), MAKELONG(16,32), icons, ids, 4, 0);
1118     ok(ret == 4, "got %u\n", ret);
1119     for (i = 0; i < ret; i++) DestroyIcon(icons[i]);
1120 }
1121 
1122 static void test_propertystore(void)
1123 {
1124     IShellLinkA *linkA;
1125     IShellLinkW *linkW;
1126     IPropertyStore *ps;
1127     HRESULT hr;
1128 
1129     hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
1130                          &IID_IShellLinkA, (void**)&linkA);
1131     ok(hr == S_OK, "got 0x%08x\n", hr);
1132 
1133     hr = IShellLinkA_QueryInterface(linkA, &IID_IShellLinkW, (void**)&linkW);
1134     ok(hr == S_OK, "got 0x%08x\n", hr);
1135 
1136     hr = IShellLinkA_QueryInterface(linkA, &IID_IPropertyStore, (void**)&ps);
1137     if (hr == S_OK) {
1138         IPropertyStoreCache *pscache;
1139 
1140         IPropertyStore_Release(ps);
1141 
1142         hr = IShellLinkW_QueryInterface(linkW, &IID_IPropertyStore, (void**)&ps);
1143         ok(hr == S_OK, "got 0x%08x\n", hr);
1144 
1145         hr = IPropertyStore_QueryInterface(ps, &IID_IPropertyStoreCache, (void**)&pscache);
1146         ok(hr == E_NOINTERFACE, "got 0x%08x\n", hr);
1147 
1148         IPropertyStore_Release(ps);
1149     }
1150     else
1151         win_skip("IShellLink doesn't support IPropertyStore.\n");
1152 
1153     IShellLinkA_Release(linkA);
1154     IShellLinkW_Release(linkW);
1155 }
1156 
1157 static void test_ExtractIcon(void)
1158 {
1159     static const WCHAR nameW[] = {'\\','e','x','t','r','a','c','t','i','c','o','n','_','t','e','s','t','.','t','x','t',0};
1160     static const WCHAR shell32W[] = {'s','h','e','l','l','3','2','.','d','l','l',0};
1161     WCHAR pathW[MAX_PATH];
1162     HICON hicon, hicon2;
1163     char path[MAX_PATH];
1164     HANDLE file;
1165     int r;
1166     ICONINFO info;
1167     BITMAP bm;
1168 
1169     /* specified instance handle */
1170     hicon = ExtractIconA(GetModuleHandleA("shell32.dll"), NULL, 0);
1171 todo_wine
1172     ok(hicon == NULL, "Got icon %p\n", hicon);
1173     hicon2 = ExtractIconA(GetModuleHandleA("shell32.dll"), "shell32.dll", -1);
1174     ok(hicon2 != NULL, "Got icon %p\n", hicon2);
1175 
1176     /* existing index */
1177     hicon = ExtractIconA(NULL, "shell32.dll", 0);
1178     ok(hicon != NULL && HandleToLong(hicon) != -1, "Got icon %p\n", hicon);
1179     DestroyIcon(hicon);
1180 
1181     /* returns number of resources */
1182     hicon = ExtractIconA(NULL, "shell32.dll", -1);
1183     ok(HandleToLong(hicon) > 1 && hicon == hicon2, "Got icon %p\n", hicon);
1184 
1185     /* invalid index, valid dll name */
1186     hicon = ExtractIconA(NULL, "shell32.dll", 3000);
1187     ok(hicon == NULL, "Got icon %p\n", hicon);
1188 
1189     /* Create a temporary non-executable file */
1190     GetTempPathA(sizeof(path), path);
1191     strcat(path, "\\extracticon_test.txt");
1192     file = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1193     ok(file != INVALID_HANDLE_VALUE, "Failed to create a test file\n");
1194     CloseHandle(file);
1195 
1196     hicon = ExtractIconA(NULL, path, 0);
1197 todo_wine
1198     ok(hicon == NULL, "Got icon %p\n", hicon);
1199 
1200     hicon = ExtractIconA(NULL, path, -1);
1201     ok(hicon == NULL, "Got icon %p\n", hicon);
1202 
1203     hicon = ExtractIconA(NULL, path, 1);
1204 todo_wine
1205     ok(hicon == NULL, "Got icon %p\n", hicon);
1206 
1207     r = DeleteFileA(path);
1208     ok(r, "failed to delete file %s (%d)\n", path, GetLastError());
1209 
1210     /* same for W variant */
1211 if (0)
1212 {
1213     /* specified instance handle, crashes on XP, 2k3 */
1214     hicon = ExtractIconW(GetModuleHandleA("shell32.dll"), NULL, 0);
1215     ok(hicon == NULL, "Got icon %p\n", hicon);
1216 }
1217     hicon2 = ExtractIconW(GetModuleHandleA("shell32.dll"), shell32W, -1);
1218     ok(hicon2 != NULL, "Got icon %p\n", hicon2);
1219 
1220     /* existing index */
1221     hicon = ExtractIconW(NULL, shell32W, 0);
1222     ok(hicon != NULL && HandleToLong(hicon) != -1, "Got icon %p\n", hicon);
1223     GetIconInfo(hicon, &info);
1224     GetObjectW(info.hbmColor, sizeof(bm), &bm);
1225     ok(bm.bmWidth == GetSystemMetrics(SM_CXICON), "got %d\n", bm.bmWidth);
1226     ok(bm.bmHeight == GetSystemMetrics(SM_CYICON), "got %d\n", bm.bmHeight);
1227     DestroyIcon(hicon);
1228 
1229     /* returns number of resources */
1230     hicon = ExtractIconW(NULL, shell32W, -1);
1231     ok(HandleToLong(hicon) > 1 && hicon == hicon2, "Got icon %p\n", hicon);
1232 
1233     /* invalid index, valid dll name */
1234     hicon = ExtractIconW(NULL, shell32W, 3000);
1235     ok(hicon == NULL, "Got icon %p\n", hicon);
1236 
1237     /* Create a temporary non-executable file */
1238     GetTempPathW(sizeof(pathW)/sizeof(pathW[0]), pathW);
1239     lstrcatW(pathW, nameW);
1240     file = CreateFileW(pathW, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
1241     ok(file != INVALID_HANDLE_VALUE, "Failed to create a test file\n");
1242     CloseHandle(file);
1243 
1244     hicon = ExtractIconW(NULL, pathW, 0);
1245 todo_wine
1246     ok(hicon == NULL, "Got icon %p\n", hicon);
1247 
1248     hicon = ExtractIconW(NULL, pathW, -1);
1249     ok(hicon == NULL, "Got icon %p\n", hicon);
1250 
1251     hicon = ExtractIconW(NULL, pathW, 1);
1252 todo_wine
1253     ok(hicon == NULL, "Got icon %p\n", hicon);
1254 
1255     r = DeleteFileW(pathW);
1256     ok(r, "failed to delete file %s (%d)\n", path, GetLastError());
1257 }
1258 
1259 static void test_ExtractAssociatedIcon(void)
1260 {
1261     char pathA[MAX_PATH];
1262     HICON hicon;
1263     WORD index;
1264 
1265     /* empty path */
1266     index = 0;
1267     *pathA = 0;
1268     hicon = ExtractAssociatedIconA(NULL, pathA, &index);
1269 todo_wine {
1270     ok(hicon != NULL, "Got icon %p\n", hicon);
1271     ok(!*pathA, "Unexpected path %s\n", pathA);
1272     ok(index == 0, "Unexpected index %u\n", index);
1273 }
1274     DestroyIcon(hicon);
1275 
1276     /* by index */
1277     index = 0;
1278     strcpy(pathA, "shell32.dll");
1279     hicon = ExtractAssociatedIconA(NULL, pathA, &index);
1280     ok(hicon != NULL, "Got icon %p\n", hicon);
1281     ok(!strcmp(pathA, "shell32.dll"), "Unexpected path %s\n", pathA);
1282     ok(index == 0, "Unexpected index %u\n", index);
1283     DestroyIcon(hicon);
1284 
1285     /* valid dll name, invalid index */
1286     index = 5000;
1287     strcpy(pathA, "user32.dll");
1288     hicon = ExtractAssociatedIconA(NULL, pathA, &index);
1289     CharLowerBuffA(pathA, strlen(pathA));
1290 todo_wine {
1291     ok(hicon != NULL, "Got icon %p\n", hicon);
1292     ok(!!strstr(pathA, "shell32.dll"), "Unexpected path %s\n", pathA);
1293 }
1294     ok(index != 5000, "Unexpected index %u\n", index);
1295     DestroyIcon(hicon);
1296 
1297     /* associated icon */
1298     index = 0xcaca;
1299     strcpy(pathA, "dummy.exe");
1300     hicon = ExtractAssociatedIconA(NULL, pathA, &index);
1301     CharLowerBuffA(pathA, strlen(pathA));
1302 todo_wine {
1303     ok(hicon != NULL, "Got icon %p\n", hicon);
1304     ok(!!strstr(pathA, "shell32.dll"), "Unexpected path %s\n", pathA);
1305 }
1306     ok(index != 0xcaca, "Unexpected index %u\n", index);
1307     DestroyIcon(hicon);
1308 }
1309 
1310 static int get_shell_icon_size(void)
1311 {
1312     char buf[10];
1313     DWORD value = 32, size = sizeof(buf), type;
1314     HKEY key;
1315 
1316     if (!RegOpenKeyA( HKEY_CURRENT_USER, "Control Panel\\Desktop\\WindowMetrics", &key ))
1317     {
1318         if (!RegQueryValueExA( key, "Shell Icon Size", NULL, &type, (BYTE *)buf, &size ) && type == REG_SZ)
1319             value = atoi( buf );
1320         RegCloseKey( key );
1321     }
1322     return value;
1323 }
1324 
1325 static void test_SHGetImageList(void)
1326 {
1327     HRESULT hr;
1328     IImageList *list, *list2;
1329     BOOL ret;
1330     HIMAGELIST lg, sm;
1331     ULONG start_refs, refs;
1332     int i, width, height, expect;
1333     BOOL dpi_aware = pIsProcessDPIAware && pIsProcessDPIAware();
1334 
1335     hr = SHGetImageList( SHIL_LARGE, &IID_IImageList, (void **)&list );
1336     ok( hr == S_OK, "got %08x\n", hr );
1337     start_refs = IImageList_AddRef( list );
1338     IImageList_Release( list );
1339 
1340     hr = SHGetImageList( SHIL_LARGE, &IID_IImageList, (void **)&list2 );
1341     ok( hr == S_OK, "got %08x\n", hr );
1342     ok( list == list2, "lists differ\n" );
1343     refs = IImageList_AddRef( list );
1344     IImageList_Release( list );
1345     ok( refs == start_refs + 1, "got %d, start_refs %d\n", refs, start_refs );
1346     IImageList_Release( list2 );
1347 
1348     hr = SHGetImageList( SHIL_SMALL, &IID_IImageList, (void **)&list2 );
1349     ok( hr == S_OK, "got %08x\n", hr );
1350 
1351     ret = Shell_GetImageLists( &lg, &sm );
1352     ok( ret, "got %d\n", ret );
1353     ok( lg == (HIMAGELIST)list, "mismatch\n" );
1354     ok( sm == (HIMAGELIST)list2, "mismatch\n" );
1355 
1356     /* Shell_GetImageLists doesn't take a reference */
1357     refs = IImageList_AddRef( list );
1358     IImageList_Release( list );
1359     ok( refs == start_refs, "got %d, start_refs %d\n", refs, start_refs );
1360 
1361     IImageList_Release( list2 );
1362     IImageList_Release( list );
1363 
1364     /* Test the icon sizes */
1365     for (i = 0; i <= SHIL_LAST; i++)
1366     {
1367         hr = SHGetImageList( i, &IID_IImageList, (void **)&list );
1368         ok( hr == S_OK ||
1369             broken( i == SHIL_JUMBO && hr == E_INVALIDARG ), /* XP and 2003 */
1370             "%d: got %08x\n", i, hr );
1371         if (FAILED(hr)) continue;
1372         IImageList_GetIconSize( list, &width, &height );
1373         switch (i)
1374         {
1375         case SHIL_LARGE:
1376             if (dpi_aware) expect = GetSystemMetrics( SM_CXICON );
1377             else expect = get_shell_icon_size();
1378             break;
1379         case SHIL_SMALL:
1380             if (dpi_aware) expect = GetSystemMetrics( SM_CXICON ) / 2;
1381             else expect = GetSystemMetrics( SM_CXSMICON );
1382             break;
1383         case SHIL_EXTRALARGE:
1384             expect = (GetSystemMetrics( SM_CXICON ) * 3) / 2;
1385             break;
1386         case SHIL_SYSSMALL:
1387             expect = GetSystemMetrics( SM_CXSMICON );
1388             break;
1389         case SHIL_JUMBO:
1390             expect = 256;
1391             break;
1392         }
1393         todo_wine_if(i == SHIL_SYSSMALL && dpi_aware && expect != GetSystemMetrics( SM_CXICON ) / 2)
1394         {
1395             ok( width == expect, "%d: got %d expect %d\n", i, width, expect );
1396             ok( height == expect, "%d: got %d expect %d\n", i, height, expect );
1397         }
1398         IImageList_Release( list );
1399     }
1400 }
1401 
1402 START_TEST(shelllink)
1403 {
1404     HRESULT r;
1405     HMODULE hmod = GetModuleHandleA("shell32.dll");
1406     HMODULE hkernel32 = GetModuleHandleA("kernel32.dll");
1407     HMODULE huser32 = GetModuleHandleA("user32.dll");
1408 
1409     pILFree = (void *)GetProcAddress(hmod, (LPSTR)155);
1410     pILIsEqual = (void *)GetProcAddress(hmod, (LPSTR)21);
1411     pSHILCreateFromPath = (void *)GetProcAddress(hmod, (LPSTR)28);
1412     pSHDefExtractIconA = (void *)GetProcAddress(hmod, "SHDefExtractIconA");
1413     pSHGetStockIconInfo = (void *)GetProcAddress(hmod, "SHGetStockIconInfo");
1414     pGetLongPathNameA = (void *)GetProcAddress(hkernel32, "GetLongPathNameA");
1415     pGetShortPathNameA = (void *)GetProcAddress(hkernel32, "GetShortPathNameA");
1416     pSHExtractIconsW = (void *)GetProcAddress(hmod, "SHExtractIconsW");
1417     pIsProcessDPIAware = (void *)GetProcAddress(huser32, "IsProcessDPIAware");
1418 
1419     r = CoInitialize(NULL);
1420     ok(r == S_OK, "CoInitialize failed (0x%08x)\n", r);
1421     if (r != S_OK)
1422         return;
1423 
1424     test_get_set();
1425     test_load_save();
1426     test_datalink();
1427     test_shdefextracticon();
1428     test_GetIconLocation();
1429     test_SHGetStockIconInfo();
1430     test_SHExtractIcons();
1431     test_propertystore();
1432     test_ExtractIcon();
1433     test_ExtractAssociatedIcon();
1434     test_SHGetImageList();
1435 
1436     CoUninitialize();
1437 }
1438