Repository: reactos/reactos
Pull request #7880: [SHELL32] Implement IShellFolder2::GetDetailsEx
Author: login: whindsaks
Labels: name: enhancement
description: For PRs with an enhancement/new feature.
name: shell
description: All PR's related to the shell (and shell extensions)
Created: 2025-04-09T22:43:59Z | Merged: 2025-04-21T21:21:44Z

Notes:
 - Handling `PKEY_DescriptionID`/`SHGDFIL_DESCRIPTIONID` fixes the Desktop icons page in Tweak UI v2.
 - Lays the foundation for fixing the official way to [detect the Recycle Bin](https://devblogs.microsoft.com/oldnewthing/20080918-00/?p=20843).
 - Pre-emptive comment about the "switch case return" on one line style; keep in mind that Windows XP supports more than 50 columns here, modern Windows even more!
 - The Init/FromBuffer functions are modeled after functions with the same names in propsys.dll but we can't use that directly here since it's Vista+. Once propsys is working properly, shell32 can switch to those functions when targeting NT6+.


## Testbot runs (Filled in by Devs)

- [ ] KVM x86:
- [ ] KVM x64:


Diff:
diff --git a/dll/win32/shell32/folders/CControlPanelFolder.cpp b/dll/win32/shell32/folders/CControlPanelFolder.cpp
index f66018e9fd062..5943bf2b1fdb7 100644
--- a/dll/win32/shell32/folders/CControlPanelFolder.cpp
+++ b/dll/win32/shell32/folders/CControlPanelFolder.cpp
@@ -84,6 +84,14 @@ HRESULT WINAPI CControlPanelEnum::Initialize(DWORD dwFlags, IEnumIDList* pRegEnu
     return S_OK;
 }
 
+static const CLSID* IsRegItem(LPCITEMIDLIST pidl)
+{
+    BYTE type = _ILGetType(pidl);
+    if (type == PT_CONTROLS_OLDREGITEM || type == PT_CONTROLS_NEWREGITEM)
+        return (CLSID*)((BYTE*)pidl + (pidl->mkid.cb - sizeof(CLSID)));
+    return NULL;
+}
+
 static LPITEMIDLIST _ILCreateCPanelApplet(LPCWSTR pszName, LPCWSTR pszDisplayName, LPCWSTR pszComment, int iIconIdx)
 {
     PIDLCPanelStruct *pCP;
@@ -565,8 +573,9 @@ HRESULT WINAPI CControlPanelFolder::GetDefaultColumnState(UINT iColumn, DWORD *p
 
 HRESULT WINAPI CControlPanelFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME("(%p)\n", this);
-    return E_NOTIMPL;
+    if (IsRegItem(pidl))
+        return m_regFolder->GetDetailsEx(pidl, pscid, pv);
+    return SH32_GetDetailsOfPKeyAsVariant(this, pidl, pscid, pv, FALSE);
 }
 
 HRESULT WINAPI CControlPanelFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
@@ -605,8 +614,12 @@ HRESULT WINAPI CControlPanelFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iCol
 
 HRESULT WINAPI CControlPanelFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
 {
-    FIXME("(%p)\n", this);
-    return E_NOTIMPL;
+    switch (column)
+    {
+        case CONTROLPANEL_COL_NAME: return MakeSCID(*pscid, FMTID_Storage, PID_STG_NAME);
+        case CONTROLPANEL_COL_COMMENT: return MakeSCID(*pscid, FMTID_SummaryInformation, PIDSI_COMMENTS);
+    }
+    return E_INVALIDARG;
 }
 
 /************************************************************************
diff --git a/dll/win32/shell32/folders/CDesktopFolder.cpp b/dll/win32/shell32/folders/CDesktopFolder.cpp
index 370a614285e3e..5749c04c7554f 100644
--- a/dll/win32/shell32/folders/CDesktopFolder.cpp
+++ b/dll/win32/shell32/folders/CDesktopFolder.cpp
@@ -955,14 +955,14 @@ HRESULT WINAPI CDesktopFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *
     return hr;
 }
 
-HRESULT WINAPI CDesktopFolder::GetDetailsEx(
-    PCUITEMID_CHILD pidl,
-    const SHCOLUMNID *pscid,
-    VARIANT *pv)
+HRESULT WINAPI CDesktopFolder::GetDetailsEx(PCUITEMID_CHILD pidl,
+                                            const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME ("(%p)\n", this);
-
-    return E_NOTIMPL;
+    HRESULT hr;
+    CComPtr<IShellFolder2> psf;
+    if (FAILED_UNEXPECTEDLY(hr = _GetSFFromPidl(pidl, &psf)))
+        return hr;
+    return psf->GetDetailsEx(pidl, pscid, pv);
 }
 
 /*************************************************************************
@@ -1002,8 +1002,14 @@ HRESULT WINAPI CDesktopFolder::GetDetailsOf(
 
 HRESULT WINAPI CDesktopFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
 {
-    FIXME ("(%p)\n", this);
-    return E_NOTIMPL;
+    // Note: All these folders use the same SHFSF_COL mapping (m_regFolder only handles a subset).
+    if (m_DesktopFSFolder)
+        return m_DesktopFSFolder->MapColumnToSCID(column, pscid);
+    if (m_SharedDesktopFSFolder)
+        return m_SharedDesktopFSFolder->MapColumnToSCID(column, pscid);
+    if (m_regFolder)
+        return m_regFolder->MapColumnToSCID(column, pscid);
+    return E_FAIL;
 }
 
 HRESULT WINAPI CDesktopFolder::GetClassID(CLSID *lpClassId)
diff --git a/dll/win32/shell32/folders/CDrivesFolder.cpp b/dll/win32/shell32/folders/CDrivesFolder.cpp
index 5fcf9d94a7e6a..4e79cde60a88e 100644
--- a/dll/win32/shell32/folders/CDrivesFolder.cpp
+++ b/dll/win32/shell32/folders/CDrivesFolder.cpp
@@ -26,6 +26,9 @@
 
 WINE_DEFAULT_DEBUG_CHANNEL(shell);
 
+extern BOOL IsDriveFloppyA(LPCSTR pszDriveRoot);
+extern BOOL IsDriveFloppyW(LPCWSTR pszDriveRoot);
+
 /*
 CDrivesFolder should create a CRegFolder to represent the virtual items that exist only in
 the registry. The CRegFolder is aggregated by the CDrivesFolder.
@@ -105,6 +108,13 @@ template<class T> static T* GetDrivePath(PCUITEMID_CHILD pidl, T *Path)
     return Path;
 }
 
+static inline UINT _ILGetRemovableTypeId(LPCITEMIDLIST pidl)
+{
+    WCHAR buf[8];
+    if (GetDrivePath(pidl, buf) && IsDriveFloppyW(buf))
+        return SHDID_COMPUTER_DRIVE35; // TODO: 3.5-inch vs 5.25-inch
+    return SHDID_COMPUTER_REMOVABLE;
+}
 
 BOOL _ILGetDriveType(LPCITEMIDLIST pidl)
 {
@@ -477,8 +487,6 @@ static HRESULT GetDriveLabel(PCWSTR DrivePath, LPWSTR szLabel, UINT cchMax)
     return hr == S_OK ? S_OK : GetRawDriveLabel(DrivePath, szLabel, cchMax);
 }
 
-BOOL IsDriveFloppyA(LPCSTR pszDriveRoot);
-
 HRESULT CDrivesExtractIcon_CreateInstance(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID * ppvOut)
 {
     CComPtr<IDefaultExtractIconInit> initIcon;
@@ -1151,10 +1159,33 @@ HRESULT WINAPI CDrivesFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *
     return S_OK;
 }
 
-HRESULT WINAPI CDrivesFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID * pscid, VARIANT * pv)
+HRESULT WINAPI CDrivesFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME("(%p)\n", this);
-    return E_NOTIMPL;
+    const CLSID *pCLSID = IsRegItem(pidl);
+    if (pscid->fmtid == FMTID_ShellDetails)
+    {
+        switch (pscid->pid)
+        {
+            case PID_DESCRIPTIONID:
+            {
+                if (pCLSID)
+                    return SHELL_CreateSHDESCRIPTIONID(pv, SHDID_ROOT_REGITEM, pCLSID);
+                UINT id = SHDID_COMPUTER_OTHER;
+                switch (_ILGetDriveType(pidl))
+                {
+                    case DRIVE_REMOVABLE: id = _ILGetRemovableTypeId(pidl); break;
+                    case DRIVE_FIXED:     id = SHDID_COMPUTER_FIXED; break;
+                    case DRIVE_REMOTE:    id = SHDID_COMPUTER_NETDRIVE; break;
+                    case DRIVE_CDROM:     id = SHDID_COMPUTER_CDROM; break;
+                    case DRIVE_RAMDISK:   id = SHDID_COMPUTER_RAMDISK; break;
+                }
+                return SHELL_CreateSHDESCRIPTIONID(pv, id, &CLSID_NULL);
+            }
+        }
+    }
+    if (pCLSID)
+        return m_regFolder->GetDetailsEx(pidl, pscid, pv);
+    return SH32_GetDetailsOfPKeyAsVariant(this, pidl, pscid, pv, FALSE);
 }
 
 HRESULT WINAPI CDrivesFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
@@ -1177,13 +1208,14 @@ HRESULT WINAPI CDrivesFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, S
         switch (MyComputerSFHeader[iColumn].colnameid)
         {
             case IDS_SHV_COLUMN_NAME:
+                return m_regFolder->GetDetailsOf(pidl, SHFSF_COL_NAME, psd);
             case IDS_SHV_COLUMN_TYPE:
-                return m_regFolder->GetDetailsOf(pidl, iColumn, psd);
+                return m_regFolder->GetDetailsOf(pidl, SHFSF_COL_TYPE, psd);
             case IDS_SHV_COLUMN_DISK_CAPACITY:
             case IDS_SHV_COLUMN_DISK_AVAILABLE:
-                return SHSetStrRet(&psd->str, ""); /* blank col */
+                return SHSetStrRetEmpty(&psd->str);
             case IDS_SHV_COLUMN_COMMENTS:
-                return m_regFolder->GetDetailsOf(pidl, 2, psd); /* 2 = comments */
+                return m_regFolder->GetDetailsOf(pidl, SHFSF_COL_COMMENT, psd);
             DEFAULT_UNREACHABLE;
         }
     }
