#include "shellprv.h"
#include "ids.h"
#include "datautil.h"

#include "resource.h"       // main symbols
#include "cowsite.h"        // CObjectWithSite
#include "dpa.h"            // CDPA

class CStartMenuPin;

//
//  PINENTRY - A single entry in the pin list
//
//  The _liPos/_cbLink point back into the CPinList._pstmLink
//
class PINENTRY {
public:
    LPITEMIDLIST    _pidl;
    IShellLink *    _psl;           // a live IShellLink
    LARGE_INTEGER   _liPos;         // location of the shell link inside the stream
    DWORD           _cbSize;        // size of the buffer pointed to by _liPos

    HRESULT UpdateShellLink();

    void FreeShellLink()
    {
        _cbSize = 0;
        ATOMICRELEASE(_psl);
    }

    void Destruct()
    {
        ILFree(_pidl);
        FreeShellLink();
    }

    static BOOL DestroyCallback(PINENTRY *self, LPVOID)
    {
        self->Destruct();
        return TRUE;
    }
};

//
//  CPinList
//

class CPinList
{
public:
    CPinList() : _dsaEntries(NULL), _pstmLink(NULL) { }

    ~CPinList()
    {
        ATOMICRELEASE(_pstmLink);
        if (_dsaEntries)
        {
            _dsaEntries.DestroyCallbackEx(PINENTRY::DestroyCallback, (void *)NULL);
        }
    }

    BOOL    Initialize() { return _dsaEntries.Create(4); }
    HRESULT Load(CStartMenuPin *psmpin);
    HRESULT Save(CStartMenuPin *psmpin);

    int AppendPidl(LPITEMIDLIST pidl)
    {
        PINENTRY entry = { pidl };
        return _dsaEntries.AppendItem(&entry);
    }

    PINENTRY *GetItemPtr(int i) { return _dsaEntries.GetItemPtr(i); }


    HRESULT SaveShellLink(PINENTRY *pentry, IStream *pstm);
    HRESULT LoadShellLink(PINENTRY *pentry, IShellLink **ppsl);
    HRESULT UpdateShellLink(PINENTRY *pentry) { return pentry->UpdateShellLink(); }

    PINENTRY *FindPidl(LPCITEMIDLIST pidl, int *pi);
    HRESULT ReplacePidl(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew);

private:
    struct ILWRITEINFO {
        IStream *pstmPidlWrite;
        IStream *pstmLinkWrite;
        CPinList *ppl;
        HRESULT hr;
        LPITEMIDLIST rgpidl[20];    // Must match ARRAYSIZE(c_rgcsidlRelative)
    };
    static BOOL ILWriteCallback(PINENTRY *pentry, ILWRITEINFO *pwi);

    CDSA<PINENTRY>  _dsaEntries;    // The items themselves
    IStream *       _pstmLink;      // PINENTRY._liPos points into this stream

};

class ATL_NO_VTABLE CStartMenuPin
    : public IShellExtInit
    , public IContextMenu
    , public IStartMenuPin
    , public CObjectWithSite
    , public CComObjectRootEx<CComSingleThreadModel>
    , public CComCoClass<CStartMenuPin, &CLSID_StartMenuPin>
{
public:
    ~CStartMenuPin();

BEGIN_COM_MAP(CStartMenuPin)
    COM_INTERFACE_ENTRY(IShellExtInit)
    // Need to use COM_INTERFACE_ENTRY_IID for the interfaces
    // that don't have an idl
    COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu)
    COM_INTERFACE_ENTRY(IStartMenuPin)
    COM_INTERFACE_ENTRY(IObjectWithSite)
END_COM_MAP()

DECLARE_NO_REGISTRY()

    // *** IShellExtInit ***
    STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdto, HKEY hkProgID);

    // *** IContextMenu ***
    STDMETHODIMP  QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags);
    STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO pici);
    STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax);

    // *** IStartMenuPin ***
    STDMETHODIMP EnumObjects(IEnumIDList **ppenum);
    STDMETHODIMP Modify(LPCITEMIDLIST pidlFrom, LPCITEMIDLIST pidlTo);
    STDMETHODIMP GetChangeCount(ULONG *pulOut);
    STDMETHODIMP IsPinnable(IDataObject *pdtobj, DWORD dwFlags, OPTIONAL LPITEMIDLIST *ppidl);
    STDMETHODIMP Resolve(HWND hwnd, DWORD dwFlags, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlResolved);

    // *** IObjectWithSite ***
    // Inherited from CObjectWithSite

public:
    HRESULT SetChangeCount(ULONG ul);

protected:

    BOOL _IsAcceptableTarget(LPCTSTR pszPath, DWORD dwAttrib, DWORD dwFlags);

    enum {
        IDM_PIN =   0,
        IDM_UNPIN = 1,
        IDM_MAX,
    };

    // These "seem" backwards, but remember: If the item is pinned,
    // then the command is "unpin".  If the item is unpinned, then
    // the command is "pin".
    inline void _SetPinned() { _idmPinCmd = IDM_UNPIN; }
    inline void _SetUnpinned() { _idmPinCmd = IDM_PIN; }
    inline BOOL _IsPinned() const { return _idmPinCmd != IDM_PIN; }
    inline BOOL _DoPin() const { return _idmPinCmd == IDM_PIN; }
    inline BOOL _DoUnpin() const { return _idmPinCmd != IDM_PIN; }
    inline UINT _GetMenuStringID() const
    {
        COMPILETIME_ASSERT(IDS_STARTPIN_UNPINME == IDS_STARTPIN_PINME + IDM_UNPIN);
        return IDS_STARTPIN_PINME + _idmPinCmd;
    }

    static BOOL ILFreeCallback(LPITEMIDLIST pidl, void *)
        { ILFree(pidl); return TRUE; }

    HRESULT _ShouldAddMenu(UINT uFlags);
    HRESULT _InitPinRegStream();

