//
//
//  assocapi.cpp
//
//     Association APIs
//
//
//


#include "priv.h"
#include "apithk.h"
#include <shstr.h>
#include <w95wraps.h>
#include <msi.h>
#include "ids.h"
#include "assoc.h"

#define ISEXTENSION(psz)   (TEXT('.') == *(psz))
#define ISGUID(psz)        (TEXT('{') == *(psz))

inline BOOL IsEmptyStr(SHSTR &str)
{
    return (!*(LPCTSTR)str);
}
        
HRESULT _AssocGetRegString(HKEY hk, LPCTSTR pszSub, LPCTSTR pszVal, SHSTR &strOut)
{
    if (!hk)
    {
        return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
    }

    DWORD cbOut = CbFromCch(strOut.GetSize());
    DWORD err = SHGetValue(hk, pszSub, pszVal, NULL, strOut.GetInplaceStr(), &cbOut);

    if (err == ERROR_SUCCESS)
        return S_OK;

    // else try to resize the buffer
    if (cbOut > CbFromCch(strOut.GetSize()))
    {
        strOut.SetSize(cbOut / sizeof(TCHAR));
        err = SHGetValue(hk, pszSub, pszVal, NULL, strOut.GetInplaceStr(), &cbOut);
    }

    return HRESULT_FROM_WIN32(err);
}

HRESULT _AssocGetRegUIString(HKEY hk, LPCTSTR pszSub, LPCTSTR pszVal, SHSTR &strOut)
{
    if (!hk)
        return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);

    HKEY hkSub;
    DWORD err;
    HRESULT hres;

    err = RegOpenKeyEx(hk, pszSub, 0, MAXIMUM_ALLOWED, &hkSub);
    if (err == ERROR_SUCCESS)
    {
        // Unfortunately, SHLoadRegUIString doesn't have a way to query the
        // buffer size, so we just have to assume INFOTIPSIZE is good enough.
        LPTSTR pszOut = strOut.GetModifyableStr(INFOTIPSIZE);
        if (pszOut == NULL)
            pszOut = strOut.GetInplaceStr();

        hres = SHLoadRegUIString(hkSub, pszVal, pszOut, strOut.GetSize());
        RegCloseKey(hkSub);
    }
    else
    {
        hres = HRESULT_FROM_WIN32(err);
    }

    return hres;

}

HRESULT _AssocGetRegData(HKEY hk, LPCTSTR pszSubKey, LPCTSTR pszValue, LPDWORD pdwType, LPBYTE pbOut, LPDWORD pcbOut)
{
    if (!hk)
        return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
    
    DWORD err;

    if (pszSubKey || pbOut || pcbOut || pdwType)
        err = SHGetValue(hk, pszSubKey, pszValue, pdwType, pbOut, pcbOut);
    else
        err = RegQueryValueEx(hk, pszValue, NULL, NULL, NULL, NULL);
        
    return HRESULT_FROM_WIN32(err);
}


BOOL _GetAppPath(LPCTSTR pszApp, SHSTR& strPath)
{
    TCHAR sz[MAX_PATH];
    _MakeAppPathKey(pszApp, sz, SIZECHARS(sz));

    return SUCCEEDED(_AssocGetRegString(HKEY_LOCAL_MACHINE, sz, NULL, strPath));
}



//
//  THE NEW WAY!
//

class CAssocW2k : public IQueryAssociations
{
public:
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef () ;
    STDMETHODIMP_(ULONG) Release ();

    // IQueryAssociations methods
    STDMETHODIMP Init(ASSOCF flags, LPCTSTR pszAssoc, HKEY hkProgid, HWND hwnd);
    STDMETHODIMP GetString(ASSOCF flags, ASSOCSTR str, LPCWSTR pszExtra, LPWSTR pszOut, DWORD *pcchOut);
    STDMETHODIMP GetKey(ASSOCF flags, ASSOCKEY, LPCWSTR pszExtra, HKEY *phkeyOut);
    STDMETHODIMP GetData(ASSOCF flags, ASSOCDATA data, LPCWSTR pszExtra, LPVOID pvOut, DWORD *pcbOut);
    STDMETHODIMP GetEnum(ASSOCF flags, ASSOCENUM assocenum, LPCWSTR pszExtra, REFIID riid, LPVOID *ppvOut);

    CAssocW2k();



protected:
    virtual ~CAssocW2k();

    //  static methods
    static HRESULT _CopyOut(BOOL fNoTruncate, SHSTR& str, LPTSTR psz, DWORD *pcch);
    static void _DefaultShellVerb(HKEY hk, LPTSTR pszVerb, DWORD cchVerb, HKEY *phkOut);

    typedef enum {
        KEYCACHE_INVALID = 0,
        KEYCACHE_HKCU    = 1,
        KEYCACHE_HKLM,
        KEYCACHE_APP,
        KEYCACHE_FIXED,
        PSZCACHE_BASE,
        PSZCACHE_HKCU    = PSZCACHE_BASE + KEYCACHE_HKCU,
        PSZCACHE_HKLM,
        PSZCACHE_APP,
        PSZCACHE_FIXED,
    } KEYCACHETYPE;

    typedef struct {
        LPTSTR pszCache;
        HKEY hkCache;
        LPTSTR psz;
        KEYCACHETYPE type;
    } KEYCACHE;
    
    static BOOL _CanUseCache(KEYCACHE &kc, LPCTSTR psz, KEYCACHETYPE type);
    static void _CacheFree(KEYCACHE &kc);
    static void _CacheKey(KEYCACHE &kc, HKEY hkCache, LPCTSTR pszName, KEYCACHETYPE type);
    static void _CacheString(KEYCACHE &kc, LPCTSTR pszCache, LPCTSTR pszName, KEYCACHETYPE type);

    void _Reset(void);

    BOOL _UseBaseClass(void);
    //
    //  retrieve the appropriate cached keys
    //
    HKEY _RootKey(BOOL fForceLM);
    HKEY _AppKey(LPCTSTR pszApp, BOOL fCreate = FALSE);
    HKEY _ExtensionKey(BOOL fForceLM);
    HKEY _OpenProgidKey(LPCTSTR pszProgid);
    HKEY _ProgidKey(BOOL fDefaultToExtension);
    HKEY _UserProgidKey(void);
    HKEY _ClassKey(BOOL fForceLM);
    HKEY _ShellVerbKey(HKEY hkey, KEYCACHETYPE type, LPCTSTR pszVerb);
    HKEY _ShellVerbKey(BOOL fForceLM, LPCTSTR pszVerb);
    HKEY _ShellNewKey(HKEY hkExt);
    HKEY _ShellNewKey(BOOL fForceLM);
    HKEY _DDEKey(BOOL fForceLM, LPCTSTR pszVerb);

