[MKSHELLLINK] Support link target and icon paths containing environment variables (#8936)

CORE-15156, CORE-19691, CORE-19692

- When specifying a "special shell folder" in the shortcut target,
  e.g.: `shell:windows\system32\cmd.exe` , or: `shell:system\cmd.exe`,
  resolve it using a ReactOS-specific path, but also use a version
  using unexpanded environment variables: for both examples,
  `%SystemRoot%\system32\cmd.exe`

- Whenever the shortcut target or the icon path contains unexpanded
  environment variables, add associated `EXP_SZ_LINK` data blocks.

- Use the previously-introduced ANSI-to-UTF16 `my_mbstowcs()` routine
  to convert the ANSI strings to Unicode for the `EXP_SZ_LINK` data blocks.
This commit is contained in:
Hermès Bélusca-Maïto
2026-05-13 22:16:05 +02:00
parent e505a8dbaa
commit af0cb55edb
+97 -10
View File
@@ -88,6 +88,8 @@ DEFINE_GUID2(CLSID_MyComputer,0x20D04FE0,0x3AEA,0x1069,0xA2,0xD8,0x08,0x00,0x2B,
#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
@@ -194,6 +196,18 @@ typedef struct _EXP_SPECIAL_FOLDER
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)
@@ -207,10 +221,11 @@ static const struct SPECIALFOLDER
unsigned char csidl;
const char* name;
const char* dummyPath;
const char* unexpPath;
} g_specialfolders[] = {
{ CSIDL_WINDOWS, "windows", "X:\\reactos" },
{ CSIDL_SYSTEM, "system", "X:\\reactos\\system32" },
{ 0, NULL, NULL }
{ CSIDL_WINDOWS, "windows", "X:\\reactos", "%SystemRoot%" },
{ CSIDL_SYSTEM, "system", "X:\\reactos\\system32", "%SystemRoot%\\system32" },
{ 0, NULL, NULL, NULL }
};
@@ -253,6 +268,14 @@ 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 const struct SPECIALFOLDER* get_special_folder(const char *target)
{
char buf[256];
@@ -304,21 +327,24 @@ static void write_string(FILE *pFile, bool bUnicode, const char *str)
int main(int argc, const char *argv[])
{
int i;
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[260];
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;
/* Parse the command-line */
for (i = 1; i < argc; ++i)
@@ -403,6 +429,30 @@ int main(int argc, const char *argv[])
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;
/* Check whether the icon path contains Win32 environment variables */
if (pszIcon && has_env_variables(pszIcon))
Header.Flags |= SLDF_HAS_EXP_ICON_SZ;
fwrite(&Header, sizeof(Header), 1, pFile);
if (Header.Flags & LINK_ID_LIST)
@@ -412,23 +462,23 @@ int main(int argc, const char *argv[])
ID_LIST_FILE IdListFile;
unsigned int cbListSize = sizeof(IdListGuid) + sizeof(uint16_t);
unsigned int cchName;
const char *pszName = pszTarget;
const char *pszName;
const char *pszOrgTarget = pszTarget;
size_t specialPathLen = 0;
uint16_t uhTmp;
const struct SPECIALFOLDER *special = get_special_folder(pszTarget);
/* 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;
CsidlBlock.cbSize = sizeof(CsidlBlock);
CsidlBlock.dwSignature = EXP_SPECIAL_FOLDER_SIG;
CsidlBlock.idSpecialFolder = special->csidl;
specialPathLen = strlen(special->dummyPath);
sprintf(targetpath, "%s\\%s", special->dummyPath, pszTarget + sizeof("shell:") + strlen(special->name));
pszName = pszTarget = targetpath;
sprintf(targetpath, "%s\\%s", special->dummyPath, pszOrgTarget + sizeof("shell:") + strlen(special->name));
pszTarget = targetpath;
}
pszName = pszTarget;
if (pszName[0] && pszName[0] != ':' && pszName[1] == ':')
{
@@ -519,6 +569,39 @@ int main(int argc, const char *argv[])
/* 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;
}
}
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 */
@@ -544,6 +627,10 @@ int main(int argc, const char *argv[])
/* 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);