You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1437 lines
58 KiB
1437 lines
58 KiB
#include "updates.h" |
|
|
|
#include <Shlwapi.h> |
|
#pragma comment(lib, "Shlwapi.lib") |
|
|
|
#include <string> |
|
|
|
#include <wil/resource.h> |
|
#include <wil/result_macros.h> |
|
#include <wrl/async.h> |
|
|
|
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<IInspectable> spInspectable; |
|
RETURN_IF_FAILED(RoActivateInstance(Wrappers::HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &spInspectable)); |
|
|
|
ComPtr<IXmlDocument> spXmlDocument; |
|
RETURN_IF_FAILED(spInspectable.As(&spXmlDocument)); |
|
spXmlDocument.CopyTo(pOutXmlToastMessage); |
|
|
|
ComPtr<IXmlDocumentIO> 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<RuntimeClassFlags<ClassicCom>, ITypedEventHandler<ToastNotification*, IInspectable*>> |
|
{ |
|
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<IToastNotification2> 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<IInspectable> dataInspectable; |
|
RETURN_IF_FAILED(RoActivateInstance(Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_NotificationData).Get(), &dataInspectable)); |
|
|
|
ComPtr<INotificationData> data; |
|
RETURN_IF_FAILED(dataInspectable.As(&data)); |
|
|
|
ComPtr<IMap<HSTRING, HSTRING>> 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<IToastNotifier2> 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<IToastActivatedEventArgs> 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<IToastNotification> 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<IJsonArrayStatics> jsonArrayStatics; |
|
RETURN_IF_FAILED(GetActivationFactory(Wrappers::HStringReference(RuntimeClass_Windows_Data_Json_JsonArray).Get(), &jsonArrayStatics)); |
|
|
|
ComPtr<IJsonArray> releases; |
|
RETURN_IF_FAILED(jsonArrayStatics->Parse(Wrappers::HStringReference(pszJsonStr).Get(), &releases)); |
|
|
|
ComPtr<IJsonObject> firstRelease; |
|
RETURN_IF_FAILED(releases->GetObjectAt(0, &firstRelease)); |
|
|
|
RETURN_IF_FAILED(firstRelease->GetNamedString(Wrappers::HStringReference(L"html_url").Get(), outHtmlUrl)); |
|
|
|
ComPtr<IJsonArray> assets; |
|
RETURN_IF_FAILED(firstRelease->GetNamedArray(Wrappers::HStringReference(L"assets").Get(), &assets)); |
|
|
|
ComPtr<IIterable<IJsonValue*>> assetsIterable; |
|
RETURN_IF_FAILED(assets.As(&assetsIterable)); |
|
|
|
ComPtr<IIterator<IJsonValue*>> assetsIterator; |
|
RETURN_IF_FAILED(assetsIterable->First(&assetsIterator)); |
|
|
|
boolean bHasCurrent = false; |
|
RETURN_IF_FAILED(assetsIterator->get_HasCurrent(&bHasCurrent)); |
|
|
|
while (bHasCurrent) |
|
{ |
|
ComPtr<IJsonValue> assetValue; |
|
RETURN_IF_FAILED(assetsIterator->get_Current(&assetValue)); |
|
|
|
ComPtr<IJsonObject> 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 - ((dwRemoteLeftMost == 22622 && szRealHash[0] != '!') ? 1 : 0); |
|
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); |
|
} |
|
} |
|
|
|
SHELLEXECUTEINFO ShExecInfo = { 0 }; |
|
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO); |
|
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"<toast scenario=\"reminder\" activationType=\"protocol\" launch=\"%s\" duration=\"%s\">\r\n" |
|
L" <visual>\r\n" |
|
L" <binding template=\"ToastGeneric\">\r\n" |
|
L" <text><![CDATA[%s]]></text>\r\n" |
|
L" <text><![CDATA[%s]]></text>\r\n" |
|
L" <text placement=\"attribution\"><![CDATA[ExplorerPatcher]]></text>\r\n%s" // <-- Progress bar goes here |
|
L" </binding>\r\n" |
|
L" </visual>\r\n" |
|
L" <audio src=\"ms-winsoundevent:Notification.Default\" loop=\"false\" silent=\"false\"/>\r\n%s" // <-- Buttons go here |
|
L"</toast>\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<IXmlDocument> 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"<toast scenario=\"reminder\" activationType=\"protocol\" launch=\"%s\" duration=\"%s\">\r\n" |
|
L" <visual>\r\n" |
|
L" <binding template=\"ToastGeneric\">\r\n" |
|
L" <text><![CDATA[%s]]></text>\r\n" |
|
L" <text><![CDATA[%s]]></text>\r\n" |
|
L" <text placement=\"attribution\"><![CDATA[ExplorerPatcher]]></text>\r\n%s" // <-- Progress bar goes here |
|
L" </binding>\r\n" |
|
L" </visual>\r\n" |
|
L" <audio src=\"ms-winsoundevent:Notification.Default\" loop=\"false\" silent=\"false\"/>\r\n%s" // <-- Buttons go here |
|
L"</toast>\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" <progress value=\"{progressValue}\" status=\"{progressStatus}\"/>\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<IXmlDocument> 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<IXmlDocument> 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" <actions>\r\n" |
|
L" <action content=\"%s\" arguments=\"%s\"/>\r\n" |
|
L" </actions>\r\n", |
|
action, L"action=update" |
|
); |
|
|
|
swprintf_s(buf, TOAST_BUFSIZ, text, wszInfoURL, L"long", title, body, progressXml, actionsXml); |
|
|
|
ComPtr<IXmlDocument> 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<IXmlDocument> 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<IToastNotificationManagerStatics> toastStatics; |
|
if (SUCCEEDED(hr)) |
|
{ |
|
hr = RoGetActivationFactory( |
|
Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), |
|
IID_PPV_ARGS(&toastStatics) |
|
); |
|
} |
|
|
|
if (SUCCEEDED(hr)) |
|
{ |
|
ComPtr<IToastNotificationManagerStatics2> toastStatics2; |
|
if (SUCCEEDED(RoGetActivationFactory( |
|
Wrappers::HStringReference(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), |
|
IID_PPV_ARGS(&toastStatics2) |
|
))) |
|
{ |
|
ComPtr<IToastNotificationHistory> 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<IToastNotifier> notifier; |
|
if (SUCCEEDED(hr)) |
|
{ |
|
hr = toastStatics->CreateToastNotifierWithId(Wrappers::HStringReference(APPID).Get(), ¬ifier); |
|
} |
|
|
|
ComPtr<IToastNotificationFactory> 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<CToastData> toastData = Make<CToastData>(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; |
|
}
|
|
|