    //
    //  actual worker routines
    //
    HRESULT _GetCommandString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _ParseCommand(ASSOCF flags, LPCTSTR pszCommand, SHSTR& strExe, PSHSTR pstrArgs);
    HRESULT _GetExeString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _GetFriendlyAppByVerb(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _GetFriendlyAppByApp(LPCTSTR pszApp, ASSOCF flags, SHSTR& strOut);
    HRESULT _GetFriendlyAppString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _GetTipString(LPCWSTR pwszValueName, BOOL fForceLM, SHSTR& strOut);
    HRESULT _GetInfoTipString(BOOL fForceLM, SHSTR& strOut);
    HRESULT _GetQuickTipString(BOOL fForceLM, SHSTR& strOut);
    HRESULT _GetTileInfoString(BOOL fForceLM, SHSTR& strOut);
    HRESULT _GetWebViewDisplayPropsString(BOOL fForceLM, SHSTR& strOut);
    HRESULT _GetShellNewValueString(BOOL fForceLM, BOOL fQueryOnly, LPCTSTR pszValue, SHSTR& strOut);
    HRESULT _GetDDEApplication(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _GetDDETopic(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut);
    HRESULT _GetContentType(SHSTR& strOut);
    HRESULT _GetMSIDescriptor(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, LPBYTE pbOut, LPDWORD pcbOut);

    HRESULT _GetShellExecKey(ASSOCF flags, BOOL fForceLM, LPCWSTR pszVerb, HKEY *phkey);
    HRESULT _CloneKey(HKEY hk, HKEY *phkey);
    HRESULT _GetShellExtension(ASSOCF flags, BOOL fForceLM, LPCTSTR pszShellEx, SHSTR& strOut);
    HRESULT _GetFriendlyDocName(SHSTR& strOut);



    //
    //  Members
    //
    LONG _cRef;
    TCHAR _szInit[MAX_PATH];
    ASSOCF _assocfBaseClass;
    HWND _hwndInit;

    BITBOOL _fInited:1;
    BITBOOL _fAppOnly:1;
    BITBOOL _fAppPath:1;
    BITBOOL _fInitedBaseClass:1;
    BITBOOL _fIsClsid:1;
    BITBOOL _fNoRemapClsid:1;
    BITBOOL _fBaseClassOnly:1;

    HKEY _hkFileExtsCU;
    HKEY _hkExtensionCU;
    HKEY _hkExtensionLM;

    KEYCACHE _kcProgid;
    KEYCACHE _kcShellVerb;
    KEYCACHE _kcApp;
    KEYCACHE _kcCommand;
    KEYCACHE _kcExecutable;
    KEYCACHE _kcShellNew;
    KEYCACHE _kcDDE;

    IQueryAssociations *_pqaBaseClass;
};

CAssocW2k::CAssocW2k() : _cRef(1)
{
};

HRESULT CAssocW2k::Init(ASSOCF flags, LPCTSTR pszAssoc, HKEY hkProgid, HWND hwnd)
{
    //
    //  pszAssoc can be:
    //  .Ext        //  Detectable
    //  {Clsid}     //  Detectable
    //  Progid      //  Ambiguous 
    //                    Default!
    //  ExeName     //  Ambiguous
    //                    Requires ASSOCF_OPEN_BYEXENAME
    //  MimeType    //  Ambiguous
    //                    NOT IMPLEMENTED...
    //  


    if (!pszAssoc && !hkProgid) 
        return E_INVALIDARG;
    
    HKEY hk = NULL;

    if (_fInited)
        _Reset();
        
    if (pszAssoc)
    {
        _fAppOnly = BOOLIFY(flags & ASSOCF_OPEN_BYEXENAME);

        if (StrChr(pszAssoc, TEXT('\\')))
        {
            // this is a path
            if (_fAppOnly)
                _fAppPath = TRUE;
            else 
            {
                //  we need the extension
                pszAssoc = PathFindExtension(pszAssoc);

                if (!*pszAssoc)
                    pszAssoc = NULL;
            }
            
        }

        if (pszAssoc && *pszAssoc)
        {
            if (ISGUID(pszAssoc))
            {
                _PathAppend(TEXT("CLSID"), pszAssoc, _szInit, SIZECHARS(_szInit));
                _fIsClsid = TRUE;

                // for legacy reasons we dont always 
                //  want to remap the clsid.
                if (flags & ASSOCF_INIT_NOREMAPCLSID)
                    _fNoRemapClsid = TRUE;
            }
            else
            {
                StrCpyN(_szInit , pszAssoc, SIZECHARS(_szInit));

                //  if we initializing to folder dont default to folder.
                if (0 == StrCmpI(_szInit, TEXT("Folder")))
                    flags &= ~ASSOCF_INIT_DEFAULTTOFOLDER;
            }
            hk = _ClassKey(FALSE);
        }
        else if (flags & ASSOCF_INIT_DEFAULTTOSTAR)
        {
            //  this is a file without an extension 
            //  but we still allow file association on HKCR\.
            _szInit[0] = '.';
            _szInit[1] = 0;
            hk = _ClassKey(FALSE);
        }
    }
    else
    {
        ASSERT(hkProgid);
        hk = SHRegDuplicateHKey(hkProgid);
        if (hk)
            _CacheKey(_kcProgid, hk, NULL, KEYCACHE_FIXED);
    }

    _assocfBaseClass = (flags & (ASSOCF_INIT_DEFAULTTOFOLDER | ASSOCF_INIT_DEFAULTTOSTAR));

    //  
    //  NOTE - we can actually do some work even if 
    //  we were unable to create the applications
    //  key.  so we want to succeed in the case
    //  of an app only association.
    //
    if (hk || _fAppOnly)
    {
        _fInited = TRUE;

        return S_OK;
    }
    else if (_UseBaseClass())
    {
        _fBaseClassOnly = TRUE;
        _fInited = TRUE;
        
        return S_OK;
    }
    
    return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
}

CAssocW2k::~CAssocW2k()
{
    _Reset();
}

#define REGFREE(hk)    if (hk) { RegCloseKey(hk); (hk) = NULL; } else { }

void CAssocW2k::_Reset(void)
{
    _CacheFree(_kcProgid);
    _CacheFree(_kcApp);
    _CacheFree(_kcShellVerb);
    _CacheFree(_kcCommand);
    _CacheFree(_kcExecutable);
    _CacheFree(_kcShellNew);
    _CacheFree(_kcDDE);

    REGFREE(_hkFileExtsCU);
    REGFREE(_hkExtensionCU);
    REGFREE(_hkExtensionLM);

    *_szInit = 0;
    _assocfBaseClass = 0;
    _hwndInit = NULL;

    _fInited = FALSE;
    _fAppOnly = FALSE;
    _fAppPath = FALSE;
    _fInitedBaseClass = FALSE;
    _fIsClsid = FALSE;
    _fBaseClassOnly = FALSE;
    
    ATOMICRELEASE(_pqaBaseClass);
}

STDMETHODIMP CAssocW2k::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CAssocW2k, IQueryAssociations),
        { 0 },
    };

    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CAssocW2k::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CAssocW2k::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

BOOL CAssocW2k::_UseBaseClass(void)
{
    if (!_pqaBaseClass && !_fInitedBaseClass)
    {
        //  try to init the base class
        IQueryAssociations *pqa;
        AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &pqa));
        if (pqa)
        {
            SHSTR strBase;
            if (_fInited && SUCCEEDED(_AssocGetRegString(_ClassKey(TRUE), NULL, TEXT("BaseClass"), strBase)))
            {
                if (SUCCEEDED(pqa->Init(_assocfBaseClass, strBase, NULL, _hwndInit)))
                    _pqaBaseClass = pqa;
            }

            if (!_pqaBaseClass)
            {
                if ((_assocfBaseClass & ASSOCF_INIT_DEFAULTTOFOLDER)
                && (SUCCEEDED(pqa->Init(0, L"Folder", NULL, _hwndInit))))
                    _pqaBaseClass = pqa;
                else if ((_assocfBaseClass & ASSOCF_INIT_DEFAULTTOSTAR)
                && (SUCCEEDED(pqa->Init(0, L"*", NULL, _hwndInit))))
                    _pqaBaseClass = pqa;
            }

            //  if we couldnt init the BaseClass, then kill the pqa
            if (!_pqaBaseClass)
                pqa->Release();
        }

        _fInitedBaseClass = TRUE;
    }

    return (_pqaBaseClass != NULL);
}
        
