1 /*
2 * PROJECT: ReactOS Shell
3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4 * PURPOSE: Implement shell property bags
5 * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6 */
7
8 #define _ATL_NO_EXCEPTIONS
9 #include "precomp.h"
10 #include <shlwapi.h>
11 #include <shlwapi_undoc.h>
12 #include <shlobj_undoc.h>
13 #include <shlguid_undoc.h>
14 #include <atlstr.h> // for CStringW
15 #include <atlsimpcoll.h> // for CSimpleMap
16 #include <atlcomcli.h> // for CComVariant
17 #include <atlconv.h> // for CA2W and CW2A
18 #include <strsafe.h> // for StringC... functions
19
20 WINE_DEFAULT_DEBUG_CHANNEL(shell);
21
22 #define MODE_CAN_READ(dwMode) \
23 (((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE)
24 #define MODE_CAN_WRITE(dwMode) \
25 (((dwMode) & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ)
26
27 class CBasePropertyBag
28 : public IPropertyBag
29 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
30 , public IPropertyBag2
31 #endif
32 {
33 protected:
34 LONG m_cRefs; // reference count
35 DWORD m_dwMode; // STGM_* flags
36
37 public:
CBasePropertyBag(DWORD dwMode)38 CBasePropertyBag(DWORD dwMode)
39 : m_cRefs(0)
40 , m_dwMode(dwMode)
41 {
42 }
43
~CBasePropertyBag()44 virtual ~CBasePropertyBag() { }
45
46 // IUnknown interface
QueryInterface(REFIID riid,void ** ppvObject)47 STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
48 {
49 if (!ppvObject)
50 return E_POINTER;
51
52 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
53 if (::IsEqualGUID(riid, IID_IPropertyBag2))
54 {
55 AddRef();
56 *ppvObject = static_cast<IPropertyBag2*>(this);
57 return S_OK;
58 }
59 #endif
60 if (::IsEqualGUID(riid, IID_IUnknown) || ::IsEqualGUID(riid, IID_IPropertyBag))
61 {
62 AddRef();
63 *ppvObject = static_cast<IPropertyBag*>(this);
64 return S_OK;
65 }
66
67 ERR("%p: %s: E_NOINTERFACE\n", this, debugstr_guid(&riid));
68 return E_NOINTERFACE;
69 }
AddRef()70 STDMETHODIMP_(ULONG) AddRef() override
71 {
72 return ::InterlockedIncrement(&m_cRefs);
73 }
Release()74 STDMETHODIMP_(ULONG) Release() override
75 {
76 if (::InterlockedDecrement(&m_cRefs) == 0)
77 {
78 delete this;
79 return 0;
80 }
81 return m_cRefs;
82 }
83
84 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
85 // IPropertyBag2 interface (stubs)
Read(_In_ ULONG cProperties,_In_ PROPBAG2 * pPropBag,_In_opt_ IErrorLog * pErrorLog,_Out_ VARIANT * pvarValue,_Out_ HRESULT * phrError)86 STDMETHODIMP Read(
87 _In_ ULONG cProperties,
88 _In_ PROPBAG2 *pPropBag,
89 _In_opt_ IErrorLog *pErrorLog,
90 _Out_ VARIANT *pvarValue,
91 _Out_ HRESULT *phrError) override
92 {
93 return E_NOTIMPL;
94 }
Write(_In_ ULONG cProperties,_In_ PROPBAG2 * pPropBag,_In_ VARIANT * pvarValue)95 STDMETHODIMP Write(
96 _In_ ULONG cProperties,
97 _In_ PROPBAG2 *pPropBag,
98 _In_ VARIANT *pvarValue)
99 {
100 return E_NOTIMPL;
101 }
CountProperties(_Out_ ULONG * pcProperties)102 STDMETHODIMP CountProperties(_Out_ ULONG *pcProperties) override
103 {
104 return E_NOTIMPL;
105 }
GetPropertyInfo(_In_ ULONG iProperty,_In_ ULONG cProperties,_Out_ PROPBAG2 * pPropBag,_Out_ ULONG * pcProperties)106 STDMETHODIMP GetPropertyInfo(
107 _In_ ULONG iProperty,
108 _In_ ULONG cProperties,
109 _Out_ PROPBAG2 *pPropBag,
110 _Out_ ULONG *pcProperties) override
111 {
112 return E_NOTIMPL;
113 }
LoadObject(_In_z_ LPCWSTR pstrName,_In_ DWORD dwHint,_In_ IUnknown * pUnkObject,_In_opt_ IErrorLog * pErrorLog)114 STDMETHODIMP LoadObject(
115 _In_z_ LPCWSTR pstrName,
116 _In_ DWORD dwHint,
117 _In_ IUnknown *pUnkObject,
118 _In_opt_ IErrorLog *pErrorLog) override
119 {
120 return E_NOTIMPL;
121 }
122 #endif
123 };
124
125 struct CPropMapEqual
126 {
IsEqualKeyCPropMapEqual127 static bool IsEqualKey(const ATL::CStringW& k1, const ATL::CStringW& k2)
128 {
129 return k1.CompareNoCase(k2) == 0;
130 }
131
IsEqualValueCPropMapEqual132 static bool IsEqualValue(const ATL::CComVariant& v1, const ATL::CComVariant& v2)
133 {
134 return false;
135 }
136 };
137
138 class CMemPropertyBag : public CBasePropertyBag
139 {
140 protected:
141 ATL::CSimpleMap<ATL::CStringW, ATL::CComVariant, CPropMapEqual> m_PropMap;
142
143 public:
CMemPropertyBag(DWORD dwMode)144 CMemPropertyBag(DWORD dwMode) : CBasePropertyBag(dwMode) { }
145
146 STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
147 _Inout_opt_ IErrorLog *pErrorLog) override;
148 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
149 };
150
151 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)152 CMemPropertyBag::Read(
153 _In_z_ LPCWSTR pszPropName,
154 _Inout_ VARIANT *pvari,
155 _Inout_opt_ IErrorLog *pErrorLog)
156 {
157 UNREFERENCED_PARAMETER(pErrorLog);
158
159 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
160
161 VARTYPE vt = V_VT(pvari);
162
163 ::VariantInit(pvari);
164
165 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
166 if (!MODE_CAN_READ(m_dwMode))
167 {
168 ERR("%p: 0x%X\n", this, m_dwMode);
169 return E_ACCESSDENIED;
170 }
171 #endif
172
173 if (!pszPropName || !pvari)
174 {
175 ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
176 return E_INVALIDARG;
177 }
178
179 INT iItem = m_PropMap.FindKey(pszPropName);
180 if (iItem == -1)
181 {
182 ERR("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
183 return E_FAIL;
184 }
185
186 HRESULT hr = ::VariantCopy(pvari, &m_PropMap.GetValueAt(iItem));
187 if (FAILED(hr))
188 {
189 ERR("%p: 0x%08X %p\n", this, hr, pvari);
190 return hr;
191 }
192
193 hr = ::VariantChangeTypeForRead(pvari, vt);
194 if (FAILED(hr))
195 {
196 ERR("%p: 0x%08X %p\n", this, hr, pvari);
197 return hr;
198 }
199
200 return hr;
201 }
202
203 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)204 CMemPropertyBag::Write(
205 _In_z_ LPCWSTR pszPropName,
206 _In_ VARIANT *pvari)
207 {
208 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
209
210 #if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
211 if (!MODE_CAN_WRITE(m_dwMode))
212 {
213 ERR("%p: 0x%X\n", this, m_dwMode);
214 return E_ACCESSDENIED;
215 }
216 #endif
217
218 if (!pszPropName || !pvari)
219 {
220 ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
221 return E_INVALIDARG;
222 }
223
224 ATL::CComVariant vari;
225 HRESULT hr = vari.Copy(pvari);
226 if (FAILED(hr))
227 {
228 ERR("%p: %s %p: 0x%08X\n", this, debugstr_w(pszPropName), pvari, hr);
229 return hr;
230 }
231
232 if (!m_PropMap.SetAt(pszPropName, vari))
233 {
234 ERR("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
235 return E_FAIL;
236 }
237
238 return hr;
239 }
240
241 /**************************************************************************
242 * SHCreatePropertyBagOnMemory (SHLWAPI.477)
243 *
244 * Creates a property bag object on memory.
245 *
246 * @param dwMode Specifies either STGM_READ, STGM_WRITE or STGM_READWRITE. Ignored on Vista+.
247 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
248 * Vista+ rejects IID_IPropertyBag2.
249 * @param ppvObj Receives an IPropertyBag pointer.
250 * @return An HRESULT value. S_OK on success, non-zero on failure.
251 * @see http://undoc.airesoft.co.uk/shlwapi.dll/SHCreatePropertyBagOnMemory.php
252 */
253 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnMemory(_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)254 SHCreatePropertyBagOnMemory(_In_ DWORD dwMode, _In_ REFIID riid, _Out_ void **ppvObj)
255 {
256 TRACE("0x%08X, %s, %p\n", dwMode, debugstr_guid(&riid), ppvObj);
257
258 *ppvObj = NULL;
259
260 CComPtr<CMemPropertyBag> pMemBag(new CMemPropertyBag(dwMode));
261
262 return pMemBag->QueryInterface(riid, ppvObj);
263 }
264
265 class CRegPropertyBag : public CBasePropertyBag
266 {
267 protected:
268 HKEY m_hKey;
269
270 HRESULT _ReadDword(LPCWSTR pszPropName, VARIANT *pvari);
271 HRESULT _ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len);
272 HRESULT _ReadBinary(LPCWSTR pszPropName, VARIANT *pvari, VARTYPE vt, DWORD uBytes);
273 HRESULT _ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit);
274 HRESULT _CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb);
275 HRESULT _GetStreamSize(IStream *pStream, LPDWORD pcbSize);
276 HRESULT _WriteStream(LPCWSTR pszPropName, IStream *pStream);
277
278 public:
CRegPropertyBag(DWORD dwMode)279 CRegPropertyBag(DWORD dwMode)
280 : CBasePropertyBag(dwMode)
281 , m_hKey(NULL)
282 {
283 }
284
~CRegPropertyBag()285 ~CRegPropertyBag() override
286 {
287 if (m_hKey)
288 ::RegCloseKey(m_hKey);
289 }
290
291 HRESULT Init(HKEY hKey, LPCWSTR lpSubKey);
292
293 STDMETHODIMP Read(_In_z_ LPCWSTR pszPropName, _Inout_ VARIANT *pvari,
294 _Inout_opt_ IErrorLog *pErrorLog) override;
295 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
296 };
297
Init(HKEY hKey,LPCWSTR lpSubKey)298 HRESULT CRegPropertyBag::Init(HKEY hKey, LPCWSTR lpSubKey)
299 {
300 REGSAM nAccess = 0;
301 if (MODE_CAN_READ(m_dwMode))
302 nAccess |= KEY_READ;
303 if (MODE_CAN_WRITE(m_dwMode))
304 nAccess |= KEY_WRITE;
305
306 LONG error;
307 if (m_dwMode & STGM_CREATE)
308 error = ::RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, nAccess, NULL, &m_hKey, NULL);
309 else
310 error = ::RegOpenKeyExW(hKey, lpSubKey, 0, nAccess, &m_hKey);
311
312 if (error != ERROR_SUCCESS)
313 {
314 ERR("%p %s 0x%08X\n", hKey, debugstr_w(lpSubKey), error);
315 return HRESULT_FROM_WIN32(error);
316 }
317
318 return S_OK;
319 }
320
_ReadDword(LPCWSTR pszPropName,VARIANT * pvari)321 HRESULT CRegPropertyBag::_ReadDword(LPCWSTR pszPropName, VARIANT *pvari)
322 {
323 DWORD cbData = sizeof(DWORD);
324 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, &V_UI4(pvari), &cbData);
325 if (error)
326 return E_FAIL;
327
328 V_VT(pvari) = VT_UI4;
329 return S_OK;
330 }
331
_ReadString(LPCWSTR pszPropName,VARIANTARG * pvarg,UINT len)332 HRESULT CRegPropertyBag::_ReadString(LPCWSTR pszPropName, VARIANTARG *pvarg, UINT len)
333 {
334 BSTR bstr = ::SysAllocStringByteLen(NULL, len);
335 V_BSTR(pvarg) = bstr;
336 if (!bstr)
337 return E_OUTOFMEMORY;
338
339 V_VT(pvarg) = VT_BSTR;
340 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, NULL, bstr, (LPDWORD)&len);
341 if (error)
342 {
343 ::VariantClear(pvarg);
344 return E_FAIL;
345 }
346
347 return S_OK;
348 }
349
_ReadStream(VARIANT * pvari,BYTE * pInit,UINT cbInit)350 HRESULT CRegPropertyBag::_ReadStream(VARIANT *pvari, BYTE *pInit, UINT cbInit)
351 {
352 IStream *pStream = SHCreateMemStream(pInit, cbInit);
353 V_UNKNOWN(pvari) = pStream;
354 if (!pStream)
355 return E_OUTOFMEMORY;
356 V_VT(pvari) = VT_UNKNOWN;
357 return S_OK;
358 }
359
360 HRESULT
_ReadBinary(LPCWSTR pszPropName,VARIANT * pvari,VARTYPE vt,DWORD uBytes)361 CRegPropertyBag::_ReadBinary(
362 LPCWSTR pszPropName,
363 VARIANT *pvari,
364 VARTYPE vt,
365 DWORD uBytes)
366 {
367 HRESULT hr = E_FAIL;
368 if (vt != VT_UNKNOWN || uBytes < sizeof(GUID))
369 return hr;
370
371 LPBYTE pbData = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, uBytes);
372 if (!pbData)
373 return hr;
374
375 if (!SHGetValueW(m_hKey, NULL, pszPropName, NULL, pbData, &uBytes) &&
376 memcmp(&GUID_NULL, pbData, sizeof(GUID)) == 0)
377 {
378 hr = _ReadStream(pvari, pbData + sizeof(GUID), uBytes - sizeof(GUID));
379 }
380
381 ::LocalFree(pbData);
382
383 return hr;
384 }
385
_CopyStreamIntoBuff(IStream * pStream,void * pv,ULONG cb)386 HRESULT CRegPropertyBag::_CopyStreamIntoBuff(IStream *pStream, void *pv, ULONG cb)
387 {
388 LARGE_INTEGER li;
389 li.QuadPart = 0;
390 HRESULT hr = pStream->Seek(li, 0, NULL);
391 if (FAILED(hr))
392 return hr;
393 return pStream->Read(pv, cb, NULL);
394 }
395
_GetStreamSize(IStream * pStream,LPDWORD pcbSize)396 HRESULT CRegPropertyBag::_GetStreamSize(IStream *pStream, LPDWORD pcbSize)
397 {
398 *pcbSize = 0;
399
400 ULARGE_INTEGER ui;
401 HRESULT hr = IStream_Size(pStream, &ui);
402 if (FAILED(hr))
403 return hr;
404
405 if (ui.DUMMYSTRUCTNAME.HighPart)
406 return E_FAIL; /* 64-bit value is not supported */
407
408 *pcbSize = ui.DUMMYSTRUCTNAME.LowPart;
409 return hr;
410 }
411
412 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)413 CRegPropertyBag::Read(
414 _In_z_ LPCWSTR pszPropName,
415 _Inout_ VARIANT *pvari,
416 _Inout_opt_ IErrorLog *pErrorLog)
417 {
418 UNREFERENCED_PARAMETER(pErrorLog);
419
420 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
421
422 if (!MODE_CAN_READ(m_dwMode))
423 {
424 ERR("%p: 0x%X\n", this, m_dwMode);
425 ::VariantInit(pvari);
426 return E_ACCESSDENIED;
427 }
428
429 VARTYPE vt = V_VT(pvari);
430 VariantInit(pvari);
431
432 HRESULT hr;
433 DWORD dwType, cbValue;
434 LONG error = SHGetValueW(m_hKey, NULL, pszPropName, &dwType, NULL, &cbValue);
435 if (error != ERROR_SUCCESS)
436 hr = E_FAIL;
437 else if (dwType == REG_SZ)
438 hr = _ReadString(pszPropName, pvari, cbValue);
439 else if (dwType == REG_BINARY)
440 hr = _ReadBinary(pszPropName, pvari, vt, cbValue);
441 else if (dwType == REG_DWORD)
442 hr = _ReadDword(pszPropName, pvari);
443 else
444 hr = E_FAIL;
445
446 if (FAILED(hr))
447 {
448 ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
449 ::VariantInit(pvari);
450 return hr;
451 }
452
453 hr = ::VariantChangeTypeForRead(pvari, vt);
454 if (FAILED(hr))
455 {
456 ERR("%p: 0x%08X %ld: %s %p\n", this, hr, dwType, debugstr_w(pszPropName), pvari);
457 ::VariantInit(pvari);
458 }
459
460 return hr;
461 }
462
463 HRESULT
_WriteStream(LPCWSTR pszPropName,IStream * pStream)464 CRegPropertyBag::_WriteStream(LPCWSTR pszPropName, IStream *pStream)
465 {
466 DWORD cbData;
467 HRESULT hr = _GetStreamSize(pStream, &cbData);
468 if (FAILED(hr) || !cbData)
469 return hr;
470
471 DWORD cbBinary = cbData + sizeof(GUID);
472 LPBYTE pbBinary = (LPBYTE)::LocalAlloc(LMEM_ZEROINIT, cbBinary);
473 if (!pbBinary)
474 return E_OUTOFMEMORY;
475
476 hr = _CopyStreamIntoBuff(pStream, pbBinary + sizeof(GUID), cbData);
477 if (SUCCEEDED(hr))
478 {
479 if (SHSetValueW(m_hKey, NULL, pszPropName, REG_BINARY, pbBinary, cbBinary))
480 hr = E_FAIL;
481 }
482
483 ::LocalFree(pbBinary);
484 return hr;
485 }
486
487 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)488 CRegPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
489 {
490 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
491
492 if (!MODE_CAN_WRITE(m_dwMode))
493 {
494 ERR("%p: 0x%X\n", this, m_dwMode);
495 return E_ACCESSDENIED;
496 }
497
498 HRESULT hr;
499 LONG error;
500 VARIANTARG vargTemp = { 0 };
501 switch (V_VT(pvari))
502 {
503 case VT_EMPTY:
504 SHDeleteValueW(m_hKey, NULL, pszPropName);
505 hr = S_OK;
506 break;
507
508 case VT_BOOL:
509 case VT_I1:
510 case VT_I2:
511 case VT_I4:
512 case VT_UI1:
513 case VT_UI2:
514 case VT_UI4:
515 case VT_INT:
516 case VT_UINT:
517 {
518 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_UI4);
519 if (FAILED(hr))
520 return hr;
521
522 error = SHSetValueW(m_hKey, NULL, pszPropName, REG_DWORD, &V_UI4(&vargTemp), sizeof(DWORD));
523 if (error)
524 hr = E_FAIL;
525
526 ::VariantClear(&vargTemp);
527 break;
528 }
529
530 case VT_UNKNOWN:
531 {
532 CComPtr<IStream> pStream;
533 hr = V_UNKNOWN(pvari)->QueryInterface(IID_IStream, (void **)&pStream);
534 if (FAILED(hr))
535 return hr;
536
537 hr = _WriteStream(pszPropName, pStream);
538 break;
539 }
540
541 default:
542 {
543 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
544 if (FAILED(hr))
545 return hr;
546
547 int cch = lstrlenW(V_BSTR(&vargTemp));
548 DWORD cb = (cch + 1) * sizeof(WCHAR);
549 error = SHSetValueW(m_hKey, NULL, pszPropName, REG_SZ, V_BSTR(&vargTemp), cb);
550 if (error)
551 hr = E_FAIL;
552
553 ::VariantClear(&vargTemp);
554 break;
555 }
556 }
557
558 return hr;
559 }
560
561 /**************************************************************************
562 * SHCreatePropertyBagOnRegKey (SHLWAPI.471)
563 *
564 * Creates a property bag object on registry key.
565 *
566 * @param hKey The registry key.
567 * @param pszSubKey The path of the sub-key.
568 * @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
569 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
570 * @param ppvObj Receives an IPropertyBag pointer.
571 * @return An HRESULT value. S_OK on success, non-zero on failure.
572 * @see https://source.winehq.org/WineAPI/SHCreatePropertyBagOnRegKey.html
573 */
574 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnRegKey(_In_ HKEY hKey,_In_z_ LPCWSTR pszSubKey,_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)575 SHCreatePropertyBagOnRegKey(
576 _In_ HKEY hKey,
577 _In_z_ LPCWSTR pszSubKey,
578 _In_ DWORD dwMode,
579 _In_ REFIID riid,
580 _Out_ void **ppvObj)
581 {
582 TRACE("%p, %s, 0x%08X, %s, %p\n", hKey, debugstr_w(pszSubKey), dwMode,
583 debugstr_guid(&riid), ppvObj);
584
585 *ppvObj = NULL;
586
587 CComPtr<CRegPropertyBag> pRegBag(new CRegPropertyBag(dwMode));
588
589 HRESULT hr = pRegBag->Init(hKey, pszSubKey);
590 if (FAILED(hr))
591 return hr;
592
593 return pRegBag->QueryInterface(riid, ppvObj);
594 }
595
596 /**************************************************************************
597 * SHGetIniStringW (SHLWAPI.294)
598 *
599 * @see https://source.winehq.org/WineAPI/SHGetIniStringW.html
600 */
601 EXTERN_C DWORD WINAPI
602 SHGetIniStringW(
603 _In_z_ LPCWSTR appName,
604 _In_z_ LPCWSTR keyName,
605 _Out_writes_to_(outLen, return + 1) LPWSTR out,
606 _In_ DWORD outLen,
607 _In_z_ LPCWSTR filename)
608 {
609 TRACE("(%s,%s,%p,%08x,%s)\n", debugstr_w(appName), debugstr_w(keyName),
610 out, outLen, debugstr_w(filename));
611
612 if (outLen == 0)
613 return 0;
614
615 // Try ".W"-appended section name. See also SHSetIniStringW
616 CStringW szSection(appName);
617 szSection += L".W";
618 CStringW pszWideBuff;
619 const INT cchWideMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
620 GetPrivateProfileStringW(szSection, keyName, NULL,
621 pszWideBuff.GetBuffer(cchWideMax), cchWideMax, filename);
622 pszWideBuff.ReleaseBuffer();
623
624 if (pszWideBuff.IsEmpty()) // It's empty or not found
625 {
626 // Try the normal section name
627 return GetPrivateProfileStringW(appName, keyName, NULL, out, outLen, filename);
628 }
629
630 // Okay, now ".W" version is valid. Its value is a UTF-7 string in UTF-16
631 CW2A wide2utf7(pszWideBuff);
632 MultiByteToWideChar(CP_UTF7, 0, wide2utf7, -1, out, outLen);
633 out[outLen - 1] = UNICODE_NULL;
634
635 return lstrlenW(out);
636 }
637
Is7BitClean(LPCWSTR psz)638 static BOOL Is7BitClean(LPCWSTR psz)
639 {
640 if (!psz)
641 return TRUE;
642
643 while (*psz)
644 {
645 if (*psz > 0x7F)
646 return FALSE;
647 ++psz;
648 }
649 return TRUE;
650 }
651
652 /**************************************************************************
653 * SHSetIniStringW (SHLWAPI.295)
654 *
655 * @see https://source.winehq.org/WineAPI/SHSetIniStringW.html
656 */
657 EXTERN_C BOOL WINAPI
SHSetIniStringW(_In_z_ LPCWSTR appName,_In_z_ LPCWSTR keyName,_In_opt_z_ LPCWSTR str,_In_z_ LPCWSTR filename)658 SHSetIniStringW(
659 _In_z_ LPCWSTR appName,
660 _In_z_ LPCWSTR keyName,
661 _In_opt_z_ LPCWSTR str,
662 _In_z_ LPCWSTR filename)
663 {
664 TRACE("(%s, %p, %s, %s)\n", debugstr_w(appName), keyName, debugstr_w(str),
665 debugstr_w(filename));
666
667 // Write a normal profile string. If str was NULL, then key will be deleted
668 if (!WritePrivateProfileStringW(appName, keyName, str, filename))
669 return FALSE;
670
671 if (Is7BitClean(str))
672 {
673 // Delete ".A" version
674 CStringW szSection(appName);
675 szSection += L".A";
676 WritePrivateProfileStringW(szSection, keyName, NULL, filename);
677
678 // Delete ".W" version
679 szSection = appName;
680 szSection += L".W";
681 WritePrivateProfileStringW(szSection, keyName, NULL, filename);
682
683 return TRUE;
684 }
685
686 // Now str is not 7-bit clean. It needs UTF-7 encoding in UTF-16.
687 // We write ".A" and ".W"-appended sections
688 CW2A wide2utf7(str, CP_UTF7);
689 CA2W utf72wide(wide2utf7, CP_ACP);
690
691 BOOL ret = TRUE;
692
693 // Write ".A" version
694 CStringW szSection(appName);
695 szSection += L".A";
696 if (!WritePrivateProfileStringW(szSection, keyName, str, filename))
697 ret = FALSE;
698
699 // Write ".W" version
700 szSection = appName;
701 szSection += L".W";
702 if (!WritePrivateProfileStringW(szSection, keyName, utf72wide, filename))
703 ret = FALSE;
704
705 return ret;
706 }
707
708 /**************************************************************************
709 * SHGetIniStringUTF7W (SHLWAPI.473)
710 *
711 * Retrieves a string value from an INI file.
712 *
713 * @param lpAppName The section name.
714 * @param lpKeyName The key name.
715 * If this string begins from '@', the value will be interpreted as UTF-7.
716 * @param lpReturnedString Receives a wide string value.
717 * @param nSize The number of characters in lpReturnedString.
718 * @param lpFileName The INI file.
719 * @return The number of characters copied to the buffer if succeeded.
720 */
721 EXTERN_C DWORD WINAPI
722 SHGetIniStringUTF7W(
723 _In_opt_z_ LPCWSTR lpAppName,
724 _In_z_ LPCWSTR lpKeyName,
725 _Out_writes_to_(nSize, return + 1) _Post_z_ LPWSTR lpReturnedString,
726 _In_ DWORD nSize,
727 _In_z_ LPCWSTR lpFileName)
728 {
729 if (*lpKeyName == L'@') // UTF-7
730 return SHGetIniStringW(lpAppName, lpKeyName + 1, lpReturnedString, nSize, lpFileName);
731
732 return GetPrivateProfileStringW(lpAppName, lpKeyName, L"", lpReturnedString, nSize, lpFileName);
733 }
734
735 /**************************************************************************
736 * SHSetIniStringUTF7W (SHLWAPI.474)
737 *
738 * Sets a string value on an INI file.
739 *
740 * @param lpAppName The section name.
741 * @param lpKeyName The key name.
742 * If this begins from '@', the value will be stored as UTF-7.
743 * @param lpString The wide string value to be set.
744 * @param lpFileName The INI file.
745 * @return TRUE if successful. FALSE if failed.
746 */
747 EXTERN_C BOOL WINAPI
SHSetIniStringUTF7W(_In_z_ LPCWSTR lpAppName,_In_z_ LPCWSTR lpKeyName,_In_opt_z_ LPCWSTR lpString,_In_z_ LPCWSTR lpFileName)748 SHSetIniStringUTF7W(
749 _In_z_ LPCWSTR lpAppName,
750 _In_z_ LPCWSTR lpKeyName,
751 _In_opt_z_ LPCWSTR lpString,
752 _In_z_ LPCWSTR lpFileName)
753 {
754 if (*lpKeyName == L'@') // UTF-7
755 return SHSetIniStringW(lpAppName, lpKeyName + 1, lpString, lpFileName);
756
757 return WritePrivateProfileStringW(lpAppName, lpKeyName, lpString, lpFileName);
758 }
759
760 class CIniPropertyBag : public CBasePropertyBag
761 {
762 protected:
763 LPWSTR m_pszFileName;
764 LPWSTR m_pszSection;
765 BOOL m_bAlternateStream; // ADS (Alternate Data Stream)
766
LooksLikeAnAlternateStream(LPCWSTR pszStart)767 static BOOL LooksLikeAnAlternateStream(LPCWSTR pszStart)
768 {
769 LPCWSTR pch = StrRChrW(pszStart, NULL, L'\\');
770 if (!pch)
771 pch = pszStart;
772 return StrChrW(pch, L':') != NULL;
773 }
774
775 HRESULT
776 _GetSectionAndName(
777 LPCWSTR pszStart,
778 LPWSTR pszSection,
779 UINT cchSectionMax,
780 LPWSTR pszName,
781 UINT cchNameMax);
782
783 public:
CIniPropertyBag(DWORD dwMode)784 CIniPropertyBag(DWORD dwMode)
785 : CBasePropertyBag(dwMode)
786 , m_pszFileName(NULL)
787 , m_pszSection(NULL)
788 , m_bAlternateStream(FALSE)
789 {
790 }
791
~CIniPropertyBag()792 ~CIniPropertyBag() override
793 {
794 ::LocalFree(m_pszFileName);
795 ::LocalFree(m_pszSection);
796 }
797
798 HRESULT Init(LPCWSTR pszIniFile, LPCWSTR pszSection);
799
800 STDMETHODIMP Read(
801 _In_z_ LPCWSTR pszPropName,
802 _Inout_ VARIANT *pvari,
803 _Inout_opt_ IErrorLog *pErrorLog) override;
804
805 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
806 };
807
Init(LPCWSTR pszIniFile,LPCWSTR pszSection)808 HRESULT CIniPropertyBag::Init(LPCWSTR pszIniFile, LPCWSTR pszSection)
809 {
810 m_pszFileName = StrDupW(pszIniFile);
811 if (!m_pszFileName)
812 return E_OUTOFMEMORY;
813
814 // Is it an ADS (Alternate Data Stream) pathname?
815 m_bAlternateStream = LooksLikeAnAlternateStream(m_pszFileName);
816
817 if (pszSection)
818 {
819 m_pszSection = StrDupW(pszSection);
820 if (!m_pszSection)
821 return E_OUTOFMEMORY;
822 }
823
824 return S_OK;
825 }
826
827 HRESULT
_GetSectionAndName(LPCWSTR pszStart,LPWSTR pszSection,UINT cchSectionMax,LPWSTR pszName,UINT cchNameMax)828 CIniPropertyBag::_GetSectionAndName(
829 LPCWSTR pszStart,
830 LPWSTR pszSection,
831 UINT cchSectionMax,
832 LPWSTR pszName,
833 UINT cchNameMax)
834 {
835 LPCWSTR pchSep = StrChrW(pszStart, L'\\');
836 if (pchSep)
837 {
838 UINT cchSep = (UINT)(pchSep - pszStart + 1);
839 StrCpyNW(pszSection, pszStart, min(cchSep, cchSectionMax));
840 StrCpyNW(pszName, pchSep + 1, cchNameMax);
841 return S_OK;
842 }
843
844 if (m_pszSection)
845 {
846 StrCpyNW(pszSection, m_pszSection, cchSectionMax);
847 StrCpyNW(pszName, pszStart, cchNameMax);
848 return S_OK;
849 }
850
851 ERR("%p: %s\n", this, debugstr_w(pszStart));
852 return E_INVALIDARG;
853 }
854
855 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)856 CIniPropertyBag::Read(
857 _In_z_ LPCWSTR pszPropName,
858 _Inout_ VARIANT *pvari,
859 _Inout_opt_ IErrorLog *pErrorLog)
860 {
861 UNREFERENCED_PARAMETER(pErrorLog);
862
863 TRACE("%p: %s %p %p\n", this, debugstr_w(pszPropName), pvari, pErrorLog);
864
865 VARTYPE vt = V_VT(pvari);
866
867 ::VariantInit(pvari);
868
869 if (!MODE_CAN_READ(m_dwMode))
870 {
871 ERR("%p: 0x%X\n", this, m_dwMode);
872 return E_ACCESSDENIED;
873 }
874
875 WCHAR szSection[64], szName[64];
876 HRESULT hr =
877 _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
878 if (FAILED(hr))
879 return hr;
880
881 const INT cchBuffMax = 4 * MAX_PATH; // UTF-7 needs 4 times length buffer.
882 CComHeapPtr<WCHAR> pszBuff;
883 if (!pszBuff.Allocate(cchBuffMax * sizeof(WCHAR)))
884 return E_OUTOFMEMORY;
885
886 if (!SHGetIniStringUTF7W(szSection, szName, pszBuff, cchBuffMax, m_pszFileName))
887 return E_FAIL;
888
889 BSTR bstr = ::SysAllocString(pszBuff);
890 V_BSTR(pvari) = bstr;
891 if (!bstr)
892 return E_OUTOFMEMORY;
893
894 V_VT(pvari) = VT_BSTR;
895 return ::VariantChangeTypeForRead(pvari, vt);
896 }
897
898 STDMETHODIMP
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)899 CIniPropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
900 {
901 TRACE("%p: %s %p\n", this, debugstr_w(pszPropName), pvari);
902
903 if (!MODE_CAN_WRITE(m_dwMode))
904 {
905 ERR("%p: 0x%X\n", this, m_dwMode);
906 return E_ACCESSDENIED;
907 }
908
909 HRESULT hr;
910 BSTR bstr;
911 VARIANTARG vargTemp = { 0 };
912 switch (V_VT(pvari))
913 {
914 case VT_EMPTY:
915 bstr = NULL;
916 break;
917
918 case VT_BSTR:
919 bstr = V_BSTR(pvari);
920 break;
921
922 default:
923 hr = ::VariantChangeType(&vargTemp, pvari, 0, VT_BSTR);
924 if (FAILED(hr))
925 goto Quit;
926
927 bstr = V_BSTR(&vargTemp);
928 break;
929 }
930
931 WCHAR szSection[64], szName[64];
932 hr = _GetSectionAndName(pszPropName, szSection, _countof(szSection), szName, _countof(szName));
933 if (SUCCEEDED(hr))
934 {
935 if (SHSetIniStringUTF7W(szSection, szName, bstr, m_pszFileName))
936 {
937 if (!m_bAlternateStream)
938 SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_pszFileName, NULL);
939 }
940 else
941 {
942 hr = E_FAIL;
943 }
944 }
945
946 Quit:
947 ::VariantClear(&vargTemp);
948 return hr;
949 }
950
951 /**************************************************************************
952 * SHCreatePropertyBagOnProfileSection (SHLWAPI.472)
953 *
954 * Creates a property bag object on INI file.
955 *
956 * @param lpFileName The INI filename.
957 * @param pszSection The optional section name.
958 * @param dwMode The combination of STGM_READ, STGM_WRITE, STGM_READWRITE, and STGM_CREATE.
959 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
960 * @param ppvObj Receives an IPropertyBag pointer.
961 * @return An HRESULT value. S_OK on success, non-zero on failure.
962 * @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/createonprofilesection.htm
963 */
964 EXTERN_C HRESULT WINAPI
SHCreatePropertyBagOnProfileSection(_In_z_ LPCWSTR lpFileName,_In_opt_z_ LPCWSTR pszSection,_In_ DWORD dwMode,_In_ REFIID riid,_Out_ void ** ppvObj)965 SHCreatePropertyBagOnProfileSection(
966 _In_z_ LPCWSTR lpFileName,
967 _In_opt_z_ LPCWSTR pszSection,
968 _In_ DWORD dwMode,
969 _In_ REFIID riid,
970 _Out_ void **ppvObj)
971 {
972 HANDLE hFile;
973 PWCHAR pchFileTitle;
974 WCHAR szBuff[MAX_PATH];
975
976 if (dwMode & STGM_CREATE)
977 {
978 hFile = ::CreateFileW(lpFileName, 0, FILE_SHARE_DELETE, 0, CREATE_NEW,
979 FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, NULL);
980 if (hFile != INVALID_HANDLE_VALUE)
981 {
982 pchFileTitle = PathFindFileNameW(lpFileName);
983 if (lstrcmpiW(pchFileTitle, L"desktop.ini") == 0)
984 {
985 StrCpyNW(szBuff, lpFileName, _countof(szBuff));
986 if (PathRemoveFileSpecW(szBuff))
987 PathMakeSystemFolderW(szBuff);
988 }
989 ::CloseHandle(hFile);
990 }
991 }
992
993 *ppvObj = NULL;
994
995 if (!PathFileExistsW(lpFileName))
996 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
997
998 CComPtr<CIniPropertyBag> pIniPB(new CIniPropertyBag(dwMode));
999
1000 HRESULT hr = pIniPB->Init(lpFileName, pszSection);
1001 if (FAILED(hr))
1002 {
1003 ERR("0x%08X\n", hr);
1004 return hr;
1005 }
1006
1007 return pIniPB->QueryInterface(riid, ppvObj);
1008 }
1009
1010 class CDesktopUpgradePropertyBag : public CBasePropertyBag
1011 {
1012 protected:
1013 BOOL _AlreadyUpgraded(HKEY hKey);
1014 VOID _MarkAsUpgraded(HKEY hkey);
1015 HRESULT _ReadFlags(VARIANT *pvari);
1016 HRESULT _ReadItemPositions(VARIANT *pvari);
1017 IStream* _GetOldDesktopViewStream();
1018 IStream* _NewStreamFromOld(IStream *pOldStream);
1019
1020 public:
CDesktopUpgradePropertyBag()1021 CDesktopUpgradePropertyBag() : CBasePropertyBag(STGM_READ) { }
1022
1023 STDMETHODIMP Read(
1024 _In_z_ LPCWSTR pszPropName,
1025 _Inout_ VARIANT *pvari,
1026 _Inout_opt_ IErrorLog *pErrorLog) override;
1027
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)1028 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override
1029 {
1030 ERR("%p: %s: Read-only\n", this, debugstr_w(pszPropName));
1031 return E_NOTIMPL;
1032 }
1033 };
1034
_MarkAsUpgraded(HKEY hkey)1035 VOID CDesktopUpgradePropertyBag::_MarkAsUpgraded(HKEY hkey)
1036 {
1037 DWORD dwValue = TRUE;
1038 SHSetValueW(hkey, NULL, L"Upgrade", REG_DWORD, &dwValue, sizeof(dwValue));
1039 }
1040
_AlreadyUpgraded(HKEY hKey)1041 BOOL CDesktopUpgradePropertyBag::_AlreadyUpgraded(HKEY hKey)
1042 {
1043 // Check the existence of the value written in _MarkAsUpgraded.
1044 DWORD dwValue, cbData = sizeof(dwValue);
1045 return SHGetValueW(hKey, NULL, L"Upgrade", NULL, &dwValue, &cbData) == ERROR_SUCCESS;
1046 }
1047
1048 typedef DWORDLONG DESKVIEW_FLAGS; // 64-bit data
1049
_ReadFlags(VARIANT * pvari)1050 HRESULT CDesktopUpgradePropertyBag::_ReadFlags(VARIANT *pvari)
1051 {
1052 DESKVIEW_FLAGS Flags;
1053 DWORD cbValue = sizeof(Flags);
1054 if (SHGetValueW(HKEY_CURRENT_USER,
1055 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DeskView",
1056 L"Settings",
1057 NULL,
1058 &Flags,
1059 &cbValue) != ERROR_SUCCESS || cbValue < sizeof(Flags))
1060 {
1061 return E_FAIL;
1062 }
1063
1064 V_UINT(pvari) = ((UINT)(Flags >> 32)) | 0x220; // FIXME: Magic number
1065 V_VT(pvari) = VT_UINT;
1066 return S_OK;
1067 }
1068
1069 typedef struct tagOLD_STREAM_HEADER
1070 {
1071 WORD wMagic;
1072 WORD awUnknown[6];
1073 WORD wSize;
1074 } OLD_STREAM_HEADER, *POLD_STREAM_HEADER;
1075
_NewStreamFromOld(IStream * pOldStream)1076 IStream* CDesktopUpgradePropertyBag::_NewStreamFromOld(IStream *pOldStream)
1077 {
1078 OLD_STREAM_HEADER Header;
1079 HRESULT hr = pOldStream->Read(&Header, sizeof(Header), NULL);
1080 if (FAILED(hr) || Header.wMagic != 28)
1081 return NULL;
1082
1083 // Move stream pointer
1084 LARGE_INTEGER li;
1085 li.QuadPart = Header.wSize - sizeof(Header);
1086 hr = pOldStream->Seek(li, STREAM_SEEK_CUR, NULL);
1087 if (FAILED(hr))
1088 return NULL;
1089
1090 // Get the size
1091 ULARGE_INTEGER uli;
1092 hr = IStream_Size(pOldStream, &uli);
1093 if (FAILED(hr))
1094 return NULL;
1095
1096 // Create new stream and attach
1097 CComPtr<IStream> pNewStream;
1098 pNewStream.Attach(SHCreateMemStream(NULL, 0));
1099 if (!pNewStream)
1100 return NULL;
1101
1102 // Subtract Header.wSize from the size
1103 uli.QuadPart -= Header.wSize;
1104
1105 // Copy to pNewStream
1106 hr = pOldStream->CopyTo(pNewStream, uli, NULL, NULL);
1107 if (FAILED(hr))
1108 return NULL;
1109
1110 li.QuadPart = 0;
1111 pNewStream->Seek(li, STREAM_SEEK_SET, NULL);
1112
1113 return pNewStream.Detach();
1114 }
1115
_GetOldDesktopViewStream()1116 IStream* CDesktopUpgradePropertyBag::_GetOldDesktopViewStream()
1117 {
1118 HKEY hKey = SHGetShellKey(SHKEY_Root_HKCU, L"Streams\\Desktop", FALSE);
1119 if (!hKey)
1120 return NULL;
1121
1122 CComPtr<IStream> pOldStream;
1123 if (!_AlreadyUpgraded(hKey))
1124 {
1125 pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView2", 0));
1126 if (pOldStream)
1127 {
1128 ULARGE_INTEGER uli;
1129 HRESULT hr = IStream_Size(pOldStream, &uli);
1130 if (SUCCEEDED(hr) && !uli.QuadPart)
1131 pOldStream.Release();
1132 }
1133
1134 if (!pOldStream)
1135 pOldStream.Attach(SHOpenRegStream2W(hKey, NULL, L"ViewView", 0));
1136
1137 _MarkAsUpgraded(hKey);
1138 }
1139
1140 ::RegCloseKey(hKey);
1141 return pOldStream.Detach();
1142 }
1143
_ReadItemPositions(VARIANT * pvari)1144 HRESULT CDesktopUpgradePropertyBag::_ReadItemPositions(VARIANT *pvari)
1145 {
1146 CComPtr<IStream> pOldStream;
1147 pOldStream.Attach(_GetOldDesktopViewStream());
1148 if (!pOldStream)
1149 return E_FAIL;
1150
1151 HRESULT hr = E_FAIL;
1152 IStream *pNewStream = _NewStreamFromOld(pOldStream);
1153 if (pNewStream)
1154 {
1155 V_UNKNOWN(pvari) = pNewStream;
1156 V_VT(pvari) = VT_UNKNOWN;
1157 hr = S_OK;
1158 }
1159
1160 return hr;
1161 }
1162
1163 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)1164 CDesktopUpgradePropertyBag::Read(
1165 _In_z_ LPCWSTR pszPropName,
1166 _Inout_ VARIANT *pvari,
1167 _Inout_opt_ IErrorLog *pErrorLog)
1168 {
1169 UNREFERENCED_PARAMETER(pErrorLog);
1170
1171 VARTYPE vt = V_VT(pvari);
1172
1173 HRESULT hr = E_FAIL;
1174 if (StrCmpW(L"FFlags", pszPropName) == 0)
1175 hr = _ReadFlags(pvari);
1176 else if (StrCmpNW(L"ItemPos", pszPropName, 7) == 0)
1177 hr = _ReadItemPositions(pvari);
1178
1179 if (FAILED(hr))
1180 {
1181 ::VariantInit(pvari);
1182 return hr;
1183 }
1184
1185 return ::VariantChangeType(pvari, pvari, 0, vt);
1186 }
1187
1188 /**************************************************************************
1189 * SHGetDesktopUpgradePropertyBag (Internal)
1190 *
1191 * Creates or gets a property bag object for desktop upgrade
1192 *
1193 * @param riid Specifies either IID_IUnknown, IID_IPropertyBag or IID_IPropertyBag2.
1194 * @param ppvObj Receives an IPropertyBag pointer.
1195 * @return An HRESULT value. S_OK on success, non-zero on failure.
1196 */
SHGetDesktopUpgradePropertyBag(REFIID riid,void ** ppvObj)1197 HRESULT SHGetDesktopUpgradePropertyBag(REFIID riid, void **ppvObj)
1198 {
1199 *ppvObj = NULL;
1200 CComPtr<CDesktopUpgradePropertyBag> pPropBag(new CDesktopUpgradePropertyBag());
1201 return pPropBag->QueryInterface(riid, ppvObj);
1202 }
1203
1204 class CViewStatePropertyBag : public CBasePropertyBag
1205 {
1206 protected:
1207 LPITEMIDLIST m_pidl = NULL;
1208 LPWSTR m_pszPath = NULL;
1209 DWORD m_dwVspbFlags = 0; // SHGVSPB_... flags
1210 CComPtr<IPropertyBag> m_pPidlBag;
1211 CComPtr<IPropertyBag> m_pUpgradeBag;
1212 CComPtr<IPropertyBag> m_pInheritBag;
1213 CComPtr<IPropertyBag> m_pUserDefaultsBag;
1214 CComPtr<IPropertyBag> m_pFolderDefaultsBag;
1215 CComPtr<IPropertyBag> m_pGlobalDefaultsBag;
1216 CComPtr<IPropertyBag> m_pReadBag;
1217 CComPtr<IPropertyBag> m_pWriteBag;
1218 BOOL m_bPidlBag = FALSE;
1219 BOOL m_bUpgradeBag = FALSE;
1220 BOOL m_bInheritBag = FALSE;
1221 BOOL m_bUserDefaultsBag = FALSE;
1222 BOOL m_bFolderDefaultsBag = FALSE;
1223 BOOL m_bGlobalDefaultsBag = FALSE;
1224 BOOL m_bReadBag = FALSE;
1225 BOOL m_bWriteBag = FALSE;
1226
1227 BOOL _IsSamePidl(LPCITEMIDLIST pidlOther) const;
1228 BOOL _IsSystemFolder() const;
1229 BOOL _CanAccessPidlBag() const;
1230 BOOL _CanAccessUserDefaultsBag() const;
1231 BOOL _CanAccessFolderDefaultsBag() const;
1232 BOOL _CanAccessGlobalDefaultsBag() const;
1233 BOOL _CanAccessInheritBag() const;
1234 BOOL _CanAccessUpgradeBag() const;
1235
1236 HKEY _GetHKey(DWORD dwVspbFlags);
1237
1238 UINT _GetMRUSize(HKEY hKey);
1239
1240 HRESULT _GetMRUSlots(
1241 LPCITEMIDLIST pidl,
1242 DWORD dwMode,
1243 HKEY hKey,
1244 UINT *puSlots,
1245 UINT cSlots,
1246 UINT *pcSlots);
1247
1248 HRESULT _GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot);
1249
1250 HRESULT _GetRegKey(
1251 LPCITEMIDLIST pidl,
1252 LPCWSTR pszBagName,
1253 DWORD dwFlags,
1254 DWORD dwMode,
1255 HKEY hKey,
1256 LPWSTR pszDest,
1257 INT cchDest);
1258
1259 HRESULT _CreateBag(
1260 LPITEMIDLIST pidl,
1261 LPCWSTR pszPath,
1262 DWORD dwVspbFlags,
1263 DWORD dwMode,
1264 REFIID riid,
1265 IPropertyBag **pppb);
1266
1267 HRESULT _FindNearestInheritBag(REFIID riid, IPropertyBag **pppb);
1268
1269 void _ResetTryAgainFlag();
1270
1271 BOOL _EnsureReadBag(DWORD dwMode, REFIID riid);
1272 BOOL _EnsurePidlBag(DWORD dwMode, REFIID riid);
1273 BOOL _EnsureInheritBag(DWORD dwMode, REFIID riid);
1274 BOOL _EnsureUpgradeBag(DWORD dwMode, REFIID riid);
1275 BOOL _EnsureUserDefaultsBag(DWORD dwMode, REFIID riid);
1276 BOOL _EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid);
1277 BOOL _EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid);
1278 BOOL _EnsureWriteBag(DWORD dwMode, REFIID riid);
1279 HRESULT _ReadPidlBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1280 HRESULT _ReadInheritBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1281 HRESULT _ReadUpgradeBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1282 HRESULT _ReadUserDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1283 HRESULT _ReadFolderDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1284 HRESULT _ReadGlobalDefaultsBag(LPCWSTR pszPropName, VARIANT *pvari, IErrorLog *pErrorLog);
1285 void _PruneMRUTree();
1286
1287 public:
CViewStatePropertyBag()1288 CViewStatePropertyBag() : CBasePropertyBag(STGM_READ) { }
1289
~CViewStatePropertyBag()1290 ~CViewStatePropertyBag() override
1291 {
1292 ::ILFree(m_pidl);
1293 ::LocalFree(m_pszPath);
1294 }
1295
1296 HRESULT Init(_In_opt_ LPCITEMIDLIST pidl, _In_opt_ LPCWSTR pszPath, _In_ DWORD dwVspbFlags);
1297 BOOL IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const;
1298
1299 STDMETHODIMP Read(
1300 _In_z_ LPCWSTR pszPropName,
1301 _Inout_ VARIANT *pvari,
1302 _Inout_opt_ IErrorLog *pErrorLog) override;
1303
1304 STDMETHODIMP Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari) override;
1305 };
1306
1307 // CViewStatePropertyBag is cached
1308 CComPtr<CViewStatePropertyBag> g_pCachedBag;
1309 extern "C"
1310 {
1311 CRITICAL_SECTION g_csBagCacheLock;
1312 }
1313
1314 HRESULT
Init(_In_opt_ LPCITEMIDLIST pidl,_In_opt_ LPCWSTR pszPath,_In_ DWORD dwVspbFlags)1315 CViewStatePropertyBag::Init(
1316 _In_opt_ LPCITEMIDLIST pidl,
1317 _In_opt_ LPCWSTR pszPath,
1318 _In_ DWORD dwVspbFlags)
1319 {
1320 if (pidl)
1321 {
1322 m_pidl = ILClone(pidl);
1323 if (!m_pidl)
1324 return E_OUTOFMEMORY;
1325 }
1326
1327 if (pszPath)
1328 {
1329 m_pszPath = StrDupW(pszPath);
1330 if (!m_pszPath)
1331 return E_OUTOFMEMORY;
1332
1333 m_dwVspbFlags = dwVspbFlags;
1334 }
1335
1336 return S_OK;
1337 }
1338
_IsSamePidl(LPCITEMIDLIST pidlOther) const1339 BOOL CViewStatePropertyBag::_IsSamePidl(LPCITEMIDLIST pidlOther) const
1340 {
1341 if (!pidlOther && !m_pidl)
1342 return TRUE;
1343
1344 return (pidlOther && m_pidl && ILIsEqual(pidlOther, m_pidl));
1345 }
1346
IsSameBag(LPCITEMIDLIST pidl,LPCWSTR pszPath,DWORD dwVspbFlags) const1347 BOOL CViewStatePropertyBag::IsSameBag(LPCITEMIDLIST pidl, LPCWSTR pszPath, DWORD dwVspbFlags) const
1348 {
1349 return (dwVspbFlags == m_dwVspbFlags && StrCmpW(pszPath, m_pszPath) == 0 && _IsSamePidl(pidl));
1350 }
1351
_IsSystemFolder() const1352 BOOL CViewStatePropertyBag::_IsSystemFolder() const
1353 {
1354 LPCITEMIDLIST ppidlLast;
1355 CComPtr<IShellFolder> psf;
1356
1357 HRESULT hr = SHBindToParent(m_pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
1358 if (FAILED(hr))
1359 return FALSE;
1360
1361 WIN32_FIND_DATAW FindData;
1362 hr = SHGetDataFromIDListW(psf, ppidlLast, SHGDFIL_FINDDATA, &FindData, sizeof(FindData));
1363 if (FAILED(hr))
1364 return FALSE;
1365
1366 return PathIsSystemFolderW(NULL, FindData.dwFileAttributes);
1367 }
1368
_CanAccessPidlBag() const1369 BOOL CViewStatePropertyBag::_CanAccessPidlBag() const
1370 {
1371 return ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER);
1372 }
1373
_CanAccessUserDefaultsBag() const1374 BOOL CViewStatePropertyBag::_CanAccessUserDefaultsBag() const
1375 {
1376 if (_CanAccessPidlBag())
1377 return TRUE;
1378
1379 return ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS);
1380 }
1381
_CanAccessFolderDefaultsBag() const1382 BOOL CViewStatePropertyBag::_CanAccessFolderDefaultsBag() const
1383 {
1384 if (_CanAccessUserDefaultsBag())
1385 return TRUE;
1386
1387 return ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER));
1388 }
1389
_CanAccessGlobalDefaultsBag() const1390 BOOL CViewStatePropertyBag::_CanAccessGlobalDefaultsBag() const
1391 {
1392 if (_CanAccessFolderDefaultsBag())
1393 return TRUE;
1394
1395 return ((m_dwVspbFlags & SHGVSPB_GLOBALDEAFAULTS) == SHGVSPB_GLOBALDEAFAULTS);
1396 }
1397
_CanAccessInheritBag() const1398 BOOL CViewStatePropertyBag::_CanAccessInheritBag() const
1399 {
1400 return (_CanAccessPidlBag() || (m_dwVspbFlags & SHGVSPB_INHERIT));
1401 }
1402
_CanAccessUpgradeBag() const1403 BOOL CViewStatePropertyBag::_CanAccessUpgradeBag() const
1404 {
1405 return StrCmpW(m_pszPath, L"Desktop") == 0;
1406 }
1407
_ResetTryAgainFlag()1408 void CViewStatePropertyBag::_ResetTryAgainFlag()
1409 {
1410 if (m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS)
1411 m_bReadBag = FALSE;
1412 else if ((m_dwVspbFlags & SHGVSPB_FOLDER) == SHGVSPB_FOLDER)
1413 m_bPidlBag = FALSE;
1414 else if (m_dwVspbFlags & SHGVSPB_INHERIT)
1415 m_bInheritBag = FALSE;
1416 else if ((m_dwVspbFlags & SHGVSPB_USERDEFAULTS) == SHGVSPB_USERDEFAULTS)
1417 m_bUserDefaultsBag = FALSE;
1418 else if ((m_dwVspbFlags & SHGVSPB_ALLUSERS) && (m_dwVspbFlags & SHGVSPB_PERFOLDER))
1419 m_bFolderDefaultsBag = FALSE;
1420 else if ((m_dwVspbFlags & SHGVSPB_GLOBALDEAFAULTS) == SHGVSPB_GLOBALDEAFAULTS)
1421 m_bGlobalDefaultsBag = FALSE;
1422 }
1423
_GetHKey(DWORD dwVspbFlags)1424 HKEY CViewStatePropertyBag::_GetHKey(DWORD dwVspbFlags)
1425 {
1426 if (!(dwVspbFlags & (SHGVSPB_INHERIT | SHGVSPB_PERUSER)))
1427 return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKLM), NULL, TRUE);
1428
1429 if ((m_dwVspbFlags & SHGVSPB_ROAM) && (dwVspbFlags & SHGVSPB_PERFOLDER))
1430 return SHGetShellKey((SHKEY_Key_Shell | SHKEY_Root_HKCU), NULL, TRUE);
1431
1432 return SHGetShellKey(SHKEY_Key_ShellNoRoam | SHKEY_Root_HKCU, NULL, TRUE);
1433 }
1434
_GetMRUSize(HKEY hKey)1435 UINT CViewStatePropertyBag::_GetMRUSize(HKEY hKey)
1436 {
1437 DWORD dwValue, cbValue = sizeof(dwValue);
1438
1439 if (SHGetValueW(hKey, NULL, L"BagMRU Size", NULL, &dwValue, &cbValue) != ERROR_SUCCESS)
1440 return 400; // The default size of the MRU (most recently used) list
1441
1442 return (UINT)dwValue;
1443 }
1444
1445 HRESULT
_GetMRUSlots(LPCITEMIDLIST pidl,DWORD dwMode,HKEY hKey,UINT * puSlots,UINT cSlots,UINT * pcSlots)1446 CViewStatePropertyBag::_GetMRUSlots(
1447 LPCITEMIDLIST pidl,
1448 DWORD dwMode,
1449 HKEY hKey,
1450 UINT *puSlots,
1451 UINT cSlots,
1452 UINT *pcSlots)
1453 {
1454 CComPtr<IMruPidlList> pMruList;
1455 HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
1456 IID_IMruPidlList, (void**)&pMruList);
1457 if (FAILED(hr))
1458 return hr;
1459
1460 UINT cMRUSize = _GetMRUSize(hKey);
1461 hr = pMruList->InitList(cMRUSize, hKey, L"BagMRU");
1462 if (FAILED(hr))
1463 return hr;
1464
1465 hr = pMruList->QueryPidl(pidl, cSlots, puSlots, pcSlots);
1466 if (hr == S_OK || MODE_CAN_WRITE(dwMode)) // FIXME: HACK! (Without this, a new pidl can never be saved)
1467 hr = pMruList->UsePidl(pidl, puSlots);
1468 else if (cSlots == 1)
1469 hr = E_FAIL;
1470
1471 return hr;
1472 }
1473
1474 HRESULT
_GetMRUSlot(LPCITEMIDLIST pidl,DWORD dwMode,HKEY hKey,UINT * pSlot)1475 CViewStatePropertyBag::_GetMRUSlot(LPCITEMIDLIST pidl, DWORD dwMode, HKEY hKey, UINT *pSlot)
1476 {
1477 UINT cSlots;
1478 return _GetMRUSlots(pidl, dwMode, hKey, pSlot, 1, &cSlots);
1479 }
1480
1481 HRESULT
_GetRegKey(LPCITEMIDLIST pidl,LPCWSTR pszBagName,DWORD dwFlags,DWORD dwMode,HKEY hKey,LPWSTR pszDest,INT cchDest)1482 CViewStatePropertyBag::_GetRegKey(
1483 LPCITEMIDLIST pidl,
1484 LPCWSTR pszBagName,
1485 DWORD dwFlags,
1486 DWORD dwMode,
1487 HKEY hKey,
1488 LPWSTR pszDest,
1489 INT cchDest)
1490 {
1491 HRESULT hr = S_OK;
1492 UINT nSlot;
1493
1494 if (dwFlags & (SHGVSPB_INHERIT | SHGVSPB_PERFOLDER))
1495 {
1496 hr = _GetMRUSlot(pidl, dwMode, hKey, &nSlot);
1497 if (SUCCEEDED(hr))
1498 {
1499 if (dwFlags & SHGVSPB_INHERIT)
1500 StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s\\Inherit", nSlot, pszBagName);
1501 else
1502 StringCchPrintfW(pszDest, cchDest, L"Bags\\%d\\%s", nSlot, pszBagName);
1503 }
1504 }
1505 else
1506 {
1507 StringCchPrintfW(pszDest, cchDest, L"Bags\\AllFolders\\%s", pszBagName);
1508 }
1509
1510 return hr;
1511 }
1512
BindCtx_CreateWithMode(DWORD dwMode,IBindCtx ** ppbc)1513 static HRESULT BindCtx_CreateWithMode(DWORD dwMode, IBindCtx **ppbc)
1514 {
1515 HRESULT hr = ::CreateBindCtx(0, ppbc);
1516 if (FAILED(hr))
1517 return hr;
1518
1519 IBindCtx *pbc = *ppbc;
1520
1521 BIND_OPTS opts = { sizeof(opts) };
1522 opts.grfMode = dwMode;
1523 hr = pbc->SetBindOptions(&opts);
1524 if (FAILED(hr))
1525 {
1526 pbc->Release();
1527 *ppbc = NULL;
1528 }
1529
1530 return hr;
1531 }
1532
1533 HRESULT
_CreateBag(LPITEMIDLIST pidl,LPCWSTR pszPath,DWORD dwVspbFlags,DWORD dwMode,REFIID riid,IPropertyBag ** pppb)1534 CViewStatePropertyBag::_CreateBag(
1535 LPITEMIDLIST pidl,
1536 LPCWSTR pszPath,
1537 DWORD dwVspbFlags,
1538 DWORD dwMode,
1539 REFIID riid,
1540 IPropertyBag **pppb)
1541 {
1542 HRESULT hr;
1543 HKEY hKey;
1544 CComPtr<IBindCtx> pBC;
1545 CComPtr<IShellFolder> psf;
1546 WCHAR szBuff[64];
1547
1548 if (MODE_CAN_WRITE(dwMode))
1549 dwMode |= STGM_CREATE;
1550
1551 if ((dwVspbFlags & SHGVSPB_ALLUSERS) && (dwVspbFlags & SHGVSPB_PERFOLDER))
1552 {
1553 hr = BindCtx_CreateWithMode(dwMode, &pBC);
1554 if (SUCCEEDED(hr))
1555 {
1556 hr = SHGetDesktopFolder(&psf);
1557 if (SUCCEEDED(hr))
1558 {
1559 hr = psf->BindToObject(m_pidl, pBC, riid, (void **)pppb);
1560 if (SUCCEEDED(hr) && !*pppb)
1561 hr = E_FAIL;
1562 }
1563 }
1564 }
1565 else
1566 {
1567 hKey = _GetHKey(dwVspbFlags);
1568 if (!hKey)
1569 return E_FAIL;
1570
1571 hr = _GetRegKey(pidl, pszPath, dwVspbFlags, dwMode, hKey, szBuff, _countof(szBuff));
1572 if (SUCCEEDED(hr))
1573 hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, dwMode, riid, (void**)pppb);
1574
1575 ::RegCloseKey(hKey);
1576 }
1577
1578 return hr;
1579 }
1580
1581 HRESULT
_FindNearestInheritBag(REFIID riid,IPropertyBag ** pppb)1582 CViewStatePropertyBag::_FindNearestInheritBag(REFIID riid, IPropertyBag **pppb)
1583 {
1584 *pppb = NULL;
1585
1586 HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
1587 if (!hKey)
1588 return E_FAIL;
1589
1590 UINT cSlots, anSlots[64];
1591 if (FAILED(_GetMRUSlots(m_pidl, 0, hKey, anSlots, _countof(anSlots), &cSlots)) || !cSlots)
1592 {
1593 ::RegCloseKey(hKey);
1594 return E_FAIL;
1595 }
1596
1597 HRESULT hr = E_FAIL;
1598 WCHAR szBuff[64];
1599 for (UINT iSlot = 0; iSlot < cSlots; ++iSlot)
1600 {
1601 StringCchPrintfW(szBuff, _countof(szBuff), L"Bags\\%d\\%s\\Inherit", anSlots[iSlot],
1602 m_pszPath);
1603 hr = SHCreatePropertyBagOnRegKey(hKey, szBuff, STGM_READ, riid, (void**)pppb);
1604 if (SUCCEEDED(hr))
1605 break;
1606 }
1607
1608 ::RegCloseKey(hKey);
1609 return hr;
1610 }
1611
_EnsureReadBag(DWORD dwMode,REFIID riid)1612 BOOL CViewStatePropertyBag::_EnsureReadBag(DWORD dwMode, REFIID riid)
1613 {
1614 if (!m_pReadBag && !m_bReadBag)
1615 {
1616 m_bReadBag = TRUE;
1617 _CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pReadBag);
1618 }
1619 return (m_pReadBag != NULL);
1620 }
1621
_EnsurePidlBag(DWORD dwMode,REFIID riid)1622 BOOL CViewStatePropertyBag::_EnsurePidlBag(DWORD dwMode, REFIID riid)
1623 {
1624 if (!m_pPidlBag && !m_bPidlBag && _CanAccessPidlBag())
1625 {
1626 m_bPidlBag = TRUE;
1627 _CreateBag(m_pidl, m_pszPath, SHGVSPB_FOLDER, dwMode, riid, &m_pPidlBag);
1628 }
1629 return (m_pPidlBag != NULL);
1630 }
1631
_EnsureInheritBag(DWORD dwMode,REFIID riid)1632 BOOL CViewStatePropertyBag::_EnsureInheritBag(DWORD dwMode, REFIID riid)
1633 {
1634 if (!m_pInheritBag && !m_bInheritBag && _CanAccessInheritBag())
1635 {
1636 m_bInheritBag = TRUE;
1637 _FindNearestInheritBag(riid, &m_pInheritBag);
1638 }
1639 return (m_pInheritBag != NULL);
1640 }
1641
_EnsureUpgradeBag(DWORD dwMode,REFIID riid)1642 BOOL CViewStatePropertyBag::_EnsureUpgradeBag(DWORD dwMode, REFIID riid)
1643 {
1644 if (!m_pUpgradeBag && !m_bUpgradeBag && _CanAccessUpgradeBag())
1645 {
1646 m_bUpgradeBag = TRUE;
1647 SHGetDesktopUpgradePropertyBag(riid, (void**)&m_pUpgradeBag);
1648 }
1649 return (m_pUpgradeBag != NULL);
1650 }
1651
_EnsureUserDefaultsBag(DWORD dwMode,REFIID riid)1652 BOOL CViewStatePropertyBag::_EnsureUserDefaultsBag(DWORD dwMode, REFIID riid)
1653 {
1654 if (!m_pUserDefaultsBag && !m_bUserDefaultsBag && _CanAccessUserDefaultsBag())
1655 {
1656 m_bUserDefaultsBag = TRUE;
1657 _CreateBag(NULL, m_pszPath, SHGVSPB_USERDEFAULTS, dwMode, riid, &m_pUserDefaultsBag);
1658 }
1659 return (m_pUserDefaultsBag != NULL);
1660 }
1661
_EnsureFolderDefaultsBag(DWORD dwMode,REFIID riid)1662 BOOL CViewStatePropertyBag::_EnsureFolderDefaultsBag(DWORD dwMode, REFIID riid)
1663 {
1664 if (!m_pFolderDefaultsBag && !m_bFolderDefaultsBag && _CanAccessFolderDefaultsBag())
1665 {
1666 m_bFolderDefaultsBag = TRUE;
1667 if (_IsSystemFolder())
1668 {
1669 _CreateBag(m_pidl, m_pszPath, SHGVSPB_PERFOLDER | SHGVSPB_ALLUSERS,
1670 dwMode, riid, &m_pFolderDefaultsBag);
1671 }
1672 }
1673 return (m_pFolderDefaultsBag != NULL);
1674 }
1675
_EnsureGlobalDefaultsBag(DWORD dwMode,REFIID riid)1676 BOOL CViewStatePropertyBag::_EnsureGlobalDefaultsBag(DWORD dwMode, REFIID riid)
1677 {
1678 if (!m_pGlobalDefaultsBag && !m_bGlobalDefaultsBag && _CanAccessGlobalDefaultsBag())
1679 {
1680 m_bGlobalDefaultsBag = TRUE;
1681 _CreateBag(NULL, m_pszPath, SHGVSPB_GLOBALDEAFAULTS, dwMode, riid, &m_pGlobalDefaultsBag);
1682 }
1683 return (m_pGlobalDefaultsBag != NULL);
1684 }
1685
1686 HRESULT
_ReadPidlBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1687 CViewStatePropertyBag::_ReadPidlBag(
1688 LPCWSTR pszPropName,
1689 VARIANT *pvari,
1690 IErrorLog *pErrorLog)
1691 {
1692 if (!_EnsurePidlBag(STGM_READ, IID_IPropertyBag))
1693 return E_FAIL;
1694
1695 return m_pPidlBag->Read(pszPropName, pvari, pErrorLog);
1696 }
1697
1698 HRESULT
_ReadInheritBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1699 CViewStatePropertyBag::_ReadInheritBag(
1700 LPCWSTR pszPropName,
1701 VARIANT *pvari,
1702 IErrorLog *pErrorLog)
1703 {
1704 if (!_EnsureInheritBag(STGM_READ, IID_IPropertyBag))
1705 return E_FAIL;
1706
1707 return m_pInheritBag->Read(pszPropName, pvari, pErrorLog);
1708 }
1709
1710 HRESULT
_ReadUpgradeBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1711 CViewStatePropertyBag::_ReadUpgradeBag(
1712 LPCWSTR pszPropName,
1713 VARIANT *pvari,
1714 IErrorLog *pErrorLog)
1715 {
1716 if (!_EnsureUpgradeBag(STGM_READ, IID_IPropertyBag))
1717 return E_FAIL;
1718
1719 return m_pUpgradeBag->Read(pszPropName, pvari, pErrorLog);
1720 }
1721
1722 HRESULT
_ReadUserDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1723 CViewStatePropertyBag::_ReadUserDefaultsBag(
1724 LPCWSTR pszPropName,
1725 VARIANT *pvari,
1726 IErrorLog *pErrorLog)
1727 {
1728 if (!_EnsureUserDefaultsBag(STGM_READ, IID_IPropertyBag))
1729 return E_FAIL;
1730
1731 return m_pUserDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1732 }
1733
1734 HRESULT
_ReadFolderDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1735 CViewStatePropertyBag::_ReadFolderDefaultsBag(
1736 LPCWSTR pszPropName,
1737 VARIANT *pvari,
1738 IErrorLog *pErrorLog)
1739 {
1740 if (!_EnsureFolderDefaultsBag(STGM_READ, IID_IPropertyBag))
1741 return E_FAIL;
1742
1743 return m_pFolderDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1744 }
1745
1746 HRESULT
_ReadGlobalDefaultsBag(LPCWSTR pszPropName,VARIANT * pvari,IErrorLog * pErrorLog)1747 CViewStatePropertyBag::_ReadGlobalDefaultsBag(
1748 LPCWSTR pszPropName,
1749 VARIANT *pvari,
1750 IErrorLog *pErrorLog)
1751 {
1752 if (!_EnsureGlobalDefaultsBag(STGM_READ, IID_IPropertyBag))
1753 return E_FAIL;
1754
1755 return m_pGlobalDefaultsBag->Read(pszPropName, pvari, pErrorLog);
1756 }
1757
1758 STDMETHODIMP
Read(_In_z_ LPCWSTR pszPropName,_Inout_ VARIANT * pvari,_Inout_opt_ IErrorLog * pErrorLog)1759 CViewStatePropertyBag::Read(
1760 _In_z_ LPCWSTR pszPropName,
1761 _Inout_ VARIANT *pvari,
1762 _Inout_opt_ IErrorLog *pErrorLog)
1763 {
1764 if ((m_dwVspbFlags & SHGVSPB_NOAUTODEFAULTS) || (m_dwVspbFlags & SHGVSPB_INHERIT))
1765 {
1766 if (!_EnsureReadBag(STGM_READ, IID_IPropertyBag))
1767 return E_FAIL;
1768
1769 return m_pReadBag->Read(pszPropName, pvari, pErrorLog);
1770 }
1771
1772 HRESULT hr = _ReadPidlBag(pszPropName, pvari, pErrorLog);
1773 if (SUCCEEDED(hr))
1774 return hr;
1775
1776 hr = _ReadInheritBag(pszPropName, pvari, pErrorLog);
1777 if (SUCCEEDED(hr))
1778 return hr;
1779
1780 hr = _ReadUpgradeBag(pszPropName, pvari, pErrorLog);
1781 if (SUCCEEDED(hr))
1782 return hr;
1783
1784 hr = _ReadUserDefaultsBag(pszPropName, pvari, pErrorLog);
1785 if (SUCCEEDED(hr))
1786 return hr;
1787
1788 hr = _ReadFolderDefaultsBag(pszPropName, pvari, pErrorLog);
1789 if (SUCCEEDED(hr))
1790 return hr;
1791
1792 return _ReadGlobalDefaultsBag(pszPropName, pvari, pErrorLog);
1793 }
1794
_PruneMRUTree()1795 void CViewStatePropertyBag::_PruneMRUTree()
1796 {
1797 HKEY hKey = _GetHKey(SHGVSPB_INHERIT);
1798 if (!hKey)
1799 return;
1800
1801 CComPtr<IMruPidlList> pMruList;
1802 HRESULT hr = ::CoCreateInstance(CLSID_MruPidlList, NULL, CLSCTX_INPROC_SERVER,
1803 IID_IMruPidlList, (void**)&pMruList);
1804 if (SUCCEEDED(hr))
1805 {
1806 hr = pMruList->InitList(200, hKey, L"BagMRU");
1807 if (SUCCEEDED(hr))
1808 pMruList->PruneKids(m_pidl);
1809 }
1810
1811 ::RegCloseKey(hKey);
1812 }
1813
_EnsureWriteBag(DWORD dwMode,REFIID riid)1814 BOOL CViewStatePropertyBag::_EnsureWriteBag(DWORD dwMode, REFIID riid)
1815 {
1816 if (!m_pWriteBag && !m_bWriteBag)
1817 {
1818 m_bWriteBag = TRUE;
1819 _CreateBag(m_pidl, m_pszPath, m_dwVspbFlags, dwMode, riid, &m_pWriteBag);
1820 if (m_pWriteBag)
1821 {
1822 _ResetTryAgainFlag();
1823 if (m_dwVspbFlags & SHGVSPB_INHERIT)
1824 _PruneMRUTree();
1825 }
1826 }
1827 return (m_pWriteBag != NULL);
1828 }
1829
Write(_In_z_ LPCWSTR pszPropName,_In_ VARIANT * pvari)1830 STDMETHODIMP CViewStatePropertyBag::Write(_In_z_ LPCWSTR pszPropName, _In_ VARIANT *pvari)
1831 {
1832 if (!_EnsureWriteBag(STGM_WRITE, IID_IPropertyBag))
1833 return E_FAIL;
1834
1835 return m_pWriteBag->Write(pszPropName, pvari);
1836 }
1837
SHIsRemovableDrive(LPCITEMIDLIST pidl)1838 static BOOL SHIsRemovableDrive(LPCITEMIDLIST pidl)
1839 {
1840 STRRET strret;
1841 CComPtr<IShellFolder> psf;
1842 WCHAR szBuff[MAX_PATH];
1843 LPCITEMIDLIST ppidlLast;
1844 INT iDrive, nType;
1845 HRESULT hr;
1846
1847 hr = SHBindToParent(pidl, IID_IShellFolder, (void **)&psf, &ppidlLast);
1848 if (FAILED(hr))
1849 return FALSE;
1850
1851 hr = psf->GetDisplayNameOf(ppidlLast, SHGDN_FORPARSING, &strret);
1852 if (FAILED(hr))
1853 return FALSE;
1854
1855 hr = StrRetToBufW(&strret, ppidlLast, szBuff, _countof(szBuff));
1856 if (FAILED(hr))
1857 return FALSE;
1858
1859 iDrive = PathGetDriveNumberW(szBuff);
1860 if (iDrive < 0)
1861 return FALSE;
1862
1863 nType = RealDriveType(iDrive, FALSE);
1864 return (nType == DRIVE_REMOVABLE || nType == DRIVE_CDROM);
1865 }
1866
1867 /**************************************************************************
1868 * SHGetViewStatePropertyBag (SHLWAPI.515)
1869 *
1870 * Retrieves a property bag in which the view state information of a folder
1871 * can be stored.
1872 *
1873 * @param pidl PIDL of the folder requested
1874 * @param bag_name Name of the property bag requested
1875 * @param flags Optional SHGVSPB_... flags
1876 * @param riid IID of requested property bag interface
1877 * @param ppv Address to receive pointer to the new interface
1878 * @return An HRESULT value. S_OK on success, non-zero on failure.
1879 * @see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shgetviewstatepropertybag
1880 */
1881 EXTERN_C HRESULT WINAPI
SHGetViewStatePropertyBag(_In_opt_ PCIDLIST_ABSOLUTE pidl,_In_opt_ LPCWSTR bag_name,_In_ DWORD flags,_In_ REFIID riid,_Outptr_ void ** ppv)1882 SHGetViewStatePropertyBag(
1883 _In_opt_ PCIDLIST_ABSOLUTE pidl,
1884 _In_opt_ LPCWSTR bag_name,
1885 _In_ DWORD flags,
1886 _In_ REFIID riid,
1887 _Outptr_ void **ppv)
1888 {
1889 HRESULT hr;
1890
1891 TRACE("%p %s 0x%X %p %p\n", pidl, debugstr_w(bag_name), flags, &riid, ppv);
1892
1893 *ppv = NULL;
1894
1895 ::EnterCriticalSection(&g_csBagCacheLock);
1896
1897 if (g_pCachedBag && g_pCachedBag->IsSameBag(pidl, bag_name, flags))
1898 {
1899 hr = g_pCachedBag->QueryInterface(riid, ppv);
1900 ::LeaveCriticalSection(&g_csBagCacheLock);
1901 return hr;
1902 }
1903
1904 if (SHIsRemovableDrive(pidl))
1905 {
1906 TRACE("pidl %p is removable\n", pidl);
1907 ::LeaveCriticalSection(&g_csBagCacheLock);
1908 return E_FAIL;
1909 }
1910
1911 CComPtr<CViewStatePropertyBag> pBag(new CViewStatePropertyBag());
1912
1913 hr = pBag->Init(pidl, bag_name, flags);
1914 if (FAILED(hr))
1915 {
1916 ERR("0x%08X\n", hr);
1917 ::LeaveCriticalSection(&g_csBagCacheLock);
1918 return hr;
1919 }
1920 g_pCachedBag = pBag;
1921 ::LeaveCriticalSection(&g_csBagCacheLock);
1922 return pBag->QueryInterface(riid, ppv);
1923 }
1924
FreeViewStatePropertyBagCache(VOID)1925 EXTERN_C VOID FreeViewStatePropertyBagCache(VOID)
1926 {
1927 ::EnterCriticalSection(&g_csBagCacheLock);
1928 g_pCachedBag.Release();
1929 ::LeaveCriticalSection(&g_csBagCacheLock);
1930 }
1931
1932 /**************************************************************************
1933 * SHGetPerScreenResName (SHLWAPI.533)
1934 *
1935 * @see https://www.geoffchappell.com/studies/windows/shell/shlwapi/api/propbag/getperscreenresname.htm
1936 */
1937 EXTERN_C INT WINAPI
SHGetPerScreenResName(_Out_writes_ (cchBuffer)LPWSTR pszBuffer,_In_ INT cchBuffer,_In_ DWORD dwReserved)1938 SHGetPerScreenResName(
1939 _Out_writes_(cchBuffer) LPWSTR pszBuffer,
1940 _In_ INT cchBuffer,
1941 _In_ DWORD dwReserved)
1942 {
1943 if (dwReserved)
1944 return 0;
1945
1946 HDC hDC = ::GetDC(NULL);
1947 INT cxWidth = ::GetDeviceCaps(hDC, HORZRES);
1948 INT cyHeight = ::GetDeviceCaps(hDC, VERTRES);
1949 INT cMonitors = ::GetSystemMetrics(SM_CMONITORS);
1950 ::ReleaseDC(NULL, hDC);
1951
1952 StringCchPrintfW(pszBuffer, cchBuffer, L"%dx%d(%d)", cxWidth, cyHeight, cMonitors);
1953 return lstrlenW(pszBuffer);
1954 }
1955
1956 /**************************************************************************
1957 * IUnknown_QueryServicePropertyBag (SHLWAPI.536)
1958 *
1959 * @param punk An IUnknown interface.
1960 * @param flags The SHGVSPB_... flags of SHGetViewStatePropertyBag.
1961 * @param riid IID of requested property bag interface.
1962 * @param ppvObj Address to receive pointer to the new interface.
1963 * @return An HRESULT value. S_OK on success, non-zero on failure.
1964 * @see https://geoffchappell.com/studies/windows/shell/shlwapi/api/util/iunknown/queryservicepropertybag.htm
1965 */
1966 EXTERN_C HRESULT WINAPI
IUnknown_QueryServicePropertyBag(_In_ IUnknown * punk,_In_ long flags,_In_ REFIID riid,_Outptr_ void ** ppvObj)1967 IUnknown_QueryServicePropertyBag(
1968 _In_ IUnknown *punk,
1969 _In_ long flags,
1970 _In_ REFIID riid,
1971 _Outptr_ void **ppvObj)
1972 {
1973 TRACE("%p 0x%x %p %p\n", punk, flags, &riid, ppvObj);
1974
1975 CComPtr<IShellBrowserService> pService;
1976 HRESULT hr = IUnknown_QueryService(punk, SID_STopLevelBrowser, IID_IShellBrowserService,
1977 (void **)&pService);
1978 if (FAILED(hr))
1979 {
1980 ERR("0x%X\n", hr);
1981 return hr;
1982 }
1983
1984 return pService->GetPropertyBag(flags, riid, ppvObj);
1985 }
1986