mirror of
https://github.com/ApfelTeeSaft/reactos.git
synced 2026-08-26 19:33:31 +00:00
CORE-15156, CORE-19691, CORE-19692 Finally get rid of the livecd_start.cmd hack introduced waaaay back in commitff6d7b0236(r54514)! See also commitsea682b6909(r54512) and71867403fd(r54513). For target paths, use the shell "special shell folder" syntax: `shell:windows\...` or `shell:system\...`, introduced in commit7b081be46d(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.
799 lines
24 KiB
C
799 lines
24 KiB
C
/*
|
||
* 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;
|
||
}
|