#include "updates.h" #include #pragma comment(lib, "Shlwapi.lib") #include #include #include #include static HRESULT String2IXMLDocument(const wchar_t* pwszData, ABI::Windows::Data::Xml::Dom::IXmlDocument** pOutXmlToastMessage) { using namespace Microsoft::WRL; using namespace ABI::Windows::Data::Xml::Dom; *pOutXmlToastMessage = nullptr; CoInitialize(nullptr); RoInitialize(RO_INIT_MULTITHREADED); ComPtr spInspectable; RETURN_IF_FAILED(RoActivateInstance(Wrappers::HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &spInspectable)); ComPtr spXmlDocument; RETURN_IF_FAILED(spInspectable.As(&spXmlDocument)); spXmlDocument.CopyTo(pOutXmlToastMessage); ComPtr spXmlDocumentIO; RETURN_IF_FAILED(spXmlDocument.As(&spXmlDocumentIO)); RETURN_IF_FAILED(spXmlDocumentIO->LoadXml(Wrappers::HStringReference(pwszData).Get())); return S_OK; } using namespace Microsoft::WRL; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation::Collections; using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::UI::Notifications; class CToastData : public RuntimeClass, ITypedEventHandler> { public: CToastData(IToastNotifier* notifier, IToastNotificationFactory* notifFactory) : notifier(notifier), notifFactory(notifFactory), cookie{}, liLastUpdate{} { } void HideToast() { if (!this) return; IToastNotification* oldToast = toast.Get(); if (oldToast) { if (notifier) { notifier->Hide(oldToast); } oldToast->remove_Activated(cookie); toast.Reset(); } } void HideAndShowToast(IXmlDocument* inputXml) { if (!this) return; HideToast(); if (notifFactory) { notifFactory->CreateToastNotification(inputXml, &toast); } IToastNotification* newToast = toast.Get(); if (newToast && notifier) { ComPtr toast2; if (SUCCEEDED(newToast->QueryInterface(IID_PPV_ARGS(&toast2)))) { toast2->put_Tag(Wrappers::HStringReference(L"ep_updates").Get()); } newToast->add_Activated(this, &cookie); notifier->Show(newToast); } } HRESULT UpdateDownloadProgress(SIZE_T downloaded, SIZE_T total) { if (!this) return E_FAIL; LARGE_INTEGER liNow; QueryPerformanceCounter(&liNow); /*if (downloaded != 0 && liNow.QuadPart - liLastUpdate.QuadPart < 1000000) // 100ms { return S_FALSE; }*/ liLastUpdate = liNow; ComPtr dataInspectable; RETURN_IF_FAILED(RoActivateInstance(Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_NotificationData).Get(), &dataInspectable)); ComPtr data; RETURN_IF_FAILED(dataInspectable.As(&data)); ComPtr> values; RETURN_IF_FAILED(data->get_Values(&values)); WCHAR szProgressValue[64]; if (total != 0) { swprintf_s(szProgressValue, L"%f", (double)downloaded / (double)total); } else { wcscpy_s(szProgressValue, ARRAYSIZE(szProgressValue), L"indeterminate"); } WCHAR szProgressStatus[80]; szProgressStatus[0] = 0; WCHAR szDownloaded[32]; szDownloaded[0] = 0; StrFormatByteSizeW(downloaded, szDownloaded, ARRAYSIZE(szDownloaded)); if (total != 0) { WCHAR szTotal[32]; szTotal[0] = 0; StrFormatByteSizeW(total, szTotal, ARRAYSIZE(szTotal)); swprintf_s(szProgressStatus, L"%s / %s", szDownloaded, szTotal); } else if (downloaded != 0) { wcscpy_s(szProgressStatus, ARRAYSIZE(szProgressStatus), szDownloaded); } else { wil::unique_hmodule hEPGui(LoadGuiModule()); LoadStringW(hEPGui.get(), IDS_UPDATES_DOWNLOADING_0, szProgressStatus, ARRAYSIZE(szProgressStatus)); } BOOLEAN bReplaced; RETURN_IF_FAILED(values->Insert( Wrappers::HStringReference(L"progressValue").Get(), Wrappers::HStringReference(szProgressValue, (UINT)wcslen(szProgressValue)).Get(), &bReplaced )); RETURN_IF_FAILED(values->Insert( Wrappers::HStringReference(L"progressStatus").Get(), Wrappers::HStringReference(szProgressStatus, (UINT)wcslen(szProgressStatus)).Get(), &bReplaced )); ComPtr notifier2; RETURN_IF_FAILED(notifier->QueryInterface(IID_PPV_ARGS(¬ifier2))); NotificationUpdateResult result; RETURN_HR(notifier2->UpdateWithTag(data.Get(), Wrappers::HStringReference(L"ep_updates").Get(), &result)); } STDMETHODIMP Invoke(IToastNotification* sender, IInspectable* argsInspectable) override { ComPtr args; RETURN_IF_FAILED(argsInspectable->QueryInterface(IID_PPV_ARGS(&args))); Wrappers::HString arguments; RETURN_IF_FAILED(args->get_Arguments(arguments.GetAddressOf())); const WCHAR* argumentsStr = arguments.GetRawBuffer(nullptr); if (!wcscmp(argumentsStr, L"action=update")) { HANDLE hEvent = CreateEventW(nullptr, FALSE, FALSE, L"EP_Ev_InstallUpdatesNoConfirm_" _T(EP_CLSID)); if (hEvent) { if (GetLastError() == ERROR_ALREADY_EXISTS) { SetEvent(hEvent); } CloseHandle(hEvent); } } return S_OK; } private: IToastNotifier* notifier; IToastNotificationFactory* notifFactory; ComPtr toast; EventRegistrationToken cookie; LARGE_INTEGER liLastUpdate; }; struct IsUpdateAvailableParameters { HINTERNET hInternet; HANDLE hEvent; }; BOOL IsUpdateAvailableHelper( WCHAR* url, char* szCheckAgainst, DWORD dwUpdateTimeout, BOOL* lpFail, CToastData* toastData, BOOL bUpdatePreferStaging, WCHAR* wszInfoURL, DWORD cchInfoURL, BOOL bNoConfirmation, HMODULE hModule, DWORD* pLeftMost, DWORD* pSecondLeft, DWORD* pSecondRight, DWORD* pRightMost) { BOOL bIsUpdateAvailable = FALSE; IsUpdateAvailableParameters params; params.hEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if (!params.hEvent) { return bIsUpdateAvailable; } Wrappers::HString hstrDownloadUrl; hstrDownloadUrl.Set(url); HINTERNET hInternet = nullptr; if (bUpdatePreferStaging) { BOOL bRet = FALSE; std::string jsonStr; hInternet = InternetOpenA(UPDATES_USER_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); if ((bRet = (hInternet != nullptr))) { HINTERNET hConnect = InternetOpenUrlW( hInternet, hstrDownloadUrl.GetRawBuffer(nullptr), nullptr, 0, INTERNET_FLAG_RAW_DATA | INTERNET_FLAG_RELOAD | INTERNET_FLAG_RESYNCHRONIZE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_DONT_CACHE, (DWORD_PTR)¶ms ); if ((bRet = (hConnect != nullptr))) { char* buffer = (char*)malloc(UPDATES_BUFSIZ); if ((bRet = (buffer != nullptr))) { DWORD dwRead = 0; while (InternetReadFile(hConnect, buffer, UPDATES_BUFSIZ, &dwRead) && dwRead != 0) { jsonStr.append(buffer, dwRead); } bRet = !jsonStr.empty(); free(buffer); } InternetCloseHandle(hConnect); } InternetCloseHandle(hInternet); } WCHAR* pszJsonStr = nullptr; if (bRet) { pszJsonStr = (WCHAR*)malloc(sizeof(WCHAR) * (jsonStr.size() + 1)); if ((bRet = (pszJsonStr != nullptr))) { MultiByteToWideChar( CP_UTF8, 0, jsonStr.c_str(), -1, pszJsonStr, (int)(jsonStr.size() + 1) ); } } if (bRet) { auto extract = [&](HSTRING* outHtmlUrl, HSTRING* outDownloadUrl) -> HRESULT { using namespace ABI::Windows::Data::Json; *outHtmlUrl = nullptr; *outDownloadUrl = nullptr; ComPtr jsonArrayStatics; RETURN_IF_FAILED(GetActivationFactory(Wrappers::HStringReference(RuntimeClass_Windows_Data_Json_JsonArray).Get(), &jsonArrayStatics)); ComPtr releases; RETURN_IF_FAILED(jsonArrayStatics->Parse(Wrappers::HStringReference(pszJsonStr).Get(), &releases)); ComPtr firstRelease; RETURN_IF_FAILED(releases->GetObjectAt(0, &firstRelease)); RETURN_IF_FAILED(firstRelease->GetNamedString(Wrappers::HStringReference(L"html_url").Get(), outHtmlUrl)); ComPtr assets; RETURN_IF_FAILED(firstRelease->GetNamedArray(Wrappers::HStringReference(L"assets").Get(), &assets)); ComPtr> assetsIterable; RETURN_IF_FAILED(assets.As(&assetsIterable)); ComPtr> assetsIterator; RETURN_IF_FAILED(assetsIterable->First(&assetsIterator)); boolean bHasCurrent = false; RETURN_IF_FAILED(assetsIterator->get_HasCurrent(&bHasCurrent)); while (bHasCurrent) { ComPtr assetValue; RETURN_IF_FAILED(assetsIterator->get_Current(&assetValue)); ComPtr asset; RETURN_IF_FAILED(assetValue->GetObjectW(&asset)); // Note: W/A macros caused this to be renamed Wrappers::HString name; RETURN_IF_FAILED(asset->GetNamedString(Wrappers::HStringReference(L"name").Get(), name.ReleaseAndGetAddressOf())); const WCHAR* pszName = name.GetRawBuffer(nullptr); if (wcscmp(pszName, _T(SETUP_UTILITY_NAME)) == 0) { RETURN_IF_FAILED(asset->GetNamedString(Wrappers::HStringReference(L"browser_download_url").Get(), outDownloadUrl)); return S_OK; } RETURN_IF_FAILED(assetsIterator->MoveNext(&bHasCurrent)); } RETURN_HR(HRESULT_FROM_WIN32(ERROR_NOT_FOUND)); }; Wrappers::HString htmlUrl; if ((bRet = SUCCEEDED(extract(htmlUrl.ReleaseAndGetAddressOf(), hstrDownloadUrl.ReleaseAndGetAddressOf())))) { if (wszInfoURL) { wcscpy_s(wszInfoURL, cchInfoURL, htmlUrl.GetRawBuffer(nullptr)); wprintf(L"[Updates] Release notes URL: \"%s\"\n", wszInfoURL); } if (hstrDownloadUrl.Get()) { wprintf(L"[Updates] Prerelease update URL: \"%s\"\n", hstrDownloadUrl.GetRawBuffer(nullptr)); bUpdatePreferStaging = FALSE; // Success } } } if (pszJsonStr) { free(pszJsonStr); } } if (!bUpdatePreferStaging && ((hInternet = InternetOpenA( UPDATES_USER_AGENT, INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0 )))) { HINTERNET hConnect = InternetOpenUrlW( hInternet, hstrDownloadUrl.GetRawBuffer(nullptr), nullptr, 0, INTERNET_FLAG_RAW_DATA | INTERNET_FLAG_RELOAD | INTERNET_FLAG_RESYNCHRONIZE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_DONT_CACHE, (DWORD_PTR)¶ms ); if (hConnect) { if (szCheckAgainst) { DWORD dwRead = 0; char hash[DOSMODE_OFFSET + UPDATES_HASH_SIZE + 1] = {}; if (InternetReadFile(hConnect, hash, DOSMODE_OFFSET + UPDATES_HASH_SIZE, &dwRead) && dwRead == DOSMODE_OFFSET + UPDATES_HASH_SIZE) { #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Hash of remote file is \"%s\" (%s).\n", DOSMODE_OFFSET + hash, (hash[0] == 0x4D && hash[1] == 0x5A) ? "valid" : "invalid"); #endif BOOL bOldType = TRUE; char *szLeftMost = nullptr, *szSecondLeft = nullptr, *szSecondRight = nullptr, *szRightMost = nullptr, *szRealHash = nullptr; if (hModule) { if (hash[0] == 0x4D && hash[1] == 0x5A) { if (strchr(DOSMODE_OFFSET + hash, '.')) { szLeftMost = DOSMODE_OFFSET + hash; if ((szSecondLeft = strchr(szLeftMost, '.'))) { *szSecondLeft = 0; szSecondLeft++; if ((szSecondRight = strchr(szSecondLeft, '.'))) { *szSecondRight = 0; szSecondRight++; if ((szRightMost = strchr(szSecondRight, '.'))) { *szRightMost = 0; szRightMost++; if ((szRealHash = strchr(szRightMost, '.'))) { bOldType = FALSE; *szRealHash = 0; szRealHash++; DWORD dwRemoteLeftMost = atoi(szLeftMost); if (pLeftMost) *pLeftMost = dwRemoteLeftMost; DWORD dwRemoteSecondLeft = atoi(szSecondLeft); if (pSecondLeft) *pSecondLeft = dwRemoteSecondLeft; DWORD dwRemoteSecondRight = atoi(szSecondRight); if (pSecondRight) *pSecondRight = dwRemoteSecondRight; DWORD dwRemoteRightMost = atoi(szRightMost); if (pRightMost) *pRightMost = dwRemoteRightMost; DWORD dwLocalLeftMost = 0; DWORD dwLocalSecondLeft = 0; DWORD dwLocalSecondRight = 0; DWORD dwLocalRightMost = 0; BOOL bExtractedFromHash = FALSE; CHAR hashCopy[100]; strcpy_s(hashCopy, 100, szCheckAgainst); char* szLocalLeftMost = nullptr, *szLocalSecondLeft = nullptr, *szLocalSecondRight = nullptr, *szLocalRightMost = nullptr, *szLocalRealHash = nullptr; if (strchr(hashCopy, '.')) { szLocalLeftMost = hashCopy; if ((szLocalSecondLeft = strchr(szLocalLeftMost, '.'))) { *szLocalSecondLeft = 0; szLocalSecondLeft++; if ((szLocalSecondRight = strchr(szLocalSecondLeft, '.'))) { *szLocalSecondRight = 0; szLocalSecondRight++; if ((szLocalRightMost = strchr(szLocalSecondRight, '.'))) { *szLocalRightMost = 0; szLocalRightMost++; if ((szLocalRealHash = strchr(szLocalRightMost, '.'))) { *szLocalRealHash = 0; szLocalRealHash++; bExtractedFromHash = TRUE; dwLocalLeftMost = atoi(szLocalLeftMost); dwLocalSecondLeft = atoi(szLocalSecondLeft); dwLocalSecondRight = atoi(szLocalSecondRight); dwLocalRightMost = atoi(szLocalRightMost); #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Local version obtained from hash is %d.%d.%d.%d.\n", dwLocalLeftMost, dwLocalSecondLeft, dwLocalSecondRight, dwLocalRightMost); #endif } } } } } if (!bExtractedFromHash) { QueryVersionInfo(hModule, VS_VERSION_INFO, &dwLocalLeftMost, &dwLocalSecondLeft, &dwLocalSecondRight, &dwLocalRightMost); } int res = 0; if (!res) { if (dwLocalLeftMost < dwRemoteLeftMost) { res = -1; } if (dwLocalLeftMost > dwRemoteLeftMost) { res = 1; } if (!res) { if (dwLocalSecondLeft < dwRemoteSecondLeft) { res = -1; } if (dwLocalSecondLeft > dwRemoteSecondLeft) { res = 1; } if (!res) { if (dwLocalSecondRight < dwRemoteSecondRight) { res = -1; } if (dwLocalSecondRight > dwRemoteSecondRight) { res = 1; } if (!res) { if (dwLocalRightMost < dwRemoteRightMost) { res = -1; } if (dwLocalRightMost > dwRemoteRightMost) { res = 1; } } } } } DWORD dwAllowDowngrades = FALSE, dwSize = sizeof(DWORD); RegGetValueW(HKEY_CURRENT_USER, _T(REGPATH), L"UpdateAllowDowngrades", RRF_RT_DWORD, nullptr, &dwAllowDowngrades, &dwSize); if ((res == 1 && dwAllowDowngrades) || res == -1) { bIsUpdateAvailable = TRUE; } else if (res == 0) { *(szSecondLeft - 1) = '.'; *(szSecondRight - 1) = '.'; *(szRightMost - 1) = '.'; *(szRealHash - 1) = '.'; if (_stricmp(DOSMODE_OFFSET + hash, szCheckAgainst)) { bIsUpdateAvailable = TRUE; } } } } } } } } } /*if (bOldType) { if (hash[0] == 0x4D && hash[1] == 0x5A && _stricmp(DOSMODE_OFFSET + hash, szCheckAgainst)) { bIsUpdateAvailable = TRUE; } }*/ } else { #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Failed. Read %d bytes.\n", dwRead); #endif if (lpFail) *lpFail = TRUE; } } else { WCHAR wszPath[MAX_PATH] = {}; SHGetFolderPathW(nullptr, SPECIAL_FOLDER_LEGACY, nullptr, SHGFP_TYPE_CURRENT, wszPath); wcscat_s(wszPath, MAX_PATH, _T(APP_RELATIVE_PATH)); BOOL bRet = CreateDirectoryW(wszPath, nullptr); if (bRet || (!bRet && GetLastError() == ERROR_ALREADY_EXISTS)) { wcscat_s(wszPath, MAX_PATH, L"\\Update for " _T(PRODUCT_NAME) L" from "); WCHAR wszURL[MAX_PATH] = {}; wcscpy_s(wszURL, MAX_PATH, hstrDownloadUrl.GetRawBuffer(nullptr)); if (wszURL[97]) // Truncate the URL for display { wszURL[96] = L'.'; wszURL[97] = L'.'; wszURL[98] = L'.'; wszURL[99] = L'e'; wszURL[100] = L'x'; wszURL[101] = L'e'; wszURL[102] = 0; } for (unsigned int i = 0; true; ++i) { if (!wszURL[i]) { break; } if (wszURL[i] == L'/') { wszURL[i] = L'\u2215'; } else if (wszURL[i] == L':') { wszURL[i] = L'\ua789'; } } wcscat_s(wszPath, MAX_PATH, wszURL); #ifdef UPDATES_VERBOSE_OUTPUT wprintf(L"[Updates] Download path is \"%s\".\n", wszPath); #endif BOOL bRet = DeleteFileW(wszPath); if (bRet || (!bRet && GetLastError() == ERROR_FILE_NOT_FOUND)) { DWORD bIsUsingEpMake = 0, dwSize = sizeof(DWORD); RegGetValueW(HKEY_CURRENT_USER, TEXT(REGPATH), L"UpdateUseLocal", RRF_RT_DWORD, nullptr, &bIsUsingEpMake, &dwSize); if (bIsUsingEpMake) { GetSystemDirectoryW(wszPath, MAX_PATH); wcscat_s(wszPath, MAX_PATH, L"\\WindowsPowerShell\\v1.0\\powershell.exe"); } FILE* f = nullptr; if (!bIsUsingEpMake && !_wfopen_s( &f, wszPath, L"wb" ) && f) { BYTE* buffer = (BYTE*)malloc(UPDATES_BUFSIZ); if (buffer != nullptr) { DWORD totalSize = 0; CHAR szContentLength[24] = {}; DWORD dwSize = sizeof(szContentLength); if (HttpQueryInfoA(hConnect, HTTP_QUERY_CONTENT_LENGTH, szContentLength, &dwSize, nullptr)) { totalSize = atoi(szContentLength); } toastData->UpdateDownloadProgress(0, totalSize); DWORD dwTotalRead = 0; DWORD dwRead = 0; bRet = FALSE; while ((bRet = InternetReadFile( hConnect, buffer, UPDATES_BUFSIZ, &dwRead ))) { if (dwRead == 0) { bIsUpdateAvailable = TRUE; #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Downloaded finished.\n"); #endif break; } #ifdef UPDATES_VERBOSE_OUTPUT // printf("[Updates] Downloaded %d bytes.\n", dwRead); #endif fwrite( buffer, sizeof(BYTE), dwRead, f ); dwTotalRead += dwRead; toastData->UpdateDownloadProgress(dwTotalRead, totalSize); dwRead = 0; } free(buffer); } fclose(f); } if (bIsUsingEpMake || bIsUpdateAvailable) { bIsUpdateAvailable = FALSE; #ifdef UPDATES_VERBOSE_OUTPUT printf( "[Updates] In order to install this update for the product \"" PRODUCT_NAME "\", please allow the request.\n" ); #endif toastData->HideToast(); BOOL bHasErrored = FALSE; BOOL bIsUACEnabled = FALSE; DWORD(*CheckElevationEnabled)(BOOL*); CheckElevationEnabled = (decltype(CheckElevationEnabled))GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CheckElevationEnabled"); if (CheckElevationEnabled) CheckElevationEnabled(&bIsUACEnabled); DWORD dwData = FALSE, dwSize = sizeof(DWORD); RegGetValueW( HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", L"ConsentPromptBehaviorAdmin", RRF_RT_DWORD, nullptr, &dwData, &dwSize ); LPWSTR wszSID = nullptr; HANDLE hMyToken = nullptr; if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hMyToken)) { PTOKEN_USER ptu = nullptr; DWORD dwSize = 0; if (!GetTokenInformation(hMyToken, TokenUser, nullptr, 0, &dwSize) && ERROR_INSUFFICIENT_BUFFER == GetLastError()) { if (nullptr != (ptu = (PTOKEN_USER)LocalAlloc(LPTR, dwSize))) { if (!GetTokenInformation(hMyToken, TokenUser, ptu, dwSize, &dwSize)) { LocalFree((HLOCAL)ptu); return FALSE; } ConvertSidToStringSidW(ptu->User.Sid, &wszSID); LocalFree((HLOCAL)ptu); } } CloseHandle(hMyToken); } size_t lnSID = (wszSID ? wcslen(wszSID) : 0); DWORD bIsBuiltInAdministratorInApprovalMode = FALSE; BOOL bIsBuiltInAdministratorAccount = IsUserAnAdmin() && lnSID && !_wcsnicmp(wszSID, L"S-1-5-", 6) && wszSID[lnSID - 4] == L'-' && wszSID[lnSID - 3] == L'5' && wszSID[lnSID - 2] == L'0' && wszSID[lnSID - 1] == L'0'; if (bIsBuiltInAdministratorAccount) { DWORD dwSSize = sizeof(DWORD); RegGetValueW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", L"FilterAdministratorToken", RRF_RT_DWORD, nullptr, &bIsBuiltInAdministratorInApprovalMode, &dwSSize); } LocalFree(wszSID); if (!bNoConfirmation && (!bIsUACEnabled || !dwData || (bIsBuiltInAdministratorAccount && !bIsBuiltInAdministratorInApprovalMode))) { WCHAR wszMsg[500]; wszMsg[0] = 0; WCHAR wszMsgFormat[500]; EP_L10N_ApplyPreferredLanguageForCurrentThread(); { wil::unique_hmodule hEPGui(LoadGuiModule()); if (LoadStringW(hEPGui.get(), IDS_UPDATES_PROMPT, wszMsgFormat, ARRAYSIZE(wszMsgFormat))) { swprintf_s(wszMsg, ARRAYSIZE(wszMsg), wszMsgFormat, hstrDownloadUrl.GetRawBuffer(nullptr)); } } if (MessageBoxW( FindWindowW(L"ExplorerPatcher_GUI_" _T(EP_CLSID), nullptr), wszMsg, _T(PRODUCT_NAME), MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION ) == IDNO) { bHasErrored = TRUE; SetLastError(ERROR_CANCELLED); } } SHELLEXECUTEINFOW ShExecInfo = { 0 }; ShExecInfo.cbSize = sizeof(ShExecInfo); ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS; ShExecInfo.hwnd = nullptr; ShExecInfo.lpVerb = bIsUsingEpMake ? L"open" : L"runas"; ShExecInfo.lpFile = wszPath; ShExecInfo.lpParameters = bIsUsingEpMake ? L"iex (irm 'https://raw.githubusercontent.com/valinet/ep_make/master/ep_make_safe.ps1')" : L"/update_silent"; ShExecInfo.lpDirectory = nullptr; ShExecInfo.nShow = SW_SHOW; ShExecInfo.hInstApp = nullptr; if (!bHasErrored && ShellExecuteExW(&ShExecInfo) && ShExecInfo.hProcess) { WaitForSingleObject(ShExecInfo.hProcess, INFINITE); DWORD dwExitCode = 0; if (GetExitCodeProcess(ShExecInfo.hProcess, &dwExitCode) && !dwExitCode) { bIsUpdateAvailable = TRUE; #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Update successful, File Explorer will probably restart momentarily.\n"); #endif } else { SetLastError(dwExitCode); #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Update failed because the following error has occured: %d.\n", dwExitCode); #endif } CloseHandle(ShExecInfo.hProcess); } else { DWORD dwError = GetLastError(); if (dwError == ERROR_CANCELLED) { #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Update failed because the request was denied.\n"); #endif } else { #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Update failed because the following error has occured: %d.\n", GetLastError()); #endif } } } } } } InternetCloseHandle(hConnect); } else { if (lpFail) *lpFail = TRUE; } InternetCloseHandle(hInternet); } CloseHandle(params.hEvent); return bIsUpdateAvailable; } BOOL IsUpdateAvailable(LPCWSTR wszDataStore, char* szCheckAgainst, BOOL* lpFail, WCHAR* wszInfoURL, DWORD cchInfoURL, HMODULE hModule, DWORD* pLeftMost, DWORD* pSecondLeft, DWORD* pSecondRight, DWORD* pRightMost) { HKEY hKey = nullptr; DWORD dwSize = 0; DWORD dwQueriedPolicy = 0; BOOL bIsPolicyMatch = FALSE; WCHAR szUpdateURL[MAX_PATH] = {}; wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STABLE)); #ifdef UPDATES_VERBOSE_OUTPUT printf("[Updates] Checking against hash \"%s\"\n", szCheckAgainst); #endif DWORD dwUpdateTimeout = UPDATES_DEFAULT_TIMEOUT; DWORD bUpdatePreferStaging = FALSE; RegCreateKeyExW( HKEY_CURRENT_USER, wszDataStore, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WOW64_64KEY, nullptr, &hKey, nullptr ); if (hKey == nullptr || hKey == INVALID_HANDLE_VALUE) { hKey = nullptr; } if (hKey) { dwSize = sizeof(szUpdateURL); RegQueryValueExW( hKey, L"UpdateURL", nullptr, nullptr, (LPBYTE)szUpdateURL, &dwSize ); if (dwSize == sizeof(WCHAR) && szUpdateURL[0] == 0) { wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STABLE)); } wcscat_s(szUpdateURL, MAX_PATH, _T("/download/")); wcscat_s(szUpdateURL, MAX_PATH, _T(SETUP_UTILITY_NAME)); if (wszInfoURL) { dwSize = sizeof(WCHAR) * cchInfoURL; RegQueryValueExW( hKey, L"UpdateURL", nullptr, nullptr, (LPBYTE)wszInfoURL, &dwSize ); if (dwSize == sizeof(WCHAR) && wszInfoURL[0] == 0) { wcscat_s(wszInfoURL, cchInfoURL, _T(UPDATES_RELEASE_INFO_URL_STABLE)); } } dwSize = sizeof(DWORD); RegQueryValueExW( hKey, L"UpdateTimeout", nullptr, nullptr, (LPBYTE)&dwUpdateTimeout, &dwSize ); dwSize = sizeof(DWORD); RegQueryValueExW( hKey, L"UpdatePreferStaging", nullptr, nullptr, (LPBYTE)&bUpdatePreferStaging, &dwSize ); if (bUpdatePreferStaging) { szUpdateURL[0] = 0; wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STAGING)); dwSize = sizeof(szUpdateURL); RegQueryValueExW( hKey, L"UpdateURLStaging", nullptr, nullptr, (LPBYTE)szUpdateURL, &dwSize ); if (dwSize == sizeof(WCHAR) && szUpdateURL[0] == 0) { wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STAGING)); } } RegCloseKey(hKey); } #ifdef UPDATES_VERBOSE_OUTPUT wprintf(L"[Updates] Update URL: %s\n", szUpdateURL); #endif return IsUpdateAvailableHelper( szUpdateURL, szCheckAgainst, dwUpdateTimeout, lpFail, nullptr, bUpdatePreferStaging, wszInfoURL, cchInfoURL, FALSE, hModule, pLeftMost, pSecondLeft, pSecondRight, pRightMost ); } BOOL UpdateProduct(LPCWSTR wszDataStore, CToastData* toastData, BOOL bNoConfirmation, HMODULE hModule) { HKEY hKey = nullptr; DWORD dwSize = 0; DWORD dwQueriedPolicy = 0; BOOL bIsPolicyMatch = FALSE; WCHAR szUpdateURL[MAX_PATH] = {}; wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STABLE)); DWORD dwUpdateTimeout = UPDATES_DEFAULT_TIMEOUT; BOOL bUpdatePreferStaging = FALSE; RegCreateKeyExW( HKEY_CURRENT_USER, wszDataStore, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WOW64_64KEY, nullptr, &hKey, nullptr ); if (hKey == nullptr || hKey == INVALID_HANDLE_VALUE) { hKey = nullptr; } if (hKey) { dwSize = sizeof(szUpdateURL); RegQueryValueExW( hKey, L"UpdateURL", nullptr, nullptr, (LPBYTE)szUpdateURL, &dwSize ); if (dwSize == sizeof(WCHAR) && szUpdateURL[0] == 0) { wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STABLE)); } wcscat_s(szUpdateURL, MAX_PATH, _T("/download/")); wcscat_s(szUpdateURL, MAX_PATH, _T(SETUP_UTILITY_NAME)); dwSize = sizeof(DWORD); RegQueryValueExW( hKey, L"UpdateTimeout", nullptr, nullptr, (LPBYTE)&dwUpdateTimeout, &dwSize ); dwSize = sizeof(BOOL); RegQueryValueExW( hKey, L"UpdatePreferStaging", nullptr, nullptr, (LPBYTE)&bUpdatePreferStaging, &dwSize ); if (bUpdatePreferStaging) { szUpdateURL[0] = 0; wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STAGING)); dwSize = sizeof(szUpdateURL); RegQueryValueExW( hKey, L"UpdateURLStaging", nullptr, nullptr, (LPBYTE)szUpdateURL, &dwSize ); if (dwSize == sizeof(WCHAR) && szUpdateURL[0] == 0) { wcscat_s(szUpdateURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STAGING)); } } RegCloseKey(hKey); } #ifdef UPDATES_VERBOSE_OUTPUT wprintf(L"[Updates] Update URL: %s\n", szUpdateURL); #endif return IsUpdateAvailableHelper( szUpdateURL, nullptr, dwUpdateTimeout, nullptr, toastData, bUpdatePreferStaging, nullptr, 0, bNoConfirmation, hModule, nullptr, nullptr, nullptr, nullptr ); } BOOL ShowUpdateSuccessNotification(HMODULE hModule, CToastData* toastData) { auto switchToThreadScopeExit = wil::scope_exit([](){ SwitchToThread(); }); EP_L10N_ApplyPreferredLanguageForCurrentThread(); wil::unique_hmodule hEPGui(LoadGuiModule()); WCHAR buf[TOAST_BUFSIZ]; DWORD dwLeftMost = 0; DWORD dwSecondLeft = 0; DWORD dwSecondRight = 0; DWORD dwRightMost = 0; QueryVersionInfo(hModule, VS_VERSION_INFO, &dwLeftMost, &dwSecondLeft, &dwSecondRight, &dwRightMost); const WCHAR text[] = L"\r\n" L" \r\n" L" \r\n" L" \r\n" L" \r\n" L" \r\n%s" // <-- Progress bar goes here L" \r\n" L" \r\n" L" \r\n"; WCHAR title[100]; WCHAR body[200]; title[0] = 0; body[0] = 0; LoadStringW(hEPGui.get(), IDS_UPDATES_SUCCESS_T, title, ARRAYSIZE(title)); WCHAR bodyFormat[200] = {}; if (LoadStringW(hEPGui.get(), IDS_UPDATES_INSTALLEDVER, bodyFormat, ARRAYSIZE(bodyFormat))) { swprintf_s(body, ARRAYSIZE(body), bodyFormat, dwLeftMost, dwSecondLeft, dwSecondRight, dwRightMost); } swprintf_s(buf, TOAST_BUFSIZ, text, L"", L"short", title, body, L"", L""); ComPtr inputXml; String2IXMLDocument(buf, &inputXml); toastData->HideAndShowToast(inputXml.Get()); return TRUE; } BOOL InstallUpdatesIfAvailable( HMODULE hModule, CToastData* toastData, DWORD dwOperation, DWORD bAllocConsole, DWORD dwUpdatePolicy) { EP_L10N_ApplyPreferredLanguageForCurrentThread(); wil::unique_hmodule hEPGui(LoadGuiModule()); WCHAR wszInfoURL[MAX_PATH] = {}; wcscat_s(wszInfoURL, MAX_PATH, _T(UPDATES_RELEASE_INFO_URL_STABLE)); WCHAR buf[TOAST_BUFSIZ]; DWORD dwLeftMost = 0; DWORD dwSecondLeft = 0; DWORD dwSecondRight = 0; DWORD dwRightMost = 0; QueryVersionInfo(hModule, VS_VERSION_INFO, &dwLeftMost, &dwSecondLeft, &dwSecondRight, &dwRightMost); if (bAllocConsole) { switch (dwUpdatePolicy) { default: case UPDATE_POLICY_AUTO: { dwUpdatePolicy = UPDATE_POLICY_AUTO; printf("[Updates] Configured update policy on this system: \"Install updates automatically\".\n"); break; } case UPDATE_POLICY_NOTIFY: { printf("[Updates] Configured update policy on this system: \"Check for updates but let me choose whether to download and install them\".\n"); break; } case UPDATE_POLICY_MANUAL: { printf("[Updates] Configured update policy on this system: \"Manually check for updates\".\n"); break; } } } const WCHAR text[] = L"\r\n" L" \r\n" L" \r\n" L" \r\n" L" \r\n" L" \r\n%s" // <-- Progress bar goes here L" \r\n" L" \r\n" L" \r\n"; WCHAR title[100]; WCHAR body[200]; WCHAR actionsXml[200]; WCHAR progressXml[200]; title[0] = 0; body[0] = 0; actionsXml[0] = 0; progressXml[0] = 0; BOOL bIsInstall = dwOperation == UPDATES_OP_INSTALL || dwOperation == UPDATES_OP_INSTALL_NO_CONFIRM; if (dwOperation == UPDATES_OP_CHECK || bIsInstall) { // title[0] = 0; body[0] = 0; actionsXml[0] = 0; progressXml[0] = 0; if (bIsInstall) { LoadStringW(hEPGui.get(), IDS_UPDATES_DOWNLOADING_T, title, ARRAYSIZE(title)); wcscpy_s(progressXml, ARRAYSIZE(progressXml), L" \r\n" ); } else if (dwOperation == UPDATES_OP_CHECK) { LoadStringW(hEPGui.get(), IDS_UPDATES_CHECKING_T, title, ARRAYSIZE(title)); } WCHAR bodyFormat[200] = {}; if (LoadStringW(hEPGui.get(), IDS_UPDATES_INSTALLEDVER, bodyFormat, ARRAYSIZE(bodyFormat))) { swprintf_s(body, ARRAYSIZE(body), bodyFormat, dwLeftMost, dwSecondLeft, dwSecondRight, dwRightMost); } swprintf_s(buf, TOAST_BUFSIZ, text, L"", L"long", title, body, progressXml, actionsXml); ComPtr inputXml; String2IXMLDocument(buf, &inputXml); toastData->HideAndShowToast(inputXml.Get()); if (bIsInstall) { toastData->UpdateDownloadProgress(0, 0); } } WCHAR dllName[MAX_PATH]; GetModuleFileNameW(hModule, dllName, MAX_PATH); wprintf(L"[Updates] Path to module: %s\n", dllName); CHAR hash[100] = {}; GetHardcodedHash(dllName, hash, 100); if (!strcmp(hash, "This")) ComputeFileHash2(hModule, dllName, hash, 100); else printf("[Updates] Using hardcoded hash.\n"); BOOL bFail = FALSE, bReturnValue = FALSE; dwLeftMost = 0; dwSecondLeft = 0; dwSecondRight = 0; dwRightMost = 0; if (IsUpdateAvailable(_T(REGPATH), hash, &bFail, wszInfoURL, MAX_PATH, hModule, &dwLeftMost, &dwSecondLeft, &dwSecondRight, &dwRightMost)) { printf("[Updates] An update is available.\n"); if ((dwOperation == UPDATES_OP_DEFAULT && dwUpdatePolicy == UPDATE_POLICY_AUTO) || bIsInstall) { BOOL bOk = UpdateProduct(_T(REGPATH), toastData, dwOperation == UPDATES_OP_INSTALL_NO_CONFIRM, hModule); if (!bOk && bIsInstall) { title[0] = 0; body[0] = 0; actionsXml[0] = 0; progressXml[0] = 0; LoadStringW(hEPGui.get(), IDS_UPDATES_DLFAILED_T, title, ARRAYSIZE(title)); LoadStringW(hEPGui.get(), IDS_UPDATES_DLFAILED_B, body, ARRAYSIZE(body)); swprintf_s(buf, TOAST_BUFSIZ, text, L"", L"short", title, body, progressXml, actionsXml); ComPtr inputXml; String2IXMLDocument(buf, &inputXml); toastData->HideAndShowToast(inputXml.Get()); } } else if ((dwOperation == UPDATES_OP_DEFAULT && dwUpdatePolicy == UPDATE_POLICY_NOTIFY) || (dwOperation == UPDATES_OP_CHECK)) { title[0] = 0; body[0] = 0; actionsXml[0] = 0; progressXml[0] = 0; if (!dwLeftMost) { LoadStringW(hEPGui.get(), IDS_UPDATES_AVAILABLE_T_U, title, ARRAYSIZE(title)); } else { WCHAR titleFormat[100]; if (LoadStringW(hEPGui.get(), IDS_UPDATES_AVAILABLE_T, titleFormat, ARRAYSIZE(titleFormat))) { swprintf_s(title, ARRAYSIZE(title), titleFormat, dwLeftMost, dwSecondLeft, dwSecondRight, dwRightMost); } } LoadStringW(hEPGui.get(), IDS_UPDATES_AVAILABLE_B, body, ARRAYSIZE(body)); WCHAR action[100]; action[0] = 0; LoadStringW(hEPGui.get(), IDS_UPDATES_AVAILABLE_A, action, ARRAYSIZE(action)); swprintf_s(actionsXml, ARRAYSIZE(actionsXml), L" \r\n" L" \r\n" L" \r\n", action, L"action=update" ); swprintf_s(buf, TOAST_BUFSIZ, text, wszInfoURL, L"long", title, body, progressXml, actionsXml); ComPtr inputXml; String2IXMLDocument(buf, &inputXml); toastData->HideAndShowToast(inputXml.Get()); } return TRUE; } else { if (bFail) { printf("[Updates] Unable to check for updates because the remote server is unavailable.\n"); } else { printf("[Updates] No updates are available.\n"); } if (dwOperation == UPDATES_OP_CHECK || bIsInstall) { title[0] = 0; body[0] = 0; actionsXml[0] = 0; progressXml[0] = 0; if (bFail) { LoadStringW(hEPGui.get(), IDS_UPDATES_CHECKFAILED_T, title, ARRAYSIZE(title)); LoadStringW(hEPGui.get(), IDS_UPDATES_CHECKFAILED_B, body, ARRAYSIZE(body)); } else { LoadStringW(hEPGui.get(), IDS_UPDATES_ISLATEST_T, title, ARRAYSIZE(title)); LoadStringW(hEPGui.get(), IDS_UPDATES_ISLATEST_B, body, ARRAYSIZE(body)); } swprintf_s(buf, TOAST_BUFSIZ, text, L"", L"short", title, body, progressXml, actionsXml); ComPtr inputXml; String2IXMLDocument(buf, &inputXml); toastData->HideAndShowToast(inputXml.Get()); } return FALSE; } } extern "C" DWORD bAllocConsole; extern "C" DWORD bShowUpdateToast; extern "C" DWORD dwUpdatePolicy; DWORD CheckForUpdatesThread(LPVOID params) { DWORD timeout = (DWORD)(UINT_PTR)params; HRESULT hr = S_OK; if (SUCCEEDED(hr)) { hr = RoInitialize(RO_INIT_MULTITHREADED); } ComPtr toastStatics; if (SUCCEEDED(hr)) { hr = RoGetActivationFactory( Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), IID_PPV_ARGS(&toastStatics) ); } if (SUCCEEDED(hr)) { ComPtr toastStatics2; if (SUCCEEDED(RoGetActivationFactory( Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), IID_PPV_ARGS(&toastStatics2) ))) { ComPtr history; if (SUCCEEDED(toastStatics2->get_History(&history))) { history->Remove(Wrappers::HStringReference(L"ep_updates").Get()); } } } while (TRUE) { HWND hShell_TrayWnd = FindWindowExW(nullptr, nullptr, L"Shell_TrayWnd", nullptr); if (hShell_TrayWnd) { Sleep(timeout); break; } Sleep(100); } printf("[Updates] Starting daemon.\n"); ComPtr notifier; if (SUCCEEDED(hr)) { hr = toastStatics->CreateToastNotifierWithId(Wrappers::HStringReference(APPID).Get(), ¬ifier); } ComPtr notifFactory; if (SUCCEEDED(hr)) { hr = RoGetActivationFactory( Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), IID_PPV_ARGS(¬ifFactory) ); } HANDLE hEvents[3]; hEvents[0] = CreateEventW(nullptr, FALSE, FALSE, L"EP_Ev_CheckForUpdates_" _T(EP_CLSID)); hEvents[1] = CreateEventW(nullptr, FALSE, FALSE, L"EP_Ev_InstallUpdates_" _T(EP_CLSID)); hEvents[2] = CreateEventW(nullptr, FALSE, FALSE, L"EP_Ev_InstallUpdatesNoConfirm_" _T(EP_CLSID)); if (hEvents[0] && hEvents[1] && hEvents[2]) { ComPtr toastData = Make(notifier.Get(), notifFactory.Get()); if (bShowUpdateToast) { ShowUpdateSuccessNotification(hModule, toastData.Get()); HKEY hKey = nullptr; RegCreateKeyExW( HKEY_CURRENT_USER, TEXT(REGPATH), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WOW64_64KEY | KEY_WRITE, nullptr, &hKey, nullptr ); if (hKey == nullptr || hKey == INVALID_HANDLE_VALUE) { hKey = nullptr; } if (hKey) { bShowUpdateToast = FALSE; RegSetValueExW( hKey, TEXT("IsUpdatePending"), 0, REG_DWORD, (const BYTE*)&bShowUpdateToast, sizeof(DWORD) ); RegCloseKey(hKey); } } if (dwUpdatePolicy != UPDATE_POLICY_MANUAL) { InstallUpdatesIfAvailable(hModule, toastData.Get(), UPDATES_OP_DEFAULT, bAllocConsole, dwUpdatePolicy); } while (TRUE) { switch (WaitForMultipleObjects(3, hEvents, FALSE, INFINITE)) { case WAIT_OBJECT_0: { InstallUpdatesIfAvailable(hModule, toastData.Get(), UPDATES_OP_CHECK, bAllocConsole, dwUpdatePolicy); break; } case WAIT_OBJECT_0 + 1: { InstallUpdatesIfAvailable(hModule, toastData.Get(), UPDATES_OP_INSTALL, bAllocConsole, dwUpdatePolicy); break; } case WAIT_OBJECT_0 + 2: { InstallUpdatesIfAvailable(hModule, toastData.Get(), UPDATES_OP_INSTALL_NO_CONFIRM, bAllocConsole, dwUpdatePolicy); break; } default: { break; } } } // Unreachable, but just in case CloseHandle(hEvents[0]); CloseHandle(hEvents[1]); CloseHandle(hEvents[2]); } return 0; }