From 79761d88fdf3903057bbe222d3ba9560c6574e87 Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Sat, 4 Jul 2026 01:10:31 +0900 Subject: [PATCH] [SHELL32][SHLWAPI][SHELL32_APITEST][SDK] Support AssocCreateElement (#9215) Prepare for CLSID_QueryAssociations. JIRA issue: CORE-20467 - Add dll/win32/shell32/evalcmd/ elements.cpp. - Implement AssocCreateElement function in elements.cpp. - Modify shell32.spec. - Fix shlwapi!AssocCreate by using AssocCreateElement. - Add SKGetValueW and SKSetValueW prototypes into . - Add AssocCreateElement prototype into . - Add some CLSIDs into . - Add some resource strings into shlwapi. - Add AssocGetPerceivedType prototype into . - Add AssocCreateElement testcase into shell32_apitest. --- dll/win32/shell32/evalcmd/CMakeLists.txt | 8 +- dll/win32/shell32/evalcmd/elements.cpp | 1699 +++++++++++++++++ dll/win32/shell32/evalcmd/evalcmd.cpp | 10 +- dll/win32/shell32/evalcmd/resource.h | 7 + dll/win32/shell32/shell32.spec | 2 +- dll/win32/shlwapi/assoc.c | 24 + dll/win32/shlwapi/lang/da-DK.rc | 3 + dll/win32/shlwapi/lang/de-DE.rc | 3 + dll/win32/shlwapi/lang/en-US.rc | 3 + dll/win32/shlwapi/lang/es-ES.rc | 3 + dll/win32/shlwapi/lang/fi-FI.rc | 3 + dll/win32/shlwapi/lang/fr-FR.rc | 3 + dll/win32/shlwapi/lang/he-IL.rc | 3 + dll/win32/shlwapi/lang/hu-HU.rc | 3 + dll/win32/shlwapi/lang/it-IT.rc | 3 + dll/win32/shlwapi/lang/ja-JP.rc | 3 + dll/win32/shlwapi/lang/ko-KR.rc | 3 + dll/win32/shlwapi/lang/lt-LT.rc | 3 + dll/win32/shlwapi/lang/nl-NL.rc | 3 + dll/win32/shlwapi/lang/no-NO.rc | 3 + dll/win32/shlwapi/lang/pl-PL.rc | 3 + dll/win32/shlwapi/lang/pt-PT.rc | 3 + dll/win32/shlwapi/lang/ro-RO.rc | 3 + dll/win32/shlwapi/lang/ru-RU.rc | 3 + dll/win32/shlwapi/lang/sk-SK.rc | 3 + dll/win32/shlwapi/lang/sl-SI.rc | 3 + dll/win32/shlwapi/lang/sq-AL.rc | 3 + dll/win32/shlwapi/lang/sv-SE.rc | 3 + dll/win32/shlwapi/lang/tr-TR.rc | 3 + dll/win32/shlwapi/lang/uk-UA.rc | 3 + dll/win32/shlwapi/lang/zh-CN.rc | 3 + dll/win32/shlwapi/lang/zh-TW.rc | 3 + dll/win32/shlwapi/resource.h | 4 + .../apitests/shell32/AssocCreateElement.cpp | 582 ++++++ .../rostests/apitests/shell32/CMakeLists.txt | 1 + modules/rostests/apitests/shell32/testlist.c | 2 + sdk/include/psdk/shlwapi.h | 10 + sdk/include/reactos/evalcmd.h | 25 + sdk/include/reactos/shlguid_undoc.h | 12 + sdk/include/reactos/shlobj_undoc.h | 28 + sdk/include/reactos/shlwapi_undoc.h | 9 + 41 files changed, 2495 insertions(+), 6 deletions(-) create mode 100644 dll/win32/shell32/evalcmd/elements.cpp create mode 100644 dll/win32/shell32/evalcmd/resource.h create mode 100644 modules/rostests/apitests/shell32/AssocCreateElement.cpp create mode 100644 sdk/include/reactos/evalcmd.h diff --git a/dll/win32/shell32/evalcmd/CMakeLists.txt b/dll/win32/shell32/evalcmd/CMakeLists.txt index cd849596fd5..8e6287d585f 100644 --- a/dll/win32/shell32/evalcmd/CMakeLists.txt +++ b/dll/win32/shell32/evalcmd/CMakeLists.txt @@ -1,7 +1,11 @@ -add_library(evalcmd STATIC evalcmd.cpp) +list(APPEND SOURCE + evalcmd.cpp + elements.cpp) + +add_library(evalcmd STATIC ${SOURCE}) add_dependencies(evalcmd psdk) -target_link_libraries(evalcmd PRIVATE pathcch_static) +target_link_libraries(evalcmd PRIVATE wine uuid pathcch_static cppstl) target_compile_definitions(evalcmd PRIVATE STATIC_PATHCCH) if(DLL_EXPORT_VERSION GREATER_EQUAL 0x600) diff --git a/dll/win32/shell32/evalcmd/elements.cpp b/dll/win32/shell32/evalcmd/elements.cpp new file mode 100644 index 00000000000..1b4f4dd49f3 --- /dev/null +++ b/dll/win32/shell32/evalcmd/elements.cpp @@ -0,0 +1,1699 @@ +/* + * PROJECT: ReactOS Shell + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: AssocCreateElement + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "evalcmd.h" +#include "resource.h" + +#include +WINE_DEFAULT_DEBUG_CHANNEL(evalcmd); + +typedef HRESULT (CALLBACK* QUERY_CALLBACK)(IQuerySourceOld*, ASSOCQUERY query, PCWSTR keyName, + PCWSTR valueName, PVOID pValue); + +typedef struct QUERYKEYVAL +{ + ASSOCQUERY query; + PCWSTR key; + PCWSTR value; +} QUERYKEYVAL, *PQUERYKEYVAL; + +#define ASSOCQUERY_DE (ASSOCQUERY_DIRECT | ASSOCQUERY_EXISTS) +#define ASSOCQUERY_SDE (ASSOCQUERY_STRING | ASSOCQUERY_DE) +#define ASSOCQUERY_SDED (ASSOCQUERY_SDE | ASSOCQUERY_DWORD) +#define ASSOCQUERY_SDEI (ASSOCQUERY_SDE | ASSOCQUERY_INDIRECT) +#define ASSOCQUERY_SDEEV (ASSOCQUERY_SDE | ASSOCQUERY_EXTRA_VERB) + +#define ASSOCQUERY_DEFAULTICON (ASSOCQUERY_SDE | 0x1) +#define ASSOCQUERY_CLSID (ASSOCQUERY_SDE | 0x3) +#define ASSOCQUERY_PROGID (ASSOCQUERY_SDE | 0x4) +#define ASSOCQUERY_SHELLEX (ASSOCQUERY_SDE | ASSOCQUERY_FALLBACK | ASSOCQUERY_EXTRA_NON_VERB | 0x2) +#define ASSOCQUERY_NOACTIVATEHANDLER (ASSOCQUERY_DE | ASSOCQUERY_EXTRA_VERB | 0x5) +#define ASSOCQUERY_COMMAND_COMMAND (ASSOCQUERY_DE | ASSOCQUERY_EXTRA_VERB | 0x6) +#define ASSOCQUERY_FRIENDLYAPPNAME ASSOCQUERY_SDEI +#define ASSOCQUERY_VERB_FRIENDLYAPPNAME (ASSOCQUERY_SDEI | ASSOCQUERY_EXTRA_VERB | 0x8) +#define ASSOCQUERY_COMMAND ASSOCQUERY_SDEEV +#define ASSOCQUERY_DDEEXEC (ASSOCQUERY_SDEEV | 0x1) +#define ASSOCQUERY_DDEEXEC_IFEXEC (ASSOCQUERY_SDEEV | 0x2) +#define ASSOCQUERY_DDEEXEC_APPLICATION (ASSOCQUERY_SDEEV | 0x3) +#define ASSOCQUERY_DDEEXEC_TOPIC (ASSOCQUERY_SDEEV | 0x4) +#define ASSOCQUERY_LOCALIZEDSTRING (ASSOCQUERY_SDEI | ASSOCQUERY_EXTRA_NON_VERB | 0x1) +#define ASSOCQUERY_SEV_7 (ASSOCQUERY_STRING | ASSOCQUERY_EXTRA_VERB | 0x7) +#define ASSOCQUERY_APPKEY_FRIENDLYAPPNAME (ASSOCQUERY_SDEEV | ASSOCQUERY_INDIRECT | 0x8) +#define ASSOCQUERY_CONTENT_TYPE (ASSOCQUERY_SDE | ASSOCQUERY_FALLBACK | 0x2) +#define ASSOCQUERY_DELEGATE (ASSOCQUERY_STRING | ASSOCQUERY_EXTRA_VERB | 0x7) +#define ASSOCQUERY_OBJECT_EV_1 (ASSOCQUERY_OBJECT | ASSOCQUERY_EXTRA_VERB | 0x1) + +static const QUERYKEYVAL g_perceivedKeyVals[] = +{ + { ASSOCQUERY_FRIENDLYAPPNAME, NULL, L"FriendlyTypeName" }, + { ASSOCQUERY_DEFAULTICON, L"DefaultIcon", NULL }, +}; + +static const QUERYKEYVAL g_shellKeyVals[] = +{ + { ASSOCQUERY_FRIENDLYAPPNAME, NULL, L"FriendlyTypeName" }, + { ASSOCQUERY_DEFAULTICON, L"DefaultIcon", NULL }, + { ASSOCQUERY_CLSID, L"Clsid", NULL }, + { ASSOCQUERY_PROGID, L"Progid", NULL }, + { ASSOCQUERY_SHELLEX, L"ShellEx\\%s", NULL }, +}; + +static const QUERYKEYVAL g_shellVerbKeyVals[] = +{ + { ASSOCQUERY_COMMAND, L"command", NULL }, + { ASSOCQUERY_DDEEXEC, L"ddeexec", NULL }, + { ASSOCQUERY_DDEEXEC_IFEXEC, L"ddeexec\\ifexec", NULL }, + { ASSOCQUERY_DDEEXEC_APPLICATION, L"ddeexec\\application", NULL }, + { ASSOCQUERY_DDEEXEC_TOPIC, L"ddeexec\\topic", NULL }, + { ASSOCQUERY_NOACTIVATEHANDLER, L"ddeexec", L"NoActivateHandler" }, + { ASSOCQUERY_COMMAND_COMMAND, L"command", L"command" }, + { ASSOCQUERY_VERB_FRIENDLYAPPNAME, NULL, L"FriendlyAppName" }, +}; + +static const QUERYKEYVAL g_shellAppKeyVals[] = +{ + { ASSOCQUERY_APPKEY_FRIENDLYAPPNAME, NULL, L"FriendlyAppName" }, +}; + +static const QUERYKEYVAL g_progidKeyVals[2] = +{ + { ASSOCQUERY_SHELLEX, L"ShellEx\\%s", NULL }, + { ASSOCQUERY_CONTENT_TYPE, NULL, L"Content Type" }, +}; + +static const QUERYKEYVAL* +_FindKeyVal(ASSOCQUERY query, const QUERYKEYVAL* pItems, UINT cItems) +{ + for (UINT iItem = 0; iItem < cItems; ++iItem) + { + if (pItems[iItem].query == query) + return &pItems[iItem]; + } + return NULL; +} + +static HRESULT SHAllocMUI(PWSTR* ppwsz) +{ + WCHAR szOutBuf[1024]; + HRESULT hr = SHLoadIndirectString(*ppwsz, szOutBuf, _countof(szOutBuf), NULL); + CoTaskMemFree(*ppwsz); + if (FAILED_UNEXPECTEDLY(hr)) + { + *ppwsz = NULL; + return hr; + } + return SHStrDupW(szOutBuf, ppwsz); +} + +static inline HRESULT _SHAllocLoadString(HINSTANCE hInstance, UINT uID, PWSTR* ppwsz) +{ + WCHAR szBuff[MAX_PATH]; + LoadStringW(hInstance, uID, szBuff, _countof(szBuff)); + return SHStrDupW(szBuff, ppwsz); +} + +static HRESULT QSOpen2( + IQuerySourceOld* pSource, + PCWSTR key, + PCWSTR pszSrc, + BOOL bCreate, + IQuerySourceOld** ppSource) +{ + WCHAR szDest[MAX_PATH]; + PathCchCombineEx(szDest, _countof(szDest), key, pszSrc, PATHCCH_NONE); + return pSource->OpenSource(szDest, bCreate, ppSource); +} + +static HRESULT _GetFileTypeName(PWSTR lpsz, PWSTR* ppwsz) +{ + WCHAR szDest[MAX_PATH], szBuff[128]; + HINSTANCE hSHLWAPI = GetModuleHandleW(L"shlwapi.dll"); + + if (!lpsz || *lpsz != '.' || !lpsz[1]) + return _SHAllocLoadString(hSHLWAPI, IDS_FILE, ppwsz); + + CharUpperW(lpsz); + LoadStringW(hSHLWAPI, IDS_ANY_FILE, szBuff, _countof(szBuff)); + StringCchPrintfW(szDest, _countof(szDest), szBuff, lpsz + 1); + return SHStrDupW(szDest, ppwsz); +} + +static inline LSTATUS +_RegQueryString(HKEY hKey, PCWSTR lpSubKey, PWSTR lpData, LONG cchData) +{ + LONG cbData = cchData * sizeof(WCHAR); + return RegQueryValueW(hKey, lpSubKey, lpData, &cbData); +} + +static LSTATUS +_RegSetVolatileString(HKEY hKey, PCWSTR lpSubKey, PCWSTR lpString) +{ + LSTATUS error; + if (lpSubKey && *lpSubKey) + error = RegCreateKeyExW(hKey, lpSubKey, 0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, + NULL, &hKey, NULL); + else + error = RegOpenKeyExW(hKey, NULL, 0, KEY_WRITE, &hKey); + if (error) + return error; + + INT cch = lstrlenW(lpString); + error = RegSetValueExW(hKey, NULL, 0, REG_SZ, (PBYTE)lpString, (cch + 1) * sizeof(WCHAR)); + RegCloseKey(hKey); + return error; +} + +static HRESULT CALLBACK +_QuerySourceDirect( + IQuerySourceOld* pSource, + ASSOCQUERY query, + PCWSTR keyName, + PCWSTR valueName, + PVOID pValue) +{ + return pSource->QueryValueDirect(keyName, valueName, (FLAGGED_BYTE_BLOB**)pValue); +} + +static HRESULT CALLBACK +_QuerySourceDword( + IQuerySourceOld* pSource, + ASSOCQUERY query, + PCWSTR keyName, + PCWSTR valueName, + PVOID pValue) +{ + return pSource->QueryValueDword(keyName, valueName, (PDWORD)pValue); +} + +static HRESULT CALLBACK +_QuerySourceExists( + IQuerySourceOld* pSource, + ASSOCQUERY query, + PCWSTR keyName, + PCWSTR valueName, + PVOID pValue) +{ + return pSource->QueryValueExists(keyName, valueName); +} + +static HRESULT CALLBACK +_QuerySourceString( + IQuerySourceOld* pSource, + ASSOCQUERY query, + PCWSTR keyName, + PCWSTR valueName, + PVOID pValue) +{ + HRESULT hr = pSource->QueryValueString(keyName, valueName, static_cast(pValue)); + if (FAILED_UNEXPECTEDLY(hr) || !(query & ASSOCQUERY_INDIRECT)) + return hr; + return SHAllocMUI(static_cast(pValue)); +} + +static inline HRESULT +_QuerySourceCreateFromKey(HKEY hKey, PCWSTR lpSubKey, BOOL bCreate, IQuerySourceOld** ppSource) +{ + return QuerySourceCreateFromKey(hKey, lpSubKey, bCreate, + IID_PPV_ARG(IQuerySourceOld, ppSource)); +} + +static HRESULT +_QuerySourceCreateFromKey2( + HKEY hKey, + PCWSTR key1, + PCWSTR key2, + IQuerySourceOld** ppSource) +{ + WCHAR szBuff[MAX_PATH]; + + if (key1) + PathCchCombineEx(szBuff, _countof(szBuff), key1, key2, PATHCCH_NONE); + else + StringCchCopyW(szBuff, _countof(szBuff), key2); + + return _QuerySourceCreateFromKey(hKey, szBuff, FALSE, ppSource); +} + +static HRESULT +_AssocOpenRegKey(HKEY hKey, PCWSTR lpSubKey, PHKEY phkResult, BOOL bCreate) +{ + *phkResult = NULL; + if (!hKey) + return HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION); + + LSTATUS error; + if ( bCreate ) + error = RegCreateKeyExW(hKey, lpSubKey, 0, NULL, 0, MAXIMUM_ALLOWED, NULL, + phkResult, NULL); + else + error = RegOpenKeyExW(hKey, lpSubKey, 0, MAXIMUM_ALLOWED, phkResult); + + if (error == ERROR_SUCCESS) + return S_OK; + + return HRESULT_FROM_WIN32(error); +} + +static HKEY _OpenProgidKey(PCWSTR psz) +{ + HKEY hKeyBase; + HRESULT hr = _AssocOpenRegKey(HKEY_CLASSES_ROOT, psz, &hKeyBase, FALSE); + if (FAILED_UNEXPECTEDLY(hr)) + return NULL; + + WCHAR szData[64]; + DWORD cbData = sizeof(szData); + if (StrCmpIW(L"Excel.Sheet.8", psz) == 0) + return hKeyBase; + + LSTATUS error = SHGetValueW(hKeyBase, L"CurVer", NULL, NULL, szData, &cbData); + if (error || cbData <= 2) + return hKeyBase; + + HKEY hKeyCurVer; + hr = _AssocOpenRegKey(HKEY_CLASSES_ROOT, szData, &hKeyCurVer, FALSE); + if (FAILED_UNEXPECTEDLY(hr)) + return hKeyBase; + + HKEY hKeyShell; + hr = _AssocOpenRegKey(hKeyCurVer, L"shell", &hKeyShell, FALSE); + if (FAILED_UNEXPECTEDLY(hr)) + { + hr = _AssocOpenRegKey(hKeyBase, L"shell", &hKeyShell, FALSE); + if (FAILED_UNEXPECTEDLY(hr)) + { + RegCloseKey(hKeyBase); + return hKeyCurVer; + } + + RegCloseKey(hKeyShell); + RegCloseKey(hKeyCurVer); + return hKeyBase; + } + + RegCloseKey(hKeyShell); + RegCloseKey(hKeyBase); + return hKeyCurVer; +} + +static inline void _MakeApplicationsKey(PCWSTR pszPath, PWSTR pszDest, UINT cchDest) +{ + HRESULT hr = PathCchCombineEx(pszDest, cchDest, L"Applications", pszPath, PATHCCH_NONE); + if (FAILED_UNEXPECTEDLY(hr)) + return; + + PathCchAddExtension(pszDest, cchDest, L".exe"); +} + +static inline BOOL _ParamIsApp(PCWSTR psz) +{ + return !StrCmpNW(psz, L"%1", 2) || !StrCmpNW(psz, L"\"%1\"", 4); +} + +static HRESULT _PathFileExists(PCWSTR lpFileName) +{ + DWORD attrs; + if ( !PathFileExistsAndAttributesW(lpFileName, &attrs) || (attrs & FILE_ATTRIBUTE_DIRECTORY)) + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + return S_OK; +} + +static HRESULT _ExeFromCmd(PCWSTR pszCmdTemplate, PWSTR* ppszApplication) +{ + if (_ParamIsApp(pszCmdTemplate)) + return SHStrDupW(L"%1", ppszApplication); + + PWSTR pszParameters = NULL; + HRESULT hr = SHEvaluateSystemCommandTemplate(pszCmdTemplate, ppszApplication, + NULL, &pszParameters); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + if (hr != S_OK || StrCmpIW(PathFindFileNameW(*ppszApplication), L"rundll32.exe") != 0) + { + CoTaskMemFree(pszParameters); + return hr; + } + + CoTaskMemFree(*ppszApplication); + *ppszApplication = NULL; + + PWSTR pchComma = StrChrW(pszParameters, L','); + if (!pchComma) + { + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + CoTaskMemFree(pszParameters); + return hr; + } + + WCHAR szExe[MAX_PATH]; + hr = _PathCopyExeAndTrimWhiteSpaces(szExe, _countof(szExe), pszParameters, + pchComma - pszParameters); + if (FAILED_UNEXPECTEDLY(hr)) + { + CoTaskMemFree(pszParameters); + return hr; + } + + PathUnquoteSpacesW(szExe); + + PathCchAddExtension(szExe, _countof(szExe), L".dll"); + + if (PathIsAbsolute(szExe)) + { + hr = _PathFileExists(szExe); + } + else if (PathIsFileSpecW(szExe)) + { + hr = _PathFindInSystem(szExe, _countof(szExe)); + } + else + { + CoTaskMemFree(pszParameters); + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + if (SUCCEEDED(hr)) + hr = SHStrDupW(szExe, ppszApplication); + + CoTaskMemFree(pszParameters); + return hr; +} + +/****************************************************************************** + * CAssocElement + */ +class CAssocElement + : public IObjectWithQuerySourceOld + , public IAssociationElementOld +{ +protected: + LONG m_cRefs = 1; + IQuerySourceOld* m_pSource = NULL; + + HRESULT _QueryKeyValAny( + QUERY_CALLBACK callback, + const QUERYKEYVAL* pItems, + UINT cItems, + IQuerySourceOld* pQS, + ASSOCQUERY query, + PCWSTR valueName, + PVOID pValue); + + HRESULT _QuerySourceAny( + QUERY_CALLBACK callback, + IQuerySourceOld* pSource, + DWORD dwFlags, + ASSOCQUERY query, + PCWSTR valueName, + PVOID pValue); + +public: + virtual ~CAssocElement(); + + /*** IUnknown ***/ + STDMETHODIMP QueryInterface(REFIID riid, PVOID* ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + /*** IObjectWithQuerySourceOld ***/ + STDMETHODIMP SetSource(IQuerySourceOld* pSource) override; + STDMETHODIMP GetSource(REFIID riid, PVOID* ppSource) override; + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + STDMETHODIMP QueryDword(ASSOCQUERY query, PCWSTR key, DWORD* pdwValue) override; + STDMETHODIMP QueryExists(ASSOCQUERY query, PCWSTR key) override; + STDMETHODIMP QueryDirect(ASSOCQUERY query, PCWSTR key, FLAGGED_BYTE_BLOB** ppBlob) override; + STDMETHODIMP QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) override; + +protected: + virtual UINT _GetQueryKeyVal(const QUERYKEYVAL** ppItems); +}; + +/****************************************************************************** + * CAssocShellElement + */ +class CAssocShellElement + : public CAssocElement + , public IPersistString2 +{ +protected: + PWSTR m_pszName = NULL; + WCHAR m_szBuff[64] = L""; + + UINT _GetQueryKeyVal(const QUERYKEYVAL** ppItems) override; + HRESULT _DefaultVerbSource(IQuerySourceOld** ppSource); + +public: + ~CAssocShellElement(); + + /*** IUnknown ***/ + STDMETHODIMP QueryInterface(REFIID riid, PVOID* ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + STDMETHODIMP QueryDword(ASSOCQUERY query, PCWSTR key, DWORD* pdwValue) override; + STDMETHODIMP QueryExists(ASSOCQUERY query, PCWSTR key) override; + STDMETHODIMP QueryDirect(ASSOCQUERY query, PCWSTR key, FLAGGED_BYTE_BLOB** ppBlob) override; + STDMETHODIMP QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) override; + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + /*** IPersistString2 ***/ + STDMETHODIMP SetString(PCWSTR psz) override; + STDMETHODIMP GetString(PWSTR* ppsz) override; + + virtual BOOL _UseEnumForDefaultVerb(); + virtual HRESULT _InitSource(); + virtual BOOL _IsAppSource(); + virtual HRESULT _GetVerbDelegate(PCWSTR pszSrc, IAssociationElementOld** ppElement); +}; + +/****************************************************************************** + * CAssocShellVerbElement + */ +class CAssocShellVerbElement + : public CAssocElement +{ +protected: + BOOL m_bAppSource = FALSE; + + UINT _GetQueryKeyVal(const QUERYKEYVAL** ppItems) override; + HRESULT _GetAppDelegate(REFIID riid, PVOID* ppv); + +public: + CAssocShellVerbElement(BOOL bAppSource); + + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppwsz) override; + STDMETHODIMP QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) override; +}; + +/****************************************************************************** + * CAssocApplicationElement + */ +class CAssocApplicationElement + : public CAssocShellElement +{ +protected: + BOOL m_bHasDir = FALSE; + + UINT _GetQueryKeyVal(const QUERYKEYVAL** ppItems) override; + HRESULT _InitSource() override; + HRESULT _GetAppDisplayName(PWSTR* ppsz); + +public: + STDMETHODIMP GetClassID(CLSID* pClsid) override; + STDMETHODIMP QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppv) override; + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppsz) override; +}; + +/****************************************************************************** + * CAssocProgidElement + */ +class CAssocProgidElement + : public CAssocShellElement +{ + IQuerySourceOld* m_pSource2 = NULL; + +public: + ~CAssocProgidElement() override; + + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + BOOL _UseEnumForDefaultVerb() override; + HRESULT _InitSource() override; +}; + +/****************************************************************************** + * CAssocClsidElement + */ +class CAssocClsidElement + : public CAssocShellElement +{ +public: + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + HRESULT _InitSource() override; +}; + +/****************************************************************************** + * CAssocSystemExtElement + */ +class CAssocSystemExtElement + : public CAssocShellElement +{ +public: + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + HRESULT _InitSource() override; +}; + +/****************************************************************************** + * CAssocFolderElement + */ +class CAssocFolderElement + : public CAssocShellElement +{ +protected: + +public: + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + HRESULT _InitSource() override; +}; + +/****************************************************************************** + * CAssocStarElement + */ +class CAssocStarElement + : public CAssocShellElement +{ +protected: + +public: + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + HRESULT _InitSource() override; +}; + +/****************************************************************************** + * CAssocPerceivedElement + */ +class CAssocPerceivedElement + : public CAssocShellElement +{ +protected: + BOOL m_bPerceived = FALSE; + + UINT _GetQueryKeyVal(const QUERYKEYVAL** ppItems) override; + +public: + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + /*** IObjectWithQuerySourceOld ***/ + STDMETHODIMP GetSource(REFIID riid, PVOID* ppSource) override; + + HRESULT _InitSource() override; + HRESULT _GetVerbDelegate(PCWSTR pszSrc, IAssociationElementOld** ppElement) override; +}; + +/****************************************************************************** + * CAssocClientElement + */ +class CAssocClientElement + : public CAssocShellElement +{ +public: + /*** IAssociationElementOld ***/ + STDMETHODIMP QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) override; + /*** IPersist ***/ + STDMETHODIMP GetClassID(CLSID* pClassID) override; + + HRESULT _InitSource() override; + HRESULT _FixNetscapeRegistration(); + BOOL _CreateRepairedNetscapeRegistration(HKEY hKey); + HRESULT _InitSourceFromKey(HKEY hKey, PCWSTR lpSubKey, DWORD dwFlags); +}; + +/****************************************************************************** + * CAssocElement methods + */ + +CAssocElement::~CAssocElement() +{ + if (m_pSource) + { + m_pSource->Release(); + m_pSource = NULL; + } +} + +UINT CAssocElement::_GetQueryKeyVal(const QUERYKEYVAL** ppItems) +{ + *ppItems = NULL; + return 0; +} + +HRESULT +CAssocElement::_QueryKeyValAny( + QUERY_CALLBACK callback, + const QUERYKEYVAL* pItems, + UINT cItems, + IQuerySourceOld* pQS, + ASSOCQUERY query, + PCWSTR valueName, + PVOID pValue) +{ + const QUERYKEYVAL* pItem = _FindKeyVal(query, pItems, cItems); + if (!pItem) + return E_INVALIDARG; + + WCHAR szBuff[128]; + PCWSTR key = pItem->key; + if ((query & ASSOCQUERY_EXTRA_NON_VERB) && key) + { + StringCchPrintfW(szBuff, _countof(szBuff), key, valueName); + key = szBuff; + } + return callback(pQS, query, key, pItem->value, pValue); +} + +HRESULT +CAssocElement::_QuerySourceAny( + QUERY_CALLBACK callback, + IQuerySourceOld* pSource, + DWORD dwFlags, + ASSOCQUERY query, + PCWSTR valueName, + PVOID pValue) +{ + if (!pSource) + return E_INVALIDARG; + + if (query == (ASSOCQUERY_SDED | ASSOCQUERY_EXTRA_NON_VERB) || + query == ASSOCQUERY_LOCALIZEDSTRING) + { + return callback(pSource, query, NULL, valueName, pValue); + } + + if ((query & dwFlags) != dwFlags) + return E_INVALIDARG; + + const QUERYKEYVAL* pItems = NULL; + UINT cItems = _GetQueryKeyVal(&pItems); + if (!cItems) + return E_INVALIDARG; + + return _QueryKeyValAny(callback, pItems, cItems, pSource, query, valueName, pValue); +} + +STDMETHODIMP CAssocElement::QueryInterface(REFIID riid, PVOID* ppv) +{ + if (riid == IID_IObjectWithQuerySourceOld) + { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + if (riid == IID_IAssociationElementOld) + { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + ERR("E_NOINTERFACE: %s\n", wine_dbgstr_guid(&riid)); + *ppv = NULL; + return E_NOINTERFACE; +} + +STDMETHODIMP_(ULONG) CAssocElement::AddRef() +{ + return InterlockedIncrement(&m_cRefs); +} + +STDMETHODIMP_(ULONG) CAssocElement::Release() +{ + LONG refs = InterlockedDecrement(&m_cRefs); + if (!refs) + delete this; + return refs; +} + +STDMETHODIMP CAssocElement::SetSource(IQuerySourceOld* pSource) +{ + if (m_pSource) + return E_UNEXPECTED; + m_pSource = pSource; + m_pSource->AddRef(); + return S_OK; +} + +STDMETHODIMP CAssocElement::GetSource(REFIID riid, PVOID* ppSource) +{ + if (!m_pSource) + { + *ppSource = NULL; + return E_NOINTERFACE; + } + return m_pSource->QueryInterface(riid, ppSource); +} + +STDMETHODIMP CAssocElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + *ppszValue = NULL; + return _QuerySourceAny(_QuerySourceString, m_pSource, (ASSOCQUERY_STRING | ASSOCQUERY_DIRECT), + query, key, ppszValue); +} + +STDMETHODIMP CAssocElement::QueryDword(ASSOCQUERY query, PCWSTR key, DWORD* pdwValue) +{ + return _QuerySourceAny(_QuerySourceDword, m_pSource, (ASSOCQUERY_DWORD | ASSOCQUERY_DIRECT), + query, key, pdwValue); +} + +STDMETHODIMP CAssocElement::QueryExists(ASSOCQUERY query, PCWSTR key) +{ + return _QuerySourceAny(_QuerySourceExists, m_pSource, (ASSOCQUERY_STRING | ASSOCQUERY_DIRECT), + query, key, NULL); +} + +STDMETHODIMP CAssocElement::QueryDirect(ASSOCQUERY query, PCWSTR key, FLAGGED_BYTE_BLOB** ppBlob) +{ + *ppBlob = NULL; + return _QuerySourceAny(_QuerySourceDirect, m_pSource, ASSOCQUERY_DIRECT, query, key, ppBlob); +} + +STDMETHODIMP CAssocElement::QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) +{ + *ppvObj = NULL; + return E_NOTIMPL; +} + +/****************************************************************************** + * CAssocShellElement methods + */ + +CAssocShellElement::~CAssocShellElement() +{ + if (m_pszName && m_pszName != m_szBuff) + LocalFree(m_pszName); +} + +STDMETHODIMP CAssocShellElement::QueryInterface(REFIID riid, PVOID* ppv) +{ + if (riid == IID_IPersist) + { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + if (riid == IID_IPersistString2) + { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + return CAssocElement::QueryInterface(riid, ppv); +} + +STDMETHODIMP_(ULONG) CAssocShellElement::AddRef() +{ + return CAssocElement::AddRef(); +} + +STDMETHODIMP_(ULONG) CAssocShellElement::Release() +{ + return CAssocElement::Release(); +} + +HRESULT CAssocShellElement::_InitSource() +{ + return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, m_pszName, 0, &m_pSource); +} + +UINT CAssocShellElement::_GetQueryKeyVal(const QUERYKEYVAL** ppItems) +{ + *ppItems = g_shellKeyVals; + return _countof(g_shellKeyVals); +} + +BOOL CAssocShellElement::_UseEnumForDefaultVerb() +{ + return FALSE; +} + +BOOL CAssocShellElement::_IsAppSource() +{ + return FALSE; +} + +HRESULT CAssocShellElement::_GetVerbDelegate(PCWSTR pszSrc, IAssociationElementOld** ppElement) +{ + if (!m_pSource) + return E_FAIL; + + HRESULT hr; + IQuerySourceOld* pSource; + if (pszSrc) + hr = QSOpen2(m_pSource, L"shell", pszSrc, 0, &pSource); + else + hr = _DefaultVerbSource(&pSource); + + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + BOOL bAppSource = _IsAppSource(); + auto pElement = new(std::nothrow) CAssocShellVerbElement(bAppSource); + if (!pElement) + { + ERR("E_OUTOFMEMORY\n"); + pSource->Release(); + return E_OUTOFMEMORY; + } + + hr = pElement->SetSource(pSource); + *ppElement = static_cast(pElement); + pSource->Release(); + return hr; +} + +STDMETHODIMP CAssocShellElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + if (!(query & ASSOCQUERY_EXTRA_VERB)) + return CAssocElement::QueryString(query, key, ppszValue); + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = pDelegate->QueryString(query, NULL, ppszValue); + pDelegate->Release(); + return hr; +} + +STDMETHODIMP CAssocShellElement::QueryDword(ASSOCQUERY query, PCWSTR key, DWORD* pdwValue) +{ + if (!(query & ASSOCQUERY_EXTRA_VERB)) + return CAssocElement::QueryDword(query, key, pdwValue); + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = pDelegate->QueryDword(query, NULL, pdwValue); + pDelegate->Release(); + return hr; +} + +STDMETHODIMP CAssocShellElement::QueryExists(ASSOCQUERY query, PCWSTR key) +{ + if (!(query & ASSOCQUERY_EXTRA_VERB)) + return CAssocElement::QueryExists(query, key); + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = pDelegate->QueryExists(query, NULL); + pDelegate->Release(); + return hr; +} + +STDMETHODIMP +CAssocShellElement::QueryDirect(ASSOCQUERY query, PCWSTR key, FLAGGED_BYTE_BLOB** ppBlob) +{ + if (!(query & ASSOCQUERY_EXTRA_VERB)) + return CAssocElement::QueryDirect(query, key, ppBlob); + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = pDelegate->QueryDirect(query, NULL, ppBlob); + pDelegate->Release(); + return hr; +} + +STDMETHODIMP +CAssocShellElement::QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) +{ + if (!(query & ASSOCQUERY_EXTRA_VERB)) + { + *ppvObj = NULL; + return E_INVALIDARG; + } + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + if (query == (ASSOCQUERY_OBJECT | ASSOCQUERY_EXTRA_VERB)) + hr = pDelegate->QueryInterface(riid, ppvObj); + else + hr = pDelegate->QueryObject(query, NULL, riid, ppvObj); + + pDelegate->Release(); + return hr; +} + +STDMETHODIMP CAssocShellElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocShellElement; + return S_OK; +} + +STDMETHODIMP CAssocShellElement::SetString(PCWSTR psz) +{ + if (m_pszName) + return E_UNEXPECTED; + + UINT cchBuff, cchString = lstrlenW(psz); + PWSTR pszBuff; + if (cchString >= _countof(m_szBuff)) + { + cchBuff = cchString + 1; + pszBuff = (PWSTR)LocalAlloc(LPTR, cchBuff * sizeof(WCHAR)); + } + else + { + pszBuff = m_szBuff; + cchBuff = _countof(m_szBuff); + } + + m_pszName = pszBuff; + if (!m_pszName) + return E_UNEXPECTED; + + StringCchCopyW(pszBuff, cchBuff, psz); + return _InitSource(); +} + +STDMETHODIMP CAssocShellElement::GetString(PWSTR* ppsz) +{ + return SHStrDupW(m_pszName, ppsz); +} + +HRESULT CAssocShellElement::_DefaultVerbSource(IQuerySourceOld** ppSource) +{ + IQuerySourceOld* pShellSource; + HRESULT hr = m_pSource->OpenSource(L"shell", FALSE, &pShellSource); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + PWSTR pszValue; + PCWSTR pszVerb = L"open"; + if (SUCCEEDED(pShellSource->QueryValueString(NULL, NULL, &pszValue))) + pszVerb = pszValue; + + hr = pShellSource->OpenSource(pszVerb, FALSE, ppSource); + if (FAILED_UNEXPECTEDLY(hr)) + { + if (pszValue) + { + INT nDelim = StrCSpnW(pszValue, L" ,"); + if (nDelim != lstrlenW(pszValue)) + { + pszValue[nDelim] = UNICODE_NULL; + hr = pShellSource->OpenSource(pszValue, FALSE, ppSource); + } + } + else + { + IEnumString* pEnum; + if (_UseEnumForDefaultVerb() && SUCCEEDED(pShellSource->EnumSources(&pEnum))) + { + PWSTR pszFirst = NULL; + ULONG cFetched = 0; + if (SUCCEEDED(pEnum->Next(1, &pszFirst, &cFetched))) + { + hr = pShellSource->OpenSource(pszFirst, FALSE, ppSource); + CoTaskMemFree(pszFirst); + } + pEnum->Release(); + } + } + } + + if (pszValue) + CoTaskMemFree(pszValue); + + pShellSource->Release(); + return hr; +} + +/****************************************************************************** + * CAssocShellVerbElement methods + */ + +CAssocShellVerbElement::CAssocShellVerbElement(BOOL bAppSource) + : m_bAppSource(bAppSource) +{ +} + +STDMETHODIMP CAssocShellVerbElement::QueryString( + ASSOCQUERY query, + PCWSTR key, + PWSTR* ppwsz) +{ + HRESULT hr = CAssocElement::QueryString(query, key, ppwsz); + if (SUCCEEDED(hr)) + return hr; + + PWSTR psz; + if (query == ASSOCQUERY_SEV_7) + { + hr = CAssocElement::QueryString(ASSOCQUERY_COMMAND, 0, (PWSTR*)&psz); + if (SUCCEEDED(hr)) + hr = _ExeFromCmd(psz, ppwsz); + if (psz) + CoTaskMemFree(psz); + return hr; + } + + if (query == ASSOCQUERY_DDEEXEC_APPLICATION) + { + hr = QueryString(ASSOCQUERY_DELEGATE, NULL, &psz); + if (SUCCEEDED(hr)) + { + PathStripPathW(psz); + PathRemoveExtensionW(psz); + } + *ppwsz = psz; + return hr; + } + + if (query == ASSOCQUERY_DDEEXEC_TOPIC) + return SHStrDupW(L"System", ppwsz); + + if (!m_bAppSource && query == ASSOCQUERY_VERB_FRIENDLYAPPNAME) + { + IAssociationElementOld* pElement; + hr = _GetAppDelegate(IID_PPV_ARG(IAssociationElementOld, &pElement)); + if (SUCCEEDED(hr)) + { + hr = pElement->QueryString(ASSOCQUERY_APPKEY_FRIENDLYAPPNAME, NULL, ppwsz); + pElement->Release(); + } + } + + return hr; +} + +UINT CAssocShellVerbElement::_GetQueryKeyVal(const QUERYKEYVAL** ppItems) +{ + *ppItems = g_shellVerbKeyVals; + return _countof(g_shellVerbKeyVals); +} + +HRESULT CAssocShellVerbElement::_GetAppDelegate(REFIID riid, PVOID* ppv) +{ + PWSTR psz = NULL; + HRESULT hr = CAssocElement::QueryString(ASSOCQUERY_DELEGATE, NULL, &psz); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + IPersistString2* pPS2; + hr = AssocCreateElement(CLSID_AssocApplicationElement, IID_PPV_ARG(IPersistString2, &pPS2)); + if (SUCCEEDED(hr)) + { + hr = pPS2->SetString(psz); + if (SUCCEEDED(hr)) + hr = pPS2->QueryInterface(riid, ppv); + pPS2->Release(); + } + + if (psz) + CoTaskMemFree(psz); + + return hr; +} + +STDMETHODIMP +CAssocShellVerbElement::QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppvObj) +{ + if (query != ASSOCQUERY_OBJECT_EV_1) + return E_INVALIDARG; + return _GetAppDelegate(riid, ppvObj); +} + +/****************************************************************************** + * CAssocApplicationElement methods + */ + +UINT CAssocApplicationElement::_GetQueryKeyVal(const QUERYKEYVAL** ppItems) +{ + *ppItems = g_shellAppKeyVals; + return _countof(g_shellAppKeyVals); +} + +HRESULT CAssocApplicationElement::_InitSource() +{ + PWSTR pchFileName = PathFindFileNameW(m_pszName); + + WCHAR szSubKey[MAX_PATH]; + _MakeApplicationsKey(pchFileName, szSubKey, _countof(szSubKey)); + + HRESULT hr = QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, szSubKey, FALSE, + IID_PPV_ARG(IQuerySourceOld, &m_pSource)); + m_bHasDir = (pchFileName != m_pszName); + if (FAILED_UNEXPECTEDLY(hr) && m_bHasDir && PathFileExistsW(m_pszName)) + return S_FALSE; + + return hr; +} + +STDMETHODIMP CAssocApplicationElement::GetClassID(CLSID* pClsid) +{ + *pClsid = CLSID_AssocApplicationElement; + return S_OK; +} + +STDMETHODIMP +CAssocApplicationElement::QueryObject(ASSOCQUERY query, PCWSTR key, REFIID riid, PVOID* ppv) +{ + if (query == ASSOCQUERY_OBJECT_EV_1) + return CAssocElement::QueryInterface(riid, ppv); + return CAssocShellElement::QueryObject(query, key, riid, ppv); +} + +STDMETHODIMP CAssocApplicationElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppsz) +{ + HRESULT hr = CAssocShellElement::QueryString(query, key, ppsz); + if (FAILED_UNEXPECTEDLY(hr) && query == ASSOCQUERY_APPKEY_FRIENDLYAPPNAME) + return _GetAppDisplayName(ppsz); + + return hr; +} + +HRESULT CAssocApplicationElement::_GetAppDisplayName(PWSTR* ppsz) +{ + PWSTR pszName; + if (!m_bHasDir) + { + HRESULT hr = QueryString(ASSOCQUERY_DELEGATE, NULL, &pszName); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + } + else + { + pszName = m_pszName; + } + + WCHAR szData[MAX_PATH]; + DWORD cbData = sizeof(szData); + DWORD flags = SHKEY_Subkey_MUICache | SHKEY_Key_ShellNoRoam | SHKEY_Root_HKCU; + HRESULT hr = SKGetValueW(flags, NULL, pszName, 0, szData, &cbData); + if (FAILED_UNEXPECTEDLY(hr)) + { + UINT cchData = MAX_PATH; + if (SHGetFileDescriptionW(pszName, NULL, NULL, szData, &cchData)) + { + INT cchName = lstrlenW(szData); + UINT cbName = (cchName + 1) * sizeof(WCHAR); + SKSetValueW(flags, NULL, pszName, REG_SZ, (PBYTE)szData, cbName); + hr = S_OK; + } + } + + if (SUCCEEDED(hr)) + hr = SHStrDupW(szData, ppsz); + + if (!m_bHasDir) + CoTaskMemFree(pszName); + + return hr; +} + +/****************************************************************************** + * CAssocProgidElement methods + */ + +CAssocProgidElement::~CAssocProgidElement() +{ + if (m_pSource2) + { + m_pSource2->Release(); + m_pSource2 = NULL; + } +} + +STDMETHODIMP CAssocProgidElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + HRESULT hr = CAssocShellElement::QueryString(query, key, ppszValue); + if (SUCCEEDED(hr)) + return hr; + + if (m_pSource2 && (query & ASSOCQUERY_FALLBACK)) + { + return _QueryKeyValAny(_QuerySourceString, + g_progidKeyVals, _countof(g_progidKeyVals), + m_pSource2, + query, + key, + ppszValue); + } + + if (m_pSource && query == ASSOCQUERY_FRIENDLYAPPNAME) + return m_pSource->QueryValueString(NULL, NULL, ppszValue); + + return hr; +} + +STDMETHODIMP CAssocProgidElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocProgidElement; + return S_OK; +} + +BOOL CAssocProgidElement::_UseEnumForDefaultVerb() +{ + return TRUE; +} + +HRESULT CAssocProgidElement::_InitSource() +{ + HRESULT hr = S_OK; + PWSTR pszName; + + if (*m_pszName == L'.') + { + hr = _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, m_pszName, 0, &m_pSource2); + if (FAILED_UNEXPECTEDLY(hr)) + goto Exit; + + hr = m_pSource2->QueryValueString(NULL, NULL, &pszName); + } + else + { + pszName = m_pszName; + } + + if (SUCCEEDED(hr)) + { + HKEY hKey = _OpenProgidKey(pszName); + if (hKey) + { + hr = _QuerySourceCreateFromKey(hKey, NULL, NULL, &m_pSource); + RegCloseKey(hKey); + } + else + { + hr = E_UNEXPECTED; + } + + if (pszName != m_pszName) + CoTaskMemFree(pszName); + } + +Exit: + if (FAILED_UNEXPECTEDLY(hr) && m_pSource2) + { + m_pSource = m_pSource2; + m_pSource2 = NULL; + return S_FALSE; + } + + return hr; +} + +/****************************************************************************** + * CAssocClsidElement methods + */ + +STDMETHODIMP CAssocClsidElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocClsidElement; + return S_OK; +} + +HRESULT CAssocClsidElement::_InitSource() +{ + return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"CLSID", m_pszName, &m_pSource); +} + +/****************************************************************************** + * CAssocSystemExtElement methods + */ + +STDMETHODIMP CAssocSystemExtElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocSystemElement; + return S_OK; +} + +HRESULT CAssocSystemExtElement::_InitSource() +{ + return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", m_pszName, + &m_pSource); +} + +/****************************************************************************** + * CAssocFolderElement methods + */ + +STDMETHODIMP CAssocFolderElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + if (query == ASSOCQUERY_FRIENDLYAPPNAME) + return _SHAllocLoadString(GetModuleHandleW(L"shlwapi.dll"), IDS_FOLDER, ppszValue); + return CAssocShellElement::QueryString(query, key, ppszValue); +} + +STDMETHODIMP CAssocFolderElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocFolderElement; + return S_OK; +} + +HRESULT CAssocFolderElement::_InitSource() +{ + return QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"Folder", FALSE, + IID_PPV_ARG(IQuerySourceOld, &m_pSource)); +} + +/****************************************************************************** + * CAssocStarElement methods + */ + +STDMETHODIMP CAssocStarElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + if (query == ASSOCQUERY_FRIENDLYAPPNAME) + return _GetFileTypeName(m_pszName, ppszValue); + + return CAssocShellElement::QueryString(query, key, ppszValue); +} + +STDMETHODIMP CAssocStarElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocStarElement; + return S_OK; +} + +HRESULT CAssocStarElement::_InitSource() +{ + return QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"*", FALSE, + IID_PPV_ARG(IQuerySourceOld, &m_pSource)); +} + +/****************************************************************************** + * CAssocPerceivedElement methods + */ + +UINT CAssocPerceivedElement::_GetQueryKeyVal(const QUERYKEYVAL** ppItems) +{ + if (m_bPerceived) + { + *ppItems = g_perceivedKeyVals; + return _countof(g_perceivedKeyVals); + } + else + { + *ppItems = g_shellKeyVals; + return _countof(g_shellKeyVals); + } +} + +STDMETHODIMP CAssocPerceivedElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocPerceivedElement; + return S_OK; +} + +STDMETHODIMP CAssocPerceivedElement::GetSource(REFIID riid, PVOID* ppSource) +{ + *ppSource = NULL; + if (m_bPerceived) + return E_FAIL; + + return CAssocElement::GetSource(riid, ppSource); +} + +HRESULT CAssocPerceivedElement::_InitSource() +{ + PERCEIVED type; + PERCEIVEDFLAG flags; + PWSTR pszType; + HRESULT hr = AssocGetPerceivedType(m_pszName, &type, &flags, &pszType); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + m_bPerceived = !(flags & PERCEIVEDFLAG_NATIVESUPPORT); + hr = _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", pszType, + &m_pSource); + CoTaskMemFree(pszType); + return hr; +} + +HRESULT CAssocPerceivedElement::_GetVerbDelegate(PCWSTR pszSrc, IAssociationElementOld** ppElement) +{ + if (m_bPerceived) + return E_FAIL; + + return CAssocShellElement::_GetVerbDelegate(pszSrc, ppElement); +} + +/****************************************************************************** + * CAssocClientElement methods + */ + +STDMETHODIMP CAssocClientElement::QueryString(ASSOCQUERY query, PCWSTR key, PWSTR* ppszValue) +{ + HRESULT hr; + + if (query == ASSOCQUERY_DEFAULTICON) + { + hr = CAssocElement::QueryString(ASSOCQUERY_DEFAULTICON, key, ppszValue); + if (SUCCEEDED(hr)) + return hr; + + IAssociationElementOld* pDelegate; + HRESULT hr = _GetVerbDelegate(key, &pDelegate); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = pDelegate->QueryString(ASSOCQUERY_DELEGATE, L"open", ppszValue); + pDelegate->Release(); + return hr; + } + + if (query == ASSOCQUERY_FRIENDLYAPPNAME) + { + hr = CAssocElement::QueryString(ASSOCQUERY_LOCALIZEDSTRING, L"LocalizedString", ppszValue); + if (SUCCEEDED(hr)) + return hr; + return CAssocElement::QueryString((ASSOCQUERY_SDED | ASSOCQUERY_EXTRA_NON_VERB), NULL, ppszValue); + } + + return CAssocShellElement::QueryString(query, key, ppszValue); +} + +STDMETHODIMP CAssocClientElement::GetClassID(CLSID* pClassID) +{ + *pClassID = CLSID_AssocClientElement; + return S_OK; +} + +HRESULT CAssocClientElement::_InitSource() +{ + WCHAR szBuff[MAX_PATH]; + StringCchPrintfW(szBuff, _countof(szBuff), L"SOFTWARE\\Clients\\%s", m_pszName); + + HRESULT hr = _InitSourceFromKey(HKEY_CURRENT_USER, szBuff, 0); + if (SUCCEEDED(hr)) + return hr; + return _InitSourceFromKey(HKEY_LOCAL_MACHINE, szBuff, 0); +} + +HRESULT CAssocClientElement::_FixNetscapeRegistration() +{ + HKEY hKeyParent; + LSTATUS error = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Clients\\Mail", 0, NULL, 0, + KEY_ALL_ACCESS, NULL, &hKeyParent, NULL); + if (error) + return E_FAIL; + + DWORD dwDisposition; + HKEY hKey; + error = RegCreateKeyExW(hKeyParent, L"Netscape Messenger", 0, NULL, REG_OPTION_VOLATILE, + KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition); + if (error) + { + SHDeleteKeyW(hKeyParent, L"Netscape Messenger"); + RegCloseKey(hKeyParent); + return E_FAIL; + } + + HRESULT hr = E_FAIL; + if (dwDisposition == REG_OPENED_EXISTING_KEY || _CreateRepairedNetscapeRegistration(hKey)) + { + IQuerySourceOld* pNewSource = NULL; + hr = _QuerySourceCreateFromKey(hKey, NULL, NULL, &pNewSource); + if (SUCCEEDED(hr)) + { + if (m_pSource) + m_pSource->Release(); + m_pSource = pNewSource; + } + } + + RegCloseKey(hKey); + + if (FAILED_UNEXPECTEDLY(hr)) + SHDeleteKeyW(hKeyParent, L"Netscape Messenger"); + + RegCloseKey(hKeyParent); + return hr; +} + +BOOL CAssocClientElement::_CreateRepairedNetscapeRegistration(HKEY hKey) +{ + HKEY hkResult; + LSTATUS error = RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"Software\\Clients\\Mail\\Netscape Messenger", + 0, KEY_READ, &hkResult); + if (error) + return FALSE; + + WCHAR szIconFile[MAX_PATH]; + error = _RegQueryString(hkResult, L"Protocols\\mailto\\DefaultIcon", + szIconFile, _countof(szIconFile)); + if (error == ERROR_SUCCESS) + { + PathParseIconLocationW(szIconFile); + StringCchCatW(szIconFile, _countof(szIconFile), L",-1349"); + _RegSetVolatileString(hKey, L"DefaultIcon", szIconFile); + } + + BOOL ret = FALSE; + PWSTR lpString2 = NULL; + if (_RegQueryString(hkResult, 0, szIconFile, _countof(szIconFile)) == ERROR_SUCCESS && + _RegSetVolatileString(hKey, 0, szIconFile) == ERROR_SUCCESS && + _RegQueryString(hkResult, L"Protocols\\mailto\\shell\\open\\command", + szIconFile, _countof(szIconFile)) == ERROR_SUCCESS && + SUCCEEDED(_ExeFromCmd(szIconFile, &lpString2))) + { + StringCchCopyW(szIconFile, _countof(szIconFile), lpString2); + CoTaskMemFree(lpString2); + PathQuoteSpacesW(szIconFile); + StringCchCatW(szIconFile, _countof(szIconFile), L" -mail"); + if (_RegSetVolatileString(hKey, L"shell\\open\\command", szIconFile) == ERROR_SUCCESS) + ret = TRUE; + } + + RegCloseKey(hkResult); + return ret; +} + +HRESULT CAssocClientElement::_InitSourceFromKey(HKEY hKey, PCWSTR lpSubKey, DWORD dwFlags) +{ + HKEY hKey2; + LSTATUS error = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ | dwFlags, &hKey2); + if (error) + return E_INVALIDARG; + + HRESULT hr = E_INVALIDARG; + HKEY hkeySrc = NULL; + WCHAR szData[80]; + DWORD dwType, cbData = sizeof(szData); + error = RegQueryValueExW(hKey2, NULL, NULL, &dwType, (PBYTE)szData, &cbData); + if (error == ERROR_SUCCESS && dwType == REG_SZ && szData[0] && + RegOpenKeyExW(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &hkeySrc) == ERROR_SUCCESS) + { + hr = _QuerySourceCreateFromKey2(hkeySrc, NULL, szData, &m_pSource); + RegCloseKey(hkeySrc); + } + + RegCloseKey(hKey2); + + if (FAILED_UNEXPECTEDLY(hr) || + lstrcmpiW(m_pszName, L"mail") != 0 || + lstrcmpiW(szData, L"Netscape Messenger") != 0 || + SUCCEEDED(QueryExists(ASSOCQUERY_COMMAND, L"open"))) + { + return hr; + } + + return _FixNetscapeRegistration(); +} + +/****************************************************************************** + * AssocCreateElement [SHELL32.764] (Vista+) + * AssocCreateElement [SHLWAPI internal] (XP) + * + * @see AssocCreate + * @see https://www.geoffchappell.com/studies/windows/shell/shell32/api/assocelem/createelement.htm + */ +EXTERN_C HRESULT WINAPI +AssocCreateElement(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ PVOID* ppvObj) +{ + IAssociationElementOld* pElement; + if (rclsid == CLSID_AssocShellElement) + { + auto pNewElement = new(std::nothrow) CAssocShellElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocApplicationElement) + { + auto pNewElement = new(std::nothrow) CAssocApplicationElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocProgidElement) + { + auto pNewElement = new(std::nothrow) CAssocProgidElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocClsidElement) + { + auto pNewElement = new(std::nothrow) CAssocClsidElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocSystemElement) + { + auto pNewElement = new(std::nothrow) CAssocSystemExtElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocFolderElement) + { + auto pNewElement = new(std::nothrow) CAssocFolderElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocStarElement) + { + auto pNewElement = new(std::nothrow) CAssocStarElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocPerceivedElement) + { + auto pNewElement = new(std::nothrow) CAssocPerceivedElement(); + pElement = static_cast(pNewElement); + } + else if (rclsid == CLSID_AssocClientElement) + { + auto pNewElement = new(std::nothrow) CAssocClientElement(); + pElement = static_cast(pNewElement); + } + else + { + ERR("rclsid: %s\n", wine_dbgstr_guid(&rclsid)); + *ppvObj = NULL; + return CLASS_E_CLASSNOTAVAILABLE; + } + + if (!pElement) + { + ERR("E_OUTOFMEMORY\n"); + return E_OUTOFMEMORY; + } + + HRESULT hr = pElement->QueryInterface(riid, ppvObj); + pElement->Release(); + return hr; +} diff --git a/dll/win32/shell32/evalcmd/evalcmd.cpp b/dll/win32/shell32/evalcmd/evalcmd.cpp index a52c08e3429..b16dcf792d7 100644 --- a/dll/win32/shell32/evalcmd/evalcmd.cpp +++ b/dll/win32/shell32/evalcmd/evalcmd.cpp @@ -14,6 +14,10 @@ #include #include #include +#include "evalcmd.h" + +#include +WINE_DEFAULT_DEBUG_CHANNEL(evalcmd); #define PATH_VALID_CHARS \ (PATH_CHAR_CLASS_DOT | PATH_CHAR_CLASS_SEMICOLON | PATH_CHAR_CLASS_COMMA | \ @@ -45,7 +49,7 @@ static PCWSTR _PathGetArgsLikeCreateProcess(PCWSTR lpString) return &lpString[lstrlenW(lpString)]; } -static HRESULT +EXTERN_C HRESULT _PathCopyExeAndTrimWhiteSpaces(PWSTR pszBuff, size_t cchBuff, PCWSTR pszSrc, size_t cchSrc) { HRESULT hr = StringCchCopyNW(pszBuff, cchBuff, pszSrc, cchSrc); @@ -140,7 +144,7 @@ static HRESULT _PathExeExists(_In_ PCWSTR pszPath) return S_OK; } -static HRESULT +EXTERN_C HRESULT _PathFindInFolder(_In_ INT csidl, _In_ PCWSTR pszSrc, _Out_ PWSTR pszPath, _In_ UINT cchPath) { WCHAR szDir[MAX_PATH]; @@ -155,7 +159,7 @@ _PathFindInFolder(_In_ INT csidl, _In_ PCWSTR pszSrc, _Out_ PWSTR pszPath, _In_ return _PathExeExists(pszPath); } -static HRESULT _PathFindInSystem(_Inout_ PWSTR pszPath, _In_ UINT cchPath) +EXTERN_C HRESULT _PathFindInSystem(_Inout_ PWSTR pszPath, _In_ UINT cchPath) { WCHAR szPath[MAX_PATH]; HRESULT hr = _PathFindInFolder(CSIDL_SYSTEM, pszPath, szPath, _countof(szPath)); diff --git a/dll/win32/shell32/evalcmd/resource.h b/dll/win32/shell32/evalcmd/resource.h new file mode 100644 index 00000000000..9e933f2b2c3 --- /dev/null +++ b/dll/win32/shell32/evalcmd/resource.h @@ -0,0 +1,7 @@ + +#pragma once + +// These values must match shlwapi's ones +#define IDS_ANY_FILE 4864 +#define IDS_FILE 4865 +#define IDS_FOLDER 4866 diff --git a/dll/win32/shell32/shell32.spec b/dll/win32/shell32/shell32.spec index 100557c4ed8..c7ec83efbd1 100644 --- a/dll/win32/shell32/shell32.spec +++ b/dll/win32/shell32/shell32.spec @@ -326,7 +326,7 @@ 761 stub -version=0x600+ -noname SHChangeNotifyDeregisterWindow 762 stub -version=0x600+ -noname Create_IUICommandFromDef 763 stub -version=0x600+ -noname Create_IEnumUICommandFromDefArray -764 stub -version=0x600+ -noname AssocCreateElement +764 stdcall -version=0x600+ -noname AssocCreateElement(ptr ptr ptr) 766 stub -version=0x600+ -noname SHCopyStreamWithProgress 777 stub -version=0x600+ -noname SHGetAssocKeys 778 stub -version=0x600+ -noname AssocGetPropListForExt diff --git a/dll/win32/shlwapi/assoc.c b/dll/win32/shlwapi/assoc.c index a7e729f0749..5954fd90322 100644 --- a/dll/win32/shlwapi/assoc.c +++ b/dll/win32/shlwapi/assoc.c @@ -28,6 +28,10 @@ #include "shlguid.h" #include "shlobj.h" #include "shlwapi.h" +#ifdef __REACTOS__ +#include "shlwapi_undoc.h" +#include "evalcmd.h" +#endif #include "wine/unicode.h" #include "wine/debug.h" @@ -43,6 +47,10 @@ WINE_DEFAULT_DEBUG_CHANNEL(shell); ASSOCF_INIT_DEFAULTTOFOLDER) #endif +#ifdef __REACTOS__ +EXTERN_C ULONG WINAPI GetProcessOsVersion(void); // FIXME: Move or delete +#endif + /************************************************************************* * SHLWAPI_ParamAToW * @@ -87,9 +95,11 @@ static BOOL SHLWAPI_ParamAToW(LPCSTR lpszParam, LPWSTR lpszBuff, DWORD dwLen, * Success: S_OK. lpInterface contains the new object. * Failure: An HRESULT error code indicating the error. * +#ifndef __REACTOS__ // WRONG NOTES! * NOTES * clsid must be equal to CLSID_QueryAssociations and * refiid must be equal to IID_IQueryAssociations, IID_IUnknown or this function will fail +#endif */ HRESULT WINAPI AssocCreate(CLSID clsid, REFIID refiid, void **lpInterface) { @@ -99,10 +109,24 @@ HRESULT WINAPI AssocCreate(CLSID clsid, REFIID refiid, void **lpInterface) if (!lpInterface) return E_INVALIDARG; +#ifdef __REACTOS__ + *(PVOID*)lpInterface = NULL; +#else *(DWORD*)lpInterface = 0; +#endif +#ifdef __REACTOS__ + if (!IsEqualGUID(&clsid, &CLSID_QueryAssociations) && + !IsEqualGUID(&clsid, &IID_IQueryAssociations)) + { + if (GetProcessOsVersion() < _WIN32_WINNT_VISTA) + return AssocCreateElement(&clsid, refiid, lpInterface); + return CLASS_E_CLASSNOTAVAILABLE; + } +#else if (!IsEqualGUID(&clsid, &CLSID_QueryAssociations)) return CLASS_E_CLASSNOTAVAILABLE; +#endif return SHCoCreateInstance( NULL, &clsid, NULL, refiid, lpInterface ); } diff --git a/dll/win32/shlwapi/lang/da-DK.rc b/dll/win32/shlwapi/lang/da-DK.rc index f743f0a73d2..51e94a61388 100644 --- a/dll/win32/shlwapi/lang/da-DK.rc +++ b/dll/win32/shlwapi/lang/da-DK.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/de-DE.rc b/dll/win32/shlwapi/lang/de-DE.rc index 79392fbdfef..d0de46995f2 100644 --- a/dll/win32/shlwapi/lang/de-DE.rc +++ b/dll/win32/shlwapi/lang/de-DE.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/en-US.rc b/dll/win32/shlwapi/lang/en-US.rc index f274217f5c7..13992eec9ab 100644 --- a/dll/win32/shlwapi/lang/en-US.rc +++ b/dll/win32/shlwapi/lang/en-US.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/es-ES.rc b/dll/win32/shlwapi/lang/es-ES.rc index c13898a86cc..db22d787a3f 100644 --- a/dll/win32/shlwapi/lang/es-ES.rc +++ b/dll/win32/shlwapi/lang/es-ES.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/fi-FI.rc b/dll/win32/shlwapi/lang/fi-FI.rc index 3b452a2fa53..e908ba034b3 100644 --- a/dll/win32/shlwapi/lang/fi-FI.rc +++ b/dll/win32/shlwapi/lang/fi-FI.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/fr-FR.rc b/dll/win32/shlwapi/lang/fr-FR.rc index 0cd0a53b13d..7fa08da46b0 100644 --- a/dll/win32/shlwapi/lang/fr-FR.rc +++ b/dll/win32/shlwapi/lang/fr-FR.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s Eo" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/he-IL.rc b/dll/win32/shlwapi/lang/he-IL.rc index 69b6d829fe0..066a5294552 100644 --- a/dll/win32/shlwapi/lang/he-IL.rc +++ b/dll/win32/shlwapi/lang/he-IL.rc @@ -37,6 +37,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/hu-HU.rc b/dll/win32/shlwapi/lang/hu-HU.rc index 296576e364f..dc4918da5e7 100644 --- a/dll/win32/shlwapi/lang/hu-HU.rc +++ b/dll/win32/shlwapi/lang/hu-HU.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/it-IT.rc b/dll/win32/shlwapi/lang/it-IT.rc index b1d939e0963..ed10a3c7dd3 100644 --- a/dll/win32/shlwapi/lang/it-IT.rc +++ b/dll/win32/shlwapi/lang/it-IT.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/ja-JP.rc b/dll/win32/shlwapi/lang/ja-JP.rc index 3241993119c..e3995e57ae7 100644 --- a/dll/win32/shlwapi/lang/ja-JP.rc +++ b/dll/win32/shlwapi/lang/ja-JP.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "このコンピューターの制限により、この操作は取り消されました。システム管理者にお問い合わせください。" IDS_RESTRICTIONS "システムの制限" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/ko-KR.rc b/dll/win32/shlwapi/lang/ko-KR.rc index e384b74e7a3..4f7afd8d4be 100644 --- a/dll/win32/shlwapi/lang/ko-KR.rc +++ b/dll/win32/shlwapi/lang/ko-KR.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/lt-LT.rc b/dll/win32/shlwapi/lang/lt-LT.rc index 20e6f9ab91c..5aa18fa101d 100644 --- a/dll/win32/shlwapi/lang/lt-LT.rc +++ b/dll/win32/shlwapi/lang/lt-LT.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/nl-NL.rc b/dll/win32/shlwapi/lang/nl-NL.rc index 78a0447b201..21c92fde4aa 100644 --- a/dll/win32/shlwapi/lang/nl-NL.rc +++ b/dll/win32/shlwapi/lang/nl-NL.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/no-NO.rc b/dll/win32/shlwapi/lang/no-NO.rc index cdd2e446173..086496183f8 100644 --- a/dll/win32/shlwapi/lang/no-NO.rc +++ b/dll/win32/shlwapi/lang/no-NO.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/pl-PL.rc b/dll/win32/shlwapi/lang/pl-PL.rc index 05103dd1a08..cb4a50d963e 100644 --- a/dll/win32/shlwapi/lang/pl-PL.rc +++ b/dll/win32/shlwapi/lang/pl-PL.rc @@ -36,6 +36,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "Operacja została anulowana ze względu na ograniczenia nałożone na ten komputer. Skontaktuj się z administratorem systemu." IDS_RESTRICTIONS "Ograniczenia" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/pt-PT.rc b/dll/win32/shlwapi/lang/pt-PT.rc index 3c2d2178cbe..5b8c6759aa4 100644 --- a/dll/win32/shlwapi/lang/pt-PT.rc +++ b/dll/win32/shlwapi/lang/pt-PT.rc @@ -38,6 +38,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/ro-RO.rc b/dll/win32/shlwapi/lang/ro-RO.rc index cd3ee99fbc6..557a4a24aba 100644 --- a/dll/win32/shlwapi/lang/ro-RO.rc +++ b/dll/win32/shlwapi/lang/ro-RO.rc @@ -38,6 +38,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EO" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/ru-RU.rc b/dll/win32/shlwapi/lang/ru-RU.rc index 9b94c7974c7..9d83394fa5d 100644 --- a/dll/win32/shlwapi/lang/ru-RU.rc +++ b/dll/win32/shlwapi/lang/ru-RU.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s ЭБ" IDS_RESTRICTED "Операция отменена из-за ограничений, действующих на этом компьютере. Обратитесь к системному администратору." IDS_RESTRICTIONS "Ограничения" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/sk-SK.rc b/dll/win32/shlwapi/lang/sk-SK.rc index 9e770303fea..7f7e747d052 100644 --- a/dll/win32/shlwapi/lang/sk-SK.rc +++ b/dll/win32/shlwapi/lang/sk-SK.rc @@ -36,6 +36,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/sl-SI.rc b/dll/win32/shlwapi/lang/sl-SI.rc index 16ea6ce9988..ad259807052 100644 --- a/dll/win32/shlwapi/lang/sl-SI.rc +++ b/dll/win32/shlwapi/lang/sl-SI.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/sq-AL.rc b/dll/win32/shlwapi/lang/sq-AL.rc index 282e09fea75..d9044942d6b 100644 --- a/dll/win32/shlwapi/lang/sq-AL.rc +++ b/dll/win32/shlwapi/lang/sq-AL.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/sv-SE.rc b/dll/win32/shlwapi/lang/sv-SE.rc index 4764caf3f0a..cdc20e223fc 100644 --- a/dll/win32/shlwapi/lang/sv-SE.rc +++ b/dll/win32/shlwapi/lang/sv-SE.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/tr-TR.rc b/dll/win32/shlwapi/lang/tr-TR.rc index b38a187321b..242a7a6ee9f 100644 --- a/dll/win32/shlwapi/lang/tr-TR.rc +++ b/dll/win32/shlwapi/lang/tr-TR.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/uk-UA.rc b/dll/win32/shlwapi/lang/uk-UA.rc index 9f50d3371c2..ebbaa498981 100644 --- a/dll/win32/shlwapi/lang/uk-UA.rc +++ b/dll/win32/shlwapi/lang/uk-UA.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/zh-CN.rc b/dll/win32/shlwapi/lang/zh-CN.rc index cc59ca32f46..01c314203dd 100644 --- a/dll/win32/shlwapi/lang/zh-CN.rc +++ b/dll/win32/shlwapi/lang/zh-CN.rc @@ -35,6 +35,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/lang/zh-TW.rc b/dll/win32/shlwapi/lang/zh-TW.rc index fc56b20d75e..1a4d64bf677 100644 --- a/dll/win32/shlwapi/lang/zh-TW.rc +++ b/dll/win32/shlwapi/lang/zh-TW.rc @@ -38,6 +38,9 @@ STRINGTABLE IDS_EB_FORMAT "%s EB" IDS_RESTRICTED "This operation has been cancelled due to restrictions on this computer. Please contact your system administrator." IDS_RESTRICTIONS "Restrictions" + IDS_ANY_FILE "%s File" + IDS_FILE "File" + IDS_FOLDER "Folder" #endif } diff --git a/dll/win32/shlwapi/resource.h b/dll/win32/shlwapi/resource.h index ae3705c2cf7..897136345e0 100644 --- a/dll/win32/shlwapi/resource.h +++ b/dll/win32/shlwapi/resource.h @@ -36,6 +36,10 @@ #define IDS_EB_FORMAT 73 #define IDS_RESTRICTED 283 #define IDS_RESTRICTIONS 284 +/* The following three values must match dll/win32/shell32/evalcmd/resource.h's ones */ +#define IDS_ANY_FILE 4864 +#define IDS_FILE 4865 +#define IDS_FOLDER 4866 #endif /* These numbers match native IDs and shouldn't be arbitrarily changed */ diff --git a/modules/rostests/apitests/shell32/AssocCreateElement.cpp b/modules/rostests/apitests/shell32/AssocCreateElement.cpp new file mode 100644 index 00000000000..88193073fa5 --- /dev/null +++ b/modules/rostests/apitests/shell32/AssocCreateElement.cpp @@ -0,0 +1,582 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) + * PURPOSE: Tests for AssocCreateElement + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#include + +#define WIN32_NO_STATUS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASSOCQUERY_DE (ASSOCQUERY_DIRECT | ASSOCQUERY_EXISTS) +#define ASSOCQUERY_SDE (ASSOCQUERY_STRING | ASSOCQUERY_DE) +#define ASSOCQUERY_SDED (ASSOCQUERY_SDE | ASSOCQUERY_DWORD) +#define ASSOCQUERY_SDEI (ASSOCQUERY_SDE | ASSOCQUERY_INDIRECT) +#define ASSOCQUERY_SDEEV (ASSOCQUERY_SDE | ASSOCQUERY_EXTRA_VERB) + +typedef HRESULT (WINAPI *FN_AssocCreateElement)(REFCLSID, REFIID, PVOID*); + +static FN_AssocCreateElement g_fnAssocCreateElement = NULL; +static BOOL g_bVistaPlus = FALSE; + +static HRESULT MyAssocCreateElement(REFCLSID rclsid, REFIID riid, PVOID* ppvObj) +{ + if (g_fnAssocCreateElement) + return g_fnAssocCreateElement(rclsid, riid, ppvObj); + return AssocCreate(rclsid, riid, ppvObj); +} + +struct CLASS_ENTRY +{ + const CLSID *pclsid; + PCWSTR pszName; +}; + +static const CLASS_ENTRY g_Classes[] = +{ + { &CLSID_AssocShellElement, L"AssocShellElement" }, + { &CLSID_AssocApplicationElement, L"AssocApplicationElement" }, + { &CLSID_AssocProgidElement, L"AssocProgidElement" }, + { &CLSID_AssocClsidElement, L"AssocClsidElement" }, + { &CLSID_AssocSystemElement, L"AssocSystemElement" }, + { &CLSID_AssocFolderElement, L"AssocFolderElement" }, + { &CLSID_AssocStarElement, L"AssocStarElement" }, + { &CLSID_AssocPerceivedElement, L"AssocPerceivedElement" }, + { &CLSID_AssocClientElement, L"AssocClientElement" }, +}; + +// Verify that CLASS_E_CLASSNOTAVAILABLE is returned when an unknown CLSID is passed +static void Test_InvalidClsid(void) +{ + IUnknown *pUnk = (IUnknown *)(LONG_PTR)0xdeadbeef; + HRESULT hr = MyAssocCreateElement(CLSID_NULL, IID_PPV_ARG(IUnknown, &pUnk)); + ok_hex(hr, CLASS_E_CLASSNOTAVAILABLE); + ok(pUnk == NULL, "pUnk should be NULL, got %p\n", pUnk); +} + +// For all public CLSIDs, verify that: +// - instantiation succeeds; +// - IPersist / IPersistString2 / IObjectWithQuerySourceOld can be obtained; and +// - GetClassID returns the CLSID passed during instantiation. +static void Test_QueryInterfaceAndClassID(void) +{ + HRESULT hr; + + for (size_t i = 0; i < _countof(g_Classes); ++i) + { + const CLASS_ENTRY& entry = g_Classes[i]; + + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + hr = MyAssocCreateElement(*entry.pclsid, + IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr == S_OK, "[%s] AssocCreateElement failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (!pElement) + continue; + + IPersist *pPersist = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IPersist, &pPersist)); + ok(hr == S_OK, "[%s] QI(IPersist) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pPersist) + { + CLSID clsid; + hr = pPersist->GetClassID(&clsid); + ok(hr == S_OK, "[%s] GetClassID failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + ok(IsEqualCLSID(clsid, *entry.pclsid), + "[%s] GetClassID returned an unexpected CLSID\n", wine_dbgstr_w(entry.pszName)); + pPersist->Release(); + } + + IPersistString2 *pPS2 = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "[%s] QI(IPersistString2) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pPS2) + pPS2->Release(); + + IObjectWithQuerySourceOld *pOWQS = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IObjectWithQuerySourceOld, &pOWQS)); + ok(hr == S_OK, "[%s] QI(IObjectWithQuerySourceOld) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pOWQS) + pOWQS->Release(); + + pElement->Release(); + } + else + { + IAssociationElementOld *pElement = NULL; + hr = MyAssocCreateElement(*entry.pclsid, + IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr == S_OK, "[%s] AssocCreateElement failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (!pElement) + continue; + + IPersist *pPersist = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IPersist, &pPersist)); + ok(hr == S_OK, "[%s] QI(IPersist) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pPersist) + { + CLSID clsid; + hr = pPersist->GetClassID(&clsid); + ok(hr == S_OK, "[%s] GetClassID failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + ok(IsEqualCLSID(clsid, *entry.pclsid), + "[%s] GetClassID returned an unexpected CLSID\n", wine_dbgstr_w(entry.pszName)); + pPersist->Release(); + } + + IPersistString2 *pPS2 = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "[%s] QI(IPersistString2) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pPS2) + pPS2->Release(); + + IObjectWithQuerySourceOld *pOWQS = NULL; + hr = pElement->QueryInterface(IID_PPV_ARG(IObjectWithQuerySourceOld, &pOWQS)); + ok(hr == S_OK, "[%s] QI(IObjectWithQuerySourceOld) failed: 0x%08lX\n", + wine_dbgstr_w(entry.pszName), hr); + if (pOWQS) + pOWQS->Release(); + + pElement->Release(); + } + } +} + +// Verify that calling QueryString before SetString results in E_INVALIDARG +static void Test_QueryBeforeInit(void) +{ + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr == S_OK, "AssocCreateElement failed: 0x%08lX\n", hr); + if (!pElement) + return; + + PWSTR psz = NULL; + _SEH2_TRY + { + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + } + _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + hr = 0xDEAD; + } + _SEH2_END; + + ok(hr == 0xDEAD, "hr was 0x%08lX\n", hr); + + pElement->Release(); + } + else + { + IAssociationElementOld *pElement = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr == S_OK, "AssocCreateElement failed: 0x%08lX\n", hr); + if (!pElement) + return; + + PWSTR psz = NULL; + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok_hex(hr, E_INVALIDARG); + ok(psz == NULL, "psz should be NULL\n"); + + pElement->Release(); + } +} + +// Verify that calling SetString twice results in E_UNEXPECTED +static void Test_DoubleSetString(void) +{ + IPersistString2 *pPS2 = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "AssocCreateElement failed: 0x%08lX\n", hr); + if (!pPS2) + return; + + hr = pPS2->SetString(L"txtfile"); + ok(hr == S_OK, "First SetString failed: 0x%08lX\n", hr); + + hr = pPS2->SetString(L"txtfile"); + if (g_bVistaPlus) + ok_hex(hr, S_OK); + else + ok_hex(hr, E_UNEXPECTED); + + pPS2->Release(); +} + +// Retrieve FriendlyTypeName / DefaultIcon using CLSID_AssocShellElement + SetString(L"txtfile") +static void Test_ShellElement_txtfile(void) +{ + IPersistString2 *pPS2 = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "AssocCreateElement(AssocShellElement) failed: 0x%08lX\n", hr); + if (!pPS2) + return; + + hr = pPS2->SetString(L"txtfile"); + if (hr != S_OK) + { + skip("\"txtfile\" ProgID was not found in HKCR, skipping\n"); + pPS2->Release(); + return; + } + + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + hr = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr == S_OK, "QI(IAssociationElement) failed: 0x%08lX\n", hr); + if (pElement) + { + PWSTR psz = NULL; + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr == S_OK, "QueryString(FriendlyTypeName) failed: 0x%08lX\n", hr); + ok(psz != NULL && psz[0] != UNICODE_NULL, "Friendly type name is empty\n"); + if (psz) + { + trace("txtfile FriendlyTypeName: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + + hr = pElement->QueryExists((ASSOCQUERY_SDE | 0x1), NULL); + ok(hr == S_OK, "QueryExists(DefaultIcon) failed: 0x%08lX\n", hr); + + pElement->Release(); + } + } + else + { + IAssociationElementOld *pElement = NULL; + hr = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr); + if (pElement) + { + PWSTR psz = NULL; + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr == S_OK, "QueryString(FriendlyTypeName) failed: 0x%08lX\n", hr); + ok(psz != NULL && psz[0] != UNICODE_NULL, "Friendly type name is empty\n"); + if (psz) + { + trace("txtfile FriendlyTypeName: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + + hr = pElement->QueryExists((ASSOCQUERY_SDE | 0x1), NULL); + ok(hr == S_OK, "QueryExists(DefaultIcon) failed: 0x%08lX\n", hr); + + pElement->Release(); + } + } + + pPS2->Release(); +} + + +// A pattern where you create a custom registry source using QuerySourceCreateFromKey +// instead of SetString, and attach it directly via IObjectWithQuerySource[Old]::SetSource. +static void Test_ShellElement_ExplicitSource(void) +{ + HRESULT hr; + if (g_bVistaPlus) + { + IQuerySource *pSource = NULL; + hr = QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"txtfile", FALSE, + IID_PPV_ARG(IQuerySource, &pSource)); + if (FAILED(hr)) + { + skip("QuerySourceCreateFromKey(txtfile) failed: 0x%08lX\n", hr); + return; + } + + IObjectWithQuerySource *pOWQS = NULL; + hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IObjectWithQuerySource, &pOWQS)); + ok(hr == S_OK, "AssocCreateElement(AssocShellElement) failed: 0x%08lX\n", hr); + if (pOWQS) + { + hr = pOWQS->SetSource(pSource); + ok(hr == S_OK, "SetSource failed: 0x%08lX\n", hr); + + IAssociationElement *pElement = NULL; + hr = pOWQS->QueryInterface(IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr); + if (pElement) + { + PWSTR psz = NULL; + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr == S_OK, "QueryString(FriendlyTypeName) failed: 0x%08lX\n", hr); + if (psz) + { + trace("txtfile (explicit source) FriendlyTypeName: %s\n", + wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + + // The second call to SetSource should result in E_UNEXPECTED + hr = pOWQS->SetSource(pSource); + ok_hex(hr, E_UNEXPECTED); + + pOWQS->Release(); + } + + if (pSource) + pSource->Release(); + } + else + { + IQuerySourceOld *pSource = NULL; + hr = QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"txtfile", FALSE, + IID_PPV_ARG(IQuerySourceOld, &pSource)); + if (FAILED(hr)) + { + skip("QuerySourceCreateFromKey(txtfile) failed: 0x%08lX\n", hr); + return; + } + + IObjectWithQuerySourceOld *pOWQS = NULL; + hr = MyAssocCreateElement(CLSID_AssocShellElement, + IID_PPV_ARG(IObjectWithQuerySourceOld, &pOWQS)); + ok(hr == S_OK, "AssocCreateElement(AssocShellElement) failed: 0x%08lX\n", hr); + if (pOWQS) + { + hr = pOWQS->SetSource(pSource); + ok(hr == S_OK, "SetSource failed: 0x%08lX\n", hr); + + IAssociationElementOld *pElement = NULL; + hr = pOWQS->QueryInterface(IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr); + if (pElement) + { + PWSTR psz = NULL; + hr = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr == S_OK, "QueryString(FriendlyTypeName) failed: 0x%08lX\n", hr); + if (psz) + { + trace("txtfile (explicit source) FriendlyTypeName: %s\n", + wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + + // The second call to SetSource should result in E_UNEXPECTED + hr = pOWQS->SetSource(pSource); + ok_hex(hr, E_UNEXPECTED); + + pOWQS->Release(); + } + + if (pSource) + pSource->Release(); + } +} + +// Verify resolution from the file extension using CLSID_AssocProgidElement + SetString(L".txt") +static void Test_ProgidElement_DotTxt(void) +{ + IPersistString2 *pPS2 = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocProgidElement, + IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "AssocCreateElement(AssocProgidElement) failed: 0x%08lX\n", hr); + if (!pPS2) + return; + + hr = pPS2->SetString(L".txt"); + ok(hr == S_OK || hr == S_FALSE, "SetString(.txt) failed: 0x%08lX\n", hr); + + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElement) failed: 0x%08lX\n", hr2); + if (pElement) + pElement->Release(); + } + else + { + IAssociationElementOld *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr2); + if (pElement) + { + PWSTR psz = NULL; + HRESULT hr3 = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr3 == S_OK, "QueryString(FriendlyTypeName) for .txt failed: 0x%08lX\n", hr3); + if (psz) + { + trace(".txt FriendlyTypeName: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + } + + pPS2->Release(); +} + +// CLSID_AssocStarElement: Verify that the generic "File" name can be retrieved even for +// unknown file extensions +static void Test_StarElement_UnknownExt(void) +{ + IPersistString2 *pPS2 = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocStarElement, + IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "AssocCreateElement(AssocStarElement) failed: 0x%08lX\n", hr); + if (!pPS2) + return; + + hr = pPS2->SetString(L".rostestunknownext"); + ok(hr == S_OK, "SetString failed: 0x%08lX\n", hr); + + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElement) failed: 0x%08lX\n", hr2); + if (pElement) + { + PWSTR psz = NULL; + HRESULT hr3 = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr3 == S_OK, "QueryString for unknown extension failed: 0x%08lX\n", hr3); + ok(psz != NULL && psz[0] != UNICODE_NULL, "Generic type name is empty\n"); + if (psz) + { + trace("Generic file type name: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + } + else + { + IAssociationElementOld *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr2); + if (pElement) + { + PWSTR psz = NULL; + HRESULT hr3 = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr3 == S_OK, "QueryString for unknown extension failed: 0x%08lX\n", hr3); + ok(psz != NULL && psz[0] != UNICODE_NULL, "Generic type name is empty\n"); + if (psz) + { + trace("Generic file type name: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + } + + pPS2->Release(); +} + +// CLSID_AssocFolderElement: Always opens HKCR\Folder +static void Test_FolderElement(void) +{ + IPersistString2 *pPS2 = NULL; + HRESULT hr = MyAssocCreateElement(CLSID_AssocFolderElement, + IID_PPV_ARG(IPersistString2, &pPS2)); + ok(hr == S_OK, "AssocCreateElement(AssocFolderElement) failed: 0x%08lX\n", hr); + if (!pPS2) + return; + + hr = pPS2->SetString(L""); + if (g_bVistaPlus) + ok(hr == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER), "SetString hr: 0x%08lX\n", hr); + else + ok(hr == S_OK, "SetString failed: 0x%08lX\n", hr); + + if (g_bVistaPlus) + { + IAssociationElement *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElement, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElement) failed: 0x%08lX\n", hr2); + if (pElement) + { + PWSTR psz = NULL; + HRESULT hr3 = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr3 == S_OK, "QueryString(Folder) failed: 0x%08lX\n", hr3); + if (psz) + { + trace("Folder type name: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + } + else + { + IAssociationElementOld *pElement = NULL; + HRESULT hr2 = pPS2->QueryInterface(IID_PPV_ARG(IAssociationElementOld, &pElement)); + ok(hr2 == S_OK, "QI(IAssociationElementOld) failed: 0x%08lX\n", hr2); + if (pElement) + { + PWSTR psz = NULL; + HRESULT hr3 = pElement->QueryString(ASSOCQUERY_SDEI, NULL, &psz); + ok(hr3 == S_OK, "QueryString(Folder) failed: 0x%08lX\n", hr3); + if (psz) + { + trace("Folder type name: %s\n", wine_dbgstr_w(psz)); + CoTaskMemFree(psz); + } + pElement->Release(); + } + } + + pPS2->Release(); +} + +START_TEST(AssocCreateElement) +{ + g_bVistaPlus = IsWindowsVistaOrGreater(); + + HINSTANCE hShell32 = GetModuleHandleW(L"shell32.dll"); + g_fnAssocCreateElement = + (FN_AssocCreateElement)GetProcAddress(hShell32, MAKEINTRESOURCEA(764)); + if (!g_fnAssocCreateElement) + trace("shell32!AssocCreateElement not found. Using shlwapi!AssocCreate instead...\n"); + + HRESULT hrCo = CoInitialize(NULL); + + Test_InvalidClsid(); + Test_QueryInterfaceAndClassID(); + Test_QueryBeforeInit(); + Test_DoubleSetString(); + Test_ShellElement_txtfile(); + Test_ShellElement_ExplicitSource(); + Test_ProgidElement_DotTxt(); + Test_StarElement_UnknownExt(); + Test_FolderElement(); + + if (SUCCEEDED(hrCo)) + CoUninitialize(); +} diff --git a/modules/rostests/apitests/shell32/CMakeLists.txt b/modules/rostests/apitests/shell32/CMakeLists.txt index 63e5865618b..7f1f6a1c5ea 100644 --- a/modules/rostests/apitests/shell32/CMakeLists.txt +++ b/modules/rostests/apitests/shell32/CMakeLists.txt @@ -3,6 +3,7 @@ spec2def(shell32_apitest.exe shell32_apitest.spec) list(APPEND SOURCE AddCommas.cpp + AssocCreateElement.cpp CFSFolder.cpp CheckEscapes.cpp CIDLData.cpp diff --git a/modules/rostests/apitests/shell32/testlist.c b/modules/rostests/apitests/shell32/testlist.c index 153c72b31a0..217b6857af3 100644 --- a/modules/rostests/apitests/shell32/testlist.c +++ b/modules/rostests/apitests/shell32/testlist.c @@ -3,6 +3,7 @@ #include extern void func_AddCommas(void); +extern void func_AssocCreateElement(void); extern void func_Control_RunDLLW(void); extern void func_CFSFolder(void); extern void func_CheckEscapes(void); @@ -64,6 +65,7 @@ extern void func_StrRStr(void); const struct test winetest_testlist[] = { { "AddCommas", func_AddCommas }, + { "AssocCreateElement", func_AssocCreateElement }, { "Control_RunDLLW", func_Control_RunDLLW }, { "CFSFolder", func_CFSFolder }, { "CheckEscapes", func_CheckEscapes }, diff --git a/sdk/include/psdk/shlwapi.h b/sdk/include/psdk/shlwapi.h index 80631a34e8d..d86eb099cdb 100644 --- a/sdk/include/psdk/shlwapi.h +++ b/sdk/include/psdk/shlwapi.h @@ -999,12 +999,22 @@ LWSTDAPI AssocCreate(_In_ CLSID, _In_ REFIID, _Outptr_ LPVOID*); LWSTDAPI_(BOOL) AssocIsDangerous(_In_ LPCWSTR); #endif // _WIN32_IE_IE60SP1 +typedef INT PERCEIVEDFLAG; +#define PERCEIVEDFLAG_UNDEFINED 0x0000 +#define PERCEIVEDFLAG_SOFTCODED 0x0001 +#define PERCEIVEDFLAG_HARDCODED 0x0002 +#define PERCEIVEDFLAG_NATIVESUPPORT 0x0004 +#define PERCEIVEDFLAG_GDIPLUS 0x0010 +#define PERCEIVEDFLAG_WMSDK 0x0020 +#define PERCEIVEDFLAG_ZIPFOLDER 0x0040 + LWSTDAPI AssocQueryStringA(_In_ ASSOCF, _In_ ASSOCSTR, _In_ LPCSTR, _In_opt_ LPCSTR, _Out_writes_opt_(*pcchOut) LPSTR, _Inout_ LPDWORD pcchOut); LWSTDAPI AssocQueryStringW(_In_ ASSOCF, _In_ ASSOCSTR, _In_ LPCWSTR, _In_opt_ LPCWSTR, _Out_writes_opt_(*pcchOut) LPWSTR, _Inout_ LPDWORD pcchOut); LWSTDAPI AssocQueryStringByKeyA(_In_ ASSOCF, _In_ ASSOCSTR, _In_ HKEY, _In_opt_ LPCSTR, _Out_writes_opt_(*pcchOut) LPSTR, _Inout_ LPDWORD pcchOut); LWSTDAPI AssocQueryStringByKeyW(_In_ ASSOCF, _In_ ASSOCSTR, _In_ HKEY, _In_opt_ LPCWSTR, _Out_writes_opt_(*pcchOut) LPWSTR, _Inout_ LPDWORD pcchOut); LWSTDAPI AssocQueryKeyA(_In_ ASSOCF, _In_ ASSOCKEY, _In_ LPCSTR, _In_opt_ LPCSTR, _Out_ PHKEY); LWSTDAPI AssocQueryKeyW(_In_ ASSOCF, _In_ ASSOCKEY, _In_ LPCWSTR, _In_opt_ LPCWSTR, _Out_ PHKEY); +LWSTDAPI AssocGetPerceivedType(LPCWSTR lpszExt, PERCEIVED *lpType, INT *lpFlag, LPWSTR *lppszType); #ifdef UNICODE #define AssocQueryString AssocQueryStringW diff --git a/sdk/include/reactos/evalcmd.h b/sdk/include/reactos/evalcmd.h new file mode 100644 index 00000000000..27c6f7e8401 --- /dev/null +++ b/sdk/include/reactos/evalcmd.h @@ -0,0 +1,25 @@ +/* + * PROJECT: ReactOS Shell + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: SHEvaluateSystemCommandTemplate + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +HRESULT _PathCopyExeAndTrimWhiteSpaces(PWSTR pszBuff, size_t cchBuff, + PCWSTR pszSrc, size_t cchSrc); +HRESULT _PathFindInFolder(_In_ INT csidl, _In_ PCWSTR pszSrc, _Out_ PWSTR pszPath, + _In_ UINT cchPath); +HRESULT _PathFindInSystem(_Inout_ PWSTR pszPath, _In_ UINT cchPath); + +HRESULT WINAPI +AssocCreateElement(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ PVOID* ppvObj); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/sdk/include/reactos/shlguid_undoc.h b/sdk/include/reactos/shlguid_undoc.h index b423499bd9c..e8c10084dfe 100644 --- a/sdk/include/reactos/shlguid_undoc.h +++ b/sdk/include/reactos/shlguid_undoc.h @@ -220,6 +220,16 @@ DEFINE_GUID(POLID_CompareJunctionness, 0xB3AB7D3B, 0x698F, 0x401E, DEFINE_GUID(IID_IObjectWithRegistryKeyOld, 0x5747C63F, 0x1DE8, 0x423F, 0x98, 0x0F, 0x00, 0xCB, 0x07, 0xF4, 0xC4, 0x5B); DEFINE_GUID(IID_IObjectWithRegistryKey, 0xD960050C, 0xF4E1, 0x4294, 0xAC, 0x4B, 0x59, 0x89, 0x13, 0x60, 0x59, 0x23); +DEFINE_GUID(CLSID_AssocApplicationElement, 0x0C2BF91B, 0x8746, 0x4FB1, 0xB4, 0xD7, 0x7C, 0x03, 0xF8, 0x90, 0xB1, 0x68); +DEFINE_GUID(CLSID_AssocClientElement, 0x3C81E7FA, 0x1F3B, 0x464A, 0xA3, 0x50, 0x11, 0x4A, 0x25, 0xBE, 0xB2, 0xA2); +DEFINE_GUID(CLSID_AssocClsidElement, 0x57AEA081, 0x5EE9, 0x4C27, 0xB2, 0x18, 0xC4, 0xB7, 0x02, 0x96, 0x4C, 0x54); +DEFINE_GUID(CLSID_AssocFolderElement, 0x7566DF7A, 0x42CC, 0x475D, 0xA0, 0x25, 0x12, 0x05, 0xDD, 0xF4, 0x91, 0x1F); +DEFINE_GUID(CLSID_AssocPerceivedElement, 0x0DC5FB21, 0xB93D, 0x4E3D, 0xBB, 0x2F, 0xCE, 0x4E, 0x36, 0xA7, 0x06, 0x01); +DEFINE_GUID(CLSID_AssocProgidElement, 0x9016D0DD, 0x7C41, 0x46CC, 0xA6, 0x64, 0xBF, 0x22, 0xF7, 0xCB, 0x18, 0x6A); +DEFINE_GUID(CLSID_AssocShellElement, 0xC461837F, 0xEA59, 0x494A, 0xB7, 0xC6, 0xCD, 0x04, 0x0E, 0x37, 0x18, 0x5E); +DEFINE_GUID(CLSID_AssocStarElement, 0x0633B720, 0x6926, 0x404C, 0xB6, 0xB3, 0x92, 0x3B, 0x1A, 0x50, 0x17, 0x43); +DEFINE_GUID(CLSID_AssocSystemElement, 0xA6C4BAAD, 0x4AF5, 0x4191, 0x86, 0x85, 0xC2, 0xC8, 0x95, 0x3A, 0x14, 0x8C); + #define CGID_IExplorerToolbar IID_IExplorerToolbar #define SID_IExplorerToolbar IID_IExplorerToolbar #define SID_ITargetFrame2 IID_ITargetFrame2 @@ -231,4 +241,6 @@ DEFINE_GUID(IID_IObjectWithRegistryKey, 0xD960050C, 0xF4E1, 0x4294, #define SID_IBandSite IID_IBandSite #define SID_IShellBrowser IID_IShellBrowser +#define IID_IObjectWithQuerySourceOld IID_IObjectWithQuerySource + #endif // __SHLGUID_UNDOC_H diff --git a/sdk/include/reactos/shlobj_undoc.h b/sdk/include/reactos/shlobj_undoc.h index 03502537c8e..7a30714d816 100644 --- a/sdk/include/reactos/shlobj_undoc.h +++ b/sdk/include/reactos/shlobj_undoc.h @@ -946,9 +946,37 @@ DECLARE_INTERFACE_(IQuerySource, IUnknown) // {7BC28AC2-0D9C-4941-BB9A-72BECB184 #define IQuerySource_OpenSource(T,a,b) (T)->lpVtbl->OpenSource(T,a,b) #endif +/***************************************************************************** + * IObjectWithQuerySourceOld interface + * + * @see IObjectWithQuerySource + * @see https://www.geoffchappell.com/studies/windows/shell/shell32/interfaces/iobjectwithquerysource.htm + */ +#define INTERFACE IObjectWithQuerySourceOld +DECLARE_INTERFACE_(IObjectWithQuerySourceOld, IUnknown) // {B3DCB623-4280-4EB1-84B3-8D07E84F299A} +{ + /*** IUnknown ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID,PVOID*) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + /*** IObjectWithQuerySourceOld ***/ + STDMETHOD(SetSource)(THIS_ IQuerySourceOld *pSource) PURE; + STDMETHOD(GetSource)(THIS_ REFIID riid, PVOID *ppSource) PURE; +}; +#undef INTERFACE + +#ifdef COBJMACROS +#define IObjectWithQuerySourceOld_QueryInterface(T,a,b) (T)->lpVtbl->QueryInterface(T,a,b) +#define IObjectWithQuerySourceOld_AddRef(T) (T)->lpVtbl->AddRef(T) +#define IObjectWithQuerySourceOld_Release(T) (T)->lpVtbl->Release(T) +#define IObjectWithQuerySourceOld_SetSource(T,a) (T)->lpVtbl->SetSource(T,a) +#define IObjectWithQuerySourceOld_GetSource(T,a,b) (T)->lpVtbl->GetSource(T,a,b) +#endif + /***************************************************************************** * IObjectWithQuerySource interface * + * @see IObjectWithQuerySourceOld * @see https://www.geoffchappell.com/studies/windows/shell/shell32/interfaces/iobjectwithquerysource.htm */ #define INTERFACE IObjectWithQuerySource diff --git a/sdk/include/reactos/shlwapi_undoc.h b/sdk/include/reactos/shlwapi_undoc.h index 63b8743a3eb..4d20865b2c6 100644 --- a/sdk/include/reactos/shlwapi_undoc.h +++ b/sdk/include/reactos/shlwapi_undoc.h @@ -358,6 +358,10 @@ enum _shellkey_flags }; HKEY WINAPI SHGetShellKey(DWORD flags, LPCWSTR sub_key, BOOL create); +HRESULT WINAPI SKGetValueW(DWORD flags, LPCWSTR subkey, LPCWSTR value, DWORD *type, + void *data, DWORD *count); +HRESULT WINAPI SKSetValueW(DWORD flags, LPCWSTR subkey, LPCWSTR value, + DWORD type, void *data, DWORD count); int WINAPIV @@ -561,6 +565,11 @@ SHWindowsPolicyEx(_In_ REFGUID rpolid, _In_ DWORD dwDefaultValue) return (SUCCEEDED(hr) ? dwData : dwDefaultValue); } +#if (NTDDI_VERSION >= NTDDI_LONGHORN) || defined(_SHELL32_) +HRESULT WINAPI +AssocCreateElement(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ PVOID* ppvObj); +#endif + /***************************************************************************** * ZoneCheck* */