Files
Timo Kreuzer 0c2acefd64 [SHLWAPI] Switch Vista checks from preprocessor to dynamic
Check the OS version in the PEB to decide how to handle certain nthings.
This allows to keep NT5 backwards compatibility, even if the the code is compiled for Vista, which is needed for Wine code.
2026-06-18 23:27:41 +00:00

2005 lines
54 KiB
C++

/*
* PROJECT: ReactOS Shell
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
* PURPOSE: Implement shell property bags
* COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <[email protected]>
*/
#define _ATL_NO_EXCEPTIONS
#include "precomp.h"
#include <shlwapi.h>
#include <shlwapi_undoc.h>
#include <shlobj_undoc.h>
#include <shlguid_undoc.h>
#include <atlstr.h> // for CStringW
#include <atlsimpcoll.h> // for CSimpleMap
#include <atlcomcli.h> // for CComVariant
#include <atlconv.h> // for CA2W and CW2A
#include <strsafe.h> // for StringC... functions
#include <cstdlib> // __min
#include <new> // std::nothrow
WINE_DEFAULT_DEBUG_CHANNEL(shell);
#define MODE_CAN_READ(dwMode) \
(((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE)
#define MODE_CAN_WRITE(dwMode) \
(((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ)
class CBasePropertyBag
: public IPropertyBag
#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || defined(__REACTOS__)
, public IPropertyBag2
#endif
{
protected:
LONG m_cRefs; // reference count
DWORD m_dwMode; // STGM_* flags
public:
CBasePropertyBag(DWORD dwMode)
: m_cRefs(0)
, m_dwMode(dwMode)
{
}
virtual ~CBasePropertyBag() { }
// IUnknown interface
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
{
if (!ppvObject)
return E_POINTER;
if (GetProcessOsVersion() < _WIN32_WINNT_VISTA)
{
if (::IsEqualGUID(riid, IID_IPropertyBag2))
{
AddRef();
*ppvObject = static_cast<IPropertyBag2*>(this);
return S_OK;
}
}
if (::IsEqualGUID(riid, IID_IUnknown) || ::IsEqualGUID(riid, IID_IPropertyBag))
{
AddRef();
*ppvObject = static_cast<IPropertyBag*>(this);
return S_OK;
}
ERR("%p: %s: E_NOINTERFACE\n", this, debugstr_guid(&riid));
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() override
{
return ::InterlockedIncrement(&m_cRefs);
}
STDMETHODIMP_(ULONG) Release() override
{
if (::InterlockedDecrement(&m_cRefs) == 0)
{
delete this;
return 0;
}
return m_cRefs;
}
#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) || defined(__REACTOS__)
// IPropertyBag2 interface (stubs)
STDMETHODIMP Read(
_In_ ULONG cProperties,
_In_ PROPBAG2 *pPropBag,
_In_opt_ IErrorLog *pErrorLog,
_Out_ VARIANT *pvarValue,
_Out_ HRESULT *phrError) override
{
return E_NOTIMPL;
}
STDMETHODIMP Write(
_In_ ULONG cProperties,
_In_ PROPBAG2 *pPropBag,
_In_ VARIANT *pvarValue)
{
return E_NOTIMPL;
}
STDMETHODIMP CountProperties(_Out_ ULONG *pcProperties) override
{
return E_NOTIMPL;
}
STDMETHODIMP GetPropertyInfo(
_In_ ULONG iProperty,
_In_ ULONG cProperties,
_Out_ PROPBAG2 *pPropBag,
_Out_ ULONG *pcProperties) override
{
return E_NOTIMPL;
}
STDMETHODIMP LoadObject(
_In_z_ LPCWSTR pstrName,
_In_ DWORD dwHint,
_In_ IUnknown *pUnkObject,
_In_opt_ IErrorLog *pErrorLog) override
{
return E_NOTIMPL;
}
#endif
};
struct CPropMapEqual
{
static bool IsEqualKey(const ATL::CStringW& k1, const ATL::CStringW& k2)
{
return k1.CompareNoCase(k2) == 0;
}
static bool IsEqualValue(const ATL::CComVariant& v1, const ATL::CComVariant& v2)
{
return false;
}
};
class CMemPropertyBag : public CBasePropertyBag
{
protected:
ATL::CSimpleMap<ATL::CStringW, ATL::CComVariant, CPropMapEqual> m_PropMap;
public:
CMemPropertyBag(DWORD dwMode) : CBasePropertyBag(dwMode) { }
STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog) override;
STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
};
STDMETHODIMP
CMemPropertyBag::Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog)
{
UNREFERENCED_PARAMETER(pErrorLog);
TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
VARTYPE vt = V_VT(pvari);
::VariantInit(pvari);
if (GetProcessOsVersion() < _WIN32_WINNT_VISTA)
{
if (!MODE_CAN_READ(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
return E_ACCESSDENIED;
}
}
if (!pszPropName || !pvari)
{
ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
return E_INVALIDARG;
}
INT iItem = m_PropMap.FindKey(pszPropName);
if (iItem == -1)
{
ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
return E_FAIL;
}
HRESULT hr = ::VariantCopy(pvari, &m_PropMap.GetValueAt(iItem));
if (FAILED(hr))
{
ERR("%p: 0x%08X %p\n", this, hr, pvari);
return hr;
}
hr = ::VariantChangeTypeForRead(pvari, vt);
if (FAILED(hr))
{
ERR("%p: 0x%08X %p\n", this, hr, pvari);
return hr;
}
return hr;
}
STDMETHODIMP
CMemPropertyBag::Write(
_In_z_ LPCWSTR pszPropName,
_In_ VARIANT *pvari)
{
TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
if (GetProcessOsVersion() < _WIN32_WINNT_VISTA)
{
if (!MODE_CAN_WRITE(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
return E_ACCESSDENIED;
}
}
if (!pszPropName || !pvari)
{
ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
return E_INVALIDARG;
}
ATL::CComVariant vari;
HRESULT hr = vari.Copy(pvari);
if (FAILED(hr))
{
ERR("%p: %s %p: 0x%08X\n", this, debugstr_w(pszPropName), pvari, hr);
return hr;
}
if (!m_PropMap.SetAt(pszPropName, vari))
{
ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
return E_FAIL;
}
return hr;
}
/**************************************************************************
* SHCreatePropertyBagOnMemory (SHLWAPI.477)
*
* Creates a property bag object on memory.
*
* @param dwMode Specifies either STGM_READ, STGM_WRITE or STGM_READWRITE. Ignored on Vista+.
* @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
* Vista+ rejects IID_IPropertyBag2.
* @param ppvObj Receives an IPropertyBag pointer.
* @return An HRESULT value. S_OK on success, non-zero on failure.
* @see http://undoc.airesoft.co.uk/shlwapi.dll/SHCreatePropertyBagOnMemory.php
*/
EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnMemory(_In_ DWORD dwMode, _In_ REFIID riid, _Out_ void **ppvObj)
{
TRACE("0x%08X, %s, %p\n", dwMode, debugstr_guid(&riid), ppvObj);
*ppvObj = NULL;
CComPtr<CMemPropertyBag> pMemBag(new(std::nothrow) CMemPropertyBag(dwMode));
if (!pMemBag)
return E_OUTOFMEMORY;
return pMemBag->QueryInterface(riid, ppvObj);
}
class CRegPropertyBag : public CBasePropertyBag
{
protected:
HKEY m_hKey;
HRESULT _ReadDword(LPCWSTR pszPropName, VARIANT *pvari);
HRESULT _ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len);
HRESULT _ReadBinary(LPCWSTR pszPropName, VARIANT *pvari, VARTYPE vt, DWORD uBytes);
HRESULT _ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit);
HRESULT _CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb);
HRESULT _GetStreamSize(IStream *pStream, LPDWORD pcbSize);
HRESULT _WriteStream(LPCWSTR pszPropName, IStream *pStream);
public:
CRegPropertyBag(DWORD dwMode)
: CBasePropertyBag(dwMode)
, m_hKey(NULL)
{
}
~CRegPropertyBag() override
{
if (m_hKey)
::RegCloseKey(m_hKey);
}
HRESULT Init(HKEY hKey, LPCWSTR lpSubKey);
STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog) override;
STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
};
HRESULT CRegPropertyBag::Init(HKEY hKey, LPCWSTR lpSubKey)
{
REGSAM nAccess = 0;
if (MODE_CAN_READ(m_dwMode))
nAccess |= KEY_READ;
if (MODE_CAN_WRITE(m_dwMode))
nAccess |= KEY_WRITE;
LONG error;
if (m_dwMode & STGM_CREATE)
error = ::RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, nAccess, NULL, &m_hKey, NULL);
else
error = ::RegOpenKeyExW(hKey, lpSubKey, 0, nAccess, &m_hKey);
if (error != ERROR_SUCCESS)
{
ERR("%p %s 0x%08X\n", hKey, debugstr_w(lpSubKey), error);
return HRESULT_FROM_WIN32(error);
}
return S_OK;
}
HRESULT CRegPropertyBag::_ReadDword(LPCWSTR pszPropName, VARIANT *pvari)
{
DWORD cbData = sizeof(DWORD);
LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, &V_UI4(pvari), &cbData);
if (error)
return E_FAIL;
V_VT(pvari) = VT_UI4;
return S_OK;
}
HRESULT CRegPropertyBag::_ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len)
{
BSTR bstr = ::SysAllocStringByteLen(NULL, len);
V_BSTR(pvarg) = bstr;
if (!bstr)
return E_OUTOFMEMORY;
V_VT(pvarg) = VT_BSTR;
LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, bstr, (LPDWORD)&len);
if (error)
{
::VariantClear(pvarg);
return E_FAIL;
}
return S_OK;
}
HRESULT CRegPropertyBag::_ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit)
{
IStream *pStream = SHCreateMemStream(pInit, cbInit);
V_UNKNOWN(pvari) = pStream;
if (!pStream)
return E_OUTOFMEMORY;
V_VT(pvari) = VT_UNKNOWN;
return S_OK;
}
HRESULT
CRegPropertyBag::_ReadBinary(
LPCWSTR pszPropName,
VARIANT *pvari,
VARTYPE vt,
DWORD uBytes)
{
HRESULT hr = E_FAIL;
if (vt != VT_UNKNOWN || uBytes < sizeof(GUID))
return hr;
LPBYTE pbData = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, uBytes);
if (!pbData)
return hr;
if (!SHGetValueW(m_hKey, NULL, pszPropName, NULL, pbData, &uBytes) &&
memcmp(&GUID_NULL, pbData, sizeof(GUID)) == 0)
{
hr = _ReadStream(pvari, pbData + sizeof(GUID), uBytes - sizeof(GUID));
}
::LocalFree(pbData);
return hr;
}
HRESULT CRegPropertyBag::_CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb)
{
LARGE_INTEGER li;
li.QuadPart = 0;
HRESULT hr = pStream->Seek(li, 0, NULL);
if (FAILED(hr))
return hr;
return pStream->Read(pv, cb, NULL);
}
HRESULT CRegPropertyBag::_GetStreamSize(IStream *pStream, LPDWORD pcbSize)
{
*pcbSize = 0;
ULARGE_INTEGER ui;
HRESULT hr = IStream_Size(pStream, &ui);
if (FAILED(hr))
return hr;
if (ui.DUMMYSTRUCTNAME.HighPart)
return E_FAIL; /* 64-bit value is not supported */
*pcbSize = ui.DUMMYSTRUCTNAME.LowPart;
return hr;
}
STDMETHODIMP
CRegPropertyBag::Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog)
{
UNREFERENCED_PARAMETER(pErrorLog);
TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
if (!MODE_CAN_READ(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
::VariantInit(pvari);
return E_ACCESSDENIED;
}
VARTYPE vt = V_VT(pvari);
VariantInit(pvari);
HRESULT hr;
DWORD dwType, cbValue;
LONG error = SHGetValueW(m_hKey, NULL, pszPropName, &dwType, NULL, &cbValue);
if (error != ERROR_SUCCESS)
hr = E_FAIL;
else if (dwType == REG_SZ)
hr = _ReadString(pszPropName, pvari, cbValue);
else if (dwType == REG_BINARY)
hr = _ReadBinary(pszPropName, pvari, vt, cbValue);
else if (dwType == REG_DWORD)
hr = _ReadDword(pszPropName, pvari);
else
hr = E_FAIL;
if (FAILED(hr))
{
ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
::VariantInit(pvari);
return hr;
}
hr = ::VariantChangeTypeForRead(pvari, vt);
if (FAILED(hr))
{
ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
::VariantInit(pvari);
}
return hr;
}
HRESULT
CRegPropertyBag::_WriteStream(LPCWSTR pszPropName, IStream *pStream)
{
DWORD cbData;
HRESULT hr = _GetStreamSize(pStream, &cbData);
if (FAILED(hr) || !cbData)
return hr;
DWORD cbBinary = cbData + sizeof(GUID);
LPBYTE pbBinary = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, cbBinary);
if (!pbBinary)
return E_OUTOFMEMORY;
hr = _CopyStreamIntoBuff(pStream, pbBinary + sizeof(GUID), cbData);
if (SUCCEEDED(hr))
{
if (SHSetValueW(m_hKey, NULL, pszPropName, REG_BINARY, pbBinary, cbBinary))
hr = E_FAIL;
}
::LocalFree(pbBinary);
return hr;
}
STDMETHODIMP
CRegPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
{
TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
if (!MODE_CAN_WRITE(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
return E_ACCESSDENIED;
}
HRESULT hr;
LONG error;
VARIANTARG vargTemp = { 0 };
switch (V_VT(pvari))
{
case VT_EMPTY:
SHDeleteValueW(m_hKey, NULL, pszPropName);
hr = S_OK;
break;
case VT_BOOL:
case VT_I1:
case VT_I2:
case VT_I4:
case VT_UI1:
case VT_UI2:
case VT_UI4:
case VT_INT:
case VT_UINT:
{
hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_UI4);
if (FAILED(hr))
return hr;
error = SHSetValueW(m_hKey, NULL, pszPropName, REG_DWORD, &V_UI4(&vargTemp), sizeof(DWORD));
if (error)
hr = E_FAIL;
::VariantClear(&vargTemp);
break;
}
case VT_UNKNOWN:
{
CComPtr<IStream> pStream;
hr = V_UNKNOWN(pvari)->QueryInterface(IID_IStream, (void **)&pStream);
if (FAILED(hr))
return hr;
hr = _WriteStream(pszPropName, pStream);
break;
}
default:
{
hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
if (FAILED(hr))
return hr;
int cch = lstrlenW(V_BSTR(&vargTemp));
DWORD cb = (cch + 1) * sizeof(WCHAR);
error = SHSetValueW(m_hKey, NULL, pszPropName, REG_SZ, V_BSTR(&vargTemp), cb);
if (error)
hr = E_FAIL;
::VariantClear(&vargTemp);
break;
}
}
return hr;
}
/**************************************************************************
* SHCreatePropertyBagOnRegKey (SHLWAPI.471)
*
* Creates a property bag object on registry key.
*
* @param hKey The registry key.
* @param pszSubKey The path of the sub-key.
* @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
* @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
* @param ppvObj Receives an IPropertyBag pointer.
* @return An HRESULT value. S_OK on success, non-zero on failure.
* @see https://source.winehq.org/WineAPI/SHCreatePropertyBagOnRegKey.html
*/
EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnRegKey(
_In_ HKEY hKey,
_In_z_ LPCWSTR pszSubKey,
_In_ DWORD dwMode,
_In_ REFIID riid,
_Out_ void **ppvObj)
{
TRACE("%p, %s, 0x%08X, %s, %p\n", hKey, debugstr_w(pszSubKey), dwMode,
debugstr_guid(&riid), ppvObj);
*ppvObj = NULL;
CComPtr<CRegPropertyBag> pRegBag(new(std::nothrow) CRegPropertyBag(dwMode));
if (!pRegBag)
return E_OUTOFMEMORY;
HRESULT hr = pRegBag->Init(hKey, pszSubKey);
if (FAILED(hr))
return hr;
return pRegBag->QueryInterface(riid, ppvObj);
}
/**************************************************************************
* SHGetIniStringW (SHLWAPI.294)
*
* @see https://source.winehq.org/WineAPI/SHGetIniStringW.html
*/
EXTERN_C DWORD WINAPI
SHGetIniStringW(
_In_z_ LPCWSTR appName,
_In_z_ LPCWSTR keyName,
_Out_writes_to_(outLen, return + 1) LPWSTR out,
_In_ DWORD outLen,
_In_z_ LPCWSTR filename)
{
TRACE("(%s,%s,%p,%08x,%s)\n", debugstr_w(appName), debugstr_w(keyName),
out, outLen, debugstr_w(filename));
if (outLen == 0)
return 0;
// Try ".W"-appended section name. See also SHSetIniStringW
CStringW szSection(appName);
szSection += L".W";
CStringW pszWideBuff;
const INT cchWideMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
GetPrivateProfileStringW(szSection, keyName, NULL,
pszWideBuff.GetBuffer(cchWideMax), cchWideMax, filename);
pszWideBuff.ReleaseBuffer();
if (pszWideBuff.IsEmpty()) // It's empty or not found
{
// Try the normal section name
return GetPrivateProfileStringW(appName, keyName, NULL, out, outLen, filename);
}
// Okay, now ".W" version is valid. Its value is a UTF-7 string in UTF-16
CW2A wide2utf7(pszWideBuff);
MultiByteToWideChar(CP_UTF7, 0, wide2utf7, -1, out, outLen);
out[outLen - 1] = UNICODE_NULL;
return lstrlenW(out);
}
static BOOL Is7BitClean(LPCWSTR psz)
{
if (!psz)
return TRUE;
while (*psz)
{
if (*psz > 0x7F)
return FALSE;
++psz;
}
return TRUE;
}
/**************************************************************************
* SHSetIniStringW (SHLWAPI.295)
*
* @see https://source.winehq.org/WineAPI/SHSetIniStringW.html
*/
EXTERN_C BOOL WINAPI
SHSetIniStringW(
_In_z_ LPCWSTR appName,
_In_z_ LPCWSTR keyName,
_In_opt_z_ LPCWSTR str,
_In_z_ LPCWSTR filename)
{
TRACE("(%s, %p, %s, %s)\n", debugstr_w(appName), keyName, debugstr_w(str),
debugstr_w(filename));
// Write a normal profile string. If str was NULL, then key will be deleted
if (!WritePrivateProfileStringW(appName, keyName, str, filename))
return FALSE;
if (Is7BitClean(str))
{
// Delete ".A" version
CStringW szSection(appName);
szSection += L".A";
WritePrivateProfileStringW(szSection, keyName, NULL, filename);
// Delete ".W" version
szSection = appName;
szSection += L".W";
WritePrivateProfileStringW(szSection, keyName, NULL, filename);
return TRUE;
}
// Now str is not 7-bit clean. It needs UTF-7 encoding in UTF-16.
// We write ".A" and ".W"-appended sections
CW2A wide2utf7(str, CP_UTF7);
CA2W utf72wide(wide2utf7, CP_ACP);
BOOL ret = TRUE;
// Write ".A" version
CStringW szSection(appName);
szSection += L".A";
if (!WritePrivateProfileStringW(szSection, keyName, str, filename))
ret = FALSE;
// Write ".W" version
szSection = appName;
szSection += L".W";
if (!WritePrivateProfileStringW(szSection, keyName, utf72wide, filename))
ret = FALSE;
return ret;
}
/**************************************************************************
* SHGetIniStringUTF7W (SHLWAPI.473)
*
* Retrieves a string value from an INI file.
*
* @param lpAppName The section name.
* @param lpKeyName The key name.
* If this string begins from '@', the value will be interpreted as UTF-7.
* @param lpReturnedString Receives a wide string value.
* @param nSize The number of characters in lpReturnedString.
* @param lpFileName The INI file.
* @return The number of characters copied to the buffer if succeeded.
*/
EXTERN_C DWORD WINAPI
SHGetIniStringUTF7W(
_In_opt_z_ LPCWSTR lpAppName,
_In_z_ LPCWSTR lpKeyName,
_Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
_In_ DWORD nSize,
_In_z_ LPCWSTR lpFileName)
{
if (*lpKeyName == L'@') // UTF-7
return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize, lpFileName);
return GetPrivateProfileStringW(lpAppName, lpKeyName, L"", lpReturnedString, nSize, lpFileName);
}
/**************************************************************************
* SHSetIniStringUTF7W (SHLWAPI.474)
*
* Sets a string value on an INI file.
*
* @param lpAppName The section name.
* @param lpKeyName The key name.
* If this begins from '@', the value will be stored as UTF-7.
* @param lpString The wide string value to be set.
* @param lpFileName The INI file.
* @return TRUE if successful. FALSE if failed.
*/
EXTERN_C BOOL WINAPI
SHSetIniStringUTF7W(
_In_z_ LPCWSTR lpAppName,
_In_z_ LPCWSTR lpKeyName,
_In_opt_z_ LPCWSTR lpString,
_In_z_ LPCWSTR lpFileName)
{
if (*lpKeyName == L'@') // UTF-7
return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName);
return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
}
class CIniPropertyBag : public CBasePropertyBag
{
protected:
LPWSTR m_pszFileName;
LPWSTR m_pszSection;
BOOL m_bAlternateStream; // ADS (Alternate Data Stream)
static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart)
{
LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\');
if (!pch)
pch = pszStart;
return StrChrW(pch, L':') != NULL;
}
HRESULT
_GetSectionAndName(
LPCWSTR pszStart,
LPWSTR pszSection,
UINT cchSectionMax,
LPWSTR pszName,
UINT cchNameMax);
public:
CIniPropertyBag(DWORD dwMode)
: CBasePropertyBag(dwMode)
, m_pszFileName(NULL)
, m_pszSection(NULL)
, m_bAlternateStream(FALSE)
{
}
~CIniPropertyBag() override
{
::LocalFree(m_pszFileName);
::LocalFree(m_pszSection);
}
HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection);
STDMETHODIMP Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog) override;
STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
};
HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection)
{
m_pszFileName = StrDupW(pszIniFile);
if (!m_pszFileName)
return E_OUTOFMEMORY;
// Is it an ADS (Alternate Data Stream) pathname?
m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName);
if (pszSection)
{
m_pszSection = StrDupW(pszSection);
if (!m_pszSection)
return E_OUTOFMEMORY;
}
return S_OK;
}
HRESULT
CIniPropertyBag::_GetSectionAndName(
LPCWSTR pszStart,
LPWSTR pszSection,
UINT cchSectionMax,
LPWSTR pszName,
UINT cchNameMax)
{
LPCWSTR pchSep = StrChrW(pszStart, L'\\');
if (pchSep)
{
UINT cchSep = (UINT)(pchSep - pszStart + 1);
StrCpyNW(pszSection, pszStart, __min(cchSep, cchSectionMax));
StrCpyNW(pszName, pchSep + 1, cchNameMax);
return S_OK;
}
if (m_pszSection)
{
StrCpyNW(pszSection, m_pszSection, cchSectionMax);
StrCpyNW(pszName, pszStart, cchNameMax);
return S_OK;
}
ERR("%p: %s\n", this, debugstr_w(pszStart));
return E_INVALIDARG;
}
STDMETHODIMP
CIniPropertyBag::Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog)
{
UNREFERENCED_PARAMETER(pErrorLog);
TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
VARTYPE vt = V_VT(pvari);
::VariantInit(pvari);
if (!MODE_CAN_READ(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
return E_ACCESSDENIED;
}
WCHAR szSection[64], szName[64];
HRESULT hr =
_GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
if (FAILED(hr))
return hr;
const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
CComHeapPtr<WCHAR> pszBuff;
if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR)))
return E_OUTOFMEMORY;
if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName))
return E_FAIL;
BSTR bstr = ::SysAllocString(pszBuff);
V_BSTR(pvari) = bstr;
if (!bstr)
return E_OUTOFMEMORY;
V_VT(pvari) = VT_BSTR;
return ::VariantChangeTypeForRead(pvari, vt);
}
STDMETHODIMP
CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
{
TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
if (!MODE_CAN_WRITE(m_dwMode))
{
ERR("%p: 0x%X\n", this, m_dwMode);
return E_ACCESSDENIED;
}
HRESULT hr;
BSTR bstr;
VARIANTARG vargTemp = { 0 };
switch (V_VT(pvari))
{
case VT_EMPTY:
bstr = NULL;
break;
case VT_BSTR:
bstr = V_BSTR(pvari);
break;
default:
hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
if (FAILED(hr))
goto Quit;
bstr = V_BSTR(&vargTemp);
break;
}
WCHAR szSection[64], szName[64];
hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
if (SUCCEEDED(hr))
{
if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName))
{
if (!m_bAlternateStream)
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL);
}
else
{
hr = E_FAIL;
}
}
Quit:
::VariantClear(&vargTemp);
return hr;
}
/**************************************************************************
* SHCreatePropertyBagOnProfileSection (SHLWAPI.472)
*
* Creates a property bag object on INI file.
*
* @param lpFileName The INI filename.
* @param pszSection The optional section name.
* @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
* @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
* @param ppvObj Receives an IPropertyBag pointer.
* @return An HRESULT value. S_OK on success, non-zero on failure.
* @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/createonprofilesection.htm
*/
EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnProfileSection(
_In_z_ LPCWSTR lpFileName,
_In_opt_z_ LPCWSTR pszSection,
_In_ DWORD dwMode,
_In_ REFIID riid,
_Out_ void **ppvObj)
{
HANDLE hFile;
PWCHAR pchFileTitle;
WCHAR szBuff[MAX_PATH];
if (dwMode & STGM_CREATE)
{
hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
pchFileTitle = PathFindFileNameW(lpFileName);
if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0)
{
StrCpyNW(szBuff, lpFileName, _countof(szBuff));
if (PathRemoveFileSpecW(szBuff))
PathMakeSystemFolderW(szBuff);
}
::CloseHandle(hFile);
}
}
*ppvObj = NULL;
if (!PathFileExistsW(lpFileName))
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
CComPtr<CIniPropertyBag> pIniPB(new(std::nothrow) CIniPropertyBag(dwMode));
if (!pIniPB)
return E_OUTOFMEMORY;
HRESULT hr = pIniPB->Init(lpFileName, pszSection);
if (FAILED(hr))
{
ERR("0x%08X\n", hr);
return hr;
}
return pIniPB->QueryInterface(riid, ppvObj);
}
class CDesktopUpgradePropertyBag : public CBasePropertyBag
{
protected:
BOOL _AlreadyUpgraded(HKEY hKey);
VOID _MarkAsUpgraded(HKEY hkey);
HRESULT _ReadFlags(VARIANT *pvari);
HRESULT _ReadItemPositions(VARIANT *pvari);
IStream* _GetOldDesktopViewStream();
IStream* _NewStreamFromOld(IStream *pOldStream);
public:
CDesktopUpgradePropertyBag() : CBasePropertyBag(STGM_READ) { }
STDMETHODIMP Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog) override;
STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override
{
ERR("%p: %s: Read-only\n", this, debugstr_w(pszPropName));
return E_NOTIMPL;
}
};
VOID CDesktopUpgradePropertyBag::_MarkAsUpgraded(HKEY hkey)
{
DWORD dwValue = TRUE;
SHSetValueW(hkey, NULL, L"Upgrade", REG_DWORD, &dwValue, sizeof(dwValue));
}
BOOL CDesktopUpgradePropertyBag::_AlreadyUpgraded(HKEY hKey)
{
// Check the existence of the value written in _MarkAsUpgraded.
DWORD dwValue, cbData = sizeof(dwValue);
return SHGetValueW(hKey, NULL, L"Upgrade", NULL, &dwValue, &cbData) == ERROR_SUCCESS;
}
typedef DWORDLONG DESKVIEW_FLAGS; // 64-bit data
HRESULT CDesktopUpgradePropertyBag::_ReadFlags(VARIANT *pvari)
{
DESKVIEW_FLAGS Flags;
DWORD cbValue = sizeof(Flags);
if (SHGetValueW(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DeskView",
L"Settings",
NULL,
&Flags,
&cbValue) != ERROR_SUCCESS || cbValue < sizeof(Flags))
{
return E_FAIL;
}
V_UINT(pvari) = ((UINT)(Flags >> 32)) | 0x220; // FIXME: Magic number
V_VT(pvari) = VT_UINT;
return S_OK;
}
typedef struct tagOLD_STREAM_HEADER
{
WORD wMagic;
WORD awUnknown[6];
WORD wSize;
} OLD_STREAM_HEADER, *POLD_STREAM_HEADER;
IStream* CDesktopUpgradePropertyBag::_NewStreamFromOld(IStream *pOldStream)
{
OLD_STREAM_HEADER Header;
HRESULT hr = pOldStream->Read(&Header, sizeof(Header), NULL);
if (FAILED(hr) || Header.wMagic != 28)
return NULL;
// Move stream pointer
LARGE_INTEGER li;
li.QuadPart = Header.wSize - sizeof(Header);
hr = pOldStream->Seek(li, STREAM_SEEK_CUR, NULL);
if (FAILED(hr))
return NULL;
// Get the size
ULARGE_INTEGER uli;
hr = IStream_Size(pOldStream, &uli);
if (FAILED(hr))
return NULL;
// Create new stream and attach
CComPtr<IStream> pNewStream;
pNewStream.Attach(SHCreateMemStream(NULL, 0));
if (!pNewStream)
return NULL;
// Subtract Header.wSize from the size
uli.QuadPart -= Header.wSize;
// Copy to pNewStream
hr = pOldStream->CopyTo(pNewStream, uli, NULL, NULL);
if (FAILED(hr))
return NULL;
li.QuadPart = 0;
pNewStream->Seek(li, STREAM_SEEK_SET, NULL);
return pNewStream.Detach();
}
IStream* CDesktopUpgradePropertyBag::_GetOldDesktopViewStream()
{
HKEY hKey = SHGetShellKey(SHKEY_Root_HKCU, L"Streams\\Desktop", FALSE);
if (!hKey)
return NULL;
CComPtr<IStream> pOldStream;
if (!_AlreadyUpgraded(hKey))
{
pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView2", 0));
if (pOldStream)
{
ULARGE_INTEGER uli;
HRESULT hr = IStream_Size(pOldStream, &uli);
if (SUCCEEDED(hr) && !uli.QuadPart)
pOldStream.Release();
}
if (!pOldStream)
pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView", 0));
_MarkAsUpgraded(hKey);
}
::RegCloseKey(hKey);
return pOldStream.Detach();
}
HRESULT CDesktopUpgradePropertyBag::_ReadItemPositions(VARIANT *pvari)
{
CComPtr<IStream> pOldStream;
pOldStream.Attach(_GetOldDesktopViewStream());
if (!pOldStream)
return E_FAIL;
HRESULT hr = E_FAIL;
IStream *pNewStream = _NewStreamFromOld(pOldStream);
if (pNewStream)
{
V_UNKNOWN(pvari) = pNewStream;
V_VT(pvari) = VT_UNKNOWN;
hr = S_OK;
}
return hr;
}
STDMETHODIMP
CDesktopUpgradePropertyBag::Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog)
{
UNREFERENCED_PARAMETER(pErrorLog);
VARTYPE vt = V_VT(pvari);
HRESULT hr = E_FAIL;
if (StrCmpW(L"FFlags", pszPropName) == 0)
hr = _ReadFlags(pvari);
else if (StrCmpNW(L"ItemPos", pszPropName, 7) == 0)
hr = _ReadItemPositions(pvari);
if (FAILED(hr))
{
::VariantInit(pvari);
return hr;
}
return ::VariantChangeType(pvari, pvari, 0, vt);
}
/**************************************************************************
* SHGetDesktopUpgradePropertyBag (Internal)
*
* Creates or gets a property bag object for desktop upgrade
*
* @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
* @param ppvObj Receives an IPropertyBag pointer.
* @return An HRESULT value. S_OK on success, non-zero on failure.
*/
HRESULT SHGetDesktopUpgradePropertyBag(REFIID riid, void **ppvObj)
{
*ppvObj = NULL;
CComPtr<CDesktopUpgradePropertyBag> pPropBag(new(std::nothrow) CDesktopUpgradePropertyBag());
if (!pPropBag)
return E_OUTOFMEMORY;
return pPropBag->QueryInterface(riid, ppvObj);
}
class CViewStatePropertyBag : public CBasePropertyBag
{
protected:
LPITEMIDLIST m_pidl = NULL;
LPWSTR m_pszPath = NULL;
DWORD m_dwVspbFlags = 0; // SHGVSPB_... flags
CComPtr<IPropertyBag> m_pPidlBag;
CComPtr<IPropertyBag> m_pUpgradeBag;
CComPtr<IPropertyBag> m_pInheritBag;
CComPtr<IPropertyBag> m_pUserDefaultsBag;
CComPtr<IPropertyBag> m_pFolderDefaultsBag;
CComPtr<IPropertyBag> m_pGlobalDefaultsBag;
CComPtr<IPropertyBag> m_pReadBag;
CComPtr<IPropertyBag> m_pWriteBag;
BOOL m_bPidlBag = FALSE;
BOOL m_bUpgradeBag = FALSE;
BOOL m_bInheritBag = FALSE;
BOOL m_bUserDefaultsBag = FALSE;
BOOL m_bFolderDefaultsBag = FALSE;
BOOL m_bGlobalDefaultsBag = FALSE;
BOOL m_bReadBag = FALSE;
BOOL m_bWriteBag = FALSE;
BOOL _IsSamePidl(LPCITEMIDLIST pidlOther) const;
BOOL _IsSystemFolder() const;
BOOL _CanAccessPidlBag() const;
BOOL _CanAccessUserDefaultsBag() const;
BOOL _CanAccessFolderDefaultsBag() const;
BOOL _CanAccessGlobalDefaultsBag() const;
BOOL _CanAccessInheritBag() const;
BOOL _CanAccessUpgradeBag() const;
HKEY _GetHKey(DWORD dwVspbFlags);
UINT _GetMRUSize(HKEY hKey);
HRESULT _GetMRUSlots(
LPCITEMIDLIST pidl,
DWORD dwMode,
HKEY hKey,
UINT *puSlots,
UINT cSlots,
UINT *pcSlots);
HRESULT _GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot);
HRESULT _GetRegKey(
LPCITEMIDLIST pidl,
LPCWSTR pszBagName,
DWORD dwFlags,
DWORD dwMode,
HKEY hKey,
LPWSTR pszDest,
INT cchDest);
HRESULT _CreateBag(
LPITEMIDLIST pidl,
LPCWSTR pszPath,
DWORD dwVspbFlags,
DWORD dwMode,
REFIID riid,
IPropertyBag **pppb);
HRESULT _FindNearestInheritBag(REFIID riid, IPropertyBag **pppb);
void _ResetTryAgainFlag();
BOOL _EnsureReadBag(DWORD dwMode, REFIID riid);
BOOL _EnsurePidlBag(DWORD dwMode, REFIID riid);
BOOL _EnsureInheritBag(DWORD dwMode, REFIID riid);
BOOL _EnsureUpgradeBag(DWORD dwMode, REFIID riid);
BOOL _EnsureUserDefaultsBag(DWORD dwMode, REFIID riid);
BOOL _EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid);
BOOL _EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid);
BOOL _EnsureWriteBag(DWORD dwMode, REFIID riid);
HRESULT _ReadPidlBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
HRESULT _ReadInheritBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
HRESULT _ReadUpgradeBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
HRESULT _ReadUserDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
HRESULT _ReadFolderDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
HRESULT _ReadGlobalDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
void _PruneMRUTree();
public:
CViewStatePropertyBag() : CBasePropertyBag(STGM_READ) { }
~CViewStatePropertyBag() override
{
::ILFree(m_pidl);
::LocalFree(m_pszPath);
}
HRESULT Init(_In_opt_ LPCITEMIDLIST pidl, _In_opt_ LPCWSTR pszPath, _In_ DWORD dwVspbFlags);
BOOL IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const;
STDMETHODIMP Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog) override;
STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
};
// CViewStatePropertyBag is cached
CComPtr<CViewStatePropertyBag> g_pCachedBag;
extern "C"
{
CRITICAL_SECTION g_csBagCacheLock;
}
HRESULT
CViewStatePropertyBag::Init(
_In_opt_ LPCITEMIDLIST pidl,
_In_opt_ LPCWSTR pszPath,
_In_ DWORD dwVspbFlags)
{
if (pidl)
{
m_pidl = ILClone(pidl);
if (!m_pidl)
return E_OUTOFMEMORY;
}
if (pszPath)
{
m_pszPath = StrDupW(pszPath);
if (!m_pszPath)
return E_OUTOFMEMORY;
m_dwVspbFlags = dwVspbFlags;
}
return S_OK;
}
BOOL CViewStatePropertyBag::_IsSamePidl(LPCITEMIDLIST pidlOther) const
{
if (!pidlOther && !m_pidl)
return TRUE;
return (pidlOther && m_pidl && ILIsEqual(pidlOther, m_pidl));
}
BOOL CViewStatePropertyBag::IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const
{
return (dwVspbFlags == m_dwVspbFlags && StrCmpW(pszPath, m_pszPath) == 0 && _IsSamePidl(pidl));
}
BOOL CViewStatePropertyBag::_IsSystemFolder() const
{
LPCITEMIDLIST ppidlLast;
CComPtr<IShellFolder> psf;
HRESULT hr = SHBindToParent(m_pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
if (FAILED(hr))
return FALSE;
WIN32_FIND_DATAW FindData;
hr = SHGetDataFromIDListW(psf, ppidlLast, SHGDFIL_FINDDATA, &FindData, sizeof(FindData));
if (FAILED(hr))
return FALSE;
return PathIsSystemFolderW(NULL, FindData.dwFileAttributes);
}
BOOL CViewStatePropertyBag::_CanAccessPidlBag() const
{
return ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER);
}
BOOL CViewStatePropertyBag::_CanAccessUserDefaultsBag() const
{
if (_CanAccessPidlBag())
return TRUE;
return ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS);
}
BOOL CViewStatePropertyBag::_CanAccessFolderDefaultsBag() const
{
if (_CanAccessUserDefaultsBag())
return TRUE;
return ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER));
}
BOOL CViewStatePropertyBag::_CanAccessGlobalDefaultsBag() const
{
if (_CanAccessFolderDefaultsBag())
return TRUE;
return ((m_dwVspbFlags & SHGVSPB_GLOBALDEFAULTS) == SHGVSPB_GLOBALDEFAULTS);
}
BOOL CViewStatePropertyBag::_CanAccessInheritBag() const
{
return (_CanAccessPidlBag() || (m_dwVspbFlags & SHGVSPB_INHERIT));
}
BOOL CViewStatePropertyBag::_CanAccessUpgradeBag() const
{
return StrCmpW(m_pszPath, L"Desktop") == 0;
}
void CViewStatePropertyBag::_ResetTryAgainFlag()
{
if (m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS)
m_bReadBag = FALSE;
else if ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER)
m_bPidlBag = FALSE;
else if (m_dwVspbFlags & SHGVSPB_INHERIT)
m_bInheritBag = FALSE;
else if ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS)
m_bUserDefaultsBag = FALSE;
else if ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER))
m_bFolderDefaultsBag = FALSE;
else if ((m_dwVspbFlags & SHGVSPB_GLOBALDEFAULTS) == SHGVSPB_GLOBALDEFAULTS)
m_bGlobalDefaultsBag = FALSE;
}
HKEY CViewStatePropertyBag::_GetHKey(DWORD dwVspbFlags)
{
if (!(dwVspbFlags & (SHGVSPB_INHERIT | SHGVSPB_PERUSER)))
return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKLM), NULL, TRUE);
if ((m_dwVspbFlags & SHGVSPB_ROAM) && (dwVspbFlags & SHGVSPB_PERFOLDER))
return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKCU), NULL, TRUE);
return SHGetShellKey(SHKEY_Key_ShellNoRoam | SHKEY_Root_HKCU, NULL, TRUE);
}
UINT CViewStatePropertyBag::_GetMRUSize(HKEY hKey)
{
DWORD dwValue, cbValue = sizeof(dwValue);
if (SHGetValueW(hKey, NULL, L"BagMRU Size", NULL, &dwValue, &cbValue) != ERROR_SUCCESS)
return 400; // The default size of the MRU (most recently used) list
return (UINT)dwValue;
}
HRESULT
CViewStatePropertyBag::_GetMRUSlots(
LPCITEMIDLIST pidl,
DWORD dwMode,
HKEY hKey,
UINT *puSlots,
UINT cSlots,
UINT *pcSlots)
{
CComPtr<IMruPidlList> pMruList;
HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
IID_IMruPidlList, (void**)&pMruList);
if (FAILED(hr))
return hr;
UINT cMRUSize = _GetMRUSize(hKey);
hr = pMruList->InitList(cMRUSize, hKey, L"BagMRU");
if (FAILED(hr))
return hr;
hr = pMruList->QueryPidl(pidl, cSlots, puSlots, pcSlots);
if (hr == S_OK || MODE_CAN_WRITE(dwMode)) // FIXME: HACK! (Without this, a new pidl can never be saved)
hr = pMruList->UsePidl(pidl, puSlots);
else if (cSlots == 1)
hr = E_FAIL;
return hr;
}
HRESULT
CViewStatePropertyBag::_GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot)
{
UINT cSlots;
return _GetMRUSlots(pidl, dwMode, hKey, pSlot, 1, &cSlots);
}
HRESULT
CViewStatePropertyBag::_GetRegKey(
LPCITEMIDLIST pidl,
LPCWSTR pszBagName,
DWORD dwFlags,
DWORD dwMode,
HKEY hKey,
LPWSTR pszDest,
INT cchDest)
{
HRESULT hr = S_OK;
UINT nSlot;
if (dwFlags & (SHGVSPB_INHERIT | SHGVSPB_PERFOLDER))
{
hr = _GetMRUSlot(pidl, dwMode, hKey, &nSlot);
if (SUCCEEDED(hr))
{
if (dwFlags & SHGVSPB_INHERIT)
StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s\\Inherit", nSlot, pszBagName);
else
StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s", nSlot, pszBagName);
}
}
else
{
StringCchPrintfW(pszDest, cchDest, L"Bags\\AllFolders\\%s", pszBagName);
}
return hr;
}
static HRESULT BindCtx_CreateWithMode(DWORD dwMode, IBindCtx **ppbc)
{
HRESULT hr = ::CreateBindCtx(0, ppbc);
if (FAILED(hr))
return hr;
IBindCtx *pbc = *ppbc;
BIND_OPTS opts = { sizeof(opts) };
opts.grfMode = dwMode;
hr = pbc->SetBindOptions(&opts);
if (FAILED(hr))
{
pbc->Release();
*ppbc = NULL;
}
return hr;
}
HRESULT
CViewStatePropertyBag::_CreateBag(
LPITEMIDLIST pidl,
LPCWSTR pszPath,
DWORD dwVspbFlags,
DWORD dwMode,
REFIID riid,
IPropertyBag **pppb)
{
HRESULT hr;
HKEY hKey;
CComPtr<IBindCtx> pBC;
CComPtr<IShellFolder> psf;
WCHAR szBuff[64];
if (MODE_CAN_WRITE(dwMode))
dwMode |= STGM_CREATE;
if ((dwVspbFlags & SHGVSPB_ALLUSERS) && (dwVspbFlags & SHGVSPB_PERFOLDER))
{
hr = BindCtx_CreateWithMode(dwMode, &pBC);
if (SUCCEEDED(hr))
{
hr = SHGetDesktopFolder(&psf);
if (SUCCEEDED(hr))
{
hr = psf->BindToObject(m_pidl, pBC, riid, (void **)pppb);
if (SUCCEEDED(hr) && !*pppb)
hr = E_FAIL;
}
}
}
else
{
hKey = _GetHKey(dwVspbFlags);
if (!hKey)
return E_FAIL;
hr = _GetRegKey(pidl, pszPath, dwVspbFlags, dwMode, hKey, szBuff, _countof(szBuff));
if (SUCCEEDED(hr))
hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, dwMode, riid, (void**)pppb);
::RegCloseKey(hKey);
}
return hr;
}
HRESULT
CViewStatePropertyBag::_FindNearestInheritBag(REFIID riid, IPropertyBag **pppb)
{
*pppb = NULL;
HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
if (!hKey)
return E_FAIL;
UINT cSlots, anSlots[64];
if (FAILED(_GetMRUSlots(m_pidl, 0, hKey, anSlots, _countof(anSlots), &cSlots)) || !cSlots)
{
::RegCloseKey(hKey);
return E_FAIL;
}
HRESULT hr = E_FAIL;
WCHAR szBuff[64];
for (UINT iSlot = 0; iSlot < cSlots; ++iSlot)
{
StringCchPrintfW(szBuff, _countof(szBuff), L"Bags\\%d\\%s\\Inherit", anSlots[iSlot],
m_pszPath);
hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, STGM_READ, riid, (void**)pppb);
if (SUCCEEDED(hr))
break;
}
::RegCloseKey(hKey);
return hr;
}
BOOL CViewStatePropertyBag::_EnsureReadBag(DWORD dwMode, REFIID riid)
{
if (!m_pReadBag && !m_bReadBag)
{
m_bReadBag = TRUE;
_CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pReadBag);
}
return (m_pReadBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsurePidlBag(DWORD dwMode, REFIID riid)
{
if (!m_pPidlBag && !m_bPidlBag && _CanAccessPidlBag())
{
m_bPidlBag = TRUE;
_CreateBag(m_pidl, m_pszPath, SHGVSPB_FOLDER, dwMode, riid, &m_pPidlBag);
}
return (m_pPidlBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsureInheritBag(DWORD dwMode, REFIID riid)
{
if (!m_pInheritBag && !m_bInheritBag && _CanAccessInheritBag())
{
m_bInheritBag = TRUE;
_FindNearestInheritBag(riid, &m_pInheritBag);
}
return (m_pInheritBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsureUpgradeBag(DWORD dwMode, REFIID riid)
{
if (!m_pUpgradeBag && !m_bUpgradeBag && _CanAccessUpgradeBag())
{
m_bUpgradeBag = TRUE;
SHGetDesktopUpgradePropertyBag(riid, (void**)&m_pUpgradeBag);
}
return (m_pUpgradeBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsureUserDefaultsBag(DWORD dwMode, REFIID riid)
{
if (!m_pUserDefaultsBag && !m_bUserDefaultsBag && _CanAccessUserDefaultsBag())
{
m_bUserDefaultsBag = TRUE;
_CreateBag(NULL, m_pszPath, SHGVSPB_USERDEFAULTS, dwMode, riid, &m_pUserDefaultsBag);
}
return (m_pUserDefaultsBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid)
{
if (!m_pFolderDefaultsBag && !m_bFolderDefaultsBag && _CanAccessFolderDefaultsBag())
{
m_bFolderDefaultsBag = TRUE;
if (_IsSystemFolder())
{
_CreateBag(m_pidl, m_pszPath, SHGVSPB_PERFOLDER | SHGVSPB_ALLUSERS,
dwMode, riid, &m_pFolderDefaultsBag);
}
}
return (m_pFolderDefaultsBag != NULL);
}
BOOL CViewStatePropertyBag::_EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid)
{
if (!m_pGlobalDefaultsBag && !m_bGlobalDefaultsBag && _CanAccessGlobalDefaultsBag())
{
m_bGlobalDefaultsBag = TRUE;
_CreateBag(NULL, m_pszPath, SHGVSPB_GLOBALDEFAULTS, dwMode, riid, &m_pGlobalDefaultsBag);
}
return (m_pGlobalDefaultsBag != NULL);
}
HRESULT
CViewStatePropertyBag::_ReadPidlBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsurePidlBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pPidlBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT
CViewStatePropertyBag::_ReadInheritBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsureInheritBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pInheritBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT
CViewStatePropertyBag::_ReadUpgradeBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsureUpgradeBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pUpgradeBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT
CViewStatePropertyBag::_ReadUserDefaultsBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsureUserDefaultsBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pUserDefaultsBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT
CViewStatePropertyBag::_ReadFolderDefaultsBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsureFolderDefaultsBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pFolderDefaultsBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT
CViewStatePropertyBag::_ReadGlobalDefaultsBag(
LPCWSTR pszPropName,
VARIANT *pvari,
IErrorLog *pErrorLog)
{
if (!_EnsureGlobalDefaultsBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pGlobalDefaultsBag->Read(pszPropName, pvari, pErrorLog);
}
STDMETHODIMP
CViewStatePropertyBag::Read(
_In_z_ LPCWSTR pszPropName,
_Inout_ VARIANT *pvari,
_Inout_opt_ IErrorLog *pErrorLog)
{
if ((m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS) || (m_dwVspbFlags & SHGVSPB_INHERIT))
{
if (!_EnsureReadBag(STGM_READ, IID_IPropertyBag))
return E_FAIL;
return m_pReadBag->Read(pszPropName, pvari, pErrorLog);
}
HRESULT hr = _ReadPidlBag(pszPropName, pvari, pErrorLog);
if (SUCCEEDED(hr))
return hr;
hr = _ReadInheritBag(pszPropName, pvari, pErrorLog);
if (SUCCEEDED(hr))
return hr;
hr = _ReadUpgradeBag(pszPropName, pvari, pErrorLog);
if (SUCCEEDED(hr))
return hr;
hr = _ReadUserDefaultsBag(pszPropName, pvari, pErrorLog);
if (SUCCEEDED(hr))
return hr;
hr = _ReadFolderDefaultsBag(pszPropName, pvari, pErrorLog);
if (SUCCEEDED(hr))
return hr;
return _ReadGlobalDefaultsBag(pszPropName, pvari, pErrorLog);
}
void CViewStatePropertyBag::_PruneMRUTree()
{
HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
if (!hKey)
return;
CComPtr<IMruPidlList> pMruList;
HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
IID_IMruPidlList, (void**)&pMruList);
if (SUCCEEDED(hr))
{
hr = pMruList->InitList(200, hKey, L"BagMRU");
if (SUCCEEDED(hr))
pMruList->PruneKids(m_pidl);
}
::RegCloseKey(hKey);
}
BOOL CViewStatePropertyBag::_EnsureWriteBag(DWORD dwMode, REFIID riid)
{
if (!m_pWriteBag && !m_bWriteBag)
{
m_bWriteBag = TRUE;
_CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pWriteBag);
if (m_pWriteBag)
{
_ResetTryAgainFlag();
if (m_dwVspbFlags & SHGVSPB_INHERIT)
_PruneMRUTree();
}
}
return (m_pWriteBag != NULL);
}
STDMETHODIMP CViewStatePropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
{
if (!_EnsureWriteBag(STGM_WRITE, IID_IPropertyBag))
return E_FAIL;
return m_pWriteBag->Write(pszPropName, pvari);
}
static BOOL SHIsRemovableDrive(LPCITEMIDLIST pidl)
{
STRRET strret;
CComPtr<IShellFolder> psf;
WCHAR szBuff[MAX_PATH];
LPCITEMIDLIST ppidlLast;
INT iDrive, nType;
HRESULT hr;
hr = SHBindToParent(pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
if (FAILED(hr))
return FALSE;
hr = psf->GetDisplayNameOf(ppidlLast, SHGDN_FORPARSING, &strret);
if (FAILED(hr))
return FALSE;
hr = StrRetToBufW(&strret, ppidlLast, szBuff, _countof(szBuff));
if (FAILED(hr))
return FALSE;
iDrive = PathGetDriveNumberW(szBuff);
if (iDrive < 0)
return FALSE;
nType = RealDriveType(iDrive, FALSE);
return (nType == DRIVE_REMOVABLE || nType == DRIVE_CDROM);
}
/**************************************************************************
* SHGetViewStatePropertyBag (SHLWAPI.515)
*
* Retrieves a property bag in which the view state information of a folder
* can be stored.
*
* @param pidl PIDL of the folder requested
* @param bag_name Name of the property bag requested
* @param flags Optional SHGVSPB_... flags
* @param riid IID of requested property bag interface
* @param ppv Address to receive pointer to the new interface
* @return An HRESULT value. S_OK on success, non-zero on failure.
* @see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shgetviewstatepropertybag
*/
EXTERN_C HRESULT WINAPI
SHGetViewStatePropertyBag(
_In_opt_ PCIDLIST_ABSOLUTE pidl,
_In_opt_ LPCWSTR bag_name,
_In_ DWORD flags,
_In_ REFIID riid,
_Outptr_ void **ppv)
{
HRESULT hr;
TRACE("%p %s 0x%X %p %p\n", pidl, debugstr_w(bag_name), flags, &riid, ppv);
*ppv = NULL;
::EnterCriticalSection(&g_csBagCacheLock);
if (g_pCachedBag && g_pCachedBag->IsSameBag(pidl, bag_name, flags))
{
hr = g_pCachedBag->QueryInterface(riid, ppv);
::LeaveCriticalSection(&g_csBagCacheLock);
return hr;
}
if (SHIsRemovableDrive(pidl))
{
TRACE("pidl %p is removable\n", pidl);
::LeaveCriticalSection(&g_csBagCacheLock);
return E_FAIL;
}
CComPtr<CViewStatePropertyBag> pBag(new(std::nothrow) CViewStatePropertyBag());
if (!pBag)
{
::LeaveCriticalSection(&g_csBagCacheLock);
return E_OUTOFMEMORY;
}
hr = pBag->Init(pidl, bag_name, flags);
if (FAILED(hr))
{
ERR("0x%08X\n", hr);
::LeaveCriticalSection(&g_csBagCacheLock);
return hr;
}
g_pCachedBag = pBag;
::LeaveCriticalSection(&g_csBagCacheLock);
return pBag->QueryInterface(riid, ppv);
}
EXTERN_C VOID FreeViewStatePropertyBagCache(VOID)
{
::EnterCriticalSection(&g_csBagCacheLock);
g_pCachedBag.Release();
::LeaveCriticalSection(&g_csBagCacheLock);
}
/**************************************************************************
* SHGetPerScreenResName (SHLWAPI.533)
*
* @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/getperscreenresname.htm
*/
EXTERN_C INT WINAPI
SHGetPerScreenResName(
_Out_writes_(cchBuffer) LPWSTR pszBuffer,
_In_ INT cchBuffer,
_In_ DWORD dwReserved)
{
if (dwReserved)
return 0;
HDC hDC = ::GetDC(NULL);
INT cxWidth = ::GetDeviceCaps(hDC, HORZRES);
INT cyHeight = ::GetDeviceCaps(hDC, VERTRES);
INT cMonitors = ::GetSystemMetrics(SM_CMONITORS);
::ReleaseDC(NULL, hDC);
StringCchPrintfW(pszBuffer, cchBuffer, L"%dx%d(%d)", cxWidth, cyHeight, cMonitors);
return lstrlenW(pszBuffer);
}
/**************************************************************************
* IUnknown_QueryServicePropertyBag (SHLWAPI.536)
*
* @param punk An IUnknown interface.
* @param flags The SHGVSPB_... flags of SHGetViewStatePropertyBag.
* @param riid IID of requested property bag interface.
* @param ppvObj Address to receive pointer to the new interface.
* @return An HRESULT value. S_OK on success, non-zero on failure.
* @see https://geoffchappell.com/studies/windows/shell/shlwapi/api/util/iunknown/queryservicepropertybag.htm
*/
EXTERN_C HRESULT WINAPI
IUnknown_QueryServicePropertyBag(
_In_ IUnknown *punk,
_In_ long flags,
_In_ REFIID riid,
_Outptr_ void **ppvObj)
{
TRACE("%p 0x%x %p %p\n", punk, flags, &riid, ppvObj);
CComPtr<IShellBrowserService> pService;
HRESULT hr = IUnknown_QueryService(punk, SID_STopLevelBrowser, IID_IShellBrowserService,
(void **)&pService);
if (FAILED(hr))
{
ERR("0x%X\n", hr);
return hr;
}
return pService->GetPropertyBag(flags, riid, ppvObj);
}