@@ -1230,10 +1262,15 @@ HRESULT WINAPI CDrivesFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, S
     return hr;
 }
 
-HRESULT WINAPI CDrivesFolder::MapColumnToSCID(UINT column, SHCOLUMNID * pscid)
+HRESULT WINAPI CDrivesFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
 {
-    FIXME("(%p)\n", this);
-    return E_NOTIMPL;
+    switch (column < _countof(MyComputerSFHeader) ? MyComputerSFHeader[column].colnameid : ~0UL)
+    {
+        case IDS_SHV_COLUMN_NAME: return MakeSCID(*pscid, FMTID_Storage, PID_STG_NAME);
+        case IDS_SHV_COLUMN_TYPE: return MakeSCID(*pscid, FMTID_Storage, PID_STG_STORAGETYPE);
+        case IDS_SHV_COLUMN_COMMENTS: return MakeSCID(*pscid, FMTID_SummaryInformation, PIDSI_COMMENTS);
+    }
+    return E_INVALIDARG;
 }
 
 /************************************************************************
diff --git a/dll/win32/shell32/folders/CFSFolder.cpp b/dll/win32/shell32/folders/CFSFolder.cpp
index 1a9161c273d94..c58bedc956086 100644
--- a/dll/win32/shell32/folders/CFSFolder.cpp
+++ b/dll/win32/shell32/folders/CFSFolder.cpp
@@ -88,9 +88,9 @@ static HKEY OpenKeyFromFileType(LPCWSTR pExtension, LPCWSTR KeyName)
     return hkey;
 }
 
-static LPCWSTR ExtensionFromPidl(PCUIDLIST_RELATIVE pidl, LPWSTR Buf, UINT cchMax)
+static LPCWSTR ExtensionFromPidl(PCUIDLIST_RELATIVE pidl, LPWSTR Buf, UINT cchMax, BOOL AllowFolder = FALSE)
 {
-    if (!_ILIsValue(pidl))
+    if (!AllowFolder && !_ILIsValue(pidl))
     {
         ERR("Invalid pidl!\n");
         return NULL;
@@ -171,6 +171,21 @@ HRESULT GetCLSIDForFileType(PCUIDLIST_RELATIVE pidl, LPCWSTR KeyName, CLSID* pcl
     return GetCLSIDForFileTypeFromExtension(pExtension, KeyName, pclsid);
 }
 
+HRESULT GetItemCLSID(PCUIDLIST_RELATIVE pidl, CLSID *pclsid)
+{
+    WCHAR buf[256];
+    LPCWSTR pExt = ExtensionFromPidl(pidl, buf, _countof(buf), TRUE);
+    if (!pExt)
+        return E_FAIL;
+    HRESULT hr = E_FAIL;
+    if (!ItemIsFolder(pidl))
+        hr = GetCLSIDForFileTypeFromExtension(pExt, L"CLSID", pclsid);
+    // TODO: Should we handle folders with desktop.ini here?
+    if (hr != S_OK && pExt[0] == '.' && pExt[1] == '{')
+        hr = CLSIDFromString(pExt + 1, pclsid);
+    return hr;
+}
+
 static HRESULT
 getDefaultIconLocation(LPWSTR szIconFile, UINT cchMax, int *piIndex, UINT uFlags)
 {
@@ -1643,12 +1658,56 @@ HRESULT WINAPI CFSFolder::GetDefaultColumnState(UINT iColumn,
         return GetDefaultFSColumnState(iColumn, *pcsFlags);
 }
 
-HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl,
-                                       const SHCOLUMNID * pscid, VARIANT * pv)
+HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME ("(%p)\n", this);
-
-    return E_NOTIMPL;
+    if (!_ILGetFSType(pidl))
+        return E_INVALIDARG;
+    HRESULT hr;
+    if (pscid->fmtid == FMTID_ShellDetails)
+    {
+        switch (pscid->pid)
+        {
+            case PID_DESCRIPTIONID:
+            {
+                if (FAILED(hr = SHELL_CreateVariantBuffer(pv, sizeof(SHDESCRIPTIONID))))
+                    return hr;
+                SHDESCRIPTIONID *pDID = (SHDESCRIPTIONID*)V_ARRAY(pv)->pvData;
+                if (ItemIsFolder(pidl))
+                    pDID->dwDescriptionId = SHDID_FS_DIRECTORY;
+                else if (_ILGetFSType(pidl) & PT_FS_FILE_FLAG)
+                    pDID->dwDescriptionId = SHDID_FS_FILE;
+                else
+                    pDID->dwDescriptionId = SHDID_FS_OTHER;
+                if (FAILED(GetItemCLSID(pidl, &pDID->clsid)))
+                    pDID->clsid = CLSID_NULL;
+                return S_OK;
+            }
+        }
+    }
+    // Handle non-string fields here when possible instead of deferring to GetDetailsOf
+    const FileStruct &fsitem = ((PIDLDATA*)pidl->mkid.abID)->u.file;
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME: // Handled directly here for faster performance
+                return SHELL_GetDetailsOfAsStringVariant(this, pidl, SHFSF_COL_NAME, pv);
+            case PID_STG_SIZE:
+                V_VT(pv) = VT_UI4;
+                V_UI4(pv) = _ILGetFileSize(pidl, NULL, 0);
+                return S_OK;
+            case PID_STG_ATTRIBUTES:
+                V_VT(pv) = VT_UI4;
+                V_UI4(pv) = fsitem.uFileAttribs;
+                return S_OK;
+            case PID_STG_WRITETIME:
+                V_VT(pv) = VT_DATE;
+                if (DosDateTimeToVariantTime(fsitem.uFileDate, fsitem.uFileTime, &V_DATE(pv)))
+                    return S_OK;
+                break;
+        }
+    }
+    return SH32_GetDetailsOfPKeyAsVariant(this, pidl, pscid, pv, TRUE);
 }
 
 HRESULT WINAPI CFSFolder::GetDetailsOf(PCUITEMID_CHILD pidl,
@@ -1714,11 +1773,18 @@ HRESULT WINAPI CFSFolder::GetDetailsOf(PCUITEMID_CHILD pidl,
     return hr;
 }
 
-HRESULT WINAPI CFSFolder::MapColumnToSCID (UINT column,
-        SHCOLUMNID * pscid)
+HRESULT WINAPI CFSFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
 {
-    FIXME ("(%p)\n", this);
-    return E_NOTIMPL;
+    switch (column)
+    {
+        case SHFSF_COL_NAME: return MakeSCID(*pscid, FMTID_Storage, PID_STG_NAME);
+        case SHFSF_COL_SIZE: return MakeSCID(*pscid, FMTID_Storage, PID_STG_SIZE);
+        case SHFSF_COL_TYPE: return MakeSCID(*pscid, FMTID_Storage, PID_STG_STORAGETYPE);
+        case SHFSF_COL_MDATE: return MakeSCID(*pscid, FMTID_Storage, PID_STG_WRITETIME);
+        case SHFSF_COL_FATTS: return MakeSCID(*pscid, FMTID_Storage, PID_STG_ATTRIBUTES);
+        case SHFSF_COL_COMMENT: return MakeSCID(*pscid, FMTID_SummaryInformation, PIDSI_COMMENTS);
+    }
+    return E_INVALIDARG;
 }
 
 /************************************************************************
diff --git a/dll/win32/shell32/folders/CRegFolder.cpp b/dll/win32/shell32/folders/CRegFolder.cpp
index efaf4711f8657..603fa47f30911 100644
--- a/dll/win32/shell32/folders/CRegFolder.cpp
+++ b/dll/win32/shell32/folders/CRegFolder.cpp
@@ -818,7 +818,18 @@ HRESULT WINAPI CRegFolder::GetDefaultColumnState(UINT iColumn, DWORD *pcsFlags)
 
 HRESULT WINAPI CRegFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    return E_NOTIMPL;
+    const CLSID *pCLSID = IsRegItem(pidl);
+    if (!pCLSID)
+        return E_INVALIDARG;
+    if (pscid->fmtid == FMTID_ShellDetails)
+    {
+        switch (pscid->pid)
+        {
+            case PID_DESCRIPTIONID:
+                return SHELL_CreateSHDESCRIPTIONID(pv, SHDID_ROOT_REGITEM, pCLSID);
+        }
+    }
+    return SH32_GetDetailsOfPKeyAsVariant(this, pidl, pscid, pv, TRUE);
 }
 
 HRESULT WINAPI CRegFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
@@ -849,7 +860,7 @@ HRESULT WINAPI CRegFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHEL
         case COL_INFOTIP:
             HKEY hKey;
             if (!HCR_RegOpenClassIDKey(*clsid, &hKey))
-                return SHSetStrRet(&psd->str, "");
+                return SHSetStrRetEmpty(&psd->str);
 
             psd->str.cStr[0] = 0x00;
             psd->str.uType = STRRET_CSTR;
@@ -857,17 +868,23 @@ HRESULT WINAPI CRegFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHEL
             RegCloseKey(hKey);
             return S_OK;
         default:
-            /* Return an empty string when we area asked for a column we don't support.
-               Only  the regfolder is supposed to do this as it supports less columns compared to other folder
+            /* Return an empty string when we are asked for a column we don't support.
+               Only the regfolder is supposed to do this as it supports fewer columns compared to other folders
                and its contents are supposed to be presented alongside items that support more columns. */
