winamp/Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp
2024-09-24 14:54:57 +02:00

1700 lines
56 KiB
C++

/*
* UpdateCheck.cpp
* ---------------
* Purpose: Class for easy software update check.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "UpdateCheck.h"
#include "mpt/binary/hex.hpp"
#include "BuildVariants.h"
#include "../common/version.h"
#include "../common/misc_util.h"
#include "../common/mptStringBuffer.h"
#include "Mptrack.h"
#include "TrackerSettings.h"
// Setup dialog stuff
#include "Mainfrm.h"
#include "mpt/system_error/system_error.hpp"
#include "mpt/crypto/hash.hpp"
#include "mpt/crypto/jwk.hpp"
#include "HTTP.h"
#include "mpt/json/json.hpp"
#include "dlg_misc.h"
#include "openmpt/sounddevice/SoundDeviceManager.hpp"
#include "ProgressDialog.h"
#include "Moddoc.h"
#include "mpt/io/io.hpp"
#include "mpt/io/io_stdstream.hpp"
OPENMPT_NAMESPACE_BEGIN
#if defined(MPT_ENABLE_UPDATE)
namespace Update {
struct windowsversion {
uint64 version_major = 0;
uint64 version_minor = 0;
uint64 servicepack_major = 0;
uint64 servicepack_minor = 0;
uint64 build = 0;
uint64 wine_major = 0;
uint64 wine_minor = 0;
uint64 wine_update = 0;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(windowsversion
,version_major
,version_minor
,servicepack_major
,servicepack_minor
,build
,wine_major
,wine_minor
,wine_update
)
struct autoupdate_installer {
std::vector<mpt::ustring> arguments = {};
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(autoupdate_installer
,arguments
)
struct autoupdate_archive {
mpt::ustring subfolder = U_("");
mpt::ustring restartbinary = U_("");
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(autoupdate_archive
,subfolder
,restartbinary
)
struct downloadinfo {
mpt::ustring url = U_("");
std::map<mpt::ustring, mpt::ustring> checksums = {};
mpt::ustring filename = U_("");
std::optional<autoupdate_installer> autoupdate_installer;
std::optional<autoupdate_archive> autoupdate_archive;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(downloadinfo
,url
,checksums
,filename
,autoupdate_installer
,autoupdate_archive
)
struct download {
mpt::ustring url = U_("");
mpt::ustring download_url = U_("");
mpt::ustring type = U_("");
bool can_autoupdate = false;
mpt::ustring autoupdate_minversion = U_("");
mpt::ustring os = U_("");
std::optional<windowsversion> required_windows_version;
std::map<mpt::ustring, bool> required_architectures = {};
std::map<mpt::ustring, bool> supported_architectures = {};
std::map<mpt::ustring, std::map<mpt::ustring, bool>> required_processor_features = {};
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(download
,url
,download_url
,type
,can_autoupdate
,autoupdate_minversion
,os
,required_windows_version
,required_architectures
,supported_architectures
,required_processor_features
)
struct versioninfo {
mpt::ustring version = U_("");
mpt::ustring date = U_("");
mpt::ustring announcement_url = U_("");
mpt::ustring changelog_url = U_("");
std::map<mpt::ustring, download> downloads = {};
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(versioninfo
,version
,date
,announcement_url
,changelog_url
,downloads
)
using versions = std::map<mpt::ustring, versioninfo>;
} // namespace Update
struct UpdateInfo {
mpt::ustring version;
mpt::ustring download;
bool IsAvailable() const
{
return !version.empty();
}
};
static bool IsCurrentArchitecture(const mpt::ustring &architecture)
{
return mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture()) == architecture;
}
static bool IsArchitectureSupported(const mpt::ustring &architecture)
{
const auto & architectures = mpt::OS::Windows::GetSupportedProcessArchitectures(mpt::OS::Windows::GetHostArchitecture());
for(const auto & arch : architectures)
{
if(mpt::OS::Windows::Name(arch) == architecture)
{
return true;
}
}
return false;
}
static bool IsArchitectureFeatureSupported(const mpt::ustring &architecture, const mpt::ustring &feature)
{
MPT_UNUSED_VARIABLE(architecture);
#ifdef MPT_ENABLE_ARCH_INTRINSICS
const CPU::Info CPUInfo = CPU::Info::Get();
if(feature == U_("")) return true;
else if(feature == U_("lm")) return (CPUInfo.AvailableFeatures & CPU::feature::lm) != 0;
else if(feature == U_("mmx")) return (CPUInfo.AvailableFeatures & CPU::feature::mmx) != 0;
else if(feature == U_("sse")) return (CPUInfo.AvailableFeatures & CPU::feature::sse) != 0;
else if(feature == U_("sse2")) return (CPUInfo.AvailableFeatures & CPU::feature::sse2) != 0;
else if(feature == U_("sse3")) return (CPUInfo.AvailableFeatures & CPU::feature::sse3) != 0;
else if(feature == U_("ssse3")) return (CPUInfo.AvailableFeatures & CPU::feature::ssse3) != 0;
else if(feature == U_("sse4.1")) return (CPUInfo.AvailableFeatures & CPU::feature::sse4_1) != 0;
else if(feature == U_("sse4.2")) return (CPUInfo.AvailableFeatures & CPU::feature::sse4_2) != 0;
else if(feature == U_("avx")) return (CPUInfo.AvailableFeatures & CPU::feature::avx) != 0;
else if(feature == U_("avx2")) return (CPUInfo.AvailableFeatures & CPU::feature::avx2) != 0;
else return false;
#else // !MPT_ENABLE_ARCH_INTRINSICS
return true;
#endif // MPT_ENABLE_ARCH_INTRINSICS
}
static mpt::ustring GetChannelName(UpdateChannel channel)
{
mpt::ustring channelName = U_("release");
switch(channel)
{
case UpdateChannelDevelopment:
channelName = U_("development");
break;
case UpdateChannelNext:
channelName = U_("next");
break;
case UpdateChannelRelease:
channelName = U_("release");
break;
default:
channelName = U_("release");
break;
}
return channelName;
}
static UpdateInfo GetBestDownload(const Update::versions &versions)
{
UpdateInfo result;
VersionWithRevision bestVersion = VersionWithRevision::Current();
for(const auto & [versionname, versioninfo] : versions)
{
if(!VersionWithRevision::Parse(versioninfo.version).IsNewerThan(bestVersion))
{
continue;
}
mpt::ustring bestDownloadName;
// check if version supports the current system
bool is_supported = false;
for(auto & [downloadname, download] : versioninfo.downloads)
{
// is it for windows?
if(download.os != U_("windows") || !download.required_windows_version)
{
continue;
}
// can the installer run on the current system?
bool download_supported = true;
for(const auto & [architecture, required] : download.required_architectures)
{
if(!(required && IsArchitectureSupported(architecture)))
{
download_supported = false;
}
}
// does the download run on current architecture?
bool architecture_supported = false;
for(const auto & [architecture, supported] : download.supported_architectures)
{
if(supported && IsCurrentArchitecture(architecture))
{
architecture_supported = true;
}
}
if(!architecture_supported)
{
download_supported = false;
}
// does the current system have all required features?
for(const auto & [architecture, features] : download.required_processor_features)
{
if(IsCurrentArchitecture(architecture))
{
for(const auto & [feature, required] : features)
{
if(!(required && IsArchitectureFeatureSupported(architecture, feature)))
{
download_supported = false;
}
}
}
}
if(mpt::OS::Windows::Version::Current().IsBefore(
mpt::osinfo::windows::Version::System(mpt::saturate_cast<uint32>(download.required_windows_version->version_major), mpt::saturate_cast<uint32>(download.required_windows_version->version_minor)),
mpt::osinfo::windows::Version::ServicePack(mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_major), mpt::saturate_cast<uint16>(download.required_windows_version->servicepack_minor)),
mpt::osinfo::windows::Version::Build(mpt::saturate_cast<uint32>(download.required_windows_version->build))
))
{
download_supported = false;
}
if(mpt::OS::Windows::IsWine() && theApp.GetWineVersion()->Version().IsValid())
{
if(theApp.GetWineVersion()->Version().IsBefore(mpt::OS::Wine::Version(mpt::saturate_cast<uint8>(download.required_windows_version->wine_major), mpt::saturate_cast<uint8>(download.required_windows_version->wine_minor), mpt::saturate_cast<uint8>(download.required_windows_version->wine_update))))
{
download_supported = false;
}
}
if(download_supported)
{
is_supported = true;
if(theApp.IsInstallerMode() && download.type == U_("installer"))
{
bestDownloadName = downloadname;
} else if(theApp.IsPortableMode() && download.type == U_("archive"))
{
bestDownloadName = downloadname;
}
}
}
if(is_supported)
{
bestVersion = VersionWithRevision::Parse(versioninfo.version);
result.version = versionname;
result.download = bestDownloadName;
}
}
return result;
}
// Update notification dialog
class UpdateDialog : public CDialog
{
protected:
const CString m_releaseVersion;
const CString m_releaseDate;
const CString m_releaseURL;
const CString m_buttonText;
CFont m_boldFont;
public:
UpdateDialog(const CString &releaseVersion, const CString &releaseDate, const CString &releaseURL, const CString &buttonText = _T("&Update"))
: CDialog(IDD_UPDATE)
, m_releaseVersion(releaseVersion)
, m_releaseDate(releaseDate)
, m_releaseURL(releaseURL)
, m_buttonText(buttonText)
{ }
BOOL OnInitDialog() override
{
CDialog::OnInitDialog();
SetDlgItemText(IDOK, m_buttonText);
CFont *font = GetDlgItem(IDC_VERSION2)->GetFont();
LOGFONT lf;
font->GetLogFont(&lf);
lf.lfWeight = FW_BOLD;
m_boldFont.CreateFontIndirect(&lf);
GetDlgItem(IDC_VERSION2)->SetFont(&m_boldFont);
SetDlgItemText(IDC_VERSION1, mpt::cfmt::val(VersionWithRevision::Current()));
SetDlgItemText(IDC_VERSION2, m_releaseVersion);
SetDlgItemText(IDC_DATE, m_releaseDate);
SetDlgItemText(IDC_SYSLINK1, _T("More information about this build:\n<a href=\"") + m_releaseURL + _T("\">") + m_releaseURL + _T("</a>"));
CheckDlgButton(IDC_CHECK1, (TrackerSettings::Instance().UpdateIgnoreVersion == m_releaseVersion) ? BST_CHECKED : BST_UNCHECKED);
return FALSE;
}
void OnDestroy()
{
TrackerSettings::Instance().UpdateIgnoreVersion = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED ? m_releaseVersion : CString();
m_boldFont.DeleteObject();
CDialog::OnDestroy();
}
void OnClickURL(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
{
CTrackApp::OpenURL(m_releaseURL);
}
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(UpdateDialog, CDialog)
ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &UpdateDialog::OnClickURL)
ON_WM_DESTROY()
END_MESSAGE_MAP()
mpt::ustring CUpdateCheck::GetStatisticsUserInformation(bool shortText)
{
if(shortText)
{
return U_("A randomized user ID is sent together with basic system information."
" This ID cannot be linked to you personally in any way."
"\nOpenMPT will use this information to gather usage statistics and to plan system support for future OpenMPT versions.");
} else
{
return U_(
"When checking for updates, OpenMPT can additionally collect basic statistical information."
" A randomized user ID is sent alongside the update check. This ID and the transmitted statistics cannot be linked to you personally in any way."
" OpenMPT will use this information to gather usage statistics and to plan system support for future OpenMPT versions."
"\nOpenMPT would collect the following statistical data points: OpenMPT version, Windows version, type of CPU, amount of RAM, sound device settings, configured update check frequency of OpenMPT.");
}
}
std::vector<mpt::ustring> CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors()
{
// IMPORTANT:
// Signing keys are *NOT* stored on the same server as openmpt.org or the updates themselves,
// because otherwise, a single compromised server could allow for rogue updates.
return {
U_("https://update.openmpt.de/update/"),
U_("https://demo-scene.de/openmpt/update/")
};
}
mpt::ustring CUpdateCheck::GetDefaultAPIURL()
{
return U_("https://update.openmpt.org/api/v3/");
}
std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
int32 CUpdateCheck::GetNumCurrentRunningInstances()
{
return s_InstanceCount.load();
}
bool CUpdateCheck::IsSuitableUpdateMoment()
{
const auto documents = theApp.GetOpenDocuments();
return std::all_of(documents.begin(), documents.end(), [](auto doc) { return !doc->IsModified(); });
}
// Start update check
void CUpdateCheck::StartUpdateCheckAsync(bool isAutoUpdate)
{
bool loadPersisted = false;
if(isAutoUpdate)
{
if(!TrackerSettings::Instance().UpdateEnabled)
{
return;
}
if(!IsSuitableUpdateMoment())
{
return;
}
int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
if(updateCheckPeriod < 0)
{
return;
}
// Do we actually need to run the update check right now?
const time_t now = time(nullptr);
const time_t lastCheck = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
// Check update interval. Note that we always check for updates when the system time had gone backwards (i.e. when the last update check supposedly happened in the future).
const double secsSinceLastCheck = difftime(now, lastCheck);
if(secsSinceLastCheck > 0.0 && secsSinceLastCheck < updateCheckPeriod * 86400.0)
{
loadPersisted = true;
}
// Never ran update checks before, so we notify the user of automatic update checks.
if(TrackerSettings::Instance().UpdateShowUpdateHint)
{
TrackerSettings::Instance().UpdateShowUpdateHint = false;
const auto checkIntervalDays = TrackerSettings::Instance().UpdateIntervalDays.Get();
CString msg = MPT_CFORMAT("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates {}. If you do not want this, you can disable update checks in the setup.")
(
checkIntervalDays == 0 ? CString(_T("on every program start")) :
checkIntervalDays == 1 ? CString(_T("every day")) :
MPT_CFORMAT("every {} days")(checkIntervalDays)
);
if(Reporting::Confirm(msg, _T("OpenMPT Update")) == cnfNo)
{
TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
return;
}
}
} else
{
if(!IsSuitableUpdateMoment())
{
Reporting::Notification(_T("Please save all modified modules before updating OpenMPT."), _T("OpenMPT Update"));
return;
}
if(!TrackerSettings::Instance().UpdateEnabled)
{
if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Update")) != cnfYes)
{
return;
}
}
}
TrackerSettings::Instance().UpdateShowUpdateHint = false;
// ask if user wants to contribute system statistics
if(!TrackerSettings::Instance().UpdateStatisticsConsentAsked)
{
const auto enableStatistics = Reporting::Confirm(
U_("Do you want to contribute to OpenMPT by providing system statistics?\r\n\r\n") +
mpt::String::Replace(CUpdateCheck::GetStatisticsUserInformation(false), U_("\n"), U_("\r\n")) + U_("\r\n\r\n") +
MPT_UFORMAT("This option was previously {} on your system.\r\n")(TrackerSettings::Instance().UpdateStatistics ? U_("enabled") : U_("disabled")),
false, !TrackerSettings::Instance().UpdateStatistics.Get());
TrackerSettings::Instance().UpdateStatistics = (enableStatistics == ConfirmAnswer::cnfYes);
TrackerSettings::Instance().UpdateStatisticsConsentAsked = true;
}
int32 expected = 0;
if(!s_InstanceCount.compare_exchange_strong(expected, 1))
{
return;
}
CUpdateCheck::Context context;
context.window = CMainFrame::GetMainFrame();
context.msgStart = MPT_WM_APP_UPDATECHECK_START;
context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
context.msgCanceled = MPT_WM_APP_UPDATECHECK_CANCELED;
context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
context.autoUpdate = isAutoUpdate;
context.loadPersisted = loadPersisted;
context.statistics = GetStatisticsDataV3(CUpdateCheck::Settings());
std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
}
CUpdateCheck::Settings::Settings()
: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
, channel(static_cast<UpdateChannel>(TrackerSettings::Instance().UpdateChannel.Get()))
, persistencePath(theApp.GetConfigPath())
, apiURL(TrackerSettings::Instance().UpdateAPIURL)
, sendStatistics(TrackerSettings::Instance().UpdateStatistics)
, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
{
}
CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
: settings(settings)
, context(context)
{
return;
}
void CUpdateCheck::ThreadFunc::operator () ()
{
SetThreadPriority(GetCurrentThread(), context.autoUpdate ? THREAD_PRIORITY_BELOW_NORMAL : THREAD_PRIORITY_NORMAL);
CheckForUpdate(settings, context);
}
std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
{
nlohmann::json j;
j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
j["OpenMPT"]["BuildVariant"] = BuildVariants().GetBuildVariantName(BuildVariants().GetBuildVariant());
j["OpenMPT"]["Architecture"] = mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture());
j["Update"]["PeriodDays"] = settings.periodDays;
j["Update"]["Channel"] = ((settings.channel == UpdateChannelRelease) ? U_("Release") : (settings.channel == UpdateChannelNext) ? U_("Next") : (settings.channel == UpdateChannelDevelopment) ? U_("Development") : U_(""));
j["System"]["Windows"]["Version"]["Name"] = mpt::OS::Windows::Version::GetName(mpt::OS::Windows::Version::Current());
j["System"]["Windows"]["Version"]["Major"] = mpt::OS::Windows::Version::Current().GetSystem().Major;
j["System"]["Windows"]["Version"]["Minor"] = mpt::OS::Windows::Version::Current().GetSystem().Minor;
j["System"]["Windows"]["ServicePack"]["Major"] = mpt::OS::Windows::Version::Current().GetServicePack().Major;
j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::OS::Windows::Version::Current().GetServicePack().Minor;
j["System"]["Windows"]["Build"] = mpt::OS::Windows::Version::Current().GetBuild();
j["System"]["Windows"]["Architecture"] = mpt::OS::Windows::Name(mpt::OS::Windows::GetHostArchitecture());
j["System"]["Windows"]["IsWine"] = mpt::OS::Windows::IsWine();
j["System"]["Windows"]["TypeRaw"] = MPT_AFORMAT("0x{}")(mpt::afmt::HEX0<8>(mpt::OS::Windows::Version::Current().GetTypeId()));
std::vector<mpt::OS::Windows::Architecture> architectures = mpt::OS::Windows::GetSupportedProcessArchitectures(mpt::OS::Windows::GetHostArchitecture());
for(const auto & arch : architectures)
{
j["System"]["Windows"]["ProcessArchitectures"][mpt::ToCharset(mpt::Charset::UTF8, mpt::OS::Windows::Name(arch))] = true;
}
j["System"]["Memory"] = mpt::OS::Windows::GetSystemMemorySize() / 1024 / 1024; // MB
j["System"]["Threads"] = std::thread::hardware_concurrency();
if(mpt::OS::Windows::IsWine())
{
mpt::OS::Wine::VersionContext v;
j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
if(v.Version().IsValid())
{
j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
}
j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
}
const SoundDevice::Identifier deviceIdentifier = TrackerSettings::Instance().GetSoundDeviceIdentifier();
const SoundDevice::Info deviceInfo = theApp.GetSoundDevicesManager()->FindDeviceInfo(deviceIdentifier);
const SoundDevice::Settings deviceSettings = TrackerSettings::Instance().GetSoundDeviceSettings(deviceIdentifier);
j["OpenMPT"]["SoundDevice"]["Type"] = deviceInfo.type;
j["OpenMPT"]["SoundDevice"]["Name"] = deviceInfo.name;
j["OpenMPT"]["SoundDevice"]["Settings"]["Samplerate"] = deviceSettings.Samplerate;
j["OpenMPT"]["SoundDevice"]["Settings"]["Latency"] = deviceSettings.Latency;
j["OpenMPT"]["SoundDevice"]["Settings"]["UpdateInterval"] = deviceSettings.UpdateInterval;
j["OpenMPT"]["SoundDevice"]["Settings"]["Channels"] = deviceSettings.Channels.GetNumHostChannels();
j["OpenMPT"]["SoundDevice"]["Settings"]["BoostThreadPriority"] = deviceSettings.BoostThreadPriority;
j["OpenMPT"]["SoundDevice"]["Settings"]["ExclusiveMode"] = deviceSettings.ExclusiveMode;
j["OpenMPT"]["SoundDevice"]["Settings"]["UseHardwareTiming"] = deviceSettings.UseHardwareTiming;
j["OpenMPT"]["SoundDevice"]["Settings"]["KeepDeviceRunning"] = deviceSettings.KeepDeviceRunning;
#ifdef MPT_ENABLE_ARCH_INTRINSICS
const CPU::Info CPUInfo = CPU::Info::Get();
j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(CPUInfo.VendorID));
j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(CPUInfo.BrandID));
j["System"]["Processor"]["CpuidRaw"] = mpt::afmt::hex0<8>(CPUInfo.CPUID);
j["System"]["Processor"]["Id"]["Family"] = CPUInfo.Family;
j["System"]["Processor"]["Id"]["Model"] = CPUInfo.Model;
j["System"]["Processor"]["Id"]["Stepping"] = CPUInfo.Stepping;
j["System"]["Processor"]["Features"]["lm"] = ((CPUInfo.AvailableFeatures & CPU::feature::lm) != 0);
j["System"]["Processor"]["Features"]["mmx"] = ((CPUInfo.AvailableFeatures & CPU::feature::mmx) != 0);
j["System"]["Processor"]["Features"]["sse"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse) != 0);
j["System"]["Processor"]["Features"]["sse2"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse2) != 0);
j["System"]["Processor"]["Features"]["sse3"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse3) != 0);
j["System"]["Processor"]["Features"]["ssse3"] = ((CPUInfo.AvailableFeatures & CPU::feature::ssse3) != 0);
j["System"]["Processor"]["Features"]["sse4.1"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse4_1) != 0);
j["System"]["Processor"]["Features"]["sse4.2"] = ((CPUInfo.AvailableFeatures & CPU::feature::sse4_2) != 0);
j["System"]["Processor"]["Features"]["avx"] = ((CPUInfo.AvailableFeatures & CPU::feature::avx) != 0);
j["System"]["Processor"]["Features"]["avx2"] = ((CPUInfo.AvailableFeatures & CPU::feature::avx2) != 0);
#endif // MPT_ENABLE_ARCH_INTRINSICS
return j.dump(1, '\t');
}
// Run update check (independent thread)
UpdateCheckResult CUpdateCheck::SearchUpdate(const CUpdateCheck::Context &context, const CUpdateCheck::Settings &settings, const std::string &statistics)
{
UpdateCheckResult result;
{
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 0))
{
throw CUpdateCheck::Cancel();
}
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 10))
{
throw CUpdateCheck::Cancel();
}
bool loaded = false;
// try to load cached results before establishing any connection
if(context.loadPersisted)
{
try
{
InputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"));
if(f.IsValid())
{
std::vector<std::byte> data = GetFileReader(f).ReadRawDataAsByteVector();
nlohmann::json::parse(mpt::buffer_cast<std::string>(data)).get<Update::versions>();
result.CheckTime = time_t{};
result.json = data;
loaded = true;
}
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
} catch(const std::exception &)
{
// ignore
}
}
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 20))
{
throw CUpdateCheck::Cancel();
}
if(!loaded)
{
HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 30))
{
throw CUpdateCheck::Cancel();
}
result = SearchUpdateModern(internet, settings);
try
{
mpt::SafeOutputFile f(settings.persistencePath + P_("update-") + mpt::PathString::FromUnicode(GetChannelName(settings.channel)) + P_(".json"), std::ios::binary);
f.stream().imbue(std::locale::classic());
mpt::IO::WriteRaw(f.stream(), mpt::as_span(result.json));
f.stream().flush();
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
} catch(const std::exception &)
{
// ignore
}
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 50))
{
throw CUpdateCheck::Cancel();
}
SendStatistics(internet, settings, statistics);
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 70))
{
throw CUpdateCheck::Cancel();
}
}
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 90))
{
throw CUpdateCheck::Cancel();
}
CleanOldUpdates(settings, context);
if(!context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, 100))
{
throw CUpdateCheck::Cancel();
}
}
return result;
}
void CUpdateCheck::CleanOldUpdates(const CUpdateCheck::Settings & /* settings */ , const CUpdateCheck::Context & /* context */ )
{
mpt::PathString dirTemp = mpt::GetTempDirectory();
if(dirTemp.empty())
{
return;
}
if(PathIsRelative(dirTemp.AsNative().c_str()))
{
return;
}
if(!dirTemp.IsDirectory())
{
return;
}
mpt::PathString dirTempOpenMPT = dirTemp + P_("OpenMPT") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
mpt::PathString dirTempOpenMPTUpdates = dirTempOpenMPT + P_("Updates") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
mpt::DeleteWholeDirectoryTree(dirTempOpenMPTUpdates);
}
void CUpdateCheck::SendStatistics(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings, const std::string &statistics)
{
if(settings.sendStatistics)
{
HTTP::Request requestStatistics;
if(settings.statisticsUUID.IsValid())
{
requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID)));
requestStatistics.method = HTTP::Method::Put;
} else
{
requestStatistics.SetURI(ParseURI(settings.apiURL + U_("statistics/")));
requestStatistics.method = HTTP::Method::Post;
}
requestStatistics.dataMimeType = HTTP::MimeType::JSON();
requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
std::string jsondata = statistics;
MPT_LOG_GLOBAL(LogInformation, "Update", mpt::ToUnicode(mpt::Charset::UTF8, jsondata));
requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
#if defined(MPT_BUILD_RETRO)
requestSatistics.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
internet(requestStatistics);
}
}
UpdateCheckResult CUpdateCheck::SearchUpdateModern(HTTP::InternetSession &internet, const CUpdateCheck::Settings &settings)
{
HTTP::Request request;
request.SetURI(ParseURI(settings.apiURL + MPT_UFORMAT("update/{}")(GetChannelName(static_cast<UpdateChannel>(settings.channel)))));
request.method = HTTP::Method::Get;
request.acceptMimeTypes = HTTP::MimeTypes::JSON();
request.flags = HTTP::NoCache;
#if defined(MPT_BUILD_RETRO)
request.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
HTTP::Result resultHTTP = internet(request);
// Retrieve HTTP status code.
if(resultHTTP.Status >= 400)
{
throw CUpdateCheck::Error(MPT_CFORMAT("Version information could not be found on the server (HTTP status code {}). Maybe your version of OpenMPT is too old!")(resultHTTP.Status));
}
// Now, evaluate the downloaded data.
UpdateCheckResult result;
result.CheckTime = time(nullptr);
try
{
nlohmann::json::parse(mpt::buffer_cast<std::string>(resultHTTP.Data)).get<Update::versions>();
result.json = resultHTTP.Data;
} catch(mpt::out_of_memory e)
{
mpt::rethrow_out_of_memory(e);
} catch(const nlohmann::json::exception &e)
{
throw CUpdateCheck::Error(MPT_CFORMAT("Could not understand server response ({}). Maybe your version of OpenMPT is too old!")(mpt::get_exception_text<mpt::ustring>(e)));
}
return result;
}
void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
{
// incremented before starting the thread
MPT_ASSERT(s_InstanceCount.load() >= 1);
UpdateCheckResult result;
try
{
context.window->SendMessage(context.msgStart, context.autoUpdate ? 1 : 0, 0);
try
{
result = SearchUpdate(context, settings, context.statistics);
} catch(const bad_uri &e)
{
throw CUpdateCheck::Error(MPT_CFORMAT("Error parsing update URL: {}")(mpt::get_exception_text<mpt::ustring>(e)));
} catch(const HTTP::exception &e)
{
throw CUpdateCheck::Error(CString(_T("HTTP error: ")) + mpt::ToCString(e.GetMessage()));
}
} catch(const CUpdateCheck::Cancel &)
{
context.window->SendMessage(context.msgCanceled, context.autoUpdate ? 1 : 0, 0);
s_InstanceCount.fetch_sub(1);
MPT_ASSERT(s_InstanceCount.load() >= 0);
return;
} catch(const CUpdateCheck::Error &e)
{
context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
s_InstanceCount.fetch_sub(1);
MPT_ASSERT(s_InstanceCount.load() >= 0);
return;
}
context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
s_InstanceCount.fetch_sub(1);
MPT_ASSERT(s_InstanceCount.load() >= 0);
}
bool CUpdateCheck::IsAutoUpdateFromMessage(WPARAM wparam, LPARAM /* lparam */ )
{
return wparam ? true : false;
}
const UpdateCheckResult &CUpdateCheck::MessageAsResult(WPARAM /* wparam */ , LPARAM lparam)
{
return *reinterpret_cast<UpdateCheckResult *>(lparam);
}
const CUpdateCheck::Error &CUpdateCheck::MessageAsError(WPARAM /* wparam */ , LPARAM lparam)
{
return *reinterpret_cast<CUpdateCheck::Error*>(lparam);
}
static const char * const updateScript = R"vbs(
Wscript.Echo
Wscript.Echo "OpenMPT portable Update"
Wscript.Echo "======================="
Wscript.Echo "[ 0%] Waiting for OpenMPT to close..."
WScript.Sleep 2000
Wscript.Echo "[ 10%] Loading update settings..."
zip = WScript.Arguments.Item(0)
subfolder = WScript.Arguments.Item(1)
dst = WScript.Arguments.Item(2)
restartbinary = WScript.Arguments.Item(3)
Wscript.Echo "[ 20%] Preparing update..."
Set fso = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("Wscript.Shell")
Set application = CreateObject("Shell.Application")
Sub CreateFolder(pathname)
If Not fso.FolderExists(pathname) Then
fso.CreateFolder pathname
End If
End Sub
Sub DeleteFolder(pathname)
If fso.FolderExists(pathname) Then
fso.DeleteFolder pathname
End If
End Sub
Sub UnZIP(zipfilename, destinationfolder)
If Not fso.FolderExists(destinationfolder) Then
fso.CreateFolder(destinationfolder)
End If
application.NameSpace(destinationfolder).Copyhere application.NameSpace(zipfilename).Items, 16+256
End Sub
Wscript.Echo "[ 30%] Changing to temporary directory..."
shell.CurrentDirectory = fso.GetParentFolderName(WScript.ScriptFullName)
Wscript.Echo "[ 40%] Decompressing update..."
UnZIP zip, fso.BuildPath(fso.GetAbsolutePathName("."), "tmp")
Wscript.Echo "[ 60%] Installing update..."
If subfolder = "" Or subfolder = "." Then
fso.CopyFolder fso.BuildPath(fso.GetAbsolutePathName("."), "tmp"), dst, True
Else
fso.CopyFolder fso.BuildPath(fso.BuildPath(fso.GetAbsolutePathName("."), "tmp"), subfolder), dst, True
End If
Wscript.Echo "[ 80%] Deleting temporary directory..."
DeleteFolder fso.BuildPath(fso.GetAbsolutePathName("."), "tmp")
Wscript.Echo "[ 90%] Restarting OpenMPT..."
application.ShellExecute fso.BuildPath(dst, restartbinary), , dst, , 10
Wscript.Echo "[100%] Update successful!"
Wscript.Echo
WScript.Sleep 1000
Wscript.Echo "Closing update window in 5 seconds..."
WScript.Sleep 1000
Wscript.Echo "Closing update window in 4 seconds..."
WScript.Sleep 1000
Wscript.Echo "Closing update window in 3 seconds..."
WScript.Sleep 1000
Wscript.Echo "Closing update window in 2 seconds..."
WScript.Sleep 1000
Wscript.Echo "Closing update window in 1 seconds..."
WScript.Sleep 1000
Wscript.Echo "Closing update window..."
WScript.Quit
)vbs";
class CDoUpdate: public CProgressDialog
{
private:
Update::download download;
class Aborted : public std::exception {};
class Warning : public std::exception
{
private:
mpt::ustring msg;
public:
Warning(const mpt::ustring &msg_)
: msg(msg_)
{
return;
}
mpt::ustring get_msg() const
{
return msg;
}
};
class Error : public std::exception
{
private:
mpt::ustring msg;
public:
Error(const mpt::ustring &msg_)
: msg(msg_)
{
return;
}
mpt::ustring get_msg() const
{
return msg;
}
};
public:
CDoUpdate(Update::download download, CWnd *parent = nullptr)
: CProgressDialog(parent)
, download(download)
{
return;
}
void UpdateProgress(const CString &text, double percent)
{
SetText(text);
SetProgress(static_cast<uint64>(percent * 100.0));
ProcessMessages();
if(m_abort)
{
throw Aborted();
}
}
void Run() override
{
try
{
SetTitle(_T("OpenMPT Update"));
SetAbortText(_T("Cancel"));
SetText(_T("OpenMPT Update"));
SetRange(0, 10000);
ProcessMessages();
Update::downloadinfo downloadinfo;
mpt::PathString dirTempOpenMPTUpdates;
mpt::PathString updateFilename;
{
UpdateProgress(_T("Connecting..."), 0.0);
HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
UpdateProgress(_T("Downloading update information..."), 1.0);
std::vector<std::byte> rawDownloadInfo;
{
HTTP::Request request;
request.SetURI(ParseURI(download.url));
request.method = HTTP::Method::Get;
request.acceptMimeTypes = HTTP::MimeTypes::JSON();
#if defined(MPT_BUILD_RETRO)
request.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
HTTP::Result resultHTTP = internet(request);
if(resultHTTP.Status != 200)
{
throw Error(MPT_UFORMAT("Error downloading update information: HTTP status {}.")(resultHTTP.Status));
}
rawDownloadInfo = std::move(resultHTTP.Data);
}
if(!TrackerSettings::Instance().UpdateSkipSignatureVerificationUNSECURE)
{
std::vector<std::byte> rawSignature;
UpdateProgress(_T("Retrieving update signature..."), 2.0);
{
HTTP::Request request;
request.SetURI(ParseURI(download.url + U_(".jws.json")));
request.method = HTTP::Method::Get;
request.acceptMimeTypes = HTTP::MimeTypes::JSON();
#if defined(MPT_BUILD_RETRO)
request.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
HTTP::Result resultHTTP = internet(request);
if(resultHTTP.Status != 200)
{
throw Error(MPT_UFORMAT("Error downloading update signature: HTTP status {}.")(resultHTTP.Status));
}
rawSignature = std::move(resultHTTP.Data);
}
UpdateProgress(_T("Retrieving update signing public keys..."), 3.0);
std::vector<mpt::crypto::asymmetric::rsassa_pss<>::public_key> keys;
{
std::vector<mpt::ustring> keyAnchors = TrackerSettings::Instance().UpdateSigningKeysRootAnchors;
if(keyAnchors.empty())
{
Reporting::Warning(U_("Warning: No update signing public key root anchors configured. Update cannot be verified."), U_("OpenMPT Update"));
}
for(const auto & keyAnchor : keyAnchors)
{
HTTP::Request request;
request.SetURI(ParseURI(keyAnchor + U_("signingkeys.jwkset.json")));
request.method = HTTP::Method::Get;
request.flags = HTTP::NoCache;
request.acceptMimeTypes = HTTP::MimeTypes::JSON();
try
{
#if defined(MPT_BUILD_RETRO)
request.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
HTTP::Result resultHTTP = internet(request);
resultHTTP.CheckStatus(200);
mpt::append(keys, mpt::crypto::asymmetric::rsassa_pss<>::parse_jwk_set(mpt::ToUnicode(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(resultHTTP.Data))));
} catch(mpt::out_of_memory e)
{
mpt::rethrow_out_of_memory(e);
} catch(const std::exception &e)
{
Reporting::Warning(MPT_UFORMAT("Warning: Retrieving update signing public keys from {} failed: {}")(keyAnchor, mpt::get_exception_text<mpt::ustring>(e)), U_("OpenMPT Update"));
} catch(...)
{
Reporting::Warning(MPT_UFORMAT("Warning: Retrieving update signing public keys from {} failed.")(keyAnchor), U_("OpenMPT Update"));
}
}
}
if(keys.empty())
{
throw Error(U_("Error retrieving update signing public keys."));
}
UpdateProgress(_T("Verifying signature..."), 4.0);
std::vector<std::byte> expectedPayload = mpt::buffer_cast<std::vector<std::byte>>(rawDownloadInfo);
mpt::ustring signature = mpt::ToUnicode(mpt::Charset::UTF8, mpt::buffer_cast<std::string>(rawSignature));
mpt::crypto::asymmetric::rsassa_pss<>::jws_verify_at_least_one(keys, expectedPayload, signature);
}
UpdateProgress(_T("Parsing update information..."), 5.0);
try
{
downloadinfo = nlohmann::json::parse(mpt::buffer_cast<std::string>(rawDownloadInfo)).get<Update::downloadinfo>();
} catch(const nlohmann::json::exception &e)
{
throw Error(MPT_UFORMAT("Error parsing update information: {}.")(mpt::get_exception_text<mpt::ustring>(e)));
}
UpdateProgress(_T("Preparing download..."), 6.0);
mpt::PathString dirTemp = mpt::GetTempDirectory();
mpt::PathString dirTempOpenMPT = dirTemp + P_("OpenMPT") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
dirTempOpenMPTUpdates = dirTempOpenMPT + P_("Updates") + mpt::PathString::FromNative(mpt::RawPathString(1, mpt::PathString::GetDefaultPathSeparator()));
updateFilename = dirTempOpenMPTUpdates + mpt::PathString::FromUnicode(downloadinfo.filename);
::CreateDirectory(dirTempOpenMPT.AsNativePrefixed().c_str(), NULL);
::CreateDirectory(dirTempOpenMPTUpdates.AsNativePrefixed().c_str(), NULL);
{
UpdateProgress(_T("Creating file..."), 7.0);
mpt::SafeOutputFile file(updateFilename, std::ios::binary);
file.stream().imbue(std::locale::classic());
file.stream().exceptions(std::ios::failbit | std::ios::badbit);
UpdateProgress(_T("Downloading update..."), 8.0);
HTTP::Request request;
request.SetURI(ParseURI(downloadinfo.url));
request.method = HTTP::Method::Get;
request.acceptMimeTypes = HTTP::MimeTypes::Binary();
request.outputStream = &file.stream();
request.progressCallback = [&](HTTP::Progress progress, uint64 transferred, std::optional<uint64> expectedSize) {
switch(progress)
{
case HTTP::Progress::Start:
SetProgress(900);
break;
case HTTP::Progress::ConnectionEstablished:
SetProgress(1000);
break;
case HTTP::Progress::RequestOpened:
SetProgress(1100);
break;
case HTTP::Progress::RequestSent:
SetProgress(1200);
break;
case HTTP::Progress::ResponseReceived:
SetProgress(1300);
break;
case HTTP::Progress::TransferBegin:
SetProgress(1400);
break;
case HTTP::Progress::TransferRunning:
if(expectedSize && ((*expectedSize) != 0))
{
SetProgress(static_cast<int64>((static_cast<double>(transferred) / static_cast<double>(*expectedSize)) * (10000.0-1500.0-400.0) + 1500.0));
} else
{
SetProgress((1500 + 9600) / 2);
}
break;
case HTTP::Progress::TransferDone:
SetProgress(9600);
break;
}
ProcessMessages();
if(m_abort)
{
throw HTTP::Abort();
}
};
#if defined(MPT_BUILD_RETRO)
request.InsecureTLSDowngradeWindowsXP();
#endif // MPT_BUILD_RETRO
HTTP::Result resultHTTP = internet(request);
if(resultHTTP.Status != 200)
{
throw Error(MPT_UFORMAT("Error downloading update: HTTP status {}.")(resultHTTP.Status));
}
}
UpdateProgress(_T("Disconnecting..."), 97.0);
}
UpdateProgress(_T("Verifying download..."), 98.0);
bool verified = false;
for(const auto & [algorithm, value] : downloadinfo.checksums)
{
if(algorithm == U_("SHA-512"))
{
std::vector<std::byte> binhash = mpt::decode_hex(value);
if(binhash.size() != 512/8)
{
throw Error(U_("Download verification failed."));
}
std::array<std::byte, 512/8> expected;
std::copy(binhash.begin(), binhash.end(), expected.begin());
mpt::crypto::hash::SHA512 hash;
mpt::ifstream f(updateFilename, std::ios::binary);
f.imbue(std::locale::classic());
f.exceptions(std::ios::badbit);
while(!mpt::IO::IsEof(f))
{
std::array<std::byte, mpt::IO::BUFFERSIZE_TINY> buf;
hash.process(mpt::IO::ReadRaw(f, mpt::as_span(buf)));
}
std::array<std::byte, 512/8> gotten = hash.result();
if(gotten != expected)
{
throw Error(U_("Download verification failed."));
}
verified = true;
}
}
if(!verified)
{
throw Error(U_("Error verifying update: No suitable checksum found."));
}
UpdateProgress(_T("Installing update..."), 99.0);
bool wantClose = false;
if(download.can_autoupdate && (Version::Current() >= Version::Parse(download.autoupdate_minversion)))
{
if(download.type == U_("installer") && downloadinfo.autoupdate_installer)
{
if(theApp.IsSourceTreeMode())
{
throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(updateFilename, mpt::String::Combine(downloadinfo.autoupdate_installer->arguments, U_(" "))));
}
if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
updateFilename.AsNative().c_str(),
mpt::ToWin(mpt::String::Combine(downloadinfo.autoupdate_installer->arguments, U_(" "))).c_str(),
dirTempOpenMPTUpdates.AsNative().c_str(),
SW_SHOWDEFAULT)) < 32)
{
throw Error(U_("Error launching update."));
}
} else if(download.type == U_("archive") && downloadinfo.autoupdate_archive)
{
try
{
mpt::SafeOutputFile file(dirTempOpenMPTUpdates + P_("update.vbs"), std::ios::binary);
file.stream().imbue(std::locale::classic());
file.stream().exceptions(std::ios::failbit | std::ios::badbit);
mpt::IO::WriteRaw(file.stream(), mpt::as_span(std::string(updateScript)));
} catch(...)
{
throw Error(U_("Error creating update script."));
}
std::vector<mpt::ustring> arguments;
arguments.push_back(U_("\"") + (dirTempOpenMPTUpdates + P_("update.vbs")).ToUnicode() + U_("\""));
arguments.push_back(U_("\"") + updateFilename.ToUnicode() + U_("\""));
arguments.push_back(U_("\"") + (downloadinfo.autoupdate_archive->subfolder.empty() ? U_(".") : downloadinfo.autoupdate_archive->subfolder) + U_("\""));
arguments.push_back(U_("\"") + theApp.GetInstallPath().WithoutTrailingSlash().ToUnicode() + U_("\""));
arguments.push_back(U_("\"") + downloadinfo.autoupdate_archive->restartbinary + U_("\""));
if(theApp.IsSourceTreeMode())
{
throw Warning(MPT_UFORMAT("Refusing to launch update '{} {}' when running from source tree.")(P_("cscript.exe"), mpt::String::Combine(arguments, U_(" "))));
}
if(reinterpret_cast<INT_PTR>(ShellExecute(NULL, NULL,
P_("cscript.exe").AsNative().c_str(),
mpt::ToWin(mpt::String::Combine(arguments, U_(" "))).c_str(),
dirTempOpenMPTUpdates.AsNative().c_str(),
SW_SHOWDEFAULT)) < 32)
{
throw Error(U_("Error launching update."));
}
wantClose = true;
} else
{
CTrackApp::OpenDirectory(dirTempOpenMPTUpdates);
wantClose = true;
}
} else
{
CTrackApp::OpenDirectory(dirTempOpenMPTUpdates);
wantClose = true;
}
UpdateProgress(_T("Waiting for installer..."), 100.0);
if(wantClose)
{
CMainFrame::GetMainFrame()->PostMessage(WM_QUIT, 0, 0);
}
EndDialog(IDOK);
} catch(mpt::out_of_memory e)
{
mpt::delete_out_of_memory(e);
Reporting::Error(U_("Not enough memory to install update."), U_("OpenMPT Update Error"));
EndDialog(IDCANCEL);
return;
} catch(const HTTP::Abort &)
{
EndDialog(IDCANCEL);
return;
} catch(const Aborted &)
{
EndDialog(IDCANCEL);
return;
} catch(const Warning &e)
{
Reporting::Warning(e.get_msg(), U_("OpenMPT Update"));
EndDialog(IDCANCEL);
return;
} catch(const Error &e)
{
Reporting::Error(e.get_msg(), U_("OpenMPT Update Error"));
EndDialog(IDCANCEL);
return;
} catch(const std::exception &e)
{
Reporting::Error(MPT_UFORMAT("Error installing update: {}")(mpt::get_exception_text<mpt::ustring>(e)), U_("OpenMPT Update Error"));
EndDialog(IDCANCEL);
return;
} catch(...)
{
Reporting::Error(U_("Error installing update."), U_("OpenMPT Update Error"));
EndDialog(IDCANCEL);
return;
}
}
};
void CUpdateCheck::AcknowledgeSuccess(const UpdateCheckResult &result)
{
if(!result.IsFromCache())
{
TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(result.CheckTime);
}
}
void CUpdateCheck::ShowSuccessGUI(const bool autoUpdate, const UpdateCheckResult &result)
{
bool modal = !autoUpdate;
Update::versions updateData = nlohmann::json::parse(mpt::buffer_cast<std::string>(result.json)).get<Update::versions>();
UpdateInfo updateInfo = GetBestDownload(updateData);
if(!updateInfo.IsAvailable())
{
if(modal)
{
Reporting::Information(U_("You already have the latest version of OpenMPT installed."), U_("OpenMPT Update"));
}
return;
}
auto &versionInfo = updateData[updateInfo.version];
if(autoUpdate && (mpt::ToCString(versionInfo.version) == TrackerSettings::Instance().UpdateIgnoreVersion))
{
return;
}
if(autoUpdate && TrackerSettings::Instance().UpdateInstallAutomatically && !updateInfo.download.empty() && versionInfo.downloads[updateInfo.download].can_autoupdate && (Version::Current() >= Version::Parse(versionInfo.downloads[updateInfo.download].autoupdate_minversion)))
{
CDoUpdate updateDlg(versionInfo.downloads[updateInfo.download], theApp.GetMainWnd());
if(updateDlg.DoModal() != IDOK)
{
return;
}
} else
{
const TCHAR *action = _T("&View Announcement");
const bool canInstall = mpt::OS::Windows::IsOriginal() && !updateInfo.download.empty() && versionInfo.downloads[updateInfo.download].can_autoupdate && (Version::Current() >= Version::Parse(versionInfo.downloads[updateInfo.download].autoupdate_minversion));
const bool canDownload = !canInstall && !updateInfo.download.empty() && !versionInfo.downloads[updateInfo.download].download_url.empty();
if(canInstall)
{
action = _T("&Install Now");
} else if(canDownload)
{
action = _T("&Download Now");
}
// always show indicator, do not highlight it with a tooltip if we show a modal window later or when it is a cached result
if(!CMainFrame::GetMainFrame()->ShowUpdateIndicator(result, mpt::ToCString(versionInfo.version), mpt::ToCString(versionInfo.announcement_url), !modal && !result.IsFromCache()))
{
// on failure to show indicator, continue and show modal dialog
modal = true;
}
if(!modal)
{
return;
}
UpdateDialog dlg(
mpt::ToCString(versionInfo.version),
mpt::ToCString(versionInfo.date),
mpt::ToCString(versionInfo.announcement_url),
action);
if(dlg.DoModal() != IDOK)
{
return;
}
if(canInstall)
{
CDoUpdate updateDlg(versionInfo.downloads[updateInfo.download], theApp.GetMainWnd());
if(updateDlg.DoModal() != IDOK)
{
return;
}
} else if(canDownload)
{
CTrackApp::OpenURL(versionInfo.downloads[updateInfo.download].download_url);
} else
{
CTrackApp::OpenURL(versionInfo.announcement_url);
}
}
}
void CUpdateCheck::ShowFailureGUI(const bool autoUpdate, const CUpdateCheck::Error &error)
{
if(!autoUpdate)
{
Reporting::Error(error.GetMessage(), _T("OpenMPT Update Error"));
}
}
CUpdateCheck::Error::Error(CString errorMessage)
: std::runtime_error(mpt::ToCharset(mpt::CharsetException, errorMessage))
, m_Message(errorMessage)
{
return;
}
CUpdateCheck::Error::Error(CString errorMessage, DWORD errorCode)
: std::runtime_error(mpt::ToCharset(mpt::CharsetException, FormatErrorCode(errorMessage, errorCode)))
, m_Message(errorMessage)
{
return;
}
CString CUpdateCheck::Error::GetMessage() const
{
return m_Message;
}
CString CUpdateCheck::Error::FormatErrorCode(CString errorMessage, DWORD errorCode)
{
errorMessage += mpt::ToCString(mpt::windows::GetErrorMessage(errorCode, GetModuleHandle(TEXT("wininet.dll"))));
return errorMessage;
}
CUpdateCheck::Cancel::Cancel()
{
return;
}
/////////////////////////////////////////////////////////////
// CUpdateSetupDlg
BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
ON_COMMAND(IDC_CHECK_UPDATEENABLED, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_RADIO1, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_RADIO2, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_RADIO3, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_BUTTON1, &CUpdateSetupDlg::OnCheckNow)
ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY, &CUpdateSetupDlg::OnSettingsChanged)
ON_COMMAND(IDC_CHECK1, &CUpdateSetupDlg::OnSettingsChanged)
ON_NOTIFY(NM_CLICK, IDC_SYSLINK1, &CUpdateSetupDlg::OnShowStatisticsData)
END_MESSAGE_MAP()
CUpdateSetupDlg::CUpdateSetupDlg()
: CPropertyPage(IDD_OPTIONS_UPDATE)
, m_SettingChangedNotifyGuard(theApp.GetSettings(), TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
{
return;
}
void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
}
BOOL CUpdateSetupDlg::OnInitDialog()
{
CPropertyPage::OnInitDialog();
CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
int radioID = 0;
uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
if(updateChannel == UpdateChannelRelease)
{
radioID = IDC_RADIO1;
} else if(updateChannel == UpdateChannelNext)
{
radioID = IDC_RADIO2;
} else if(updateChannel == UpdateChannelDevelopment)
{
radioID = IDC_RADIO3;
} else
{
radioID = IDC_RADIO1;
}
CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
int ndx;
ndx = m_CbnUpdateFrequency.AddString(_T("always"));
m_CbnUpdateFrequency.SetItemData(ndx, 0);
if(periodDays >= 0)
{
m_CbnUpdateFrequency.SetCurSel(ndx);
}
ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
m_CbnUpdateFrequency.SetItemData(ndx, 1);
if(periodDays >= 1)
{
m_CbnUpdateFrequency.SetCurSel(ndx);
}
ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
m_CbnUpdateFrequency.SetItemData(ndx, 7);
if(periodDays >= 7)
{
m_CbnUpdateFrequency.SetCurSel(ndx);
}
ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
m_CbnUpdateFrequency.SetItemData(ndx, 30);
if(periodDays >= 30)
{
m_CbnUpdateFrequency.SetCurSel(ndx);
}
ndx = m_CbnUpdateFrequency.AddString(_T("never"));
m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
if(periodDays < 0)
{
m_CbnUpdateFrequency.SetCurSel(ndx);
}
CheckDlgButton(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY, TrackerSettings::Instance().UpdateInstallAutomatically ? BST_CHECKED : BST_UNCHECKED);
CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateStatistics ? BST_CHECKED : BST_UNCHECKED);
GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(mpt::ToCString(CUpdateCheck::GetStatisticsUserInformation(true)));
EnableDisableDialog();
m_SettingChangedNotifyGuard.Register(this);
SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
return TRUE;
}
void CUpdateSetupDlg::OnShowStatisticsData(NMHDR * /*pNMHDR*/, LRESULT * /*pResult*/)
{
CUpdateCheck::Settings settings;
uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
const int channelRadio = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3);
if(channelRadio == IDC_RADIO1) updateChannel = UpdateChannelRelease;
if(channelRadio == IDC_RADIO2) updateChannel = UpdateChannelNext;
if(channelRadio == IDC_RADIO3) updateChannel = UpdateChannelDevelopment;
int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
settings.periodDays = updateCheckPeriod;
settings.channel = static_cast<UpdateChannel>(updateChannel);
settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
mpt::ustring statistics;
statistics += U_("Update:") + UL_("\n");
statistics += UL_("\n");
{
statistics += U_("GET ") + settings.apiURL + MPT_UFORMAT("update/{}")(GetChannelName(static_cast<UpdateChannel>(settings.channel))) + UL_("\n");
statistics += UL_("\n");
std::vector<mpt::ustring> keyAnchors = TrackerSettings::Instance().UpdateSigningKeysRootAnchors;
for(const auto & keyAnchor : keyAnchors)
{
statistics += U_("GET ") + keyAnchor + U_("signingkeys.jwkset.json") + UL_("\n");
statistics += UL_("\n");
}
}
if(settings.sendStatistics)
{
statistics += U_("Statistics:") + UL_("\n");
statistics += UL_("\n");
if(settings.statisticsUUID.IsValid())
{
statistics += U_("PUT ") + settings.apiURL + MPT_UFORMAT("statistics/{}")(settings.statisticsUUID) + UL_("\n");
} else
{
statistics += U_("POST ") + settings.apiURL + U_("statistics/") + UL_("\n");
}
statistics += mpt::String::Replace(mpt::ToUnicode(mpt::Charset::UTF8, CUpdateCheck::GetStatisticsDataV3(settings)), U_("\t"), U_(" "));
}
InfoDialog dlg(this);
dlg.SetCaption(_T("Update Statistics Data"));
dlg.SetContent(mpt::ToWin(mpt::String::Replace(statistics, U_("\n"), U_("\r\n"))));
dlg.DoModal();
}
void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
{
if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
{
CString updateText;
const time_t t = TrackerSettings::Instance().UpdateLastUpdateCheck.Get();
if(t > 0)
{
const tm* const lastUpdate = localtime(&t);
if(lastUpdate != nullptr)
{
updateText.Format(_T("The last successful update check was run on %04d-%02d-%02d, %02d:%02d."), lastUpdate->tm_year + 1900, lastUpdate->tm_mon + 1, lastUpdate->tm_mday, lastUpdate->tm_hour, lastUpdate->tm_min);
}
}
updateText += _T("\r\n");
SetDlgItemText(IDC_LASTUPDATE, updateText);
}
}
void CUpdateSetupDlg::EnableDisableDialog()
{
BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
GetDlgItem(IDC_STATIC_UDATECHANNEL)->EnableWindow(status);
GetDlgItem(IDC_RADIO1)->EnableWindow(status);
GetDlgItem(IDC_RADIO2)->EnableWindow(status);
GetDlgItem(IDC_RADIO3)->EnableWindow(status);
GetDlgItem(IDC_STATIC_UPDATECHECK)->EnableWindow(status);
GetDlgItem(IDC_STATIC_UPDATEFREQUENCY)->EnableWindow(status);
GetDlgItem(IDC_COMBO_UPDATEFREQUENCY)->EnableWindow(status);
GetDlgItem(IDC_BUTTON1)->EnableWindow(status);
GetDlgItem(IDC_LASTUPDATE)->EnableWindow(status);
GetDlgItem(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY)->EnableWindow(status);
GetDlgItem(IDC_STATIC_UPDATEPRIVACY)->EnableWindow(status);
GetDlgItem(IDC_CHECK1)->EnableWindow(status);
GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->EnableWindow(status);
GetDlgItem(IDC_SYSLINK1)->EnableWindow(status);
}
void CUpdateSetupDlg::OnSettingsChanged()
{
EnableDisableDialog();
SetModified(TRUE);
}
void CUpdateSetupDlg::OnOK()
{
uint32 updateChannel = TrackerSettings::Instance().UpdateChannel;
const int channelRadio = GetCheckedRadioButton(IDC_RADIO1, IDC_RADIO3);
if(channelRadio == IDC_RADIO1) updateChannel = UpdateChannelRelease;
if(channelRadio == IDC_RADIO2) updateChannel = UpdateChannelNext;
if(channelRadio == IDC_RADIO3) updateChannel = UpdateChannelDevelopment;
int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
TrackerSettings::Instance().UpdateInstallAutomatically = (IsDlgButtonChecked(IDC_CHECK_UPDATEINSTALLAUTOMATICALLY) != BST_UNCHECKED);
TrackerSettings::Instance().UpdateChannel = updateChannel;
TrackerSettings::Instance().UpdateStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
CPropertyPage::OnOK();
}
BOOL CUpdateSetupDlg::OnSetActive()
{
CMainFrame::m_nLastOptionsPage = OPTIONS_PAGE_UPDATE;
return CPropertyPage::OnSetActive();
}
void CUpdateSetupDlg::OnCheckNow()
{
CUpdateCheck::DoManualUpdateCheck();
}
#endif // MPT_ENABLE_UPDATE
OPENMPT_NAMESPACE_END