SHELL32: implement loading and saving of MSI advertised shortcut info

Mike McCormack mike at codeweavers.com
Fri Feb 25 06:40:48 CST 2005


ChangeLog:
* implement loading and saving of MSI advertised shortcut info
* make more test cases pass
* read and write the location block
* improve the binary compatibility of lnk files
-------------- next part --------------
Index: dlls/shell32/shelllink.c
===================================================================
RCS file: /home/wine/wine/dlls/shell32/shelllink.c,v
retrieving revision 1.83
diff -u -p -r1.83 shelllink.c
--- dlls/shell32/shelllink.c	17 Feb 2005 11:51:44 -0000	1.83
+++ dlls/shell32/shelllink.c	25 Feb 2005 12:34:22 -0000
@@ -1,7 +1,8 @@
 /*
  *
- *	Copyright 1997	Marcus Meissner
- *	Copyright 1998	Juergen Schmied
+ *      Copyright 1997  Marcus Meissner
+ *      Copyright 1998  Juergen Schmied
+ *      Copyright 2005  Mike McCormack
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,6 +22,14 @@
  *   Nearly complete informations about the binary formats 
  *   of .lnk files available at http://www.wotsit.org
  *
+ *  You can use winedump to examine the contents of a link file:
+ *   winedump lnk sc.lnk
+ *
+ *  MSI advertised shortcuts are totally undocumented.  They provide an
+ *   icon for a program that is not yet installed, and invoke MSI to
+ *   install the program when the shortcut is clicked on.  They are
+ *   created by passing a special string to SetPath, and the information
+ *   in that string is parsed an stored.
  */
 
 #include "config.h"
@@ -58,19 +67,28 @@
 #include "shlguid.h"
 #include "shlwapi.h"
 
+#include "initguid.h"
+
 WINE_DEFAULT_DEBUG_CHANNEL(shell);
 
+DEFINE_GUID( SHELL32_AdvtShortcutProduct,
+       0x9db1186f,0x40df,0x11d1,0xaa,0x8c,0x00,0xc0,0x4f,0xb6,0x78,0x63);
+DEFINE_GUID( SHELL32_AdvtShortcutComponent,
+       0x9db1186e,0x40df,0x11d1,0xaa,0x8c,0x00,0xc0,0x4f,0xb6,0x78,0x63);
+
 /* link file formats */
 
 /* flag1: lnk elements: simple link has 0x0B */
-#define SCF_PIDL   1
-#define SCF_NORMAL 2
+#define SCF_PIDL 1
+#define SCF_LOCATION 2
 #define SCF_DESCRIPTION 4
 #define SCF_RELATIVE 8
 #define SCF_WORKDIR 0x10
 #define SCF_ARGS 0x20
 #define SCF_CUSTOMICON 0x40
 #define SCF_UNICODE 0x80
+#define SCF_PRODUCT 0x800
+#define SCF_COMPONENT 0x1000
 
 #include "pshpack1.h"
 
@@ -113,6 +131,21 @@ typedef struct _LOCAL_VOLUME_INFO
     DWORD dwVolLabelOfs;
 } LOCAL_VOLUME_INFO;
 
+typedef struct tagLINK_ADVERTISEINFO
+{
+    DWORD size;
+    DWORD magic;
+    CHAR  bufA[MAX_PATH];
+    WCHAR bufW[MAX_PATH];
+} LINK_ADVERTISEINFO;
+
+typedef struct volume_info_t
+{
+    DWORD type;
+    DWORD serial;
+    WCHAR label[12];  /* assume 8.3 */
+} volume_info;
+
 #include "poppack.h"
 
 static IShellLinkAVtbl		slvt;
@@ -146,6 +179,9 @@ typedef struct
 	LPWSTR        sWorkDir;
 	LPWSTR        sDescription;
 	LPWSTR        sPathRel;
+ 	LPWSTR        sProduct;
+ 	LPWSTR        sComponent;
+	volume_info   volume;
 
 	BOOL		bDirty;
 } IShellLinkImpl;
@@ -172,7 +208,6 @@ inline static LPWSTR HEAP_strdupAtoW( HA
     return p;
 }
 
-
 /**************************************************************************
  *  IPersistFile_QueryInterface
  */