-            return SHSetStrRet(&psd->str, "");
+            return SHSetStrRetEmpty(&psd->str);
     }
     return E_FAIL;
 }
 
 HRESULT WINAPI CRegFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
 {
-    return E_NOTIMPL;
+    switch (column)
+    {
+        case COL_NAME: return MakeSCID(*pscid, FMTID_Storage, PID_STG_NAME);
+        case COL_TYPE: return MakeSCID(*pscid, FMTID_Storage, PID_STG_STORAGETYPE);
+        case COL_INFOTIP: return MakeSCID(*pscid, FMTID_SummaryInformation, PIDSI_COMMENTS);
+    }
+    return E_INVALIDARG;
 }
 
 static HRESULT CALLBACK RegFolderContextMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj,
diff --git a/dll/win32/shell32/shfldr.h b/dll/win32/shell32/shfldr.h
index 2ab941e494c49..fb04ea565eae4 100644
--- a/dll/win32/shell32/shfldr.h
+++ b/dll/win32/shell32/shfldr.h
@@ -23,6 +23,7 @@
 
 #ifndef _SHFLDR_H_
 #define _SHFLDR_H_
+#include <ntquery.h> // For PID_STG_*
 
 #define CHARS_IN_GUID 39
 
@@ -94,6 +95,23 @@ SHELL_GetDefaultFolderEnumSHCONTF();
 BOOL
 SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGAOF Query, SHCONTF Flags);
 
+static inline HRESULT
+MakeSCID(SHCOLUMNID &scid, REFCLSID fmtid, UINT pid)
+{
+    scid.fmtid = fmtid;
+    scid.pid = pid;
+    return S_OK;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid);
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar);
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar);
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL UseFsColMap);
+
 HRESULT
 SHELL_CreateAbsolutePidl(IShellFolder *pSF, PCUIDLIST_RELATIVE pidlChild, PIDLIST_ABSOLUTE *ppPidl);
 
