xref: /reactos/sdk/tools/mkshelllink/mkshelllink.c (revision 1ac9e484)
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 #define SW_SHOWNORMAL 1
22 #define SW_SHOWMINNOACTIVE 7
23 
24 typedef struct _GUID
25 {
26     uint32_t Data1;
27     uint16_t  Data2;
28     uint16_t  Data3;
29     uint8_t  Data4[8];
30 } GUID;
31 
32 typedef struct _FILETIME {
33     uint32_t dwLowDateTime;
34     uint32_t dwHighDateTime;
35 } FILETIME, *PFILETIME;
36 
37 #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 } }
38 DEFINE_GUID2(CLSID_ShellLink,0x00021401L,0,0,0xC0,0,0,0,0,0,0,0x46);
39 DEFINE_GUID2(CLSID_MyComputer,0x20D04FE0,0x3AEA,0x1069,0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D);
40 
41 #define LINK_ID_LIST       0x01
42 #define LINK_FILE          0x02
43 #define LINK_DESCRIPTION   0x04
44 #define LINK_RELATIVE_PATH 0x08
45 #define LINK_WORKING_DIR   0x10
46 #define LINK_CMD_LINE_ARGS 0x20
47 #define LINK_ICON          0x40
48 #define LINK_UNICODE       0x80
49 
50 #define LOCATOR_LOCAL   0x1
51 #define LOCATOR_NETWORK 0x2
52 
53 #pragma pack(push, 1)
54 
55 /* Specification: http://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf */
56 
57 typedef struct _LNK_HEADER
58 {
59     uint32_t Signature;
60     GUID Guid;
61     uint32_t Flags;
62     uint32_t Attributes;
63     FILETIME CreationTime;
64     FILETIME ModificationTime;
65     FILETIME LastAccessTime;
66     uint32_t FileSize;
67     uint32_t IconNr;
68     uint32_t Show;
69     uint32_t Hotkey;
70     uint32_t Unknown;
71     uint32_t Unknown2;
72 } LNK_HEADER;
73 
74 typedef struct _LNK_LOCATOR_INFO
75 {
76     uint32_t Size;
77     uint32_t DataOffset;
78     uint32_t Flags;
79     uint32_t LocalVolumeInfoOffset;
80     uint32_t LocalBasePathnameOffset;
81     uint32_t NetworkVolumeInfoOffset;
82     uint32_t RemainingPathnameOffset;
83     char Data[0];
84 } LNK_LOCATOR_INFO;
85 
86 typedef struct _LNK_LOCAL_VOLUME_INFO
87 {
88     uint32_t Size;
89     uint32_t VolumeType; /* See GetDriveType */
90     uint32_t SerialNumber;
91     uint32_t VolumeNameOffset;
92     char VolumeLabel[0];
93 } LNK_LOCAL_VOLUME_INFO;
94 
95 #define PT_GUID     0x1F
96 #define PT_DRIVE1   0x2F
97 #define PT_FOLDER   0x31
98 #define PT_VALUE    0x32
99 
100 typedef struct _ID_LIST_FILE
101 {
102     uint16_t Size;
103     uint8_t Type;
104     uint8_t dummy;
105     uint32_t dwFileSize;
106     uint16_t uFileDate;
107     uint16_t uFileTime;
108     uint16_t uFileAttribs;
109     char szName[0];
110 } ID_LIST_FILE;
111 
112 typedef struct _ID_LIST_GUID
113 {
114     uint16_t Size;
115     uint8_t Type;
116     uint8_t dummy;
117     GUID guid;
118 } ID_LIST_GUID;
119 
120 typedef struct _ID_LIST_DRIVE
121 {
122     uint16_t Size;
123     uint8_t Type;
124     char szDriveName[20];
125     uint16_t unknown;
126 } ID_LIST_DRIVE;
127 
128 #pragma pack(pop)
129 
130 int main(int argc, const char *argv[])
131 {
132     int i;
133     const char *pszOutputPath = "shortcut.lnk";
134     const char *pszTarget = NULL;
135     const char *pszDescription = "Description";
136     const char *pszWorkingDir = NULL;
137     const char *pszCmdLineArgs = NULL;
138     const char *pszIcon = NULL;
139     int IconNr = 0;
140     GUID Guid = CLSID_MyComputer;
141     int bHelp = 0, bMinimized = 0;
142     FILE *pFile;
143     LNK_HEADER Header;
144     uint16_t uhTmp;
145     uint32_t dwTmp;
146 
147     for (i = 1; i < argc; ++i)
148     {
149         if (argv[i][0] != '-' && argv[i][0] != '/')
150             pszTarget = argv[i];
151         else if (!strcmp(argv[i] + 1, "h"))
152             bHelp = 1;
153         else if (!strcmp(argv[i] + 1, "o") && i + 1 < argc)
154             pszOutputPath = argv[++i];
155         else if (!strcmp(argv[i] + 1, "d") && i + 1 < argc)
156             pszDescription = argv[++i];
157         else if (!strcmp(argv[i] + 1, "w") && i + 1 < argc)
158             pszWorkingDir = argv[++i];
159         else if (!strcmp(argv[i] + 1, "c") && i + 1 < argc)
160             pszCmdLineArgs = argv[++i];
161         else if (!strcmp(argv[i] + 1, "i") && i + 1 < argc)
162         {
163             pszIcon = argv[++i];
164             if (i + 1 < argc && isdigit(argv[i + 1][0]))
165                 IconNr = atoi(argv[++i]);
166         }
167         else if (!strcmp(argv[i] + 1, "m"))
168             bMinimized = 1;
169         else if (!strcmp(argv[i] + 1, "g") && i + 1 < argc)
170         {
171             unsigned Data4Tmp[8], j;
172 
173             sscanf(argv[++i], "{%8x-%4hx-%4hx-%2x%2x-%2x%2x%2x%2x%2x%2x}",
174                   &Guid.Data1, &Guid.Data2, &Guid.Data3,
175                   &Data4Tmp[0], &Data4Tmp[1], &Data4Tmp[2], &Data4Tmp[3],
176                   &Data4Tmp[4], &Data4Tmp[5], &Data4Tmp[6], &Data4Tmp[7]);
177             for (j = 0; j < 8; ++j)
178                 Guid.Data4[j] = (uint8_t)Data4Tmp[j];
179         }
180         else
181             printf("Invalid option: %s\n", argv[i]);
182     }
183 
184     if (!pszTarget || bHelp)
185     {
186         printf("Usage: %s [-o path][-d descr][-w path][-c cmd_line_args][-i icon_path [nr]][-h][-g guid] target\n"
187                "-o path\tSets output path\n"
188                "-d descr\tSets shortcut description\n"
189                "-w path\tSets working directory for executable\n"
190                "-c cmd_line_args\tSets command line arguments passed to program\n"
191                "-i icon_path [nr]\tSets icon file and optionally icon index\n"
192                "-m\tStart minimized\n"
193                "-g guid\tSets GUID to which target path is relative. Default value is MyComputer GUID.\n"
194                "target\tAbsolute or relative to guid specified with -g option path\n", argv[0]);
195         return 0;
196     }
197 
198     pFile = fopen(pszOutputPath, "wb");
199     if (!pFile)
200     {
201         printf("Failed to open %s\n", pszOutputPath);
202         return -1;
203     }
204 
205     // Header
206     memset(&Header, 0, sizeof(Header));
207     Header.Signature = (uint32_t)'L';
208     Header.Guid = CLSID_ShellLink;
209     Header.Flags = LINK_ID_LIST;
210     if (pszDescription)
211         Header.Flags |= LINK_DESCRIPTION;
212     if (pszWorkingDir)
213         Header.Flags |= LINK_WORKING_DIR;
214     if (pszCmdLineArgs)
215         Header.Flags |= LINK_CMD_LINE_ARGS;
216     if (pszIcon)
217         Header.Flags |= LINK_ICON;
218     Header.IconNr = IconNr;
219     Header.Show = bMinimized ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL;
220     fwrite(&Header, sizeof(Header), 1, pFile);
221 
222     if (Header.Flags & LINK_ID_LIST)
223     {
224         ID_LIST_FILE IdListFile;
225         ID_LIST_GUID IdListGuid;
226         ID_LIST_DRIVE IdListDrive;
227         unsigned cbListSize = sizeof(IdListGuid) + sizeof(uint16_t), cchName;
228         const char *pszName = pszTarget;
229 
230         // ID list
231         // It seems explorer does not accept links without id list. List is relative to desktop.
232 
233         pszName = pszTarget;
234 
235         if (pszName[0] && pszName[1] == ':')
236         {
237             cbListSize += sizeof(IdListDrive);
238             pszName += 2;
239             while (*pszName == '\\' || *pszName == '/')
240                 ++pszName;
241         }
242 
243         while (*pszName)
244         {
245             cchName = 0;
246             while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/')
247                 ++cchName;
248 
249             if (cchName != 1 || pszName[0] != '.')
250                 cbListSize += sizeof(IdListFile) + 2 * (cchName + 1);
251 
252             pszName += cchName;
253             while (*pszName == '\\' || *pszName == '/')
254                 ++pszName;
255         }
256 
257         uhTmp = cbListSize;
258         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); // size
259 
260         IdListGuid.Size = sizeof(IdListGuid);
261         IdListGuid.Type = PT_GUID;
262         IdListGuid.dummy = 0x50;
263         IdListGuid.guid = Guid;
264         fwrite(&IdListGuid, sizeof(IdListGuid), 1, pFile);
265 
266         pszName = pszTarget;
267 
268         if (isalpha(pszName[0]) && pszName[1] == ':')
269         {
270             memset(&IdListDrive, 0, sizeof(IdListDrive));
271             IdListDrive.Size = sizeof(IdListDrive);
272             IdListDrive.Type = PT_DRIVE1;
273             sprintf(IdListDrive.szDriveName, "%c:\\", pszName[0]);
274             fwrite(&IdListDrive, sizeof(IdListDrive), 1, pFile);
275             pszName += 2;
276             while(*pszName == '\\' || *pszName == '/')
277                 ++pszName;
278         }
279 
280         while (*pszName)
281         {
282             cchName = 0;
283             while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/')
284                 ++cchName;
285 
286             if (cchName != 1 || pszName[0] != '.')
287             {
288                 memset(&IdListFile, 0, sizeof(IdListFile));
289                 IdListFile.Size = sizeof(IdListFile) + 2 * (cchName + 1);
290                 if (!pszName[cchName])
291                     IdListFile.Type = PT_VALUE; // File
292                 else
293                     IdListFile.Type = PT_FOLDER;
294                 fwrite(&IdListFile, sizeof(IdListFile), 1, pFile);
295                 fwrite(pszName, cchName, 1, pFile);
296                 fputc(0, pFile);
297                 fwrite(pszName, cchName, 1, pFile);
298                 fputc(0, pFile);
299             }
300 
301             pszName += cchName;
302             while (*pszName == '\\' || *pszName == '/')
303                 ++pszName;
304         }
305 
306         uhTmp = 0; // list end
307         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
308     }
309 
310     if (Header.Flags & LINK_DESCRIPTION)
311     {
312         // Dscription
313         uhTmp = strlen(pszDescription);
314         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
315         fputs(pszDescription, pFile);
316     }
317 
318     if (Header.Flags & LINK_RELATIVE_PATH)
319     {
320         // Relative Path
321         uhTmp = strlen(pszTarget);
322         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
323         fputs(pszTarget, pFile);
324     }
325 
326     if (Header.Flags & LINK_WORKING_DIR)
327     {
328         // Working Dir
329         uhTmp = strlen(pszWorkingDir);
330         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
331         fputs(pszWorkingDir, pFile);
332     }
333 
334     if (Header.Flags & LINK_CMD_LINE_ARGS)
335     {
336         // Command line arguments
337         uhTmp = strlen(pszCmdLineArgs);
338         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
339         fputs(pszCmdLineArgs, pFile);
340     }
341 
342     if (Header.Flags & LINK_ICON)
343     {
344         // Command line arguments
345         uhTmp = strlen(pszIcon);
346         fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
347         fputs(pszIcon, pFile);
348     }
349 
350     // Extra stuff
351     dwTmp = 0;
352     fwrite(&dwTmp, sizeof(dwTmp), 1, pFile);
353 
354     fclose(pFile);
355 
356     return 0;
357 }
358