1 /* 2 * 3 * Copyright 1997 Marcus Meissner 4 * Copyright 1998 Juergen Schmied 5 * Copyright 2005 Mike McCormack 6 * Copyright 2009 Andrew Hill 7 * Copyright 2013 Dominik Hornung 8 * Copyright 2017 Hermes Belusca-Maito 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 23 * 24 * NOTES 25 * Nearly complete information about the binary formats 26 * of .lnk files available at http://www.wotsit.org 27 * 28 * You can use winedump to examine the contents of a link file: 29 * winedump lnk sc.lnk 30 * 31 * MSI advertised shortcuts are totally undocumented. They provide an 32 * icon for a program that is not yet installed, and invoke MSI to 33 * install the program when the shortcut is clicked on. They are 34 * created by passing a special string to SetPath, and the information 35 * in that string is parsed an stored. 36 */ 37 /* 38 * In the following is listed more documentation about the Shell Link file format, 39 * as well as its interface. 40 * 41 * General introduction about "Shell Links" (MSDN): 42 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb776891(v=vs.85).aspx 43 * 44 * 45 * Details of the file format: 46 * 47 * - Official MSDN documentation "[MS-SHLLINK]: Shell Link (.LNK) Binary File Format": 48 * https://msdn.microsoft.com/en-us/library/dd871305.aspx 49 * 50 * - Forensics: 51 * http://forensicswiki.org/wiki/LNK 52 * http://computerforensics.parsonage.co.uk/downloads/TheMeaningofLIFE.pdf 53 * https://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf 54 * https://github.com/libyal/liblnk/blob/master/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc 55 * 56 * - List of possible shell link header flags (SHELL_LINK_DATA_FLAGS enumeration): 57 * https://msdn.microsoft.com/en-us/library/windows/desktop/bb762540(v=vs.85).aspx 58 * https://msdn.microsoft.com/en-us/library/dd891314.aspx 59 * 60 * 61 * In addition to storing its target by using a PIDL, a shell link file also 62 * stores metadata to make the shell able to track the link target, in situations 63 * where the link target is moved amongst local or network directories, or moved 64 * to different volumes. For this, two structures are used: 65 * 66 * - The first and oldest one (from NewShell/WinNT4) is the "LinkInfo" structure, 67 * stored in a serialized manner at the beginning of the shell link file: 68 * https://msdn.microsoft.com/en-us/library/dd871404.aspx 69 * The official API for manipulating this is located in LINKINFO.DLL . 70 * 71 * - The second, more recent one, is an extra binary block appended to the 72 * extra-data list of the shell link file: this is the "TrackerDataBlock": 73 * https://msdn.microsoft.com/en-us/library/dd891376.aspx 74 * Its purpose is for link tracking, and works in coordination with the 75 * "Distributed Link Tracking" service ('TrkWks' client, 'TrkSvr' server). 76 * See a detailed explanation at: 77 * http://www.serverwatch.com/tutorials/article.php/1476701/Searching-for-the-Missing-Link-Distributed-Link-Tracking.htm 78 * 79 * 80 * MSI installations most of the time create so-called "advertised shortcuts". 81 * They provide an icon for a program that may not be installed yet, and invoke 82 * MSI to install the program when the shortcut is opened (resolved). 83 * The philosophy of this approach is explained in detail inside the MSDN article 84 * "Application Resiliency: Unlock the Hidden Features of Windows Installer" 85 * (by Michael Sanford), here: 86 * https://msdn.microsoft.com/en-us/library/aa302344.aspx 87 * 88 * This functionality is implemented by adding a binary "Darwin" data block 89 * of type "EXP_DARWIN_LINK", signature EXP_DARWIN_ID_SIG == 0xA0000006, 90 * to the shell link file: 91 * https://msdn.microsoft.com/en-us/library/dd871369.aspx 92 * or, this could be done more simply by specifying a special link target path 93 * with the IShellLink::SetPath() function. Defining the following GUID: 94 * SHELL32_AdvtShortcutComponent = "::{9db1186e-40df-11d1-aa8c-00c04fb67863}:" 95 * setting a target of the form: 96 * "::{SHELL32_AdvtShortcutComponent}:<MSI_App_ID>" 97 * would automatically create the necessary binary block. 98 * 99 * With that, the target of the shortcut now becomes the MSI data. The latter 100 * is parsed from MSI and retrieved by the shell that then can run the program. 101 * 102 * This MSI functionality, dubbed "link blessing", actually originates from an 103 * older technology introduced in Internet Explorer 3 (and now obsolete since 104 * Internet Explorer 7), called "MS Internet Component Download (MSICD)", see 105 * this MSDN introductory article: 106 * https://msdn.microsoft.com/en-us/library/aa741198(v=vs.85).aspx 107 * and leveraged in Internet Explorer 4 with "Software Update Channels", see: 108 * https://msdn.microsoft.com/en-us/library/aa740931(v=vs.85).aspx 109 * Applications supporting this technology could present shell links having 110 * a special target, see subsection "Modifying the Shortcut" in the article: 111 * https://msdn.microsoft.com/en-us/library/aa741201(v=vs.85).aspx#pub_shor 112 * 113 * Similarly as for the MSI shortcuts, these MSICD shortcuts are created by 114 * specifying a special link target path with the IShellLink::SetPath() function, 115 * defining the following GUID: 116 * SHELL32_AdvtShortcutProduct = "::{9db1186f-40df-11d1-aa8c-00c04fb67863}:" 117 * and setting a target of the form: 118 * "::{SHELL32_AdvtShortcutProduct}:<AppName>::<Path>" . 119 * A tool, called "blesslnk.exe", was also provided for automatizing the process; 120 * its ReadMe can be found in the (now outdated) MS "Internet Client SDK" (INetSDK, 121 * for MS Windows 95 and NT), whose contents can be read at: 122 * http://www.msfn.org/board/topic/145352-new-windows-lnk-vulnerability/?page=4#comment-944223 123 * The MS INetSDK can be found at: 124 * https://web.archive.org/web/20100924000013/http://support.microsoft.com/kb/177877 125 * 126 * Internally the shell link target of these MSICD shortcuts is converted into 127 * a binary data block of a type similar to Darwin / "EXP_DARWIN_LINK", but with 128 * a different signature EXP_LOGO3_ID_SIG == 0xA0000007 . Such shell links are 129 * called "Logo3" shortcuts. They were evoked in this user comment in "The Old 130 * New Thing" blog: 131 * https://blogs.msdn.microsoft.com/oldnewthing/20121210-00/?p=5883#comment-1025083 132 * 133 * The shell exports the API 'SoftwareUpdateMessageBox' (in shdocvw.dll) that 134 * displays a message when an update for an application supporting this 135 * technology is available. 136 * 137 */ 138 139 #include "precomp.h" 140 141 #include <appmgmt.h> 142 143 WINE_DEFAULT_DEBUG_CHANNEL(shell); 144 145 #define SHLINK_LOCAL 0 146 #define SHLINK_REMOTE 1 147 148 /* link file formats */ 149 150 #include "pshpack1.h" 151 152 struct LOCATION_INFO 153 { 154 DWORD dwTotalSize; 155 DWORD dwHeaderSize; 156 DWORD dwFlags; 157 DWORD dwVolTableOfs; 158 DWORD dwLocalPathOfs; 159 DWORD dwNetworkVolTableOfs; 160 DWORD dwFinalPathOfs; 161 }; 162 163 struct LOCAL_VOLUME_INFO 164 { 165 DWORD dwSize; 166 DWORD dwType; 167 DWORD dwVolSerial; 168 DWORD dwVolLabelOfs; 169 }; 170 171 struct volume_info 172 { 173 DWORD type; 174 DWORD serial; 175 WCHAR label[12]; /* assume 8.3 */ 176 }; 177 178 #include "poppack.h" 179 180 /* IShellLink Implementation */ 181 182 static HRESULT ShellLink_UpdatePath(LPCWSTR sPathRel, LPCWSTR path, LPCWSTR sWorkDir, LPWSTR* psPath); 183 184 /* strdup on the process heap */ 185 static LPWSTR __inline HEAP_strdupAtoW(HANDLE heap, DWORD flags, LPCSTR str) 186 { 187 INT len; 188 LPWSTR p; 189 190 assert(str); 191 192 len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); 193 p = (LPWSTR)HeapAlloc(heap, flags, len * sizeof(WCHAR)); 194 if (!p) 195 return p; 196 MultiByteToWideChar(CP_ACP, 0, str, -1, p, len); 197 return p; 198 } 199 200 static LPWSTR __inline strdupW(LPCWSTR src) 201 { 202 LPWSTR dest; 203 if (!src) return NULL; 204 dest = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(src) + 1) * sizeof(WCHAR)); 205 if (dest) 206 wcscpy(dest, src); 207 return dest; 208 } 209 210 // TODO: Use it for constructor & destructor too 211 VOID CShellLink::Reset() 212 { 213 ILFree(m_pPidl); 214 m_pPidl = NULL; 215 216 HeapFree(GetProcessHeap(), 0, m_sPath); 217 m_sPath = NULL; 218 ZeroMemory(&volume, sizeof(volume)); 219 220 HeapFree(GetProcessHeap(), 0, m_sDescription); 221 m_sDescription = NULL; 222 HeapFree(GetProcessHeap(), 0, m_sPathRel); 223 m_sPathRel = NULL; 224 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 225 m_sWorkDir = NULL; 226 HeapFree(GetProcessHeap(), 0, m_sArgs); 227 m_sArgs = NULL; 228 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 229 m_sIcoPath = NULL; 230 231 m_bRunAs = FALSE; 232 m_bDirty = FALSE; 233 234 if (m_pDBList) 235 SHFreeDataBlockList(m_pDBList); 236 m_pDBList = NULL; 237 238 /**/sProduct = sComponent = NULL;/**/ 239 } 240 241 CShellLink::CShellLink() 242 { 243 m_Header.dwSize = sizeof(m_Header); 244 m_Header.clsid = CLSID_ShellLink; 245 m_Header.dwFlags = 0; 246 247 m_Header.dwFileAttributes = 0; 248 ZeroMemory(&m_Header.ftCreationTime, sizeof(m_Header.ftCreationTime)); 249 ZeroMemory(&m_Header.ftLastAccessTime, sizeof(m_Header.ftLastAccessTime)); 250 ZeroMemory(&m_Header.ftLastWriteTime, sizeof(m_Header.ftLastWriteTime)); 251 m_Header.nFileSizeLow = 0; 252 253 m_Header.nIconIndex = 0; 254 m_Header.nShowCommand = SW_SHOWNORMAL; 255 m_Header.wHotKey = 0; 256 257 m_pPidl = NULL; 258 259 m_sPath = NULL; 260 ZeroMemory(&volume, sizeof(volume)); 261 262 m_sDescription = NULL; 263 m_sPathRel = NULL; 264 m_sWorkDir = NULL; 265 m_sArgs = NULL; 266 m_sIcoPath = NULL; 267 m_bRunAs = FALSE; 268 m_bDirty = FALSE; 269 m_pDBList = NULL; 270 271 m_sLinkPath = NULL; 272 m_iIdOpen = -1; 273 274 /**/sProduct = sComponent = NULL;/**/ 275 } 276 277 CShellLink::~CShellLink() 278 { 279 TRACE("-- destroying IShellLink(%p)\n", this); 280 281 ILFree(m_pPidl); 282 283 HeapFree(GetProcessHeap(), 0, m_sPath); 284 285 HeapFree(GetProcessHeap(), 0, m_sDescription); 286 HeapFree(GetProcessHeap(), 0, m_sPathRel); 287 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 288 HeapFree(GetProcessHeap(), 0, m_sArgs); 289 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 290 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 291 SHFreeDataBlockList(m_pDBList); 292 } 293 294 HRESULT STDMETHODCALLTYPE CShellLink::GetClassID(CLSID *pclsid) 295 { 296 TRACE("%p %p\n", this, pclsid); 297 298 if (pclsid == NULL) 299 return E_POINTER; 300 *pclsid = CLSID_ShellLink; 301 return S_OK; 302 } 303 304 /************************************************************************ 305 * IPersistStream_IsDirty (IPersistStream) 306 */ 307 HRESULT STDMETHODCALLTYPE CShellLink::IsDirty() 308 { 309 TRACE("(%p)\n", this); 310 return (m_bDirty ? S_OK : S_FALSE); 311 } 312 313 HRESULT STDMETHODCALLTYPE CShellLink::Load(LPCOLESTR pszFileName, DWORD dwMode) 314 { 315 TRACE("(%p, %s, %x)\n", this, debugstr_w(pszFileName), dwMode); 316 317 if (dwMode == 0) 318 dwMode = STGM_READ | STGM_SHARE_DENY_WRITE; 319 320 CComPtr<IStream> stm; 321 HRESULT hr = SHCreateStreamOnFileW(pszFileName, dwMode, &stm); 322 if (SUCCEEDED(hr)) 323 { 324 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 325 m_sLinkPath = strdupW(pszFileName); 326 hr = Load(stm); 327 ShellLink_UpdatePath(m_sPathRel, pszFileName, m_sWorkDir, &m_sPath); 328 m_bDirty = FALSE; 329 } 330 TRACE("-- returning hr %08x\n", hr); 331 return hr; 332 } 333 334 HRESULT STDMETHODCALLTYPE CShellLink::Save(LPCOLESTR pszFileName, BOOL fRemember) 335 { 336 TRACE("(%p)->(%s)\n", this, debugstr_w(pszFileName)); 337 338 if (!pszFileName) 339 return E_FAIL; 340 341 CComPtr<IStream> stm; 342 HRESULT hr = SHCreateStreamOnFileW(pszFileName, STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, &stm); 343 if (SUCCEEDED(hr)) 344 { 345 hr = Save(stm, FALSE); 346 347 if (SUCCEEDED(hr)) 348 { 349 if (m_sLinkPath) 350 HeapFree(GetProcessHeap(), 0, m_sLinkPath); 351 352 m_sLinkPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(pszFileName) + 1) * sizeof(WCHAR)); 353 if (m_sLinkPath) 354 wcscpy(m_sLinkPath, pszFileName); 355 356 m_bDirty = FALSE; 357 } 358 else 359 { 360 DeleteFileW(pszFileName); 361 WARN("Failed to create shortcut %s\n", debugstr_w(pszFileName)); 362 } 363 } 364 365 return hr; 366 } 367 368 HRESULT STDMETHODCALLTYPE CShellLink::SaveCompleted(LPCOLESTR pszFileName) 369 { 370 FIXME("(%p)->(%s)\n", this, debugstr_w(pszFileName)); 371 return S_OK; 372 } 373 374 HRESULT STDMETHODCALLTYPE CShellLink::GetCurFile(LPOLESTR *ppszFileName) 375 { 376 *ppszFileName = NULL; 377 378 if (!m_sLinkPath) 379 { 380 /* IPersistFile::GetCurFile called before IPersistFile::Save */ 381 return S_FALSE; 382 } 383 384 *ppszFileName = (LPOLESTR)CoTaskMemAlloc((wcslen(m_sLinkPath) + 1) * sizeof(WCHAR)); 385 if (!*ppszFileName) 386 { 387 /* out of memory */ 388 return E_OUTOFMEMORY; 389 } 390 391 /* copy last saved filename */ 392 wcscpy(*ppszFileName, m_sLinkPath); 393 394 return S_OK; 395 } 396 397 static HRESULT Stream_LoadString(IStream* stm, BOOL unicode, LPWSTR *pstr) 398 { 399 TRACE("%p\n", stm); 400 401 USHORT len; 402 DWORD count = 0; 403 HRESULT hr = stm->Read(&len, sizeof(len), &count); 404 if (FAILED(hr) || count != sizeof(len)) 405 return E_FAIL; 406 407 if (unicode) 408 len *= sizeof(WCHAR); 409 410 TRACE("reading %d\n", len); 411 LPSTR temp = (LPSTR)HeapAlloc(GetProcessHeap(), 0, len + sizeof(WCHAR)); 412 if (!temp) 413 return E_OUTOFMEMORY; 414 count = 0; 415 hr = stm->Read(temp, len, &count); 416 if (FAILED(hr) || count != len) 417 { 418 HeapFree(GetProcessHeap(), 0, temp); 419 return E_FAIL; 420 } 421 422 TRACE("read %s\n", debugstr_an(temp, len)); 423 424 /* convert to unicode if necessary */ 425 LPWSTR str; 426 if (!unicode) 427 { 428 count = MultiByteToWideChar(CP_ACP, 0, temp, len, NULL, 0); 429 str = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (count + 1) * sizeof(WCHAR)); 430 if (!str) 431 { 432 HeapFree(GetProcessHeap(), 0, temp); 433 return E_OUTOFMEMORY; 434 } 435 MultiByteToWideChar(CP_ACP, 0, temp, len, str, count); 436 HeapFree(GetProcessHeap(), 0, temp); 437 } 438 else 439 { 440 count /= sizeof(WCHAR); 441 str = (LPWSTR)temp; 442 } 443 str[count] = 0; 444 445 *pstr = str; 446 447 return S_OK; 448 } 449 450 451 /* 452 * NOTE: The following 5 functions are part of LINKINFO.DLL 453 */ 454 static BOOL ShellLink_GetVolumeInfo(LPCWSTR path, CShellLink::volume_info *volume) 455 { 456 WCHAR drive[4] = { path[0], ':', '\\', 0 }; 457 458 volume->type = GetDriveTypeW(drive); 459 BOOL bRet = GetVolumeInformationW(drive, volume->label, _countof(volume->label), &volume->serial, NULL, NULL, NULL, 0); 460 TRACE("ret = %d type %d serial %08x name %s\n", bRet, 461 volume->type, volume->serial, debugstr_w(volume->label)); 462 return bRet; 463 } 464 465 static HRESULT Stream_ReadChunk(IStream* stm, LPVOID *data) 466 { 467 struct sized_chunk 468 { 469 DWORD size; 470 unsigned char data[1]; 471 } *chunk; 472 473 TRACE("%p\n", stm); 474 475 DWORD size; 476 ULONG count; 477 HRESULT hr = stm->Read(&size, sizeof(size), &count); 478 if (FAILED(hr) || count != sizeof(size)) 479 return E_FAIL; 480 481 chunk = static_cast<sized_chunk *>(HeapAlloc(GetProcessHeap(), 0, size)); 482 if (!chunk) 483 return E_OUTOFMEMORY; 484 485 chunk->size = size; 486 hr = stm->Read(chunk->data, size - sizeof(size), &count); 487 if (FAILED(hr) || count != (size - sizeof(size))) 488 { 489 HeapFree(GetProcessHeap(), 0, chunk); 490 return E_FAIL; 491 } 492 493 TRACE("Read %d bytes\n", chunk->size); 494 495 *data = chunk; 496 497 return S_OK; 498 } 499 500 static BOOL Stream_LoadVolume(LOCAL_VOLUME_INFO *vol, CShellLink::volume_info *volume) 501 { 502 volume->serial = vol->dwVolSerial; 503 volume->type = vol->dwType; 504 505 if (!vol->dwVolLabelOfs) 506 return FALSE; 507 if (vol->dwSize <= vol->dwVolLabelOfs) 508 return FALSE; 509 INT len = vol->dwSize - vol->dwVolLabelOfs; 510 511 LPSTR label = (LPSTR)vol; 512 label += vol->dwVolLabelOfs; 513 MultiByteToWideChar(CP_ACP, 0, label, len, volume->label, _countof(volume->label)); 514 515 return TRUE; 516 } 517 518 static LPWSTR Stream_LoadPath(LPCSTR p, DWORD maxlen) 519 { 520 UINT len = 0; 521 522 while (p[len] && len < maxlen) 523 len++; 524 525 UINT wlen = MultiByteToWideChar(CP_ACP, 0, p, len, NULL, 0); 526 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wlen + 1) * sizeof(WCHAR)); 527 if (!path) 528 return NULL; 529 MultiByteToWideChar(CP_ACP, 0, p, len, path, wlen); 530 path[wlen] = 0; 531 532 return path; 533 } 534 535 static HRESULT Stream_LoadLocation(IStream *stm, 536 CShellLink::volume_info *volume, LPWSTR *path) 537 { 538 char *p = NULL; 539 HRESULT hr = Stream_ReadChunk(stm, (LPVOID*) &p); 540 if (FAILED(hr)) 541 return hr; 542 543 LOCATION_INFO *loc = reinterpret_cast<LOCATION_INFO *>(p); 544 if (loc->dwTotalSize < sizeof(LOCATION_INFO)) 545 { 546 HeapFree(GetProcessHeap(), 0, p); 547 return E_FAIL; 548 } 549 550 /* if there's valid local volume information, load it */ 551 if (loc->dwVolTableOfs && 552 ((loc->dwVolTableOfs + sizeof(LOCAL_VOLUME_INFO)) <= loc->dwTotalSize)) 553 { 554 LOCAL_VOLUME_INFO *volume_info; 555 556 volume_info = (LOCAL_VOLUME_INFO*) &p[loc->dwVolTableOfs]; 557 Stream_LoadVolume(volume_info, volume); 558 } 559 560 /* if there's a local path, load it */ 561 DWORD n = loc->dwLocalPathOfs; 562 if (n && n < loc->dwTotalSize) 563 *path = Stream_LoadPath(&p[n], loc->dwTotalSize - n); 564 565 TRACE("type %d serial %08x name %s path %s\n", volume->type, 566 volume->serial, debugstr_w(volume->label), debugstr_w(*path)); 567 568 HeapFree(GetProcessHeap(), 0, p); 569 return S_OK; 570 } 571 572 573 /* 574 * The format of the advertised shortcut info is: 575 * 576 * Offset Description 577 * ------ ----------- 578 * 0 Length of the block (4 bytes, usually 0x314) 579 * 4 tag (dword) 580 * 8 string data in ASCII 581 * 8+0x104 string data in UNICODE 582 * 583 * In the original Win32 implementation the buffers are not initialized 584 * to zero, so data trailing the string is random garbage. 585 */ 586 HRESULT CShellLink::GetAdvertiseInfo(LPWSTR *str, DWORD dwSig) 587 { 588 LPEXP_DARWIN_LINK pInfo; 589 590 *str = NULL; 591 592 pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig); 593 if (!pInfo) 594 return E_FAIL; 595 596 /* Make sure that the size of the structure is valid */ 597 if (pInfo->dbh.cbSize != sizeof(*pInfo)) 598 { 599 ERR("Ooops. This structure is not as expected...\n"); 600 return E_FAIL; 601 } 602 603 TRACE("dwSig %08x string = '%s'\n", pInfo->dbh.dwSignature, debugstr_w(pInfo->szwDarwinID)); 604 605 *str = pInfo->szwDarwinID; 606 return S_OK; 607 } 608 609 /************************************************************************ 610 * IPersistStream_Load (IPersistStream) 611 */ 612 HRESULT STDMETHODCALLTYPE CShellLink::Load(IStream *stm) 613 { 614 TRACE("%p %p\n", this, stm); 615 616 if (!stm) 617 return STG_E_INVALIDPOINTER; 618 619 /* Free all the old stuff */ 620 Reset(); 621 622 ULONG dwBytesRead = 0; 623 HRESULT hr = stm->Read(&m_Header, sizeof(m_Header), &dwBytesRead); 624 if (FAILED(hr)) 625 return hr; 626 627 if (dwBytesRead != sizeof(m_Header)) 628 return E_FAIL; 629 if (m_Header.dwSize != sizeof(m_Header)) 630 return E_FAIL; 631 if (!IsEqualIID(m_Header.clsid, CLSID_ShellLink)) 632 return E_FAIL; 633 634 /* Load the new data in order */ 635 636 if (TRACE_ON(shell)) 637 { 638 SYSTEMTIME stCreationTime; 639 SYSTEMTIME stLastAccessTime; 640 SYSTEMTIME stLastWriteTime; 641 WCHAR sTemp[MAX_PATH]; 642 643 FileTimeToSystemTime(&m_Header.ftCreationTime, &stCreationTime); 644 FileTimeToSystemTime(&m_Header.ftLastAccessTime, &stLastAccessTime); 645 FileTimeToSystemTime(&m_Header.ftLastWriteTime, &stLastWriteTime); 646 647 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stCreationTime, 648 NULL, sTemp, _countof(sTemp)); 649 TRACE("-- stCreationTime: %s\n", debugstr_w(sTemp)); 650 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastAccessTime, 651 NULL, sTemp, _countof(sTemp)); 652 TRACE("-- stLastAccessTime: %s\n", debugstr_w(sTemp)); 653 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stLastWriteTime, 654 NULL, sTemp, _countof(sTemp)); 655 TRACE("-- stLastWriteTime: %s\n", debugstr_w(sTemp)); 656 } 657 658 /* load all the new stuff */ 659 if (m_Header.dwFlags & SLDF_HAS_ID_LIST) 660 { 661 hr = ILLoadFromStream(stm, &m_pPidl); 662 if (FAILED(hr)) 663 return hr; 664 } 665 pdump(m_pPidl); 666 667 /* Load the location information... */ 668 if (m_Header.dwFlags & SLDF_HAS_LINK_INFO) 669 { 670 hr = Stream_LoadLocation(stm, &volume, &m_sPath); 671 if (FAILED(hr)) 672 return hr; 673 } 674 /* ... but if it is required not to use it, clear it */ 675 if (m_Header.dwFlags & SLDF_FORCE_NO_LINKINFO) 676 { 677 HeapFree(GetProcessHeap(), 0, m_sPath); 678 m_sPath = NULL; 679 ZeroMemory(&volume, sizeof(volume)); 680 } 681 682 BOOL unicode = !!(m_Header.dwFlags & SLDF_UNICODE); 683 684 if (m_Header.dwFlags & SLDF_HAS_NAME) 685 { 686 hr = Stream_LoadString(stm, unicode, &m_sDescription); 687 if (FAILED(hr)) 688 return hr; 689 TRACE("Description -> %s\n", debugstr_w(m_sDescription)); 690 } 691 692 if (m_Header.dwFlags & SLDF_HAS_RELPATH) 693 { 694 hr = Stream_LoadString(stm, unicode, &m_sPathRel); 695 if (FAILED(hr)) 696 return hr; 697 TRACE("Relative Path-> %s\n", debugstr_w(m_sPathRel)); 698 } 699 700 if (m_Header.dwFlags & SLDF_HAS_WORKINGDIR) 701 { 702 hr = Stream_LoadString(stm, unicode, &m_sWorkDir); 703 if (FAILED(hr)) 704 return hr; 705 PathRemoveBackslash(m_sWorkDir); 706 TRACE("Working Dir -> %s\n", debugstr_w(m_sWorkDir)); 707 } 708 709 if (m_Header.dwFlags & SLDF_HAS_ARGS) 710 { 711 hr = Stream_LoadString(stm, unicode, &m_sArgs); 712 if (FAILED(hr)) 713 return hr; 714 TRACE("Arguments -> %s\n", debugstr_w(m_sArgs)); 715 } 716 717 if (m_Header.dwFlags & SLDF_HAS_ICONLOCATION) 718 { 719 hr = Stream_LoadString(stm, unicode, &m_sIcoPath); 720 if (FAILED(hr)) 721 return hr; 722 TRACE("Icon file -> %s\n", debugstr_w(m_sIcoPath)); 723 } 724 725 /* Now load the optional data block list */ 726 hr = SHReadDataBlockList(stm, &m_pDBList); 727 if (FAILED(hr)) // FIXME: Should we fail? 728 return hr; 729 730 if (TRACE_ON(shell)) 731 { 732 #if (NTDDI_VERSION < NTDDI_LONGHORN) 733 if (m_Header.dwFlags & SLDF_HAS_LOGO3ID) 734 { 735 hr = GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG); 736 if (SUCCEEDED(hr)) 737 TRACE("Product -> %s\n", debugstr_w(sProduct)); 738 } 739 #endif 740 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 741 { 742 hr = GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG); 743 if (SUCCEEDED(hr)) 744 TRACE("Component -> %s\n", debugstr_w(sComponent)); 745 } 746 } 747 748 if (m_Header.dwFlags & SLDF_RUNAS_USER) 749 m_bRunAs = TRUE; 750 else 751 m_bRunAs = FALSE; 752 753 TRACE("OK\n"); 754 755 pdump(m_pPidl); 756 757 return S_OK; 758 } 759 760 /************************************************************************ 761 * Stream_WriteString 762 * 763 * Helper function for IPersistStream_Save. Writes a unicode string 764 * with terminating nul byte to a stream, preceded by the its length. 765 */ 766 static HRESULT Stream_WriteString(IStream* stm, LPCWSTR str) 767 { 768 USHORT len = wcslen(str) + 1; // FIXME: Possible overflows? 769 DWORD count; 770 771 HRESULT hr = stm->Write(&len, sizeof(len), &count); 772 if (FAILED(hr)) 773 return hr; 774 775 len *= sizeof(WCHAR); 776 777 hr = stm->Write(str, len, &count); 778 if (FAILED(hr)) 779 return hr; 780 781 return S_OK; 782 } 783 784 /************************************************************************ 785 * Stream_WriteLocationInfo 786 * 787 * Writes the location info to a stream 788 * 789 * FIXME: One day we might want to write the network volume information 790 * and the final path. 791 * Figure out how Windows deals with unicode paths here. 792 */ 793 static HRESULT Stream_WriteLocationInfo(IStream* stm, LPCWSTR path, 794 CShellLink::volume_info *volume) 795 { 796 LOCAL_VOLUME_INFO *vol; 797 LOCATION_INFO *loc; 798 799 TRACE("%p %s %p\n", stm, debugstr_w(path), volume); 800 801 /* figure out the size of everything */ 802 DWORD label_size = WideCharToMultiByte(CP_ACP, 0, volume->label, -1, 803 NULL, 0, NULL, NULL); 804 DWORD path_size = WideCharToMultiByte(CP_ACP, 0, path, -1, 805 NULL, 0, NULL, NULL); 806 DWORD volume_info_size = sizeof(*vol) + label_size; 807 DWORD final_path_size = 1; 808 DWORD total_size = sizeof(*loc) + volume_info_size + path_size + final_path_size; 809 810 /* create pointers to everything */ 811 loc = static_cast<LOCATION_INFO *>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, total_size)); 812 vol = (LOCAL_VOLUME_INFO*) &loc[1]; 813 LPSTR szLabel = (LPSTR) &vol[1]; 814 LPSTR szPath = &szLabel[label_size]; 815 LPSTR szFinalPath = &szPath[path_size]; 816 817 /* fill in the location information header */ 818 loc->dwTotalSize = total_size; 819 loc->dwHeaderSize = sizeof(*loc); 820 loc->dwFlags = 1; 821 loc->dwVolTableOfs = sizeof(*loc); 822 loc->dwLocalPathOfs = sizeof(*loc) + volume_info_size; 823 loc->dwNetworkVolTableOfs = 0; 824 loc->dwFinalPathOfs = sizeof(*loc) + volume_info_size + path_size; 825 826 /* fill in the volume information */ 827 vol->dwSize = volume_info_size; 828 vol->dwType = volume->type; 829 vol->dwVolSerial = volume->serial; 830 vol->dwVolLabelOfs = sizeof(*vol); 831 832 /* copy in the strings */ 833 WideCharToMultiByte(CP_ACP, 0, volume->label, -1, 834 szLabel, label_size, NULL, NULL); 835 WideCharToMultiByte(CP_ACP, 0, path, -1, 836 szPath, path_size, NULL, NULL); 837 *szFinalPath = 0; 838 839 ULONG count = 0; 840 HRESULT hr = stm->Write(loc, total_size, &count); 841 HeapFree(GetProcessHeap(), 0, loc); 842 843 return hr; 844 } 845 846 /************************************************************************ 847 * IPersistStream_Save (IPersistStream) 848 * 849 * FIXME: makes assumptions about byte order 850 */ 851 HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty) 852 { 853 TRACE("%p %p %x\n", this, stm, fClearDirty); 854 855 m_Header.dwSize = sizeof(m_Header); 856 m_Header.clsid = CLSID_ShellLink; 857 858 /* 859 * Reset the flags: keep only the flags related to data blocks as they were 860 * already set in accordance by the different mutator member functions. 861 * The other flags will be determined now by the presence or absence of data. 862 */ 863 m_Header.dwFlags &= (SLDF_RUN_WITH_SHIMLAYER | SLDF_RUNAS_USER | 864 SLDF_RUN_IN_SEPARATE | SLDF_HAS_DARWINID | 865 #if (NTDDI_VERSION < NTDDI_LONGHORN) 866 SLDF_HAS_LOGO3ID | 867 #endif 868 SLDF_HAS_EXP_ICON_SZ | SLDF_HAS_EXP_SZ); 869 // TODO: When we will support Vista+ functionality, add other flags to this list. 870 871 /* The stored strings are in UNICODE */ 872 m_Header.dwFlags |= SLDF_UNICODE; 873 874 if (m_pPidl) 875 m_Header.dwFlags |= SLDF_HAS_ID_LIST; 876 if (m_sPath) 877 m_Header.dwFlags |= SLDF_HAS_LINK_INFO; 878 if (m_sDescription && *m_sDescription) 879 m_Header.dwFlags |= SLDF_HAS_NAME; 880 if (m_sPathRel && *m_sPathRel) 881 m_Header.dwFlags |= SLDF_HAS_RELPATH; 882 if (m_sWorkDir && *m_sWorkDir) 883 m_Header.dwFlags |= SLDF_HAS_WORKINGDIR; 884 if (m_sArgs && *m_sArgs) 885 m_Header.dwFlags |= SLDF_HAS_ARGS; 886 if (m_sIcoPath && *m_sIcoPath) 887 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 888 if (m_bRunAs) 889 m_Header.dwFlags |= SLDF_RUNAS_USER; 890 891 /* Write the shortcut header */ 892 ULONG count; 893 HRESULT hr = stm->Write(&m_Header, sizeof(m_Header), &count); 894 if (FAILED(hr)) 895 { 896 ERR("Write failed\n"); 897 return hr; 898 } 899 900 /* Save the data in order */ 901 902 if (m_pPidl) 903 { 904 hr = ILSaveToStream(stm, m_pPidl); 905 if (FAILED(hr)) 906 { 907 ERR("Failed to write PIDL\n"); 908 return hr; 909 } 910 } 911 912 if (m_sPath) 913 { 914 hr = Stream_WriteLocationInfo(stm, m_sPath, &volume); 915 if (FAILED(hr)) 916 return hr; 917 } 918 919 if (m_Header.dwFlags & SLDF_HAS_NAME) 920 { 921 hr = Stream_WriteString(stm, m_sDescription); 922 if (FAILED(hr)) 923 return hr; 924 } 925 926 if (m_Header.dwFlags & SLDF_HAS_RELPATH) 927 { 928 hr = Stream_WriteString(stm, m_sPathRel); 929 if (FAILED(hr)) 930 return hr; 931 } 932 933 if (m_Header.dwFlags & SLDF_HAS_WORKINGDIR) 934 { 935 hr = Stream_WriteString(stm, m_sWorkDir); 936 if (FAILED(hr)) 937 return hr; 938 } 939 940 if (m_Header.dwFlags & SLDF_HAS_ARGS) 941 { 942 hr = Stream_WriteString(stm, m_sArgs); 943 if (FAILED(hr)) 944 return hr; 945 } 946 947 if (m_Header.dwFlags & SLDF_HAS_ICONLOCATION) 948 { 949 hr = Stream_WriteString(stm, m_sIcoPath); 950 if (FAILED(hr)) 951 return hr; 952 } 953 954 /* 955 * Now save the data block list. 956 * 957 * NOTE that both advertised Product and Component are already saved 958 * inside Logo3 and Darwin data blocks in the m_pDBList list, and the 959 * m_Header.dwFlags is suitably initialized. 960 */ 961 hr = SHWriteDataBlockList(stm, m_pDBList); 962 if (FAILED(hr)) 963 return hr; 964 965 /* Clear the dirty bit if requested */ 966 if (fClearDirty) 967 m_bDirty = FALSE; 968 969 return hr; 970 } 971 972 /************************************************************************ 973 * IPersistStream_GetSizeMax (IPersistStream) 974 */ 975 HRESULT STDMETHODCALLTYPE CShellLink::GetSizeMax(ULARGE_INTEGER *pcbSize) 976 { 977 TRACE("(%p)\n", this); 978 return E_NOTIMPL; 979 } 980 981 static BOOL SHELL_ExistsFileW(LPCWSTR path) 982 { 983 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(path)) 984 return FALSE; 985 986 return TRUE; 987 } 988 989 /************************************************************************** 990 * ShellLink_UpdatePath 991 * update absolute path in sPath using relative path in sPathRel 992 */ 993 static HRESULT ShellLink_UpdatePath(LPCWSTR sPathRel, LPCWSTR path, LPCWSTR sWorkDir, LPWSTR* psPath) 994 { 995 if (!path || !psPath) 996 return E_INVALIDARG; 997 998 if (!*psPath && sPathRel) 999 { 1000 WCHAR buffer[2*MAX_PATH], abs_path[2*MAX_PATH]; 1001 LPWSTR final = NULL; 1002 1003 /* first try if [directory of link file] + [relative path] finds an existing file */ 1004 1005 GetFullPathNameW(path, MAX_PATH * 2, buffer, &final); 1006 if (!final) 1007 final = buffer; 1008 wcscpy(final, sPathRel); 1009 1010 *abs_path = '\0'; 1011 1012 if (SHELL_ExistsFileW(buffer)) 1013 { 1014 if (!GetFullPathNameW(buffer, MAX_PATH, abs_path, &final)) 1015 wcscpy(abs_path, buffer); 1016 } 1017 else 1018 { 1019 /* try if [working directory] + [relative path] finds an existing file */ 1020 if (sWorkDir) 1021 { 1022 wcscpy(buffer, sWorkDir); 1023 wcscpy(PathAddBackslashW(buffer), sPathRel); 1024 1025 if (SHELL_ExistsFileW(buffer)) 1026 if (!GetFullPathNameW(buffer, MAX_PATH, abs_path, &final)) 1027 wcscpy(abs_path, buffer); 1028 } 1029 } 1030 1031 /* FIXME: This is even not enough - not all shell links can be resolved using this algorithm. */ 1032 if (!*abs_path) 1033 wcscpy(abs_path, sPathRel); 1034 1035 *psPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(abs_path) + 1) * sizeof(WCHAR)); 1036 if (!*psPath) 1037 return E_OUTOFMEMORY; 1038 1039 wcscpy(*psPath, abs_path); 1040 } 1041 1042 return S_OK; 1043 } 1044 1045 HRESULT STDMETHODCALLTYPE CShellLink::GetPath(LPSTR pszFile, INT cchMaxPath, WIN32_FIND_DATAA *pfd, DWORD fFlags) 1046 { 1047 HRESULT hr; 1048 LPWSTR pszFileW; 1049 WIN32_FIND_DATAW wfd; 1050 1051 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n", 1052 this, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(m_sPath)); 1053 1054 /* Allocate a temporary UNICODE buffer */ 1055 pszFileW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchMaxPath * sizeof(WCHAR)); 1056 if (!pszFileW) 1057 return E_OUTOFMEMORY; 1058 1059 /* Call the UNICODE function */ 1060 hr = GetPath(pszFileW, cchMaxPath, &wfd, fFlags); 1061 1062 /* Convert the file path back to ANSI */ 1063 WideCharToMultiByte(CP_ACP, 0, pszFileW, -1, 1064 pszFile, cchMaxPath, NULL, NULL); 1065 1066 /* Free the temporary buffer */ 1067 HeapFree(GetProcessHeap(), 0, pszFileW); 1068 1069 if (pfd) 1070 { 1071 ZeroMemory(pfd, sizeof(*pfd)); 1072 1073 /* Copy the file data if a file path was returned */ 1074 if (*pszFile) 1075 { 1076 DWORD len; 1077 1078 /* Copy the fixed part */ 1079 CopyMemory(pfd, &wfd, FIELD_OFFSET(WIN32_FIND_DATAA, cFileName)); 1080 1081 /* Convert the file names to ANSI */ 1082 len = lstrlenW(wfd.cFileName); 1083 WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, len + 1, 1084 pfd->cFileName, sizeof(pfd->cFileName), NULL, NULL); 1085 len = lstrlenW(wfd.cAlternateFileName); 1086 WideCharToMultiByte(CP_ACP, 0, wfd.cAlternateFileName, len + 1, 1087 pfd->cAlternateFileName, sizeof(pfd->cAlternateFileName), NULL, NULL); 1088 } 1089 } 1090 1091 return hr; 1092 } 1093 1094 HRESULT STDMETHODCALLTYPE CShellLink::GetIDList(LPITEMIDLIST *ppidl) 1095 { 1096 TRACE("(%p)->(ppidl=%p)\n", this, ppidl); 1097 1098 if (!m_pPidl) 1099 { 1100 *ppidl = NULL; 1101 return S_FALSE; 1102 } 1103 1104 *ppidl = ILClone(m_pPidl); 1105 return S_OK; 1106 } 1107 1108 HRESULT STDMETHODCALLTYPE CShellLink::SetIDList(LPCITEMIDLIST pidl) 1109 { 1110 TRACE("(%p)->(pidl=%p)\n", this, pidl); 1111 return SetTargetFromPIDLOrPath(pidl, NULL); 1112 } 1113 1114 HRESULT STDMETHODCALLTYPE CShellLink::GetDescription(LPSTR pszName, INT cchMaxName) 1115 { 1116 TRACE("(%p)->(%p len=%u)\n", this, pszName, cchMaxName); 1117 1118 if (cchMaxName) 1119 *pszName = 0; 1120 1121 if (m_sDescription) 1122 WideCharToMultiByte(CP_ACP, 0, m_sDescription, -1, 1123 pszName, cchMaxName, NULL, NULL); 1124 1125 return S_OK; 1126 } 1127 1128 HRESULT STDMETHODCALLTYPE CShellLink::SetDescription(LPCSTR pszName) 1129 { 1130 TRACE("(%p)->(pName=%s)\n", this, pszName); 1131 1132 HeapFree(GetProcessHeap(), 0, m_sDescription); 1133 m_sDescription = NULL; 1134 1135 if (pszName) 1136 { 1137 m_sDescription = HEAP_strdupAtoW(GetProcessHeap(), 0, pszName); 1138 if (!m_sDescription) 1139 return E_OUTOFMEMORY; 1140 } 1141 m_bDirty = TRUE; 1142 1143 return S_OK; 1144 } 1145 1146 HRESULT STDMETHODCALLTYPE CShellLink::GetWorkingDirectory(LPSTR pszDir, INT cchMaxPath) 1147 { 1148 TRACE("(%p)->(%p len=%u)\n", this, pszDir, cchMaxPath); 1149 1150 if (cchMaxPath) 1151 *pszDir = 0; 1152 1153 if (m_sWorkDir) 1154 WideCharToMultiByte(CP_ACP, 0, m_sWorkDir, -1, 1155 pszDir, cchMaxPath, NULL, NULL); 1156 1157 return S_OK; 1158 } 1159 1160 HRESULT STDMETHODCALLTYPE CShellLink::SetWorkingDirectory(LPCSTR pszDir) 1161 { 1162 TRACE("(%p)->(dir=%s)\n", this, pszDir); 1163 1164 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 1165 m_sWorkDir = NULL; 1166 1167 if (pszDir) 1168 { 1169 m_sWorkDir = HEAP_strdupAtoW(GetProcessHeap(), 0, pszDir); 1170 if (!m_sWorkDir) 1171 return E_OUTOFMEMORY; 1172 } 1173 m_bDirty = TRUE; 1174 1175 return S_OK; 1176 } 1177 1178 HRESULT STDMETHODCALLTYPE CShellLink::GetArguments(LPSTR pszArgs, INT cchMaxPath) 1179 { 1180 TRACE("(%p)->(%p len=%u)\n", this, pszArgs, cchMaxPath); 1181 1182 if (cchMaxPath) 1183 *pszArgs = 0; 1184 1185 if (m_sArgs) 1186 WideCharToMultiByte(CP_ACP, 0, m_sArgs, -1, 1187 pszArgs, cchMaxPath, NULL, NULL); 1188 1189 return S_OK; 1190 } 1191 1192 HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCSTR pszArgs) 1193 { 1194 TRACE("(%p)->(args=%s)\n", this, pszArgs); 1195 1196 HeapFree(GetProcessHeap(), 0, m_sArgs); 1197 m_sArgs = NULL; 1198 1199 if (pszArgs) 1200 { 1201 m_sArgs = HEAP_strdupAtoW(GetProcessHeap(), 0, pszArgs); 1202 if (!m_sArgs) 1203 return E_OUTOFMEMORY; 1204 } 1205 1206 m_bDirty = TRUE; 1207 1208 return S_OK; 1209 } 1210 1211 HRESULT STDMETHODCALLTYPE CShellLink::GetHotkey(WORD *pwHotkey) 1212 { 1213 TRACE("(%p)->(%p)(0x%08x)\n", this, pwHotkey, m_Header.wHotKey); 1214 *pwHotkey = m_Header.wHotKey; 1215 return S_OK; 1216 } 1217 1218 HRESULT STDMETHODCALLTYPE CShellLink::SetHotkey(WORD wHotkey) 1219 { 1220 TRACE("(%p)->(hotkey=%x)\n", this, wHotkey); 1221 1222 m_Header.wHotKey = wHotkey; 1223 m_bDirty = TRUE; 1224 1225 return S_OK; 1226 } 1227 1228 HRESULT STDMETHODCALLTYPE CShellLink::GetShowCmd(INT *piShowCmd) 1229 { 1230 TRACE("(%p)->(%p) %d\n", this, piShowCmd, m_Header.nShowCommand); 1231 *piShowCmd = m_Header.nShowCommand; 1232 return S_OK; 1233 } 1234 1235 HRESULT STDMETHODCALLTYPE CShellLink::SetShowCmd(INT iShowCmd) 1236 { 1237 TRACE("(%p) %d\n", this, iShowCmd); 1238 1239 m_Header.nShowCommand = iShowCmd; 1240 m_bDirty = TRUE; 1241 1242 return S_OK; 1243 } 1244 1245 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(LPSTR pszIconPath, INT cchIconPath, INT *piIcon) 1246 { 1247 HRESULT hr; 1248 LPWSTR pszIconPathW; 1249 1250 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath, cchIconPath, piIcon); 1251 1252 /* Allocate a temporary UNICODE buffer */ 1253 pszIconPathW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchIconPath * sizeof(WCHAR)); 1254 if (!pszIconPathW) 1255 return E_OUTOFMEMORY; 1256 1257 /* Call the UNICODE function */ 1258 hr = GetIconLocation(pszIconPathW, cchIconPath, piIcon); 1259 1260 /* Convert the file path back to ANSI */ 1261 WideCharToMultiByte(CP_ACP, 0, pszIconPathW, -1, 1262 pszIconPath, cchIconPath, NULL, NULL); 1263 1264 /* Free the temporary buffer */ 1265 HeapFree(GetProcessHeap(), 0, pszIconPathW); 1266 1267 return hr; 1268 } 1269 1270 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1271 { 1272 HRESULT hr; 1273 LPWSTR pszIconFileW; 1274 1275 TRACE("(%p)->(%u %p len=%u piIndex=%p pwFlags=%p)\n", this, uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1276 1277 /* Allocate a temporary UNICODE buffer */ 1278 pszIconFileW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, cchMax * sizeof(WCHAR)); 1279 if (!pszIconFileW) 1280 return E_OUTOFMEMORY; 1281 1282 /* Call the UNICODE function */ 1283 hr = GetIconLocation(uFlags, pszIconFileW, cchMax, piIndex, pwFlags); 1284 1285 /* Convert the file path back to ANSI */ 1286 WideCharToMultiByte(CP_ACP, 0, pszIconFileW, -1, 1287 pszIconFile, cchMax, NULL, NULL); 1288 1289 /* Free the temporary buffer */ 1290 HeapFree(GetProcessHeap(), 0, pszIconFileW); 1291 1292 return hr; 1293 } 1294 1295 HRESULT STDMETHODCALLTYPE CShellLink::Extract(PCSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) 1296 { 1297 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszFile, nIconIndex); 1298 1299 LPWSTR str = NULL; 1300 if (pszFile) 1301 { 1302 str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile); 1303 if (!str) 1304 return E_OUTOFMEMORY; 1305 } 1306 1307 HRESULT hr = Extract(str, nIconIndex, phiconLarge, phiconSmall, nIconSize); 1308 1309 if (str) 1310 HeapFree(GetProcessHeap(), 0, str); 1311 1312 return hr; 1313 } 1314 1315 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCSTR pszIconPath, INT iIcon) 1316 { 1317 TRACE("(%p)->(path=%s iicon=%u)\n", this, pszIconPath, iIcon); 1318 1319 LPWSTR str = NULL; 1320 if (pszIconPath) 1321 { 1322 str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszIconPath); 1323 if (!str) 1324 return E_OUTOFMEMORY; 1325 } 1326 1327 HRESULT hr = SetIconLocation(str, iIcon); 1328 1329 if (str) 1330 HeapFree(GetProcessHeap(), 0, str); 1331 1332 return hr; 1333 } 1334 1335 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCSTR pszPathRel, DWORD dwReserved) 1336 { 1337 TRACE("(%p)->(path=%s %x)\n", this, pszPathRel, dwReserved); 1338 1339 HeapFree(GetProcessHeap(), 0, m_sPathRel); 1340 m_sPathRel = NULL; 1341 1342 if (pszPathRel) 1343 { 1344 m_sPathRel = HEAP_strdupAtoW(GetProcessHeap(), 0, pszPathRel); 1345 m_bDirty = TRUE; 1346 } 1347 1348 return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath); 1349 } 1350 1351 static LPWSTR 1352 shelllink_get_msi_component_path(LPWSTR component) 1353 { 1354 DWORD Result, sz = 0; 1355 1356 Result = CommandLineFromMsiDescriptor(component, NULL, &sz); 1357 if (Result != ERROR_SUCCESS) 1358 return NULL; 1359 1360 sz++; 1361 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sz * sizeof(WCHAR)); 1362 Result = CommandLineFromMsiDescriptor(component, path, &sz); 1363 if (Result != ERROR_SUCCESS) 1364 { 1365 HeapFree(GetProcessHeap(), 0, path); 1366 path = NULL; 1367 } 1368 1369 TRACE("returning %s\n", debugstr_w(path)); 1370 1371 return path; 1372 } 1373 1374 HRESULT STDMETHODCALLTYPE CShellLink::Resolve(HWND hwnd, DWORD fFlags) 1375 { 1376 HRESULT hr = S_OK; 1377 BOOL bSuccess; 1378 1379 TRACE("(%p)->(hwnd=%p flags=%x)\n", this, hwnd, fFlags); 1380 1381 /* FIXME: use IResolveShellLink interface? */ 1382 1383 // FIXME: See InvokeCommand(). 1384 1385 #if (NTDDI_VERSION < NTDDI_LONGHORN) 1386 // NOTE: For Logo3 (EXP_LOGO3_ID_SIG), check also for SHRestricted(REST_NOLOGO3CHANNELNOTIFY) 1387 if (m_Header.dwFlags & SLDF_HAS_LOGO3ID) 1388 { 1389 FIXME("Logo3 links are not supported yet!\n"); 1390 return E_FAIL; 1391 } 1392 #endif 1393 1394 /* Resolve Darwin (MSI) target */ 1395 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 1396 { 1397 LPWSTR component = NULL; 1398 hr = GetAdvertiseInfo(&component, EXP_DARWIN_ID_SIG); 1399 if (FAILED(hr)) 1400 return E_FAIL; 1401 1402 /* Clear the cached path */ 1403 HeapFree(GetProcessHeap(), 0, m_sPath); 1404 m_sPath = NULL; 1405 m_sPath = shelllink_get_msi_component_path(component); 1406 if (!m_sPath) 1407 return E_FAIL; 1408 } 1409 1410 if (!m_sPath && m_pPidl) 1411 { 1412 WCHAR buffer[MAX_PATH]; 1413 1414 bSuccess = SHGetPathFromIDListW(m_pPidl, buffer); 1415 if (bSuccess && *buffer) 1416 { 1417 m_sPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(buffer) + 1) * sizeof(WCHAR)); 1418 if (!m_sPath) 1419 return E_OUTOFMEMORY; 1420 1421 wcscpy(m_sPath, buffer); 1422 1423 m_bDirty = TRUE; 1424 } 1425 else 1426 { 1427 hr = S_OK; /* don't report an error occurred while just caching information */ 1428 } 1429 } 1430 1431 // FIXME: Strange to do that here... 1432 if (!m_sIcoPath && m_sPath) 1433 { 1434 m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(m_sPath) + 1) * sizeof(WCHAR)); 1435 if (!m_sIcoPath) 1436 return E_OUTOFMEMORY; 1437 1438 wcscpy(m_sIcoPath, m_sPath); 1439 m_Header.nIconIndex = 0; 1440 1441 m_bDirty = TRUE; 1442 } 1443 1444 return hr; 1445 } 1446 1447 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCSTR pszFile) 1448 { 1449 TRACE("(%p)->(path=%s)\n", this, pszFile); 1450 1451 if (!pszFile) 1452 return E_INVALIDARG; 1453 1454 LPWSTR str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile); 1455 if (!str) 1456 return E_OUTOFMEMORY; 1457 1458 HRESULT hr = SetPath(str); 1459 HeapFree(GetProcessHeap(), 0, str); 1460 1461 return hr; 1462 } 1463 1464 HRESULT STDMETHODCALLTYPE CShellLink::GetPath(LPWSTR pszFile, INT cchMaxPath, WIN32_FIND_DATAW *pfd, DWORD fFlags) 1465 { 1466 WCHAR buffer[MAX_PATH]; 1467 1468 TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%u)(%s)\n", 1469 this, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(m_sPath)); 1470 1471 if (cchMaxPath) 1472 *pszFile = 0; 1473 // FIXME: What if cchMaxPath == 0 , or pszFile == NULL ?? 1474 1475 // FIXME: What about Darwin?? 1476 1477 /* 1478 * Retrieve the path to the target from the PIDL (if we have one). 1479 * NOTE: Do NOT use the cached path (m_sPath from link info). 1480 */ 1481 if (m_pPidl && SHGetPathFromIDListW(m_pPidl, buffer)) 1482 { 1483 if (fFlags & SLGP_SHORTPATH) 1484 GetShortPathNameW(buffer, buffer, _countof(buffer)); 1485 // FIXME: Add support for SLGP_UNCPRIORITY 1486 } 1487 else 1488 { 1489 *buffer = 0; 1490 } 1491 1492 /* If we have a FindData structure, initialize it */ 1493 if (pfd) 1494 { 1495 ZeroMemory(pfd, sizeof(*pfd)); 1496 1497 /* Copy the file data if the target is a file path */ 1498 if (*buffer) 1499 { 1500 pfd->dwFileAttributes = m_Header.dwFileAttributes; 1501 pfd->ftCreationTime = m_Header.ftCreationTime; 1502 pfd->ftLastAccessTime = m_Header.ftLastAccessTime; 1503 pfd->ftLastWriteTime = m_Header.ftLastWriteTime; 1504 pfd->nFileSizeHigh = 0; 1505 pfd->nFileSizeLow = m_Header.nFileSizeLow; 1506 1507 /* 1508 * Build temporarily a short path in pfd->cFileName (of size MAX_PATH), 1509 * then extract and store the short file name in pfd->cAlternateFileName. 1510 */ 1511 GetShortPathNameW(buffer, pfd->cFileName, _countof(pfd->cFileName)); 1512 lstrcpynW(pfd->cAlternateFileName, 1513 PathFindFileNameW(pfd->cFileName), 1514 _countof(pfd->cAlternateFileName)); 1515 1516 /* Now extract and store the long file name in pfd->cFileName */ 1517 lstrcpynW(pfd->cFileName, 1518 PathFindFileNameW(buffer), 1519 _countof(pfd->cFileName)); 1520 } 1521 } 1522 1523 /* Finally check if we have a raw path the user actually wants to retrieve */ 1524 if ((fFlags & SLGP_RAWPATH) && (m_Header.dwFlags & SLDF_HAS_EXP_SZ)) 1525 { 1526 /* Search for a target environment block */ 1527 LPEXP_SZ_LINK pInfo; 1528 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG); 1529 if (pInfo && (pInfo->cbSize == sizeof(*pInfo))) 1530 lstrcpynW(buffer, pInfo->szwTarget, cchMaxPath); 1531 } 1532 1533 /* For diagnostics purposes only... */ 1534 // NOTE: SLGP_UNCPRIORITY is unsupported 1535 fFlags &= ~(SLGP_RAWPATH | SLGP_SHORTPATH); 1536 if (fFlags) FIXME("(%p): Unsupported flags %lu\n", this, fFlags); 1537 1538 /* Copy the data back to the user */ 1539 if (*buffer) 1540 lstrcpynW(pszFile, buffer, cchMaxPath); 1541 1542 return (*buffer ? S_OK : S_FALSE); 1543 } 1544 1545 HRESULT STDMETHODCALLTYPE CShellLink::GetDescription(LPWSTR pszName, INT cchMaxName) 1546 { 1547 TRACE("(%p)->(%p len=%u)\n", this, pszName, cchMaxName); 1548 1549 *pszName = 0; 1550 if (m_sDescription) 1551 lstrcpynW(pszName, m_sDescription, cchMaxName); 1552 1553 return S_OK; 1554 } 1555 1556 HRESULT STDMETHODCALLTYPE CShellLink::SetDescription(LPCWSTR pszName) 1557 { 1558 TRACE("(%p)->(desc=%s)\n", this, debugstr_w(pszName)); 1559 1560 HeapFree(GetProcessHeap(), 0, m_sDescription); 1561 if (pszName) 1562 { 1563 m_sDescription = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1564 (wcslen(pszName) + 1) * sizeof(WCHAR)); 1565 if (!m_sDescription) 1566 return E_OUTOFMEMORY; 1567 1568 wcscpy(m_sDescription, pszName); 1569 } 1570 else 1571 m_sDescription = NULL; 1572 1573 m_bDirty = TRUE; 1574 1575 return S_OK; 1576 } 1577 1578 HRESULT STDMETHODCALLTYPE CShellLink::GetWorkingDirectory(LPWSTR pszDir, INT cchMaxPath) 1579 { 1580 TRACE("(%p)->(%p len %u)\n", this, pszDir, cchMaxPath); 1581 1582 if (cchMaxPath) 1583 *pszDir = 0; 1584 1585 if (m_sWorkDir) 1586 lstrcpynW(pszDir, m_sWorkDir, cchMaxPath); 1587 1588 return S_OK; 1589 } 1590 1591 HRESULT STDMETHODCALLTYPE CShellLink::SetWorkingDirectory(LPCWSTR pszDir) 1592 { 1593 TRACE("(%p)->(dir=%s)\n", this, debugstr_w(pszDir)); 1594 1595 HeapFree(GetProcessHeap(), 0, m_sWorkDir); 1596 if (pszDir) 1597 { 1598 m_sWorkDir = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1599 (wcslen(pszDir) + 1) * sizeof(WCHAR)); 1600 if (!m_sWorkDir) 1601 return E_OUTOFMEMORY; 1602 wcscpy(m_sWorkDir, pszDir); 1603 } 1604 else 1605 m_sWorkDir = NULL; 1606 1607 m_bDirty = TRUE; 1608 1609 return S_OK; 1610 } 1611 1612 HRESULT STDMETHODCALLTYPE CShellLink::GetArguments(LPWSTR pszArgs, INT cchMaxPath) 1613 { 1614 TRACE("(%p)->(%p len=%u)\n", this, pszArgs, cchMaxPath); 1615 1616 if (cchMaxPath) 1617 *pszArgs = 0; 1618 1619 if (m_sArgs) 1620 lstrcpynW(pszArgs, m_sArgs, cchMaxPath); 1621 1622 return S_OK; 1623 } 1624 1625 HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCWSTR pszArgs) 1626 { 1627 TRACE("(%p)->(args=%s)\n", this, debugstr_w(pszArgs)); 1628 1629 HeapFree(GetProcessHeap(), 0, m_sArgs); 1630 if (pszArgs) 1631 { 1632 m_sArgs = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1633 (wcslen(pszArgs) + 1) * sizeof(WCHAR)); 1634 if (!m_sArgs) 1635 return E_OUTOFMEMORY; 1636 1637 wcscpy(m_sArgs, pszArgs); 1638 } 1639 else 1640 m_sArgs = NULL; 1641 1642 m_bDirty = TRUE; 1643 1644 return S_OK; 1645 } 1646 1647 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(LPWSTR pszIconPath, INT cchIconPath, INT *piIcon) 1648 { 1649 TRACE("(%p)->(%p len=%u iicon=%p)\n", this, pszIconPath, cchIconPath, piIcon); 1650 1651 if (cchIconPath) 1652 *pszIconPath = 0; 1653 1654 *piIcon = 0; 1655 1656 /* Update the original icon path location */ 1657 if (m_Header.dwFlags & SLDF_HAS_EXP_ICON_SZ) 1658 { 1659 WCHAR szPath[MAX_PATH]; 1660 1661 /* Search for an icon environment block */ 1662 LPEXP_SZ_LINK pInfo; 1663 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG); 1664 if (pInfo && (pInfo->cbSize == sizeof(*pInfo))) 1665 { 1666 m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION; 1667 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 1668 m_sIcoPath = NULL; 1669 1670 SHExpandEnvironmentStringsW(pInfo->szwTarget, szPath, _countof(szPath)); 1671 1672 m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1673 (wcslen(szPath) + 1) * sizeof(WCHAR)); 1674 if (!m_sIcoPath) 1675 return E_OUTOFMEMORY; 1676 1677 wcscpy(m_sIcoPath, szPath); 1678 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 1679 1680 m_bDirty = TRUE; 1681 } 1682 } 1683 1684 *piIcon = m_Header.nIconIndex; 1685 1686 if (m_sIcoPath) 1687 lstrcpynW(pszIconPath, m_sIcoPath, cchIconPath); 1688 1689 return S_OK; 1690 } 1691 1692 static HRESULT SHELL_PidlGetIconLocationW(PCIDLIST_ABSOLUTE pidl, 1693 UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1694 { 1695 LPCITEMIDLIST pidlLast; 1696 CComPtr<IShellFolder> psf; 1697 1698 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast); 1699 if (FAILED_UNEXPECTEDLY(hr)) 1700 return hr; 1701 1702 CComPtr<IExtractIconW> pei; 1703 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IExtractIconW, &pei)); 1704 if (FAILED_UNEXPECTEDLY(hr)) 1705 return hr; 1706 1707 hr = pei->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1708 if (FAILED_UNEXPECTEDLY(hr)) 1709 return hr; 1710 1711 return S_OK; 1712 } 1713 1714 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags) 1715 { 1716 HRESULT hr; 1717 /* 1718 * It is possible for a shell link to point to another shell link, 1719 * and in particular there is the possibility to point to itself. 1720 * Now, suppose we ask such a link to retrieve its associated icon. 1721 * This function would be called, and due to COM would be called again 1722 * recursively. To solve this issue, we forbid calling GetIconLocation() 1723 * with GIL_FORSHORTCUT set in uFlags, as done by Windows (shown by tests). 1724 */ 1725 if (uFlags & GIL_FORSHORTCUT) 1726 return E_INVALIDARG; 1727 1728 /* 1729 * Now, we set GIL_FORSHORTCUT so that: i) we allow the icon extractor 1730 * of the target to give us a suited icon, and ii) we protect ourselves 1731 * against recursive call. 1732 */ 1733 uFlags |= GIL_FORSHORTCUT; 1734 1735 if (m_pPidl || m_sPath) 1736 { 1737 /* first look for an icon using the PIDL (if present) */ 1738 if (m_pPidl) 1739 hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1740 else 1741 hr = E_FAIL; 1742 1743 #if 0 // FIXME: Analyse further whether this is needed... 1744 /* if we couldn't find an icon yet, look for it using the file system path */ 1745 if (FAILED(hr) && m_sPath) 1746 { 1747 LPITEMIDLIST pidl; 1748 CComPtr<IShellFolder> pdsk; 1749 1750 hr = SHGetDesktopFolder(&pdsk); 1751 1752 /* LPITEMIDLIST pidl = ILCreateFromPathW(sPath); */ 1753 hr = pdsk->ParseDisplayName(0, NULL, m_sPath, NULL, &pidl, NULL); 1754 if (SUCCEEDED(hr)) 1755 { 1756 hr = SHELL_PidlGetIconLocationW(pidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags); 1757 SHFree(pidl); 1758 } 1759 } 1760 #endif 1761 return hr; 1762 } 1763 1764 return S_OK; 1765 } 1766 1767 HRESULT STDMETHODCALLTYPE CShellLink::Extract(PCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) 1768 { 1769 UNIMPLEMENTED; 1770 return E_FAIL; 1771 } 1772 1773 #if 0 1774 /* Extends the functionality of PathUnExpandEnvStringsW */ 1775 BOOL PathFullyUnExpandEnvStringsW( 1776 _In_ LPCWSTR pszPath, 1777 _Out_ LPWSTR pszBuf, 1778 _In_ UINT cchBuf) 1779 { 1780 BOOL Ret = FALSE; // Set to TRUE as soon as PathUnExpandEnvStrings starts unexpanding. 1781 BOOL res; 1782 LPCWSTR p; 1783 1784 // *pszBuf = L'\0'; 1785 while (*pszPath && cchBuf > 0) 1786 { 1787 /* Attempt unexpanding the path */ 1788 res = PathUnExpandEnvStringsW(pszPath, pszBuf, cchBuf); 1789 if (!res) 1790 { 1791 /* The unexpansion failed. Try to find a path delimiter. */ 1792 p = wcspbrk(pszPath, L" /\\:*?\"<>|%"); 1793 if (!p) /* None found, we will copy the remaining path */ 1794 p = pszPath + wcslen(pszPath); 1795 else /* Found one, we will copy the delimiter and skip it */ 1796 ++p; 1797 /* If we overflow, we cannot unexpand more, so return FALSE */ 1798 if (p - pszPath >= cchBuf) 1799 return FALSE; // *pszBuf = L'\0'; 1800 1801 /* Copy the untouched portion of path up to the delimiter, included */ 1802 wcsncpy(pszBuf, pszPath, p - pszPath); 1803 pszBuf[p - pszPath] = L'\0'; // NULL-terminate 1804 1805 /* Advance the pointers and decrease the remaining buffer size */ 1806 cchBuf -= (p - pszPath); 1807 pszBuf += (p - pszPath); 1808 pszPath += (p - pszPath); 1809 } 1810 else 1811 { 1812 /* 1813 * The unexpansion succeeded. Skip the unexpanded part by trying 1814 * to find where the original path and the unexpanded string 1815 * become different. 1816 * NOTE: An alternative(?) would be to stop also at the last 1817 * path delimiter encountered in the loop (i.e. would be the 1818 * first path delimiter in the strings). 1819 */ 1820 LPWSTR q; 1821 1822 /* 1823 * The algorithm starts at the end of the strings and loops back 1824 * while the characters are equal, until it finds a discrepancy. 1825 */ 1826 p = pszPath + wcslen(pszPath); 1827 q = pszBuf + wcslen(pszBuf); // This wcslen should be < cchBuf 1828 while ((*p == *q) && (p > pszPath) && (q > pszBuf)) 1829 { 1830 --p; --q; 1831 } 1832 /* Skip discrepancy */ 1833 ++p; ++q; 1834 1835 /* Advance the pointers and decrease the remaining buffer size */ 1836 cchBuf -= (q - pszBuf); 1837 pszBuf = q; 1838 pszPath = p; 1839 1840 Ret = TRUE; 1841 } 1842 } 1843 1844 return Ret; 1845 } 1846 #endif 1847 1848 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon) 1849 { 1850 HRESULT hr = E_FAIL; 1851 WCHAR szUnExpIconPath[MAX_PATH]; 1852 1853 TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath), iIcon); 1854 1855 if (pszIconPath) 1856 { 1857 /* Try to fully unexpand the icon path */ 1858 1859 /* 1860 * Check whether the user-given file path contains unexpanded 1861 * environment variables. If so, create a target environment block. 1862 * Note that in this block we will store the user-given path. 1863 * It will contain the unexpanded environment variables, but 1864 * it can also contain already expanded path that the user does 1865 * not want to see them unexpanded (e.g. so that they always 1866 * refer to the same place even if the would-be corresponding 1867 * environment variable could change). 1868 */ 1869 // FIXME: http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i 1870 // if (PathFullyUnExpandEnvStringsW(pszIconPath, szUnExpIconPath, _countof(szUnExpIconPath))) 1871 SHExpandEnvironmentStringsW(pszIconPath, szUnExpIconPath, _countof(szUnExpIconPath)); 1872 if (wcscmp(pszIconPath, szUnExpIconPath) != 0) 1873 { 1874 /* Unexpansion succeeded, so we need an icon environment block */ 1875 EXP_SZ_LINK buffer; 1876 LPEXP_SZ_LINK pInfo; 1877 1878 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG); 1879 if (pInfo) 1880 { 1881 /* Make sure that the size of the structure is valid */ 1882 if (pInfo->cbSize != sizeof(*pInfo)) 1883 { 1884 ERR("Ooops. This structure is not as expected...\n"); 1885 1886 /* Invalid structure, remove it altogether */ 1887 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ; 1888 RemoveDataBlock(EXP_SZ_ICON_SIG); 1889 1890 /* Reset the pointer and go use the static buffer */ 1891 pInfo = NULL; 1892 } 1893 } 1894 if (!pInfo) 1895 { 1896 /* Use the static buffer */ 1897 pInfo = &buffer; 1898 buffer.cbSize = sizeof(buffer); 1899 buffer.dwSignature = EXP_SZ_ICON_SIG; 1900 } 1901 1902 lstrcpynW(pInfo->szwTarget, szUnExpIconPath, _countof(pInfo->szwTarget)); 1903 WideCharToMultiByte(CP_ACP, 0, szUnExpIconPath, -1, 1904 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL); 1905 1906 hr = S_OK; 1907 if (pInfo == &buffer) 1908 hr = AddDataBlock(pInfo); 1909 if (hr == S_OK) 1910 m_Header.dwFlags |= SLDF_HAS_EXP_ICON_SZ; 1911 } 1912 else 1913 { 1914 /* Unexpansion failed, so we need to remove any icon environment block */ 1915 m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ; 1916 RemoveDataBlock(EXP_SZ_ICON_SIG); 1917 } 1918 } 1919 1920 /* Store the original icon path location (this one may contain unexpanded environment strings) */ 1921 if (pszIconPath) 1922 { 1923 m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION; 1924 HeapFree(GetProcessHeap(), 0, m_sIcoPath); 1925 m_sIcoPath = NULL; 1926 1927 m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1928 (wcslen(pszIconPath) + 1) * sizeof(WCHAR)); 1929 if (!m_sIcoPath) 1930 return E_OUTOFMEMORY; 1931 1932 wcscpy(m_sIcoPath, pszIconPath); 1933 m_Header.dwFlags |= SLDF_HAS_ICONLOCATION; 1934 } 1935 1936 hr = S_OK; 1937 1938 m_Header.nIconIndex = iIcon; 1939 m_bDirty = TRUE; 1940 1941 return hr; 1942 } 1943 1944 HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwReserved) 1945 { 1946 TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel), dwReserved); 1947 1948 HeapFree(GetProcessHeap(), 0, m_sPathRel); 1949 if (pszPathRel) 1950 { 1951 m_sPathRel = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 1952 (wcslen(pszPathRel) + 1) * sizeof(WCHAR)); 1953 if (!m_sPathRel) 1954 return E_OUTOFMEMORY; 1955 wcscpy(m_sPathRel, pszPathRel); 1956 } 1957 else 1958 m_sPathRel = NULL; 1959 1960 m_bDirty = TRUE; 1961 1962 return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath); 1963 } 1964 1965 static LPWSTR GetAdvertisedArg(LPCWSTR str) 1966 { 1967 if (!str) 1968 return NULL; 1969 1970 LPCWSTR p = wcschr(str, L':'); 1971 if (!p) 1972 return NULL; 1973 1974 DWORD len = p - str; 1975 LPWSTR ret = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (len + 1)); 1976 if (!ret) 1977 return ret; 1978 1979 memcpy(ret, str, sizeof(WCHAR)*len); 1980 ret[len] = 0; 1981 return ret; 1982 } 1983 1984 HRESULT CShellLink::WriteAdvertiseInfo(LPCWSTR string, DWORD dwSig) 1985 { 1986 EXP_DARWIN_LINK buffer; 1987 LPEXP_DARWIN_LINK pInfo; 1988 1989 if ( (dwSig != EXP_DARWIN_ID_SIG) 1990 #if (NTDDI_VERSION < NTDDI_LONGHORN) 1991 && (dwSig != EXP_LOGO3_ID_SIG) 1992 #endif 1993 ) 1994 { 1995 return E_INVALIDARG; 1996 } 1997 1998 if (!string) 1999 return S_FALSE; 2000 2001 pInfo = (LPEXP_DARWIN_LINK)SHFindDataBlock(m_pDBList, dwSig); 2002 if (pInfo) 2003 { 2004 /* Make sure that the size of the structure is valid */ 2005 if (pInfo->dbh.cbSize != sizeof(*pInfo)) 2006 { 2007 ERR("Ooops. This structure is not as expected...\n"); 2008 2009 /* Invalid structure, remove it altogether */ 2010 if (dwSig == EXP_DARWIN_ID_SIG) 2011 m_Header.dwFlags &= ~SLDF_HAS_DARWINID; 2012 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2013 else if (dwSig == EXP_LOGO3_ID_SIG) 2014 m_Header.dwFlags &= ~SLDF_HAS_LOGO3ID; 2015 #endif 2016 RemoveDataBlock(dwSig); 2017 2018 /* Reset the pointer and go use the static buffer */ 2019 pInfo = NULL; 2020 } 2021 } 2022 if (!pInfo) 2023 { 2024 /* Use the static buffer */ 2025 pInfo = &buffer; 2026 buffer.dbh.cbSize = sizeof(buffer); 2027 buffer.dbh.dwSignature = dwSig; 2028 } 2029 2030 lstrcpynW(pInfo->szwDarwinID, string, _countof(pInfo->szwDarwinID)); 2031 WideCharToMultiByte(CP_ACP, 0, string, -1, 2032 pInfo->szDarwinID, _countof(pInfo->szDarwinID), NULL, NULL); 2033 2034 HRESULT hr = S_OK; 2035 if (pInfo == &buffer) 2036 hr = AddDataBlock(pInfo); 2037 if (hr == S_OK) 2038 { 2039 if (dwSig == EXP_DARWIN_ID_SIG) 2040 m_Header.dwFlags |= SLDF_HAS_DARWINID; 2041 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2042 else if (dwSig == EXP_LOGO3_ID_SIG) 2043 m_Header.dwFlags |= SLDF_HAS_LOGO3ID; 2044 #endif 2045 } 2046 2047 return hr; 2048 } 2049 2050 HRESULT CShellLink::SetAdvertiseInfo(LPCWSTR str) 2051 { 2052 HRESULT hr; 2053 LPCWSTR szComponent = NULL, szProduct = NULL, p; 2054 INT len; 2055 GUID guid; 2056 WCHAR szGuid[38+1]; 2057 2058 /**/sProduct = sComponent = NULL;/**/ 2059 2060 while (str[0]) 2061 { 2062 /* each segment must start with two colons */ 2063 if (str[0] != ':' || str[1] != ':') 2064 return E_FAIL; 2065 2066 /* the last segment is just two colons */ 2067 if (!str[2]) 2068 break; 2069 str += 2; 2070 2071 /* there must be a colon straight after a guid */ 2072 p = wcschr(str, L':'); 2073 if (!p) 2074 return E_FAIL; 2075 len = p - str; 2076 if (len != 38) 2077 return E_FAIL; 2078 2079 /* get the guid, and check if it's validly formatted */ 2080 memcpy(szGuid, str, sizeof(WCHAR)*len); 2081 szGuid[len] = 0; 2082 2083 hr = CLSIDFromString(szGuid, &guid); 2084 if (hr != S_OK) 2085 return hr; 2086 str = p + 1; 2087 2088 /* match it up to a guid that we care about */ 2089 if (IsEqualGUID(guid, SHELL32_AdvtShortcutComponent) && !szComponent) 2090 szComponent = str; /* Darwin */ 2091 else if (IsEqualGUID(guid, SHELL32_AdvtShortcutProduct) && !szProduct) 2092 szProduct = str; /* Logo3 */ 2093 else 2094 return E_FAIL; 2095 2096 /* skip to the next field */ 2097 str = wcschr(str, L':'); 2098 if (!str) 2099 return E_FAIL; 2100 } 2101 2102 /* we have to have a component for an advertised shortcut */ 2103 if (!szComponent) 2104 return E_FAIL; 2105 2106 szComponent = GetAdvertisedArg(szComponent); 2107 szProduct = GetAdvertisedArg(szProduct); 2108 2109 hr = WriteAdvertiseInfo(szComponent, EXP_DARWIN_ID_SIG); 2110 // if (FAILED(hr)) 2111 // return hr; 2112 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2113 hr = WriteAdvertiseInfo(szProduct, EXP_LOGO3_ID_SIG); 2114 // if (FAILED(hr)) 2115 // return hr; 2116 #endif 2117 2118 HeapFree(GetProcessHeap(), 0, (PVOID)szComponent); 2119 HeapFree(GetProcessHeap(), 0, (PVOID)szProduct); 2120 2121 if (TRACE_ON(shell)) 2122 { 2123 GetAdvertiseInfo(&sComponent, EXP_DARWIN_ID_SIG); 2124 TRACE("Component = %s\n", debugstr_w(sComponent)); 2125 #if (NTDDI_VERSION < NTDDI_LONGHORN) 2126 GetAdvertiseInfo(&sProduct, EXP_LOGO3_ID_SIG); 2127 TRACE("Product = %s\n", debugstr_w(sProduct)); 2128 #endif 2129 } 2130 2131 return S_OK; 2132 } 2133 2134 /* 2135 * Since the real PathResolve (from Wine) is unimplemented at the moment, 2136 * we use this local implementation, until a better one is written (using 2137 * code parts of the SHELL_xxx helpers in Wine's shellpath.c). 2138 */ 2139 static BOOL HACKISH_PathResolve( 2140 IN OUT PWSTR pszPath, 2141 IN PZPCWSTR dirs OPTIONAL, 2142 IN UINT fFlags) 2143 { 2144 // FIXME: This is unimplemented!!! 2145 #if 0 2146 return PathResolve(pszPath, dirs, fFlags); 2147 #else 2148 BOOL Success = FALSE; 2149 USHORT i; 2150 LPWSTR fname = NULL; 2151 WCHAR szPath[MAX_PATH]; 2152 2153 /* First, search for a valid existing path */ 2154 2155 // NOTE: See also: SHELL_FindExecutable() 2156 2157 /* 2158 * List of extensions searched for, by PathResolve with the flag 2159 * PRF_TRYPROGRAMEXTENSIONS == PRF_EXECUTABLE | PRF_VERIFYEXISTS set, 2160 * according to MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776478(v=vs.85).aspx 2161 */ 2162 static PCWSTR Extensions[] = {L".pif", L".com", L".bat", L".cmd", L".lnk", L".exe", NULL}; 2163 #define LNK_EXT_INDEX 4 // ".lnk" has index 4 in the array above 2164 2165 /* 2166 * Start at the beginning of the list if PRF_EXECUTABLE is set, otherwise 2167 * just use the last element 'NULL' (no extension checking). 2168 */ 2169 i = ((fFlags & PRF_EXECUTABLE) ? 0 : _countof(Extensions) - 1); 2170 for (; i < _countof(Extensions); ++i) 2171 { 2172 /* Ignore shell links ".lnk" if needed */ 2173 if ((fFlags & PRF_DONTFINDLNK) && (i == LNK_EXT_INDEX)) 2174 continue; 2175 2176 Success = (SearchPathW(NULL, pszPath, Extensions[i], 2177 _countof(szPath), szPath, NULL) != 0); 2178 if (!Success) 2179 { 2180 ERR("SearchPathW(pszPath = '%S') failed\n", pszPath); 2181 } 2182 else 2183 { 2184 ERR("SearchPathW(pszPath = '%S', szPath = '%S') succeeded\n", pszPath, szPath); 2185 break; 2186 } 2187 } 2188 2189 if (!Success) 2190 { 2191 ERR("SearchPathW(pszPath = '%S') failed\n", pszPath); 2192 2193 /* We failed, try with PathFindOnPath, as explained by MSDN */ 2194 // Success = PathFindOnPathW(pszPath, dirs); 2195 StringCchCopyW(szPath, _countof(szPath), pszPath); 2196 Success = PathFindOnPathW(szPath, dirs); 2197 if (!Success) 2198 { 2199 ERR("PathFindOnPathW(pszPath = '%S') failed\n", pszPath); 2200 2201 /* We failed again, fall back to building a possible non-existing path */ 2202 if (!GetFullPathNameW(pszPath, _countof(szPath), szPath, &fname)) 2203 { 2204 ERR("GetFullPathNameW(pszPath = '%S') failed\n", pszPath); 2205 return FALSE; 2206 } 2207 2208 Success = PathFileExistsW(szPath); 2209 if (!Success) 2210 ERR("PathFileExistsW(szPath = '%S') failed\n", szPath); 2211 2212 /******************************************************/ 2213 /* Question: Why this line is needed only for files?? */ 2214 if (fname && (_wcsicmp(pszPath, fname) == 0)) 2215 *szPath = L'\0'; 2216 /******************************************************/ 2217 } 2218 else 2219 { 2220 ERR("PathFindOnPathW(pszPath = '%S' ==> '%S') succeeded\n", pszPath, szPath); 2221 } 2222 } 2223 2224 /* Copy back the results to the caller */ 2225 StringCchCopyW(pszPath, MAX_PATH, szPath); 2226 2227 /* 2228 * Since the called functions always checked whether the file path existed, 2229 * we do not need to redo a final check: we can use instead the cached 2230 * result in 'Success'. 2231 */ 2232 return ((fFlags & PRF_VERIFYEXISTS) ? Success : TRUE); 2233 #endif 2234 } 2235 2236 HRESULT CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl, LPCWSTR pszFile) 2237 { 2238 HRESULT hr = S_OK; 2239 LPITEMIDLIST pidlNew = NULL; 2240 WCHAR szPath[MAX_PATH]; 2241 2242 /* 2243 * Not both 'pidl' and 'pszFile' should be set. 2244 * But either one or both can be NULL. 2245 */ 2246 if (pidl && pszFile) 2247 return E_FAIL; 2248 2249 if (pidl) 2250 { 2251 /* Clone the PIDL */ 2252 pidlNew = ILClone(pidl); 2253 if (!pidlNew) 2254 return E_FAIL; 2255 } 2256 else if (pszFile) 2257 { 2258 /* Build a PIDL for this path target */ 2259 hr = SHILCreateFromPathW(pszFile, &pidlNew, NULL); 2260 if (FAILED(hr)) 2261 { 2262 /* This failed, try to resolve the path, then create a simple PIDL */ 2263 2264 StringCchCopyW(szPath, _countof(szPath), pszFile); 2265 // FIXME: Because PathResolve is unimplemented, we use our hackish implementation! 2266 HACKISH_PathResolve(szPath, NULL, PRF_TRYPROGRAMEXTENSIONS); 2267 2268 pidlNew = SHSimpleIDListFromPathW(szPath); 2269 /******************************************************/ 2270 /* Question: Why this line is needed only for files?? */ 2271 hr = (*szPath ? S_OK : E_INVALIDARG); // S_FALSE 2272 /******************************************************/ 2273 } 2274 } 2275 // else if (!pidl && !pszFile) { pidlNew = NULL; hr = S_OK; } 2276 2277 ILFree(m_pPidl); 2278 m_pPidl = pidlNew; 2279 2280 if (!pszFile) 2281 { 2282 if (SHGetPathFromIDListW(pidlNew, szPath)) 2283 pszFile = szPath; 2284 } 2285 2286 // TODO: Fully update link info, tracker, file attribs... 2287 2288 // if (pszFile) 2289 if (!pszFile) 2290 { 2291 *szPath = L'\0'; 2292 pszFile = szPath; 2293 } 2294 2295 /* Update the cached path (for link info) */ 2296 ShellLink_GetVolumeInfo(pszFile, &volume); 2297 m_sPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, 2298 (wcslen(pszFile) + 1) * sizeof(WCHAR)); 2299 if (!m_sPath) 2300 return E_OUTOFMEMORY; 2301 wcscpy(m_sPath, pszFile); 2302 2303 m_bDirty = TRUE; 2304 return hr; 2305 } 2306 2307 HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCWSTR pszFile) 2308 { 2309 LPWSTR unquoted = NULL; 2310 HRESULT hr = S_OK; 2311 2312 TRACE("(%p)->(path=%s)\n", this, debugstr_w(pszFile)); 2313 2314 if (!pszFile) 2315 return E_INVALIDARG; 2316 2317 /* 2318 * Allow upgrading Logo3 shortcuts (m_Header.dwFlags & SLDF_HAS_LOGO3ID), 2319 * but forbid upgrading Darwin ones. 2320 */ 2321 if (m_Header.dwFlags & SLDF_HAS_DARWINID) 2322 return S_FALSE; 2323 2324 /* quotes at the ends of the string are stripped */ 2325 SIZE_T len = wcslen(pszFile); 2326 if (pszFile[0] == L'"' && pszFile[len-1] == L'"') 2327 { 2328 unquoted = strdupW(pszFile); 2329 PathUnquoteSpacesW(unquoted); 2330 pszFile = unquoted; 2331 } 2332 2333 /* any other quote marks are invalid */ 2334 if (wcschr(pszFile, L'"')) 2335 { 2336 hr = S_FALSE; 2337 goto end; 2338 } 2339 2340 /* Clear the cached path */ 2341 HeapFree(GetProcessHeap(), 0, m_sPath); 2342 m_sPath = NULL; 2343 2344 /* Check for an advertised target (Logo3 or Darwin) */ 2345 if (SetAdvertiseInfo(pszFile) != S_OK) 2346 { 2347 /* This is not an advertised target, but a regular path */ 2348 WCHAR szPath[MAX_PATH]; 2349 2350 /* 2351 * Check whether the user-given file path contains unexpanded 2352 * environment variables. If so, create a target environment block. 2353 * Note that in this block we will store the user-given path. 2354 * It will contain the unexpanded environment variables, but 2355 * it can also contain already expanded path that the user does 2356 * not want to see them unexpanded (e.g. so that they always 2357 * refer to the same place even if the would-be corresponding 2358 * environment variable could change). 2359 */ 2360 if (*pszFile) 2361 SHExpandEnvironmentStringsW(pszFile, szPath, _countof(szPath)); 2362 else 2363 *szPath = L'\0'; 2364 2365 if (*pszFile && (wcscmp(pszFile, szPath) != 0)) 2366 { 2367 /* 2368 * The user-given file path contains unexpanded environment 2369 * variables, so we need a target environment block. 2370 */ 2371 EXP_SZ_LINK buffer; 2372 LPEXP_SZ_LINK pInfo; 2373 2374 pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_LINK_SIG); 2375 if (pInfo) 2376 { 2377 /* Make sure that the size of the structure is valid */ 2378 if (pInfo->cbSize != sizeof(*pInfo)) 2379 { 2380 ERR("Ooops. This structure is not as expected...\n"); 2381 2382 /* Invalid structure, remove it altogether */ 2383 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ; 2384 RemoveDataBlock(EXP_SZ_LINK_SIG); 2385 2386 /* Reset the pointer and go use the static buffer */ 2387 pInfo = NULL; 2388 } 2389 } 2390 if (!pInfo) 2391 { 2392 /* Use the static buffer */ 2393 pInfo = &buffer; 2394 buffer.cbSize = sizeof(buffer); 2395 buffer.dwSignature = EXP_SZ_LINK_SIG; 2396 } 2397 2398 lstrcpynW(pInfo->szwTarget, pszFile, _countof(pInfo->szwTarget)); 2399 WideCharToMultiByte(CP_ACP, 0, pszFile, -1, 2400 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL); 2401 2402 hr = S_OK; 2403 if (pInfo == &buffer) 2404 hr = AddDataBlock(pInfo); 2405 if (hr == S_OK) 2406 m_Header.dwFlags |= SLDF_HAS_EXP_SZ; 2407 2408 /* Now, make pszFile point to the expanded buffer */ 2409 pszFile = szPath; 2410 } 2411 else 2412 { 2413 /* 2414 * The user-given file path does not contain unexpanded environment 2415 * variables, so we need to remove any target environment block. 2416 */ 2417 m_Header.dwFlags &= ~SLDF_HAS_EXP_SZ; 2418 RemoveDataBlock(EXP_SZ_LINK_SIG); 2419 2420 /* pszFile points to the user path */ 2421 } 2422 2423 /* Set the target */ 2424 hr = SetTargetFromPIDLOrPath(NULL, pszFile); 2425 } 2426 2427 m_bDirty = TRUE; 2428 2429 end: 2430 HeapFree(GetProcessHeap(), 0, unquoted); 2431 return hr; 2432 } 2433 2434 HRESULT STDMETHODCALLTYPE CShellLink::AddDataBlock(void* pDataBlock) 2435 { 2436 if (SHAddDataBlock(&m_pDBList, (DATABLOCK_HEADER*)pDataBlock)) 2437 { 2438 m_bDirty = TRUE; 2439 return S_OK; 2440 } 2441 return S_FALSE; 2442 } 2443 2444 HRESULT STDMETHODCALLTYPE CShellLink::CopyDataBlock(DWORD dwSig, void** ppDataBlock) 2445 { 2446 DATABLOCK_HEADER* pBlock; 2447 PVOID pDataBlock; 2448 2449 TRACE("%p %08x %p\n", this, dwSig, ppDataBlock); 2450 2451 *ppDataBlock = NULL; 2452 2453 pBlock = SHFindDataBlock(m_pDBList, dwSig); 2454 if (!pBlock) 2455 { 2456 ERR("unknown datablock %08x (not found)\n", dwSig); 2457 return E_FAIL; 2458 } 2459 2460 pDataBlock = LocalAlloc(LMEM_ZEROINIT, pBlock->cbSize); 2461 if (!pDataBlock) 2462 return E_OUTOFMEMORY; 2463 2464 CopyMemory(pDataBlock, pBlock, pBlock->cbSize); 2465 2466 *ppDataBlock = pDataBlock; 2467 return S_OK; 2468 } 2469 2470 HRESULT STDMETHODCALLTYPE CShellLink::RemoveDataBlock(DWORD dwSig) 2471 { 2472 if (SHRemoveDataBlock(&m_pDBList, dwSig)) 2473 { 2474 m_bDirty = TRUE; 2475 return S_OK; 2476 } 2477 return S_FALSE; 2478 } 2479 2480 HRESULT STDMETHODCALLTYPE CShellLink::GetFlags(DWORD *pdwFlags) 2481 { 2482 TRACE("%p %p\n", this, pdwFlags); 2483 *pdwFlags = m_Header.dwFlags; 2484 return S_OK; 2485 } 2486 2487 HRESULT STDMETHODCALLTYPE CShellLink::SetFlags(DWORD dwFlags) 2488 { 2489 #if 0 // FIXME! 2490 m_Header.dwFlags = dwFlags; 2491 m_bDirty = TRUE; 2492 return S_OK; 2493 #else 2494 FIXME("\n"); 2495 return E_NOTIMPL; 2496 #endif 2497 } 2498 2499 /************************************************************************** 2500 * CShellLink implementation of IShellExtInit::Initialize() 2501 * 2502 * Loads the shelllink from the dataobject the shell is pointing to. 2503 */ 2504 HRESULT STDMETHODCALLTYPE CShellLink::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) 2505 { 2506 TRACE("%p %p %p %p\n", this, pidlFolder, pdtobj, hkeyProgID); 2507 2508 if (!pdtobj) 2509 return E_FAIL; 2510 2511 FORMATETC format; 2512 format.cfFormat = CF_HDROP; 2513 format.ptd = NULL; 2514 format.dwAspect = DVASPECT_CONTENT; 2515 format.lindex = -1; 2516 format.tymed = TYMED_HGLOBAL; 2517 2518 STGMEDIUM stgm; 2519 HRESULT hr = pdtobj->GetData(&format, &stgm); 2520 if (FAILED(hr)) 2521 return hr; 2522 2523 UINT count = DragQueryFileW((HDROP)stgm.hGlobal, -1, NULL, 0); 2524 if (count == 1) 2525 { 2526 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, NULL, 0); 2527 count++; 2528 LPWSTR path = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR)); 2529 if (path) 2530 { 2531 count = DragQueryFileW((HDROP)stgm.hGlobal, 0, path, count); 2532 hr = Load(path, 0); 2533 HeapFree(GetProcessHeap(), 0, path); 2534 } 2535 } 2536 ReleaseStgMedium(&stgm); 2537 2538 return S_OK; 2539 } 2540 2541 HRESULT STDMETHODCALLTYPE CShellLink::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) 2542 { 2543 int id = 1; 2544 2545 TRACE("%p %p %u %u %u %u\n", this, 2546 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 2547 2548 if (!hMenu) 2549 return E_INVALIDARG; 2550 2551 WCHAR wszOpen[20]; 2552 if (!LoadStringW(shell32_hInstance, IDS_OPEN_VERB, wszOpen, _countof(wszOpen))) 2553 *wszOpen = L'\0'; 2554 2555 MENUITEMINFOW mii; 2556 ZeroMemory(&mii, sizeof(mii)); 2557 mii.cbSize = sizeof(mii); 2558 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE; 2559 mii.dwTypeData = wszOpen; 2560 mii.cch = wcslen(mii.dwTypeData); 2561 mii.wID = idCmdFirst + id++; 2562 mii.fState = MFS_DEFAULT | MFS_ENABLED; 2563 mii.fType = MFT_STRING; 2564 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 2565 return E_FAIL; 2566 m_iIdOpen = 1; 2567 2568 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, id); 2569 } 2570 2571 HRESULT STDMETHODCALLTYPE CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 2572 { 2573 LPWSTR args = NULL; 2574 LPWSTR path = NULL; 2575 2576 TRACE("%p %p\n", this, lpici); 2577 2578 if (lpici->cbSize < sizeof(CMINVOKECOMMANDINFO)) 2579 return E_INVALIDARG; 2580 2581 // NOTE: We could use lpici->hwnd (certainly in case lpici->fMask doesn't contain CMIC_MASK_FLAG_NO_UI) 2582 // as the parent window handle... ? 2583 /* FIXME: get using interface set from IObjectWithSite?? */ 2584 // NOTE: We might need an extended version of Resolve that provides us with paths... 2585 HRESULT hr = Resolve(lpici->hwnd, 0); 2586 if (FAILED(hr)) 2587 { 2588 TRACE("failed to resolve component with error 0x%08x", hr); 2589 return hr; 2590 } 2591 2592 path = strdupW(m_sPath); 2593 2594 if ( lpici->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && 2595 (lpici->fMask & CMIC_MASK_UNICODE) ) 2596 { 2597 LPCMINVOKECOMMANDINFOEX iciex = (LPCMINVOKECOMMANDINFOEX)lpici; 2598 SIZE_T len = 2; 2599 2600 if (m_sArgs) 2601 len += wcslen(m_sArgs); 2602 if (iciex->lpParametersW) 2603 len += wcslen(iciex->lpParametersW); 2604 2605 args = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); 2606 *args = 0; 2607 if (m_sArgs) 2608 wcscat(args, m_sArgs); 2609 if (iciex->lpParametersW) 2610 { 2611 wcscat(args, L" "); 2612 wcscat(args, iciex->lpParametersW); 2613 } 2614 } 2615 else if (m_sArgs != NULL) 2616 { 2617 args = strdupW(m_sArgs); 2618 } 2619 2620 SHELLEXECUTEINFOW sei; 2621 ZeroMemory(&sei, sizeof(sei)); 2622 sei.cbSize = sizeof(sei); 2623 sei.fMask = SEE_MASK_HASLINKNAME | SEE_MASK_UNICODE | 2624 (lpici->fMask & (SEE_MASK_NOASYNC | SEE_MASK_ASYNCOK | SEE_MASK_FLAG_NO_UI)); 2625 sei.lpFile = path; 2626 sei.lpClass = m_sLinkPath; 2627 sei.nShow = m_Header.nShowCommand; 2628 sei.lpDirectory = m_sWorkDir; 2629 sei.lpParameters = args; 2630 sei.lpVerb = L"open"; 2631 2632 // HACK for ShellExecuteExW 2633 if (m_sPath && wcsstr(m_sPath, L".cpl")) 2634 sei.lpVerb = L"cplopen"; 2635 2636 if (ShellExecuteExW(&sei)) 2637 hr = S_OK; 2638 else 2639 hr = E_FAIL; 2640 2641 HeapFree(GetProcessHeap(), 0, args); 2642 HeapFree(GetProcessHeap(), 0, path); 2643 2644 return hr; 2645 } 2646 2647 HRESULT STDMETHODCALLTYPE CShellLink::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax) 2648 { 2649 FIXME("%p %lu %u %p %p %u\n", this, idCmd, uType, pwReserved, pszName, cchMax); 2650 return E_NOTIMPL; 2651 } 2652 2653 INT_PTR CALLBACK ExtendedShortcutProc(HWND hwndDlg, UINT uMsg, 2654 WPARAM wParam, LPARAM lParam) 2655 { 2656 switch(uMsg) 2657 { 2658 case WM_INITDIALOG: 2659 if (lParam) 2660 { 2661 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000); 2662 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0); 2663 } 2664 return TRUE; 2665 case WM_COMMAND: 2666 { 2667 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000); 2668 if (LOWORD(wParam) == IDOK) 2669 { 2670 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED) 2671 EndDialog(hwndDlg, 1); 2672 else 2673 EndDialog(hwndDlg, 0); 2674 } 2675 else if (LOWORD(wParam) == IDCANCEL) 2676 { 2677 EndDialog(hwndDlg, -1); 2678 } 2679 else if (LOWORD(wParam) == 14000) 2680 { 2681 if (SendMessage(hDlgCtrl, BM_GETCHECK, 0, 0) == BST_CHECKED) 2682 SendMessage(hDlgCtrl, BM_SETCHECK, BST_UNCHECKED, 0); 2683 else 2684 SendMessage(hDlgCtrl, BM_SETCHECK, BST_CHECKED, 0); 2685 } 2686 } 2687 } 2688 return FALSE; 2689 } 2690 2691 EXTERN_C HRESULT 2692 WINAPI 2693 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder, 2694 UINT cidl, 2695 PCUITEMID_CHILD_ARRAY apidl, 2696 DWORD dwFlags); 2697 2698 /************************************************************************** 2699 * SH_GetTargetTypeByPath 2700 * 2701 * Function to get target type by passing full path to it 2702 */ 2703 LPWSTR SH_GetTargetTypeByPath(LPCWSTR lpcwFullPath) 2704 { 2705 LPCWSTR pwszExt; 2706 static WCHAR wszBuf[MAX_PATH]; 2707 2708 /* Get file information */ 2709 SHFILEINFOW fi; 2710 if (!SHGetFileInfoW(lpcwFullPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES)) 2711 { 2712 ERR("SHGetFileInfoW failed for %ls (%lu)\n", lpcwFullPath, GetLastError()); 2713 fi.szTypeName[0] = L'\0'; 2714 fi.hIcon = NULL; 2715 } 2716 2717 pwszExt = PathFindExtensionW(lpcwFullPath); 2718 if (pwszExt[0]) 2719 { 2720 if (!fi.szTypeName[0]) 2721 { 2722 /* The file type is unknown, so default to string "FileExtension File" */ 2723 size_t cchRemaining = 0; 2724 LPWSTR pwszEnd = NULL; 2725 2726 StringCchPrintfExW(wszBuf, _countof(wszBuf), &pwszEnd, &cchRemaining, 0, L"%s ", pwszExt + 1); 2727 } 2728 else 2729 { 2730 /* Update file type */ 2731 StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s (%s)", fi.szTypeName, pwszExt); 2732 } 2733 } 2734 2735 return wszBuf; 2736 } 2737 2738 /************************************************************************** 2739 * SH_ShellLinkDlgProc 2740 * 2741 * dialog proc of the shortcut property dialog 2742 */ 2743 2744 INT_PTR CALLBACK CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) 2745 { 2746 CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER)); 2747 2748 switch(uMsg) 2749 { 2750 case WM_INITDIALOG: 2751 { 2752 LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam; 2753 if (ppsp == NULL) 2754 break; 2755 2756 TRACE("ShellLink_DlgProc (WM_INITDIALOG hwnd %p lParam %p ppsplParam %x)\n", hwndDlg, lParam, ppsp->lParam); 2757 2758 pThis = reinterpret_cast<CShellLink *>(ppsp->lParam); 2759 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis); 2760 2761 TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", pThis->m_sArgs, pThis->sComponent, pThis->m_sDescription, 2762 pThis->m_sIcoPath, pThis->m_sPath, pThis->m_sPathRel, pThis->sProduct, pThis->m_sWorkDir); 2763 2764 /* Get file information */ 2765 // FIXME! FIXME! Shouldn't we use pThis->m_sIcoPath, pThis->m_Header.nIconIndex instead??? 2766 SHFILEINFOW fi; 2767 if (!SHGetFileInfoW(pThis->m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON)) 2768 { 2769 ERR("SHGetFileInfoW failed for %ls (%lu)\n", pThis->m_sLinkPath, GetLastError()); 2770 fi.szTypeName[0] = L'\0'; 2771 fi.hIcon = NULL; 2772 } 2773 2774 if (fi.hIcon) // TODO: destroy icon 2775 SendDlgItemMessageW(hwndDlg, 14000, STM_SETICON, (WPARAM)fi.hIcon, 0); 2776 else 2777 ERR("ExtractIconW failed %ls %u\n", pThis->m_sIcoPath, pThis->m_Header.nIconIndex); 2778 2779 /* Target type */ 2780 if (pThis->m_sPath) 2781 SetDlgItemTextW(hwndDlg, 14005, SH_GetTargetTypeByPath(pThis->m_sPath)); 2782 2783 /* Target location */ 2784 if (pThis->m_sPath) 2785 { 2786 WCHAR target[MAX_PATH]; 2787 StringCchCopyW(target, _countof(target), pThis->m_sPath); 2788 PathRemoveFileSpecW(target); 2789 SetDlgItemTextW(hwndDlg, 14007, PathFindFileNameW(target)); 2790 } 2791 2792 /* Target path */ 2793 if (pThis->m_sPath) 2794 { 2795 WCHAR newpath[2*MAX_PATH] = L"\0"; 2796 if (wcschr(pThis->m_sPath, ' ')) 2797 StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", pThis->m_sPath); 2798 else 2799 StringCchCopyExW(newpath, _countof(newpath), pThis->m_sPath, NULL, NULL, 0); 2800 2801 if (pThis->m_sArgs && pThis->m_sArgs[0]) 2802 { 2803 StringCchCatW(newpath, _countof(newpath), L" "); 2804 StringCchCatW(newpath, _countof(newpath), pThis->m_sArgs); 2805 } 2806 SetDlgItemTextW(hwndDlg, 14009, newpath); 2807 } 2808 2809 /* Working dir */ 2810 if (pThis->m_sWorkDir) 2811 SetDlgItemTextW(hwndDlg, 14011, pThis->m_sWorkDir); 2812 2813 /* Description */ 2814 if (pThis->m_sDescription) 2815 SetDlgItemTextW(hwndDlg, 14019, pThis->m_sDescription); 2816 2817 return TRUE; 2818 } 2819 2820 case WM_NOTIFY: 2821 { 2822 LPPSHNOTIFY lppsn = (LPPSHNOTIFY)lParam; 2823 if (lppsn->hdr.code == PSN_APPLY) 2824 { 2825 WCHAR wszBuf[MAX_PATH]; 2826 /* set working directory */ 2827 GetDlgItemTextW(hwndDlg, 14011, wszBuf, _countof(wszBuf)); 2828 pThis->SetWorkingDirectory(wszBuf); 2829 /* set link destination */ 2830 GetDlgItemTextW(hwndDlg, 14009, wszBuf, _countof(wszBuf)); 2831 LPWSTR lpszArgs = NULL; 2832 LPWSTR unquoted = strdupW(wszBuf); 2833 StrTrimW(unquoted, L" "); 2834 if (!PathFileExistsW(unquoted)) 2835 { 2836 lpszArgs = PathGetArgsW(unquoted); 2837 PathRemoveArgsW(unquoted); 2838 StrTrimW(lpszArgs, L" "); 2839 } 2840 if (unquoted[0] == '"' && unquoted[wcslen(unquoted)-1] == '"') 2841 PathUnquoteSpacesW(unquoted); 2842 2843 2844 WCHAR *pwszExt = PathFindExtensionW(unquoted); 2845 if (!wcsicmp(pwszExt, L".lnk")) 2846 { 2847 // FIXME load localized error msg 2848 MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR); 2849 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); 2850 return TRUE; 2851 } 2852 2853 if (!PathFileExistsW(unquoted)) 2854 { 2855 // FIXME load localized error msg 2856 MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR); 2857 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); 2858 return TRUE; 2859 } 2860 2861 pThis->SetPath(unquoted); 2862 if (lpszArgs) 2863 pThis->SetArguments(lpszArgs); 2864 else 2865 pThis->SetArguments(L"\0"); 2866 2867 HeapFree(GetProcessHeap(), 0, unquoted); 2868 2869 TRACE("This %p m_sLinkPath %S\n", pThis, pThis->m_sLinkPath); 2870 pThis->Save(pThis->m_sLinkPath, TRUE); 2871 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); 2872 return TRUE; 2873 } 2874 break; 2875 } 2876 2877 case WM_COMMAND: 2878 switch(LOWORD(wParam)) 2879 { 2880 case 14020: 2881 SHOpenFolderAndSelectItems(pThis->m_pPidl, 0, NULL, 0); 2882 /// 2883 /// FIXME 2884 /// open target directory 2885 /// 2886 return TRUE; 2887 2888 case 14021: 2889 { 2890 WCHAR wszPath[MAX_PATH] = L""; 2891 2892 if (pThis->m_sIcoPath) 2893 wcscpy(wszPath, pThis->m_sIcoPath); 2894 INT IconIndex = pThis->m_Header.nIconIndex; 2895 if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex)) 2896 { 2897 pThis->SetIconLocation(wszPath, IconIndex); 2898 /// 2899 /// FIXME redraw icon 2900 /// 2901 } 2902 return TRUE; 2903 } 2904 2905 case 14022: 2906 { 2907 INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)pThis->m_bRunAs); 2908 if (result == 1 || result == 0) 2909 { 2910 if (pThis->m_bRunAs != result) 2911 { 2912 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 2913 } 2914 2915 pThis->m_bRunAs = result; 2916 } 2917 return TRUE; 2918 } 2919 } 2920 if (HIWORD(wParam) == EN_CHANGE) 2921 PropSheet_Changed(GetParent(hwndDlg), hwndDlg); 2922 break; 2923 2924 default: 2925 break; 2926 } 2927 return FALSE; 2928 } 2929 2930 /************************************************************************** 2931 * ShellLink_IShellPropSheetExt interface 2932 */ 2933 2934 HRESULT STDMETHODCALLTYPE CShellLink::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) 2935 { 2936 HPROPSHEETPAGE hPage = SH_CreatePropertySheetPage(IDD_SHORTCUT_PROPERTIES, SH_ShellLinkDlgProc, (LPARAM)this, NULL); 2937 if (hPage == NULL) 2938 { 2939 ERR("failed to create property sheet page\n"); 2940 return E_FAIL; 2941 } 2942 2943 if (!pfnAddPage(hPage, lParam)) 2944 return E_FAIL; 2945 2946 return S_OK; 2947 } 2948 2949 HRESULT STDMETHODCALLTYPE CShellLink::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) 2950 { 2951 TRACE("(%p) (uPageID %u, pfnReplacePage %p lParam %p\n", this, uPageID, pfnReplacePage, lParam); 2952 return E_NOTIMPL; 2953 } 2954 2955 HRESULT STDMETHODCALLTYPE CShellLink::SetSite(IUnknown *punk) 2956 { 2957 TRACE("%p %p\n", this, punk); 2958 2959 m_site = punk; 2960 2961 return S_OK; 2962 } 2963 2964 HRESULT STDMETHODCALLTYPE CShellLink::GetSite(REFIID iid, void ** ppvSite) 2965 { 2966 TRACE("%p %s %p\n", this, debugstr_guid(&iid), ppvSite); 2967 2968 if (m_site == NULL) 2969 return E_FAIL; 2970 2971 return m_site->QueryInterface(iid, ppvSite); 2972 } 2973 2974 HRESULT STDMETHODCALLTYPE CShellLink::DragEnter(IDataObject *pDataObject, 2975 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) 2976 { 2977 TRACE("(%p)->(DataObject=%p)\n", this, pDataObject); 2978 LPCITEMIDLIST pidlLast; 2979 CComPtr<IShellFolder> psf; 2980 2981 HRESULT hr = SHBindToParent(m_pPidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast); 2982 2983 if (SUCCEEDED(hr)) 2984 { 2985 hr = psf->GetUIObjectOf(0, 1, &pidlLast, IID_NULL_PPV_ARG(IDropTarget, &m_DropTarget)); 2986 2987 if (SUCCEEDED(hr)) 2988 hr = m_DropTarget->DragEnter(pDataObject, dwKeyState, pt, pdwEffect); 2989 else 2990 *pdwEffect = DROPEFFECT_NONE; 2991 } 2992 else 2993 *pdwEffect = DROPEFFECT_NONE; 2994 2995 return S_OK; 2996 } 2997 2998 HRESULT STDMETHODCALLTYPE CShellLink::DragOver(DWORD dwKeyState, POINTL pt, 2999 DWORD *pdwEffect) 3000 { 3001 TRACE("(%p)\n", this); 3002 HRESULT hr = S_OK; 3003 if (m_DropTarget) 3004 hr = m_DropTarget->DragOver(dwKeyState, pt, pdwEffect); 3005 return hr; 3006 } 3007 3008 HRESULT STDMETHODCALLTYPE CShellLink::DragLeave() 3009 { 3010 TRACE("(%p)\n", this); 3011 HRESULT hr = S_OK; 3012 if (m_DropTarget) 3013 { 3014 hr = m_DropTarget->DragLeave(); 3015 m_DropTarget.Release(); 3016 } 3017 3018 return hr; 3019 } 3020 3021 HRESULT STDMETHODCALLTYPE CShellLink::Drop(IDataObject *pDataObject, 3022 DWORD dwKeyState, POINTL pt, DWORD *pdwEffect) 3023 { 3024 TRACE("(%p)\n", this); 3025 HRESULT hr = S_OK; 3026 if (m_DropTarget) 3027 hr = m_DropTarget->Drop(pDataObject, dwKeyState, pt, pdwEffect); 3028 3029 return hr; 3030 } 3031 3032 /************************************************************************** 3033 * IShellLink_ConstructFromFile 3034 */ 3035 HRESULT WINAPI IShellLink_ConstructFromPath(WCHAR *path, REFIID riid, LPVOID *ppv) 3036 { 3037 CComPtr<IPersistFile> ppf; 3038 HRESULT hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IPersistFile, &ppf)); 3039 if (FAILED(hr)) 3040 return hr; 3041 3042 hr = ppf->Load(path, 0); 3043 if (FAILED(hr)) 3044 return hr; 3045 3046 return ppf->QueryInterface(riid, ppv); 3047 } 3048 3049 HRESULT WINAPI IShellLink_ConstructFromFile(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID *ppv) 3050 { 3051 WCHAR path[MAX_PATH]; 3052 if (!ILGetDisplayNameExW(psf, pidl, path, 0)) 3053 return E_FAIL; 3054 3055 return IShellLink_ConstructFromPath(path, riid, ppv); 3056 } 3057