diff --git a/dll/win32/shell32/shlfolder.cpp b/dll/win32/shell32/shlfolder.cpp
index 268eadb238a73..375fd990ba061 100644
--- a/dll/win32/shell32/shlfolder.cpp
+++ b/dll/win32/shell32/shlfolder.cpp
@@ -54,6 +54,91 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        vt = VT_BSTR; return SHFSF_COL_NAME;
+            case PID_STG_SIZE:        vt = VT_UI8; return SHFSF_COL_SIZE;
+            case PID_STG_STORAGETYPE: vt = VT_BSTR; return SHFSF_COL_TYPE;
+            case PID_STG_ATTRIBUTES:  vt = VT_BSTR; return SHFSF_COL_FATTS;
+            case PID_STG_WRITETIME:   vt = VT_DATE; return SHFSF_COL_MDATE;
+        }
+    }
+    if (pscid->fmtid == FMTID_SummaryInformation && pscid->pid == PIDSI_COMMENTS)
+    {
+        vt = VT_BSTR;
+        return SHFSF_COL_COMMENT;
+    }
+    return E_INVALIDARG;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid)
+{
+    for (UINT i = 0; i <= SHCIDS_COLUMNMASK; ++i)
+    {
+        SHCOLUMNID scid;
+        HRESULT hr = pSF->MapColumnToSCID(i, &scid);
+        if (FAILED(hr))
+            break;
+        if (IsEqualPropertyKey(scid, *pscid))
+            return i;
+    }
+    return E_FAIL;
+}
+
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar)
+{
+    V_VT(pVar) = VT_EMPTY;
+    SHELLDETAILS sd;
+    sd.str.uType = STRRET_CSTR;
+    HRESULT hr = pSF->GetDetailsOf(pidl, Column, &sd);
+    if (FAILED(hr))
+        return hr;
+    if (FAILED(hr = StrRetToBSTR(&sd.str, pidl, &V_BSTR(pVar))))
+        return hr;
+    V_VT(pVar) = VT_BSTR;
+    return hr;
+}
+
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar)
+{
+    HRESULT hr = SHELL_GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar);
+    if (FAILED(hr))
+        return hr;
+    if (vt == VT_EMPTY)
+    {
+        SHCOLSTATEF state;
+        if (FAILED(pSF->GetDefaultColumnState(Column, &state)))
+            state = SHCOLSTATE_TYPE_STR;
+        if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_INT)
+            vt = VT_I8;
+        else if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)
+            vt = VT_DATE;
+        else
+            vt = VT_BSTR;
+    }
+    if (vt != VT_BSTR)
+        VariantChangeType(pVar, pVar, 0, vt);
+    return hr;
+}
+
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL UseFsColMap)
+{
+    // CFSFolder and CRegFolder uses the SHFSF_COL columns and can use the faster and better version,
+    // everyone else must ask the folder to map the SCID to a column.
+    VARTYPE vt = VT_EMPTY;
+    HRESULT hr = UseFsColMap ? MapSCIDToShell32FsColumn(pscid, vt) : SHELL_MapSCIDToColumn(pSF, pscid);
+    return SUCCEEDED(hr) ? SHELL_GetDetailsOfColumnAsVariant(pSF, pidl, hr, vt, pVar) : hr;
+}
+
 HRESULT SHELL_CreateAbsolutePidl(IShellFolder *pSF, PCUIDLIST_RELATIVE pidlChild, PIDLIST_ABSOLUTE *ppPidl)
 {
     PIDLIST_ABSOLUTE pidlFolder;
diff --git a/dll/win32/shell32/wine/pidl.c b/dll/win32/shell32/wine/pidl.c
index 43511ca40f149..ad86794634e2a 100644
--- a/dll/win32/shell32/wine/pidl.c
+++ b/dll/win32/shell32/wine/pidl.c
@@ -1254,6 +1254,33 @@ LPITEMIDLIST WINAPI SHSimpleIDListFromPathAW(LPCVOID lpszPath)
     return SHSimpleIDListFromPathA (lpszPath);
 }
 
+static HRESULT GetShellFolder2ItemDetailsExToBuffer(IShellFolder2 *psf2, LPCITEMIDLIST pidl,
+                                                    const SHCOLUMNID *pscid, void*buf, UINT cb)
+{
+    VARIANT var;
+    V_VT(&var) = VT_EMPTY;
+    HRESULT hr = IShellFolder2_GetDetailsEx(psf2, pidl, pscid, &var);
+    if (SUCCEEDED(hr))
+    {
+        hr = SHELL_VariantToBuffer(&var, buf, cb);
+        VariantClear(&var);
+    }
+    return hr;
+}
+
+static HRESULT GetShellFolder1ItemDetailsExToBuffer(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
+                                                    const SHCOLUMNID *pscid, void*buf, UINT cb)
+{
+    IShellFolder2 *psf2;
+    HRESULT hr = IShellFolder_QueryInterface(psf, &IID_IShellFolder2, (void**)&psf2);
+    if (SUCCEEDED(hr))
+    {
+        hr = GetShellFolder2ItemDetailsExToBuffer(psf2, pidl, pscid, buf, cb);
+        IShellFolder2_Release(psf2);
+    }
+    return hr;
+}
+
 /*************************************************************************
  * SHGetDataFromIDListA [SHELL32.247]
  *
@@ -1303,8 +1330,10 @@ HRESULT WINAPI SHGetDataFromIDListA(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[0] = '\0';
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+        return SHGetDataFromIDListW(psf, pidl, nFormat, dest, len);
+
+    case SHGDFIL_NETRESOURCE:
         FIXME_(shell)("SHGDFIL %i stub\n", nFormat);
         break;
 
@@ -1334,7 +1363,7 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
 
     switch (nFormat)
     {
-    case SHGDFIL_FINDDATA:
+    case SHGDFIL_FINDDATA: /* FIXME: Ask the folder for PID_FINDDATA */
         pfd = dest;
 
         if (_ILIsDrive(pidl))
@@ -1362,8 +1391,14 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[13] = 0;
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+    {
+        /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
+        SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
+        return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
+    }
+
+    case SHGDFIL_NETRESOURCE:
         FIXME_(shell)("SHGDFIL %i stub\n", nFormat);
         break;
 