@@ -251,8 +286,9 @@ static HRESULT WINAPI IPersistFile_fnLoa
 
 static BOOL StartLinkProcessor( LPCOLESTR szLink )
 {
-    static const WCHAR szFormat[] = {'w','i','n','e','m','e','n','u','b','u','i','l','d','e','r','.','e','x','e',
-                              ' ','-','r',' ','"','%','s','"',0 };
+    static const WCHAR szFormat[] = {
+        'w','i','n','e','m','e','n','u','b','u','i','l','d','e','r','.','e','x','e',
+        ' ','-','r',' ','"','%','s','"',0 };
     LONG len;
     LPWSTR buffer;
     STARTUPINFOW si;
@@ -290,7 +326,7 @@ static HRESULT WINAPI IPersistFile_fnSav
 
     TRACE("(%p)->(%s)\n",This,debugstr_w(pszFileName));
 
-    if (!pszFileName || !This->sPath)
+    if (!pszFileName)
         return E_FAIL;
 
     r = CreateStreamOnFile(pszFileName, STGM_READWRITE | STGM_CREATE, &stm);
@@ -469,44 +505,175 @@ static HRESULT Stream_LoadString( IStrea
     return S_OK;
 }
 
-static HRESULT Stream_LoadLocation( IStream* stm )
+static HRESULT Stream_ReadChunk( IStream* stm, LPVOID *data )
 {
     DWORD size;
     ULONG count;
     HRESULT r;
-    LOCATION_INFO *loc;
+    struct sized_chunk {
+        DWORD size;
+        unsigned char data[1];
+    } *chunk;
 
     TRACE("%p\n",stm);
 
     r = IStream_Read( stm, &size, sizeof(size), &count );
-    if( FAILED( r ) )
-        return r;
-    if( count != sizeof(loc->dwTotalSize) )
+    if( FAILED( r )  || count != sizeof(size) )
         return E_FAIL;
 
-    loc = HeapAlloc( GetProcessHeap(), 0, size );
-    if( ! loc )
+    chunk = HeapAlloc( GetProcessHeap(), 0, size );
+    if( !chunk )
         return E_OUTOFMEMORY;
 
-    r = IStream_Read( stm, &loc->dwHeaderSize, size-sizeof(size), &count );
+    chunk->size = size;
+    r = IStream_Read( stm, chunk->data, size - sizeof(size), &count );
+    if( FAILED( r ) || count != (size - sizeof(size)) )
+    {
+        HeapFree( GetProcessHeap(), 0, chunk );
+        return E_FAIL;
+    }
+
+    TRACE("Read %ld bytes\n",chunk->size);
+
+    *data = (LPVOID) chunk;
+
+    return S_OK;
+}
+
+static BOOL Stream_LoadVolume( LOCAL_VOLUME_INFO *vol, volume_info *volume )
+{
+    const int label_sz = sizeof volume->label/sizeof volume->label[0];
+    LPSTR label;
+    int len;
+
+    volume->serial = vol->dwVolSerial;
+    volume->type = vol->dwType;
+
+    if( !vol->dwVolLabelOfs )
+        return FALSE;
+    if( vol->dwSize <= vol->dwVolLabelOfs )
+        return FALSE;
+    len = vol->dwSize - vol->dwVolLabelOfs;
+
+    label = (LPSTR) vol;
+    label += vol->dwVolLabelOfs;
+    MultiByteToWideChar( CP_ACP, 0, label, len, volume->label, label_sz-1);
+
+    return TRUE;
+}
+
+static LPWSTR Stream_LoadPath( LPSTR p, DWORD maxlen )
+{
+    int len = 0, wlen;
+    LPWSTR path;
+
+    while( p[len] && (len < maxlen) )
+        len++;
+
+    wlen = MultiByteToWideChar(CP_ACP, 0, p, len, NULL, 0);
+    path = HeapAlloc(GetProcessHeap(), 0, (wlen+1)*sizeof(WCHAR));
+    MultiByteToWideChar(CP_ACP, 0, p, len, path, wlen);
+    path[wlen] = 0;
+
+    return path;
+}
+
+static HRESULT Stream_LoadLocation( IStream *stm,
+                volume_info *volume, LPWSTR *path )
+{
+    unsigned char *p = NULL;
+    LOCATION_INFO *loc;
+    HRESULT r;
+    int n;
+
+    r = Stream_ReadChunk( stm, (LPVOID*) &p );
+    if( FAILED(r) )
+        return r;
+
+    loc = (LOCATION_INFO*) p;
+    if (loc->dwTotalSize < sizeof(LOCATION_INFO))
+    {
+        HeapFree( GetProcessHeap(), 0, p );
+        return E_FAIL;
+    }
+
+    /* if there's valid local volume information, load it */
+    if( loc->dwVolTableOfs && 
+       ((loc->dwVolTableOfs + sizeof(LOCAL_VOLUME_INFO)) <= loc->dwTotalSize) )
+    {
+        LOCAL_VOLUME_INFO *volume_info;
+
+        volume_info = (LOCAL_VOLUME_INFO*) &p[loc->dwVolTableOfs];
+        Stream_LoadVolume( volume_info, volume );
+    }
+
+    /* if there's a local path, load it */
+    n = loc->dwLocalPathOfs;
+    if( n && (n < loc->dwTotalSize) )
+        *path = Stream_LoadPath( &p[n], loc->dwTotalSize - n );
+
+    TRACE("type %ld serial %08lx name %s path %s\n", volume->type,
+          volume->serial, debugstr_w(volume->label), debugstr_w(*path));
+
+    HeapFree( GetProcessHeap(), 0, p );
+    return S_OK;
+}
+
+/*
+ *  The format of the advertised shortcut info seems to be:
+ *
+ *  Offset     Description
+ *  ------     -----------
+ *
+ *    0          Length of the block (4 bytes, usually 0x314)
+ *    4          tag (dword)
+ *    8          string data in ASCII
+ *    8+0x104    string data in UNICODE
+ *
+ * In the original Win32 implementation the buffers are not initialized
+ *  to zero, so data trailing the string is random garbage.
+ */
+static HRESULT Stream_LoadAdvertiseInfo( IStream* stm, LPWSTR *str )
+{
+    DWORD size;
+    ULONG count;
+    HRESULT r;
+    LINK_ADVERTISEINFO buffer;
+    
+    TRACE("%p\n",stm);
+
+    r = IStream_Read( stm, &buffer.size, sizeof (DWORD), &count );
     if( FAILED( r ) )
-        goto end;
-    if( count != (size - sizeof(size)) )
+        return r;
+
+    /* make sure that we read the size of the structure even on error */
+    size = sizeof buffer - sizeof (DWORD);
+    if( buffer.size != sizeof buffer )
     {
-        r = E_FAIL;
-        goto end;
+        ERR("Ooops.  This structure is different to expected...\n");
+        return E_FAIL;
     }
-    loc->dwTotalSize = size;
 
-    TRACE("Read %ld bytes\n",count);
+    r = IStream_Read( stm, &buffer.magic, size, &count );
+    if( FAILED( r ) )
+        return r;
 
-    /* FIXME: do something useful with it */
-    HeapFree( GetProcessHeap(), 0, loc );
+    if( count != size )
+        return E_FAIL;
+
+    TRACE("magic %08lx  string = %s\n", buffer.magic, debugstr_w(buffer.bufW));
+
+    if( (buffer.magic&0xffff0000) != 0xa0000000 )
+    {
+        ERR("Unknown magic number %08lx in advertised shortcut\n", buffer.magic);
+        return E_FAIL;
+    }
+
+    *str = HeapAlloc( GetProcessHeap(), 0, 
+                     (strlenW(buffer.bufW)+1) * sizeof(WCHAR) );
+    strcpyW( *str, buffer.bufW );
 
     return S_OK;
-end:
-    HeapFree( GetProcessHeap(), 0, loc );
-    return r;
 }
 
 /************************************************************************
@@ -519,12 +686,12 @@ static HRESULT WINAPI IPersistStream_fnL
     LINK_HEADER hdr;
     ULONG    dwBytesRead;
     BOOL     unicode;
-    WCHAR    sTemp[MAX_PATH];
     HRESULT  r;
+    DWORD    zero;
 
     _ICOM_THIS_From_IPersistStream(IShellLinkImpl, iface);
 
-    TRACE("(%p)(%p)\n", This, stm);
+    TRACE("%p %p\n", This, stm);
 
     if( !stm )
 	  return STG_E_INVALIDPOINTER;
@@ -541,30 +708,61 @@ static HRESULT WINAPI IPersistStream_fnL
     if( !IsEqualIID(&hdr.MagicGuid, &CLSID_ShellLink) )
         return E_FAIL;
 
-    /* if( hdr.dwFlags & SCF_PIDL ) */  /* FIXME: seems to always have a PIDL */
-    {
-        r = ILLoadFromStream( stm, &This->pPidl );
-        if( FAILED( r ) )
-            return r;
-    }
+    /* free all the old stuff */
+    ILFree(This->pPidl);
+    This->pPidl = NULL;
+    memset( &This->volume, 0, sizeof This->volume );
+    HeapFree(GetProcessHeap(), 0, This->sPath);
+    This->sPath = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sDescription);
+    This->sDescription = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sPathRel);
+    This->sPathRel = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sWorkDir);
+    This->sWorkDir = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sArgs);
+    This->sArgs = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sIcoPath);
+    This->sIcoPath = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sProduct);
+    This->sProduct = NULL;
+    HeapFree(GetProcessHeap(), 0, This->sComponent);
+    This->sComponent = NULL;
+        
     This->wHotKey = (WORD)hdr.wHotKey;
     This->iIcoNdx = hdr.nIcon;
     FileTimeToSystemTime (&hdr.Time1, &This->time1);
     FileTimeToSystemTime (&hdr.Time2, &This->time2);
     FileTimeToSystemTime (&hdr.Time3, &This->time3);
