xref: /reactos/dll/win32/shell32/utils.cpp (revision 31ce37da)
1 /*
2  * PROJECT:     shell32
3  * LICENSE:     LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4  * PURPOSE:     Utility functions
5  * COPYRIGHT:   Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 
10 WINE_DEFAULT_DEBUG_CHANNEL(shell);
11 
12 static BOOL
13 OpenEffectiveToken(
14     _In_ DWORD DesiredAccess,
15     _Out_ HANDLE *phToken)
16 {
17     BOOL ret;
18 
19     if (phToken == NULL)
20     {
21         SetLastError(ERROR_INVALID_PARAMETER);
22         return FALSE;
23     }
24 
25     *phToken = NULL;
26 
27     ret = OpenThreadToken(GetCurrentThread(), DesiredAccess, FALSE, phToken);
28     if (!ret && GetLastError() == ERROR_NO_TOKEN)
29         ret = OpenProcessToken(GetCurrentProcess(), DesiredAccess, phToken);
30 
31     return ret;
32 }
33 
34 HRESULT
35 Shell_TranslateIDListAlias(
36     _In_ LPCITEMIDLIST pidl,
37     _In_ HANDLE hToken,
38     _Out_ LPITEMIDLIST *ppidlAlias,
39     _In_ DWORD dwFlags)
40 {
41     return E_FAIL; //FIXME
42 }
43 
44 BOOL BindCtx_ContainsObject(_In_ IBindCtx *pBindCtx, _In_ LPCWSTR pszName)
45 {
46     CComPtr<IUnknown> punk;
47     if (!pBindCtx || FAILED(pBindCtx->GetObjectParam(const_cast<LPWSTR>(pszName), &punk)))
48         return FALSE;
49     return TRUE;
50 }
51 
52 BOOL SHSkipJunctionBinding(_In_ IBindCtx *pbc, _In_ CLSID *pclsid)
53 {
54     if (!pbc)
55         return FALSE;
56 
57     BIND_OPTS BindOps = { sizeof(BindOps) };
58     if (SUCCEEDED(pbc->GetBindOptions(&BindOps)) && BindOps.grfFlags == OLECONTF_LINKS)
59         return TRUE;
60 
61     return pclsid && SHSkipJunction(pbc, pclsid);
62 }
63 
64 HRESULT SHIsFileSysBindCtx(_In_ IBindCtx *pBindCtx, _Out_opt_ WIN32_FIND_DATAW **ppFindData)
65 {
66     CComPtr<IUnknown> punk;
67     CComPtr<IFileSystemBindData> pBindData;
68 
69     if (!pBindCtx || FAILED(pBindCtx->GetObjectParam((LPWSTR)STR_FILE_SYS_BIND_DATA, &punk)))
70         return S_FALSE;
71 
72     if (FAILED(punk->QueryInterface(IID_PPV_ARG(IFileSystemBindData, &pBindData))))
73         return S_FALSE;
74 
75     HRESULT hr = S_OK;
76     if (ppFindData)
77     {
78         *ppFindData = (WIN32_FIND_DATAW*)LocalAlloc(LPTR, sizeof(WIN32_FIND_DATAW));
79         if (*ppFindData)
80             pBindData->GetFindData(*ppFindData);
81         else
82             hr = E_OUTOFMEMORY;
83     }
84 
85     return hr;
86 }
87 
88 BOOL Shell_FailForceReturn(_In_ HRESULT hr)
89 {
90     DWORD code = HRESULT_CODE(hr);
91 
92     switch (code)
93     {
94         case ERROR_BAD_NETPATH:
95         case ERROR_BAD_NET_NAME:
96         case ERROR_CANCELLED:
97             return TRUE;
98 
99         default:
100             return (ERROR_FILE_NOT_FOUND <= code && code <= ERROR_PATH_NOT_FOUND);
101     }
102 }
103 
104 HRESULT
105 SHBindToObjectEx(
106     _In_opt_ IShellFolder *pShellFolder,
107     _In_ LPCITEMIDLIST pidl,
108     _In_opt_ IBindCtx *pBindCtx,
109     _In_ REFIID riid,
110     _Out_ void **ppvObj)
111 {
112     CComPtr<IShellFolder> psfDesktop;
113 
114     *ppvObj = NULL;
115 
116     if (!pShellFolder)
117     {
118         SHGetDesktopFolder(&psfDesktop);
119         if (!psfDesktop)
120             return E_FAIL;
121 
122         pShellFolder = psfDesktop;
123     }
124 
125     HRESULT hr;
126     if (_ILIsDesktop(pidl))
127         hr = pShellFolder->QueryInterface(riid, ppvObj);
128     else
129         hr = pShellFolder->BindToObject(pidl, pBindCtx, riid, ppvObj);
130 
131     if (SUCCEEDED(hr) && !*ppvObj)
132         hr = E_FAIL;
133 
134     return hr;
135 }
136 
137 HRESULT SHBindToObject(
138     _In_opt_ IShellFolder *psf,
139     _In_ LPCITEMIDLIST pidl,
140     _In_ REFIID riid,
141     _Out_ void **ppvObj)
142 {
143     return SHBindToObjectEx(psf, pidl, NULL, riid, ppvObj);
144 }
145 
146 HRESULT
147 Shell_DisplayNameOf(
148     _In_ IShellFolder *psf,
149     _In_ LPCITEMIDLIST pidl,
150     _In_ DWORD dwFlags,
151     _Out_ LPWSTR pszBuf,
152     _In_ UINT cchBuf)
153 {
154     *pszBuf = UNICODE_NULL;
155     STRRET sr;
156     HRESULT hr = psf->GetDisplayNameOf(pidl, dwFlags, &sr);
157     if (FAILED(hr))
158         return hr;
159     return StrRetToBufW(&sr, pidl, pszBuf, cchBuf);
160 }
161 
162 DWORD
163 SHGetAttributes(_In_ IShellFolder *psf, _In_ LPCITEMIDLIST pidl, _In_ DWORD dwAttributes)
164 {
165     LPCITEMIDLIST pidlLast;
166 
167     if (psf)
168     {
169         psf->AddRef();
170         pidlLast = pidl;
171     }
172     else
173     {
174         SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast);
175     }
176 
177     if (!psf)
178         return 0;
179 
180     DWORD oldAttrs = dwAttributes;
181     if (FAILED(psf->GetAttributesOf(1, &pidlLast, &dwAttributes)))
182         dwAttributes = 0;
183     else
184         dwAttributes &= oldAttrs;
185 
186     if ((dwAttributes & SFGAO_FOLDER) &&
187         (dwAttributes & SFGAO_STREAM) &&
188         !(dwAttributes & SFGAO_STORAGEANCESTOR) &&
189         (oldAttrs & SFGAO_STORAGEANCESTOR) &&
190         (SHGetObjectCompatFlags(psf, NULL) & 0x200))
191     {
192         dwAttributes &= ~(SFGAO_STREAM | SFGAO_STORAGEANCESTOR);
193         dwAttributes |= SFGAO_STORAGEANCESTOR;
194     }
195 
196     if (psf)
197         psf->Release();
198 
199     return dwAttributes;
200 }
201 
202 HRESULT SHCoInitializeAnyApartment(VOID)
203 {
204     HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
205     if (FAILED(hr))
206         hr = CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE);
207     return hr;
208 }
209 
210 HRESULT
211 SHGetNameAndFlagsW(
212     _In_ LPCITEMIDLIST pidl,
213     _In_ DWORD dwFlags,
214     _Out_opt_ LPWSTR pszText,
215     _In_ UINT cchBuf,
216     _Inout_opt_ DWORD *pdwAttributes)
217 {
218     if (pszText)
219         *pszText = UNICODE_NULL;
220 
221     HRESULT hrCoInit = SHCoInitializeAnyApartment();
222 
223     CComPtr<IShellFolder> psfFolder;
224     LPCITEMIDLIST ppidlLast;
225     HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &psfFolder), &ppidlLast);
226     if (SUCCEEDED(hr))
227     {
228         if (pszText)
229             hr = Shell_DisplayNameOf(psfFolder, ppidlLast, dwFlags, pszText, cchBuf);
230 
231         if (SUCCEEDED(hr))
232         {
233             if (pdwAttributes)
234                 *pdwAttributes = SHGetAttributes(psfFolder, ppidlLast, *pdwAttributes);
235         }
236     }
237 
238     if (SUCCEEDED(hrCoInit))
239         CoUninitialize();
240 
241     return hr;
242 }
243 
244 EXTERN_C HWND
245 BindCtx_GetUIWindow(_In_ IBindCtx *pBindCtx)
246 {
247     HWND hWnd = NULL;
248 
249     CComPtr<IUnknown> punk;
250     if (pBindCtx && SUCCEEDED(pBindCtx->GetObjectParam((LPWSTR)L"UI During Binding", &punk)))
251         IUnknown_GetWindow(punk, &hWnd);
252 
253     return hWnd;
254 }
255 
256 class CDummyOleWindow : public IOleWindow
257 {
258 protected:
259     LONG m_cRefs;
260     HWND m_hWnd;
261 
262 public:
263     CDummyOleWindow() : m_cRefs(1), m_hWnd(NULL) { }
264     virtual ~CDummyOleWindow() { }
265 
266     // IUnknown methods
267     STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObj) override
268     {
269         static const QITAB c_tab[] =
270         {
271             QITABENT(CDummyOleWindow, IOleWindow),
272             { NULL }
273         };
274         return ::QISearch(this, c_tab, riid, ppvObj);
275     }
276     STDMETHODIMP_(ULONG) AddRef() override
277     {
278         return ++m_cRefs;
279     }
280     STDMETHODIMP_(ULONG) Release() override
281     {
282         if (--m_cRefs == 0)
283         {
284             delete this;
285             return 0;
286         }
287         return m_cRefs;
288     }
289 
290     // IOleWindow methods
291     STDMETHODIMP GetWindow(HWND *phWnd) override
292     {
293         *phWnd = m_hWnd;
294         if (!m_hWnd)
295             return E_NOTIMPL;
296         return S_OK;
297     }
298     STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode) override
299     {
300         return E_NOTIMPL;
301     }
302 };
303 
304 EXTERN_C HRESULT
305 BindCtx_RegisterObjectParam(
306     _In_ IBindCtx *pBindCtx,
307     _In_ LPOLESTR pszKey,
308     _In_opt_ IUnknown *punk,
309     _Out_ LPBC *ppbc)
310 {
311     HRESULT hr = S_OK;
312     CDummyOleWindow *pUnknown = NULL;
313 
314     *ppbc = pBindCtx;
315 
316     if (pBindCtx)
317     {
318         pBindCtx->AddRef();
319     }
320     else
321     {
322         hr = CreateBindCtx(0, ppbc);
323         if (FAILED(hr))
324             return hr;
325     }
326 
327     if (!punk)
328         punk = pUnknown = new CDummyOleWindow();
329 
330     hr = (*ppbc)->RegisterObjectParam(pszKey, punk);
331 
332     if (pUnknown)
333         pUnknown->Release();
334 
335     if (FAILED(hr))
336     {
337         (*ppbc)->Release();
338         *ppbc = NULL;
339     }
340 
341     return hr;
342 }
343 
344 /*************************************************************************
345  *                SHSetFolderPathA (SHELL32.231)
346  *
347  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shsetfolderpatha
348  */
349 EXTERN_C
350 HRESULT WINAPI
351 SHSetFolderPathA(
352     _In_ INT csidl,
353     _In_ HANDLE hToken,
354     _In_ DWORD dwFlags,
355     _In_ LPCSTR pszPath)
356 {
357     TRACE("(%d, %p, 0x%X, %s)\n", csidl, hToken, dwFlags, debugstr_a(pszPath));
358     CStringW strPathW(pszPath);
359     return SHSetFolderPathW(csidl, hToken, dwFlags, strPathW);
360 }
361 
362 /*************************************************************************
363  *                PathIsSlowA (SHELL32.240)
364  *
365  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-pathisslowa
366  */
367 EXTERN_C
368 BOOL WINAPI
369 PathIsSlowA(
370     _In_ LPCSTR pszFile,
371     _In_ DWORD dwAttr)
372 {
373     TRACE("(%s, 0x%X)\n", debugstr_a(pszFile), dwAttr);
374     CStringW strFileW(pszFile);
375     return PathIsSlowW(strFileW, dwAttr);
376 }
377 
378 /*************************************************************************
379  *                ExtractIconResInfoA (SHELL32.221)
380  */
381 EXTERN_C
382 WORD WINAPI
383 ExtractIconResInfoA(
384     _In_ HANDLE hHandle,
385     _In_ LPCSTR lpFileName,
386     _In_ WORD wIndex,
387     _Out_ LPWORD lpSize,
388     _Out_ LPHANDLE lpIcon)
389 {
390     TRACE("(%p, %s, %u, %p, %p)\n", hHandle, debugstr_a(lpFileName), wIndex, lpSize, lpIcon);
391 
392     if (!lpFileName)
393         return 0;
394 
395     CStringW strFileNameW(lpFileName);
396     return ExtractIconResInfoW(hHandle, strFileNameW, wIndex, lpSize, lpIcon);
397 }
398 
399 /*************************************************************************
400  *                ShortSizeFormatW (SHELL32.204)
401  */
402 EXTERN_C
403 LPWSTR WINAPI
404 ShortSizeFormatW(
405     _In_ DWORD dwNumber,
406     _Out_writes_(0x8FFF) LPWSTR pszBuffer)
407 {
408     TRACE("(%lu, %p)\n", dwNumber, pszBuffer);
409     return StrFormatByteSizeW(dwNumber, pszBuffer, 0x8FFF);
410 }
411 
412 /*************************************************************************
413  *                SHOpenEffectiveToken (SHELL32.235)
414  */
415 EXTERN_C BOOL WINAPI SHOpenEffectiveToken(_Out_ LPHANDLE phToken)
416 {
417     TRACE("%p\n", phToken);
418     return OpenEffectiveToken(TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, phToken);
419 }
420 
421 /*************************************************************************
422  *                SHGetUserSessionId (SHELL32.248)
423  */
424 EXTERN_C DWORD WINAPI SHGetUserSessionId(_In_opt_ HANDLE hToken)
425 {
426     DWORD dwSessionId, dwLength;
427     BOOL bOpenToken = FALSE;
428 
429     TRACE("%p\n", hToken);
430 
431     if (!hToken)
432         bOpenToken = SHOpenEffectiveToken(&hToken);
433 
434     if (!hToken ||
435         !GetTokenInformation(hToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId), &dwLength))
436     {
437         dwSessionId = 0;
438     }
439 
440     if (bOpenToken)
441         CloseHandle(hToken);
442 
443     return dwSessionId;
444 }
445 
446 /*************************************************************************
447  *                SHInvokePrivilegedFunctionW (SHELL32.246)
448  */
449 EXTERN_C
450 HRESULT WINAPI
451 SHInvokePrivilegedFunctionW(
452     _In_ LPCWSTR pszName,
453     _In_ PRIVILEGED_FUNCTION fn,
454     _In_opt_ LPARAM lParam)
455 {
456     TRACE("(%s %p %p)\n", debugstr_w(pszName), fn, lParam);
457 
458     if (!pszName || !fn)
459         return E_INVALIDARG;
460 
461     HANDLE hToken = NULL;
462     TOKEN_PRIVILEGES NewPriv, PrevPriv;
463     BOOL bAdjusted = FALSE;
464 
465     if (SHOpenEffectiveToken(&hToken) &&
466         ::LookupPrivilegeValueW(NULL, pszName, &NewPriv.Privileges[0].Luid))
467     {
468         NewPriv.PrivilegeCount = 1;
469         NewPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
470 
471         DWORD dwReturnSize;
472         bAdjusted = ::AdjustTokenPrivileges(hToken, FALSE, &NewPriv,
473                                             sizeof(PrevPriv), &PrevPriv, &dwReturnSize);
474     }
475 
476     HRESULT hr = fn(lParam);
477 
478     if (bAdjusted)
479         ::AdjustTokenPrivileges(hToken, FALSE, &PrevPriv, 0, NULL, NULL);
480 
481     if (hToken)
482         ::CloseHandle(hToken);
483 
484     return hr;
485 }
486 
487 /*************************************************************************
488  *                SHTestTokenPrivilegeW (SHELL32.236)
489  *
490  * @see http://undoc.airesoft.co.uk/shell32.dll/SHTestTokenPrivilegeW.php
491  */
492 EXTERN_C
493 BOOL WINAPI
494 SHTestTokenPrivilegeW(
495     _In_opt_ HANDLE hToken,
496     _In_ LPCWSTR lpName)
497 {
498     LUID Luid;
499     DWORD dwLength;
500     PTOKEN_PRIVILEGES pTokenPriv;
501     HANDLE hNewToken = NULL;
502     BOOL ret = FALSE;
503 
504     TRACE("(%p, %s)\n", hToken, debugstr_w(lpName));
505 
506     if (!lpName)
507         return FALSE;
508 
509     if (!hToken)
510     {
511         if (!SHOpenEffectiveToken(&hNewToken))
512             goto Quit;
513 
514         if (!hNewToken)
515             return FALSE;
516 
517         hToken = hNewToken;
518     }
519 
520     if (!LookupPrivilegeValueW(NULL, lpName, &Luid))
521         return FALSE;
522 
523     dwLength = 0;
524     if (!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwLength))
525         goto Quit;
526 
527     pTokenPriv = (PTOKEN_PRIVILEGES)LocalAlloc(LPTR, dwLength);
528     if (!pTokenPriv)
529         goto Quit;
530 
531     if (GetTokenInformation(hToken, TokenPrivileges, pTokenPriv, dwLength, &dwLength))
532     {
533         UINT iPriv, cPrivs;
534         cPrivs = pTokenPriv->PrivilegeCount;
535         for (iPriv = 0; !ret && iPriv < cPrivs; ++iPriv)
536         {
537             ret = RtlEqualLuid(&Luid, &pTokenPriv->Privileges[iPriv].Luid);
538         }
539     }
540 
541     LocalFree(pTokenPriv);
542 
543 Quit:
544     if (hToken == hNewToken)
545         CloseHandle(hNewToken);
546 
547     return ret;
548 }
549 
550 BOOL IsShutdownAllowed(VOID)
551 {
552     return SHTestTokenPrivilegeW(NULL, SE_SHUTDOWN_NAME);
553 }
554 
555 /*************************************************************************
556  *                IsSuspendAllowed (SHELL32.53)
557  */
558 BOOL WINAPI IsSuspendAllowed(VOID)
559 {
560     TRACE("()\n");
561     return IsShutdownAllowed() && IsPwrSuspendAllowed();
562 }
563 
564 /*************************************************************************
565  *                SHGetShellStyleHInstance (SHELL32.749)
566  */
567 EXTERN_C HINSTANCE
568 WINAPI
569 SHGetShellStyleHInstance(VOID)
570 {
571     HINSTANCE hInst = NULL;
572     WCHAR szPath[MAX_PATH], szColorName[100];
573     HRESULT hr;
574     CStringW strShellStyle;
575 
576     TRACE("SHGetShellStyleHInstance called\n");
577 
578     /* First, attempt to load the shellstyle dll from the current active theme */
579     hr = GetCurrentThemeName(szPath, _countof(szPath), szColorName, _countof(szColorName), NULL, 0);
580     if (FAILED(hr))
581         goto DoDefault;
582 
583     /* Strip the theme filename */
584     PathRemoveFileSpecW(szPath);
585 
586     strShellStyle = szPath;
587     strShellStyle += L"\\Shell\\";
588     strShellStyle += szColorName;
589     strShellStyle += L"\\ShellStyle.dll";
590 
591     hInst = LoadLibraryExW(strShellStyle, NULL, LOAD_LIBRARY_AS_DATAFILE);
592     if (hInst)
593         return hInst;
594 
595     /* Otherwise, use the version stored in the System32 directory */
596 DoDefault:
597     if (!ExpandEnvironmentStringsW(L"%SystemRoot%\\System32\\ShellStyle.dll",
598                                    szPath, _countof(szPath)))
599     {
600         ERR("Expand failed\n");
601         return NULL;
602     }
603     return LoadLibraryExW(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
604 }
605 
606 /*************************************************************************
607  *                SHCreatePropertyBag (SHELL32.715)
608  */
609 EXTERN_C HRESULT
610 WINAPI
611 SHCreatePropertyBag(_In_ REFIID riid, _Out_ void **ppvObj)
612 {
613     return SHCreatePropertyBagOnMemory(STGM_READWRITE, riid, ppvObj);
614 }
615 
616 /*************************************************************************
617  *                SheRemoveQuotesA (SHELL32.@)
618  */
619 EXTERN_C LPSTR
620 WINAPI
621 SheRemoveQuotesA(LPSTR psz)
622 {
623     PCHAR pch;
624 
625     if (*psz == '"')
626     {
627         for (pch = psz + 1; *pch && *pch != '"'; ++pch)
628         {
629             *(pch - 1) = *pch;
630         }
631 
632         if (*pch == '"')
633             *(pch - 1) = ANSI_NULL;
634     }
635 
636     return psz;
637 }
638 
639 /*************************************************************************
640  *                SheRemoveQuotesW (SHELL32.@)
641  *
642  * ExtractAssociatedIconExW uses this function.
643  */
644 EXTERN_C LPWSTR
645 WINAPI
646 SheRemoveQuotesW(LPWSTR psz)
647 {
648     PWCHAR pch;
649 
650     if (*psz == L'"')
651     {
652         for (pch = psz + 1; *pch && *pch != L'"'; ++pch)
653         {
654             *(pch - 1) = *pch;
655         }
656 
657         if (*pch == L'"')
658             *(pch - 1) = UNICODE_NULL;
659     }
660 
661     return psz;
662 }
663 
664 /*************************************************************************
665  *  SHFindComputer [SHELL32.91]
666  *
667  * Invokes the shell search in My Computer. Used in SHFindFiles.
668  * Two parameters are ignored.
669  */
670 EXTERN_C BOOL
671 WINAPI
672 SHFindComputer(LPCITEMIDLIST pidlRoot, LPCITEMIDLIST pidlSavedSearch)
673 {
674     UNREFERENCED_PARAMETER(pidlRoot);
675     UNREFERENCED_PARAMETER(pidlSavedSearch);
676 
677     TRACE("%p %p\n", pidlRoot, pidlSavedSearch);
678 
679     IContextMenu *pCM;
680     HRESULT hr = CoCreateInstance(CLSID_ShellSearchExt, NULL, CLSCTX_INPROC_SERVER,
681                                   IID_IContextMenu, (void **)&pCM);
682     if (FAILED(hr))
683     {
684         ERR("0x%08X\n", hr);
685         return hr;
686     }
687 
688     CMINVOKECOMMANDINFO InvokeInfo = { sizeof(InvokeInfo) };
689     InvokeInfo.lpParameters = "{996E1EB1-B524-11D1-9120-00A0C98BA67D}";
690     InvokeInfo.nShow = SW_SHOWNORMAL;
691     hr = pCM->InvokeCommand(&InvokeInfo);
692     pCM->Release();
693 
694     return SUCCEEDED(hr);
695 }
696 
697 static HRESULT
698 Int64ToStr(
699     _In_ LONGLONG llValue,
700     _Out_writes_(cchValue) LPWSTR pszValue,
701     _In_ UINT cchValue)
702 {
703     WCHAR szBuff[40];
704     UINT ich = 0, ichValue;
705 #if (WINVER >= _WIN32_WINNT_VISTA)
706     BOOL bMinus = (llValue < 0);
707 
708     if (bMinus)
709         llValue = -llValue;
710 #endif
711 
712     if (cchValue <= 0)
713         return E_FAIL;
714 
715     do
716     {
717         szBuff[ich++] = (WCHAR)(L'0' + (llValue % 10));
718         llValue /= 10;
719     } while (llValue != 0 && ich < _countof(szBuff) - 1);
720 
721 #if (WINVER >= _WIN32_WINNT_VISTA)
722     if (bMinus && ich < _countof(szBuff))
723         szBuff[ich++] = '-';
724 #endif
725 
726     for (ichValue = 0; ich > 0 && ichValue < cchValue; ++ichValue)
727     {
728         --ich;
729         pszValue[ichValue] = szBuff[ich];
730     }
731 
732     if (ichValue >= cchValue)
733     {
734         pszValue[cchValue - 1] = UNICODE_NULL;
735         return E_FAIL;
736     }
737 
738     pszValue[ichValue] = UNICODE_NULL;
739     return S_OK;
740 }
741 
742 static VOID
743 Int64GetNumFormat(
744     _Out_ NUMBERFMTW *pDest,
745     _In_opt_ const NUMBERFMTW *pSrc,
746     _In_ DWORD dwNumberFlags,
747     _Out_writes_(cchDecimal) LPWSTR pszDecimal,
748     _In_ INT cchDecimal,
749     _Out_writes_(cchThousand) LPWSTR pszThousand,
750     _In_ INT cchThousand)
751 {
752     WCHAR szBuff[20];
753 
754     if (pSrc)
755         *pDest = *pSrc;
756     else
757         dwNumberFlags = 0;
758 
759     if (!(dwNumberFlags & FMT_USE_NUMDIGITS))
760     {
761         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_IDIGITS, szBuff, _countof(szBuff));
762         pDest->NumDigits = StrToIntW(szBuff);
763     }
764 
765     if (!(dwNumberFlags & FMT_USE_LEADZERO))
766     {
767         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_ILZERO, szBuff, _countof(szBuff));
768         pDest->LeadingZero = StrToIntW(szBuff);
769     }
770 
771     if (!(dwNumberFlags & FMT_USE_GROUPING))
772     {
773         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, szBuff, _countof(szBuff));
774         pDest->Grouping = StrToIntW(szBuff);
775     }
776 
777     if (!(dwNumberFlags & FMT_USE_DECIMAL))
778     {
779         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, pszDecimal, cchDecimal);
780         pDest->lpDecimalSep = pszDecimal;
781     }
782 
783     if (!(dwNumberFlags & FMT_USE_THOUSAND))
784     {
785         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, pszThousand, cchThousand);
786         pDest->lpThousandSep = pszThousand;
787     }
788 
789     if (!(dwNumberFlags & FMT_USE_NEGNUMBER))
790     {
791         GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, szBuff, _countof(szBuff));
792         pDest->NegativeOrder = StrToIntW(szBuff);
793     }
794 }
795 
796 /*************************************************************************
797  *  Int64ToString [SHELL32.209]
798  *
799  * @see http://undoc.airesoft.co.uk/shell32.dll/Int64ToString.php
800  */
801 EXTERN_C
802 INT WINAPI
803 Int64ToString(
804     _In_ LONGLONG llValue,
805     _Out_writes_(cchOut) LPWSTR pszOut,
806     _In_ UINT cchOut,
807     _In_ BOOL bUseFormat,
808     _In_opt_ const NUMBERFMTW *pNumberFormat,
809     _In_ DWORD dwNumberFlags)
810 {
811     INT ret;
812     NUMBERFMTW NumFormat;
813     WCHAR szValue[80], szDecimalSep[6], szThousandSep[6];
814 
815     Int64ToStr(llValue, szValue, _countof(szValue));
816 
817     if (bUseFormat)
818     {
819         Int64GetNumFormat(&NumFormat, pNumberFormat, dwNumberFlags,
820                           szDecimalSep, _countof(szDecimalSep),
821                           szThousandSep, _countof(szThousandSep));
822         ret = GetNumberFormatW(LOCALE_USER_DEFAULT, 0, szValue, &NumFormat, pszOut, cchOut);
823         if (ret)
824             --ret;
825         return ret;
826     }
827 
828     if (FAILED(StringCchCopyW(pszOut, cchOut, szValue)))
829         return 0;
830 
831     return lstrlenW(pszOut);
832 }
833 
834 /*************************************************************************
835  *  LargeIntegerToString [SHELL32.210]
836  *
837  * @see http://undoc.airesoft.co.uk/shell32.dll/LargeIntegerToString.php
838  */
839 EXTERN_C
840 INT WINAPI
841 LargeIntegerToString(
842     _In_ const LARGE_INTEGER *pLargeInt,
843     _Out_writes_(cchOut) LPWSTR pszOut,
844     _In_ UINT cchOut,
845     _In_ BOOL bUseFormat,
846     _In_opt_ const NUMBERFMTW *pNumberFormat,
847     _In_ DWORD dwNumberFlags)
848 {
849     return Int64ToString(pLargeInt->QuadPart, pszOut, cchOut, bUseFormat,
850                          pNumberFormat, dwNumberFlags);
851 }
852 
853 /*************************************************************************
854  *  CopyStreamUI [SHELL32.726]
855  *
856  * Copy a stream to another stream with optional progress display.
857  */
858 EXTERN_C
859 HRESULT WINAPI
860 CopyStreamUI(
861     _In_ IStream *pSrc,
862     _Out_ IStream *pDst,
863     _Inout_opt_ IProgressDialog *pProgress,
864     _In_opt_ DWORDLONG dwlSize)
865 {
866     HRESULT hr = E_FAIL;
867     DWORD cbBuff, cbRead, dwSizeToWrite;
868     DWORDLONG cbDone;
869     LPVOID pBuff;
870     CComHeapPtr<BYTE> pHeapPtr;
871     STATSTG Stat;
872     BYTE abBuff[1024];
873 
874     TRACE("(%p, %p, %p, %I64u)\n", pSrc, pDst, pProgress, dwlSize);
875 
876     if (dwlSize == 0) // Invalid size?
877     {
878         // Get the stream size
879         ZeroMemory(&Stat, sizeof(Stat));
880         if (FAILED(pSrc->Stat(&Stat, STATFLAG_NONAME)))
881             pProgress = NULL; // No size info. Disable progress
882         else
883             dwlSize = Stat.cbSize.QuadPart;
884     }
885 
886     if (!pProgress) // Progress is disabled?
887     {
888         ULARGE_INTEGER uliSize;
889 
890         if (dwlSize > 0)
891             uliSize.QuadPart = dwlSize;
892         else
893             uliSize.HighPart = uliSize.LowPart = INVALID_FILE_SIZE;
894 
895         return pSrc->CopyTo(pDst, uliSize, NULL, NULL); // One punch
896     }
897 
898     // Allocate the buffer if necessary
899     if (dwlSize > 0 && dwlSize <= sizeof(abBuff))
900     {
901         cbBuff = sizeof(abBuff);
902         pBuff = abBuff;
903     }
904     else
905     {
906 #define COPY_STREAM_DEFAULT_BUFFER_SIZE 0x4000
907         cbBuff = COPY_STREAM_DEFAULT_BUFFER_SIZE;
908         if (pHeapPtr.AllocateBytes(cbBuff))
909         {
910             pBuff = pHeapPtr;
911         }
912         else // Low memory?
913         {
914             cbBuff = sizeof(abBuff);
915             pBuff = abBuff;
916         }
917 #undef COPY_STREAM_DEFAULT_BUFFER_SIZE
918     }
919 
920     // Start reading
921     LARGE_INTEGER zero;
922     zero.QuadPart = 0;
923     pSrc->Seek(zero, 0, NULL);
924     pDst->Seek(zero, 0, NULL);
925     cbDone = 0;
926     pProgress->SetProgress64(cbDone, dwlSize);
927 
928     // Repeat reading and writing until goal
929     for (;;)
930     {
931         hr = pSrc->Read(pBuff, cbBuff, &cbRead);
932         if (FAILED(hr))
933             break;
934 
935         // Calculate the size to write
936         if (dwlSize > 0)
937             dwSizeToWrite = (DWORD)min((DWORDLONG)(dwlSize - cbDone), (DWORDLONG)cbRead);
938         else
939             dwSizeToWrite = cbRead;
940 
941         if (dwSizeToWrite == 0) // No need to write?
942         {
943             hr = S_OK;
944             break;
945         }
946 
947         hr = pDst->Write(pBuff, dwSizeToWrite, NULL);
948         if (hr != S_OK)
949             break;
950 
951         cbDone += dwSizeToWrite;
952 
953         if (pProgress->HasUserCancelled()) // Cancelled?
954         {
955             hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
956             break;
957         }
958         pProgress->SetProgress64(cbDone, dwlSize);
959 
960         if (dwlSize > 0 && cbDone >= dwlSize) // Reached the goal?
961         {
962             hr = S_OK;
963             break;
964         }
965     }
966 
967     return hr;
968 }
969 
970 /*************************************************************************
971  *  SHOpenPropSheetA [SHELL32.707]
972  *
973  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj/nf-shlobj-shopenpropsheeta
974  */
975 EXTERN_C
976 BOOL WINAPI
977 SHOpenPropSheetA(
978     _In_opt_ LPCSTR pszCaption,
979     _In_opt_ HKEY *ahKeys,
980     _In_ UINT cKeys,
981     _In_ const CLSID *pclsidDefault,
982     _In_ IDataObject *pDataObject,
983     _In_opt_ IShellBrowser *pShellBrowser,
984     _In_opt_ LPCSTR pszStartPage)
985 {
986     CStringW strStartPageW, strCaptionW;
987     LPCWSTR pszCaptionW = NULL, pszStartPageW = NULL;
988 
989     TRACE("(%s, %p, %u, %p, %p, %p, %s)", debugstr_a(pszCaption), ahKeys, cKeys, pclsidDefault,
990           pDataObject, pShellBrowser, debugstr_a(pszStartPage));
991 
992     if (pszCaption)
993     {
994         strStartPageW = pszCaption;
995         pszCaptionW = strCaptionW;
996     }
997 
998     if (pszStartPage)
999     {
1000         strStartPageW = pszStartPage;
1001         pszStartPageW = strStartPageW;
1002     }
1003 
1004     return SHOpenPropSheetW(pszCaptionW, ahKeys, cKeys, pclsidDefault,
1005                             pDataObject, pShellBrowser, pszStartPageW);
1006 }
1007 
1008 /*************************************************************************
1009  *  Activate_RunDLL [SHELL32.105]
1010  *
1011  * Unlocks the foreground window and allows the shell window to become the
1012  * foreground window. Every parameter is unused.
1013  */
1014 EXTERN_C
1015 BOOL WINAPI
1016 Activate_RunDLL(
1017     _In_ HWND hwnd,
1018     _In_ HINSTANCE hinst,
1019     _In_ LPCWSTR cmdline,
1020     _In_ INT cmdshow)
1021 {
1022     DWORD dwProcessID;
1023 
1024     UNREFERENCED_PARAMETER(hwnd);
1025     UNREFERENCED_PARAMETER(hinst);
1026     UNREFERENCED_PARAMETER(cmdline);
1027     UNREFERENCED_PARAMETER(cmdshow);
1028 
1029     TRACE("(%p, %p, %s, %d)\n", hwnd, hinst, debugstr_w(cmdline), cmdline);
1030 
1031     GetWindowThreadProcessId(GetShellWindow(), &dwProcessID);
1032     return AllowSetForegroundWindow(dwProcessID);
1033 }
1034 
1035 /*************************************************************************
1036  *                SHStartNetConnectionDialogA (SHELL32.12)
1037  *
1038  * @see https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shstartnetconnectiondialoga
1039  */
1040 EXTERN_C
1041 HRESULT WINAPI
1042 SHStartNetConnectionDialogA(
1043     _In_ HWND hwnd,
1044     _In_ LPCSTR pszRemoteName,
1045     _In_ DWORD dwType)
1046 {
1047     LPCWSTR pszRemoteNameW = NULL;
1048     CStringW strRemoteNameW;
1049 
1050     TRACE("(%p, %s, %lu)\n", hwnd, debugstr_a(pszRemoteName), dwType);
1051 
1052     if (pszRemoteName)
1053     {
1054         strRemoteNameW = pszRemoteName;
1055         pszRemoteNameW = strRemoteNameW;
1056     }
1057 
1058     return SHStartNetConnectionDialogW(hwnd, pszRemoteNameW, dwType);
1059 }
1060 
1061 /*************************************************************************
1062  * Helper functions for PathIsEqualOrSubFolder
1063  */
1064 
1065 static INT
1066 DynamicPathCommonPrefixW(
1067     _In_ LPCWSTR lpszPath1,
1068     _In_ LPCWSTR lpszPath2,
1069     _Out_ CStringW& strPath)
1070 {
1071     SIZE_T cchPath1 = wcslen(lpszPath1);
1072     SIZE_T cchPath2 = wcslen(lpszPath2);
1073     LPWSTR lpszPath = strPath.GetBuffer((INT)max(cchPath1, cchPath2) + 16);
1074     INT ret = PathCommonPrefixW(lpszPath1, lpszPath2, lpszPath);
1075     strPath.ReleaseBuffer();
1076     return ret;
1077 }
1078 
1079 EXTERN_C HRESULT WINAPI
1080 SHGetPathCchFromIDListW(LPCITEMIDLIST pidl, LPWSTR pszPath, SIZE_T cchPathMax);
1081 
1082 static HRESULT
1083 DynamicSHGetPathFromIDListW(
1084     _In_ LPCITEMIDLIST pidl,
1085     _Out_ CStringW& strPath)
1086 {
1087     HRESULT hr;
1088 
1089     for (UINT cchPath = MAX_PATH;; cchPath *= 2)
1090     {
1091         LPWSTR lpszPath = strPath.GetBuffer(cchPath);
1092         if (!lpszPath)
1093             return E_OUTOFMEMORY;
1094 
1095         hr = SHGetPathCchFromIDListW(pidl, lpszPath, cchPath);
1096         strPath.ReleaseBuffer();
1097 
1098         if (hr != E_NOT_SUFFICIENT_BUFFER)
1099             break;
1100 
1101         if (cchPath >= MAXUINT / 2)
1102         {
1103             hr = E_FAIL;
1104             break;
1105         }
1106     }
1107 
1108     if (FAILED(hr))
1109         strPath.Empty();
1110 
1111     return hr;
1112 }
1113 
1114 static HRESULT
1115 DynamicSHGetSpecialFolderPathW(
1116     _In_ HWND hwndOwner,
1117     _Out_ CStringW& strPath,
1118     _In_ INT nCSIDL,
1119     _In_ BOOL bCreate)
1120 {
1121     LPITEMIDLIST pidl;
1122     HRESULT hr = SHGetSpecialFolderLocation(hwndOwner, nCSIDL, &pidl);
1123     if (SUCCEEDED(hr))
1124     {
1125         hr = DynamicSHGetPathFromIDListW(pidl, strPath);
1126         CoTaskMemFree(pidl);
1127     }
1128 
1129     if (FAILED(hr))
1130         strPath.Empty();
1131     else if (bCreate)
1132         CreateDirectoryW(strPath, NULL);
1133 
1134     return hr;
1135 }
1136 
1137 static VOID
1138 DynamicPathRemoveBackslashW(
1139     _Out_ CStringW& strPath)
1140 {
1141     INT nLength = strPath.GetLength();
1142     if (nLength > 0 && strPath[nLength - 1] == L'\\')
1143         strPath = strPath.Left(nLength - 1);
1144 }
1145 
1146 /*************************************************************************
1147  *                PathIsEqualOrSubFolder (SHELL32.755)
1148  */
1149 EXTERN_C
1150 BOOL WINAPI
1151 PathIsEqualOrSubFolder(
1152     _In_ LPCWSTR pszPath1OrCSIDL,
1153     _In_ LPCWSTR pszPath2)
1154 {
1155     CStringW strCommon, strPath1;
1156 
1157     TRACE("(%s %s)\n", debugstr_w(pszPath1OrCSIDL), debugstr_w(pszPath2));
1158 
1159     if (IS_INTRESOURCE(pszPath1OrCSIDL))
1160     {
1161         DynamicSHGetSpecialFolderPathW(
1162             NULL, strPath1, LOWORD(pszPath1OrCSIDL) | CSIDL_FLAG_DONT_VERIFY, FALSE);
1163     }
1164     else
1165     {
1166         strPath1 = pszPath1OrCSIDL;
1167     }
1168 
1169     DynamicPathRemoveBackslashW(strPath1);
1170 
1171     if (!DynamicPathCommonPrefixW(strPath1, pszPath2, strCommon))
1172         return FALSE;
1173 
1174     return strPath1.CompareNoCase(strCommon) == 0;
1175 }
1176