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