-#if 1
-    GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&This->time1, NULL, sTemp, 256);
-    TRACE("-- time1: %s\n", debugstr_w(sTemp) );
-    GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&This->time2, NULL, sTemp, 256);
-    TRACE("-- time1: %s\n", debugstr_w(sTemp) );
-    GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE,&This->time3, NULL, sTemp, 256);
-    TRACE("-- time1: %s\n", debugstr_w(sTemp) );
-    pdump (This->pPidl);
-#endif
-    if( hdr.dwFlags & SCF_NORMAL )
-        r = Stream_LoadLocation( stm );
+    if (TRACE_ON(shell))
+    {
+        WCHAR sTemp[MAX_PATH];
+        GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE, &This->time1,
+                       NULL, sTemp, sizeof(sTemp)/sizeof(*sTemp));
+        TRACE("-- time1: %s\n", debugstr_w(sTemp) );
+        GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE, &This->time2,
+                       NULL, sTemp, sizeof(sTemp)/sizeof(*sTemp));
+        TRACE("-- time2: %s\n", debugstr_w(sTemp) );
+        GetDateFormatW(LOCALE_USER_DEFAULT,DATE_SHORTDATE, &This->time3,
+                       NULL, sTemp, sizeof(sTemp)/sizeof(*sTemp));
+        TRACE("-- time3: %s\n", debugstr_w(sTemp) );
+    }
+
+    /* load all the new stuff */
+    if( hdr.dwFlags & SCF_PIDL )
+    {
+        r = ILLoadFromStream( stm, &This->pPidl );
+        if( FAILED( r ) )
+            return r;
+    }
+    pdump(This->pPidl);
+
+    /* load the location information */
+    if( hdr.dwFlags & SCF_LOCATION )
+        r = Stream_LoadLocation( stm, &This->volume, &This->sPath );
     if( FAILED( r ) )
         goto end;