HRESULT CAssocW2k::_CopyOut(BOOL fNoTruncate, SHSTR& str, LPTSTR psz, DWORD *pcch)
{
    //  if caller doesnt want any return size, 
    //  the incoming pointer is actually the size of the buffer
    
    ASSERT(pcch);
    ASSERT(psz || !IS_INTRESOURCE(pcch));
    
    HRESULT hr;
    DWORD cch = IS_INTRESOURCE(pcch) ? PtrToUlong(pcch) : *pcch;
    DWORD cchStr = str.GetLen();

    if (psz)
    {
        if (!fNoTruncate || cch > cchStr)
        {
            StrCpyN(psz, str, cch);
            hr = S_OK;
        }
        else
            hr = E_POINTER;
    }
    else
        hr = S_FALSE;
    
    //  return the number of chars written/required
    if (!IS_INTRESOURCE(pcch))
        *pcch = (hr == S_OK) ? lstrlen(psz) + 1 : cchStr + 1;

    return hr;
}


BOOL CAssocW2k::_CanUseCache(KEYCACHE &kc, LPCTSTR psz, KEYCACHETYPE type)
{
    if (KEYCACHE_FIXED == kc.type)
        return TRUE;
        
    if (KEYCACHE_INVALID != kc.type && type == kc.type)
    {
        return ((!psz && !kc.psz)
            || (psz && kc.psz && 0 == StrCmpC(psz, kc.psz)));
    }
    
    return FALSE;
}

void CAssocW2k::_CacheFree(KEYCACHE &kc)
{
    if (kc.pszCache)
        LocalFree(kc.pszCache);
    if (kc.hkCache)
        RegCloseKey(kc.hkCache);
    if (kc.psz)
        LocalFree(kc.psz);

    ZeroMemory(&kc, sizeof(kc));
}

void CAssocW2k::_CacheKey(KEYCACHE &kc, HKEY hkCache, LPCTSTR pszName, KEYCACHETYPE type)
{
    _CacheFree(kc);
    ASSERT(hkCache);

    kc.hkCache = hkCache;

    if (pszName)
        kc.psz = StrDup(pszName);
        
    if (!pszName || kc.psz)
        kc.type = type;
}

void CAssocW2k::_CacheString(KEYCACHE &kc, LPCTSTR pszCache, LPCTSTR pszName, KEYCACHETYPE type)
{
    _CacheFree(kc);
    ASSERT(pszCache && *pszCache);

    kc.pszCache = StrDup(pszCache);
    if (kc.pszCache)
    {
        if (pszName)
            kc.psz = StrDup(pszName);

        if (!pszName || kc.psz)
            kc.type = type;
    }
}

void CAssocW2k::_DefaultShellVerb(HKEY hk, LPTSTR pszVerb, DWORD cchVerb, HKEY *phkOut)
{
    //  default to "open"
    BOOL fDefaultSpecified = FALSE;
    TCHAR sz[MAX_PATH];
    DWORD cb = sizeof(sz);
    *phkOut = NULL;

    //  see if something is specified...
    if (ERROR_SUCCESS == SHGetValue(hk, TEXT("shell"), NULL, NULL, (LPVOID)sz, &cb) && *sz)
        fDefaultSpecified = TRUE;
    else
        StrCpy(sz, TEXT("open"));
        
    HKEY hkShell;
    if (SUCCEEDED(_AssocOpenRegKey(hk, TEXT("shell"), &hkShell)))
    {
        HKEY hkVerb;
        if (FAILED(_AssocOpenRegKey(hkShell, sz, &hkVerb)))
        {
            if (fDefaultSpecified)
            {
                // try to find one of the ordered verbs
                int c = StrCSpn(sz, TEXT(" ,"));
                sz[c] = 0;
                _AssocOpenRegKey(hkShell, sz, &hkVerb);
            }
            else
            {
                // otherwise just use the first key we find....
                cb = SIZECHARS(sz);
                if (ERROR_SUCCESS == RegEnumKeyEx(hkShell, 0, sz, &cb, NULL, NULL, NULL, NULL))
                    _AssocOpenRegKey(hkShell, sz, &hkVerb);
            }
        }

        if (hkVerb)
        {
            if (phkOut)
                *phkOut = hkVerb;
            else
                RegCloseKey(hkVerb);
        }
        
        RegCloseKey(hkShell);
    }

    if (pszVerb)
        StrCpyN(pszVerb, sz, cchVerb);

}

HKEY CAssocW2k::_RootKey(BOOL fForceLM)
{
    //
    //  this is one of the few places where there is no fallback to LM
    //  if there is no CU, then we return NULL
    //  we need to use a local for CU, but we can use a global for LM
    //

    if (!fForceLM)
    {
        if (!_hkFileExtsCU)
        {
            _AssocOpenRegKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts"), &_hkFileExtsCU);
        }
        
        return _hkFileExtsCU;
    }

    return HKEY_CLASSES_ROOT;

}

HKEY CAssocW2k::_AppKey(LPCTSTR pszApp, BOOL fCreate)
{
    //  right now we should only get NULL for the pszApp 
    //  when we are initialized with an EXE.
    ASSERT(_fAppOnly || pszApp);
    // else if (!_fAppOnly) TODO: handle getting it from default verb...or not
    
    if (!pszApp)
        pszApp = _fAppPath ? PathFindFileName(_szInit) : _szInit;

    if (_CanUseCache(_kcApp, pszApp, KEYCACHE_APP))
        return _kcApp.hkCache;
    else 
    {
        HKEY hk;
        TCHAR sz[MAX_PATH];
    
        _MakeApplicationsKey(pszApp, sz, SIZECHARS(sz));

        _AssocOpenRegKey(HKEY_CLASSES_ROOT, sz, &hk, fCreate);

        if (hk)
        {
            _CacheKey(_kcApp, hk, pszApp, KEYCACHE_APP);
        }

        return hk;
    }
}

HKEY CAssocW2k::_ExtensionKey(BOOL fForceLM)
{
    if (_fAppOnly)
        return _AppKey(NULL);
    if (!ISEXTENSION(_szInit) && !_fIsClsid)
        return NULL;

    if (!fForceLM)
    {
        if (!_hkExtensionCU)
            _AssocOpenRegKey(_RootKey(FALSE), _szInit, &_hkExtensionCU);

        //  NOTE there is no fallback here
        return _hkExtensionCU;
    }

    if (!_hkExtensionLM)
        _AssocOpenRegKey(_RootKey(TRUE), _szInit, &_hkExtensionLM);

    return _hkExtensionLM;
}

