Reactos
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}