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