HKEY CAssocW2k::_OpenProgidKey(LPCTSTR pszProgid)
{
    HKEY hkOut;
    if (SUCCEEDED(_AssocOpenRegKey(_RootKey(TRUE), pszProgid, &hkOut)))
    {
        // Check for a newer version of the ProgID
        TCHAR sz[MAX_PATH];
        DWORD cb = sizeof(sz);

        //
        //  APPCOMPAT LEGACY - Quattro Pro 2000 and Excel 2000 dont get along - ZekeL - 7-MAR-2000
        //  mill bug #129525.  the problem is if Quattro is installed
        //  first, then excel picks up quattro's CurVer key for some
        //  reason.  then we end up using Quattro.Worksheet as the current
        //  version of the Excel.Sheet.  this is bug in both of their code.
        //  since quattro cant even open the file when we give it to them,
        //  they never should take the assoc in the first place, and when excel
        //  takes over it shouldnt have preserved the CurVer key from the
        //  previous association.  we could add some code to insure that the 
        //  CurVer key follows the OLE progid naming conventions and that it must
        //  be derived from the same app name as the progid in order to take 
        //  precedence but for now we will block CurVer from working whenever
        //  the progid is excel.sheet.8 (excel 2000)
        //
        if (StrCmpI(TEXT("Excel.Sheet.8"), pszProgid)
        && ERROR_SUCCESS == SHGetValue(hkOut, TEXT("CurVer"), NULL, NULL, sz, &cb) 
        && (cb > sizeof(TCHAR)))
        {
            //  cache this bubby
            HKEY hkTemp = hkOut;            
            if (SUCCEEDED(_AssocOpenRegKey(_RootKey(TRUE), sz, &hkOut)))
            {
                //
                //  APPCOMPAT LEGACY - order of preference - ZekeL - 22-JUL-99
                //  this is to support associations that installed empty curver
                //  keys, like microsoft project.
                //
                //  1.  curver with shell subkey
                //  2.  progid with shell subkey
                //  3.  curver without shell subkey
                //  4.  progid without shell subkey
                //
                HKEY hkShell;

                if (SUCCEEDED(_AssocOpenRegKey(hkOut, TEXT("shell"), &hkShell)))
                {
                    RegCloseKey(hkShell);
                    RegCloseKey(hkTemp);    // close old ProgID key
                }
                else if (SUCCEEDED(_AssocOpenRegKey(hkTemp, TEXT("shell"), &hkShell)))
                {
                    RegCloseKey(hkShell);
                    RegCloseKey(hkOut);
                    hkOut = hkTemp;
                }
                else
                    RegCloseKey(hkTemp);
                
            }
            else  // reset!
                hkOut = hkTemp;
        }
    }

    return hkOut;
}

//  we only need to build this once, so build it for 
//  the lowest common denominator...
HKEY CAssocW2k::_ProgidKey(BOOL fDefaultToExtension)
{
    HKEY hkExt = _ExtensionKey(TRUE);
    TCHAR sz[MAX_PATH];
    ULONG cb = sizeof(sz);
    LPCTSTR psz;
    HKEY hkRet = NULL;

    if (!hkExt && !ISEXTENSION(_szInit))
    {
        psz = _szInit;
    }
    else if (!_fNoRemapClsid && hkExt && (ERROR_SUCCESS == SHGetValue(hkExt, _fIsClsid ? TEXT("ProgID") : NULL, NULL, NULL, sz, &cb))
        && (cb > sizeof(TCHAR)))
    {
        psz = sz;
    }
    else
        psz = NULL;

    if (psz && *psz)
    {
        hkRet = _OpenProgidKey(psz);
    }

    if (!hkRet && fDefaultToExtension && hkExt)
        hkRet = SHRegDuplicateHKey(hkExt);

    return hkRet;
}

HKEY CAssocW2k::_UserProgidKey(void)
{
    SHSTR strApp;
    if (SUCCEEDED(_AssocGetRegString(_ExtensionKey(FALSE), NULL, TEXT("Application"), strApp)))
    {
        HKEY hkRet = _AppKey(strApp);

        if (hkRet)
            return SHRegDuplicateHKey(hkRet);
    }

    return NULL;
}

HKEY CAssocW2k::_ClassKey(BOOL fForceLM)
{
    //  REARCHITECT - we are not supporting clsids correctly here.
    HKEY hkRet = NULL;
    if (_fAppOnly)
        return _AppKey(NULL);
    else
    {
        KEYCACHETYPE type;
        if (!fForceLM)
            type = KEYCACHE_HKCU;
        else
            type = KEYCACHE_HKLM;

        if (_CanUseCache(_kcProgid, NULL, type))
            hkRet = _kcProgid.hkCache;
        else
        {
            if (!fForceLM)
                hkRet = _UserProgidKey();

            if (!hkRet)
                hkRet = _ProgidKey(TRUE);

            //  cache the value off
            if (hkRet)
                _CacheKey(_kcProgid, hkRet, NULL, type);
            
        }
    }
    return hkRet;
}

HKEY CAssocW2k::_ShellVerbKey(HKEY hkey, KEYCACHETYPE type, LPCTSTR pszVerb)
{
    HKEY hkRet = NULL;
    //  check our cache
    if (_CanUseCache(_kcShellVerb, pszVerb, type))
        hkRet = _kcShellVerb.hkCache;
    else if (hkey)
    {
        //  NO cache hit
        if (!pszVerb)
            _DefaultShellVerb(hkey, NULL, 0, &hkRet);
        else
        {
            TCHAR szKey[MAX_PATH];

            _PathAppend(TEXT("shell"), pszVerb, szKey, SIZECHARS(szKey));
            _AssocOpenRegKey(hkey, szKey, &hkRet);
        }
        
        // only replace the cache if we got something
        if (hkRet)
            _CacheKey(_kcShellVerb, hkRet, pszVerb, type);
    }

    return hkRet;
}

HKEY CAssocW2k::_ShellVerbKey(BOOL fForceLM, LPCTSTR pszVerb)
{
    HKEY hk = NULL;

    if (!fForceLM)
    {
        hk = _ShellVerbKey(_ClassKey(FALSE), KEYCACHE_HKCU, pszVerb);
        if (!hk && _szInit[0]) // szInit[0] = NULL, if Iqa is inited by key.
            hk = _ShellVerbKey(_ExtensionKey(FALSE), KEYCACHE_HKCU, pszVerb);
    }
    if (!hk) 
    {
        KEYCACHETYPE type = (_fAppOnly) ? KEYCACHE_APP : KEYCACHE_HKLM;
        hk = _ShellVerbKey(_ClassKey(TRUE), type, pszVerb);
        if (!hk && _szInit[0]) // szInit[0] = NULL, if Iqa is inited by key.
            hk = _ShellVerbKey(_ExtensionKey(TRUE), type, pszVerb);
    }
    
    return hk;
}

HKEY CAssocW2k::_ShellNewKey(HKEY hkExt)
{
    //  
    //  shellnew keys look like this
    //  \.ext
    //      @ = "progid"
    //      \progid
    //          \shellnew
    //  -- OR --
    //  \.ext 
    //      \shellnew
    //
    HKEY hk = NULL;
    SHSTR strProgid;
    if (SUCCEEDED(_AssocGetRegString(hkExt, NULL, NULL, strProgid)))
    {
        strProgid.Append(TEXT("\\shellnew"));
        
        _AssocOpenRegKey(hkExt, TEXT("shellnew"), &hk);
    }
    
    if (!hk)
        _AssocOpenRegKey(hkExt, TEXT("shellnew"), &hk);

    return hk;
}

HKEY CAssocW2k::_ShellNewKey(BOOL fForceLM)
{
    ASSERT(!_fAppOnly);

    if (_CanUseCache(_kcShellNew, NULL, KEYCACHE_HKLM))
        return _kcShellNew.hkCache;

    HKEY hk = _ShellNewKey(_ExtensionKey(TRUE));
        

    if (hk)
        _CacheKey(_kcShellNew, hk, NULL, KEYCACHE_HKLM);

    return hk;
}

HRESULT CAssocW2k::_GetCommandString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    HRESULT hr = E_INVALIDARG;
    KEYCACHETYPE type;

    if (!fForceLM)
        type = PSZCACHE_HKCU;
    else if (_fAppOnly) 
        type = PSZCACHE_APP;
    else
        type = PSZCACHE_HKLM;

    if (pszVerb && !*pszVerb) 
        pszVerb = NULL;

    if (_CanUseCache(_kcCommand, pszVerb, type))
    {
        hr = strOut.SetStr(_kcCommand.pszCache);
    }
    
    if (FAILED(hr))    
    {
        hr = _AssocGetRegString(_ShellVerbKey(fForceLM, pszVerb), TEXT("command"), NULL, strOut);

        if (SUCCEEDED(hr))
        {
            _CacheString(_kcCommand, strOut, pszVerb, type);
        }
    }

    return hr;
}

