diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp | 1699 |
1 files changed, 1699 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp new file mode 100644 index 00000000..f3febde2 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/mptrack/UpdateCheck.cpp @@ -0,0 +1,1699 @@ +/* + * 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 |