1 /* 2 * PROJECT: ReactOS Applications Manager 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Classes for working with available applications 5 * COPYRIGHT: Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org) 6 * Copyright 2020 He Yang (1160386205@qq.com) 7 * Copyright 2021-2023 Mark Jansen <mark.jansen@reactos.org> 8 */ 9 10 #include "rapps.h" 11 #include "appview.h" 12 13 CAppInfo::CAppInfo(const CStringW &Identifier, AppsCategories Category) 14 : szIdentifier(Identifier), iCategory(Category) 15 { 16 } 17 18 CAppInfo::~CAppInfo() 19 { 20 } 21 22 CAvailableApplicationInfo::CAvailableApplicationInfo( 23 CConfigParser *Parser, 24 const CStringW &PkgName, 25 AppsCategories Category, 26 const CPathW &BasePath) 27 : CAppInfo(PkgName, Category), m_Parser(Parser), m_ScrnshotRetrieved(false), m_LanguagesLoaded(false) 28 { 29 m_Parser->GetString(L"Name", szDisplayName); 30 m_Parser->GetString(L"Version", szDisplayVersion); 31 m_Parser->GetString(L"URLDownload", m_szUrlDownload); 32 m_Parser->GetString(L"Description", szComments); 33 34 CPathW IconPath = BasePath; 35 IconPath += L"icons"; 36 37 CStringW IconName; 38 if (m_Parser->GetString(L"Icon", IconName)) 39 { 40 IconPath += IconName; 41 } 42 else 43 { 44 // inifile.ico 45 IconPath += (szIdentifier + L".ico"); 46 } 47 48 if (PathFileExistsW(IconPath)) 49 { 50 szDisplayIcon = (LPCWSTR)IconPath; 51 } 52 53 INT iSizeBytes; 54 55 if (m_Parser->GetInt(L"SizeBytes", iSizeBytes)) 56 { 57 StrFormatByteSizeW(iSizeBytes, m_szSize.GetBuffer(MAX_PATH), MAX_PATH); 58 m_szSize.ReleaseBuffer(); 59 } 60 61 m_Parser->GetString(L"URLSite", m_szUrlSite); 62 } 63 64 CAvailableApplicationInfo::~CAvailableApplicationInfo() 65 { 66 delete m_Parser; 67 } 68 69 VOID 70 CAvailableApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit) 71 { 72 RichEdit->SetText(szDisplayName, CFE_BOLD); 73 InsertVersionInfo(RichEdit); 74 RichEdit->LoadAndInsertText(IDS_AINFO_LICENSE, LicenseString(), 0); 75 InsertLanguageInfo(RichEdit); 76 77 RichEdit->LoadAndInsertText(IDS_AINFO_SIZE, m_szSize, 0); 78 RichEdit->LoadAndInsertText(IDS_AINFO_URLSITE, m_szUrlSite, CFE_LINK); 79 RichEdit->LoadAndInsertText(IDS_AINFO_DESCRIPTION, szComments, 0); 80 RichEdit->LoadAndInsertText(IDS_AINFO_URLDOWNLOAD, m_szUrlDownload, CFE_LINK); 81 RichEdit->LoadAndInsertText(IDS_AINFO_PACKAGE_NAME, szIdentifier, 0); 82 } 83 84 int 85 CompareVersion(const CStringW &left, const CStringW &right) 86 { 87 int nLeft = 0, nRight = 0; 88 89 while (true) 90 { 91 CStringW leftPart = left.Tokenize(L".", nLeft); 92 CStringW rightPart = right.Tokenize(L".", nRight); 93 94 if (leftPart.IsEmpty() && rightPart.IsEmpty()) 95 return 0; 96 if (leftPart.IsEmpty()) 97 return -1; 98 if (rightPart.IsEmpty()) 99 return 1; 100 101 int leftVal, rightVal; 102 103 if (!StrToIntExW(leftPart, STIF_DEFAULT, &leftVal)) 104 leftVal = 0; 105 if (!StrToIntExW(rightPart, STIF_DEFAULT, &rightVal)) 106 rightVal = 0; 107 108 if (leftVal > rightVal) 109 return 1; 110 if (rightVal < leftVal) 111 return -1; 112 } 113 } 114 115 VOID 116 CAvailableApplicationInfo::InsertVersionInfo(CAppRichEdit *RichEdit) 117 { 118 CStringW szRegName; 119 m_Parser->GetString(L"RegName", szRegName); 120 121 BOOL bIsInstalled = ::GetInstalledVersion(NULL, szRegName) || ::GetInstalledVersion(NULL, szDisplayName); 122 if (bIsInstalled) 123 { 124 CStringW szInstalledVersion; 125 CStringW szNameVersion = szDisplayName + L" " + szDisplayVersion; 126 BOOL bHasInstalledVersion = ::GetInstalledVersion(&szInstalledVersion, szRegName) || 127 ::GetInstalledVersion(&szInstalledVersion, szDisplayName) || 128 ::GetInstalledVersion(&szInstalledVersion, szNameVersion); 129 130 if (bHasInstalledVersion) 131 { 132 BOOL bHasUpdate = CompareVersion(szInstalledVersion, szDisplayVersion) < 0; 133 if (bHasUpdate) 134 RichEdit->LoadAndInsertText(IDS_STATUS_UPDATE_AVAILABLE, CFE_ITALIC); 135 else 136 RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC); 137 138 RichEdit->LoadAndInsertText(IDS_AINFO_VERSION, szInstalledVersion, 0); 139 } 140 else 141 { 142 RichEdit->LoadAndInsertText(IDS_STATUS_INSTALLED, CFE_ITALIC); 143 } 144 } 145 else 146 { 147 RichEdit->LoadAndInsertText(IDS_STATUS_NOTINSTALLED, CFE_ITALIC); 148 } 149 150 RichEdit->LoadAndInsertText(IDS_AINFO_AVAILABLEVERSION, szDisplayVersion, 0); 151 } 152 153 CStringW 154 CAvailableApplicationInfo::LicenseString() 155 { 156 INT IntBuffer; 157 m_Parser->GetInt(L"LicenseType", IntBuffer); 158 CStringW szLicenseString; 159 m_Parser->GetString(L"License", szLicenseString); 160 LicenseType licenseType; 161 162 if (IsKnownLicenseType(IntBuffer)) 163 { 164 licenseType = static_cast<LicenseType>(IntBuffer); 165 } 166 else 167 { 168 licenseType = LICENSE_NONE; 169 if (szLicenseString.CompareNoCase(L"Freeware") == 0) 170 { 171 licenseType = LICENSE_FREEWARE; 172 szLicenseString = L""; 173 } 174 } 175 176 CStringW szLicense; 177 switch (licenseType) 178 { 179 case LICENSE_OPENSOURCE: 180 szLicense.LoadStringW(IDS_LICENSE_OPENSOURCE); 181 break; 182 case LICENSE_FREEWARE: 183 szLicense.LoadStringW(IDS_LICENSE_FREEWARE); 184 break; 185 case LICENSE_TRIAL: 186 szLicense.LoadStringW(IDS_LICENSE_TRIAL); 187 break; 188 default: 189 return szLicenseString; 190 } 191 192 if (!szLicenseString.IsEmpty()) 193 szLicense += L" (" + szLicenseString + L")"; 194 return szLicense; 195 } 196 197 VOID 198 CAvailableApplicationInfo::InsertLanguageInfo(CAppRichEdit *RichEdit) 199 { 200 if (!m_LanguagesLoaded) 201 { 202 RetrieveLanguages(); 203 } 204 205 if (m_LanguageLCIDs.GetSize() == 0) 206 { 207 return; 208 } 209 210 const INT nTranslations = m_LanguageLCIDs.GetSize(); 211 CStringW szLangInfo; 212 CStringW szLoadedTextAvailability; 213 CStringW szLoadedAInfoText; 214 215 szLoadedAInfoText.LoadStringW(IDS_AINFO_LANGUAGES); 216 217 const LCID lcEnglish = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT); 218 if (m_LanguageLCIDs.Find(GetUserDefaultLCID()) >= 0) 219 { 220 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_AVAILABLE_TRANSLATION); 221 if (nTranslations > 1) 222 { 223 CStringW buf; 224 buf.LoadStringW(IDS_LANGUAGE_MORE_PLACEHOLDER); 225 szLangInfo.Format(buf, nTranslations - 1); 226 } 227 else 228 { 229 szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE); 230 szLangInfo = L" (" + szLangInfo + L")"; 231 } 232 } 233 else if (m_LanguageLCIDs.Find(lcEnglish) >= 0) 234 { 235 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_ENGLISH_TRANSLATION); 236 if (nTranslations > 1) 237 { 238 CStringW buf; 239 buf.LoadStringW(IDS_LANGUAGE_AVAILABLE_PLACEHOLDER); 240 szLangInfo.Format(buf, nTranslations - 1); 241 } 242 else 243 { 244 szLangInfo.LoadStringW(IDS_LANGUAGE_SINGLE); 245 szLangInfo = L" (" + szLangInfo + L")"; 246 } 247 } 248 else 249 { 250 szLoadedTextAvailability.LoadStringW(IDS_LANGUAGE_NO_TRANSLATION); 251 } 252 253 RichEdit->InsertText(szLoadedAInfoText, CFE_BOLD); 254 RichEdit->InsertText(szLoadedTextAvailability, NULL); 255 RichEdit->InsertText(szLangInfo, CFE_ITALIC); 256 } 257 258 VOID 259 CAvailableApplicationInfo::RetrieveLanguages() 260 { 261 m_LanguagesLoaded = true; 262 263 CStringW szBuffer; 264 if (!m_Parser->GetString(L"Languages", szBuffer)) 265 { 266 return; 267 } 268 269 // Parse parameter string 270 int iIndex = 0; 271 while (true) 272 { 273 CStringW szLocale = szBuffer.Tokenize(L"|", iIndex); 274 if (szLocale.IsEmpty()) 275 break; 276 277 szLocale = L"0x" + szLocale; 278 279 INT iLCID; 280 if (StrToIntExW(szLocale, STIF_SUPPORT_HEX, &iLCID)) 281 { 282 m_LanguageLCIDs.Add(static_cast<LCID>(iLCID)); 283 } 284 } 285 } 286 287 BOOL 288 CAvailableApplicationInfo::Valid() const 289 { 290 return !szDisplayName.IsEmpty() && !m_szUrlDownload.IsEmpty(); 291 } 292 293 BOOL 294 CAvailableApplicationInfo::CanModify() 295 { 296 return FALSE; 297 } 298 299 BOOL 300 CAvailableApplicationInfo::RetrieveIcon(CStringW &Path) const 301 { 302 Path = szDisplayIcon; 303 return !Path.IsEmpty(); 304 } 305 306 #define MAX_SCRNSHOT_NUM 16 307 BOOL 308 CAvailableApplicationInfo::RetrieveScreenshot(CStringW &Path) 309 { 310 if (!m_ScrnshotRetrieved) 311 { 312 static_assert(MAX_SCRNSHOT_NUM < 10000, "MAX_SCRNSHOT_NUM is too big"); 313 for (int i = 0; i < MAX_SCRNSHOT_NUM; i++) 314 { 315 CStringW ScrnshotField; 316 ScrnshotField.Format(L"Screenshot%d", i + 1); 317 CStringW ScrnshotLocation; 318 if (!m_Parser->GetString(ScrnshotField, ScrnshotLocation)) 319 { 320 // We stop at the first screenshot not found, 321 // so screenshots _have_ to be consecutive 322 break; 323 } 324 325 if (PathIsURLW(ScrnshotLocation.GetString())) 326 { 327 m_szScrnshotLocation.Add(ScrnshotLocation); 328 } 329 } 330 m_ScrnshotRetrieved = true; 331 } 332 333 if (m_szScrnshotLocation.GetSize() > 0) 334 { 335 Path = m_szScrnshotLocation[0]; 336 } 337 338 return !Path.IsEmpty(); 339 } 340 341 VOID 342 CAvailableApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const 343 { 344 Url = m_szUrlDownload; 345 m_Parser->GetString(L"SHA1", Sha1); 346 INT iSizeBytes; 347 348 if (m_Parser->GetInt(L"SizeBytes", iSizeBytes)) 349 { 350 SizeInBytes = (ULONG)iSizeBytes; 351 } 352 else 353 { 354 SizeInBytes = 0; 355 } 356 } 357 358 VOID 359 CAvailableApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload) 360 { 361 License = LicenseString(); 362 Size = m_szSize; 363 UrlSite = m_szUrlSite; 364 UrlDownload = m_szUrlDownload; 365 } 366 367 BOOL 368 CAvailableApplicationInfo::UninstallApplication(BOOL bModify) 369 { 370 ATLASSERT(FALSE && "Should not be called"); 371 return FALSE; 372 } 373 374 CInstalledApplicationInfo::CInstalledApplicationInfo( 375 HKEY Key, 376 const CStringW &KeyName, 377 AppsCategories Category, 378 int KeyIndex) 379 : CAppInfo(KeyName, Category), m_hKey(Key), iKeyIndex(KeyIndex) 380 { 381 if (GetApplicationRegString(L"DisplayName", szDisplayName)) 382 { 383 GetApplicationRegString(L"DisplayIcon", szDisplayIcon); 384 GetApplicationRegString(L"DisplayVersion", szDisplayVersion); 385 GetApplicationRegString(L"Comments", szComments); 386 } 387 } 388 389 CInstalledApplicationInfo::~CInstalledApplicationInfo() 390 { 391 } 392 393 VOID 394 CInstalledApplicationInfo::AddApplicationRegString( 395 CAppRichEdit *RichEdit, 396 UINT StringID, 397 const CStringW &String, 398 DWORD TextFlags) 399 { 400 CStringW Tmp; 401 if (GetApplicationRegString(String, Tmp)) 402 { 403 RichEdit->InsertTextWithString(StringID, Tmp, TextFlags); 404 } 405 } 406 407 VOID 408 CInstalledApplicationInfo::ShowAppInfo(CAppRichEdit *RichEdit) 409 { 410 RichEdit->SetText(szDisplayName, CFE_BOLD); 411 RichEdit->InsertText(L"\n", 0); 412 413 RichEdit->InsertTextWithString(IDS_INFO_VERSION, szDisplayVersion, 0); 414 AddApplicationRegString(RichEdit, IDS_INFO_PUBLISHER, L"Publisher", 0); 415 AddApplicationRegString(RichEdit, IDS_INFO_REGOWNER, L"RegOwner", 0); 416 AddApplicationRegString(RichEdit, IDS_INFO_PRODUCTID, L"ProductID", 0); 417 AddApplicationRegString(RichEdit, IDS_INFO_HELPLINK, L"HelpLink", CFM_LINK); 418 AddApplicationRegString(RichEdit, IDS_INFO_HELPPHONE, L"HelpTelephone", 0); 419 AddApplicationRegString(RichEdit, IDS_INFO_README, L"Readme", 0); 420 AddApplicationRegString(RichEdit, IDS_INFO_CONTACT, L"Contact", 0); 421 AddApplicationRegString(RichEdit, IDS_INFO_UPDATEINFO, L"URLUpdateInfo", CFM_LINK); 422 AddApplicationRegString(RichEdit, IDS_INFO_INFOABOUT, L"URLInfoAbout", CFM_LINK); 423 RichEdit->InsertTextWithString(IDS_INFO_COMMENTS, szComments, 0); 424 425 if (m_szInstallDate.IsEmpty()) 426 { 427 RetrieveInstallDate(); 428 } 429 430 RichEdit->InsertTextWithString(IDS_INFO_INSTALLDATE, m_szInstallDate, 0); 431 AddApplicationRegString(RichEdit, IDS_INFO_INSTLOCATION, L"InstallLocation", 0); 432 AddApplicationRegString(RichEdit, IDS_INFO_INSTALLSRC, L"InstallSource", 0); 433 434 if (m_szUninstallString.IsEmpty()) 435 { 436 RetrieveUninstallStrings(); 437 } 438 439 RichEdit->InsertTextWithString(IDS_INFO_UNINSTALLSTR, m_szUninstallString, 0); 440 RichEdit->InsertTextWithString(IDS_INFO_MODIFYPATH, m_szModifyString, 0); 441 } 442 443 VOID 444 CInstalledApplicationInfo::RetrieveInstallDate() 445 { 446 DWORD dwInstallTimeStamp; 447 SYSTEMTIME InstallLocalTime; 448 if (GetApplicationRegString(L"InstallDate", m_szInstallDate)) 449 { 450 ZeroMemory(&InstallLocalTime, sizeof(InstallLocalTime)); 451 // Check if we have 8 characters to parse the datetime. 452 // Maybe other formats exist as well? 453 m_szInstallDate = m_szInstallDate.Trim(); 454 if (m_szInstallDate.GetLength() == 8) 455 { 456 InstallLocalTime.wYear = wcstol(m_szInstallDate.Left(4).GetString(), NULL, 10); 457 InstallLocalTime.wMonth = wcstol(m_szInstallDate.Mid(4, 2).GetString(), NULL, 10); 458 InstallLocalTime.wDay = wcstol(m_szInstallDate.Mid(6, 2).GetString(), NULL, 10); 459 } 460 } 461 // It might be a DWORD (Unix timestamp). try again. 462 else if (GetApplicationRegDword(L"InstallDate", &dwInstallTimeStamp)) 463 { 464 FILETIME InstallFileTime; 465 SYSTEMTIME InstallSystemTime; 466 467 UnixTimeToFileTime(dwInstallTimeStamp, &InstallFileTime); 468 FileTimeToSystemTime(&InstallFileTime, &InstallSystemTime); 469 470 // convert to localtime 471 SystemTimeToTzSpecificLocalTime(NULL, &InstallSystemTime, &InstallLocalTime); 472 } 473 474 // convert to readable date string 475 int cchTimeStrLen = GetDateFormatW(LOCALE_USER_DEFAULT, 0, &InstallLocalTime, NULL, 0, 0); 476 477 GetDateFormatW( 478 LOCALE_USER_DEFAULT, // use default locale for current user 479 0, &InstallLocalTime, NULL, m_szInstallDate.GetBuffer(cchTimeStrLen), cchTimeStrLen); 480 m_szInstallDate.ReleaseBuffer(); 481 } 482 483 VOID 484 CInstalledApplicationInfo::RetrieveUninstallStrings() 485 { 486 DWORD dwWindowsInstaller = 0; 487 if (GetApplicationRegDword(L"WindowsInstaller", &dwWindowsInstaller) && dwWindowsInstaller) 488 { 489 // MSI has the same info in Uninstall / modify, so manually build it 490 m_szUninstallString.Format(L"msiexec /x%s", szIdentifier.GetString()); 491 } 492 else 493 { 494 GetApplicationRegString(L"UninstallString", m_szUninstallString); 495 } 496 DWORD dwNoModify = 0; 497 if (!GetApplicationRegDword(L"NoModify", &dwNoModify)) 498 { 499 CStringW Tmp; 500 if (GetApplicationRegString(L"NoModify", Tmp)) 501 { 502 dwNoModify = Tmp.GetLength() > 0 ? (Tmp[0] == '1') : 0; 503 } 504 else 505 { 506 dwNoModify = 0; 507 } 508 } 509 if (!dwNoModify) 510 { 511 if (dwWindowsInstaller) 512 { 513 m_szModifyString.Format(L"msiexec /i%s", szIdentifier.GetString()); 514 } 515 else 516 { 517 GetApplicationRegString(L"ModifyPath", m_szModifyString); 518 } 519 } 520 } 521 522 BOOL 523 CInstalledApplicationInfo::Valid() const 524 { 525 return !szDisplayName.IsEmpty(); 526 } 527 528 BOOL 529 CInstalledApplicationInfo::CanModify() 530 { 531 if (m_szUninstallString.IsEmpty()) 532 { 533 RetrieveUninstallStrings(); 534 } 535 536 return !m_szModifyString.IsEmpty(); 537 } 538 539 BOOL 540 CInstalledApplicationInfo::RetrieveIcon(CStringW &Path) const 541 { 542 Path = szDisplayIcon; 543 return !Path.IsEmpty(); 544 } 545 546 BOOL 547 CInstalledApplicationInfo::RetrieveScreenshot(CStringW & /*Path*/) 548 { 549 return FALSE; 550 } 551 552 VOID 553 CInstalledApplicationInfo::GetDownloadInfo(CStringW &Url, CStringW &Sha1, ULONG &SizeInBytes) const 554 { 555 ATLASSERT(FALSE && "Should not be called"); 556 } 557 558 VOID 559 CInstalledApplicationInfo::GetDisplayInfo(CStringW &License, CStringW &Size, CStringW &UrlSite, CStringW &UrlDownload) 560 { 561 ATLASSERT(FALSE && "Should not be called"); 562 } 563 564 BOOL 565 CInstalledApplicationInfo::UninstallApplication(BOOL bModify) 566 { 567 if (m_szUninstallString.IsEmpty()) 568 { 569 RetrieveUninstallStrings(); 570 } 571 572 BOOL bSuccess = StartProcess(bModify ? m_szModifyString : m_szUninstallString, TRUE); 573 574 if (bSuccess && !bModify) 575 WriteLogMessage(EVENTLOG_SUCCESS, MSG_SUCCESS_REMOVE, szDisplayName); 576 577 return bSuccess; 578 } 579 580 BOOL 581 CInstalledApplicationInfo::GetApplicationRegString(LPCWSTR lpKeyName, CStringW &String) 582 { 583 ULONG nChars = 0; 584 // Get the size 585 if (m_hKey.QueryStringValue(lpKeyName, NULL, &nChars) != ERROR_SUCCESS) 586 { 587 String.Empty(); 588 return FALSE; 589 } 590 591 LPWSTR Buffer = String.GetBuffer(nChars); 592 LONG lResult = m_hKey.QueryStringValue(lpKeyName, Buffer, &nChars); 593 if (nChars > 0 && Buffer[nChars - 1] == UNICODE_NULL) 594 nChars--; 595 String.ReleaseBuffer(nChars); 596 597 if (lResult != ERROR_SUCCESS) 598 { 599 String.Empty(); 600 return FALSE; 601 } 602 603 if (String.Find('%') >= 0) 604 { 605 CStringW Tmp; 606 DWORD dwLen = ExpandEnvironmentStringsW(String, NULL, 0); 607 if (dwLen > 0) 608 { 609 BOOL bSuccess = ExpandEnvironmentStringsW(String, Tmp.GetBuffer(dwLen), dwLen) == dwLen; 610 Tmp.ReleaseBuffer(dwLen - 1); 611 if (bSuccess) 612 { 613 String = Tmp; 614 } 615 else 616 { 617 String.Empty(); 618 return FALSE; 619 } 620 } 621 } 622 623 return TRUE; 624 } 625 626 BOOL 627 CInstalledApplicationInfo::GetApplicationRegDword(LPCWSTR lpKeyName, DWORD *lpValue) 628 { 629 DWORD dwSize = sizeof(DWORD), dwType; 630 if (RegQueryValueExW(m_hKey, lpKeyName, NULL, &dwType, (LPBYTE)lpValue, &dwSize) != ERROR_SUCCESS || 631 dwType != REG_DWORD) 632 { 633 return FALSE; 634 } 635 636 return TRUE; 637 } 638