BOOL _PathIsFile(LPCTSTR pszPath)
{
    DWORD attrs = GetFileAttributes(pszPath);

    return ((DWORD)-1 != attrs && !(attrs & FILE_ATTRIBUTE_DIRECTORY));
}
    
HRESULT CAssocW2k::_ParseCommand(ASSOCF flags, LPCTSTR pszCommand, SHSTR& strExe, PSHSTR pstrArgs)
{
    //  we just need to find where the params begin, and the exe ends...

    LPTSTR pch = PathGetArgs(pszCommand);

    if (*pch)
        *(--pch) = TEXT('\0');
    else
        pch = NULL;

    HRESULT hr = strExe.SetStr(pszCommand);

    //  to prevent brace proliferation
    if (S_OK != hr)
        goto quit;

    strExe.Trim();
    PathUnquoteSpaces(strExe.GetInplaceStr());

    //
    //  WARNING:  Expensive disk hits all over!
    //
    // We check for %1 since it is what appears under (for example) HKCR\exefile\shell\open\command
    // This will save us a chain of 35 calls to _PathIsFile("%1") when launching or getting a 
    // context menu on a shortcut to an .exe or .bat file.
    if ((ASSOCF_VERIFY & flags) 
        && (0 != StrCmp(strExe, TEXT("%1")))
        && (!_PathIsFile(strExe)) )
    {
        hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);

        if (PathIsFileSpec(strExe))
        {
            if (_GetAppPath(strExe, strExe))
            {
                if (_PathIsFile(strExe))
                    hr = S_OK;
            }
            else
            {
                LPTSTR pszTemp = strExe.GetModifyableStr(MAX_PATH);
                if (pszTemp == NULL)
                    hr = E_OUTOFMEMORY;
                else
                {
                    if (PathFindOnPathEx(pszTemp, NULL, PFOPEX_DEFAULT | PFOPEX_OPTIONAL))
                    {
                       //  the find does a disk check for us...
                       hr = S_OK;
                    }
                }
            }
        }
        else             
        {
            //
            //  sometimes the path is not properly quoted.
            //  these keys will still work because of the
            //  way CreateProcess works, but we need to do
            //  some fiddling to figure that out.
            //

            //  if we found args, put them back...
            //  and try some different args
            while (pch)
            {
                *pch++ = TEXT(' ');

                if (pch = StrChr(pch, TEXT(' ')))
                    *pch = TEXT('\0');

                if (S_OK == strExe.SetStr(pszCommand))
                {
                    strExe.Trim();

                    if (_PathIsFile(strExe))
                    {
                        hr = S_FALSE;

                        //  this means that we found something
                        //  but the command line was kinda screwed
                        break;

                    }
                    //  this is where we loop again
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                    break;
                }
            }//  while (pch)
        }
    }

    if (SUCCEEDED(hr) && pch)
    {
        //  currently right before the args, on a NULL terminator
        ASSERT(!*pch);
        pch++;
        
        if ((ASSOCF_REMAPRUNDLL & flags) 
        && 0 == StrCmpNIW(PathFindFileName(strExe), TEXT("rundll"), SIZECHARS(TEXT("rundll")) -1))
        {
            LPTSTR pchComma = StrChr(pch, TEXT(','));
            //  make the comma the beginning of the args
            if (pchComma)
                *pchComma = TEXT('\0');

            if (!*(PathFindExtension(pch)) 
            && lstrlen(++pchComma) > SIZECHARS(TEXT(".dll")))
                StrCat(pch, TEXT(".dll"));

            //  recurse :P
            hr = _ParseCommand(flags, pch, strExe, pstrArgs);
        }
        //  set the args if we got'em
        else if (pstrArgs)
            pstrArgs->SetStr(pch);
    }
    
quit:
    return hr;
}

HRESULT CAssocW2k::_GetFriendlyDocName(SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    HKEY hkProgId = _ProgidKey(_fIsClsid);
    if (hkProgId)
    {
        //  first try the MUI friendly version of the string
        //  if that fails fall back to the default value of the progid.
        hr = _AssocGetRegUIString(hkProgId, NULL, L"FriendlyTypeName", strOut);
        if (FAILED(hr))
            hr = _AssocGetRegString(hkProgId, NULL, NULL, strOut);
            
        RegCloseKey(hkProgId);
    }

    if (FAILED(hr) || IsEmptyStr(strOut))
    {
        hr = E_FAIL;
        if (!_fIsClsid)
        {
            //  fallback code
            TCHAR szDesc[MAX_PATH];
            *szDesc = 0;
            
            if (_assocfBaseClass & ASSOCF_INIT_DEFAULTTOFOLDER || 0 == StrCmpIW(L"Folder", _szInit))
            {
                //  load the folder description "Folder"
                LoadString(HINST_THISDLL, IDS_FOLDERTYPENAME, szDesc, ARRAYSIZE(szDesc));
            }
            else if (ISEXTENSION(_szInit) && _szInit[1])
            {
                TCHAR szTemplate[128];   // "%s File"
                CharUpper(_szInit);
                LoadString(HINST_THISDLL, IDS_EXTTYPETEMPLATE, szTemplate, ARRAYSIZE(szTemplate));
                wnsprintf(szDesc, ARRAYSIZE(szDesc), szTemplate, _szInit + 1);
            }
            else if (_assocfBaseClass & ASSOCF_INIT_DEFAULTTOSTAR)
            {
                //  load the file description "File"
                LoadString(HINST_THISDLL, IDS_FILETYPENAME, szDesc, ARRAYSIZE(szDesc));
            }
            else if (_szInit[0])
            {
                StrCpyN(szDesc, _szInit, ARRAYSIZE(szDesc));
                CharUpper(szDesc);
            }
            
            if (*szDesc)
                hr = strOut.SetStr(szDesc);
        }            
    }
    return hr;
}

HRESULT CAssocW2k::_GetExeString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    KEYCACHETYPE type;

    if (!fForceLM)
        type = PSZCACHE_HKCU;
    else if (_fAppOnly) 
        type = PSZCACHE_APP;
    else
        type = PSZCACHE_HKLM;

    if (_CanUseCache(_kcExecutable, pszVerb, type))
        hr = strOut.SetStr(_kcExecutable.pszCache);

    if (FAILED(hr))
    {
        SHSTR strCommand;

        hr = _GetCommandString(flags, fForceLM, pszVerb, strCommand);
        
        if (S_OK == hr)
        {
            SHSTR strArgs;

            strCommand.Trim();
            hr = _ParseCommand(flags | ASSOCF_REMAPRUNDLL, strCommand, strOut, &strArgs);
            
            if (S_FALSE == hr)
            {
                hr = S_OK;

//                if (!ASSOCF_NOFIXUPS & flags)
//                    AssocSetCommandByKey(ASSOCF_SET_SUBSTENV, hkey, pszVerb, strExe.GetStr(), strArgs.GetStr());
            }
        }

        if (SUCCEEDED(hr))
            _CacheString(_kcExecutable, strOut, pszVerb, type);
    }

    return hr;
}    

HRESULT _AssocGetDarwinProductString(LPCTSTR pszDarwinCommand, SHSTR& strOut)
{
    DWORD cch = strOut.GetSize();
    UINT err = MsiGetProductInfo(pszDarwinCommand, INSTALLPROPERTY_PRODUCTNAME, strOut.GetInplaceStr(), &cch);

    if (err == ERROR_MORE_DATA  && cch > strOut.GetSize())
    {
        if (SUCCEEDED(strOut.SetSize(cch)))
            err = MsiGetProductInfo(pszDarwinCommand, INSTALLPROPERTY_PRODUCTNAME, strOut.GetInplaceStr(), &cch);
        else 
            return E_OUTOFMEMORY;
    }

    if (err)
        return HRESULT_FROM_WIN32(err);
    return ERROR_SUCCESS;
}

