Files
reactos/sdk/tools/mkshelllink/mkshelllink.c
T
Hermès Bélusca-Maïto a46e1e96ec [BOOTDATA][CMAKE][MKSHELLLINK] Improve shell link shortcuts creation for the LiveImage (#8936)
CORE-15156, CORE-19691, CORE-19692

Finally get rid of the livecd_start.cmd hack introduced waaaay back
in commit ff6d7b0236 (r54514)!
See also commits ea682b6909 (r54512) and 71867403fd (r54513).

For target paths, use the shell "special shell folder" syntax:
`shell:windows\...` or `shell:system\...`, introduced in commit
7b081be46d (PR #7158) by Whindmar Saksit.

Specify an explicit icon path and index for the "Read Me.lnk" shortcut.

Includes ideas from PR #7154 by Katayama Hirofumi MZ.

The generated shell links are confirmed to work on ReactOS, but also on
Windows 2003 and Windows 7.

- Change the MKSHELLLINK icon parameter syntax to be: `-i [icon_path[,nr]]`
  where, either both `icon_path` and icon index are given, separated by
  a comma ',' , or, either the `icon_path` is given but the index is
  optional (default: 0), or, only the icon index is given, in which case
  the icon path is set to the target instead.

- Use a `VERBATIM` command-line for `add_custom_command()`, so that *nix
  builds can cope with parameters containing backslashes.

- The shortcut target path, working directory, command-line arguments,
  and icon path all may specify explicit Win32 environment variables
  (like `%SystemRoot%`, `%HOMEDRIVE%`, etc.). Because these environment
  variables are specified as data given to the build tool via CMake,
  **AND** we have to workaround keeping these variables unexpanded when
  they are transmitted to the tool via CMD.EXE (on builds made on Windows),
  specify these variables in an "escaped" format, using `^%` instead:
  `^%SystemRoot^%`, etc.
  Additionally these paths may be explicitly quoted and passed that way
  to the MKSHELLLINK tool.

  In order to deal with both unquoting the strings and unescaping the
  environment variables, introduce a helper function and invoke them on
  the aforementioned strings.
2026-05-13 22:16:07 +02:00

799 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <[email protected]>
* Copyright 2024 Whindmar Saksit <[email protected]>
* Copyright 2026 Hermès Bélusca-Maïto <[email protected]>
*/
/* INCLUDES ******************************************************************/
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> // For isalpha(), isdigit()
#ifndef C_ASSERT
#define C_ASSERT(expr) extern char (*c_assert(void)) [(expr) ? 1 : -1]
#endif
/* Function attributes for GCC */
#if !defined(_MSC_VER) && !defined(__cdecl)
#define __cdecl // __attribute__((cdecl)) // warning: cdecl attribute ignored
#endif
#ifndef _MSC_VER
#include <stdint.h>
#else
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef __int32 int32_t;
#endif
C_ASSERT(sizeof(wchar_t) == sizeof(uint16_t)); // Ensure wchar_t is 16-bit (UTF16 character)
#ifndef _countof
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#ifdef _WIN32
#define strcasecmp _stricmp
#endif
/* SHELL LINK DEFINITIONS ****************************************************/
#define SW_SHOWNORMAL 1
#define SW_SHOWMINNOACTIVE 7
typedef struct _GUID
{
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
C_ASSERT(sizeof(GUID) == 16);
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, 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 LOCATOR_LOCAL 0x1
#define LOCATOR_NETWORK 0x2
#pragma pack(push, 1)
/*
* 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 SLDF_HAS_EXP_SZ 0x00000200
#define SLDF_HAS_EXP_ICON_SZ 0x00004000
#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 Size;
GUID Guid;
uint32_t Flags;
uint32_t Attributes;
FILETIME CreationTime;
FILETIME LastAccessTime;
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;
uint32_t DataOffset;
uint32_t Flags;
uint32_t LocalVolumeInfoOffset;
uint32_t LocalBasePathnameOffset;
uint32_t NetworkVolumeInfoOffset;
uint32_t RemainingPathnameOffset;
char Data[0];
} LNK_LOCATOR_INFO;
// SHELL_LINK_INFO_VOLUME_IDA
typedef struct _LNK_LOCAL_VOLUME_INFO
{
uint32_t Size;
uint32_t VolumeType; /* See GetDriveType */
uint32_t SerialNumber;
uint32_t VolumeNameOffset;
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;
uint8_t Type;
uint8_t dummy;
uint32_t dwFileSize;
uint16_t uFileDate;
uint16_t uFileTime;
uint16_t uFileAttribs;
/* Here are coming two strings. The first is the long name.
The second the dos name when needed or just 0x00 */
char szName[0];
} ID_LIST_FILE;
#define EXP_SPECIAL_FOLDER_SIG 0xA0000005
typedef struct _EXP_SPECIAL_FOLDER
{
uint32_t cbSize;
uint32_t dwSignature;
uint32_t idSpecialFolder;
uint32_t cbOffset;
} EXP_SPECIAL_FOLDER;
#define MAX_PATH 260
#define EXP_SZ_LINK_SIG 0xA0000001
#define EXP_SZ_ICON_SIG 0xA0000007
typedef struct _EXP_SZ_LINK
{
uint32_t cbSize;
uint32_t dwSignature;
char szTarget[MAX_PATH];
wchar_t szwTarget[MAX_PATH];
} EXP_SZ_LINK;
#pragma pack(pop)
/* GLOBALS *******************************************************************/
static bool g_Verbose = false;
/* 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;
const char* dummyPath;
const char* unexpPath;
} g_specialfolders[] = {
{ CSIDL_WINDOWS, "windows", "X:\\reactos", "%SystemRoot%" },
{ CSIDL_SYSTEM, "system", "X:\\reactos\\system32", "%SystemRoot%\\system32" },
{ 0, NULL, NULL, NULL }
};
/* FUNCTIONS *****************************************************************/
/*
* Poor-man ANSI-to-UTF16LE conversion.
* We cannot use the host mbstowcs() routine, since on *nix systems the iconv
* library being used may have been compiled with a 32-bit wchar_t (even if
* the tool is compiled with: `-fshort-wchar -fwide-exec-charset=UTF-16LE`).
*/
size_t
my_mbstowcs(
wchar_t *wcstr,
const char *mbstr,
size_t count)
{
size_t i;
size_t len = strlen(mbstr) + 1; // Count the NUL-terminator.
/* Convert up to the NUL-terminator (included) and at most 'count' characters */
count = min(count, len);
/* POSIX extension: if wcstr is NULL, return the number of wide
* characters that would be written to wcstr, if converted. */
if (!wcstr)
return count;
if (count == 0)
return (size_t)0; // dest string exists, but 0 bytes converted.
for (i = 0; i < count; ++i)
wcstr[i] = (wchar_t)mbstr[i];
/* Return the number of wide characters, excluding the NUL-terminator, written to wcstr */
return (i - 1);
}
#define mbstowcs my_mbstowcs
static void __cdecl trace(const char *format, ...)
{
va_list args;
if (!g_Verbose)
return;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
static bool is_path_separator(char c)
{
return (c == '\\' || c == '/');
}
/* Check whether a string contains Win32 environment variables,
* by searching for a pair of '%'. */
static bool has_env_variables(const char *str)
{
const char *ptr = strchr(str, '%');
return (ptr && strchr(ptr + 1, '%'));
}
static void strip_quotes_and_unescape_envvars(char **pstr)
{
char *str = *pstr;
char *ptr, *dst;
/* Skip leading spaces */
while (isspace(*str))
++str;
/* Strip trailing spaces */
ptr = str + strlen(str);
while ((ptr > str) && isspace(*(ptr-1)))
--ptr;
*ptr = '\0';
/* If quoted, skip the leading and strip the trailing quotes */
if ((ptr > str + 1) && (*str == '"') && (*(ptr-1) == '"'))
{
++str;
*(--ptr) = '\0';
}
/* Un-escape the '^%' in environment variables names */
for (dst = ptr = str; *ptr; ++ptr, ++dst)
{
if (*ptr == '^' && *(ptr+1) == '%')
++ptr;
if (ptr > dst) // Copy characters as soon as source advances destination.
*dst = *ptr;
}
*dst = '\0';
/* Return the string */
*pstr = str;
}
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))
return NULL;
target += sizeof("shell:") - 1;
for (unsigned long i = 0;; ++i)
{
unsigned long len;
const struct SPECIALFOLDER *special = &g_specialfolders[i];
if (!special->name)
return NULL;
len = strlen(special->name);
strncpy(buf, target, sizeof(buf));
buf[len] = '\0';
if (!strcasecmp(special->name, buf) && (is_path_separator(target[len]) || !target[len]))
return &g_specialfolders[i];
}
}
/*
* Write an ANSI string buffer to the shell link,
* converting it to UTF16LE Unicode if necessary.
*/
static void write_string(FILE *pFile, bool bUnicode, const char *str)
{
uint16_t uhTmp;
/* String length in number of ANSI or Unicode characters */
uhTmp = strlen(str);
fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
/* String data (ANSI or Unicode) */
if (!bUnicode)
{
fwrite(str, uhTmp, 1, pFile);
}
else
{
wchar_t tmpBufferU[1024]; // Large-enough buffer for string conversions.
uhTmp = (uint16_t)mbstowcs(tmpBufferU, str, _countof(tmpBufferU));
fwrite(tmpBufferU, uhTmp * sizeof(wchar_t), 1, pFile);
}
}
int main(int argc, const char *argv[])
{
const char *pszOutputPath = "shortcut.lnk";
const char *pszTarget = NULL;
const char *pszDescription = NULL;
const char *pszWorkingDir = NULL;
const char *pszCmdLineArgs = NULL;
const char *pszIcon = NULL;
char targetpath[MAX_PATH];
int32_t IconIndex = 0;
GUID Guid = CLSID_MyComputer;
bool bHelp = false, bUnicode = false, bMinimized = false;
int i;
FILE *pFile;
LNK_HEADER Header;
uint32_t dwTmp;
const struct SPECIALFOLDER *special;
EXP_SPECIAL_FOLDER CsidlBlock, *pCsidlBlock = NULL;
EXP_SZ_LINK SzLinkBlock, *pSzLinkBlock = NULL;
EXP_SZ_LINK SzIconBlock, *pSzIconBlock = NULL;
/* Check for verbose mode in the command-line */
for (i = 1; i < argc; ++i)
{
if ((argv[i][0] == '-' || argv[i][0] == '/') &&
!strcmp(argv[i] + 1, "v"))
{
g_Verbose = true;
break;
}
}
/* Parse the command-line */
for (i = 1; i < argc; ++i)
{
trace("argv[%d]: %s\n", i, argv[i]);
if (argv[i][0] != '-' && argv[i][0] != '/')
pszTarget = argv[i];
else if (!strcmp(argv[i] + 1, "h"))
bHelp = true;
else if (!strcmp(argv[i] + 1, "v"))
/* Verbose mode, separately handled above */;
else if (!strcmp(argv[i] + 1, "o") && (i + 1 < argc))
{
++i;
trace("argv[%d]: %s\n", i, argv[i]);
pszOutputPath = argv[i];
}
else if (!strcmp(argv[i] + 1, "u"))
bUnicode = true;
else if (!strcmp(argv[i] + 1, "d") && (i + 1 < argc))
{
++i;
trace("argv[%d]: %s\n", i, argv[i]);
pszDescription = argv[i];
}
else if (!strcmp(argv[i] + 1, "w") && (i + 1 < argc))
{
++i;
trace("argv[%d]: %s\n", i, argv[i]);
pszWorkingDir = argv[i];
}
else if (!strcmp(argv[i] + 1, "c") && (i + 1 < argc))
{
++i;
trace("argv[%d]: %s\n", i, argv[i]);
pszCmdLineArgs = argv[i];
}
else if (!strcmp(argv[i] + 1, "i") && (i + 1 < argc))
{
++i;
trace("argv[%d]: %s\n", i, argv[i]);
pszIcon = argv[i];
}
else if (!strcmp(argv[i] + 1, "m"))
bMinimized = true;
else if (!strcmp(argv[i] + 1, "g") && (i + 1 < argc))
{
unsigned Data4Tmp[8], j;
++i;
trace("argv[%d]: %s\n", i, argv[i]);
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]);
for (j = 0; j < 8; ++j)
Guid.Data4[j] = (uint8_t)Data4Tmp[j];
}
else
{
printf("Invalid option: %s\n", argv[i]);
bHelp = true;
}
}
if (argc > 1)
trace("\n");
if (!pszTarget || bHelp)
{
printf("Usage: %s [-h][-v][-o path][-u][-d descr][-w path][-c cmdline_args][-i [icon_path[,nr]]][-g guid] target\n"
"-h\tShows this help.\n"
"-v\tEnables verbose mode for diagnostics.\n"
"-o path\tSets the output path.\n"
"-u\tCreates a Unicode-aware shortcut.\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 path and optionally its index. If only an index is given (but no path), the target file is used instead.\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;
}
/* Process the parameters */
if (pszTarget)
strip_quotes_and_unescape_envvars((char**)&pszTarget);
if (pszWorkingDir)
strip_quotes_and_unescape_envvars((char**)&pszWorkingDir);
if (pszCmdLineArgs)
strip_quotes_and_unescape_envvars((char**)&pszCmdLineArgs);
if (pszIcon)
{
char *ptr, *endptr;
strip_quotes_and_unescape_envvars((char**)&pszIcon);
/* Check whether only an icon index was given */
dwTmp = strtol(pszIcon, &endptr, 0);
if (endptr && endptr != pszIcon)
{
/* Only an index was given; save it, and use the target as the icon file */
IconIndex = dwTmp;
pszIcon = (char*)1; // Canary value to tell to use pszTarget, once resolved.
}
else
{
/* No index was given: this is a file path, optionally followed by the icon index */
ptr = strrchr(pszIcon, ',');
if (ptr) // && isdigit(ptr[1])
{
/* We may have an index that follows the icon path */
dwTmp = strtol(ptr+1, &endptr, 0);
if (endptr && endptr != (ptr+1))
{
/* It's valid, retrieve it and truncate the string to where the icon path ends */
IconIndex = dwTmp;
*ptr = '\0';
}
}
pszIcon = pszIcon;
}
}
trace("OutputPath = %s\n"
"Target = %s\n"
"Description = %s\n"
"WorkingDir = %s\n"
"CmdLineArgs = %s\n"
"Icon = %s (%d)\n\n",
pszOutputPath, pszTarget, pszDescription,
pszWorkingDir, pszCmdLineArgs,
(pszIcon == (char*)1) ? pszTarget : pszIcon, IconIndex);
pFile = fopen(pszOutputPath, "wb");
if (!pFile)
{
printf("Failed to open %s\n", pszOutputPath);
return -1;
}
/* Header */
memset(&Header, 0, sizeof(Header));
Header.Size = sizeof(Header);
Header.Guid = CLSID_ShellLink;
Header.Flags = LINK_ID_LIST;
if (bUnicode)
Header.Flags |= LINK_UNICODE;
if (pszDescription)
Header.Flags |= LINK_DESCRIPTION;
if (pszWorkingDir)
Header.Flags |= LINK_WORKING_DIR;
if (pszCmdLineArgs)
Header.Flags |= LINK_CMDLINE_ARGS;
if (pszIcon)
Header.Flags |= LINK_ICON;
Header.IconIndex = IconIndex;
Header.ShowCmd = (bMinimized ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL);
/* Verify the target type before proceeding further */
special = get_special_folder(pszTarget);
if (special)
Header.Flags |= LINK_ID_LIST; // Definitively needed in this case.
/* Check whether the target path contains Win32 environment variables */
if (has_env_variables(pszTarget))
{
Header.Flags |= SLDF_HAS_EXP_SZ;
Header.Flags &= ~LINK_ID_LIST; // We cannot deal with PIDLs in this case.
}
if ((Header.Flags & LINK_ID_LIST) && special)
Header.Flags &= ~LINK_RELATIVE_PATH;
/* If it's not a direct target, check whether it uses a special
* folder that is defined using Win32 environment variables */
if (special && special->unexpPath && has_env_variables(special->unexpPath))
Header.Flags |= SLDF_HAS_EXP_SZ;
/*
* For the icon, first check the canary value that tells to use the target path.
* If present, the icon path will contain Win32 environment variables if the target has them.
* Otherwise, explicitly check whether the specified icon path contains environment variables.
*/
if ((pszIcon == (char*)1) && (Header.Flags & SLDF_HAS_EXP_SZ))
Header.Flags |= SLDF_HAS_EXP_ICON_SZ;
else if (pszIcon && has_env_variables(pszIcon))
Header.Flags |= SLDF_HAS_EXP_ICON_SZ;
trace("Header.Flags = 0x%08x\n"
"Header.IconIndex = %d\n"
"Header.ShowCmd = %u\n",
Header.Flags, Header.IconIndex, Header.ShowCmd);
fwrite(&Header, sizeof(Header), 1, pFile);
if (Header.Flags & LINK_ID_LIST)
{
ID_LIST_GUID IdListGuid;
ID_LIST_DRIVE IdListDrive;
ID_LIST_FILE IdListFile;
unsigned int cbListSize = sizeof(IdListGuid) + sizeof(uint16_t);
unsigned int cchName;
const char *pszName;
const char *pszOrgTarget = pszTarget;
size_t specialPathLen = 0;
uint16_t uhTmp;
/* ID list. It appears explorer does not accept links
* without an ID list. It is relative to desktop. */
if (special)
{
CsidlBlock.cbSize = sizeof(CsidlBlock);
CsidlBlock.dwSignature = EXP_SPECIAL_FOLDER_SIG;
CsidlBlock.idSpecialFolder = special->csidl;
specialPathLen = strlen(special->dummyPath);
sprintf(targetpath, "%s\\%s", special->dummyPath, pszOrgTarget + sizeof("shell:") + strlen(special->name));
pszTarget = targetpath;
}
pszName = pszTarget;
if (pszName[0] && pszName[0] != ':' && pszName[1] == ':')
{
cbListSize += sizeof(IdListDrive);
pszName += 2;
while (is_path_separator(*pszName))
++pszName;
}
while (*pszName)
{
cchName = 0;
while (pszName[cchName] && !is_path_separator(pszName[cchName]))
++cchName;
if (cchName != 1 || pszName[0] != '.')
{
/* Don't care about the DOS name */
//cbListSize += sizeof(IdListFile) + 2 * (cchName + 1);
cbListSize += sizeof(IdListFile) + (cchName + 1) + 1;
}
if (special && ((pszName+cchName)-pszTarget == specialPathLen))
{
/* Point the special folder block to the rest of the path in the ID list */
CsidlBlock.cbOffset = cbListSize - sizeof(uint16_t);
pCsidlBlock = &CsidlBlock;
}
pszName += cchName;
while (is_path_separator(*pszName))
++pszName;
}
/* ID list size */
uhTmp = cbListSize;
fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
IdListGuid.Size = sizeof(IdListGuid);
IdListGuid.Type = PT_GUID;
IdListGuid.uSortOrder = REGITEMORDER_MYCOMPUTER;
IdListGuid.guid = Guid;
fwrite(&IdListGuid, sizeof(IdListGuid), 1, pFile);
pszName = pszTarget;
if (isalpha(pszName[0]) && pszName[1] == ':')
{
memset(&IdListDrive, 0, sizeof(IdListDrive));
IdListDrive.Size = sizeof(IdListDrive);
IdListDrive.Type = PT_DRIVE1;
sprintf(IdListDrive.szDriveName, "%c:\\", pszName[0]);
fwrite(&IdListDrive, sizeof(IdListDrive), 1, pFile);
pszName += 2;
while (is_path_separator(*pszName))
++pszName;
}
while (*pszName)
{
cchName = 0;
while (pszName[cchName] && !is_path_separator(pszName[cchName]))
++cchName;
if (cchName != 1 || pszName[0] != '.')
{
memset(&IdListFile, 0, sizeof(IdListFile));
/* Don't care about the DOS name */
//IdListFile.Size = sizeof(IdListFile) + 2 * (cchName + 1);
IdListFile.Size = sizeof(IdListFile) + (cchName + 1) + 1;
if (!pszName[cchName])
IdListFile.Type = PT_VALUE; // File
else
IdListFile.Type = PT_FOLDER;
fwrite(&IdListFile, sizeof(IdListFile), 1, pFile);
fwrite(pszName, cchName, 1, pFile);
fputc(0, pFile); // NUL-terminate
/* Don't care about the DOS name */
//fwrite(pszName, cchName, 1, pFile);
fputc(0, pFile); // NUL-terminate
}
pszName += cchName;
while (is_path_separator(*pszName))
++pszName;
}
/* End of ID list */
uhTmp = 0;
fwrite(&uhTmp, sizeof(uhTmp), 1, pFile);
/* Reset the target path to the actual unexpanded path */
if (special)
{
sprintf(targetpath, "%s\\%s", special->unexpPath, pszOrgTarget + sizeof("shell:") + strlen(special->name));
pszTarget = targetpath;
}
}
/* Now, set the icon path to the target path if it has the specific canary value */
if (pszIcon == (char*)1)
pszIcon = pszTarget;
if (Header.Flags & SLDF_HAS_EXP_SZ)
{
memset(&SzLinkBlock, 0, sizeof(SzLinkBlock));
SzLinkBlock.cbSize = sizeof(SzLinkBlock);
SzLinkBlock.dwSignature = EXP_SZ_LINK_SIG;
/* We have to store both ANSI and UTF16 versions of the string! */
strncpy(SzLinkBlock.szTarget, pszTarget, _countof(SzLinkBlock.szTarget));
mbstowcs(SzLinkBlock.szwTarget, pszTarget, _countof(SzLinkBlock.szwTarget));
pSzLinkBlock = &SzLinkBlock;
}
if (Header.Flags & SLDF_HAS_EXP_ICON_SZ)
{
memset(&SzIconBlock, 0, sizeof(SzIconBlock));
SzIconBlock.cbSize = sizeof(SzIconBlock);
SzIconBlock.dwSignature = EXP_SZ_ICON_SIG;
/* We have to store both ANSI and UTF16 versions of the string! */
strncpy(SzIconBlock.szTarget, pszIcon, _countof(SzIconBlock.szTarget));
mbstowcs(SzIconBlock.szwTarget, pszIcon, _countof(SzIconBlock.szwTarget));
pSzIconBlock = &SzIconBlock;
}
/* Description */
if (Header.Flags & LINK_DESCRIPTION)
write_string(pFile, bUnicode, pszDescription);
/* Relative path */
if (Header.Flags & LINK_RELATIVE_PATH)
write_string(pFile, bUnicode, pszTarget);
/* Working directory */
if (Header.Flags & LINK_WORKING_DIR)
write_string(pFile, bUnicode, pszWorkingDir);
/* Command-line arguments */
if (Header.Flags & LINK_CMDLINE_ARGS)
write_string(pFile, bUnicode, pszCmdLineArgs);
/* Icon path */
if (Header.Flags & LINK_ICON)
write_string(pFile, bUnicode, pszIcon);
/* Write the data block list */
if (pCsidlBlock)
fwrite(pCsidlBlock, sizeof(*pCsidlBlock), 1, pFile);
if (pSzLinkBlock)
fwrite(pSzLinkBlock, sizeof(*pSzLinkBlock), 1, pFile);
if (pSzIconBlock)
fwrite(pSzIconBlock, sizeof(*pSzIconBlock), 1, pFile);
/* End of data block list */
dwTmp = 0;
fwrite(&dwTmp, sizeof(dwTmp), 1, pFile);
fclose(pFile);
return 0;
}