https://git.reactos.org/?p=reactos.git;a=commitdiff;h=96aa1eee179b7c0225adb3c642a30d8d3e6904a1
commit 96aa1eee179b7c0225adb3c642a30d8d3e6904a1 Author: Whindmar Saksit <whinds...@proton.me> AuthorDate: Sat Mar 15 16:31:22 2025 +0100 Commit: GitHub <nore...@github.com> CommitDate: Sat Mar 15 16:31:22 2025 +0100 [LNKTOOL][SHELL32] Add utility to create/dump .lnk files (#7745) Unlike mkshelllink, this uses the native COM interface to create the link. --- dll/win32/shell32/CShellLink.cpp | 19 +- .../rosapps/applications/devutils/CMakeLists.txt | 1 + .../applications/devutils/lnktool/CMakeLists.txt | 7 + .../applications/devutils/lnktool/lnktool.cpp | 874 +++++++++++++++++++++ .../applications/devutils/lnktool/lnktool.rc | 9 + sdk/include/psdk/shlwapi.h | 17 + sdk/include/reactos/shellutils.h | 2 + 7 files changed, 919 insertions(+), 10 deletions(-) diff --git a/dll/win32/shell32/CShellLink.cpp b/dll/win32/shell32/CShellLink.cpp index edc482f8883..3ea7c3d5e6c 100644 --- a/dll/win32/shell32/CShellLink.cpp +++ b/dll/win32/shell32/CShellLink.cpp @@ -544,7 +544,7 @@ static BOOL Stream_LoadVolume(LOCAL_VOLUME_INFO *vol, CShellLink::volume_info *v INT len = vol->dwSize - vol->dwVolLabelOfs; LPSTR label = (LPSTR)vol; - label += vol->dwVolLabelOfs; + label += vol->dwVolLabelOfs; // FIXME: 0x14 Unicode MultiByteToWideChar(CP_ACP, 0, label, len, volume->label, _countof(volume->label)); return TRUE; @@ -595,7 +595,7 @@ static HRESULT Stream_LoadLocation(IStream *stm, /* if there's a local path, load it */ DWORD n = loc->dwLocalPathOfs; if (n && n < loc->dwTotalSize) - *path = Stream_LoadPath(&p[n], loc->dwTotalSize - n); + *path = Stream_LoadPath(&p[n], loc->dwTotalSize - n); // FIXME: Unicode offset (if present) TRACE("type %d serial %08x name %s path %s\n", volume->type, volume->serial, debugstr_w(volume->label), debugstr_w(*path)); @@ -850,7 +850,7 @@ static HRESULT Stream_WriteString(IStream* stm, LPCWSTR str) * Figure out how Windows deals with unicode paths here. */ static HRESULT Stream_WriteLocationInfo(IStream* stm, LPCWSTR path, - CShellLink::volume_info *volume) + CShellLink::volume_info *volume) // FIXME: Write Unicode strings { LOCAL_VOLUME_INFO *vol; LOCATION_INFO *loc; @@ -913,6 +913,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty) m_Header.dwSize = sizeof(m_Header); m_Header.clsid = CLSID_ShellLink; + m_Header.dwReserved3 = m_Header.dwReserved2 = m_Header.wReserved1 = 0; /* Store target attributes */ WIN32_FIND_DATAW wfd = {}; @@ -934,8 +935,9 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty) * already set in accordance by the different mutator member functions. * The other flags will be determined now by the presence or absence of data. */ - m_Header.dwFlags &= (SLDF_RUN_WITH_SHIMLAYER | SLDF_RUNAS_USER | - SLDF_RUN_IN_SEPARATE | SLDF_HAS_DARWINID | + UINT NT6SimpleFlags = LOBYTE(GetVersion()) > 6 ? (0x00040000 | 0x00400000 | 0x00800000 | 0x02000000) : 0; + m_Header.dwFlags &= (SLDF_RUN_WITH_SHIMLAYER | SLDF_RUNAS_USER | SLDF_RUN_IN_SEPARATE | + SLDF_HAS_DARWINID | SLDF_FORCE_NO_LINKINFO | NT6SimpleFlags | #if (NTDDI_VERSION < NTDDI_LONGHORN) SLDF_HAS_LOGO3ID | #endif @@ -2488,14 +2490,11 @@ HRESULT STDMETHODCALLTYPE CShellLink::GetFlags(DWORD *pdwFlags) HRESULT STDMETHODCALLTYPE CShellLink::SetFlags(DWORD dwFlags) { -#if 0 // FIXME! + if (m_Header.dwFlags == dwFlags) + return S_FALSE; m_Header.dwFlags = dwFlags; m_bDirty = TRUE; return S_OK; -#else - FIXME("\n"); - return E_NOTIMPL; -#endif } /************************************************************************** diff --git a/modules/rosapps/applications/devutils/CMakeLists.txt b/modules/rosapps/applications/devutils/CMakeLists.txt index 446fbc1ea74..2ffa876e181 100644 --- a/modules/rosapps/applications/devutils/CMakeLists.txt +++ b/modules/rosapps/applications/devutils/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(createspec) add_subdirectory(gdb2) add_subdirectory(gdihv) add_subdirectory(genguid) +add_subdirectory(lnktool) add_subdirectory(nls2txt) add_subdirectory(shimdbg) add_subdirectory(shimtest_ros) diff --git a/modules/rosapps/applications/devutils/lnktool/CMakeLists.txt b/modules/rosapps/applications/devutils/lnktool/CMakeLists.txt new file mode 100644 index 00000000000..009f746e742 --- /dev/null +++ b/modules/rosapps/applications/devutils/lnktool/CMakeLists.txt @@ -0,0 +1,7 @@ + +add_executable(lnktool lnktool.cpp lnktool.rc) + +set_module_type(lnktool win32cui UNICODE) +target_link_libraries(lnktool uuid) +add_importlibs(lnktool ole32 comctl32 shell32 shlwapi advapi32 user32 msvcrt kernel32) +add_cd_file(TARGET lnktool DESTINATION reactos/system32 FOR all) diff --git a/modules/rosapps/applications/devutils/lnktool/lnktool.cpp b/modules/rosapps/applications/devutils/lnktool/lnktool.cpp new file mode 100644 index 00000000000..32499fcdf07 --- /dev/null +++ b/modules/rosapps/applications/devutils/lnktool/lnktool.cpp @@ -0,0 +1,874 @@ +/* + * PROJECT: lnktool + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: ShellLink (.lnk) utility + * COPYRIGHT: Copyright 2025 Whindmar Saksit <whinds...@proton.me> + */ + +#define INDENT " " +static const char g_Usage[] = "" + "lnktool <Create|Dump|RemoveDatablock> <LnkFile> [Options]\n" + "\n" + "Create Options:\n" + "/Pidl <Path>\n" + "/Path <TargetPath>\n" + "/Icon <Path,Index>\n" + "/Arguments <String>\n" + "/RelativePath <LnkPath>\n" + "/WorkingDir <Path>\n" + "/Comment <String>\n" + "/ShowCmd <Number>\n" + "/SpecialFolderOffset <CSIDL> <Offset|-Count>\n" + "/AddExp <TargetPath>\n" + "/AddExpIcon <Path>\n" + "/RemoveExpIcon\n" // Removes the EXP_SZ_ICON block and flag + "/AddSLDF <Flag>\n" + "/RemoveSLDF <Flag>\n"; + +#include <windows.h> +#include <shlobj.h> +#include <shlwapi.h> +#include <wchar.h> +#include <stdio.h> // For shellutils.h (sprintf) +#include <shellutils.h> +#include <shlwapi_undoc.h> +#include <undocshell.h> // SHELL_LINK_HEADER + +enum { slgp_relativepriority = 0x08 }; +#define PT_DESKTOP_REGITEM 0x1F +#define PT_COMPUTER_REGITEM 0x2E +#define PT_FS 0x30 +#define PT_FS_UNICODE_FLAG 0x04 + +static const struct { UINT Flag; PCSTR Name; } g_SLDF[] = +{ + { SLDF_HAS_ID_LIST, "IDLIST" }, + { SLDF_HAS_LINK_INFO, "LINKINFO" }, + { SLDF_HAS_NAME, "NAME" }, + { SLDF_HAS_RELPATH, "RELPATH" }, + { SLDF_HAS_WORKINGDIR, "WORKINGDIR" }, + { SLDF_HAS_ARGS, "ARGS" }, + { SLDF_HAS_ICONLOCATION, "ICONLOCATION" }, + { SLDF_UNICODE, "UNICODE" }, + { SLDF_FORCE_NO_LINKINFO, "FORCE_NO_LINKINFO" }, + { SLDF_HAS_EXP_SZ, "EXP_SZ" }, + { SLDF_RUN_IN_SEPARATE, "RUN_IN_SEPARATE" }, + { 0x00000800, "LOGO3ID" }, + { SLDF_HAS_DARWINID, "DARWINID" }, + { SLDF_RUNAS_USER, "RUNAS_USER" }, + { SLDF_HAS_EXP_ICON_SZ, "EXP_ICON_SZ" }, + { SLDF_NO_PIDL_ALIAS, "NO_PIDL_ALIAS" }, + { SLDF_FORCE_UNCNAME, "FORCE_UNCNAME" }, + { SLDF_RUN_WITH_SHIMLAYER, "RUN_WITH_SHIMLAYER" }, + { 0x00040000, "FORCE_NO_LINKTRACK" }, + { 0x00080000, "ENABLE_TARGET_METADATA" }, + { 0x00100000, "DISABLE_LINK_PATH_TRACKING" }, + { 0x00200000, "DISABLE_KNOWNFOLDER_RELATIVE_TRACKING" }, + { 0x00400000, "NO_KF_ALIAS" }, + { 0x00800000, "ALLOW_LINK_TO_LINK" }, + { 0x01000000, "UNALIAS_ON_SAVE" }, + { 0x02000000, "PREFER_ENVIRONMENT_PATH" }, + { 0x04000000, "KEEP_LOCAL_IDLIST_FOR_UNC_TARGET" }, + { 0x08000000, "PERSIST_VOLUME_ID_RELATIVE" }, +}; + +static const struct { UINT Flag; PCSTR Name; } g_DBSig[] = +{ + { EXP_SZ_LINK_SIG, "SZ_LINK" }, + { EXP_SZ_ICON_SIG, "SZ_ICON" }, + { EXP_SPECIAL_FOLDER_SIG, "SPECIALFOLDER" }, + { EXP_TRACKER_SIG, "TRACKER" }, + { 0xA0000009, "PROPERTYSTORAGE" }, + { EXP_KNOWN_FOLDER_SIG, "KNOWNFOLDER" }, + { EXP_VISTA_ID_LIST_SIG, "VISTAPIDL" }, +}; + +static LONG StrToNum(PCWSTR in) +{ + PWCHAR end; + LONG v = wcstol(in, &end, 0); + if (v == LONG_MAX) + v = wcstoul(in, &end, 0); + return (end > in) ? v : 0; +} + +template<class T> +static UINT MapToNumber(PCWSTR Name, const T &Map) +{ + CHAR buf[200]; + WideCharToMultiByte(CP_ACP, 0, Name, -1, buf, _countof(buf), NULL, NULL); + buf[_countof(buf) - 1] = ANSI_NULL; + for (UINT i = 0; i < _countof(Map); ++i) + { + if (!StrCmpIA(buf, Map[i].Name)) + return Map[i].Flag; + } + return StrToNum(Name); +} + +template<class T> +static PCSTR MapToName(UINT Value, const T &Map, PCSTR Fallback = NULL) +{ + for (UINT i = 0; i < _countof(Map); ++i) + { + if (Map[i].Flag == Value) + return Map[i].Name; + } + return Fallback; +} + +#define GetSLDF(Name) MapToNumber((Name), g_SLDF) +#define GetDatablockSignature(Name) MapToNumber((Name), g_DBSig) + +static int ErrMsg(int Error) +{ + WCHAR buf[400]; + for (UINT e = Error, cch; ;) + { + lstrcpynW(buf, L"?", _countof(buf)); + cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, e, 0, buf, _countof(buf), NULL); + while (cch && buf[cch - 1] <= ' ') + buf[--cch] = UNICODE_NULL; // Remove trailing newlines + if (cch || HIWORD(e) != HIWORD(HRESULT_FROM_WIN32(1))) + break; + e = HRESULT_CODE(e); // "WIN32_FROM_HRESULT" + } + wprintf(Error < 0 ? L"Error 0x%.8X %s\n" : L"Error %d %s\n", Error, buf); + return Error; +} + +static int SuccessOrReportError(int Error) +{ + return Error ? ErrMsg(Error) : Error; +} + +static inline HRESULT Seek(IStream *pStrm, int Move, int Origin = FILE_CURRENT, ULARGE_INTEGER *pNewPos = NULL) +{ + LARGE_INTEGER Pos; + Pos.QuadPart = Move; + return pStrm->Seek(Pos, Origin, pNewPos); +} + +static HRESULT IL_LoadFromStream(IStream *pStrm, PIDLIST_ABSOLUTE *ppidl) +{ + HMODULE hShell32 = LoadLibraryA("SHELL32"); + HRESULT (WINAPI*ILLFS)(IStream*, PIDLIST_ABSOLUTE*); + (FARPROC&)ILLFS = GetProcAddress(hShell32, (char*)26); // NT5 + if (!ILLFS) + (FARPROC&)ILLFS = GetProcAddress(hShell32, (char*)846); // NT6 + return ILLFS(pStrm, ppidl); +} + +static HRESULT SHParseName(PCWSTR Path, PIDLIST_ABSOLUTE *ppidl) +{ + HMODULE hShell32 = LoadLibraryA("SHELL32"); + int (WINAPI*SHPDN)(PCWSTR, void*, PIDLIST_ABSOLUTE*, UINT, UINT*); + (FARPROC&)SHPDN = GetProcAddress(hShell32, "SHParseDisplayName"); + if (SHPDN) + return SHPDN(Path, NULL, ppidl, 0, NULL); + return SHILCreateFromPath(Path, ppidl, NULL); +} + +static HRESULT SHParseNameEx(PCWSTR Path, PIDLIST_ABSOLUTE *ppidl, bool Simple) +{ + if (!Simple) + return SHParseName(Path, ppidl); + *ppidl = SHSimpleIDListFromPath(Path); + return *ppidl ? S_OK : E_FAIL; +} + +static INT_PTR SHGetPidlInfo(LPCITEMIDLIST pidl, SHFILEINFOW &shfi, UINT Flags, UINT Depth = 0) +{ + LPITEMIDLIST pidlDup = NULL, pidlChild; + if (Depth > 0) + { + if ((pidl = pidlDup = pidlChild = ILClone(pidl)) == NULL) + return 0; + while (Depth--) + { + if (LPITEMIDLIST pidlNext = ILGetNext(pidlChild)) + pidlChild = pidlNext; + } + pidlChild->mkid.cb = 0; + } + INT_PTR ret = SHGetFileInfoW((PWSTR)pidl, 0, &shfi, sizeof(shfi), Flags | SHGFI_PIDL); + ILFree(pidlDup); + return ret; +} + +static void PrintOffsetString(PCSTR Name, LPCVOID Base, UINT Min, UINT Ansi, UINT Unicode, PCSTR Indent = "") +{ + if (Unicode && Unicode >= Min) + wprintf(L"%hs%hs=%ls", Indent, Name, (PWSTR)((BYTE*)Base + Unicode)); + else + wprintf(L"%hs%hs=%hs", Indent, Name, Ansi >= Min ? (char*)Base + Ansi : ""); + wprintf(L"\n"); // Separate function call in case the (untrusted) input ends with a DEL character +} + +static void Print(PCSTR Name, REFGUID Guid, PCSTR Indent = "") +{ + WCHAR Buffer[39]; + StringFromGUID2(Guid, Buffer, _countof(Buffer)); + wprintf(L"%hs%hs=%ls\n", Indent, Name, Buffer); +} + +template<class V, class T> +static void DumpFlags(V Value, T *pInfo, UINT Count, PCSTR Prefix = NULL) +{ + if (!Prefix) + Prefix = ""; + for (SIZE_T i = 0; i < Count; ++i) + { + if (Value & pInfo[i].Flag) + wprintf(L"%hs%#.8x:%hs\n", Prefix, pInfo[i].Flag, const_cast<PCSTR>(pInfo[i].Name)); + Value &= ~pInfo[i].Flag; + } + if (Value) + wprintf(L"%hs%#.8x:%hs\n", Prefix, Value, "?"); +} + +static void Dump(LPITEMIDLIST pidl, PCSTR Heading = NULL) +{ + struct GUIDPIDL { WORD cb; BYTE Type, Unknown; GUID guid; }; + struct DRIVE { BYTE cb1, cb2, Type; char Name[4]; BYTE Unk[18]; }; + struct FS95 { WORD cb; BYTE Type, Unknown, Data[4+2+2+2]; char Name[1]; }; + if (Heading) + wprintf(L"%hs ", Heading); + if (!pidl || !pidl->mkid.cb) + wprintf(L"[Desktop (%hs)]", pidl ? "Empty" : "NULL"); + for (UINT i = 0; pidl && pidl->mkid.cb; ++i, pidl = ILGetNext(pidl)) + { + SHFILEINFOW shfi; + PWSTR buf = shfi.szDisplayName; + GUIDPIDL *pidlGUID = (GUIDPIDL*)pidl; + UINT cb = pidl->mkid.cb; + UINT type = cb >= 3 ? (pidlGUID->Type & ~0x80) : 0, folder = type & 0x70; + if (i) + wprintf(L" "); + if (cb < 3) + { + wprintf(L"[? %ub]", cb); + } + else if (i == 0 && cb == sizeof(GUIDPIDL) && type == PT_DESKTOP_REGITEM) guiditem: + { + if (!SHGetPidlInfo(pidl, shfi, SHGFI_DISPLAYNAME, 1)) + StringFromGUID2(*(GUID*)((char*)pidl + cb - 16), buf, 39); + wprintf(L"[%.2X %ub %s]", pidl->mkid.abID[0], cb, buf); + } + else if (i == 1 && cb == sizeof(GUIDPIDL) && type == PT_COMPUTER_REGITEM) + { + goto guiditem; + } + else if (i == 1 && cb == sizeof(DRIVE) && folder == 0x20) + { + DRIVE *p = (DRIVE*)pidl; + wprintf(L"[%.2X %ub \"%.3hs\"]", p->Type, cb, p->Name); + } + else if (cb > FIELD_OFFSET(FS95, Name) && folder == PT_FS) + { + FS95 *p = (FS95*)pidl; + const BOOL wide = type & PT_FS_UNICODE_FLAG; + wprintf(wide ? L"[%.2X %ub \"%.256ls\"]" : L"[%.2X %ub \"%.256hs\"]", p->Type, cb, p->Name); + } + else + { + wprintf(L"[%.2X %ub ?]", pidl->mkid.abID[0], cb); + } + } + wprintf(L"\n"); +} + +static HRESULT Save(IShellLink *pSL, PCWSTR LnkPath) +{ + HRESULT hr; + IPersistFile *pPF; + if (SUCCEEDED(hr = pSL->QueryInterface(IID_PPV_ARG(IPersistFile, &pPF)))) + { + if (SUCCEEDED(hr = pPF->Save(LnkPath, FALSE)) && hr != S_OK) + hr = E_FAIL; + pPF->Release(); + } + return hr; +} + +static HRESULT Load(IUnknown *pUnk, IStream *pStream) +{ + IPersistStream *pPS; + HRESULT hr = pUnk->QueryInterface(IID_PPV_ARG(IPersistStream, &pPS)); + if (SUCCEEDED(hr)) + { + hr = pPS->Load(pStream); + pPS->Release(); + } + return hr; +} + +static HRESULT Open(PCWSTR Path, IStream **ppStream, IShellLink **ppLink, UINT Mode = STGM_READ) +{ + if (Mode & (STGM_WRITE | STGM_READWRITE)) + Mode = (Mode & ~STGM_WRITE) | STGM_READWRITE | STGM_SHARE_DENY_WRITE; + else + Mode |= STGM_READ | STGM_SHARE_DENY_NONE; + IStream *pStream; + HRESULT hr = SHCreateStreamOnFileW(Path, Mode, &pStream); + if (SUCCEEDED(hr) && ppLink) + { + hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLink, ppLink)); + if (SUCCEEDED(hr) && !(Mode & STGM_CREATE)) + { + if (FAILED(hr = Load(*ppLink, pStream))) + { + (*ppLink)->Release(); + *ppLink = NULL; + } + IStream_Reset(pStream); + } + } + + if (SUCCEEDED(hr) && ppStream) + *ppStream = pStream; + else if (pStream) + pStream->Release(); + return hr; +} + +#define FreeBlock SHFree +static inline LPDATABLOCK_HEADER NextBlock(LPDATABLOCK_HEADER pdbh) +{ + return (LPDATABLOCK_HEADER)((char*)pdbh + pdbh->cbSize); +} + +template<class T> +static HRESULT ReadBlock(IStream *pStream, T **ppData = NULL) +{ + DWORD cb = 0; + HRESULT hr = IStream_Read(pStream, &cb, sizeof(cb)); + if (SUCCEEDED(hr)) + { + UINT Remain = cb - min(sizeof(DWORD), cb); + if (!ppData) + return Seek(pStream, Remain); + *ppData = (T*)SHAlloc(Remain + sizeof(DWORD)); + if (!*ppData) + return E_OUTOFMEMORY; + ((DATABLOCK_HEADER*)*ppData)->cbSize = cb; + if (SUCCEEDED(hr = IStream_Read(pStream, &((DATABLOCK_HEADER*)*ppData)->dwSignature, Remain))) + return cb; + } + return hr; +} + +struct DataBlockEnum +{ + DataBlockEnum(LPDATABLOCK_HEADER pHead) : m_It(pHead) {} + + LPDATABLOCK_HEADER Get() + { + if (m_It && m_It->cbSize >= FIELD_OFFSET(DATABLOCK_HEADER, dwSignature)) + return m_It + (m_It->dwSignature == ~0UL); // SHFindDataBlock compatible + return NULL; + } + void Next() + { + if (m_It && m_It->cbSize) + m_It = NextBlock(m_It); + } + LPDATABLOCK_HEADER m_It; +}; + +static HRESULT ReadAndDumpString(PCSTR Name, UINT Id, const SHELL_LINK_HEADER &slh, IStream *pStream) +{ + if (!(Id & slh.dwFlags)) + return S_OK; + WORD cch; + HRESULT hr = IStream_Read(pStream, &cch, sizeof(cch)); + if (FAILED(hr)) + return hr; + UINT cb = (UINT)cch * ((slh.dwFlags & SLDF_UNICODE) ? sizeof(WCHAR) : sizeof(CHAR)); + void *data = SHAlloc(cb); + if (!data) + return E_OUTOFMEMORY; + if (FAILED(hr = IStream_Read(pStream, data, cb))) + return hr; + if (slh.dwFlags & SLDF_UNICODE) + wprintf(L"%hs=%.*ls\n", Name, cch, (PWSTR)data); + else + wprintf(L"%hs=%.*hs\n", Name, cch, (PCSTR)data); + SHFree(data); + return S_OK; +} + +static HRESULT DumpCommand(PCWSTR Path) +{ + IStream *pStream; + HRESULT hr = Open(Path, &pStream, NULL); + if (FAILED(hr)) + return hr; + + LPITEMIDLIST pidl; + SHELL_LINK_HEADER slh; + if (SUCCEEDED(hr = IStream_Read(pStream, &slh, sizeof(slh)))) + { + #define DUMPSLH(name, fmt, field) wprintf(L"%hs=" fmt L"\n", (name), (slh).field) + DUMPSLH("Flags", L"%#.8x", dwFlags); + DumpFlags(slh.dwFlags, g_SLDF, _countof(g_SLDF), INDENT); + DUMPSLH("FileAttributes", L"%#.8x", dwFileAttributes); + DUMPSLH("Size", L"%u", nFileSizeLow); + DUMPSLH("IconIndex", L"%d", nIconIndex); + DUMPSLH("ShowCommand", L"%d", nShowCommand); + DUMPSLH("HotKey", L"%#.4x", wHotKey); + + if (SUCCEEDED(hr) && (slh.dwFlags & SLDF_HAS_ID_LIST)) + { + if (SUCCEEDED(hr = IL_LoadFromStream(pStream, &pidl))) + { + Dump(pidl, "PIDL:"); + ILFree(pidl); + } + } + if (SUCCEEDED(hr) && (slh.dwFlags & SLDF_HAS_LINK_INFO)) + { + int offset; + SHELL_LINK_INFOW *p; + if ((hr = ReadBlock(pStream, &p)) >= (signed)sizeof(SHELL_LINK_INFOA)) + { + wprintf(L"%hs:\n", "LINKINFO"); + wprintf(L"%hs%hs=%#.8x\n", INDENT, "Flags", p->dwFlags); + if (p->dwFlags & SLI_VALID_LOCAL) + { + if (p->cbVolumeIDOffset) + { + SHELL_LINK_INFO_VOLUME_IDW *pVol = (SHELL_LINK_INFO_VOLUME_IDW*)((char*)p + p->cbVolumeIDOffset); + wprintf(L"%hs%hs=%d\n", INDENT, "DriveType", pVol->dwDriveType); + wprintf(L"%hs%hs=%#.8x\n", INDENT, "Serial", pVol->nDriveSerialNumber); + offset = pVol->cbVolumeLabelOffset == 0x14 ? pVol->cbVolumeLabelUnicodeOffset : 0; + if (offset || pVol->cbVolumeLabelOffset != 0x14) // 0x14 from [MS-SHLLINK] documentation + PrintOffsetString("Label", pVol, 0x10, pVol->cbVolumeLabelOffset, offset, INDENT); + } + offset = p->cbHeaderSize >= sizeof(SHELL_LINK_INFOW) ? p->cbCommonPathSuffixUnicodeOffset : 0; + PrintOffsetString("CommonSuffix", p, sizeof(SHELL_LINK_INFOA), p->cbCommonPathSuffixOffset, offset, INDENT); + + offset = p->cbHeaderSize >= sizeof(SHELL_LINK_INFOW) ? p->cbLocalBasePathUnicodeOffset : 0; + PrintOffsetString("LocalBase", p, sizeof(SHELL_LINK_INFOA), p->cbLocalBasePathOffset, offset, INDENT); + } + SHELL_LINK_INFO_CNR_LINKW *pCNR = (SHELL_LINK_INFO_CNR_LINKW*)((char*)p + p->cbCommonNetworkRelativeLinkOffset); + if ((p->dwFlags & SLI_VALID_NETWORK) && p->cbCommonNetworkRelativeLinkOffset) + { + wprintf(L"%hs%hs=%#.8x\n", INDENT, "CNR", pCNR->dwFlags); + wprintf(L"%hs%hs=%#.8x\n", INDENT, "Provider", pCNR->dwNetworkProviderType); // WNNC_NET_* + + offset = pCNR->cbNetNameOffset > 0x14 ? pCNR->cbNetNameUnicodeOffset : 0; + PrintOffsetString("NetName", pCNR, sizeof(SHELL_LINK_INFO_CNR_LINKA), pCNR->cbNetNameOffset, offset, INDENT); + + offset = pCNR->cbNetNameOffset > 0x14 ? pCNR->cbDeviceNameUnicodeOffset : 0; + if (pCNR->dwFlags & SLI_CNR_VALID_DEVICE) + PrintOffsetString("DeviceName", pCNR, sizeof(SHELL_LINK_INFO_CNR_LINKA), pCNR->cbDeviceNameOffset, offset, INDENT); + } + FreeBlock(p); + } + } + if (SUCCEEDED(hr)) + hr = ReadAndDumpString("NAME", SLDF_HAS_NAME, slh, pStream); + if (SUCCEEDED(hr)) + hr = ReadAndDumpString("RELPATH", SLDF_HAS_RELPATH, slh, pStream); + if (SUCCEEDED(hr)) + hr = ReadAndDumpString("WORKINGDIR", SLDF_HAS_WORKINGDIR, slh, pStream); + if (SUCCEEDED(hr)) + hr = ReadAndDumpString("ARGS", SLDF_HAS_ARGS, slh, pStream); + if (SUCCEEDED(hr)) + hr = ReadAndDumpString("ICONLOCATION", SLDF_HAS_ICONLOCATION, slh, pStream); + + LPDATABLOCK_HEADER pDBListHead, pDBH; + if (SUCCEEDED(hr) && SUCCEEDED(hr = SHReadDataBlockList(pStream, &pDBListHead))) + { + for (DataBlockEnum it(pDBListHead); (pDBH = it.Get()) != NULL; it.Next()) + { + PCSTR SigName = MapToName(pDBH->dwSignature, g_DBSig, NULL); + wprintf(L"DataBlock: %.8X %ub", pDBH->dwSignature, pDBH->cbSize); + void *pFirstMember = ((EXP_SZ_LINK*)pDBH)->szTarget; + if (pDBH->dwSignature == EXP_SZ_LINK_SIG && pDBH->cbSize > FIELD_OFFSET(EXP_SZ_LINK, szwTarget)) + { + wprintf(L" %hs\n", "SZ_LINK"); + printdbpaths: + EXP_SZ_LINK *p = (EXP_SZ_LINK*)pDBH; + wprintf(L"%hs%hs=%.260hs\n", INDENT, "Ansi", p->szTarget); + wprintf(L"%hs%hs=%.260ls\n", INDENT, "Wide", p->szwTarget); + } + else if (pDBH->dwSignature == EXP_SZ_ICON_SIG && pDBH->cbSize > FIELD_OFFSET(EXP_SZ_LINK, szwTarget)) + { + wprintf(L" %hs\n", "SZ_ICON/LOGO3"); + goto printdbpaths; + } + else if (pDBH->dwSignature == EXP_DARWIN_ID_SIG && pDBH->cbSize == sizeof(EXP_DARWIN_LINK)) + { + wprintf(L" %hs\n", "DARWIN_LINK"); + goto printdbpaths; + } + else if (pDBH->dwSignature == NT_CONSOLE_PROPS_SIG && pDBH->cbSize >= sizeof(NT_CONSOLE_PROPS)) + { + wprintf(L" %hs\n", "NT_CONSOLE_PROPS"); + NT_CONSOLE_PROPS *p = (NT_CONSOLE_PROPS*)pDBH; + wprintf(L"%hsInsert=%d Quick=%d %ls\n", INDENT, p->bInsertMode, p->bQuickEdit, p->FaceName); + } + else if (pDBH->dwSignature == EXP_SPECIAL_FOLDER_SIG && pDBH->cbSize == sizeof(EXP_SPECIAL_FOLDER)) + { + wprintf(L" %hs\n", SigName); + EXP_SPECIAL_FOLDER *p = (EXP_SPECIAL_FOLDER*)pDBH; + wprintf(L"%hsCSIDL=%#x Offset=%#x\n", INDENT, p->idSpecialFolder, p->cbOffset); + } + else if (pDBH->dwSignature == EXP_TRACKER_SIG) + { + wprintf(L" %hs\n", SigName); + EXP_TRACKER *p = (EXP_TRACKER*)pDBH; + UINT len = FIELD_OFFSET(EXP_TRACKER, nLength) + p->nLength; + if (len >= FIELD_OFFSET(EXP_TRACKER, szMachineID)) + wprintf(L"%hsVersion=%d\n", INDENT, p->nVersion); + if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidVolume)) + wprintf(L"%hsMachine=%hs\n", INDENT, p->szMachineID); + if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidObject)) + Print("Volume", p->guidDroidVolume, INDENT); + if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidBirthVolume)) + Print("Object", p->guidDroidObject, INDENT); + if (len >= FIELD_OFFSET(EXP_TRACKER, guidDroidBirthObject)) + Print("BirthVolume", p->guidDroidBirthVolume, INDENT); + if (len >= sizeof(EXP_TRACKER)) + Print("BirthObject", p->guidDroidBirthObject, INDENT); + } + else if (pDBH->dwSignature == EXP_SHIM_SIG) + { + wprintf(L" %hs\n", SigName ? SigName : "SHIM"); + wprintf(L"%hs%ls\n", INDENT, (PWSTR)pFirstMember); + } + else if (pDBH->dwSignature == EXP_VISTA_ID_LIST_SIG && pDBH->cbSize >= 8 + 2) + { + wprintf(L" %hs\n", SigName); + Dump((LPITEMIDLIST)pFirstMember, INDENT + 1); + } + else + { + wprintf(SigName ? L" %hs\n" : L"\n", SigName); + } + } + SHFreeDataBlockList(pDBListHead); + } + } + pStream->Release(); + + // Now dump using the API + HRESULT hr2; + WCHAR buf[MAX_PATH * 2]; + IShellLink *pSL; + if (FAILED(hr2 = Open(Path, NULL, &pSL))) + return hr2; + wprintf(L"\n"); + + if (SUCCEEDED(hr2 = pSL->GetIDList(&pidl))) + { + Dump(pidl, "GetIDList:"); + ILFree(pidl); + } + else + { + wprintf(L"%hs: %#x\n", "GetIDList", hr2); + } + + static const BYTE GetPathFlags[] = { 0, SLGP_SHORTPATH, SLGP_RAWPATH, slgp_relativepriority }; + for (UINT i = 0; i < _countof(GetPathFlags); ++i) + { + if (SUCCEEDED(hr2 = pSL->GetPath(buf, _countof(buf), NULL, GetPathFlags[i]))) + wprintf(L"GetPath(%#.2x): %ls\n", GetPathFlags[i], buf); + else + wprintf(L"GetPath(%#.2x): %#x\n", GetPathFlags[i], hr2); + } + + if (SUCCEEDED(hr2 = pSL->GetWorkingDirectory(buf, _countof(buf)))) + wprintf(L"%hs: %ls\n", "GetWorkingDirectory", buf); + else + wprintf(L"%hs: %#x\n", "GetWorkingDirectory", hr2); + + if (SUCCEEDED(hr2 = pSL->GetArguments(buf, _countof(buf)))) + wprintf(L"%hs: %ls\n", "GetArguments", buf); + else + wprintf(L"%hs: %#x\n", "GetArguments", hr2); + + if (SUCCEEDED(hr2 = pSL->GetDescription(buf, _countof(buf)))) + wprintf(L"%hs: %ls\n", "GetDescription", buf); + else + wprintf(L"%hs: %#x\n", "GetDescription", hr2); + + int index = 123456789; + if (SUCCEEDED(hr2 = pSL->GetIconLocation(buf, _countof(buf), &index))) + wprintf(L"%hs: %ls,%d\n", "GetIconLocation", buf, index); + else + wprintf(L"%hs: %#x\n", "GetIconLocation", hr2); + + IExtractIconW *pEI; + if (SUCCEEDED(pSL->QueryInterface(IID_PPV_ARG(IExtractIconW, &pEI)))) + { + index = 123456789; + UINT flags = 0; + if (SUCCEEDED(hr2 = pEI->GetIconLocation(0, buf, _countof(buf), &index, &flags))) + wprintf(L"%hs: %#x %ls,%d %#.4x\n", "EI:GetIconLocation", hr2, buf, index, flags); + else + wprintf(L"%hs: %#x %#.4x\n", "EI:GetIconLocation", hr2, flags); + pEI->Release(); + } + + pSL->Release(); + return hr; +} + +#define RemoveSLDF(pSL, Flag) RemoveDatablock((pSL), 0, (Flag)) +#define AddSLDF(pSL, Flag) RemoveDatablock((pSL), 0, 0, (Flag)) + +static HRESULT RemoveDatablock(IShellLinkW *pSL, UINT Signature, UINT KillFlag = 0, UINT AddFlag = 0) +{ + IShellLinkDataList *pSLDL; + HRESULT hr = pSL->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pSLDL)); + if (SUCCEEDED(hr)) + { + if (Signature) + hr = pSLDL->RemoveDataBlock(Signature); + DWORD OrgFlags; + if ((AddFlag | KillFlag) && SUCCEEDED(pSLDL->GetFlags(&OrgFlags))) + pSLDL->SetFlags((OrgFlags & ~KillFlag) | AddFlag); + pSLDL->Release(); + } + return hr; +} + +template<class T> +static HRESULT AddDataBlock(IShellLinkW *pSL, T &DataBlock) +{ + IShellLinkDataList *pSLDL; + HRESULT hr = pSL->QueryInterface(IID_PPV_ARG(IShellLinkDataList, &pSLDL)); + if (SUCCEEDED(hr)) + { + hr = pSLDL->AddDataBlock(&DataBlock); + pSLDL->Release(); + } + return hr; +} + +static BOOL TryGetUpdatedPidl(IShellLinkW *pSL, PIDLIST_ABSOLUTE &pidl) +{ + PIDLIST_ABSOLUTE pidlNew; + if (pSL->GetIDList(&pidlNew) == S_OK) + { + ILFree(pidl); + pidl = pidlNew; + } + return pidl != NULL; +} + +static HRESULT CreateCommand(PCWSTR LnkPath, UINT argc, WCHAR **argv) +{ + IShellLinkW *pSL; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLinkW, &pSL)); + if (FAILED(hr)) + return hr; + + UINT TargetCount = 0, DumpResult = 0, ForceAddSLDF = 0, ForceRemoveSLDF = 0; + PIDLIST_ABSOLUTE pidlTarget = NULL; + for (UINT i = 0; i < argc && SUCCEEDED(hr); ++i) + { + hr = E_INVALIDARG; + if (!StrCmpIW(argv[i], L"/Pidl") && i + 1 < argc) + { + bool Simple = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; + if (SUCCEEDED(hr = SHParseNameEx(argv[++i], &pidlTarget, Simple))) + TargetCount += SUCCEEDED(hr = pSL->SetIDList(pidlTarget)); + } + else if (!StrCmpIW(argv[i], L"/Path") && i + 1 < argc) + { + TargetCount += SUCCEEDED(hr = pSL->SetPath(argv[++i])); + } + else if (!StrCmpIW(argv[i], L"/Icon") && i + 1 < argc) + { + int index = PathParseIconLocation(argv[++i]); + hr = pSL->SetIconLocation(argv[i], index); + } + else if (!StrCmpIW(argv[i], L"/Arguments") && i + 1 < argc) + { + hr = pSL->SetArguments(argv[++i]); + } + else if (!StrCmpIW(argv[i], L"/RelativePath") && i + 1 < argc) + { + hr = pSL->SetRelativePath(argv[++i], 0); + } + else if (!StrCmpIW(argv[i], L"/WorkingDir") && i + 1 < argc) + { + hr = pSL->SetWorkingDirectory(argv[++i]); + } + else if (!StrCmpIW(argv[i], L"/Comment") && i + 1 < argc) + { + hr = pSL->SetDescription(argv[++i]); + } + else if (!StrCmpIW(argv[i], L"/ShowCmd") && i + 1 < argc) + { + if (int sw = StrToNum(argv[++i])) // Don't allow SW_HIDE + hr = pSL->SetShowCmd(sw); + } + else if (!StrCmpIW(argv[i], L"/AddExp") && ++i < argc) + { + EXP_SZ_LINK db = { sizeof(db), EXP_SZ_LINK_SIG }; + WideCharToMultiByte(CP_ACP, 0, argv[i], -1, db.szTarget, _countof(db.szTarget), NULL, NULL); + lstrcpynW(db.szwTarget, argv[i], _countof(db.szwTarget)); + if (SUCCEEDED(hr = AddDataBlock(pSL, db))) + TargetCount += SUCCEEDED(hr = AddSLDF(pSL, SLDF_HAS_EXP_SZ)); + } + else if (!StrCmpIW(argv[i], L"/AddExpIcon") && ++i < argc) + { + EXP_SZ_LINK db = { sizeof(db), EXP_SZ_ICON_SIG }; + WideCharToMultiByte(CP_ACP, 0, argv[i], -1, db.szTarget, _countof(db.szTarget), NULL, NULL); + lstrcpynW(db.szwTarget, argv[i], _countof(db.szwTarget)); + if (SUCCEEDED(hr = AddDataBlock(pSL, db))) + hr = AddSLDF(pSL, SLDF_HAS_EXP_ICON_SZ); + } + else if (!StrCmpIW(argv[i], L"/RemoveExpIcon")) + { + hr = RemoveDatablock(pSL, EXP_SZ_ICON_SIG, SLDF_HAS_EXP_ICON_SZ); + } + else if (!StrCmpIW(argv[i], L"/SpecialFolderOffset") && i + 2 < argc) + { + EXP_SPECIAL_FOLDER db = { sizeof(db), EXP_SPECIAL_FOLDER_SIG }; + if ((db.idSpecialFolder = StrToNum(argv[++i])) == 0) + { + if (!StrCmpIW(argv[i], L"Windows")) + db.idSpecialFolder = CSIDL_WINDOWS; + if (!StrCmpIW(argv[i], L"System")) + db.idSpecialFolder = CSIDL_SYSTEM; + } + db.cbOffset = StrToNum(argv[++i]); + if ((signed)db.cbOffset < 0 && TryGetUpdatedPidl(pSL, pidlTarget)) + { + UINT i = 0, c = -(signed)db.cbOffset; + db.cbOffset = 0; + for (PIDLIST_ABSOLUTE pidl = pidlTarget; i < c && pidl->mkid.cb; ++i, pidl = ILGetNext(pidl)) + db.cbOffset += pidl->mkid.cb; + } + hr = AddDataBlock(pSL, db); + } + else if (!StrCmpIW(argv[i], L"/RemoveDatablock")) + { + if (UINT sig = GetDatablockSignature(argv[++i])) + hr = RemoveDatablock(pSL, sig); + } + else if (!StrCmpIW(argv[i], L"/AddSLDF") && i + 1 < argc) + { + bool Force = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; + UINT Flag = GetSLDF(argv[++i]); + if (Flag > SLDF_UNICODE) + hr = AddSLDF(pSL, Flag); + if (Force) + ForceAddSLDF |= Flag; + } + else if (!StrCmpIW(argv[i], L"/RemoveSLDF") && i + 1 < argc) + { + bool Force = !StrCmpIW(argv[i + 1], L"/Force") && i + 2 < argc && ++i; + UINT Flag = GetSLDF(argv[++i]); + if (Flag) + hr = RemoveSLDF(pSL, Flag); + if (Force) + ForceRemoveSLDF |= Flag; + } + else if (!StrCmpIW(argv[i], L"/Dump")) + { + DumpResult++; + hr = S_OK; + } + else if (!StrCmpIW(argv[i], L"/ForceCreate")) + { + TargetCount++; + hr = S_OK; + } + else + { + wprintf(L"%hsUnable to parse \"%ls\"!\n", "Error: ", argv[i]); + } + } + + if (SUCCEEDED(hr)) + { + if (TargetCount) + { + if (SUCCEEDED(hr = Save(pSL, LnkPath)) && (ForceAddSLDF | ForceRemoveSLDF)) + { + IStream *pStream; + if (SUCCEEDED(Open(LnkPath, &pStream, NULL, STGM_READWRITE))) + { + SHELL_LINK_HEADER slh; + if (SUCCEEDED(IStream_Read(pStream, &slh, sizeof(slh)))) + { + slh.dwFlags = (slh.dwFlags & ~ForceRemoveSLDF) | ForceAddSLDF; + if (SUCCEEDED(Seek(pStream, 0, FILE_BEGIN))) + IStream_Write(pStream, &slh, sizeof(slh)); + } + pStream->Release(); + } + } + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + if (SUCCEEDED(hr) && DumpResult) + DumpCommand(LnkPath); + } + pSL->Release(); + ILFree(pidlTarget); + return hr; +} + +static HRESULT RemoveDatablockCommand(PCWSTR LnkPath, UINT argc, WCHAR **argv) +{ + IShellLink *pSL; + HRESULT hr = Open(LnkPath, NULL, &pSL, STGM_READWRITE); + if (FAILED(hr)) + return hr; + + hr = E_INVALIDARG; + for (UINT i = 0; i < argc; ++i) + { + UINT Sig = GetDatablockSignature(argv[i]); + if (!Sig) + { + hr = E_INVALIDARG; + break; + } + hr = RemoveDatablock(pSL, Sig); + } + + if (SUCCEEDED(hr)) + hr = Save(pSL, LnkPath); + pSL->Release(); + return hr; +} + +static int ProcessCommandLine(int argc, WCHAR **argv) +{ + if (argc < 3) + { + wprintf(L"%hs", g_Usage); + return argc < 2 ? 0 : ERROR_INVALID_PARAMETER; + } + + if (!StrCmpIW(argv[1], L"Dump")) + return SuccessOrReportError(DumpCommand(argv[2])); + if (!StrCmpIW(argv[1], L"Create")) + return SuccessOrReportError(CreateCommand(argv[2], argc - 3, &argv[3])); + if (!StrCmpIW(argv[1], L"RemoveDatablock")) + return SuccessOrReportError(RemoveDatablockCommand(argv[2], argc - 3, &argv[3])); + + return SuccessOrReportError(ERROR_INVALID_PARAMETER); +} + +EXTERN_C int wmain(int argc, WCHAR **argv) +{ + HRESULT hrInit = OleInitialize(NULL); + int result = ProcessCommandLine(argc, argv); + if (SUCCEEDED(hrInit)) + OleUninitialize(); + return result; +} diff --git a/modules/rosapps/applications/devutils/lnktool/lnktool.rc b/modules/rosapps/applications/devutils/lnktool/lnktool.rc new file mode 100644 index 00000000000..fceb662389f --- /dev/null +++ b/modules/rosapps/applications/devutils/lnktool/lnktool.rc @@ -0,0 +1,9 @@ +#include <windef.h> +#include <winuser.h> + +#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Shell Link Utility" +#define REACTOS_STR_INTERNAL_NAME "lnktool" +#define REACTOS_STR_ORIGINAL_FILENAME "lnktool.exe" +#include <reactos/version.rc> + +#include <reactos/manifest_exe.rc> diff --git a/sdk/include/psdk/shlwapi.h b/sdk/include/psdk/shlwapi.h index 547cf01ea8a..aac7061c01e 100644 --- a/sdk/include/psdk/shlwapi.h +++ b/sdk/include/psdk/shlwapi.h @@ -25,6 +25,15 @@ #include <objbase.h> #include <shtypes.h> +#ifndef _SHLWAPI_ +#define LWSTDAPI_(type) EXTERN_C DECLSPEC_IMPORT type WINAPI +#define LWSTDAPIV_(type) EXTERN_C DECLSPEC_IMPORT type STDAPIVCALLTYPE +#else +#define LWSTDAPI_(type) type WINAPI +#define LWSTDAPIV_(type) type STDAPIVCALLTYPE +#endif +#define LWSTDAPI LWSTDAPI_(HRESULT) + #ifdef __cplusplus extern "C" { #endif /* defined(__cplusplus) */ @@ -1897,6 +1906,14 @@ SHCreateStreamOnFileEx( HRESULT WINAPI SHCreateStreamWrapper(LPBYTE,DWORD,DWORD,struct IStream**); +#ifndef _SHLWAPI_ +LWSTDAPI IStream_Reset(_In_ struct IStream*); +#if !defined(IStream_Read) && defined(__cplusplus) +LWSTDAPI IStream_Read(_In_ struct IStream*, _Out_ void*, _In_ ULONG); +LWSTDAPI IStream_Write(_In_ struct IStream*, _In_ const void*, _In_ ULONG); +#endif +#endif + #endif /* NO_SHLWAPI_STREAM */ #ifndef NO_SHLWAPI_SHARED diff --git a/sdk/include/reactos/shellutils.h b/sdk/include/reactos/shellutils.h index c45b6ad8770..2165a6706dc 100644 --- a/sdk/include/reactos/shellutils.h +++ b/sdk/include/reactos/shellutils.h @@ -127,6 +127,7 @@ SHELL_ErrorBox(H hwndOwner, UINT Error = GetLastError()) #endif #ifdef __cplusplus +#ifdef DECLARE_CLASSFACTORY // ATL template <typename T> class CComCreatorCentralInstance { @@ -456,6 +457,7 @@ HRESULT inline ShellObjectCreatorInit(T1 initArg1, T2 initArg2, T3 initArg3, T4 return hResult; } +#endif // DECLARE_CLASSFACTORY (ATL) template<class P, class R> static HRESULT SHILClone(P pidl, R *ppOut) {