protected:
    IDataObject *_pdtobj;
    UINT        _idmPinCmd;         // Which command did we add?

    LPITEMIDLIST _pidl;             // IContextMenu identity
};

#define REGSTR_PATH_STARTFAVS       TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartPage")
#define REGSTR_VAL_STARTFAVS        TEXT("Favorites")
#define REGSTR_VAL_STARTFAVCHANGES  TEXT("FavoritesChanges")
#define REGSTR_VAL_STARTFAVLINKS    TEXT("FavoritesResolve")

IStream *_OpenPinRegStream(DWORD grfMode)
{
    return SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_STARTFAVS, grfMode);
}

IStream *_OpenLinksRegStream(DWORD grfMode)
{
    return SHOpenRegStream2(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS, REGSTR_VAL_STARTFAVLINKS, grfMode);
}

const LARGE_INTEGER c_li0 = { 0, 0 };
const ULARGE_INTEGER& c_uli0 = (ULARGE_INTEGER&)c_li0;

HRESULT IStream_GetPos(IStream *pstm, LARGE_INTEGER *pliPos)
{
    return pstm->Seek(c_li0, STREAM_SEEK_CUR, (ULARGE_INTEGER*)pliPos);
}

HRESULT IStream_Copy(IStream *pstmFrom, IStream *pstmTo, DWORD cb)
{
    ULARGE_INTEGER uliToCopy, uliCopied;
    uliToCopy.QuadPart = cb;
    HRESULT hr = pstmFrom->CopyTo(pstmTo, uliToCopy, NULL, &uliCopied);
    if (SUCCEEDED(hr) && uliToCopy.QuadPart != uliCopied.QuadPart)
    {
        hr = E_FAIL;
    }
    return hr;
}

class ATL_NO_VTABLE CStartMenuPinEnum
    : public IEnumIDList
    , public CComObjectRootEx<CComSingleThreadModel>
    , public CComCoClass<CStartMenuPinEnum>
{
public:
    ~CStartMenuPinEnum()
    {
        ATOMICRELEASE(_pstm);
    }

BEGIN_COM_MAP(CStartMenuPinEnum)
    COM_INTERFACE_ENTRY(IEnumIDList)
END_COM_MAP()

    /// *** IEnumIDList ***
    STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
    STDMETHODIMP Skip(ULONG celt);
    STDMETHODIMP Reset();
    STDMETHODIMP Clone(IEnumIDList **ppenum);

private:
    HRESULT _NextPidlFromStream(LPITEMIDLIST *ppidl);
    HRESULT _InitPinRegStream();

private:
    HRESULT     _hrLastEnum;        // Result of last IEnumIDList::Next
    IStream *   _pstm;
};

CStartMenuPin::~CStartMenuPin()
{
    ILFree(_pidl);
    if (_pdtobj)
        _pdtobj->Release();
}

BOOL _IsLocalHardDisk(LPCTSTR pszPath)
{
    //  Reject CDs, floppies, network drives, etc.
    //
    int iDrive = PathGetDriveNumber(pszPath);
    if (iDrive < 0 ||                   // reject UNCs
        RealDriveType(iDrive, /* fOkToHitNet = */ FALSE) != DRIVE_FIXED) // reject slow media
    {
        return FALSE;
    }
    return TRUE;
}

BOOL CStartMenuPin::_IsAcceptableTarget(LPCTSTR pszPath, DWORD dwAttrib, DWORD dwFlags)
{
    //  Regitems ("Internet" or "Email" for example) are acceptable
    //  provided we aren't restricted to EXEs only.
    if (!(dwAttrib & SFGAO_FILESYSTEM))
    {
        return !(dwFlags & SMPINNABLE_EXEONLY);
    }

    //  Otherwise, it's a file.

    //  If requested, reject non-EXEs.
    //  (Like the Start Menu, we treat MSC files as if they were EXEs)
    if (dwFlags & SMPINNABLE_EXEONLY)
    {
        LPCTSTR pszExt = PathFindExtension(pszPath);
        if (StrCmpIC(pszExt, TEXT(".EXE")) != 0 &&
            StrCmpIC(pszExt, TEXT(".MSC")) != 0)
        {
            return FALSE;
        }
    }

    //  If requested, reject slow media
    if (dwFlags & SMPINNABLE_REJECTSLOWMEDIA)
    {
        if (!_IsLocalHardDisk(pszPath))
        {
            return FALSE;
        }

        // If it's a shortcut, then apply the same rule to the shortcut.
        if (PathIsLnk(pszPath))
        {
            BOOL fLocal = TRUE;
            IShellLink *psl;
            if (SUCCEEDED(LoadFromFile(CLSID_ShellLink, pszPath, IID_PPV_ARG(IShellLink, &psl))))
            {
                // IShellLink::GetPath returns S_FALSE if target is not a path
                TCHAR szPath[MAX_PATH];
                if (S_OK == psl->GetPath(szPath, ARRAYSIZE(szPath), NULL, 0))
                {
                    fLocal = _IsLocalHardDisk(szPath);
                }
                psl->Release();
            }
            if (!fLocal)
            {
                return FALSE;
            }
        }
    }

    //  All tests pass!

    return TRUE;

}

