diff --git a/dll/shellext/CMakeLists.txt b/dll/shellext/CMakeLists.txt index 8d005319c4c1f..a9c8fe256efad 100644 --- a/dll/shellext/CMakeLists.txt +++ b/dll/shellext/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(acppage) +add_subdirectory(cabview) add_subdirectory(cryptext) add_subdirectory(deskadp) add_subdirectory(deskmon) diff --git a/dll/shellext/cabview/CMakeLists.txt b/dll/shellext/cabview/CMakeLists.txt new file mode 100644 index 0000000000000..3a574a8933fd5 --- /dev/null +++ b/dll/shellext/cabview/CMakeLists.txt @@ -0,0 +1,21 @@ + +spec2def(cabview.dll cabview.spec) + +list(APPEND SOURCE + cabview.cpp + extract.cpp + folder.cpp + resource.h) + +add_library(cabview MODULE + ${SOURCE} + cabview.spec + cabview.rc + ${CMAKE_CURRENT_BINARY_DIR}/cabview.def) + +set_module_type(cabview win32dll UNICODE) +target_link_libraries(cabview uuid cpprt atl_classes) +set_target_cpp_properties(cabview WITH_EXCEPTIONS) +add_importlibs(cabview cabinet oleaut32 ole32 shlwapi comctl32 shell32 user32 advapi32 msvcrt kernel32 ntdll) +add_pch(cabview precomp.h SOURCE) +add_cd_file(TARGET cabview DESTINATION reactos/system32 FOR all) diff --git a/dll/shellext/cabview/cabview.cpp b/dll/shellext/cabview/cabview.cpp new file mode 100644 index 0000000000000..fc6d2d7302ed1 --- /dev/null +++ b/dll/shellext/cabview/cabview.cpp @@ -0,0 +1,66 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: DLL entry point + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#include "cabview.h" + +#include +DEFINE_GUID(CLSID_CabFolder, 0x0CD7A5C0,0x9F37,0x11CE,0xAE,0x65,0x08,0x00,0x2B,0x2E,0x12,0x62); + +CComModule g_Module; + +BEGIN_OBJECT_MAP(ObjectMap) + OBJECT_ENTRY(CLSID_CabFolder, CCabFolder) +END_OBJECT_MAP() + +EXTERN_C BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hInstance); + g_Module.Init(ObjectMap, hInstance, NULL); + break; + } + + return TRUE; +} + +STDAPI DllCanUnloadNow() +{ + return g_Module.DllCanUnloadNow(); +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + return g_Module.DllGetClassObject(rclsid, riid, ppv); +} + +STDAPI DllRegisterServer() +{ + HRESULT hr; + + hr = g_Module.DllRegisterServer(FALSE); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + hr = g_Module.UpdateRegistryFromResource(IDR_FOLDER, TRUE, NULL); + if (FAILED(hr)) + return hr; + + return S_OK; +} + +STDAPI DllUnregisterServer() +{ + HRESULT hr1 = g_Module.DllUnregisterServer(FALSE); + HRESULT hr2 = g_Module.UpdateRegistryFromResource(IDR_FOLDER, FALSE, NULL); + if (FAILED_UNEXPECTEDLY(hr1)) + return hr1; + if (FAILED_UNEXPECTEDLY(hr2)) + return hr2; + return S_OK; +} diff --git a/dll/shellext/cabview/cabview.h b/dll/shellext/cabview/cabview.h new file mode 100644 index 0000000000000..dbc20744dfd72 --- /dev/null +++ b/dll/shellext/cabview/cabview.h @@ -0,0 +1,240 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Main header file + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#pragma once +#include "precomp.h" +#include "resource.h" + +#define FLATFOLDER TRUE + +EXTERN_C const GUID CLSID_CabFolder; + +enum EXTRACTCALLBACKMSG { ECM_BEGIN, ECM_FILE, ECM_PREPAREPATH, ECM_ERROR }; +struct EXTRACTCALLBACKDATA +{ + LPCWSTR Path; + const FDINOTIFICATION *pfdin; + HRESULT hr; +}; +typedef HRESULT (CALLBACK*EXTRACTCALLBACK)(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &data, LPVOID cookie); +HRESULT ExtractCabinet(LPCWSTR cab, LPCWSTR destination, EXTRACTCALLBACK callback, LPVOID cookie); + +class CEnumIDList : + public CComObjectRootEx, + public IEnumIDList +{ +protected: + HDPA m_Items; + ULONG m_Pos; + +public: + static int CALLBACK DPADestroyCallback(void *pidl, void *pData) + { + SHFree(pidl); + return TRUE; + } + + CEnumIDList() : m_Pos(0) + { + m_Items = DPA_Create(0); + } + + virtual ~CEnumIDList() + { + DPA_DestroyCallback(m_Items, DPADestroyCallback, NULL); + } + + int FindNamedItem(PCUITEMID_CHILD pidl) const; + HRESULT Fill(LPCWSTR path, HWND hwnd = NULL, SHCONTF contf = 0); + HRESULT Fill(PCIDLIST_ABSOLUTE pidl, HWND hwnd = NULL, SHCONTF contf = 0); + + HRESULT Append(LPCITEMIDLIST pidl) + { + return DPA_AppendPtr(m_Items, (void*)pidl) != DPA_ERR ? S_OK : E_OUTOFMEMORY; + } + + UINT GetCount() const { return m_Items ? DPA_GetPtrCount(m_Items) : 0; } + + // IEnumIDList + IFACEMETHODIMP Next(ULONG celt, PITEMID_CHILD *rgelt, ULONG *pceltFetched) + { + if (!rgelt) + return E_INVALIDARG; + HRESULT hr = S_FALSE; + UINT count = GetCount(), fetched = 0; + if (m_Pos < count && fetched < celt) + { + if (SUCCEEDED(hr = SHILClone(DPA_FastGetPtr(m_Items, m_Pos), &rgelt[fetched]))) + fetched++; + } + if (pceltFetched) + *pceltFetched = fetched; + m_Pos += fetched; + return FAILED(hr) ? hr : (celt == fetched && fetched) ? S_OK : S_FALSE; + } + + IFACEMETHODIMP Reset() + { + m_Pos = 0; + return S_OK; + } + + IFACEMETHODIMP Skip(ULONG celt) + { + UINT count = GetCount(), newpos = m_Pos + celt; + if (celt > count || newpos >= count) + return E_INVALIDARG; + m_Pos = newpos; + return S_OK; + } + + IFACEMETHODIMP Clone(IEnumIDList **ppenum) + { + UNIMPLEMENTED; + *ppenum = NULL; + return E_NOTIMPL; + } + + static CEnumIDList* CreateInstance() + { + CComPtr obj; + return SUCCEEDED(ShellObjectCreator(obj)) ? obj.Detach() : NULL; + } + + DECLARE_NO_REGISTRY() + DECLARE_NOT_AGGREGATABLE(CEnumIDList) + + BEGIN_COM_MAP(CEnumIDList) + COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList) + END_COM_MAP() +}; + +class CCabFolder : + public CComCoClass, + public CComObjectRootEx, + public IShellFolder2, + public IPersistFolder2, + public IShellFolderViewCB, + public IShellIcon +{ +protected: + CComHeapPtr m_CurDir; + HWND m_ShellViewWindow = NULL; + +public: + HRESULT ExtractFilesUI(HWND hWnd, IDataObject *pDO); + HRESULT GetItemDetails(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd, VARIANT *pv); + int MapSCIDToColumn(const SHCOLUMNID &scid); + HRESULT CompareID(LPARAM lParam, PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2); + + HRESULT CreateEnum(CEnumIDList **List) + { + CEnumIDList *pEIDL = CEnumIDList::CreateInstance(); + *List = pEIDL; + return pEIDL ? pEIDL->Fill(m_CurDir) : E_OUTOFMEMORY; + } + + // IShellFolder2 + IFACEMETHODIMP GetDefaultSearchGUID(GUID *pguid) override + { + return E_NOTIMPL; + } + + IFACEMETHODIMP EnumSearches(IEnumExtraSearch **ppenum) override + { + return E_NOTIMPL; + } + + IFACEMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay) override; + + IFACEMETHODIMP GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags) override; + + IFACEMETHODIMP GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) override; + + IFACEMETHODIMP GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd) override; + + IFACEMETHODIMP MapColumnToSCID(UINT column, SHCOLUMNID *pscid) override; + + IFACEMETHODIMP ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes) override + { + UNIMPLEMENTED; + return E_NOTIMPL; + } + + IFACEMETHODIMP EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) override; + + IFACEMETHODIMP BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) override; + + IFACEMETHODIMP BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) override + { + UNIMPLEMENTED; + return E_NOTIMPL; + } + + IFACEMETHODIMP CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) override; + + IFACEMETHODIMP CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut) override; + + IFACEMETHODIMP GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF *rgfInOut) override; + + IFACEMETHODIMP GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT *prgfInOut, LPVOID *ppvOut) override; + + IFACEMETHODIMP GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName) override; + + IFACEMETHODIMP SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut) override + { + return E_NOTIMPL; + } + + // IPersistFolder2 + IFACEMETHODIMP GetCurFolder(PIDLIST_ABSOLUTE *pidl) override + { + LPITEMIDLIST curdir = (LPITEMIDLIST)m_CurDir; + return curdir ? SHILClone(curdir, pidl) : E_UNEXPECTED; + } + + IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidl) override + { + WCHAR path[MAX_PATH]; + if (SHGetPathFromIDListW(pidl, path)) + { + PIDLIST_ABSOLUTE curdir = ILClone(pidl); + if (curdir) + { + m_CurDir.Attach(curdir); + return S_OK; + } + return E_OUTOFMEMORY; + } + return E_INVALIDARG; + } + + IFACEMETHODIMP GetClassID(CLSID *lpClassId) override + { + *lpClassId = CLSID_CabFolder; + return S_OK; + } + + // IShellFolderViewCB + IFACEMETHODIMP MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) override; + + // IShellIcon + IFACEMETHODIMP GetIconOf(PCUITEMID_CHILD pidl, UINT flags, int *pIconIndex) override; + + DECLARE_NO_REGISTRY() + DECLARE_NOT_AGGREGATABLE(CCabFolder) + + BEGIN_COM_MAP(CCabFolder) + COM_INTERFACE_ENTRY_IID(IID_IShellFolder, IShellFolder) + COM_INTERFACE_ENTRY_IID(IID_IShellFolder2, IShellFolder2) + COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersist) + COM_INTERFACE_ENTRY_IID(IID_IPersistFolder, IPersistFolder) + COM_INTERFACE_ENTRY_IID(IID_IPersistFolder2, IPersistFolder2) + COM_INTERFACE_ENTRY_IID(IID_IShellFolderViewCB, IShellFolderViewCB) + COM_INTERFACE_ENTRY_IID(IID_IShellIcon, IShellIcon) + END_COM_MAP() +}; diff --git a/dll/shellext/cabview/cabview.rc b/dll/shellext/cabview/cabview.rc new file mode 100644 index 0000000000000..526888b7b69c6 --- /dev/null +++ b/dll/shellext/cabview/cabview.rc @@ -0,0 +1,75 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Main resource file + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#include +#include + +#include "resource.h" + +IDI_FOLDER ICON "res/folder.ico" + +#define REACTOS_VERSION_DLL +#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Cabinet Shell Extension" +#define REACTOS_STR_INTERNAL_NAME "cabview" +#define REACTOS_STR_ORIGINAL_FILENAME "cabview.dll" +#include + +#include + +IDR_FOLDER REGISTRY "res/cabview.rgs" + +/* UTF-8 */ +#pragma code_page(65001) + +#ifdef LANGUAGE_DE_DE + #include "lang/de-DE.rc" +#endif +#ifdef LANGUAGE_EN_US + #include "lang/en-US.rc" +#endif +#ifdef LANGUAGE_ET_EE + #include "lang/et-EE.rc" +#endif +#ifdef LANGUAGE_FR_FR + #include "lang/fr-FR.rc" +#endif +#ifdef LANGUAGE_HI_IN + #include "lang/hi-IN.rc" +#endif +#ifdef LANGUAGE_IT_IT + #include "lang/it-IT.rc" +#endif +#ifdef LANGUAGE_JA_JP + #include "lang/ja-JP.rc" +#endif +#ifdef LANGUAGE_PL_PL + #include "lang/pl-PL.rc" +#endif +#ifdef LANGUAGE_PT_PT + #include "lang/pt-PT.rc" +#endif +#ifdef LANGUAGE_RO_RO + #include "lang/ro-RO.rc" +#endif +#ifdef LANGUAGE_RU_RU + #include "lang/ru-RU.rc" +#endif +#ifdef LANGUAGE_SV_SE + #include "lang/sv-SE.rc" +#endif +#ifdef LANGUAGE_TR_TR + #include "lang/tr-TR.rc" +#endif +#ifdef LANGUAGE_ZH_CN + #include "lang/zh-CN.rc" +#endif +#ifdef LANGUAGE_ZH_HK + #include "lang/zh-HK.rc" +#endif +#ifdef LANGUAGE_ZH_TW + #include "lang/zh-TW.rc" +#endif diff --git a/dll/shellext/cabview/cabview.spec b/dll/shellext/cabview/cabview.spec new file mode 100644 index 0000000000000..b16365d0c9fc2 --- /dev/null +++ b/dll/shellext/cabview/cabview.spec @@ -0,0 +1,4 @@ +@ stdcall -private DllCanUnloadNow() +@ stdcall -private DllGetClassObject(ptr ptr ptr) +@ stdcall -private DllRegisterServer() +@ stdcall -private DllUnregisterServer() diff --git a/dll/shellext/cabview/extract.cpp b/dll/shellext/cabview/extract.cpp new file mode 100644 index 0000000000000..cdb1ff19d79d7 --- /dev/null +++ b/dll/shellext/cabview/extract.cpp @@ -0,0 +1,248 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: FDI API wrapper + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#include "precomp.h" +#include "cabview.h" +#include "util.h" +#include + +struct EXTRACTCABINETINTERNALDATA +{ + LPCWSTR destination; + EXTRACTCALLBACK callback; + LPVOID cookie; +}; + +static LPWSTR BuildPath(LPCWSTR Dir, LPCSTR File, UINT Attr) +{ + UINT cp = Attr & _A_NAME_IS_UTF ? CP_UTF8 : CP_ACP; + UINT cchfile = MultiByteToWideChar(cp, 0, File, -1, 0, 0); + SIZE_T lendir = lstrlenW(Dir), cch = lendir + 1 + cchfile; + LPWSTR path = (LPWSTR)SHAlloc(cch * sizeof(*path)); + if (path) + { + lstrcpyW(path, Dir); + if (lendir && !IsPathSep(path[lendir - 1])) + path[lendir++] = '\\'; + + LPWSTR dst = &path[lendir]; + MultiByteToWideChar(cp, 0, File + IsPathSep(*File), -1, dst, cchfile); + for (SIZE_T i = 0; dst[i]; ++i) + { + if (dst[i] == L':' && lendir) // Don't allow absolute paths + dst[i] = L'_'; + if (dst[i] == L'/') // Normalize + dst[i] = L'\\'; + } + } + return path; +} + +static HRESULT HResultFrom(const ERF &erf) +{ + switch (erf.fError ? erf.erfOper : FDIERROR_NONE) + { + case FDIERROR_NONE: + return erf.fError ? HRESULT_FROM_WIN32(erf.erfType) : S_OK; + case FDIERROR_CABINET_NOT_FOUND: + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + case FDIERROR_ALLOC_FAIL: + return E_OUTOFMEMORY; + case FDIERROR_USER_ABORT: + return S_FALSE; + default: + return erf.erfType ? HRESULT_FROM_WIN32(erf.erfType) : E_FAIL; + } +} + +FNFREE(CabMemFree) +{ + SHFree(pv); +} + +FNALLOC(CabMemAlloc) +{ + return SHAlloc(cb); +} + +FNCLOSE(CabClose) +{ + return CloseHandle((HANDLE)hf) ? 0 : -1; +} + +static INT_PTR CabOpenEx(LPCWSTR path, UINT access, UINT share, UINT disp, UINT attr) +{ + return (INT_PTR)CreateFileW(path, access, share, NULL, disp, attr, NULL); +} + +FNOPEN(CabOpen) +{ + UINT disp = (oflag & _O_CREAT) ? CREATE_ALWAYS : OPEN_EXISTING; + UINT access = GENERIC_READ; + if (oflag & _O_RDWR) + access = GENERIC_READ | GENERIC_WRITE; + else if (oflag & _O_WRONLY) + access = GENERIC_WRITE; + UNREFERENCED_PARAMETER(pmode); + WCHAR buf[MAX_PATH * 2]; + MultiByteToWideChar(CP_UTF8, 0, pszFile, -1, buf, _countof(buf)); + return CabOpenEx(buf, access, FILE_SHARE_READ, disp, FILE_ATTRIBUTE_NORMAL); +} + +FNREAD(CabRead) +{ + DWORD dwBytesRead; + return ReadFile((HANDLE)hf, pv, cb, &dwBytesRead, NULL) ? dwBytesRead : -1; +} + +FNWRITE(CabWrite) +{ + DWORD dwBytesWritten; + return WriteFile((HANDLE)hf, pv, cb, &dwBytesWritten, NULL) ? dwBytesWritten : -1; +} + +FNSEEK(CabSeek) +{ + return SetFilePointer((HANDLE)hf, dist, NULL, seektype); +} + +static HRESULT Init(HFDI &hfdi, ERF &erf) +{ + const int cpu = cpuUNKNOWN; + hfdi = FDICreate(CabMemAlloc, CabMemFree, CabOpen, CabRead, CabWrite, CabClose, CabSeek, cpu, &erf); + return hfdi ? S_OK : HResultFrom(erf); +} + +FNFDINOTIFY(ExtractCabinetCallback) +{ + const UINT attrmask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE; + EXTRACTCABINETINTERNALDATA &ecd = *(EXTRACTCABINETINTERNALDATA*)pfdin->pv; + EXTRACTCALLBACKDATA noti; + HRESULT hr; + FILETIME ft; + + noti.pfdin = pfdin; + switch (fdint) + { + case fdintCOPY_FILE: + hr = ecd.callback(ECM_FILE, noti, ecd.cookie); + if (hr == S_OK) + { + hr = E_OUTOFMEMORY; + LPWSTR path = BuildPath(ecd.destination, pfdin->psz1, pfdin->attribs); + if (path) + { + // Callee is using SHPPFW_IGNOREFILENAME so we don't need to remove the name. + /*LPWSTR file = PathFindFileNameW(path); + if (file > path) + { + file[-1] = L'\0';*/ + noti.Path = path; + ecd.callback(ECM_PREPAREPATH, noti, ecd.cookie); + /* file[-1] = L'\\'; + }*/ + UINT attr = pfdin->attribs & attrmask; + UINT access = GENERIC_READ | GENERIC_WRITE, share = FILE_SHARE_DELETE; + INT_PTR handle = CabOpenEx(path, access, share, CREATE_NEW, attr | FILE_FLAG_SEQUENTIAL_SCAN); + noti.hr = HResultFromWin32(GetLastError()); + SHFree(path); + if (handle != (INT_PTR)-1) + return handle; + if (ecd.callback(ECM_ERROR, noti, ecd.cookie) != E_NOTIMPL) + hr = noti.hr; + } + } + return hr == S_FALSE ? 0 : -1; + + case fdintCLOSE_FILE_INFO: + if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &ft)) + SetFileTime((HANDLE)(pfdin->hf), NULL, NULL, &ft); + return !CabClose(pfdin->hf); + + case fdintNEXT_CABINET: + if (pfdin->fdie && pfdin->fdie != FDIERROR_USER_ABORT) + { + if (pfdin->fdie == FDIERROR_CABINET_NOT_FOUND) + noti.hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + else + noti.hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + ecd.callback(ECM_ERROR, noti, ecd.cookie); + } + return pfdin->fdie ? -1 : 0; + + case fdintPARTIAL_FILE: + return 0; + + case fdintCABINET_INFO: + return 0; + + case fdintENUMERATE: + return 0; + } + return -1; +} + +HRESULT ExtractCabinet(LPCWSTR cab, LPCWSTR destination, EXTRACTCALLBACK callback, LPVOID cookie) +{ + BOOL quick = !destination; + if (!destination) + destination = L"?:"; // Dummy path for callers that enumerate without extracting + EXTRACTCABINETINTERNALDATA data = { destination, callback, cookie }; + EXTRACTCALLBACKDATA noti; + ERF erf = { }; + HFDI hfdi; + UINT total = 0, files = 0; + HRESULT hr = Init(hfdi, erf); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + UINT share = FILE_SHARE_READ | FILE_SHARE_DELETE; + INT_PTR hf = quick ? -1 : CabOpenEx(cab, GENERIC_READ, share, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); + if (hf != -1) + { + FDICABINETINFO ci; + if (FDIIsCabinet(hfdi, hf, &ci)) + { + total = ci.cbCabinet; + files = ci.cFiles; + } + CabClose(hf); + } + + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + char buf[MAX_PATH * 2], *name = 0; + if (!WideCharToMultiByte(CP_UTF8, 0, cab, -1, buf, _countof(buf), NULL, NULL)) + { + *buf = '\0'; + hr = E_INVALIDARG; + } + for (UINT i = 0; buf[i]; ++i) + { + if (buf[i] == '\\' || buf[i] == '/') + name = &buf[i + 1]; + } + if (name > buf && *name) + { + // Format the name the way FDI likes it + name[-1] = ANSI_NULL; + char namebuf[MAX_PATH]; + namebuf[0] = '\\'; + lstrcpyA(namebuf + 1, name); + name = namebuf; + + FDINOTIFICATION fdin; + fdin.cb = total; + fdin.hf = files; + noti.Path = cab; + noti.pfdin = &fdin; + callback(ECM_BEGIN, noti, cookie); + + hr = FDICopy(hfdi, name, buf, 0, ExtractCabinetCallback, NULL, &data) ? S_OK : HResultFrom(erf); + } + FDIDestroy(hfdi); + return hr; +} diff --git a/dll/shellext/cabview/folder.cpp b/dll/shellext/cabview/folder.cpp new file mode 100644 index 0000000000000..c96e941392c7b --- /dev/null +++ b/dll/shellext/cabview/folder.cpp @@ -0,0 +1,857 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Shell folder implementation + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#include "cabview.h" +#include "util.h" + +enum FOLDERCOLUMNS +{ + COL_NAME, // PKEY_ItemNameDisplay + COL_SIZE, // PKEY_Size + COL_TYPE, // PKEY_ItemTypeText + COL_MDATE, // PKEY_DateModified + COL_PATH, // PKEY_?: Archive-relative path + COL_ATT, // PKEY_FileAttributes + COLCOUNT +}; + +static const struct FOLDERCOLUMN +{ + BYTE TextId; + BYTE LvcFmt; + BYTE LvcChars; + BYTE ColFlags; + const GUID *pkg; + BYTE pki; +} g_Columns[] = +{ + { IDS_COL_NAME, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_NAME }, + { IDS_COL_SIZE, LVCFMT_RIGHT, 16, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_SIZE }, + { IDS_COL_TYPE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_STORAGETYPE }, + { IDS_COL_MDATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_WRITETIME }, + { IDS_COL_PATH, LVCFMT_LEFT, 30, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &CLSID_CabFolder, 0 }, + { IDS_COL_ATT, LVCFMT_RIGHT, 10, SHCOLSTATE_TYPE_STR, &FMTID_Storage, PID_STG_ATTRIBUTES }, +}; + +#include +struct CABITEM +{ + WORD cb; + WORD Unknown; // Not sure what Windows uses this for, we always store 0 + UINT Size; + WORD Date, Time; // DOS + WORD Attrib; + WORD NameOffset; + WCHAR Path[ANYSIZE_ARRAY]; + +#if FLATFOLDER + inline bool IsFolder() const { return false; } +#else + inline BOOL IsFolder() const { return Attrib & FILE_ATTRIBUTE_DIRECTORY; } +#endif + enum { FSATTS = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY }; + WORD GetFSAttributes() const { return Attrib & FSATTS; } + LPCWSTR GetName() const { return Path + NameOffset; } + + template static CABITEM* Validate(PIDL pidl) + { + CABITEM *p = (CABITEM*)pidl; + return p && p->cb > FIELD_OFFSET(CABITEM, Path[1]) && p->Unknown == 0 ? p : NULL; + } +}; +#include + +static CABITEM* CreateItem(LPCWSTR Path, UINT Attrib, UINT Size, UINT DateTime) +{ + const SIZE_T len = lstrlenW(Path), cb = FIELD_OFFSET(CABITEM, Path[len + 1]); + if (cb > 0xffff) + return NULL; + CABITEM *p = (CABITEM*)SHAlloc(cb + sizeof(USHORT)); + if (p) + { + p->cb = (USHORT)cb; + p->Unknown = 0; + p->Size = Size; + p->Attrib = Attrib; + p->Date = HIWORD(DateTime); + p->Time = LOWORD(DateTime); + p->NameOffset = 0; + for (UINT i = 0;; ++i) + { + WCHAR c = Path[i]; + if (c == L':') // Don't allow absolute paths + c = L'_'; + if (c == L'/') // Normalize + c = L'\\'; + if (c == '\\') + p->NameOffset = i + 1; + p->Path[i] = c; + if (!c) + break; + } + ((SHITEMID*)((BYTE*)p + cb))->cb = 0; + } + return p; +} + +static CABITEM* CreateItem(LPCSTR Path, UINT Attrib, UINT Size = 0, UINT DateTime = 0) +{ + WCHAR buf[MAX_PATH * 2]; + UINT codepage = (Attrib & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP; + if (MultiByteToWideChar(codepage, 0, Path, -1, buf, _countof(buf))) + return CreateItem(buf, Attrib, Size, DateTime); + return NULL; +} + +static HRESULT CALLBACK ItemMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, + UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + enum { IDC_EXTRACT, IDC_EXTRACTALL }; + HRESULT hr = E_NOTIMPL; + const BOOL Background = !pdtobj; + + switch (uMsg) + { + case DFM_MODIFYQCMFLAGS: + { + *((UINT*)lParam) = wParam | CMF_NOVERBS | (Background ? 0 : CMF_VERBSONLY); + return S_OK; + } + + case DFM_MERGECONTEXTMENU: + { + QCMINFO &qcmi = *(QCMINFO*)lParam; + UINT pos = qcmi.indexMenu, id = 0; + if (Background || SUCCEEDED(hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACT, IDS_EXTRACT, MFS_DEFAULT))) + { + hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACTALL, IDS_EXTRACTALL); + if (SUCCEEDED(hr) && !Background) + { + --pos; + InsertMenuItem(qcmi, pos, id, 0, -1); // Separator + } + } + if (SUCCEEDED(hr)) + { + qcmi.idCmdFirst = id + 1; + hr = S_FALSE; // Don't add verbs + } + break; + } + + case DFM_INVOKECOMMAND: + { + hr = S_FALSE; + CCabFolder *pCabFolder = static_cast(psf); + switch (wParam) + { + case IDC_EXTRACT: + case IDC_EXTRACTALL: + hr = pCabFolder->ExtractFilesUI(hwnd, wParam == IDC_EXTRACT ? pdtobj : NULL); + break; + } + break; + } + } + return hr; +} + +static HRESULT CALLBACK FolderBackgroundMenuCallback(IShellFolder *psf, HWND hwnd, + IDataObject *pdtobj, UINT uMsg, + WPARAM wParam, LPARAM lParam) +{ + return ItemMenuCallback(psf, hwnd, NULL, uMsg, wParam, lParam); +} + +int CEnumIDList::FindNamedItem(PCUITEMID_CHILD pidl) const +{ + CABITEM *needle = (CABITEM*)pidl; + for (ULONG i = 0, c = GetCount(); i < c; ++i) + { + CABITEM *item = (CABITEM*)DPA_FastGetPtr(m_Items, i); + if (!lstrcmpiW(needle->Path, item->Path)) + return i; + } + return -1; +} + +struct FILLCALLBACKDATA +{ + CEnumIDList *pEIDL; + SHCONTF ContF; +}; + +static HRESULT CALLBACK EnumFillCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie) +{ + FILLCALLBACKDATA &data = *(FILLCALLBACKDATA*)cookie; + + switch ((UINT)msg) + { + case ECM_FILE: + { + const FDINOTIFICATION &fdin = *ecd.pfdin; + HRESULT hr = S_FALSE; + SFGAOF attr = MapFSToSFAttributes(fdin.attribs & CABITEM::FSATTS); + if (IncludeInEnumIDList(data.ContF, attr)) + { + UINT datetime = MAKELONG(fdin.time, fdin.date); + CABITEM *item = CreateItem(fdin.psz1, fdin.attribs, fdin.cb, datetime); + if (!item) + return E_OUTOFMEMORY; + if (FAILED(hr = data.pEIDL->Append((LPCITEMIDLIST)item))) + SHFree(item); + } + return SUCCEEDED(hr) ? S_FALSE : hr; // Never extract + } + } + return E_NOTIMPL; +} + +HRESULT CEnumIDList::Fill(LPCWSTR path, HWND hwnd, SHCONTF contf) +{ + FILLCALLBACKDATA data = { this, contf }; + return ExtractCabinet(path, NULL, EnumFillCallback, &data); +} + +HRESULT CEnumIDList::Fill(PCIDLIST_ABSOLUTE pidl, HWND hwnd, SHCONTF contf) +{ + WCHAR path[MAX_PATH]; + if (SHGetPathFromIDListW(pidl, path)) + return Fill(path, hwnd, contf); + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); +} + +IFACEMETHODIMP CCabFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay) +{ + if (pSort) + *pSort = COL_NAME; + if (pDisplay) + *pDisplay = COL_NAME; + return S_OK; +} + +IFACEMETHODIMP CCabFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags) +{ + if (!pcsFlags || iColumn >= _countof(g_Columns)) + return E_INVALIDARG; + *pcsFlags = g_Columns[iColumn].ColFlags; + return S_OK; +} + +IFACEMETHODIMP CCabFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName) +{ + CABITEM *item = CABITEM::Validate(pidl); + if (!item || !pName) + return E_INVALIDARG; + + if (dwFlags & SHGDN_FORPARSING) + { + if (dwFlags & SHGDN_INFOLDER) + return StrTo(FLATFOLDER ? item->Path : item->GetName(), *pName); + + WCHAR parent[MAX_PATH]; + if (!SHGetPathFromIDListW(m_CurDir, parent)) + return E_FAIL; + UINT cch = lstrlenW(parent) + 1 + lstrlenW(item->Path) + 1; + pName->uType = STRRET_WSTR; + pName->pOleStr = (LPWSTR)SHAlloc(cch * sizeof(WCHAR)); + if (!pName->pOleStr) + return E_OUTOFMEMORY; + lstrcpyW(pName->pOleStr, parent); + PathAppendW(pName->pOleStr, item->Path); + return S_OK; + } + + SHFILEINFO fi; + DWORD attr = item->IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0; + UINT flags = SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES; + if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags)) + return StrTo(fi.szDisplayName, *pName); + return StrTo(item->GetName(), *pName); +} + +HRESULT CCabFolder::GetItemDetails(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd, VARIANT *pv) +{ + HRESULT hr = E_FAIL; + STRRET *psr = &psd->str, srvar; + CABITEM *item = CABITEM::Validate(pidl); + if (!item) + return E_INVALIDARG; + + switch (iColumn) + { + case COL_NAME: + { + hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, pv ? &srvar : psr); + return SUCCEEDED(hr) && pv ? StrRetToVariantBSTR(&srvar, *pv) : hr; + } + + case COL_SIZE: + { + UINT data = item->Size; + if (pv) + { + V_VT(pv) = VT_UI4; + V_UI4(pv) = data; + } + else + { + psr->uType = STRRET_CSTR; + StrFormatByteSizeA(data, psr->cStr, _countof(psr->cStr)); + } + return S_OK; + } + + case COL_TYPE: + { + SHFILEINFO fi; + LPCWSTR data = fi.szTypeName; + DWORD attr = item->GetFSAttributes(); + UINT flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES; + if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags)) + return pv ? StrTo(data, *pv) : StrTo(data, *psr); + break; + } + + case COL_MDATE: + { + if (pv) + { + if (DosDateTimeToVariantTime(item->Date, item->Time, &V_DATE(pv))) + { + V_VT(pv) = VT_DATE; + return S_OK; + } + } + else + { + FILETIME utc, loc; + if (DosDateTimeToFileTime(item->Date, item->Time, &utc) && FileTimeToLocalFileTime(&utc, &loc)) + { + psr->uType = STRRET_CSTR; + if (SHFormatDateTimeA(&loc, NULL, psr->cStr, _countof(psr->cStr))) + { + return S_OK; + } + } + } + break; + } + + case COL_PATH: + { + UINT len = item->NameOffset ? item->NameOffset - 1 : 0; + return pv ? StrTo(item->Path, len, *pv) : StrTo(item->Path, len, *psr); + } + + case COL_ATT: + { + UINT data = item->GetFSAttributes(); + if (pv) + { + V_VT(pv) = VT_UI4; + V_UI4(pv) = data; + } + else + { + UINT i = 0; + psr->uType = STRRET_CSTR; + if (data & FILE_ATTRIBUTE_READONLY) psr->cStr[i++] = 'R'; + if (data & FILE_ATTRIBUTE_HIDDEN) psr->cStr[i++] = 'H'; + if (data & FILE_ATTRIBUTE_SYSTEM) psr->cStr[i++] = 'S'; + if (data & FILE_ATTRIBUTE_ARCHIVE) psr->cStr[i++] = 'A'; + psr->cStr[i++] = '\0'; + } + return S_OK; + } + } + return hr; +} + +IFACEMETHODIMP CCabFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) +{ + if (!pscid || !pv) + return E_INVALIDARG; + + CABITEM *item; + int col = MapSCIDToColumn(*pscid); + if (col >= 0) + { + return GetItemDetails(pidl, col, NULL, pv); + } + else if ((item = CABITEM::Validate(pidl)) == NULL) + { + return E_INVALIDARG; + } + else if (IsEqual(*pscid, FMTID_ShellDetails, PID_FINDDATA)) + { + WIN32_FIND_DATA wfd; + ZeroMemory(&wfd, sizeof(wfd)); + wfd.dwFileAttributes = item->GetFSAttributes(); + wfd.nFileSizeLow = item->Size; + DosDateTimeToFileTime(item->Date, item->Time, &wfd.ftLastWriteTime); + lstrcpyn(wfd.cFileName, item->GetName(), MAX_PATH); + return InitVariantFromBuffer(&wfd, sizeof(wfd), pv); + } + return E_FAIL; +} + +IFACEMETHODIMP CCabFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd) +{ + if (!psd || iColumn >= _countof(g_Columns)) + { + return E_INVALIDARG; + } + else if (!pidl) + { + psd->fmt = g_Columns[iColumn].LvcFmt; + psd->cxChar = g_Columns[iColumn].LvcChars; + WCHAR buf[MAX_PATH]; + if (LoadStringW(_AtlBaseModule.GetResourceInstance(), g_Columns[iColumn].TextId, buf, _countof(buf))) + return StrTo(buf, psd->str); + return E_FAIL; + } + return GetItemDetails(pidl, iColumn, psd, NULL); +} + +int CCabFolder::MapSCIDToColumn(const SHCOLUMNID &scid) +{ + for (UINT i = 0; i < _countof(g_Columns); ++i) + { + if (g_Columns[i].pkg && IsEqual(scid, *g_Columns[i].pkg, g_Columns[i].pki)) + return i; + } + return -1; +} + +IFACEMETHODIMP CCabFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid) +{ + if (column < _countof(g_Columns) && g_Columns[column].pkg) + { + pscid->fmtid = *g_Columns[column].pkg; + pscid->pid = g_Columns[column].pki; + return S_OK; + } + return E_FAIL; +} + +IFACEMETHODIMP CCabFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) +{ + CEnumIDList *p = CEnumIDList::CreateInstance(); + *ppEnumIDList = static_cast(p); + return p ? p->Fill(m_CurDir, hwndOwner, dwFlags) : E_OUTOFMEMORY; +} + +IFACEMETHODIMP CCabFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) +{ + UNIMPLEMENTED; + return E_NOTIMPL; +} + +HRESULT CCabFolder::CompareID(LPARAM lParam, PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2) +{ + CABITEM *p1 = (CABITEM*)pidl1, *p2 = (CABITEM*)pidl2; + HRESULT hr = S_OK; + int ret = 0; + + if (lParam & (SHCIDS_ALLFIELDS | SHCIDS_CANONICALONLY)) + { + ret = lstrcmpiW(p1->Path, p2->Path); + if (ret && (lParam & SHCIDS_ALLFIELDS)) + { + for (UINT i = 0; ret && SUCCEEDED(hr) && i < COLCOUNT; ++i) + { + hr = (i == COL_NAME) ? 0 : CompareID(i, pidl1, pidl2); + ret = (short)HRESULT_CODE(hr); + } + } + } + else + { + UINT col = lParam & SHCIDS_COLUMNMASK; + switch (col) + { + case COL_NAME: + ret = StrCmpLogicalW(p1->GetName(), p2->GetName()); + break; + + case COL_SIZE: + ret = p1->Size - p2->Size; + break; + + case COL_MDATE: + ret = MAKELONG(p1->Time, p1->Date) - MAKELONG(p2->Time, p2->Date); + break; + + default: + { + if (col < COLCOUNT) + { + PWSTR str1, str2; + if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl1, col, str1))) + { + if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl2, col, str2))) + { + ret = StrCmpLogicalW(str1, str2); + SHFree(str2); + } + SHFree(str1); + } + } + else + { + hr = E_INVALIDARG; + } + } + } + } + return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(ret) : hr; +} + +IFACEMETHODIMP CCabFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) +{ + C_ASSERT(FLATFOLDER); + if (!pidl1 || !ILIsSingle(pidl1) || !pidl2 || !ILIsSingle(pidl2)) + return E_UNEXPECTED; + + return CompareID(lParam, pidl1, pidl2); +} + +IFACEMETHODIMP CCabFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppv) +{ + if (riid == IID_IShellView) + { + SFV_CREATE sfvc = { sizeof(SFV_CREATE), static_cast(this) }; + return SHCreateShellFolderView(&sfvc, (IShellView**)ppv); + } + if (riid == IID_IContextMenu) + { + LPFNDFMCALLBACK func = FolderBackgroundMenuCallback; + IContextMenu **ppCM = (IContextMenu**)ppv; + return CDefFolderMenu_Create2(m_CurDir, hwndOwner, 0, NULL, this, func, 0, NULL, ppCM); + } + return E_NOINTERFACE; +} + +IFACEMETHODIMP CCabFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF *rgfInOut) +{ + if (!cidl) + { + const SFGAOF ThisFolder = (SFGAO_FOLDER | SFGAO_BROWSABLE | SFGAO_CANLINK); + *rgfInOut = *rgfInOut & ThisFolder; + return S_OK; + } + else if (!apidl) + { + return E_INVALIDARG; + } + HRESULT hr = S_OK; + const SFGAOF filemask = SFGAO_READONLY | SFGAO_HIDDEN | SFGAO_SYSTEM | SFGAO_ISSLOW; + SFGAOF remain = *rgfInOut & filemask, validate = *rgfInOut & SFGAO_VALIDATE; + CComPtr list; + for (UINT i = 0; i < cidl && (remain || validate); ++i) + { + CABITEM *item = CABITEM::Validate(apidl[i]); + if (!item) + { + hr = E_INVALIDARG; + break; + } + else if (validate) + { + if (!list && FAILED_UNEXPECTEDLY(hr = CreateEnum(&list))) + return hr; + if (list->FindNamedItem((PCUITEMID_CHILD)item) == -1) + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + SFGAOF att = MapFSToSFAttributes(item->GetFSAttributes()) | SFGAO_ISSLOW; + remain &= att & ~(FLATFOLDER ? SFGAO_FOLDER : 0); + } + *rgfInOut = remain; + return hr; +} + +IFACEMETHODIMP CCabFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT *prgfInOut, LPVOID *ppvOut) +{ + HRESULT hr = E_NOINTERFACE; + if (riid == IID_IExtractIconA || riid == IID_IExtractIconW) + { + if (cidl != 1) + return E_INVALIDARG; + CABITEM *item = CABITEM::Validate(apidl[0]); + if (!item) + return E_INVALIDARG; + + DWORD attr = item->GetFSAttributes(); + return SHCreateFileExtractIconW(item->GetName(), attr, riid, ppvOut); + } + else if (riid == IID_IContextMenu && cidl) + { + LPFNDFMCALLBACK func = ItemMenuCallback; + IContextMenu **ppCM = (IContextMenu**)ppvOut; + return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, func, 0, NULL, ppCM); + } + else if (riid == IID_IDataObject && cidl) + { + // Note: This IDataObject is only compatible with IContextMenu, it cannot handle drag&drop of virtual items! + return CIDLData_CreateFromIDArray(m_CurDir, cidl, apidl, (IDataObject**)ppvOut); + } + return hr; +} + +IFACEMETHODIMP CCabFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case SFVM_WINDOWCREATED: + m_ShellViewWindow = (HWND)wParam; + return S_OK; + case SFVM_WINDOWCLOSING: + m_ShellViewWindow = NULL; + return S_OK; + } + return E_NOTIMPL; +} + +IFACEMETHODIMP CCabFolder::GetIconOf(PCUITEMID_CHILD pidl, UINT flags, int *pIconIndex) +{ + if (CABITEM *item = CABITEM::Validate(pidl)) + { + int index = MapPIDLToSystemImageListIndex(this, pidl, flags); + if (index == -1 && item->IsFolder()) + index = (flags & GIL_OPENICON) ? SIID_FOLDEROPEN : SIID_FOLDER; + if (index != -1) + { + *pIconIndex = index; + return S_OK; + } + } + return S_FALSE; +} + +static HRESULT GetFsPathFromIDList(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath) +{ + BOOL ret = SHGetPathFromIDListW(pidl, pszPath); + if (!ret && ILIsEmpty(pidl)) + ret = SHGetSpecialFolderPathW(NULL, pszPath, CSIDL_DESKTOPDIRECTORY, TRUE); + return ret ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); +} + +static int CALLBACK FolderBrowseCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + WCHAR buf[MAX_PATH]; + switch (uMsg) + { + case BFFM_INITIALIZED: + { + if (LoadStringW(_AtlBaseModule.GetResourceInstance(), IDS_EXTRACT, buf, _countof(buf))) + { + // Remove leading and trailing dots + WCHAR *s = buf, *e = s + lstrlenW(s); + while (*s == '.') ++s; + while (e > s && e[-1] == '.') *--e = UNICODE_NULL; + SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)s); + SendMessageW(GetDlgItem(hwnd, IDOK), WM_SETTEXT, 0, (LPARAM)s); + } + if (lpData) + { + SendMessageW(hwnd, BFFM_SETEXPANDED, FALSE, lpData); + SendMessageW(hwnd, BFFM_SETSELECTION, FALSE, lpData); + } + break; + } + + case BFFM_SELCHANGED: + { + SFGAOF wanted = SFGAO_FILESYSTEM | SFGAO_FOLDER, query = wanted | SFGAO_STREAM; + PCIDLIST_ABSOLUTE pidl = (PCIDLIST_ABSOLUTE)lParam; + BOOL enable = ILIsEmpty(pidl); // Allow the desktop + PCUITEMID_CHILD child; + IShellFolder *pSF; + if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &child))) + { + SFGAOF attrib = query; + if (SUCCEEDED(pSF->GetAttributesOf(1, &child, &attrib))) + enable = (attrib & query) == wanted; + pSF->Release(); + } + if (enable) + { + // We don't trust .zip folders, check the file-system to make sure + UINT attrib = SUCCEEDED(GetFsPathFromIDList(pidl, buf)) ? GetFileAttributesW(buf) : 0; + enable = (attrib & FILE_ATTRIBUTE_DIRECTORY) && attrib != INVALID_FILE_ATTRIBUTES; + } + PostMessageW(hwnd, BFFM_ENABLEOK, 0, enable); + break; + } + } + return 0; +} + +struct EXTRACTFILESDATA +{ + CCabFolder *pLifetimeCF; + HWND hWndOwner; + CIDA *pCIDA; + STGMEDIUM cidamedium; + IDataObject *pDO; + IStream *pMarshalDO; + IProgressDialog *pPD; + UINT cabfiles, completed; + WCHAR path[MAX_PATH], cab[MAX_PATH]; +}; + +static HWND GetUiOwner(const EXTRACTFILESDATA &data) +{ + HWND hWnd; + if (SUCCEEDED(IUnknown_GetWindow(data.pPD, &hWnd)) && IsWindowVisible(hWnd)) + return hWnd; + return IsWindowVisible(data.hWndOwner) ? data.hWndOwner : NULL; +} + +static HRESULT CALLBACK ExtractFilesCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie) +{ + EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)cookie; + switch ((UINT)msg) + { + case ECM_BEGIN: + { + data.cabfiles = (UINT)(SIZE_T)ecd.pfdin->hf; + return S_OK; + } + + case ECM_FILE: + { + if (data.pPD && data.pPD->HasUserCancelled()) + return HRESULT_FROM_WIN32(ERROR_CANCELLED); + HRESULT hr = data.pCIDA ? S_FALSE : S_OK; // Filtering or all items? + if (hr != S_OK) + { + CABITEM *needle = CreateItem(ecd.pfdin->psz1, ecd.pfdin->attribs); + if (!needle) + return E_OUTOFMEMORY; + for (UINT i = 0; i < data.pCIDA->cidl && hr == S_FALSE; ++i) + { + C_ASSERT(FLATFOLDER); + LPCITEMIDLIST pidlChild = ILFindLastID(HIDA_GetPIDLItem(data.pCIDA, i)); + CABITEM *haystack = CABITEM::Validate(pidlChild); + if (!haystack && FAILED_UNEXPECTEDLY(hr = E_FAIL)) + break; + if (!lstrcmpiW(needle->Path, haystack->Path)) + { + if (data.pPD) + data.pPD->SetLine(1, needle->Path, TRUE, NULL); + hr = S_OK; // Found it in the list of files to extract + } + } + SHFree(needle); + } + if (data.pPD) + data.pPD->SetProgress(data.completed++, data.cabfiles); + return hr; + } + + case ECM_PREPAREPATH: + { + UINT flags = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME; + return SHPathPrepareForWriteW(GetUiOwner(data), NULL, ecd.Path, flags); + } + + case ECM_ERROR: + { + return ErrorBox(GetUiOwner(data), ecd.hr); + } + } + return E_NOTIMPL; +} + +static void Free(EXTRACTFILESDATA &data) +{ + if (data.pPD) + { + data.pPD->StopProgressDialog(); + data.pPD->Release(); + } + CDataObjectHIDA::DestroyCIDA(data.pCIDA, data.cidamedium); + IUnknown_Set((IUnknown**)&data.pDO, NULL); + IUnknown_Set((IUnknown**)&data.pMarshalDO, NULL); + IUnknown_Set((IUnknown**)&data.pLifetimeCF, NULL); + SHFree(&data); +} + +static DWORD CALLBACK ExtractFilesThread(LPVOID pParam) +{ + EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)pParam; + HRESULT hr = S_OK; + if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ProgressDialog, NULL, IID_PPV_ARG(IProgressDialog, &data.pPD)))) + { + // TODO: IActionProgress SPACTION_COPYING + if (SUCCEEDED(data.pPD->StartProgressDialog(data.hWndOwner, NULL, PROGDLG_NOTIME, NULL))) + { + data.pPD->SetTitle(data.cab); + data.pPD->SetLine(2, data.path, TRUE, NULL); + data.pPD->SetAnimation(GetModuleHandleW(L"SHELL32"), 161); + data.pPD->SetProgress(0, 0); + } + } + if (data.pMarshalDO) + { + hr = CoGetInterfaceAndReleaseStream(data.pMarshalDO, IID_PPV_ARG(IDataObject, &data.pDO)); + data.pMarshalDO = NULL; + if (SUCCEEDED(hr)) + hr = CDataObjectHIDA::CreateCIDA(data.pDO, &data.pCIDA, data.cidamedium); + } + if (SUCCEEDED(hr)) + { + ExtractCabinet(data.cab, data.path, ExtractFilesCallback, &data); + } + Free(data); + return 0; +} + +HRESULT CCabFolder::ExtractFilesUI(HWND hWnd, IDataObject *pDO) +{ + if (!IsWindowVisible(hWnd) && IsWindowVisible(m_ShellViewWindow)) + hWnd = m_ShellViewWindow; + + EXTRACTFILESDATA *pData = (EXTRACTFILESDATA*)SHAlloc(sizeof(*pData)); + if (!pData) + return E_OUTOFMEMORY; + ZeroMemory(pData, sizeof(*pData)); + pData->hWndOwner = hWnd; + pData->pLifetimeCF = this; + pData->pLifetimeCF->AddRef(); + + HRESULT hr = GetFsPathFromIDList(m_CurDir, pData->cab); + if (SUCCEEDED(hr) && pDO) + { + hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDO, &pData->pMarshalDO); + } + if (SUCCEEDED(hr)) + { + hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); + LPITEMIDLIST pidlInitial = ILClone(m_CurDir); + ILRemoveLastID(pidlInitial); // Remove the "name.cab" part (we can't extract into ourselves) + UINT bif = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; + BROWSEINFO bi = { hWnd, NULL, NULL, pData->cab, bif, FolderBrowseCallback, (LPARAM)pidlInitial }; + if (PIDLIST_ABSOLUTE folder = SHBrowseForFolderW(&bi)) + { + hr = GetFsPathFromIDList(folder, pData->path); + ILFree(folder); + if (SUCCEEDED(hr)) + { + UINT ctf = CTF_COINIT | CTF_PROCESS_REF | CTF_THREAD_REF | CTF_FREELIBANDEXIT; + hr = SHCreateThread(ExtractFilesThread, pData, ctf, NULL) ? S_OK : E_OUTOFMEMORY; + } + } + ILFree(pidlInitial); + } + if (hr != S_OK) + Free(*pData); + return hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ? S_OK : hr; +} diff --git a/dll/shellext/cabview/lang/de-DE.rc b/dll/shellext/cabview/lang/de-DE.rc new file mode 100644 index 0000000000000..3951491271b75 --- /dev/null +++ b/dll/shellext/cabview/lang/de-DE.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: German (Germany) resource file + * TRANSLATOR: Copyright 2018 Robert Naumann + */ + +LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Name" + IDS_COL_TYPE "Dateityp" + IDS_COL_SIZE "Größe" + IDS_COL_MDATE "Änderungsdatum" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/en-US.rc b/dll/shellext/cabview/lang/en-US.rc new file mode 100644 index 0000000000000..4205c4912dcee --- /dev/null +++ b/dll/shellext/cabview/lang/en-US.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: English (United States) resource file + * TRANSLATOR: Copyright 2024 Whindmar Saksit + */ + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +STRINGTABLE +BEGIN + IDS_COL_NAME "Name" + IDS_COL_SIZE "Size" + IDS_COL_TYPE "Type" + IDS_COL_MDATE "Date modified" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/et-EE.rc b/dll/shellext/cabview/lang/et-EE.rc new file mode 100644 index 0000000000000..bd1cd3029d90e --- /dev/null +++ b/dll/shellext/cabview/lang/et-EE.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Estonian resource file + * TRANSLATOR: Copyright 2018 Joann Mõndresku + */ + +LANGUAGE LANG_ESTONIAN, SUBLANG_DEFAULT + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nimi" + IDS_COL_TYPE "Tüüp" + IDS_COL_SIZE "Suurus" + IDS_COL_MDATE "Kuupäeval muudetud" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/fr-FR.rc b/dll/shellext/cabview/lang/fr-FR.rc new file mode 100644 index 0000000000000..5c848d20827bb --- /dev/null +++ b/dll/shellext/cabview/lang/fr-FR.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: French (France) resource file + * TRANSLATOR: Copyright 2018 Pierre Schweitzer + */ + +LANGUAGE LANG_FRENCH, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nom" + IDS_COL_TYPE "Type" + IDS_COL_SIZE "Taille" + IDS_COL_MDATE "Date de modification" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/hi-IN.rc b/dll/shellext/cabview/lang/hi-IN.rc new file mode 100644 index 0000000000000..943076df12300 --- /dev/null +++ b/dll/shellext/cabview/lang/hi-IN.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Hindi (India) resource file + * TRANSLATOR: Copyright 2019 Arnav Bhatt +*/ + +LANGUAGE LANG_HINDI, SUBLANG_HINDI_INDIA + +STRINGTABLE +BEGIN + IDS_COL_NAME "नाम" + IDS_COL_TYPE "प्रकार" + IDS_COL_SIZE "साइज़" + IDS_COL_MDATE "तिथि संशोधित" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/it-IT.rc b/dll/shellext/cabview/lang/it-IT.rc new file mode 100644 index 0000000000000..912949f98df06 --- /dev/null +++ b/dll/shellext/cabview/lang/it-IT.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Italian resource file + * TRANSLATOR: Copyright 2018 George Bișoc + */ + +LANGUAGE LANG_ITALIAN, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nome" + IDS_COL_TYPE "Tipo" + IDS_COL_SIZE "Dimensione" + IDS_COL_MDATE "Data modificata" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/ja-JP.rc b/dll/shellext/cabview/lang/ja-JP.rc new file mode 100644 index 0000000000000..fcc6dc8d90e23 --- /dev/null +++ b/dll/shellext/cabview/lang/ja-JP.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Japanese resource file + * TRANSLATOR: Copyright 2018 Katayama Hirofumi MZ + */ + +LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT + +STRINGTABLE +BEGIN + IDS_COL_NAME "名前" + IDS_COL_TYPE "種類" + IDS_COL_SIZE "サイズ" + IDS_COL_MDATE "変更日" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/pl-PL.rc b/dll/shellext/cabview/lang/pl-PL.rc new file mode 100644 index 0000000000000..0d6fb1ba41ae2 --- /dev/null +++ b/dll/shellext/cabview/lang/pl-PL.rc @@ -0,0 +1,22 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Polish resource file + * TRANSLATORS: Copyright 2018-2019 Adam Słaboń + * Copyright 2020 Piotr Hetnarowicz + */ + +LANGUAGE LANG_POLISH, SUBLANG_DEFAULT + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nazwa" + IDS_COL_TYPE "Typ" + IDS_COL_SIZE "Rozmiar oryginalny" + IDS_COL_MDATE "Data modyfikacji" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/pt-PT.rc b/dll/shellext/cabview/lang/pt-PT.rc new file mode 100644 index 0000000000000..9671f14a3bf9d --- /dev/null +++ b/dll/shellext/cabview/lang/pt-PT.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Portuguese (Portugal) resource file + * TRANSLATOR: Copyright 2020 Jose Carlos Jesus + */ + +LANGUAGE LANG_PORTUGUESE, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nome" + IDS_COL_TYPE "Tipo" + IDS_COL_SIZE "Tamanho" + IDS_COL_MDATE "Data da modificação" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/ro-RO.rc b/dll/shellext/cabview/lang/ro-RO.rc new file mode 100644 index 0000000000000..9e9c0684fd341 --- /dev/null +++ b/dll/shellext/cabview/lang/ro-RO.rc @@ -0,0 +1,22 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Romanian resource file + * TRANSLATORS: Copyright 2018 George Bișoc + * Copyright 2022-2024 Andrei Miloiu + */ + +LANGUAGE LANG_ROMANIAN, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Nume" + IDS_COL_TYPE "Tip" + IDS_COL_SIZE "Dimensiune" + IDS_COL_MDATE "Data modificată" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/ru-RU.rc b/dll/shellext/cabview/lang/ru-RU.rc new file mode 100644 index 0000000000000..e377821fcb365 --- /dev/null +++ b/dll/shellext/cabview/lang/ru-RU.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Russian resource translation + * TRANSLATOR: Copyright 2018-2020 Stanislav Motylkov + */ + +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT + +STRINGTABLE +BEGIN + IDS_COL_NAME "Название" + IDS_COL_TYPE "Тип" + IDS_COL_SIZE "Размер" + IDS_COL_MDATE "Дата изменения" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/sv-SE.rc b/dll/shellext/cabview/lang/sv-SE.rc new file mode 100644 index 0000000000000..f65e8352d64d3 --- /dev/null +++ b/dll/shellext/cabview/lang/sv-SE.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Swedish resource file + * TRANSLATOR: Copyright 2018 Andreas Bjerkeholt + */ + +LANGUAGE LANG_SWEDISH, SUBLANG_NEUTRAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "Namn" + IDS_COL_TYPE "Typ" + IDS_COL_SIZE "Storlek" + IDS_COL_MDATE "Ändrad den" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extrahera..." + IDS_EXTRACTALL "Extrahera &alla..." +END diff --git a/dll/shellext/cabview/lang/tr-TR.rc b/dll/shellext/cabview/lang/tr-TR.rc new file mode 100644 index 0000000000000..e0d5a7a81bfe9 --- /dev/null +++ b/dll/shellext/cabview/lang/tr-TR.rc @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Turkish resource file + * TRANSLATOR: Copyright 2021 Süleyman Poyraz + */ + +LANGUAGE LANG_TURKISH, SUBLANG_DEFAULT + +STRINGTABLE +BEGIN + IDS_COL_NAME "İsim" + IDS_COL_TYPE "Tür" + IDS_COL_SIZE "Boyut" + IDS_COL_MDATE "Değiştirilme Tarihi" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/zh-CN.rc b/dll/shellext/cabview/lang/zh-CN.rc new file mode 100644 index 0000000000000..3437d81d1b3b6 --- /dev/null +++ b/dll/shellext/cabview/lang/zh-CN.rc @@ -0,0 +1,22 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Chinese (Simplified) resource file + * TRANSLATORS: Copyright 2018 Li Keqing + * Copyright 2021 Wu Haotian + */ + +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED + +STRINGTABLE +BEGIN + IDS_COL_NAME "名称" + IDS_COL_TYPE "类型" + IDS_COL_SIZE "大小" + IDS_COL_MDATE "修改日期" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/zh-HK.rc b/dll/shellext/cabview/lang/zh-HK.rc new file mode 100644 index 0000000000000..a5969223b8d1c --- /dev/null +++ b/dll/shellext/cabview/lang/zh-HK.rc @@ -0,0 +1,22 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Chinese (Hong Kong) resources file + * TRANSLATOR: Copyright 2021 Chan Chilung + * REFERENCES: Chinese (Simplified) resource file + */ + +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_HONGKONG + +STRINGTABLE +BEGIN + IDS_COL_NAME "名稱" + IDS_COL_TYPE "類型" + IDS_COL_SIZE "大小" + IDS_COL_MDATE "修改日期" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/lang/zh-TW.rc b/dll/shellext/cabview/lang/zh-TW.rc new file mode 100644 index 0000000000000..bbe5accd92fb9 --- /dev/null +++ b/dll/shellext/cabview/lang/zh-TW.rc @@ -0,0 +1,23 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Chinese (Traditional) resource file + * TRANSLATOR: Copyright 2020-2021 Chan Chilung + * REFERENCES: Chinese (Simplified) resource translation + * Copyright 2018 Li Keqing + */ + +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL + +STRINGTABLE +BEGIN + IDS_COL_NAME "名稱" + IDS_COL_TYPE "類型" + IDS_COL_SIZE "大小" + IDS_COL_MDATE "修改日期" + IDS_COL_PATH "Path" + IDS_COL_ATT "Attributes" + + IDS_EXTRACT "Extract..." + IDS_EXTRACTALL "Extract &All..." +END diff --git a/dll/shellext/cabview/precomp.h b/dll/shellext/cabview/precomp.h new file mode 100644 index 0000000000000..d69f972a2e68b --- /dev/null +++ b/dll/shellext/cabview/precomp.h @@ -0,0 +1,34 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Precompiled header file + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#pragma once +#define NTOS_MODE_USER +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define NTSTATUS LONG // for debug.h +#include +#include +#include +#include + +#ifndef SFGAO_SYSTEM +#define SFGAO_SYSTEM 0x00001000 +#endif + +#ifndef SHGSI_ICONLOCATION +#define SIID_FOLDER 3 +#define SIID_FOLDEROPEN 4 +#endif + +EXTERN_C INT WINAPI SHFormatDateTimeA(const FILETIME UNALIGNED *fileTime, DWORD *flags, LPSTR buf, UINT size); diff --git a/dll/shellext/cabview/res/cabview.rgs b/dll/shellext/cabview/res/cabview.rgs new file mode 100644 index 0000000000000..f931620096253 --- /dev/null +++ b/dll/shellext/cabview/res/cabview.rgs @@ -0,0 +1,47 @@ +HKCR +{ + NoRemove CLSID + { + '{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}' = s 'Cabinet Shell Folder' + { + InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } + ShellFolder + { + val Attributes = d '0x680001a0' + } + 'Implemented Categories' + { + '{00021490-0000-0000-C000-000000000046}' + { + } + } + } + } + + NoRemove CABFolder + { + CLSID = s '{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}' + DefaultIcon = s '%MODULE%' + + NoRemove Shell + { + NoRemove Open + { + val BrowserFlags = d '0x10' + val ExplorerFlags = d '0x20' + command = s '"explorer.exe" "%%L"' + } + } + } + NoRemove '.cab' = s 'CABFolder' + { + } + + NoRemove SystemFileAssociations + { + '.cab' + { + CLSID = s '{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}' + } + } +} diff --git a/dll/shellext/cabview/res/folder.ico b/dll/shellext/cabview/res/folder.ico new file mode 100644 index 0000000000000..04f98dcb2fdb9 Binary files /dev/null and b/dll/shellext/cabview/res/folder.ico differ diff --git a/dll/shellext/cabview/resource.h b/dll/shellext/cabview/resource.h new file mode 100644 index 0000000000000..b964a8c2c10ec --- /dev/null +++ b/dll/shellext/cabview/resource.h @@ -0,0 +1,25 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Resource header file + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#pragma once + +/* Icons */ +#define IDI_FOLDER 1 + +/* Registry */ +#define IDR_FOLDER 8000 + +/* Strings */ +#define IDS_COL_NAME 1 +#define IDS_COL_SIZE 2 +#define IDS_COL_TYPE 3 +#define IDS_COL_MDATE 4 +#define IDS_COL_PATH 5 +#define IDS_COL_ATT 6 + +#define IDS_EXTRACT 72 +#define IDS_EXTRACTALL 82 diff --git a/dll/shellext/cabview/util.h b/dll/shellext/cabview/util.h new file mode 100644 index 0000000000000..5f3d5e38196b2 --- /dev/null +++ b/dll/shellext/cabview/util.h @@ -0,0 +1,156 @@ +/* + * PROJECT: ReactOS CabView Shell Extension + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Utility header file + * COPYRIGHT: Copyright 2024 Whindmar Saksit + */ + +#pragma once + +template static int ErrorBox(H hWnd, int Error) +{ + SHELL_ErrorBox(hWnd, Error); + return Error; +} + +template static inline bool IsPathSep(T c) +{ + return c == '\\' || c == '/'; +} + +inline bool IsEqual(const SHCOLUMNID &scid, REFGUID guid, UINT pid) +{ + return scid.pid == pid && IsEqualGUID(scid.fmtid, guid); +} + +inline HRESULT InitVariantFromBuffer(const void *buffer, UINT cb, VARIANT *pv) +{ + SAFEARRAY *pa = SafeArrayCreateVector(VT_UI1, 0, cb); + if (pa) + { + CopyMemory(pa->pvData, buffer, cb); + V_VT(pv) = VT_UI1 | VT_ARRAY; + V_UNION(pv, parray) = pa; + return S_OK; + } + V_VT(pv) = VT_EMPTY; + return E_OUTOFMEMORY; +} + +inline void FreeStrRet(STRRET &str) +{ + if (str.uType == STRRET_WSTR) + { + SHFree(str.pOleStr); + str.uType = STRRET_CSTR; + } +} + +inline HRESULT StrTo(LPCWSTR str, UINT len, STRRET &sr) +{ + LPWSTR data = (LPWSTR)SHAlloc(++len * sizeof(WCHAR)); + if (!data) + return E_OUTOFMEMORY; + lstrcpynW(data, str, len); + sr.uType = STRRET_WSTR; + sr.pOleStr = data; + return S_OK; +} + +inline HRESULT StrTo(LPCWSTR str, STRRET &sr) +{ + return StrTo(str, lstrlenW(str), sr); +} + +inline HRESULT StrTo(LPCWSTR str, UINT len, VARIANT &v) +{ + BSTR data = SysAllocStringLen(str, len); + if (!data) + return E_OUTOFMEMORY; + V_VT(&v) = VT_BSTR; + V_BSTR(&v) = data; + return S_OK; +} + +inline HRESULT StrTo(LPCWSTR str, VARIANT &v) +{ + return StrTo(str, lstrlenW(str), v); +} + +inline HRESULT StrRetToVariantBSTR(STRRET *psr, VARIANT &v) +{ + HRESULT hr = StrRetToBSTR(psr, NULL, &V_BSTR(&v)); + if (SUCCEEDED(hr)) + V_VT(&v) = VT_BSTR; + return hr; +} + +inline HRESULT GetDetailsOf(IShellFolder2 &Folder, PCUITEMID_CHILD pidl, UINT Column, PWSTR &String) +{ + SHELLDETAILS details; + HRESULT hr = Folder.GetDetailsOf(pidl, Column, &details); + if (SUCCEEDED(hr)) + hr = StrRetToStrW(&details.str, pidl, &String); + return hr; +} + +inline HRESULT InsertMenuItem(QCMINFO &qcmi, UINT &Pos, UINT &TrackId, UINT Id, UINT ResId, int State = 0) +{ + UINT flags = 0; + WCHAR string[MAX_PATH]; + string[0] = UNICODE_NULL; + if ((Id += qcmi.idCmdFirst) > qcmi.idCmdLast) + return E_FAIL; + else if (ResId == (UINT)-1) + flags |= MF_SEPARATOR; + else if (!LoadStringW(_AtlBaseModule.GetResourceInstance(), ResId, string, _countof(string))) + return E_FAIL; + + MENUITEMINFOW mii; + mii.cbSize = FIELD_OFFSET(MENUITEMINFOW, hbmpItem); // USER32 version agnostic + mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE; + mii.fType = flags; + mii.wID = Id; + mii.dwTypeData = string; + mii.cch = 0; + mii.fState = State; + if (!InsertMenuItemW(qcmi.hmenu, Pos, TRUE, &mii)) + return E_FAIL; + Pos++; + TrackId = max(TrackId, Id); + return S_OK; +} + +inline SFGAOF MapFSToSFAttributes(UINT att) +{ + return ((att & FILE_ATTRIBUTE_READONLY) ? SFGAO_READONLY : 0) | + ((att & FILE_ATTRIBUTE_HIDDEN) ? SFGAO_HIDDEN : 0) | + ((att & FILE_ATTRIBUTE_SYSTEM) ? SFGAO_SYSTEM : 0); +} + +inline bool IncludeInEnumIDList(SHCONTF contf, SFGAOF att) +{ + const SHCONTF both = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS; + const SFGAOF superbits = SFGAO_HIDDEN | SFGAO_READONLY | SFGAO_SYSTEM; + const bool isfile = (att & (SFGAO_STREAM | SFGAO_FOLDER)) != SFGAO_FOLDER; + if ((contf & both) != both && !(contf & SHCONTF_STORAGE)) + { + if (isfile && (contf & SHCONTF_FOLDERS)) + return false; + if ((att & SFGAO_FOLDER) && (contf & SHCONTF_NONFOLDERS)) + return false; + } + if ((att & SFGAO_HIDDEN) && !(contf & (SHCONTF_INCLUDEHIDDEN | SHCONTF_STORAGE))) + return false; + if ((att & superbits) > SFGAO_HIDDEN && !(contf & (SHCONTF_INCLUDESUPERHIDDEN | SHCONTF_STORAGE))) + return false; + return true; +} + +inline int MapPIDLToSystemImageListIndex(IShellFolder *pSF, PCUITEMID_CHILD pidl, UINT GilFlags = 0) +{ + int normal, open; + BOOL qopen = GilFlags & GIL_OPENICON; + normal = SHMapPIDLToSystemImageListIndex(pSF, pidl, qopen ? &open : NULL); + return qopen && open != -1 ? open : normal; +} diff --git a/dll/win32/browseui/CProgressDialog.cpp b/dll/win32/browseui/CProgressDialog.cpp index 6d32d01ecb2ca..4f748b1205743 100644 --- a/dll/win32/browseui/CProgressDialog.cpp +++ b/dll/win32/browseui/CProgressDialog.cpp @@ -44,6 +44,7 @@ CProgressDialog::CProgressDialog() { + this->hwnd = NULL; this->lines[0] = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); this->lines[1] = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); this->lines[2] = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE); diff --git a/media/inf/syssetup.inf b/media/inf/syssetup.inf index 14700f060e126..1ef4a04bc3dd3 100644 --- a/media/inf/syssetup.inf +++ b/media/inf/syssetup.inf @@ -52,6 +52,7 @@ AddReg=Classes 11,,amstream.dll,1 11,,avifil32.dll,1 11,,browseui.dll,1 +11,,cabview.dll,1 11,,comcat.dll,1 11,,cryptdlg.dll,1 11,,cryptnet.dll,1