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
is_path_separator(unsigned int c)151 static unsigned int is_path_separator(unsigned int c)
152 {
153 return c == '\\' || c == '/';
154 }
155
get_special_folder(const char * target)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
main(int argc,const char * argv[])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