diff --git a/dll/win32/shell32/wine/shell32_main.h b/dll/win32/shell32/wine/shell32_main.h
index ec6c1f5d01c8b..901b63bd146ff 100644
--- a/dll/win32/shell32/wine/shell32_main.h
+++ b/dll/win32/shell32/wine/shell32_main.h
@@ -215,6 +215,73 @@ LPWSTR SH_FormatFileSizeWithBytes(PULARGE_INTEGER lpQwSize, LPWSTR pszBuf, UINT
 HRESULT WINAPI DoRegisterServer(void);
 HRESULT WINAPI DoUnregisterServer(void);
 
+/* Property system */
+static inline HRESULT
+SHELL_CreateVariantBufferEx(VARIANT *pVar, UINT cb, VARTYPE vt)
+{
+    SAFEARRAY *pSA = SafeArrayCreateVector(vt, 0, cb);
+    if (pSA)
+    {
+        V_VT(pVar) = VT_ARRAY | vt;
+        V_ARRAY(pVar) = pSA;
+        return S_OK;
+    }
+    return E_OUTOFMEMORY;
+}
+
+static inline HRESULT
+SHELL_CreateVariantBuffer(VARIANT *pVar, UINT cb)
+{
+    return SHELL_CreateVariantBufferEx(pVar, cb, VT_UI1);
+}
+
+static inline HRESULT
+SHELL_InitVariantFromBuffer(VARIANT *pVar, const void *pData, UINT cb)
+{
+    HRESULT hr = SHELL_CreateVariantBuffer(pVar, cb);
+    if (SUCCEEDED(hr))
+        CopyMemory(V_ARRAY(pVar)->pvData, pData, cb);
+    return hr;
+}
+
+static inline void*
+SHELL_GetSafeArrayDataPtr(const SAFEARRAY *pSA, SIZE_T *pcb)
+{
+    if (pSA->cDims != 1)
+        return NULL;
+    LONG lob, upb;
+    SafeArrayGetLBound((SAFEARRAY*)pSA, 1, &lob);
+    SafeArrayGetUBound((SAFEARRAY*)pSA, 1, &upb);
+    *pcb = (SIZE_T)(upb - lob) + 1;
+    return pSA->pvData;
+}
+
+static inline HRESULT
+SHELL_VariantToBuffer(VARIANT *pVar, void *pData, SIZE_T cb)
+{
+    if (V_VT(pVar) != (VT_ARRAY | VT_UI1))
+        return E_INVALIDARG;
+    SIZE_T cbArr;
+    void *pArrData = SHELL_GetSafeArrayDataPtr(V_ARRAY(pVar), &cbArr);
+    if (!pArrData || cbArr < cb)
+        return E_FAIL;
+    CopyMemory(pData, pArrData, cb);
+    return (cbArr > cb) ? S_FALSE : S_OK;
+}
+
+static inline HRESULT
+SHELL_CreateSHDESCRIPTIONID(VARIANT *pVar, DWORD Id, const CLSID *pCLSID)
+{
+    HRESULT hr = SHELL_CreateVariantBuffer(pVar, sizeof(SHDESCRIPTIONID));
+    if (SUCCEEDED(hr))
+    {
+        SHDESCRIPTIONID *pDID = (SHDESCRIPTIONID*)V_ARRAY(pVar)->pvData;
+        pDID->dwDescriptionId = Id;
+        pDID->clsid = *pCLSID;
+    }
+    return hr;
+}
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif
diff --git a/sdk/include/reactos/shellutils.h b/sdk/include/reactos/shellutils.h
index 2165a6706dccc..531d022ba5413 100644
--- a/sdk/include/reactos/shellutils.h
+++ b/sdk/include/reactos/shellutils.h
@@ -474,6 +474,13 @@ template<class B, class R> static HRESULT SHILCombine(B base, PCUIDLIST_RELATIVE
 static inline bool StrIsNullOrEmpty(LPCSTR str) { return !str || !*str; }
 static inline bool StrIsNullOrEmpty(LPCWSTR str) { return !str || !*str; }
 
+HRESULT inline SHSetStrRetEmpty(LPSTRRET pStrRet)
+{
+    pStrRet->uType = STRRET_CSTR;
+    pStrRet->cStr[0] = '\0';
+    return S_OK;
+}
+
 HRESULT inline SHSetStrRet(LPSTRRET pStrRet, LPCSTR pstrValue)
 {
     pStrRet->uType = STRRET_CSTR;


Reviews:
user: login: learn-more
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T11:46:11Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T11:48:07Z
user: login: Doug-Lyons
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:34:52Z
user: login: Doug-Lyons
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:47:03Z
user: login: Doug-Lyons
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:55:05Z
user: login: cbialorucki
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T02:04:46Z
user: login: HBelusca
body: Since the code dramatically lacks (doxygen) documentation for all the new functions, it's hard to do actual review of the code, except for checking the basic stuff ("obvious" bugs, or style), so here's my few comments.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:38:52Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:52:34Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:55:57Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:57:53Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T19:24:41Z
user: login: HBelusca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T19:35:23Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T19:39:39Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T19:44:14Z
user: login: HBelusca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T20:43:50Z

Review comments:
diff_hunk: @@ -171,6 +176,21 @@ HRESULT GetCLSIDForFileType(PCUIDLIST_RELATIVE pidl, LPCWSTR KeyName, CLSID* pcl
     return GetCLSIDForFileTypeFromExtension(pExtension, KeyName, pclsid);
 }
 
+HRESULT GetItemCLSID(PCUIDLIST_RELATIVE pidl, CLSID *pclsid)
+{
+    WCHAR buf[256];
+    LPCWSTR pExt = ExtensionFromPidlEx(pidl, buf, _countof(buf), TRUE);
+    if (!pExt)
+        return E_FAIL;
+    HRESULT hr = E_FAIL;
+    if (!ItemIsFolder(pidl))
+        hr = GetCLSIDForFileTypeFromExtension(pExt, L"CLSID", pclsid);
+    // TODO: Should we handle folders with desktop.ini here?
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: learn-more
body: Maybe, but that is scattered all around the code right now, it might make sense to first clean that up a bit?
created_at: 2025-04-10T10:41:27Z
author_association: MEMBER
diff_hunk: @@ -171,6 +176,21 @@ HRESULT GetCLSIDForFileType(PCUIDLIST_RELATIVE pidl, LPCWSTR KeyName, CLSID* pcl
     return GetCLSIDForFileTypeFromExtension(pExtension, KeyName, pclsid);
 }
 
+HRESULT GetItemCLSID(PCUIDLIST_RELATIVE pidl, CLSID *pclsid)
+{
+    WCHAR buf[256];
+    LPCWSTR pExt = ExtensionFromPidlEx(pidl, buf, _countof(buf), TRUE);
+    if (!pExt)
+        return E_FAIL;
+    HRESULT hr = E_FAIL;
+    if (!ItemIsFolder(pidl))
+        hr = GetCLSIDForFileTypeFromExtension(pExt, L"CLSID", pclsid);
+    // TODO: Should we handle folders with desktop.ini here?
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: whindsaks
body: I agree. ROS does not support "god mode" folders at all and the desktop.ini handling is all over the place but I can't fix that in this PR.

Tweak UI filters out everything that reports itself as FS so that part works fine without any extra handling.
created_at: 2025-04-10T11:48:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -860,14 +871,20 @@ HRESULT WINAPI CRegFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHEL
             /* Return an empty string when we area asked for a column we don't support.
path: dll/win32/shell32/folders/CRegFolder.cpp
user: login: Doug-Lyons
body: ```suggestion
            /* Return an empty string when we are asked for a column we don't support.
```
created_at: 2025-04-10T12:34:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -860,14 +871,20 @@ HRESULT WINAPI CRegFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHEL
             /* Return an empty string when we area asked for a column we don't support.
                Only  the regfolder is supposed to do this as it supports less columns compared to other folder
path: dll/win32/shell32/folders/CRegFolder.cpp
user: login: Doug-Lyons
body: ```suggestion
               Only  the regfolder is supposed to do this as it supports less columns compared to other folders
```
created_at: 2025-04-10T12:47:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,6 +215,73 @@ LPWSTR SH_FormatFileSizeWithBytes(PULARGE_INTEGER lpQwSize, LPWSTR pszBuf, UINT
 HRESULT WINAPI DoRegisterServer(void);
 HRESULT WINAPI DoUnregisterServer(void);
 
+/* Property system */
+static inline HRESULT
+SHELL_CreateVariantBufferEx(VARIANT *pVar, UINT cb, VARTYPE vt)
+{
+    SAFEARRAY *pSA = SafeArrayCreateVector(vt, 0, cb);
+    if (pSA)
+    {
+        V_VT(pVar) = VT_ARRAY | vt;
+        V_ARRAY(pVar) = pSA;
+        return S_OK;
+    }
+    return E_OUTOFMEMORY;
+}
+
+static inline HRESULT
+SHELL_CreateVariantBuffer(VARIANT *pVar, UINT cb)
+{
+    return SHELL_CreateVariantBufferEx(pVar, cb, VT_UI1);
+}
+
+static inline HRESULT
+SHELL_InitVariantFromBuffer(VARIANT *pVar, const void *pData, UINT cb)
+{
+    HRESULT hr = SHELL_CreateVariantBuffer(pVar, cb);
+    if (SUCCEEDED(hr))
+        CopyMemory(V_ARRAY(pVar)->pvData, pData, cb);
+    return hr;
+}
+
+static inline void*
+SHELL_GetSafeArrayDataPtr(const SAFEARRAY *pSA, SIZE_T *pcb)
+{
+    if (pSA->cDims != 1)
+        return NULL;
+    LONG lob, upb;
+    SafeArrayGetLBound((SAFEARRAY*)pSA, 1, &lob);
+    SafeArrayGetUBound((SAFEARRAY*)pSA, 1, &upb);
+    *pcb = (SIZE_T)(upb - lob) + 1;
+    return pSA->pvData;
+}
+
+static inline HRESULT
+SHELL_VariantToBuffer(VARIANT *pVar, void *pData, SIZE_T cb)
+{
+    if (V_VT(pVar) != (VT_ARRAY | VT_UI1))
+        return E_INVALIDARG;
+    SIZE_T cbArr;
+    void *pArrData = SHELL_GetSafeArrayDataPtr(V_ARRAY(pVar), &cbArr);
+    if (!pArrData || cbArr < cb)
path: dll/win32/shell32/wine/shell32_main.h
user: login: HBelusca
body: Why should the function fail, if the cbArr corresponding to the pVar, is smaller than the given cb size of pData? I would have thought the function would fail if cbArr is larger than what the user specifies as the size for pData (and the data copy isn't done, contrary to what you do below...)
created_at: 2025-04-11T18:22:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1362,8 +1391,14 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[13] = 0;
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+        {
+            /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
+            SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
+            return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
+        }
path: dll/win32/shell32/wine/pidl.c
user: login: HBelusca
body: ```suggestion
    {
        /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
        SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
        return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
    }
```
created_at: 2025-04-11T18:24:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,6 +54,89 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        return (vt = VT_BSTR, SHFSF_COL_NAME);
path: dll/win32/shell32/shlfolder.cpp
user: login: HBelusca
body: Should be the same without weird syntax.
```suggestion
            case PID_STG_NAME:        vt = VT_BSTR; return SHFSF_COL_NAME;
```
created_at: 2025-04-11T18:26:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,6 +54,89 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        return (vt = VT_BSTR, SHFSF_COL_NAME);
+            case PID_STG_SIZE:        return (vt = VT_UI8, SHFSF_COL_SIZE);
+            case PID_STG_STORAGETYPE: return (vt = VT_BSTR, SHFSF_COL_TYPE);
+            case PID_STG_ATTRIBUTES:  return (vt = VT_BSTR, SHFSF_COL_FATTS);
+            case PID_STG_WRITETIME:   return (vt = VT_DATE, SHFSF_COL_MDATE);
+        }
+    }
+    if (pscid->fmtid == FMTID_SummaryInformation && pscid->pid == PIDSI_COMMENTS)
+    {
+        vt = VT_BSTR;
+        return SHFSF_COL_COMMENT;
+    }
+    return E_INVALIDARG;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid)
+{
+    for (UINT i = 0; i <= SHCIDS_COLUMNMASK; ++i)
+    {
+        SHCOLUMNID scid;
+        HRESULT hr = pSF->MapColumnToSCID(i, &scid);
+        if (FAILED(hr))
+            break;
+        if (IsEqualPropertyKey(scid, *pscid))
+            return i;
+    }
+    return E_FAIL;
+}
+
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar)
+{
+    V_VT(pVar) = VT_EMPTY;
+    SHELLDETAILS sd;
+    sd.str.uType = STRRET_CSTR;
+    HRESULT hr = pSF->GetDetailsOf(pidl, Column, &sd);
+    if (FAILED(hr))
+        return hr;
+    if (FAILED(hr = StrRetToBSTR(&sd.str, pidl, &V_BSTR(pVar))))
+        return hr;
+    V_VT(pVar) = VT_BSTR;
+    return hr;
+}
+
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar)
+{
+    HRESULT hr = SHELL_GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar);
+    if (FAILED(hr))
+        return hr;
+    if (vt == VT_EMPTY)
+    {
+        SHCOLSTATEF state;
+        if (FAILED(pSF->GetDefaultColumnState(Column, &state)))
+            state = SHCOLSTATE_TYPE_STR;
+        if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_INT)
+            vt = VT_I8;
+        else if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)
+            vt = VT_DATE;
+        else
+            vt = VT_BSTR;
+    }
+    if (vt != VT_BSTR)
+        VariantChangeType(pVar, pVar, 0, vt);
+    return hr;
+}
+
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL Direct)
+{
+    VARTYPE vt = VT_EMPTY;
+    HRESULT hr = Direct ? MapSCIDToShell32FsColumn(pscid, vt) : SHELL_MapSCIDToColumn(pSF, pscid);
path: dll/win32/shell32/shlfolder.cpp
user: login: HBelusca
body: What does "Direct" exactly mean in this context? (There is zero comments in your code...)
created_at: 2025-04-11T18:28:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,6 +54,89 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        return (vt = VT_BSTR, SHFSF_COL_NAME);
+            case PID_STG_SIZE:        return (vt = VT_UI8, SHFSF_COL_SIZE);
+            case PID_STG_STORAGETYPE: return (vt = VT_BSTR, SHFSF_COL_TYPE);
+            case PID_STG_ATTRIBUTES:  return (vt = VT_BSTR, SHFSF_COL_FATTS);
+            case PID_STG_WRITETIME:   return (vt = VT_DATE, SHFSF_COL_MDATE);
+        }
+    }
+    if (pscid->fmtid == FMTID_SummaryInformation && pscid->pid == PIDSI_COMMENTS)
+    {
+        vt = VT_BSTR;
+        return SHFSF_COL_COMMENT;
+    }
+    return E_INVALIDARG;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid)
+{
+    for (UINT i = 0; i <= SHCIDS_COLUMNMASK; ++i)
+    {
+        SHCOLUMNID scid;
+        HRESULT hr = pSF->MapColumnToSCID(i, &scid);
+        if (FAILED(hr))
+            break;
+        if (IsEqualPropertyKey(scid, *pscid))
+            return i;
+    }
+    return E_FAIL;
+}
+
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar)
+{
+    V_VT(pVar) = VT_EMPTY;
+    SHELLDETAILS sd;
+    sd.str.uType = STRRET_CSTR;
+    HRESULT hr = pSF->GetDetailsOf(pidl, Column, &sd);
+    if (FAILED(hr))
+        return hr;
+    if (FAILED(hr = StrRetToBSTR(&sd.str, pidl, &V_BSTR(pVar))))
+        return hr;
+    V_VT(pVar) = VT_BSTR;
+    return hr;
+}
+
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar)
+{
+    HRESULT hr = SHELL_GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar);
+    if (FAILED(hr))
+        return hr;
+    if (vt == VT_EMPTY)
+    {
+        SHCOLSTATEF state;
+        if (FAILED(pSF->GetDefaultColumnState(Column, &state)))
+            state = SHCOLSTATE_TYPE_STR;
+        if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_INT)
+            vt = VT_I8;
+        else if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)
+            vt = VT_DATE;
+        else
+            vt = VT_BSTR;
+    }
+    if (vt != VT_BSTR)
+        VariantChangeType(pVar, pVar, 0, vt);
+    return hr;
+}
+
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL Direct)
path: dll/win32/shell32/shlfolder.cpp
user: login: HBelusca
body: Why "SH32" instead of "SHELL" ?
created_at: 2025-04-11T18:28:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1714,11 +1778,18 @@ HRESULT WINAPI CFSFolder::GetDetailsOf(PCUITEMID_CHILD pidl,
     return hr;
 }
 
