1 /*
2  * PROJECT:     ReactOS Zip Shell Extension
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Zip extraction
5  * COPYRIGHT:   Copyright 2017-2019 Mark Jansen (mark.jansen@reactos.org)
6  *              Copyright 2023 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #include "precomp.h"
10 #include <atlpath.h>
11 
12 class CZipExtract :
13     public IZip
14 {
15     CStringW m_Filename;
16     CStringW m_Directory;
17     CStringA m_Password;
18     bool m_DirectoryChanged;
19     unzFile uf;
20 public:
21     CZipExtract(PCWSTR Filename)
22         :m_DirectoryChanged(false)
23         ,uf(NULL)
24     {
25         m_Filename = Filename;
26         m_Directory = m_Filename;
27         PWSTR Dir = m_Directory.GetBuffer();
28         PathRemoveExtensionW(Dir);
29         m_Directory.ReleaseBuffer();
30     }
31 
32     ~CZipExtract()
33     {
34         if (uf)
35         {
36             DPRINT1("WARNING: uf not closed!\n");
37             Close();
38         }
39     }
40 
41     void Close()
42     {
43         if (uf)
44             unzClose(uf);
45         uf = NULL;
46     }
47 
48     // *** IZip methods ***
49     STDMETHODIMP QueryInterface(REFIID riid, void  **ppvObject)
50     {
51         if (riid == IID_IUnknown)
52         {
53             *ppvObject = this;
54             AddRef();
55             return S_OK;
56         }
57         return E_NOINTERFACE;
58     }
59     STDMETHODIMP_(ULONG) AddRef(void)
60     {
61         return 2;
62     }
63     STDMETHODIMP_(ULONG) Release(void)
64     {
65         return 1;
66     }
67     STDMETHODIMP_(unzFile) getZip()
68     {
69         return uf;
70     }
71 
72     class CExtractSettingsPage : public CPropertyPageImpl<CExtractSettingsPage>
73     {
74     private:
75         HANDLE m_hExtractionThread;
76         bool m_bExtractionThreadCancel;
77 
78         CZipExtract* m_pExtract;
79         CStringA* m_pPassword;
80         CStringW m_OldStatus;
81 
82     public:
83         CExtractSettingsPage(CZipExtract* extract, CStringA* password)
84             :CPropertyPageImpl<CExtractSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
85             ,m_hExtractionThread(NULL)
86             ,m_bExtractionThreadCancel(false)
87             ,m_pExtract(extract)
88             ,m_pPassword(password)
89         {
90             m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_TITLE);
91             m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_DEST_SUBTITLE);
92             m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
93         }
94 
95         int OnSetActive()
96         {
97             SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
98             m_pExtract->m_DirectoryChanged = false;
99             GetParent().CenterWindow(::GetDesktopWindow());
100             SetWizardButtons(PSWIZB_NEXT);
101             return 0;
102         }
103 
104         int OnWizardNext()
105         {
106             if (m_hExtractionThread != NULL)
107             {
108                 /* We enter here when extraction has finished, and go to next page if it succeeded */
109                 WaitForSingleObject(m_hExtractionThread, INFINITE);
110                 CloseHandle(m_hExtractionThread);
111                 m_hExtractionThread = NULL;
112                 m_pExtract->Release();
113                 if (!m_bExtractionThreadCancel)
114                 {
115                     return 0;
116                 }
117                 else
118                 {
119                     SetWindowLongPtr(DWLP_MSGRESULT, -1);
120                     return TRUE;
121                 }
122             }
123 
124             /* We end up here if the user manually clicks Next: start extraction */
125             m_bExtractionThreadCancel = false;
126 
127             /* Grey out every control during extraction to prevent user interaction */
128             ::EnableWindow(GetDlgItem(IDC_BROWSE), FALSE);
129             ::EnableWindow(GetDlgItem(IDC_DIRECTORY), FALSE);
130             ::EnableWindow(GetDlgItem(IDC_PASSWORD), FALSE);
131             SetWizardButtons(0);
132 
133             ::GetWindowTextW(GetDlgItem(IDC_STATUSTEXT), m_OldStatus.GetBuffer(MAX_PATH), MAX_PATH);
134             m_OldStatus.ReleaseBuffer();
135             CStringW strExtracting(MAKEINTRESOURCEW(IDS_EXTRACTING));
136             SetDlgItemTextW(IDC_STATUSTEXT, strExtracting);
137 
138             if (m_pExtract->m_DirectoryChanged)
139                 UpdateDirectory();
140 
141             m_pExtract->AddRef();
142 
143             m_hExtractionThread = CreateThread(NULL, 0,
144                                                &CExtractSettingsPage::ExtractEntry,
145                                                this,
146                                                0, NULL);
147             if (!m_hExtractionThread)
148             {
149                 /* Extraction thread creation failed, do not go to the next page */
150                 DWORD err = GetLastError();
151                 DPRINT1("ERROR, m_hExtractionThread: CreateThread failed: 0x%x\n", err);
152                 m_pExtract->Release();
153 
154                 SetWindowLongPtr(DWLP_MSGRESULT, -1);
155 
156                 ::EnableWindow(GetDlgItem(IDC_BROWSE), TRUE);
157                 ::EnableWindow(GetDlgItem(IDC_DIRECTORY), TRUE);
158                 ::EnableWindow(GetDlgItem(IDC_PASSWORD), TRUE);
159                 SetWizardButtons(PSWIZB_NEXT);
160             }
161             return TRUE;
162         }
163 
164         void WizardReset()
165         {
166             SetDlgItemTextW(IDC_STATUSTEXT, m_OldStatus);
167         }
168 
169         static DWORD WINAPI ExtractEntry(LPVOID lpParam)
170         {
171             CExtractSettingsPage* pPage = (CExtractSettingsPage*)lpParam;
172             bool res = pPage->m_pExtract->Extract(pPage->m_hWnd, pPage->GetDlgItem(IDC_PROGRESS), &(pPage->m_bExtractionThreadCancel));
173             /* Failing and cancelling extraction both mean we stay on the same property page */
174             pPage->m_bExtractionThreadCancel = !res;
175 
176             pPage->SetWizardButtons(PSWIZB_NEXT);
177             if (!res)
178             {
179                 /* Extraction failed/cancelled: the page becomes interactive again */
180                 ::EnableWindow(pPage->GetDlgItem(IDC_BROWSE), TRUE);
181                 ::EnableWindow(pPage->GetDlgItem(IDC_DIRECTORY), TRUE);
182                 ::EnableWindow(pPage->GetDlgItem(IDC_PASSWORD), TRUE);
183 
184                 /* Reset the progress bar's appearance */
185                 CWindow Progress(pPage->GetDlgItem(IDC_PROGRESS));
186                 Progress.SendMessage(PBM_SETRANGE32, 0, 1);
187                 Progress.SendMessage(PBM_SETPOS, 0, 0);
188                 pPage->WizardReset();
189             }
190             SendMessageCallback(pPage->GetParent().m_hWnd, PSM_PRESSBUTTON, PSBTN_NEXT, 0, NULL, NULL);
191 
192             return 0;
193         }
194 
195         BOOL OnQueryCancel()
196         {
197             if (m_hExtractionThread != NULL)
198             {
199                 /* Extraction will check the value of m_bExtractionThreadCancel between each file in the archive */
200                 m_bExtractionThreadCancel = true;
201                 return TRUE;
202             }
203             return FALSE;
204         }
205 
206         struct browse_info
207         {
208             HWND hWnd;
209             PCWSTR Directory;
210         };
211 
212         static INT CALLBACK s_BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lp, LPARAM pData)
213         {
214             if (uMsg == BFFM_INITIALIZED)
215             {
216                 browse_info* info = (browse_info*)pData;
217                 CWindow dlg(hWnd);
218                 dlg.SendMessage(BFFM_SETSELECTION, TRUE, (LPARAM)info->Directory);
219                 dlg.CenterWindow(info->hWnd);
220             }
221             return 0;
222         }
223 
224         LRESULT OnBrowse(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
225         {
226             BROWSEINFOW bi = { m_hWnd };
227             WCHAR path[MAX_PATH];
228             bi.pszDisplayName = path;
229             bi.lpfn = s_BrowseCallbackProc;
230             bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
231             CStringW title(MAKEINTRESOURCEW(IDS_WIZ_BROWSE_TITLE));
232             bi.lpszTitle = title;
233 
234             if (m_pExtract->m_DirectoryChanged)
235                 UpdateDirectory();
236 
237             browse_info info = { m_hWnd, m_pExtract->m_Directory.GetString() };
238             bi.lParam = (LPARAM)&info;
239 
240             CComHeapPtr<ITEMIDLIST> pidl;
241             pidl.Attach(SHBrowseForFolderW(&bi));
242 
243             WCHAR tmpPath[MAX_PATH];
244             if (pidl && SHGetPathFromIDListW(pidl, tmpPath))
245             {
246                 m_pExtract->m_Directory = tmpPath;
247                 SetDlgItemTextW(IDC_DIRECTORY, m_pExtract->m_Directory);
248                 m_pExtract->m_DirectoryChanged = false;
249             }
250             return 0;
251         }
252 
253         LRESULT OnEnChangeDirectory(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
254         {
255             m_pExtract->m_DirectoryChanged = true;
256             return 0;
257         }
258 
259         LRESULT OnPassword(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
260         {
261             CStringA Password;
262             if (_CZipAskPassword(m_hWnd, NULL, Password) == eAccept)
263             {
264                 *m_pPassword = Password;
265             }
266             return 0;
267         }
268 
269         void UpdateDirectory()
270         {
271             GetDlgItemText(IDC_DIRECTORY, m_pExtract->m_Directory);
272             m_pExtract->m_DirectoryChanged = false;
273         }
274 
275     public:
276         enum { IDD = IDD_PROPPAGEDESTINATION };
277 
278         BEGIN_MSG_MAP(CCompleteSettingsPage)
279             COMMAND_ID_HANDLER(IDC_BROWSE, OnBrowse)
280             COMMAND_ID_HANDLER(IDC_PASSWORD, OnPassword)
281             COMMAND_HANDLER(IDC_DIRECTORY, EN_CHANGE, OnEnChangeDirectory)
282             CHAIN_MSG_MAP(CPropertyPageImpl<CExtractSettingsPage>)
283         END_MSG_MAP()
284     };
285 
286 
287     class CCompleteSettingsPage : public CPropertyPageImpl<CCompleteSettingsPage>
288     {
289     private:
290         CZipExtract* m_pExtract;
291 
292     public:
293         CCompleteSettingsPage(CZipExtract* extract)
294             :CPropertyPageImpl<CCompleteSettingsPage>(MAKEINTRESOURCE(IDS_WIZ_TITLE))
295             , m_pExtract(extract)
296         {
297             m_psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_TITLE);
298             m_psp.pszHeaderSubTitle = MAKEINTRESOURCE(IDS_WIZ_COMPL_SUBTITLE);
299             m_psp.dwFlags |= PSP_USETITLE | PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
300         }
301 
302 
303         int OnSetActive()
304         {
305             SetWizardButtons(PSWIZB_FINISH);
306             CStringW Path = m_pExtract->m_Directory;
307             PWSTR Ptr = Path.GetBuffer(MAX_PATH);
308             RECT rc;
309             ::GetWindowRect(GetDlgItem(IDC_DESTDIR), &rc);
310             HDC dc = GetDC();
311             PathCompactPathW(dc, Ptr, rc.right - rc.left);
312             ReleaseDC(dc);
313             Path.ReleaseBuffer();
314             SetDlgItemTextW(IDC_DESTDIR, Path);
315             CheckDlgButton(IDC_SHOW_EXTRACTED, BST_CHECKED);
316             return 0;
317         }
318         BOOL OnWizardFinish()
319         {
320             if (IsDlgButtonChecked(IDC_SHOW_EXTRACTED) == BST_CHECKED)
321             {
322                 ShellExecuteW(NULL, L"explore", m_pExtract->m_Directory, NULL, NULL, SW_SHOW);
323             }
324             return FALSE;
325         }
326 
327     public:
328         enum { IDD = IDD_PROPPAGECOMPLETE };
329 
330         BEGIN_MSG_MAP(CCompleteSettingsPage)
331             CHAIN_MSG_MAP(CPropertyPageImpl<CCompleteSettingsPage>)
332         END_MSG_MAP()
333     };
334 
335 
336     /* NOTE: This callback is needed to set large icon correctly. */
337     static INT CALLBACK s_PropSheetCallbackProc(HWND hwndDlg, UINT uMsg, LPARAM lParam)
338     {
339         if (uMsg == PSCB_INITIALIZED)
340         {
341             HICON hIcon = LoadIconW(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCEW(IDI_ZIPFLDR));
342             CWindow dlg(hwndDlg);
343             dlg.SetIcon(hIcon, TRUE);
344         }
345 
346         return 0;
347     }
348 
349     void runWizard()
350     {
351         PROPSHEETHEADERW psh = { sizeof(psh), 0 };
352         psh.dwFlags = PSH_WIZARD97 | PSH_HEADER | PSH_USEICONID | PSH_USECALLBACK;
353         psh.hInstance = _AtlBaseModule.GetResourceInstance();
354 
355         CExtractSettingsPage extractPage(this, &m_Password);
356         CCompleteSettingsPage completePage(this);
357         HPROPSHEETPAGE hpsp[] =
358         {
359             extractPage.Create(),
360             completePage.Create()
361         };
362 
363         psh.phpage = hpsp;
364         psh.nPages = _countof(hpsp);
365         psh.pszIcon = MAKEINTRESOURCE(IDI_ZIPFLDR);
366         psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
367         psh.pszbmHeader = MAKEINTRESOURCE(IDB_HEADER);
368         psh.pfnCallback = s_PropSheetCallbackProc;
369 
370         PropertySheetW(&psh);
371     }
372 
373     eZipExtractError ExtractSingle(
374         HWND hDlg,
375         PCWSTR FullPath,
376         bool is_dir,
377         unz_file_info64* Info,
378         CStringW Name,
379         CStringA Password,
380         bool* bOverwriteAll,
381         const bool* bCancel,
382         int* ErrorCode
383     )
384     {
385         int err;
386         BYTE Buffer[2048];
387         DWORD dwFlags = SHPPFW_DIRCREATE | (is_dir ? SHPPFW_NONE : SHPPFW_IGNOREFILENAME);
388         HRESULT hr = SHPathPrepareForWriteW(hDlg, NULL, FullPath, dwFlags);
389         if (FAILED_UNEXPECTEDLY(hr))
390         {
391             *ErrorCode = hr;
392             return eDirectoryError;
393         }
394         if (is_dir)
395             return eNoError;
396 
397         if (Info->flag & MINIZIP_PASSWORD_FLAG)
398         {
399             eZipPasswordResponse Response = eAccept;
400             do
401             {
402                 /* If there is a password set, try it */
403                 if (!Password.IsEmpty())
404                 {
405                     err = unzOpenCurrentFilePassword(uf, Password);
406                     if (err == UNZ_OK)
407                     {
408                         /* Try to read some bytes, because unzOpenCurrentFilePassword does not return failure */
409                         char Buf[10];
410                         err = unzReadCurrentFile(uf, Buf, sizeof(Buf));
411                         unzCloseCurrentFile(uf);
412                         if (err >= UNZ_OK)
413                         {
414                             /* 're'-open the file so that we can begin to extract */
415                             err = unzOpenCurrentFilePassword(uf, Password);
416                             break;
417                         }
418                     }
419                 }
420                 Response = _CZipAskPassword(hDlg, Name, Password);
421             } while (Response == eAccept);
422 
423             if (Response == eSkip)
424             {
425                 return eNoError;
426             }
427             else if (Response == eAbort)
428             {
429                 return eExtractAbort;
430             }
431         }
432         else
433         {
434             err = unzOpenCurrentFile(uf);
435         }
436 
437         if (err != UNZ_OK)
438         {
439             DPRINT1("ERROR, unzOpenCurrentFilePassword: 0x%x\n", err);
440             *ErrorCode = err;
441             return eOpenError;
442         }
443 
444         HANDLE hFile = CreateFileW(FullPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
445         if (hFile == INVALID_HANDLE_VALUE)
446         {
447             DWORD dwErr = GetLastError();
448             if (dwErr == ERROR_FILE_EXISTS)
449             {
450                 bool bOverwrite = *bOverwriteAll;
451                 if (!*bOverwriteAll)
452                 {
453                     eZipConfirmResponse Result = _CZipAskReplace(hDlg, FullPath);
454                     switch (Result)
455                     {
456                     case eYesToAll:
457                         *bOverwriteAll = true;
458                         /* fall through */
459                     case eYes:
460                         bOverwrite = true;
461                         break;
462                     case eNo:
463                         break;
464                     case eCancel:
465                         unzCloseCurrentFile(uf);
466                         return eExtractAbort;
467                     }
468                 }
469 
470                 if (bOverwrite)
471                 {
472                     hFile = CreateFileW(FullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
473                     if (hFile == INVALID_HANDLE_VALUE)
474                     {
475                         dwErr = GetLastError();
476                     }
477                 }
478                 else
479                 {
480                     unzCloseCurrentFile(uf);
481                     return eNoError;
482                 }
483             }
484             if (hFile == INVALID_HANDLE_VALUE)
485             {
486                 unzCloseCurrentFile(uf);
487                 DPRINT1("ERROR, CreateFile: 0x%x (%s)\n", dwErr, *bOverwriteAll ? "Y" : "N");
488                 *ErrorCode = dwErr;
489                 return eFileError;
490             }
491         }
492 
493         do
494         {
495             if (*bCancel)
496             {
497                 CloseHandle(hFile);
498                 BOOL deleteResult = DeleteFileW(FullPath);
499                 if (!deleteResult)
500                     DPRINT1("ERROR, DeleteFile: 0x%x\n", GetLastError());
501                 return eExtractAbort;
502             }
503 
504             err = unzReadCurrentFile(uf, Buffer, sizeof(Buffer));
505 
506             if (err < 0)
507             {
508                 DPRINT1("ERROR, unzReadCurrentFile: 0x%x\n", err);
509                 break;
510             }
511             else if (err > 0)
512             {
513                 DWORD dwWritten;
514                 if (!WriteFile(hFile, Buffer, err, &dwWritten, NULL))
515                 {
516                     DPRINT1("ERROR, WriteFile: 0x%x\n", GetLastError());
517                     break;
518                 }
519                 if (dwWritten != (DWORD)err)
520                 {
521                     DPRINT1("ERROR, WriteFile: dwWritten:%d err:%d\n", dwWritten, err);
522                     break;
523                 }
524             }
525 
526         } while (err > 0);
527 
528         /* Update Filetime */
529         FILETIME LocalFileTime;
530         DosDateTimeToFileTime((WORD)(Info->dosDate >> 16), (WORD)Info->dosDate, &LocalFileTime);
531         FILETIME FileTime;
532         LocalFileTimeToFileTime(&LocalFileTime, &FileTime);
533         SetFileTime(hFile, &FileTime, &FileTime, &FileTime);
534 
535         /* Done */
536         CloseHandle(hFile);
537 
538         if (err)
539         {
540             unzCloseCurrentFile(uf);
541             DPRINT1("ERROR, unzReadCurrentFile2: 0x%x\n", err);
542             *ErrorCode = err;
543             return eUnpackError;
544         }
545         else
546         {
547             err = unzCloseCurrentFile(uf);
548             if (err != UNZ_OK)
549             {
550                 DPRINT1("ERROR(non-fatal), unzCloseCurrentFile: 0x%x\n", err);
551             }
552         }
553         return eNoError;
554     }
555 
556     bool Extract(HWND hDlg, HWND hProgress, const bool* bCancel)
557     {
558         unz_global_info64 gi;
559         uf = unzOpen2_64(m_Filename.GetString(), &g_FFunc);
560         int err = unzGetGlobalInfo64(uf, &gi);
561         if (err != UNZ_OK)
562         {
563             DPRINT1("ERROR, unzGetGlobalInfo64: 0x%x\n", err);
564             Close();
565             return false;
566         }
567 
568         CZipEnumerator zipEnum;
569         if (!zipEnum.initialize(this))
570         {
571             DPRINT1("ERROR, zipEnum.initialize\n");
572             Close();
573             return false;
574         }
575 
576         CWindow Progress(hProgress);
577         Progress.SendMessage(PBM_SETRANGE32, 0, gi.number_entry);
578         Progress.SendMessage(PBM_SETPOS, 0, 0);
579 
580         CStringW BaseDirectory = m_Directory;
581         CStringW Name;
582         CStringA Password = m_Password;
583         unz_file_info64 Info;
584         int CurrentFile = 0;
585         bool bOverwriteAll = false;
586         while (zipEnum.next(Name, Info))
587         {
588             if (*bCancel)
589             {
590                 Close();
591                 return false;
592             }
593 
594             bool is_dir = Name.GetLength() > 0 && Name[Name.GetLength()-1] == '/';
595 
596             // Build a combined path
597             CPathW FullPath(BaseDirectory);
598             FullPath += Name;
599 
600             // We use SHPathPrepareForWrite for this path.
601             // SHPathPrepareForWrite will prepare the necessary directories.
602             // Windows and ReactOS SHPathPrepareForWrite do not support '/'.
603             FullPath.m_strPath.Replace(L'/', L'\\');
604 
605         Retry:
606             eZipExtractError Result = ExtractSingle(hDlg, FullPath, is_dir, &Info, Name, Password, &bOverwriteAll, bCancel, &err);
607             if (Result != eDirectoryError)
608                 CurrentFile++;
609             switch (Result)
610             {
611                 case eNoError:
612                     break;
613 
614                 case eExtractAbort:
615                 case eUnpackError:
616                 {
617                     Close();
618                     return false;
619                 }
620 
621                 case eDirectoryError:
622                 {
623                     WCHAR StrippedPath[MAX_PATH] = { 0 };
624 
625                     StrCpyNW(StrippedPath, FullPath, _countof(StrippedPath));
626                     if (!is_dir)
627                         PathRemoveFileSpecW(StrippedPath);
628                     PathStripPathW(StrippedPath);
629                     if (ShowExtractError(hDlg, StrippedPath, err, eDirectoryError) == IDRETRY)
630                         goto Retry;
631                     Close();
632                     return false;
633                 }
634 
635                 case eFileError:
636                 {
637                     int Result = ShowExtractError(hDlg, FullPath, err, eFileError);
638                     switch (Result)
639                     {
640                     case IDABORT:
641                         Close();
642                         return false;
643                     case IDRETRY:
644                         CurrentFile--;
645                         goto Retry;
646                     case IDIGNORE:
647                         break;
648                     }
649                     break;
650                 }
651 
652                 case eOpenError:
653                 {
654                     if (err == UNZ_BADZIPFILE &&
655                         Info.compression_method != 0 &&
656                         Info.compression_method != Z_DEFLATED &&
657                         Info.compression_method != Z_BZIP2ED)
658                     {
659                         if (ShowExtractError(hDlg, FullPath, Info.compression_method, eOpenError) == IDYES)
660                             break;
661                     }
662                     Close();
663                     return false;
664                 }
665             }
666             if (Result == eNoError && is_dir)
667                 continue;
668             Progress.SendMessage(PBM_SETPOS, CurrentFile, 0);
669         }
670 
671         Close();
672         return true;
673     }
674 
675     int ShowExtractError(HWND hDlg, PCWSTR path, int Error, eZipExtractError ErrorType)
676     {
677         CStringW strTitle(MAKEINTRESOURCEW(IDS_ERRORTITLE));
678         CStringW strErr, strText;
679         PWSTR Win32ErrorString;
680 
681         if (ErrorType == eFileError || ErrorType == eOpenError)
682             strText.LoadString(IDS_CANTEXTRACTFILE);
683         else
684             strText.LoadString(GetModuleHandleA("shell32.dll"), 128); // IDS_CREATEFOLDER_DENIED
685 
686         strText.FormatMessage(strText.GetString(), path);
687 
688         if (ErrorType == eFileError || HRESULT_FACILITY(Error) == FACILITY_WIN32)
689         {
690             if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
691                                NULL, ErrorType == eFileError ? Error : HRESULT_CODE(Error), 0,
692                                (PWSTR)&Win32ErrorString, 0, NULL) != 0)
693             {
694                 strErr.SetString(Win32ErrorString);
695                 LocalFree(Win32ErrorString);
696             }
697         }
698         if (ErrorType == eOpenError)
699             strErr.Format(IDS_DECOMPRESSERROR, Error);
700         else if (strErr.GetLength() == 0)
701             strErr.Format(IDS_UNKNOWNERROR, Error);
702 
703         strText.Append(L"\r\n\r\n" + strErr);
704 
705         UINT mbFlags = MB_ICONWARNING;
706         if (ErrorType == eDirectoryError)
707             mbFlags |= MB_RETRYCANCEL;
708         else if (ErrorType == eFileError)
709             mbFlags |= MB_ABORTRETRYIGNORE;
710         else if (ErrorType == eOpenError)
711             mbFlags |= MB_YESNO;
712 
713         return MessageBoxW(hDlg, strText, strTitle, mbFlags);
714     }
715 };
716 
717 
718 void _CZipExtract_runWizard(PCWSTR Filename)
719 {
720     CZipExtract extractor(Filename);
721     extractor.runWizard();
722 }
723 
724