From 55092de2802d8d3890970df54d31ab2c24ee358a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herm=C3=A8s=20B=C3=A9lusca-Ma=C3=AFto?= Date: Mon, 27 Apr 2026 17:46:46 +0200 Subject: [PATCH] [MKSHELLLINK] Enhance the code (#8936) - Use the `bool` type from `stdbool.h` - Update "Specification" URL, using instead the "[MS-SHLLINK]: Shell Link (.LNK) Binary File Format" - Document which structures in `undocshell.h` correspond to those in MKSHELLLINK. - Add `SLDF_*` constants from PSDK `shlobj.h` for consumption, and alias the tool-specific `LINK_*` ones to these. - The first member of the `LNK_HEADER`/`SHELL_LINK_HEADER` structure is not a signature, but its size. Fix the `FILETIME` fields ordering. The `IconIndex` member is signed. The `HotKey` member is a 16-bits value. - The third member of the `ID_LIST_GUID`/`tagGUIDStruct` structure is not "dummy", but is a sort-order. Add also some known corresponding values, and unhardcode the value used in the code. - Make the `is_path_separator()` return a `bool`, and use the helper more thoroughly in the code. - Improve the help/usage text shown by the tool. - In case an invalid/unknown argument is encountered, set the `bHelp` flag just after showing the error, so as to display the tool usage and bail out early. - Improve/clarify some code comments. - Update the file header notice. --- sdk/tools/mkshelllink/mkshelllink.c | 259 +++++++++++++++++----------- 1 file changed, 161 insertions(+), 98 deletions(-) diff --git a/sdk/tools/mkshelllink/mkshelllink.c b/sdk/tools/mkshelllink/mkshelllink.c index c01ad361db8..770ab751ef7 100644 --- a/sdk/tools/mkshelllink/mkshelllink.c +++ b/sdk/tools/mkshelllink/mkshelllink.c @@ -1,14 +1,22 @@ -/* COPYRIGHT: See COPYING in the top level directory - * PROJECT: ReactOS Shell Link maker - * FILE: tools/mkshelllink/mkshelllink.c - * PURPOSE: Shell Link maker - * PROGRAMMER: Rafal Harabien +/* + * PROJECT: ReactOS Shell Link maker + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Shell Link maker + * COPYRIGHT: Copyright 2011 Rafal Harabien + * Copyright 2024 Whindmar Saksit */ +/* INCLUDES ******************************************************************/ + +#include #include #include #include -#include +#include // For isalpha(), isdigit() + +#ifndef C_ASSERT +#define C_ASSERT(expr) extern char (*c_assert(void)) [(expr) ? 1 : -1] +#endif #ifndef _MSC_VER #include @@ -16,67 +24,90 @@ typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; #endif #ifdef _WIN32 #define strcasecmp _stricmp #endif + +/* SHELL LINK DEFINITIONS ****************************************************/ + #define SW_SHOWNORMAL 1 #define SW_SHOWMINNOACTIVE 7 -#define CSIDL_WINDOWS 0x24 -#define CSIDL_SYSTEM 0x25 typedef struct _GUID { uint32_t Data1; - uint16_t Data2; - uint16_t Data3; + uint16_t Data2; + uint16_t Data3; uint8_t Data4[8]; } GUID; +C_ASSERT(sizeof(GUID) == 16); -typedef struct _FILETIME { +typedef struct _FILETIME +{ uint32_t dwLowDateTime; uint32_t dwHighDateTime; } FILETIME, *PFILETIME; #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 } } -DEFINE_GUID2(CLSID_ShellLink,0x00021401L,0,0,0xC0,0,0,0,0,0,0,0x46); +DEFINE_GUID2(CLSID_ShellLink, 0x00021401,0,0,0xC0,0,0,0,0,0,0,0x46); DEFINE_GUID2(CLSID_MyComputer,0x20D04FE0,0x3AEA,0x1069,0xA2,0xD8,0x08,0x00,0x2B,0x30,0x30,0x9D); -#define LINK_ID_LIST 0x01 -#define LINK_FILE 0x02 -#define LINK_DESCRIPTION 0x04 -#define LINK_RELATIVE_PATH 0x08 -#define LINK_WORKING_DIR 0x10 -#define LINK_CMD_LINE_ARGS 0x20 -#define LINK_ICON 0x40 -#define LINK_UNICODE 0x80 - #define LOCATOR_LOCAL 0x1 #define LOCATOR_NETWORK 0x2 #pragma pack(push, 1) -/* Specification: http://ithreats.files.wordpress.com/2009/05/lnk_the_windows_shortcut_file_format.pdf */ +/* + * Specification: + * "[MS-SHLLINK]: Shell Link (.LNK) Binary File Format" + * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/16cb4ca1-9339-4d0c-a68d-bf1d6cc0f943 + * Definitions adapted from undocshell.h + */ +/* SHELL_LINK_HEADER flags. SHELL_LINK_DATA_FLAGS definitions taken from shlobj.h */ +#define SLDF_DEFAULT 0x00000000 +#define SLDF_HAS_ID_LIST 0x00000001 +#define SLDF_HAS_LINK_INFO 0x00000002 +#define SLDF_HAS_NAME 0x00000004 +#define SLDF_HAS_RELPATH 0x00000008 +#define SLDF_HAS_WORKINGDIR 0x00000010 +#define SLDF_HAS_ARGS 0x00000020 +#define SLDF_HAS_ICONLOCATION 0x00000040 +#define SLDF_UNICODE 0x00000080 + +#define LINK_ID_LIST SLDF_HAS_ID_LIST +#define LINK_FILE SLDF_HAS_LINK_INFO +#define LINK_DESCRIPTION SLDF_HAS_NAME +#define LINK_RELATIVE_PATH SLDF_HAS_RELPATH +#define LINK_WORKING_DIR SLDF_HAS_WORKINGDIR +#define LINK_CMDLINE_ARGS SLDF_HAS_ARGS +#define LINK_ICON SLDF_HAS_ICONLOCATION +#define LINK_UNICODE SLDF_UNICODE + +// SHELL_LINK_HEADER typedef struct _LNK_HEADER { - uint32_t Signature; + uint32_t Size; GUID Guid; uint32_t Flags; uint32_t Attributes; FILETIME CreationTime; - FILETIME ModificationTime; FILETIME LastAccessTime; - uint32_t FileSize; - uint32_t IconNr; - uint32_t Show; - uint32_t Hotkey; - uint32_t Unknown; - uint32_t Unknown2; + FILETIME LastWriteTime; + uint32_t FileSizeLow; /* Only the least significant 32 bits */ + int32_t IconIndex; + uint32_t ShowCmd; + uint16_t HotKey; + uint16_t Reserved1; + uint32_t Reserved2; + uint32_t Reserved3; } LNK_HEADER; +// SHELL_LINK_INFOA typedef struct _LNK_LOCATOR_INFO { uint32_t Size; @@ -89,6 +120,7 @@ typedef struct _LNK_LOCATOR_INFO char Data[0]; } LNK_LOCATOR_INFO; +// SHELL_LINK_INFO_VOLUME_IDA typedef struct _LNK_LOCAL_VOLUME_INFO { uint32_t Size; @@ -98,11 +130,37 @@ typedef struct _LNK_LOCAL_VOLUME_INFO char VolumeLabel[0]; } LNK_LOCAL_VOLUME_INFO; +/* For ITEMIDLIST/SHITEMID */ #define PT_GUID 0x1F #define PT_DRIVE1 0x2F #define PT_FOLDER 0x31 #define PT_VALUE 0x32 +/* uSortOrder values */ +#define REGITEMORDER_DEFAULT 0x80 +#define REGITEMORDER_LIBRARIES 0x42 +#define REGITEMORDER_USERSFILEFOLDER 0x44 +#define REGITEMORDER_MYCOMPUTER 0x50 + +// struct tagGUIDStruct +typedef struct _ID_LIST_GUID +{ + uint16_t Size; + uint8_t Type; + uint8_t uSortOrder; + GUID guid; +} ID_LIST_GUID; + +// struct tagDriveStruct +typedef struct _ID_LIST_DRIVE +{ + uint16_t Size; + uint8_t Type; + char szDriveName[20]; + uint16_t unknown; +} ID_LIST_DRIVE; + +// struct tagFileStruct typedef struct _ID_LIST_FILE { uint16_t Size; @@ -115,31 +173,25 @@ typedef struct _ID_LIST_FILE char szName[0]; } ID_LIST_FILE; -typedef struct _ID_LIST_GUID -{ - uint16_t Size; - uint8_t Type; - uint8_t dummy; - GUID guid; -} ID_LIST_GUID; - -typedef struct _ID_LIST_DRIVE -{ - uint16_t Size; - uint8_t Type; - char szDriveName[20]; - uint16_t unknown; -} ID_LIST_DRIVE; - #define EXP_SPECIAL_FOLDER_SIG 0xA0000005 typedef struct _EXP_SPECIAL_FOLDER { - uint32_t cbSize, dwSignature, idSpecialFolder, cbOffset; + uint32_t cbSize; + uint32_t dwSignature; + uint32_t idSpecialFolder; + uint32_t cbOffset; } EXP_SPECIAL_FOLDER; #pragma pack(pop) -static const struct SPECIALFOLDER { + +/* GLOBALS *******************************************************************/ + +/* For a complete list, see: https://smallvoid.com/article/winnt-shell-keyword.html */ +#define CSIDL_WINDOWS 0x24 +#define CSIDL_SYSTEM 0x25 +static const struct SPECIALFOLDER +{ unsigned char csidl; const char* name; } g_specialfolders[] = { @@ -148,14 +200,18 @@ static const struct SPECIALFOLDER { { 0, NULL} }; -static unsigned int is_path_separator(unsigned int c) + +/* FUNCTIONS *****************************************************************/ + +static bool is_path_separator(char c) { - return c == '\\' || c == '/'; + return (c == '\\' || c == '/'); } static const struct SPECIALFOLDER* get_special_folder(const char *target) { char buf[256]; + strncpy(buf, target, sizeof(buf)); buf[sizeof("shell:") - 1] = '\0'; if (strcasecmp("shell:", buf)) @@ -186,63 +242,68 @@ int main(int argc, const char *argv[]) const char *pszCmdLineArgs = NULL; const char *pszIcon = NULL; char targetpath[260]; - int IconNr = 0; + int32_t IconIndex = 0; GUID Guid = CLSID_MyComputer; - int bHelp = 0, bMinimized = 0; + bool bHelp = false, bMinimized = false; FILE *pFile; LNK_HEADER Header; uint16_t uhTmp; uint32_t dwTmp; EXP_SPECIAL_FOLDER CsidlBlock, *pCsidlBlock = NULL; + /* Parse the command-line */ for (i = 1; i < argc; ++i) { if (argv[i][0] != '-' && argv[i][0] != '/') pszTarget = argv[i]; else if (!strcmp(argv[i] + 1, "h")) - bHelp = 1; - else if (!strcmp(argv[i] + 1, "o") && i + 1 < argc) + bHelp = true; + else if (!strcmp(argv[i] + 1, "o") && (i + 1 < argc)) pszOutputPath = argv[++i]; - else if (!strcmp(argv[i] + 1, "d") && i + 1 < argc) + else if (!strcmp(argv[i] + 1, "d") && (i + 1 < argc)) pszDescription = argv[++i]; - else if (!strcmp(argv[i] + 1, "w") && i + 1 < argc) + else if (!strcmp(argv[i] + 1, "w") && (i + 1 < argc)) pszWorkingDir = argv[++i]; - else if (!strcmp(argv[i] + 1, "c") && i + 1 < argc) + else if (!strcmp(argv[i] + 1, "c") && (i + 1 < argc)) pszCmdLineArgs = argv[++i]; - else if (!strcmp(argv[i] + 1, "i") && i + 1 < argc) + else if (!strcmp(argv[i] + 1, "i") && (i + 1 < argc)) { pszIcon = argv[++i]; - if (i + 1 < argc && isdigit(argv[i + 1][0])) - IconNr = atoi(argv[++i]); + if ((i + 1 < argc) && isdigit(argv[i + 1][0])) + IconIndex = atoi(argv[++i]); } else if (!strcmp(argv[i] + 1, "m")) - bMinimized = 1; - else if (!strcmp(argv[i] + 1, "g") && i + 1 < argc) + bMinimized = true; + else if (!strcmp(argv[i] + 1, "g") && (i + 1 < argc)) { unsigned Data4Tmp[8], j; sscanf(argv[++i], "{%8x-%4hx-%4hx-%2x%2x-%2x%2x%2x%2x%2x%2x}", - &Guid.Data1, &Guid.Data2, &Guid.Data3, - &Data4Tmp[0], &Data4Tmp[1], &Data4Tmp[2], &Data4Tmp[3], - &Data4Tmp[4], &Data4Tmp[5], &Data4Tmp[6], &Data4Tmp[7]); + &Guid.Data1, &Guid.Data2, &Guid.Data3, + &Data4Tmp[0], &Data4Tmp[1], &Data4Tmp[2], &Data4Tmp[3], + &Data4Tmp[4], &Data4Tmp[5], &Data4Tmp[6], &Data4Tmp[7]); for (j = 0; j < 8; ++j) Guid.Data4[j] = (uint8_t)Data4Tmp[j]; } else + { printf("Invalid option: %s\n", argv[i]); + bHelp = true; + } } if (!pszTarget || bHelp) { - printf("Usage: %s [-o path][-d descr][-w path][-c cmd_line_args][-i icon_path [nr]][-h][-g guid] target\n" - "-o path\tSets output path\n" - "-d descr\tSets shortcut description\n" - "-w path\tSets working directory for executable\n" - "-c cmd_line_args\tSets command line arguments passed to program\n" - "-i icon_path [nr]\tSets icon file and optionally icon index\n" - "-m\tStart minimized\n" - "-g guid\tSets GUID to which target path is relative. Default value is MyComputer GUID.\n" - "target\tAbsolute or relative to guid specified with -g option path\n", argv[0]); + printf("Usage: %s [-h][-o path][-d descr][-w path][-c cmdline_args][-i icon_path [nr]][-g guid] target\n" + "-h\tShows this help.\n" + "-o path\tSets the output path.\n" + "-d descr\tSets the shortcut description.\n" + "-w path\tSets the working directory for the executable.\n" + "-c cmdline_args\tSets the command-line arguments passed to the program.\n" + "-i icon_path [nr]\tSets the icon file and optionally its index.\n" + "-m\tStart minimized.\n" + "-g guid\tSets the GUID to which the target path is relative. Default value is MyComputer GUID.\n" + "target\tAbsolute or relative to GUID specified with the -g option path.\n", argv[0]); return 0; } @@ -253,9 +314,9 @@ int main(int argc, const char *argv[]) return -1; } - // Header + /* Header */ memset(&Header, 0, sizeof(Header)); - Header.Signature = (uint32_t)'L'; + Header.Size = sizeof(Header); Header.Guid = CLSID_ShellLink; Header.Flags = LINK_ID_LIST; if (pszDescription) @@ -263,11 +324,11 @@ int main(int argc, const char *argv[]) if (pszWorkingDir) Header.Flags |= LINK_WORKING_DIR; if (pszCmdLineArgs) - Header.Flags |= LINK_CMD_LINE_ARGS; + Header.Flags |= LINK_CMDLINE_ARGS; if (pszIcon) Header.Flags |= LINK_ICON; - Header.IconNr = IconNr; - Header.Show = bMinimized ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL; + Header.IconIndex = IconIndex; + Header.ShowCmd = (bMinimized ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL); fwrite(&Header, sizeof(Header), 1, pFile); if (Header.Flags & LINK_ID_LIST) @@ -280,9 +341,8 @@ int main(int argc, const char *argv[]) int index = 1, specialindex = -1; const struct SPECIALFOLDER *special = get_special_folder(pszTarget); - // ID list - // It seems explorer does not accept links without id list. List is relative to desktop. - + /* ID list. It appears explorer does not accept links + * without an ID list. It is relative to desktop. */ if (special) { Header.Flags &= ~LINK_RELATIVE_PATH; @@ -299,14 +359,14 @@ int main(int argc, const char *argv[]) ++index; cbListSize += sizeof(IdListDrive); pszName += 2; - while (*pszName == '\\' || *pszName == '/') + while (is_path_separator(*pszName)) ++pszName; } while (*pszName) { cchName = 0; - while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/') + while (pszName[cchName] && !is_path_separator(pszName[cchName])) ++cchName; if (cchName != 1 || pszName[0] != '.') @@ -319,16 +379,17 @@ int main(int argc, const char *argv[]) } pszName += cchName; - while (*pszName == '\\' || *pszName == '/') + while (is_path_separator(*pszName)) ++pszName; } + /* ID list size */ uhTmp = cbListSize; - fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); // size + fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); IdListGuid.Size = sizeof(IdListGuid); IdListGuid.Type = PT_GUID; - IdListGuid.dummy = 0x50; + IdListGuid.uSortOrder = REGITEMORDER_MYCOMPUTER; IdListGuid.guid = Guid; fwrite(&IdListGuid, sizeof(IdListGuid), 1, pFile); @@ -342,14 +403,14 @@ int main(int argc, const char *argv[]) sprintf(IdListDrive.szDriveName, "%c:\\", pszName[0]); fwrite(&IdListDrive, sizeof(IdListDrive), 1, pFile); pszName += 2; - while(*pszName == '\\' || *pszName == '/') + while (is_path_separator(*pszName)) ++pszName; } while (*pszName) { cchName = 0; - while (pszName[cchName] && pszName[cchName] != '\\' && pszName[cchName] != '/') + while (pszName[cchName] && !is_path_separator(pszName[cchName])) ++cchName; if (cchName != 1 || pszName[0] != '.') @@ -368,17 +429,18 @@ int main(int argc, const char *argv[]) } pszName += cchName; - while (*pszName == '\\' || *pszName == '/') + while (is_path_separator(*pszName)) ++pszName; } - uhTmp = 0; // list end + /* End of ID list */ + uhTmp = 0; fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); } if (Header.Flags & LINK_DESCRIPTION) { - // Description + /* Description */ uhTmp = strlen(pszDescription); fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); fputs(pszDescription, pFile); @@ -386,7 +448,7 @@ int main(int argc, const char *argv[]) if (Header.Flags & LINK_RELATIVE_PATH) { - // Relative Path + /* Relative path */ uhTmp = strlen(pszTarget); fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); fputs(pszTarget, pFile); @@ -394,15 +456,15 @@ int main(int argc, const char *argv[]) if (Header.Flags & LINK_WORKING_DIR) { - // Working Dir + /* Working directory */ uhTmp = strlen(pszWorkingDir); fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); fputs(pszWorkingDir, pFile); } - if (Header.Flags & LINK_CMD_LINE_ARGS) + if (Header.Flags & LINK_CMDLINE_ARGS) { - // Command line arguments + /* Command-line arguments */ uhTmp = strlen(pszCmdLineArgs); fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); fputs(pszCmdLineArgs, pFile); @@ -410,15 +472,16 @@ int main(int argc, const char *argv[]) if (Header.Flags & LINK_ICON) { - // Command line arguments + /* Icon path */ uhTmp = strlen(pszIcon); fwrite(&uhTmp, sizeof(uhTmp), 1, pFile); fputs(pszIcon, pFile); } - // Extra stuff + /* Write the data block list */ if (pCsidlBlock) fwrite(pCsidlBlock, sizeof(*pCsidlBlock), 1, pFile); + /* End of data block list */ dwTmp = 0; fwrite(&dwTmp, sizeof(dwTmp), 1, pFile);