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