BOOL IsStartPanelOn()
{
    SHELLSTATE ss = { 0 };
    SHGetSetSettings(&ss, SSF_STARTPANELON, FALSE);

    return ss.fStartPanelOn;
}

HRESULT CStartMenuPin::IsPinnable(IDataObject *pdtobj, DWORD dwFlags, OPTIONAL LPITEMIDLIST *ppidl)
{
    HRESULT hr = S_FALSE;

    LPITEMIDLIST pidlRet = NULL;

    if (pdtobj &&                                   // must have a data object
        !SHRestricted(REST_NOSMPINNEDLIST) &&       // cannot be restricted
        IsStartPanelOn())                           // start panel must be on
    {
        STGMEDIUM medium = {0};
        LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
        if (pida)
        {
            if (pida->cidl == 1)
            {
                pidlRet = IDA_FullIDList(pida, 0);
                if (pidlRet)
                {
                    DWORD dwAttr = SFGAO_FILESYSTEM;            // only SFGAO_FILESYSTEM is valid
                    TCHAR szPath[MAX_PATH];

                    if (SUCCEEDED(SHGetNameAndFlags(pidlRet, SHGDN_FORPARSING,
                                        szPath, ARRAYSIZE(szPath), &dwAttr)) &&
                        _IsAcceptableTarget(szPath, dwAttr, dwFlags))
                    {
                        hr = S_OK;
                    }
                }
            }
            HIDA_ReleaseStgMedium(pida, &medium);
        }
    }

    // Return pidlRet only if the call succeeded and the caller requested it
    if (hr != S_OK || !ppidl)
    {
        ILFree(pidlRet);
        pidlRet = NULL;
    }

    if (ppidl)
    {
        *ppidl = pidlRet;
    }

    return hr;

}

// Returns S_OK if should add, S_FALSE if not

HRESULT CStartMenuPin::_ShouldAddMenu(UINT uFlags)
{
    // "Pin" is never a default verb
    if (uFlags & CMF_DEFAULTONLY)
        return S_FALSE;

    HRESULT hr;

    // The context menu appears only for fast media
    //
    // If extended verbs are disabled, then show the menu only for EXEs

    DWORD dwFlags = SMPINNABLE_REJECTSLOWMEDIA;
    if (!(uFlags & CMF_EXTENDEDVERBS))
    {
        dwFlags |= SMPINNABLE_EXEONLY;
    }

    hr = IsPinnable(_pdtobj, dwFlags, &_pidl);

    if (S_OK == hr)
    {
        //  If we are enclosed inside a shortcut, change our identity to the
        //  enclosing shortcut.

        IPersistFile *ppf;
        if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_LinkSite, IID_PPV_ARG(IPersistFile, &ppf))))
        {
            LPOLESTR pszFile = NULL;
            if (ppf->GetCurFile(&pszFile) == S_OK && pszFile)
            {
                // ILCreateFromPathEx turns %USERPROFILE%\Desktop\foo.lnk
                // into CSIDL_DESKTOP\foo.lnk for us.
                LPITEMIDLIST pidl;
                if (SUCCEEDED(ILCreateFromPathEx(pszFile, NULL, ILCFP_FLAG_NORMAL, &pidl, NULL)))
                if (pidl)
                {
                    ILFree(_pidl);
                    _pidl = pidl;
                    hr = S_OK;
                }
                CoTaskMemFree(pszFile);
            }
            ppf->Release();
        }
    }

    return hr;
}

// IShellExtInit::Initialize
HRESULT CStartMenuPin::Initialize(LPCITEMIDLIST, IDataObject *pdtobj, HKEY)
{
    IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);    // just grab this guy
    return S_OK;
}

// IContextMenu::QueryContextMenu

HRESULT CStartMenuPin::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
    HRESULT hr = _ShouldAddMenu(uFlags);
    if (S_OK == hr)
    {
        _SetUnpinned();

        //  Determine whether this item is already on the Start Page or not.
        IEnumIDList *penum;
        hr = EnumObjects(&penum);
        if (SUCCEEDED(hr))
        {
            LPITEMIDLIST pidl;
            while (penum->Next(1, &pidl, NULL) == S_OK)
            {
                BOOL bSame = ILIsEqual(pidl, _pidl);
                ILFree(pidl);
                if (bSame)
                {
                    _SetPinned();
                    break;
                }
            }
            penum->Release();

            TCHAR szCommand[MAX_PATH];
            if (LoadString(g_hinst, _GetMenuStringID(), szCommand, ARRAYSIZE(szCommand)))
            {
                InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,
                           idCmdFirst + _idmPinCmd, szCommand);
            }

            hr = ResultFromShort(IDM_MAX);
        }
    }
    return hr;
}

const LPCTSTR c_rgpszVerb[] =
{
    TEXT("pin"),                    // IDM_PIN
    TEXT("unpin"),                  // IDM_UNPIN
};

// *** IContextMenu::InvokeCommand