+
     unicode = hdr.dwFlags & SCF_UNICODE;
     if( hdr.dwFlags & SCF_DESCRIPTION )
     {
@@ -583,7 +781,7 @@ static HRESULT WINAPI IPersistStream_fnL
         goto end;
 
     if( hdr.dwFlags & SCF_WORKDIR )
-          {
+    {
         r = Stream_LoadString( stm, unicode, &This->sWorkDir );
         TRACE("Working Dir  -> %s\n",debugstr_w(This->sWorkDir));
     }
@@ -606,6 +804,26 @@ static HRESULT WINAPI IPersistStream_fnL
     if( FAILED( r ) )
         goto end;
 
+    if( hdr.dwFlags & SCF_PRODUCT )
+    {
+        r = Stream_LoadAdvertiseInfo( stm, &This->sProduct );
+        TRACE("Product      -> %s\n",debugstr_w(This->sProduct));
+    }
+    if( FAILED( r ) )
+        goto end;
+
+    if( hdr.dwFlags & SCF_COMPONENT )
+    {
+        r = Stream_LoadAdvertiseInfo( stm, &This->sComponent );
+        TRACE("Component    -> %s\n",debugstr_w(This->sComponent));
+    }
+    if( FAILED( r ) )
+        goto end;
+
+    r = IStream_Read(stm, &zero, sizeof zero, &dwBytesRead);
+    if( FAILED( r ) || zero || dwBytesRead != sizeof zero )
+        ERR("Last word was not zero\n");
+
     TRACE("OK\n");
 
     pdump (This->pPidl);
@@ -640,19 +858,81 @@ static HRESULT Stream_WriteString( IStre
     return S_OK;
 }
 
-static HRESULT Stream_WriteLocationInfo( IStream* stm, LPCWSTR filename )
+/************************************************************************
+ * Stream_WriteLocationInfo
+ *
+ * Writes the location info to a stream
+ *
+ * FIXME: One day we might want to write the network volume information
+ *        and the final path.
+ *        Figure out how Windows deals with unicode pathes here.
+ */
+static HRESULT Stream_WriteLocationInfo( IStream* stm, LPCWSTR path,
+                                         volume_info *volume )
 {
-    LOCATION_INFO loc;
-    ULONG count;
+    DWORD total_size, path_size, volume_info_size, label_size, final_path_size;
+    LOCAL_VOLUME_INFO *vol;
+    LOCATION_INFO *loc;
+    LPSTR szLabel, szPath, szFinalPath;
+    ULONG count = 0;
 
-    FIXME("writing empty location info\n");
+    TRACE("%p %s %p\n", stm, debugstr_w(path), volume);
 
-    memset( &loc, 0, sizeof(loc) );
-    loc.dwTotalSize = sizeof(loc) - sizeof(loc.dwTotalSize);
+    /* figure out the size of everything */
+    label_size = WideCharToMultiByte( CP_ACP, 0, volume->label, -1,
+                                      NULL, 0, NULL, NULL );
+    path_size = WideCharToMultiByte( CP_ACP, 0, path, -1,
+                                     NULL, 0, NULL, NULL );
+    volume_info_size = sizeof *vol + label_size;
+    final_path_size = 1;
+    total_size = sizeof *loc + volume_info_size + path_size + final_path_size;
+
+    /* create pointers to everything */
+    loc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, total_size);
+    vol = (LOCAL_VOLUME_INFO*) &loc[1];
+    szLabel = (LPSTR) &vol[1];
+    szPath = &szLabel[label_size];
+    szFinalPath = &szPath[path_size];
+
+    /* fill in the location information header */
+    loc->dwTotalSize = total_size;
+    loc->dwHeaderSize = sizeof (*loc);
+    loc->dwFlags = 1;
+    loc->dwVolTableOfs = sizeof (*loc);
+    loc->dwLocalPathOfs = sizeof (*loc) + volume_info_size;
+    loc->dwNetworkVolTableOfs = 0;
+    loc->dwFinalPathOfs = sizeof (*loc) + volume_info_size + path_size;
+
+    /* fill in the volume information */
+    vol->dwSize = volume_info_size;
+    vol->dwType = volume->type;
+    vol->dwVolSerial = volume->serial;
+    vol->dwVolLabelOfs = sizeof (*vol);
+
+    /* copy in the strings */
+    WideCharToMultiByte( CP_ACP, 0, volume->label, -1,
+                         szLabel, label_size, NULL, NULL );
+    WideCharToMultiByte( CP_ACP, 0, path, -1,
+                         szPath, path_size, NULL, NULL );
+    szFinalPath[0] = 0;
 
-    /* FIXME: fill this in */
+    return IStream_Write( stm, loc, total_size, &count );
+}
 
-    return IStream_Write( stm, &loc, loc.dwTotalSize, &count );
+static HRESULT Stream_WriteAdvertiseInfo( IStream* stm, LPCWSTR string, DWORD magic )
+{
+    ULONG count;
+    LINK_ADVERTISEINFO buffer;
+    
+    TRACE("%p\n",stm);
+
+    memset( &buffer, 0, sizeof buffer );
+    buffer.size = sizeof buffer;
+    buffer.magic = magic;
+    strncpyW( buffer.bufW, string, MAX_PATH );
+    WideCharToMultiByte(CP_ACP, 0, string, -1, buffer.bufA, MAX_PATH, NULL, NULL );
+
+    return IStream_Write( stm, &buffer, buffer.size, &count );
 }
 
 /************************************************************************
@@ -670,38 +950,39 @@ static HRESULT WINAPI IPersistStream_fnS
     LINK_HEADER header;
     WCHAR   exePath[MAX_PATH];
     ULONG   count;
+    DWORD   zero;
     HRESULT r;
 
     _ICOM_THIS_From_IPersistStream(IShellLinkImpl, iface);
 
-    TRACE("(%p) %p %x\n", This, stm, fClearDirty);
+    TRACE("%p %p %x\n", This, stm, fClearDirty);
 
     *exePath = '\0';
 
     if (This->sPath)
     {
-        SHELL_FindExecutable(NULL, This->sPath, wOpen, exePath, MAX_PATH, NULL, NULL, NULL, NULL);
+        SHELL_FindExecutable(NULL, This->sPath, wOpen, exePath, MAX_PATH,
+                             NULL, NULL, NULL, NULL);
         /*
          * windows can create lnk files to executables that do not exist yet
          * so if the executable does not exist the just trust the path they
          * gave us
          */
-        if( !*exePath ) strcpyW(exePath,This->sPath);
+        if (!*exePath) strcpyW(exePath,This->sPath);
     }
 
