Reactos
at master 874 lines 32 kB view raw
1/* 2 * PROJECT: lnktool 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: ShellLink (.lnk) utility 5 * COPYRIGHT: Copyright 2025 Whindmar Saksit <whindsaks@proton.me> 6 */ 7 8#define INDENT " " 9static const char g_Usage[] = "" 10 "lnktool <Create|Dump|RemoveDatablock> <LnkFile> [Options]\n" 11 "\n" 12 "Create Options:\n" 13 "/Pidl <Path>\n" 14 "/Path <TargetPath>\n" 15 "/Icon <Path,Index>\n" 16 "/Arguments <String>\n" 17 "/RelativePath <LnkPath>\n" 18 "/WorkingDir <Path>\n" 19 "/Comment <String>\n" 20 "/ShowCmd <Number>\n" 21 "/SpecialFolderOffset <CSIDL> <Offset|-Count>\n" 22 "/AddExp <TargetPath>\n" 23 "/AddExpIcon <Path>\n" 24 "/RemoveExpIcon\n" // Removes the EXP_SZ_ICON block and flag 25 "/AddSLDF <Flag>\n" 26 "/RemoveSLDF <Flag>\n"; 27 28#include <windows.h> 29#include <shlobj.h> 30#include <shlwapi.h> 31#include <wchar.h> 32#include <stdio.h> // For shellutils.h (sprintf) 33#include <shellutils.h> 34#include <shlwapi_undoc.h> 35#include <undocshell.h> // SHELL_LINK_HEADER 36 37enum { slgp_relativepriority = 0x08 }; 38#define PT_DESKTOP_REGITEM 0x1F 39#define PT_COMPUTER_REGITEM 0x2E 40#define PT_FS 0x30 41#define PT_FS_UNICODE_FLAG 0x04 42 43static const struct { UINT Flag; PCSTR Name; } g_SLDF[] = 44{ 45 { SLDF_HAS_ID_LIST, "IDLIST" }, 46 { SLDF_HAS_LINK_INFO, "LINKINFO" }, 47 { SLDF_HAS_NAME, "NAME" }, 48 { SLDF_HAS_RELPATH, "RELPATH" }, 49 { SLDF_HAS_WORKINGDIR, "WORKINGDIR" }, 50 { SLDF_HAS_ARGS, "ARGS" }, 51 { SLDF_HAS_ICONLOCATION, "ICONLOCATION" }, 52 { SLDF_UNICODE, "UNICODE" }, 53 { SLDF_FORCE_NO_LINKINFO, "FORCE_NO_LINKINFO" }, 54 { SLDF_HAS_EXP_SZ, "EXP_SZ" }, 55 { SLDF_RUN_IN_SEPARATE, "RUN_IN_SEPARATE" }, 56 { 0x00000800, "LOGO3ID" }, 57 { SLDF_HAS_DARWINID, "DARWINID" }, 58 { SLDF_RUNAS_USER, "RUNAS_USER" }, 59 { SLDF_HAS_EXP_ICON_SZ, "EXP_ICON_SZ" }, 60 { SLDF_NO_PIDL_ALIAS, "NO_PIDL_ALIAS" }, 61 { SLDF_FORCE_UNCNAME, "FORCE_UNCNAME" }, 62 { SLDF_RUN_WITH_SHIMLAYER, "RUN_WITH_SHIMLAYER" }, 63 { 0x00040000, "FORCE_NO_LINKTRACK" }, 64 { 0x00080000, "ENABLE_TARGET_METADATA" }, 65 { 0x00100000, "DISABLE_LINK_PATH_TRACKING" }, 66 { 0x00200000, "DISABLE_KNOWNFOLDER_RELATIVE_TRACKING" }, 67 { 0x00400000, "NO_KF_ALIAS" }, 68 { 0x00800000, "ALLOW_LINK_TO_LINK" }, 69 { 0x01000000, "UNALIAS_ON_SAVE" }, 70 { 0x02000000, "PREFER_ENVIRONMENT_PATH" }, 71 { 0x04000000, "KEEP_LOCAL_IDLIST_FOR_UNC_TARGET" }, 72 { 0x08000000, "PERSIST_VOLUME_ID_RELATIVE" }, 73}; 74 75static const struct { UINT Flag; PCSTR Name; } g_DBSig[] = 76{ 77 { EXP_SZ_LINK_SIG, "SZ_LINK" }, 78 { EXP_SZ_ICON_SIG, "SZ_ICON" }, 79 { EXP_SPECIAL_FOLDER_SIG, "SPECIALFOLDER" }, 80 { EXP_TRACKER_SIG, "TRACKER" }, 81 { 0xA0000009, "PROPERTYSTORAGE" }, 82 { EXP_KNOWN_FOLDER_SIG, "KNOWNFOLDER" }, 83 { EXP_VISTA_ID_LIST_SIG, "VISTAPIDL" }, 84}; 85 86static LONG StrToNum(PCWSTR in) 87{ 88 PWCHAR end; 89 LONG v = wcstol(in, &end, 0); 90 if (v == LONG_MAX) 91 v = wcstoul(in, &end, 0); 92 return (end > in) ? v : 0; 93} 94 95template<class T> 96static UINT MapToNumber(PCWSTR Name, const T &Map) 97{ 98 CHAR buf[200]; 99 WideCharToMultiByte(CP_ACP, 0, Name, -1, buf, _countof(buf), NULL, NULL); 100 buf[_countof(buf) - 1] = ANSI_NULL; 101 for (UINT i = 0; i < _countof(Map); ++i) 102 { 103 if (!StrCmpIA(buf, Map[i].Name)) 104 return Map[i].Flag; 105 } 106 return StrToNum(Name); 107} 108 109template<class T> 110static PCSTR MapToName(UINT Value, const T &Map, PCSTR Fallback = NULL) 111{ 112 for (UINT i = 0; i < _countof(Map); ++i) 113 { 114 if (Map[i].Flag == Value) 115 return Map[i].Name; 116 } 117 return Fallback; 118} 119 120#define GetSLDF(Name) MapToNumber((Name), g_SLDF) 121#define GetDatablockSignature(Name) MapToNumber((Name), g_DBSig) 122 123static int ErrMsg(int Error) 124{ 125 WCHAR buf[400]; 126 for (UINT e = Error, cch; ;) 127 { 128 lstrcpynW(buf, L"?", _countof(buf)); 129 cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, e, 0, buf, _countof(buf), NULL); 130 while (cch && buf[cch - 1] <= ' ') 131 buf[--cch] = UNICODE_NULL; // Remove trailing newlines 132 if (cch || HIWORD(e) != HIWORD(HRESULT_FROM_WIN32(1))) 133 break; 134 e = HRESULT_CODE(e); // "WIN32_FROM_HRESULT" 135 } 136 wprintf(Error < 0 ? L"Error 0x%.8X %s\n" : L"Error %d %s\n", Error, buf); 137 return Error; 138} 139 140static int SuccessOrReportError(int Error) 141{ 142 return Error ? ErrMsg(Error) : Error; 143} 144 145static inline HRESULT Seek(IStream *pStrm, int Move, int Origin = FILE_CURRENT, ULARGE_INTEGER *pNewPos = NULL) 146{ 147 LARGE_INTEGER Pos; 148 Pos.QuadPart = Move; 149 return pStrm->Seek(Pos, Origin, pNewPos); 150} 151 152static HRESULT IL_LoadFromStream(IStream *pStrm, PIDLIST_ABSOLUTE *ppidl) 153{ 154 HMODULE hShell32 = LoadLibraryA("SHELL32"); 155 HRESULT (WINAPI*ILLFS)(IStream*, PIDLIST_ABSOLUTE*); 156 (FARPROC&)ILLFS = GetProcAddress(hShell32, (char*)26); // NT5 157 if (!ILLFS) 158 (FARPROC&)ILLFS = GetProcAddress(hShell32, (char*)846); // NT6 159 return ILLFS(pStrm, ppidl); 160} 161 162static HRESULT SHParseName(PCWSTR Path, PIDLIST_ABSOLUTE *ppidl) 163{ 164 HMODULE hShell32 = LoadLibraryA("SHELL32"); 165 int (WINAPI*SHPDN)(PCWSTR, void*, PIDLIST_ABSOLUTE*, UINT, UINT*); 166 (FARPROC&)SHPDN = GetProcAddress(hShell32, "SHParseDisplayName"); 167 if (SHPDN) 168 return SHPDN(Path, NULL, ppidl, 0, NULL); 169 return SHILCreateFromPath(Path, ppidl, NULL); 170} 171 172static HRESULT SHParseNameEx(PCWSTR Path, PIDLIST_ABSOLUTE *ppidl, bool Simple) 173{ 174 if (!Simple) 175 return SHParseName(Path, ppidl); 176 *ppidl = SHSimpleIDListFromPath(Path); 177 return *ppidl ? S_OK : E_FAIL; 178} 179 180static INT_PTR SHGetPidlInfo(LPCITEMIDLIST pidl, SHFILEINFOW &shfi, UINT Flags, UINT Depth = 0) 181{ 182 LPITEMIDLIST pidlDup = NULL, pidlChild; 183 if (Depth > 0) 184 { 185 if ((pidl = pidlDup = pidlChild = ILClone(pidl)) == NULL) 186 return 0; 187 while (Depth--) 188 { 189 if (LPITEMIDLIST pidlNext = ILGetNext(pidlChild)) 190 pidlChild = pidlNext; 191 } 192 pidlChild->mkid.cb = 0; 193 } 194 INT_PTR ret = SHGetFileInfoW((PWSTR)pidl, 0, &shfi, sizeof(shfi), Flags | SHGFI_PIDL); 195 ILFree(pidlDup); 196 return ret; 197} 198 199static void PrintOffsetString(PCSTR Name, LPCVOID Base, UINT Min, UINT Ansi, UINT Unicode, PCSTR Indent = "") 200{ 201 if (Unicode && Unicode >= Min) 202 wprintf(L"%hs%hs=%ls", Indent, Name, (PWSTR)((BYTE*)Base + Unicode)); 203 else 204 wprintf(L"%hs%hs=%hs", Indent, Name, Ansi >= Min ? (char*)Base + Ansi : ""); 205 wprintf(L"\n"); // Separate function call in case the (untrusted) input ends with a DEL character 206} 207 208static void Print(PCSTR Name, REFGUID Guid, PCSTR Indent = "") 209{ 210 WCHAR Buffer[39]; 211 StringFromGUID2(Guid, Buffer, _countof(Buffer)); 212 wprintf(L"%hs%hs=%ls\n", Indent, Name, Buffer); 213} 214 215template<class V, class T> 216static void DumpFlags(V Value, T *pInfo, UINT Count, PCSTR Prefix = NULL) 217{ 218 if (!Prefix) 219 Prefix = ""; 220 for (SIZE_T i = 0; i < Count; ++i) 221 { 222 if (Value & pInfo[i].Flag) 223 wprintf(L"%hs%#.8x:%hs\n", Prefix, pInfo[i].Flag, const_cast<PCSTR>(pInfo[i].Name)); 224 Value &= ~pInfo[i].Flag; 225 } 226 if (Value) 227 wprintf(L"%hs%#.8x:%hs\n", Prefix, Value, "?"); 228} 229 230static void Dump(LPITEMIDLIST pidl, PCSTR Heading = NULL) 231{ 232 struct GUIDPIDL { WORD cb; BYTE Type, Unknown; GUID guid; }; 233 struct DRIVE { BYTE cb1, cb2, Type; char Name[4]; BYTE Unk[18]; }; 234 struct FS95 { WORD cb; BYTE Type, Unknown, Data[4+2+2+2]; char Name[1]; }; 235 if (Heading) 236 wprintf(L"%hs ", Heading); 237 if (!pidl || !pidl->mkid.cb) 238 wprintf(L"[Desktop (%hs)]", pidl ? "Empty" : "NULL"); 239 for (UINT i = 0; pidl && pidl->mkid.cb; ++i, pidl = ILGetNext(pidl)) 240 { 241 SHFILEINFOW shfi; 242 PWSTR buf = shfi.szDisplayName; 243 GUIDPIDL *pidlGUID = (GUIDPIDL*)pidl; 244 UINT cb = pidl->mkid.cb; 245 UINT type = cb >= 3 ? (pidlGUID->Type & ~0x80) : 0, folder = type & 0x70; 246 if (i) 247 wprintf(L" "); 248 if (cb < 3) 249 { 250 wprintf(L"[? %ub]", cb); 251 } 252 else if (i == 0 && cb == sizeof(GUIDPIDL) && type == PT_DESKTOP_REGITEM) guiditem: 253 { 254 if (!SHGetPidlInfo(pidl, shfi, SHGFI_DISPLAYNAME, 1)) 255 StringFromGUID2(*(GUID*)((char*)pidl + cb - 16), buf, 39); 256 wprintf(L"[%.2X %ub %s]", pidl->mkid.abID[0], cb, buf); 257 } 258 else if (i == 1 && cb == sizeof(GUIDPIDL) && type == PT_COMPUTER_REGITEM) 259 { 260 goto guiditem; 261 } 262 else if (i == 1 && cb == sizeof(DRIVE) && folder == 0x20) 263 { 264 DRIVE *p = (DRIVE*)pidl; 265 wprintf(L"[%.2X %ub \"%.3hs\"]", p->Type, cb, p->Name); 266 } 267 else if (cb > FIELD_OFFSET(FS95, Name) && folder == PT_FS) 268 { 269 FS95 *p = (FS95*)pidl; 270 const BOOL wide = type & PT_FS_UNICODE_FLAG; 271 wprintf(wide ? L"[%.2X %ub \"%.256ls\"]" : L"[%.2X %ub \"%.256hs\"]", p->Type, cb, p->Name); 272 } 273 else 274 { 275 wprintf(L"[%.2X %ub ?]", pidl->mkid.abID[0], cb); 276 } 277 } 278 wprintf(L"\n"); 279} 280 281static HRESULT Save(IShellLink *pSL, PCWSTR LnkPath) 282{ 283 HRESULT hr; 284 IPersistFile *pPF; 285 if (SUCCEEDED(hr = pSL->QueryInterface(IID_PPV_ARG(IPersistFile, &pPF)))) 286 { 287 if (SUCCEEDED(hr = pPF->Save(LnkPath, FALSE)) && hr != S_OK) 288 hr = E_FAIL; 289 pPF->Release(); 290 } 291 return hr; 292} 293 294static HRESULT Load(IUnknown *pUnk, IStream *pStream) 295{ 296 IPersistStream *pPS; 297 HRESULT hr = pUnk->QueryInterface(IID_PPV_ARG(IPersistStream, &pPS)); 298 if (SUCCEEDED(hr)) 299 { 300 hr = pPS->Load(pStream); 301 pPS->Release(); 302 } 303 return hr; 304} 305 306static HRESULT Open(PCWSTR Path, IStream **ppStream, IShellLink **ppLink, UINT Mode = STGM_READ) 307{ 308 if (Mode & (STGM_WRITE | STGM_READWRITE)) 309 Mode = (Mode & ~STGM_WRITE) | STGM_READWRITE | STGM_SHARE_DENY_WRITE; 310 else 311 Mode |= STGM_READ | STGM_SHARE_DENY_NONE; 312 IStream *pStream; 313 HRESULT hr = SHCreateStreamOnFileW(Path, Mode, &pStream); 314 if (SUCCEEDED(hr) && ppLink) 315 { 316 hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLink, ppLink)); 317 if (SUCCEEDED(hr) && !(Mode & STGM_CREATE)) 318 { 319 if (FAILED(hr = Load(*ppLink, pStream))) 320 { 321 (*ppLink)->Release(); 322 *ppLink = NULL; 323 } 324 IStream_Reset(pStream); 325 } 326 } 327 328 if (SUCCEEDED(hr) && ppStream) 329 *ppStream = pStream; 330 else if (pStream) 331 pStream->Release(); 332 return hr; 333} 334 335#define FreeBlock SHFree 336static inline LPDATABLOCK_HEADER NextBlock(LPDATABLOCK_HEADER pdbh) 337{ 338 return (LPDATABLOCK_HEADER)((char*)pdbh + pdbh->cbSize); 339} 340 341template<class T> 342static HRESULT ReadBlock(IStream *pStream, T **ppData = NULL) 343{ 344 DWORD cb = 0; 345 HRESULT hr = IStream_Read(pStream, &cb, sizeof(cb)); 346 if (SUCCEEDED(hr)) 347 { 348 UINT Remain = cb - min(sizeof(DWORD), cb); 349 if (!ppData) 350 return Seek(pStream, Remain); 351 *ppData = (T*)SHAlloc(Remain + sizeof(DWORD)); 352 if (!*ppData) 353 return E_OUTOFMEMORY; 354 ((DATABLOCK_HEADER*)*ppData)->cbSize = cb; 355 if (SUCCEEDED(hr = IStream_Read(pStream, &((DATABLOCK_HEADER*)*ppData)->dwSignature, Remain))) 356 return cb; 357 } 358 return hr; 359} 360 361struct DataBlockEnum 362{ 363 DataBlockEnum(LPDATABLOCK_HEADER pHead) : m_It(pHead) {} 364 365 LPDATABLOCK_HEADER Get() 366 { 367 if (m_It && m_It->cbSize >= FIELD_OFFSET(DATABLOCK_HEADER, dwSignature)) 368 return m_It + (m_It->dwSignature == ~0UL); // SHFindDataBlock compatible 369 return NULL; 370 } 371 void Next() 372 { 373 if (m_It && m_It->cbSize) 374 m_It = NextBlock(m_It); 375 } 376 LPDATABLOCK_HEADER m_It; 377}; 378 379static HRESULT ReadAndDumpString(PCSTR Name, UINT Id, const SHELL_LINK_HEADER &slh, IStream *pStream) 380{ 381 if (!(Id & slh.dwFlags)) 382 return S_OK; 383 WORD cch; 384 HRESULT hr = IStream_Read(pStream, &cch, sizeof(cch)); 385 if (FAILED(hr)) 386 return hr; 387 UINT cb = (UINT)cch * ((slh.dwFlags & SLDF_UNICODE) ? sizeof(WCHAR) : sizeof(CHAR)); 388 void *data = SHAlloc(cb); 389 if (!data) 390 return E_OUTOFMEMORY; 391 if (FAILED(hr = IStream_Read(pStream, data, cb))) 392 return hr; 393 if (slh.dwFlags & SLDF_UNICODE) 394 wprintf(L"%hs=%.*ls\n", Name, cch, (PWSTR)data); 395 else 396 wprintf(L"%hs=%.*hs\n", Name, cch, (PCSTR)data); 397 SHFree(data); 398 return S_OK; 399} 400 401static HRESULT DumpCommand(PCWSTR Path) 402{ 403 IStream *pStream; 404 HRESULT hr = Open(Path, &pStream, NULL); 405 if (FAILED(hr)) 406 return hr; 407 408 LPITEMIDLIST pidl; 409 SHELL_LINK_HEADER slh; 410 if (SUCCEEDED(hr = IStream_Read(pStream, &slh, sizeof(slh)))) 411 { 412 #define DUMPSLH(name, fmt, field) wprintf(L"%hs=" fmt L"\n", (name), (slh).field) 413 DUMPSLH("Flags", L"%#.8x", dwFlags); 414 DumpFlags(slh.dwFlags, g_SLDF, _countof(g_SLDF), INDENT); 415 DUMPSLH("FileAttributes", L"%#.8x", dwFileAttributes); 416 DUMPSLH("Size", L"%u", nFileSizeLow); 417 DUMPSLH("IconIndex", L"%d", nIconIndex); 418 DUMPSLH("ShowCommand", L"%d", nShowCommand); 419 DUMPSLH("HotKey", L"%#.4x", wHotKey); 420 421 if (SUCCEEDED(hr) && (slh.dwFlags & SLDF_HAS_ID_LIST)) 422 { 423 if (SUCCEEDED(hr = IL_LoadFromStream(pStream, &pidl))) 424 { 425 Dump(pidl, "PIDL:"); 426 ILFree(pidl); 427 } 428 } 429 if (SUCCEEDED(hr) && (slh.dwFlags & SLDF_HAS_LINK_INFO)) 430 { 431 int offset; 432 SHELL_LINK_INFOW *p; 433 if ((hr = ReadBlock(pStream, &p)) >= (signed)sizeof(SHELL_LINK_INFOA)) 434 { 435 wprintf(L"%hs:\n", "LINKINFO"); 436 wprintf(L"%hs%hs=%#.8x\n", INDENT, "Flags", p->dwFlags); 437 if (p->dwFlags & SLI_VALID_LOCAL) 438 { 439 if (p->cbVolumeIDOffset) 440 { 441 SHELL_LINK_INFO_VOLUME_IDW *pVol = (SHELL_LINK_INFO_VOLUME_IDW*)((char*)p + p->cbVolumeIDOffset); 442 wprintf(L"%hs%hs=%d\n", INDENT, "DriveType", pVol->dwDriveType); 443 wprintf(L"%hs%hs=%#.8x\n", INDENT, "Serial", pVol->nDriveSerialNumber); 444 offset = pVol->cbVolumeLabelOffset == 0x14 ? pVol->cbVolumeLabelUnicodeOffset : 0; 445 if (offset || pVol->cbVolumeLabelOffset != 0x14) // 0x14 from [MS-SHLLINK] documentation 446 PrintOffsetString("Label", pVol, 0x10, pVol->cbVolumeLabelOffset, offset, INDENT); 447 } 448 offset = p->cbHeaderSize >= sizeof(SHELL_LINK_INFOW) ? p->cbCommonPathSuffixUnicodeOffset : 0; 449 PrintOffsetString("CommonSuffix", p, sizeof(SHELL_LINK_INFOA), p->cbCommonPathSuffixOffset, offset, INDENT); 450 451 offset = p->cbHeaderSize >= sizeof(SHELL_LINK_INFOW) ? p->cbLocalBasePathUnicodeOffset : 0; 452 PrintOffsetString("LocalBase", p, sizeof(SHELL_LINK_INFOA), p->cbLocalBasePathOffset, offset, INDENT); 453 } 454 SHELL_LINK_INFO_CNR_LINKW *pCNR = (SHELL_LINK_INFO_CNR_LINKW*)((char*)p + p->cbCommonNetworkRelativeLinkOffset); 455 if ((p->dwFlags & SLI_VALID_NETWORK) && p->cbCommonNetworkRelativeLinkOffset) 456 { 457 wprintf(L"%hs%hs=%#.8x\n", INDENT, "CNR", pCNR->dwFlags); 458 wprintf(L"%hs%hs=%#.8x\n", INDENT, "Provider", pCNR->dwNetworkProviderType); // WNNC_NET_* 459 460 offset = pCNR->cbNetNameOffset > 0x14 ? pCNR->cbNetNameUnicodeOffset : 0; 461 PrintOffsetString("NetName", pCNR, sizeof(SHELL_LINK_INFO_CNR_LINKA), pCNR->cbNetNameOffset, offset, INDENT); 462 463 offset = pCNR->cbNetNameOffset > 0x14 ? pCNR->cbDeviceNameUnicodeOffset : 0; 464 if (pCNR->dwFlags & SLI_CNR_VALID_DEVICE) 465 PrintOffsetString("DeviceName", pCNR, sizeof(SHELL_LINK_INFO_CNR_LINKA), pCNR->cbDeviceNameOffset, offset, INDENT); 466 } 467 FreeBlock(p); 468 } 469 } 470 if (SUCCEEDED(hr)) 471 hr = ReadAndDumpString("NAME", SLDF_HAS_NAME, slh, pStream); 472 if (SUCCEEDED(hr)) 473 hr = ReadAndDumpString("RELPATH", SLDF_HAS_RELPATH, slh, pStream); 474 if (SUCCEEDED(hr)) 475 hr = ReadAndDumpString("WORKINGDIR", SLDF_HAS_WORKINGDIR, slh, pStream); 476 if (SUCCEEDED(hr)) 477 hr = ReadAndDumpString("ARGS", SLDF_HAS_ARGS, slh, pStream); 478 if (SUCCEEDED(hr)) 479 hr = ReadAndDumpString("ICONLOCATION", SLDF_HAS_ICONLOCATION, slh, pStream); 480 481 LPDATABLOCK_HEADER pDBListHead, pDBH; 482 if (SUCCEEDED(hr) && SUCCEEDED(hr = SHReadDataBlockList(pStream, &pDBListHead))) 483 { 484 for (DataBlockEnum it(pDBListHead); (pDBH = it.Get()) != NULL; it.Next()) 485 { 486 PCSTR SigName = MapToName(pDBH->dwSignature, g_DBSig, NULL); 487 wprintf(L"DataBlock: %.8X %ub", pDBH->dwSignature, pDBH->cbSize); 488 void *pFirstMember = ((EXP_SZ_LINK*)pDBH)->szTarget; 489 if (pDBH->dwSignature == EXP_SZ_LINK_SIG && pDBH->cbSize > FIELD_OFFSET(EXP_SZ_LINK, szwTarget)) 490 { 491 wprintf(L" %hs\n", "SZ_LINK"); 492 printdbpaths: 493 EXP_SZ_LINK *p = (EXP_SZ_LINK*)pDBH; 494 wprintf(L"%hs%hs=%.260hs\n", INDENT, "Ansi", p->szTarget); 495 wprintf(L"%hs%hs=%.260ls\n", INDENT, "Wide", p->szwTarget); 496 } 497 else if (pDBH->dwSignature == EXP_SZ_ICON_SIG && pDBH->cbSize > FIELD_OFFSET(EXP_SZ_LINK, szwTarget)) 498 { 499 wprintf(L" %hs\n", "SZ_ICON/LOGO3"); 500 goto printdbpaths; 501 } 502 else if (pDBH->dwSignature == EXP_DARWIN_ID_SIG && pDBH->cbSize == sizeof(EXP_DARWIN_LINK)) 503 { 504 wprintf(L" %hs\n", "DARWIN_LINK"); 505 goto printdbpaths; 506 } 507 else if (pDBH->dwSignature == NT_CONSOLE_PROPS_SIG && pDBH->cbSize >= sizeof(NT_CONSOLE_PROPS)) 508 { 509 wprintf(L" %hs\n", "NT_CONSOLE_PROPS"); 510 NT_CONSOLE_PROPS *p = (NT_CONSOLE_PROPS*)pDBH; 511 wprintf(L"%hsInsert=%d Quick=%d %ls\n", INDENT, p->bInsertMode, p->bQuickEdit, p->FaceName); 512 } 513 else if (pDBH->dwSignature == EXP_SPECIAL_FOLDER_SIG && pDBH->cbSize == sizeof(EXP_SPECIAL_FOLDER)) 514 { 515 wprintf(L" %hs\n", SigName); 516 EXP_SPECIAL_FOLDER *p = (EXP_SPECIAL_FOLDER*)pDBH; 517 wprintf(L"%hsCSIDL=%#x Offset=%#x\n", INDENT, p->idSpecialFolder, p->cbOffset); 518 } 519 else if (pDBH->dwSignature == EXP_TRACKER_SIG) 520 { 521 wprintf(L" %hs\n", SigName); 522 EXP_TRACKER *p = (EXP_TRACKER*)pDBH; 523 UINT len = FIELD_OFFSET(EXP_TRACKER, nLength) + p->nLength; 524 if (len >= FIELD_OFFSET(EXP_TRACKER, szMachineID)) 525 wprintf(L"%hsVersion=%d\n", INDENT, p->nVersion); 526 if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidVolume)) 527 wprintf(L"%hsMachine=%hs\n", INDENT, p->szMachineID); 528 if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidObject)) 529 Print("Volume", p->guidDroidVolume, INDENT); 530 if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidBirthVolume)) 531 Print("Object", p->guidDroidObject, INDENT); 532 if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidBirthObject)) 533 Print("BirthVolume", p->guidDroidBirthVolume, INDENT); 534 if (len >= sizeof(EXP_TRACKER)) 535 Print("BirthObject", p->guidDroidBirthObject, INDENT); 536 } 537 else if (pDBH->dwSignature == EXP_SHIM_SIG) 538 { 539 wprintf(L" %hs\n", SigName ? SigName : "SHIM"); 540 wprintf(L"%hs%ls\n", INDENT, (PWSTR)pFirstMember); 541 } 542 else if (pDBH->dwSignature == EXP_VISTA_ID_LIST_SIG && pDBH->cbSize >= 8 + 2) 543 { 544 wprintf(L" %hs\n", SigName); 545 Dump((LPITEMIDLIST)pFirstMember, INDENT + 1); 546 } 547 else 548 { 549 wprintf(SigName ? L" %hs\n" : L"\n", SigName); 550 } 551 } 552 SHFreeDataBlockList(pDBListHead); 553 } 554 } 555 pStream->Release(); 556 557 // Now dump using the API 558 HRESULT hr2; 559 WCHAR buf[MAX_PATH * 2]; 560 IShellLink *pSL; 561 if (FAILED(hr2 = Open(Path, NULL, &pSL))) 562 return hr2; 563 wprintf(L"\n"); 564 565 if (SUCCEEDED(hr2 = pSL->GetIDList(&pidl))) 566 { 567 Dump(pidl, "GetIDList:"); 568 ILFree(pidl); 569 } 570 else 571 { 572 wprintf(L"%hs: %#x\n", "GetIDList", hr2); 573 } 574 575 static const BYTE GetPathFlags[] = { 0, SLGP_SHORTPATH, SLGP_RAWPATH, slgp_relativepriority }; 576 for (UINT i = 0; i < _countof(GetPathFlags); ++i) 577 { 578 if (SUCCEEDED(hr2 = pSL->GetPath(buf, _countof(buf), NULL, GetPathFlags[i]))) 579 wprintf(L"GetPath(%#.2x): %ls\n", GetPathFlags[i], buf); 580 else 581 wprintf(L"GetPath(%#.2x): %#x\n", GetPathFlags[i], hr2); 582 } 583 584 if (SUCCEEDED(hr2 = pSL->GetWorkingDirectory(buf, _countof(buf)))) 585 wprintf(L"%hs: %ls\n", "GetWorkingDirectory", buf); 586 else 587 wprintf(L"%hs: %#x\n", "GetWorkingDirectory", hr2); 588 589 if (SUCCEEDED(hr2 = pSL->GetArguments(buf, _countof(buf)))) 590 wprintf(L"%hs: %ls\n", "GetArguments", buf); 591 else 592 wprintf(L"%hs: %#x\n", "GetArguments", hr2); 593 594 if (SUCCEEDED(hr2 = pSL->GetDescription(buf, _countof(buf)))) 595 wprintf(L"%hs: %ls\n", "GetDescription", buf); 596 else 597 wprintf(L"%hs: %#x\n", "GetDescription", hr2); 598 599 int index = 123456789; 600 if (SUCCEEDED(hr2 = pSL->GetIconLocation(buf, _countof(buf), &index))) 601 wprintf(L"%hs: %ls,%d\n", "GetIconLocation", buf, index); 602 else 603 wprintf(L"%hs: %#x\n", "GetIconLocation", hr2); 604 605 IExtractIconW *pEI; 606 if (SUCCEEDED(pSL->QueryInterface(IID_PPV_ARG(IExtractIconW, &pEI)))) 607 { 608 index = 123456789; 609 UINT flags = 0; 610 if (SUCCEEDED(hr2 = pEI->GetIconLocation(0, buf, _countof(buf), &index, &flags))) 611 wprintf(L"%hs: %#x %ls,%d %#.4x\n", "EI:GetIconLocation", hr2, buf, index, flags); 612 else 613 wprintf(L"%hs: %#x %#.4x\n", "EI:GetIconLocation", hr2, flags); 614 pEI->Release(); 615 } 616 617 pSL->Release(); 618 return hr; 619} 620 621#define RemoveSLDF(pSL, Flag) RemoveDatablock((pSL), 0, (Flag)) 622#define AddSLDF(pSL, Flag) RemoveDatablock((pSL), 0, 0, (Flag)) 623 624static HRESULT RemoveDatablock(IShellLinkW *pSL, UINT Signature, UINT KillFlag = 0, UINT AddFlag = 0) 625{ 626 IShellLinkDataList *pSLDL; 627 HRESULT hr = pSL->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pSLDL)); 628 if (SUCCEEDED(hr)) 629 { 630 if (Signature) 631 hr = pSLDL->RemoveDataBlock(Signature); 632 DWORD OrgFlags; 633 if ((AddFlag | KillFlag) && SUCCEEDED(pSLDL->GetFlags(&OrgFlags))) 634 pSLDL->SetFlags((OrgFlags & ~KillFlag) | AddFlag); 635 pSLDL->Release(); 636 } 637 return hr; 638} 639 640template<class T> 641static HRESULT AddDataBlock(IShellLinkW *pSL, T &DataBlock) 642{ 643 IShellLinkDataList *pSLDL; 644 HRESULT hr = pSL->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pSLDL)); 645 if (SUCCEEDED(hr)) 646 { 647 hr = pSLDL->AddDataBlock(&DataBlock); 648 pSLDL->Release(); 649 } 650 return hr; 651} 652 653static BOOL TryGetUpdatedPidl(IShellLinkW *pSL, PIDLIST_ABSOLUTE &pidl) 654{ 655 PIDLIST_ABSOLUTE pidlNew; 656 if (pSL->GetIDList(&pidlNew) == S_OK) 657 { 658 ILFree(pidl); 659 pidl = pidlNew; 660 } 661 return pidl != NULL; 662} 663 664static HRESULT CreateCommand(PCWSTR LnkPath, UINT argc, WCHAR **argv) 665{ 666 IShellLinkW *pSL; 667 HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLinkW, &pSL)); 668 if (FAILED(hr)) 669 return hr; 670 671 UINT TargetCount = 0, DumpResult = 0, ForceAddSLDF = 0, ForceRemoveSLDF = 0; 672 PIDLIST_ABSOLUTE pidlTarget = NULL; 673 for (UINT i = 0; i < argc && SUCCEEDED(hr); ++i) 674 { 675 hr = E_INVALIDARG; 676 if (!StrCmpIW(argv[i], L"/Pidl") && i + 1 < argc) 677 { 678 bool Simple = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; 679 if (SUCCEEDED(hr = SHParseNameEx(argv[++i], &pidlTarget, Simple))) 680 TargetCount += SUCCEEDED(hr = pSL->SetIDList(pidlTarget)); 681 } 682 else if (!StrCmpIW(argv[i], L"/Path") && i + 1 < argc) 683 { 684 TargetCount += SUCCEEDED(hr = pSL->SetPath(argv[++i])); 685 } 686 else if (!StrCmpIW(argv[i], L"/Icon") && i + 1 < argc) 687 { 688 int index = PathParseIconLocation(argv[++i]); 689 hr = pSL->SetIconLocation(argv[i], index); 690 } 691 else if (!StrCmpIW(argv[i], L"/Arguments") && i + 1 < argc) 692 { 693 hr = pSL->SetArguments(argv[++i]); 694 } 695 else if (!StrCmpIW(argv[i], L"/RelativePath") && i + 1 < argc) 696 { 697 hr = pSL->SetRelativePath(argv[++i], 0); 698 } 699 else if (!StrCmpIW(argv[i], L"/WorkingDir") && i + 1 < argc) 700 { 701 hr = pSL->SetWorkingDirectory(argv[++i]); 702 } 703 else if (!StrCmpIW(argv[i], L"/Comment") && i + 1 < argc) 704 { 705 hr = pSL->SetDescription(argv[++i]); 706 } 707 else if (!StrCmpIW(argv[i], L"/ShowCmd") && i + 1 < argc) 708 { 709 if (int sw = StrToNum(argv[++i])) // Don't allow SW_HIDE 710 hr = pSL->SetShowCmd(sw); 711 } 712 else if (!StrCmpIW(argv[i], L"/AddExp") && ++i < argc) 713 { 714 EXP_SZ_LINK db = { sizeof(db), EXP_SZ_LINK_SIG }; 715 WideCharToMultiByte(CP_ACP, 0, argv[i], -1, db.szTarget, _countof(db.szTarget), NULL, NULL); 716 lstrcpynW(db.szwTarget, argv[i], _countof(db.szwTarget)); 717 if (SUCCEEDED(hr = AddDataBlock(pSL, db))) 718 TargetCount += SUCCEEDED(hr = AddSLDF(pSL, SLDF_HAS_EXP_SZ)); 719 } 720 else if (!StrCmpIW(argv[i], L"/AddExpIcon") && ++i < argc) 721 { 722 EXP_SZ_LINK db = { sizeof(db), EXP_SZ_ICON_SIG }; 723 WideCharToMultiByte(CP_ACP, 0, argv[i], -1, db.szTarget, _countof(db.szTarget), NULL, NULL); 724 lstrcpynW(db.szwTarget, argv[i], _countof(db.szwTarget)); 725 if (SUCCEEDED(hr = AddDataBlock(pSL, db))) 726 hr = AddSLDF(pSL, SLDF_HAS_EXP_ICON_SZ); 727 } 728 else if (!StrCmpIW(argv[i], L"/RemoveExpIcon")) 729 { 730 hr = RemoveDatablock(pSL, EXP_SZ_ICON_SIG, SLDF_HAS_EXP_ICON_SZ); 731 } 732 else if (!StrCmpIW(argv[i], L"/SpecialFolderOffset") && i + 2 < argc) 733 { 734 EXP_SPECIAL_FOLDER db = { sizeof(db), EXP_SPECIAL_FOLDER_SIG }; 735 if ((db.idSpecialFolder = StrToNum(argv[++i])) == 0) 736 { 737 if (!StrCmpIW(argv[i], L"Windows")) 738 db.idSpecialFolder = CSIDL_WINDOWS; 739 if (!StrCmpIW(argv[i], L"System")) 740 db.idSpecialFolder = CSIDL_SYSTEM; 741 } 742 db.cbOffset = StrToNum(argv[++i]); 743 if ((signed)db.cbOffset < 0 && TryGetUpdatedPidl(pSL, pidlTarget)) 744 { 745 UINT i = 0, c = -(signed)db.cbOffset; 746 db.cbOffset = 0; 747 for (PIDLIST_ABSOLUTE pidl = pidlTarget; i < c && pidl->mkid.cb; ++i, pidl = ILGetNext(pidl)) 748 db.cbOffset += pidl->mkid.cb; 749 } 750 hr = AddDataBlock(pSL, db); 751 } 752 else if (!StrCmpIW(argv[i], L"/RemoveDatablock")) 753 { 754 if (UINT sig = GetDatablockSignature(argv[++i])) 755 hr = RemoveDatablock(pSL, sig); 756 } 757 else if (!StrCmpIW(argv[i], L"/AddSLDF") && i + 1 < argc) 758 { 759 bool Force = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; 760 UINT Flag = GetSLDF(argv[++i]); 761 if (Flag > SLDF_UNICODE) 762 hr = AddSLDF(pSL, Flag); 763 if (Force) 764 ForceAddSLDF |= Flag; 765 } 766 else if (!StrCmpIW(argv[i], L"/RemoveSLDF") && i + 1 < argc) 767 { 768 bool Force = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; 769 UINT Flag = GetSLDF(argv[++i]); 770 if (Flag) 771 hr = RemoveSLDF(pSL, Flag); 772 if (Force) 773 ForceRemoveSLDF |= Flag; 774 } 775 else if (!StrCmpIW(argv[i], L"/Dump")) 776 { 777 DumpResult++; 778 hr = S_OK; 779 } 780 else if (!StrCmpIW(argv[i], L"/ForceCreate")) 781 { 782 TargetCount++; 783 hr = S_OK; 784 } 785 else 786 { 787 wprintf(L"%hsUnable to parse \"%ls\"!\n", "Error: ", argv[i]); 788 } 789 } 790 791 if (SUCCEEDED(hr)) 792 { 793 if (TargetCount) 794 { 795 if (SUCCEEDED(hr = Save(pSL, LnkPath)) && (ForceAddSLDF | ForceRemoveSLDF)) 796 { 797 IStream *pStream; 798 if (SUCCEEDED(Open(LnkPath, &pStream, NULL, STGM_READWRITE))) 799 { 800 SHELL_LINK_HEADER slh; 801 if (SUCCEEDED(IStream_Read(pStream, &slh, sizeof(slh)))) 802 { 803 slh.dwFlags = (slh.dwFlags & ~ForceRemoveSLDF) | ForceAddSLDF; 804 if (SUCCEEDED(Seek(pStream, 0, FILE_BEGIN))) 805 IStream_Write(pStream, &slh, sizeof(slh)); 806 } 807 pStream->Release(); 808 } 809 } 810 } 811 else 812 { 813 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 814 } 815 816 if (SUCCEEDED(hr) && DumpResult) 817 DumpCommand(LnkPath); 818 } 819 pSL->Release(); 820 ILFree(pidlTarget); 821 return hr; 822} 823 824static HRESULT RemoveDatablockCommand(PCWSTR LnkPath, UINT argc, WCHAR **argv) 825{ 826 IShellLink *pSL; 827 HRESULT hr = Open(LnkPath, NULL, &pSL, STGM_READWRITE); 828 if (FAILED(hr)) 829 return hr; 830 831 hr = E_INVALIDARG; 832 for (UINT i = 0; i < argc; ++i) 833 { 834 UINT Sig = GetDatablockSignature(argv[i]); 835 if (!Sig) 836 { 837 hr = E_INVALIDARG; 838 break; 839 } 840 hr = RemoveDatablock(pSL, Sig); 841 } 842 843 if (SUCCEEDED(hr)) 844 hr = Save(pSL, LnkPath); 845 pSL->Release(); 846 return hr; 847} 848 849static int ProcessCommandLine(int argc, WCHAR **argv) 850{ 851 if (argc < 3) 852 { 853 wprintf(L"%hs", g_Usage); 854 return argc < 2 ? 0 : ERROR_INVALID_PARAMETER; 855 } 856 857 if (!StrCmpIW(argv[1], L"Dump")) 858 return SuccessOrReportError(DumpCommand(argv[2])); 859 if (!StrCmpIW(argv[1], L"Create")) 860 return SuccessOrReportError(CreateCommand(argv[2], argc - 3, &argv[3])); 861 if (!StrCmpIW(argv[1], L"RemoveDatablock")) 862 return SuccessOrReportError(RemoveDatablockCommand(argv[2], argc - 3, &argv[3])); 863 864 return SuccessOrReportError(ERROR_INVALID_PARAMETER); 865} 866 867EXTERN_C int wmain(int argc, WCHAR **argv) 868{ 869 HRESULT hrInit = OleInitialize(NULL); 870 int result = ProcessCommandLine(argc, argv); 871 if (SUCCEEDED(hrInit)) 872 OleUninitialize(); 873 return result; 874}