HRESULT CStartMenuPin::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
    LPCMINVOKECOMMANDINFOEX picix = reinterpret_cast<LPCMINVOKECOMMANDINFOEX>(pici);
    HRESULT hr = E_INVALIDARG;
    UINT idmCmd;

    if (IS_INTRESOURCE(pici->lpVerb))
    {
        idmCmd = PtrToInt(pici->lpVerb);
    }
    else
    {
        // Convert the string to an ID (or out of range if invalid)
        LPCTSTR pszVerb;
#ifdef UNICODE
        WCHAR szVerb[MAX_PATH];
        if (pici->cbSize >= CMICEXSIZE_NT4 &&
            (pici->fMask & CMIC_MASK_UNICODE) &&
            picix->lpVerbW)
        {
            pszVerb = picix->lpVerbW;
        }
        else
        {
            SHAnsiToTChar(pici->lpVerb, szVerb, ARRAYSIZE(szVerb));
            pszVerb = szVerb;
        }
#else
        pszVerb = pici->lpVerb;
#endif
        for (idmCmd = 0; idmCmd < ARRAYSIZE(c_rgpszVerb); idmCmd++)
        {
            if (lstrcmpi(pszVerb, c_rgpszVerb[idmCmd]) == 0)
            {
                break;
            }
        }
    }

    if (idmCmd == _idmPinCmd)
    {
        if (_idmPinCmd == IDM_PIN)
        {
            hr = Modify(NULL, _pidl);
        }
        else
        {
            hr = Modify(_pidl, NULL);
        }
    }

    return hr;
}

// *** IContextMenu::GetCommandString

HRESULT CStartMenuPin::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax)
{
    TCHAR szBuf[MAX_PATH];
    LPCTSTR pszResult = NULL;

    switch (uType & ~GCS_UNICODE)
    {
    case GCS_VERBA:
        if (idCmd < ARRAYSIZE(c_rgpszVerb))
        {
            pszResult = c_rgpszVerb[idCmd];
        }
        break;

    case GCS_HELPTEXTA:
        if (idCmd < ARRAYSIZE(c_rgpszVerb))
        {
            COMPILETIME_ASSERT(IDS_STARTPIN_PINME_HELP + IDM_UNPIN == IDS_STARTPIN_UNPINME_HELP);
            if (LoadString(g_hinst, IDS_STARTPIN_PINME_HELP + (UINT)idCmd, szBuf, ARRAYSIZE(szBuf)))
            {
                pszResult = szBuf;
            }
        }
        break;
    }

    if (pszResult)
    {
        if (uType & GCS_UNICODE)
        {
            SHTCharToUnicode(pszResult, (LPWSTR)pszName, cchMax);
        }
        else
        {
            SHTCharToAnsi(pszResult, pszName, cchMax);
        }
        return S_OK;
    }

    return E_NOTIMPL;
}

PINENTRY *CPinList::FindPidl(LPCITEMIDLIST pidl, int *pi)
{
    for (int i = _dsaEntries.GetItemCount() - 1; i >= 0; i--)
    {
        PINENTRY *pentry = _dsaEntries.GetItemPtr(i);
        if (ILIsEqual(pentry->_pidl, pidl))
        {
            if (pi)
            {
                *pi = i;
            }
            return pentry;
        }
    }
    return NULL;
}

HRESULT CPinList::ReplacePidl(LPCITEMIDLIST pidlOld, LPCITEMIDLIST pidlNew)
{
    int i;
    PINENTRY *pentry = FindPidl(pidlOld, &i);
    if (pentry)
    {
        if (pidlNew == NULL)            // Delete
        {
            pentry->Destruct();
            _dsaEntries.DeleteItem(i);
            return S_OK;
        }
        else
        if (IS_INTRESOURCE(pidlNew))    // Move
        {
            // Move the pidl from i to iPos
            PINENTRY entry = *pentry;
            int iPos = ((int)(INT_PTR)pidlNew) - 1;
            if (i < iPos)
            {
                // Moving down; others move up
                iPos--;
                // Must use MoveMemory because the memory blocks overlap
                MoveMemory(_dsaEntries.GetItemPtr(i),
                           _dsaEntries.GetItemPtr(i+1),
                           sizeof(PINENTRY) * (iPos-i));
            }
            else if (i > iPos)
            {
                // Moving up; others move down
                // Must use MoveMemory because the memory blocks overlap
                MoveMemory(_dsaEntries.GetItemPtr(iPos+1),
                           _dsaEntries.GetItemPtr(iPos),
                           sizeof(PINENTRY) * (i-iPos));
            }
            _dsaEntries.SetItem(iPos, &entry);
            return S_OK;
        }
        else                            // Replace
        {
            if (Pidl_Set(&pentry->_pidl, pidlNew))
            {
                // Failure to update the shell link is not fatal;
                // it just means we won't be able to repair the
                // shortcut if it breaks.
                pentry->UpdateShellLink();
                return S_OK;
            }
            else
            {
                return E_OUTOFMEMORY;
            }
        }
    }
    return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
}