-    /* if there's no PIDL, generate one */
-    if( ! This->pPidl ) This->pPidl = ILCreateFromPathW(exePath);
-
     memset(&header, 0, sizeof(header));
     header.dwSize = sizeof(header);
+    header.fStartup = This->iShowCmd;
     memcpy(&header.MagicGuid, &CLSID_ShellLink, sizeof(header.MagicGuid) );
 
     header.wHotKey = This->wHotKey;
     header.nIcon = This->iIcoNdx;
     header.dwFlags = SCF_UNICODE;   /* strings are in unicode */
-    header.dwFlags |= SCF_NORMAL;   /* how do we determine this ? */
     if( This->pPidl )
         header.dwFlags |= SCF_PIDL;
+    if( This->sPath )
+        header.dwFlags |= SCF_LOCATION;
     if( This->sDescription )
         header.dwFlags |= SCF_DESCRIPTION;
     if( This->sWorkDir )
@@ -710,6 +991,10 @@ static HRESULT WINAPI IPersistStream_fnS
         header.dwFlags |= SCF_ARGS;
     if( This->sIcoPath )
         header.dwFlags |= SCF_CUSTOMICON;
+    if( This->sProduct )
+        header.dwFlags |= SCF_PRODUCT;
+    if( This->sComponent )
+        header.dwFlags |= SCF_COMPONENT;
 
     SystemTimeToFileTime ( &This->time1, &header.Time1 );
     SystemTimeToFileTime ( &This->time2, &header.Time2 );
@@ -736,9 +1021,9 @@ static HRESULT WINAPI IPersistStream_fnS
         }
     }
 
-    Stream_WriteLocationInfo( stm, exePath );
+    if( This->sPath )
+        Stream_WriteLocationInfo( stm, exePath, &This->volume );
 
-    TRACE("Description = %s\n", debugstr_w(This->sDescription));
     if( This->sDescription )
         r = Stream_WriteString( stm, This->sDescription );
 
@@ -754,6 +1039,16 @@ static HRESULT WINAPI IPersistStream_fnS
     if( This->sIcoPath )
         r = Stream_WriteString( stm, This->sIcoPath );
 
+    if( This->sProduct )
+        r = Stream_WriteAdvertiseInfo( stm, This->sProduct, 0xa0000007 );
+
+    if( This->sComponent )
+        r = Stream_WriteAdvertiseInfo( stm, This->sComponent, 0xa0000006 );
+
+    /* the last field is a single zero dword */
+    zero = 0;
+    r = IStream_Write( stm, &zero, sizeof zero, &count );
+
     return S_OK;
 }
 