HRESULT CAssocW2k::_GetFriendlyAppByVerb(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    if (pszVerb && !*pszVerb) 
        pszVerb = NULL;

    HKEY hk = _ShellVerbKey(fForceLM, pszVerb);

    if (hk)
    {
        HRESULT hr = _AssocGetRegUIString(hk, NULL, TEXT("FriendlyAppName"), strOut);

        if (FAILED(hr))
        {
            SHSTR str;
            //  check the appkey, for this executeables friendly
            //  name.  this should be the most common case
            hr = _GetExeString(flags, fForceLM, pszVerb, str);

            if (SUCCEEDED(hr))
            {
                hr = _GetFriendlyAppByApp(str, flags, strOut);
            }

            //  if the EXE isnt on the System, then Darwin might
            //  be able to tell us something about it...
            if (FAILED(hr))
            {
                hr = _AssocGetRegString(hk, TEXT("command"), TEXT("command"), str);
                if (SUCCEEDED(hr))
                {
                    hr = _AssocGetDarwinProductString(str, strOut);
                }
            }
        }


        return hr;
    }

    return E_FAIL;
}

HRESULT _GetFriendlyAppByCache(HKEY hkApp, LPCTSTR pszApp, BOOL fVerifyCache, BOOL fNoFixUps, SHSTR& strOut)
{
    HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);

    if (pszApp)
    {
        FILETIME ftCurr;
        if (MyGetLastWriteTime(pszApp, &ftCurr))
        {
            if (fVerifyCache)
            {
                FILETIME ftCache = {0};
                DWORD cbCache = sizeof(ftCache);
                SHGetValue(hkApp, TEXT("shell"), TEXT("FriendlyCacheCTime"), NULL, &ftCache, &cbCache);

                if (0 == CompareFileTime(&ftCurr, &ftCache))
                    hr = S_OK;
            }

            if (FAILED(hr))
            {
                //  need to get this from the file itself
                LPTSTR pszOut = strOut.GetModifyableStr(MAX_PATH); // How big is big enough?
                UINT cch = strOut.GetSize();

                if (pszOut == NULL)
                    pszOut = strOut.GetInplaceStr();
                if (SHGetFileDescription(pszApp, NULL, NULL, pszOut, &cch))
                    hr = S_OK;

                if (SUCCEEDED(hr) && !(fNoFixUps))
                {
                    SHSetValue(hkApp, TEXT("shell"), TEXT("FriendlyCache"), REG_SZ, strOut, CbFromCch(strOut.GetLen() +1));
                    SHSetValue(hkApp, TEXT("shell"), TEXT("FriendlyCacheCTime"), REG_BINARY, &ftCurr, sizeof(ftCurr));
                }
            }
        }
    }
    return hr;
}

HRESULT CAssocW2k::_GetFriendlyAppByApp(LPCTSTR pszApp, ASSOCF flags, SHSTR& strOut)
{
    HKEY hk = _AppKey(pszApp ? PathFindFileName(pszApp) : NULL, TRUE);
    HRESULT hr = _AssocGetRegUIString(hk, NULL, TEXT("FriendlyAppName"), strOut);

    ASSERT(hk == _kcApp.hkCache);
    
    if (FAILED(hr))
    {
        //  we have now tried the default
        //  we need to try our private cache
        hr = _AssocGetRegUIString(hk, TEXT("shell"), TEXT("FriendlyCache"), strOut);

        if (flags & ASSOCF_VERIFY)
        {
            SHSTR strExe;
  
            if (!pszApp)
            {
                ASSERT(_fAppOnly);
                if (_fAppPath)
                {
                    pszApp = _szInit;
                }
                else if (SUCCEEDED(_GetExeString(flags, FALSE, NULL, strExe)))
                {
                    pszApp = strExe;
                }
            }

            hr = _GetFriendlyAppByCache(hk, pszApp, SUCCEEDED(hr), (flags & ASSOCF_NOFIXUPS), strOut);
        }
    }
    return hr;
}
        
HRESULT CAssocW2k::_GetFriendlyAppString(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    // Algorithm:
    //     if there is a named value "friendly", return its value;
    //     if it is a darwin app, return darwin product name;
    //     if there is app key, return its named value "friendly"
    //     o/w, get friendly name from the exe, and cache it in its app key
    
    //  check the verb first.  that overrides the
    //  general exe case
    HRESULT hr; 

    if (_fAppOnly)
    {
        hr = _GetFriendlyAppByApp(NULL, flags, strOut);
    }
    else
    {
        hr = _GetFriendlyAppByVerb(flags, fForceLM, pszVerb, strOut);
    }

    return hr;
}

HRESULT CAssocW2k::_GetShellExtension(ASSOCF flags, BOOL fForceLM, LPCTSTR pszShellEx, SHSTR& strOut)
{

    HRESULT hr = E_FAIL;
    if (pszShellEx && *pszShellEx)
    {
        // Try to get the extension handler under ProgID first.
        HKEY hk = _ClassKey(fForceLM);
        TCHAR szHandler[140] = TEXT("ShellEx\\");
        StrCatBuff(szHandler, pszShellEx, ARRAYSIZE(szHandler));
 
        if (hk)
        {
            hr = _AssocGetRegString(hk, szHandler, NULL, strOut);
        }

        // Else try to get the extension handler under file extension.
        if (FAILED(hr) && _szInit[0]) // szInit[0] = NULL, if Iqa is inited by key.
        {
            //  reuse hk here
            hk = _ExtensionKey(fForceLM);
            if (hk)
            {
                hr = _AssocGetRegString(hk, szHandler, NULL, strOut);
            }            
        }
        
    }        
    return hr;
}

HRESULT CAssocW2k::_GetTipString(LPCWSTR pwszValueName, BOOL fForceLM, SHSTR& strOut)
{
    HRESULT hr = _AssocGetRegUIString(_ClassKey(fForceLM), NULL, pwszValueName, strOut);
    if (FAILED(hr))
        hr = _AssocGetRegUIString(_ExtensionKey(fForceLM), NULL, pwszValueName, strOut);
    if (FAILED(hr) && !fForceLM)
        hr = _AssocGetRegUIString(_ExtensionKey(TRUE), NULL, pwszValueName, strOut);
    return hr;
}

HRESULT CAssocW2k::_GetInfoTipString(BOOL fForceLM, SHSTR& strOut)
{
    return _GetTipString(L"InfoTip", fForceLM, strOut);
}

HRESULT CAssocW2k::_GetQuickTipString(BOOL fForceLM, SHSTR& strOut)
{
    return _GetTipString(L"QuickTip", fForceLM, strOut);
}

HRESULT CAssocW2k::_GetTileInfoString(BOOL fForceLM, SHSTR& strOut)
{
    return _GetTipString(L"TileInfo", fForceLM, strOut);
}

HRESULT CAssocW2k::_GetWebViewDisplayPropsString(BOOL fForceLM, SHSTR& strOut)
{
    return _GetTipString(L"WebViewDisplayProperties", fForceLM, strOut);
}


