xref: /reactos/sdk/tools/mkshelllink/mkshelllink.c (revision 4e5e72fa)
1 /* COPYRIGHT:       See COPYING in the top level directory
2  * PROJECT:         ReactOS Shell Link maker
3  * FILE:            tools/mkshelllink/mkshelllink.c
4  * PURPOSE:         Shell Link maker
5  * PROGRAMMER:      Rafal Harabien
6  */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 
13 #ifndef _MSC_VER
14 #include <stdint.h>
15 #else
16 typedef unsigned __int8  uint8_t;
17 typedef unsigned __int16 uint16_t;
18 typedef unsigned __int32 uint32_t;
19 #endif
20 
21 #ifdef _WIN32
22 #define strcasecmp _stricmp
23 #endif
24 
25 #define SW_SHOWNORMAL 1
26 #define SW_SHOWMINNOACTIVE 7
27 #define CSIDL_WINDOWS 0x24
28 #define CSIDL_SYSTEM 0x25
29 
30 typedef struct _GUID
31 {
32     uint32_t Data1;
33     uint16_t  Data2;
34     uint16_t  Data3;
35     uint8_t  Data4[8];
36 } GUID;
37 
38 typedef struct _FILETIME {
39     uint32_t dwLowDateTime;
40     uint32_t dwHighDateTime;
41 } FILETIME, *PFILETIME;
42 
43 #define DEFINE_GUID2(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) const GUID name = { l,w1,w2,{ b1,b2,b3,b4,b5,b6,b7,b8 } }
44 DEFINE_GUID2(CLSID_ShellLink,0x00021401L,0,0,0xC0,0,0,0,0,0,0,0x46);
45 DEFINE_GUID2(CLSID_MyComputer,0x20D04FE0,0x3AEA,0x1069,0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D);
46 
47 #define LINK_ID_LIST       0x01
48 #define LINK_FILE          0x02
49 #define LINK_DESCRIPTION   0x04
50 #define LINK_RELATIVE_PATH 0x08
51 #define LINK_WORKING_DIR   0x10
52 #define LINK_CMD_LINE_ARGS 0x20
53 #define LINK_ICON          0x40
54 #define LINK_UNICODE       0x80
55 
56 #define LOCATOR_LOCAL   0x1
57 #define LOCATOR_NETWORK 0x2
58 
59 #pragma pack(push, 1)
60 
61 /* Specification: http://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf */
62 
63 typedef struct _LNK_HEADER
64 {
65     uint32_t Signature;
66     GUID Guid;
67     uint32_t Flags;
68     uint32_t Attributes;
69     FILETIME CreationTime;
70     FILETIME ModificationTime;
71     FILETIME LastAccessTime;
72     uint32_t FileSize;
73     uint32_t IconNr;
74     uint32_t Show;
75     uint32_t Hotkey;
76     uint32_t Unknown;
77     uint32_t Unknown2;
78 } LNK_HEADER;
79 
80 typedef struct _LNK_LOCATOR_INFO
81 {
82     uint32_t Size;
83     uint32_t DataOffset;
84     uint32_t Flags;
85     uint32_t LocalVolumeInfoOffset;
86     uint32_t LocalBasePathnameOffset;
87     uint32_t NetworkVolumeInfoOffset;
88     uint32_t RemainingPathnameOffset;
89     char Data[0];
90 } LNK_LOCATOR_INFO;
91 
92 typedef struct _LNK_LOCAL_VOLUME_INFO
93 {
94     uint32_t Size;
95     uint32_t VolumeType; /* See GetDriveType */
96     uint32_t SerialNumber;
97     uint32_t VolumeNameOffset;
98     char VolumeLabel[0];
99 } LNK_LOCAL_VOLUME_INFO;
100 
101 #define PT_GUID     0x1F
102 #define PT_DRIVE1   0x2F
103 #define PT_FOLDER   0x31
104 #define PT_VALUE    0x32
105 
106 typedef struct _ID_LIST_FILE
107 {
108     uint16_t Size;
109     uint8_t Type;
110     uint8_t dummy;
111     uint32_t dwFileSize;
112     uint16_t uFileDate;
113     uint16_t uFileTime;
114     uint16_t uFileAttribs;
115     char szName[0];
116 } ID_LIST_FILE;
117 
118 typedef struct _ID_LIST_GUID
119 {
120     uint16_t Size;
121     uint8_t Type;
122     uint8_t dummy;
123     GUID guid;
124 } ID_LIST_GUID;
125 
126 typedef struct _ID_LIST_DRIVE
127 {
128     uint16_t Size;
129     uint8_t Type;
130     char szDriveName[20];
131     uint16_t unknown;
132 } ID_LIST_DRIVE;
133 
134 #define EXP_SPECIAL_FOLDER_SIG 0xA0000005
135 typedef struct _EXP_SPECIAL_FOLDER
136 {
137     uint32_t cbSize, dwSignature, idSpecialFolder, cbOffset;
138 } EXP_SPECIAL_FOLDER;
139 
140 #pragma pack(pop)
141 
142 static const struct SPECIALFOLDER {
143     unsigned char csidl;
144     const char* name;
145 } g_specialfolders[] = {
146     { CSIDL_WINDOWS, "windows" },
147     { CSIDL_SYSTEM, "system" },
148     { 0, NULL}
149 };
150 
151 static unsigned int is_path_separator(unsigned int c)
152 {
153     return c == '\\' || c == '/';
154 }
155 
156 static const struct SPECIALFOLDER* get_special_folder(const char *target)
157 {
158     char buf[256];
159     strncpy(buf, target, sizeof(buf));
160     buf[sizeof("shell:") - 1] = '\0';
161     if (strcasecmp("shell:", buf))
162         return NULL;
163 
164     target += sizeof("shell:") - 1;
165     for (unsigned long i = 0;; ++i)
166     {
167         unsigned long len;
168         const struct SPECIALFOLDER *special = &g_specialfolders[i];
169         if (!special->name)
170             return NULL;
171         len = strlen(special->name);
172         strncpy(buf, target, sizeof(buf));
173         buf[len] = '\0';
174         if (!strcasecmp(special->name, buf) && (is_path_separator(target[len]) || !target[len]))
175             return &g_specialfolders[i];
176     }
177 }
178 
179 int main(int argc, const char *argv[])
180 {
181     int i;
182     const char *pszOutputPath = "shortcut.lnk";
183     const char *pszTarget = NULL;
184     const char *pszDescription = NULL;
185     const char *pszWorkingDir = NULL;
186     const char *pszCmdLineArgs = NULL;
187     const char *pszIcon = NULL;
188     char targetpath[260];
189     int IconNr = 0;
190     GUID Guid = CLSID_MyComputer;
191     int bHelp = 0, bMinimized = 0;
192     FILE *pFile;
193     LNK_HEADER Header;
194     uint16_t uhTmp;
195     uint32_t dwTmp;
196     EXP_SPECIAL_FOLDER CsidlBlock, *pCsidlBlock = NULL;
197 
198     for (i = 1; i < argc; ++i)
199     {
200         if (argv[i][0] != '-' && argv[i][0] != '/')
201             pszTarget = argv[i];
202         else if (!strcmp(argv[i] + 1, "h"))
203             bHelp = 1;
204         else if (!strcmp(argv[i] + 1, "o") && i + 1 < argc)
205             pszOutputPath = argv[++i];
206         else if (!strcmp(argv[i] + 1, "d") && i + 1 < argc)
207             pszDescription = argv[++i];
208         else if (!strcmp(argv[i] + 1, "w") && i + 1 < argc)
209             pszWorkingDir = argv[++i];
210         else if (!strcmp(argv[i] + 1, "c") && i + 1 < argc)
211             pszCmdLineArgs = argv[++i];
212         else if (!strcmp(argv[i] + 1, "i") && i + 1 < argc)
213         {
214             pszIcon = argv[++i];
215             if (i + 1 < argc && isdigit(argv[i + 1][0]))
216                 IconNr = atoi(argv[++i]);
217         }
218         else if (!strcmp(argv[i] + 1, "m"))
219             bMinimized = 1;
220         else if (!strcmp(argv[i] + 1, "g") && i + 1 < argc)
221         {
222             unsigned Data4Tmp[8], j;
223 
224             sscanf(argv[++i], "{%8x-%4hx-%4hx-%2x%2x-%2x%2x%2x%2x%2x%2x}",
225                   &Guid.Data1, &Guid.Data2, &Guid.Data3,
226                   &Data4Tmp[0], &Data4Tmp[1], &Data4Tmp[2], &Data4Tmp[3],
227                   &Data4Tmp[4], &Data4Tmp[5], &Data4Tmp[6], &Data4Tmp[7]);
228             for (j = 0; j < 8; ++j)
229                 Guid.Data4[j] = (uint8_t)Data4Tmp[j];
230         }
231         else
232             printf("Invalid option: %s\n", argv[i]);
233     }
234 
235     if (!pszTarget || bHelp)
236     {
237         printf("Usage: %s [-o path][-d descr][-w path][-c cmd_line_args][-i icon_path [nr]][-h][-g guid] target\n"
238                "-o path\tSets output path\n"
239                "-d descr\tSets shortcut description\n"
240                "-w path\tSets working directory for executable\n"
241                "-c cmd_line_args\tSets command line arguments passed to program\n"
242                "-i icon_path [nr]\tSets icon file and optionally icon index\n"
243                "-m\tStart minimized\n"
244                "-g guid\tSets GUID to which target path is relative. Default value is MyComputer GUID.\n"
245                "target\tAbsolute or relative to guid specified with -g option path\n", argv[0]);
246         return 0;
247     }
248 
249     pFile = fopen(pszOutputPath, "wb");
250     if (!pFile)
251     {
252         printf("Failed to open %s\n", pszOutputPath);
253         return -1;
254     }
255 
256     // Header
257     memset(&Header, 0, sizeof(Header));
258     Header.Signature = (uint32_t)'L';
259     Header.Guid = CLSID_ShellLink;
260     Header.Flags = LINK_ID_LIST;
261     if (pszDescription)
262         Header.Flags |= LINK_DESCRIPTION;
263     if (pszWorkingDir)
264         Header.Flags |= LINK_WORKING_DIR;
265     if (pszCmdLineArgs)
266         Header.Flags |= LINK_CMD_LINE_ARGS;
267     if (pszIcon)
268         Header.Flags |= LINK_ICON;
269     Header.IconNr = IconNr;
270     Header.Show = bMinimized ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL;
271     fwrite(&Header, sizeof(Header), 1, pFile);
272 
273     if (Header.Flags & LINK_ID_LIST)
274     {
275         ID_LIST_FILE IdListFile;
276         ID_LIST_GUID IdListGuid;
277         ID_LIST_DRIVE IdListDrive;
278         unsigned cbListSize = sizeof(IdListGuid) + sizeof(uint16_t), cchName;
279         const char *pszName = pszTarget;
280         int index = 1, specialindex = -1;
281         const struct SPECIALFOLDER *special = get_special_folder(pszTarget);
282 
283         // ID list
284         // It seems explorer does not accept links without id list. List is relative to desktop.
285 
286         if (special)
287         {
288             Header.Flags &= ~LINK_RELATIVE_PATH;
289             CsidlBlock.cbSize = sizeof(CsidlBlock);
290             CsidlBlock.dwSignature = EXP_SPECIAL_FOLDER_SIG;
291             CsidlBlock.idSpecialFolder = special->csidl;
292             specialindex = 3; // Skip GUID, drive and fake windows/reactos folder
293             sprintf(targetpath, "x:\\reactos\\%s", pszTarget + sizeof("shell:") + strlen(special->name));
294             pszName = pszTarget = targetpath;
295         }
296 
297         if (pszName[0] && pszName[0] != ':' && pszName[1] == ':')
298         {
299             ++index;
300             cbListSize += sizeof(IdListDrive);
301             pszName += 2;
302             while (*pszName == '\\' || *pszName == '/')
303                 ++pszName;
304         }
305 
306         while (*pszName)
307         {
308             cchName = 0;
309             while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/')
310                 ++cchName;
311 
312             if (cchName != 1 || pszName[0] != '.')
313                 cbListSize += sizeof(IdListFile) + 2 * (cchName + 1);
314 
315             if (++index == specialindex)
316             {
317                 CsidlBlock.cbOffset = cbListSize - sizeof(uint16_t);
318                 pCsidlBlock = &CsidlBlock;
319             }
320 
321             pszName += cchName;
322             while (*pszName == '\\' || *pszName == '/')
323                 ++pszName;
324         }
325 
326         uhTmp = cbListSize;
327         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); // size
328 
329         IdListGuid.Size = sizeof(IdListGuid);
330         IdListGuid.Type = PT_GUID;
331         IdListGuid.dummy = 0x50;
332         IdListGuid.guid = Guid;
333         fwrite(&IdListGuid, sizeof(IdListGuid), 1, pFile);
334 
335         pszName = pszTarget;
336 
337         if (isalpha(pszName[0]) && pszName[1] == ':')
338         {
339             memset(&IdListDrive, 0, sizeof(IdListDrive));
340             IdListDrive.Size = sizeof(IdListDrive);
341             IdListDrive.Type = PT_DRIVE1;
342             sprintf(IdListDrive.szDriveName, "%c:\\", pszName[0]);
343             fwrite(&IdListDrive, sizeof(IdListDrive), 1, pFile);
344             pszName += 2;
345             while(*pszName == '\\' || *pszName == '/')
346                 ++pszName;
347         }
348 
349         while (*pszName)
350         {
351             cchName = 0;
352             while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/')
353                 ++cchName;
354 
355             if (cchName != 1 || pszName[0] != '.')
356             {
357                 memset(&IdListFile, 0, sizeof(IdListFile));
358                 IdListFile.Size = sizeof(IdListFile) + 2 * (cchName + 1);
359                 if (!pszName[cchName])
360                     IdListFile.Type = PT_VALUE; // File
361                 else
362                     IdListFile.Type = PT_FOLDER;
363                 fwrite(&IdListFile, sizeof(IdListFile), 1, pFile);
364                 fwrite(pszName, cchName, 1, pFile);
365                 fputc(0, pFile);
366                 fwrite(pszName, cchName, 1, pFile);
367                 fputc(0, pFile);
368             }
369 
370             pszName += cchName;
371             while (*pszName == '\\' || *pszName == '/')
372                 ++pszName;
373         }
374 
375         uhTmp = 0; // list end
376         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
377     }
378 
379     if (Header.Flags & LINK_DESCRIPTION)
380     {
381         // Description
382         uhTmp = strlen(pszDescription);
383         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
384         fputs(pszDescription, pFile);
385     }
386 
387     if (Header.Flags & LINK_RELATIVE_PATH)
388     {
389         // Relative Path
390         uhTmp = strlen(pszTarget);
391         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
392         fputs(pszTarget, pFile);
393     }
394 
395     if (Header.Flags & LINK_WORKING_DIR)
396     {
397         // Working Dir
398         uhTmp = strlen(pszWorkingDir);
399         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
400         fputs(pszWorkingDir, pFile);
401     }
402 
403     if (Header.Flags & LINK_CMD_LINE_ARGS)
404     {
405         // Command line arguments
406         uhTmp = strlen(pszCmdLineArgs);
407         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
408         fputs(pszCmdLineArgs, pFile);
409     }
410 
411     if (Header.Flags & LINK_ICON)
412     {
413         // Command line arguments
414         uhTmp = strlen(pszIcon);
415         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
416         fputs(pszIcon, pFile);
417     }
418 
419     // Extra stuff
420     if (pCsidlBlock)
421         fwrite(pCsidlBlock, sizeof(*pCsidlBlock), 1, pFile);
422     dwTmp = 0;
423     fwrite(&dwTmp, sizeof(dwTmp), 1, pFile);
424 
425     fclose(pFile);
426 
427     return 0;
428 }
429