@@ -828,13 +1123,9 @@ HRESULT WINAPI IShellLink_Constructor (
 
 static BOOL SHELL_ExistsFileW(LPCWSTR path)
 {
-    HANDLE hfile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
-
-    if (hfile != INVALID_HANDLE_VALUE) {
-	CloseHandle(hfile);
-	return TRUE;
-    } else
+    if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(path))
         return FALSE;
+    return TRUE;
 }
 
 /**************************************************************************
@@ -1017,7 +1308,10 @@ static HRESULT WINAPI IShellLinkA_fnGetP
     TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%lu)(%s)\n",
           This, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(This->sPath));
 
-    if( cchMaxPath )
+    if (This->sComponent || This->sProduct)
+        return S_FALSE;
+
+    if (cchMaxPath)
         pszFile[0] = 0;
     if (This->sPath)
         WideCharToMultiByte( CP_ACP, 0, This->sPath, -1,
@@ -1025,18 +1319,16 @@ static HRESULT WINAPI IShellLinkA_fnGetP
 
     if (pfd) FIXME("(%p): WIN32_FIND_DATA is not yet filled.\n", This);
 
-    return NOERROR;
+    return S_OK;
 }
 
 static HRESULT WINAPI IShellLinkA_fnGetIDList(IShellLinkA * iface, LPITEMIDLIST * ppidl)
 {
-	IShellLinkImpl *This = (IShellLinkImpl *)iface;
-
-	TRACE("(%p)->(ppidl=%p)\n",This, ppidl);
+    IShellLinkImpl *This = (IShellLinkImpl *)iface;
 
-	*ppidl = ILClone(This->pPidl);
+    TRACE("(%p)->(ppidl=%p)\n",This, ppidl);
 
-	return NOERROR;
+    return IShellLinkW_GetIDList((IShellLinkW*)&(This->lpvtblw), ppidl);
 }
 
 static HRESULT WINAPI IShellLinkA_fnSetIDList(IShellLinkA * iface, LPCITEMIDLIST pidl)
@@ -1218,21 +1510,23 @@ static HRESULT WINAPI IShellLinkA_fnGetI
 
     TRACE("(%p)->(%p len=%u iicon=%p)\n", This, pszIconPath, cchIconPath, piIcon);
 
-    if (cchIconPath)
-        pszIconPath[0] = 0;
+    pszIconPath[0] = 0;
+    *piIcon = This->iIcoNdx;
 
-    if (This->sIcoPath) {
+    if (This->sIcoPath)
+    {
         WideCharToMultiByte(CP_ACP, 0, This->sIcoPath, -1, pszIconPath, cchIconPath, NULL, NULL);
-	*piIcon = This->iIcoNdx;
 	return S_OK;
     }
 
-    if (This->pPidl || This->sPath) {
+    if (This->pPidl || This->sPath)
+    {
 	IShellFolder* pdsk;
 
 	HRESULT hr = SHGetDesktopFolder(&pdsk);
 
-	if (SUCCEEDED(hr)) {
+	if (SUCCEEDED(hr))
+        {
 	    /* first look for an icon using the PIDL (if present) */
 	    if (This->pPidl)
 		hr = SHELL_PidlGeticonLocationA(pdsk, This->pPidl, pszIconPath, cchIconPath, piIcon);
@@ -1240,7 +1534,8 @@ static HRESULT WINAPI IShellLinkA_fnGetI
 		hr = E_FAIL;
 
 	    /* if we couldn't find an icon yet, look for it using the file system path */
-	    if (FAILED(hr) && This->sPath) {
+	    if (FAILED(hr) && This->sPath)
+            {
 		LPITEMIDLIST pidl;
 
 		hr = IShellFolder_ParseDisplayName(pdsk, 0, NULL, This->sPath, NULL, &pidl, NULL);
@@ -1256,8 +1551,8 @@ static HRESULT WINAPI IShellLinkA_fnGetI
 	}
 
 	return hr;
-    } else
-        return E_FAIL;
+    }
+    return S_OK;
 }
 
 static HRESULT WINAPI IShellLinkA_fnSetIconLocation(IShellLinkA * iface, LPCSTR pszIconPath,INT iIcon)
@@ -1333,28 +1628,20 @@ static HRESULT WINAPI IShellLinkA_fnReso
 
 static HRESULT WINAPI IShellLinkA_fnSetPath(IShellLinkA * iface, LPCSTR pszFile)
 {
+    HRESULT r;
+    LPWSTR str;
     IShellLinkImpl *This = (IShellLinkImpl *)iface;
-    char buffer[MAX_PATH];
-    LPSTR fname;
-    HRESULT hr = S_OK;
 
     TRACE("(%p)->(path=%s)\n",This, pszFile);
 
-    if(*pszFile == '\0')
-        *buffer = '\0';
-    else if (!GetFullPathNameA(pszFile, MAX_PATH, buffer, &fname))
-	return E_FAIL;
-    else if(!PathFileExistsA(buffer))
-        hr = S_FALSE;
-
-    HeapFree(GetProcessHeap(), 0, This->sPath);
-    This->sPath = HEAP_strdupAtoW(GetProcessHeap(), 0, buffer);
-    if( !This->sPath )
+    str = HEAP_strdupAtoW(GetProcessHeap(), 0, pszFile);
+    if( !str ) 
         return E_OUTOFMEMORY;
 
-    This->bDirty = TRUE;
+    r = IShellLinkW_SetPath((IShellLinkW*)&(This->lpvtblw), str);
+    HeapFree( GetProcessHeap(), 0, str );
 
-    return hr;
+    return r;
 }
 
 /**************************************************************************
@@ -1426,17 +1713,20 @@ static HRESULT WINAPI IShellLinkW_fnGetP
 {
     _ICOM_THIS_From_IShellLinkW(IShellLinkImpl, iface);
 
-    TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%lu)\n",
-		  This, pszFile, cchMaxPath, pfd, fFlags);
+    TRACE("(%p)->(pfile=%p len=%u find_data=%p flags=%lu)(%s)\n",
+          This, pszFile, cchMaxPath, pfd, fFlags, debugstr_w(This->sPath));
 
-    if( cchMaxPath )
+    if (This->sComponent || This->sProduct)
+        return S_FALSE;
+
+    if (cchMaxPath)
         pszFile[0] = 0;
-    if( This->sPath )
+    if (This->sPath)
         lstrcpynW( pszFile, This->sPath, cchMaxPath );
 
     if (pfd) FIXME("(%p): WIN32_FIND_DATA is not yet filled.\n", This);
 
-    return NOERROR;
+    return S_OK;
 }
 
 static HRESULT WINAPI IShellLinkW_fnGetIDList(IShellLinkW * iface, LPITEMIDLIST * ppidl)
@@ -1445,11 +1735,9 @@ static HRESULT WINAPI IShellLinkW_fnGetI
 
     TRACE("(%p)->(ppidl=%p)\n",This, ppidl);
 
-    if( This->pPidl)
-        *ppidl = ILClone( This->pPidl );
-    else
-        *ppidl = NULL;
-
+    if (!This->pPidl)
+        return S_FALSE;
+    *ppidl = ILClone(This->pPidl);
     return S_OK;
 }
 
@@ -1476,8 +1764,7 @@ static HRESULT WINAPI IShellLinkW_fnGetD
 
     TRACE("(%p)->(%p len=%u)\n",This, pszName, cchMaxName);
 
-    if( cchMaxName )
-        pszName[0] = 0;
+    pszName[0] = 0;
     if( This->sDescription )
         lstrcpynW( pszName, This->sDescription, cchMaxName );
 
@@ -1637,21 +1924,23 @@ static HRESULT WINAPI IShellLinkW_fnGetI
 
     TRACE("(%p)->(%p len=%u iicon=%p)\n", This, pszIconPath, cchIconPath, piIcon);
 
-    if (cchIconPath)
-        pszIconPath[0] = 0;
+    pszIconPath[0] = 0;
+    *piIcon = This->iIcoNdx;
 
-    if (This->sIcoPath) {
+    if (This->sIcoPath)
+    {
 	lstrcpynW(pszIconPath, This->sIcoPath, cchIconPath);
-	*piIcon = This->iIcoNdx;
 	return S_OK;
     }
 
-    if (This->pPidl || This->sPath) {
+    if (This->pPidl || This->sPath)
+    {
 	IShellFolder* pdsk;
 
 	HRESULT hr = SHGetDesktopFolder(&pdsk);
 
-	if (SUCCEEDED(hr)) {
+	if (SUCCEEDED(hr))
+        {
 	    /* first look for an icon using the PIDL (if present) */
 	    if (This->pPidl)
 		hr = SHELL_PidlGeticonLocationW(pdsk, This->pPidl, pszIconPath, cchIconPath, piIcon);