HRESULT CAssocW2k::_GetShellNewValueString(BOOL fForceLM, BOOL fQueryOnly, LPCTSTR pszValue, SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    HKEY hk = _ShellNewKey(fForceLM);

    if (hk)
    {
        TCHAR sz[MAX_PATH];
        if (!pszValue)
        {
            //  get the default value....
            DWORD cch = SIZECHARS(sz);
            //  we want a pszValue....
            if (ERROR_SUCCESS == RegEnumValue(hk, 0, sz, &cch, NULL, NULL, NULL, NULL))
                pszValue = sz;
        }

        hr = _AssocGetRegString(hk, NULL, pszValue, strOut);
    }
    
    return hr;
}

HKEY CAssocW2k::_DDEKey(BOOL fForceLM, LPCTSTR pszVerb)
{
    HKEY hkRet = NULL;
    KEYCACHETYPE type;

    if (!fForceLM)
    {
        type = KEYCACHE_HKCU;
    }
    else
    {
        type = KEYCACHE_HKLM;
    }

    if (_CanUseCache(_kcDDE, pszVerb, type))
    {
        hkRet = _kcDDE.hkCache;
    }
    else
    {
        if (SUCCEEDED(_AssocOpenRegKey(_ShellVerbKey(fForceLM, pszVerb), TEXT("ddeexec"), &hkRet)))
        {
            _CacheKey(_kcDDE, hkRet, pszVerb, type);
        }
    }

    return hkRet;
}

HRESULT CAssocW2k::_GetDDEApplication(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    HKEY hk = _DDEKey(fForceLM, pszVerb);

    if (hk)
    {
        hr = _AssocGetRegString(hk, TEXT("Application"), NULL, strOut);

        if (FAILED(hr) || IsEmptyStr(strOut))
        {
            hr = E_FAIL;
            //  this means we should figure it out
            if (SUCCEEDED(_GetExeString(flags, fForceLM, pszVerb, strOut)))
            {
                PathRemoveExtension(strOut.GetInplaceStr());
                PathStripPath(strOut.GetInplaceStr());

                if (!IsEmptyStr(strOut))
                {
                    //  we have a useful app name
                    hr = S_OK;
                    
                    if (!(flags & ASSOCF_NOFIXUPS))
                    {
                        //  lets put it back!
                        SHSetValue(_DDEKey(fForceLM, pszVerb), TEXT("Application"), NULL, REG_SZ, strOut.GetStr(), CbFromCch(strOut.GetLen() +1));
                    }
                }
            }
        }
    }
    
    return hr;
}

HRESULT CAssocW2k::_GetDDETopic(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    HKEY hk = _DDEKey(fForceLM, pszVerb);

    if (hk)
    {
        hr = _AssocGetRegString(hk, TEXT("Topic"), NULL, strOut);

        if (FAILED(hr) || IsEmptyStr(strOut))
            hr = strOut.SetStr(TEXT("System"));
    }
    
    return hr;
}

HRESULT CAssocW2k::_GetContentType(SHSTR& strOut)
{
    HRESULT hr = E_FAIL;
    if (ISEXTENSION(_szInit))
    {
        HKEY hk = _ExtensionKey(TRUE);

        if (hk)
        {
            hr = _AssocGetRegString(hk, NULL, TEXT("Content Type"), strOut);
        }
    }
    return hr;
}
 

HRESULT CAssocW2k::GetString(ASSOCF flags, ASSOCSTR str, LPCTSTR pszExtra, LPTSTR pszOut, DWORD *pcchOut)
{
    RIP(_fInited);
    if (!_fInited)
        return E_UNEXPECTED;
        
    HRESULT hr = E_INVALIDARG;
    SHSTR strOut;

    if (str && str < ASSOCSTR_MAX && pcchOut && (pszOut || !IS_INTRESOURCE(pcchOut)))
    {
        BOOL fForceLM = (_fAppOnly) || (flags & ASSOCF_NOUSERSETTINGS);

        if (!_fBaseClassOnly || ASSOCSTR_FRIENDLYDOCNAME == str)
        {
            switch(str)
            {
            case ASSOCSTR_COMMAND:
                hr = _GetCommandString(flags, fForceLM, pszExtra, strOut);
                break;

            case ASSOCSTR_EXECUTABLE:
                hr = _GetExeString(flags, fForceLM, pszExtra, strOut);
                break;

            case ASSOCSTR_FRIENDLYAPPNAME:
                hr = _GetFriendlyAppString(flags, fForceLM, pszExtra, strOut);
                break;

            case ASSOCSTR_SHELLNEWVALUE:
                if (!_fAppOnly)
                    hr = _GetShellNewValueString(fForceLM, (pszOut == NULL), pszExtra, strOut);
                break;
                
            case ASSOCSTR_NOOPEN:
                if (!_fAppOnly)
                    hr = _AssocGetRegString(_ClassKey(fForceLM), NULL, TEXT("NoOpen"), strOut);
                break;

            case ASSOCSTR_FRIENDLYDOCNAME:
                if (!_fAppOnly)
                    hr = _GetFriendlyDocName(strOut);
                break;

            case ASSOCSTR_DDECOMMAND:
                hr = _AssocGetRegString(_DDEKey(fForceLM, pszExtra), NULL, NULL, strOut);
                break;
                
            case ASSOCSTR_DDEIFEXEC:
                hr = _AssocGetRegString(_DDEKey(fForceLM, pszExtra), TEXT("IfExec"), NULL, strOut);
                break;

            case ASSOCSTR_DDEAPPLICATION:
                hr = _GetDDEApplication(flags, fForceLM, pszExtra, strOut);
                break;

            case ASSOCSTR_DDETOPIC:
                hr = _GetDDETopic(flags, fForceLM, pszExtra, strOut);
                break;

            case ASSOCSTR_INFOTIP:
                hr = _GetInfoTipString(fForceLM, strOut);
                break;

            case ASSOCSTR_QUICKTIP:
                hr = _GetQuickTipString(fForceLM, strOut);
                break;

            case ASSOCSTR_TILEINFO:
                hr = _GetTileInfoString(fForceLM, strOut);
                break;

            case ASSOCSTR_CONTENTTYPE:
                hr = _GetContentType(strOut);
                break;

             case ASSOCSTR_DEFAULTICON:
                hr = _AssocGetRegString(_ClassKey(fForceLM), TEXT("DefaultIcon"), NULL, strOut);
                break;

            case ASSOCSTR_SHELLEXTENSION:
                hr = _GetShellExtension(flags, fForceLM, pszExtra, strOut);
                if (FAILED(hr) && !fForceLM)
                    hr = _GetShellExtension(flags, TRUE, pszExtra, strOut);
                break;

            default:
                //
                // Turn off this assert message until we have a clean way to support new ASSOCSTR types 
                // in both shell32 and shlwapi
                //
#if 0
                AssertMsg(FALSE, TEXT("CAssocW2k::GetString() mismatched headers - ZekeL"));
#endif
                hr = E_INVALIDARG;
                break;
            }
        }
        
        if (SUCCEEDED(hr))
            hr = _CopyOut(flags & ASSOCF_NOTRUNCATE, strOut, pszOut, pcchOut);
        else if (!(flags & ASSOCF_IGNOREBASECLASS) && _UseBaseClass())
        {
            HRESULT hrT = _pqaBaseClass->GetString(flags, str, pszExtra, pszOut, pcchOut);
            if (SUCCEEDED(hrT))
                hr = hrT;
        }
    }
    
    return hr;
}

