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