@@ -1659,12 +1948,14 @@ static HRESULT WINAPI IShellLinkW_fnGetI
 		hr = E_FAIL;
 
 	    /* if we couldn't find an icon yet, look for it using the file system path */
-	    if (FAILED(hr) && This->sPath) {
+	    if (FAILED(hr) && This->sPath)
+            {
 		LPITEMIDLIST pidl;
 
 		hr = IShellFolder_ParseDisplayName(pdsk, 0, NULL, This->sPath, NULL, &pidl, NULL);
 
-		if (SUCCEEDED(hr)) {
+		if (SUCCEEDED(hr))
+                {
 		    hr = SHELL_PidlGeticonLocationW(pdsk, pidl, pszIconPath, cchIconPath, piIcon);
 
 		    SHFree(pidl);
@@ -1673,10 +1964,9 @@ static HRESULT WINAPI IShellLinkW_fnGetI
 
 	    IShellFolder_Release(pdsk);
 	}
-
 	return hr;
-    } else
-        return E_FAIL;
+    }
+    return S_OK;
 }
 
 static HRESULT WINAPI IShellLinkW_fnSetIconLocation(IShellLinkW * iface, LPCWSTR pszIconPath,INT iIcon)
@@ -1756,6 +2046,100 @@ static HRESULT WINAPI IShellLinkW_fnReso
     return hr;
 }
 