HRESULT CAssocW2k::_GetMSIDescriptor(ASSOCF flags, BOOL fForceLM, LPCTSTR pszVerb, LPBYTE pbOut, LPDWORD pcbOut)
{
    // what do we do with A/W thunks of REG_MULTI_SZ

    // the darwin ID is always a value name that is the same as the name of the parent key,
    // so instead of reading the default value we read the value with the name of the
    // parent key.
    //
    //  shell
    //    |
    //    -- Open
    //         |
    //         -- Command
    //              (Default)   =   "%SystemRoot%\system32\normal_app.exe"      <-- this is the normal app value
    //              Command     =   "[DarwinID] /c"                             <-- this is the darwin ID value
    //
    //  HACK!  Access 95 (shipping product) creates a "Command" value under
    //  the Command key but it is >>not<< a Darwin ID.  I don't know what
    //  they were smoking.  So we also check the key type and it must be
    //  REG_MULTI_SZ or we will ignore it.
    //
    //
    DWORD dwType;
    HRESULT hr = _AssocGetRegData(_ShellVerbKey(fForceLM, pszVerb), TEXT("command"), TEXT("command"), &dwType, pbOut, pcbOut);

    if (SUCCEEDED(hr) && dwType != REG_MULTI_SZ)
        hr = E_UNEXPECTED;

    return hr;
}

HRESULT CAssocW2k::GetData(ASSOCF flags, ASSOCDATA data, LPCWSTR pszExtra, LPVOID pvOut, DWORD *pcbOut)
{
    RIP(_fInited);
    if (!_fInited)
        return E_UNEXPECTED;
        
    HRESULT hr = E_INVALIDARG;

    if (data && data < ASSOCSTR_MAX)
    {
        BOOL fForceLM = (_fAppOnly) || (flags & ASSOCF_NOUSERSETTINGS);
        DWORD cbReal;
        if (pcbOut && IS_INTRESOURCE(pcbOut))
        {
            cbReal = PtrToUlong(pcbOut);
            pcbOut = &cbReal;
        }

        if (!_fBaseClassOnly)
        {
            switch(data)
            {
            case ASSOCDATA_MSIDESCRIPTOR:
                hr = _GetMSIDescriptor(flags, fForceLM, pszExtra, (LPBYTE)pvOut, pcbOut);
                break;

            case ASSOCDATA_NOACTIVATEHANDLER:
                hr = _AssocGetRegData(_DDEKey(fForceLM, pszExtra), NULL, TEXT("NoActivateHandler"), NULL, (LPBYTE) pvOut, pcbOut);
                break;

            case ASSOCDATA_QUERYCLASSSTORE:
                hr = _AssocGetRegData(_ClassKey(fForceLM), NULL, TEXT("QueryClassStore"), NULL, (LPBYTE) pvOut, pcbOut);                
                break;

            case ASSOCDATA_HASPERUSERASSOC:
                {
                    HKEY hk = _UserProgidKey();
                    if (hk && _ShellVerbKey(hk, KEYCACHE_HKCU, pszExtra))
                        hr = S_OK;
                    else
                        hr = S_FALSE;

                    REGFREE(hk);
                }
                break;

            case ASSOCDATA_EDITFLAGS:
                hr = _AssocGetRegData(_ClassKey(fForceLM), NULL, TEXT("EditFlags"), NULL, (LPBYTE) pvOut, pcbOut);                               
                break;
                
            default:
                AssertMsg(FALSE, TEXT("CAssocW2k::GetString() mismatched headers - ZekeL"));
                hr = E_INVALIDARG;
                break;
            }
        }

        if (FAILED(hr) && !(flags & ASSOCF_IGNOREBASECLASS) && _UseBaseClass())
        {
            HRESULT hrT = _pqaBaseClass->GetData(flags, data, pszExtra, pvOut, pcbOut);
            if (SUCCEEDED(hrT))
                hr = hrT;
        }
    }
    
    return hr;
}

HRESULT CAssocW2k::GetEnum(ASSOCF flags, ASSOCENUM assocenum, LPCTSTR pszExtra, REFIID riid, LPVOID *ppvOut)
{
    return E_NOTIMPL;
}

HRESULT CAssocW2k::_GetShellExecKey(ASSOCF flags, BOOL fForceLM, LPCWSTR pszVerb, HKEY *phkey)
{
    HKEY hkProgid = NULL;

    if (pszVerb && !*pszVerb) 
        pszVerb = NULL;

    if (!fForceLM)
    {
        hkProgid = _ClassKey(FALSE);
        if (hkProgid && (!(flags & ASSOCF_VERIFY) || _ShellVerbKey(hkProgid, KEYCACHE_HKCU, pszVerb)))
            *phkey = SHRegDuplicateHKey(hkProgid);
    }

    if (!*phkey) 
    {
        KEYCACHETYPE type = (_fAppOnly) ? KEYCACHE_APP : KEYCACHE_HKLM;
        hkProgid = _ClassKey(TRUE);
        if (hkProgid && (!(flags & ASSOCF_VERIFY) || _ShellVerbKey(hkProgid, type, pszVerb)))
            *phkey = SHRegDuplicateHKey(hkProgid);
    }

    return *phkey ? S_OK : HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
}
    
HRESULT CAssocW2k::_CloneKey(HKEY hk, HKEY *phkey)
{
    if (hk)
        *phkey = SHRegDuplicateHKey(hk);

    return *phkey ? S_OK : HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION);
}

HRESULT CAssocW2k::GetKey(ASSOCF flags, ASSOCKEY key, LPCTSTR pszExtra, HKEY *phkey)
{
    RIP(_fInited);
    if (!_fInited)
        return E_UNEXPECTED;

    HRESULT hr = E_INVALIDARG;
    
    if (key && key < ASSOCKEY_MAX && phkey)
    {
        BOOL fForceLM = (_fAppOnly) || (flags & ASSOCF_NOUSERSETTINGS);
        *phkey = NULL;

        if (!_fBaseClassOnly)
        {
            switch (key)
            {
            case ASSOCKEY_SHELLEXECCLASS:
                hr = _GetShellExecKey(flags, fForceLM, pszExtra, phkey);
                break;

            case ASSOCKEY_APP:
                hr = _fAppOnly ? _CloneKey(_AppKey(NULL), phkey) : E_INVALIDARG;
                break;

            case ASSOCKEY_CLASS:
                hr = _CloneKey(_ClassKey(fForceLM), phkey);
                break;

            case ASSOCKEY_BASECLASS:
                //  fall through and it is handled by the BaseClass handling
                break;
                
            default:
                AssertMsg(FALSE, TEXT("CAssocW2k::GetKey() mismatched headers - ZekeL"));
                hr = E_INVALIDARG;
                break;
            }
        }
        
        if (FAILED(hr) && !(flags & ASSOCF_IGNOREBASECLASS) && _UseBaseClass())
        {
            //  it is possible to indicate the depth of the 
            //  base class by pszExtra being an INT
            if (key == ASSOCKEY_BASECLASS)
            {
                int depth = IS_INTRESOURCE(pszExtra) ? LOWORD(pszExtra) : 0;
                if (depth)
                {
                    //  go deeper than this
                    depth--;
                    hr = _pqaBaseClass->GetKey(flags, key, MAKEINTRESOURCE(depth), phkey);
                }
                else
                {
                    //  just return this baseclass
                    hr = _pqaBaseClass->GetKey(flags, ASSOCKEY_CLASS, pszExtra, phkey);
                }
            }
            else
            {
                //  forward to the base class
                hr = _pqaBaseClass->GetKey(flags, key, pszExtra, phkey);
            }
        }
        
    }

    return hr;
}

HRESULT AssocCreateW2k(REFIID riid, LPVOID *ppvOut)
{
    HRESULT hr = E_OUTOFMEMORY;
    CAssocW2k *passoc = new CAssocW2k();
    if (passoc)
    {
        hr = passoc->QueryInterface(riid, ppvOut);
        passoc->Release();
    }
    return hr;
}