-HRESULT WINAPI CFSFolder::MapColumnToSCID (UINT column,
-        SHCOLUMNID * pscid)
+HRESULT WINAPI CFSFolder::MapColumnToSCID (UINT column, SHCOLUMNID *pscid)
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: HBelusca
body: ```suggestion
HRESULT WINAPI CFSFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid)
```
created_at: 2025-04-11T18:30:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1643,12 +1663,56 @@ HRESULT WINAPI CFSFolder::GetDefaultColumnState(UINT iColumn,
         return GetDefaultFSColumnState(iColumn, *pcsFlags);
 }
 
-HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl,
-                                       const SHCOLUMNID * pscid, VARIANT * pv)
+HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME ("(%p)\n", this);
-
-    return E_NOTIMPL;
+    if (!_ILGetFSType(pidl))
+        return E_INVALIDARG;
+    HRESULT hr;
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: HBelusca
body: In case there's no future plans to use this hr in other parts of this function, moving it into the `case PID_DESCRIPTIONID:` may be a good idea.
created_at: 2025-04-11T18:31:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,6 +106,11 @@ static LPCWSTR ExtensionFromPidl(PCUIDLIST_RELATIVE pidl, LPWSTR Buf, UINT cchMa
     return pExtension;
 }
 
+static LPCWSTR ExtensionFromPidl(PCUIDLIST_RELATIVE pidl, LPWSTR Buf, UINT cchMax)
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: HBelusca
body: Since this is C++, what about keeping the existing `ExtensionFromPidl` function, but making its `AllowFolder` parameter optional, with the default value being FALSE ?
```c
static LPCWSTR ExtensionFromPidl(PCUIDLIST_RELATIVE pidl, LPWSTR Buf, UINT cchMax, BOOL AllowFolder = FALSE)
```
created_at: 2025-04-11T18:33:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,6 +26,9 @@
 
 WINE_DEFAULT_DEBUG_CHANNEL(shell);
 