+static LPWSTR ShellLink_GetAdvertisedArg(LPCWSTR str)
+{
+    LPWSTR ret;
+    LPCWSTR p;
+    DWORD len;
+
+    p = strchrW( str, ':' );
+    if( !p )
+        return NULL;
+    len = p - str;
+    ret = HeapAlloc( GetProcessHeap(), 0, sizeof(WCHAR)*(len+1));
+    if( !ret )
+        return ret;
+    memcpy( ret, str, sizeof(WCHAR)*len );
+    ret[len] = 0;
+    return ret;
+}
+
+static HRESULT ShellLink_SetAdvertiseInfo(IShellLinkImpl *This, LPCWSTR str)
+{
+    LPCWSTR szComponent = NULL, szProduct = NULL, p;
+    WCHAR szGuid[39];
+    HRESULT r;
+    GUID guid;
+    int len;
+
+    while( str[0] )
+    {
+        /* each segment must start with two colons */
+        if( str[0] != ':' || str[1] != ':' )
+            return E_FAIL;
+
+        /* the last segment is just two colons */
+        if( !str[2] )
+            break;
+        str += 2;
+
+        /* there must be a colon straight after a guid */
+        p = strchrW( str, ':' );
+        if( !p )
+            return E_FAIL;
+        len = p - str;
+        if( len != 38 )
+            return E_FAIL;
+
+        /* get the guid, and check it's validly formatted */
+        memcpy( szGuid, str, sizeof(WCHAR)*len );
+        szGuid[len] = 0;
+        r = CLSIDFromString( szGuid, &guid );
+        if( r != S_OK )
+            return r;
+        str = p + 1;
+
+        /* match it up to a guid that we care about */
+        if( IsEqualGUID( &guid, &SHELL32_AdvtShortcutComponent ) && !szComponent )
+            szComponent = str;
+        else if( IsEqualGUID( &guid, &SHELL32_AdvtShortcutProduct ) && !szProduct )
+            szProduct = str;
+        else
+            return E_FAIL;
+
+        /* skip to the next field */
+        str = strchrW( str, ':' );
+        if( !str )
+            return E_FAIL;
+    }
+
+    /* we have to have at least one of these two for an advertised shortcut */
+    if( !szComponent && !szProduct )
+        return E_FAIL;
+
+    This->sComponent = ShellLink_GetAdvertisedArg( szComponent );
+    This->sProduct = ShellLink_GetAdvertisedArg( szProduct );
+
+    TRACE("Component = %s\n", debugstr_w(This->sComponent));
+    TRACE("Product = %s\n", debugstr_w(This->sProduct));
+
+    return S_OK;
+}
+
+static BOOL ShellLink_GetVolumeInfo(LPWSTR path, volume_info *volume)
+{
+    const int label_sz = sizeof volume->label/sizeof volume->label[0];
+    WCHAR drive[4] = { path[0], ':', '\\', 0 };
+    BOOL r;
+
+    volume->type = GetDriveTypeW(drive);
+    r = GetVolumeInformationW(drive, volume->label, label_sz,
+                              &volume->serial, NULL, NULL, NULL, 0);
+    TRACE("r = %d type %ld serial %08lx name %s\n", r,
+          volume->type, volume->serial, debugstr_w(volume->label));
+    return r;
+}
+
 static HRESULT WINAPI IShellLinkW_fnSetPath(IShellLinkW * iface, LPCWSTR pszFile)
 {
     _ICOM_THIS_From_IShellLinkW(IShellLinkImpl, iface);
@@ -1765,20 +2149,35 @@ static HRESULT WINAPI IShellLinkW_fnSetP
 
     TRACE("(%p)->(path=%s)\n",This, debugstr_w(pszFile));
 
-    if (*pszFile == '\0')
-        *buffer = '\0';
-    else if (!GetFullPathNameW(pszFile, MAX_PATH, buffer, &fname))
-	return E_FAIL;
-    else if(!PathFileExistsW(buffer))
-        hr = S_FALSE;
-
     HeapFree(GetProcessHeap(), 0, This->sPath);
-    This->sPath = HeapAlloc( GetProcessHeap(), 0,
+    This->sPath = NULL;
+
+    HeapFree(GetProcessHeap(), 0, This->sComponent);
+    This->sComponent = NULL;
+
+    if (This->pPidl)
+        ILFree(This->pPidl);
+    This->pPidl = NULL;
+
+    if (S_OK != ShellLink_SetAdvertiseInfo( This, pszFile ))
+    {
+        if (*pszFile == '\0')
+            *buffer = '\0';
+        else if (!GetFullPathNameW(pszFile, MAX_PATH, buffer, &fname))
+	    return E_FAIL;
+        else if(!PathFileExistsW(buffer))
+            hr = S_FALSE;
+
+        This->pPidl = SHSimpleIDListFromPathW(pszFile);
+        ShellLink_GetVolumeInfo(buffer, &This->volume);
+
+        This->sPath = HeapAlloc( GetProcessHeap(), 0,
                              (lstrlenW( buffer )+1) * sizeof (WCHAR) );
-    if (!This->sPath)
-        return E_OUTOFMEMORY;
+        if (!This->sPath)
+            return E_OUTOFMEMORY;
 
-    lstrcpyW(This->sPath, buffer);
+        lstrcpyW(This->sPath, buffer);
+    }
     This->bDirty = TRUE;
 
     return hr;
Index: dlls/shell32/tests/shelllink.c
===================================================================
RCS file: /home/wine/wine/dlls/shell32/tests/shelllink.c,v
retrieving revision 1.3
diff -u -p -r1.3 shelllink.c
--- dlls/shell32/tests/shelllink.c	16 Feb 2005 16:27:42 -0000	1.3
+++ dlls/shell32/tests/shelllink.c	25 Feb 2005 12:34:22 -0000
@@ -234,9 +234,7 @@ static void test_get_set()
     ok(SUCCEEDED(r), "GetIconLocation failed (0x%08lx)\n", r);
     }
     ok(*buffer=='\0', "GetIconLocation returned '%s'\n", buffer);
-    todo_wine {
     ok(i==0, "GetIconLocation returned %d\n", i);
-    }
 
     str="c:\\nonexistent\\file";
     r = IShellLinkA_SetIconLocation(sl, str, 0xbabecafe);
@@ -383,9 +381,7 @@ static void check_lnk_(int line, const W
     }
 
     r = IPersistFile_Load(pf, path, STGM_READ);
-    todo_wine {
     lok(SUCCEEDED(r), "load failed (0x%08lx)\n", r);
-    }
     IPersistFile_Release(pf);
     if (!SUCCEEDED(r))
     {
@@ -473,7 +469,7 @@ static void test_load_save()
 
     /* Save an empty .lnk file */
     memset(&desc, 0, sizeof(desc));
-    create_lnk(lnkfile, &desc, 1);
+    create_lnk(lnkfile, &desc, 0);
 
     /* It should come back as a bunch of empty strings */
     desc.description="";


More information about the wine-patches mailing list