HRESULT CStartMenuPin::Modify(LPCITEMIDLIST pidlFrom, LPCITEMIDLIST pidlTo)
{
    HRESULT hr;

    if(SHRestricted(REST_NOSMPINNEDLIST))
        return E_ACCESSDENIED;

    // Remap pidls to logical pidls (change CSIDL_DESKTOPDIRECTORY
    // to CSIDL_DESKTOP, etc.) so we don't get faked out when people
    // access objects sometimes directly on the desktop and sometimes
    // via their full filesystem name.

    LPITEMIDLIST pidlFromFree = NULL;
    LPITEMIDLIST pidlToFree = NULL;

    if (!IS_INTRESOURCE(pidlFrom))
    {
        pidlFromFree = SHLogILFromFSIL(pidlFrom);
        if (pidlFromFree) {
            pidlFrom = pidlFromFree;
        }
    }

    if (!IS_INTRESOURCE(pidlTo))
    {
        pidlToFree = SHLogILFromFSIL(pidlTo);
        if (pidlToFree) {
            pidlTo = pidlToFree;
        }
    }

    CPinList pl;
    hr = pl.Load(this);
    if (SUCCEEDED(hr))
    {
        if (SUCCEEDED(hr))
        {
            if (pidlFrom)
            {
                hr = pl.ReplacePidl(pidlFrom, pidlTo);
            }
            else if (pidlTo)
            {
                LPITEMIDLIST pidl = ILClone(pidlTo);
                if (pidl)
                {
                    int iPos = pl.AppendPidl(pidl);
                    if (iPos >= 0)
                    {
                        // Failure to update the shell link is not fatal;
                        // it just means we won't be able to repair the
                        // shortcut if it breaks.
                        pl.GetItemPtr(iPos)->UpdateShellLink();
                    }
                    else
                    {
                        ILFree(pidl);
                        hr = E_OUTOFMEMORY;
                    }
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
            else
            {
                // pidlFrom == pidlTo == NULL?  What does that mean?
                hr = E_INVALIDARG;
            }

            if (SUCCEEDED(hr))
            {
                hr = pl.Save(this);
            }
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;             // could not create dpa
    }

    ILFree(pidlFromFree);
    ILFree(pidlToFree);

    return hr;
}

//
//  Find the pidl on the pin list and resolve the shortcut that
//  tracks it.
//
//  Returns S_OK if the pidl changed and was resolved.
//  Returns S_FALSE if the pidl did not change.
//  Returns an error if the Resolve failed.
//

HRESULT CStartMenuPin::Resolve(HWND hwnd, DWORD dwFlags, LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlResolved)
{
    *ppidlResolved = NULL;

    if(SHRestricted(REST_NOSMPINNEDLIST))
        return E_ACCESSDENIED;

    // Remap pidls to logical pidls (change CSIDL_DESKTOPDIRECTORY
    // to CSIDL_DESKTOP, etc.) so we don't get faked out when people
    // access objects sometimes directly on the desktop and sometimes
    // via their full filesystem name.

    LPITEMIDLIST pidlFree = SHLogILFromFSIL(pidl);
    if (pidlFree) {
        pidl = pidlFree;
    }

    CPinList pl;
    HRESULT hr = pl.Load(this);
    if (SUCCEEDED(hr))
    {
        PINENTRY *pentry = pl.FindPidl(pidl, NULL);
        if (pentry)
        {
            IShellLink *psl;
            hr =  pl.LoadShellLink(pentry, &psl);
            if (SUCCEEDED(hr))
            {
                hr = psl->Resolve(hwnd, dwFlags);
                if (hr == S_OK)
                {
                    IPersistStream *pps;
                    hr = psl->QueryInterface(IID_PPV_ARG(IPersistStream, &pps));
                    if (SUCCEEDED(hr))
                    {
                        if (pps->IsDirty() == S_OK)
                        {
                            LPITEMIDLIST pidlNew;
                            hr = psl->GetIDList(&pidlNew);
                            if (SUCCEEDED(hr) && hr != S_OK)
                            {
                                // GetIDList returns S_FALSE on failure...
                                hr = E_FAIL;
                            }
                            if (SUCCEEDED(hr))
                            {
                                ILFree(pentry->_pidl);
                                pentry->_pidl = pidlNew;
                                hr = SHILClone(pidlNew, ppidlResolved);
                            }
                        }
                        pps->Release();
                    }
                }
                else if (SUCCEEDED(hr))
                {
                    // S_FALSE means "cancelled by user"
                    hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
                }
                psl->Release();
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        }

        if (hr == S_OK)
        {
            pl.Save(this); // if this fails, tough
        }

    }

    ILFree(pidlFree);

    return hr;
}

//
//  The target pidl has changed (or it's brand new).  Create an IShellLink
//  around it so we can resolve it later.
//
HRESULT PINENTRY::UpdateShellLink()
{
    ASSERT(_pidl);

    // Pitch the old link; it's useless now.
    FreeShellLink();

    HRESULT hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IShellLink, &_psl));
    if (SUCCEEDED(hr))
    {
        hr = _psl->SetIDList(_pidl);
        if (FAILED(hr))
        {
            FreeShellLink();        // pitch it; it's no good
        }
    }
    return hr;
}

HRESULT CPinList::SaveShellLink(PINENTRY *pentry, IStream *pstm)
{
    HRESULT hr;
    if (pentry->_psl)
    {
        // It's still in the form of an IShellLink.
        // Save it to the stream, then go back and update the size information.
        LARGE_INTEGER liPos, liPosAfter;
        DWORD cbSize = 0;
        IPersistStream *pps = NULL;
        if (SUCCEEDED(hr = IStream_GetPos(pstm, &liPos)) &&
            // Write a dummy DWORD; we will come back and patch it up later
            SUCCEEDED(hr = IStream_Write(pstm, &cbSize, sizeof(cbSize))) &&
            SUCCEEDED(hr = pentry->_psl->QueryInterface(IID_PPV_ARG(IPersistStream, &pps))))
        {
            if (SUCCEEDED(hr = pps->Save(pstm, TRUE)) &&
                SUCCEEDED(hr = IStream_GetPos(pstm, &liPosAfter)) &&
                SUCCEEDED(hr = pstm->Seek(liPos, STREAM_SEEK_SET, NULL)))
            {
                cbSize = liPosAfter.LowPart - liPos.LowPart - sizeof(DWORD);
                if (SUCCEEDED(hr = IStream_Write(pstm, &cbSize, sizeof(cbSize))) &&
                    SUCCEEDED(hr = pstm->Seek(liPosAfter, STREAM_SEEK_SET, NULL)))
                {
                    // Hooray!  All got saved okay
                }
            }
            pps->Release();
        }
    }
    else
    {
        // It's just a reference back into our parent stream; copy it
        if (SUCCEEDED(hr = IStream_Write(pstm, &pentry->_cbSize, sizeof(pentry->_cbSize))))
        {
            // If _cbSize == 0 then _pstmLink might be NULL, so guard against it
            if (pentry->_cbSize)
            {
                if (SUCCEEDED(hr = _pstmLink->Seek(pentry->_liPos, STREAM_SEEK_SET, NULL)) &&
                    SUCCEEDED(hr = IStream_Copy(_pstmLink, pstm, pentry->_cbSize)))
                {
                    // Hooray! All got saved okay
                }
            }
            else
            {
                // Entry was blank - nothing to do, vacuous success
            }
        }
    }
    return hr;
}

HRESULT CPinList::LoadShellLink(PINENTRY *pentry, IShellLink **ppsl)
{
    HRESULT hr;
    if (pentry->_psl)
    {
        hr = S_OK;              // We already have the link
    }
    else if (pentry->_cbSize == 0)
    {
        hr = E_FAIL;            // no link available
    }
    else
    {                           // gotta make it
        IPersistStream *pps;
        hr = SHCoCreateInstance(NULL, &CLSID_ShellLink, NULL, IID_PPV_ARG(IPersistStream, &pps));
        if (SUCCEEDED(hr))
        {
            if (SUCCEEDED(hr = _pstmLink->Seek(pentry->_liPos, STREAM_SEEK_SET, NULL)) &&
                SUCCEEDED(hr = pps->Load(_pstmLink)) &&
                SUCCEEDED(hr = pps->QueryInterface(IID_PPV_ARG(IShellLink, &pentry->_psl))))
            {
                // woo-hoo! All got loaded okay
            }
            pps->Release();
        }
    }

    *ppsl = pentry->_psl;

    if (SUCCEEDED(hr))
    {
        pentry->_psl->AddRef();
        hr = S_OK;
    }

    return hr;
}


HRESULT CStartMenuPin::GetChangeCount(ULONG *pulOut)
{
    DWORD cb = sizeof(*pulOut);
    if (SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS,
                   REGSTR_VAL_STARTFAVCHANGES, NULL, pulOut, &cb) != ERROR_SUCCESS)
    {
        *pulOut = 0;
    }

    return S_OK;
}

HRESULT CStartMenuPin::SetChangeCount(ULONG ulChange)
{
    SHSetValue(HKEY_CURRENT_USER, REGSTR_PATH_STARTFAVS,
               REGSTR_VAL_STARTFAVCHANGES, REG_DWORD, &ulChange,
               sizeof(ulChange));

    return S_OK;
}

//
//  We scan this list in order, so if there is a CSIDL that is a subdirectory
//  of another CSIDL, we must put the subdirectory first.  For example,
//  CSIDL_PROGRAMS is typically a subdirectory of CSIDL_STARTMENU, so we
//  must put CSIDL_PROGRAMS first so we get the best match.
//
//  Furthermore, directories pinned items are more likely to be found in
//  should come before less popular directories.
//
const int c_rgcsidlRelative[] = {
    // Most common: Start Menu stuff
    CSIDL_PROGRAMS,                 // Programs must come before StartMenu
    CSIDL_STARTMENU,                // Programs must come before StartMenu

    // Next most common: My Documents stuff
    CSIDL_MYPICTURES,               // MyXxx must come before Personal
    CSIDL_MYMUSIC,                  // MyXxx must come before Personal
    CSIDL_MYVIDEO,                  // MyXxx must come before Personal
    CSIDL_PERSONAL,                 // MyXxx must come before Personal
    CSIDL_COMMON_PROGRAMS,          // Programs must come before StartMenu
    CSIDL_COMMON_STARTMENU,         // Programs must come before StartMenu

    // Next most common: Desktop stuff
    CSIDL_DESKTOPDIRECTORY,
    CSIDL_COMMON_DESKTOPDIRECTORY,

    // Next most common: Program files stuff
    CSIDL_PROGRAM_FILES_COMMON,     // ProgramFilesCommon must come before ProgramFiles
    CSIDL_PROGRAM_FILES,            // ProgramFilesCommon must come before ProgramFiles
    CSIDL_PROGRAM_FILES_COMMONX86,  // ProgramFilesCommon must come before ProgramFiles
    CSIDL_PROGRAM_FILESX86,         // ProgramFilesCommon must come before ProgramFiles

    // Other stuff (less common)
    CSIDL_APPDATA,
    CSIDL_COMMON_APPDATA,
    CSIDL_SYSTEM,
    CSIDL_SYSTEMX86,
    CSIDL_WINDOWS,
    CSIDL_PROFILE,                  // Must come after all other profile-relative directories
};

BOOL CPinList::ILWriteCallback(PINENTRY *pentry, ILWRITEINFO *pwi)
{
    BYTE csidl = CSIDL_DESKTOP;     // Assume nothing interesting
    LPITEMIDLIST pidlWrite = pentry->_pidl;  // Assume nothing interesting

    for (int i = 0; i < ARRAYSIZE(pwi->rgpidl); i++)
    {
        LPITEMIDLIST pidlT;
        if (pwi->rgpidl[i] &&
            (pidlT = ILFindChild(pwi->rgpidl[i], pentry->_pidl)))
        {
            csidl = (BYTE)c_rgcsidlRelative[i];
            pidlWrite = pidlT;
            break;
        }
    }

    if (SUCCEEDED(pwi->hr = IStream_Write(pwi->pstmPidlWrite, &csidl, sizeof(csidl))) &&
        SUCCEEDED(pwi->hr = IStream_WritePidl(pwi->pstmPidlWrite, pidlWrite)) &&
        SUCCEEDED(pwi->hr = pwi->ppl->SaveShellLink(pentry, pwi->pstmLinkWrite)))
    {
        // woo-hoo, all written successfully
    }

    return SUCCEEDED(pwi->hr);
}

#define CSIDL_END ((BYTE)0xFF)

HRESULT CPinList::Save(CStartMenuPin *psmpin)
{
    ILWRITEINFO wi;

    COMPILETIME_ASSERT(ARRAYSIZE(c_rgcsidlRelative) == ARRAYSIZE(wi.rgpidl));

    for (int i = 0; i < ARRAYSIZE(c_rgcsidlRelative); i++)
    {
        SHGetSpecialFolderLocation(NULL, c_rgcsidlRelative[i], &wi.rgpidl[i]);
    }

    wi.pstmPidlWrite = _OpenPinRegStream(STGM_WRITE);
    if (wi.pstmPidlWrite)
    {
        wi.pstmLinkWrite = _OpenLinksRegStream(STGM_WRITE);
        if (wi.pstmLinkWrite)
        {
            wi.hr = S_OK;
            wi.ppl = this;
            _dsaEntries.EnumCallbackEx(ILWriteCallback, &wi);

            if (SUCCEEDED(wi.hr))
            {
                BYTE csidlEnd = CSIDL_END;
                wi.hr = IStream_Write(wi.pstmPidlWrite, &csidlEnd, sizeof(csidlEnd));
            }

            if (FAILED(wi.hr))
            {
                wi.pstmPidlWrite->SetSize(c_uli0);
                wi.pstmLinkWrite->SetSize(c_uli0);
            }
            wi.pstmLinkWrite->Release();
        }
        wi.pstmPidlWrite->Release();
    }
    else
    {
        wi.hr = E_ACCESSDENIED; // Most common reason is lack of write permission
    }

    for (i = 0; i < ARRAYSIZE(c_rgcsidlRelative); i++)
    {
        ILFree(wi.rgpidl[i]);
    }

    // Bump the change count so people can detect and refresh
    ULONG ulChange;
    psmpin->GetChangeCount(&ulChange);
    psmpin->SetChangeCount(ulChange + 1);

    // Notify everyone that the pin list changed
    SHChangeDWORDAsIDList dwidl;
    dwidl.cb      = SIZEOF(dwidl) - SIZEOF(dwidl.cbZero);
    dwidl.dwItem1 = SHCNEE_PINLISTCHANGED;
    dwidl.dwItem2 = 0;
    dwidl.cbZero  = 0;

    SHChangeNotify(SHCNE_EXTENDED_EVENT, SHCNF_FLUSH, (LPCITEMIDLIST)&dwidl, NULL);

    return wi.hr;
}

HRESULT CPinList::Load(CStartMenuPin *psmpin)
{
    HRESULT hr;

    if (Initialize())
    {
        IEnumIDList *penum;

        hr = psmpin->EnumObjects(&penum);
        if (SUCCEEDED(hr))
        {
            LPITEMIDLIST pidl;
            while (penum->Next(1, &pidl, NULL) == S_OK)
            {
                if (AppendPidl(pidl) < 0)
                {
                    ILFree(pidl);
                    hr = E_OUTOFMEMORY;
                    break;
                }
            }
            penum->Release();
        }

        if (SUCCEEDED(hr))
        {
            //
            //  Now read the persisted shortcuts.
            //
            _pstmLink = _OpenLinksRegStream(STGM_READ);
            if (_pstmLink)
            {
                for (int i = 0; i < _dsaEntries.GetItemCount(); i++)
                {
                    PINENTRY *pentry = _dsaEntries.GetItemPtr(i);
                    LARGE_INTEGER liSeek = { 0, 0 };
                    if (SUCCEEDED(hr = IStream_Read(_pstmLink, &liSeek.LowPart, sizeof(liSeek.LowPart))) && // read size
                        SUCCEEDED(hr = IStream_GetPos(_pstmLink, &pentry->_liPos)) &&  // read current pos
                        SUCCEEDED(hr = _pstmLink->Seek(liSeek, STREAM_SEEK_CUR, NULL))) // skip over link
                    {
                        pentry->_cbSize = liSeek.LowPart; // set this only on success
                    }
                    else
                    {
                        break;
                    }
                }
            }

            // If we encountered an error,
            // then throw all the shortcuts away because they are
            // probably corrupted.
            if (FAILED(hr))
            {
                for (int i = 0; i < _dsaEntries.GetItemCount(); i++)
                {
                    _dsaEntries.GetItemPtr(i)->FreeShellLink();
                }
            }

            // Problems reading the persisted shortcuts are ignored
            // since they are merely advisory.
            hr = S_OK;
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

//
//  Reading a pidl from a stream is a dangerous proposition because
//  a corrupted pidl can cause a shell extension to go haywire.
//
//  A pinned item is stored in the stream in the form
//
//  [byte:csidl] [dword:cbPidl] [size_is(cbPidl):pidl]
//
//  With the special csidl = -1 indicating the end of the list.
//
//  We use a byte for the csidl so a corrupted stream won't accidentally
//  pass "CSIDL_FLAG_CREATE" as a csidl to SHGetSpecialFolderLocation.

HRESULT CStartMenuPinEnum::_NextPidlFromStream(LPITEMIDLIST *ppidl)
{
    BYTE csidl;
    HRESULT hr = IStream_Read(_pstm, &csidl, sizeof(csidl));
    if (SUCCEEDED(hr))
    {
        if (csidl == CSIDL_END)
        {
            hr = S_FALSE;     // end of enumeration
        }
        else
        {
            LPITEMIDLIST pidlRoot;
            hr = SHGetSpecialFolderLocation(NULL, csidl, &pidlRoot);
            if (SUCCEEDED(hr))
            {
                LPITEMIDLIST pidl;
                hr = IStream_ReadPidl(_pstm, &pidl);
                if (SUCCEEDED(hr))
                {
                    hr = SHILCombine(pidlRoot, pidl, ppidl);
                    ILFree(pidl);
                }
                ILFree(pidlRoot);
            }
        }
    }

    return hr;
}

// *** IEnumIDList::Next

HRESULT CStartMenuPinEnum::Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched)
{
    HRESULT hr;

    ASSERT(celt > 0);

    // If there was an error or EOF on the last call to IEnumIDList::Next,
    // then that result is sticky.  Once an enumeration has errored, it stays
    // in the error state; once it has reached EOF, it stays at EOF.  The
    // only way to clear the state is to perform a Reset().

    if (_hrLastEnum != S_OK)
    {
        return _hrLastEnum;
    }

    if (!_pstm)
    {
        _pstm = _OpenPinRegStream(STGM_READ);
    }

    if (_pstm)
    {
        rgelt[0] = NULL;
        hr = _NextPidlFromStream(rgelt);
    }
    else
    {
        hr = S_FALSE;   // No stream therefore no items
    }

    if (pceltFetched)
    {
        *pceltFetched = hr == S_OK ? 1 : 0;
    }

    // Remember the return code for next time.  If an error occured or EOF,
    // then free the memory used for enumeration.
    _hrLastEnum = hr;
    if (_hrLastEnum != S_OK)
    {
        ATOMICRELEASE(_pstm);
    }
    return hr;
}

// *** IEnumIDList::Skip

HRESULT CStartMenuPinEnum::Skip(ULONG)
{
    return E_NOTIMPL;
}

// *** IEnumIDList::Reset

HRESULT CStartMenuPinEnum::Reset()
{
    _hrLastEnum = S_OK;
    ATOMICRELEASE(_pstm);
    return S_OK;
}


// *** IEnumIDList::Clone

STDMETHODIMP CStartMenuPinEnum::Clone(IEnumIDList **ppenum)
{
    *ppenum = NULL;
    return E_NOTIMPL;
}

// *** IStartMenuPin::EnumObjects

STDMETHODIMP CStartMenuPin::EnumObjects(IEnumIDList **ppenum)
{
    _InitPinRegStream();

    *ppenum = NULL;
    return CStartMenuPinEnum::CreateInstance(ppenum);
}

STDAPI CStartMenuPin_CreateInstance(IUnknown* punkOuter, REFIID riid, void** ppunk)
{
    return CStartMenuPin::_CreatorClass::CreateInstance(punkOuter, riid, ppunk);
}

// *** IStartMenuPin::_InitPinRegStream
//
// If the pin list has not yet been created, then create a default one.
//

static LPCTSTR c_rgszDefaultPin[] = {
    TEXT("shell:::{2559a1f4-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientInet
    TEXT("shell:::{2559a1f5-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientMail
#ifdef NOTYET
    // NOTE!  Before you turn this on, make sure wmp is installed on ia64
    TEXT("shell:::{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}"), // CLSID_AutoCMClientMedia
#endif
};

HRESULT CStartMenuPin::_InitPinRegStream()
{
    HRESULT hr = S_OK;

    if(SHRestricted(REST_NOSMPINNEDLIST))
        return hr;  //Nothing to initialize.

    IStream *pstm = _OpenPinRegStream(STGM_READ);

    BOOL fEmpty = pstm == NULL || SHIsEmptyStream(pstm);
    ATOMICRELEASE(pstm);

    if (fEmpty)
    {
        //  Create a default pin list
        CPinList pl;

        // Do not call pl.Load() because that will recurse back into us!

        if (pl.Initialize())
        {
            for (int i = 0; i < ARRAYSIZE(c_rgszDefaultPin); i++)
            {
                LPITEMIDLIST pidl = ILCreateFromPath(c_rgszDefaultPin[i]);
                if (pidl && pl.AppendPidl(pidl) < 0)
                {
                    ILFree(pidl);
                }
            }

            hr = pl.Save(this);
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    return hr;
}