+BOOL IsDriveFloppyA(LPCSTR pszDriveRoot);
+BOOL IsDriveFloppyW(LPCWSTR pszDriveRoot);
path: dll/win32/shell32/folders/CDrivesFolder.cpp
user: login: HBelusca
body: maybe `extern BOOL ....` to underline the fact both these functions are NOT defined in this compilation unit/file.
created_at: 2025-04-11T18:35:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,6 +215,73 @@ LPWSTR SH_FormatFileSizeWithBytes(PULARGE_INTEGER lpQwSize, LPWSTR pszBuf, UINT
 HRESULT WINAPI DoRegisterServer(void);
 HRESULT WINAPI DoUnregisterServer(void);
 
+/* Property system */
+static inline HRESULT
+SHELL_CreateVariantBufferEx(VARIANT *pVar, UINT cb, VARTYPE vt)
+{
+    SAFEARRAY *pSA = SafeArrayCreateVector(vt, 0, cb);
+    if (pSA)
+    {
+        V_VT(pVar) = VT_ARRAY | vt;
+        V_ARRAY(pVar) = pSA;
+        return S_OK;
+    }
+    return E_OUTOFMEMORY;
+}
+
+static inline HRESULT
+SHELL_CreateVariantBuffer(VARIANT *pVar, UINT cb)
+{
+    return SHELL_CreateVariantBufferEx(pVar, cb, VT_UI1);
+}
+
+static inline HRESULT
+SHELL_InitVariantFromBuffer(VARIANT *pVar, const void *pData, UINT cb)
+{
+    HRESULT hr = SHELL_CreateVariantBuffer(pVar, cb);
+    if (SUCCEEDED(hr))
+        CopyMemory(V_ARRAY(pVar)->pvData, pData, cb);
+    return hr;
+}
+
+static inline void*
+SHELL_GetSafeArrayDataPtr(const SAFEARRAY *pSA, SIZE_T *pcb)
+{
+    if (pSA->cDims != 1)
+        return NULL;
+    LONG lob, upb;
+    SafeArrayGetLBound((SAFEARRAY*)pSA, 1, &lob);
+    SafeArrayGetUBound((SAFEARRAY*)pSA, 1, &upb);
+    *pcb = (SIZE_T)(upb - lob) + 1;
+    return pSA->pvData;
+}
+
+static inline HRESULT
+SHELL_VariantToBuffer(VARIANT *pVar, void *pData, SIZE_T cb)
+{
+    if (V_VT(pVar) != (VT_ARRAY | VT_UI1))
+        return E_INVALIDARG;
+    SIZE_T cbArr;
+    void *pArrData = SHELL_GetSafeArrayDataPtr(V_ARRAY(pVar), &cbArr);
+    if (!pArrData || cbArr < cb)
path: dll/win32/shell32/wine/shell32_main.h
user: login: whindsaks
body: Because [the documentation](https://learn.microsoft.com/en-us/windows/win32/api/propvarutil/nf-propvarutil-varianttobuffer) says so: 

 > E_FAIL: The VARIANT buffer value had fewer than cb bytes.

It also matches how you use the function `VariantToBuffer(pSomeVariant, &MyFooStruct, sizeof(FOO)); /* Get the FOO struct */`.
created_at: 2025-04-11T18:52:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,6 +54,89 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        return (vt = VT_BSTR, SHFSF_COL_NAME);
+            case PID_STG_SIZE:        return (vt = VT_UI8, SHFSF_COL_SIZE);
+            case PID_STG_STORAGETYPE: return (vt = VT_BSTR, SHFSF_COL_TYPE);
+            case PID_STG_ATTRIBUTES:  return (vt = VT_BSTR, SHFSF_COL_FATTS);
+            case PID_STG_WRITETIME:   return (vt = VT_DATE, SHFSF_COL_MDATE);
+        }
+    }
+    if (pscid->fmtid == FMTID_SummaryInformation && pscid->pid == PIDSI_COMMENTS)
+    {
+        vt = VT_BSTR;
+        return SHFSF_COL_COMMENT;
+    }
+    return E_INVALIDARG;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid)
+{
+    for (UINT i = 0; i <= SHCIDS_COLUMNMASK; ++i)
+    {
+        SHCOLUMNID scid;
+        HRESULT hr = pSF->MapColumnToSCID(i, &scid);
+        if (FAILED(hr))
+            break;
+        if (IsEqualPropertyKey(scid, *pscid))
+            return i;
+    }
+    return E_FAIL;
+}
+
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar)
+{
+    V_VT(pVar) = VT_EMPTY;
+    SHELLDETAILS sd;
+    sd.str.uType = STRRET_CSTR;
+    HRESULT hr = pSF->GetDetailsOf(pidl, Column, &sd);
+    if (FAILED(hr))
+        return hr;
+    if (FAILED(hr = StrRetToBSTR(&sd.str, pidl, &V_BSTR(pVar))))
+        return hr;
+    V_VT(pVar) = VT_BSTR;
+    return hr;
+}
+
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar)
+{
+    HRESULT hr = SHELL_GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar);
+    if (FAILED(hr))
+        return hr;
+    if (vt == VT_EMPTY)
+    {
+        SHCOLSTATEF state;
+        if (FAILED(pSF->GetDefaultColumnState(Column, &state)))
+            state = SHCOLSTATE_TYPE_STR;
+        if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_INT)
+            vt = VT_I8;
+        else if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)
+            vt = VT_DATE;
+        else
+            vt = VT_BSTR;
+    }
+    if (vt != VT_BSTR)
+        VariantChangeType(pVar, pVar, 0, vt);
+    return hr;
+}
+
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL Direct)
path: dll/win32/shell32/shlfolder.cpp
user: login: whindsaks
body: Functions named SHELL_ you are allowed to copy and use outside shell32, the others, you cannot because it uses internal shell32 details (in this case, the helper function MapSCIDToShell32FsColumn it calls knows the internal CFSFolder column order).
created_at: 2025-04-11T18:55:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1643,12 +1663,56 @@ HRESULT WINAPI CFSFolder::GetDefaultColumnState(UINT iColumn,
         return GetDefaultFSColumnState(iColumn, *pcsFlags);
 }
 
-HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl,
-                                       const SHCOLUMNID * pscid, VARIANT * pv)
+HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
 {
-    FIXME ("(%p)\n", this);
-
-    return E_NOTIMPL;
+    if (!_ILGetFSType(pidl))
+        return E_INVALIDARG;
+    HRESULT hr;
path: dll/win32/shell32/folders/CFSFolder.cpp
user: login: whindsaks
body: There are other PID_STG_xyz values to handle in the future (date created, date accessed, infotip etc.). And file size handling > 4GB in the future is something that can fail (has to hit the disk). We need to kill Wines `_ILGetFileSize` (broken by design) and replace it with a method inside CFSFolder that has a failure case.
created_at: 2025-04-11T18:57:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1362,8 +1391,14 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[13] = 0;
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+        {
+            /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
+            SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
+            return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
+        }
path: dll/win32/shell32/wine/pidl.c
user: login: whindsaks
body: This would place the brackets on the same indent as the bracket for the switch itself, this is worse than my code that technically does not conform to the coding style?

Also, once PKEY_DescriptionID is added to the headers, I don't need the brackets at all.
created_at: 2025-04-11T19:24:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1362,8 +1391,14 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[13] = 0;
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+        {
+            /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
+            SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
+            return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
+        }
path: dll/win32/shell32/wine/pidl.c
user: login: HBelusca
body: It's to have the brackets (and also the code inside) positioned as expected relative to the `case foobar:` that is above.
created_at: 2025-04-11T19:35:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -54,6 +54,89 @@ BOOL SHELL_IncludeItemInFolderEnum(IShellFolder *pSF, PCUITEMID_CHILD pidl, SFGA
     return TRUE;
 }
 
+static HRESULT
+MapSCIDToShell32FsColumn(const SHCOLUMNID *pscid, VARTYPE &vt)
+{
+    if (pscid->fmtid == FMTID_Storage)
+    {
+        switch (pscid->pid)
+        {
+            case PID_STG_NAME:        return (vt = VT_BSTR, SHFSF_COL_NAME);
+            case PID_STG_SIZE:        return (vt = VT_UI8, SHFSF_COL_SIZE);
+            case PID_STG_STORAGETYPE: return (vt = VT_BSTR, SHFSF_COL_TYPE);
+            case PID_STG_ATTRIBUTES:  return (vt = VT_BSTR, SHFSF_COL_FATTS);
+            case PID_STG_WRITETIME:   return (vt = VT_DATE, SHFSF_COL_MDATE);
+        }
+    }
+    if (pscid->fmtid == FMTID_SummaryInformation && pscid->pid == PIDSI_COMMENTS)
+    {
+        vt = VT_BSTR;
+        return SHFSF_COL_COMMENT;
+    }
+    return E_INVALIDARG;
+}
+
+HRESULT
+SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid)
+{
+    for (UINT i = 0; i <= SHCIDS_COLUMNMASK; ++i)
+    {
+        SHCOLUMNID scid;
+        HRESULT hr = pSF->MapColumnToSCID(i, &scid);
+        if (FAILED(hr))
+            break;
+        if (IsEqualPropertyKey(scid, *pscid))
+            return i;
+    }
+    return E_FAIL;
+}
+
+HRESULT
+SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar)
+{
+    V_VT(pVar) = VT_EMPTY;
+    SHELLDETAILS sd;
+    sd.str.uType = STRRET_CSTR;
+    HRESULT hr = pSF->GetDetailsOf(pidl, Column, &sd);
+    if (FAILED(hr))
+        return hr;
+    if (FAILED(hr = StrRetToBSTR(&sd.str, pidl, &V_BSTR(pVar))))
+        return hr;
+    V_VT(pVar) = VT_BSTR;
+    return hr;
+}
+
+HRESULT
+SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar)
+{
+    HRESULT hr = SHELL_GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar);
+    if (FAILED(hr))
+        return hr;
+    if (vt == VT_EMPTY)
+    {
+        SHCOLSTATEF state;
+        if (FAILED(pSF->GetDefaultColumnState(Column, &state)))
+            state = SHCOLSTATE_TYPE_STR;
+        if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_INT)
+            vt = VT_I8;
+        else if ((state & SHCOLSTATE_TYPEMASK) == SHCOLSTATE_TYPE_DATE)
+            vt = VT_DATE;
+        else
+            vt = VT_BSTR;
+    }
+    if (vt != VT_BSTR)
+        VariantChangeType(pVar, pVar, 0, vt);
+    return hr;
+}
+
+HRESULT
+SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL Direct)
+{
+    VARTYPE vt = VT_EMPTY;
+    HRESULT hr = Direct ? MapSCIDToShell32FsColumn(pscid, vt) : SHELL_MapSCIDToColumn(pSF, pscid);
path: dll/win32/shell32/shlfolder.cpp
user: login: whindsaks
body: I will change the parameter name.
created_at: 2025-04-11T19:39:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1362,8 +1391,14 @@ HRESULT WINAPI SHGetDataFromIDListW(LPSHELLFOLDER psf, LPCITEMIDLIST pidl,
             pfd->cAlternateFileName[13] = 0;
         return S_OK;
 
-    case SHGDFIL_NETRESOURCE:
     case SHGDFIL_DESCRIPTIONID:
+        {
+            /* TODO: Use PKEY_DescriptionID when the propsys headers are ready */
+            SHCOLUMNID scid = { FMTID_ShellDetails, PID_DESCRIPTIONID };
+            return GetShellFolder1ItemDetailsExToBuffer(psf, pidl, &scid, dest, len);
+        }
path: dll/win32/shell32/wine/pidl.c
user: login: whindsaks
body: That will make it

```
switch ()
{
case :
{
}
}
```

which is ugly but OK.
created_at: 2025-04-11T19:44:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -215,6 +215,73 @@ LPWSTR SH_FormatFileSizeWithBytes(PULARGE_INTEGER lpQwSize, LPWSTR pszBuf, UINT
 HRESULT WINAPI DoRegisterServer(void);
 HRESULT WINAPI DoUnregisterServer(void);
 
+/* Property system */
+static inline HRESULT
+SHELL_CreateVariantBufferEx(VARIANT *pVar, UINT cb, VARTYPE vt)
+{
+    SAFEARRAY *pSA = SafeArrayCreateVector(vt, 0, cb);
+    if (pSA)
+    {
+        V_VT(pVar) = VT_ARRAY | vt;
+        V_ARRAY(pVar) = pSA;
+        return S_OK;
+    }
+    return E_OUTOFMEMORY;
+}
+
+static inline HRESULT
+SHELL_CreateVariantBuffer(VARIANT *pVar, UINT cb)
+{
+    return SHELL_CreateVariantBufferEx(pVar, cb, VT_UI1);
+}
+
+static inline HRESULT
+SHELL_InitVariantFromBuffer(VARIANT *pVar, const void *pData, UINT cb)
+{
+    HRESULT hr = SHELL_CreateVariantBuffer(pVar, cb);
+    if (SUCCEEDED(hr))
+        CopyMemory(V_ARRAY(pVar)->pvData, pData, cb);
+    return hr;
+}
+
+static inline void*
+SHELL_GetSafeArrayDataPtr(const SAFEARRAY *pSA, SIZE_T *pcb)
+{
+    if (pSA->cDims != 1)
+        return NULL;
+    LONG lob, upb;
+    SafeArrayGetLBound((SAFEARRAY*)pSA, 1, &lob);
+    SafeArrayGetUBound((SAFEARRAY*)pSA, 1, &upb);
+    *pcb = (SIZE_T)(upb - lob) + 1;
+    return pSA->pvData;
+}
+
+static inline HRESULT
+SHELL_VariantToBuffer(VARIANT *pVar, void *pData, SIZE_T cb)
+{
+    if (V_VT(pVar) != (VT_ARRAY | VT_UI1))
+        return E_INVALIDARG;
+    SIZE_T cbArr;
+    void *pArrData = SHELL_GetSafeArrayDataPtr(V_ARRAY(pVar), &cbArr);
+    if (!pArrData || cbArr < cb)
path: dll/win32/shell32/wine/shell32_main.h
user: login: HBelusca
body: This is.... interesting, but ok let's accept what they say. (I wonder whether we have a test for this.)
created_at: 2025-04-11T20:43:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: HBelusca
created_at: 2025-04-11T18:40:42Z
body: > The Init/FromBuffer functions are modeled after functions with the same names in propsys.dll but we can't use that directly here since it's Vista+.

Maybe this could be somewhat mitigated by making propsys a static library that is linked into shell32 when we're in NT5 compilation mode, and that static library also linked into the propsys.dll build target to make the actual dll. And/or, have a propsys"_vista" dll that the shell32 links to it (meaning, that if we want to test our shell32 on windows 2003, we would also bring our propsys dll along).
author_association: CONTRIBUTOR
user: login: whindsaks
created_at: 2025-04-11T19:07:20Z
body: > Maybe this could be somewhat mitigated by making propsys a static library that is linked into shell32 when we're in NT5 compilation mode.

That might be a good idea for the future, at some point if IShellItem2 is to be supported in NT5 mode, we might need a lot more of propsys.

So far, we only really need InitVariantFromBuffer (GetDetailsEx) and VariantToBuffer (SHGetDataFromIDListW).

Having all the PKEY GUIDs defined would also help and that is just a header fix.

The final option for testing on 2003 would be to force the tester to install the Windows Desktop Search application because that is the origin of propsys.dll and is fully supported on NT5 (might not support all the functions Vista has however).


author_association: CONTRIBUTOR