xref: /reactos/base/applications/rapps/geninst.cpp (revision 190b3da9)
1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Act as installer and uninstaller for applications distributed in archives
5  * COPYRIGHT:   Copyright 2024 Whindmar Saksit <whindsaks@proton.me>
6  */
7 
8 #include "defines.h"
9 #include <shlobj.h>
10 #include <shlwapi.h>
11 #include <setupapi.h>
12 #include <commctrl.h>
13 #include "resource.h"
14 #include "appdb.h"
15 #include "appinfo.h"
16 #include "misc.h"
17 #include "configparser.h"
18 #include "unattended.h"
19 
20 #include "minizip/ioapi.h"
21 #include "minizip/iowin32.h"
22 #include "minizip/unzip.h"
23 extern "C" {
24     #include "minizip/ioapi.c"
25     #include "minizip/iowin32.c"
26     #include "minizip/unzip.c"
27 }
28 
29 #define REGPATH_UNINSTALL L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
30 
31 #define DB_GENINST_FILES L"Files"
32 #define DB_GENINST_DIR L"Dir"
33 #define DB_GENINST_ICON L"Icon"
34 #define DB_GENINST_LNK L"Lnk"
35 #define DB_GENINST_DELFILE L"DelFile" // Delete files generated by the application
36 #define DB_GENINST_DELDIR L"DelDir"
37 #define DB_GENINST_DELDIREMPTY L"DelDirEmpty"
38 #define DB_GENINST_DELREG L"DelReg"
39 #define DB_GENINST_DELREGEMPTY L"DelRegEmpty"
40 
41 enum {
42     UNOP_FILE = 'F',
43     UNOP_DIR = 'D',
44     UNOP_EMPTYDIR = 'd',
45     UNOP_REGKEY = 'K',
46     UNOP_EMPTYREGKEY = 'k',
47 };
48 
49 static int
50 ExtractFilesFromZip(LPCWSTR Archive, const CStringW &OutputDir,
51                     EXTRACTCALLBACK Callback, void *Cookie)
52 {
53     const UINT pkzefsutf8 = 1 << 11; // APPNOTE; APPENDIX D
54     zlib_filefunc64_def zff;
55     fill_win32_filefunc64W(&zff);
56     unzFile hzf = unzOpen2_64(Archive, &zff);
57     if (!hzf)
58         return UNZ_BADZIPFILE;
59     CStringA narrow;
60     CStringW path, dir;
61     int zerr = unzGoToFirstFile(hzf);
62     for (; zerr == UNZ_OK; zerr = unzGoToNextFile(hzf))
63     {
64         unz_file_info64 fi;
65         zerr = unzGetCurrentFileInfo64(hzf, &fi, NULL, 0, NULL, 0, NULL, 0);
66         if (zerr != UNZ_OK)
67             break;
68         LPSTR file = narrow.GetBuffer(fi.size_filename);
69         zerr = unzGetCurrentFileInfo64(hzf, &fi, file, narrow.GetAllocLength(), NULL, 0, NULL, 0);
70         if (zerr != UNZ_OK)
71             break;
72         narrow.ReleaseBuffer(fi.size_filename);
73         narrow.Replace('/', '\\');
74         while (narrow[0] == '\\')
75             narrow = narrow.Mid(1);
76         UINT codepage = (fi.flag & pkzefsutf8) ? CP_UTF8 : 1252;
77         UINT cch = MultiByteToWideChar(codepage, 0, narrow, -1, NULL, 0);
78         cch = MultiByteToWideChar(codepage, 0, narrow, -1, path.GetBuffer(cch - 1), cch);
79         if (!cch)
80             break;
81         path.ReleaseBuffer(cch - 1);
82         DWORD fileatt = FILE_ATTRIBUTE_NORMAL, err;
83         BYTE attsys = HIBYTE(fi.version), dos = 0, ntfs = 10, vfat = 14;
84         if ((attsys == dos || attsys == ntfs || attsys == vfat) && LOBYTE(fi.external_fa))
85             fileatt = LOBYTE(fi.external_fa);
86 
87         if (!NotifyFileExtractCallback(path, fi.uncompressed_size, fileatt,
88                                        Callback, Cookie))
89             continue; // Skip file
90 
91         path = BuildPath(OutputDir, path);
92         SplitFileAndDirectory(path, &dir);
93         if (!dir.IsEmpty() && (err = CreateDirectoryTree(dir)))
94         {
95             zerr = UNZ_ERRNO;
96             SetLastError(err);
97             break;
98         }
99 
100         if ((zerr = unzOpenCurrentFile(hzf)) != UNZ_OK)
101             break;
102         zerr = UNZ_ERRNO;
103         HANDLE hFile = CreateFileW(path, GENERIC_READ | GENERIC_WRITE,
104                                    FILE_SHARE_READ | FILE_SHARE_DELETE,
105                                    NULL, CREATE_ALWAYS, fileatt, NULL);
106         if (hFile != INVALID_HANDLE_VALUE)
107         {
108             DWORD len = 1024 * 4, cb;
109             LPSTR buf = narrow.GetBuffer(len);
110             for (zerr = UNZ_OK; zerr == UNZ_OK;)
111             {
112                 len = zerr = unzReadCurrentFile(hzf, buf, len);
113                 if (zerr <= 0)
114                     break;
115                 zerr = WriteFile(hFile, buf, len, &cb, NULL) && cb == len ? UNZ_OK : UNZ_ERRNO;
116             }
117             CloseHandle(hFile);
118         }
119         unzCloseCurrentFile(hzf);
120     }
121     unzClose(hzf);
122     return zerr == UNZ_END_OF_LIST_OF_FILE ? UNZ_OK : zerr;
123 }
124 
125 static UINT
126 ExtractZip(LPCWSTR Archive, const CStringW &OutputDir,
127            EXTRACTCALLBACK Callback, void *Cookie)
128 {
129     int zerr = ExtractFilesFromZip(Archive, OutputDir, Callback, Cookie);
130     return zerr == UNZ_ERRNO ? GetLastError() : zerr ? ERROR_INTERNAL_ERROR : 0;
131 }
132 
133 static UINT
134 ExtractCab(LPCWSTR Archive, const CStringW &OutputDir,
135            EXTRACTCALLBACK Callback, void *Cookie)
136 {
137     if (ExtractFilesFromCab(Archive, OutputDir, Callback, Cookie))
138         return ERROR_SUCCESS;
139     UINT err = GetLastError();
140     return err ? err : ERROR_INTERNAL_ERROR;
141 }
142 
143 enum { IM_STARTPROGRESS = WM_APP, IM_PROGRESS, IM_END };
144 
145 static struct CommonInfo
146 {
147     LPCWSTR AppName;
148     HWND hDlg;
149     DWORD Error, Count;
150     BOOL Silent;
151     CRegKey *ArpKey, Entries;
152 
153     CommonInfo(LPCWSTR DisplayName, BOOL IsSilent = FALSE)
154     : AppName(DisplayName), hDlg(NULL), Error(0), Count(0), Silent(IsSilent), ArpKey(NULL)
155     {
156     }
157     inline HWND GetGuiOwner() const { return Silent ? NULL : hDlg; }
158 } *g_pInfo;
159 
160 struct InstallInfo : CommonInfo
161 {
162     CConfigParser &Parser;
163     LPCWSTR ArchivePath, InstallDir, ShortcutFile;
164     CStringW UninstFile, MainApp;
165     UINT InstallDirLen, EntryCount;
166     BOOL PerUser;
167 
168     InstallInfo(LPCWSTR AppName, CConfigParser &CP, LPCWSTR Archive)
169     : CommonInfo(AppName), Parser(CP), ArchivePath(Archive)
170     {
171         EntryCount = 0;
172     }
173 };
174 
175 static UINT
176 ErrorBox(UINT Error = GetLastError())
177 {
178     if (!Error)
179         Error = ERROR_INTERNAL_ERROR;
180     WCHAR buf[400];
181     UINT fmf = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
182     FormatMessageW(fmf, NULL, Error, 0, buf, _countof(buf), NULL);
183     MessageBoxW(g_pInfo->GetGuiOwner(), buf, 0, MB_OK | MB_ICONSTOP);
184     g_pInfo->Error = Error;
185     return Error;
186 }
187 
188 static LPCWSTR
189 GetCommonString(LPCWSTR Name, CStringW &Output, LPCWSTR Default = L"")
190 {
191     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
192     BOOL found = Info.Parser.GetString(Name, Output);
193     return (found && !Output.IsEmpty() ? Output : Output = Default).GetString();
194 }
195 
196 static LPCWSTR
197 GetGenerateString(LPCWSTR Name, CStringW &Output, LPCWSTR Default = L"")
198 {
199     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
200     UINT r = Info.Parser.GetSectionString(DB_GENINSTSECTION, Name, Output);
201     return (r ? Output : Output = Default).GetString();
202 }
203 
204 static void
205 WriteArpEntry(LPCWSTR Name, LPCWSTR Value, UINT Type = REG_SZ)
206 {
207     // Write a "Add/Remove programs" value if we have a valid uninstaller key
208     if (g_pInfo->ArpKey)
209     {
210         UINT err = g_pInfo->ArpKey->SetStringValue(Name, Value, Type);
211         if (err)
212             ErrorBox(err);
213     }
214 }
215 
216 static BOOL
217 AddEntry(WCHAR Type, LPCWSTR Value)
218 {
219     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
220     CStringW name;
221     name.Format(L"%c%u", Type, ++Info.EntryCount);
222     UINT err = Info.Entries.SetStringValue(name, Value);
223     if (err)
224         ErrorBox(err);
225     return !err;
226 }
227 
228 static HRESULT
229 GetCustomIconPath(InstallInfo &Info, CStringW &Path)
230 {
231     if (*GetGenerateString(DB_GENINST_ICON, Path))
232     {
233         Path = BuildPath(Info.InstallDir, Path);
234         int idx = PathParseIconLocation(Path.GetBuffer());
235         Path.ReleaseBuffer();
236         HICON hIco = NULL;
237         if (ExtractIconExW(Path, idx, &hIco, NULL, 1))
238             DestroyIcon(hIco);
239         if (idx)
240             Path.AppendFormat(L",%d", idx);
241         return hIco ? S_OK : S_FALSE;
242     }
243     return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS);
244 }
245 
246 static BOOL
247 GetLocalizedSMFolderName(LPCWSTR WinVal, LPCWSTR RosInf, LPCWSTR RosVal, CStringW &Output)
248 {
249     CRegKey key;
250     if (key.Open(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion", KEY_READ) == ERROR_SUCCESS &&
251         GetRegString(key, WinVal, Output) && !Output.IsEmpty())
252     {
253         return TRUE;
254     }
255     WCHAR windir[MAX_PATH];
256     GetWindowsDirectoryW(windir, _countof(windir));
257     CStringW path = BuildPath(BuildPath(windir, L"inf"), RosInf), section;
258     DWORD lang = 0, lctype = LOCALE_ILANGUAGE | LOCALE_RETURN_NUMBER;
259     if (GetLocaleInfoW(GetUserDefaultLCID(), lctype, (LPWSTR) &lang, sizeof(lang) / sizeof(WCHAR)))
260     {
261         section.Format(L"Strings.%.4x", lang);
262         if (ReadIniValue(path, section, RosVal, Output) > 0)
263             return TRUE;
264         section.Format(L"Strings.%.2x", PRIMARYLANGID(lang));
265         if (ReadIniValue(path, section, RosVal, Output) > 0)
266             return TRUE;
267     }
268     return ReadIniValue(path, L"Strings", RosVal, Output) > 0;
269 }
270 
271 static BOOL
272 CreateShortcut(const CStringW &Target)
273 {
274     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
275     UINT csidl = Info.PerUser ? CSIDL_PROGRAMS : CSIDL_COMMON_PROGRAMS;
276     CStringW rel = Info.ShortcutFile, path, dir, tmp;
277 
278     if (FAILED(GetSpecialPath(csidl, path, Info.GetGuiOwner())))
279         return TRUE; // Pretend everything is OK
280 
281     int cat;
282     if (Info.Parser.GetInt(DB_CATEGORY, cat) && cat == ENUM_CAT_GAMES)
283     {
284         // Try to find the name of the Games folder in the Start Menu
285         if (GetLocalizedSMFolderName(L"SM_GamesName", L"shortcuts.inf", L"Games", tmp))
286         {
287             path = BuildPath(path, tmp);
288         }
289     }
290 
291     // SHPathPrepareForWrite will prepare the necessary directories.
292     // Windows and ReactOS SHPathPrepareForWrite do not support '/'.
293     rel.Replace('/', '\\');
294     path = BuildPath(path, rel.GetString());
295     UINT SHPPFW = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME;
296     HRESULT hr = SHPathPrepareForWriteW(Info.GetGuiOwner(), NULL, path, SHPPFW);
297     if ((Info.Error = ErrorFromHResult(hr)) != 0)
298     {
299         ErrorBox(Info.Error);
300         return FALSE;
301     }
302 
303     CComPtr<IShellLinkW> link;
304     hr = link.CoCreateInstance(CLSID_ShellLink, IID_IShellLinkW);
305     if (SUCCEEDED(hr))
306     {
307         if (SUCCEEDED(hr = link->SetPath(Target)))
308         {
309             if (SUCCEEDED(GetCustomIconPath(Info, tmp)))
310             {
311                 LPWSTR p = tmp.GetBuffer();
312                 int idx = PathParseIconLocation(p);
313                 link->SetIconLocation(p, idx);
314             }
315             CComPtr<IPersistFile> persist;
316             if (SUCCEEDED(hr = link->QueryInterface(IID_IPersistFile, (void**)&persist)))
317             {
318                 hr = persist->Save(path, FALSE);
319             }
320         }
321     }
322     if (SUCCEEDED(hr))
323     {
324         if (AddEntry(UNOP_FILE, path))
325         {
326             SplitFileAndDirectory(path, &dir);
327             AddEntry(UNOP_EMPTYDIR, dir);
328         }
329     }
330     else
331     {
332         ErrorBox(ErrorFromHResult(hr));
333     }
334     return !Info.Error;
335 }
336 
337 static BOOL
338 InstallFiles(const CStringW &SourceDirBase, const CStringW &Spec,
339              const CStringW &DestinationDir)
340 {
341     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
342     CStringW sourcedir, filespec;
343     filespec = SplitFileAndDirectory(Spec, &sourcedir); // Split "[OptionalDir\]*.ext"
344     sourcedir = BuildPath(SourceDirBase, sourcedir);
345     BOOL success = TRUE;
346     WIN32_FIND_DATAW wfd;
347     HANDLE hFind = FindFirstFileW(BuildPath(sourcedir, filespec), &wfd);
348     if (hFind == INVALID_HANDLE_VALUE)
349     {
350         DWORD error = GetLastError();
351         if (error == ERROR_FILE_NOT_FOUND)
352             return TRUE;
353         else
354             return !ErrorBox(error);
355     }
356 
357     for (;;)
358     {
359         CStringW from = BuildPath(sourcedir, wfd.cFileName);
360         CStringW to = BuildPath(DestinationDir, wfd.cFileName);
361         CStringW uninstpath = to.Mid(Info.InstallDirLen - 1);
362         if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
363         {
364             LPWSTR p = wfd.cFileName;
365             BOOL dots = p[0] == '.' && (!p[1] || (p[1] == '.' && !p[2]));
366             if (!dots)
367             {
368                 Info.Error = CreateDirectoryTree(to);
369                 if (Info.Error)
370                 {
371                     success = !ErrorBox(Info.Error);
372                 }
373                 else
374                 {
375                     success = AddEntry(UNOP_EMPTYDIR, uninstpath);
376                 }
377 
378                 if (success)
379                 {
380                     success = InstallFiles(from, filespec, to);
381                 }
382             }
383         }
384         else
385         {
386             success = MoveFileEx(from, to, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
387             if (success)
388             {
389                 if (Info.MainApp.IsEmpty())
390                 {
391                     Info.MainApp = to;
392                 }
393                 success = AddEntry(UNOP_FILE, uninstpath);
394             }
395             else
396             {
397                 ErrorBox();
398             }
399             SendMessage(g_pInfo->hDlg, IM_PROGRESS, 0, 0);
400         }
401 
402         if (!success || !FindNextFileW(hFind, &wfd))
403             break;
404     }
405     FindClose(hFind);
406     return success;
407 }
408 
409 static void
410 AddUninstallOperationsFromDB(LPCWSTR Name, WCHAR UnOp, CStringW PathPrefix = CStringW(L""))
411 {
412     CStringW item, tmp;
413     if (*GetGenerateString(Name, tmp))
414     {
415         for (int pos = 1; pos > 0;)
416         {
417             pos = tmp.Find(L'|');
418             item = pos <= 0 ? tmp : tmp.Left(pos);
419             tmp = tmp.Mid(pos + 1);
420             AddEntry(UnOp, PathPrefix + item);
421         }
422     }
423 }
424 
425 static BOOL CALLBACK
426 ExtractCallback(const EXTRACTCALLBACKINFO &, void *Cookie)
427 {
428     InstallInfo &Info = *(InstallInfo *) Cookie;
429     Info.Count += 1;
430     return TRUE;
431 }
432 
433 static DWORD CALLBACK
434 ExtractAndInstallThread(LPVOID Parameter)
435 {
436     const BOOL PerUserModeDefault = TRUE;
437     InstallInfo &Info = *static_cast<InstallInfo *>(g_pInfo);
438     LPCWSTR AppName = Info.AppName, Archive = Info.ArchivePath, None = L"!";
439     CStringW installdir, tempdir, files, shortcut, tmp;
440     HRESULT hr;
441     CRegKey arpkey;
442     Info.ArpKey = &arpkey;
443 
444     if (!*GetGenerateString(DB_GENINST_FILES, files, L"*.exe|*.*"))
445         return ErrorBox(ERROR_BAD_FORMAT);
446 
447     GetCommonString(DB_SCOPE, tmp);
448     if (tmp.CompareNoCase(L"User") == 0)
449         Info.PerUser = TRUE;
450     else if (tmp.CompareNoCase(L"Machine") == 0)
451         Info.PerUser = FALSE;
452     else
453         Info.PerUser = PerUserModeDefault;
454 
455     hr = GetProgramFilesPath(installdir, Info.PerUser, Info.GetGuiOwner());
456     if ((Info.Error = ErrorFromHResult(hr)) != 0)
457         return ErrorBox(Info.Error);
458 
459     GetGenerateString(DB_GENINST_DIR, tmp);
460     if (tmp.Find('%') == 0 && ExpandEnvStrings(tmp))
461         installdir = tmp;
462     else if (tmp.Compare(None))
463         installdir = BuildPath(installdir, tmp.IsEmpty() ? AppName : tmp.GetString());
464     Info.InstallDir = installdir.GetString();
465     Info.InstallDirLen = installdir.GetLength() + 1;
466     hr = SHPathPrepareForWriteW(Info.GetGuiOwner(), NULL, installdir, SHPPFW_DIRCREATE);
467     if ((Info.Error = ErrorFromHResult(hr)) != 0)
468         return ErrorBox(Info.Error);
469 
470     // Create the destination directory, and inside it, a temporary directory
471     // where we will extract the archive to before moving the files to their
472     // final location (adding uninstall entries as we go)
473     tempdir.Format(L"%s\\~RAM%u.tmp", installdir.GetString(), GetCurrentProcessId());
474     Info.Error = CreateDirectoryTree(tempdir.GetString());
475     if (Info.Error)
476         return ErrorBox(Info.Error);
477 
478     if (!*GetGenerateString(DB_GENINST_LNK, shortcut))
479         shortcut.Format(L"%s.lnk", AppName);
480     Info.ShortcutFile = shortcut.Compare(None) ? shortcut.GetString() : NULL;
481 
482     // Create the uninstall registration key
483     LPCWSTR arpkeyname = AppName;
484     tmp = BuildPath(REGPATH_UNINSTALL, arpkeyname);
485     HKEY hRoot = Info.PerUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
486     REGSAM regsam = KEY_READ | KEY_WRITE | (IsSystem64Bit() ? KEY_WOW64_64KEY : KEY_WOW64_32KEY);
487     Info.Error = arpkey.Create(hRoot, tmp, NULL, REG_OPTION_NON_VOLATILE, regsam);
488     if (!Info.Error)
489     {
490         arpkey.RecurseDeleteKey(GENERATE_ARPSUBKEY);
491         Info.Error = Info.Entries.Create(arpkey, GENERATE_ARPSUBKEY);
492     }
493     if (Info.Error)
494         ErrorBox(Info.Error);
495 
496     if (!Info.Error)
497     {
498         BOOL isCab = SplitFileAndDirectory(Archive).Right(4).CompareNoCase(L".cab") == 0;
499         Info.Error = isCab ? ExtractCab(Archive, tempdir, ExtractCallback, &Info)
500                            : ExtractZip(Archive, tempdir, ExtractCallback, &Info);
501     }
502 
503     if (!Info.Error)
504     {
505         // We now know how many files we extracted, change from marquee to normal progress bar.
506         SendMessage(Info.hDlg, IM_STARTPROGRESS, 0, 0);
507 
508         for (int pos = 1; pos > 0 && !Info.Error;)
509         {
510             pos = files.Find(L'|');
511             CStringW item = pos <= 0 ? files : files.Left(pos);
512             files = files.Mid(pos + 1);
513             InstallFiles(tempdir, item, installdir);
514         }
515 
516         if (!Info.MainApp.IsEmpty())
517         {
518             AddUninstallOperationsFromDB(DB_GENINST_DELREG, UNOP_REGKEY);
519             AddUninstallOperationsFromDB(DB_GENINST_DELREGEMPTY, UNOP_EMPTYREGKEY);
520             AddUninstallOperationsFromDB(DB_GENINST_DELFILE, UNOP_FILE, L"\\");
521             AddUninstallOperationsFromDB(DB_GENINST_DELDIR, UNOP_DIR, L"\\");
522             AddUninstallOperationsFromDB(DB_GENINST_DELDIREMPTY, UNOP_EMPTYDIR, L"\\");
523             AddEntry(UNOP_EMPTYDIR, L"\\");
524 
525             WriteArpEntry(L"DisplayName", AppName);
526             WriteArpEntry(L"InstallLocation", Info.InstallDir); // Note: This value is used by the uninstaller!
527 
528             LPWSTR p = tmp.GetBuffer(1 + MAX_PATH);
529             p[0] = L'\"';
530             GetModuleFileName(NULL, p + 1, MAX_PATH);
531             tmp.ReleaseBuffer();
532             UINT cch = tmp.GetLength(), bitness = IsSystem64Bit() ? 64 : 32;
533             WCHAR modechar = Info.PerUser ? 'U' : 'M';
534             LPCWSTR unparamsfmt = L"\" /" CMD_KEY_UNINSTALL L" /K%s \"%c%d\\%s\"";
535             (tmp = tmp.Mid(0, cch)).AppendFormat(unparamsfmt, L"", modechar, bitness, arpkeyname);
536             WriteArpEntry(L"UninstallString", tmp);
537             (tmp = tmp.Mid(0, cch)).AppendFormat(unparamsfmt, L" /S", modechar, bitness, arpkeyname);
538             WriteArpEntry(L"QuietUninstallString", tmp);
539 
540             if (GetCustomIconPath(Info, tmp) != S_OK)
541                 tmp = Info.MainApp;
542             WriteArpEntry(L"DisplayIcon", tmp);
543 
544             if (*GetCommonString(DB_VERSION, tmp))
545                 WriteArpEntry(L"DisplayVersion", tmp);
546 
547             SYSTEMTIME st;
548             GetSystemTime(&st);
549             tmp.Format(L"%.4u%.2u%.2u", st.wYear, st.wMonth, st.wDay);
550             WriteArpEntry(L"InstallDate", tmp);
551 
552             if (*GetCommonString(DB_PUBLISHER, tmp))
553                 WriteArpEntry(L"Publisher", tmp);
554 
555 #if DBG
556             tmp.Format(L"sys64=%d rapps%d", IsSystem64Bit(), sizeof(void*) * 8);
557             WriteArpEntry(L"_DEBUG", tmp);
558 #endif
559         }
560 
561         if (!Info.Error && Info.ShortcutFile)
562         {
563             CreateShortcut(Info.MainApp);
564         }
565     }
566 
567     DeleteDirectoryTree(tempdir.GetString());
568     RemoveDirectory(installdir.GetString()); // This is harmless even if we installed something
569     return 0;
570 }
571 
572 static DWORD CALLBACK
573 WorkerThread(LPVOID Parameter)
574 {
575     ((LPTHREAD_START_ROUTINE)Parameter)(NULL);
576     return SendMessage(g_pInfo->hDlg, IM_END, 0, 0);
577 }
578 
579 static INT_PTR CALLBACK
580 UIDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
581 {
582     HWND hPB = GetDlgItem(hDlg, 1);
583     switch(uMsg)
584     {
585         case IM_STARTPROGRESS:
586             SetWindowLongPtr(hPB, GWL_STYLE, WS_CHILD | WS_VISIBLE);
587             SendMessageW(hPB, PBM_SETMARQUEE, FALSE, 0);
588             SendMessageW(hPB, PBM_SETRANGE32, 0, g_pInfo->Count);
589             SendMessageW(hPB, PBM_SETPOS, 0, 0);
590             break;
591         case IM_PROGRESS:
592             SendMessageW(hPB, PBM_DELTAPOS, 1, 0);
593             break;
594         case IM_END:
595             DestroyWindow(hDlg);
596             break;
597         case WM_INITDIALOG:
598         {
599             SendMessageW(hPB, PBM_SETMARQUEE, TRUE, 0);
600             g_pInfo->hDlg = hDlg;
601             HICON hIco = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_MAIN));
602             SendMessageW(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIco);
603             SendMessageW(hDlg, WM_SETTEXT, 0, (LPARAM)g_pInfo->AppName);
604             if (!SHCreateThread(WorkerThread, (void*)lParam, CTF_COINIT, NULL))
605             {
606                 ErrorBox();
607                 SendMessageW(hDlg, IM_END, 0, 0);
608             }
609             break;
610         }
611         case WM_CLOSE:
612             return TRUE;
613         case WM_DESTROY:
614             PostMessage(NULL, WM_QUIT, 0, 0);
615             break;
616     }
617     return FALSE;
618 }
619 
620 static BOOL
621 CreateUI(BOOL Silent, LPTHREAD_START_ROUTINE ThreadProc)
622 {
623     enum { DLGW = 150, DLGH = 20, PAD = 4, PADx2 = PAD * 2, CHILDCOUNT = 1 };
624     const UINT DlgStyle = WS_CAPTION | DS_MODALFRAME | DS_NOFAILCREATE | DS_CENTER;
625     static const WORD DlgTmpl[] =
626     {
627         LOWORD(DlgStyle), HIWORD(DlgStyle), 0, 0, CHILDCOUNT, 0, 0, DLGW, DLGH, 0, 0, 0,
628         PBS_MARQUEE, HIWORD(WS_CHILD | WS_VISIBLE), 0, 0, PAD, PAD, DLGW - PADx2, DLGH - PADx2, 1,
629         'm', 's', 'c', 't', 'l', 's', '_', 'p', 'r', 'o', 'g', 'r', 'e', 's', 's', '3', '2', 0, 0,
630     };
631     HWND hWnd = CreateDialogIndirectParamW(NULL, (LPCDLGTEMPLATE)DlgTmpl, NULL,
632                                            UIDlgProc, (LPARAM)ThreadProc);
633     if (!hWnd)
634     {
635         ErrorBox();
636         return FALSE;
637     }
638     else if (!Silent)
639     {
640         ShowWindow(hWnd, SW_SHOW);
641     }
642     MSG Msg;
643     while (GetMessageW(&Msg, NULL, 0, 0))
644     {
645         TranslateMessage(&Msg);
646         DispatchMessageW(&Msg);
647     }
648     return TRUE;
649 }
650 
651 BOOL
652 ExtractAndRunGeneratedInstaller(const CAvailableApplicationInfo &AppInfo, LPCWSTR Archive)
653 {
654     InstallInfo Info(AppInfo.szDisplayName, *AppInfo.GetConfigParser(), Archive);
655     g_pInfo = &Info;
656     return CreateUI(Info.Silent, ExtractAndInstallThread) ? !Info.Error : FALSE;
657 }
658 
659 struct UninstallInfo : CommonInfo
660 {
661     CInstalledApplicationInfo &AppInfo;
662     UninstallInfo(CInstalledApplicationInfo &Info, BOOL IsSilent)
663     : CommonInfo(Info.szDisplayName, IsSilent), AppInfo(Info)
664     {
665         ArpKey = &Info.GetRegKey();
666     }
667 };
668 
669 enum UninstallStage
670 {
671     US_ITEMS,
672     US_CONTAINERS,
673     UINSTALLSTAGECOUNT
674 };
675 
676 static DWORD CALLBACK
677 UninstallThread(LPVOID Parameter)
678 {
679     UninstallInfo &Info = *static_cast<UninstallInfo *>(g_pInfo);
680 
681     CStringW tmp, path, installdir;
682     path.LoadString(IDS_INSTGEN_CONFIRMUNINST);
683     tmp.Format(path.GetString(), Info.AppName);
684     if (!Info.Silent &&
685         MessageBox(Info.GetGuiOwner(), tmp, Info.AppName, MB_YESNO | MB_ICONQUESTION) != IDYES)
686     {
687         Info.Error = ERROR_CANCELLED;
688         SendMessage(Info.hDlg, IM_END, 0, 0);
689         return 0;
690     }
691 
692     Info.Error = Info.Entries.Open(*Info.ArpKey, GENERATE_ARPSUBKEY, KEY_READ);
693     if (Info.Error)
694         return ErrorBox(Info.Error);
695 
696     RegQueryInfoKey(Info.Entries, NULL, NULL, NULL, NULL, NULL, NULL, &Info.Count, NULL, NULL, NULL, NULL);
697     Info.Count *= UINSTALLSTAGECOUNT;
698     SendMessage(Info.hDlg, IM_STARTPROGRESS, 0, 0);
699 
700     if (!GetRegString(*Info.ArpKey, L"InstallLocation", installdir) || installdir.IsEmpty())
701         return ErrorBox(ERROR_INVALID_NAME);
702 
703     for (UINT stage = 0; stage < UINSTALLSTAGECOUNT; ++stage)
704     {
705         for (UINT vi = 0;; ++vi)
706         {
707             WCHAR value[MAX_PATH], data[MAX_PATH * 2];
708             DWORD valsize = _countof(value), size = sizeof(data) - sizeof(WCHAR), rt;
709             data[_countof(data) - 1] = UNICODE_NULL;
710             UINT err = RegEnumValue(Info.Entries, vi, value, &valsize, NULL, &rt, (BYTE*)data, &size);
711             if (err)
712             {
713                 if (err != ERROR_NO_MORE_ITEMS)
714                 {
715                     return ErrorBox(err);
716                 }
717                 break;
718             }
719 
720             LPCWSTR str = data;
721             WORD op = value[0];
722             switch(*data ? MAKEWORD(stage, op) : 0)
723             {
724                 case MAKEWORD(US_ITEMS, UNOP_REGKEY):
725                 case MAKEWORD(US_CONTAINERS, UNOP_EMPTYREGKEY):
726                 {
727                     REGSAM wowsam = 0;
728                     HKEY hKey = NULL;
729                     path.Format(L"%.4s", data);
730                     if (path.CompareNoCase(L"HKCR") == 0)
731                         hKey = HKEY_CLASSES_ROOT;
732                     else if (path.CompareNoCase(L"HKCU") == 0)
733                         hKey = HKEY_CURRENT_USER;
734                     else if (path.CompareNoCase(L"HKLM") == 0)
735                         hKey = HKEY_LOCAL_MACHINE;
736 
737                     if (data[4] == '6' && data[5] == '4')
738                         wowsam = KEY_WOW64_64KEY;
739                     else if (data[4] == '3' && data[5] == '2')
740                         wowsam = KEY_WOW64_32KEY;
741 
742                     str = &data[wowsam ? 6 : 4];
743                     if (!hKey || *str != L'\\')
744                         break;
745                     tmp = SplitFileAndDirectory(++str, &path);
746                     if (!tmp.IsEmpty() && !path.IsEmpty())
747                     {
748                         CRegKey key;
749                         err = key.Open(hKey, path, DELETE | wowsam);
750                         if (err == ERROR_SUCCESS)
751                         {
752                             if (op == UNOP_REGKEY)
753                                 err = key.RecurseDeleteKey(tmp);
754                             else if (RegKeyHasValues(hKey, str, wowsam) == S_FALSE)
755                                 err = key.DeleteSubKey(tmp);
756                         }
757                         switch(err)
758                         {
759                             case ERROR_SUCCESS:
760                             case ERROR_FILE_NOT_FOUND:
761                             case ERROR_PATH_NOT_FOUND:
762                                 break;
763                             default:
764                                 return ErrorBox(err);
765                         }
766                     }
767                     break;
768                 }
769 
770                 case MAKEWORD(US_ITEMS, UNOP_FILE):
771                 {
772                     if (*data == L'\\')
773                         str = (path = BuildPath(installdir, data));
774 
775                     if (!DeleteFile(str))
776                     {
777                         err = GetLastError();
778                         if (err != ERROR_FILE_NOT_FOUND)
779                         {
780                             return ErrorBox(err);
781                         }
782                     }
783                     break;
784                 }
785 
786                 case MAKEWORD(US_CONTAINERS, UNOP_EMPTYDIR):
787                 case MAKEWORD(US_CONTAINERS, UNOP_DIR):
788                 {
789                     if (*data == L'\\')
790                         str = (path = BuildPath(installdir, data));
791 
792                     if (op == UNOP_DIR)
793                         DeleteDirectoryTree(str, Info.GetGuiOwner());
794                     else
795                         RemoveDirectory(str);
796                     break;
797                 }
798             }
799             SendMessage(Info.hDlg, IM_PROGRESS, 0, 0);
800         }
801     }
802     if (!Info.Error)
803     {
804         Info.Error = CAppDB::RemoveInstalledAppFromRegistry(&Info.AppInfo);
805         if (Info.Error)
806             return ErrorBox(Info.Error);
807     }
808     return 0;
809 }
810 
811 BOOL
812 UninstallGenerated(CInstalledApplicationInfo &AppInfo, UninstallCommandFlags Flags)
813 {
814     UninstallInfo Info(AppInfo, Flags & UCF_SILENT);
815     g_pInfo = &Info;
816     return CreateUI(Info.Silent, UninstallThread) ? !Info.Error : FALSE;
817 }
818